【Scratch3.0で自作エクステンション入門】テキストデータを読み出し・保存するエクステンションを作ろう
※ 当ページには【広告/PR】を含む場合があります。
2020/10/11
2021/12/11
オリジナルのエクステンションを作成
GUIの設定
600x372
80x80
fileio.png
fileio-small.png
scratch-gui/src/lib/libraries/extensions/fileio
index.jsx
scratch-gui/src/lib/libraries/extensions
index.jsx
fileio
import React from 'react';
import {FormattedMessage} from 'react-intl';
//...中略
//👇先程保存しておいた画像の場所を登録
import fileioIconURL from './fileio/fileio.png';
import fileioInsetIconURL from './fileio/fileio-small.png';
export default [
//...中略
//👇新しいエクステンションUIを登録
{
name: 'Custom File I/O using Browser\'s Dialog',
extensionId: 'fileio',
iconURL: fileioIconURL,
insetIconURL: fileioInsetIconURL,
description: (
<FormattedMessage
defaultMessage="Handle files between local and server."
description="Description for the 'Fileio' extension"
id="gui.extension.fileio.description"
/>
),
featured: true
}
];
余談〜2つの画像の必要性
index.jsx
import React from 'react';
import {FormattedMessage} from 'react-intl';
//...中略
export default [
//...中略
//👇新しいエクステンションUIを登録
{
name: 'Custom File I/O using Browser\'s Dialog',
extensionId: 'fileio',
featured: true
}
];
description
ブロックアイコンを作る
書き出し
data:image/svg+xml;base64,...
data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iNDJweCIgaGVpZ2h0PSI0MnB4IiBjb250ZW50PScmbHQ7bXhmaWxlIGhvc3Q9ImFwcC5kaWFncmFtcy5uZXQiIG1vZGlmaWVkPSIyMDIwLTEwLTA4VDA2OjU1OjEyLjgxOFoiIGFnZW50PSI1LjAgKFgxMSkiIHZlcnNpb249IjEzLjcuOCIgZXRhZz0iWExVelVlU252T3hYM29taTNiZWEiJmd0OyZsdDtkaWFncmFtIGlkPSJiTXp5dW5TbUNHanV6aFlfa2ttYSImZ3Q7alpOTmI0TXdESVovRGRlcUpGdTdIbGRLdDhNbVRlcGh4eW9GRjZJRmpFS2d0TDkrempDbHFLcTBDNG9mTy9IckR3SVpGZDJiVlZYK2lTbVlRTXpUTHBDYlFJam5NS1N2QitjZXlKWG9RV1oxMnFOd0JEdDlBWVp6cG8xT29aNEVPa1RqZERXRkNaWWxKRzdDbExWNG1vWWQwVXl6VmlxRE83QkxsTG1uM3pwMWVVOWZ4SExrNzZDemZNZ2NMbGE5cDFCRE1GZFM1eXJGMHcyU2NTQWppK2o2VTlGRllIenZocjcwOTdZUHZGZGhGa3Izbnd2YzkxYVpobXRqWGU0OEZOdUNkWnBxLzFBSE1GOVlhNmV4Sk5jQm5jTWlrT3ZjRllic2tJNUQ3S3ZSbVk5eFdCRlZiQ1VrQ2l5QjJsbjhnUWdOa3JVcHNhUlU2Nk0yWmtDQmtQUDVPdDRLSDV5cnlrc3B1c3d2MDB4ZEdndXp4R0NUN211d3JVNmczdE9ranpwcnJQTGk5dlNVZjdGQzdSUEdMZVd0V1NIWFN6cWhlOWl6OERvSjJtREFBcHc5VXdoZmtBc2VIbSt2V0xKOUduZmhpVkYrc3dZRFU3eDkyZlhsY1VCMDRCa041cmdMZjc2Ykgwckd2dz09Jmx0Oy9kaWFncmFtJmd0OyZsdDsvbXhmaWxlJmd0OycgdmVyc2lvbj0iMS4xIiB2aWV3Qm94PSItLjUgLS41IDQyIDQyIiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciPgogPGcgcG9pbnRlci1ldmVudHM9ImFsbCI+CiAgPHJlY3Qgd2lkdGg9IjQwIiBoZWlnaHQ9IjQwIiBmaWxsPSJub25lIi8+CiAgPHBhdGggZD0ibTAgNC42MWMwLjMtMi40MyAyLjMtNC4zNCA0LjgyLTQuNjFoMjYuODhsOC4zIDcuOTZ2MjcuMDdjLTAuMTQgMi42Ni0yLjMzIDQuOC01LjA5IDQuOTdoLTMwLjA5Yy0yLjQ4LTAuMjUtNC40Ny0yLjExLTQuODItNC40OXptMy4yNyAzMC4xOGMwIDAuOTYgMC43IDEuNzkgMS42OCAxLjk4aDMwLjAyYzAuOTYtMC4yMSAxLjY0LTEuMDMgMS42My0xLjk4di0yNC45N2gtNi43di02LjU1aC0yNC45NWMtMC45IDAuMjItMS41NSAwLjk3LTEuNiAxLjg2em03LjIxLTIxLjk4aDE4LjUzdjMuMzFoLTE4LjUzem0wIDYuM2gxOC41M3YzLjMxaC0xOC41M3ptMCA2LjNoMTguNTN2My4zNmgtMTguNTN6IiBmaWxsPSIjMDBiZWYyIi8+CiA8L2c+Cjwvc3ZnPgo=
拡張機能の追加
scratch-vm/src/extensions
scratch3_fileio
index.js
const ArgumentType = require('../../extension-support/argument-type');
const BlockType = require('../../extension-support/block-type');
const Cast = require('../../util/cast');
const log = require('../../util/log');
// 👇先程作成したアイコン画像
const commonSvgIcon = 'data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iNDJweCIgaGVpZ2h0PSI0MnB4IiBjb250ZW50PScmbHQ7bXhmaWxlIGhvc3Q9ImFwcC5kaWFncmFtcy5uZXQiIG1vZGlmaWVkPSIyMDIwLTEwLTA4VDA2OjU1OjEyLjgxOFoiIGFnZW50PSI1LjAgKFgxMSkiIHZlcnNpb249IjEzLjcuOCIgZXRhZz0iWExVelVlU252T3hYM29taTNiZWEiJmd0OyZsdDtkaWFncmFtIGlkPSJiTXp5dW5TbUNHanV6aFlfa2ttYSImZ3Q7alpOTmI0TXdESVovRGRlcUpGdTdIbGRLdDhNbVRlcGh4eW9GRjZJRmpFS2d0TDkrempDbHFLcTBDNG9mTy9IckR3SVpGZDJiVlZYK2lTbVlRTXpUTHBDYlFJam5NS1N2QitjZXlKWG9RV1oxMnFOd0JEdDlBWVp6cG8xT29aNEVPa1RqZERXRkNaWWxKRzdDbExWNG1vWWQwVXl6VmlxRE83QkxsTG1uM3pwMWVVOWZ4SExrNzZDemZNZ2NMbGE5cDFCRE1GZFM1eXJGMHcyU2NTQWppK2o2VTlGRllIenZocjcwOTdZUHZGZGhGa3Izbnd2YzkxYVpobXRqWGU0OEZOdUNkWnBxLzFBSE1GOVlhNmV4Sk5jQm5jTWlrT3ZjRllic2tJNUQ3S3ZSbVk5eFdCRlZiQ1VrQ2l5QjJsbjhnUWdOa3JVcHNhUlU2Nk0yWmtDQmtQUDVPdDRLSDV5cnlrc3B1c3d2MDB4ZEdndXp4R0NUN211d3JVNmczdE9ranpwcnJQTGk5dlNVZjdGQzdSUEdMZVd0V1NIWFN6cWhlOWl6OERvSjJtREFBcHc5VXdoZmtBc2VIbSt2V0xKOUduZmhpVkYrc3dZRFU3eDkyZlhsY1VCMDRCa041cmdMZjc2Ykgwckd2dz09Jmx0Oy9kaWFncmFtJmd0OyZsdDsvbXhmaWxlJmd0OycgdmVyc2lvbj0iMS4xIiB2aWV3Qm94PSItLjUgLS41IDQyIDQyIiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciPgogPGcgcG9pbnRlci1ldmVudHM9ImFsbCI+CiAgPHJlY3Qgd2lkdGg9IjQwIiBoZWlnaHQ9IjQwIiBmaWxsPSJub25lIi8+CiAgPHBhdGggZD0ibTAgNC42MWMwLjMtMi40MyAyLjMtNC4zNCA0LjgyLTQuNjFoMjYuODhsOC4zIDcuOTZ2MjcuMDdjLTAuMTQgMi42Ni0yLjMzIDQuOC01LjA5IDQuOTdoLTMwLjA5Yy0yLjQ4LTAuMjUtNC40Ny0yLjExLTQuODItNC40OXptMy4yNyAzMC4xOGMwIDAuOTYgMC43IDEuNzkgMS42OCAxLjk4aDMwLjAyYzAuOTYtMC4yMSAxLjY0LTEuMDMgMS42My0xLjk4di0yNC45N2gtNi43di02LjU1aC0yNC45NWMtMC45IDAuMjItMS41NSAwLjk3LTEuNiAxLjg2em03LjIxLTIxLjk4aDE4LjUzdjMuMzFoLTE4LjUzem0wIDYuM2gxOC41M3YzLjMxaC0xOC41M3ptMCA2LjNoMTguNTN2My4zNmgtMTguNTN6IiBmaWxsPSIjMDBiZWYyIi8+CiA8L2c+Cjwvc3ZnPgo=';
const blockIconURI = commonSvgIcon;
const menuIconURI = commonSvgIcon;
class Scratch3FileIO {
constructor (runtime) {
this.runtime = runtime;
}
getInfo () {
return {
id: 'fileio',
name: 'Custom File I/O',
menuIconURI: menuIconURI,
blockIconURI: blockIconURI,
blocks: [
{
opcode: 'writeLog',
blockType: BlockType.COMMAND,
text: 'log [TEXT]',
arguments: {
TEXT: {
type: ArgumentType.STRING,
defaultValue: "hello"
}
}
},
{
opcode: 'getBrowser',
text: 'browser',
blockType: BlockType.REPORTER
}
],
menus: {
}
};
}
writeLog (args) {
const text = Cast.toString(args.TEXT);
log.log(text);
}
getBrowser () {
return navigator.userAgent;
}
}
module.exports = Scratch3FileIO;
余談〜ブロックアイコンが不要なとき
index.js
const ArgumentType = require('../../extension-support/argument-type');
const BlockType = require('../../extension-support/block-type');
const Cast = require('../../util/cast');
const log = require('../../util/log');
class Scratch3FileIO {
constructor (runtime) {
this.runtime = runtime;
}
getInfo () {
return {
id: 'fileio',
name: 'Custom File I/O',
blocks: [
{
opcode: 'writeLog',
blockType: BlockType.COMMAND,
text: 'log [TEXT]',
arguments: {
TEXT: {
type: ArgumentType.STRING,
defaultValue: "hello"
}
}
},
{
opcode: 'getBrowser',
text: 'browser',
blockType: BlockType.REPORTER
}
],
menus: {
}
};
}
writeLog (args) {
const text = Cast.toString(args.TEXT);
log.log(text);
}
getBrowser () {
return navigator.userAgent;
}
}
module.exports = Scratch3FileIO;
vmの拡張機能リストに追加
scratch-vm/src/extension-support/extension-manager.js
builtinExtensions
const dispatch = require('../dispatch/central-dispatch');
const log = require('../util/log');
const maybeFormatMessage = require('../util/maybe-format-message');
const BlockType = require('./block-type');
// These extensions are currently built into the VM repository but should not be loaded at startup.
// TODO: move these out into a separate repository?
// TODO: change extension spec so that library info, including extension ID, can be collected through static methods
const builtinExtensions = {
//...中略
//👇新規エクステンションのindex.jsのパスで登録
fileio: () => require('../extensions/scratch3_fileio')
};
//...以下略
scratch-gui
$ npm start
#OR
$ yarn start
http://localhost:8601
スクラッチからブラウザーのファイル操作ダイアログを呼び出す
scratch-vm/src/extensions/scratch3_fileio/index.js
Firefoxなどでは動かないので注意が必要です。
この記事では原則Google Chromeで動作確認をしています。
ローカルのファイルを選択する際に、ブラウザのファイルピッカーダイアログを利用します。
ブラウザのセキュリティ設計の関係によっては、
javascript側からのファイルピッカーの呼び出しが利用できない場合があります。
エクステンション・プログラミングの基礎
BlockType
+ COMMAND:
関数を実行するブロック。
引数を定義して利用することも可能
+ REPORTER:
特定の文字列や数値などを返すブロック。
引数を与えて利用することもできる
+ BOOLEAN:
判定に特化したブロック。
true/falseのどちらかを返す。
引数を与えて真偽を判断して返すことも可能。
+ HAT:
エクステンションのコード内部の変数(true/false)を監視して、
trueのときにだけ処理を実行するブロック。
イベントのトリガーとして利用できる
ファイルから読み出す
Scratch3FileIO
//...中略
class Scratch3FileIO {
constructor (runtime) {
this.runtime = runtime;
//👇テキスト読み込み時の完了時のトリガーとして利用
this.isReadText = false;
//👇アップロードさせたテキストの中身を取得
this.reader = new FileReader();
this.reader.onload = () => {
log.log(this.reader.result);
this.isReadText = true;
};
//👇input要素を隠れdomとして作成
this.input = document.createElement('input');
this.input.type = 'file';
this.input.accept = 'text/plain';
this.input.addEventListener('change', this.clickCallback);
this.clickCallback = (event) => {
log.log(event.target.files[0]);
this.reader.readAsText(event.target.files[0]);
};
}
getInfo() {
return {
id: 'fileio',
name: 'Custom File I/O',
menuIconURI: menuIconURI,
blockIconURI: blockIconURI,
blocks: [
//...中略
//👇readFile, loadedFile, getTextの3つのブロックを定義
{
opcode: 'readFile',
text: 'Upload',
blockType: BlockType.COMMAND
},
{
opcode: 'loadedFile',
text: 'Loaded text',
blockType: BlockType.HAT
},
{
opcode: 'getText',
text: 'Show text',
blockType: BlockType.REPORTER
}
],
//...中略
};
}
//...中略
//👇readFile, loadedFile, getTextの3つのブロックに対応した関数の定義
readFile() {
if (this.input) {
this.input.click();
}
}
loadedFile() {
const triggered = this.isReadText;
this.isReadText = false;
return triggered;
}
getText () {
return this.reader.result;
}
}
//...中略
getInfo
opcode
text
readFile
type=file
input
COMMAND
HAT
loadedFile
isReadText
reader
result
getText
REPORTER
loadedFile
たこ:とてもげんぎ
x座標:-132
y座標:-82
スコア:520
ステージ:うみのなか
プレイヤー:あなた
ファイルとしてダウンロードする
//...中略
class Scratch3FileIO {
constructor (runtime) {
//...中略
//👇擬似的なa要素をdownload属性付きで追加
this.downloadLink = document.createElement('a');
this.downloadLink.setAttribute('href', '#');
this.downloadLink.setAttribute('download', 'savedata.txt');
}
getInfo() {
return {
id: 'fileio',
name: 'Custom File I/O',
menuIconURI: menuIconURI,
blockIconURI: blockIconURI,
blocks: [
//...中略
//👇writeFileのブロックを定義
{
opcode: 'writeFile',
text: 'Download [TEXT]',
blockType: BlockType.COMMAND,
arguments: {
TEXT: {
type: ArgumentType.STRING,
defaultValue: 'hello'
}
}
}
],
//...中略
};
}
//...中略
//👇writeFileのブロックに対応した関数の定義
writeFile(args) {
this.downloadCallback = () => {
const content = Cast.toString(args.TEXT);
const blob = new Blob([ content ], { "type" : "text/plain" });
if (window.navigator.msSaveBlob) {
window.navigator.msSaveBlob(blob, "test.txt");
} else {
this.downloadLink.href = window.URL.createObjectURL(blob);
}
};
this.downloadLink.addEventListener('click', this.downloadCallback);
this.downloadLink.click();
this.downloadLink.removeEventListener('click', this.downloadCallback);
}
}
//...中略
getInfo
writeFile
COMMAND
TEXT
TEXT
Cast.toString
Blob
ObjectURL
addEventListener
click
removeEventListener
まとめ
記事を書いた人
ナンデモ系エンジニア
これからの"地方格差"なきプログラミング教育とは何かを考えながら、 地方密着型プログラミング学習関連テーマの記事を不定期で独自にブログ発信しています。
カテゴリー