state/model files refactoring
This commit is contained in:
parent
4350b67f80
commit
05fb68ed14
11 changed files with 209 additions and 89 deletions
|
|
@ -1,11 +1,11 @@
|
|||
// Portions of this file are Copyright 2021 Google LLC, and licensed under GPL2+. See COPYING.
|
||||
|
||||
import React, { CSSProperties, forwardRef, useContext, useEffect, useRef, useState } from 'react';
|
||||
import {ModelContext, MultiLayoutComponentId, SingleLayoutComponentId, State} from './app-state'
|
||||
import {MultiLayoutComponentId, SingleLayoutComponentId, State} from './state/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 { Model, ModelContext } from './state/model';
|
||||
import * as monaco from 'monaco-editor/esm/vs/editor/editor.api';
|
||||
import {StlViewer} from "react-stl-viewer";
|
||||
import { Button } from 'primereact/button';
|
||||
|
|
@ -18,11 +18,14 @@ import { Badge } from 'primereact/badge';
|
|||
import { Menu } from 'primereact/menu';
|
||||
import { ToggleButton } from 'primereact/togglebutton';
|
||||
import { ConfirmDialog, confirmDialog } from 'primereact/confirmdialog';
|
||||
import { Toast } from 'primereact/toast';
|
||||
import { getParentDir } from './filesystem';
|
||||
import { BreadCrumb } from 'primereact/breadcrumb';
|
||||
import { ScrollPanel } from 'primereact/scrollpanel';
|
||||
import { Splitter, SplitterPanel } from 'primereact/splitter';
|
||||
import { Toolbar } from 'primereact/toolbar';
|
||||
import { buildUrlForStateParams } from './state/fragment-state';
|
||||
import { blankProjectState } from './state/initial-state';
|
||||
|
||||
// import "primereact/resources/themes/lara-light-indigo/theme.css";
|
||||
// import "primereact/resources/primereact.min.css";
|
||||
|
|
|
|||
|
|
@ -80,7 +80,7 @@ export async function createEditorFS(workingDir='/home'): Promise<FS> {
|
|||
allMounts[`${workingDir}/${n}`] = librariesMounts[n];
|
||||
}
|
||||
|
||||
await configureAndInstallFS(window, {
|
||||
await configureAndInstallFS(typeof window === 'object' && window || self, {
|
||||
fs: "OverlayFS",
|
||||
options: {
|
||||
readable: {
|
||||
|
|
|
|||
|
|
@ -6,11 +6,9 @@ import {App} from './App';
|
|||
import { createEditorFS } from './filesystem';
|
||||
import { registerOpenSCADLanguage } from './language/openscad-register-language';
|
||||
import { zipArchives } from './zip-archives';
|
||||
import {readStateFromFragment} from './fragment-state'
|
||||
import { State } from './app-state';
|
||||
import defaultScad from './default-scad'
|
||||
import {readStateFromFragment} from './state/fragment-state'
|
||||
import { createInitialState } from './state/initial-state';
|
||||
import './index.css';
|
||||
// import "react-widgets/styles.css";
|
||||
|
||||
import debug from 'debug';
|
||||
const log = debug('app:log');
|
||||
|
|
@ -28,56 +26,11 @@ if (process.env.NODE_ENV !== 'production') {
|
|||
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';
|
||||
// type Mode = State['view']['layout']['mode'];
|
||||
// const mode: Mode = window.matchMedia("(min-width: 768px)").matches
|
||||
// ? 'multi' : 'single';
|
||||
|
||||
const defaultSourcePath = '/home/playground.scad';
|
||||
const initialState: State = {
|
||||
params: {
|
||||
sourcePath: defaultSourcePath,
|
||||
source: defaultScad,
|
||||
features: [],
|
||||
},
|
||||
view: {
|
||||
layout: {
|
||||
mode: 'multi',
|
||||
editor: true,
|
||||
viewer: true,
|
||||
customizer: false,
|
||||
} as any
|
||||
},
|
||||
...(readStateFromFragment() ?? {})
|
||||
};
|
||||
|
||||
if (initialState.view.layout.mode != mode) {
|
||||
if (mode === 'multi' && initialState.view.layout.mode === 'single') {
|
||||
initialState.view.layout = {
|
||||
mode,
|
||||
editor: true,
|
||||
viewer: true,
|
||||
customizer: initialState.view.layout.focus == 'customizer'
|
||||
}
|
||||
} else if (mode === 'single' && initialState.view.layout.mode === 'multi') {
|
||||
initialState.view.layout = {
|
||||
mode,
|
||||
focus: initialState.view.layout.viewer ? 'viewer'
|
||||
: initialState.view.layout.customizer ? 'customizer'
|
||||
: 'editor'
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fs.writeFile(initialState.params.sourcePath, initialState.params.source);
|
||||
if (initialState.params.sourcePath !== defaultSourcePath) {
|
||||
fs.writeFile(defaultSourcePath, defaultScad);
|
||||
}
|
||||
|
||||
const defaultFeatures = ['manifold', 'fast-csg', 'lazy-union'];
|
||||
defaultFeatures.forEach(f => {
|
||||
if (initialState.params.features.indexOf(f) < 0)
|
||||
initialState.params.features.push(f);
|
||||
});
|
||||
const initialState = createInitialState(fs, readStateFromFragment());
|
||||
|
||||
const root = ReactDOM.createRoot(
|
||||
document.getElementById('root') as HTMLElement
|
||||
|
|
|
|||
80
src/state/app-state-future.ts
Normal file
80
src/state/app-state-future.ts
Normal file
|
|
@ -0,0 +1,80 @@
|
|||
export type CustomizerValues = {name: string, value: string|number|boolean}[]
|
||||
export type CustomizerSchema = {
|
||||
presets: {
|
||||
// No name = default preset
|
||||
name?: string,
|
||||
values: CustomizerValues
|
||||
},
|
||||
variables: ({
|
||||
type: 'string'
|
||||
default: string
|
||||
} | {
|
||||
type: 'boolean'
|
||||
default: boolean
|
||||
} | {
|
||||
type: 'enum',
|
||||
default: number | string
|
||||
values: number[] | string[]
|
||||
labels?: string[]
|
||||
} | {
|
||||
type: 'range',
|
||||
default: number,
|
||||
start?: number, // Thingiverse supports foo = 10; // [50]
|
||||
end: number,
|
||||
step?: number
|
||||
})
|
||||
}
|
||||
|
||||
export interface SourceLocation {
|
||||
path: string,
|
||||
row?: number,
|
||||
column?: number,
|
||||
}
|
||||
export interface PlaygroundState {
|
||||
fs: {
|
||||
inputRoot: string,
|
||||
resources: {
|
||||
path: string,
|
||||
lastModified: number,
|
||||
type: 'file' | 'mounted-zip',
|
||||
mimeType: string,
|
||||
// If mimeType ends w/ ;base64, this is a base64 encoded string
|
||||
content: string,
|
||||
}[],
|
||||
},
|
||||
fileExplorerPath?: string,
|
||||
scadInputPath?: string,
|
||||
outputLogs?: {
|
||||
// sourceLastModified: number,
|
||||
stdout: string,
|
||||
stderr: string,
|
||||
errors: {
|
||||
message: string,
|
||||
loc: SourceLocation
|
||||
}[],
|
||||
warnings: {
|
||||
message: string,
|
||||
loc: SourceLocation
|
||||
}[],
|
||||
},
|
||||
|
||||
viewer: {
|
||||
mode: 'normal' | 'maximized' | 'full-screen'
|
||||
// TODO: camera, zoom...
|
||||
},
|
||||
|
||||
output?: {
|
||||
path: string,
|
||||
timestamp: number,
|
||||
sizeBytes: number,
|
||||
formattedSize: string,
|
||||
},
|
||||
|
||||
customizer: {
|
||||
visible?: boolean,
|
||||
schema?: CustomizerSchema,
|
||||
values: CustomizerValues,
|
||||
},
|
||||
};
|
||||
|
||||
export {}
|
||||
|
|
@ -2,9 +2,6 @@
|
|||
|
||||
import * as monaco from 'monaco-editor/esm/vs/editor/editor.api';
|
||||
|
||||
import React from 'react';
|
||||
import { Model } from "./model"
|
||||
|
||||
export type MultiLayoutComponentId = 'editor' | 'viewer' | 'customizer';
|
||||
export type SingleLayoutComponentId = MultiLayoutComponentId;
|
||||
|
||||
|
|
@ -41,12 +38,11 @@ export interface State {
|
|||
elapsedMillis: number,
|
||||
formattedElapsedMillis: string,
|
||||
formattedStlFileSize: string,
|
||||
// path: string,
|
||||
// timestamp: number,
|
||||
// sizeBytes: number,
|
||||
// formattedSize: string,
|
||||
},
|
||||
};
|
||||
|
||||
export const ModelContext = React.createContext<Model | null>(null);
|
||||
|
||||
},
|
||||
() => { throw new Error('Not implemented'); }
|
||||
));
|
||||
|
||||
export {}
|
||||
|
|
@ -1,3 +1,5 @@
|
|||
// Portions of this file are Copyright 2021 Google LLC, and licensed under GPL2+. See COPYING.
|
||||
|
||||
export default `
|
||||
rotate([90, 0, 0]) {
|
||||
translate([-50, 0, 50])
|
||||
|
|
@ -1,10 +1,16 @@
|
|||
// Portions of this file are Copyright 2021 Google LLC, and licensed under GPL2+. See COPYING.
|
||||
|
||||
import { State } from "./app-state";
|
||||
import { validateArray, validateBoolean, validateString, validateStringEnum } from "./utils";
|
||||
import { validateArray, validateBoolean, validateString, validateStringEnum } from "../utils";
|
||||
|
||||
export function buildUrlForStateParams(state: State) {//partialState: {params: State['params'], view: State['view']}) {
|
||||
return `${location.protocol}//${location.host}${location.pathname}#${encodeStateParamsAsFragment(state)}`;
|
||||
}
|
||||
export function writeStateInFragment(state: State) {
|
||||
window.location.hash = encodeURIComponent(JSON.stringify({
|
||||
window.location.hash = encodeStateParamsAsFragment(state);
|
||||
}
|
||||
export function encodeStateParamsAsFragment(state: State) {
|
||||
return encodeURIComponent(JSON.stringify({
|
||||
params: state.params,
|
||||
view: state.view
|
||||
}));
|
||||
76
src/state/initial-state.ts
Normal file
76
src/state/initial-state.ts
Normal file
|
|
@ -0,0 +1,76 @@
|
|||
// Portions of this file are Copyright 2021 Google LLC, and licensed under GPL2+. See COPYING.
|
||||
|
||||
import defaultScad from './default-scad';
|
||||
import { State } from './app-state';
|
||||
|
||||
export const defaultSourcePath = '/home/playground.scad';
|
||||
|
||||
export const blankProjectState: State = {
|
||||
params: {
|
||||
sourcePath: defaultSourcePath,
|
||||
source: '',
|
||||
features: [],
|
||||
},
|
||||
view: {
|
||||
layout: {
|
||||
mode: 'single',
|
||||
focus: 'editor'
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
export function createInitialState(fs: any, state: State | null) {
|
||||
|
||||
type Mode = State['view']['layout']['mode'];
|
||||
const mode: Mode = window.matchMedia("(min-width: 768px)").matches
|
||||
? 'multi' : 'single';
|
||||
|
||||
const initialState: State = {
|
||||
params: {
|
||||
sourcePath: defaultSourcePath,
|
||||
source: defaultScad,
|
||||
features: [],
|
||||
},
|
||||
view: {
|
||||
layout: {
|
||||
mode: 'multi',
|
||||
editor: true,
|
||||
viewer: true,
|
||||
customizer: false,
|
||||
} as any
|
||||
},
|
||||
...(state ?? {})
|
||||
};
|
||||
|
||||
if (initialState.view.layout.mode != mode) {
|
||||
if (mode === 'multi' && initialState.view.layout.mode === 'single') {
|
||||
initialState.view.layout = {
|
||||
mode,
|
||||
editor: true,
|
||||
viewer: true,
|
||||
customizer: initialState.view.layout.focus == 'customizer'
|
||||
}
|
||||
} else if (mode === 'single' && initialState.view.layout.mode === 'multi') {
|
||||
initialState.view.layout = {
|
||||
mode,
|
||||
focus: initialState.view.layout.viewer ? 'viewer'
|
||||
: initialState.view.layout.customizer ? 'customizer'
|
||||
: 'editor'
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fs.writeFile(initialState.params.sourcePath, initialState.params.source);
|
||||
if (initialState.params.sourcePath !== defaultSourcePath) {
|
||||
fs.writeFile(defaultSourcePath, defaultScad);
|
||||
}
|
||||
|
||||
const defaultFeatures = ['manifold', 'fast-csg', 'lazy-union'];
|
||||
defaultFeatures.forEach(f => {
|
||||
if (initialState.params.features.indexOf(f) < 0)
|
||||
initialState.params.features.push(f);
|
||||
});
|
||||
|
||||
return initialState;
|
||||
}
|
||||
|
||||
|
|
@ -1,9 +1,11 @@
|
|||
// Portions of this file are Copyright 2021 Google LLC, and licensed under GPL2+. See COPYING.
|
||||
|
||||
import { checkSyntax, render, RenderArgs, RenderOutput } from "./actions";
|
||||
import React from "react"
|
||||
import { checkSyntax, render, RenderArgs, RenderOutput } from "../actions";
|
||||
import { MultiLayoutComponentId, SingleLayoutComponentId, State } from "./app-state";
|
||||
import { bubbleUpDeepMutations } from "./deep-mutate";
|
||||
import { bubbleUpDeepMutations } from "../deep-mutate";
|
||||
import { writeStateInFragment } from "./fragment-state";
|
||||
import { formatBytes, formatMillis } from '../utils'
|
||||
|
||||
export class Model {
|
||||
constructor(private fs: FS, public state: State, private setStateCallback?: (state: State) => void) {
|
||||
|
|
@ -160,24 +162,7 @@ export class Model {
|
|||
});
|
||||
}})
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
function formatBytes(n: number) {
|
||||
if (n < 1024) {
|
||||
return `${Math.floor(n)} bytes`;
|
||||
}
|
||||
n /= 1024;
|
||||
if (n < 1024) {
|
||||
return `${Math.floor(n * 10) / 10} kB`;
|
||||
}
|
||||
n /= 1024;
|
||||
return `${Math.floor(n * 10) / 10} MB`;
|
||||
}
|
||||
export const ModelContext = React.createContext<Model | null>(null);
|
||||
|
||||
function formatMillis(n: number) {
|
||||
if (n < 1000)
|
||||
return `${Math.floor(n)} millis`;
|
||||
|
||||
return `${Math.floor(n / 100) / 10} sec`;
|
||||
}
|
||||
19
src/utils.ts
19
src/utils.ts
|
|
@ -68,3 +68,22 @@ export const validateArray = <T>(a: Array<T>, validateElement: (e: T) => T, orEl
|
|||
if (!(a instanceof Array)) return orElse();
|
||||
return a.map(validateElement);
|
||||
}
|
||||
|
||||
export function formatBytes(n: number) {
|
||||
if (n < 1024) {
|
||||
return `${Math.floor(n)} bytes`;
|
||||
}
|
||||
n /= 1024;
|
||||
if (n < 1024) {
|
||||
return `${Math.floor(n * 10) / 10} kB`;
|
||||
}
|
||||
n /= 1024;
|
||||
return `${Math.floor(n * 10) / 10} MB`;
|
||||
}
|
||||
|
||||
export function formatMillis(n: number) {
|
||||
if (n < 1000)
|
||||
return `${Math.floor(n)} millis`;
|
||||
|
||||
return `${Math.floor(n / 100) / 10} sec`;
|
||||
}
|
||||
|
|
@ -24,8 +24,8 @@
|
|||
"jsx": "react-jsx"
|
||||
},
|
||||
"include": [
|
||||
"src/*.ts",
|
||||
"src/*.tsx",
|
||||
"src/**/*.ts",
|
||||
"src/**/*.tsx",
|
||||
// "public"
|
||||
]
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue