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 readFile(filePath) {}
async readFile(filePath, encoding = 'utf8') {}
async writeFile(filePath, data) {}

View File

@@ -5,6 +5,7 @@ goog.require('layui');
goog.require('Mixly.PageBase');
goog.require('Mixly.Env');
goog.require('Mixly.Msg');
goog.require('Mixly.Debug');
goog.require('Mixly.HTMLTemplate');
goog.require('Mixly.DragV');
goog.require('Mixly.StatusBar');
@@ -17,6 +18,7 @@ const {
PageBase,
Env,
Msg,
Debug,
HTMLTemplate,
DragV,
StatusBar,
@@ -142,6 +144,141 @@ class StatusBarAmpy extends PageBase {
fileTreeMenu.add({
weight: 14,
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) => {
const selectedNodeId = this.#fileTree_.getSelectedNodeId();
let type = $trigger.attr('type');
@@ -155,7 +292,7 @@ class StatusBarAmpy extends PageBase {
});
fileTreeMenu.add({
weight: 15,
weight: 19,
id: 'refresh',
preconditionFn: ($trigger) => {
const selectedNodeId = this.#fileTree_.getSelectedNodeId();
@@ -187,7 +324,7 @@ class StatusBarAmpy extends PageBase {
});
fileTreeMenu.add({
weight: 16,
weight: 20,
id: 'sep6',
preconditionFn: ($trigger) => {
let type = $trigger.attr('type');
@@ -197,7 +334,7 @@ class StatusBarAmpy extends PageBase {
});
fileTreeMenu.add({
weight: 17,
weight: 21,
id: 'exit',
preconditionFn: ($trigger) => {
let type = $trigger.attr('type');

View File

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

View File

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

View File

@@ -54,8 +54,8 @@ class AmpyExt extends Ampy {
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 get(port, baud, filePath, encoding = 'utf8') {
return this.exec(port, this.render('get', { port, baud, filePath, encoding }));
}
async mkdir(port, baud, folderPath) {

View File

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

View File

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

View File

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

View File

@@ -38,6 +38,9 @@
"statusbar.ampy.refresh": "Refresh",
"statusbar.ampy.exit": "Exit",
"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.fs.newMapFolder": "Create a new mapping directory",
"statusbar.fs.localFolderNotExist": "Local mapping directory does not exist",

View File

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

View File

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