From 05fb68ed14ab32aa10c9a7446c83493ff3301ab8 Mon Sep 17 00:00:00 2001 From: ochafik Date: Sun, 26 Mar 2023 15:28:05 +0100 Subject: [PATCH] state/model files refactoring --- src/App.tsx | 7 ++- src/filesystem.ts | 2 +- src/index.tsx | 59 +++-------------------- src/state/app-state-future.ts | 80 +++++++++++++++++++++++++++++++ src/{ => state}/app-state.ts | 14 ++---- src/{ => state}/default-scad.ts | 2 + src/{ => state}/fragment-state.ts | 10 +++- src/state/initial-state.ts | 76 +++++++++++++++++++++++++++++ src/{ => state}/model.ts | 25 ++-------- src/utils.ts | 19 ++++++++ tsconfig.json | 4 +- 11 files changed, 209 insertions(+), 89 deletions(-) create mode 100644 src/state/app-state-future.ts rename src/{ => state}/app-state.ts (83%) rename src/{ => state}/default-scad.ts (77%) rename src/{ => state}/fragment-state.ts (74%) create mode 100644 src/state/initial-state.ts rename src/{ => state}/model.ts (92%) diff --git a/src/App.tsx b/src/App.tsx index b3b71c7..18eaddd 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -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"; diff --git a/src/filesystem.ts b/src/filesystem.ts index f469aff..9eaded9 100644 --- a/src/filesystem.ts +++ b/src/filesystem.ts @@ -80,7 +80,7 @@ export async function createEditorFS(workingDir='/home'): Promise { allMounts[`${workingDir}/${n}`] = librariesMounts[n]; } - await configureAndInstallFS(window, { + await configureAndInstallFS(typeof window === 'object' && window || self, { fs: "OverlayFS", options: { readable: { diff --git a/src/index.tsx b/src/index.tsx index 349a058..cd2ceaa 100644 --- a/src/index.tsx +++ b/src/index.tsx @@ -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 diff --git a/src/state/app-state-future.ts b/src/state/app-state-future.ts new file mode 100644 index 0000000..6c7e186 --- /dev/null +++ b/src/state/app-state-future.ts @@ -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 {} \ No newline at end of file diff --git a/src/app-state.ts b/src/state/app-state.ts similarity index 83% rename from src/app-state.ts rename to src/state/app-state.ts index 386e441..8dbfa7a 100644 --- a/src/app-state.ts +++ b/src/state/app-state.ts @@ -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(null); - - }, - () => { throw new Error('Not implemented'); } -)); - +export {} \ No newline at end of file diff --git a/src/default-scad.ts b/src/state/default-scad.ts similarity index 77% rename from src/default-scad.ts rename to src/state/default-scad.ts index e99a60a..07af119 100644 --- a/src/default-scad.ts +++ b/src/state/default-scad.ts @@ -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]) diff --git a/src/fragment-state.ts b/src/state/fragment-state.ts similarity index 74% rename from src/fragment-state.ts rename to src/state/fragment-state.ts index ca5266d..bfd7de8 100644 --- a/src/fragment-state.ts +++ b/src/state/fragment-state.ts @@ -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 })); diff --git a/src/state/initial-state.ts b/src/state/initial-state.ts new file mode 100644 index 0000000..c37f2b5 --- /dev/null +++ b/src/state/initial-state.ts @@ -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; +} + diff --git a/src/model.ts b/src/state/model.ts similarity index 92% rename from src/model.ts rename to src/state/model.ts index 59759b0..187d960 100644 --- a/src/model.ts +++ b/src/state/model.ts @@ -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(null); -function formatMillis(n: number) { - if (n < 1000) - return `${Math.floor(n)} millis`; - - return `${Math.floor(n / 100) / 10} sec`; -} \ No newline at end of file diff --git a/src/utils.ts b/src/utils.ts index c9bff30..0c45b87 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -68,3 +68,22 @@ export const validateArray = (a: Array, 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`; +} \ No newline at end of file diff --git a/tsconfig.json b/tsconfig.json index 12ee360..5b9a466 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -24,8 +24,8 @@ "jsx": "react-jsx" }, "include": [ - "src/*.ts", - "src/*.tsx", + "src/**/*.ts", + "src/**/*.tsx", // "public" ] }