Files
mixly3/common/modules/mixly-modules/common/mfile.js
2024-12-20 18:51:04 +08:00

388 lines
12 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
goog.loadJs('common', () => {
goog.require('layui');
goog.require('Blockly');
goog.require('Base64');
goog.require('Mixly.Config');
goog.require('Mixly.MArray');
goog.require('Mixly.Boards');
goog.require('Mixly.XML');
goog.require('Mixly.LayerExt');
goog.require('Mixly.Msg');
goog.provide('Mixly.MFile');
const { form, util } = layui;
const {
Config,
MArray,
Boards,
XML,
LayerExt,
Msg,
MFile
} = Mixly;
const { BOARD, SOFTWARE } = Config;
MFile.SAVE_FILTER_TYPE = {
mix: { name: Msg.Lang['file.type.mix'], extensions: ['mix'] },
py: { name: Msg.Lang['file.type.python'], extensions: ['py'] },
ino: { name: Msg.Lang['file.type.arduino'], extensions: ['ino'] },
hex: { name: Msg.Lang['file.type.hex'], extensions: ['hex'] },
bin: { name: Msg.Lang['file.type.bin'], extensions: ['bin'] },
mil: { name: Msg.Lang['file.type.mil'], extensions: ['mil'] }
};
MFile.saveFilters = [ MFile.SAVE_FILTER_TYPE.mix ];
MFile.OPEN_FILTER_TYPE = ['mix','xml', 'py', 'ino', 'hex', 'bin'];
MFile.openFilters = ['mix'];
/**
* @function 更新保存文件时可用的后缀
* @param config { array }
* config = ["py", "ino", "hex", "bin", "png"]
* 注mix后缀为默认添加列表后缀名顺序即为保存时自上而下显示的顺序
* @param priority { string },配置需要优先显示的后缀名,没有此项可不填
* @return void
**/
MFile.updateSaveFilters = (config, priority = null) => {
if (typeof config !== 'object')
config = [];
MFile.saveFilters = [ MFile.SAVE_FILTER_TYPE.mix ];
let saveFilterType = ['mix'];
for (let i of config)
if (MFile.SAVE_FILTER_TYPE[i] && !saveFilterType.includes(i)) {
saveFilterType.push(i);
if (i === priority) {
MFile.saveFilters.unshift(MFile.SAVE_FILTER_TYPE[i]);
} else {
MFile.saveFilters.push(MFile.SAVE_FILTER_TYPE[i]);
}
}
}
/**
* @function 更新打开文件时的可用后缀
* @param config { array }
* config = ["py", "ino", "hex", "bin"]
* @return void
**/
MFile.updateOpenFilters = (config, priority) => {
if (typeof config !== 'object')
config = [];
MFile.openFilters = ['mix', 'xml'];
for (let i of config)
if (MFile.OPEN_FILTER_TYPE.includes(i) && !MFile.openFilters.includes(i))
MFile.openFilters.push(i);
}
MFile.init = () => {
const saveConfig = BOARD?.nav?.save ?? {};
let saveFilters = [], openFilters = [];
for (let i in saveConfig)
if (saveConfig[i]) {
saveFilters.push(i);
openFilters.push(i);
}
if (BOARD?.nav?.setting?.thirdPartyLibrary)
saveFilters.push('mil');
MFile.updateOpenFilters(openFilters);
MFile.updateSaveFilters(saveFilters);
}
MFile.init();
MFile.getCode = (type) => {
const { mainEditor } = Editor;
const { codeEditor, blockEditor } = mainEditor;
const { editor, generator } = blockEditor;
if (mainEditor.selected === 'CODE')
return codeEditor.getValue();
else {
return generator.workspaceToCode(editor) || '';
}
}
MFile.getMix = () => {
const mixDom = $(Blockly.Xml.workspaceToDom(Editor.blockEditor)),
version = SOFTWARE?.version ?? 'Mixly 2.0',
boardName = Boards.getSelectedBoardName(),
board = BOARD?.boardType ?? 'default',
config = Boards.getSelectedBoardConfig();
mixDom.removeAttr('xmlns')
.attr('version', version)
.attr('board', board + '@' + boardName);
let xmlStr = mixDom[0].outerHTML;
let code = MFile.getCode();
if (config) {
try {
xmlStr += `<config>${JSON.stringify(config)}</config>`;
} catch (error) {
console.log(error);
}
}
if (BOARD.saveMixWithCode) {
code = Base64.encode(code);
xmlStr += `<code>${code}</code>`;
}
return xmlStr;
}
MFile.getMil = () => {
const mixDom = $(MFile.getMix());
let xmlDom, configDom, codeDom;
for (let i = 0; mixDom[i]; i++) {
switch (mixDom[i].nodeName) {
case 'XML':
xmlDom = $(mixDom[i]);
break;
case 'CONFIG':
configDom = $(mixDom[i]);
break;
case 'CODE':
codeDom = $(mixDom[i]);
break;
}
}
if (!xmlDom) return '';
configDom && configDom.remove();
codeDom && codeDom.remove();
xmlDom.attr('type', 'lib');
xmlDom.find('block,shadow').removeAttr('id varid x y');
const blocksDom = xmlDom.children('block');
let blockXmlList = [];
for (let i = 0; blocksDom[i]; i++) {
const outerHTML = blocksDom[i].outerHTML;
if (!blockXmlList.includes(outerHTML))
blockXmlList.push(outerHTML);
else
blocksDom[i].remove();
}
return xmlDom[0].outerHTML;
}
MFile.parseMix = (xml, useCode = false, useIncompleteBlocks = false, endFunc = (message) => {}) => {
const mixDom = xml;
let xmlDom, configDom, codeDom;
for (let i = 0; mixDom[i]; i++) {
switch (mixDom[i].nodeName) {
case 'XML':
xmlDom = $(mixDom[i]);
break;
case 'CONFIG':
configDom = $(mixDom[i]);
break;
case 'CODE':
codeDom = $(mixDom[i]);
break;
}
}
if (!xmlDom && !codeDom) {
layer.msg(Msg.Lang['editor.invalidData'], { time: 1000 });
return;
}
for (let i of ['version', 'id', 'type', 'varid', 'name', 'x', 'y', 'items']) {
const nowDom = xmlDom.find('*[' + i + ']');
if (nowDom.length) {
for (let j = 0; nowDom[j]; j++) {
let attr = $(nowDom[j]).attr(i);
try {
attr = attr.replaceAll('\\\"', '');
} catch (error) {
console.log(error);
}
$(nowDom[j]).attr(i, attr);
}
}
}
let config, configStr = configDom && configDom.html();
try {
if (configStr)
config = JSON.parse(configStr);
} catch (error) {
console.log(error);
}
let boardName = xmlDom.attr('board') ?? '';
Boards.setSelectedBoard(boardName, config);
let code = codeDom ? codeDom.html() : '';
if (Base64.isValid(code)) {
code = Base64.decode(code);
} else {
try {
code = util.unescape(code);
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;
}
});
} catch (error) {
console.log(error);
}
}
if (useCode) {
if (!codeDom) {
layer.msg(Msg.Lang['editor.invalidData'], { time: 1000 });
return;
}
Editor.mainEditor.drag.full('NEGATIVE'); // 完全显示代码编辑器
Editor.codeEditor.setValue(code, -1);
Editor.blockEditor.clear();
endFunc('USE_CODE');
return;
}
const blockDom = mixDom.find('block');
const shadowDom = mixDom.find('shadow');
blockDom.removeAttr('id varid');
shadowDom.removeAttr('id varid');
let blocks = [];
let undefinedBlocks = [];
for (let i = 0; blockDom[i]; i++) {
const blockType = $(blockDom[i]).attr('type');
if (blockType && !blocks.includes(blockType))
blocks.push(blockType);
}
for (let i = 0; shadowDom[i]; i++) {
const shadowType = $(shadowDom[i]).attr('type');
if (shadowType && !blocks.includes(shadowType))
blocks.push(shadowType);
}
const blocklyGenerator = Editor.mainEditor.blockEditor.generator;
for (let i of blocks) {
if (Blockly.Blocks[i] && blocklyGenerator.forBlock[i]) {
continue;
}
undefinedBlocks.push(i);
}
if (undefinedBlocks.length) {
MFile.showParseMixErrorDialog(mixDom, undefinedBlocks, endFunc);
return;
}
Editor.blockEditor.clear();
Blockly.Xml.domToWorkspace(xmlDom[0], Editor.blockEditor);
Editor.blockEditor.scrollCenter();
Blockly.hideChaff();
if (!useIncompleteBlocks && codeDom) {
const workspaceCode = MFile.getCode();
if (workspaceCode !== code) {
Editor.mainEditor.drag.full('NEGATIVE'); // 完全显示代码编辑器
Editor.codeEditor.setValue(code, -1);
}
endFunc();
return;
}
Editor.mainEditor.drag.full('POSITIVE'); // 完全显示块编辑器
if (useIncompleteBlocks)
endFunc('USE_INCOMPLETE_BLOCKS');
else
endFunc();
}
MFile.removeUndefinedBlocks = (xml, undefinedBlocks) => {
for (let i of undefinedBlocks) {
xml.find('*[type='+i+']').remove();
}
}
MFile.showParseMixErrorDialog = (xml, undefinedBlocks, endFunc = () => {}) => {
const { PARSE_MIX_ERROR_DIV } = XML.TEMPLATE_STR;
const renderStr = XML.render(PARSE_MIX_ERROR_DIV, {
text: undefinedBlocks.join('<br/>'),
btn1Name: Msg.Lang['editor.cancel'],
btn2Name: Msg.Lang['editor.ignoreBlocks'],
btn3Name: Msg.Lang['editor.loadCode']
})
LayerExt.open({
title: Msg.Lang['editor.parseMixErrorInfo'],
id: 'parse-mix-error-layer',
area: ['50%', '250px'],
max: ['500px', '250px'],
min: ['350px', '100px'],
shade: LayerExt.SHADE_ALL,
content: renderStr,
borderRadius: '5px',
success: (layero, index) => {
$('#parse-mix-error-layer').css('overflow', 'hidden');
form.render(null, 'parse-mix-error-filter');
layero.find('button').click((event) => {
layer.close(index);
const mId = $(event.currentTarget).attr('m-id');
switch (mId) {
case '0':
break;
case '1':
for (let i of undefinedBlocks) {
xml.find('*[type='+i+']').remove();
}
MFile.parseMix(xml, false, true, endFunc);
break;
case '2':
MFile.parseMix(xml, true, false, endFunc);
break;
}
});
}
});
}
MFile.openFile = (filters, readType = 'text', sucFunc = () => {}) => {
const loadFileDom = $('<input></input>');
loadFileDom.attr({
id: 'web-open-file',
type: 'file',
name: 'web-open-file',
accept: filters
});
loadFileDom.change(function(event) {
MFile.onclickOpenFile(this, readType, (data) => {
sucFunc(data);
});
});
loadFileDom.css('display', 'none');
$('#web-open-file').remove();
$('body').append(loadFileDom);
loadFileDom.click();
}
MFile.onclickOpenFile = (input, readType, endFunc) => {
const files = input.files;
//限制上传文件的 大小,此处为10M
if (files[0].size > 5 * 1024 * 1024) {
layer.msg('所选择文件大小必须在5MB内', { time: 1000 });
$('#web-open-file').remove();
endFunc(null);
return false;
}
const resultFile = input.files[0];
// 如果文件存在
if (resultFile) {
const filename = resultFile.name;
const reader = new FileReader();
switch (readType) {
case 'text':
reader.readAsText(resultFile);
break;
case 'bin':
reader.readAsBinaryString(resultFile);
break;
case 'url':
reader.readAsDataURL(resultFile);
break;
default:
reader.readAsArrayBuffer(resultFile);
}
reader.onload = function (e) {
const data = e.target.result;
$('#web-open-file').remove();
endFunc({ data, filename });
};
} else {
endFunc(null);
}
}
});