Skip to content

Commit 3d0fb6c

Browse files
committed
Use dnd-kit to handle app & file drag & drop, improve mobile drag interaction
1 parent 5842abf commit 3d0fb6c

File tree

14 files changed

+821
-485
lines changed

14 files changed

+821
-485
lines changed

package-lock.json

Lines changed: 50 additions & 10 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

web/app/(main-layout)/layout.tsx

Lines changed: 14 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,13 @@
11
import Nav from "@/components/interface/navigation/nav";
22
import CapacitorProvider from "@/components/providers/capacitor-provider";
3+
import DndProvider from "@/components/providers/dnd-provider";
34
import EditorContextProvider from "@/components/providers/editor-context-provider";
45
import InterModuleCommunicationProvider from "@/components/providers/imc-provider";
56
import PlatformAssistantProvider from "@/components/providers/platform-assistant-provider";
67
import RemoteModuleProvider from "@/components/providers/remote-module-provider";
78
import WrappedHeroUIProvider from "@/components/providers/wrapped-hero-ui-provider";
89
import { Analytics } from "@vercel/analytics/next";
10+
import { ReactFlowProvider } from "@xyflow/react";
911
import "material-icons/iconfont/material-icons.css";
1012
import type { Metadata } from "next";
1113
import { Suspense } from "react";
@@ -31,14 +33,18 @@ export default function RootLayout({
3133
<EditorContextProvider>
3234
<CapacitorProvider>
3335
<InterModuleCommunicationProvider>
34-
<RemoteModuleProvider isPreventingCSS={true}>
35-
<Toaster />
36-
<Nav>
37-
<PlatformAssistantProvider>
38-
{children}
39-
</PlatformAssistantProvider>
40-
</Nav>
41-
</RemoteModuleProvider>
36+
<ReactFlowProvider>
37+
<DndProvider>
38+
<RemoteModuleProvider isPreventingCSS={true}>
39+
<Toaster />
40+
<Nav>
41+
<PlatformAssistantProvider>
42+
{children}
43+
</PlatformAssistantProvider>
44+
</Nav>
45+
</RemoteModuleProvider>
46+
</DndProvider>
47+
</ReactFlowProvider>
4248
</InterModuleCommunicationProvider>
4349
</CapacitorProvider>
4450
</EditorContextProvider>

web/components/app-loaders/sandbox-app-loader.tsx

Lines changed: 14 additions & 55 deletions
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,11 @@ import BaseAppLoader from "@/components/app-loaders/base-app-loader";
22
import Loading from "@/components/interface/status-screens/loading";
33
import { EditorContext } from "@/components/providers/editor-context-provider";
44
import { IMCContext } from "@/components/providers/imc-provider";
5-
import { DragEventTypeEnum, PlatformEnum } from "@/lib/enums";
5+
import { PlatformEnum } from "@/lib/enums";
66
import { usePlatformApi } from "@/lib/hooks/use-platform-api";
77
import { getPlatform } from "@/lib/platform-api/platform-checker";
8-
import { ExtensionApp, FileDragData } from "@/lib/types";
9-
import { addToast } from "@heroui/react";
8+
import { ExtensionApp } from "@/lib/types";
9+
import { useDroppable } from "@dnd-kit/core";
1010
import {
1111
ConnectionListener,
1212
IMCMessage,
@@ -32,6 +32,16 @@ export default function SandboxAppLoader({
3232
const editorContext = useContext(EditorContext);
3333
const imcContext = useContext(IMCContext);
3434

35+
const { resolvedTheme } = useTheme();
36+
const { platformApi } = usePlatformApi();
37+
38+
const { setNodeRef } = useDroppable({
39+
id: "app-node-view-" + viewModel.viewId,
40+
data: {
41+
viewId: viewModel.viewId,
42+
},
43+
});
44+
3545
const [currentExtension, setCurrentExtension] = useState<
3646
ExtensionApp | undefined
3747
>();
@@ -48,9 +58,6 @@ export default function SandboxAppLoader({
4858
// const [isConnected, setIsConnected] = useState<boolean>(false);
4959
const [isInitialized, setIsInitialized] = useState<boolean>(false);
5060

51-
const { resolvedTheme } = useTheme();
52-
const { platformApi } = usePlatformApi();
53-
5461
// Update view Id when the view model changes
5562
useEffect(() => {
5663
// If the view Id changes (e.g. when switching file but not extension),
@@ -137,13 +144,6 @@ export default function SandboxAppLoader({
137144
};
138145
}, []);
139146

140-
useEffect(() => {
141-
console.log(
142-
"Is dragging over canvas: ",
143-
editorContext?.editorStates.isDraggingOverCanvas,
144-
);
145-
}, [editorContext?.editorStates.isDraggingOverCanvas]);
146-
147147
// Set is loading extension to true when current extension changes
148148
useEffect(() => {
149149
if (currentExtension) {
@@ -316,48 +316,7 @@ export default function SandboxAppLoader({
316316
return (
317317
<div
318318
className="relative h-full w-full"
319-
onDragOver={(e) => {
320-
e.stopPropagation();
321-
const types = e.dataTransfer.types;
322-
if (
323-
types.includes(`application/${DragEventTypeEnum.File.toLowerCase()}`)
324-
) {
325-
e.preventDefault(); // allow drop
326-
e.dataTransfer.dropEffect = "move";
327-
} else {
328-
e.dataTransfer.dropEffect = "none";
329-
}
330-
}}
331-
onDrop={async (e) => {
332-
const dataText = e.dataTransfer.getData(
333-
`application/${DragEventTypeEnum.File.toLowerCase()}`,
334-
);
335-
if (!dataText) {
336-
return;
337-
}
338-
console.log("Dropped item:", dataText);
339-
try {
340-
const data = JSON.parse(dataText) as FileDragData;
341-
342-
e.preventDefault();
343-
const uri = data.uri;
344-
345-
// Send uri to app view
346-
await imcContext?.polyIMC?.sendMessage(
347-
viewModel.viewId,
348-
IMCMessageTypeEnum.EditorAppReceiveFileUri,
349-
{
350-
uri,
351-
},
352-
);
353-
} catch (error) {
354-
addToast({
355-
title: "Failed to open file",
356-
description: "The dropped file data is invalid.",
357-
color: "danger",
358-
});
359-
}
360-
}}
319+
ref={setNodeRef}
361320
>
362321
{isLookingForExtension ? (
363322
<div className="bg-content1 h-full w-full">
Lines changed: 73 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -1,51 +1,95 @@
11
import AppPreviewCard from "@/components/marketplace/app/app-preview-card";
2+
import { DraggableItem } from "@/components/misc/draggable-item";
23
import { EditorContext } from "@/components/providers/editor-context-provider";
3-
import { DragEventTypeEnum } from "@/lib/enums";
44
import { useScreenSize } from "@/lib/hooks/use-screen-size";
55
import { useTabViewManager } from "@/lib/hooks/use-tab-view-manager";
6-
import { AppDragData, AppViewConfig } from "@/lib/types";
6+
import {
7+
AppDragData,
8+
AppViewConfig,
9+
DragData,
10+
ExtensionApp,
11+
} from "@/lib/types";
12+
import { useDraggable } from "@dnd-kit/core";
713
import { Button } from "@heroui/react";
8-
import { useContext } from "react";
14+
import { useContext, useEffect, useState } from "react";
915
import { v4 } from "uuid";
1016

1117
export default function AppExplorer() {
1218
const editorContext = useContext(EditorContext);
1319

20+
const extensions = editorContext?.persistSettings?.extensions ?? [];
21+
22+
const previews = extensions.map((ext, index) => (
23+
<DraggableAppPreviewCard key={index} ext={ext} />
24+
));
25+
26+
return (
27+
<div className="grid h-full grid-rows-[max-content_auto_max-content] gap-y-2">
28+
<p className="text-center">Tap or drag an extension to open it.</p>
29+
30+
<div className="grid h-fit max-h-full w-full grid-cols-2 gap-2 overflow-x-hidden overflow-y-auto px-4">
31+
{previews}
32+
</div>
33+
<div className="flex flex-col gap-y-1 px-4 pb-2">
34+
<Button
35+
color="secondary"
36+
onPress={() => {
37+
editorContext?.setEditorStates((prev) => ({
38+
...prev,
39+
isMarketplaceOpen: true,
40+
}));
41+
}}
42+
>
43+
Explorer Community Workflows/Apps
44+
</Button>
45+
</div>
46+
</div>
47+
);
48+
}
49+
50+
function DraggableAppPreviewCard({ ext }: { ext: ExtensionApp }) {
51+
const editorContext = useContext(EditorContext);
52+
1453
const { createAppViewInCanvasView } = useTabViewManager();
1554
const { isLandscape } = useScreenSize();
55+
const { setNodeRef, listeners, isDragging } = useDraggable({
56+
id: `draggable-app-${ext.config.id}`,
57+
data: {
58+
type: "app",
59+
data: {
60+
app: ext,
61+
} as AppDragData,
62+
} as DragData,
63+
});
1664

17-
const extensions = editorContext?.persistSettings?.extensions ?? [];
65+
const [isDragFinished, setIsDragFinished] = useState(false);
1866

19-
const previews = extensions.map((ext, index) => (
20-
<div
21-
key={index}
22-
className="w-full h-fit"
23-
draggable
24-
onDragStart={(e) => {
25-
editorContext?.setEditorStates((prev) => ({
26-
...prev,
27-
isDraggingOverCanvas: true,
28-
}));
29-
e.dataTransfer.setData(
30-
`application/${DragEventTypeEnum.App.toLowerCase()}`,
31-
JSON.stringify({
32-
app: ext,
33-
} as AppDragData),
34-
);
35-
}}
36-
onDragEnd={() => {
37-
editorContext?.setEditorStates((prev) => ({
38-
...prev,
39-
isDraggingOverCanvas: false,
40-
}));
41-
}}
67+
useEffect(() => {
68+
if (isDragging) {
69+
setIsDragFinished(false);
70+
} else {
71+
// Delay 200ms and then set isDragFinished to true,
72+
// so the app preview button is not triggered via a press event.
73+
setTimeout(() => {
74+
setIsDragFinished(true);
75+
}, 200);
76+
}
77+
}, [isDragging]);
78+
79+
return (
80+
<DraggableItem
81+
className="h-fit w-full"
82+
ref={setNodeRef}
83+
listeners={listeners}
4284
>
4385
<AppPreviewCard
4486
extension={ext}
4587
isShowInstalledChip={false}
4688
isShowUninstallButton={false}
4789
isShowUseButton
4890
isShowCompatibleChip={false}
91+
isShowContextMenu={false}
92+
isDisableButtonPress={!isDragFinished}
4993
onPress={(ext) => {
5094
const config: AppViewConfig = {
5195
app: ext.config.id,
@@ -62,30 +106,8 @@ export default function AppExplorer() {
62106
}));
63107
}
64108
}}
109+
listeners={listeners}
65110
/>
66-
</div>
67-
));
68-
69-
return (
70-
<div className="h-full grid grid-rows-[max-content_auto_max-content] gap-y-2">
71-
<p className="text-center">Tap or drag an extension to open it.</p>
72-
73-
<div className="grid grid-cols-2 gap-2 h-fit w-full max-h-full overflow-y-auto overflow-x-hidden px-4">
74-
{previews}
75-
</div>
76-
<div className="flex flex-col gap-y-1 px-4 pb-2">
77-
<Button
78-
color="secondary"
79-
onPress={() => {
80-
editorContext?.setEditorStates((prev) => ({
81-
...prev,
82-
isMarketplaceOpen: true,
83-
}));
84-
}}
85-
>
86-
Explorer Community Workflows/Apps
87-
</Button>
88-
</div>
89-
</div>
111+
</DraggableItem>
90112
);
91113
}

0 commit comments

Comments
 (0)