初始化提交
This commit is contained in:
209
common/modules/mixly-modules/electron/ampy-fs.js
Normal file
209
common/modules/mixly-modules/electron/ampy-fs.js
Normal file
@@ -0,0 +1,209 @@
|
||||
goog.loadJs('electron', () => {
|
||||
|
||||
goog.require('path');
|
||||
goog.require('Mixly.Env');
|
||||
goog.require('Mixly.FS');
|
||||
goog.require('Mixly.Debug');
|
||||
goog.require('Mixly.MJSON');
|
||||
goog.require('Mixly.Electron.Ampy');
|
||||
goog.provide('Mixly.Electron.AmpyFS');
|
||||
|
||||
const {
|
||||
Env,
|
||||
FS,
|
||||
Debug,
|
||||
MJSON,
|
||||
Electron
|
||||
} = Mixly;
|
||||
const { Ampy } = Electron;
|
||||
const fs_extra = Mixly.require('fs-extra');
|
||||
|
||||
|
||||
class AmpyFS extends FS {
|
||||
#ampy_ = null;
|
||||
#port_ = '';
|
||||
#baud_ = 115200;
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
this.#ampy_ = new Ampy();
|
||||
}
|
||||
|
||||
async rename(oldPath, newPath) {
|
||||
let stdout = '', error = null;
|
||||
try {
|
||||
const output = await this.#ampy_.rename(this.#port_, this.#baud_, oldPath, newPath);
|
||||
stdout = output.stdout;
|
||||
} catch (e) {
|
||||
error = e;
|
||||
Debug.error(error);
|
||||
}
|
||||
return [error, stdout];
|
||||
}
|
||||
|
||||
async createFile(filePath) {
|
||||
let stdout = '', error = null;
|
||||
try {
|
||||
const output = await this.#ampy_.mkfile(this.#port_, this.#baud_, filePath);
|
||||
stdout = output.stdout;
|
||||
} catch (e) {
|
||||
error = e;
|
||||
Debug.error(error);
|
||||
}
|
||||
return [error, stdout];
|
||||
}
|
||||
|
||||
async readFile(filePath) {
|
||||
let stdout = '', error = null;
|
||||
try {
|
||||
const output = await this.#ampy_.get(this.#port_, this.#baud_, filePath);
|
||||
stdout = output.stdout;
|
||||
stdout = stdout.replaceAll('\r\r', '\r');
|
||||
} catch (e) {
|
||||
error = e;
|
||||
Debug.error(error);
|
||||
}
|
||||
return [error, stdout];
|
||||
}
|
||||
|
||||
async writeFile(filePath, data) {
|
||||
let stdout = '', error = null;
|
||||
try {
|
||||
const startFilePath = path.join(Env.clientPath, 'temp/temp');
|
||||
await fs_extra.outputFile(startFilePath, data);
|
||||
const output = await this.#ampy_.put(this.#port_, this.#baud_, startFilePath, filePath);
|
||||
stdout = output.stdout;
|
||||
} catch (e) {
|
||||
error = e;
|
||||
Debug.error(error);
|
||||
}
|
||||
return [error, stdout];
|
||||
}
|
||||
|
||||
async isFile(filePath) {
|
||||
/*const [error, stdout] = await this.readDirectory(filePath);
|
||||
if (error) {
|
||||
return true;
|
||||
}
|
||||
return false;*/
|
||||
let error = null;
|
||||
if (path.extname(filePath)) {
|
||||
return [error, true];
|
||||
} else {
|
||||
return [error, false];
|
||||
}
|
||||
}
|
||||
|
||||
async renameFile(oldFilePath, newFilePath) {
|
||||
return this.rename(oldFilePath, newFilePath);
|
||||
}
|
||||
|
||||
// async moveFile(oldFilePath, newFilePath) {}
|
||||
|
||||
// async copyFile(oldFilePath, newFilePath) {}
|
||||
|
||||
async deleteFile(filePath) {
|
||||
let stdout = '', error = null;
|
||||
try {
|
||||
const output = await this.#ampy_.rm(this.#port_, this.#baud_, filePath);
|
||||
stdout = output.stdout;
|
||||
} catch (e) {
|
||||
error = e;
|
||||
Debug.error(error);
|
||||
}
|
||||
return [error, stdout];
|
||||
}
|
||||
|
||||
async createDirectory(folderPath) {
|
||||
let stdout = '', error = null;
|
||||
try {
|
||||
const output = await this.#ampy_.mkdir(this.#port_, this.#baud_, folderPath);
|
||||
stdout = output.stdout;
|
||||
} catch (e) {
|
||||
error = e;
|
||||
Debug.error(error);
|
||||
}
|
||||
return [error, stdout];
|
||||
}
|
||||
|
||||
async readDirectory(folderPath) {
|
||||
let stdout = [], error = null;
|
||||
try {
|
||||
const output = await this.#ampy_.ls(this.#port_, this.#baud_, folderPath);
|
||||
const dirs = Array.from(output.stdout.split('\r\n'));
|
||||
for (let i in dirs) {
|
||||
if (!dirs[i]) {
|
||||
continue;
|
||||
}
|
||||
stdout.push(MJSON.parse(dirs[i].replaceAll('\'', '"')));
|
||||
}
|
||||
} catch (e) {
|
||||
error = e;
|
||||
Debug.error(error);
|
||||
}
|
||||
return [error, stdout];
|
||||
}
|
||||
|
||||
async isDirectory(folderPath) {
|
||||
/*const [error, stdout] = await this.readDirectory(folderPath);
|
||||
if (error) {
|
||||
return false;
|
||||
}
|
||||
return true;*/
|
||||
let error = null;
|
||||
if (path.extname(folderPath)) {
|
||||
return [error, false];
|
||||
} else {
|
||||
return [error, true];
|
||||
}
|
||||
}
|
||||
|
||||
async isDirectoryEmpty(folderPath) {
|
||||
/*const [error, stdout] = await this.readDirectory(folderPath);
|
||||
let isEmpty = false;
|
||||
if (error || !stdout.length) {
|
||||
isEmpty = true;
|
||||
}*/
|
||||
return [null, false];
|
||||
}
|
||||
|
||||
async renameDirectory(oldFolderPath, newFolderPath) {
|
||||
return this.rename(oldFolderPath, newFolderPath);
|
||||
}
|
||||
|
||||
// async moveDirectory(oldFolderPath, newFolderPath) {}
|
||||
|
||||
// async copyDirectory(oldFolderPath, newFolderPath) {}
|
||||
|
||||
async deleteDirectory(folderPath) {
|
||||
let stdout = '', error = null;
|
||||
try {
|
||||
const output = await this.#ampy_.rmdir(this.#port_, this.#baud_, folderPath);
|
||||
stdout = output.stdout;
|
||||
} catch (e) {
|
||||
error = e;
|
||||
Debug.error(error);
|
||||
}
|
||||
return [error, stdout];
|
||||
}
|
||||
|
||||
setPortName(port) {
|
||||
this.#port_ = port;
|
||||
}
|
||||
|
||||
getPortName() {
|
||||
return this.#port_;
|
||||
}
|
||||
|
||||
setBaudRate(baud) {
|
||||
this.#baud_ = baud;
|
||||
}
|
||||
|
||||
getBaudRate() {
|
||||
return this.#baud_;
|
||||
}
|
||||
}
|
||||
|
||||
Electron.AmpyFS = AmpyFS;
|
||||
|
||||
});
|
||||
118
common/modules/mixly-modules/electron/ampy.js
Normal file
118
common/modules/mixly-modules/electron/ampy.js
Normal file
@@ -0,0 +1,118 @@
|
||||
goog.loadJs('electron', () => {
|
||||
|
||||
goog.require('path');
|
||||
goog.require('Mustache');
|
||||
goog.require('Mixly.Ampy');
|
||||
goog.require('Mixly.Env');
|
||||
goog.require('Mixly.Serial');
|
||||
goog.require('Mixly.Electron');
|
||||
goog.provide('Mixly.Electron.Ampy');
|
||||
|
||||
const {
|
||||
Ampy,
|
||||
Env,
|
||||
Serial,
|
||||
Electron
|
||||
} = Mixly;
|
||||
|
||||
const util = Mixly.require('node:util');
|
||||
const child_process = Mixly.require('node:child_process');
|
||||
|
||||
class AmpyExt extends Ampy {
|
||||
static {
|
||||
this.TEMPLATE = {
|
||||
ls: '{{&y}} -p {{&port}} -b {{&baud}} -i 0 ls "{{&folderPath}}"',
|
||||
get: '{{&y}} -p {{&port}} -b {{&baud}} -i 0 get "{{&filePath}}"',
|
||||
mkdir: '{{&y}} -p {{&port}} -b {{&baud}} -i 0 mkdir "{{&folderPath}}"',
|
||||
mkfile: '{{&y}} -p {{&port}} -b {{&baud}} -i 0 mkfile "{{&filePath}}"',
|
||||
isdir: '{{&y}} -p {{&port}} -b {{&baud}} -i 0 isdir "{{&folderPath}}"',
|
||||
isfile: '{{&y}} -p {{&port}} -b {{&baud}} -i 0 isfile "{{&filePath}}"',
|
||||
put: '{{&y}} -p {{&port}} -b {{&baud}} -i 0 put "{{&startPath}}" "{{&endPath}}"',
|
||||
rm: '{{&y}} -p {{&port}} -b {{&baud}} -i 0 rm "{{&filePath}}"',
|
||||
rmdir: '{{&y}} -p {{&port}} -b {{&baud}} -i 0 rmdir "{{&folderPath}}"',
|
||||
rename: '{{&y}} -p {{&port}} -b {{&baud}} -i 0 rename "{{&oldPath}}" "{{&newPath}}"',
|
||||
run: '{{&y}} -p {{&port}} -b {{&baud}} -i 0 run "{{&filePath}}"'
|
||||
}
|
||||
|
||||
this.AMPY_PATH = path.join(Env.srcDirPath, './tools/python/ampy/cli.py');
|
||||
|
||||
this.AMPY_TEMPLATE = Mustache.render('"{{&python3}}" "{{&y}}"', {
|
||||
python3: Env.python3Path,
|
||||
ampy: this.AMPY_PATH
|
||||
});
|
||||
}
|
||||
#exec_ = util.promisify(child_process.exec);
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
}
|
||||
|
||||
async ls(port, baud, folderPath) {
|
||||
return this.exec(port, this.render('ls', { port, baud, folderPath }));
|
||||
}
|
||||
|
||||
async get(port, baud, filePath) {
|
||||
return this.exec(port, this.render('get', { port, baud, filePath }));
|
||||
}
|
||||
|
||||
async mkdir(port, baud, folderPath) {
|
||||
return this.exec(port, this.render('mkdir', { port, baud, folderPath }));
|
||||
}
|
||||
|
||||
async mkfile(port, baud, filePath) {
|
||||
return this.exec(port, this.render('mkfile', { port, baud, filePath }));
|
||||
}
|
||||
|
||||
async isdir(port, baud, folderPath) {
|
||||
return this.exec(port, this.render('isdir', { port, baud, folderPath }));
|
||||
}
|
||||
|
||||
async isfile(port, baud, filePath) {
|
||||
return this.exec(port, this.render('isfile', { port, baud, filePath }));
|
||||
}
|
||||
|
||||
async put(port, baud, startPath, endPath) {
|
||||
return this.exec(port, this.render('put', { port, baud, startPath, endPath }));
|
||||
}
|
||||
|
||||
async rm(port, baud, filePath) {
|
||||
return this.exec(port, this.render('rm', { port, baud, filePath }));
|
||||
}
|
||||
|
||||
async rmdir(port, baud, folderPath) {
|
||||
return this.exec(port, this.render('rmdir', { port, baud, folderPath }));
|
||||
}
|
||||
|
||||
async rename(port, baud, oldPath, newPath) {
|
||||
return this.exec(port, this.render('rename', { port, baud, oldPath, newPath }));
|
||||
}
|
||||
|
||||
async run(port, baud, filePath) {
|
||||
return this.exec(port, this.render('run', { port, baud, filePath }));
|
||||
}
|
||||
|
||||
render(templateName, args) {
|
||||
return Mustache.render(AmpyExt.TEMPLATE[templateName], {
|
||||
...args,
|
||||
ampy: AmpyExt.AMPY_TEMPLATE
|
||||
});
|
||||
}
|
||||
|
||||
async exec(port, command) {
|
||||
const portsName = Serial.getCurrentPortsName();
|
||||
if (!portsName.includes(port)) {
|
||||
throw new Error('无可用串口');
|
||||
return;
|
||||
}
|
||||
const { mainStatusBarTabs } = Mixly;
|
||||
const statusBarSerial = mainStatusBarTabs.getStatusBarById(port);
|
||||
if (statusBarSerial) {
|
||||
await statusBarSerial.close();
|
||||
}
|
||||
return this.#exec_(command);
|
||||
}
|
||||
}
|
||||
|
||||
Electron.Ampy = AmpyExt;
|
||||
|
||||
});
|
||||
627
common/modules/mixly-modules/electron/arduino-shell.js
Normal file
627
common/modules/mixly-modules/electron/arduino-shell.js
Normal file
@@ -0,0 +1,627 @@
|
||||
goog.loadJs('electron', () => {
|
||||
|
||||
goog.require('path');
|
||||
goog.require('layui');
|
||||
goog.require('Blockly');
|
||||
goog.require('Mixly.Env');
|
||||
goog.require('Mixly.LayerExt');
|
||||
goog.require('Mixly.Config');
|
||||
goog.require('Mixly.Title');
|
||||
goog.require('Mixly.Boards');
|
||||
goog.require('Mixly.MFile');
|
||||
goog.require('Mixly.MArray');
|
||||
goog.require('Mixly.Msg');
|
||||
goog.require('Mixly.MString');
|
||||
goog.require('Mixly.Workspace');
|
||||
goog.require('Mixly.Serial');
|
||||
goog.require('Mixly.Electron.Shell');
|
||||
goog.provide('Mixly.Electron.ArduShell');
|
||||
|
||||
const {
|
||||
Env,
|
||||
Electron,
|
||||
LayerExt,
|
||||
Title,
|
||||
Boards,
|
||||
MFile,
|
||||
MArray,
|
||||
Msg,
|
||||
MString,
|
||||
Workspace,
|
||||
Serial,
|
||||
Config
|
||||
} = Mixly;
|
||||
|
||||
const { BOARD, SOFTWARE, USER } = Config;
|
||||
|
||||
const fs = Mixly.require('node:fs');
|
||||
const fs_plus = Mixly.require('fs-plus');
|
||||
const fs_extra = Mixly.require('fs-extra');
|
||||
const lodash_fp = Mixly.require('lodash/fp');
|
||||
const child_process = Mixly.require('node:child_process');
|
||||
const iconv_lite = Mixly.require('iconv-lite');
|
||||
|
||||
const {
|
||||
ArduShell,
|
||||
Shell
|
||||
} = Electron;
|
||||
|
||||
ArduShell.DEFAULT_CONFIG = goog.getJSON(path.join(Env.templatePath, 'json/arduino-cli-config.json'));
|
||||
|
||||
ArduShell.binFilePath = '';
|
||||
|
||||
ArduShell.shellPath = null;
|
||||
|
||||
ArduShell.shell = null;
|
||||
|
||||
ArduShell.ERROR_ENCODING = Env.currentPlatform == 'win32' ? 'cp936' : 'utf-8';
|
||||
|
||||
ArduShell.updateShellPath = () => {
|
||||
let shellPath = path.join(Env.clientPath, 'arduino-cli');
|
||||
if (Env.currentPlatform === 'win32')
|
||||
shellPath = path.join(shellPath, 'arduino-cli.exe');
|
||||
else
|
||||
shellPath = path.join(shellPath, 'arduino-cli');
|
||||
if (!fs_plus.isFileSync(shellPath)) {
|
||||
const { defaultPath = {} } = SOFTWARE;
|
||||
if (typeof defaultPath[Env.currentPlatform] === 'object') {
|
||||
let defaultShellPath = defaultPath[Env.currentPlatform].arduinoCli ?? '';
|
||||
defaultShellPath = path.join(Env.clientPath, defaultShellPath);
|
||||
if (fs_plus.isFileSync(defaultShellPath))
|
||||
shellPath = defaultShellPath;
|
||||
else
|
||||
shellPath = null;
|
||||
}
|
||||
}
|
||||
ArduShell.shellPath = shellPath;
|
||||
}
|
||||
|
||||
ArduShell.updateConfig = (config) => {
|
||||
if (!ArduShell.shellPath) return;
|
||||
const configPath = path.join(ArduShell.shellPath, '../arduino-cli.json');
|
||||
let nowConfig = fs_extra.readJsonSync(configPath, { throws: false }) ?? { ...ArduShell.DEFAULT_CONFIG };
|
||||
if (typeof config === 'object') {
|
||||
if (MArray.equals(nowConfig.directories, config.directories))
|
||||
return;
|
||||
nowConfig = {
|
||||
...nowConfig,
|
||||
...config
|
||||
};
|
||||
fs_extra.outputJson(configPath, nowConfig, {
|
||||
spaces: ' '
|
||||
})
|
||||
.then(() => {
|
||||
console.log('arduino-cli.json已更新');
|
||||
})
|
||||
.catch((error) => {
|
||||
console.log(error);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
ArduShell.init = () => {
|
||||
ArduShell.updateShellPath();
|
||||
if (!ArduShell.shellPath) return;
|
||||
ArduShell.updateConfig({
|
||||
directories: {
|
||||
data: path.join(ArduShell.shellPath, '../Arduino15'),
|
||||
downloads: path.join(ArduShell.shellPath, '../staging'),
|
||||
user: path.join(ArduShell.shellPath, '../Arduino')
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
ArduShell.init();
|
||||
|
||||
ArduShell.burn = () => {
|
||||
Mixly.Electron.BU.initBurn();
|
||||
}
|
||||
|
||||
/**
|
||||
* @function 编译
|
||||
* @description 开始一个编译过程
|
||||
* @return void
|
||||
*/
|
||||
ArduShell.initCompile = () => {
|
||||
ArduShell.compile(() => {});
|
||||
}
|
||||
|
||||
/**
|
||||
* @function 编译
|
||||
* @description 开始一个编译过程
|
||||
* @return void
|
||||
*/
|
||||
ArduShell.compile = (doFunc = () => {}) => {
|
||||
if (!ArduShell.shellPath) {
|
||||
ArduShell.shellPath = '';
|
||||
}
|
||||
const { mainStatusBarTabs } = Mixly;
|
||||
const statusBarTerminal = mainStatusBarTabs.getStatusBarById('output');
|
||||
statusBarTerminal.setValue('');
|
||||
mainStatusBarTabs.changeTo("output");
|
||||
ArduShell.compiling = true;
|
||||
ArduShell.uploading = false;
|
||||
const boardType = Boards.getSelectedBoardCommandParam();
|
||||
mainStatusBarTabs.show();
|
||||
const layerNum = layer.open({
|
||||
type: 1,
|
||||
title: Msg.Lang['shell.compiling'] + "...",
|
||||
content: $('#mixly-loader-div'),
|
||||
shade: LayerExt.SHADE_NAV,
|
||||
resize: false,
|
||||
closeBtn: 0,
|
||||
success: () => {
|
||||
$(".layui-layer-page").css("z-index", "198910151");
|
||||
$("#mixly-loader-btn").off("click").click(() => {
|
||||
$("#mixly-loader-btn").css('display', 'none');
|
||||
layer.title(Msg.Lang['shell.aborting'] + '...', layerNum);
|
||||
ArduShell.cancel();
|
||||
});
|
||||
},
|
||||
end: () => {
|
||||
$('#mixly-loader-div').css('display', 'none');
|
||||
$("layui-layer-shade" + layerNum).remove();
|
||||
$("#mixly-loader-btn").off("click");
|
||||
$("#mixly-loader-btn").css('display', 'inline-block');
|
||||
}
|
||||
});
|
||||
setTimeout(() => {
|
||||
statusBarTerminal.setValue(Msg.Lang['shell.compiling'] + "...\n");
|
||||
|
||||
let myLibPath = path.join(Env.boardDirPath, "/libraries/myLib/");
|
||||
if (fs_plus.isDirectorySync(myLibPath))
|
||||
myLibPath += '\",\"';
|
||||
else
|
||||
myLibPath = '';
|
||||
const thirdPartyPath = path.join(Env.boardDirPath, 'libraries/ThirdParty');
|
||||
if (fs_plus.isDirectorySync(thirdPartyPath)) {
|
||||
const libList = fs.readdirSync(thirdPartyPath);
|
||||
for (let libName of libList) {
|
||||
const libPath = path.join(thirdPartyPath, libName, 'libraries');
|
||||
if (!fs_plus.isDirectorySync(libPath)) continue;
|
||||
myLibPath += libPath + ',';
|
||||
}
|
||||
}
|
||||
const configPath = path.join(ArduShell.shellPath, '../arduino-cli.json'),
|
||||
defaultLibPath = path.join(ArduShell.shellPath, '../libraries'),
|
||||
buildPath = path.join(Env.clientPath, './mixlyBuild'),
|
||||
buildCachePath = path.join(Env.clientPath, './mixlyBuildCache'),
|
||||
codePath = path.join(Env.clientPath, './testArduino/testArduino.ino');
|
||||
const cmdStr = '\"'
|
||||
+ ArduShell.shellPath
|
||||
+ '\" compile -b '
|
||||
+ boardType
|
||||
+ ' --config-file \"'
|
||||
+ configPath
|
||||
+ '\" --build-cache-path \"' + buildCachePath + '\" --verbose --libraries \"'
|
||||
+ myLibPath
|
||||
+ defaultLibPath
|
||||
+ '\" --build-path \"'
|
||||
+ buildPath
|
||||
+ '\" \"'
|
||||
+ codePath
|
||||
+ '\" --no-color';
|
||||
ArduShell.runCmd(layerNum, 'compile', cmdStr, doFunc);
|
||||
}, 100);
|
||||
}
|
||||
|
||||
/**
|
||||
* @function 初始化上传
|
||||
* @description 关闭已打开的串口,获取当前所连接的设备数,然后开始上传程序
|
||||
* @return void
|
||||
*/
|
||||
ArduShell.initUpload = () => {
|
||||
const { mainStatusBarTabs } = Mixly;
|
||||
ArduShell.compiling = false;
|
||||
ArduShell.uploading = true;
|
||||
const boardType = Boards.getSelectedBoardCommandParam();
|
||||
const uploadType = Boards.getSelectedBoardConfigParam('upload_method');
|
||||
let port = Serial.getSelectedPortName();
|
||||
switch (uploadType) {
|
||||
case 'STLinkMethod':
|
||||
case 'jlinkMethod':
|
||||
case 'usb':
|
||||
port = 'None';
|
||||
break;
|
||||
}
|
||||
if (port) {
|
||||
const statusBarSerial = mainStatusBarTabs.getStatusBarById(port);
|
||||
if (statusBarSerial) {
|
||||
statusBarSerial.close().finally(() => {
|
||||
ArduShell.upload(boardType, port);
|
||||
});
|
||||
} else {
|
||||
ArduShell.upload(boardType, port);
|
||||
}
|
||||
} else {
|
||||
layer.msg(Msg.Lang['statusbar.serial.noDevice'], {
|
||||
time: 1000
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @function 上传程序
|
||||
* @description 通过所选择串口号开始一个上传过程
|
||||
* @return void
|
||||
*/
|
||||
ArduShell.upload = (boardType, port) => {
|
||||
if (!ArduShell.shellPath) {
|
||||
ArduShell.shellPath = '';
|
||||
}
|
||||
const { mainStatusBarTabs } = Mixly;
|
||||
const statusBarTerminal = mainStatusBarTabs.getStatusBarById('output');
|
||||
statusBarTerminal.setValue('');
|
||||
mainStatusBarTabs.changeTo('output');
|
||||
const layerNum = layer.open({
|
||||
type: 1,
|
||||
title: Msg.Lang['shell.uploading'] + "...",
|
||||
content: $('#mixly-loader-div'),
|
||||
shade: LayerExt.SHADE_NAV,
|
||||
resize: false,
|
||||
closeBtn: 0,
|
||||
success: function () {
|
||||
$(".layui-layer-page").css("z-index", "198910151");
|
||||
$("#mixly-loader-btn").off("click").click(() => {
|
||||
$("#mixly-loader-btn").css('display', 'none');
|
||||
layer.title(Msg.Lang['shell.aborting'] + '...', layerNum);
|
||||
ArduShell.cancel();
|
||||
});
|
||||
},
|
||||
end: function () {
|
||||
$('#mixly-loader-div').css('display', 'none');
|
||||
$("layui-layer-shade" + layerNum).remove();
|
||||
$("#mixly-loader-btn").off("click");
|
||||
$("#mixly-loader-btn").css('display', 'inline-block');
|
||||
}
|
||||
});
|
||||
mainStatusBarTabs.show();
|
||||
statusBarTerminal.setValue(Msg.Lang['shell.uploading'] + "...\n");
|
||||
const configPath = path.join(ArduShell.shellPath, '../arduino-cli.json'),
|
||||
defaultLibPath = path.join(ArduShell.shellPath, '../libraries'),
|
||||
buildPath = path.join(Env.clientPath, './mixlyBuild'),
|
||||
buildCachePath = path.join(Env.clientPath, './mixlyBuildCache'),
|
||||
codePath = path.join(Env.clientPath, './testArduino/testArduino.ino');
|
||||
let cmdStr = '';
|
||||
if (ArduShell.binFilePath !== '') {
|
||||
cmdStr = '\"'
|
||||
+ ArduShell.shellPath
|
||||
+ '\" -b '
|
||||
+ boardType
|
||||
+ ' upload -p '
|
||||
+ port
|
||||
+ ' --config-file \"'
|
||||
+ configPath
|
||||
+ '\" --verbose '
|
||||
+ '-i \"' + ArduShell.binFilePath + '\" --no-color';
|
||||
ArduShell.binFilePath = '';
|
||||
} else {
|
||||
let myLibPath = path.join(Env.boardDirPath, "/libraries/myLib/");
|
||||
if (fs_plus.isDirectorySync(myLibPath)) {
|
||||
myLibPath += '\",\"';
|
||||
} else {
|
||||
myLibPath = '';
|
||||
}
|
||||
const thirdPartyPath = path.join(Env.boardDirPath, 'libraries/ThirdParty');
|
||||
if (fs_plus.isDirectorySync(thirdPartyPath)) {
|
||||
const libList = fs.readdirSync(thirdPartyPath);
|
||||
for (let libName of libList) {
|
||||
const libPath = path.join(thirdPartyPath, libName, 'libraries');
|
||||
if (!fs_plus.isDirectorySync(libPath)) continue;
|
||||
myLibPath += libPath + ',';
|
||||
}
|
||||
}
|
||||
cmdStr = '\"'
|
||||
+ ArduShell.shellPath
|
||||
+ '\" compile -b '
|
||||
+ boardType
|
||||
+ ' --upload -p '
|
||||
+ port
|
||||
+ ' --config-file \"'
|
||||
+ configPath
|
||||
+ '\" --build-cache-path \"' + buildCachePath + '\" --verbose --libraries \"'
|
||||
+ myLibPath
|
||||
+ defaultLibPath
|
||||
+ '\" --build-path \"'
|
||||
+ buildPath
|
||||
+ '\" \"'
|
||||
+ codePath
|
||||
+ '\" --no-color';
|
||||
}
|
||||
ArduShell.runCmd(layerNum, 'upload', cmdStr,
|
||||
function () {
|
||||
mainStatusBarTabs.add('serial', port);
|
||||
mainStatusBarTabs.changeTo(port);
|
||||
const statusBarSerial = mainStatusBarTabs.getStatusBarById(port);
|
||||
statusBarSerial.open();
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @function 取消编译或上传
|
||||
* @description 取消正在执行的编译或上传过程
|
||||
* @return void
|
||||
*/
|
||||
ArduShell.cancel = function () {
|
||||
ArduShell.shell && ArduShell.shell.kill();
|
||||
ArduShell.shell = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* @function 检测文件扩展名
|
||||
* @description 检测文件扩展名是否在扩展名列表内
|
||||
* @param fileName {String} 文件名
|
||||
* @param extensionList {Array} 扩展名列表
|
||||
* @return Boolean
|
||||
*/
|
||||
ArduShell.checkFileNameExtension = (fileName, extensionList) => {
|
||||
if (!fileName) return false;
|
||||
let fileNameToLowerCase = fileName;
|
||||
let fileNameLen = fileNameToLowerCase.length;
|
||||
let fileType = fileNameToLowerCase.substring(fileNameToLowerCase.lastIndexOf("."), fileNameLen);
|
||||
if (extensionList.includes(fileType)) {
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @function 检测文件扩展名
|
||||
* @description 检测文件扩展名是否为.c/.cpp或.h/.hpp
|
||||
* @param fileName {String} 文件名
|
||||
* @return Boolean
|
||||
*/
|
||||
ArduShell.isCppOrHpp = (fileName) => {
|
||||
return ArduShell.checkFileNameExtension(fileName, [".c", ".cpp", ".h", ".hpp"])
|
||||
}
|
||||
|
||||
/**
|
||||
* @function 检测文件扩展名
|
||||
* @description 检测文件扩展名是否为.mix/.xml或.ino
|
||||
* @param fileName {String} 文件名
|
||||
* @return Boolean
|
||||
*/
|
||||
ArduShell.isMixOrIno = (fileName) => {
|
||||
return ArduShell.checkFileNameExtension(fileName, [".mix", ".xml", ".ino"]);
|
||||
}
|
||||
|
||||
/**
|
||||
* @function 删除给定文件夹下文件
|
||||
* @description 删除给定文件夹下.c/.cpp和.h/.hpp文件
|
||||
* @param dir {String} 文件夹路径
|
||||
* @return void
|
||||
*/
|
||||
ArduShell.clearDirCppAndHppFiles = (dir) => {
|
||||
if (fs_plus.isDirectorySync(dir)) {
|
||||
let libDir = fs.readdirSync(dir);
|
||||
for (let i = 0; i < libDir.length; i++) {
|
||||
if (ArduShell.isCppOrHpp(libDir[i])) {
|
||||
const nowPath = path.join(dir, libDir[i]);
|
||||
fs.unlinkSync(nowPath);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @function 拷贝文件
|
||||
* @description 拷贝给定文件夹下.c/.cpp和.h/.hpp文件到目标目录
|
||||
* @param oldDir {String} 起始文件夹路径
|
||||
* @param newDir {String} 目标文件夹路径
|
||||
* @return void
|
||||
*/
|
||||
ArduShell.copyHppAndCppFiles = (oldDir, newDir) => {
|
||||
if (fs_plus.isDirectorySync(oldDir) && fs_plus.isDirectorySync(newDir)) {
|
||||
let oldLibDir = fs.readdirSync(oldDir);
|
||||
for (let i = 0; i < oldLibDir.length; i++) {
|
||||
if (ArduShell.isCppOrHpp(oldLibDir[i])) {
|
||||
const oldPath = path.join(oldDir, oldLibDir[i]);
|
||||
const newPath = path.join(newDir, oldLibDir[i]);
|
||||
try {
|
||||
fs.copyFileSync(oldPath, newPath);
|
||||
} catch (e) {
|
||||
console.log(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @function 写库文件
|
||||
* @description 将库文件数据写入本地
|
||||
* @param inPath {string} 需要写入库文件的目录
|
||||
* @return void
|
||||
*/
|
||||
ArduShell.writeLibFiles = (inPath) => {
|
||||
return new Promise((resolve, reject) => {
|
||||
const promiseList = [];
|
||||
for (let name in Blockly.Arduino.libs_) {
|
||||
const data = Blockly.Arduino.libs_[name];
|
||||
const codePath = path.join(inPath, name + '.h');
|
||||
promiseList.push(
|
||||
new Promise((childResolve, childReject) => {
|
||||
fs_extra.outputFile(codePath, data)
|
||||
.finally(() => {
|
||||
childResolve();
|
||||
});
|
||||
}
|
||||
));
|
||||
}
|
||||
if (!promiseList.length) {
|
||||
resolve();
|
||||
return;
|
||||
}
|
||||
Promise.all(promiseList)
|
||||
.finally(() => {
|
||||
resolve();
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* @function 运行一个cmd命令
|
||||
* @description 输入编译或上传的cmd命令
|
||||
* @param cmd {String} 输入的cmd命令
|
||||
* @return void
|
||||
*/
|
||||
ArduShell.runCmd = (layerNum, type, cmd, sucFunc) => {
|
||||
const { mainStatusBarTabs } = Mixly;
|
||||
const statusBarTerminal = mainStatusBarTabs.getStatusBarById('output');
|
||||
const mainWorkspace = Workspace.getMain();
|
||||
const editor = mainWorkspace.getEditorsManager().getActive();
|
||||
const code = editor.getCode();
|
||||
const testArduinoDirPath = path.join(Env.clientPath, 'testArduino');
|
||||
const codePath = path.join(testArduinoDirPath, 'testArduino.ino');
|
||||
const nowFilePath = Title.getFilePath();
|
||||
ArduShell.clearDirCppAndHppFiles(testArduinoDirPath);
|
||||
if (USER.compileCAndH === 'yes' && fs_plus.isFileSync(nowFilePath) && ArduShell.isMixOrIno(nowFilePath)) {
|
||||
const nowDirPath = path.dirname(nowFilePath);
|
||||
ArduShell.copyHppAndCppFiles(nowDirPath, testArduinoDirPath);
|
||||
}
|
||||
ArduShell.writeLibFiles(testArduinoDirPath)
|
||||
.then(() => {
|
||||
return fs_extra.outputFile(codePath, code);
|
||||
})
|
||||
.then(() => {
|
||||
ArduShell.shell = new Shell(cmd);
|
||||
return ArduShell.shell.exec(cmd);
|
||||
})
|
||||
.then((info) => {
|
||||
layer.close(layerNum);
|
||||
let message = '';
|
||||
if (info.code) {
|
||||
|
||||
message = (type === 'compile' ? Msg.Lang['shell.compileFailed'] : Msg.Lang['shell.uploadFailed']);
|
||||
statusBarTerminal.addValue("==" + message + "==\n");
|
||||
} else {
|
||||
message = (type === 'compile' ? Msg.Lang['shell.compileSucc'] : Msg.Lang['shell.uploadSucc']);
|
||||
statusBarTerminal.addValue(`==${message}(${Msg.Lang['shell.timeCost']} ${info.time})==\n`);
|
||||
sucFunc();
|
||||
}
|
||||
layer.msg(message, { time: 1000 });
|
||||
})
|
||||
.catch((error) => {
|
||||
layer.close(layerNum);
|
||||
console.log(error);
|
||||
})
|
||||
.finally(() => {
|
||||
statusBarTerminal.scrollToBottom();
|
||||
});
|
||||
}
|
||||
|
||||
ArduShell.saveBinOrHex = function (writePath) {
|
||||
ArduShell.writeFile(Env.clientPath + "/mixlyBuild", writePath);
|
||||
}
|
||||
|
||||
ArduShell.writeFile = function (readPath, writePath) {
|
||||
const { mainStatusBarTabs } = Mixly;
|
||||
const statusBarTerminal = mainStatusBarTabs.getStatusBarById('output');
|
||||
ArduShell.compile(function () {
|
||||
window.setTimeout(function () {
|
||||
const layerNum = layer.open({
|
||||
type: 1,
|
||||
title: Msg.Lang['shell.saving'] + '...',
|
||||
content: $('#mixly-loader-div'),
|
||||
shade: LayerExt.SHADE_ALL,
|
||||
resize: false,
|
||||
closeBtn: 0,
|
||||
success: function () {
|
||||
$(".layui-layer-page").css("z-index", "198910151");
|
||||
$("#mixly-loader-btn").off("click").click(() => {
|
||||
layer.close(layerNum);
|
||||
ArduShell.cancel();
|
||||
});
|
||||
window.setTimeout(function () {
|
||||
try {
|
||||
readPath = readPath.replace(/\\/g, "/");
|
||||
writePath = writePath.replace(/\\/g, "/");
|
||||
} catch (e) {
|
||||
console.log(e);
|
||||
}
|
||||
try {
|
||||
let writeDirPath = writePath.substring(0, writePath.lastIndexOf("."));
|
||||
let writeFileName = writePath.substring(writePath.lastIndexOf("/") + 1, writePath.lastIndexOf("."));
|
||||
let writeFileType = writePath.substring(writePath.lastIndexOf(".") + 1);
|
||||
if (!fs.existsSync(writeDirPath)) {
|
||||
fs.mkdirSync(writeDirPath);
|
||||
}
|
||||
if (fs.existsSync(writePath)) {
|
||||
fs.unlinkSync(writePath);
|
||||
}
|
||||
let readBinFilePath = readPath + "/testArduino.ino." + writeFileType;
|
||||
let binFileData = fs.readFileSync(readBinFilePath);
|
||||
fs.writeFileSync(writePath, binFileData);
|
||||
let binFileType = [
|
||||
".eep",
|
||||
".hex",
|
||||
".with_bootloader.bin",
|
||||
".with_bootloader.hex",
|
||||
".bin",
|
||||
".elf",
|
||||
".map",
|
||||
".partitions.bin",
|
||||
".bootloader.bin"
|
||||
]
|
||||
for (let i = 0; i < binFileType.length; i++) {
|
||||
let readFilePath = readPath + "/testArduino.ino" + binFileType[i];
|
||||
let writeFilePath = writeDirPath + "/" + writeFileName + binFileType[i];
|
||||
if (fs.existsSync(readFilePath)) {
|
||||
let binData = fs.readFileSync(readFilePath);
|
||||
fs.writeFileSync(writeFilePath, binData);
|
||||
}
|
||||
}
|
||||
layer.msg(Msg.Lang['shell.saveSucc'], {
|
||||
time: 1000
|
||||
});
|
||||
} catch (e) {
|
||||
console.log(e);
|
||||
statusBarTerminal.addValue(e + "\n");
|
||||
}
|
||||
layer.close(layerNum);
|
||||
}, 500);
|
||||
},
|
||||
end: function () {
|
||||
$('#mixly-loader-div').css('display', 'none');
|
||||
$("layui-layer-shade" + layerNum).remove();
|
||||
$("#mixly-loader-btn").off("click");
|
||||
}
|
||||
});
|
||||
}, 1000);
|
||||
});
|
||||
}
|
||||
|
||||
ArduShell.showUploadBox = function (filePath) {
|
||||
const dirPath = path.dirname(filePath);
|
||||
const fileName = path.basename(filePath, path.extname(filePath));
|
||||
if (fs_plus.isDirectorySync(path.join(dirPath, fileName))) {
|
||||
filePath = path.join(dirPath, fileName, path.basename(filePath));
|
||||
}
|
||||
const layerNum = layer.msg(Msg.Lang['shell.uploadWithFileInfo'], {
|
||||
time: -1,
|
||||
btn: [Msg.Lang['nav.btn.stop'], Msg.Lang['nav.btn.upload']],
|
||||
shade: LayerExt.SHADE_ALL,
|
||||
btnAlign: 'c',
|
||||
yes: function () {
|
||||
layer.close(layerNum);
|
||||
ArduShell.binFilePath = '';
|
||||
},
|
||||
btn2: function () {
|
||||
layer.close(layerNum);
|
||||
ArduShell.uploadWithBinOrHex(filePath);
|
||||
},
|
||||
end: function () {
|
||||
ArduShell.binFilePath = '';
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
ArduShell.uploadWithBinOrHex = function (filePath) {
|
||||
layer.closeAll();
|
||||
ArduShell.binFilePath = filePath;
|
||||
ArduShell.initUpload();
|
||||
}
|
||||
|
||||
});
|
||||
740
common/modules/mixly-modules/electron/burn-upload.js
Normal file
740
common/modules/mixly-modules/electron/burn-upload.js
Normal file
@@ -0,0 +1,740 @@
|
||||
goog.loadJs('electron', () => {
|
||||
|
||||
goog.require('layui');
|
||||
goog.require('Mixly.Config');
|
||||
goog.require('Mixly.LayerExt');
|
||||
goog.require('Mixly.Env');
|
||||
goog.require('Mixly.Boards');
|
||||
goog.require('Mixly.MString');
|
||||
goog.require('Mixly.Msg');
|
||||
goog.require('Mixly.Workspace');
|
||||
goog.require('Mixly.Debug');
|
||||
goog.require('Mixly.Electron.Serial');
|
||||
goog.provide('Mixly.Electron.BU');
|
||||
|
||||
const {
|
||||
Electron,
|
||||
Config,
|
||||
LayerExt,
|
||||
Env,
|
||||
Boards,
|
||||
MString,
|
||||
Msg,
|
||||
Workspace,
|
||||
Serial,
|
||||
Debug
|
||||
} = Mixly;
|
||||
|
||||
const { BU } = Electron;
|
||||
const { BOARD, SELECTED_BOARD, USER } = Config;
|
||||
|
||||
var downloadShell = null;
|
||||
|
||||
const { form } = layui;
|
||||
|
||||
const fs = Mixly.require('node:fs');
|
||||
const fs_plus = Mixly.require('fs-plus');
|
||||
const fs_extra = Mixly.require('fs-extra');
|
||||
const path = Mixly.require('node:path');
|
||||
const lodash_fp = Mixly.require('lodash/fp');
|
||||
const child_process = Mixly.require('node:child_process');
|
||||
const iconv_lite = Mixly.require('iconv-lite');
|
||||
const os = Mixly.require('node:os');
|
||||
|
||||
BU.uploading = false;
|
||||
|
||||
BU.burning = false;
|
||||
|
||||
BU.shell = null;
|
||||
|
||||
/**
|
||||
* @function 根据传入的stdout判断磁盘数量并选择对应操作
|
||||
* @param type {string} 值为'burn' | 'upload'
|
||||
* @param stdout {string} 磁盘名称字符串,形如'G:K:F:'
|
||||
* @param startPath {string} 需要拷贝的文件路径
|
||||
* @return {void}
|
||||
**/
|
||||
BU.checkNumOfDisks = function (type, stdout, startPath) {
|
||||
const { mainStatusBarTabs } = Mixly;
|
||||
const statusBarTerminal = mainStatusBarTabs.getStatusBarById('output');
|
||||
let wmicResult = stdout;
|
||||
wmicResult = wmicResult.replace(/\s+/g, "");
|
||||
wmicResult = wmicResult.replace("DeviceID", "");
|
||||
// wmicResult = 'G:K:F:';
|
||||
let result = wmicResult.split(':');
|
||||
let pathAdd = (Env.currentPlatform === "win32") ? ':' : '';
|
||||
if (stdout.indexOf(":") != stdout.lastIndexOf(":")) {
|
||||
let form = layui.form;
|
||||
let devicesName = $('#mixly-selector-type');
|
||||
let oldDevice = $('#mixly-selector-type option:selected').val();
|
||||
devicesName.empty();
|
||||
for (let i = 0; i < result.length; i++) {
|
||||
if (result[i]) {
|
||||
if (oldDevice == result[i] + pathAdd) {
|
||||
devicesName.append('<option value="' + result[i] + pathAdd + '" selected>' + result[i] + pathAdd + '</option>');
|
||||
} else {
|
||||
devicesName.append('<option value="' + result[i] + pathAdd + '">' + result[i] + pathAdd + '</option>');
|
||||
}
|
||||
}
|
||||
}
|
||||
form.render();
|
||||
let initBtnClicked = false;
|
||||
const layerNum = layer.open({
|
||||
type: 1,
|
||||
id: "serial-select",
|
||||
title: Msg.Lang['shell.tooManyDevicesInfo'],
|
||||
area: ['350px', '150px'],
|
||||
content: $('#mixly-selector-div'),
|
||||
shade: LayerExt.SHADE_ALL,
|
||||
resize: false,
|
||||
closeBtn: 0,
|
||||
success: function (layero) {
|
||||
$('#serial-select').css('height', '195px');
|
||||
$(".layui-layer-page").css("z-index","198910151");
|
||||
$("#mixly-selector-btn1").off("click").click(() => {
|
||||
layer.close(layerNum);
|
||||
BU.cancel();
|
||||
});
|
||||
$("#mixly-selector-btn2").off("click").click(() => {
|
||||
layer.close(layerNum);
|
||||
initBtnClicked = true;
|
||||
});
|
||||
},
|
||||
end: function () {
|
||||
$('#mixly-selector-div').css('display', 'none');
|
||||
$("#layui-layer-shade" + layerNum).remove();
|
||||
if (initBtnClicked) {
|
||||
BU.initWithDropdownBox(type, startPath);
|
||||
}
|
||||
$("#mixly-selector-btn1").off("click");
|
||||
$("#mixly-selector-btn2").off("click");
|
||||
}
|
||||
});
|
||||
} else {
|
||||
const layerNum = layer.open({
|
||||
type: 1,
|
||||
title: (type === 'burn'? Msg.Lang['shell.burning'] : Msg.Lang['shell.uploading']) + '...',
|
||||
content: $('#mixly-loader-div'),
|
||||
shade: LayerExt.SHADE_ALL,
|
||||
resize: false,
|
||||
closeBtn: 0,
|
||||
success: function (layero, index) {
|
||||
if (type === 'burn') {
|
||||
BU.copyFiles(type, index, startPath, result[0] + pathAdd + '/');
|
||||
} else {
|
||||
const mainWorkspace = Workspace.getMain();
|
||||
const editor = mainWorkspace.getEditorsManager().getActive();
|
||||
const code = editor.getCode();
|
||||
fs_extra.outputFile(startPath, code)
|
||||
.then(() => {
|
||||
BU.copyFiles(type, index, startPath, result[0] + pathAdd + '/');
|
||||
})
|
||||
.catch((error) => {
|
||||
layer.close(index);
|
||||
BU.burning = false;
|
||||
BU.uploading = false;
|
||||
statusBarTerminal.setValue(error + '\n');
|
||||
});
|
||||
}
|
||||
$("#mixly-loader-btn").off("click").click(() => {
|
||||
layer.close(index);
|
||||
BU.cancel();
|
||||
});
|
||||
},
|
||||
end: function () {
|
||||
$('#mixly-selector-div').css('display', 'none');
|
||||
$("#layui-layer-shade" + layerNum).remove();
|
||||
$("#mixly-loader-btn").off("click");
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @function 将文件或文件夹下所有文件拷贝到指定文件夹
|
||||
* @param type {string} 值为'burn' | 'upload'
|
||||
* @param layerNum {number} 烧录或上传加载弹窗的编号,用于关闭此弹窗
|
||||
* @param startPath {string} 需要拷贝的文件或文件夹的路径
|
||||
* @param desPath {string} 文件的目的路径
|
||||
**/
|
||||
BU.copyFiles = (type, layerNum, startPath, desPath) => {
|
||||
const { mainStatusBarTabs } = Mixly;
|
||||
const statusBarTerminal = mainStatusBarTabs.getStatusBarById('output');
|
||||
const { burn, upload } = SELECTED_BOARD;
|
||||
if (type === 'upload' && upload.copyLib) {
|
||||
const mainWorkspace = Workspace.getMain();
|
||||
const editor = mainWorkspace.getEditorsManager().getActive();
|
||||
const code = editor.getCode();
|
||||
let startLibPath = path.dirname(upload.filePath);
|
||||
let pyFileArr = BU.copyLib(startPath, code);
|
||||
startPath = path.dirname(startPath);
|
||||
}
|
||||
// 如果需要拷贝的是文件,则在目的路径后要加上文件名
|
||||
if (fs_plus.isFileSync(startPath)) {
|
||||
desPath = path.join(desPath, path.basename(startPath));
|
||||
}
|
||||
fs_extra.copy(startPath, desPath)
|
||||
.then(() => {
|
||||
layer.close(layerNum);
|
||||
const message = (type === 'burn'? Msg.Lang['shell.burnSucc'] : Msg.Lang['shell.uploadSucc']);
|
||||
layer.msg(message, {
|
||||
time: 1000
|
||||
});
|
||||
statusBarTerminal.setValue(`==${message}==`);
|
||||
if (type === 'upload' && Serial.uploadPorts.length === 1) {
|
||||
if (USER.autoOpenPort === 'no') {
|
||||
return;
|
||||
}
|
||||
mainStatusBarTabs.add('serial', port);
|
||||
mainStatusBarTabs.changeTo(port);
|
||||
const statusBarSerial = mainStatusBarTabs.getStatusBarById(port);
|
||||
statusBarSerial.open();
|
||||
}
|
||||
})
|
||||
.catch((error) => {
|
||||
layer.close(layerNum);
|
||||
statusBarTerminal.setValue(error + '\n');
|
||||
console.log(error);
|
||||
})
|
||||
.finally(() => {
|
||||
BU.burning = false;
|
||||
BU.uploading = false;
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* @function 判断当前环境,以开始一个上传过程
|
||||
* @param type {string} 值为'burn' | 'upload'
|
||||
* @param startPath {string} 需要拷贝的文件或文件夹的路径
|
||||
* @return {void}
|
||||
*/
|
||||
BU.initWithDropdownBox = function (type, startPath) {
|
||||
const { mainStatusBarTabs } = Mixly;
|
||||
const statusBarTerminal = mainStatusBarTabs.getStatusBarById('output');
|
||||
const layerNum = layer.open({
|
||||
type: 1,
|
||||
title: (type === 'burn'? Msg.Lang['shell.burning'] : Msg.Lang['shell.uploading']) + '...',
|
||||
content: $('#mixly-loader-div'),
|
||||
shade: LayerExt.SHADE_ALL,
|
||||
resize: false,
|
||||
closeBtn: 0,
|
||||
success: function (layero, index) {
|
||||
$(".layui-layer-page").css("z-index","198910151");
|
||||
$("#mixly-loader-btn").off("click").click(() => {
|
||||
layer.close(index);
|
||||
BU.cancel();
|
||||
});
|
||||
const desPath = $('#mixly-selector-type option:selected').val();
|
||||
if (type === 'burn') {
|
||||
BU.copyFiles(type, index, startPath, desPath);
|
||||
} else {
|
||||
const mainWorkspace = Workspace.getMain();
|
||||
const editor = mainWorkspace.getEditorsManager().getActive();
|
||||
const code = editor.getCode();
|
||||
fs_extra.outputFile(startPath, code)
|
||||
.then(() => {
|
||||
BU.copyFiles(type, index, startPath, desPath);
|
||||
})
|
||||
.catch((error) => {
|
||||
layer.close(index);
|
||||
BU.burning = false;
|
||||
BU.uploading = false;
|
||||
statusBarTerminal.setValue(error + '\n');
|
||||
});
|
||||
}
|
||||
},
|
||||
end: function () {
|
||||
$('#mixly-loader-div').css('display', 'none');
|
||||
$("#layui-layer-shade" + layerNum).remove();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* @function 根据传入的盘符名称获取对应的磁盘名称
|
||||
* @param type {string} 值为'burn' | 'upload'
|
||||
* @param volumeName {string} 所要查找盘符的名称
|
||||
* @param startPath {string} 需要拷贝文件的路径
|
||||
* @return {void}
|
||||
*/
|
||||
BU.getDisksWithVolumesName = function (type, volumeName, startPath) {
|
||||
let dirPath = path.dirname(startPath);
|
||||
fs_extra.ensureDirSync(dirPath);
|
||||
if (Env.currentPlatform === "win32") {
|
||||
child_process.exec('wmic logicaldisk where "' + volumeName + '" get DeviceID', function (err, stdout, stderr) {
|
||||
if (err || stderr) {
|
||||
$('#mixly-loader-div').css('display', 'none');
|
||||
console.log("root path open failed" + err + stderr);
|
||||
layer.msg(Msg.Lang['statusbar.serial.noDevice'], {
|
||||
time: 1000
|
||||
});
|
||||
BU.burning = false;
|
||||
BU.uploading = false;
|
||||
return;
|
||||
}
|
||||
BU.checkNumOfDisks(type, stdout, startPath);
|
||||
});
|
||||
} else {
|
||||
let diskPath = '/Volumes/';
|
||||
let addChar = ' ';
|
||||
if (Env.currentPlatform === "linux") {
|
||||
diskPath = '/media/';
|
||||
addChar = '';
|
||||
}
|
||||
let stdout = '';
|
||||
let result = null;
|
||||
result = volumeName.split('/');
|
||||
let deviceNum = 0;
|
||||
for (var i = 0; i < result.length; i++) {
|
||||
if (result[i] === '') continue;
|
||||
for (var j = 0; ; j++) {
|
||||
if (fs_plus.isDirectorySync(diskPath + result[i] + (j == 0 ? '' : (addChar + j)))) {
|
||||
stdout += diskPath + result[i] + (j == 0 ? '' : (addChar + j)) + ':';
|
||||
deviceNum++;
|
||||
} else if (fs_plus.isDirectorySync(diskPath + os.userInfo().username + '/' + result[i] + (j == 0 ? '' : (addChar + j)))) {
|
||||
stdout += diskPath + os.userInfo().username + '/' + result[i] + (j == 0 ? '' : (addChar + j)) + ':';
|
||||
deviceNum++;
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (deviceNum === 0) {
|
||||
layer.msg(Msg.Lang['statusbar.serial.noDevice'], {
|
||||
time: 1000
|
||||
});
|
||||
BU.burning = false;
|
||||
BU.uploading = false;
|
||||
return;
|
||||
}
|
||||
BU.checkNumOfDisks(type, stdout, startPath);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @function 取消烧录或上传
|
||||
* @return {void}
|
||||
*/
|
||||
BU.cancel = function () {
|
||||
if (BU.shell) {
|
||||
BU.shell.stdout.end();
|
||||
BU.shell.stdin.end();
|
||||
if (Env.currentPlatform === 'win32') {
|
||||
child_process.exec('taskkill /pid ' + BU.shell.pid + ' /f /t');
|
||||
} else {
|
||||
BU.shell.kill("SIGTERM");
|
||||
}
|
||||
BU.shell = null;
|
||||
} else {
|
||||
if (BU.uploading) {
|
||||
BU.uploading = false;
|
||||
layer.msg(Msg.Lang['shell.uploadCanceled'], {
|
||||
time: 1000
|
||||
});
|
||||
} else if (BU.burning) {
|
||||
BU.burning = false;
|
||||
layer.msg(Msg.Lang['shell.burnCanceled'], {
|
||||
time: 1000
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @function 开始一个烧录过程
|
||||
* @return {void}
|
||||
*/
|
||||
BU.initBurn = function () {
|
||||
if (BU.burning) return;
|
||||
const { burn } = SELECTED_BOARD;
|
||||
BU.burning = true;
|
||||
BU.uploading = false;
|
||||
if (burn.type === 'volume') {
|
||||
BU.getDisksWithVolumesName('burn', burn.volume, burn.filePath);
|
||||
} else {
|
||||
const port = Serial.getSelectedPortName();
|
||||
BU.burnWithPort(port, burn.command);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @function 开始一个上传过程
|
||||
* @return {void}
|
||||
*/
|
||||
BU.initUpload = function () {
|
||||
if (BU.uploading) return;
|
||||
const { upload } = SELECTED_BOARD;
|
||||
BU.burning = false;
|
||||
BU.uploading = true;
|
||||
if (upload.type === "volume") {
|
||||
BU.getDisksWithVolumesName('upload', upload.volume, upload.filePath);
|
||||
} else {
|
||||
const port = Serial.getSelectedPortName();
|
||||
BU.uploadWithPort(port, upload.command);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @function 递归代码找出import项并拷贝对应库文件到filePath所在目录
|
||||
* @param filePath {string} 主代码文件所在路径
|
||||
* @param code {string} 主代码数据
|
||||
* @return {array} 库列表
|
||||
**/
|
||||
BU.copyLib = function (filePath, code) {
|
||||
const dirPath = path.dirname(filePath);
|
||||
const fileName = path.basename(filePath);
|
||||
fs_extra.ensureDirSync(dirPath);
|
||||
try {
|
||||
const libFiles = fs.readdirSync(dirPath);
|
||||
for (let value of libFiles) {
|
||||
if (value !== fileName) {
|
||||
fs.unlinkSync(path.join(dirPath, value));
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
console.log(e);
|
||||
}
|
||||
var pyFileArr = [];
|
||||
pyFileArr = BU.searchLibs(dirPath, code, pyFileArr);
|
||||
return pyFileArr;
|
||||
}
|
||||
|
||||
/**
|
||||
* @function 获取当前代码数据中所使用的库并检测此文件是否在库目录下存在,
|
||||
* 若存在则拷贝到主文件所在目录
|
||||
* @param dirPath {string} 主代码文件所在目录的路径
|
||||
* @param code {string} 主代码数据
|
||||
* @param libArr {array} 当前已查找出的库列表
|
||||
* @return {array} 库列表
|
||||
**/
|
||||
BU.searchLibs = function (dirPath, code, libArr) {
|
||||
const { mainStatusBarTabs } = Mixly;
|
||||
const statusBarTerminal = mainStatusBarTabs.getStatusBarById('output');
|
||||
const { upload } = SELECTED_BOARD;
|
||||
let arrayObj = new Array();
|
||||
code.trim().split("\n").forEach(function (v, i) {
|
||||
arrayObj.push(v);
|
||||
});
|
||||
let moduleName = "";
|
||||
let pyFileArr = [];
|
||||
for (let i = 0; i < arrayObj.length; i++) {
|
||||
let fromLoc = arrayObj[i].indexOf("from");
|
||||
let importLoc = arrayObj[i].indexOf("import");
|
||||
const str = arrayObj[i].substring(0, (fromLoc === -1)? importLoc : fromLoc);
|
||||
str.split('').forEach((ch) => {
|
||||
if (ch !== ' ' && ch !== '\t') {
|
||||
fromLoc = -1;
|
||||
importLoc = -1;
|
||||
return;
|
||||
}
|
||||
});
|
||||
if (fromLoc !== -1) {
|
||||
moduleName = arrayObj[i].substring(fromLoc + 4, arrayObj[i].indexOf("import"));
|
||||
} else if (importLoc !== -1) {
|
||||
let endPos = arrayObj[i].indexOf("as");
|
||||
if (endPos === -1) {
|
||||
endPos = arrayObj[i].length;
|
||||
}
|
||||
moduleName = arrayObj[i].substring(importLoc + 6, endPos);
|
||||
} else {
|
||||
continue;
|
||||
}
|
||||
moduleName = moduleName.replaceAll(' ', '');
|
||||
moduleName = moduleName.replaceAll('\r', '');
|
||||
let moduleArr = moduleName.split(",");
|
||||
for (let j = 0; j < moduleArr.length; j++) {
|
||||
if (!libArr.includes(moduleArr[j] + '.py') && !libArr.includes(moduleArr[j] + '.mpy')) {
|
||||
try {
|
||||
let oldLibPath = null;
|
||||
if (!(upload.libPath && upload.libPath.length))
|
||||
return;
|
||||
for (let nowDirPath of upload.libPath) {
|
||||
const nowMpyFilePath = path.join(nowDirPath, moduleArr[j] + '.mpy');
|
||||
const nowPyFilePath = path.join(nowDirPath, moduleArr[j] + '.py');
|
||||
if (fs_plus.isFileSync(nowMpyFilePath)) {
|
||||
oldLibPath = nowMpyFilePath;
|
||||
break;
|
||||
} else if (fs_plus.isFileSync(nowPyFilePath)) {
|
||||
oldLibPath = nowPyFilePath;
|
||||
}
|
||||
}
|
||||
if (oldLibPath) {
|
||||
const extname = path.extname(oldLibPath);
|
||||
const newLibPath = path.join(dirPath, moduleArr[j] + extname);
|
||||
statusBarTerminal.addValue(Msg.Lang['shell.copyLib'] + ' ' + moduleArr[j] + '\n');
|
||||
fs.copyFileSync(oldLibPath, newLibPath);
|
||||
libArr.push(moduleArr[j] + extname);
|
||||
if (extname === '.py') {
|
||||
pyFileArr.push(moduleArr[j] + extname);
|
||||
code = fs.readFileSync(oldLibPath, 'utf8');
|
||||
libArr = BU.searchLibs(dirPath, code, libArr);
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
console.log(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return libArr;
|
||||
}
|
||||
|
||||
/**
|
||||
* @function 通过cmd烧录
|
||||
* @param layerNum {number} 烧录或上传加载弹窗的编号,用于关闭此弹窗
|
||||
* @param port {string} 所选择的串口
|
||||
* @param command {string} 需要执行的指令
|
||||
* @return {void}
|
||||
*/
|
||||
BU.burnByCmd = function (layerNum, port, command) {
|
||||
const { mainStatusBarTabs } = Mixly;
|
||||
const statusBarTerminal = mainStatusBarTabs.getStatusBarById('output');
|
||||
statusBarTerminal.setValue(Msg.Lang['shell.burning'] + '...\n');
|
||||
BU.runCmd(layerNum, 'burn', port, command);
|
||||
}
|
||||
|
||||
/**
|
||||
* @function 通过cmd上传
|
||||
* @param layerNum {number} 烧录或上传加载弹窗的编号,用于关闭此弹窗
|
||||
* @param port {string} 所选择的串口
|
||||
* @param command {string} 需要执行的指令
|
||||
* @return {void}
|
||||
*/
|
||||
BU.uploadByCmd = async function (layerNum, port, command) {
|
||||
const { mainStatusBarTabs } = Mixly;
|
||||
const statusBarTerminal = mainStatusBarTabs.getStatusBarById('output');
|
||||
statusBarTerminal.setValue(Msg.Lang['shell.uploading'] + '...\n');
|
||||
const { upload } = SELECTED_BOARD;
|
||||
const mainWorkspace = Workspace.getMain();
|
||||
const editor = mainWorkspace.getEditorsManager().getActive();
|
||||
const code = editor.getCode();
|
||||
if (upload.copyLib) {
|
||||
BU.copyLib(upload.filePath, code);
|
||||
}
|
||||
fs_extra.outputFile(upload.filePath, code)
|
||||
.then(() => {
|
||||
BU.runCmd(layerNum, 'upload', port, command);
|
||||
})
|
||||
.catch((error) => {
|
||||
statusBarTerminal.setValue(error.toString() + '\n');
|
||||
console.log(error);
|
||||
layer.close(layerNum);
|
||||
BU.uploading = false;
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* @function 运行cmd
|
||||
* @param layerNum {number} 烧录或上传加载弹窗的编号,用于关闭此弹窗
|
||||
* @param type {string} 值为 'burn' | 'upload'
|
||||
* @param port {string} 所选择的串口
|
||||
* @param command {string} 需要执行的指令
|
||||
* @param sucFunc {function} 指令成功执行后所要执行的操作
|
||||
* @return {void}
|
||||
*/
|
||||
BU.runCmd = function (layerNum, type, port, command, sucFunc) {
|
||||
const { mainStatusBarTabs } = Mixly;
|
||||
const statusBarTerminal = mainStatusBarTabs.getStatusBarById('output');
|
||||
mainStatusBarTabs.changeTo('output');
|
||||
mainStatusBarTabs.show();
|
||||
let nowCommand = MString.tpl(command, { com: port });
|
||||
|
||||
BU.shell = child_process.exec(nowCommand, { encoding: 'binary' }, function (error, stdout, stderr) {
|
||||
layer.close(layerNum);
|
||||
BU.burning = false;
|
||||
BU.uploading = false;
|
||||
BU.shell = null;
|
||||
const text = statusBarTerminal.getValue();
|
||||
if (text.lastIndexOf('\n') !== text.length - 1) {
|
||||
statusBarTerminal.addValue('\n');
|
||||
}
|
||||
if (error) {
|
||||
if (Env.currentPlatform === 'win32') {
|
||||
error = iconv_lite.decode(Buffer.from(error.message, 'binary'), 'cp936');
|
||||
} else {
|
||||
let lines = error.message.split('\n');
|
||||
for (let i in lines) {
|
||||
if (lines[i].indexOf('Command failed') !== -1) {
|
||||
continue;
|
||||
}
|
||||
lines[i] = iconv_lite.decode(Buffer.from(lines[i], 'binary'), 'utf-8');
|
||||
}
|
||||
error = lines.join('\n');
|
||||
}
|
||||
error = MString.decode(error);
|
||||
statusBarTerminal.addValue(error);
|
||||
statusBarTerminal.addValue('==' + (type === 'burn' ? Msg.Lang['shell.burnFailed'] : Msg.Lang['shell.uploadFailed']) + '==\n');
|
||||
} else {
|
||||
layer.msg((type === 'burn' ? Msg.Lang['shell.burnSucc'] : Msg.Lang['shell.uploadSucc']), {
|
||||
time: 1000
|
||||
});
|
||||
statusBarTerminal.addValue('==' + (type === 'burn' ? Msg.Lang['shell.burnSucc'] : Msg.Lang['shell.uploadSucc']) + '==\n');
|
||||
if (type === 'upload') {
|
||||
mainStatusBarTabs.show();
|
||||
mainStatusBarTabs.add('serial', port);
|
||||
const statusBarSerial = mainStatusBarTabs.getStatusBarById(port);
|
||||
statusBarSerial.setValue('');
|
||||
mainStatusBarTabs.changeTo(port);
|
||||
statusBarSerial.open().catch(Debug.error);
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
BU.shell.stdout.on('data', function (data) {
|
||||
if (BU.uploading || BU.burning) {
|
||||
data = iconv_lite.decode(Buffer.from(data, 'binary'), 'utf-8');
|
||||
data = MString.decode(data);
|
||||
statusBarTerminal.addValue(data);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* @function 特殊固件的烧录
|
||||
* @return {void}
|
||||
**/
|
||||
BU.burnWithSpecialBin = () => {
|
||||
const { mainStatusBarTabs } = Mixly;
|
||||
const statusBarTerminal = mainStatusBarTabs.getStatusBarById('output');
|
||||
const $selector = $('#mixly-selector-type');
|
||||
let oldOption = $('#mixly-selector-type option:selected').val();
|
||||
$selector.empty();
|
||||
const firmwareList = SELECTED_BOARD.burn.special;
|
||||
let firmwareObj = {};
|
||||
for (let firmware of firmwareList) {
|
||||
if (!firmware?.name && !firmware?.command) return;
|
||||
firmwareObj[firmware.name] = firmware.command;
|
||||
if (`${firmware.name}` == oldOption) {
|
||||
$selector.append($(`<option value="${firmware.name}" selected>${firmware.name}</option>`));
|
||||
} else {
|
||||
$selector.append($(`<option value="${firmware.name}">${firmware.name}</option>`));
|
||||
}
|
||||
}
|
||||
form.render();
|
||||
|
||||
let initBtnClicked = false;
|
||||
|
||||
const layerNum = layer.open({
|
||||
type: 1,
|
||||
id: "serial-select",
|
||||
title: "请选择固件:",
|
||||
area: ['350px', '150px'],
|
||||
content: $('#mixly-selector-div'),
|
||||
shade: Mixly.LayerExt.SHADE_ALL,
|
||||
resize: false,
|
||||
closeBtn: 0,
|
||||
success: function (layero) {
|
||||
$('#serial-select').css('height', '180px');
|
||||
$('#serial-select').css('overflow', 'inherit');
|
||||
$(".layui-layer-page").css("z-index", "198910151");
|
||||
$("#mixly-selector-btn1").off("click").click(() => {
|
||||
layer.close(layerNum);
|
||||
});
|
||||
$("#mixly-selector-btn2").click(() => {
|
||||
layer.close(layerNum);
|
||||
initBtnClicked = true;
|
||||
});
|
||||
},
|
||||
end: function () {
|
||||
$("#mixly-selector-btn1").off("click");
|
||||
$("#mixly-selector-btn2").off("click");
|
||||
$('#mixly-selector-div').css('display', 'none');
|
||||
$(".layui-layer-shade").remove();
|
||||
if (initBtnClicked) {
|
||||
let selectedFirmwareName = $('#mixly-selector-type option:selected').val();
|
||||
statusBarTerminal.setValue('');
|
||||
mainStatusBarTabs.changeTo('output');
|
||||
mainStatusBarTabs.show();
|
||||
BU.burning = true;
|
||||
BU.uploading = false;
|
||||
const port = Serial.getSelectedPortName();
|
||||
BU.burnWithPort(port, firmwareObj[selectedFirmwareName]);
|
||||
} else {
|
||||
layer.msg(Msg.Lang['shell.burnCanceled'], { time: 1000 });
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* @function 通过串口执行命令行烧录或上传操作
|
||||
* @param type {string} 值为 'burn' | 'upload'
|
||||
* @param port {string} 所选择的串口
|
||||
* @param command {string} 需要执行的指令
|
||||
* @return {void}
|
||||
**/
|
||||
BU.operateWithPort = (type, port, command) => {
|
||||
if (!port) {
|
||||
layer.msg(Msg.Lang['statusbar.serial.noDevice'], {
|
||||
time: 1000
|
||||
});
|
||||
BU.burning = false;
|
||||
BU.uploading = false;
|
||||
return;
|
||||
}
|
||||
const title = (type === 'burn' ? Msg.Lang['shell.burning'] : Msg.Lang['shell.uploading']) + '...';
|
||||
const operate = () => {
|
||||
const layerNum = layer.open({
|
||||
type: 1,
|
||||
title,
|
||||
content: $('#mixly-loader-div'),
|
||||
shade: LayerExt.SHADE_NAV,
|
||||
resize: false,
|
||||
closeBtn: 0,
|
||||
success: function (layero, index) {
|
||||
$(".layui-layer-page").css("z-index","198910151");
|
||||
switch (type) {
|
||||
case 'burn':
|
||||
BU.burnByCmd(index, port, command);
|
||||
break;
|
||||
case 'upload':
|
||||
default:
|
||||
BU.uploadByCmd(index, port, command);
|
||||
}
|
||||
$("#mixly-loader-btn").off("click").click(() => {
|
||||
$("#mixly-loader-btn").css('display', 'none');
|
||||
layer.title(Msg.Lang['shell.aborting'] + '...', index);
|
||||
BU.cancel(type);
|
||||
});
|
||||
},
|
||||
end: function () {
|
||||
$('#mixly-loader-div').css('display', 'none');
|
||||
$("layui-layer-shade" + layerNum).remove();
|
||||
$("#mixly-loader-btn").off("click");
|
||||
$("#mixly-loader-btn").css('display', 'inline-block');
|
||||
}
|
||||
});
|
||||
}
|
||||
const { mainStatusBarTabs } = Mixly;
|
||||
const statusBarSerial = mainStatusBarTabs.getStatusBarById(port);
|
||||
if (statusBarSerial) {
|
||||
statusBarSerial.close()
|
||||
.finally(() => {
|
||||
operate();
|
||||
});
|
||||
} else {
|
||||
operate();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @function 通过串口执行命令行烧录操作
|
||||
* @param port {string} 所选择的串口
|
||||
* @param command {string} 需要执行的指令
|
||||
* @return {void}
|
||||
**/
|
||||
BU.burnWithPort = (port, command) => {
|
||||
BU.operateWithPort('burn', port, command);
|
||||
}
|
||||
|
||||
/**
|
||||
* @function 通过串口执行命令行上传操作
|
||||
* @param port {string} 所选择的串口
|
||||
* @param command {string} 需要执行的指令
|
||||
* @return {void}
|
||||
**/
|
||||
BU.uploadWithPort = (port, command) => {
|
||||
BU.operateWithPort('upload', port, command);
|
||||
}
|
||||
|
||||
});
|
||||
115
common/modules/mixly-modules/electron/cloud-download.js
Normal file
115
common/modules/mixly-modules/electron/cloud-download.js
Normal file
@@ -0,0 +1,115 @@
|
||||
goog.loadJs('electron', () => {
|
||||
|
||||
goog.require('path');
|
||||
goog.require('Mixly.Env');
|
||||
goog.require('Mixly.MJSON');
|
||||
goog.require('Mixly.Electron');
|
||||
goog.provide('Mixly.Electron.CloudDownload');
|
||||
|
||||
const {
|
||||
Env,
|
||||
MJSON,
|
||||
Electron
|
||||
} = Mixly;
|
||||
|
||||
const { CloudDownload } = Electron;
|
||||
|
||||
const fs = Mixly.require('fs');
|
||||
const fs_plus = Mixly.require('fs-plus');
|
||||
const fs_extra = Mixly.require('fs-extra');
|
||||
const node_downloader_helper = Mixly.require('node-downloader-helper');
|
||||
|
||||
CloudDownload.getJson = (url, downloadDir, endFunc) => {
|
||||
if (url) {
|
||||
CloudDownload.download(url, downloadDir)
|
||||
.then((message) => {
|
||||
if (message[0]) {
|
||||
throw message[0];
|
||||
} else {
|
||||
let jsonObj = null;
|
||||
if (fs_plus.isFileSync(message[1])) {
|
||||
let data = fs.readFileSync(message[1], 'utf-8');
|
||||
jsonObj = MJSON.parse(data);
|
||||
}
|
||||
if (jsonObj) {
|
||||
return jsonObj;
|
||||
} else {
|
||||
throw('解析失败');
|
||||
}
|
||||
}
|
||||
|
||||
})
|
||||
.then((configObj) => {
|
||||
endFunc([null, configObj]);
|
||||
})
|
||||
.catch((error) => {
|
||||
endFunc([error, null]);
|
||||
});
|
||||
} else {
|
||||
endFunc(['url读取出错!', null]);
|
||||
}
|
||||
}
|
||||
|
||||
CloudDownload.download = (url, downloadDir, options = {}) => {
|
||||
return new Promise((resolve, reject) => {
|
||||
const DEFAULT_OPTIONS = {
|
||||
progress: null,
|
||||
end: null,
|
||||
error: null
|
||||
}
|
||||
if (typeof options !== 'object')
|
||||
options = DEFAULT_OPTIONS;
|
||||
else
|
||||
options = { ...DEFAULT_OPTIONS, ...options };
|
||||
try {
|
||||
fs_extra.ensureDirSync(downloadDir);
|
||||
const fileName = path.basename(url);
|
||||
const filePath = path.join(downloadDir, './' + fileName);
|
||||
if (fs_plus.isFileSync(filePath))
|
||||
fs_extra.removeSync(filePath);
|
||||
} catch (error) {
|
||||
resolve([error, url]);
|
||||
return;
|
||||
}
|
||||
const { DownloaderHelper } = node_downloader_helper;
|
||||
const dl = new DownloaderHelper(url, downloadDir, {
|
||||
override: true,
|
||||
timeout: 15000,
|
||||
retry: false
|
||||
});
|
||||
dl.on('progress', (stats) => {
|
||||
if (typeof options.progress === 'function') {
|
||||
options.progress(stats);
|
||||
}
|
||||
});
|
||||
dl.on('end', (downloadInfo) => {
|
||||
if (typeof options.end === 'function') {
|
||||
options.end(downloadInfo);
|
||||
}
|
||||
resolve([null, downloadInfo.filePath]);
|
||||
});
|
||||
dl.on('error', (error) => {
|
||||
console.log('Download Failed', error);
|
||||
if (typeof options.error === 'function') {
|
||||
options.error(error);
|
||||
}
|
||||
resolve([error, url]);
|
||||
});
|
||||
dl.on('timeout', () => {
|
||||
console.log('Download Timeout');
|
||||
if (typeof options.timeout === 'function') {
|
||||
options.timeout('Download Timeout');
|
||||
}
|
||||
resolve(['Download Timeout', url]);
|
||||
});
|
||||
dl.start().catch((error) => {
|
||||
console.log('Download Failed', error);
|
||||
if (typeof options.error === 'function') {
|
||||
options.error(error);
|
||||
}
|
||||
resolve([error, url]);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
});
|
||||
50
common/modules/mixly-modules/electron/electron.js
Normal file
50
common/modules/mixly-modules/electron/electron.js
Normal file
@@ -0,0 +1,50 @@
|
||||
goog.loadJs('electron', () => {
|
||||
|
||||
goog.require('path');
|
||||
goog.require('Mixly.Url');
|
||||
goog.provide('Mixly.Electron');
|
||||
|
||||
const {
|
||||
Url,
|
||||
Env,
|
||||
Electron
|
||||
} = Mixly;
|
||||
|
||||
const electron_remote = Mixly.require('@electron/remote');
|
||||
|
||||
const {
|
||||
Menu,
|
||||
BrowserWindow
|
||||
} = electron_remote;
|
||||
|
||||
Electron.newBrowserWindow = (indexPath, config = {}) => {
|
||||
Menu.setApplicationMenu(null);
|
||||
const win = new BrowserWindow({
|
||||
...{
|
||||
show: false,
|
||||
minHeight: 400,
|
||||
minWidth: 700,
|
||||
width: 0,
|
||||
height: 0,
|
||||
icon: path.join(Env.indexDirPath, '../files/mixly.ico'),
|
||||
webPreferences: {
|
||||
nodeIntegration: true,
|
||||
enableRemoteModule: true,
|
||||
contextIsolation: false,
|
||||
spellcheck: false
|
||||
}
|
||||
},
|
||||
...(config.window ?? {})
|
||||
});
|
||||
|
||||
win.loadFile(indexPath);
|
||||
|
||||
win.once('ready-to-show', () => {
|
||||
win.maximize();
|
||||
win.show();
|
||||
});
|
||||
|
||||
return win;
|
||||
}
|
||||
|
||||
});
|
||||
23
common/modules/mixly-modules/electron/events.js
Normal file
23
common/modules/mixly-modules/electron/events.js
Normal file
@@ -0,0 +1,23 @@
|
||||
goog.loadJs('electron', () => {
|
||||
|
||||
goog.require('Mixly.Command');
|
||||
goog.require('Mixly.Config');
|
||||
goog.require('Mixly.Electron');
|
||||
goog.provide('Mixly.Electron.Events');
|
||||
|
||||
const { Command, Config, Electron } = Mixly;
|
||||
const { Events } = Electron;
|
||||
const { SOFTWARE } = Config;
|
||||
|
||||
const electron = Mixly.require('electron');
|
||||
const ipcRenderer = electron.ipcRenderer;
|
||||
|
||||
ipcRenderer.on('command', (event, message) => {
|
||||
if (SOFTWARE.debug) {
|
||||
console.log('receive -> ', message);
|
||||
}
|
||||
const commandObj = Command.parse(message);
|
||||
Command.run(commandObj);
|
||||
});
|
||||
|
||||
});
|
||||
172
common/modules/mixly-modules/electron/file-tree.js
Normal file
172
common/modules/mixly-modules/electron/file-tree.js
Normal file
@@ -0,0 +1,172 @@
|
||||
goog.loadJs('electron', () => {
|
||||
|
||||
goog.require('path');
|
||||
goog.require('Mixly.FileTree');
|
||||
goog.require('Mixly.Events');
|
||||
goog.require('Mixly.Registry');
|
||||
goog.require('Mixly.Debug');
|
||||
goog.require('Mixly.Electron.FS');
|
||||
goog.provide('Mixly.Electron.FileTree');
|
||||
|
||||
const {
|
||||
FileTree,
|
||||
Events,
|
||||
Registry,
|
||||
Debug,
|
||||
Electron
|
||||
} = Mixly;
|
||||
const { FS } = Electron;
|
||||
|
||||
const chokidar = Mixly.require('chokidar');
|
||||
|
||||
class FileTreeExt extends FileTree {
|
||||
static {
|
||||
this.worker = new Worker('../common/modules/mixly-modules/workers/nodejs/node-file-watcher.js', {
|
||||
name: 'nodeFileWatcher'
|
||||
});
|
||||
this.watcherEventsRegistry = new Registry();
|
||||
this.worker.addEventListener('message', (event) => {
|
||||
const { data } = event;
|
||||
const events = this.watcherEventsRegistry.getItem(data.watcher);
|
||||
if (!events) {
|
||||
return;
|
||||
}
|
||||
events.run('change', data);
|
||||
});
|
||||
this.worker.addEventListener('error', (event) => {
|
||||
Debug.error(event);
|
||||
});
|
||||
|
||||
this.addEventListener = function(folderPath, func) {
|
||||
FileTreeExt.watch(folderPath);
|
||||
let events = this.watcherEventsRegistry.getItem(folderPath);
|
||||
if (!events) {
|
||||
events = new Events(['change']);
|
||||
this.watcherEventsRegistry.register(folderPath, events);
|
||||
}
|
||||
return events.bind('change', func);
|
||||
}
|
||||
|
||||
this.removeEventListener = function(folderPath, eventId) {
|
||||
let events = this.watcherEventsRegistry.getItem(folderPath);
|
||||
if (!events) {
|
||||
return;
|
||||
}
|
||||
if (!events.length('change')) {
|
||||
this.watcherEventsRegistry.unregister(folderPath);
|
||||
this.unwatch(folderPath);
|
||||
}
|
||||
}
|
||||
|
||||
this.watch = function(folderPath) {
|
||||
FileTreeExt.worker.postMessage({
|
||||
func: 'watch',
|
||||
args: [folderPath]
|
||||
});
|
||||
}
|
||||
|
||||
this.unwatch = function(folderPath) {
|
||||
FileTreeExt.worker.postMessage({
|
||||
func: 'unwatch',
|
||||
args: [folderPath]
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
constructor() {
|
||||
super(FS);
|
||||
this.watcher = null;
|
||||
this.watcherEventsListenerIdRegistry = new Registry();
|
||||
}
|
||||
|
||||
async readFolder(inPath) {
|
||||
let output = [];
|
||||
const fs = this.getFS();
|
||||
const status = await fs.isDirectory(inPath);
|
||||
if (!status) {
|
||||
return output;
|
||||
}
|
||||
const children = await fs.readDirectory(inPath);
|
||||
for (let data of children) {
|
||||
const dataPath = path.join(inPath, data);
|
||||
if (await fs.isDirectory(dataPath)) {
|
||||
const isDirEmtpy = await fs.isDirectoryEmpty(dataPath);
|
||||
output.push({
|
||||
type: 'folder',
|
||||
id: dataPath,
|
||||
children: !isDirEmtpy
|
||||
});
|
||||
if (isDirEmtpy) {
|
||||
this.watchEmptyFolder(dataPath);
|
||||
}
|
||||
} else {
|
||||
output.push({
|
||||
type: 'file',
|
||||
id: dataPath,
|
||||
children: false
|
||||
});
|
||||
}
|
||||
}
|
||||
return output;
|
||||
}
|
||||
|
||||
watchFolder(folderPath) {
|
||||
super.watchFolder(folderPath);
|
||||
let id = this.watcherEventsListenerIdRegistry.getItem(folderPath);
|
||||
if (id) {
|
||||
return;
|
||||
}
|
||||
id = FileTreeExt.addEventListener(folderPath, (data) => {
|
||||
if (data.event === 'unlinkDir') {
|
||||
this.unwatchFolder(path.join(data.path));
|
||||
}
|
||||
const watcherPath = path.join(data.watcher);
|
||||
if (this.isWatched(watcherPath)) {
|
||||
this.refreshFolder(watcherPath);
|
||||
}
|
||||
});
|
||||
this.watcherEventsListenerIdRegistry.register(folderPath, id);
|
||||
}
|
||||
|
||||
watchEmptyFolder(folderPath) {
|
||||
super.watchFolder(folderPath);
|
||||
let id = this.watcherEventsListenerIdRegistry.getItem(folderPath);
|
||||
if (id) {
|
||||
return;
|
||||
}
|
||||
id = FileTreeExt.addEventListener(folderPath, (data) => {
|
||||
const watcherPath = path.join(data.watcher);
|
||||
if (this.isWatched(watcherPath)) {
|
||||
this.refreshFolder(watcherPath);
|
||||
}
|
||||
if (this.isClosed(watcherPath)) {
|
||||
this.unwatchFolder(watcherPath);
|
||||
}
|
||||
});
|
||||
this.watcherEventsListenerIdRegistry.register(folderPath, id);
|
||||
}
|
||||
|
||||
unwatchFolder(folderPath) {
|
||||
const keys = this.watchRegistry.keys();
|
||||
for (let key of keys) {
|
||||
if (key.indexOf(folderPath) === -1) {
|
||||
continue;
|
||||
}
|
||||
const type = this.watchRegistry.getItem(key);
|
||||
if (type === 'file') {
|
||||
this.unwatchFile(key);
|
||||
}
|
||||
const id = this.watcherEventsListenerIdRegistry.getItem(key);
|
||||
if (!id) {
|
||||
continue;
|
||||
}
|
||||
FileTreeExt.removeEventListener(key, id);
|
||||
this.watcherEventsListenerIdRegistry.unregister(key);
|
||||
}
|
||||
super.unwatchFolder(folderPath);
|
||||
}
|
||||
}
|
||||
|
||||
Electron.FileTree = FileTreeExt;
|
||||
|
||||
});
|
||||
291
common/modules/mixly-modules/electron/file.js
Normal file
291
common/modules/mixly-modules/electron/file.js
Normal file
@@ -0,0 +1,291 @@
|
||||
goog.loadJs('electron', () => {
|
||||
|
||||
goog.require('path');
|
||||
goog.require('Blockly');
|
||||
goog.require('Mixly.Env');
|
||||
goog.require('Mixly.LayerExt');
|
||||
goog.require('Mixly.Config');
|
||||
goog.require('Mixly.Title');
|
||||
goog.require('Mixly.MFile');
|
||||
goog.require('Mixly.XML');
|
||||
goog.require('Mixly.Msg');
|
||||
goog.require('Mixly.Workspace');
|
||||
goog.require('Mixly.Electron.ArduShell');
|
||||
goog.require('Mixly.Electron.BU');
|
||||
goog.provide('Mixly.Electron.File');
|
||||
|
||||
const {
|
||||
Env,
|
||||
LayerExt,
|
||||
Config,
|
||||
Title,
|
||||
MFile,
|
||||
XML,
|
||||
Msg,
|
||||
Workspace,
|
||||
Electron
|
||||
} = Mixly;
|
||||
|
||||
const { BOARD } = Config;
|
||||
|
||||
const { ArduShell, BU, File } = Electron;
|
||||
|
||||
const fs = Mixly.require('fs');
|
||||
const fs_plus = Mixly.require('fs-plus');
|
||||
const fs_extra = Mixly.require('fs-extra');
|
||||
const fs_promise = Mixly.require('node:fs/promises');
|
||||
const electron_remote = Mixly.require('@electron/remote');
|
||||
const { dialog, app } = electron_remote;
|
||||
|
||||
const { MSG } = Blockly.Msg;
|
||||
|
||||
File.DEFAULT_PATH = path.join(app.getAppPath(), 'src/sample');
|
||||
File.workingPath = File.DEFAULT_PATH;
|
||||
File.openedFilePath = null;
|
||||
|
||||
File.userPath = {
|
||||
img: null,
|
||||
mix: null,
|
||||
code: null,
|
||||
hex: null
|
||||
}
|
||||
|
||||
File.showSaveDialog = (title, filters, endFunc) => {
|
||||
const currentWindow = electron_remote.getCurrentWindow();
|
||||
currentWindow.focus();
|
||||
dialog.showSaveDialog(currentWindow, {
|
||||
title,
|
||||
defaultPath: File.workingPath,
|
||||
filters,
|
||||
// nameFieldLabel: Msg.Lang['替换文件'],
|
||||
showsTagField: true,
|
||||
properties: ['showHiddenFiles'],
|
||||
message: title
|
||||
}).then(result => {
|
||||
let res = result.filePath;
|
||||
if (res)
|
||||
endFunc(res);
|
||||
}).catch(error => {
|
||||
console.log(error);
|
||||
});
|
||||
}
|
||||
|
||||
File.showOpenDialog = (title, filters, endFunc) => {
|
||||
const currentWindow = electron_remote.getCurrentWindow();
|
||||
currentWindow.focus();
|
||||
dialog.showOpenDialog(currentWindow, {
|
||||
title,
|
||||
defaultPath: File.workingPath,
|
||||
filters,
|
||||
properties: ['openFile', 'showHiddenFiles'],
|
||||
message: title
|
||||
})
|
||||
.then(result => {
|
||||
let res = result.filePaths[0];
|
||||
if (res)
|
||||
endFunc(res);
|
||||
})
|
||||
.catch(error => {
|
||||
console.log(error);
|
||||
});
|
||||
}
|
||||
|
||||
File.save = (endFunc = () => {}) => {
|
||||
if (File.openedFilePath) {
|
||||
const mainWorkspace = Workspace.getMain();
|
||||
const editor = mainWorkspace.getEditorsManager().getActive();
|
||||
const extname = path.extname(File.openedFilePath);
|
||||
let data = '';
|
||||
switch (extname) {
|
||||
case '.mix':
|
||||
data = editor.getValue();
|
||||
break;
|
||||
case '.py':
|
||||
case '.ino':
|
||||
data = editor.getCode();
|
||||
break;
|
||||
default:
|
||||
layer.msg(Msg.Lang['file.type.error'], { time: 1000 });
|
||||
return;
|
||||
}
|
||||
fs_extra.outputFile(File.openedFilePath, data)
|
||||
.then(() => {
|
||||
Title.updeteFilePath(File.openedFilePath);
|
||||
layer.msg(Msg.Lang['file.saveSucc'], { time: 1000 });
|
||||
})
|
||||
.catch((error) => {
|
||||
File.openedFilePath = null;
|
||||
console.log(error);
|
||||
layer.msg(Msg.Lang['file.saveFailed'], { time: 1000 });
|
||||
})
|
||||
.finally(() => {
|
||||
endFunc();
|
||||
})
|
||||
} else {
|
||||
File.saveAs(endFunc);
|
||||
}
|
||||
}
|
||||
|
||||
File.saveAs = (endFunc = () => {}) => {
|
||||
File.showSaveDialog(Msg.Lang['file.saveAs'], MFile.saveFilters, (filePath) => {
|
||||
const mainWorkspace = Workspace.getMain();
|
||||
const editor = mainWorkspace.getEditorsManager().getActive();
|
||||
const extname = path.extname(filePath);
|
||||
if (['.mix', '.py', '.ino'].includes(extname)) {
|
||||
File.openedFilePath = filePath;
|
||||
File.workingPath = path.dirname(filePath);
|
||||
File.save(endFunc);
|
||||
} else {
|
||||
switch (extname) {
|
||||
case '.bin':
|
||||
case '.hex':
|
||||
if (BOARD?.nav?.compile) {
|
||||
ArduShell.saveBinOrHex(filePath);
|
||||
} else {
|
||||
const hexStr = MFile.getHex();
|
||||
fs_extra.outputFile(filePath, hexStr)
|
||||
.then(() => {
|
||||
layer.msg(Msg.Lang['file.saveSucc'], { time: 1000 });
|
||||
})
|
||||
.catch((error) => {
|
||||
console.log(error);
|
||||
layer.msg(Msg.Lang['file.saveFailed'], { time: 1000 });
|
||||
})
|
||||
.finally(() => {
|
||||
endFunc();
|
||||
});
|
||||
}
|
||||
break;
|
||||
case '.mil':
|
||||
const milStr = editor.getMil();
|
||||
const $mil = $(milStr);
|
||||
$mil.attr('name', path.basename(filePath, '.mil'));
|
||||
fs_extra.outputFile(filePath, $mil[0].outerHTML)
|
||||
.then(() => {
|
||||
layer.msg('file.saveSucc', { time: 1000 });
|
||||
})
|
||||
.catch((error) => {
|
||||
console.log(error);
|
||||
layer.msg(Msg.Lang['file.saveFailed'], { time: 1000 });
|
||||
})
|
||||
.finally(() => {
|
||||
endFunc();
|
||||
});
|
||||
break;
|
||||
default:
|
||||
layer.msg(Msg.Lang['file.type.error'], { time: 1000 });
|
||||
endFunc();
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
File.exportLib = (endFunc = () => {}) => {
|
||||
File.showSaveDialog(Msg.Lang['file.exportAs'], [ MFile.SAVE_FILTER_TYPE.mil ], (filePath) => {
|
||||
const mainWorkspace = Workspace.getMain();
|
||||
const editor = mainWorkspace.getEditorsManager().getActive();
|
||||
const milStr = editor.getMil();
|
||||
const $mil = $(milStr);
|
||||
$mil.attr('name', path.basename(filePath, '.mil'));
|
||||
fs_extra.outputFile(filePath, $mil[0].outerHTML)
|
||||
.then(() => {
|
||||
layer.msg('file.saveSucc', { time: 1000 });
|
||||
})
|
||||
.catch((error) => {
|
||||
console.log(error);
|
||||
layer.msg(Msg.Lang['file.saveFailed'], { time: 1000 });
|
||||
})
|
||||
.finally(() => {
|
||||
endFunc();
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
File.new = () => {
|
||||
const mainWorkspace = Workspace.getMain();
|
||||
const editor = mainWorkspace.getEditorsManager().getActive();
|
||||
const blockEditor = editor.getPage('block').getEditor();
|
||||
const codeEditor = editor.getPage('code').getEditor();
|
||||
const generator = Blockly.generator;
|
||||
const blocksList = blockEditor.getAllBlocks();
|
||||
if (editor.getPageType() === 'code') {
|
||||
const code = codeEditor.getValue(),
|
||||
workspaceToCode = generator.workspaceToCode(blockEditor) || '';
|
||||
if (!blocksList.length && workspaceToCode === code) {
|
||||
layer.msg(Msg.Lang['editor.codeEditorEmpty'], { time: 1000 });
|
||||
File.openedFilePath = null;
|
||||
Title.updateTitle(Title.title);
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
if (!blocksList.length) {
|
||||
layer.msg(Msg.Lang['editor.blockEditorEmpty'], { time: 1000 });
|
||||
File.openedFilePath = null;
|
||||
Title.updateTitle(Title.title);
|
||||
return;
|
||||
}
|
||||
}
|
||||
layer.confirm(MSG['confirm_newfile'], {
|
||||
title: false,
|
||||
shade: LayerExt.SHADE_ALL,
|
||||
resize: false,
|
||||
success: (layero) => {
|
||||
const { classList } = layero[0].childNodes[1].childNodes[0];
|
||||
classList.remove('layui-layer-close2');
|
||||
classList.add('layui-layer-close1');
|
||||
},
|
||||
btn: [Msg.Lang['nav.btn.ok'], Msg.Lang['nav.btn.cancel']],
|
||||
btn2: (index, layero) => {
|
||||
layer.close(index);
|
||||
}
|
||||
}, (index, layero) => {
|
||||
layer.close(index);
|
||||
blockEditor.clear();
|
||||
blockEditor.scrollCenter();
|
||||
Blockly.hideChaff();
|
||||
codeEditor.setValue(generator.workspaceToCode(blockEditor) || '', -1);
|
||||
File.openedFilePath = null;
|
||||
Title.updateTitle(Title.title);
|
||||
});
|
||||
}
|
||||
|
||||
File.open = () => {
|
||||
File.showOpenDialog(Msg.Lang['file.open'], [
|
||||
{ name: Msg.Lang['file.type.mix'], extensions: MFile.openFilters }
|
||||
], (filePath) => {
|
||||
File.openFile(filePath);
|
||||
});
|
||||
}
|
||||
|
||||
File.openFile = (filePath) => {
|
||||
const extname = path.extname(filePath);
|
||||
let data;
|
||||
if (!fs_plus.isFileSync(filePath)) {
|
||||
console.log(filePath + '不存在');
|
||||
return;
|
||||
}
|
||||
try {
|
||||
data = fs.readFileSync(filePath, 'utf-8');
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
return;
|
||||
}
|
||||
if (['.bin', '.hex'].includes(extname)) {
|
||||
if (BOARD?.nav?.compile) {
|
||||
ArduShell.showUploadBox(filePath);
|
||||
} else {
|
||||
MFile.loadHex(data);
|
||||
}
|
||||
} else if (['.mix', '.xml', '.ino', '.py'].includes(extname)) {
|
||||
const mainWorkspace = Workspace.getMain();
|
||||
const editor = mainWorkspace.getEditorsManager().getActive();
|
||||
editor.setValue(data, extname);
|
||||
File.openedFilePath = filePath;
|
||||
File.workingPath = path.dirname(filePath);
|
||||
Title.updeteFilePath(File.openedFilePath);
|
||||
} else {
|
||||
layer.msg(Msg.Lang['file.type.error'], { time: 1000 });
|
||||
}
|
||||
}
|
||||
|
||||
});
|
||||
110
common/modules/mixly-modules/electron/footerlayer-example.js
Normal file
110
common/modules/mixly-modules/electron/footerlayer-example.js
Normal file
@@ -0,0 +1,110 @@
|
||||
goog.loadJs('electron', () => {
|
||||
|
||||
goog.require('path');
|
||||
goog.require('layui');
|
||||
goog.require('Mixly.Env');
|
||||
goog.require('Mixly.Config');
|
||||
goog.require('Mixly.MFile');
|
||||
goog.require('Mixly.Title');
|
||||
goog.require('Mixly.XML');
|
||||
goog.require('Mixly.FooterLayerExample');
|
||||
goog.require('Mixly.Electron.File');
|
||||
goog.provide('Mixly.Electron.FooterLayerExample');
|
||||
|
||||
const {
|
||||
Env,
|
||||
Config,
|
||||
MFile,
|
||||
Title,
|
||||
XML,
|
||||
FooterLayerExample,
|
||||
Electron
|
||||
} = Mixly;
|
||||
|
||||
const { dropdown, tree } = layui;
|
||||
|
||||
const { File } = Electron;
|
||||
|
||||
const { BOARD } = Config;
|
||||
|
||||
const fs = Mixly.require('fs');
|
||||
const fs_plus = Mixly.require('fs-plus');
|
||||
const electron_remote = Mixly.require('@electron/remote');
|
||||
const { app } = electron_remote;
|
||||
|
||||
class FooterLayerExampleExt extends FooterLayerExample {
|
||||
constructor(element) {
|
||||
super(element);
|
||||
}
|
||||
|
||||
getRoot() {
|
||||
let exampleList = [];
|
||||
let samplePath = path.join(Env.boardDirPath, 'examples');
|
||||
const sampleList = this.getExamplesByPath(samplePath, '.mix');
|
||||
if (sampleList.length) {
|
||||
exampleList.push({
|
||||
id: samplePath,
|
||||
title: BOARD.boardType,
|
||||
children: []
|
||||
});
|
||||
}
|
||||
const thirdPartyPath = path.join(Env.boardDirPath, 'libraries/ThirdParty');
|
||||
if (fs_plus.isDirectorySync(thirdPartyPath)) {
|
||||
const libList = fs.readdirSync(thirdPartyPath);
|
||||
for (let lib of libList) {
|
||||
const libPath = path.join(thirdPartyPath, lib);
|
||||
if (fs_plus.isFileSync(libPath))
|
||||
continue;
|
||||
const examplesPath = path.join(libPath, 'examples');
|
||||
if (fs_plus.isFileSync(examplesPath))
|
||||
continue;
|
||||
const thirdPartyList = this.getExamplesByPath(examplesPath, '.mix');
|
||||
if (thirdPartyList.length) {
|
||||
exampleList.push({
|
||||
id: examplesPath,
|
||||
title: lib,
|
||||
children: []
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
return exampleList;
|
||||
}
|
||||
|
||||
getChildren(inPath) {
|
||||
return this.getExamplesByPath(inPath, '.mix');
|
||||
}
|
||||
|
||||
dataToWorkspace(inPath) {
|
||||
if (!fs_plus.isFileSync(inPath)) {
|
||||
return;
|
||||
}
|
||||
const data = fs.readFileSync(inPath, 'utf8');
|
||||
const extname = path.extname(inPath);
|
||||
this.updateCode(extname, data);
|
||||
File.openedFilePath = null;
|
||||
}
|
||||
|
||||
getExamplesByPath(inPath, fileExtname) {
|
||||
let exampleList = [];
|
||||
if (fs_plus.isDirectorySync(inPath)) {
|
||||
const dataList = fs.readdirSync(inPath);
|
||||
for (let data of dataList) {
|
||||
const dataPath = path.join(inPath, data);
|
||||
if (fs_plus.isDirectorySync(dataPath)) {
|
||||
exampleList.push({ title: data, id: dataPath, children: [] });
|
||||
} else {
|
||||
const extname = path.extname(data);
|
||||
if (extname === fileExtname) {
|
||||
exampleList.push({ title: data, id: dataPath });
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return exampleList;
|
||||
}
|
||||
}
|
||||
|
||||
Electron.FooterLayerExample = FooterLayerExampleExt;
|
||||
|
||||
});
|
||||
153
common/modules/mixly-modules/electron/fs-board.js
Normal file
153
common/modules/mixly-modules/electron/fs-board.js
Normal file
@@ -0,0 +1,153 @@
|
||||
goog.loadJs('electron', () => {
|
||||
|
||||
goog.require('layui');
|
||||
goog.require('path');
|
||||
goog.require('Mustache');
|
||||
goog.require('Mixly.Env');
|
||||
goog.require('Mixly.FSBoard');
|
||||
goog.require('Mixly.LayerExt');
|
||||
goog.require('Mixly.Debug');
|
||||
goog.require('Mixly.Msg');
|
||||
goog.require('Mixly.Electron.Shell');
|
||||
goog.provide('Mixly.Electron.FSBoard');
|
||||
|
||||
const {
|
||||
Env,
|
||||
FSBoard,
|
||||
LayerExt,
|
||||
Debug,
|
||||
Msg,
|
||||
Electron = {}
|
||||
} = Mixly;
|
||||
|
||||
const { Shell } = Electron;
|
||||
|
||||
const fs_extra = Mixly.require('fs-extra');
|
||||
|
||||
const { layer } = layui;
|
||||
|
||||
|
||||
class FSBoardExt extends FSBoard {
|
||||
#shell_ = null;
|
||||
constructor() {
|
||||
super();
|
||||
this.#shell_ = new Shell();
|
||||
}
|
||||
|
||||
download(usrFolder, fsType) {
|
||||
return new Promise(async (resolve, reject) => {
|
||||
try {
|
||||
await super.download(usrFolder, fsType);
|
||||
} catch (error) {
|
||||
Debug.error(error);
|
||||
resolve();
|
||||
return;
|
||||
}
|
||||
const layerNum = layer.open({
|
||||
type: 1,
|
||||
title: `${Msg.Lang['shell.downloading']}...`,
|
||||
content: $('#mixly-loader-div'),
|
||||
shade: LayerExt.SHADE_NAV,
|
||||
resize: false,
|
||||
closeBtn: 0,
|
||||
success: () => {
|
||||
$("#mixly-loader-btn").off("click").click(() => {
|
||||
$("#mixly-loader-btn").css('display', 'none');
|
||||
layer.title(Msg.Lang['shell.aborting'] + '...', layerNum);
|
||||
this.cancel();
|
||||
});
|
||||
const { mainStatusBarTabs } = Mixly;
|
||||
const statusBarTerminal = mainStatusBarTabs.getStatusBarById('output');
|
||||
statusBarTerminal.setValue('');
|
||||
mainStatusBarTabs.changeTo('output');
|
||||
const commandTemplate = this.getSelectedFSDownloadCommand();
|
||||
const command = this.#renderTemplate_(commandTemplate);
|
||||
this.#shell_.exec(command)
|
||||
.then((info) => {
|
||||
if (info.code) {
|
||||
statusBarTerminal.addValue(`\n==${Msg.Lang['dushell.downloadFailed']}==\n`);
|
||||
} else {
|
||||
statusBarTerminal.addValue(`\n==${Msg.Lang['shell.burdownloadSucc']}(${Msg.Lang['shell.timeCost']} ${info.time})==\n`);
|
||||
}
|
||||
})
|
||||
.catch(Debug.error)
|
||||
.finally(() => {
|
||||
layer.close(layerNum);
|
||||
resolve();
|
||||
});
|
||||
},
|
||||
end: function () {
|
||||
$("#mixly-loader-btn").off("click");
|
||||
$("#mixly-loader-btn").css('display', 'inline-block');
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
upload(usrFolder, fsType) {
|
||||
return new Promise(async (resolve, reject) => {
|
||||
try {
|
||||
await super.upload(usrFolder, fsType);
|
||||
} catch (error) {
|
||||
Debug.error(error);
|
||||
resolve();
|
||||
return;
|
||||
}
|
||||
const layerNum = layer.open({
|
||||
type: 1,
|
||||
title: Msg.Lang['shell.uploading'] + '...',
|
||||
content: $('#mixly-loader-div'),
|
||||
shade: LayerExt.SHADE_NAV,
|
||||
resize: false,
|
||||
closeBtn: 0,
|
||||
success: () => {
|
||||
$("#mixly-loader-btn").off("click").click(() => {
|
||||
$("#mixly-loader-btn").css('display', 'none');
|
||||
layer.title('上传终止中...', layerNum);
|
||||
this.cancel();
|
||||
});
|
||||
const { mainStatusBarTabs } = Mixly;
|
||||
const statusBarTerminal = mainStatusBarTabs.getStatusBarById('output');
|
||||
statusBarTerminal.setValue('');
|
||||
mainStatusBarTabs.changeTo('output');
|
||||
const commandTemplate = this.getSelectedFSUploadCommand();
|
||||
const command = this.#renderTemplate_(commandTemplate);
|
||||
this.#shell_.exec(command)
|
||||
.then((info) => {
|
||||
if (info.code) {
|
||||
statusBarTerminal.addValue(`\n==${Msg.Lang['shell.uploadFailed']}==\n`);
|
||||
} else {
|
||||
statusBarTerminal.addValue(`\n==${Msg.Lang['shell.uploadSucc']}(${Msg.Lang['shell.timeCost']} ${info.time})==\n`);
|
||||
}
|
||||
})
|
||||
.catch(Debug.error)
|
||||
.finally(() => {
|
||||
layer.close(layerNum);
|
||||
resolve();
|
||||
});
|
||||
},
|
||||
end: function () {
|
||||
$("#mixly-loader-btn").off("click");
|
||||
$("#mixly-loader-btn").css('display', 'inline-block');
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
#renderTemplate_(template) {
|
||||
const config = this.getConfig();
|
||||
return Mustache.render(template, {
|
||||
...config,
|
||||
python3: `"${Env.python3Path}"`,
|
||||
esptool: `"${Env.python3Path}" "${path.join(Env.srcDirPath, 'tools/python/esptool/__init__.py')}"`
|
||||
});
|
||||
}
|
||||
|
||||
cancel() {
|
||||
this.#shell_ && this.#shell_.kill();
|
||||
}
|
||||
}
|
||||
|
||||
Electron.FSBoard = FSBoardExt;
|
||||
|
||||
});
|
||||
158
common/modules/mixly-modules/electron/fs.js
Normal file
158
common/modules/mixly-modules/electron/fs.js
Normal file
@@ -0,0 +1,158 @@
|
||||
goog.loadJs('electron', () => {
|
||||
|
||||
goog.require('path');
|
||||
goog.require('Mixly.Msg');
|
||||
goog.require('Mixly.Electron');
|
||||
goog.provide('Mixly.Electron.FS');
|
||||
|
||||
const { Msg, Electron } = Mixly;
|
||||
const { FS } = Electron;
|
||||
|
||||
const fs_plus = Mixly.require('fs-plus');
|
||||
const fs_extra = Mixly.require('fs-extra');
|
||||
const fs_promise = Mixly.require('node:fs/promises');
|
||||
const electron_remote = Mixly.require('@electron/remote');
|
||||
const { dialog, app } = electron_remote;
|
||||
|
||||
FS.showOpenFilePicker = () => {
|
||||
return new Promise((resolve, reject) => {
|
||||
const currentWindow = electron_remote.getCurrentWindow();
|
||||
currentWindow.focus();
|
||||
dialog.showOpenDialog(currentWindow, {
|
||||
title: Msg.Lang['file.openFile'],
|
||||
defaultPath: File.workingPath,
|
||||
filters,
|
||||
properties: ['openFile', 'showHiddenFiles'],
|
||||
message: Msg.Lang['file.openFile']
|
||||
})
|
||||
.then(result => {
|
||||
const filePath = result.filePaths[0];
|
||||
if (filePath) {
|
||||
resolve(new File(filePath));
|
||||
} else {
|
||||
reject('file not found');
|
||||
}
|
||||
})
|
||||
.catch(error => {
|
||||
reject(error);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
FS.showDirectoryPicker = () => {
|
||||
return new Promise((resolve, reject) => {
|
||||
const currentWindow = electron_remote.getCurrentWindow();
|
||||
currentWindow.focus();
|
||||
dialog.showOpenDialog(currentWindow, {
|
||||
title: Msg.Lang['file.openFolder'],
|
||||
// defaultPath: File.workingPath,
|
||||
// filters,
|
||||
properties: ['openDirectory', 'createDirectory'],
|
||||
message: Msg.Lang['file.openFolder']
|
||||
})
|
||||
.then(result => {
|
||||
const folderPath = result.filePaths[0];
|
||||
if (folderPath) {
|
||||
resolve(folderPath);
|
||||
} else {
|
||||
resolve(null);
|
||||
}
|
||||
})
|
||||
.catch(reject);
|
||||
});
|
||||
}
|
||||
|
||||
FS.showSaveFilePicker = (fileName, ext) => {
|
||||
return new Promise((resolve, reject) => {
|
||||
const currentWindow = electron_remote.getCurrentWindow();
|
||||
currentWindow.focus();
|
||||
dialog.showSaveDialog(currentWindow, {
|
||||
filters: [{
|
||||
name: Msg.Lang['file.type.mix'],
|
||||
extensions: [ext.substring(ext.lastIndexOf('.') + 1)]
|
||||
}],
|
||||
defaultPath: fileName,
|
||||
showsTagField: true,
|
||||
properties: ['showHiddenFiles']
|
||||
}).then(result => {
|
||||
let filePath = result.filePath;
|
||||
if (filePath) {
|
||||
resolve(filePath);
|
||||
} else {
|
||||
resolve(null);
|
||||
}
|
||||
}).catch(reject);
|
||||
});
|
||||
}
|
||||
|
||||
FS.createFile = (filePath) => {
|
||||
return fs_extra.ensureFile(filePath);
|
||||
}
|
||||
|
||||
FS.readFile = (filePath) => {
|
||||
return fs_promise.readFile(filePath, { encoding: 'utf8' });
|
||||
}
|
||||
|
||||
FS.writeFile = (filePath, data) => {
|
||||
return fs_promise.writeFile(filePath, data, { encoding: 'utf8' });
|
||||
}
|
||||
|
||||
FS.isFile = (filePath) => {
|
||||
return new Promise((resolve, reject) => {
|
||||
resolve(fs_plus.isFileSync(filePath));
|
||||
});
|
||||
}
|
||||
|
||||
FS.renameFile = (oldFilePath, newFilePath) => {
|
||||
return fs_promise.rename(oldFilePath, newFilePath);
|
||||
}
|
||||
|
||||
FS.moveFile = (oldFilePath, newFilePath) => {
|
||||
return fs_extra.move(oldFilePath, newFilePath, { overwrite: true });
|
||||
}
|
||||
|
||||
FS.copyFile = (oldFilePath, newFilePath) => {
|
||||
return fs_extra.copy(oldFilePath, newFilePath);
|
||||
}
|
||||
|
||||
FS.deleteFile = (filePath) => {
|
||||
return fs_extra.remove(filePath);
|
||||
}
|
||||
|
||||
FS.createDirectory = (folderPath) => {
|
||||
return fs_extra.ensureDir(folderPath);
|
||||
}
|
||||
|
||||
FS.readDirectory = (folderPath) => {
|
||||
return fs_promise.readdir(folderPath);
|
||||
}
|
||||
|
||||
FS.isDirectory = (folderPath) => {
|
||||
return new Promise((resolve, reject) => {
|
||||
fs_plus.isDirectory(folderPath, (status) => {
|
||||
resolve(status);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
FS.isDirectoryEmpty = async (folderPath) => {
|
||||
return !(await FS.readDirectory(folderPath)).length;
|
||||
}
|
||||
|
||||
FS.renameDirectory = (oldFolderPath, newFolderPath) => {
|
||||
return fs_promise.rename(oldFolderPath, newFolderPath);
|
||||
}
|
||||
|
||||
FS.moveDirectory = (oldFolderPath, newFolderPath) => {
|
||||
return fs_extra.move(oldFolderPath, newFolderPath, { overwrite: true });
|
||||
}
|
||||
|
||||
FS.copyDirectory = (oldFolderPath, newFolderPath) => {
|
||||
return fs_extra.copy(oldFolderPath, newFolderPath);
|
||||
}
|
||||
|
||||
FS.deleteDirectory = (folderPath) => {
|
||||
return fs_extra.remove(folderPath);
|
||||
}
|
||||
|
||||
});
|
||||
1186
common/modules/mixly-modules/electron/lib-manager.js
Normal file
1186
common/modules/mixly-modules/electron/lib-manager.js
Normal file
File diff suppressed because it is too large
Load Diff
63
common/modules/mixly-modules/electron/loader.js
Normal file
63
common/modules/mixly-modules/electron/loader.js
Normal file
@@ -0,0 +1,63 @@
|
||||
goog.loadJs('electron', () => {
|
||||
|
||||
goog.require('Mixly.Url');
|
||||
goog.require('Mixly.Config');
|
||||
goog.require('Mixly.Env');
|
||||
goog.require('Mixly.Electron.Serial');
|
||||
goog.require('Mixly.Electron.PythonShell');
|
||||
goog.require('Mixly.Electron.Events');
|
||||
goog.provide('Mixly.Electron.Loader');
|
||||
|
||||
const {
|
||||
Url,
|
||||
Config,
|
||||
Env,
|
||||
Electron
|
||||
} = Mixly;
|
||||
|
||||
const { BOARD } = Config;
|
||||
|
||||
const {
|
||||
Serial,
|
||||
PythonShell,
|
||||
Loader
|
||||
} = Electron;
|
||||
|
||||
|
||||
Loader.onbeforeunload = function(reload = false) {
|
||||
const pageReload = (href) => {
|
||||
if (!reload) {
|
||||
window.location.replace(href);
|
||||
} else {
|
||||
window.location.reload(true);
|
||||
}
|
||||
}
|
||||
let href = Config.pathPrefix + 'index.html?' + Url.jsonToUrl({ boardType: BOARD.boardType ?? 'None' });
|
||||
let endPromise = [];
|
||||
const { mainStatusBarTabs } = Mixly;
|
||||
Serial.getCurrentPortsName().map((name) => {
|
||||
const statusBarSerial = mainStatusBarTabs.getStatusBarById(name);
|
||||
if (statusBarSerial) {
|
||||
endPromise.push(statusBarSerial.close());
|
||||
}
|
||||
});
|
||||
endPromise.push(PythonShell.stop());
|
||||
Promise.all(endPromise)
|
||||
.finally(() => {
|
||||
pageReload(href);
|
||||
});
|
||||
};
|
||||
|
||||
Loader.closePort = (serialport) => {
|
||||
return new Promise((resolve, reject) => {
|
||||
serialport.close(() => {
|
||||
resolve();
|
||||
});
|
||||
})
|
||||
}
|
||||
|
||||
Loader.reload = () => {
|
||||
Loader.onbeforeunload(true);
|
||||
}
|
||||
|
||||
});
|
||||
214
common/modules/mixly-modules/electron/python-shell.js
Normal file
214
common/modules/mixly-modules/electron/python-shell.js
Normal file
@@ -0,0 +1,214 @@
|
||||
goog.loadJs('electron', () => {
|
||||
|
||||
goog.require('dayjs.duration');
|
||||
goog.require('Mixly.Env');
|
||||
goog.require('Mixly.Msg');
|
||||
goog.require('Mixly.Debug');
|
||||
goog.require('Mixly.Workspace');
|
||||
goog.require('Mixly.MString');
|
||||
goog.require('Mixly.Electron');
|
||||
goog.provide('Mixly.Electron.PythonShell');
|
||||
|
||||
const {
|
||||
Env,
|
||||
Msg,
|
||||
Debug,
|
||||
Workspace,
|
||||
MString,
|
||||
Electron
|
||||
} = Mixly;
|
||||
|
||||
const iconv_lite = Mixly.require('iconv-lite');
|
||||
const python_shell = Mixly.require('python-shell');
|
||||
const child_process = Mixly.require('node:child_process');
|
||||
const fs_extra = Mixly.require('fs-extra');
|
||||
|
||||
|
||||
class PythonShell {
|
||||
static {
|
||||
this.ENCODING = Env.currentPlatform == 'win32' ? 'cp936' : 'utf-8';
|
||||
this.pythonShell = null;
|
||||
|
||||
this.init = function () {
|
||||
this.pythonShell = new PythonShell();
|
||||
}
|
||||
|
||||
this.run = function () {
|
||||
const mainWorkspace = Workspace.getMain();
|
||||
const editor = mainWorkspace.getEditorsManager().getActive();
|
||||
const code = editor.getCode();
|
||||
return this.pythonShell.run(code);
|
||||
}
|
||||
|
||||
this.stop = function () {
|
||||
return this.pythonShell.stop();
|
||||
}
|
||||
}
|
||||
|
||||
#shell_ = null;
|
||||
#statusBarTerminal_ = null;
|
||||
#options_ = {
|
||||
pythonPath: Env.python3Path,
|
||||
pythonOptions: ['-u'],
|
||||
encoding: 'binary',
|
||||
mode: 'utf-8'
|
||||
};
|
||||
#cursor_ = {
|
||||
row: 0,
|
||||
column: 0
|
||||
};
|
||||
#prompt_ = '';
|
||||
#waittingForInput_ = false;
|
||||
#running_ = false;
|
||||
#onCursorChangeEvent_ = () => this.#onCursorChange_();
|
||||
#commands_ = [
|
||||
{
|
||||
name: 'REPL-Enter',
|
||||
bindKey: 'Enter',
|
||||
exec: (editor) => {
|
||||
const session = editor.getSession();
|
||||
const cursor = session.selection.getCursor();
|
||||
if (cursor.row === this.#cursor_.row) {
|
||||
const newPos = this.#statusBarTerminal_.getEndPos();
|
||||
let str = this.#statusBarTerminal_.getValueRange(this.#cursor_, newPos);
|
||||
str = str.replace(this.#prompt_, '');
|
||||
this.#shell_.stdin.write(escape(str) + '\n');
|
||||
this.#statusBarTerminal_.addValue('\n');
|
||||
this.#exitInput_();
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}, {
|
||||
name: 'REPL-ChangeEditor',
|
||||
bindKey: 'Delete|Ctrl-X|Backspace',
|
||||
exec: (editor) => {
|
||||
const session = editor.getSession();
|
||||
const cursor = session.selection.getCursor();
|
||||
if (cursor.row < this.#cursor_.row || cursor.column <= this.#cursor_.column) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
];
|
||||
constructor(config) {
|
||||
Object.assign(this.#options_, config);
|
||||
const { mainStatusBarTabs } = Mixly;
|
||||
this.#statusBarTerminal_ = mainStatusBarTabs.getStatusBarById('output');
|
||||
}
|
||||
|
||||
#addEventsListener_() {
|
||||
const { stdout, stderr } = this.#shell_;
|
||||
stdout.setEncoding('binary');
|
||||
stdout.on('data', (data) => {
|
||||
data = iconv_lite.decode(Buffer.from(data, 'binary'), PythonShell.ENCODING);
|
||||
data = MString.decode(data);
|
||||
this.#statusBarTerminal_.addValue(data);
|
||||
const keyIndex = data.lastIndexOf('>>>');
|
||||
if (keyIndex !== -1) {
|
||||
this.#prompt_ = data.substring(keyIndex);
|
||||
setTimeout(() => this.#enterInput_(), 500);
|
||||
}
|
||||
});
|
||||
stderr.setEncoding('binary');
|
||||
stderr.on('data', (data) => {
|
||||
data = iconv_lite.decode(Buffer.from(data, 'binary'), PythonShell.ENCODING);
|
||||
data = MString.decode(data);
|
||||
data = data.replace(/(?<![\w+])__import__\("pyinput"\).input\(/g, 'input(');
|
||||
this.#statusBarTerminal_.addValue(data);
|
||||
});
|
||||
}
|
||||
|
||||
#onCursorChange_() {
|
||||
const editor = this.#statusBarTerminal_.getEditor();
|
||||
const session = editor.getSession();
|
||||
const cursor = session.selection.getCursor();
|
||||
editor.setReadOnly(
|
||||
cursor.row < this.#cursor_.row || cursor.column < this.#cursor_.column
|
||||
);
|
||||
}
|
||||
|
||||
#enterInput_() {
|
||||
if (!this.#running_) {
|
||||
return;
|
||||
}
|
||||
this.#waittingForInput_ = true;
|
||||
this.#cursor_ = this.#statusBarTerminal_.getEndPos();
|
||||
const editor = this.#statusBarTerminal_.getEditor();
|
||||
editor.setReadOnly(false);
|
||||
editor.focus();
|
||||
const session = editor.getSession();
|
||||
session.selection.on('changeCursor', this.#onCursorChangeEvent_);
|
||||
editor.commands.addCommands(this.#commands_);
|
||||
}
|
||||
|
||||
#exitInput_() {
|
||||
this.#waittingForInput_ = false;
|
||||
const editor = this.#statusBarTerminal_.getEditor();
|
||||
const session = editor.getSession();
|
||||
session.selection.off('changeCursor', this.#onCursorChangeEvent_);
|
||||
editor.commands.removeCommands(this.#commands_);
|
||||
this.#prompt_ = '';
|
||||
this.cursor_ = { row: 0, column: 0 };
|
||||
editor.setReadOnly(true);
|
||||
}
|
||||
|
||||
run(code) {
|
||||
this.stop()
|
||||
.then(() => {
|
||||
try {
|
||||
code = code.replace(/(?<![\w+])input\(/g, '__import__("pyinput").input(');
|
||||
if (code.indexOf('import turtle') !== -1) {
|
||||
code += '\nturtle.done()\n';
|
||||
}
|
||||
} catch (error) {
|
||||
Debug.error(error);
|
||||
}
|
||||
const { mainStatusBarTabs } = Mixly;
|
||||
mainStatusBarTabs.changeTo('output');
|
||||
mainStatusBarTabs.show();
|
||||
return fs_extra.outputFile(Env.pyFilePath, code, 'utf8');
|
||||
})
|
||||
.then(() => {
|
||||
this.#statusBarTerminal_.setValue(`${Msg.Lang['shell.running']}...\n`);
|
||||
const startTime = Number(new Date());
|
||||
this.#shell_ = new python_shell.PythonShell(Env.pyFilePath, this.#options_);
|
||||
this.#running_ = true;
|
||||
this.#addEventsListener_();
|
||||
this.#shell_.on('close', (code) => {
|
||||
this.#running_ = false;
|
||||
const endTime = Number(new Date());
|
||||
const duration = dayjs.duration(endTime - startTime).format('HH:mm:ss.SSS');
|
||||
this.#statusBarTerminal_.addValue(`\n==${Msg.Lang['shell.finish']}(${Msg.Lang['shell.timeCost']} ${duration})==`);
|
||||
});
|
||||
})
|
||||
.catch(Debug.error);
|
||||
}
|
||||
|
||||
stop() {
|
||||
return new Promise((resolve, reject) => {
|
||||
if (this.#waittingForInput_) {
|
||||
this.#exitInput_();
|
||||
}
|
||||
if (this.#running_) {
|
||||
this.#shell_.childProcess.on('exit', () => {
|
||||
resolve();
|
||||
});
|
||||
this.#shell_.stdin.end();
|
||||
this.#shell_.stdout.end();
|
||||
if (Env.currentPlatform === 'win32') {
|
||||
child_process.exec(`taskkill /pid ${this.#shell_.childProcess.pid} /f /t`);
|
||||
} else {
|
||||
this.#shell_.kill('SIGTERM');
|
||||
}
|
||||
} else {
|
||||
resolve();
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
Electron.PythonShell = PythonShell;
|
||||
|
||||
});
|
||||
260
common/modules/mixly-modules/electron/serial.js
Normal file
260
common/modules/mixly-modules/electron/serial.js
Normal file
@@ -0,0 +1,260 @@
|
||||
goog.loadJs('electron', () => {
|
||||
|
||||
goog.require('layui');
|
||||
goog.require('Mixly.Serial');
|
||||
goog.require('Mixly.Env');
|
||||
goog.require('Mixly.Msg');
|
||||
goog.require('Mixly.Debug');
|
||||
goog.require('Mixly.Electron');
|
||||
goog.provide('Mixly.Electron.Serial');
|
||||
|
||||
const lodash_fp = Mixly.require('lodash/fp');
|
||||
const child_process = Mixly.require('node:child_process');
|
||||
const serialport = Mixly.require('serialport');
|
||||
|
||||
const {
|
||||
SerialPort,
|
||||
ReadlineParser,
|
||||
ByteLengthParser
|
||||
} = serialport;
|
||||
|
||||
const {
|
||||
Serial,
|
||||
Env,
|
||||
Msg,
|
||||
Debug,
|
||||
Electron
|
||||
} = Mixly;
|
||||
|
||||
const { form } = layui;
|
||||
|
||||
|
||||
class ElectronSerial extends Serial {
|
||||
static {
|
||||
this.getConfig = function () {
|
||||
return Serial.getConfig();
|
||||
}
|
||||
|
||||
this.getSelectedPortName = function () {
|
||||
return Serial.getSelectedPortName();
|
||||
}
|
||||
|
||||
this.getCurrentPortsName = function () {
|
||||
return Serial.getCurrentPortsName();
|
||||
}
|
||||
|
||||
this.getPorts = async function () {
|
||||
return new Promise((resolve, reject) => {
|
||||
if (Env.currentPlatform === 'linux') {
|
||||
child_process.exec('ls /dev/ttyACM* /dev/ttyUSB* /dev/tty*USB*', (err, stdout, stderr) => {
|
||||
let portsName = stdout.split('\n');
|
||||
let newPorts = [];
|
||||
for (let i = 0; i < portsName.length; i++) {
|
||||
if (!portsName[i]) {
|
||||
continue;
|
||||
}
|
||||
newPorts.push({
|
||||
vendorId: 'None',
|
||||
productId: 'None',
|
||||
name: portsName[i]
|
||||
});
|
||||
}
|
||||
resolve(newPorts);
|
||||
});
|
||||
} else {
|
||||
SerialPort.list().then(ports => {
|
||||
let newPorts = [];
|
||||
for (let i = 0; i < ports.length; i++) {
|
||||
let port = ports[i];
|
||||
newPorts.push({
|
||||
vendorId: port.vendorId,
|
||||
productId: port.productId,
|
||||
name: port.path
|
||||
});
|
||||
}
|
||||
resolve(newPorts);
|
||||
}).catch(reject);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
this.refreshPorts = function () {
|
||||
this.getPorts()
|
||||
.then((ports) => {
|
||||
Serial.renderSelectBox(ports);
|
||||
})
|
||||
.catch(Debug.error);
|
||||
}
|
||||
}
|
||||
|
||||
#serialport_ = null;
|
||||
#parserBytes_ = null;
|
||||
#parserLine_ = null;
|
||||
|
||||
constructor(port) {
|
||||
super(port);
|
||||
}
|
||||
|
||||
#addEventsListener_() {
|
||||
this.#parserBytes_.on('data', (buffer) => {
|
||||
this.onBuffer(buffer);
|
||||
});
|
||||
|
||||
this.#parserLine_.on('data', (str) => {
|
||||
this.onString(str);
|
||||
});
|
||||
|
||||
this.#serialport_.on('error', (error) => {
|
||||
this.onError(error);
|
||||
this.onClose(1);
|
||||
});
|
||||
|
||||
this.#serialport_.on('open', () => {
|
||||
this.onOpen();
|
||||
});
|
||||
|
||||
this.#serialport_.on('close', () => {
|
||||
this.onClose(1);
|
||||
});
|
||||
}
|
||||
|
||||
async open(baud) {
|
||||
return new Promise((resolve, reject) => {
|
||||
const portsName = Serial.getCurrentPortsName();
|
||||
const currentPort = this.getPortName();
|
||||
if (!portsName.includes(currentPort)) {
|
||||
reject('无可用串口');
|
||||
return;
|
||||
}
|
||||
if (this.isOpened()) {
|
||||
resolve();
|
||||
return;
|
||||
}
|
||||
baud = baud ?? this.getBaudRate();
|
||||
|
||||
this.#serialport_ = new SerialPort({
|
||||
path: currentPort,
|
||||
baudRate: baud, // 波特率
|
||||
dataBits: 8, // 数据位
|
||||
parity: 'none', // 奇偶校验
|
||||
stopBits: 1, // 停止位
|
||||
flowControl: false,
|
||||
autoOpen: false // 不自动打开
|
||||
}, false);
|
||||
this.#parserBytes_ = this.#serialport_.pipe(new ByteLengthParser({ length: 1 }));
|
||||
this.#parserLine_ = this.#serialport_.pipe(new ReadlineParser());
|
||||
this.#serialport_.open((error) => {
|
||||
if (error) {
|
||||
this.onError(error);
|
||||
reject(error);
|
||||
} else {
|
||||
super.open(baud);
|
||||
this.setBaudRate(baud);
|
||||
resolve();
|
||||
}
|
||||
});
|
||||
this.#addEventsListener_();
|
||||
});
|
||||
}
|
||||
|
||||
async close() {
|
||||
return new Promise((resolve, reject) => {
|
||||
if (!this.isOpened()) {
|
||||
resolve();
|
||||
return;
|
||||
}
|
||||
super.close();
|
||||
this.#serialport_.close((error) => {
|
||||
if (error) {
|
||||
reject(error);
|
||||
} else {
|
||||
resolve();
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
async setBaudRate(baud) {
|
||||
return new Promise((resolve, reject) => {
|
||||
if (!this.isOpened() || this.getBaudRate() === baud) {
|
||||
resolve();
|
||||
return;
|
||||
}
|
||||
this.#serialport_.update({ baudRate: baud - 0 }, (error) => {
|
||||
if (error) {
|
||||
reject(error);
|
||||
} else {
|
||||
super.setBaudRate(baud);
|
||||
resolve();
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
async send(data) {
|
||||
return new Promise((resolve, reject) => {
|
||||
if (!this.isOpened()) {
|
||||
resolve();
|
||||
return;
|
||||
}
|
||||
this.#serialport_.write(data, (error) => {
|
||||
if (error) {
|
||||
reject(error);
|
||||
} else {
|
||||
resolve();
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
async sendString(str) {
|
||||
return this.send(str);
|
||||
}
|
||||
|
||||
async sendBuffer(buffer) {
|
||||
return this.send(buffer);
|
||||
}
|
||||
|
||||
async setDTRAndRTS(dtr, rts) {
|
||||
return new Promise((resolve, reject) => {
|
||||
if (!this.isOpened()) {
|
||||
resolve();
|
||||
return;
|
||||
}
|
||||
this.#serialport_.set({ dtr, rts }, (error) => {
|
||||
if (error) {
|
||||
reject(error);
|
||||
} else {
|
||||
super.setDTRAndRTS(dtr, rts);
|
||||
resolve();
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
async setDTR(dtr) {
|
||||
return this.setDTRAndRTS(dtr, this.getRTS());
|
||||
}
|
||||
|
||||
async setRTS(rts) {
|
||||
return this.setDTRAndRTS(this.getDTR(), rts);
|
||||
}
|
||||
|
||||
onBuffer(buffer) {
|
||||
super.onBuffer(buffer);
|
||||
for (let i = 0; i < buffer.length; i++) {
|
||||
super.onByte(buffer[i]);
|
||||
}
|
||||
const string = this.decodeBuffer(buffer);
|
||||
if (!string) {
|
||||
return;
|
||||
}
|
||||
for (let char of string) {
|
||||
super.onChar(char);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Electron.Serial = ElectronSerial;
|
||||
|
||||
});
|
||||
88
common/modules/mixly-modules/electron/shell.js
Normal file
88
common/modules/mixly-modules/electron/shell.js
Normal file
@@ -0,0 +1,88 @@
|
||||
goog.loadJs('electron', () => {
|
||||
|
||||
goog.require('dayjs.duration');
|
||||
goog.require('Mixly.Env');
|
||||
goog.require('Mixly.MString');
|
||||
goog.require('Mixly.Electron');
|
||||
goog.provide('Mixly.Electron.Shell');
|
||||
|
||||
const {
|
||||
Env,
|
||||
MString,
|
||||
Electron
|
||||
} = Mixly;
|
||||
|
||||
const child_process = Mixly.require('node:child_process');
|
||||
const iconv_lite = Mixly.require('iconv-lite');
|
||||
|
||||
class Shell {
|
||||
static {
|
||||
this.ENCODING = Env.currentPlatform == 'win32' ? 'cp936' : 'utf-8';
|
||||
}
|
||||
|
||||
#shell_ = null;
|
||||
constructor() {}
|
||||
|
||||
#addEventsListener_() {
|
||||
const { mainStatusBarTabs } = Mixly;
|
||||
const statusBarTerminal = mainStatusBarTabs.getStatusBarById('output');
|
||||
const { stdout, stderr } = this.#shell_;
|
||||
stdout.on('data', (data) => {
|
||||
if (data.length > 1000) {
|
||||
return;
|
||||
}
|
||||
data = iconv_lite.decode(Buffer.from(data, 'binary'), 'utf-8');
|
||||
statusBarTerminal.addValue(data);
|
||||
});
|
||||
stderr.on('data', (data) => {
|
||||
let lines = data.split('\n');
|
||||
for (let i in lines) {
|
||||
let encoding = 'utf-8';
|
||||
if (lines[i].indexOf('can\'t open device') !== -1) {
|
||||
encoding = Shell.ENCODING;
|
||||
}
|
||||
lines[i] = iconv_lite.decode(Buffer.from(lines[i], 'binary'), encoding);
|
||||
}
|
||||
data = lines.join('\n');
|
||||
data = MString.decode(data);
|
||||
statusBarTerminal.addValue(data);
|
||||
});
|
||||
}
|
||||
|
||||
exec(command) {
|
||||
return new Promise((resolve, reject) => {
|
||||
const { mainStatusBarTabs } = Mixly;
|
||||
const statusBarTerminal = mainStatusBarTabs.getStatusBarById('output');
|
||||
const startTime = Number(new Date());
|
||||
this.#shell_ = child_process.exec(command, {
|
||||
maxBuffer: 4096 * 1000000,
|
||||
encoding: 'binary'
|
||||
});
|
||||
this.#addEventsListener_();
|
||||
this.#shell_.on('close', (code) => {
|
||||
const endTime = Number(new Date());
|
||||
const duration = dayjs.duration(endTime - startTime).format('HH:mm:ss.SSS');
|
||||
const info = { code, time: duration };
|
||||
resolve(info);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
kill() {
|
||||
this.#shell_.stdin.end();
|
||||
this.#shell_.stdout.end();
|
||||
if (Env.currentPlatform === 'win32') {
|
||||
child_process.exec(`taskkill /pid ${this.#shell_.pid} /f /t`);
|
||||
} else {
|
||||
this.#shell_.kill('SIGTERM');
|
||||
}
|
||||
}
|
||||
|
||||
getShell() {
|
||||
return this.#shell_;
|
||||
}
|
||||
}
|
||||
|
||||
Mixly.Electron.Shell = Shell;
|
||||
|
||||
});
|
||||
154
common/modules/mixly-modules/electron/wiki-generator.js
Normal file
154
common/modules/mixly-modules/electron/wiki-generator.js
Normal file
@@ -0,0 +1,154 @@
|
||||
goog.loadJs('electron', () => {
|
||||
|
||||
goog.require('path');
|
||||
goog.require('Blockly');
|
||||
goog.require('Mustache');
|
||||
goog.require('Mixly.Env');
|
||||
goog.require('Mixly.IdGenerator');
|
||||
goog.require('Mixly.Electron');
|
||||
goog.provide('Mixly.Electron.WikiGenerator');
|
||||
|
||||
const {
|
||||
Env,
|
||||
IdGenerator,
|
||||
Electron
|
||||
} = Mixly;
|
||||
const fs_extra = Mixly.require('fs-extra');
|
||||
const fs = Mixly.require('fs');
|
||||
|
||||
class WikiGenerator {
|
||||
static {
|
||||
this.WIKI_PAGE_FILE = goog.get(path.join(Env.templatePath, 'markdown/wiki-page-file.md'));
|
||||
this.WIKI_PAGE_DIR = goog.get(path.join(Env.templatePath, 'markdown/wiki-page-dir.md'));
|
||||
}
|
||||
|
||||
#$xml_ = null;
|
||||
#desPath_ = '';
|
||||
#tree_ = [];
|
||||
constructor($xml, desPath) {
|
||||
this.#$xml_ = $xml;
|
||||
this.#desPath_ = desPath;
|
||||
this.workspace = Mixly.Workspace.getMain().getEditorsManager().getActive().getPage('block').getEditor();
|
||||
this.generator = Mixly.Workspace.getMain().getEditorsManager().getActive().getPage('block').generator;
|
||||
}
|
||||
|
||||
buildTree($nodes) {
|
||||
let output = [];
|
||||
for (let i = 0; i < $nodes.length; i++) {
|
||||
let child = {};
|
||||
child.id = $nodes[i].getAttribute('id') ?? IdGenerator.generate();
|
||||
if ($nodes[i].nodeName == 'CATEGORY') {
|
||||
child.name = $nodes[i].getAttribute('name') ?? child.id;
|
||||
child.children = this.buildTree($($nodes[i]).children());
|
||||
output.push(child);
|
||||
} else if ($nodes[i].nodeName == 'BLOCK') {
|
||||
child.name = $nodes[i].getAttribute('type') ?? child.id;
|
||||
child.children = false;
|
||||
child.xml = $nodes[i].outerHTML;
|
||||
output.push(child);
|
||||
}
|
||||
}
|
||||
return output;
|
||||
}
|
||||
|
||||
async generate() {
|
||||
let output = this.buildTree(this.#$xml_.children());
|
||||
fs_extra.ensureDirSync('./outputBlock/');
|
||||
fs_extra.emptyDirSync('./outputBlock/');
|
||||
let info = await this.generateWikiPage('./outputBlock/', {
|
||||
title: 'Micropython ESP32',
|
||||
order: 1
|
||||
}, output);
|
||||
fs_extra.outputFileSync(path.join('./outputBlock/', 'README.md'), info.md);
|
||||
}
|
||||
|
||||
generateImage(desPath) {
|
||||
return new Promise((resolve, reject) => {
|
||||
Blockly.Screenshot.workspaceToSvg_(this.workspace, (datauri) => {
|
||||
const base64 = datauri.replace(/^data:image\/\w+;base64,/, '');
|
||||
const dataBuffer = new Buffer(base64, 'base64');
|
||||
fs_extra.outputFile(desPath, dataBuffer, (error) => {
|
||||
resolve(error);
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
async generateWikiPage(rootPath, parentInfo, nodes) {
|
||||
let config = {
|
||||
id: parentInfo.id,
|
||||
title: parentInfo.title,
|
||||
order: parentInfo.order
|
||||
};
|
||||
let blocksNum = 0;
|
||||
for (let node of nodes) {
|
||||
if (!node.children) {
|
||||
blocksNum += 1;
|
||||
}
|
||||
}
|
||||
config.index = !!blocksNum;
|
||||
if (blocksNum) {
|
||||
let blocks = [];
|
||||
for (let node of nodes) {
|
||||
if (node.children) {
|
||||
continue;
|
||||
}
|
||||
this.workspace.clear();
|
||||
let xmlNode = Blockly.utils.xml.textToDom(`<xml>${node.xml}</xml>`);
|
||||
Blockly.Xml.domToWorkspace(xmlNode, this.workspace);
|
||||
let code = this.generator.workspaceToCode(this.workspace) || '';
|
||||
code = code.replace(/(_E[0-9A-F]{1}_[0-9A-F]{2}_[0-9A-F]{2})+/g, function (s) {
|
||||
try {
|
||||
return decodeURIComponent(s.replace(/_/g, '%'));
|
||||
} catch (error) {
|
||||
return s;
|
||||
}
|
||||
});
|
||||
await this.generateImage(path.join(rootPath, '../assets', parentInfo.title, node.id + '.png'))
|
||||
blocks.push({
|
||||
imgPath: `./assets/${parentInfo.title}/${node.id}.png`,
|
||||
code: code,
|
||||
type: node.name
|
||||
});
|
||||
}
|
||||
config.blocks = blocks;
|
||||
return {
|
||||
file: true,
|
||||
md: Mustache.render(WikiGenerator.WIKI_PAGE_FILE, config)
|
||||
};
|
||||
} else {
|
||||
let index = 0;
|
||||
for (let i in nodes) {
|
||||
if (!nodes[i].children) {
|
||||
continue;
|
||||
}
|
||||
index += 1;
|
||||
let order = String(index * 5);
|
||||
let mdIndex = Array(4 - Math.min(order.length, 4)).fill('0').join('') + order;
|
||||
let desPath = path.join(rootPath, mdIndex + '-' + nodes[i].id);
|
||||
let info = await this.generateWikiPage(desPath, {
|
||||
id: nodes[i].id,
|
||||
title: nodes[i].name,
|
||||
order: order,
|
||||
}, nodes[i].children);
|
||||
if (info.file) {
|
||||
fs_extra.outputFileSync(`${desPath}.md`, info.md);
|
||||
} else if (!info.isEmpty) {
|
||||
fs_extra.outputFileSync(path.join(desPath, 'README.md'), info.md);
|
||||
} else if (info.isEmpty) {
|
||||
index -= 1;
|
||||
}
|
||||
}
|
||||
config.blocks = [];
|
||||
return {
|
||||
file: false,
|
||||
isEmpty: !index,
|
||||
md: Mustache.render(WikiGenerator.WIKI_PAGE_DIR, config)
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Electron.WikiGenerator = WikiGenerator;
|
||||
|
||||
});
|
||||
284
common/modules/mixly-modules/electron/wiki-manager.js
Normal file
284
common/modules/mixly-modules/electron/wiki-manager.js
Normal file
@@ -0,0 +1,284 @@
|
||||
goog.loadJs('electron', () => {
|
||||
|
||||
goog.require('path');
|
||||
goog.require('Mixly.Config');
|
||||
goog.require('Mixly.Env');
|
||||
goog.require('Mixly.Msg');
|
||||
goog.require('Mixly.Electron');
|
||||
goog.provide('Mixly.Electron.WikiManager');
|
||||
|
||||
const {
|
||||
Config,
|
||||
Env,
|
||||
Msg,
|
||||
Electron
|
||||
} = Mixly;
|
||||
|
||||
const { WikiManager } = Electron;
|
||||
|
||||
const { BOARD } = Config;
|
||||
|
||||
const fs = Mixly.require('fs');
|
||||
const fs_plus = Mixly.require('fs-plus');
|
||||
const fs_extra = Mixly.require('fs-extra');
|
||||
const path = Mixly.require('path');
|
||||
const json2md = Mixly.require('json2md');
|
||||
const electron_localshortcut = Mixly.require('electron-localshortcut');
|
||||
const electron_remote = Mixly.require('@electron/remote');
|
||||
const { ipcMain } = electron_remote;
|
||||
|
||||
|
||||
class WikiPage {
|
||||
constructor(indexPath, gotoInfo = null) {
|
||||
this.gotoInfo = gotoInfo;
|
||||
this.updateContentFile();
|
||||
this.win = Electron.newBrowserWindow(indexPath);
|
||||
this.isDestroyed = false;
|
||||
this.addReceiveCommandEvent();
|
||||
this.addLocalShortcutEvent();
|
||||
this.win.on('close', () => {
|
||||
this.isDestroyed = true;
|
||||
});
|
||||
$(window).unload(() => {
|
||||
if (!this.isDestroyed)
|
||||
this.win.close();
|
||||
});
|
||||
}
|
||||
|
||||
addLocalShortcutEvent() {
|
||||
//打开或关闭开发者工具
|
||||
electron_localshortcut.register(this.win, 'CmdOrCtrl+Shift+I', () => {
|
||||
if (!this.isDestroyed)
|
||||
this.win.webContents.toggleDevTools();
|
||||
});
|
||||
|
||||
//重载页面
|
||||
electron_localshortcut.register(this.win, 'CmdOrCtrl+R', () => {
|
||||
this.reload();
|
||||
});
|
||||
}
|
||||
|
||||
addReceiveCommandEvent() {
|
||||
ipcMain.on('command', (event, command) => {
|
||||
if (typeof command !== 'object') return;
|
||||
|
||||
switch (command.func) {
|
||||
case 'getPath':
|
||||
this.updateWiki();
|
||||
break;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
sendCommand(command) {
|
||||
if (this.isDestroyed || typeof command !== 'object') return;
|
||||
this.win.webContents.send('command', command);
|
||||
}
|
||||
|
||||
reload() {
|
||||
if (!this.isDestroyed) {
|
||||
this.updateContentFile();
|
||||
this.win.reload();
|
||||
}
|
||||
}
|
||||
|
||||
getPagePath(contentPath, contentList) {
|
||||
if (typeof contentList !== 'object' || !contentPath.length) return null;
|
||||
if (contentPath.length === 1) {
|
||||
for (let key in contentList) {
|
||||
const child = contentList[key];
|
||||
if (child?.link?.title !== contentPath[0]) {
|
||||
continue;
|
||||
}
|
||||
const { title, source } = child.link;
|
||||
if (title !== contentPath[0] || typeof source !== 'string') {
|
||||
return null;
|
||||
}
|
||||
try {
|
||||
const filePath = source.match(/(?<=(\?file=))[^\s]*/g);
|
||||
if (filePath?.length) {
|
||||
return filePath[0];
|
||||
}
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
return null;
|
||||
} else {
|
||||
for (let key in contentList) {
|
||||
const child = contentList[key];
|
||||
if (child
|
||||
&& child.length === 2
|
||||
&& child[0].h5 === contentPath[0]) {
|
||||
let childPath = [ ...contentPath ];
|
||||
childPath.shift();
|
||||
return this.getPagePath(childPath, child[1].ul);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
goto(pageList, scrollPos) {
|
||||
const args = [];
|
||||
const pagePath = this.getPagePath(pageList, this.contentList);
|
||||
if (!pageList) return;
|
||||
args.push(pagePath);
|
||||
scrollPos && args.push(scrollPos);
|
||||
this.sendCommand({
|
||||
func: 'goto',
|
||||
args
|
||||
});
|
||||
this.win.focus();
|
||||
}
|
||||
|
||||
updateContentFile() {
|
||||
const wikiContentPath = path.join(Env.boardDirPath, 'wiki/content.md');
|
||||
const defaultWikiPath = path.join(Env.boardDirPath, 'wiki/wiki-libs/' + Msg.nowLang);
|
||||
const wikiHomePagePath = path.join(defaultWikiPath, 'home');
|
||||
const thirdPartyLibsPath = path.join(Env.boardDirPath, 'libraries/ThirdParty/');
|
||||
const changelogPath = path.join(Env.clientPath, 'CHANGELOG');
|
||||
const wikiList = [];
|
||||
if (fs_plus.isFileSync(wikiHomePagePath + '.md'))
|
||||
wikiList.push({
|
||||
h4: {
|
||||
link: {
|
||||
title: Msg.Lang['wiki.home'],
|
||||
source: '?file=' + encodeURIComponent(wikiHomePagePath)
|
||||
}
|
||||
}
|
||||
});
|
||||
if (fs_plus.isDirectorySync(defaultWikiPath)) {
|
||||
const childContentList = this.getContentJson(defaultWikiPath, BOARD.boardType);
|
||||
if (childContentList)
|
||||
wikiList.push(childContentList);
|
||||
}
|
||||
if (fs_plus.isDirectorySync(thirdPartyLibsPath)) {
|
||||
const libsName = fs.readdirSync(thirdPartyLibsPath);
|
||||
for (let name of libsName) {
|
||||
const libWikiPath = path.join(thirdPartyLibsPath, name , 'wiki', Msg.nowLang);
|
||||
if (fs_plus.isDirectorySync(libWikiPath)) {
|
||||
const childContentList = this.getContentJson(libWikiPath, name);
|
||||
if (childContentList) {
|
||||
wikiList.push(childContentList);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
this.contentList = wikiList;
|
||||
try {
|
||||
const md = json2md(wikiList);
|
||||
const lineList = md.split('\n');
|
||||
for (let i = 0; i < lineList.length; i++) {
|
||||
if (!lineList[i].replaceAll(' ', '')) {
|
||||
lineList.splice(i, 1);
|
||||
i--;
|
||||
} else {
|
||||
if (!lineList[i].indexOf('#####'))
|
||||
lineList[i] = '\n' + lineList[i];
|
||||
}
|
||||
}
|
||||
fs_extra.outputFile(wikiContentPath, lineList.join('\n'));
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
}
|
||||
}
|
||||
|
||||
updateWiki() {
|
||||
const args = [
|
||||
{
|
||||
default: path.join(Env.boardDirPath, 'wiki/wiki-libs/'),
|
||||
thirdParty: path.join(Env.boardDirPath, 'libraries/ThirdParty/'),
|
||||
content: path.join(Env.boardDirPath, 'wiki/content.md')
|
||||
}
|
||||
];
|
||||
if (this.gotoInfo) {
|
||||
const { page, scrollPos } = this.gotoInfo;
|
||||
const pagePath = this.getPagePath(this.gotoInfo.page, this.contentList);
|
||||
if (pagePath) {
|
||||
const goto = [];
|
||||
goto.push(pagePath);
|
||||
scrollPos && goto.push(scrollPos);
|
||||
args[0].goto = goto;
|
||||
}
|
||||
this.gotoInfo = null;
|
||||
}
|
||||
this.sendCommand({
|
||||
func: 'setPath',
|
||||
args
|
||||
});
|
||||
}
|
||||
|
||||
getContentJson(dirPath, title = null) {
|
||||
const dirNameList = path.basename(dirPath).split('-');
|
||||
if (dirNameList.length !== 2 && !title) return null;
|
||||
const contentList = [];
|
||||
contentList.push({ h5: title ?? dirNameList[1] });
|
||||
contentList.push({ ul: [] });
|
||||
const { ul } = contentList[1];
|
||||
const keyList = fs.readdirSync(dirPath);
|
||||
for (let key of keyList) {
|
||||
const nowPath = path.join(dirPath, key);
|
||||
if (fs_plus.isDirectorySync(nowPath)) {
|
||||
const childContentList = this.getContentJson(nowPath);
|
||||
if (childContentList && childContentList[1].ul.length)
|
||||
ul.push(childContentList);
|
||||
} else {
|
||||
const extname = path.extname(key);
|
||||
if (extname !== '.md') continue;
|
||||
const fileNameList = path.basename(key, '.md').split('-');
|
||||
if (fileNameList.length !== 2) continue;
|
||||
const newPath = path.join(path.dirname(nowPath), path.basename(key, '.md'));
|
||||
ul.push({ link: { title: fileNameList[1], source: '?file=' + encodeURIComponent(newPath) + ' \"' + fileNameList[1] + '\"' } });
|
||||
}
|
||||
}
|
||||
return contentList;
|
||||
}
|
||||
}
|
||||
|
||||
WikiManager.WikiPage = WikiPage;
|
||||
|
||||
WikiManager.openWiki = (gotoInfo) => {
|
||||
const goto = (gotoInfo && typeof gotoInfo === 'object') ? gotoInfo[Msg.nowLang] : null;
|
||||
if (!WikiManager.wiki || WikiManager.wiki.isDestroyed) {
|
||||
const wikiPath = path.join(Env.indexDirPath, '../common/wiki/index.html');
|
||||
if (fs_plus.isFileSync(wikiPath)) {
|
||||
WikiManager.wiki = new WikiPage(wikiPath, goto);
|
||||
} else {
|
||||
layer.msg(Msg.Lang['wiki.pageNotFound'], { time: 1000 });
|
||||
}
|
||||
} else {
|
||||
const { win } = WikiManager.wiki;
|
||||
win && win.focus();
|
||||
if (goto) {
|
||||
const { page, scrollPos } = goto;
|
||||
WikiManager.wiki.goto(page, scrollPos);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
WikiManager.registerContextMenu = () => {
|
||||
const openWikiPage = {
|
||||
displayText: Msg.Lang['wiki.open'],
|
||||
preconditionFn: function(scope) {
|
||||
const { wiki } = scope.block;
|
||||
if (typeof wiki === 'object') {
|
||||
if (typeof wiki[Msg.nowLang] === 'object'
|
||||
&& typeof wiki[Msg.nowLang].page === 'object') {
|
||||
return 'enabled';
|
||||
}
|
||||
}
|
||||
return 'hidden';
|
||||
},
|
||||
callback: function(scope) {
|
||||
const { wiki } = scope.block;
|
||||
WikiManager.openWiki(wiki);
|
||||
},
|
||||
scopeType: Blockly.ContextMenuRegistry.ScopeType.BLOCK,
|
||||
id: 'wiki_open',
|
||||
weight: 200
|
||||
};
|
||||
Blockly.ContextMenuRegistry.registry.register(openWikiPage);
|
||||
}
|
||||
|
||||
});
|
||||
Reference in New Issue
Block a user