Update: 优化Python Online板卡下「本地文件系统」
This commit is contained in:
File diff suppressed because one or more lines are too long
@@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
96
boards/default_src/python_pyodide/others/filesystem-fs.js
Normal file
96
boards/default_src/python_pyodide/others/filesystem-fs.js
Normal 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]);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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 () {
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
@@ -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
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -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++) {
|
||||||
|
|||||||
@@ -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) {
|
||||||
|
|||||||
2
common/modules/web-modules/browserfs.min.js
vendored
2
common/modules/web-modules/browserfs.min.js
vendored
File diff suppressed because one or more lines are too long
Reference in New Issue
Block a user