Simplify file handling: give up trying to execute examples in their folders, just copy all files to /. Breaks autocomplete

This commit is contained in:
ochafik 2023-03-26 23:39:41 +01:00
commit 51b481866f
8 changed files with 90 additions and 52 deletions

View file

@ -11,7 +11,7 @@ import { buildUrlForStateParams } from '../state/fragment-state';
import { blankProjectState } from '../state/initial-state';
import { ModelContext, FSContext } from './contexts';
import FilePicker, { } from './FilePicker';
import { isFileWritable } from '../state/model';
// import { isFileWritable } from '../state/model';
// import "primereact/resources/themes/lara-light-indigo/theme.css";
// import "primereact/resources/primereact.min.css";
@ -186,7 +186,7 @@ export default function EditorPanel({className, style}: {className?: string, sty
onMount={onMount} // TODO: This looks a bit silly, does it trigger a re-render??
options={{
...openscadEditorOptions,
readOnly: !isFileWritable(state.params.sourcePath)
// readOnly: !isFileWritable(state.params.sourcePath)
}}
/>
</div>

View file

@ -4,7 +4,8 @@ import { CSSProperties, useContext } from 'react';
import { TreeSelect } from 'primereact/treeselect';
import TreeNode from 'primereact/treenode';
import { ModelContext, FSContext } from './contexts';
import { isFileWritable } from '../state/model';
// import { isFileWritable } from '../state/model';
import { join } from '../fs/filesystem';
function listFilesAsNodes(fs: FS, path: string, accept?: (path: string) => boolean): TreeNode[] {
const files: [string, string][] = []
@ -13,7 +14,7 @@ function listFilesAsNodes(fs: FS, path: string, accept?: (path: string) => boole
if (name.startsWith('.')) {
continue;
}
const childPath = `${path}/${name}`;
const childPath = join(path, name);//`${path}/${name}`;
if (accept && !accept(childPath)) {
continue;
}
@ -35,7 +36,8 @@ function listFilesAsNodes(fs: FS, path: string, accept?: (path: string) => boole
}
nodes.push({
// icon: path == '/home' ? 'pi-home' : ...
icon: isDirectory ? 'pi pi-folder' : isFileWritable(path) ? 'pi pi-file' : 'pi pi-lock',
// icon: isDirectory ? 'pi pi-folder' : isFileWritable(path) ? 'pi pi-file' : 'pi pi-lock',
icon: isDirectory ? 'pi pi-folder' : 'pi pi-file',
label: name,
data: path,
key: path,
@ -54,7 +56,24 @@ export default function FilePicker({className, style}: {className?: string, styl
const fs = useContext(FSContext);
const fsItems = fs && listFilesAsNodes(fs, '/home')
const fsItems = fs && //listFilesAsNodes(fs, '/home')
[
{
icon: 'pi pi-home',
label: 'User files',
key: '/',
children: listFilesAsNodes(fs, '/'),//
// children: listFilesAsNodes(fs, '/', f => f != librariesFolder && !f.startsWith(`${librariesFolder}/`)),
selectable: false
},
{
icon: 'pi pi-database',
label: 'Builtin libraries',
key: '/libraries',
children: listFilesAsNodes(fs, '/libraries'),
selectable: false
},
] || [];
return (
<TreeSelect

View file

@ -10,13 +10,22 @@ export type FSMounts = {
export type Symlinks = {[alias: string]: string};
export const getParentDir = (path: string) => path.split('/').slice(0, -1).join('/');
export const getParentDir = (path: string) => {
let d = path.split('/').slice(0, -1).join('/');
return d === '' ? (path.startsWith('/') ? '/' : '.') : d;
}
export const getFileName = (path: string) => path.split('/').splice(-1)[0];
export function readDirAsArray(fs: FS, path: string): Promise<string[] | undefined> {
return new Promise((res, rej) => fs.readdir(path, (err, files) => err ? rej(err) : res(files)));
}
export function join(a: string, b: string): string {
if (a === '.') return b;
if (a.endsWith('/')) return join(a.substring(0, a.length - 1), b);
return b === '.' ? a : `${a}/${b}`;
}
export async function getBrowserFSLibrariesMounts(archiveNames: string[]) {
const Buffer = BrowserFS.BFSRequire('buffer').Buffer;
const fetchData = async (url: string) => (await fetch(url)).arrayBuffer();
@ -38,7 +47,11 @@ export async function getBrowserFSLibrariesMounts(archiveNames: string[]) {
export async function symlinkLibraries(archiveNames: string[], fs: FS, prefix='/libraries', cwd='/tmp') {
const createSymlink = async (target: string, source: string) => {
// console.log('symlink', target, source);
await fs.symlink(target, source);
try {
await fs.symlink(target, source);
} catch (e) {
console.error(`symlink(${target}, ${source}) failed: `, e);
}
// await symlink(target, source);
};

View file

@ -22,13 +22,8 @@ if (process.env.NODE_ENV !== 'production') {
(async () => {
const workingDir = '/home';
const fs = await createEditorFS(workingDir)!;
await registerOpenSCADLanguage(fs, workingDir, zipArchives);
// type Mode = State['view']['layout']['mode'];
// const mode: Mode = window.matchMedia("(min-width: 768px)").matches
// ? 'multi' : 'single';
const fs = await createEditorFS('/libraries/');
await registerOpenSCADLanguage(fs, '/', zipArchives);
const initialState = createInitialState(fs, readStateFromFragment());

View file

@ -1,7 +1,7 @@
// Portions of this file are Copyright 2021 Google LLC, and licensed under GPL2+. See COPYING.
import * as monaco from 'monaco-editor/esm/vs/editor/editor.api';
import { getParentDir } from '../fs/filesystem';
import { getFileName, getParentDir } from '../fs/filesystem';
import { spawnOpenSCAD } from "./openscad-runner";
import { processMergedOutputs } from "./output-parser";
import { AbortablePromise, turnIntoDelayableExecution } from '../utils';
@ -14,10 +14,12 @@ export const checkSyntax =
// const timestamp = Date.now();
source = '$preview=true;\n' + source;
sourcePath = getFileName(sourcePath);
const job = spawnOpenSCAD({
inputs: [[sourcePath, source + '\n']],
args: [sourcePath, "-o", "out.ast"],
// workingDir: sourcePath.startsWith('/') ? getParentDir(sourcePath) : '/home'
});
return AbortablePromise<SyntaxCheckOutput>((res, rej) => {
@ -49,26 +51,29 @@ export type RenderArgs = {
isPreview: boolean
}
export const render =
turnIntoDelayableExecution(renderDelay, (params: RenderArgs) => {
const args = [
params.sourcePath,
"-o", "out.stl",
"--export-format=binstl",
...(params.features ?? []).map(f => `--enable=${f}`),
...(params.extraArgs ?? [])
]
turnIntoDelayableExecution(renderDelay, ({sourcePath, source, isPreview, features, extraArgs}: RenderArgs) => {
const prefixLines: string[] = [];
if (params.isPreview) {
if (isPreview) {
prefixLines.push('$preview=true;');
}
const source = [...prefixLines, params.source].join('\n');
source = [...prefixLines, source].join('\n');
sourcePath = getFileName(sourcePath);
const args = [
sourcePath,
"-o", "out.stl",
"--export-format=binstl",
...(features ?? []).map(f => `--enable=${f}`),
...(extraArgs ?? [])
]
const job = spawnOpenSCAD({
// wasmMemory,
inputs: [[params.sourcePath, source]],
inputs: [[sourcePath, source]],
args,
outputPaths: ['out.stl']
outputPaths: ['out.stl'],
// workingDir: sourcePath.startsWith('/') ? getParentDir(sourcePath) : '/home'
});
return AbortablePromise<RenderOutput>((resolve, reject) => {
@ -79,7 +84,7 @@ export const render =
const {logText, markers} = processMergedOutputs(result.mergedOutputs, {
shiftSourceLines: {
sourcePath: params.sourcePath,
sourcePath: sourcePath,
skipLines: prefixLines.length
}
});

View file

@ -2,7 +2,7 @@
import OpenSCAD from "../wasm/openscad.js";
import { createEditorFS, getBrowserFSLibrariesMounts, symlinkLibraries } from "../fs/filesystem";
import { createEditorFS, getBrowserFSLibrariesMounts, getParentDir, symlinkLibraries } from "../fs/filesystem";
import { OpenSCADInvocation, OpenSCADInvocationResults } from "./openscad-runner";
import { zipArchives } from "../fs/zip-archives";
declare var BrowserFS: BrowserFSInterface
@ -33,41 +33,43 @@ addEventListener('message', async (e) => {
console.debug('stderr: ' + text);
mergedOutputs.push({ stderr: text })
},
// ENV: {
// OPENSCADPATH: '/home'
// }
});
// const librariesFolder = '/home/.local/share/OpenSCAD/libraries'
const fs = await createEditorFS();
await symlinkLibraries(allArchiveNames, fs, librariesFolder, '/home');//'/home', '/home');
// This will mount lots of libraries' ZIP archives under /libraries/<name> -> <name>.zip
await createEditorFS('');
instance.FS.mkdir('/libraries');
// https://github.com/emscripten-core/emscripten/issues/10061
const BFS = new BrowserFS.EmscriptenFS(
instance.FS,
instance.PATH ?? {
join2: (a: string, b: string) => `${a}/${b}`,
join: (...args: string[]) => args.join('/'),
}, instance.ERRNO_CODES ?? {});
instance.FS.mount(BFS, {root: '/home'}, '/home');
},
instance.ERRNO_CODES ?? {}
);
instance.FS.mount(BFS, {root: '/'}, '/libraries');
//await symlinkLibraries(allArchiveNames, instance.FS, '/home/libraries', '/home');
await symlinkLibraries(allArchiveNames, instance.FS, '/libraries', "/");
instance.FS.chdir('/home');
// Fonts are seemingly resolved from $(cwd)/fonts
instance.FS.chdir("/");
if (inputs) {
for (const [path, content] of inputs) {
try {
// const parent = getParentDir(path);
// instance.FS.writeFile(path, content);
fs.writeFile(path, content);
instance.FS.writeFile(path, content);
// fs.writeFile(path, content);
} catch (e) {
console.error(`Error while trying to write ${path}`, e);
}
}
}
console.log('Invoking OpenSCAD from ', workingDir, args)
console.log('Invoking OpenSCAD with: ', args)
const start = performance.now();
const exitCode = instance.callMain(args);
const end = performance.now();

View file

@ -3,7 +3,7 @@
import defaultScad from './default-scad';
import { State } from './app-state';
export const defaultSourcePath = 'playground.scad';
export const defaultSourcePath = '/playground.scad';
export const blankProjectState: State = {
params: {

View file

@ -8,8 +8,8 @@ import { formatBytes, formatMillis } from '../utils'
import { getParentDir } from "../fs/filesystem";
// export const isFileWritable = (path: string) => !path.startsWith('/');
export const isFileWritable = (path: string) => getParentDir(path) === '/home';
// export const isFileWritable = (path: string) => !path.startsWith(librariesFolder + '/');
// export const isFileWritable = (path: string) => getParentDir(path) === '/home';
// export const isFileWritable = (path: string) => !path.startsWith('/libraries/');
export class Model {
constructor(private fs: FS, public state: State, private setStateCallback?: (state: State) => void) {
@ -96,7 +96,11 @@ export class Model {
// alert(`TODO: open ${path}`);
if (this.mutate(s => {
s.params.source = new TextDecoder("utf-8").decode(this.fs.readFileSync(path));
s.params.sourcePath = path;
if (s.params.sourcePath != path) {
s.params.sourcePath = path;
s.lastCheckerRun = undefined;
s.output = undefined;
}
})) {
this.processSource();
}
@ -110,10 +114,10 @@ export class Model {
private processSource() {
const params = this.state.params;
if (isFileWritable(params.sourcePath)) {
const absolutePath = `/home/${params.sourcePath}`;
this.fs.writeFile(absolutePath, params.source);
}
// if (isFileWritable(params.sourcePath)) {
// const absolutePath = params.sourcePath.startsWith('/') ? params.sourcePath : `/${params.sourcePath}`;
this.fs.writeFile(params.sourcePath, params.source);
// }
this.checkSyntax();
this.render({isPreview: true, now: false});
}