'use strict'; let elutil = elUtil_new(); const testfunc = async () => { elutil.openURL('http://ocoge.club'); // console.log(elutil.app_path); } //============ User Customize Start =============== // カスタムブロックカラー定義 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 snippets_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 }, 'snippets_blocks': { "colourPrimary": snippets_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 }, "snippets_category": { "colour": snippets_color } }, }); // Customize messages Blockly.Msg["CONTROLS_IF_MSG_THEN"] = "ならば"; Blockly.Msg["CONTROLS_REPEAT_INPUT_DO"] = ""; 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["UGJ_DRAW_GRIDEYEDATA_TITLE"] = "赤外線アレイセンサ画像表示 %1 温度データ %2 温度範囲上限 %3 %4 温度範囲下限 %5 %6"; Blockly.Msg["UGJ_DRAW_GRIDEYEDATA_TOOLTIP"] = "AMG8833の温度データを、画像としてキャンバスに描画します。「着色」をチェックすると、温度範囲で設定されている色をつけて表示します。"; Blockly.Msg["GPIO_OPEN_TITLE"] = "GPIO を使えるようにする"; Blockly.Msg["GPIO_OPEN_TOOLTIP"] = "GPIOを初期化して接続します。"; Blockly.Msg["GPIO_CLOSE_TITLE"] = "GPIO の後片付けをする"; Blockly.Msg["GPIO_CLOSE_TOOLTIP"] = "GPIOとの接続を終了します。"; Blockly.Msg["GPIO_CLOSE_TITLE"] = "GPIO の後片付けをする"; 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"] = "PWM : GPIO %1 に、パルス周波数 %2 Hz, デューティー比 %3 %%で出力"; Blockly.Msg["PWM_TOOLTIP"] = "パルス周波数をセットして、GPIO端子がPWM出力できるようにします。レンジは100固定です。"; Blockly.Msg["I2C_OPEN_TITLE"] = "アドレス %1 の I2C デバイスを開く"; Blockly.Msg["I2C_OPEN_TOOLTIP"] = "I2C接続されたデバイスとの通信を開始します。一度にオープンできるI2Cデバイスはひとつだけです。"; 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_CLOSE_TITLE"] = "I2C デバイスを閉じる"; Blockly.Msg["I2C_CLOSE_TOOLTIP"] = "I2C接続されたデバイスと通信を切断します。"; 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_BYTE_DATA_TITLE"] = "レジスタ %1 に %2 を書き込む"; Blockly.Msg["I2C_WRITE_BYTE_DATA_TOOLTIP"] = "デバイスの指定されたレジスタに1バイトを書き込みます。"; Blockly.Msg["I2C_READ_BYTE_DATA_TITLE"] = "レジスタ %1 の値"; Blockly.Msg["I2C_READ_BYTE_DATA_TOOLTIP"] = "デバイスの指定されたレジスタから1バイトを読み込みます。"; Blockly.Msg["I2C_WRITE_I2C_BLOCK_DATA_TITLE"] = "レジスタ %1 に %2 を書き込む"; Blockly.Msg["I2C_WRITE_I2C_BLOCK_DATA_TOOLTIP"] = "デバイスの指定されたレジスタに最大32バイトのテキストデータを書き込みます。"; Blockly.Msg["I2C_READ_DEVICE_TITLE"] = "i2cデバイスから %1 バイト受け取る"; Blockly.Msg["I2C_READ_DEVICE_TOOLTIP"] = "デバイスから指定したバイト数のデータを受け取ります。データが指定の長さより短いこともあります。"; Blockly.Msg["I2C_WRITE_DEVICE_TITLE"] = "i2c デバイスに %1 を送信"; Blockly.Msg["I2C_WRITE_DEVICE_TOOLTIP"] = "i2c デバイスにデータを送信します。"; Blockly.Msg["UGJ_BME280_TITLE"] = "BME280(アドレス %1 )から %2 %3 %4 を取得"; Blockly.Msg["UGJ_BME280_TOOLTIP"] = "環境センサーBME280で、気温(摂氏)、湿度(%)、気圧(hPa)を計測し、それぞれを変数に代入します。"; Blockly.Msg["UGJ_BME280_READ_TEMP"] = "気温"; Blockly.Msg["UGJ_BME280_READ_HUM"] = "湿度"; Blockly.Msg["UGJ_BME280_READ_PRES"] = "気圧"; Blockly.Msg["UGJ_GESTURE_INIT_TITLE"] = "ジェスチャーセンサー(アドレス: %1 )を初期化"; Blockly.Msg["UGJ_GESTURE_INIT_TOOLTIP"] = "PAJ7620 ジェスチャーセンサーを使用する準備をします。"; Blockly.Msg["UGJ_GESTURE_READ_TITLE"] = "ジェスチャーの値"; Blockly.Msg["UGJ_GESTURE_READ_TOOLTIP"] = "センサーから現在のジェスチャーの値(0〜9)を読み込みます"; Blockly.Msg["UGJ_GESTURE_STOP_TITLE"] = "ジェスチャーセンサーから切断"; Blockly.Msg["UGJ_GESTURE_STOP_TOOLTIP"] = "センサーとの接続を停止します。"; Blockly.Msg["UGJ_GRIDEYE_INIT_TITLE"] = "赤外線アレイセンサ(アドレス: %1 )を初期化"; Blockly.Msg["UGJ_GRIDEYE_INIT_TOOLTIP"] = "赤外線アレイセンサ AMG8833 の使用準備をします。"; Blockly.Msg["UGJ_GRIDEYE_THERMISTOR_TITLE"] = "赤外線アレイセンサ本体温度"; Blockly.Msg["UGJ_GRIDEYE_THERMISTOR_TOOLTIP"] = "AMG8833に内蔵されたサーミスタ(温度センサ)の値を取得します。"; Blockly.Msg["UGJ_GRIDEYE_READ_TITLE"] = "赤外線アレイセンサの値"; Blockly.Msg["UGJ_GRIDEYE_READ_TOOLTIP"] = "AMG8833から読み取った温度データを、8x8の配列で取得します。"; Blockly.Msg["UGJ_GRIDEYE_STOP_TITLE"] = "赤外線アレイセンサから切断"; Blockly.Msg["UGJ_GRIDEYE_STOP_TOOLTIP"] = "センサーとの接続を停止します。"; Blockly.Msg["UGJ_GRIDEYE_CANVAS_CREATE_TITLE"] = "赤外線アレイセンサデータ表示キャンバスを作成"; Blockly.Msg["UGJ_GRIDEYE_CANVAS_CREATE_TOOLTIP"] = "ディスプレイエリアにAMG8833データ表示用キャンバスを作成します。"; Blockly.Msg["UGJ_TEACHABLE_MACHINE_TITLE"] = "TensorFlow.jsによる画像分類器の準備"; Blockly.Msg["UGJ_TEACHABLE_MACHINE_TOOLTIP"] = "TensorFlow.jsにMobileNet, KNN Classifierを読み込んで、画像認識(分類)を行う準備をします。"; Blockly.Msg["UGJ_GRIDEYE_PREDICT_CLASS_TITLE"] = "赤外線アレイセンサの画像で推論を行う"; Blockly.Msg["UGJ_GRIDEYE_PREDICT_CLASS_TOOLTIP"] = "キャンバスに表示されたAMG8833の画像を元に画像分類の推論を行います。推論の結果として定義済みのラベルを返します。"; Blockly.Msg["UGJ_GRIDEYE_ADD_EXAMPLE_TITLE"] = "赤外線アレイセンサの画像にラベル %1 をつけてデータセットへ追加"; Blockly.Msg["UGJ_GRIDEYE_ADD_EXAMPLE_TOOLTIP"] = "キャンバスに表示されているAMG8833の画像にラベル(クラス名)をつけてデータセットへ追加します。"; Blockly.Msg["UGJ_TENSORSET_STRINGIFY_TITLE"] = "学習したクラスデータセットを文字列に変換"; Blockly.Msg["UGJ_TENSORSET_STRINGIFY_TOOLTIP"] = "学習したクラスデータセットを文字列に変換して保存します。"; Blockly.Msg["UGJ_TENSORSET_PARSE_TITLE"] = "クラスデータ文字列 %1 を画像分類器にセット"; Blockly.Msg["UGJ_TENSORSET_PARSE_TOOLTIP"] = "JSONテキストをパースして画像分類器に戻します。"; 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); //============ 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 elutil.selectMascotFile(); if (fname) { ugj_canvasBgImg(fname, -1, -1); elutil.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, ' '); // 新規ワークスペース const ugj_newWorkspace = () => { ugj_confirm('新規ワークスペース', '保存していない内容はすべて破棄されます。よろしいですか?', okey => { if (okey) { workspace.clear(); elutil.newFile(); } }); } // ワークスペースをファイルに保存・読込 const ugj_saveWorkspaceToFile = async () => { let xml = Blockly.Xml.workspaceToDom(workspace); let xml_text = Blockly.Xml.domToText(xml); if (await elutil.saveWsFile(xml_text) === false) { window.alert('保存できませんでした。'); } } const ugj_loadWorkspaceFromFile = async () => { let xml_text = await elutil.loadWsFile(); if (xml_text.length > 0) { let xml = Blockly.Xml.textToDom(xml_text); Blockly.Xml.domToWorkspace(xml, workspace); } } // ワークスペースを別名で保存 const ugj_saveWorkspaceAs = () => { elutil.newFile(); ugj_saveWorkspaceToFile(); } // ワークスペースをローカルストレージに保存・読込 const ugj_saveWorkspace = () => { // Workspace let xml = Blockly.Xml.workspaceToDom(workspace); let xml_text = Blockly.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.Xml.textToDom(xml_text); Blockly.Xml.domToWorkspace(xml, workspace); } } } // // Python コードフォーマッタ YAPF をコール // const ugj_pyBeautify = (code) => { // let formatted; // formatted = window.ocogeapi.child_process.spawnSync('python3', ['-m', 'yapf'], { input: code }).stdout.toString(); // return formatted; // } // Python コードフォーマッタ Black をコール const ugj_pyBeautify = (code) => { let formatted = ''; // // formatted = window.ocogeapi.child_process.spawnSync('python3', ['-m', 'black', '-'], { input: code }).stdout.toString(); // formatted = require('child_process').spawnSync('python3', ['-m', 'black', '-'], { input: code }).stdout.toString(); return formatted; } // ワークスペースからコードを生成して必要であれば整形処理をする const ugj_createCode = (args) => { const addAsync = args.async || false; const beautify = args.beautify || false; const prettify = args.prettify || false; const ext = args.ext || 'js'; let code; if (ext == 'py') { // Python コード出力 try { code = Blockly.Python.workspaceToCode(workspace); } catch (e) { // Pythonコードを持たないブロックがある場合 window.alert('Python 非対応のブロックが使用されています。\n' + e.message); } code = Blockly.Python.workspaceToCode(workspace); } else { // Javascript コード出力 code = Blockly.JavaScript.workspaceToCode(workspace); } if (ext == 'py') { //Python // コードを綺麗に if (beautify) code = ugj_pyBeautify(code); } else { // JavaScript // await使用のため、必要に応じてコード全体をasync付き即時関数でラップ if (addAsync) { code = [ // '(async () => {', 'const main = async () => {', code, `}`, `main();`, // '})();' ].join('\n'); } // コードを綺麗に if (beautify) code = js_beautify(code, { indent_size: 2 }); } // シンタックスハイライト(HTML化): 先に HTML エンティティをエスケープ if (prettify) code = PR.prettyPrintOne(ugj_htmlEntities(code), ext, true); return code; } // ブロックスクリプト実行 const ugj_runCode = async () => { document.activeElement.blur(); //実行ボタンからフォーカスを外す:エンターキー押下が悪さをするため // let code = ugj_createCode({ 'async': true }); // await eval(code).catch(e => { alert(e); }); let AsyncFunction = Object.getPrototypeOf(async function () { }).constructor let ocogeFunc = new AsyncFunction(ugj_createCode({})); await ocogeFunc().catch(e => { window.alert(e); }); console.log('Code Execution done.'); } // コードをダイアログで表示・保存 // エレメントのオブジェクトとかコールバックとか Python対応とか // 色々この中で完結させてみる const ugj_showCode = () => { var ext = 'js'; const dialog = document.getElementById('codeDlg'); const content = document.getElementById('dlgContent'); const btn_close = document.getElementById('dlgClose'); const btn_export = document.getElementById('dlgExport'); const chkbox_cli = document.getElementById('dlgCli'); const dialog_title = document.getElementById('dlgTitle'); const showCode = () => content.innerHTML = ugj_createCode({ 'beautify': true, 'ext': ext, 'prettify': true }); showCode(); dialog.showModal(); const close_cb = () => { dialog.close(); btn_close.removeEventListener('click', close_cb); btn_export.removeEventListener('click', export_cb); } const export_cb = () => { let code = ugj_createCode({ 'ext': ext, 'async': true, 'beautify': true }); // blackboardWrite()とwindow.alert()、fukidashi()をconsole.log()に書き換え、 // document... と ugj_... と elutil... をコメントアウト(ブラウザ関連部分の追放という意味では不完全なので注意) // あと正規表現もいい加減 if (chkbox_cli.checked && ext == 'js') code = code.replace(/const appendDiv[^#]*\/\/#/gm, 'const blackboardWrite = text => console.log(text);').replace('window.alert', 'console.log').replace(/ugj_fukidashi(.*), \d+(\);)/gm, 'console.log$1$2').replace(/(^(?=.*document.)[^;]*;)/gm, '/* $1 */').replace(/(^(?=.*ugj_)[^;]*;)/gm, '/* $1 */').replace(/(^(?=.*elutil.)[^;]*;)/gm, '/* $1 */'); if (elutil.saveFile(code, ext) === false) { window.alert('保存できませんでした。'); } close_cb(); } btn_close.addEventListener('click', close_cb); btn_export.addEventListener('click', export_cb); // document.addEventListener('keypress', (ev) => { // if (ev.key == 'p' && ext == 'js') { // ext = 'py'; // showCode(); // } // }); // document.addEventListener('keyup', (ev) => { // if (ev.key == 'p') { // ext = 'js'; // showCode(); // } // }); } // フキダシ let ugj_fdTimeoutID = null; let ugj_fdRecentBox = null; const ugj_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: './google-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) { elutil.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(); elutil.loadPrefsFromLS(); setTimeout(() => { // 環境設定のロードが終わってからイベントリスナを作成 workspace.addChangeListener(ugj_wsUpdateCB); }, 100); // 背景canvas ugj_canvasBgImg(elutil.getMascotFilePath(), -1, -1); } window.onbeforeunload = () => { ugj_saveWorkspace(); elutil.savePrefsToLS(); elutil.killAllChildren(); elutil.cleanupGPIO(); }