feat(core): micropython 板卡文件管理 右键菜单添加 上传 和 下载 选项
This commit is contained in:
@@ -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) {}
|
||||||
|
|
||||||
|
|||||||
@@ -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');
|
||||||
|
|||||||
@@ -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",
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
@@ -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) {
|
||||||
|
|||||||
@@ -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);
|
||||||
|
|||||||
@@ -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) {
|
||||||
|
|||||||
@@ -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);
|
||||||
|
|||||||
@@ -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",
|
||||||
|
|||||||
@@ -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": "本地映射目录不存在",
|
||||||
|
|||||||
@@ -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": "本機映射目錄不存在",
|
||||||
|
|||||||
Reference in New Issue
Block a user