incname commited on
Commit
cb329cc
·
1 Parent(s): 35a1aa5

Update BlockEditor.tsx

Browse files
client/src/components/BlockEditor/BlockEditor.tsx CHANGED
@@ -585,27 +585,6 @@ export default function BlockEditor() {
585
  const remoteUpdateRef = useRef(false);
586
  const isDraggingRef = useRef(false);
587
 
588
- // Refs to avoid stale closures in Blockly change listeners.
589
- // The socket emit functions change when projectId changes, but the
590
- // BlockEditor's main useEffect only re-runs on [framework, activeFileId].
591
- // By calling through refs, listeners always invoke the latest version.
592
- const emitBlockStateSyncRef = useRef(emitBlockStateSync);
593
- const emitBlockDragStartRef = useRef(emitBlockDragStart);
594
- const emitBlockDragMoveRef = useRef(emitBlockDragMove);
595
- const emitBlockDragEndRef = useRef(emitBlockDragEnd);
596
- const emitBlockConnectRef = useRef(emitBlockConnect);
597
- const emitBlockChangeRef = useRef(emitBlockChange);
598
- const emitBlockCreateEventRef = useRef(emitBlockCreateEvent);
599
- const emitPointerMoveRef = useRef(emitPointerMove);
600
- emitBlockStateSyncRef.current = emitBlockStateSync;
601
- emitBlockDragStartRef.current = emitBlockDragStart;
602
- emitBlockDragMoveRef.current = emitBlockDragMove;
603
- emitBlockDragEndRef.current = emitBlockDragEnd;
604
- emitBlockConnectRef.current = emitBlockConnect;
605
- emitBlockChangeRef.current = emitBlockChange;
606
- emitBlockCreateEventRef.current = emitBlockCreateEvent;
607
- emitPointerMoveRef.current = emitPointerMove;
608
-
609
  // Modal state for editing variable/function blocks
610
  const [editingModal, setEditingModal] = useState<EditModalData | null>(null);
611
  const [editFormValues, setEditFormValues] = useState<Record<string, string>>({});
@@ -747,7 +726,7 @@ export default function BlockEditor() {
747
  x: (relX - ws.scrollX) / ws.scale,
748
  y: (relY - ws.scrollY) / ws.scale,
749
  };
750
- emitPointerMoveRef.current(wsCoord.x, wsCoord.y);
751
  };
752
  svg.addEventListener('mousemove', handleMouseMove);
753
  // Store for cleanup
