Update: 优化Python Online板卡下「本地文件系统」

This commit is contained in:
王立帮
2024-11-27 03:08:43 +08:00
parent b984af1998
commit e0534d607d
9 changed files with 205 additions and 31 deletions

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,45 @@
import * as path from 'path';
import { FileTree } from 'mixly';
import FileSystemFS from './filesystem-fs';
export default class FileSystemFileTree extends FileTree {
constructor() {
super(new FileSystemFS());
}
async readFolder(inPath) {
const fs = this.getFS();
const [, status] = await fs.isDirectory(inPath);
let output = [];
if (!status) {
return output;
}
const result = await fs.readDirectory(inPath);
let children = [];
if (result.length == 2) {
children = result[1];
}
for (let data of children) {
const dataPath = path.join(inPath, data);
const [, isDirectory] = await fs.isDirectory(dataPath);
if (isDirectory) {
const [, isDirEmpty] = await fs.isDirectoryEmpty(dataPath);
output.push({
type: 'folder',
id: dataPath,
children: !isDirEmpty,
title: `/${this.getRootFolderName()}${dataPath}`
});
} else {
output.push({
type: 'file',
id: dataPath,
children: false,
title: `/${this.getRootFolderName()}${dataPath}`
});
}
}
return output;
}
}

View File

@@ -0,0 +1,96 @@
import { FS } from 'mixly';
export default class FileSystemFS extends FS {
static {
this.pool = window.workerpool.pool('../common/modules/mixly-modules/workers/web/file-system-access.js', {
workerOpts: {
name: 'pyodideFileSystemAccess'
},
workerType: 'web'
});
}
constructor() {
super();
}
async showDirectoryPicker() {
const directoryHandle = await window.showDirectoryPicker({ mode: 'readwrite' });
const permissionStatus = await directoryHandle.requestPermission({ mode: 'readwrite' });
if (permissionStatus !== 'granted') {
throw new Error('readwrite access to directory not granted');
}
await FileSystemFS.pool.exec('addFileSystemHandler', [directoryHandle]);
return directoryHandle;
}
async createFile(filePath) {
return this.writeFile(filePath, '');
}
async readFile(path) {
return FileSystemFS.pool.exec('readFile', [path, 'utf8']);
}
async writeFile(path, data) {
return FileSystemFS.pool.exec('writeFile', [path, data, 'utf8']);
}
async isFile(path) {
const [error, stats] = await FileSystemFS.pool.exec('stat', [path]);
if (stats && stats.mode === 33188) {
return [error, true];
}
return [error, false];
}
async renameFile(oldFilePath, newFilePath) {
return await FileSystemFS.pool.exec('rename', [oldFilePath, newFilePath]);
}
async moveFile(oldFilePath, newFilePath) {
return this.renameFile(oldFilePath, newFilePath);
}
async deleteFile(filePath) {
return FileSystemFS.pool.exec('unlink', [filePath]);
}
async createDirectory(folderPath) {
return FileSystemFS.pool.exec('mkdir', [folderPath, 0o777]);
}
async readDirectory(path) {
const result = await FileSystemFS.pool.exec('readdir', [path]);
if (result[0]) {
return [result[0], null];
}
return result;
}
async isDirectory(path) {
const [error, stats] = await FileSystemFS.pool.exec('stat', [path]);
if (stats && stats.mode === 33188) {
return [error, false];
}
return [error, true];
}
async isDirectoryEmpty(path) {
const [error, result = []] = await this.readDirectory(path);
return [error, !result?.length];
}
async renameDirectory(oldFolderPath, newFolderPath) {
return await FileSystemFS.pool.exec('rename', [oldFolderPath, newFolderPath]);
}
async moveDirectory(oldFolderPath, newFolderPath) {
return FileSystemFS.pool.exec('rename', [oldFolderPath, newFolderPath]);
}
async deleteDirectory(folderPath) {
return FileSystemFS.pool.exec('rmdir', [folderPath]);
}
}

View File

