Simplify file handling: give up trying to execute examples in their folders, just copy all files to /. Breaks autocomplete
This commit is contained in:
parent
bcebd5ae00
commit
51b481866f
8 changed files with 90 additions and 52 deletions
|
|
@ -11,7 +11,7 @@ import { buildUrlForStateParams } from '../state/fragment-state';
|
||||||
import { blankProjectState } from '../state/initial-state';
|
import { blankProjectState } from '../state/initial-state';
|
||||||
import { ModelContext, FSContext } from './contexts';
|
import { ModelContext, FSContext } from './contexts';
|
||||||
import FilePicker, { } from './FilePicker';
|
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/themes/lara-light-indigo/theme.css";
|
||||||
// import "primereact/resources/primereact.min.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??
|
onMount={onMount} // TODO: This looks a bit silly, does it trigger a re-render??
|
||||||
options={{
|
options={{
|
||||||
...openscadEditorOptions,
|
...openscadEditorOptions,
|
||||||
readOnly: !isFileWritable(state.params.sourcePath)
|
// readOnly: !isFileWritable(state.params.sourcePath)
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,8 @@ import { CSSProperties, useContext } from 'react';
|
||||||
import { TreeSelect } from 'primereact/treeselect';
|
import { TreeSelect } from 'primereact/treeselect';
|
||||||
import TreeNode from 'primereact/treenode';
|
import TreeNode from 'primereact/treenode';
|
||||||
import { ModelContext, FSContext } from './contexts';
|
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[] {
|
function listFilesAsNodes(fs: FS, path: string, accept?: (path: string) => boolean): TreeNode[] {
|
||||||
const files: [string, string][] = []
|
const files: [string, string][] = []
|
||||||
|
|
@ -13,7 +14,7 @@ function listFilesAsNodes(fs: FS, path: string, accept?: (path: string) => boole
|
||||||
if (name.startsWith('.')) {
|
if (name.startsWith('.')) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
const childPath = `${path}/${name}`;
|
const childPath = join(path, name);//`${path}/${name}`;
|
||||||
if (accept && !accept(childPath)) {
|
if (accept && !accept(childPath)) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
@ -35,7 +36,8 @@ function listFilesAsNodes(fs: FS, path: string, accept?: (path: string) => boole
|
||||||
}
|
}
|
||||||
nodes.push({
|
nodes.push({
|
||||||
// icon: path == '/home' ? 'pi-home' : ...
|
// 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,
|
label: name,
|
||||||
data: path,
|
data: path,
|
||||||
key: path,
|
key: path,
|
||||||
|
|
@ -54,7 +56,24 @@ export default function FilePicker({className, style}: {className?: string, styl
|
||||||
|
|
||||||
const fs = useContext(FSContext);
|
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 (
|
return (
|
||||||
<TreeSelect
|
<TreeSelect
|
||||||
|
|
|
||||||
|
|
@ -10,13 +10,22 @@ export type FSMounts = {
|
||||||
|
|
||||||
export type Symlinks = {[alias: string]: string};
|
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 const getFileName = (path: string) => path.split('/').splice(-1)[0];
|
||||||
|
|
||||||
export function readDirAsArray(fs: FS, path: string): Promise<string[] | undefined> {
|
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)));
|
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[]) {
|
export async function getBrowserFSLibrariesMounts(archiveNames: string[]) {
|
||||||
const Buffer = BrowserFS.BFSRequire('buffer').Buffer;
|
const Buffer = BrowserFS.BFSRequire('buffer').Buffer;
|
||||||
const fetchData = async (url: string) => (await fetch(url)).arrayBuffer();
|
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') {
|
export async function symlinkLibraries(archiveNames: string[], fs: FS, prefix='/libraries', cwd='/tmp') {
|
||||||
const createSymlink = async (target: string, source: string) => {
|
const createSymlink = async (target: string, source: string) => {
|
||||||
// console.log('symlink', target, source);
|
// 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);
|
// await symlink(target, source);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -22,13 +22,8 @@ if (process.env.NODE_ENV !== 'production') {
|
||||||
|
|
||||||
(async () => {
|
(async () => {
|
||||||
|
|
||||||
const workingDir = '/home';
|
const fs = await createEditorFS('/libraries/');
|
||||||
const fs = await createEditorFS(workingDir)!;
|
await registerOpenSCADLanguage(fs, '/', zipArchives);
|
||||||
await registerOpenSCADLanguage(fs, workingDir, zipArchives);
|
|
||||||
|
|
||||||
// type Mode = State['view']['layout']['mode'];
|
|
||||||
// const mode: Mode = window.matchMedia("(min-width: 768px)").matches
|
|
||||||
// ? 'multi' : 'single';
|
|
||||||
|
|
||||||
const initialState = createInitialState(fs, readStateFromFragment());
|
const initialState = createInitialState(fs, readStateFromFragment());
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
// Portions of this file are Copyright 2021 Google LLC, and licensed under GPL2+. See COPYING.
|
// 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 * 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 { spawnOpenSCAD } from "./openscad-runner";
|
||||||
import { processMergedOutputs } from "./output-parser";
|
import { processMergedOutputs } from "./output-parser";
|
||||||
import { AbortablePromise, turnIntoDelayableExecution } from '../utils';
|
import { AbortablePromise, turnIntoDelayableExecution } from '../utils';
|
||||||
|
|
@ -14,10 +14,12 @@ export const checkSyntax =
|
||||||
// const timestamp = Date.now();
|
// const timestamp = Date.now();
|
||||||
|
|
||||||
source = '$preview=true;\n' + source;
|
source = '$preview=true;\n' + source;
|
||||||
|
sourcePath = getFileName(sourcePath);
|
||||||
|
|
||||||
const job = spawnOpenSCAD({
|
const job = spawnOpenSCAD({
|
||||||
inputs: [[sourcePath, source + '\n']],
|
inputs: [[sourcePath, source + '\n']],
|
||||||
args: [sourcePath, "-o", "out.ast"],
|
args: [sourcePath, "-o", "out.ast"],
|
||||||
|
// workingDir: sourcePath.startsWith('/') ? getParentDir(sourcePath) : '/home'
|
||||||
});
|
});
|
||||||
|
|
||||||
return AbortablePromise<SyntaxCheckOutput>((res, rej) => {
|
return AbortablePromise<SyntaxCheckOutput>((res, rej) => {
|
||||||
|
|
@ -49,26 +51,29 @@ export type RenderArgs = {
|
||||||
isPreview: boolean
|
isPreview: boolean
|
||||||
}
|
}
|
||||||
export const render =
|
export const render =
|
||||||
turnIntoDelayableExecution(renderDelay, (params: RenderArgs) => {
|
turnIntoDelayableExecution(renderDelay, ({sourcePath, source, isPreview, features, extraArgs}: RenderArgs) => {
|
||||||
const args = [
|
|
||||||
params.sourcePath,
|
|
||||||
"-o", "out.stl",
|
|
||||||
"--export-format=binstl",
|
|
||||||
...(params.features ?? []).map(f => `--enable=${f}`),
|
|
||||||
...(params.extraArgs ?? [])
|
|
||||||
]
|
|
||||||
|
|
||||||
const prefixLines: string[] = [];
|
const prefixLines: string[] = [];
|
||||||
if (params.isPreview) {
|
if (isPreview) {
|
||||||
prefixLines.push('$preview=true;');
|
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({
|
const job = spawnOpenSCAD({
|
||||||
// wasmMemory,
|
// wasmMemory,
|
||||||
inputs: [[params.sourcePath, source]],
|
inputs: [[sourcePath, source]],
|
||||||
args,
|
args,
|
||||||
outputPaths: ['out.stl']
|
outputPaths: ['out.stl'],
|
||||||
|
// workingDir: sourcePath.startsWith('/') ? getParentDir(sourcePath) : '/home'
|
||||||
});
|
});
|
||||||
|
|
||||||
return AbortablePromise<RenderOutput>((resolve, reject) => {
|
return AbortablePromise<RenderOutput>((resolve, reject) => {
|
||||||
|
|
@ -79,7 +84,7 @@ export const render =
|
||||||
|
|
||||||
const {logText, markers} = processMergedOutputs(result.mergedOutputs, {
|
const {logText, markers} = processMergedOutputs(result.mergedOutputs, {
|
||||||
shiftSourceLines: {
|
shiftSourceLines: {
|
||||||
sourcePath: params.sourcePath,
|
sourcePath: sourcePath,
|
||||||
skipLines: prefixLines.length
|
skipLines: prefixLines.length
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,7 @@
|
||||||
|
|
||||||
import OpenSCAD from "../wasm/openscad.js";
|
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 { OpenSCADInvocation, OpenSCADInvocationResults } from "./openscad-runner";
|
||||||
import { zipArchives } from "../fs/zip-archives";
|
import { zipArchives } from "../fs/zip-archives";
|
||||||
declare var BrowserFS: BrowserFSInterface
|
declare var BrowserFS: BrowserFSInterface
|
||||||
|
|
@ -33,41 +33,43 @@ addEventListener('message', async (e) => {
|
||||||
console.debug('stderr: ' + text);
|
console.debug('stderr: ' + text);
|
||||||
mergedOutputs.push({ stderr: text })
|
mergedOutputs.push({ stderr: text })
|
||||||
},
|
},
|
||||||
// ENV: {
|
|
||||||
// OPENSCADPATH: '/home'
|
|
||||||
// }
|
|
||||||
});
|
});
|
||||||
|
|
||||||
// const librariesFolder = '/home/.local/share/OpenSCAD/libraries'
|
// This will mount lots of libraries' ZIP archives under /libraries/<name> -> <name>.zip
|
||||||
const fs = await createEditorFS();
|
await createEditorFS('');
|
||||||
await symlinkLibraries(allArchiveNames, fs, librariesFolder, '/home');//'/home', '/home');
|
|
||||||
|
instance.FS.mkdir('/libraries');
|
||||||
|
|
||||||
// https://github.com/emscripten-core/emscripten/issues/10061
|
// https://github.com/emscripten-core/emscripten/issues/10061
|
||||||
const BFS = new BrowserFS.EmscriptenFS(
|
const BFS = new BrowserFS.EmscriptenFS(
|
||||||
instance.FS,
|
instance.FS,
|
||||||
instance.PATH ?? {
|
instance.PATH ?? {
|
||||||
join2: (a: string, b: string) => `${a}/${b}`,
|
join2: (a: string, b: string) => `${a}/${b}`,
|
||||||
join: (...args: string[]) => args.join('/'),
|
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) {
|
if (inputs) {
|
||||||
for (const [path, content] of inputs) {
|
for (const [path, content] of inputs) {
|
||||||
try {
|
try {
|
||||||
// const parent = getParentDir(path);
|
// const parent = getParentDir(path);
|
||||||
// instance.FS.writeFile(path, content);
|
instance.FS.writeFile(path, content);
|
||||||
fs.writeFile(path, content);
|
// fs.writeFile(path, content);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.error(`Error while trying to write ${path}`, 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 start = performance.now();
|
||||||
const exitCode = instance.callMain(args);
|
const exitCode = instance.callMain(args);
|
||||||
const end = performance.now();
|
const end = performance.now();
|
||||||
|
|
|
||||||
|
|
@ -3,7 +3,7 @@
|
||||||
import defaultScad from './default-scad';
|
import defaultScad from './default-scad';
|
||||||
import { State } from './app-state';
|
import { State } from './app-state';
|
||||||
|
|
||||||
export const defaultSourcePath = 'playground.scad';
|
export const defaultSourcePath = '/playground.scad';
|
||||||
|
|
||||||
export const blankProjectState: State = {
|
export const blankProjectState: State = {
|
||||||
params: {
|
params: {
|
||||||
|
|
|
||||||
|
|
@ -8,8 +8,8 @@ import { formatBytes, formatMillis } from '../utils'
|
||||||
import { getParentDir } from "../fs/filesystem";
|
import { getParentDir } from "../fs/filesystem";
|
||||||
|
|
||||||
// export const isFileWritable = (path: string) => !path.startsWith('/');
|
// export const isFileWritable = (path: string) => !path.startsWith('/');
|
||||||
export const isFileWritable = (path: string) => getParentDir(path) === '/home';
|
// export const isFileWritable = (path: string) => getParentDir(path) === '/home';
|
||||||
// export const isFileWritable = (path: string) => !path.startsWith(librariesFolder + '/');
|
// export const isFileWritable = (path: string) => !path.startsWith('/libraries/');
|
||||||
|
|
||||||
export class Model {
|
export class Model {
|
||||||
constructor(private fs: FS, public state: State, private setStateCallback?: (state: State) => void) {
|
constructor(private fs: FS, public state: State, private setStateCallback?: (state: State) => void) {
|
||||||
|
|
@ -96,7 +96,11 @@ export class Model {
|
||||||
// alert(`TODO: open ${path}`);
|
// alert(`TODO: open ${path}`);
|
||||||
if (this.mutate(s => {
|
if (this.mutate(s => {
|
||||||
s.params.source = new TextDecoder("utf-8").decode(this.fs.readFileSync(path));
|
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();
|
this.processSource();
|
||||||
}
|
}
|
||||||
|
|
@ -110,10 +114,10 @@ export class Model {
|
||||||
|
|
||||||
private processSource() {
|
private processSource() {
|
||||||
const params = this.state.params;
|
const params = this.state.params;
|
||||||
if (isFileWritable(params.sourcePath)) {
|
// if (isFileWritable(params.sourcePath)) {
|
||||||
const absolutePath = `/home/${params.sourcePath}`;
|
// const absolutePath = params.sourcePath.startsWith('/') ? params.sourcePath : `/${params.sourcePath}`;
|
||||||
this.fs.writeFile(absolutePath, params.source);
|
this.fs.writeFile(params.sourcePath, params.source);
|
||||||
}
|
// }
|
||||||
this.checkSyntax();
|
this.checkSyntax();
|
||||||
this.render({isPreview: true, now: false});
|
this.render({isPreview: true, now: false});
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue