integrate codeedit loader (just hello world for now)

This commit is contained in:
sawka 2024-06-03 11:35:06 -07:00
parent 394b9dce23
commit 3c86bfea8d
11 changed files with 251 additions and 36 deletions

View File

@ -1,9 +1,11 @@
// Copyright 2024, Command Line Inc. // Copyright 2024, Command Line Inc.
// SPDX-License-Identifier: Apache-2.0 // SPDX-License-Identifier: Apache-2.0
import { CodeEditView } from "@/app/view/codeedit";
import { PlotView } from "@/app/view/plotview"; import { PlotView } from "@/app/view/plotview";
import { PreviewView } from "@/app/view/preview"; import { PreviewView } from "@/app/view/preview";
import { TerminalView } from "@/app/view/term"; import { TerminalView } from "@/app/view/term";
import { ErrorBoundary } from "@/element/errorboundary";
import { CenteredDiv } from "@/element/quickelems"; import { CenteredDiv } from "@/element/quickelems";
import * as WOS from "@/store/wos"; import * as WOS from "@/store/wos";
import * as React from "react"; import * as React from "react";
@ -32,6 +34,7 @@ const Block = ({ tabId, blockId }: { tabId: string; blockId: string }) => {
let blockElem: JSX.Element = null; let blockElem: JSX.Element = null;
const [blockData, blockDataLoading] = WOS.useWaveObjectValue<Block>(WOS.makeORef("block", blockId)); const [blockData, blockDataLoading] = WOS.useWaveObjectValue<Block>(WOS.makeORef("block", blockId));
console.log("blockData: ", blockData);
if (blockDataLoading) { if (blockDataLoading) {
blockElem = <CenteredDiv>Loading...</CenteredDiv>; blockElem = <CenteredDiv>Loading...</CenteredDiv>;
} else if (blockData.view === "term") { } else if (blockData.view === "term") {
@ -40,6 +43,8 @@ const Block = ({ tabId, blockId }: { tabId: string; blockId: string }) => {
blockElem = <PreviewView blockId={blockId} />; blockElem = <PreviewView blockId={blockId} />;
} else if (blockData.view === "plot") { } else if (blockData.view === "plot") {
blockElem = <PlotView />; blockElem = <PlotView />;
} else if (blockData.view === "codeedit") {
blockElem = <CodeEditView />;
} }
return ( return (
<div className="block" ref={blockRef}> <div className="block" ref={blockRef}>
@ -53,7 +58,9 @@ const Block = ({ tabId, blockId }: { tabId: string; blockId: string }) => {
</div> </div>
</div> </div>
<div key="content" className="block-content"> <div key="content" className="block-content">
<React.Suspense fallback={<CenteredDiv>Loading...</CenteredDiv>}>{blockElem}</React.Suspense> <ErrorBoundary>
<React.Suspense fallback={<CenteredDiv>Loading...</CenteredDiv>}>{blockElem}</React.Suspense>
</ErrorBoundary>
</div> </div>
</div> </div>
); );

View File

@ -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 <pre className="error-boundary">{errorMsg}</pre>;
} else {
return <>{this.props.children}</>;
}
}
}

View File

@ -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%;
}

View File

@ -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<HTMLDivElement>(null);
const monacoRef = React.useRef<MonacoTypes.editor.IStandaloneCodeEditor | null>(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 (
<div className="codeedit" ref={divRef}>
{divDims != null && monacoLoaded ? (
<Editor
theme={theme}
height={divDims.height}
defaultLanguage={"text/markdown"}
value={text}
onMount={handleEditorMount}
options={editorOpts}
onChange={handleEditorChange}
/>
) : null}
</div>
);
}
export function CodeEditView() {
return (
<div className="view-codeedit">
<CodeEdit />
</div>
);
}

View File

@ -4,7 +4,6 @@
import { FileInfo } from "@/bindings/fileservice"; import { FileInfo } from "@/bindings/fileservice";
import { Table, createColumnHelper, flexRender, getCoreRowModel, useReactTable } from "@tanstack/react-table"; import { Table, createColumnHelper, flexRender, getCoreRowModel, useReactTable } from "@tanstack/react-table";
import * as jotai from "jotai"; import * as jotai from "jotai";
import path from "path";
import React from "react"; import React from "react";
import "./directorypreview.less"; import "./directorypreview.less";
@ -108,7 +107,7 @@ function TableBody({ table, setFileName }: TableBodyProps) {
key={cell.id} key={cell.id}
style={{ width: `calc(var(--col-${cell.column.id}-size) * 1px)` }} style={{ width: `calc(var(--col-${cell.column.id}-size) * 1px)` }}
> >
{path.basename(cell.renderValue<any>())} {cell.renderValue<any>()}
</div> </div>
))} ))}
</div> </div>

