ocoge/index_elutil.js

364 lines
13 KiB
JavaScript
Raw Normal View History

/** Electron / Node.js に由来する機能に関する諸々 */
/** ブラウザ動作時にもちょびっと対応 */
'use strict'
// 定数
const ugj_const = {
app_name: 'ocoge',
mascot_path: './img/',
mascot_defname: 'tamachee.png',
library_path: './lib/',
// executable_path: './bin/',
localStorage_fname: 'ocoge.json',
error_ja_all: 'エラーが発生しました。\n『おこげ倶楽部』までお問い合わせください。',
pig: 'pigpio',
lg: 'lgpio', // 対応未定
i2c_defbus: '1', // 文字列リテラルで指定
dev_hash: '4e9205f9b7e571bec1aa52ab7871f420684fcf96149672a4d550a95863d6b072'
}
/** クラス elUtil ****************************************************************** */
// Electron 動作用
class elUtil {
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.mascotFilePath = this.path.join(ugj_const.mascot_path, ugj_const.mascot_defname);
this.children = [];
this.gpio_backend = ugj_const.pig;
this.i2c_bus = ugj_const.i2c_defbus;
this.doc_root = process.env["HOME"] + '/Documents/';
this.doc_current = process.env["HOME"] + '/Documents';
this.executable_path = process.env["HOME"] + '/.local/lib/ocogeclub/'
}
// 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);
this.doc_current = this.path.dirname(filename);
return filename;
}
// ファイル書き込み
writeToFile(filepath, data) {
try {
this.fs.writeFileSync(filepath, data);
return true;
}
catch (err) {
return false;
}
}
// 子プロセス関連
// 新しい子プロセスを作成し、配列に保存
addChild(child) {
this.children.push(child);
}
// 全ての子プロセスを殺し、配列をクリア
killAllChildren() {
this.children.forEach(function (child) {
child.kill();
});
this.children = [];
}
// GPIO 関連リロードでGPIOをロックしたままハンドルを失うのを防ぐ
cleanupGPIO() {
require('@ocogeclub/' + this.gpio_backend).close_all_handle();
require('@ocogeclub/paj7620').stop();
require('@ocogeclub/amg8833').stop();
}
// 設定(保存ファイルパスと未保存フラグ)をローカルストレージに保存
savePrefsToLS() {
let wc = '0';
if (this.wsChanged) wc = '1';
let o = {
'saveFilepath': this.saveFilepath,
'wsChanged': wc,
'mascotFilePath': this.mascotFilePath,
'doc_current': this.doc_current,
'i2c_bus': this.i2c_bus
};
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;
}
}
// マスコット画像パスをプロパティにセット
setMascotFilePath(fpath) {
this.mascotFilePath = fpath;
}
getMascotFilePath() {
return this.mascotFilePath;
}
// i2cバス番号変更
setI2cbusNo(n) {
this.i2c_bus = n;
}
// ファイル名にアプリケーションのドキュメントルートまでのパスをつけて返す
getDocPath(filename) {
return this.path.join(this.appDocRoot, filename);
}
}
// ブラウザ動作用
class brUtil {
constructor() {
// GPIOブロックは使えません
this.gpio_backend = ugj_const.pig;
}
// マスコット
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() { ; }
}
// Electron 動作 / ブラウザ動作自動判別
// const is_el = (typeof window.ocogeapi !== 'undefined')
const is_el = (typeof require === 'function')
// utilクラスのインスタンスを返す
const elUtil_new = () => {
if (is_el) return new elUtil
else return new brUtil
}
// "require" for web browsers if contextIsolation is false && nodeIntegration is true:
if (!is_el) {
var require = module_name => {
let block;
switch (module_name) {
case '@tensorflow/tfjs-node':
block = 'TensorFlow';
break;
case '@vladmandic/face-api':
block = 'Face-API';
break;
case 'axios':
block = 'URLを取得';
break;
case 'nodemailer':
block = 'メール送信';
break;
case '@ocogeclub/pigpio':
block = 'GPIO';
break;
case '@ocogeclub/bme280':
block = 'BME280';
break;
case '@ocogeclub/amg8833':
block = '赤外線アレイセンサ';
break;
case '@ocogeclub/paj7620':
block = 'ジェスチャーセンサー';
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/ をご覧ください。`;
}
}