From 3c86bfea8de0a9cbf1cafc2b84313f910224f5ff Mon Sep 17 00:00:00 2001 From: sawka Date: Mon, 3 Jun 2024 11:35:06 -0700 Subject: [PATCH] integrate codeedit loader (just hello world for now) --- frontend/app/block/block.tsx | 9 +- frontend/app/element/errorboundary.tsx | 25 ++++++ frontend/app/view/codeedit.less | 9 ++ frontend/app/view/codeedit.tsx | 112 +++++++++++++++++++++++++ frontend/app/view/directorypreview.tsx | 3 +- frontend/app/view/view.less | 10 +++ frontend/app/workspace/workspace.tsx | 10 +++ package.json | 5 +- public/style.less | 4 + vite.config.ts | 11 ++- yarn.lock | 89 +++++++++++++------- 11 files changed, 251 insertions(+), 36 deletions(-) create mode 100644 frontend/app/element/errorboundary.tsx create mode 100644 frontend/app/view/codeedit.less create mode 100644 frontend/app/view/codeedit.tsx diff --git a/frontend/app/block/block.tsx b/frontend/app/block/block.tsx index 9dd06fbd0..a3cc83ae0 100644 --- a/frontend/app/block/block.tsx +++ b/frontend/app/block/block.tsx @@ -1,9 +1,11 @@ // Copyright 2024, Command Line Inc. // SPDX-License-Identifier: Apache-2.0 +import { CodeEditView } from "@/app/view/codeedit"; import { PlotView } from "@/app/view/plotview"; import { PreviewView } from "@/app/view/preview"; import { TerminalView } from "@/app/view/term"; +import { ErrorBoundary } from "@/element/errorboundary"; import { CenteredDiv } from "@/element/quickelems"; import * as WOS from "@/store/wos"; import * as React from "react"; @@ -32,6 +34,7 @@ const Block = ({ tabId, blockId }: { tabId: string; blockId: string }) => { let blockElem: JSX.Element = null; const [blockData, blockDataLoading] = WOS.useWaveObjectValue(WOS.makeORef("block", blockId)); + console.log("blockData: ", blockData); if (blockDataLoading) { blockElem = Loading...; } else if (blockData.view === "term") { @@ -40,6 +43,8 @@ const Block = ({ tabId, blockId }: { tabId: string; blockId: string }) => { blockElem = ; } else if (blockData.view === "plot") { blockElem = ; + } else if (blockData.view === "codeedit") { + blockElem = ; } return (
@@ -53,7 +58,9 @@ const Block = ({ tabId, blockId }: { tabId: string; blockId: string }) => {
- Loading...}>{blockElem} + + Loading...}>{blockElem} +
); diff --git a/frontend/app/element/errorboundary.tsx b/frontend/app/element/errorboundary.tsx new file mode 100644 index 000000000..617306a41 --- /dev/null +++ b/frontend/app/element/errorboundary.tsx @@ -0,0 +1,25 @@ +// Copyright 2024, Command Line Inc. +// SPDX-License-Identifier: Apache-2.0 + +import React, { ReactNode } from "react"; + +export class ErrorBoundary extends React.Component<{ children: ReactNode }, { error: Error }> { + constructor(props) { + super(props); + this.state = { error: null }; + } + + componentDidCatch(error: Error, errorInfo: React.ErrorInfo) { + this.setState({ error: error }); + } + + render() { + const { error } = this.state; + if (error) { + const errorMsg = `Error: ${error?.message}\n\n${error?.stack}`; + return
{errorMsg}
; + } else { + return <>{this.props.children}; + } + } +} diff --git a/frontend/app/view/codeedit.less b/frontend/app/view/codeedit.less new file mode 100644 index 000000000..fe8f87ba1 --- /dev/null +++ b/frontend/app/view/codeedit.less @@ -0,0 +1,9 @@ +// Copyright 2024, Command Line Inc. +// SPDX-License-Identifier: Apache-2.0 + +.codeedit { + display: flex; + flex-direction: column; + height: 100%; + width: 100%; +} diff --git a/frontend/app/view/codeedit.tsx b/frontend/app/view/codeedit.tsx new file mode 100644 index 000000000..010a195c9 --- /dev/null +++ b/frontend/app/view/codeedit.tsx @@ -0,0 +1,112 @@ +// Copyright 2024, Command Line Inc. +// SPDX-License-Identifier: Apache-2.0 + +import "./codeedit.less"; + +import { globalStore } from "@/store/global"; +import loader from "@monaco-editor/loader"; +import { Editor, Monaco } from "@monaco-editor/react"; +import * as jotai from "jotai"; +import type * as MonacoTypes from "monaco-editor/esm/vs/editor/editor.api"; +import * as React from "react"; + +// there is a global monaco variable (TODO get the correct TS type) +declare var monaco: Monaco; +let monacoLoadedAtom = jotai.atom(false); + +function loadMonaco() { + loader.config({ paths: { vs: "./monaco" } }); + loader + .init() + .then(() => { + monaco.editor.defineTheme("wave-theme-dark", { + base: "hc-black", + inherit: true, + rules: [], + colors: { + "editor.background": "#000000", + }, + }); + monaco.editor.defineTheme("wave-theme-light", { + base: "hc-light", + inherit: true, + rules: [], + colors: { + "editor.background": "#fefefe", + }, + }); + globalStore.set(monacoLoadedAtom, true); + console.log("monaco loaded", monaco); + }) + .catch((e) => { + console.error("error loading monaco", e); + }); +} + +// TODO: need to update these on theme change (pull from CSS vars) +document.addEventListener("DOMContentLoaded", () => { + setTimeout(loadMonaco, 30); +}); + +function defaultEditorOptions(): MonacoTypes.editor.IEditorOptions { + const opts: MonacoTypes.editor.IEditorOptions = { + scrollBeyondLastLine: false, + fontSize: 12, + fontFamily: "Hack", + }; + return opts; +} + +export function CodeEdit() { + const divRef = React.useRef(null); + const monacoRef = React.useRef(null); + const theme = "wave-theme-dark"; + const [divDims, setDivDims] = React.useState(null); + const monacoLoaded = jotai.useAtomValue(monacoLoadedAtom); + + React.useEffect(() => { + if (!divRef.current) { + return; + } + const height = divRef.current.clientHeight; + const width = divRef.current.clientWidth; + setDivDims({ height, width }); + }, [divRef.current]); + + function handleEditorMount(editor: MonacoTypes.editor.IStandaloneCodeEditor) { + monacoRef.current = editor; + const monacoModel = editor.getModel(); + monaco.editor.setModelLanguage(monacoModel, "text/markdown"); + } + + function handleEditorChange(newText: string, ev: MonacoTypes.editor.IModelContentChangedEvent) { + // TODO + } + + const text = "Hello, world!"; + const editorOpts = defaultEditorOptions(); + + return ( +
+ {divDims != null && monacoLoaded ? ( + + ) : null} +
+ ); +} + +export function CodeEditView() { + return ( +
+ +
+ ); +} diff --git a/frontend/app/view/directorypreview.tsx b/frontend/app/view/directorypreview.tsx index bdeeb757b..464ae20e0 100644 --- a/frontend/app/view/directorypreview.tsx +++ b/frontend/app/view/directorypreview.tsx @@ -4,7 +4,6 @@ import { FileInfo } from "@/bindings/fileservice"; import { Table, createColumnHelper, flexRender, getCoreRowModel, useReactTable } from "@tanstack/react-table"; import * as jotai from "jotai"; -import path from "path"; import React from "react"; import "./directorypreview.less"; @@ -108,7 +107,7 @@ function TableBody({ table, setFileName }: TableBodyProps) { key={cell.id} style={{ width: `calc(var(--col-${cell.column.id}-size) * 1px)` }} > - {path.basename(cell.renderValue())} + {cell.renderValue()} ))} diff --git a/frontend/app/view/view.less b/frontend/app/view/view.less index 3a1fcb2c5..d3e1ddc5f 100644 --- a/frontend/app/view/view.less +++ b/frontend/app/view/view.less @@ -26,6 +26,16 @@ } } +.view-codeedit { + display: flex; + flex-direction: column; + width: 100%; + height: 100%; + overflow: hidden; + align-items: center; + justify-content: center; +} + .view-preview { display: flex; flex-direction: row; diff --git a/frontend/app/workspace/workspace.tsx b/frontend/app/workspace/workspace.tsx index 31dc57f56..325c4e0f6 100644 --- a/frontend/app/workspace/workspace.tsx +++ b/frontend/app/workspace/workspace.tsx @@ -84,6 +84,13 @@ function Widgets() { createBlock(plotDef); } + async function clickEdit() { + const editDef: BlockDef = { + view: "codeedit", + }; + createBlock(editDef); + } + return (
clickTerminal()}> @@ -104,6 +111,9 @@ function Widgets() {
clickPlot()}>
+
clickEdit()}> + +
diff --git a/package.json b/package.json index 6ddd4853f..476668b66 100644 --- a/package.json +++ b/package.json @@ -37,10 +37,13 @@ "typescript": "^5.4.5", "typescript-eslint": "^7.8.0", "vite": "^5.0.0", + "vite-plugin-static-copy": "^1.0.5", "vite-tsconfig-paths": "^4.3.2", "vitest": "^1.6.0" }, "dependencies": { + "@monaco-editor/loader": "^1.4.0", + "@monaco-editor/react": "^4.6.0", "@observablehq/plot": "^0.6.14", "@tanstack/react-table": "^8.17.3", "@xterm/addon-fit": "^0.10.0", @@ -49,7 +52,7 @@ "clsx": "^2.1.1", "immer": "^10.1.1", "jotai": "^2.8.0", - "path": "^0.12.7", + "monaco-editor": "^0.49.0", "react": "^18.3.1", "react-dom": "^18.3.1", "react-markdown": "^9.0.1", diff --git a/public/style.less b/public/style.less index 766c4de14..4a0cbdfe4 100644 --- a/public/style.less +++ b/public/style.less @@ -36,3 +36,7 @@ body { border-bottom: 1px solid var(--border-color); flex-shrink: 0; } + +.error-boundary { + color: var(--error-color); +} diff --git a/vite.config.ts b/vite.config.ts index d709ef5c0..66fa0b4a8 100644 --- a/vite.config.ts +++ b/vite.config.ts @@ -1,13 +1,20 @@ import react from "@vitejs/plugin-react"; import { defineConfig } from "vite"; +import { viteStaticCopy } from "vite-plugin-static-copy"; import tsconfigPaths from "vite-tsconfig-paths"; export default defineConfig({ - plugins: [react({}), tsconfigPaths()], - define: { "process.env": process.env }, + plugins: [ + react({}), + tsconfigPaths(), + viteStaticCopy({ + targets: [{ src: "node_modules/monaco-editor/min/vs/*", dest: "monaco" }], + }), + ], publicDir: "public", build: { target: "es6", + sourcemap: true, rollupOptions: { input: { app: "public/index.html", diff --git a/yarn.lock b/yarn.lock index 157902f90..d8512509a 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1913,6 +1913,30 @@ __metadata: languageName: node linkType: hard +"@monaco-editor/loader@npm:^1.4.0": + version: 1.4.0 + resolution: "@monaco-editor/loader@npm:1.4.0" + dependencies: + state-local: "npm:^1.0.6" + peerDependencies: + monaco-editor: ">= 0.21.0 < 1" + checksum: 10c0/68938350adf2f42363a801d87f5d00c87d397d4cba7041141af10a9216bd35c85209b4723a26d56cb32e68eef61471deda2a450f8892891118fbdce7fa1d987d + languageName: node + linkType: hard + +"@monaco-editor/react@npm:^4.6.0": + version: 4.6.0 + resolution: "@monaco-editor/react@npm:4.6.0" + dependencies: + "@monaco-editor/loader": "npm:^1.4.0" + peerDependencies: + monaco-editor: ">= 0.25.0 < 1" + react: ^16.8.0 || ^17.0.0 || ^18.0.0 + react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0 + checksum: 10c0/231e9a9b66a530db326f6732de0ebffcce6b79dcfaf4948923d78b9a3d5e2a04b7a06e1f85bbbca45a5ae15c107a124e4c5c46cabadc20a498fb5f2d05f7f379 + languageName: node + linkType: hard + "@ndelangen/get-tarball@npm:^3.0.7": version: 3.0.9 resolution: "@ndelangen/get-tarball@npm:3.0.9" @@ -4650,7 +4674,7 @@ __metadata: languageName: node linkType: hard -"chokidar@npm:^3.6.0": +"chokidar@npm:^3.5.3, chokidar@npm:^3.6.0": version: 3.6.0 resolution: "chokidar@npm:3.6.0" dependencies: @@ -6161,7 +6185,7 @@ __metadata: languageName: node linkType: hard -"fast-glob@npm:^3.2.9, fast-glob@npm:^3.3.2": +"fast-glob@npm:^3.2.11, fast-glob@npm:^3.2.9, fast-glob@npm:^3.3.2": version: 3.3.2 resolution: "fast-glob@npm:3.3.2" dependencies: @@ -7001,13 +7025,6 @@ __metadata: languageName: node linkType: hard -"inherits@npm:2.0.3": - version: 2.0.3 - resolution: "inherits@npm:2.0.3" - checksum: 10c0/6e56402373149ea076a434072671f9982f5fad030c7662be0332122fe6c0fa490acb3cc1010d90b6eff8d640b1167d77674add52dfd1bb85d545cf29e80e73e7 - languageName: node - linkType: hard - "inline-style-parser@npm:0.2.3": version: 0.2.3 resolution: "inline-style-parser@npm:0.2.3" @@ -8816,6 +8833,13 @@ __metadata: languageName: node linkType: hard +"monaco-editor@npm:^0.49.0": + version: 0.49.0 + resolution: "monaco-editor@npm:0.49.0" + checksum: 10c0/c62d19e65f8ad441d0b29d8f43181c730bd373854ff0f9331c42ed1b97b729ab099e6f961c2e377acddda6923b8030e0453ae87edd3f7db3ace75c91f44431cd + languageName: node + linkType: hard + "ms@npm:2.0.0": version: 2.0.0 resolution: "ms@npm:2.0.0" @@ -9344,16 +9368,6 @@ __metadata: languageName: node linkType: hard -"path@npm:^0.12.7": - version: 0.12.7 - resolution: "path@npm:0.12.7" - dependencies: - process: "npm:^0.11.1" - util: "npm:^0.10.3" - checksum: 10c0/f795ce5438a988a590c7b6dfd450ec9baa1c391a8be4c2dea48baa6e0f5b199e56cd83b8c9ebf3991b81bea58236d2c32bdafe2c17a2e70c3a2e4c69891ade59 - languageName: node - linkType: hard - "pathe@npm:^1.1.1, pathe@npm:^1.1.2": version: 1.1.2 resolution: "pathe@npm:1.1.2" @@ -9568,7 +9582,7 @@ __metadata: languageName: node linkType: hard -"process@npm:^0.11.1, process@npm:^0.11.10": +"process@npm:^0.11.10": version: 0.11.10 resolution: "process@npm:0.11.10" checksum: 10c0/40c3ce4b7e6d4b8c3355479df77aeed46f81b279818ccdc500124e6a5ab882c0cc81ff7ea16384873a95a74c4570b01b120f287abbdd4c877931460eca6084b3 @@ -10643,6 +10657,13 @@ __metadata: languageName: node linkType: hard +"state-local@npm:^1.0.6": + version: 1.0.7 + resolution: "state-local@npm:1.0.7" + checksum: 10c0/8dc7daeac71844452fafb514a6d6b6f40d7e2b33df398309ea1c7b3948d6110c57f112b7196500a10c54fdde40291488c52c875575670fb5c819602deca48bd9 + languageName: node + linkType: hard + "statuses@npm:2.0.1": version: 2.0.1 resolution: "statuses@npm:2.0.1" @@ -10950,6 +10971,8 @@ __metadata: dependencies: "@chromatic-com/storybook": "npm:^1.3.3" "@eslint/js": "npm:^9.2.0" + "@monaco-editor/loader": "npm:^1.4.0" + "@monaco-editor/react": "npm:^4.6.0" "@observablehq/plot": "npm:^0.6.14" "@storybook/addon-essentials": "npm:^8.0.10" "@storybook/addon-interactions": "npm:^8.0.10" @@ -10974,7 +10997,7 @@ __metadata: immer: "npm:^10.1.1" jotai: "npm:^2.8.0" less: "npm:^4.2.0" - path: "npm:^0.12.7" + monaco-editor: "npm:^0.49.0" prettier: "npm:^3.2.5" prettier-plugin-jsdoc: "npm:^1.3.0" prettier-plugin-organize-imports: "npm:^3.2.4" @@ -10990,6 +11013,7 @@ __metadata: typescript-eslint: "npm:^7.8.0" uuid: "npm:^9.0.1" vite: "npm:^5.0.0" + vite-plugin-static-copy: "npm:^1.0.5" vite-tsconfig-paths: "npm:^4.3.2" vitest: "npm:^1.6.0" languageName: unknown @@ -11529,15 +11553,6 @@ __metadata: languageName: node linkType: hard -"util@npm:^0.10.3": - version: 0.10.4 - resolution: "util@npm:0.10.4" - dependencies: - inherits: "npm:2.0.3" - checksum: 10c0/d29f6893e406b63b088ce9924da03201df89b31490d4d011f1c07a386ea4b3dbe907464c274023c237da470258e1805d806c7e4009a5974cd6b1d474b675852a - languageName: node - linkType: hard - "util@npm:^0.12.4, util@npm:^0.12.5": version: 0.12.5 resolution: "util@npm:0.12.5" @@ -11627,6 +11642,20 @@ __metadata: languageName: node linkType: hard +"vite-plugin-static-copy@npm:^1.0.5": + version: 1.0.5 + resolution: "vite-plugin-static-copy@npm:1.0.5" + dependencies: + chokidar: "npm:^3.5.3" + fast-glob: "npm:^3.2.11" + fs-extra: "npm:^11.1.0" + picocolors: "npm:^1.0.0" + peerDependencies: + vite: ^5.0.0 + checksum: 10c0/b11c6a0bd31b8592af4099f75122b2073738659d1d06c6aeedd47d15b84758a57c17d56b6e79cefa3b7871c8f51980b423a9d524bbceb4b17363d4363e78a63b + languageName: node + linkType: hard + "vite-tsconfig-paths@npm:^4.3.2": version: 4.3.2 resolution: "vite-tsconfig-paths@npm:4.3.2"