@@ -37,8 +37,6 @@ export default class PythonShell {
const $content = footerBar.getContent(); const $content = footerBar.getContent();
$content.after(this.$loader); $content.after(this.$loader);
this.statusBarImage = StatusBarImage.init();
this.statusBarFileSystem = StatusBarFileSystem.init();
const projectPath = path.relative(Env.indexDirPath, Env.boardDirPath); const projectPath = path.relative(Env.indexDirPath, Env.boardDirPath);
const loader = new KernelLoader({ const loader = new KernelLoader({
rootPath: path.join(projectPath, 'deps'), rootPath: path.join(projectPath, 'deps'),
@@ -61,6 +59,8 @@ export default class PythonShell {
this.kernelLoaded = true; this.kernelLoaded = true;
this.$loader.remove(); this.$loader.remove();
this.$loader = null; this.$loader = null;
this.statusBarImage = StatusBarImage.init();
this.statusBarFileSystem = StatusBarFileSystem.init();
} }
this.run = async function () { this.run = async function () {

View File

@@ -11,14 +11,13 @@ import {
ContextMenu, ContextMenu,
Debug, Debug,
StatusBarsManager, StatusBarsManager,
Workspace, Workspace
Web
} from 'mixly'; } from 'mixly';
import FileSystemFileTree from './filesystem-file-tree';
import FILE_SYSTEM_TEMPLATE from '../templates/html/statusbar-filesystem.html'; import FILE_SYSTEM_TEMPLATE from '../templates/html/statusbar-filesystem.html';
import FILE_SYSTEM_OPEN_FS_TEMPLATE from '../templates/html/statusbar-filesystem-open-fs.html'; import FILE_SYSTEM_OPEN_FS_TEMPLATE from '../templates/html/statusbar-filesystem-open-fs.html';
import FILE_SYSTEM_EDITOR_EMPTY_TEMPLATE from '../templates/html/statusbar-filesystem-editor-empty.html'; import FILE_SYSTEM_EDITOR_EMPTY_TEMPLATE from '../templates/html/statusbar-filesystem-editor-empty.html';
const { FileTree } = Web;
export default class StatusBarFileSystem extends PageBase { export default class StatusBarFileSystem extends PageBase {
@@ -65,7 +64,7 @@ export default class StatusBarFileSystem extends PageBase {
super(); super();
const $content = $(HTMLTemplate.get('html/statusbar/statusbar-filesystem.html').render()); const $content = $(HTMLTemplate.get('html/statusbar/statusbar-filesystem.html').render());
this.setContent($content); this.setContent($content);
this.#fileTree_ = new FileTree(); this.#fileTree_ = new FileSystemFileTree();
this.#$fileTree_ = $content.children('.file-tree'); this.#$fileTree_ = $content.children('.file-tree');
this.#$openFS_ = $(HTMLTemplate.get('html/statusbar/statusbar-filesystem-open-fs.html').render({ this.#$openFS_ = $(HTMLTemplate.get('html/statusbar/statusbar-filesystem-open-fs.html').render({
msg: { msg: {
@@ -109,12 +108,17 @@ export default class StatusBarFileSystem extends PageBase {
const filePath = selected[0].id; const filePath = selected[0].id;
this.#fileTree_.showProgress(); this.#fileTree_.showProgress();
const fs = this.#fileTree_.getFS(); const fs = this.#fileTree_.getFS();
const result = await fs.readFile(filePath); const [error, result] = await fs.readFile(filePath);
this.showEditor(); if (error) {
this.#editor_.setValue(result); this.hideEditor();
this.#editor_.scrollToTop(); this.#fileTree_.deselectAll();
this.#editor_.focus(); } else {
this.setStatus(false); this.showEditor();
this.#editor_.setValue(result);
this.#editor_.scrollToTop();
this.#editor_.focus();
this.setStatus(false);
}
this.#fileTree_.hideProgress(); this.#fileTree_.hideProgress();
}); });
@@ -140,6 +144,25 @@ export default class StatusBarFileSystem extends PageBase {
const fileTreeContextMenu = this.#fileTree_.getContextMenu(); const fileTreeContextMenu = this.#fileTree_.getContextMenu();
const fileTreeMenu = fileTreeContextMenu.getItem('menu'); const fileTreeMenu = fileTreeContextMenu.getItem('menu');
fileTreeMenu.add({
weight: 7,
type: 'copy_path',
data: {
isHtmlName: true,
name: ContextMenu.getItem(Msg.Lang['fileTree.copyPath'], ''),
callback: (_, { $trigger }) => {
let outPath = null;
let type = $trigger.attr('type');
if (type === 'root') {
outPath = this.#fileTree_.getRootFolderTitle();
} else {
outPath = $trigger.attr('title');
}
navigator.clipboard.writeText(outPath).catch(Debug.error);
}
}
});
fileTreeMenu.add({ fileTreeMenu.add({
weight: 14, weight: 14,
type: 'sep5', type: 'sep5',
@@ -384,25 +407,26 @@ export default class StatusBarFileSystem extends PageBase {
if (!directoryHandle.name) { if (!directoryHandle.name) {
return; return;
} }
const rootPath = '/' + directoryHandle.name;
this.#fileTree_.setFolderPath('/'); this.#fileTree_.setFolderPath('/');
this.#fileTree_.setRootFolderTitle(rootPath);
this.#fileTree_.setRootFolderName(directoryHandle.name); this.#fileTree_.setRootFolderName(directoryHandle.name);
this.#fileTree_.openRootFolder(); this.#fileTree_.openRootFolder();
this.showFileTree(); this.showFileTree();
return window.pyodide.mountNativeFS('/' + directoryHandle.name, directoryHandle); return window.pyodide.mountNativeFS(rootPath, directoryHandle);
}) })
.then((nativefs) => { .then((nativefs) => {
console.log(nativefs)
this.#nativefs_ = nativefs; this.#nativefs_ = nativefs;
}) })
.catch(Debug.error); .catch(Debug.error);
} }
closeFS() { closeFS() {
const rootPath = this.#fileTree_.getFolderPath(); const rootPath = '/' + this.#fileTree_.getRootFolderTitle();
const lookup = window.pyodide.FS.lookupPath(rootPath, { const lookup = window.pyodide.FS.lookupPath(rootPath, {
follow_mount: false follow_mount: false
}); });
if (window.pyodide.isMountpoint(lookup.node)) { if (window.pyodide.FS.isMountpoint(lookup.node)) {
window.pyodide.FS.unmount(rootPath); window.pyodide.FS.unmount(rootPath);
} }
this.#fileTree_.deselectAll(); this.#fileTree_.deselectAll();
@@ -433,7 +457,7 @@ export default class StatusBarFileSystem extends PageBase {
} }
setStatus(isChanged) { setStatus(isChanged) {
if (this.#changed_ === isChanged || !this.#$close_) { if (this.#changed_ === isChanged) {
return; return;
} }
this.#changed_ = isChanged; this.#changed_ = isChanged;

View File

@@ -57,6 +57,7 @@ class FileTree extends Component {
#rootFolderOpened_ = false; #rootFolderOpened_ = false;
#rootPath_ = ''; #rootPath_ = '';
#rootName_ = ''; #rootName_ = '';
#rootTitle_ = '';
#fs_ = null; #fs_ = null;
#contextMenu_ = null; #contextMenu_ = null;
#selected_ = null; #selected_ = null;
@@ -474,7 +475,7 @@ class FileTree extends Component {
this.nodeAliveRegistry.reset(); this.nodeAliveRegistry.reset();
this.#jstree_.refresh(); this.#jstree_.refresh();
this.watchFolder(this.#rootPath_); this.watchFolder(this.#rootPath_);
this.#$rootFolder_.attr('title', this.#rootPath_); this.setRootFolderTitle(this.#rootPath_);
const rootNodeName = path.basename(folderPath).toUpperCase(); const rootNodeName = path.basename(folderPath).toUpperCase();
this.setRootFolderName(rootNodeName); this.setRootFolderName(rootNodeName);
} }
@@ -492,6 +493,15 @@ class FileTree extends Component {
return this.#rootName_; return this.#rootName_;
} }
setRootFolderTitle(name) {
this.#rootTitle_ = name;
this.#$rootFolder_.attr('title', name);
}
getRootFolderTitle() {
return this.#rootTitle_;
}
refreshFolder(folderPath) { refreshFolder(folderPath) {
// 延迟刷新节点防止过于频繁的IO操作 // 延迟刷新节点防止过于频繁的IO操作
let eventId = this.delayRefreshRegistry.getItem(folderPath); let eventId = this.delayRefreshRegistry.getItem(folderPath);
@@ -507,10 +517,9 @@ class FileTree extends Component {
const node = this.#jstree_.get_node(folderPath); const node = this.#jstree_.get_node(folderPath);
const nodeIsOpened = node && !this.isClosed(folderPath); const nodeIsOpened = node && !this.isClosed(folderPath);
if (nodeIsOpened) { if (nodeIsOpened) {
if (this.isWatched(folderPath)) { this.watchFolder(folderPath);
this.clearFolderTemp(folderPath); this.clearFolderTemp(folderPath);
this.#jstree_.refresh_node(folderPath); this.#jstree_.refresh_node(folderPath);
}
} else { } else {
this.unwatchFolder(folderPath); this.unwatchFolder(folderPath);
} }
@@ -620,7 +629,7 @@ class FileTree extends Component {
let output = []; let output = [];
const content = await this.readFolder(inPath); const content = await this.readFolder(inPath);
for (let item of content) { for (let item of content) {
const { type, id, children } = item; const { type, id, title, children } = item;
const text = path.basename(id); const text = path.basename(id);
let icon = 'icon-doc'; let icon = 'icon-doc';
if (type === 'folder') { if (type === 'folder') {
@@ -635,7 +644,7 @@ class FileTree extends Component {
li_attr: { li_attr: {
type, type,
name: text, name: text,
title: id title: title ?? id
}, },
icon icon
}); });

View File

@@ -35,7 +35,7 @@ class Menu {
} }
const { id, weight } = item; const { id, weight } = item;
if (this.#ids_[id]) { if (this.#ids_[id]) {
this.unregister(id); delete this.#ids_[id];
} }
let i = 0; let i = 0;
for (; i < this.#menuItems_.length; i++) { for (; i < this.#menuItems_.length; i++) {

View File

@@ -71,12 +71,12 @@ const readFile = function(fname, encoding, flag) {
return createPromise(fs.readFile, fname, encoding); return createPromise(fs.readFile, fname, encoding);
} }
const writeFile = function(fname, data, encoding, flag, mode) { const writeFile = function(fname, data, encoding) {
return createPromise(fs.writeFile, fname, data, encoding, flag, mode); return createPromise(fs.writeFile, fname, data, encoding);
} }
const appendFile = function(fname, data, encoding, flag, mode) { const appendFile = function(fname, data, encoding) {
return createPromise(fs.appendFile, fname, data, encoding, flag, mode); return createPromise(fs.appendFile, fname, data, encoding);
} }
const chmod = function(p, mode) { const chmod = function(p, mode) {

File diff suppressed because one or more lines are too long