View File

@ -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 { .view-preview {
display: flex; display: flex;
flex-direction: row; flex-direction: row;

View File

@ -84,6 +84,13 @@ function Widgets() {
createBlock(plotDef); createBlock(plotDef);
} }
async function clickEdit() {
const editDef: BlockDef = {
view: "codeedit",
};
createBlock(editDef);
}
return ( return (
<div className="workspace-widgets"> <div className="workspace-widgets">
<div className="widget" onClick={() => clickTerminal()}> <div className="widget" onClick={() => clickTerminal()}>
@ -104,6 +111,9 @@ function Widgets() {
<div className="widget" onClick={() => clickPlot()}> <div className="widget" onClick={() => clickPlot()}>
<i className="fa fa-solid fa-chart-simple fa-fw" /> <i className="fa fa-solid fa-chart-simple fa-fw" />
</div> </div>
<div className="widget" onClick={() => clickEdit()}>
<i className="fa-sharp fa-solid fa-pen-to-square"></i>
</div>
<div className="widget no-hover"> <div className="widget no-hover">
<i className="fa fa-solid fa-plus fa-fw" /> <i className="fa fa-solid fa-plus fa-fw" />
</div> </div>

View File

@ -37,10 +37,13 @@
"typescript": "^5.4.5", "typescript": "^5.4.5",
"typescript-eslint": "^7.8.0", "typescript-eslint": "^7.8.0",
"vite": "^5.0.0", "vite": "^5.0.0",
"vite-plugin-static-copy": "^1.0.5",
"vite-tsconfig-paths": "^4.3.2", "vite-tsconfig-paths": "^4.3.2",
"vitest": "^1.6.0" "vitest": "^1.6.0"
}, },
"dependencies": { "dependencies": {
"@monaco-editor/loader": "^1.4.0",
"@monaco-editor/react": "^4.6.0",
"@observablehq/plot": "^0.6.14", "@observablehq/plot": "^0.6.14",
"@tanstack/react-table": "^8.17.3", "@tanstack/react-table": "^8.17.3",
"@xterm/addon-fit": "^0.10.0", "@xterm/addon-fit": "^0.10.0",
@ -49,7 +52,7 @@
"clsx": "^2.1.1", "clsx": "^2.1.1",
"immer": "^10.1.1", "immer": "^10.1.1",
"jotai": "^2.8.0", "jotai": "^2.8.0",
"path": "^0.12.7", "monaco-editor": "^0.49.0",
"react": "^18.3.1", "react": "^18.3.1",
"react-dom": "^18.3.1", "react-dom": "^18.3.1",
"react-markdown": "^9.0.1", "react-markdown": "^9.0.1",

View File

@ -36,3 +36,7 @@ body {
border-bottom: 1px solid var(--border-color); border-bottom: 1px solid var(--border-color);
flex-shrink: 0; flex-shrink: 0;
} }
.error-boundary {
color: var(--error-color);
}

View File

@ -1,13 +1,20 @@
import react from "@vitejs/plugin-react"; import react from "@vitejs/plugin-react";
import { defineConfig } from "vite"; import { defineConfig } from "vite";
import { viteStaticCopy } from "vite-plugin-static-copy";
import tsconfigPaths from "vite-tsconfig-paths"; import tsconfigPaths from "vite-tsconfig-paths";
export default defineConfig({ export default defineConfig({
plugins: [react({}), tsconfigPaths()], plugins: [
define: { "process.env": process.env }, react({}),
tsconfigPaths(),
viteStaticCopy({
targets: [{ src: "node_modules/monaco-editor/min/vs/*", dest: "monaco" }],
}),
],
publicDir: "public", publicDir: "public",
build: { build: {
target: "es6", target: "es6",
sourcemap: true,
rollupOptions: { rollupOptions: {
input: { input: {
app: "public/index.html", app: "public/index.html",

View File

@ -1913,6 +1913,30 @@ __metadata:
languageName: node languageName: node
linkType: hard 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": "@ndelangen/get-tarball@npm:^3.0.7":
version: 3.0.9 version: 3.0.9
resolution: "@ndelangen/get-tarball@npm:3.0.9" resolution: "@ndelangen/get-tarball@npm:3.0.9"
@ -4650,7 +4674,7 @@ __metadata:
languageName: node languageName: node
linkType: hard linkType: hard
"chokidar@npm:^3.6.0": "chokidar@npm:^3.5.3, chokidar@npm:^3.6.0":
version: 3.6.0 version: 3.6.0
resolution: "chokidar@npm:3.6.0" resolution: "chokidar@npm:3.6.0"
dependencies: dependencies:
@ -6161,7 +6185,7 @@ __metadata:
languageName: node languageName: node
linkType: hard 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 version: 3.3.2
resolution: "fast-glob@npm:3.3.2" resolution: "fast-glob@npm:3.3.2"
dependencies: dependencies:
@ -7001,13 +7025,6 @@ __metadata:
languageName: node languageName: node
linkType: hard 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": "inline-style-parser@npm:0.2.3":
version: 0.2.3 version: 0.2.3
resolution: "inline-style-parser@npm:0.2.3" resolution: "inline-style-parser@npm:0.2.3"
@ -8816,6 +8833,13 @@ __metadata:
languageName: node languageName: node
linkType: hard 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": "ms@npm:2.0.0":
version: 2.0.0 version: 2.0.0
resolution: "ms@npm:2.0.0" resolution: "ms@npm:2.0.0"
@ -9344,16 +9368,6 @@ __metadata:
languageName: node languageName: node
linkType: hard 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": "pathe@npm:^1.1.1, pathe@npm:^1.1.2":
version: 1.1.2 version: 1.1.2
resolution: "pathe@npm:1.1.2" resolution: "pathe@npm:1.1.2"
@ -9568,7 +9582,7 @@ __metadata:
languageName: node languageName: node
linkType: hard linkType: hard
"process@npm:^0.11.1, process@npm:^0.11.10": "process@npm:^0.11.10":
version: 0.11.10 version: 0.11.10
resolution: "process@npm:0.11.10" resolution: "process@npm:0.11.10"
checksum: 10c0/40c3ce4b7e6d4b8c3355479df77aeed46f81b279818ccdc500124e6a5ab882c0cc81ff7ea16384873a95a74c4570b01b120f287abbdd4c877931460eca6084b3 checksum: 10c0/40c3ce4b7e6d4b8c3355479df77aeed46f81b279818ccdc500124e6a5ab882c0cc81ff7ea16384873a95a74c4570b01b120f287abbdd4c877931460eca6084b3
@ -10643,6 +10657,13 @@ __metadata:
languageName: node languageName: node
linkType: hard 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": "statuses@npm:2.0.1":
version: 2.0.1 version: 2.0.1
resolution: "statuses@npm:2.0.1" resolution: "statuses@npm:2.0.1"
@ -10950,6 +10971,8 @@ __metadata:
dependencies: dependencies:
"@chromatic-com/storybook": "npm:^1.3.3" "@chromatic-com/storybook": "npm:^1.3.3"
"@eslint/js": "npm:^9.2.0" "@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" "@observablehq/plot": "npm:^0.6.14"
"@storybook/addon-essentials": "npm:^8.0.10" "@storybook/addon-essentials": "npm:^8.0.10"
"@storybook/addon-interactions": "npm:^8.0.10" "@storybook/addon-interactions": "npm:^8.0.10"
@ -10974,7 +10997,7 @@ __metadata:
immer: "npm:^10.1.1" immer: "npm:^10.1.1"
jotai: "npm:^2.8.0" jotai: "npm:^2.8.0"
less: "npm:^4.2.0" less: "npm:^4.2.0"
path: "npm:^0.12.7" monaco-editor: "npm:^0.49.0"
prettier: "npm:^3.2.5" prettier: "npm:^3.2.5"
prettier-plugin-jsdoc: "npm:^1.3.0" prettier-plugin-jsdoc: "npm:^1.3.0"
prettier-plugin-organize-imports: "npm:^3.2.4" prettier-plugin-organize-imports: "npm:^3.2.4"
@ -10990,6 +11013,7 @@ __metadata:
typescript-eslint: "npm:^7.8.0" typescript-eslint: "npm:^7.8.0"
uuid: "npm:^9.0.1" uuid: "npm:^9.0.1"
vite: "npm:^5.0.0" vite: "npm:^5.0.0"
vite-plugin-static-copy: "npm:^1.0.5"
vite-tsconfig-paths: "npm:^4.3.2" vite-tsconfig-paths: "npm:^4.3.2"
vitest: "npm:^1.6.0" vitest: "npm:^1.6.0"
languageName: unknown languageName: unknown
@ -11529,15 +11553,6 @@ __metadata:
languageName: node languageName: node
linkType: hard 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": "util@npm:^0.12.4, util@npm:^0.12.5":
version: 0.12.5 version: 0.12.5
resolution: "util@npm:0.12.5" resolution: "util@npm:0.12.5"
@ -11627,6 +11642,20 @@ __metadata:
languageName: node languageName: node
linkType: hard 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": "vite-tsconfig-paths@npm:^4.3.2":
version: 4.3.2 version: 4.3.2
resolution: "vite-tsconfig-paths@npm:4.3.2" resolution: "vite-tsconfig-paths@npm:4.3.2"