Add react-stl-viewer + fix preview vs. render flow
This commit is contained in:
parent
d8cc597eba
commit
42d0d0a5d2
17 changed files with 313 additions and 109 deletions
17
LICENSE.md
17
LICENSE.md
|
|
@ -15,6 +15,7 @@ The WASM build was made possible by https://github.com/DSchroer/openscad-wasm.
|
|||
|
||||
* [Monaco Editor](#monaco-editor)
|
||||
* [Viewstl Plugin](#viewstl-plugin)
|
||||
* [React Stl Viewer](#react-stl-viewer)
|
||||
* [Three.js](#threejs)
|
||||
* [Boost](#boost)
|
||||
* [GNU MPFR](#gnu-mpfr)
|
||||
|
|
@ -106,6 +107,22 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|||
SOFTWARE.
|
||||
```
|
||||
|
||||
## React STL Viewer
|
||||
|
||||
https://github.com/gabotechs/react-stl-viewer
|
||||
|
||||
```
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2022 Gabriel Musat Mestre
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
```
|
||||
|
||||
## Three.js
|
||||
|
||||
Used for the 3D visualization
|
||||
|
|
|
|||
|
|
@ -15,6 +15,7 @@
|
|||
"react": "^18.2.0",
|
||||
"react-dom": "^18.2.0",
|
||||
"react-scripts": "^5.0.1",
|
||||
"react-stl-viewer": "^2.2.5",
|
||||
"web-vitals": "^2.1.4"
|
||||
},
|
||||
"scripts": {
|
||||
|
|
@ -49,7 +50,6 @@
|
|||
"rollup": "^2.79.1",
|
||||
"ts-loader": "^9.4.2",
|
||||
"tslib": "^2.5.0",
|
||||
"typescript": "^5.0.2",
|
||||
"webpack-cli": "^5.0.1"
|
||||
}
|
||||
}
|
||||
|
|
|
|||
8
public/browserfs.min.js
vendored
Normal file
8
public/browserfs.min.js
vendored
Normal file
File diff suppressed because one or more lines are too long
|
|
@ -3,8 +3,8 @@
|
|||
<head>
|
||||
<meta charset="utf-8" />
|
||||
|
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/BrowserFS/2.0.0/browserfs.min.js"></script>
|
||||
<!-- <script src="https://cdnjs.cloudflare.com/ajax/libs/BrowserFS/2.0.0/browserfs.js"></script> -->
|
||||
<!-- <script src="https://cdnjs.cloudflare.com/ajax/libs/BrowserFS/2.0.0/browserfs.min.js" defer></script> -->
|
||||
<script src="browserfs.min.js"></script>
|
||||
|
||||
<link rel="icon" href="%PUBLIC_URL%/favicon.ico" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
|
|
@ -27,6 +27,17 @@
|
|||
Learn how to configure a non-root public URL by running `npm run build`.
|
||||
-->
|
||||
<title>OpenSCAD Playground</title>
|
||||
<style>
|
||||
#root,
|
||||
body {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
flex: 1;
|
||||
margin: 0;
|
||||
width: 100vw;
|
||||
height: 100vh;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<noscript>You need to enable JavaScript to run the OpenSCAD Playground.</noscript>
|
||||
|
|
|
|||
|
|
@ -36,3 +36,10 @@
|
|||
transform: rotate(360deg);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
.logs-container {
|
||||
overflow-y: scroll;
|
||||
height: calc(min(200px, 30vh));
|
||||
position: relative;
|
||||
}
|
||||
100
src/App.tsx
100
src/App.tsx
|
|
@ -1,55 +1,103 @@
|
|||
// Portions of this file are Copyright 2021 Google LLC, and licensed under GPL2+. See COPYING.
|
||||
|
||||
import React, { useState } from 'react';
|
||||
import React, { useContext, useEffect, useState } from 'react';
|
||||
import {ModelContext, State} from './app-state'
|
||||
import Editor, { loader, Monaco } from '@monaco-editor/react';
|
||||
import './App.css';
|
||||
import openscadEditorOptions from './language/openscad-editor-options';
|
||||
import { Model } from './model';
|
||||
import * as monaco from 'monaco-editor/esm/vs/editor/editor.api';
|
||||
import {StlViewer} from "react-stl-viewer";
|
||||
|
||||
let monacoInstance: Monaco
|
||||
loader.init().then(mi => monacoInstance = mi);
|
||||
|
||||
export function App({initialState}: {initialState: State}) {
|
||||
const [state, setState] = useState(initialState);
|
||||
function EditorPanel() {
|
||||
const model = useContext(ModelContext);
|
||||
const [editor, setEditor] = useState(null as monaco.editor.IStandaloneCodeEditor | null)
|
||||
const model = new Model(state, setState);
|
||||
|
||||
if (editor) {
|
||||
const checkerRun = model.state.lastCheckerRun;
|
||||
const editorModel = editor.getModel();
|
||||
if (editorModel && state.checkerRun) {
|
||||
monacoInstance.editor.setModelMarkers(editorModel, 'openscad', state.checkerRun.markers);
|
||||
if (editorModel && checkerRun) {
|
||||
monacoInstance.editor.setModelMarkers(editorModel, 'openscad', checkerRun.markers);
|
||||
}
|
||||
}
|
||||
|
||||
const source = model.source;
|
||||
const onMount = (editor: monaco.editor.IStandaloneCodeEditor) => {
|
||||
editor.addAction({
|
||||
id: "openscad-render",
|
||||
label: "Render OpenSCAD",
|
||||
keybindings: [
|
||||
monaco.KeyMod.CtrlCmd | monaco.KeyCode.Enter,
|
||||
monaco.KeyCode.F6,
|
||||
],
|
||||
run: () => model.render()
|
||||
});
|
||||
setEditor(editor)
|
||||
}
|
||||
|
||||
return (
|
||||
<ModelContext.Provider value={model}>
|
||||
<div className="App">
|
||||
<header className="App-header">
|
||||
<img src="logo.png" className="App-logo" alt="logo" />
|
||||
<p>
|
||||
Edit <code>src/App.tsx</code> and save to reload.
|
||||
</p>
|
||||
<a
|
||||
className="App-link"
|
||||
href="https://openscad.org"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
>
|
||||
Learn OpenSCAD
|
||||
</a>
|
||||
<div className="editor-panel" style={{
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
position: 'relative'
|
||||
}}>
|
||||
<Editor
|
||||
className="openscad-editor"
|
||||
defaultLanguage="openscad"
|
||||
value={source}
|
||||
value={model.state.params.source}
|
||||
onChange={s => model.source = s ?? ''}
|
||||
onMount={e => setEditor(e)} // 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={openscadEditorOptions}
|
||||
height="50vh"/>
|
||||
</header>
|
||||
|
||||
<div className="logs-container">
|
||||
<pre><code id="logs">{model.state.lastCheckerRun?.logText ?? 'No log yet!'}</code></pre>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
export function App({initialState}: {initialState: State}) {
|
||||
const [state, setState] = useState(initialState);
|
||||
const [editor, setEditor] = useState(null as monaco.editor.IStandaloneCodeEditor | null)
|
||||
|
||||
if (editor) {
|
||||
const editorModel = editor.getModel();
|
||||
if (editorModel && state.lastCheckerRun) {
|
||||
monacoInstance.editor.setModelMarkers(editorModel, 'openscad', state.lastCheckerRun.markers);
|
||||
}
|
||||
}
|
||||
|
||||
const model = new Model(state, setState);
|
||||
useEffect(() => model.init());
|
||||
|
||||
return (
|
||||
<ModelContext.Provider value={model}>
|
||||
<div style={{display: 'flex', flexDirection: 'column', flex: 1}}>
|
||||
<div style={{display: 'flex', flexDirection: 'row', flex: 1}}>
|
||||
<div style={{width: "50vw"}}>
|
||||
<EditorPanel/>
|
||||
</div>
|
||||
{state.output?.stlFileURL &&
|
||||
<StlViewer
|
||||
style={{
|
||||
flex: 1
|
||||
}}
|
||||
showAxes={true}
|
||||
orbitControls
|
||||
shadows
|
||||
url={state.output?.stlFileURL ?? ''}
|
||||
/>
|
||||
}
|
||||
</div>
|
||||
<div style={{display: 'flex', flexDirection: 'row'}}>
|
||||
<button onClick={() => model.render()}>Render</button>
|
||||
{model.state.previewing && 'previewing... '}
|
||||
{model.state.rendering && 'rendering... '}
|
||||
{model.state.checkingSyntax && 'checking syntax... '}
|
||||
</div>
|
||||
</div>
|
||||
</ModelContext.Provider>
|
||||
);
|
||||
|
|
|
|||
11
src/BrowserFS.ts → src/BrowserFS.d.ts
vendored
11
src/BrowserFS.ts → src/BrowserFS.d.ts
vendored
|
|
@ -1,12 +1,15 @@
|
|||
// Portions of this file are Copyright 2021 Google LLC, and licensed under GPL2+. See COPYING.
|
||||
|
||||
import { FS } from "./filesystem";
|
||||
// import { FS } from "./filesystem";
|
||||
|
||||
interface EmscriptenFS extends FS {
|
||||
declare interface FS {
|
||||
readdir(path: string, cb: (err: any, files: string[]) => void): void;
|
||||
symlink(target: string, source: string): void;
|
||||
}
|
||||
|
||||
};
|
||||
declare interface EmscriptenFS extends FS {}
|
||||
|
||||
export let BrowserFS = (window as any)['BrowserFS'] as {
|
||||
declare type BrowserFSInterface = {
|
||||
BFSRequire: (name: string) => any,
|
||||
|
||||
install: (windowOrSelf: Window) => void,
|
||||
|
|
@ -2,19 +2,22 @@
|
|||
|
||||
import * as monaco from 'monaco-editor/esm/vs/editor/editor.api';
|
||||
import { spawnOpenSCAD } from "./openscad-runner";
|
||||
import { joinMergedOutputs, parseMergedOutputs } from "./output-parser";
|
||||
import { processMergedOutputs } from "./output-parser";
|
||||
import { AbortablePromise, turnIntoDelayableExecution } from './utils';
|
||||
|
||||
const syntaxDelay = 300;
|
||||
|
||||
type SyntaxCheckOutput = {logText: string, markers: monaco.editor.IMarkerData[]};
|
||||
export const checkSyntax = (source: string, callback: (out: SyntaxCheckOutput) => void) =>
|
||||
turnIntoDelayableExecution(syntaxDelay, () => {
|
||||
export const checkSyntax =
|
||||
turnIntoDelayableExecution(syntaxDelay, (source: string) => {
|
||||
// const timestamp = Date.now();
|
||||
|
||||
source = '$preview=true;\n' + source;
|
||||
const sourceFile = 'input.scad';
|
||||
|
||||
const job = spawnOpenSCAD({
|
||||
inputs: [['input.scad', source + '\n']],
|
||||
args: ["input.scad", "-o", "out.ast"],
|
||||
inputs: [[sourceFile, source + '\n']],
|
||||
args: [sourceFile, "-o", "out.ast"],
|
||||
});
|
||||
|
||||
return AbortablePromise<SyntaxCheckOutput>((res, rej) => {
|
||||
|
|
@ -22,9 +25,7 @@ export const checkSyntax = (source: string, callback: (out: SyntaxCheckOutput) =
|
|||
try {
|
||||
const result = await job;
|
||||
// console.log(result);
|
||||
const logText = joinMergedOutputs(result.mergedOutputs);
|
||||
const markers = parseMergedOutputs(result.mergedOutputs);
|
||||
res({logText, markers});
|
||||
res(processMergedOutputs(result.mergedOutputs, {shiftSourceLines: {[sourceFile]: 1}}));
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
rej(e);
|
||||
|
|
@ -32,25 +33,34 @@ export const checkSyntax = (source: string, callback: (out: SyntaxCheckOutput) =
|
|||
})()
|
||||
return () => job.kill();
|
||||
});
|
||||
}, callback);
|
||||
|
||||
var sourceFileName;
|
||||
// var editor;
|
||||
});
|
||||
|
||||
var renderDelay = 1000;
|
||||
type RenderOutput = {stlFile: File, logText: string, markers: monaco.editor.IMarkerData[], elapsedMillis?: number}
|
||||
export type RenderOutput = {stlFile: File, logText: string, markers: monaco.editor.IMarkerData[], elapsedMillis?: number}
|
||||
|
||||
export const render = (source: string, features: string[], callback: (result: RenderOutput) => void) =>
|
||||
turnIntoDelayableExecution(renderDelay, () => {
|
||||
export type RenderArgs = {
|
||||
source: string,
|
||||
features?: string[],
|
||||
extraArgs?: string[],
|
||||
isPreview?: boolean
|
||||
}
|
||||
export const render =
|
||||
turnIntoDelayableExecution(renderDelay, (args: RenderArgs) => {
|
||||
|
||||
const prefixLines: string[] = [];
|
||||
if (args.isPreview) prefixLines.push('$preview=false;');
|
||||
const source = args.isPreview ? [...prefixLines, args.source].join('\n') : args.source
|
||||
const inputFile = 'input.scad';
|
||||
|
||||
const job = spawnOpenSCAD({
|
||||
// wasmMemory,
|
||||
inputs: [['input.scad', source]],
|
||||
args: [
|
||||
"input.scad",
|
||||
inputFile,
|
||||
"-o", "out.stl",
|
||||
"--export-format=binstl",
|
||||
...features.map(f => `--enable=${f}`),
|
||||
...(args.features ?? []).map(f => `--enable=${f}`),
|
||||
...(args.extraArgs ?? [])
|
||||
],
|
||||
outputPaths: ['out.stl']
|
||||
});
|
||||
|
|
@ -61,8 +71,9 @@ export const render = (source: string, features: string[], callback: (result: Re
|
|||
const result = await job;
|
||||
console.log(result);
|
||||
|
||||
const logText = joinMergedOutputs(result.mergedOutputs);
|
||||
const markers = parseMergedOutputs(result.mergedOutputs);
|
||||
const {logText, markers} = processMergedOutputs(result.mergedOutputs, {
|
||||
shiftSourceLines: {[inputFile]: prefixLines.length}
|
||||
});
|
||||
|
||||
if (result.error) {
|
||||
reject(result.error);
|
||||
|
|
@ -87,4 +98,4 @@ export const render = (source: string, features: string[], callback: (result: Re
|
|||
|
||||
return () => job.kill()
|
||||
});
|
||||
}, callback);
|
||||
});
|
||||
|
|
|
|||
|
|
@ -8,23 +8,27 @@ import { Model } from "./model"
|
|||
export interface State {
|
||||
params: {
|
||||
source: string,
|
||||
features: string[],
|
||||
},
|
||||
checkerRun?: {
|
||||
lastCheckerRun?: {
|
||||
logText: string,
|
||||
markers: monaco.editor.IMarkerData[]
|
||||
}
|
||||
rendering?: boolean,
|
||||
previewing?: boolean,
|
||||
checkingSyntax?: boolean,
|
||||
|
||||
output?: {
|
||||
path: string,
|
||||
timestamp: number,
|
||||
sizeBytes: number,
|
||||
formattedSize: string,
|
||||
stlFile: File,
|
||||
stlFileURL: string,
|
||||
},
|
||||
};
|
||||
|
||||
export const ModelContext = createContext(new Model(
|
||||
{
|
||||
params: {
|
||||
source: ''
|
||||
source: '',
|
||||
features: ['manifold', 'lazy-union'],
|
||||
}
|
||||
},
|
||||
() => { throw new Error('Not implemented'); }
|
||||
|
|
|
|||
|
|
@ -1,18 +1,19 @@
|
|||
// Portions of this file are Copyright 2021 Google LLC, and licensed under GPL2+. See COPYING.
|
||||
|
||||
import { State } from "./app-state";
|
||||
import { validateString } from "./utils";
|
||||
import { validateArray, validateString } from "./utils";
|
||||
|
||||
export function writeStateInFragment(state: State) {
|
||||
window.location.hash = encodeURIComponent(JSON.stringify(state));
|
||||
window.location.hash = encodeURIComponent(JSON.stringify(state.params));
|
||||
}
|
||||
export function readStateFromFragment(): State | null {
|
||||
if (window.location.hash.startsWith('#') && window.location.hash.length > 1) {
|
||||
try {
|
||||
const state = JSON.parse(decodeURIComponent(window.location.hash.substring(1)));
|
||||
const params = JSON.parse(decodeURIComponent(window.location.hash.substring(1)));
|
||||
return {
|
||||
params: {
|
||||
source: validateString(state.params?.source),
|
||||
source: validateString(params?.source),
|
||||
features: validateArray(params?.features, validateString),
|
||||
},
|
||||
};
|
||||
} catch (e) {
|
||||
|
|
|
|||
|
|
@ -2,7 +2,6 @@
|
|||
|
||||
import React from 'react';
|
||||
import ReactDOM from 'react-dom/client';
|
||||
import './index.css';
|
||||
import {App} from './App';
|
||||
import reportWebVitals from './reportWebVitals';
|
||||
import { createEditorFS } from './filesystem';
|
||||
|
|
@ -10,6 +9,7 @@ import { registerOpenSCADLanguage } from './language/openscad-register-language'
|
|||
import { zipArchives } from './zip-archives';
|
||||
import {readStateFromFragment} from './fragment-state'
|
||||
import { State } from './app-state';
|
||||
import './index.css';
|
||||
|
||||
(async () => {
|
||||
|
||||
|
|
@ -23,6 +23,12 @@ import { State } from './app-state';
|
|||
}
|
||||
} as State;
|
||||
|
||||
const defaultFeatures = ['manifold', 'fast-csg', 'lazy-union'];
|
||||
defaultFeatures.forEach(f => {
|
||||
if (initialState.params.features.indexOf(f) < 0)
|
||||
initialState.params.features.push(f);
|
||||
});
|
||||
|
||||
const root = ReactDOM.createRoot(
|
||||
document.getElementById('root') as HTMLElement
|
||||
);
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
// Portions of this file are Copyright 2021 Google LLC, and licensed under GPL2+. See COPYING.
|
||||
|
||||
export default `
|
||||
$fa=undef;
|
||||
$fs=undef;
|
||||
|
|
|
|||
|
|
@ -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 { FS, readDirAsArray, Symlinks } from '../filesystem';
|
||||
import { readDirAsArray, Symlinks } from '../filesystem';
|
||||
import { ParsedFile, ParsedFunctionoidDef, parseOpenSCAD, stripComments } from './openscad-pseudoparser';
|
||||
import builtinSignatures from './openscad-builtins'
|
||||
import { mapObject } from '../utils';
|
||||
|
|
|
|||
88
src/model.ts
88
src/model.ts
|
|
@ -1,25 +1,32 @@
|
|||
// Portions of this file are Copyright 2021 Google LLC, and licensed under GPL2+. See COPYING.
|
||||
|
||||
import { checkSyntax } from "./actions";
|
||||
import { checkSyntax, render, RenderArgs, RenderOutput } from "./actions";
|
||||
import { State } from "./app-state";
|
||||
import { bubbleUpDeepMutations } from "./deep-mutate";
|
||||
import { writeStateInFragment } from "./fragment-state";
|
||||
|
||||
export class Model {
|
||||
constructor(private state: State, private setState_: (state: State) => void) {}
|
||||
constructor(public state: State, private setStateCallback?: (state: State) => void) {
|
||||
}
|
||||
|
||||
init() {
|
||||
if (!this.state.output && !this.state.lastCheckerRun && !this.state.previewing && !this.state.checkingSyntax && !this.state.rendering &&
|
||||
this.state.params.source.trim() != '') {
|
||||
this.processSource();
|
||||
}
|
||||
}
|
||||
|
||||
private setState(state: State) {
|
||||
this.state = state;
|
||||
this.setState_(state);
|
||||
writeStateInFragment(state);
|
||||
this.setStateCallback && this.setStateCallback(state);
|
||||
}
|
||||
|
||||
mutate(f: (state: State) => void) {
|
||||
const state = this.state;
|
||||
const mutated = bubbleUpDeepMutations(state, f);
|
||||
const mutated = bubbleUpDeepMutations(this.state, f);
|
||||
// No matter how deep the mutation happened, the top-level object's identity
|
||||
// will have changed iff the mutated values are different.
|
||||
if (mutated !== state) {
|
||||
if (mutated !== this.state) {
|
||||
this.setState(mutated);
|
||||
return true;
|
||||
}
|
||||
|
|
@ -27,16 +34,73 @@ export class Model {
|
|||
return false;
|
||||
}
|
||||
|
||||
get source(): string {
|
||||
return this.state.params.source;//.source, this.state.editor;//params.source.content;
|
||||
}
|
||||
// get features() { return this.state_.params.features; }
|
||||
|
||||
// get source(): string { return this.state_.params.source; }
|
||||
|
||||
set source(source) {
|
||||
if (this.mutate(s => s.params.source = source)) {
|
||||
checkSyntax(source, checkerRun => this.mutate(s => s.checkerRun = checkerRun))({now: false});
|
||||
set source(source: string) {
|
||||
if (this.mutate(s => { s.params.source = source; })) {
|
||||
this.processSource();
|
||||
}
|
||||
}
|
||||
|
||||
private processSource() {
|
||||
this.mutate(s => {
|
||||
s.previewing = true;
|
||||
s.checkingSyntax = true;
|
||||
});
|
||||
checkSyntax(this.state.params.source)({now: false, callback: checkerRun => this.mutate(s => {
|
||||
s.lastCheckerRun = checkerRun;
|
||||
s.checkingSyntax = false;
|
||||
})});
|
||||
render({...this.renderArgs, isPreview: true})({now: false, callback: output => this.handleRenderOutput(output, s => {
|
||||
s.previewing = false;
|
||||
})});
|
||||
}
|
||||
|
||||
// checkSyntax() {
|
||||
// this.mutate(s => s.checkingSyntax = true);
|
||||
// checkSyntax(this.state.params.source)({now: false, callback: checkerRun => this.mutate(s => {
|
||||
// s.lastCheckerRun = checkerRun;
|
||||
// s.checkingSyntax = false;
|
||||
// })});
|
||||
// }
|
||||
// preview() {
|
||||
// this.mutate(s => s.previewing = true);
|
||||
// render({...this.renderArgs, isPreview: true})({now: false, callback: output => this.handleRenderOutput(output, s => {
|
||||
// s.previewing = false;
|
||||
// })});
|
||||
// }
|
||||
|
||||
private handleRenderOutput(output: RenderOutput, extraMutations: (s: State) => void) {
|
||||
this.mutate(s => {
|
||||
s.lastCheckerRun = {
|
||||
logText: output.logText,
|
||||
markers: output.markers,
|
||||
}
|
||||
if (s.output?.stlFileURL) {
|
||||
URL.revokeObjectURL(s.output.stlFileURL);
|
||||
}
|
||||
s.output = {
|
||||
stlFile: output.stlFile,
|
||||
stlFileURL: URL.createObjectURL(output.stlFile),
|
||||
};
|
||||
extraMutations(s);
|
||||
});
|
||||
}
|
||||
|
||||
private get renderArgs(): RenderArgs {
|
||||
const source = this.state.params.source;
|
||||
const features = this.state.params.features;
|
||||
return {source, features, extraArgs: ['-D$preview=true']};
|
||||
}
|
||||
|
||||
render() {
|
||||
this.mutate(s => s.rendering = true);
|
||||
|
||||
render(this.renderArgs)({now: true, callback: output => this.handleRenderOutput(output, s => {
|
||||
s.rendering = false;
|
||||
})})
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,16 +1,32 @@
|
|||
import * as monaco from 'monaco-editor/esm/vs/editor/editor.api';
|
||||
import { MergedOutputs } from "./openscad-worker";
|
||||
|
||||
export function joinMergedOutputs(mergedOutputs: MergedOutputs) {
|
||||
const ignoredLogs = new Set([
|
||||
'Could not initialize localization.'
|
||||
]);
|
||||
|
||||
type MergedOutputsOptions = {
|
||||
shiftSourceLines: {[path: string]: number}
|
||||
}
|
||||
export const processMergedOutputs = (outputs: MergedOutputs, opts: MergedOutputsOptions) => ({
|
||||
logText: joinMergedOutputs(outputs, opts),
|
||||
markers: parseMergedOutputs(outputs, opts)
|
||||
});
|
||||
|
||||
export function joinMergedOutputs(mergedOutputs: MergedOutputs, opts: MergedOutputsOptions) {
|
||||
let allLines = [];
|
||||
for (const {stderr, stdout, error} of mergedOutputs){
|
||||
allLines.push(stderr ?? stdout ?? `EXCEPTION: ${error}`);
|
||||
const line = stderr ?? stdout ?? `EXCEPTION: ${error}`;
|
||||
if (ignoredLogs.has(line)) {
|
||||
continue;
|
||||
}
|
||||
allLines.push(line);
|
||||
}
|
||||
|
||||
return allLines.join("\n");
|
||||
}
|
||||
|
||||
export function parseMergedOutputs(mergedOutputs: MergedOutputs): monaco.editor.IMarkerData[] {
|
||||
export function parseMergedOutputs(mergedOutputs: MergedOutputs, opts: MergedOutputsOptions): monaco.editor.IMarkerData[] {
|
||||
let unmatchedLines = [];
|
||||
|
||||
const markers = [];
|
||||
|
|
@ -25,6 +41,10 @@ export function parseMergedOutputs(mergedOutputs: MergedOutputs): monaco.editor.
|
|||
severity: monaco.MarkerSeverity.Error
|
||||
})
|
||||
}
|
||||
const getLine = (path: string, lineStr: string) => {
|
||||
const shift = opts.shiftSourceLines[path] ?? 0;
|
||||
return Number(lineStr) - shift;
|
||||
}
|
||||
for (const {stderr, stdout, error} of mergedOutputs){
|
||||
if (stderr) {
|
||||
if (stderr.startsWith('ERROR:')) errorCount++;
|
||||
|
|
@ -33,14 +53,14 @@ export function parseMergedOutputs(mergedOutputs: MergedOutputs): monaco.editor.
|
|||
let m = /^ERROR: Parser error in file "([^"]+)", line (\d+): (.*)$/.exec(stderr)
|
||||
if (m) {
|
||||
const [_, file, line, error] = m
|
||||
addError(error, file, Number(line));
|
||||
addError(error, file, getLine(file, line));
|
||||
continue;
|
||||
}
|
||||
|
||||
m = /^ERROR: Parser error: (.*?) in file ([^",]+), line (\d+)$/.exec(stderr)
|
||||
if (m) {
|
||||
const [_, error, file, line] = m
|
||||
addError(error, file, Number(line));
|
||||
addError(error, file, getLine(file, line));
|
||||
continue;
|
||||
}
|
||||
|
||||
|
|
@ -48,9 +68,9 @@ export function parseMergedOutputs(mergedOutputs: MergedOutputs): monaco.editor.
|
|||
if (m) {
|
||||
const [_, warning, file, line] = m
|
||||
markers.push({
|
||||
startLineNumber: Number(line),
|
||||
startLineNumber: getLine(file, line),
|
||||
startColumn: 1,
|
||||
endLineNumber: Number(line),
|
||||
endLineNumber: getLine(file, line),
|
||||
endColumn: 1000,
|
||||
message: warning,
|
||||
severity: monaco.MarkerSeverity.Warning
|
||||
|
|
|
|||
12
src/utils.ts
12
src/utils.ts
|
|
@ -22,17 +22,20 @@ export function AbortablePromise<T>(f: (resolve: (result: T) => void, reject: (e
|
|||
return Object.assign(promise, {kill: kill!});
|
||||
}
|
||||
|
||||
|
||||
export function turnIntoDelayableExecution<T>(delay: number, job: () => AbortablePromise<T>, callback: (result: T) => void) {
|
||||
// <T extends any[]>(...args: T)
|
||||
export function turnIntoDelayableExecution<T extends any[], R>(
|
||||
delay: number,
|
||||
job: (...args: T) => AbortablePromise<R>) {
|
||||
let pendingId: number | null;
|
||||
let runningJobKillSignal: (() => void) | null;
|
||||
|
||||
return (...args: T) => async ({now, callback}: {now: boolean, callback: (result: R) => void}) => {
|
||||
const doExecute = async () => {
|
||||
if (runningJobKillSignal) {
|
||||
runningJobKillSignal();
|
||||
runningJobKillSignal = null;
|
||||
}
|
||||
const abortablePromise = job();
|
||||
const abortablePromise = job(...args);
|
||||
runningJobKillSignal = abortablePromise.kill;
|
||||
try {
|
||||
callback(await abortablePromise);
|
||||
|
|
@ -40,7 +43,6 @@ export function turnIntoDelayableExecution<T>(delay: number, job: () => Abortabl
|
|||
runningJobKillSignal = null;
|
||||
}
|
||||
}
|
||||
return async ({now}: {now: boolean}) => {
|
||||
if (pendingId) {
|
||||
clearTimeout(pendingId);
|
||||
pendingId = null;
|
||||
|
|
@ -54,7 +56,7 @@ export function turnIntoDelayableExecution<T>(delay: number, job: () => Abortabl
|
|||
}
|
||||
|
||||
export const validateString = (s: string, orElse: () => string = () => '') => s != null && typeof s === 'string' ? s : orElse();
|
||||
export const validateArray = <T>(a: Array<T>, validateElement: (e: T) => T, orElse: () => T[]) => {
|
||||
export const validateArray = <T>(a: Array<T>, validateElement: (e: T) => T, orElse: () => T[] = () => []) => {
|
||||
if (!(a instanceof Array)) return orElse();
|
||||
return a.map(validateElement);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@
|
|||
"ES2021",
|
||||
"WebWorker",
|
||||
],
|
||||
"rootDir": "src",
|
||||
"outDir": "js",
|
||||
"allowJs": true,
|
||||
"skipLibCheck": true,
|
||||
|
|
@ -19,7 +20,7 @@
|
|||
"moduleResolution": "node",
|
||||
"resolveJsonModule": true,
|
||||
"isolatedModules": true,
|
||||
"noEmit": true,
|
||||
"noEmit": false,
|
||||
"jsx": "react-jsx"
|
||||
},
|
||||
"include": [
|
||||
|
|
|
|||
Loading…
Reference in a new issue