ocoge/apptool.js

434 lines
16 KiB
JavaScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

/** Electron / Node.js に由来する機能に関する諸々 */
/** ブラウザ動作時にもちょびっと対応 */
'use strict'
// 定数
const ugj_const = {
app_name: 'ocoge',
mascot_dirname: 'img',
mascot_defname: 'tamachee.png',
library_dirname: 'lib',
document_root: 'Documents',
tmp_dir: '.ocogeclub/tmp', //ホームディレクトリからのパス
executable_path: 'ocogeclub/bin/', //ホームディレクトリからのパス
localStorage_fname: 'ocoge.json',
error_ja_all: 'エラーが発生しました。\n『おこげ倶楽部』までお問い合わせください。',
pig: 'pigpio',
rg: '@ocoge/rgpio',
i2c_defbus: '1', // 文字列リテラルで指定
lang: 'js',
dev_hash: '4e9205f9b7e571bec1aa52ab7871f420684fcf96149672a4d550a95863d6b072'
}
/** クラス appTool ****************************************************************** */
// Electron 動作用
class appTool {
constructor() {
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.children = [];
this.gpio_lib = ugj_const.rg;
this.i2c_bus = ugj_const.i2c_defbus;
this.lang = ugj_const.lang;
this.doc_root = this.path.join(process.env["HOME"], ugj_const.document_root);
this.doc_current = this.path.join(process.env["HOME"], ugj_const.document_root);
this.executable_path = this.path.join(process.env["HOME"], ugj_const.executable_path);
this.tmp_dir = this.path.join(process.env["HOME"], ugj_const.tmp_dir);
const EventEmitter = require('events');
this.ugjEmitter = new EventEmitter();
}
// static init = async () => {
// return new elUtil(await elUtil.get_app_path());
// }
// test
async init() {
this.app_path = await this.ipcRenderer.invoke('get_app_path');
this.mascotFilePath = this.path.join(this.app_path, ugj_const.mascot_dirname, ugj_const.mascot_defname);
this.library_path = this.path.join(this.app_path, ugj_const.library_dirname);
// this.blocks_dir = this.path.join(this.app_path, ugj_const.blocks_dir);
}
// 0で数値の桁合わせ : NUM=値 LEN=桁数
zeroPadding(NUM, LEN) {
return (Array(LEN).join('0') + NUM).slice(-LEN);
}
// 現在の日付時刻から workspace フォルダ内のユニークなファイルパスを作成
getUniqueFilepath() {
let today = new Date();
let filename = today.getFullYear() + '-' + this.zeroPadding((today.getMonth() + 1), 2) + '-' + this.zeroPadding(today.getDate(), 2) + '-' + this.zeroPadding(today.getHours(), 2) + '-' + this.zeroPadding(today.getMinutes(), 2) + '-' + this.zeroPadding(today.getSeconds(), 2);
let filepath = this.path.join(this.doc_current, filename);
return filepath;
}
// リンクを外部ブラウザで開く
openURL(url) {
this.shell.openExternal(url);
}
// saveFilepath を更新
// ウィンドウタイトルバーテキストを変更
setSaveFilepath(filepath) {
this.saveFilepath = filepath;
this.setWsChanged(false);
}
// ワークスペースが変更された・保存された
// ウィンドウタイトルバーテキストを変更
setWsChanged(changed) {
let title;
this.wsChanged = changed;
if (this.saveFilepath) title = this.saveFilepath + ' - ' + ugj_const.app_name;
else title = ugj_const.app_name;
if (changed) title = '*' + title;
this.ipcRenderer.send('set_title', title);
}
// 保存ファイルプロパティを更新
newFile() { this.setSaveFilepath(null); }
// ワークスペースファイル読み込みの一連の動作のラッパ
async loadWsFile() {
let filepath = await this.openFile('xml', this.doc_current);
if (filepath.length > 0) {
if (this.saveFilepath === null) {
this.setSaveFilepath(filepath);
} //読み込みに失敗してもsaveFilepathが更新されてしまうのはちょっと具合が悪いかも
this.doc_current = this.path.dirname(filepath);
return this.readFromFile(filepath);
} else {
return '';
}
}
// その他ファイル読み込みの一連の動作のラッパ
async loadFile(ext) {
let filepath = await this.openFile(ext, this.doc_current);
if (filepath.length > 0) {
this.doc_current = this.path.dirname(filepath);
return this.readFromFile(filepath);
} else {
return '';
}
}
async selectMascotFile() {
return await this.openFile('png', ugj_const.mascot_path);
}
// オープンファイルダイアログ
async openFile(ext, dpath) {
let title = 'Select a file';
let filter;
if (ext == 'xml') {
filter = { name: 'XML - Extensible Markup Language', extensions: ['xml'] };
} else if (ext == 'js') {
filter = { name: 'JS - JavaScript', extensions: ['js'] };
} else if (ext == 'png') {
filter = { name: 'PNG - Portable Network Graphics', extensions: ['png'] };
} else {
filter = { name: 'text file', extensions: ['txt'] };
}
let filepaths = await this.ipcRenderer.invoke('open_dialog', title, dpath, filter);
if (filepaths == undefined) {
return '';
} else {
return filepaths[0];
}
}
// ファイルからデータを読み込み
readFromFile(filepath) {
let data = '';
try {
data = this.fs.readFileSync(filepath, 'utf-8');
}
catch (err) {
console.log(err);
}
return data;
}
// テキストファイル読み込み: 外部スクリプト動的読み込みに使用
readTextFile(filepath) {
return this.readFromFile(filepath);
}
// ワークスペースファイル保存の一連の動作のラッパ
async saveWsFile(data) {
if (this.saveFilepath === null) {
let filepath = await this.selectSaveFile('xml');
if (filepath === undefined) { //キャンセル
return undefined;
} else {
this.setSaveFilepath(filepath);
} //これも保存が成功したら変更するようにすべきかしら
} else this.setWsChanged(false);
return this.writeToFile(this.saveFilepath, data);
}
// その他ファイル保存の一連の動作のラッパ
async saveFile(data, ext) {
let filepath = await this.selectSaveFile(ext);
if (filepath === undefined) { //キャンセル
return undefined;
}
return this.writeToFile(filepath, data);
}
// ファイル保存ダイアログ
async selectSaveFile(ext) {
let title = '保存先を決定してください';
let filter, filter_name;
let defName;
if (ext == 'xml') {
filter = { name: 'xml file', extensions: ['xml'] };
defName = this.getUniqueFilepath() + '.xml';
} else if (ext == 'js' || ext == 'py') {
if (ext == 'js') filter_name = 'javascript file';
else filter_name = 'python file'
filter = { name: filter_name, extensions: [ext] };
// ワークスペース保存名がある場合、それをベースにファイル名の候補を決める
if (this.saveFilepath === null) {
defName = this.getUniqueFilepath() + '.' + ext;
} else {
let dirname = this.path.dirname(this.saveFilepath);
let basename = this.path.basename(this.saveFilepath, '.xml');
defName = this.path.join(dirname, basename) + '.' + ext;
}
} else {
filter = { name: 'text file', extensions: ['txt'] };
}
let filename = await this.ipcRenderer.invoke('save_dialog', title, defName, filter);
if (filename) this.doc_current = this.path.dirname(filename);
return filename;
}
// ファイル書き込み
writeToFile(filepath, data) {
try {
this.fs.writeFileSync(filepath, data);
return true;
}
catch (err) {
return false;
}
}
// GPIO 関連リロードでGPIOをロックしたままハンドルを失うのを防ぐ
cleanupGPIO() {
// this.ugjEmitter.emit('device_stop');//デバイス停止イベント
require(this.gpio_lib).close_all_handle();
}
// 設定(保存ファイルパスと未保存フラグ)をローカルストレージに保存
savePrefsToLS() {
let wc = '0';
if (this.wsChanged) wc = '1';
// const ser_port = document.getElementById('dlgPort').value;
let o = {
'saveFilepath': this.saveFilepath,
'wsChanged': wc,
'mascotFilePath': this.mascotFilePath,
'doc_current': this.doc_current,
'i2c_bus': this.i2c_bus,
'lang': this.lang,
};
let s = JSON.stringify(o);
localStorage.setItem(ugj_const.localStorage_fname, s);
}
// 設定(保存ファイルパスと未保存フラグ)をローカルストレージからロード
loadPrefsFromLS() {
let s = localStorage.getItem(ugj_const.localStorage_fname);
if (s !== null) {
let o = JSON.parse(s);
this.setSaveFilepath(o.saveFilepath);
if (o.wsChanged == '0') this.setWsChanged(false);
else this.setWsChanged(true);
if (o.mascotFilePath) this.setMascotFilePath(o.mascotFilePath);
if (o.doc_current) this.doc_current = o.doc_current;
if (o.i2c_bus) {
this.i2c_bus = o.i2c_bus;
this.ipcRenderer.send('i2c_check_menu', 'i2c-' + this.i2c_bus);
}
if (o.lang) {
// let l;
// if (o.lang == 'js' || o.gpio_lib) {
// l = o.lang + '-' + o.gpio_lib;
// } else {
// l = o.lang;
// }
this.setLang(o.lang);
console.log(`lang= ${this.lang}`);
this.ipcRenderer.send('lang_check_menu', this.lang);
}
}
}
// マスコット画像パスをプロパティにセット
setMascotFilePath(fpath) {
this.mascotFilePath = fpath;
}
getMascotFilePath() {
return this.mascotFilePath;
}
// i2cバス番号変更
setI2cbusNo(n) {
this.i2c_bus = n;
}
// 言語/GPIOライブラリ変更
setLang(l) {
this.lang = l;
// if (l == 'js-pigpio') {
// this.lang = 'js';
// this.gpio_lib = ugj_const.pig;
// } else if (l == 'js-rgpio') {
// this.lang = 'js';
// this.gpio_lib = ugj_const.rg;
// } else this.lang = 'py';
const exp = document.getElementById('dlgExport');
if (this.lang == 'js') {
exp.innerText = 'ファイルへ保存';
exp.title = 'ソースコードをファイルに保存します。';
} else {
exp.innerText = 'デプロイ';
exp.title = 'ソースコードをデバイスに転送します。';
}
}
// PyBfm を起動
launchPyBfm() {
let script_path = this.path.join(apptool.library_path, 'pybfm.py');
require('child_process').spawn('python3', [script_path]);
}
// ファイル名にアプリケーションのドキュメントルートまでのパスをつけて返す
getDocPath(filename) {
return this.path.join(this.appDocRoot, filename);
}
// Python コードフォーマッタ Black をコール
pyBeautify = (code) => {
let formatted = '';
formatted = require('child_process').spawnSync('python3', ['-m', 'black', '-'], { input: code }).stdout.toString();
if (!formatted) formatted = code;
return formatted;
}
// メインプロセスNode-Canvas 呼び出しOLED テキスト画像生成
async textToRGBA(text, font, color, start_x, start_y) {
return await this.ipcRenderer.invoke('text_to_rgba', text, font, color, start_x, start_y);
}
}
// ブラウザ動作用
class webTool {
constructor() {
// GPIOブロックは使えません
this.gpio_lib = ugj_const.rg;
this.lang = 'js';
this.blocks_dir = ugj_const.blocks_dir;
}
// マスコット
getMascotFilePath() { return `./img/${ugj_const.mascot_defname}`; }
//ワークスペースのダウンロード
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;
}
//ワークスペースのインポート
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 '';
}
// index.jsから万一呼ばれても何もしない関数
openURL() { ; }
selectMascotFile() { ; }
setMascotFilePath(fname) { ; }
saveFile() { ; }
savePrefsToLS() { ; }
loadPrefsFromLS() { ; }
newFile() { ; }
setWsChanged() { ; }
killAllChildren() { ; }
cleanupGPIO() { ; }
pyBeautify(code) { return code; }
path = {
join: function (a = '', b = '', c = '', d = '', e = '') {
const removeTrailingSlash = path => path.endsWith('/') ? path.substr(0, path.length - 1) : path;
return removeTrailingSlash(a) +
(b ? '/' + removeTrailingSlash(b) : '') +
(c ? '/' + removeTrailingSlash(c) : '') +
(d ? '/' + removeTrailingSlash(d) : '') +
(e ? '/' + removeTrailingSlash(e) : '')
}
}
}
// Electron 動作 / ブラウザ動作自動判別
// const is_el = (typeof window.ocogeapi !== 'undefined')
const is_app = (typeof require === 'function');
// utilクラスのインスタンスを返す
const appTool_new = () => {
if (is_app) {
let ap = new appTool;
ap.init();
return ap;
}
else return new webTool;
}
// "require" for web browsers if contextIsolation is false && nodeIntegration is true:
if (!is_app) {
var require = module_name => {
let block;
switch (module_name) {
case '@tensorflow/tfjs-node':
block = 'TensorFlow';
break;
case '@tensorflow-models/blazeface':
block = '顔認識';
break;
case 'axios':
block = 'URLを取得';
break;
case 'nodemailer':
block = 'メール送信';
break;
case 'pigpio':
block = 'GPIO';
break;
case 'fs':
block = 'ファイル';
break;
case 'path':
block = 'キャンバス保存';
break;
case 'child_process':
block = '外部プログラム実行';
break;
default:
throw new Error(ugj_const.error_ja_all);
}
throw `ブロック「${block}」は、Web体験版ではご利用になれません。\n詳しくは https://ocoge.club/ をご覧ください。`;
}
var setscriptlangtopy = () => apptool.lang = 'py';
}