diff --git a/index.js b/index.js index 2b7bd06..0ba7414 100644 --- a/index.js +++ b/index.js @@ -313,7 +313,8 @@ const ugj_loadWorkspace = () => { // Python コードフォーマッタ Black をコール const ugj_pyBeautify = (code) => { let formatted; - formatted = window.ocogeapi.child_process.spawnSync('python3', ['-m', 'black', '-'], { input: code }).stdout.toString(); + // 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; } diff --git a/index_elutil.js b/index_elutil.js index a84c277..297c8f5 100644 --- a/index_elutil.js +++ b/index_elutil.js @@ -18,10 +18,10 @@ const ugj_const = { // Electron 動作用 class elUtil { constructor() { - this.path = window.ocogeapi.path - this.fs = window.ocogeapi.fs - this.ipcRenderer = window.ocogeapi.electron_ipcRenderer - this.shell = window.ocogeapi.electron_shell + this.path = require('path') //window.ocogeapi.path + this.fs = require('fs') //window.ocogeapi.fs + this.ipcRenderer = require('electron').ipcRenderer //window.ocogeapi.electron_ipcRenderer + this.shell = require('electron').shell //window.ocogeapi.electron_shell this.saveFilepath = null; this.wsChanged = false; this.mascotFilePath = this.path.join(ugj_const.mascot_path, ugj_const.mascot_defname); @@ -271,7 +271,8 @@ class brUtil { } // Electron 動作 / ブラウザ動作自動判別 -const is_el = (typeof window.ocogeapi !== 'undefined') +// const is_el = (typeof window.ocogeapi !== 'undefined') +const is_el = (typeof require === 'function') // utilクラスのインスタンスを返す const elUtil_new = () => { @@ -279,23 +280,9 @@ const elUtil_new = () => { else return new brUtil } -// "require" for "BLOCK"s -// ブラウザ動作時にはすべてアラートを表示 -const require = module_name => { - if (is_el) { - switch (module_name) { - case '@ocogeclub/lgpio': - return window.ocogeapi.lgpio; - case '@ocogeclub/bme280': - return window.ocogeapi.bme280; - case 'fs': - return window.ocogeapi.fs; - case 'path': - return window.ocogeapi.path; - default: - throw new Error(`Unknown module "${module_name}" required.\nStopped.`); - } - } else { +// "require" for web browsers if contextIsolation is false && nodeIntegration is true: +if (!is_el) { + var require = module_name => { let block; switch (module_name) { case '@ocogeclub/lgpio': @@ -313,3 +300,43 @@ const require = module_name => { throw `ブロック「${block}」は、Web体験版ではご利用になれません。\n詳しくは https://ocoge.club/ をご覧ください。`; } } + + +// "require" for "BLOCK"s if contextIsolation is true +// ブラウザ動作時にはすべてアラートを表示 +// const require = module_name => { +// if (is_el) { +// switch (module_name) { +// case '@ocogeclub/lgpio': +// return window.ocogeapi.lgpio; +// case '@ocogeclub/bme280': +// return window.ocogeapi.bme280; +// case 'fs': +// return window.ocogeapi.fs; +// case 'path': +// return window.ocogeapi.path; +// case '@tensorflow/tfjs-node': +// return window.ocogeapi.tfjs_node; +// case '@vladmandic/face-api/dist/face-api.node.js': +// return window.ocogeapi.face_api; +// default: +// throw new Error(`Unknown module "${module_name}" required.\nStopped.`); +// } +// } else { +// let block; +// switch (module_name) { +// case '@ocogeclub/lgpio': +// block = 'GPIO'; +// break; +// case '@ocogeclub/bme280': +// block = 'BME280'; +// break; +// case 'fs': +// block = 'ファイル'; +// break; +// default: +// throw new Error(ugj_const.error_ja_all); +// } +// throw `ブロック「${block}」は、Web体験版ではご利用になれません。\n詳しくは https://ocoge.club/ をご覧ください。`; +// } +// } diff --git a/main.js b/main.js index c61c4e3..b6d9ba7 100644 --- a/main.js +++ b/main.js @@ -16,9 +16,9 @@ function createWindow() { width: 1280, height: 940, webPreferences: { - preload: path.join(__dirname, 'preload.js') - // nodeIntegration: true, - // contextIsolation: false + // preload: path.join(__dirname, 'preload.js'), + nodeIntegration: true, + contextIsolation: false } }) /** Maximize Window at startup */ diff --git a/preload.js b/preload.js index ea5a702..43077cc 100644 --- a/preload.js +++ b/preload.js @@ -1,6 +1,6 @@ const electron = require("electron") const path = require('path') - +// const tf = require('@tensorflow/tfjs-node') electron.contextBridge.exposeInMainWorld( "ocogeapi", { @@ -11,6 +11,9 @@ electron.contextBridge.exposeInMainWorld( fs: require('fs'), path: path, child_process: require('child_process'), + tfjs_node: require('@tensorflow/tfjs-node'), + // tfjs_node: tf, + // face_api: require('@vladmandic/face-api/dist/face-api.node.js'), lgpio: require('@ocogeclub/lgpio'), bme280: require('@ocogeclub/bme280'), // グローバル変数 diff --git a/ugj_script.js b/ugj_script.js deleted file mode 100644 index fc3f5e4..0000000 --- a/ugj_script.js +++ /dev/null @@ -1,673 +0,0 @@ -const testfunc = () => { - elec.openURL('http://ocoge.club'); -} - -//============ User Customize Start =============== -// カスタムブロックカラー定義 -Blockly.HSV_SATURATION = 0.55; -Blockly.HSV_VALUE = 0.75; -var gpio_color = '0'; -var multimedia_color = '240'; -var network_color = '340'; -var special_color = '20'; -var snippets_color = '90'; -// テーマ -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 - }, - 'multimedia_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 - }, - "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["GPIOCHIP_OPEN_TITLE"] = "GPIO を使えるようにする"; -Blockly.Msg["GPIOCHIP_OPEN_TOOLTIP"] = "GPIOを初期化して接続します。"; -Blockly.Msg["GPIOCHIP_CLOSE_TITLE"] = "GPIO の後片付けをする"; -Blockly.Msg["GPIOCHIP_CLOSE_TOOLTIP"] = "GPIOとの接続を終了します。"; -Blockly.Msg["GPIOCHIP_CLOSE_TITLE"] = "GPIO の後片付けをする"; -Blockly.Msg["GPIO_CLAIM_INPUT_TITLE"] = "GPIO %1 を入力モードにして %2"; -Blockly.Msg["GPIO_CLAIM_INPUT_TOOLTIP"] = "GPIO端子を入力モードにして、プルアップ・プルダウン・無しを設定します。"; -Blockly.Msg["GPIO_CLAIM_OUTPUT_TITLE"] = "GPIO %1 を出力モードにする"; -Blockly.Msg["GPIO_CLAIM_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["TX_SERVO_TITLE"] = "GPIO %1 のサーボモータの回転を %2 にする"; -Blockly.Msg["TX_SERVO_TOOLTIP"] = "サーボモータの回転をパルス幅(1000~2000μsec)までの数値で指定します。"; -Blockly.Msg["TX_PWM_TITLE"] = "PWM : GPIO %1 に、パルス周波数 %2 Hz, デューティー比 %3 %%で出力"; -Blockly.Msg["TX_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 を速度 %2 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_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_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_FACEAPI_TITLE"] = "TensorFlowによる顔検出: %1 ランドマークを検出 %2 %3"; -Blockly.Msg["UGJ_FACEAPI_TOOLTIP"] = "TensorFlow とFaceAPI をロードし、顔検出をできるようにします。"; - -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部品のインスタンス - 画面上の必要な部品はすべてここで取得しておく -ugjel_displayArea = document.getElementById('display_area'); // ディスプレイ部 -ugjel_blackboard = document.getElementById('blackboard'); // 黒板 -ugjel_inputForm = document.getElementById('inputForm'); // 入力フォーム -ugjel_inputBox = document.getElementById('inputBox'); // 入力フィールド - -// その他のプロパティ -ugj_inputEvLstnrID = 0; // 入力フォームの動的イベントリスナの最新のID -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 = () => { - let fname = elec.selectMascotFile(); - if (fname) { - ugj_canvasBgImg(fname, -1, -1); - elec.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(); - elec.newFile(); - } - }); -} - -// ワークスペースをファイルに保存・読込 -const ugj_saveWorkspaceToFile = () => { - let xml = Blockly.Xml.workspaceToDom(workspace); - let xml_text = Blockly.Xml.domToText(xml); - if (elec.saveWsFile(xml_text) === false) { - alert('保存できませんでした。'); - } -} -const ugj_loadWorkspaceFromFile = () => { - let xml_text = elec.loadWsFile(); - if (xml_text.length > 0) { - let xml = Blockly.Xml.textToDom(xml_text); - Blockly.Xml.domToWorkspace(xml, workspace); - } -} - -// ワークスペースを別名で保存 -const ugj_saveWorkspaceAs = () => { - elec.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 = require('child_process').spawnSync('python3', ['-m', 'yapf'], { input: code }).stdout.toString(); -// return formatted; -// } -// Python コードフォーマッタ Black をコール -const ugj_pyBeautify = (code) => { - let formatted; - formatted = require('child_process').spawnSync('python3', ['-m', 'black', '-'], { input: code }).stdout.toString(); - return formatted; -} - -// ワークスペースからコードを生成して必要であれば整形処理をする -const ugj_createCode = (args) => { - addAsync = args.async || false; - beautify = args.beautify || false; - prettify = args.prettify || false; - ext = args.ext || 'js'; - - let code, requires; - 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 () => {', - code, - '})();' - ].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 = () => { - document.activeElement.blur(); //実行ボタンからフォーカスを外す:エンターキー押下が悪さをするため - let code = ugj_createCode({ 'async': true }); - try { - eval(code); - } catch (e) { - alert(e); - } -} - -// コードをダイアログで表示・保存 -// エレメントのオブジェクトとかコールバックとか 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 = () => { - code = ugj_createCode({ 'ext': ext, 'async': true, 'beautify': true }); - // blackboardWrite()とwindow.alert()、fukidashi()をconsole.log()に書き換え、 - // document... と ugj_... と elec... をコメントアウト(ブラウザ関連部分の追放という意味では不完全なので注意) - // あと正規表現もいい加減 - 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(/(^(?=.*elec.)[^;]*;)/gm, '/* $1 */'); - if (elec.saveFile(code, ext) === false) { - 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(); - } - }); -} - - -// フキダシ -ugj_fdTimeoutID = null; -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) { - 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) { - elec.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(); - elec.loadPrefsFromLS(); - setTimeout(() => { // 環境設定のロードが終わってからイベントリスナを作成 - workspace.addChangeListener(ugj_wsUpdateCB); - }, 100); - // 背景canvas - ugj_canvasBgImg(elec.getMascotFilePath(), -1, -1); -} -window.onbeforeunload = () => { - ugj_saveWorkspace(); - elec.savePrefsToLS(); - elec.killAllChildren(); -} - -// Electron動作とブラウザ動作を自動で仕分け -if (typeof require == 'function') { - // requireが使える = Electron - var elec = require('./ugj_eleclib'); - console.log('elec loaded.'); -} else { - //ブラウザ動作 - //requireのダミー - var require = (e) => { - alert(`この機能またはブロック [${e}] は web 版ではご利用になれません。\n詳しくはお問い合わせください。`); - // if (e=='elec' || e=='ocoge' || e=='child_process' || e=='@google-cloud/speech' || e=='nodemailer' || e=='fs' ) - // alert(`この機能またはブロックは web 版ではご利用になれません。\n詳しくはお問い合わせください。`); - // else return; - } - //elecを名前空間で置き換え - var elec = {}; - elec.saveFile = () => require('elec'); - elec.savePrefsToLS = () => { ; } - elec.loadPrefsFromLS = () => { ; } - elec.newFile = () => { ; } - elec.setWsChanged = () => { ; } - - // マスコット - elec.getMascotFilePath = () => './img/tamachee.png'; - - //ワークスペースのダウンロード - elec.saveWsFile = xml_text => { - let blob = new Blob([xml_text], { "type": "text/xml" }); - const downLoadLink = document.createElement("a"); - document.body.appendChild(downLoadLink); - downLoadLink.download = 'workspace.xml'; - downLoadLink.href = URL.createObjectURL(blob); - downLoadLink.click(); - downLoadLink.parentElement.removeChild(downLoadLink); - return true; - } - //ワークスペースのインポート - elec.loadWsFile = () => { - const fileInputEl = document.createElement('input'); - document.body.appendChild(fileInputEl); - fileInputEl.type = 'file'; - fileInputEl.addEventListener('change', ev => { - let reader = new FileReader(); - reader.readAsText(ev.target.files[0]); - reader.addEventListener('load', () => { - let xml = Blockly.Xml.textToDom(reader.result); - Blockly.Xml.domToWorkspace(xml, workspace); - }); - }); - fileInputEl.click(); - fileInputEl.parentElement.removeChild(fileInputEl); - return ''; - } -} \ No newline at end of file