ギザギザ画像よ、さよーなら！！Scratch3.0のブロックコードをSVG形式のベクター画像でダウンロードするChrome拡張機能の開発

# Scratch指導者必携のChrome拡張機能ツールの開発
[「#S-Hash Scratch」](https://chrome.google.com/webstore/detail/s-hash-scratch/jpefpbkmogghljlfgmpbaokfbdjhadbg)は、Scratch3.0のブロックコードをSVG形式のベクター画像でダウンロードするChrome拡張機能[(カンパウェア)](#カンパウェア)です。Scratch3.0プロジェクトエディターは、HTML内部のSVG要素で、ブロックコード等を描画しています。
Scratch3.0のエディターページのdocumentオブジェクトからブロックコードのSVG要素を取り出し、SVG形式のファイルとしてダウンロードできるようにしました。

[Chrome拡張機能 「#S-Hash Scratch」](https://chrome.google.com/webstore/detail/s-hash-scratch/jpefpbkmogghljlfgmpbaokfbdjhadbg)

## 処理の概要

### バックグラウンドからコンテントスクリプトの呼び出し

* アイコンのクリックでコンテントスクリプトを読み出す
* コンテントスクリプトで非同期で処理を実行する

### SVGの取り出し

1. "svg.blocklySvg g.blocklyWorkspace"を持つSVG要素を取得する（クローン）。
1. SVG要素の中の不要な要素を削除する。
1. image要素の参照形式から、g要素の埋め込み形式に展開する。
1. SVG要素のすべての要素にstyleを適用する。
1. "&amp;nbsp;"を可読可能文字に変換する。
1. fontにArialを追加する。
1. SVG形式ファイルとしてダウンロードを実行する。 


## 参考にしたページ

**Scratchblocks2svg Chrome Extension**
https://github.com/summerscar/scratchblocks2svg

既に、この拡張機能でSVG形式でのダウンロード機能は、実現できています。ほぼ、このソースコードをベースに開発しました。

``` download.js
function exportData(text) {
  const saveLink = document.createElement('a')
  document.body.appendChild(saveLink)
  
  const data = new Blob([text], { type: 'text' })
  const url = window.URL.createObjectURL(data)
  saveLink.href = url
  
  // File name: project-DATE-TIME
  const date = new Date()
  const timestamp = `${date.toLocaleDateString()}-${date.toLocaleTimeString()}`
  saveLink.download = `block_${timestamp}.svg`
  saveLink.click()
  window.URL.revokeObjectURL(url)
  document.body.removeChild(saveLink)
}
```


**ブラウザで非同期処理を同期的に処理する fetch(Promise) + async/await**
https://qiita.com/msquare33/items/a8b51d6f4d6be770e7d6

fetch（非同期）を同期的に処理するところを使わせていただきました。

``` asyncawait.js
getConfig = async() => {
  var conf1 = await (await fetch("/conf/conf1.yml?_="+(new Date()).getDate())).text();
  console.log(conf1);
  var conf2 = await (await fetch("/conf/conf2.yml?_="+(new Date()).getDate())).text();
  console.log(conf2);
}

window.onload = function(){
  getConfig();
}
```

**JavaScriptでスタイルがcssに定義されているsvgにスタイルを適用しながらいい感じにpngに変換した**
https://qiita.com/Nikkely/items/aa485ebdbec51e49ecbc

取り出すSVGの各要素にstyleを適用する方法を流用しています。

```slow.getComputedStyle.js
const queue = []
queue.push([svgElement, virtualSvg])
while (queue.length != 0) {
  const pair = queue.pop()
  const rEle = pair[0]
  const vEle = pair[1]
  
  const computedStyle = window.getComputedStyle(rEle, '')
  for (let property of computedStyle) {
    vEle.style[property] = computedStyle.getPropertyValue(property)
  }
  
  const rChildren = rEle.children
  if (rChildren.length !== 0) {
    for (let rChild of rChildren) {
      const vChild = rChild.cloneNode(false)
      vEle.appendChild(vChild)
      queue.push([rChild, vChild])
    }
  } else {
    vEle.innerHTML = rEle.innerHTML
  }
}
```

# 処理の詳細
ソースコードは、githubで公開しています。
https://github.com/jp-96/hashscratch

## バックグラウンドからコンテントスクリプトの呼び出し

### アイコンのクリックでコンテントスクリプトを読み出す

Chromeのブラウザーアクションで、onClickedイベントにリスナー関数を追加します。
クリックでイベントが呼び出されるとChromeのタブへメッセージを送信し、コンテキストスクリプトからのsendMessageメソッドによる応答を待ちます。

``` background.js
// Called when the user clicks on the browser action.
var isBussy=false;
chrome.browserAction.onClicked.addListener(function(tab) {
  const urlScratch = "https://scratch.mit.edu/projects/";
  const urlChampierre = "https://champierre.github.io/scratch3/";
  if ((tab.url.slice(0,urlScratch.length) == urlScratch) || (tab.url.slice(0,urlChampierre.length) == urlChampierre)){
    if (isBussy){return;}
    isBussy = true;
    chrome.browserAction.setBadgeBackgroundColor({ color: [255, 0, 0, 100] });
    chrome.browserAction.setBadgeText({ text: '#' });
    chrome.tabs.sendMessage(tab.id, {command: "onClicked"}, function(response) {
      chrome.browserAction.setBadgeText({ text: '' });
      console.log(response);
      isBussy = false;
    });
  } else {
    console.log('Not supported: ' + tab.url);
  }
})
```


### コンテントスクリプトで非同期で処理を実行する

background.jsからのsendMessageで、メッセージを受信したら、downloadBlockCodeAsSVG関数を非同期で呼び出し、trueを返します。downloadBlockCodeAsSVG関数では、SVGの取り出し処理を実行し、最後に引数のsendMessageコールバック関数で、処理の完了をbackground.jsへ通知します。

```content_script.js
chrome.extension.onMessage.addListener(
  function(request, sender, sendMessage) {
    downloadBlockCodeAsSVG(sendMessage);
    return true;
  }
)

async function downloadBlockCodeAsSVG(sendMessage) {
  
  //
  // SVGの取り出し処理
  //
  
  // completed
  sendMessage({success: true, reason: 'Hashed Scratch!'})
}
```

#まとめ
1. Scratch3.0のブロックコードをSVG形式ファイルとしてダウンロードできます。
2. バックグラウンドからコンテントスクリプトの呼び出しを行いました。
3. SVGの取り出しとダウンロードは、非同期関数で実装しています。

---
##カンパウェア
**「#S - Hash Scratch」は、カンパウェア（donationware）です。**
次の電子書籍を購入することで、寄付することができます。
[https://leanpub.com/hash-scratch
![donate](https://d2sofvawe08yqg.cloudfront.net/hash-scratch/medium?1581605151)](https://leanpub.com/hash-scratch)