mirror of
https://github.com/ocogeclub/ocoge.git
synced 2024-11-25 00:49:48 +00:00
659 lines
25 KiB
JavaScript
659 lines
25 KiB
JavaScript
'use strict';
|
||
|
||
let apptool = appTool_new();
|
||
|
||
const testfunc = async () => {
|
||
apptool.openURL('http://ocoge.club');
|
||
}
|
||
|
||
//============ User Customize Start ===============
|
||
// カスタムブロックカラー定義
|
||
Blockly.utils.colour.setHsvSaturation(0.55);
|
||
Blockly.utils.colour.setHsvValue(0.75);
|
||
// Blockly.HSV_SATURATION = 0.55;
|
||
// Blockly.HSV_VALUE = 0.75;
|
||
var gpio_color = '0';
|
||
var sonsor_color = '20';
|
||
var multimedia_color = '240';
|
||
var network_color = '340';
|
||
var special_color = '40';
|
||
var rp2_color = '180';
|
||
// テーマ
|
||
var theme = Blockly.Theme.defineTheme('ocoge', {
|
||
'base': Blockly.Themes.Classic,
|
||
'startHats': true,
|
||
'componentStyles': {
|
||
'toolboxBackgroundColour': 'aliceblue',
|
||
'flyoutBackgroundColour': 'lavender',
|
||
'toolboxForegroundColour': 'white',
|
||
'flyoutForegroundColour': 'steelblue'
|
||
},
|
||
'blockStyles': {
|
||
'gpio_blocks': {
|
||
"colourPrimary": gpio_color
|
||
},
|
||
'sensor_blocks': {
|
||
"colourPrimary": sonsor_color
|
||
},
|
||
'multimedia_blocks': {
|
||
"colourPrimary": multimedia_color
|
||
},
|
||
'colour_blocks': {
|
||
"colourPrimary": multimedia_color
|
||
},
|
||
'network_blocks': {
|
||
"colourPrimary": network_color
|
||
},
|
||
'special_blocks': {
|
||
"colourPrimary": special_color
|
||
},
|
||
'rp2_blocks': {
|
||
"colourPrimary": rp2_color
|
||
}
|
||
},
|
||
'categoryStyles': {
|
||
"gpio_category": {
|
||
"colour": gpio_color
|
||
},
|
||
"sensor_category": {
|
||
"colour": sonsor_color
|
||
},
|
||
"multimedia_category": {
|
||
"colour": multimedia_color
|
||
},
|
||
"network_category": {
|
||
"colour": network_color
|
||
},
|
||
"special_category": {
|
||
"colour": special_color
|
||
},
|
||
"rp2_category": {
|
||
"colour": rp2_color
|
||
}
|
||
},
|
||
});
|
||
|
||
// Customize messages
|
||
Blockly.Msg["CONTROLS_IF_MSG_THEN"] = "ならば";
|
||
Blockly.Msg["CONTROLS_REPEAT_INPUT_DO"] = "";
|
||
Blockly.Msg["CONTROLS_FOR_TITLE"] = "%1 を %2 から %3 まで %4 ずつ増やし(減らし)て";
|
||
Blockly.Msg["CONTROLS_FOR_TOOLTIP"] = "インデックス番号を決められた数ずつ増やし(減らし)ながら、ステートメントを実行します。";
|
||
Blockly.Msg["CONTROLS_FOREACH_TITLE"] = "リスト%2の各%1について";
|
||
Blockly.Msg["CONTROLS_FOREACH_TOOLTIP"] = "リストの各項目について、その項目を変数「項目」としてステートメントを実行します。";
|
||
Blockly.Msg["MATH_CHANGE_TITLE"] = "変数 %1 を %2 増やす";
|
||
Blockly.Msg["VARIABLES_SET"] = "変数 %1 を %2 にする";
|
||
// Blockly.Msg["TEXT_PRINT_TITLE"] = "ダイアログに %1 を表示";
|
||
Blockly.Msg["LOGIC_BOOLEAN_FALSE"] = "偽";
|
||
Blockly.Msg["LOGIC_BOOLEAN_TOOLTIP"] = "真 または 偽 を返します。";
|
||
Blockly.Msg["LOGIC_BOOLEAN_TRUE"] = "真";
|
||
|
||
// ローカライズ対応しといた方がいいかと思ってやってみたけど面倒になってきた
|
||
Blockly.Msg["UGJ_CONTROL_FOR_TITLE"] = "%1 %2 を %3 から %4 まで %5 ずつ %6 %7 %8";
|
||
Blockly.Msg["UGJ_CONTROL_FOR_INDEX"] = "番号";
|
||
Blockly.Msg["UGJ_CONTROL_FOR_INCREASE"] = "増やして";
|
||
Blockly.Msg["UGJ_CONTROL_FOR_DECREASE"] = "減らして";
|
||
Blockly.Msg["UGJ_CONTROL_FOR_TOOLTIP"] = "インデックス番号を決められた数ずつ増やし(減らし)ながら、ステートメントを実行します。";
|
||
Blockly.Msg["UGJ_FOREACH_TITLE"] = "リスト %1 の各 %2 について %3 %4";
|
||
Blockly.Msg["UGJ_FOREACH_ITEM"] = "項目";
|
||
Blockly.Msg["UGJ_FOREACH_TOOLTIP"] = "リストの各項目について、その項目を変数「項目」としてステートメントを実行します。";
|
||
|
||
Blockly.Msg["GPIO_OPEN_TITLE"] = "GPIO を使えるようにする";
|
||
Blockly.Msg["GPIO_OPEN_TOOLTIP"] = "rgpiod デーモンに接続します。";
|
||
Blockly.Msg["GPIO_CLOSE_TITLE"] = "GPIO の後片付けをする";
|
||
Blockly.Msg["GPIO_CLOSE_TOOLTIP"] = "rgpiod デーモンから切断します。";
|
||
Blockly.Msg["GPIO_SET_INPUT_TITLE"] = "GPIO %1 を入力モードにして %2";
|
||
Blockly.Msg["GPIO_SET_INPUT_TOOLTIP"] = "GPIO端子を入力モードにして、プルアップ・プルダウン・無しを設定します。";
|
||
Blockly.Msg["GPIO_SET_INPUT_PULLUP"] = "プルアップ";
|
||
Blockly.Msg["GPIO_SET_INPUT_PULLDOWN"] = "プルダウン";
|
||
Blockly.Msg["GPIO_SET_INPUT_PULLNONE"] = "プル無し";
|
||
Blockly.Msg["GPIO_SET_OUTPUT_TITLE"] = "GPIO %1 を出力モードにする";
|
||
Blockly.Msg["GPIO_SET_OUTPUT_TOOLTIP"] = "GPIO端子のモードを出力に設定します。";
|
||
Blockly.Msg["GPIO_READ_TITLE"] = "GPIO %1 の値";
|
||
Blockly.Msg["GPIO_READ_TOOLTIP"] = "GPIO端子の値をデジタル値(0または1)で読み取ります。";
|
||
Blockly.Msg["GPIO_WRITE_TITLE"] = "GPIO %1 の値を %2 にする";
|
||
Blockly.Msg["GPIO_WRITE_TOOLTIP"] = "GPIO端子の値をデジタル値(0または1)で出力します。";
|
||
Blockly.Msg["SERVO_TITLE"] = "GPIO %1 のサーボモータの回転を %2 にする";
|
||
Blockly.Msg["SERVO_TOOLTIP"] = "サーボモータの回転をパルス幅(1000~2000μsec)までの数値で指定します。";
|
||
Blockly.Msg["PWM_TITLE"] = "GPIO %1 をパルス周波数 %2 Hzの PWM 出力に設定";
|
||
Blockly.Msg["PWM_TOOLTIP"] = "[RP2] パルス周波数をセットして、GPIO端子がPWM出力できるようにします。";
|
||
Blockly.Msg["SERIAL_OPEN_TITLE"] = "シリアルポートを速度 %1 bpsで開く";
|
||
Blockly.Msg["SERIAL_OPEN_TOOLTIP"] = "シリアルデバイスとの接続を開きます。";
|
||
Blockly.Msg["SERIAL_CLOSE_TITLE"] = "シリアルポートを閉じる";
|
||
Blockly.Msg["SERIAL_CLOSE_TOOLTIP"] = "シリアルデバイスとの接続を閉じます。";
|
||
Blockly.Msg["SERIAL_WRITE_TITLE"] = "シリアルポートに %1 を送信する";
|
||
Blockly.Msg["SERIAL_WRITE_TOOLTIP"] = "シリアル接続されたデバイスにデータを送信します。シリアルポートは開かれていなくてはいけません。";
|
||
Blockly.Msg["SERIAL_READ_TITLE"] = "シリアルポートから %1 文字読み込む";
|
||
Blockly.Msg["SERIAL_READ_TOOLTIP"] = "オープン済みシリアルポートから、指定のバイト数だけデータを読み込みます。";
|
||
Blockly.Msg["I2C_WRITE_BYTE_TITLE"] = "デバイスに1バイトデータ %1 を送信";
|
||
Blockly.Msg["I2C_WRITE_BYTE_TOOLTIP"] = "i2cデバイスに1バイトデータを送信します。0-0xFFの範囲の数字で入力してください。";
|
||
Blockly.Msg["I2C_READ_BYTE_TITLE"] = "I2C デバイスから 1 バイト受け取る";
|
||
Blockly.Msg["I2C_READ_BYTE_TOOLTIP"] = "オープン済み I2C デバイスからデータを 1 バイト受け取ります。";
|
||
Blockly.Msg["I2C_WRITE_I2C_BLOCK_DATA_TITLE"] = "";
|
||
Blockly.Msg["I2C_WRITE_I2C_BLOCK_DATA_TOOLTIP"] = "";
|
||
Blockly.Msg["I2C_READ_DEVICE_TITLE"] = "";
|
||
Blockly.Msg["I2C_READ_DEVICE_TOOLTIP"] = "";
|
||
Blockly.Msg["I2C_WRITE_DEVICE_TITLE"] = "";
|
||
Blockly.Msg["I2C_WRITE_DEVICE_TOOLTIP"] = "";
|
||
|
||
Blockly.Msg["UGJ_CODECHAR_TITLE"] = "コード %1 の文字";
|
||
Blockly.Msg["UGJ_CODECHAR_TOOLTIP"] = "文字コードを文字に変換します。";
|
||
Blockly.Msg["UGJ_CHARCODE_TITLE"] = "%1 の文字コード";
|
||
Blockly.Msg["UGJ_CHARCODE_TOOLTIP"] = "入力テキストの1文字目の文字コードを返します。";
|
||
Blockly.Msg["UGJ_HEXTODEC_TITLE"] = "0x %1";
|
||
Blockly.Msg["UGJ_HEXTODEC_TOOLTIP"] = "16進数を10進数に変換します。";
|
||
Blockly.Msg["UGJ_DECTOHEX_TITLE"] = "%1 を16進数に変換";
|
||
Blockly.Msg["UGJ_DECTOHEX_TOOLTIP"] = "10進数を16進数に変換します。";
|
||
|
||
Blockly.Msg["UGJ_CANVAS_INIT_TITLE"] = "キャンバスを表示";
|
||
Blockly.Msg["UGJ_CANVAS_INIT_TOOLTIP"] = "キャンバスを表示し、使用できるようにします。";
|
||
Blockly.Msg["UGJ_SLEEP_TITLE"] = "%1 秒待つ";
|
||
Blockly.Msg["UGJ_SLEEP_TOOLTIP"] = "指定した秒数だけ処理を中断します。";
|
||
|
||
// Customize Toolbox
|
||
class CustomCategory extends Blockly.ToolboxCategory {
|
||
/** Constructor for a custom category. @override */
|
||
constructor(categoryDef, toolbox, opt_parent) {
|
||
super(categoryDef, toolbox, opt_parent);
|
||
}
|
||
/** @override */
|
||
addColourBorder_(colour) {
|
||
this.rowDiv_.style.backgroundColor = colour;
|
||
}
|
||
/** @override */
|
||
setSelected(isSelected) {
|
||
// We do not store the label span on the category, so use getElementsByClassName.
|
||
var labelDom = this.rowDiv_.getElementsByClassName('blocklyTreeLabel')[0];
|
||
if (isSelected) {
|
||
// Change the background color of the div to white.
|
||
this.rowDiv_.style.backgroundColor = 'white';
|
||
// Set the colour of the text to the colour of the category.
|
||
labelDom.style.color = this.colour_;
|
||
this.iconDom_.style.color = this.colour_;
|
||
} else {
|
||
// Set the background back to the original colour.
|
||
this.rowDiv_.style.backgroundColor = this.colour_;
|
||
// Set the text back to white.
|
||
labelDom.style.color = 'white';
|
||
this.iconDom_.style.color = 'white';
|
||
}
|
||
// This is used for accessibility purposes.
|
||
Blockly.utils.aria.setState(/** @type {!Element} */(this.htmlDiv_),
|
||
Blockly.utils.aria.State.SELECTED, isSelected);
|
||
}
|
||
}
|
||
Blockly.registry.register(
|
||
Blockly.registry.Type.TOOLBOX_ITEM,
|
||
Blockly.ToolboxCategory.registrationName,
|
||
CustomCategory, true);
|
||
|
||
// Always await function call
|
||
Blockly.JavaScript['procedures_callreturn'] = function (block) {
|
||
// Call a procedure with a return value.
|
||
var funcName = Blockly.JavaScript.variableDB_.getName(
|
||
block.getFieldValue('NAME'), Blockly.PROCEDURE_CATEGORY_NAME);
|
||
var args = [];
|
||
var variables = block.getVars();
|
||
for (var i = 0; i < variables.length; i++) {
|
||
args[i] = Blockly.JavaScript.valueToCode(block, 'ARG' + i,
|
||
Blockly.JavaScript.ORDER_NONE) || 'null';
|
||
}
|
||
var code = 'await ' + funcName + '(' + args.join(', ') + ')';
|
||
return [code, Blockly.JavaScript.ORDER_AWAIT];
|
||
};
|
||
//============ User Customize End ===============
|
||
|
||
|
||
//背景canvasとマスコットの準備
|
||
const ugj_canvasBgImg = (imgSrc, x, y) => { //x,y == -1: center or middle
|
||
let el = document.getElementById('canvas_bg');
|
||
let ctx = el.getContext('2d');
|
||
ctx.fillStyle = 'rgb(255,255,255)';
|
||
ctx.fillRect(0, 0, 480, 360);
|
||
let img = new Image();
|
||
img.src = imgSrc;
|
||
img.onload = () => {
|
||
if (x < 0) { //センタリング
|
||
let w = img.width;
|
||
if (w >= 480) x = 0;
|
||
else x = Math.floor((480 - w) / 2);
|
||
}
|
||
if (y < 0) { //縦中寄せ
|
||
let h = img.height;
|
||
if (h >= 360) y = 0;
|
||
else y = Math.floor((360 - h) / 2);
|
||
}
|
||
ctx.drawImage(img, x, y);
|
||
}
|
||
};
|
||
|
||
// HTML部品のインスタンス - 画面上の必要な部品はすべてここで取得しておく
|
||
let ugjel_displayArea = document.getElementById('display_area'); // ディスプレイ部
|
||
let ugjel_blackboard = document.getElementById('blackboard'); // 黒板
|
||
let ugjel_inputForm = document.getElementById('inputForm'); // 入力フォーム
|
||
let ugjel_inputBox = document.getElementById('inputBox'); // 入力フィールド
|
||
|
||
// その他のプロパティ
|
||
let ugj_inputEvLstnrID = 0; // 入力フォームの動的イベントリスナの最新のID
|
||
let ugj_sounds = (names => { // サウンドファイルのいろいろの配列の初期化
|
||
let sounds = [];
|
||
names.forEach(value => {
|
||
let filepath = `./sounds/${value}.wav`;
|
||
sounds[value] = { 'file': filepath, 'audio': new Audio(filepath) };
|
||
});
|
||
return sounds;
|
||
})(['meow', 'bounce', 'type_chime', 'type_dink', 'type_tap', 'type_space', 'type_return']); // サウンドファイルのベース名のリスト
|
||
|
||
// メソッド
|
||
|
||
// マスコット選択
|
||
const ugj_selectMascot = async () => {
|
||
let fname = await apptool.selectMascotFile();
|
||
if (fname) {
|
||
ugj_canvasBgImg(fname, -1, -1);
|
||
apptool.setMascotFilePath(fname);
|
||
}
|
||
}
|
||
|
||
// サウンド再生 - 連続再生のため、再生開始後すぐにオーディオ要素を再生成する
|
||
const ugj_soundPlay = soundName => {
|
||
ugj_sounds[soundName]['audio'].play();
|
||
ugj_sounds[soundName]['audio'] = new Audio(ugj_sounds[soundName]['file']);
|
||
};
|
||
|
||
// OK,Cancel 2択のダイアログを表示
|
||
const ugj_confirm = (title, message, callback) => {
|
||
CustomDialog.show(title, message, {
|
||
showOkay: true,
|
||
onOkay: () => callback(true),
|
||
showCancel: true,
|
||
onCancel: () => callback(false)
|
||
});
|
||
};
|
||
|
||
const ugj_htmlEntities = str =>// HTMLエンティティのエスケープ
|
||
String(str).replace(/</g, '<').replace(/>/g, '>').replace(/"/g, '"');//.replace(/&/g, '&').replace(/ /g, ' ');
|
||
|
||
// 新規ワークスペース
|
||
const ugj_newWorkspace = () => {
|
||
ugj_confirm('新規ワークスペース', '保存していない内容はすべて破棄されます。よろしいですか?', okey => {
|
||
if (okey) {
|
||
workspace.clear();
|
||
apptool.newFile();
|
||
}
|
||
});
|
||
}
|
||
|
||
// ワークスペースをファイルに保存・読込
|
||
const ugj_saveWorkspaceToFile = async () => {
|
||
let xml = Blockly.Xml.workspaceToDom(workspace);
|
||
let xml_text = Blockly.utils.xml.domToText(xml);
|
||
if (await apptool.saveWsFile(xml_text) === false) {
|
||
window.alert('保存できませんでした。');
|
||
}
|
||
}
|
||
const ugj_loadWorkspaceFromFile = async () => {
|
||
let xml_text = await apptool.loadWsFile();
|
||
if (xml_text.length > 0) {
|
||
let xml = Blockly.utils.xml.textToDom(xml_text);
|
||
Blockly.Xml.domToWorkspace(xml, workspace);
|
||
}
|
||
}
|
||
|
||
// ワークスペースを別名で保存
|
||
const ugj_saveWorkspaceAs = () => {
|
||
apptool.newFile();
|
||
ugj_saveWorkspaceToFile();
|
||
}
|
||
|
||
// ワークスペースをローカルストレージに保存・読込
|
||
const ugj_saveWorkspace = () => {
|
||
// Workspace
|
||
let xml = Blockly.Xml.workspaceToDom(workspace);
|
||
let xml_text = Blockly.utils.xml.domToText(xml);
|
||
localStorage.setItem("ocoge.xml", xml_text);
|
||
}
|
||
const ugj_loadWorkspace = () => {
|
||
// Workspace
|
||
let xml_text = localStorage.getItem("ocoge.xml");
|
||
if (xml_text !== null) {
|
||
if (xml_text.length != 0) {
|
||
let xml = Blockly.utils.xml.textToDom(xml_text);
|
||
Blockly.Xml.domToWorkspace(xml, workspace);
|
||
}
|
||
}
|
||
}
|
||
|
||
// コード生成
|
||
const ugj_generateCode = () => {
|
||
let code;
|
||
if (apptool.lang == 'py') { // Python コード出力
|
||
try {
|
||
code = Blockly.Python.workspaceToCode(workspace);
|
||
} catch (e) { // Pythonコードを持たないブロックがある場合
|
||
window.alert('Python 非対応のブロックが使用されています。\n' + e.message);
|
||
code = '';
|
||
}
|
||
} else { // Javascript コード出力
|
||
try {
|
||
code = Blockly.JavaScript.workspaceToCode(workspace).replace(/(?<=^|\n)function \w+\(.*\)/g, 'async $&');
|
||
} catch (e) { // JSコードを持たないブロックがある場合
|
||
window.alert('Python 専用ブロックが使用されています。\n' + e.message);
|
||
code = '';
|
||
}
|
||
// .replace 以降は、関数の常時非同期化のため...要するに async 付加
|
||
}
|
||
return code;
|
||
}
|
||
|
||
const ugj_asyncIife = code => {
|
||
// await使用のため、必要に応じてコード全体をasync付き即時関数でラップ
|
||
code = [
|
||
'const main = async () => {',
|
||
code,
|
||
`}`,
|
||
`main();`,
|
||
].join('\n');
|
||
return code;
|
||
}
|
||
|
||
// // Python コードフォーマッタ Black をコール
|
||
// const ugj_pyBeautify = (code) => {
|
||
// let formatted = '';
|
||
// formatted = require('child_process').spawnSync('python3', ['-m', 'black', '-'], { input: code }).stdout.toString();
|
||
// if (!formatted) formatted = code;
|
||
// return formatted;
|
||
// }
|
||
|
||
// コードフォーマッティング
|
||
const ugj_formatCode = code => {
|
||
if (apptool.lang == 'py') // フォーマッティング
|
||
code = apptool.pyBeautify(code);
|
||
else
|
||
code = js_beautify(code, { indent_size: 2 });
|
||
return code;
|
||
}
|
||
|
||
// ブロックスクリプト実行
|
||
const ugj_runCode = async () => {
|
||
document.activeElement.blur(); //実行ボタンからフォーカスを外す:エンターキー押下が悪さをするため
|
||
let btnel = document.getElementById('runbtn');
|
||
btnel.disabled = true;
|
||
let code = ugj_generateCode();
|
||
if (apptool.lang == 'js') {
|
||
let AsyncFunction = Object.getPrototypeOf(async function () { }).constructor
|
||
let ocogeFunc = new AsyncFunction(code);
|
||
await ocogeFunc().catch(e => { window.alert(e); });
|
||
console.log('Code Execution done.');
|
||
} else if (apptool.lang == 'py') {
|
||
if (code) ugj_spawnPyboard(code, 'r');
|
||
}
|
||
btnel.disabled = false;
|
||
}
|
||
|
||
// Pyboard ocoge
|
||
const ugj_spawnPyboard = (code, mode) => {
|
||
let script_path = apptool.path.join(apptool.library_path, 'pybtool.py');
|
||
let p = require('child_process').spawn('python3', [script_path, mode], { env: { "PYTHONUTF8": "1" } });
|
||
p.stdin.write(code);
|
||
p.stdin.end();
|
||
p.stderr.on('data', d => { console.error(d.toString()) });
|
||
p.stdout.on('data', d => { console.log(d.toString()) });
|
||
p.on('close', c => {
|
||
if (c == 0) console.log(`コード実行 OK`);
|
||
else _fukidashi(`実行時エラーが発生しました。詳しくはコンソールを参照してください。`, 5);
|
||
});
|
||
}
|
||
|
||
// コードをダイアログで表示・保存
|
||
const ugj_showCode = () => {
|
||
const dialog = document.getElementById('codeDlg');
|
||
const content = document.getElementById('code');
|
||
const btn_close = document.getElementById('dlgClose');
|
||
const btn_export = document.getElementById('dlgExport');
|
||
|
||
let code = ugj_generateCode(); // コード生成
|
||
|
||
content.innerHTML = ugj_htmlEntities(ugj_formatCode(code));
|
||
if (apptool.lang == 'py') content.setAttribute('class', 'language-python');
|
||
else content.setAttribute('class', 'language-javascript');
|
||
Prism.highlightElement(content);
|
||
|
||
dialog.showModal();
|
||
|
||
const save_to_file = code => {
|
||
if (apptool.saveFile(code, apptool.lang) === false) window.alert('保存できませんでした。');
|
||
}
|
||
|
||
const close_cb = () => {
|
||
dialog.close();
|
||
btn_close.removeEventListener('click', close_cb);
|
||
btn_export.removeEventListener('click', export_cb);
|
||
document.removeEventListener('keyup', keyev_cb);
|
||
}
|
||
const export_cb = () => {
|
||
if (apptool.lang == 'js') {
|
||
save_to_file(ugj_formatCode(ugj_asyncIife(code)));
|
||
} else {
|
||
ugj_spawnPyboard(ugj_formatCode(code), 'd');
|
||
}
|
||
close_cb();
|
||
}
|
||
const keyev_cb = ev => {
|
||
if (ev.key == 's' && ev.ctrlKey) {
|
||
if (apptool.lang == 'js') {
|
||
// JS-CLI モード
|
||
// blackboardWrite()とwindow.alert()、fukidashi()をconsole.log()に書き換え、
|
||
// document... と ugj_... と apptool... をコメントアウト(ブラウザ関連部分の追放という意味では不完全なので注意)
|
||
// あと正規表現もいい加減
|
||
code = code.replace(/const appendDiv[^#]*\/\/#/gm, 'const blackboardWrite = text => console.log(text);').replace('window.alert', 'console.log').replace(/_fukidashi(.*), \d+(\);)/gm, 'console.log$1$2').replace(/(^(?=.*document.)[^;]*;)/gm, '/* $1 */').replace(/(^(?=.*ugj_)[^;]*;)/gm, '/* $1 */').replace(/(^(?=.*apptool.)[^;]*;)/gm, '/* $1 */');
|
||
}
|
||
save_to_file(ugj_formatCode(ugj_asyncIife(code)));
|
||
close_cb();
|
||
}
|
||
}
|
||
btn_close.addEventListener('click', close_cb);
|
||
btn_export.addEventListener('click', export_cb);
|
||
document.addEventListener('keyup', keyev_cb);
|
||
}
|
||
|
||
|
||
// フキダシ
|
||
let ugj_fdTimeoutID = null;
|
||
let ugj_fdRecentBox = null;
|
||
const _fukidashi = (text, sec) => {
|
||
// Canvas Context
|
||
const context = document.getElementById('canvas').getContext('2d');
|
||
// 吹き出しを消去する関数
|
||
const clearFd = (x, y, w, h) => context.clearRect(x, y, w, h);
|
||
// 前回の思い出を忘れる
|
||
if (ugj_fdRecentBox !== null) {
|
||
clearFd(ugj_fdRecentBox.x, ugj_fdRecentBox.y, ugj_fdRecentBox.w, ugj_fdRecentBox.h);
|
||
clearTimeout(ugj_fdTimeoutID);
|
||
ugj_fdRecentBox = null;
|
||
}
|
||
|
||
// 基本設定
|
||
let rtopX = 170; // フキダシ右上 X座標
|
||
let rtopY = 40; // フキダシ右上 Y座標
|
||
let boxWidth = 140;
|
||
let padding = 5;
|
||
let radius = 5;// 円弧の半径
|
||
|
||
// 吹き出しの背景色
|
||
context.fillStyle = "#b7e6ff";
|
||
|
||
// テキスト設定
|
||
let limitedWidth = boxWidth - (padding * 2);
|
||
let size = 14;
|
||
context.font = size + "px ''";
|
||
|
||
// テキスト調整 行に分解
|
||
let lineTextList = text.split("\n");
|
||
let newLineTextList = [];
|
||
lineTextList.forEach((lineText) => {
|
||
if (context.measureText(lineText).width > limitedWidth) {
|
||
let characterList = lineText.split("");// 1文字ずつ分割
|
||
let preLineText = "";
|
||
lineText = "";
|
||
characterList.forEach((character) => {
|
||
lineText += character;
|
||
if (context.measureText(lineText).width > limitedWidth) {
|
||
newLineTextList.push(preLineText);
|
||
lineText = character;
|
||
}
|
||
preLineText = lineText;
|
||
});
|
||
}
|
||
newLineTextList.push(lineText);
|
||
});
|
||
let lineLength = newLineTextList.length;
|
||
|
||
// 角丸
|
||
let width = boxWidth;// 枠の幅
|
||
let height = (size * lineLength) + (padding * 3); // 枠の高さ
|
||
let toRadianCoefficient = Math.PI / 180; // 角度からラジアンへの変換係数
|
||
// 角丸原点(左上座標)
|
||
let boxOrigin = {
|
||
"x": rtopX - width,
|
||
"y": rtopY,
|
||
}
|
||
// 円弧から円弧までの直線は自動で引かれます、角度は回り方によって変わります。
|
||
// arc(中心x, 中心y, 半径, 開始角度, 終了角度, 反時計回り)
|
||
context.beginPath();
|
||
context.arc(boxOrigin.x + radius, boxOrigin.y + radius, radius, 180 * toRadianCoefficient, 270 * toRadianCoefficient, false);// 左上
|
||
context.arc(boxOrigin.x + width - radius, boxOrigin.y + radius, radius, 270 * toRadianCoefficient, 0, false);// 右上
|
||
context.arc(boxOrigin.x + width - radius, boxOrigin.y + height - radius, radius, 0, 90 * toRadianCoefficient, false);// 右下
|
||
context.arc(boxOrigin.x + radius, boxOrigin.y + height - radius, radius, 90 * toRadianCoefficient, 180 * toRadianCoefficient, false);// 左下
|
||
context.closePath();
|
||
context.fill();
|
||
|
||
// 矢印(ヒゲ)
|
||
let arrow = {
|
||
"x": rtopX - width / 2 + 40,
|
||
"y": rtopY + height + 10,
|
||
"width": 10,
|
||
"height": 10,
|
||
}
|
||
context.beginPath();
|
||
context.moveTo(arrow.x, arrow.y);
|
||
context.lineTo(arrow.x, arrow.y - arrow.height);
|
||
context.lineTo(arrow.x - arrow.width, arrow.y - arrow.height);
|
||
context.fill();
|
||
|
||
// テキスト描画
|
||
context.fillStyle = "#000000";
|
||
newLineTextList.forEach((lineText, index) => {
|
||
context.fillText(lineText, boxOrigin.x + padding, boxOrigin.y + padding + (size * (index + 1)));
|
||
});
|
||
|
||
// 描画した吹き出しの位置情報を保存
|
||
ugj_fdRecentBox = { x: boxOrigin.x, y: boxOrigin.y, w: width, h: height + arrow.height };
|
||
// 指定時間後に消去(0以下で自動消去なし)
|
||
if (sec > 0) {
|
||
ugj_fdTimeoutID = setTimeout(() => {
|
||
clearFd(boxOrigin.x, boxOrigin.y, width, height + arrow.height);
|
||
}, sec * 1000);
|
||
}
|
||
|
||
// return [boxOrigin.x, boxOrigin.y, width, height+arrow.height];
|
||
// https://qiita.com/horikeso/items/95595f379a8dfa63c34a
|
||
}
|
||
|
||
// コードジェネレータモード(Javascript/Python切り替え)
|
||
var isPyMode;
|
||
const ugj_pyMode = (checked) => isPyMode = checked;
|
||
|
||
|
||
//=====================================
|
||
//======= Blockly GUI codes ===========
|
||
// Use in a block or block definition:
|
||
|
||
// Resizable workspace injection script
|
||
var blocklyArea = document.getElementById('blocklyArea');
|
||
var blocklyDiv = document.getElementById('blocklyDiv');
|
||
// var workspace = Blockly.inject(blocklyDiv,
|
||
// {toolbox: document.getElementById('toolbox')});
|
||
var workspace = Blockly.inject(blocklyDiv,
|
||
{
|
||
toolbox: document.getElementById('toolbox'),
|
||
theme: theme,
|
||
renderer: 'thrasos',
|
||
|
||
scrollbars: true,
|
||
grid: {
|
||
spacing: 20,
|
||
length: 1,
|
||
colour: '#888',//888
|
||
snap: true
|
||
},
|
||
zoom: { startScale: 1.0, controls: true },
|
||
trashcan: true,
|
||
media: './node_modules/blockly/media/'
|
||
});
|
||
var onresize = function (e) {
|
||
// Compute the absolute coordinates and dimensions of blocklyArea.
|
||
var element = blocklyArea;
|
||
var x = 0;
|
||
var y = 0;
|
||
do {
|
||
x += element.offsetLeft;
|
||
y += element.offsetTop;
|
||
element = element.offsetParent;
|
||
} while (element);
|
||
// Position blocklyDiv over blocklyArea.
|
||
blocklyDiv.style.left = x + 'px';
|
||
blocklyDiv.style.top = y + 'px';
|
||
blocklyDiv.style.width = blocklyArea.offsetWidth + 'px';
|
||
blocklyDiv.style.height = blocklyArea.offsetHeight + 'px';
|
||
Blockly.svgResize(workspace);
|
||
};
|
||
window.addEventListener('resize', onresize, false);
|
||
onresize();
|
||
Blockly.svgResize(workspace);
|
||
//=====================================
|
||
//=====================================
|
||
|
||
|
||
// ワークスペースの未保存の変更のフラグ
|
||
const ugj_wsUpdateCB = event => {
|
||
if (event.type != Blockly.Events.UI) {
|
||
apptool.setWsChanged(true);
|
||
}
|
||
}
|
||
|
||
// ウィンドウロード・アンロード時
|
||
window.onload = () => {
|
||
// var menu = document.getElementById('conmenu'); //独自コンテキストメニュー
|
||
// var area = document.getElementById('dlgContent'); //対象エリア
|
||
// var body = document.body; //bodyエリア
|
||
// body.oncontextmenu = () => false;
|
||
// //右クリック時に独自コンテキストメニューを表示する
|
||
// area.addEventListener('contextmenu', function (e) {
|
||
// menu.style.left = e.pageX + 'px';
|
||
// menu.style.top = e.pageY + 'px';
|
||
// menu.classList.add('on');
|
||
// });
|
||
// //左クリック時に独自コンテキストメニューを非表示にする
|
||
// body.addEventListener('click', function () {
|
||
// if (menu.classList.contains('on')) {
|
||
// menu.classList.remove('on');
|
||
// }
|
||
// });
|
||
|
||
// ワークスペースといくつかの環境のオートリストア
|
||
ugj_loadWorkspace();
|
||
apptool.loadPrefsFromLS();
|
||
setTimeout(() => { // 環境設定のロードが終わってからイベントリスナを作成
|
||
workspace.addChangeListener(ugj_wsUpdateCB);
|
||
}, 100);
|
||
// 背景canvas
|
||
ugj_canvasBgImg(apptool.getMascotFilePath(), -1, -1);
|
||
}
|
||
window.addEventListener('beforeunload', () => {
|
||
// window.onbeforeunload = () => {
|
||
ugj_saveWorkspace();
|
||
apptool.savePrefsToLS();
|
||
apptool.cleanupGPIO();
|
||
// }
|
||
});
|