feat(core): micropython 板卡文件管理 右键菜单添加 上传下载 选项

This commit is contained in:
王立帮
2025-08-21 21:40:18 +08:00
parent 4a45323c82
commit 30f3da24b1
11 changed files with 171 additions and 14 deletions

View File

@@ -11,7 +11,7 @@ class FS {
async createFile(filePath) {} async createFile(filePath) {}
async readFile(filePath) {} async readFile(filePath, encoding = 'utf8') {}
async writeFile(filePath, data) {} async writeFile(filePath, data) {}

View File

@@ -5,6 +5,7 @@ goog.require('layui');
goog.require('Mixly.PageBase'); goog.require('Mixly.PageBase');
goog.require('Mixly.Env'); goog.require('Mixly.Env');
goog.require('Mixly.Msg'); goog.require('Mixly.Msg');
goog.require('Mixly.Debug');
goog.require('Mixly.HTMLTemplate'); goog.require('Mixly.HTMLTemplate');
goog.require('Mixly.DragV'); goog.require('Mixly.DragV');
goog.require('Mixly.StatusBar'); goog.require('Mixly.StatusBar');
@@ -17,6 +18,7 @@ const {
PageBase, PageBase,
Env, Env,
Msg, Msg,
Debug,
HTMLTemplate, HTMLTemplate,
DragV, DragV,
StatusBar, StatusBar,
@@ -142,6 +144,141 @@ class StatusBarAmpy extends PageBase {
fileTreeMenu.add({ fileTreeMenu.add({
weight: 14, weight: 14,
id: 'sep5', id: 'sep5',
data: '---------'
});
fileTreeMenu.add({
weight: 15,
id: 'upload-folder',
preconditionFn: ($trigger) => {
let type = $trigger.attr('type');
if (type === 'file') {
return false;
}
return true;
},
data: {
isHtmlName: true,
name: Menu.getItem(Msg.Lang['statusbar.ampy.uploadFolder'], ''),
callback: async (_, { $trigger }) => {
this.#fileTree_.showProgress();
try {
const fp = await window.showDirectoryPicker();
if (!fp) {
this.#fileTree_.hideProgress();
return;
}
const type = $trigger.attr('type');
let folderPath = '/';
if (type !== 'root') {
folderPath = $trigger.attr('id');
}
const fs = this.#fileTree_.getFS();
try {
for await (const [key, value] of fp.entries()) {
if (value.kind !== 'file') {
continue;
}
const file = await value.getFile();
const arrayBuffer = await file.arrayBuffer();
const [error,] = await fs.writeFile(path.join(folderPath, file.name), arrayBuffer);
if (error) {
Debug.error(error);
}
}
} catch (error) {
Debug.error(error);
}
this.#fileTree_.refreshFolder(folderPath);
} catch (_) { }
this.#fileTree_.hideProgress();
}
}
});
fileTreeMenu.add({
weight: 16,
id: 'upload-file',
preconditionFn: ($trigger) => {
let type = $trigger.attr('type');
if (type === 'file') {
return false;
}
return true;
},
data: {
isHtmlName: true,
name: Menu.getItem(Msg.Lang['statusbar.ampy.uploadFile'], ''),
callback: async (_, { $trigger }) => {
this.#fileTree_.showProgress();
try {
const [ fp ] = await window.showOpenFilePicker({
multiple: false
});
if (!fp) {
this.#fileTree_.hideProgress();
return;
}
const type = $trigger.attr('type');
let folderPath = '/';
if (type !== 'root') {
folderPath = $trigger.attr('id');
}
const file = await fp.getFile();
const arrayBuffer = await file.arrayBuffer();
const fs = this.#fileTree_.getFS();
const [error,] = await fs.writeFile(path.join(folderPath, file.name), arrayBuffer);
if (error) {
Debug.error(error);
} else {
this.#fileTree_.refreshFolder(folderPath);
}
} catch (_) { }
this.#fileTree_.hideProgress();
}
}
});
fileTreeMenu.add({
weight: 17,
id: 'download',
preconditionFn: ($trigger) => {
const type = $trigger.attr('type');
if (type === 'file') {
return true;
}
return false;
},
data: {
isHtmlName: true,
name: Menu.getItem(Msg.Lang['statusbar.ampy.download'], ''),
callback: async (_, { $trigger }) => {
const filePath = $trigger.attr('id');
this.#fileTree_.showProgress();
const fp = await window.showSaveFilePicker({
suggestedName: path.basename(filePath)
});
if (!fp) {
this.#fileTree_.hideProgress();
return;
}
const fs = this.#fileTree_.getFS();
const [error, result] = await fs.readFile(filePath, 'buffer');
if (error) {
Debug.error(error);
} else {
const writer = await fp.createWritable();
await writer.write(result);
await writer.close();
}
this.#fileTree_.hideProgress();
}
}
});
fileTreeMenu.add({
weight: 18,
id: 'sep6',
preconditionFn: ($trigger) => { preconditionFn: ($trigger) => {
const selectedNodeId = this.#fileTree_.getSelectedNodeId(); const selectedNodeId = this.#fileTree_.getSelectedNodeId();
let type = $trigger.attr('type'); let type = $trigger.attr('type');
@@ -155,7 +292,7 @@ class StatusBarAmpy extends PageBase {
}); });
fileTreeMenu.add({ fileTreeMenu.add({
weight: 15, weight: 19,
id: 'refresh', id: 'refresh',
preconditionFn: ($trigger) => { preconditionFn: ($trigger) => {
const selectedNodeId = this.#fileTree_.getSelectedNodeId(); const selectedNodeId = this.#fileTree_.getSelectedNodeId();
@@ -187,7 +324,7 @@ class StatusBarAmpy extends PageBase {
}); });
fileTreeMenu.add({ fileTreeMenu.add({
weight: 16, weight: 20,
id: 'sep6', id: 'sep6',
preconditionFn: ($trigger) => { preconditionFn: ($trigger) => {
let type = $trigger.attr('type'); let type = $trigger.attr('type');
@@ -197,7 +334,7 @@ class StatusBarAmpy extends PageBase {
}); });
fileTreeMenu.add({ fileTreeMenu.add({
weight: 17, weight: 21,
id: 'exit', id: 'exit',
preconditionFn: ($trigger) => { preconditionFn: ($trigger) => {
let type = $trigger.attr('type'); let type = $trigger.attr('type');

View File

@@ -957,6 +957,7 @@
"Mixly.PageBase", "Mixly.PageBase",
"Mixly.Env", "Mixly.Env",
"Mixly.Msg", "Mixly.Msg",
"Mixly.Debug",
"Mixly.HTMLTemplate", "Mixly.HTMLTemplate",
"Mixly.DragV", "Mixly.DragV",
"Mixly.StatusBar", "Mixly.StatusBar",

View File

@@ -54,12 +54,16 @@ class AmpyFS extends FS {
return [error, stdout]; return [error, stdout];
} }
async readFile(filePath) { async readFile(filePath, encoding = 'utf8') {
let stdout = '', error = null; let stdout = '', error = null;
try { try {
const output = await this.#ampy_.get(this.#port_, this.#baud_, filePath); const output = await this.#ampy_.get(this.#port_, this.#baud_, filePath);
stdout = output.stdout; stdout = output.stdout;
stdout = this.#decoder_.decode(this.#ampy_.unhexlify(stdout)); if (encoding = 'utf8') {
stdout = this.#decoder_.decode(this.#ampy_.unhexlify(stdout));
} else {
stdout = this.#ampy_.unhexlify(stdout);
}
} catch (e) { } catch (e) {
error = e; error = e;
Debug.error(error); Debug.error(error);
@@ -71,6 +75,9 @@ class AmpyFS extends FS {
let stdout = '', error = null; let stdout = '', error = null;
try { try {
const startFilePath = path.join(Env.clientPath, 'temp/temp'); const startFilePath = path.join(Env.clientPath, 'temp/temp');
if (data.constructor.name === 'ArrayBuffer') {
data = Buffer.from(data);
}
await fs_extra.outputFile(startFilePath, data); await fs_extra.outputFile(startFilePath, data);
const output = await this.#ampy_.put(this.#port_, this.#baud_, startFilePath, filePath); const output = await this.#ampy_.put(this.#port_, this.#baud_, startFilePath, filePath);
stdout = output.stdout; stdout = output.stdout;

View File

@@ -54,8 +54,8 @@ class AmpyExt extends Ampy {
return this.exec(port, this.render('ls', { port, baud, folderPath })); return this.exec(port, this.render('ls', { port, baud, folderPath }));
} }
async get(port, baud, filePath) { async get(port, baud, filePath, encoding = 'utf8') {
return this.exec(port, this.render('get', { port, baud, filePath })); return this.exec(port, this.render('get', { port, baud, filePath, encoding }));
} }
async mkdir(port, baud, folderPath) { async mkdir(port, baud, folderPath) {

View File

@@ -72,12 +72,12 @@ class AmpyFS extends FS {
return [error, stdout]; return [error, stdout];
} }
async readFile(filePath) { async readFile(filePath, encoding = 'utf8') {
let stdout = '', error = null, ampy = null; let stdout = '', error = null, ampy = null;
try { try {
ampy = await this.getAmpy(); ampy = await this.getAmpy();
await ampy.enter(); await ampy.enter();
stdout = await ampy.get(filePath); stdout = await ampy.get(filePath, encoding);
} catch (e) { } catch (e) {
error = e; error = e;
Debug.error(error); Debug.error(error);

View File

@@ -211,7 +211,7 @@ class AmpyExt extends Ampy {
this.#active_ = false; this.#active_ = false;
} }
async get(filename, timeout = 5000) { async get(filename, encoding = 'utf8', timeout = 5000) {
if (!this.isActive()) { if (!this.isActive()) {
throw new Error(Msg.Lang['ampy.portIsNotOpen']); throw new Error(Msg.Lang['ampy.portIsNotOpen']);
} }
@@ -222,7 +222,11 @@ class AmpyExt extends Ampy {
if (dataError) { if (dataError) {
return ''; return '';
} }
return this.#device_.decode(this.unhexlify(data)); if (encoding === 'utf8') {
return this.#device_.decode(this.unhexlify(data));
} else {
return this.unhexlify(data);
}
} }
async put(filename, data, timeout = 5000) { async put(filename, data, timeout = 5000) {

View File

@@ -57,8 +57,7 @@ File.open = async () => {
const fileConfig = { const fileConfig = {
multiple: false, multiple: false,
types: File.getFileTypes(filters), types: File.getFileTypes(filters),
excludeAcceptAllOption: true, excludeAcceptAllOption: true
multiple: false,
}; };
try { try {
const [ obj ] = await window.showOpenFilePicker(fileConfig); const [ obj ] = await window.showOpenFilePicker(fileConfig);

View File

@@ -38,6 +38,9 @@
"statusbar.ampy.refresh": "Refresh", "statusbar.ampy.refresh": "Refresh",
"statusbar.ampy.exit": "Exit", "statusbar.ampy.exit": "Exit",
"statusbar.ampy.cannotEdit": "This file type does not support editing", "statusbar.ampy.cannotEdit": "This file type does not support editing",
"statusbar.ampy.uploadFile": "Upload file",
"statusbar.ampy.uploadFolder": "Upload folder",
"statusbar.ampy.download": "Download",
"statusbar.dropdownMenu.noOptions": "No options", "statusbar.dropdownMenu.noOptions": "No options",
"statusbar.fs.newMapFolder": "Create a new mapping directory", "statusbar.fs.newMapFolder": "Create a new mapping directory",
"statusbar.fs.localFolderNotExist": "Local mapping directory does not exist", "statusbar.fs.localFolderNotExist": "Local mapping directory does not exist",

View File

@@ -38,6 +38,9 @@
"statusbar.ampy.refresh": "刷新", "statusbar.ampy.refresh": "刷新",
"statusbar.ampy.exit": "退出", "statusbar.ampy.exit": "退出",
"statusbar.ampy.cannotEdit": "该文件类型不支持编辑", "statusbar.ampy.cannotEdit": "该文件类型不支持编辑",
"statusbar.ampy.uploadFile": "上传文件",
"statusbar.ampy.uploadFolder": "上传文件夹",
"statusbar.ampy.download": "下载",
"statusbar.dropdownMenu.noOptions": "无选项", "statusbar.dropdownMenu.noOptions": "无选项",
"statusbar.fs.newMapFolder": "新建映射目录", "statusbar.fs.newMapFolder": "新建映射目录",
"statusbar.fs.localFolderNotExist": "本地映射目录不存在", "statusbar.fs.localFolderNotExist": "本地映射目录不存在",

View File

@@ -39,6 +39,9 @@
"statusbar.ampy.refresh": "刷新", "statusbar.ampy.refresh": "刷新",
"statusbar.ampy.exit": "退出", "statusbar.ampy.exit": "退出",
"statusbar.ampy.cannotEdit": "該文件類型不支援編輯", "statusbar.ampy.cannotEdit": "該文件類型不支援編輯",
"statusbar.ampy.uploadFile": "上傳檔案",
"statusbar.ampy.uploadFolder": "上傳資料夾",
"statusbar.ampy.download": "下載",
"statusbar.dropdownMenu.noOptions": "無選項", "statusbar.dropdownMenu.noOptions": "無選項",
"statusbar.fs.newMapFolder": "新映射目錄", "statusbar.fs.newMapFolder": "新映射目錄",
"statusbar.fs.localFolderNotExist": "本機映射目錄不存在", "statusbar.fs.localFolderNotExist": "本機映射目錄不存在",