diff --git a/ui/src/components/Canvas.tsx b/ui/src/components/Canvas.tsx index afc7736d..ead7c85e 100644 --- a/ui/src/components/Canvas.tsx +++ b/ui/src/components/Canvas.tsx @@ -68,9 +68,7 @@ const ScopeNode = memo(({ data, id, isConnectable }) => { const [frame] = React.useState({ translate: [0, 0], }); - const setSelected = useStore(store, (state) => state.setSelected); const selected = useStore(store, (state) => state.selected); - const { setNodes } = useReactFlow(); const onResize = useCallback(({ width, height, offx, offy }) => { const node = nodesMap.get(id); @@ -186,6 +184,8 @@ const ScopeNode = memo(({ data, id, isConnectable }) => { ); }); +// FIXME: the resultblock is rendered every time the parent codeNode changes (e.g., dragging), we may set the result number as a state of a pod to memoize the resultblock. + function ResultBlock({ pod, id }) { const store = useContext(RepoContext); if (!store) throw new Error("Missing BearContext.Provider in the tree"); @@ -252,12 +252,8 @@ function ResultBlock({ pod, id }) { const CodeNode = memo(({ data, id, isConnectable }) => { const store = useContext(RepoContext); if (!store) throw new Error("Missing BearContext.Provider in the tree"); - const pod = useStore(store, (state) => state.pods[id]); - const setPodContent = useStore(store, (state) => state.setPodContent); - const updatePod = useStore(store, (state) => state.updatePod); - const clearResults = useStore(store, (s) => s.clearResults); + // const pod = useStore(store, (state) => state.pods[id]); const wsRun = useStore(store, (state) => state.wsRun); - const nodesMap = useStore(store, (state) => state.ydoc.getMap("pods")); const ref = useRef(null); const [target, setTarget] = React.useState(null); const [frame] = React.useState({ @@ -266,30 +262,25 @@ const CodeNode = memo(({ data, id, isConnectable }) => { // right, bottom const [layout, setLayout] = useState("right"); const { setNodes } = useReactFlow(); - const selected = useStore(store, (state) => state.selected); + // const selected = useStore(store, (state) => state.selected); const setSelected = useStore(store, (state) => state.setSelected); + const getPod = useStore(store, (state) => state.getPod); + const pod = getPod(id); + + const showResult = useStore( + store, + (state) => + state.pods[id].running || + state.pods[id].result || + state.pods[id].error || + state.pods[id].stdout || + state.pods[id].stderr + ); - const onResize = useCallback(({ width, height, offx, offy }) => { - const node = nodesMap.get(id); - if (node) { - node.style = { ...node.style, width, height }; - node.position.x += offx; - node.position.y += offy; - nodesMap.set(id, node); - } - }, []); - const onLayout = useCallback(({ height }) => { - const node = nodesMap.get(id); - if (node) { - node.style = { ...node.style, height }; - nodesMap.set(id, node); - } - }, []); - - React.useEffect(() => { + useEffect(() => { setTarget(ref.current); }, []); - if (!pod) return ERROR; + // if (!pod) return ERROR; return ( (({ data, id, isConnectable }) => { ); }} > - { - setPodContent({ id: pod.id, content: value }); - }} - lang={pod.lang || "javascript"} - onRun={() => { - clearResults(pod.id); - wsRun(pod.id); - }} - onLayout={onLayout} - /> - {(pod.running || - pod.stdout || - pod.stderr || - pod.result || - pod.error) && ( + + {showResult && ( (({ data, id, isConnectable }) => { )} - {false && ( - { - e.setOrigin(["%", "%"]); - e.dragStart && e.dragStart.set(frame.translate); - }} - onResize={(e) => { - const beforeTranslate = e.drag.beforeTranslate; - frame.translate = beforeTranslate; - e.target.style.width = `${e.width}px`; - e.target.style.height = `${e.height}px`; - e.target.style.transform = `translate(${beforeTranslate[0]}px, ${beforeTranslate[1]}px)`; - onResize({ - width: e.width, - height: e.height, - offx: beforeTranslate[0], - offy: beforeTranslate[1], - }); - updatePod({ - id, - data: { - width: e.width, - height: e.height, - }, - }); - }} - /> - )} ); }); @@ -484,35 +422,39 @@ export function Canvas() { const store = useContext(RepoContext); if (!store) throw new Error("Missing BearContext.Provider in the tree"); // the real pods - const id2children = useStore(store, (state) => state.id2children); - const pods = useStore(store, (state) => state.pods); + const getId2children = useStore(store, (state) => state.getId2children); + // const pods = useStore(store, (state) => state.pods); + const getPod = useStore(store, (state) => state.getPod); const nodesMap = useStore(store, (state) => state.ydoc.getMap("pods")); const getRealNodes = useCallback( (id, level) => { let res: any[] = []; - let children = id2children[id]; + let children = getId2children(id) || []; + console.log("getChildren", id, children); + const pod = getPod(id); if (id !== "ROOT") { res.push({ id: id, - type: pods[id].type === "CODE" ? "code" : "scope", + type: pod.type === "CODE" ? "code" : "scope", data: { // label: `ID: ${id}, parent: ${pods[id].parent}, pos: ${pods[id].x}, ${pods[id].y}`, label: id, }, // position: { x: 100, y: 100 }, - position: { x: pods[id].x, y: pods[id].y }, - parentNode: pods[id].parent !== "ROOT" ? pods[id].parent : undefined, + position: { x: pod.x, y: pod.y }, + parentNode: pod.parent !== "ROOT" ? pod.parent : undefined, extent: "parent", dragHandle: ".custom-drag-handle", level, style: { backgroundColor: - pods[id].type === "CODE" + pod.type === "CODE" ? undefined : level2color[level] || level2color["default"], width: 700, - height: pods[id].height, + // for code node, don't set height, let it be auto + height: pod.height || undefined, }, }); } @@ -521,7 +463,7 @@ export function Canvas() { } return res; }, - [id2children, pods] + [getId2children, getPod] ); useEffect(() => { let nodes = getRealNodes("ROOT", -1); @@ -593,7 +535,8 @@ export function Canvas() { } else { style = { width: 700, - height: 300, + // we must not set the height here, otherwise the auto layout will not work + height: undefined, }; } @@ -640,19 +583,19 @@ export function Canvas() { (node) => { let x = node.position.x; let y = node.position.y; - let parent = pods[node.parent]; + let parent = getPod(node.parent); while (parent) { x += parent.x; y += parent.y; if (parent.parent) { - parent = pods[parent.parent]; + parent = getPod(parent.parent); } else { break; } } return [x, y]; }, - [pods] + [getPod] ); const getScopeAt = useCallback( diff --git a/ui/src/components/MyMonaco.tsx b/ui/src/components/MyMonaco.tsx index aee2969a..22e74a20 100644 --- a/ui/src/components/MyMonaco.tsx +++ b/ui/src/components/MyMonaco.tsx @@ -1,5 +1,5 @@ import { Position } from "monaco-editor"; -import { useState, useContext } from "react"; +import { useState, useContext, memo } from "react"; import MonacoEditor, { MonacoDiffEditor } from "react-monaco-editor"; import { monaco } from "react-monaco-editor"; import { useStore } from "zustand"; @@ -298,20 +298,31 @@ async function updateGitGutter(editor) { // not, and the instance will only be mounted once. All variables, even a object // like the pod object will be fixed at original state: changing pod.staged // won't be visible in the editorDidMount callback. -export function MyMonaco({ - lang = "javascript", - value = "", + +interface MyMonacoProps { + id: string; + gitvalue: string; +} + +export const MyMonaco = memo(function MyMonaco({ id = "0", gitvalue = null, - onChange = (value) => {}, - onRun = () => {}, - onLayout = (height) => {}, }) { - // console.log("rendering monaco .."); // there's no racket language support const store = useContext(RepoContext); if (!store) throw new Error("Missing BearContext.Provider in the tree"); const showLineNumbers = useStore(store, (state) => state.showLineNumbers); + const getPod = useStore(store, (state) => state.getPod); + const setPodContent = useStore(store, (state) => state.setPodContent); + const clearResults = useStore(store, (s) => s.clearResults); + const wsRun = useStore(store, (state) => state.wsRun); + const value = getPod(id).content || ""; + let lang = getPod(id).lang || "javascript"; + const onChange = (value) => setPodContent({ id, content: value }); + const onRun = () => { + clearResults(id); + wsRun(id); + }; if (lang === "racket") { lang = "scheme"; @@ -346,7 +357,7 @@ export function MyMonaco({ // width: 800 // editor.layout({ width: 800, height: contentHeight }); editor.layout(); - onLayout(`${contentHeight}px`); + // onLayout(`${contentHeight}px`); }; editor.onDidContentSizeChange(updateHeight); // FIXME clean up? @@ -420,4 +431,4 @@ export function MyMonaco({ editorDidMount={onEditorDidMount} /> ); -} +}); diff --git a/ui/src/lib/nodes.tsx b/ui/src/lib/nodes.tsx index 1227f678..148346a5 100644 --- a/ui/src/lib/nodes.tsx +++ b/ui/src/lib/nodes.tsx @@ -29,15 +29,22 @@ export function useNodesStateSynced(nodeList) { if (!isNodeAddChange(change) && !isNodeResetChange(change)) { if (isNodeRemoveChange(change)) { nodesMap.delete(change.id); + return; } const node = nextNodes.find((n) => n.id === change.id); if (!node) return; - if (change.type === "select") { + if (change.type === "select" && change.selected) { + // FIXME: consider the case where only unselect is called setSelected(node.id); return; } + if (change.type === "dimensions" && node.type === "code") { + // the re-size event of codeNode don't need to be sync, just skip. + return; + } + if (node) { nodesMap.set(change.id, node); } diff --git a/ui/src/lib/store.tsx b/ui/src/lib/store.tsx index 8cae736a..543e9e70 100644 --- a/ui/src/lib/store.tsx +++ b/ui/src/lib/store.tsx @@ -185,6 +185,9 @@ export interface RepoSlice { deleteClient: (clientId: any) => void; flipShowLineNumbers: () => void; disconnect: () => void; + getPod: (string) => any; + getPods: () => any; + getId2children: (string) => string[]; } type BearState = RepoSlice & RuntimeSlice; @@ -752,6 +755,9 @@ const createRepoSlice: StateCreator< state.ydoc.destroy(); }) ), + getPod: (id: string) => get().pods[id], + getPods: () => get().pods, + getId2children: (id: string) => get().id2children[id], }); export const createRepoStore = () =>