merge: 合并master到develop

This commit is contained in:
王立帮
2025-05-18 00:17:23 +08:00
89 changed files with 4695 additions and 860 deletions

View File

@@ -41,10 +41,11 @@ class ContextMenu {
}
}
#selector_ = null;
#menus_ = new Registry();
#events_ = new Events(['getMenu']);
constructor(selector, config = {}) {
this.selector = selector;
this.#selector_ = selector;
this.menu = $.contextMenu({
selector,
build: ($trigger) => {
@@ -67,20 +68,20 @@ class ContextMenu {
return ContextMenu.generate(menu, $trigger, e);
}
dispose() {
$.contextMenu('destroy', this.selector);
this.#events_.reset();
this.#menus_.reset();
this.menu = null;
this.selector = null;
}
show() {
$(this.selector).contextMenu();
$(this.#selector_).contextMenu();
}
hide() {
$(this.selector).contextMenu('hide');
$(this.#selector_).contextMenu('hide');
}
dispose() {
$.contextMenu('destroy', this.#selector_);
this.#events_.reset();
this.#menus_.reset();
this.menu = null;
this.#selector_ = null;
}
bind(type, func) {

View File

@@ -1,35 +1,66 @@
goog.loadJs('common', () => {
goog.require('tippy');
goog.require('Mixly.IdGenerator');
goog.require('Mixly.ContextMenu');
goog.provide('Mixly.DropdownMenu');
const { ContextMenu } = Mixly;
const {
IdGenerator,
ContextMenu
} = Mixly;
class DropdownMenu extends ContextMenu {
#contextMenu_ = null;
#layer_ = null;
static {
this.$container = $('<div class="mixly-dropdown-menus"></div>');
$(document.body).append(this.$container);
}
#shown_ = false;
constructor(selector) {
#layer_ = null;
#contextMenuId_ = '';
#$contextMenuElem_ = null;
constructor(elem) {
const layer = tippy(elem, {
allowHTML: true,
content: '',
trigger: 'click',
interactive: true,
maxWidth: 'none',
offset: [0, 0],
appendTo: document.body,
arrow: false,
placement: 'bottom-start',
delay: 0,
duration: [0, 0],
onCreate: (instance) => {
$(instance.popper).addClass('mixly-drapdown-menu');
},
onMount: () => {
this.show();
},
onHide: () => {
this.#shown_ && this.hide();
}
});
const contextMenuId = IdGenerator.generate();
const selector = `body > .mixly-dropdown-menus > div[m-id="${contextMenuId}"]`;
super(selector, {
trigger: 'none',
appendTo: $(layer.popper).children().children(),
zIndex: 1001,
position: (opt) => {
opt.$menu.css({
top: 0,
left: 0,
position: 'relative',
margin: 0
});
opt.$menu.css('margin', 0);
},
events: {
show: (opt) => {
opt.$menu.detach();
$('body > .mixly-drapdown-menu > .tippy-box > .tippy-content').empty().append(opt.$menu);
this.#layer_.setProps({});
show: () => {
this.#shown_ = true;
this.#layer_.setProps({});
},
hide: (opt) => {
hide: () => {
this.#shown_ = false;
if (this.#layer_.state.isShown) {
this.#layer_.hide();
@@ -38,33 +69,26 @@ class DropdownMenu extends ContextMenu {
}
});
this.#layer_ = tippy($(selector)[0], {
allowHTML: true,
content: '',
trigger: 'click',
interactive: true,
maxWidth: 'none',
offset: [ 0, 0 ],
appendTo: document.body,
arrow: false,
placement: 'bottom-start',
delay: 0,
duration: [ 0, 0 ],
onCreate: (instance) => {
$(instance.popper).addClass('mixly-drapdown-menu');
},
onMount: (instance) => {
this.show();
},
onHide: () => {
this.#shown_ && this.hide();
}
});
this.#$contextMenuElem_ = $(`<div m-id="${contextMenuId}"><div>`);
DropdownMenu.$container.append(this.#$contextMenuElem_);
this.#contextMenuId_ = contextMenuId;
this.#layer_ = layer;
}
show() {
this.#$contextMenuElem_.contextMenu();
}
hide() {
this.#$contextMenuElem_.contextMenu('hide');
}
dispose() {
super.dispose();
this.#layer_.destroy();
this.#layer_ = null;
this.#$contextMenuElem_.remove();
this.#$contextMenuElem_ = null;
}
}

View File

@@ -60,7 +60,7 @@ class EditorBlockly extends EditorBase {
this.initBlockly = () => {
const DEFAULT_CATEGORIES = HTMLTemplate.get('xml/default-categories.xml').render();
const media = path.join(Env.srcDirPath, 'common/media/blockly');
const renderer = ['geras', 'zelos'].includes(USER.blockRenderer) ? USER.blockRenderer : 'geras';
const renderer = ['geras', 'zelos', 'thrasos'].includes(USER.blockRenderer) ? USER.blockRenderer : 'geras';
this.editor = Blockly.inject(this.$blockly[0], {
media,
toolbox: DEFAULT_CATEGORIES,

View File

@@ -22,7 +22,7 @@ Env.hasSocketServer = false;
Env.hasCompiler = false;
/**
* 获取当前mixly2.0的路径
* 获取当前mixly的路径
* @type {String}
*/
Env.clientPath = null;

View File

@@ -16,7 +16,8 @@ goog.require('Mixly.Debug');
goog.require('Mixly.API2');
goog.require('Mixly.Electron.LibManager');
goog.require('Mixly.Electron.File');
goog.require('Mixly.WebSocket.Socket');
goog.require('Mixly.WebCompiler.Loader');
goog.require('Mixly.WebSocket.Loader');
goog.provide('Mixly.Loader');
const {
@@ -35,16 +36,20 @@ const {
API2,
Electron = {},
Web = {},
WebCompiler = {},
WebSocket = {}
} = Mixly;
const { LibManager, File } = goog.isElectron? Electron : Web;
const { Socket } = WebSocket;
window.addEventListener('load', () => {
if (!goog.isElectron && Env.hasSocketServer) {
Socket.init();
if (!goog.isElectron) {
if (Env.hasSocketServer) {
WebSocket.Loader.init();
} else if (Env.hasCompiler) {
WebCompiler.Loader.init();
}
}
const app = new App($('body')[0]);
Mixly.app = app;

View File

@@ -7,9 +7,9 @@ goog.provide('Mixly.LocalStorage');
const { MArray, LocalStorage } = Mixly;
LocalStorage.PATH = {
USER: 'mixly2.0/user',
BOARD: 'mixly2.0/boards/{{d.boardType}}/user',
THIRD_PARTY: 'mixly2.0/boards/{{d.boardType}}/third_party/{{d.thirdPartyName}}'
USER: 'mixly3.0/user',
BOARD: 'mixly3.0/boards/{{d.boardType}}/user',
THIRD_PARTY: 'mixly3.0/boards/{{d.boardType}}/third_party/{{d.thirdPartyName}}'
};
LocalStorage.set = function (path, value) {

View File

@@ -0,0 +1,91 @@
goog.loadJs('common', () => {
goog.require('io');
goog.require('Mixly');
goog.provide('Mixly.Socket');
class Socket {
#socket_ = null;
constructor(path, option) {
this.#socket_ = io(path, option);
}
#detectStatus_(status, callback) {
window.setTimeout(() => {
if (status.finished) {
return;
}
if (this.isConnected()) {
this.#detectStatus_(status, callback);
} else {
callback({
error: 'socket is not connected'
});
status.finished = true;
}
}, 1000);
}
emit(eventName, ...args) {
const callback = args.pop();
if (this.isConnected()) {
let emitStatus = {
finished: false
};
let status = this.#socket_.emit(eventName, ...args, (...callbackArgs) => {
if (emitStatus.finished) {
return;
}
emitStatus.finished = true;
callback(...callbackArgs);
});
this.#detectStatus_(emitStatus, callback);
return status;
} else {
callback({
error: 'socket is not connected'
});
return false;
}
}
async emitAsync(eventName, ...args) {
return new Promise((resolve, reject) => {
if (this.isConnected()) {
const callback = (...callbackArgs) => {
if (callbackArgs[0].error) {
reject(callbackArgs[0].error);
return;
}
resolve(...callbackArgs);
}
let emitStatus = {
finished: false
};
let status = this.#socket_.emit(eventName, ...args, (...callbackArgs) => {
if (emitStatus.finished) {
return;
}
emitStatus.finished = true;
callback(...callbackArgs);
});
this.#detectStatus_(emitStatus, callback);
} else {
reject('socket is not connected');
}
})
}
getSocket() {
return this.#socket_;
}
isConnected() {
return this.#socket_?.connected;
}
}
Mixly.Socket = Socket;
});

View File

@@ -86,6 +86,7 @@ class StatusBarsManager extends PagesManager {
#shown_ = false;
#dropdownMenu_ = null;
#$dropdownBtn_ = null;
constructor(element) {
const managerHTMLTemplate = HTMLTemplate.get('html/statusbar/statusbars-manager.html');
@@ -103,6 +104,7 @@ class StatusBarsManager extends PagesManager {
this.tabId = tabHTMLTemplate.id;
this.id = IdGenerator.generate();
this.addEventsType(['show', 'hide', 'onSelectMenu', 'getMenu']);
this.#$dropdownBtn_ = $tab.find('.statusbar-dropdown-menu > button');
$tab.find('.statusbar-close > button').click(() => this.hide());
this.#addDropdownMenu_();
this.#addEventsListener_();
@@ -146,7 +148,6 @@ class StatusBarsManager extends PagesManager {
}
#addDropdownMenu_() {
const selector = `div[m-id="${this.tabId}"] > .statusbar-dropdown-menu > .layui-btn`;
let menu = new Menu();
let serialChildMenu = new Menu(true);
menu.add({
@@ -240,7 +241,7 @@ class StatusBarsManager extends PagesManager {
}
return result;
});
this.#dropdownMenu_ = new DropdownMenu(selector);
this.#dropdownMenu_ = new DropdownMenu(this.#$dropdownBtn_[0]);
this.#dropdownMenu_.register('menu', menu);
this.#dropdownMenu_.bind('getMenu', () => 'menu');
}
@@ -278,6 +279,9 @@ class StatusBarsManager extends PagesManager {
dispose() {
StatusBarsManager.remove(this);
this.#dropdownMenu_.dispose();
this.#$dropdownBtn_.remove();
this.#$dropdownBtn_ = null;
super.dispose();
}
}

View File

@@ -49,6 +49,7 @@
"Mixly.Web.FS",
"Mixly.Web.File",
"Mixly.Web.Serial",
"Mixly.WebCompiler.ArduShell",
"Mixly.WebSocket.File",
"Mixly.WebSocket.Serial",
"Mixly.WebSocket.ArduShell",
@@ -190,6 +191,7 @@
"path": "/common/dropdown-menu.js",
"require": [
"tippy",
"Mixly.IdGenerator",
"Mixly.ContextMenu"
],
"provide": [
@@ -652,7 +654,8 @@
"Mixly.API2",
"Mixly.Electron.LibManager",
"Mixly.Electron.File",
"Mixly.WebSocket.Socket"
"Mixly.WebCompiler.Loader",
"Mixly.WebSocket.Loader"
],
"provide": [
"Mixly.Loader"
@@ -918,6 +921,16 @@
"Mixly.RightSideBarsManager"
]
},
{
"path": "/common/socket.js",
"require": [
"io",
"Mixly"
],
"provide": [
"Mixly.Socket"
]
},
{
"path": "/common/statusbar-ampy.js",
"require": [
@@ -1428,6 +1441,7 @@
{
"path": "/electron/loader.js",
"require": [
"path",
"Mixly.Url",
"Mixly.Config",
"Mixly.Env",
@@ -1552,10 +1566,9 @@
"FSWrapper",
"DAPWrapper",
"PartialFlashing",
"ESPTool",
"esptooljs",
"AdafruitESPTool",
"CryptoJS",
"AvrUploader",
"Mixly.Env",
"Mixly.LayerExt",
"Mixly.Config",
@@ -1712,19 +1725,38 @@
]
},
{
"path": "/web-compiler/compiler.js",
"path": "/web-compiler/arduino-shell.js",
"require": [
"Mixly.Url",
"Mixly.Config",
"Mixly.LayerExt",
"layui",
"avrbro",
"esptooljs",
"AdafruitESPTool",
"CryptoJS",
"dayjs.duration",
"Mixly.Boards",
"Mixly.MFile",
"Mixly.Debug",
"Mixly.LayerExt",
"Mixly.Msg",
"Mixly.Web.BU",
"Mixly.Web.Serial"
"Mixly.Workspace",
"Mixly.LayerProgress",
"Mixly.Web.Serial",
"Mixly.WebCompiler"
],
"provide": [
"Mixly.WebCompiler.Compiler"
"Mixly.WebCompiler.ArduShell"
]
},
{
"path": "/web-compiler/loader.js",
"require": [
"Mixly.Debug",
"Mixly.Config",
"Mixly.StatusBarsManager",
"Mixly.Socket",
"Mixly.WebCompiler.ArduShell"
],
"provide": [
"Mixly.WebCompiler.Loader"
]
},
{
@@ -1810,6 +1842,22 @@
"Mixly.WebSocket.File"
]
},
{
"path": "/web-socket/loader.js",
"require": [
"Mixly.Debug",
"Mixly.Config",
"Mixly.StatusBarsManager",
"Mixly.Socket",
"Mixly.WebSocket.Serial",
"Mixly.WebSocket.ArduShell",
"Mixly.WebSocket.BU",
"Mixly.WebSocket.Ampy"
],
"provide": [
"Mixly.WebSocket.Loader"
]
},
{
"path": "/web-socket/serial.js",
"require": [
@@ -1824,25 +1872,9 @@
"Mixly.WebSocket.Serial"
]
},
{
"path": "/web-socket/socket.js",
"require": [
"Mixly.Debug",
"Mixly.StatusBarsManager",
"Mixly.WebSocket",
"Mixly.WebSocket.Serial",
"Mixly.WebSocket.ArduShell",
"Mixly.WebSocket.BU",
"Mixly.WebSocket.Ampy"
],
"provide": [
"Mixly.WebSocket.Socket"
]
},
{
"path": "/web-socket/web-socket.js",
"require": [
"io",
"Mixly"
],
"provide": [

View File

@@ -1,5 +1,6 @@
goog.loadJs('electron', () => {
goog.require('path');
goog.require('Mixly.Url');
goog.require('Mixly.Config');
goog.require('Mixly.Env');
@@ -32,7 +33,7 @@ Loader.onbeforeunload = function(reload = false) {
window.location.reload(true);
}
}
let href = Env.srcDirPath + '/index.html?' + Url.jsonToUrl({ boardType: BOARD.boardType ?? 'None' });
let href = path.join(Env.srcDirPath, 'index.html') + '?' + Url.jsonToUrl({ boardType: BOARD.boardType ?? 'None' });
let endPromise = [];
const { mainStatusBarTabs } = Mixly;
Serial.getCurrentPortsName().map((name) => {

View File

@@ -0,0 +1,399 @@
goog.loadJs('web', () => {
goog.require('layui');
goog.require('avrbro');
goog.require('esptooljs');
goog.require('AdafruitESPTool');
goog.require('CryptoJS');
goog.require('dayjs.duration');
goog.require('Mixly.Boards');
goog.require('Mixly.Debug');
goog.require('Mixly.LayerExt');
goog.require('Mixly.Msg');
goog.require('Mixly.Workspace');
goog.require('Mixly.LayerProgress');
goog.require('Mixly.Web.Serial');
goog.require('Mixly.WebCompiler');
goog.provide('Mixly.WebCompiler.ArduShell');
const {
Boards,
Debug,
LayerExt,
Msg,
Workspace,
LayerProgress,
Web,
WebCompiler
} = Mixly;
const { Serial } = Web;
const { layer } = layui;
const { ESPLoader, Transport } = esptooljs;
function hexToBinaryString (hex) {
let binaryString = '';
for (let i = 0; i < hex.length; i += 2) {
const byte = parseInt(hex.substr(i, 2), 16);
binaryString += String.fromCharCode(byte);
}
return binaryString;
}
class WebCompilerArduShell {
static {
this.mixlySocket = null;
this.socket = null;
this.shell = null;
this.getSocket = function () {
return this.socket;
}
this.getMixlySocket = function () {
return this.mixlySocket;
}
this.init = function (mixlySocket) {
this.mixlySocket = mixlySocket;
this.socket = mixlySocket.getSocket();
this.shell = new WebCompilerArduShell();
const socket = this.socket;
socket.on('arduino.dataEvent', (data) => {
if (data.length > 1000) {
return;
}
const { mainStatusBarTabs } = Mixly;
const statusBarTerminal = mainStatusBarTabs.getStatusBarById('output');
statusBarTerminal.addValue(data);
});
socket.on('arduino.errorEvent', (data) => {
const { mainStatusBarTabs } = Mixly;
const statusBarTerminal = mainStatusBarTabs.getStatusBarById('output');
try {
data = unescape(data.replace(/(_E[0-9A-F]{1}_[0-9A-F]{2}_[0-9A-F]{2})+/gm, '%$1'));
data = unescape(data.replace(/\\(u[0-9a-fA-F]{4})/gm, '%$1'));
} catch (error) {
Debug.error(error);
}
statusBarTerminal.addValue(data);
});
}
this.initCompile = function () {
if (!this.mixlySocket.isConnected()) {
layer.msg(Msg.Lang['websocket.offline'], { time: 1000 });
return;
}
const { mainStatusBarTabs } = Mixly;
const statusBarTerminal = mainStatusBarTabs.getStatusBarById('output');
mainStatusBarTabs.changeTo('output');
mainStatusBarTabs.show();
const mainWorkspace = Workspace.getMain();
const editor = mainWorkspace.getEditorsManager().getActive();
const code = editor.getCode();
statusBarTerminal.setValue(`${Msg.Lang['shell.compiling']}...\n`);
this.shell.compile(code)
.then((info) => {
this.endCallback(info.code, info.time);
})
.catch((error) => {
Debug.error(error);
statusBarTerminal.addValue(`\n==${Msg.Lang['shell.compileFailed']}==\n`);
});
}
this.initUpload = function () {
if (!this.mixlySocket.isConnected()) {
layer.msg(Msg.Lang['websocket.offline'], { time: 1000 });
return;
}
const port = Serial.getSelectedPortName();
if (!port) {
layer.msg(Msg.Lang['statusbar.serial.noDevice'], {
time: 1000
});
return;
}
const { mainStatusBarTabs } = Mixly;
const statusBarTerminal = mainStatusBarTabs.getStatusBarById('output');
mainStatusBarTabs.changeTo('output');
mainStatusBarTabs.show();
statusBarTerminal.setValue(`${Msg.Lang['shell.uploading']}...\n`);
const mainWorkspace = Workspace.getMain();
const editor = mainWorkspace.getEditorsManager().getActive();
const code = editor.getCode();
const statusBarSerial = mainStatusBarTabs.getStatusBarById(port);
const closePromise = statusBarSerial ? statusBarSerial.close() : Promise.resolve();
closePromise
.then(() => {
return this.shell.upload(port, code)
})
.then((info) => {
this.endCallback(info.code, info.time);
if (info.code || !Serial.portIsLegal(port)) {
return;
}
mainStatusBarTabs.add('serial', port);
mainStatusBarTabs.changeTo(port);
const statusBarSerial = mainStatusBarTabs.getStatusBarById(port);
statusBarSerial.open()
.then(() => {
const baudRates = code.match(/(?<=Serial.begin[\s]*\([\s]*)[0-9]*(?=[\s]*\))/g);
if (!baudRates?.length) {
return statusBarSerial.setBaudRate(9600);
} else {
return statusBarSerial.setBaudRate(baudRates[0] - 0);
}
})
.catch(Debug.error);
})
.catch((error) => {
Debug.error(error);
statusBarTerminal.addValue(`\n==${Msg.Lang['shell.uploadFailed']}==\n`);
});
}
this.endCallback = function (code, time) {
const { mainStatusBarTabs } = Mixly;
const statusBarTerminal = mainStatusBarTabs.getStatusBarById('output');
mainStatusBarTabs.changeTo('output');
let message = '';
if (code) {
message = (this.shell.isCompiling() ? Msg.Lang['shell.compileFailed'] : Msg.Lang['shell.uploadFailed']);
statusBarTerminal.addValue(`\n==${message}==\n`);
} else {
message = (this.shell.isCompiling() ? Msg.Lang['shell.compileSucc'] : Msg.Lang['shell.uploadSucc']);
statusBarTerminal.addValue(`\n==${message}(${Msg.Lang['shell.timeCost']} ${
dayjs.duration(time).format('HH:mm:ss.SSS')
})==\n`);
}
layer.msg(message, { time: 1000 });
}
}
#running_ = false;
#upload_ = false;
#killing_ = false;
#layer_ = null;
constructor() {
this.#layer_ = new LayerProgress({
width: 200,
cancelValue: Msg.Lang['nav.btn.stop'],
skin: 'layui-anim layui-anim-scale',
cancel: () => {
if (this.#killing_) {
return false;
}
this.#layer_.title(`${Msg.Lang['shell.aborting']}...`);
this.#killing_ = true;
this.kill().catch(Debug.error);
return false;
},
cancelDisplay: false
});
}
async compile(code) {
return new Promise(async (resolve, reject) => {
this.#running_ = true;
this.#upload_ = false;
this.#killing_ = false;
this.showProgress();
const key = Boards.getSelectedBoardCommandParam();
const config = { key, code };
const mixlySocket = WebCompilerArduShell.getMixlySocket();
mixlySocket.emit('arduino.compile', config, (response) => {
this.hideProgress();
if (response.error) {
reject(response.error);
return;
}
const [error, result] = response;
if (error) {
reject(error);
} else {
resolve(result);
}
});
});
}
async upload(port, code) {
return new Promise(async (resolve, reject) => {
this.#running_ = true;
this.#upload_ = true;
this.#killing_ = false;
this.showProgress();
const key = Boards.getSelectedBoardCommandParam();
const config = { key, code };
const mixlySocket = WebCompilerArduShell.getMixlySocket();
mixlySocket.emit('arduino.upload', config, async (response) => {
if (response.error) {
this.hideProgress();
reject(response.error);
return;
}
const [error, result] = response;
if (error) {
this.hideProgress();
reject(error);
return;
}
if (result.code !== 0) {
this.hideProgress();
resolve(result);
return;
}
const { files } = result;
try {
const keys = Boards.getSelectedBoardKey().split(':');
if (`${keys[0]}:${keys[1]}` === 'arduino:avr') {
await this.uploadWithAvrbro(port, files);
} else {
await this.uploadWithEsptool(port, files);
}
} catch (error) {
this.hideProgress();
reject(error);
return;
}
this.hideProgress();
result.files = null;
resolve(result);
});
});
}
async uploadWithEsptool(port, files) {
const { mainStatusBarTabs } = Mixly;
const statusBarTerminal = mainStatusBarTabs.getStatusBarById('output');
let esploader = null;
let transport = null;
let baudrate = 115200;
let eraseAll = true;
try {
const keys = Boards.getSelectedBoardKey().split(':');
if (`${keys[0]}:${keys[1]}` === 'esp32:esp32') {
baudrate = Boards.getSelectedBoardConfigParam('UploadSpeed');
eraseAll = Boards.getSelectedBoardConfigParam('EraseFlash') === 'all';
} else {
baudrate = Boards.getSelectedBoardConfigParam('baud');
eraseAll = Boards.getSelectedBoardConfigParam('wipe') === 'all';
}
transport = new Transport(Serial.getPort(port), false);
esploader = new ESPLoader({
transport,
baudrate,
terminal: {
clean() {},
writeLine(data) {
statusBarTerminal.addValue(data + '\n');
},
write(data) {
statusBarTerminal.addValue(data);
}
}
});
let chip = await esploader.main();
} catch (error) {
await transport.disconnect();
throw new Error(error);
}
let data = [];
statusBarTerminal.addValue("\n");
for (let file of files) {
if (file.data && file.offset) {
data.push({
address: parseInt(file.offset, 16),
data: hexToBinaryString(file.data)
});
}
}
const flashOptions = {
fileArray: data,
flashSize: 'keep',
eraseAll,
compress: true,
calculateMD5Hash: (image) => CryptoJS.MD5(CryptoJS.enc.Latin1.parse(image))
};
try {
await esploader.writeFlash(flashOptions);
await transport.setDTR(false);
await new Promise((resolve) => setTimeout(resolve, 100));
await transport.setDTR(true);
await transport.disconnect();
} catch (error) {
await transport.disconnect();
throw new Error(error);
}
}
async uploadWithAvrbro(port, files) {
const key = Boards.getSelectedBoardKey();
const boardId = key.split(':')[2];
let boardName = '';
if (boardId === 'uno') {
boardName = 'uno';
} else if (boardId === 'nano') {
const cpu = Boards.getSelectedBoardConfigParam('cpu');
if (cpu === 'atmega328old') {
boardName = 'nano';
} else {
boardName = 'nano (new bootloader)';
}
} else if (boardId === 'pro') {
boardName = 'pro-mini';
} else if (boardId === 'mega') {
boardName = 'mega';
} else if (boardId === 'leonardo') {
boardName = 'leonardo';
}
const buffer = avrbro.parseHex(files[0].data);
await avrbro.flash(Serial.getPort(port), buffer, { boardName });
}
async kill() {
return new Promise(async (resolve, reject) => {
const mixlySocket = WebCompilerArduShell.getMixlySocket();
mixlySocket.emit('arduino.kill', (response) => {
if (response.error) {
reject(response.error);
return;
}
const [error, result] = response;
if (error) {
reject(error);
} else {
resolve(result);
}
});
});
}
showProgress() {
const message = this.isCompiling() ? Msg.Lang['shell.compiling'] : Msg.Lang['shell.uploading'];
this.#layer_.title(`${message}...`);
this.#layer_.show();
}
hideProgress() {
this.#layer_.hide();
}
isUploading() {
return this.#running_ && this.#upload_;
}
isCompiling() {
return this.#running_ && !this.#upload_;
}
}
WebCompiler.ArduShell = WebCompilerArduShell;
});

View File

@@ -1,184 +0,0 @@
goog.loadJs('web', () => {
goog.require('Mixly.Url');
goog.require('Mixly.Config');
goog.require('Mixly.LayerExt');
goog.require('Mixly.Boards');
goog.require('Mixly.MFile');
goog.require('Mixly.Msg');
goog.require('Mixly.Web.BU');
goog.require('Mixly.Web.Serial');
goog.provide('Mixly.WebCompiler.Compiler');
const {
WebCompiler,
Url,
Boards,
MFile,
Config,
LayerExt,
Msg,
Web
} = Mixly;
const { SOFTWARE, BOARD } = Config;
const { Compiler } = WebCompiler;
const { BU, Serial } = Web;
const DEFAULT_CONFIG = {
"enabled": true,
"protocol": "http:",
"ip": "localhost",
"domain": null
};
Compiler.CONFIG = { ...DEFAULT_CONFIG, ...(SOFTWARE?.webCompiler ?? {}) };
const { CONFIG } = Compiler;
let { hostname, protocol, port } = window.location;
if (port) {
port = ':' + port;
}
Compiler.protocol = protocol;
Compiler.URL = Compiler.protocol + '//' + hostname + port + '/compile';
Compiler.compile = () => {
const { mainStatusBarTabs } = Mixly;
const statusBarTerminal = mainStatusBarTabs.getStatusBarById('output');
mainStatusBarTabs.show();
statusBarTerminal.setValue('');
Compiler.generateCommand('compile', (error, obj, layerNum) => {
layer.close(layerNum);
let message = Msg.Lang['shell.compileSucc'];
if (error) {
message = Msg.Lang['shell.compileFailed'];
}
layer.msg(message, { time: 1000 });
statusBarTerminal.addValue("==" + message + "(" + Msg.Lang['shell.timeCost'] + " " + obj.timeCost + ")==\n");
});
}
Compiler.upload = async () => {
const { mainStatusBarTabs } = Mixly;
const statusBarTerminal = mainStatusBarTabs.getStatusBarById('output');
mainStatusBarTabs.show();
statusBarTerminal.setValue('');
BU.burning = false;
BU.uploading = true;
const board = Boards.getSelectedBoardCommandParam();
const boardParam = board.split(':');
const portName = 'web-serial';
if (boardParam[1] === 'avr') {
let boardUpload;
switch (boardParam[2]) {
case 'uno':
boardUpload = 'uno';
break;
case 'nano':
if (boardParam.length > 3 && boardParam[3] === 'cpu=atmega328old') {
boardUpload = 'nanoOldBootloader';
} else {
boardUpload = 'nano';
}
break;
case 'pro':
boardUpload = 'proMini';
break;
}
Serial.portClose(portName, async () => {
mainStatusBarTabs.changeTo('output');
try {
await AvrUploader.connect(boardUpload, {});
Compiler.generateCommand('upload', BU.uploadWithAvrUploader);
} catch (error) {
statusBarTerminal.addValue(error.toString() + '\n');
}
});
} else {
Serial.connect(portName, 115200, async (port) => {
if (!port) {
layer.msg(Msg.Lang['已取消连接'], { time: 1000 });
return;
}
mainStatusBarTabs.changeTo('output');
Compiler.generateCommand('upload', BU.uploadWithEsptool);
});
}
}
Compiler.generateCommand = (operate, endFunc = (errorMessage, data, layerNum) => {}) => {
const code = MFile.getCode();
let type;
const boardType = Boards.getSelectedBoardCommandParam();
let command = {
board: encodeURIComponent(boardType),
code: encodeURIComponent(code),
visitorId: BOARD.visitorId.str32CRC32b,
operate
};
const { mainStatusBarTabs } = Mixly;
const statusBarTerminal = mainStatusBarTabs.getStatusBarById('output');
let commandStr = Compiler.URL + '?' + Url.jsonToUrl(command);
statusBarTerminal.setValue(Msg.Lang['shell.compiling'] + '...\n');
console.log('send -> ', commandStr);
const compileLayer = layer.open({
type: 1,
title: Msg.Lang['shell.compiling'] + "...",
content: $('#mixly-loader-div'),
shade: LayerExt.SHADE_NAV,
closeBtn: 0,
success: function () {
$(".layui-layer-page").css("z-index", "198910151");
$("#mixly-loader-btn").off("click").click(() => {
layer.close(compileLayer);
});
},
end: function () {
$('#mixly-loader-div').css('display', 'none');
$(".layui-layer-shade").remove();
}
});
Compiler.sendCommand(compileLayer, commandStr, endFunc);
}
Compiler.sendCommand = (layerType, command, endFunc = (errorMessage, data, layerNum) => {}) => {
/*
fetch(command).then(function(response) {
console.log(response);
if(response.ok) {
return response.blob();
}
throw new Error('Network response was not ok.');
}).then(function(myBlob) {
var objectURL = URL.createObjectURL(myBlob);
console.log(objectURL);
}).catch(function(error) {
console.log('There has been a problem with your fetch operation: ', error.message);
});
*/
const { mainStatusBarTabs } = Mixly;
const statusBarTerminal = mainStatusBarTabs.getStatusBarById('output');
let req = new Request(command);
fetch(req, {
credentials: 'omit', // 设置不传递cookie
mode: 'cors', // 设置请求不允许跨域
}).then(res => {
return res.text();
}).then((data) => {
const dataObj = JSON.parse(data);
console.log(dataObj);
if (dataObj.error) {
statusBarTerminal.addValue(decodeURIComponent(dataObj.error));
endFunc(true, null, layerType);
} else {
statusBarTerminal.addValue(decodeURIComponent(dataObj.compileMessage));
endFunc(false, {
data: dataObj.data,
timeCost: decodeURIComponent(dataObj.timeCost)
}, layerType);
}
})
.catch((error) => {
endFunc(true, error.toString(), layerType);
});
}
});

View File

@@ -0,0 +1,53 @@
goog.loadJs('web', () => {
goog.require('Mixly.Debug');
goog.require('Mixly.Config');
goog.require('Mixly.StatusBarsManager');
goog.require('Mixly.Socket');
goog.require('Mixly.WebCompiler.ArduShell');
goog.provide('Mixly.WebCompiler.Loader');
const {
Debug,
Config,
StatusBarsManager,
Socket,
WebCompiler
} = Mixly;
const {
Loader,
ArduShell
} = WebCompiler;
const { SOFTWARE } = Config;
Loader.init = function () {
let url = '';
if (SOFTWARE.webCompiler?.url) {
const info = new window.URL(SOFTWARE.webCompiler.url);
if (info.hostname === 'default') {
info.hostname = window.location.hostname;
url = info.origin;
} else {
url = SOFTWARE.webCompiler.url;
}
} else {
url = `wss://${window.location.host}`;
}
const mixlySocket = new Socket(`${url}/compile`, {
path: '/mixly-socket/',
reconnection: true,
reconnectionDelayMax: 10000,
transports: ['websocket'],
protocols: ['my-protocol-v1']
});
const socket = mixlySocket.getSocket();
socket.on('connect', () => {});
socket.on('disconnect', () => {});
ArduShell.init(mixlySocket);
}
});

View File

@@ -128,7 +128,7 @@ class WebSocketArduShell {
statusBarSerial.open()
.then(() => {
const baudRates = code.match(/(?<=Serial.begin[\s]*\([\s]*)[0-9]*(?=[\s]*\))/g);
if (!baudRates.length) {
if (!baudRates?.length) {
return statusBarSerial.setBaudRate(9600);
} else {
return statusBarSerial.setBaudRate(baudRates[0] - 0);

View File

@@ -1,31 +1,48 @@
goog.loadJs('web', () => {
goog.require('Mixly.Debug');
goog.require('Mixly.Config');
goog.require('Mixly.StatusBarsManager');
goog.require('Mixly.WebSocket');
goog.require('Mixly.Socket');
goog.require('Mixly.WebSocket.Serial');
goog.require('Mixly.WebSocket.ArduShell');
goog.require('Mixly.WebSocket.BU');
goog.require('Mixly.WebSocket.Ampy');
goog.provide('Mixly.WebSocket.Socket');
goog.provide('Mixly.WebSocket.Loader');
const {
Debug,
Config,
StatusBarsManager,
Socket,
WebSocket
} = Mixly;
const {
Socket,
Loader,
Serial,
ArduShell,
BU,
Ampy
} = WebSocket;
const { SOFTWARE } = Config;
Socket.init = function () {
const mixlySocket = new WebSocket('wss://127.0.0.1:4000', {
Loader.init = function () {
let url = '';
if (SOFTWARE.webSocket?.url) {
const info = new window.URL(SOFTWARE.webSocket.url);
if (info.hostname === 'default') {
info.hostname = window.location.hostname;
url = info.origin;
} else {
url = SOFTWARE.webSocket.url;
}
} else {
url = `wss://${window.location.host}`;
}
const mixlySocket = new Socket(`${url}/all`, {
path: '/mixly-socket/',
reconnection: true,
reconnectionDelayMax: 10000,

View File

@@ -1,91 +1,6 @@
goog.loadJs('web', () => {
goog.require('io');
goog.require('Mixly');
goog.provide('Mixly.WebSocket');
class WebSocket {
#socket_ = null;
constructor(path, option) {
this.#socket_ = io(path, option);
}
#detectStatus_(status, callback) {
window.setTimeout(() => {
if (status.finished) {
return;
}
if (this.isConnected()) {
this.#detectStatus_(status, callback);
} else {
callback({
error: 'socket is not connected'
});
status.finished = true;
}
}, 1000);
}
emit(eventName, ...args) {
const callback = args.pop();
if (this.isConnected()) {
let emitStatus = {
finished: false
};
let status = this.#socket_.emit(eventName, ...args, (...callbackArgs) => {
if (emitStatus.finished) {
return;
}
emitStatus.finished = true;
callback(...callbackArgs);
});
this.#detectStatus_(emitStatus, callback);
return status;
} else {
callback({
error: 'socket is not connected'
});
return false;
}
}
async emitAsync(eventName, ...args) {
return new Promise((resolve, reject) => {
if (this.isConnected()) {
const callback = (...callbackArgs) => {
if (callbackArgs[0].error) {
reject(callbackArgs[0].error);
return;
}
resolve(...callbackArgs);
}
let emitStatus = {
finished: false
};
let status = this.#socket_.emit(eventName, ...args, (...callbackArgs) => {
if (emitStatus.finished) {
return;
}
emitStatus.finished = true;
callback(...callbackArgs);
});
this.#detectStatus_(emitStatus, callback);
} else {
reject('socket is not connected');
}
})
}
getSocket() {
return this.#socket_;
}
isConnected() {
return this.#socket_?.connected;
}
}
Mixly.WebSocket = WebSocket;
});

View File

@@ -166,11 +166,13 @@ class AmpyExt extends Ampy {
if (data.length < 1) {
throw new Error(Msg.Lang['ampy.waitingFirstEOFTimeout']);
}
let start = data.toLowerCase().indexOf('>ok');
if (start === -1){
let start = data.toLowerCase().lastIndexOf('ok');
if (start === -1) {
start = 0;
} else {
start += 2;
}
data = data.substring(start + 3, data.length - 1);
data = data.substring(start, data.length - 1);
let dataError = await this.readUntil('\x04', true, timeout);
if (dataError.length < 1) {
throw new Error(Msg.Lang['ampy.secondEOFTimeout']);

View File

@@ -5,10 +5,9 @@ goog.require('BoardId');
goog.require('FSWrapper');
goog.require('DAPWrapper');
goog.require('PartialFlashing');
goog.require('ESPTool');
goog.require('esptooljs');
goog.require('AdafruitESPTool');
goog.require('CryptoJS');
goog.require('AvrUploader');
goog.require('Mixly.Env');
goog.require('Mixly.LayerExt');
goog.require('Mixly.Config');
@@ -52,7 +51,7 @@ const { BOARD, SELECTED_BOARD } = Config;
const {
ESPLoader,
Transport
} = ESPTool;
} = esptooljs;
BU.uploading = false;
BU.burning = false;
@@ -333,6 +332,9 @@ BU.burnWithEsptool = async (binFile, erase) => {
};
try {
await esploader.writeFlash(flashOptions);
await transport.setDTR(false);
await new Promise((resolve) => setTimeout(resolve, 100));
await transport.setDTR(true);
BU.progressLayer.hide();
layer.msg(Msg.Lang['shell.burnSucc'], { time: 1000 });
statusBarTerminal.addValue(`==${Msg.Lang['shell.burnSucc']}==\n`);
@@ -660,6 +662,9 @@ BU.uploadWithAmpy = async (portName) => {
for (let item of rootInfo) {
rootMap[item[0]] = item[1];
}
if (cwd === '/') {
cwd = '';
}
if (libraries && libraries instanceof Object) {
for (let key in libraries) {
if (rootMap[`${cwd}/${key}`] !== undefined && rootMap[`${cwd}/${key}`] === libraries[key].size) {
@@ -692,110 +697,6 @@ BU.uploadWithAmpy = async (portName) => {
}
}
function hexToBuf (hex) {
var typedArray = new Uint8Array(hex.match(/[\da-f]{2}/gi).map(function (h) {
return parseInt(h, 16)
}));
return typedArray.buffer;
}
BU.uploadWithEsptool = async (endType, obj, layerType) => {
const portName = 'web-serial';
const portObj = Serial.portsOperator[portName];
const { serialport, toolConfig } = portObj;
let prevBaud = toolConfig.baudRates;
if (prevBaud !== 115200) {
toolConfig.baudRates = 115200;
await serialport.setBaudRate(toolConfig.baudRates);
}
const { mainStatusBarTabs } = Mixly;
const statusBarTerminal = mainStatusBarTabs.getStatusBarById('output');
let firmwareData = obj.data;
if (endType || typeof firmwareData !== 'object') {
statusBarTerminal.addValue(Msg.Lang['shell.bin.readFailed'] + "\n");
layer.close(layerType);
return;
}
layer.title(Msg.Lang['shell.uploading'] + '...', layerType);
statusBarTerminal.addValue(Msg.Lang['shell.bin.reading'] + "... ");
let firmwareList = [];
for (let i of firmwareData) {
if (!i.offset || !i.data) {
continue;
}
const firmware = {
offset: i.offset,
binBuf: hexToBuf(i.data)
};
firmwareList.push(firmware);
}
statusBarTerminal.addValue("Done!\n");
BU.burning = true;
BU.uploading = false;
statusBarTerminal.addValue(Msg.Lang['shell.uploading'] + '...\n');
mainStatusBarTabs.show();
mainStatusBarTabs.changeTo('output');
try {
SerialPort.refreshOutputBuffer = false;
SerialPort.refreshInputBuffer = true;
await espTool.reset();
if (await clickSync()) {
// await clickErase();
for (let i of firmwareList) {
await clickProgram(i.offset, i.binBuf);
}
}
layer.close(layerType);
layer.msg(Msg.Lang['shell.uploadSucc'], { time: 1000 });
statusBarTerminal.addValue(`==${Msg.Lang['shell.uploadSucc']}==\n`);
Serial.reset(portName, 'upload');
mainStatusBarTabs.changeTo(portName);
} catch (error) {
Debug.error(error);
layer.close(layerType);
statusBarTerminal.addValue(`==${Msg.Lang['shell.uploadFailed']}==\n`);
} finally {
SerialPort.refreshOutputBuffer = true;
SerialPort.refreshInputBuffer = false;
const code = MFile.getCode();
const baudRateList = code.match(/(?<=Serial.begin[\s]*\([\s]*)[0-9]*(?=[\s]*\))/g);
if (baudRateList && Serial.BAUDRATES.includes(baudRateList[0]-0)) {
prevBaud = baudRateList[0]-0;
}
if (toolConfig.baudRates !== prevBaud) {
toolConfig.baudRates = prevBaud;
await serialport.setBaudRate(prevBaud);
}
}
}
BU.uploadWithAvrUploader = async (endType, obj, layerType) => {
let firmwareData = obj.data;
const { mainStatusBarTabs } = Mixly;
const statusBarTerminal = mainStatusBarTabs.getStatusBarById('output');
if (endType || typeof firmwareData !== 'object') {
statusBarTerminal.addValue(Msg.Lang['shell.bin.readFailed'] + "\n");
layer.close(layerType);
return;
}
statusBarTerminal.addValue(Msg.Lang['shell.uploading'] + '...\n');
layer.title(Msg.Lang['shell.uploading'] + '...', layerType);
let uploadSucMessageShow = true;
AvrUploader.upload(firmwareData[0].data, (progress) => {
if (progress >= 100 && uploadSucMessageShow) {
statusBarTerminal.addValue(`==${Msg.Lang['shell.uploadSucc']}==\n`);
layer.msg(Msg.Lang['shell.uploadSucc'], { time: 1000 });
layer.close(layerType);
uploadSucMessageShow = false;
}
}, true)
.catch((error) => {
layer.close(layerType);
statusBarTerminal.addValue(`==${Msg.Lang['shell.uploadFailed']}==\n`);
});
}
/**
* @function 特殊固件的烧录
* @return {void}

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long