Skip to content

refactor Canvas #189

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 6 commits into from
Jan 3, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1,719 changes: 540 additions & 1,179 deletions ui/src/components/Canvas.tsx

Large diffs are not rendered by default.

518 changes: 518 additions & 0 deletions ui/src/components/nodes/Code.tsx

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ import { ResizableBox } from "react-resizable";
import { useApolloClient } from "@apollo/client";

import { useStore } from "zustand";
import { RepoContext, RoleType } from "../lib/store";
import { RepoContext, RoleType } from "../../lib/store";

import ReactFlow, {
addEdge,
Expand Down Expand Up @@ -49,7 +49,7 @@ import DeleteIcon from "@mui/icons-material/Delete";
import ViewComfyIcon from "@mui/icons-material/ViewComfy";
import RectangleIcon from "@mui/icons-material/Rectangle";
import DisabledByDefaultIcon from "@mui/icons-material/DisabledByDefault";
import { resetSelection } from "../lib/nodes";
import { resetSelection } from "../../lib/nodes";

import {
BoldExtension,
Expand Down Expand Up @@ -91,7 +91,6 @@ import { AllStyledComponent } from "@remirror/styles/emotion";
import { TableExtension } from "@remirror/extension-react-tables";
import { GenIcon, IconBase } from "@remirror/react-components";

import styles from "./canvas.style.js";
import { htmlToProsemirrorNode } from "remirror";
import { styled } from "@mui/material";

Expand Down Expand Up @@ -274,26 +273,11 @@ export const RichNode = memo<Props>(function ({
id,
isConnectable,
selected,
xPos,
yPos,
}) {
const store = useContext(RepoContext);
if (!store) throw new Error("Missing BearContext.Provider in the tree");
// const pod = useStore(store, (state) => state.pods[id]);
const wsRun = useStore(store, (state) => state.wsRun);
const clearResults = useStore(store, (s) => s.clearResults);
const ref = useRef(null);
const [target, setTarget] = React.useState<any>(null);
const [frame] = React.useState({
translate: [0, 0],
});
// right, bottom
const [layout, setLayout] = useState("bottom");
const isRightLayout = layout === "right";
const setPodName = useStore(store, (state) => state.setPodName);
const setPodPosition = useStore(store, (state) => state.setPodPosition);
const setCurrentEditor = useStore(store, (state) => state.setCurrentEditor);
const setPodParent = useStore(store, (state) => state.setPodParent);
const getPod = useStore(store, (state) => state.getPod);
const pod = getPod(id);
const role = useStore(store, (state) => state.role);
Expand All @@ -305,15 +289,6 @@ export const RichNode = memo<Props>(function ({
);
const inputRef = useRef<HTMLInputElement>(null);

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((e, data) => {
const { size } = data;
const node = nodesMap.get(id);
Expand All @@ -323,16 +298,6 @@ export const RichNode = memo<Props>(function ({
}
}, []);
const nodesMap = useStore(store, (state) => state.ydoc.getMap<Node>("pods"));
const apolloClient = useApolloClient();
const deletePod = useStore(store, (state) => state.deletePod);
const deleteNodeById = (id) => {
deletePod(apolloClient, { id: id, toDelete: [] });
nodesMap.delete(id);
};

useEffect(() => {
setTarget(ref.current);
}, []);

useEffect(() => {
if (!data.name) return;
Expand All @@ -342,26 +307,6 @@ export const RichNode = memo<Props>(function ({
}
}, [data.name, setPodName, id]);

useEffect(() => {
// get relative position
const node = nodesMap.get(id);
if (node?.position) {
// update pods[id].position but don't trigger DB update (dirty: false)
setPodPosition({
id,
x: node.position.x,
y: node.position.y,
dirty: false,
});
}
}, [xPos, yPos, setPodPosition, id]);

useEffect(() => {
if (data.parent !== undefined) {
setPodParent({ id, parent: data.parent, dirty: false });
}
}, [data.parent, setPodParent, id]);

if (!pod) return null;

// onsize is banned for a guest, FIXME: ugly code
Expand All @@ -372,7 +317,7 @@ export const RichNode = memo<Props>(function ({
<ResizableBox
onResizeStop={onResize}
height={pod.height || 100}
width={width}
width={width || 0}
axis={"x"}
minConstraints={[200, 200]}
>
Expand Down Expand Up @@ -457,7 +402,6 @@ export const RichNode = memo<Props>(function ({
}}
></InputBase>
</Box>
{/* <Box sx={styles["pod-index"]}>[{index}]</Box> */}
<Box
sx={{
display: "flex",
Expand All @@ -478,7 +422,7 @@ export const RichNode = memo<Props>(function ({
<IconButton
size="small"
onClick={() => {
deleteNodeById(id);
nodesMap.delete(id);
}}
>
<DeleteIcon fontSize="inherit" />
Expand Down
247 changes: 247 additions & 0 deletions ui/src/components/nodes/Scope.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,247 @@
import {
useCallback,
useState,
useRef,
useContext,
useEffect,
memo,
} from "react";
import * as React from "react";
import ReactFlow, {
addEdge,
applyEdgeChanges,
applyNodeChanges,
Background,
MiniMap,
Controls,
Handle,
useReactFlow,
Position,
ConnectionMode,
MarkerType,
Node,
} from "reactflow";
import "reactflow/dist/style.css";

import Box from "@mui/material/Box";
import InputBase from "@mui/material/InputBase";
import CircularProgress from "@mui/material/CircularProgress";
import Tooltip from "@mui/material/Tooltip";
import IconButton from "@mui/material/IconButton";
import Grid from "@mui/material/Grid";
import DeleteIcon from "@mui/icons-material/Delete";
import Moveable from "react-moveable";

import { useStore } from "zustand";

import { RepoContext, RoleType } from "../../lib/store";

interface Props {
data: any;
id: string;
isConnectable: boolean;
selected: boolean;
// note that xPos and yPos are the absolute position of the node
xPos: number;
yPos: number;
}

export const ScopeNode = memo<Props>(function ScopeNode({
data,
id,
isConnectable,
selected,
}) {
// add resize to the node
const ref = useRef(null);
const store = useContext(RepoContext);
if (!store) throw new Error("Missing BearContext.Provider in the tree");
const reactFlowInstance = useReactFlow();
const setPodName = useStore(store, (state) => state.setPodName);
const setPodGeo = useStore(store, (state) => state.setPodGeo);
const [target, setTarget] = React.useState<any>();
const nodesMap = useStore(store, (state) => state.ydoc.getMap<Node>("pods"));
const [frame] = React.useState({
translate: [0, 0],
});
// const selected = useStore(store, (state) => state.pods[id]?.selected);
const role = useStore(store, (state) => state.role);
const inputRef = useRef<HTMLInputElement>(null);

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);
}
}, []);

useEffect(() => {
setTarget(ref.current);
}, []);

useEffect(() => {
if (!data.name) return;
setPodName({ id, name: data.name || "" });
if (inputRef?.current) {
inputRef.current.value = data.name;
}
}, [data.name, id, setPodName]);

return (
<Box
ref={ref}
sx={{
width: "100%",
height: "100%",
border: "solid 1px #d6dee6",
borderRadius: "4px",
}}
className="custom-drag-handle"
>
<Box
sx={{
display: "flex",
marginLeft: "10px",
borderRadius: "4px",
position: "absolute",
border: "solid 1px #d6dee6",
right: "25px",
top: "-15px",
background: "white",
zIndex: 250,
justifyContent: "center",
}}
>
{role !== RoleType.GUEST && (
<Tooltip title="Delete" className="nodrag">
<IconButton
size="small"
onClick={(e: any) => {
// This does not work, will throw "Parent node
// jqgdsz2ns6k57vich0bf not found" when deleting a scope.
//
// nodesMap.delete(id);
//
// But this works:
reactFlowInstance.deleteElements({ nodes: [{ id }] });
Comment on lines +123 to +129
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@li-xin-yi nodesMap.delete and reactFlowInstance.deleteElements are indeed different, te latter seems to support recursive deletion of a scope.

}}
>
<DeleteIcon fontSize="inherit" />
</IconButton>
</Tooltip>
)}
</Box>
<Handle
type="source"
position={Position.Top}
id="top"
isConnectable={isConnectable}
/>
{/* The header of scope nodes. */}
<Box
className="custom-drag-handle"
// bgcolor={"rgb(225,225,225)"}
sx={{ display: "flex" }}
>
<Grid container spacing={2} sx={{ alignItems: "center" }}>
<Grid item xs={4}>
{/* <IconButton size="small">
<CircleIcon sx={{ color: "red" }} fontSize="inherit" />
</IconButton> */}
</Grid>
<Grid item xs={4}>
<Box
sx={{
display: "flex",
flexGrow: 1,
justifyContent: "center",
}}
>
<InputBase
className="nodrag"
defaultValue={data.name || "Scope"}
onBlur={(e) => {
const name = e.target.value;
if (name === data.name) return;
const node = nodesMap.get(id);
if (node) {
nodesMap.set(id, {
...node,
data: { ...node.data, name },
});
}
// setPodName({ id, name });
}}
inputRef={inputRef}
disabled={role === RoleType.GUEST}
inputProps={{
style: {
padding: "0px",
textAlign: "center",
textOverflow: "ellipsis",
},
}}
></InputBase>
</Box>
</Grid>
<Grid item xs={4}></Grid>
</Grid>
</Box>
<Handle
type="source"
position={Position.Bottom}
id="bottom"
isConnectable={isConnectable}
/>
<Handle
type="source"
position={Position.Left}
id="left"
isConnectable={isConnectable}
/>
<Handle
type="source"
position={Position.Right}
id="right"
isConnectable={isConnectable}
/>
{selected && role !== RoleType.GUEST && (
<Moveable
target={target}
resizable={true}
keepRatio={false}
throttleResize={1}
renderDirections={["e", "s", "se"]}
edge={false}
zoom={1}
origin={false}
padding={{ left: 0, top: 0, right: 0, bottom: 0 }}
onResizeStart={(e) => {
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],
});
setPodGeo(id, {
width: e.width,
height: e.height,
});
}}
/>
)}
</Box>
);
});
Loading