diff --git a/src/components/EditorPanel.tsx b/src/components/EditorPanel.tsx index 1bf929f..03dbb0b 100644 --- a/src/components/EditorPanel.tsx +++ b/src/components/EditorPanel.tsx @@ -9,10 +9,11 @@ import { MenuItem } from 'primereact/menuitem'; import { TreeSelect } from 'primereact/treeselect'; import TreeNode from 'primereact/treenode'; import { Menu } from 'primereact/menu'; -import { getParentDir } from '../fs/filesystem'; +import { getParentDir, librariesFolder } from '../fs/filesystem'; import { buildUrlForStateParams } from '../state/fragment-state'; import { blankProjectState } from '../state/initial-state'; import { ModelContext, FSContext } from './contexts'; +import FilePicker, { isFileWritable } from './FilePicker'; // import "primereact/resources/themes/lara-light-indigo/theme.css"; // import "primereact/resources/primereact.min.css"; @@ -21,42 +22,45 @@ import { ModelContext, FSContext } from './contexts'; let monacoInstance: Monaco loader.init().then(mi => monacoInstance = mi); -const isFileWritable = (path: string) => getParentDir(path) === '/home' +// const isFileWritable = (path: string) => getParentDir(path) === '/home' -function listFilesAsNodes(fs: FS, path: string): TreeNode[] { - const files: [string, string][] = [] - const dirs: [string, string][] = [] - for (const name of fs.readdirSync(path)) { - const childPath = `${path}/${name}`; - const stat = fs.lstatSync(childPath); - const isDirectory = stat.isDirectory(); - if (!isDirectory && !name.endsWith('.scad')) { - continue; - } - (isDirectory ? dirs : files).push([name, childPath]); - } - [files, dirs].forEach(arr => arr.sort(([a], [b]) => a.localeCompare(b))); +// function listFilesAsNodes(fs: FS, path: string): TreeNode[] { +// const files: [string, string][] = [] +// const dirs: [string, string][] = [] +// for (const name of fs.readdirSync(path)) { +// if (name.startsWith('.')) { +// continue; +// } +// const childPath = `${path}/${name}`; +// const stat = fs.lstatSync(childPath); +// const isDirectory = stat.isDirectory(); +// if (!isDirectory && !name.endsWith('.scad')) { +// continue; +// } +// (isDirectory ? dirs : files).push([name, childPath]); +// } +// [files, dirs].forEach(arr => arr.sort(([a], [b]) => a.localeCompare(b))); - const nodes: TreeNode[] = [] - for (const [arr, isDirectory] of [[files, false], [dirs, true]] as [[string, string][], boolean][]) { - for (const [name, path] of arr) { - const children = isDirectory ? listFilesAsNodes(fs, path) : undefined; - if (isDirectory && children!.length == 0) { - continue; - } - nodes.push({ - // icon: path == '/home' ? 'pi-home' : ... - icon: isDirectory ? 'pi pi-folder' : isFileWritable(path) ? 'pi pi-file' : 'pi pi-lock', - label: name, - data: path, - key: path, - children, - selectable: !isDirectory // && (name == 'LICENSE' || name.endsWith('.scad') || name.endsWith('.scad') - }); - } - } - return nodes; -} +// const nodes: TreeNode[] = [] +// for (const [arr, isDirectory] of [[files, false], [dirs, true]] as [[string, string][], boolean][]) { +// for (const [name, path] of arr) { +// const children = isDirectory ? listFilesAsNodes(fs, path) : undefined; +// if (isDirectory && children!.length == 0) { +// continue; +// } +// nodes.push({ +// // icon: path == '/home' ? 'pi-home' : ... +// icon: isDirectory ? 'pi pi-folder' : isFileWritable(path) ? 'pi pi-file' : 'pi pi-lock', +// label: name, +// data: path, +// key: path, +// children, +// selectable: !isDirectory // && (name == 'LICENSE' || name.endsWith('.scad') || name.endsWith('.scad') +// }); +// } +// } +// return nodes; +// } export default function EditorPanel({className, style}: {className?: string, style?: CSSProperties}) { @@ -67,7 +71,7 @@ export default function EditorPanel({className, style}: {className?: string, sty const state = model.state; - const fs = useContext(FSContext); + // const fs = useContext(FSContext); const [editor, setEditor] = useState(null as monaco.editor.IStandaloneCodeEditor | null) // const [modelFile, setModelFile] = useState(state.params.sourcePath); @@ -100,8 +104,6 @@ export default function EditorPanel({className, style}: {className?: string, sty setEditor(editor) } - const fsItems = fs && listFilesAsNodes(fs, '/home') || []; - return (
- {/* */}
@@ -214,73 +215,39 @@ export default function EditorPanel({className, style}: {className?: string, sty ] as MenuItem[]} popup ref={menu} />
- {/* */} - {/*
*/} - {/* */} - {/* */} -
- model.source = s ?? ''} - onMount={onMount} // TODO: This looks a bit silly, does it trigger a re-render?? - options={{ - ...openscadEditorOptions, - readOnly: !isFileWritable(state.params.sourcePath) - }} - // height="100%" - // width="100%" - // height="80vh" - /> -
+ +
+ model.source = s ?? ''} + onMount={onMount} // TODO: This looks a bit silly, does it trigger a re-render?? + options={{ + ...openscadEditorOptions, + readOnly: !isFileWritable(state.params.sourcePath) + }} + /> +
- {/*
- */} - {/* */} -
-
{state.lastCheckerRun?.logText ?? 'No log yet!'}
-
- {/*
*/} - {/*
-
*/} - {/*
-
*/} - {/* */} - {/*
*/} +
+
{state.lastCheckerRun?.logText ?? 'No log yet!'}
+
) diff --git a/src/components/FilePicker.tsx b/src/components/FilePicker.tsx new file mode 100644 index 0000000..df37229 --- /dev/null +++ b/src/components/FilePicker.tsx @@ -0,0 +1,85 @@ +// Portions of this file are Copyright 2021 Google LLC, and licensed under GPL2+. See COPYING. + +import { CSSProperties, useContext } from 'react'; +import { TreeSelect } from 'primereact/treeselect'; +import TreeNode from 'primereact/treenode'; +import { getParentDir, librariesFolder } from '../fs/filesystem'; +import { ModelContext, FSContext } from './contexts'; + +export const isFileWritable = (path: string) => getParentDir(path) === '/home' + +function listFilesAsNodes(fs: FS, path: string): TreeNode[] { + const files: [string, string][] = [] + const dirs: [string, string][] = [] + for (const name of fs.readdirSync(path)) { + if (name.startsWith('.')) { + continue; + } + const childPath = `${path}/${name}`; + const stat = fs.lstatSync(childPath); + const isDirectory = stat.isDirectory(); + if (!isDirectory && !name.endsWith('.scad')) { + continue; + } + (isDirectory ? dirs : files).push([name, childPath]); + } + [files, dirs].forEach(arr => arr.sort(([a], [b]) => a.localeCompare(b))); + + const nodes: TreeNode[] = [] + for (const [arr, isDirectory] of [[files, false], [dirs, true]] as [[string, string][], boolean][]) { + for (const [name, path] of arr) { + const children = isDirectory ? listFilesAsNodes(fs, path) : undefined; + if (isDirectory && children!.length == 0) { + continue; + } + nodes.push({ + // icon: path == '/home' ? 'pi-home' : ... + icon: isDirectory ? 'pi pi-folder' : isFileWritable(path) ? 'pi pi-file' : 'pi pi-lock', + label: name, + data: path, + key: path, + children, + selectable: !isDirectory // && (name == 'LICENSE' || name.endsWith('.scad') || name.endsWith('.scad') + }); + } + } + return nodes; +} + +export default function FilePicker({className, style}: {className?: string, style?: CSSProperties}) { + const model = useContext(ModelContext); + if (!model) throw new Error('No model'); + const state = model.state; + + const fs = useContext(FSContext); + + const fsItems = fs && [ + { + icon: 'pi pi-home', + label: 'User files', + key: '/home', + children: listFilesAsNodes(fs, '/home'), + selectable: false + }, + { + icon: 'pi pi-database', + label: 'Builtin libraries', + key: librariesFolder, + children: listFilesAsNodes(fs, librariesFolder), + selectable: false + }, + ] || []; + + return ( + model.openFile(String(e.value))} + // dropdownIcon="pi pi-folder-open" + filter + style={style} + // style={{style}} + options={fsItems} /> + ) +} diff --git a/src/components/Footer.tsx b/src/components/Footer.tsx index 897be92..fc284c1 100644 --- a/src/components/Footer.tsx +++ b/src/components/Footer.tsx @@ -9,9 +9,10 @@ import { ProgressBar } from 'primereact/progressbar'; import { MenuItem } from 'primereact/menuitem'; 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 SettingsMenu from './SettingsMenu'; +import HelpMenu from './HelpMenu'; function downloadOutput(state: State) { if (!state.output) return; @@ -46,11 +47,11 @@ function downloadOutput(state: State) { export default function Footer({style}: {style?: CSSProperties}) { const model = useContext(ModelContext); if (!model) throw new Error('No model'); + const state = model.state; - const menu = useRef(null); + const helpMenu = useRef(null); const toast = useRef(null); - const state = model.state; const severityByMarkerSeverity = new Map([ [monaco.MarkerSeverity.Error, 'danger'], @@ -147,49 +148,9 @@ export default function Footer({style}: {style?: CSSProperties}) { // )} - model.changeLayout(e.value ? 'multi' : 'single')} /> + - window.open('https://github.com/openscad/openscad-playground/tree/rewrite1', '_blank'), - }, - { - label: 'LICENSES', - icon: 'pi pi-info-circle', - command: () => window.open('https://github.com/openscad/openscad-playground/blob/rewrite1/LICENSE.md', '_blank'), - }, - { - label: 'OpenSCAD Docs', - icon: 'pi pi-book', - command: () => window.open('http://openscad.org/documentation.html', '_blank'), - }, - { - label: 'OpenSCAD Cheatsheet', - icon: 'pi pi-palette', - command: () => window.open('http://openscad.org/cheatsheet/', '_blank'), - }, - { - label: 'BOSL2 Cheatsheet', - icon: 'pi pi-palette', - command: () => window.open('https://github.com/revarbat/BOSL2/wiki/CheatSheet', '_blank'), - }, - ] as MenuItem[]} popup ref={menu} /> -