Skip to content

Commit 2ccaa87

Browse files
li-xin-yilihebi
andauthored
Fix: sync store.pods[id].parent/position immediately on updates from anywhere (#140)
* setpodposition * listen to change in parent and position * adjust formatting Co-authored-by: Hebi Li <[email protected]>
1 parent c4356a7 commit 2ccaa87

File tree

2 files changed

+147
-44
lines changed

2 files changed

+147
-44
lines changed

ui/src/components/Canvas.tsx

Lines changed: 123 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -64,16 +64,28 @@ interface Props {
6464
id: string;
6565
isConnectable: boolean;
6666
selected: boolean;
67+
// note that xPos and yPos are the absolute position of the node
68+
xPos: number;
69+
yPos: number;
6770
}
6871

69-
const ScopeNode = memo<Props>(({ data, id, isConnectable, selected }) => {
72+
const ScopeNode = memo<Props>(function ScopeNode({
73+
data,
74+
id,
75+
isConnectable,
76+
selected,
77+
xPos,
78+
yPos,
79+
}) {
7080
// add resize to the node
7181
const ref = useRef(null);
7282
const store = useContext(RepoContext);
7383
if (!store) throw new Error("Missing BearContext.Provider in the tree");
7484
const flow = useReactFlow();
7585
const setPodName = useStore(store, (state) => state.setPodName);
7686
const updatePod = useStore(store, (state) => state.updatePod);
87+
const setPodPosition = useStore(store, (state) => state.setPodPosition);
88+
const setPodParent = useStore(store, (state) => state.setPodParent);
7789
const [target, setTarget] = React.useState<any>();
7890
const nodesMap = useStore(store, (state) => state.ydoc.getMap<Node>("pods"));
7991
const [frame] = React.useState({
@@ -118,6 +130,26 @@ const ScopeNode = memo<Props>(({ data, id, isConnectable, selected }) => {
118130
}
119131
}, [data.name, id, setPodName]);
120132

133+
useEffect(() => {
134+
// get relative position
135+
const node = nodesMap.get(id);
136+
if (node?.position) {
137+
// update pods[id].position but don't trigger DB update (dirty: false)
138+
setPodPosition({
139+
id,
140+
x: node.position.x,
141+
y: node.position.y,
142+
dirty: false,
143+
});
144+
}
145+
}, [xPos, yPos, setPodPosition, id]);
146+
147+
useEffect(() => {
148+
if (data.parent && data.parent !== "ROOT") {
149+
setPodParent({ id, parent: data.parent, dirty: false });
150+
}
151+
}, [data.parent, setPodParent, id]);
152+
121153
return (
122154
<Box
123155
ref={ref}
@@ -144,7 +176,7 @@ const ScopeNode = memo<Props>(({ data, id, isConnectable, selected }) => {
144176
}}
145177
>
146178
{role !== RoleType.GUEST && (
147-
<Tooltip title="Delete">
179+
<Tooltip title="Delete" className="nodrag">
148180
<IconButton
149181
size="small"
150182
onClick={(e: any) => {
@@ -192,7 +224,10 @@ const ScopeNode = memo<Props>(({ data, id, isConnectable, selected }) => {
192224
if (name === data.name) return;
193225
const node = nodesMap.get(id);
194226
if (node) {
195-
nodesMap.set(id, { ...node, data: { ...node.data, name } });
227+
nodesMap.set(id, {
228+
...node,
229+
data: { ...node.data, name },
230+
});
196231
}
197232
// setPodName({ id, name });
198233
}}
@@ -398,7 +433,14 @@ function ResultBlock({ pod, id }) {
398433
);
399434
}
400435

401-
const CodeNode = memo<Props>(({ data, id, isConnectable, selected }) => {
436+
const CodeNode = memo<Props>(function ({
437+
data,
438+
id,
439+
isConnectable,
440+
selected,
441+
xPos,
442+
yPos,
443+
}) {
402444
const store = useContext(RepoContext);
403445
if (!store) throw new Error("Missing BearContext.Provider in the tree");
404446
// const pod = useStore(store, (state) => state.pods[id]);
@@ -412,10 +454,10 @@ const CodeNode = memo<Props>(({ data, id, isConnectable, selected }) => {
412454
// right, bottom
413455
const [layout, setLayout] = useState("bottom");
414456
const isRightLayout = layout === "right";
415-
const { setNodes } = useReactFlow();
416-
// const selected = useStore(store, (state) => state.selected);
417457
const setPodName = useStore(store, (state) => state.setPodName);
458+
const setPodPosition = useStore(store, (state) => state.setPodPosition);
418459
const setCurrentEditor = useStore(store, (state) => state.setCurrentEditor);
460+
const setPodParent = useStore(store, (state) => state.setPodParent);
419461
const getPod = useStore(store, (state) => state.getPod);
420462
const pod = getPod(id);
421463
const role = useStore(store, (state) => state.role);
@@ -464,6 +506,26 @@ const CodeNode = memo<Props>(({ data, id, isConnectable, selected }) => {
464506
}
465507
}, [data.name, setPodName, id]);
466508

509+
useEffect(() => {
510+
// get relative position
511+
const node = nodesMap.get(id);
512+
if (node?.position) {
513+
// update pods[id].position but don't trigger DB update (dirty: false)
514+
setPodPosition({
515+
id,
516+
x: node.position.x,
517+
y: node.position.y,
518+
dirty: false,
519+
});
520+
}
521+
}, [xPos, yPos, setPodPosition, id]);
522+
523+
useEffect(() => {
524+
if (data.parent !== undefined) {
525+
setPodParent({ id, parent: data.parent, dirty: false });
526+
}
527+
}, [data.parent, setPodParent, id]);
528+
467529
if (!pod) return null;
468530

469531
// onsize is banned for a guest, FIXME: ugly code
@@ -568,6 +630,7 @@ const CodeNode = memo<Props>(({ data, id, isConnectable, selected }) => {
568630
zIndex: 250,
569631
justifyContent: "center",
570632
}}
633+
className="nodrag"
571634
>
572635
{role !== RoleType.GUEST && (
573636
<Tooltip title="Run (shift-enter)">
@@ -696,6 +759,7 @@ export function Canvas() {
696759
// label: `ID: ${id}, parent: ${pods[id].parent}, pos: ${pods[id].x}, ${pods[id].y}`,
697760
label: id,
698761
name: pod.name,
762+
parent: pod.parent,
699763
},
700764
// position: { x: 100, y: 100 },
701765
position: { x: pod.x, y: pod.y },
@@ -823,6 +887,7 @@ export function Canvas() {
823887
data: {
824888
label: id,
825889
name: "",
890+
parent: "ROOT",
826891
},
827892
level: 0,
828893
extent: "parent",
@@ -901,6 +966,7 @@ export function Canvas() {
901966
if (commonParent !== undefined && commonParent !== "ROOT") {
902967
const currentParent = nodesMap.get(commonParent);
903968
if (currentParent) {
969+
console.log("currentParent", currentParent);
904970
if (
905971
mousePos.x < currentParent.positionAbsolute!.x ||
906972
mousePos.x >
@@ -911,56 +977,75 @@ export function Canvas() {
911977
) {
912978
// the mouse is outside the current parent, the nodes can't be dragged out
913979
// console.log("Cannot drop outside parent scope");
980+
// but position should also be updated
981+
nodes.forEach((node) => {
982+
setPodPosition({
983+
id: node.id,
984+
x: node.position.x,
985+
y: node.position.y,
986+
dirty: true,
987+
});
988+
});
914989
return;
915990
}
916991
}
917992
}
918993

994+
// no target scope, or the target scope is the same as the current parent
995+
if (!scope || scope.id === commonParent) {
996+
// only update position and exit, avoid updating parentNode
997+
nodes.forEach((node) => {
998+
setPodPosition({
999+
id: node.id,
1000+
x: node.position.x,
1001+
y: node.position.y,
1002+
dirty: true,
1003+
});
1004+
});
1005+
return;
1006+
}
1007+
9191008
// check if this position is inside parent scope
9201009
nodes.forEach((node) => {
9211010
let absX = node.position.x;
9221011
let absY = node.position.y;
9231012

924-
if (scope) {
925-
console.log("dropped into scope:", scope);
926-
// compute the actual position
927-
let [dx, dy] = getAbsPos({ node: scope, nodesMap });
928-
absX = node.positionAbsolute!.x - dx;
929-
absY = node.positionAbsolute!.y - dy;
930-
// auto-align the node to, keep it bound in the scope
931-
// FIXME: it assumes the scope must be larger than the node
932-
933-
absX = Math.max(absX, 0);
934-
absX = Math.min(absX, scope.width! - node.width!);
935-
absY = Math.max(absY, 0);
936-
absY = Math.min(absY, scope.height! - node.height!);
937-
}
938-
setPodPosition({
1013+
console.log("dropped into scope:", scope);
1014+
// compute the actual position
1015+
let [dx, dy] = getAbsPos({ node: scope, nodesMap });
1016+
absX = node.positionAbsolute!.x - dx;
1017+
absY = node.positionAbsolute!.y - dy;
1018+
// auto-align the node to, keep it bound in the scope
1019+
// FIXME: it assumes the scope must be larger than the node
1020+
1021+
absX = Math.max(absX, 0);
1022+
absX = Math.min(absX, scope.width! - node.width!);
1023+
absY = Math.max(absY, 0);
1024+
absY = Math.min(absY, scope.height! - node.height!);
1025+
1026+
setPodParent({
9391027
id: node.id,
940-
x: absX,
941-
y: absY,
1028+
parent: scope.id,
1029+
dirty: true,
9421030
});
9431031

944-
// check if this position is inside parent scope
945-
if (scope && scope.id !== commonParent) {
946-
// FIXME: to enable collaborative editing, consider how to sync dropping scope immediately. consider useEffect in each node when data.parent or parent changes.
947-
setPodParent({
948-
id: node.id,
949-
parent: scope.id,
950-
});
951-
}
952-
9531032
const currentNode = nodesMap.get(node.id);
9541033
if (currentNode) {
955-
if (scope) {
956-
currentNode.style!.backgroundColor = level2color[scope.level + 1];
957-
(currentNode as any).level = scope.level + 1;
958-
currentNode.parentNode = scope.id;
959-
}
1034+
currentNode.style!.backgroundColor = level2color[scope.level + 1];
1035+
(currentNode as any).level = scope.level + 1;
1036+
currentNode.parentNode = scope.id;
1037+
currentNode.data!.parent = scope.id;
9601038
currentNode.position = { x: absX, y: absY };
961-
9621039
nodesMap.set(node.id, currentNode);
9631040
}
1041+
1042+
// update
1043+
setPodPosition({
1044+
id: node.id,
1045+
x: absX,
1046+
y: absY,
1047+
dirty: true,
1048+
});
9641049
});
9651050
},
9661051
// We need to monitor nodes, so that getScopeAt can have all the nodes.

ui/src/lib/store.tsx

Lines changed: 24 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -177,8 +177,26 @@ export interface RepoSlice {
177177
content: { data: { html: string; text: string; image: string } };
178178
count: number;
179179
}) => void;
180-
setPodPosition: ({ id, x, y }: any) => void;
181-
setPodParent: ({ id, parent }: any) => void;
180+
setPodPosition: ({
181+
id,
182+
x,
183+
y,
184+
dirty,
185+
}: {
186+
id: string;
187+
x: number;
188+
y: number;
189+
dirty: boolean;
190+
}) => void;
191+
setPodParent: ({
192+
id,
193+
parent,
194+
dirty,
195+
}: {
196+
id: string;
197+
parent: string;
198+
dirty: boolean;
199+
}) => void;
182200
currentEditor: string | null;
183201
setCurrentEditor: (id: string | null) => void;
184202
setUser: (user: any) => void;
@@ -492,13 +510,13 @@ const createRepoSlice: StateCreator<
492510
state.pods[id].render = value;
493511
})
494512
),
495-
setPodPosition: ({ id, x, y }) =>
513+
setPodPosition: ({ id, x, y, dirty = true }) =>
496514
set(
497515
produce((state) => {
498516
let pod = state.pods[id];
499517
pod.x = x;
500518
pod.y = y;
501-
pod.dirty = true;
519+
pod.dirty ||= dirty;
502520
}),
503521
false,
504522
// @ts-ignore
@@ -653,15 +671,15 @@ const createRepoSlice: StateCreator<
653671
}
654672
})
655673
),
656-
setPodParent: ({ id, parent }) =>
674+
setPodParent: ({ id, parent, dirty = true }) =>
657675
set(
658676
produce((state) => {
659677
// FIXME I need to modify many pods here.
660678
if (state.pods[id]?.parent === parent) return;
661679
const oldparent = state.pods[state.pods[id].parent];
662680
state.pods[id].parent = parent;
663681
// FXME I'm marking all the pods as dirty here.
664-
state.pods[id].dirty = true;
682+
state.pods[id].dirty ||= dirty;
665683
state.pods[parent].children.push(state.pods[id]);
666684
let idx = oldparent.children.findIndex(({ id: _id }) => _id === id);
667685
oldparent.children.splice(idx, 1);

0 commit comments

Comments
 (0)