Skip to content

Commit 9f2fe2f

Browse files
authored
refactor: unify import component logic and remove deprecated hooks (#1488)
## Description Refactored component import functionality by removing deprecated hooks and consolidating the logic in the ComponentLibraryProvider. This change simplifies the component import flow by: 1. Removing `useComponentUploader` and `useImportComponent` hooks 2. Eliminating the `ComponentDuplicateDialog` from FlowCanvas 3. Moving file reading functionality to a dedicated utility function 4. Streamlining the component import process through the ComponentLibraryProvider ## Type of Change - [x] Cleanup/Refactor - [x] Improvement ## Checklist - [x] I have tested this does not break current pipelines / runs functionality - [ ] I have tested the changes on staging ## Test Instructions 1. [Screen Recording 2025-12-06 at 12.33.55 PM.mov <span class="graphite__hidden">(uploaded via Graphite)</span> <img class="graphite__hidden" src="https://app.graphite.com/user-attachments/thumbnails/826c55e6-636b-4532-b2dd-9b343cd11129.mov" />](https://app.graphite.com/user-attachments/video/826c55e6-636b-4532-b2dd-9b343cd11129.mov) Test importing components by dragging and dropping YAML files onto the canvas 2. Verify that component import works through the sidebar import functionality 3. Confirm that duplicate component detection still works properly 4. Test that all 3 major ways of importing work exactly same 1. Test import via "Add component" from "Used in Pipelines" 2. Test import via "Drop on canvas" 3. Test import from "Import Dialog" 5. Ensure Drop File on Canvas works in all ways - no regression 6. Ensure Drag-drop components from component library works exactly same, no regression ## Additional Comments This change reduces code duplication and centralizes component import logic in the ComponentLibraryProvider, making the codebase more maintainable.
1 parent c8f15b3 commit 9f2fe2f

File tree

10 files changed

+141
-383
lines changed

10 files changed

+141
-383
lines changed

src/components/shared/ReactFlow/FlowCanvas/FlowCanvas.tsx

Lines changed: 44 additions & 71 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,6 @@ import { useEffect, useRef, useState } from "react";
2222
import { ConfirmationDialog } from "@/components/shared/Dialogs";
2323
import { BlockStack } from "@/components/ui/layout";
2424
import useComponentSpecToEdges from "@/hooks/useComponentSpecToEdges";
25-
import useComponentUploader from "@/hooks/useComponentUploader";
2625
import useConfirmationDialog from "@/hooks/useConfirmationDialog";
2726
import { useCopyPaste } from "@/hooks/useCopyPaste";
2827
import { useGhostNode } from "@/hooks/useGhostNode";
@@ -31,6 +30,7 @@ import { useNodeCallbacks } from "@/hooks/useNodeCallbacks";
3130
import { useSubgraphKeyboardNavigation } from "@/hooks/useSubgraphKeyboardNavigation";
3231
import useToastNotification from "@/hooks/useToastNotification";
3332
import { cn } from "@/lib/utils";
33+
import { useComponentLibrary } from "@/providers/ComponentLibraryProvider";
3434
import { useComponentSpec } from "@/providers/ComponentSpecProvider";
3535
import { useContextPanel } from "@/providers/ContextPanelProvider";
3636
import { hydrateComponentReference } from "@/services/componentService";
@@ -40,15 +40,14 @@ import {
4040
isNotMaterializedComponentReference,
4141
type TaskSpec,
4242
} from "@/utils/componentSpec";
43-
import { loadComponentAsRefFromText } from "@/utils/componentStore";
43+
import { readTextFromFile } from "@/utils/dom";
4444
import { deselectAllNodes } from "@/utils/flowUtils";
4545
import createNodesFromComponentSpec from "@/utils/nodes/createNodesFromComponentSpec";
4646
import {
4747
getSubgraphComponentSpec,
4848
updateSubgraphSpec,
4949
} from "@/utils/subgraphUtils";
5050

51-
import ComponentDuplicateDialog from "../../Dialogs/ComponentDuplicateDialog";
5251
import { useBetaFlagValue } from "../../Settings/useBetaFlags";
5352
import { useNodesOverlay } from "../NodesOverlay/NodesOverlayProvider";
5453
import { getBulkUpdateConfirmationDetails } from "./ConfirmationDialogs/BulkUpdateConfirmationDialog";
@@ -169,6 +168,8 @@ const FlowCanvas = ({
169168
const [shiftKeyPressed, setShiftKeyPressed] = useState(false);
170169
const [metaKeyPressed, setMetaKeyPressed] = useState(false);
171170

171+
const { addToComponentLibrary } = useComponentLibrary();
172+
172173
const handleKeyDown = (event: KeyboardEvent) => {
173174
const target = event.target as HTMLElement;
174175
const isInputFocused =
@@ -465,67 +466,7 @@ const FlowCanvas = ({
465466
};
466467
}, [isConnecting, reactFlowInstance]);
467468

468-
const {
469-
handleDrop,
470-
existingAndNewComponent,
471-
handleCancelUpload,
472-
handleImportComponent,
473-
} = useComponentUploader(readOnly, {
474-
onImportSuccess: async (
475-
content: string,
476-
dropEvent?: DragEvent<HTMLDivElement>,
477-
) => {
478-
if (readOnly) return;
479-
480-
try {
481-
// Parse the imported YAML to get the component spec
482-
const componentRef = await loadComponentAsRefFromText(content);
483-
484-
if (!componentRef.spec) {
485-
console.error("Failed to parse component spec from imported content");
486-
return;
487-
}
488-
489-
let position;
490-
491-
if (dropEvent && reactFlowInstance) {
492-
// Use the drop position if available
493-
position = getPositionFromEvent(dropEvent, reactFlowInstance);
494-
} else {
495-
// Fallback to center of the canvas viewport
496-
const { domNode } = store.getState();
497-
const boundingRect = domNode?.getBoundingClientRect();
498-
499-
if (boundingRect && reactFlowInstance) {
500-
position = reactFlowInstance.screenToFlowPosition({
501-
x: boundingRect.x + boundingRect.width / 2,
502-
y: boundingRect.y + boundingRect.height / 2,
503-
});
504-
}
505-
}
506-
507-
if (position) {
508-
const taskSpec: TaskSpec = {
509-
annotations: {},
510-
componentRef: { ...componentRef },
511-
};
512-
const { spec: newComponentSpec } = addTask(
513-
"task",
514-
taskSpec,
515-
position,
516-
componentSpec,
517-
);
518-
519-
setComponentSpec(newComponentSpec);
520-
}
521-
} catch (error) {
522-
console.error("Failed to add imported component to canvas:", error);
523-
notify("Failed to add component to canvas", "error");
524-
}
525-
},
526-
});
527-
528-
const onDragOver = (event: DragEvent<HTMLDivElement>) => {
469+
const onDragOver = async (event: DragEvent<HTMLDivElement>) => {
529470
event.preventDefault();
530471

531472
// Check if we're dragging files
@@ -564,10 +505,48 @@ const FlowCanvas = ({
564505

565506
const onDrop = async (event: DragEvent<HTMLDivElement>) => {
566507
event.preventDefault();
508+
if (readOnly) return;
567509

568510
// Handle file drops
569511
if (event.dataTransfer.files.length > 0) {
570-
handleDrop(event);
512+
try {
513+
// Parse the imported YAML to get the component spec
514+
const content = await readTextFromFile(event.dataTransfer.files[0]);
515+
const hydratedComponentRef = await hydrateComponentReference({
516+
text: content,
517+
});
518+
519+
if (!hydratedComponentRef) {
520+
notify(
521+
"Failed to parse component spec from imported content",
522+
"error",
523+
);
524+
return;
525+
}
526+
527+
const result = await addToComponentLibrary(hydratedComponentRef);
528+
529+
if (result && reactFlowInstance) {
530+
// Use the drop position if available
531+
const position = getPositionFromEvent(event, reactFlowInstance);
532+
const taskSpec: TaskSpec = {
533+
annotations: {},
534+
componentRef: hydratedComponentRef,
535+
};
536+
const { spec: newComponentSpec } = addTask(
537+
"task",
538+
taskSpec,
539+
position,
540+
componentSpec,
541+
);
542+
543+
setComponentSpec(newComponentSpec);
544+
}
545+
} catch (error) {
546+
console.error("Failed to add imported component to canvas:", error);
547+
notify("Failed to add component to canvas", "error");
548+
}
549+
571550
return;
572551
}
573552

@@ -1030,12 +1009,6 @@ const FlowCanvas = ({
10301009
currentSubgraphSpec={currentSubgraphSpec}
10311010
onCreateSubgraph={handleCreateSubgraph}
10321011
/>
1033-
<ComponentDuplicateDialog
1034-
existingComponent={existingAndNewComponent?.existingComponent}
1035-
newComponent={existingAndNewComponent?.newComponent}
1036-
setClose={handleCancelUpload}
1037-
handleImportComponent={handleImportComponent}
1038-
/>
10391012
</BlockStack>
10401013
);
10411014
};

src/components/shared/ReactFlow/FlowSidebar/components/ImportComponent.tsx

Lines changed: 5 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -31,10 +31,8 @@ import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs";
3131
import useToastNotification from "@/hooks/useToastNotification";
3232
import { cn } from "@/lib/utils";
3333
import { useComponentLibrary } from "@/providers/ComponentLibraryProvider";
34-
import {
35-
getStringFromData,
36-
hydrateComponentReference,
37-
} from "@/services/componentService";
34+
import { hydrateComponentReference } from "@/services/componentService";
35+
import { getStringFromData } from "@/utils/string";
3836

3937
enum TabType {
4038
URL = "URL",
@@ -121,33 +119,15 @@ const ImportComponent = ({
121119
throw new Error("Failed to hydrate component");
122120
}
123121

124-
const abortController = new AbortController();
125-
126-
return await Promise.all([
127-
Promise.race([
128-
createPromiseFromDomEvent(
129-
window,
130-
"tangle.library.componentAdded",
131-
abortController.signal,
132-
),
133-
createPromiseFromDomEvent(
134-
window,
135-
"tangle.library.duplicateDialogClosed",
136-
abortController.signal,
137-
),
138-
]),
139-
addToComponentLibrary(hydratedComponent),
140-
]).finally(() => {
141-
abortController.abort();
142-
});
122+
return await addToComponentLibrary(hydratedComponent);
143123
},
144-
onSuccess: async ([result, _]) => {
124+
onSuccess: async (result) => {
145125
setIsOpen(false);
146126
setUrl("");
147127
setSelectedFile(null);
148128
setSelectedFileName("");
149129

150-
if (result instanceof CustomEvent && result.detail?.component) {
130+
if (result) {
151131
notify("Component imported successfully", "success");
152132
}
153133
},
@@ -351,23 +331,4 @@ const ImportComponent = ({
351331
);
352332
};
353333

354-
function createPromiseFromDomEvent(
355-
eventTarget: EventTarget,
356-
eventName: string,
357-
abortSignal?: AbortSignal,
358-
) {
359-
return new Promise<Event>((resolve) => {
360-
const handleEvent = (event: Event) => {
361-
eventTarget.removeEventListener(eventName, handleEvent);
362-
363-
resolve(event);
364-
};
365-
366-
eventTarget.addEventListener(eventName, handleEvent, {
367-
once: true,
368-
signal: abortSignal,
369-
});
370-
});
371-
}
372-
373334
export default ImportComponent;

src/hooks/useComponentUploader.tsx

Lines changed: 0 additions & 129 deletions
This file was deleted.

0 commit comments

Comments
 (0)