@@ -770,7 +749,7 @@ export default function BlockEditor() {
770
  dragEndId++;
771
  isDraggingRef.current = true;
772
  draggedBlockId = e.blockId;
773
- emitBlockDragStartRef.current(e.blockId, xy.x, xy.y);
774
  // Poll block position during drag (BLOCK_MOVE may not fire)
775
  dragPoll = setInterval(() => {
776
  if (remoteUpdateRef.current || !isDraggingRef.current) {
@@ -780,14 +759,14 @@ export default function BlockEditor() {
780
  const b = ws.getBlockById(draggedBlockId!);
781
  if (b) {
782
  const pos = b.getRelativeToSurfaceXY();
783
- emitBlockDragMoveRef.current(draggedBlockId!, pos.x, pos.y);
784
  }
785
  }, 100);
786
  } else if (e.isEnd) {
787
  const endedBlockId = e.blockId;
788
  draggedBlockId = null;
789
  if (dragPoll) { clearInterval(dragPoll); dragPoll = null; }
790
- emitBlockDragEndRef.current(endedBlockId);
791
  // Defer isDraggingRef=false + saveBlocks until after Blockly
792
  // applies the connection (which fires BLOCK_MOVE next)
793
  const thisId = ++dragEndId;
@@ -803,12 +782,12 @@ export default function BlockEditor() {
803
  // fires BLOCK_MOVE synchronously with newParentId/newInputName.
804
  // isDraggingRef.current ensures we only emit for user-initiated drags.
805
  if (e.type === Blockly.Events.BLOCK_MOVE && e.newParentId && isDraggingRef.current) {
806
- emitBlockConnectRef.current(e.blockId, e.newParentId, e.newInputName || undefined);
807
  }
808
  // Broadcast field value changes (dropdowns, text inputs, etc.)
809
  // Only emit for user-initiated changes, not during remote updates or workspace load.
810
  if (e.type === Blockly.Events.BLOCK_CHANGE && e.element === 'field' && !remoteUpdateRef.current) {
811
- emitBlockChangeRef.current(e.blockId, e.name, e.newValue, e.element);
812
  }
813
  // Broadcast block creation (toolbox drag-out, paste, duplicate) so collaborator
814
  // screens can create the block immediately instead of waiting for XML sync.
@@ -818,7 +797,7 @@ export default function BlockEditor() {
818
  try {
819
  const dom = Blockly.Xml.blockToDom(block, { xy: true } as any);
820
  const xml = Blockly.Xml.domToText(dom);
821
- emitBlockCreateEventRef.current(e.blockId, xml);
822
  } catch {}
823
  }
824
  }
@@ -843,7 +822,7 @@ export default function BlockEditor() {
843
  const currentFileId = useEditorStore.getState().activeFileId;
844
  if (currentFileId) {
845
  setBlocksXml(currentFileId, xml);
846
- emitBlockStateSyncRef.current(currentFileId, xml);
847
  let finalCode = '';
848
  try {
849
  // Try manual code generation first (uses our compile functions directly)
@@ -934,11 +913,8 @@ export default function BlockEditor() {
934
  remoteUpdateRef.current = true;
935
  const ws = workspaceRef.current;
936
  if (ws) {
937
- // Snapshot and clear atomically so no items are lost
938
- const queue = useCollaborationStore.getState().pendingBlockConnects;
939
- useCollaborationStore.setState({ pendingBlockConnects: [] });
940
  const unprocessed: Array<{ childId: string; parentId: string; inputName?: string }> = [];
941
- for (const connect of queue) {
942
  const child = ws.getBlockById(connect.childId);
943
  const parent = ws.getBlockById(connect.parentId);
944
  if (!child || !parent) {
@@ -972,8 +948,8 @@ export default function BlockEditor() {
972
  }
973
  }
974
  }
975
- // Put back any items that couldn't be processed yet
976
- if (unprocessed.length > 0) {
977
  useCollaborationStore.setState({ pendingBlockConnects: unprocessed });
978
  }
979
  ws.render();
@@ -987,10 +963,8 @@ export default function BlockEditor() {
987
  remoteUpdateRef.current = true;
988
  const ws = workspaceRef.current;
989
  if (ws) {
990
- // Snapshot and clear atomically
991
- const queue = useCollaborationStore.getState().pendingBlockChanges;
992
- useCollaborationStore.setState({ pendingBlockChanges: [] });
993
- for (const change of queue) {
994
  const block = ws.getBlockById(change.blockId);
995
  if (!block) continue;
996
  try {
@@ -1008,11 +982,10 @@ export default function BlockEditor() {
1008
  remoteUpdateRef.current = true;
1009
  const ws = workspaceRef.current;
1010
  if (ws) {
1011
- // Snapshot the queue and clear atomically so no items are lost
1012
- const queue = useCollaborationStore.getState().pendingBlockCreates;
1013
- useCollaborationStore.setState({ pendingBlockCreates: [] });
1014
  const unprocessed: Array<{ blockId: string; xml: string }> = [];
1015
- for (const create of queue) {
 
 
1016
  if (ws.getBlockById(create.blockId)) continue;
1017
  try {
1018
  const dom = Blockly.utils.xml.textToDom(create.xml);
@@ -1021,7 +994,7 @@ export default function BlockEditor() {
1021
  unprocessed.push(create);
1022
  }
1023
  }
1024
- if (unprocessed.length > 0) {
1025
  useCollaborationStore.setState({ pendingBlockCreates: unprocessed });
1026
  }
1027
  }
 
585
  const remoteUpdateRef = useRef(false);
586
  const isDraggingRef = useRef(false);
587
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
588
  // Modal state for editing variable/function blocks
589
  const [editingModal, setEditingModal] = useState<EditModalData | null>(null);
590
  const [editFormValues, setEditFormValues] = useState<Record<string, string>>({});
 
726
  x: (relX - ws.scrollX) / ws.scale,
727
  y: (relY - ws.scrollY) / ws.scale,
728
  };
729
+ emitPointerMove(wsCoord.x, wsCoord.y);
730
  };
731
  svg.addEventListener('mousemove', handleMouseMove);
732
  // Store for cleanup
 
749
  dragEndId++;
750
  isDraggingRef.current = true;
751
  draggedBlockId = e.blockId;
752
+ emitBlockDragStart(e.blockId, xy.x, xy.y);
753
  // Poll block position during drag (BLOCK_MOVE may not fire)
754
  dragPoll = setInterval(() => {
755
  if (remoteUpdateRef.current || !isDraggingRef.current) {
 
759
  const b = ws.getBlockById(draggedBlockId!);
760
  if (b) {
761
  const pos = b.getRelativeToSurfaceXY();
762
+ emitBlockDragMove(draggedBlockId!, pos.x, pos.y);
763
  }
764
  }, 100);
765
  } else if (e.isEnd) {
766
  const endedBlockId = e.blockId;
767
  draggedBlockId = null;
768
  if (dragPoll) { clearInterval(dragPoll); dragPoll = null; }
769
+ emitBlockDragEnd(endedBlockId);
770
  // Defer isDraggingRef=false + saveBlocks until after Blockly
771
  // applies the connection (which fires BLOCK_MOVE next)
772
  const thisId = ++dragEndId;
 
782
  // fires BLOCK_MOVE synchronously with newParentId/newInputName.
783
  // isDraggingRef.current ensures we only emit for user-initiated drags.
784
  if (e.type === Blockly.Events.BLOCK_MOVE && e.newParentId && isDraggingRef.current) {
785
+ emitBlockConnect(e.blockId, e.newParentId, e.newInputName || undefined);
786
  }
787
  // Broadcast field value changes (dropdowns, text inputs, etc.)
788
  // Only emit for user-initiated changes, not during remote updates or workspace load.
789
  if (e.type === Blockly.Events.BLOCK_CHANGE && e.element === 'field' && !remoteUpdateRef.current) {
790
+ emitBlockChange(e.blockId, e.name, e.newValue, e.element);
791
  }
792
  // Broadcast block creation (toolbox drag-out, paste, duplicate) so collaborator
793
  // screens can create the block immediately instead of waiting for XML sync.
 
797
  try {
798
  const dom = Blockly.Xml.blockToDom(block, { xy: true } as any);
799
  const xml = Blockly.Xml.domToText(dom);
800
+ emitBlockCreateEvent(e.blockId, xml);
801
  } catch {}
802
  }
803
  }
 
822
  const currentFileId = useEditorStore.getState().activeFileId;
823
  if (currentFileId) {
824
  setBlocksXml(currentFileId, xml);
825
+ emitBlockStateSync(currentFileId, xml);
826
  let finalCode = '';
827
  try {
828
  // Try manual code generation first (uses our compile functions directly)
 
913
  remoteUpdateRef.current = true;
914
  const ws = workspaceRef.current;
915
  if (ws) {
 
 
 
916
  const unprocessed: Array<{ childId: string; parentId: string; inputName?: string }> = [];
917
+ for (const connect of pendingBlockConnects) {
918
  const child = ws.getBlockById(connect.childId);
919
  const parent = ws.getBlockById(connect.parentId);
920
  if (!child || !parent) {
 
948
  }
949
  }
950
  }
951
+ // Only update store when items were actually consumed (blocks existed)
952
+ if (unprocessed.length < pendingBlockConnects.length) {
953
  useCollaborationStore.setState({ pendingBlockConnects: unprocessed });
954
  }
955
  ws.render();
 
963
  remoteUpdateRef.current = true;
964
  const ws = workspaceRef.current;
965
  if (ws) {
966
+ let change;
967
+ while ((change = useCollaborationStore.getState().shiftPendingBlockChange())) {
 
 
968
  const block = ws.getBlockById(change.blockId);
969
  if (!block) continue;
970
  try {
 
982
  remoteUpdateRef.current = true;
983
  const ws = workspaceRef.current;
984
  if (ws) {
 
 
 
985
  const unprocessed: Array<{ blockId: string; xml: string }> = [];
986
+ let create;
987
+ while ((create = useCollaborationStore.getState().shiftPendingBlockCreate())) {
988
+ // Skip if block already exists (e.g. already created by XML sync)
989
  if (ws.getBlockById(create.blockId)) continue;
990
  try {
991
  const dom = Blockly.utils.xml.textToDom(create.xml);
 
994
  unprocessed.push(create);
995
  }
996
  }
997
+ if (unprocessed.length < pendingBlockCreates.length) {
998
  useCollaborationStore.setState({ pendingBlockCreates: unprocessed });
999
  }
1000
  }