Skip to content

Commit b9ef7dd

Browse files
committed
Add drag & drop to open app
1 parent d017a87 commit b9ef7dd

File tree

8 files changed

+136
-99
lines changed

8 files changed

+136
-99
lines changed

web/components/explorer/app/app-explorer.tsx

Lines changed: 22 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import ExtensionPreview from "@/components/extension/extension-preview";
22
import { EditorContext } from "@/components/providers/editor-context-provider";
33
import { useTabViewManager } from "@/lib/hooks/use-tab-view-manager";
44
import { AppViewConfig } from "@/lib/types";
5+
import { Button } from "@heroui/react";
56
import { useContext } from "react";
67
import { v4 } from "uuid";
78

@@ -13,7 +14,14 @@ export default function AppExplorer() {
1314
const extensions = editorContext?.persistSettings?.extensions ?? [];
1415

1516
const previews = extensions.map((ext, index) => (
16-
<div key={index} className="w-full h-fit">
17+
<div
18+
key={index}
19+
className="w-full h-fit"
20+
draggable
21+
onDragStart={(e) => {
22+
e.dataTransfer.setData("text/plain", JSON.stringify(ext));
23+
}}
24+
>
1725
<ExtensionPreview
1826
extension={ext}
1927
isShowInstalledChip={false}
@@ -34,12 +42,23 @@ export default function AppExplorer() {
3442
));
3543

3644
return (
37-
<div className="p-4 h-full">
45+
<div className="h-full grid grid-rows-[max-content_auto_max-content] gap-y-2">
3846
<p className="text-center">Tap or drag an extension to open it.</p>
3947

40-
<div className="mt-4 grid grid-cols-2 gap-2 h-full w-full overflow-y-auto">
48+
<div className="grid grid-cols-2 gap-2 h-full w-full overflow-y-auto overflow-x-hidden px-4">
4149
{previews}
4250
</div>
51+
<Button
52+
className="mx-4 mb-2"
53+
onPress={() => {
54+
editorContext?.setEditorStates((prev) => ({
55+
...prev,
56+
isMarketplaceOpen: true,
57+
}));
58+
}}
59+
>
60+
Browse More Apps
61+
</Button>
4362
</div>
4463
);
4564
}

web/components/explorer/file-system/fs-explorer.tsx

Lines changed: 1 addition & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -102,23 +102,7 @@ export default function FileSystemExplorer({
102102

103103
// Browse inside a project
104104
return (
105-
<div className="relative h-full w-full">
106-
{/* <div className="flex w-full justify-center">
107-
<div className="w-fit">
108-
<Tabs
109-
tabItems={tabItems}
110-
selectedItem={tabItems[selectedTabIndex]}
111-
setSelectedItem={(item) => {
112-
const index = tabItems.findIndex(
113-
(tab) => tab.name === item?.name,
114-
);
115-
setSelectedTabIndex(index !== -1 ? index : 0);
116-
}}
117-
isClosable={false}
118-
/>
119-
</div>
120-
</div> */}
121-
105+
<div className="relative h-full w-full px-2 py-1">
122106
<div className="flex h-full w-full flex-col space-y-2">
123107
<div className="bg-default text-default-foreground flex h-10 w-full items-center rounded-xl px-3">
124108
<div className="flex w-full">

web/components/interface/editor-toolbar.tsx

Lines changed: 16 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,15 @@
11
"use client";
22

3-
import { Button, Divider, Tooltip } from "@heroui/react";
4-
import { useContext, useState } from "react";
53
import Icon from "@/components/misc/icon";
64
import AppSettingsModal from "@/components/modals/app-settings-modal";
5+
import usePlatformAIAssistant from "@/lib/hooks/use-platform-ai-assistant";
6+
import useRecorder from "@/lib/hooks/use-recorder";
7+
import { Button, Divider, Tooltip } from "@heroui/react";
78
import { AnimatePresence, motion } from "framer-motion";
8-
import { EditorContext } from "../providers/editor-context-provider";
9+
import { useContext, useState } from "react";
910
import AgentConfigModal from "../modals/agent-config-modal";
1011
import ExtensionMarketplaceModal from "../modals/extension-marketplace-modal";
11-
import usePlatformAIAssistant from "@/lib/hooks/use-platform-ai-assistant";
12-
import useRecorder from "@/lib/hooks/use-recorder";
12+
import { EditorContext } from "../providers/editor-context-provider";
1313

1414
export default function EditorToolbar() {
1515
const editorContext = useContext(EditorContext);
@@ -18,7 +18,6 @@ export default function EditorToolbar() {
1818
const { isRecording, record } = useRecorder();
1919

2020
const [isAgentListModalOpen, setIsAgentListModalOpen] = useState(false);
21-
const [isExtensionModalOpen, setIsExtensionModalOpen] = useState(false);
2221
const [isAppSettingsModalOpen, setAppIsSettingsModalOpen] = useState(false);
2322

2423
function setIsOpen(val: boolean) {
@@ -146,15 +145,23 @@ export default function EditorToolbar() {
146145
isIconOnly
147146
className="text-default-foreground h-8 w-8 min-w-8 px-1 py-1"
148147
onPress={() => {
149-
setIsExtensionModalOpen(true);
148+
editorContext?.setEditorStates((prev) => ({
149+
...prev,
150+
isMarketplaceOpen: true,
151+
}));
150152
}}
151153
>
152154
<Icon name="dashboard_customize" variant="outlined" />
153155
</Button>
154156
</Tooltip>
155157
<ExtensionMarketplaceModal
156-
isOpen={isExtensionModalOpen}
157-
setIsOpen={setIsExtensionModalOpen}
158+
isOpen={editorContext?.editorStates.isMarketplaceOpen || false}
159+
setIsOpen={(isOpen) =>
160+
editorContext?.setEditorStates((prev) => ({
161+
...prev,
162+
isMarketplaceOpen: isOpen,
163+
}))
164+
}
158165
/>
159166

160167
{/* <SettingPopover /> */}

web/components/interface/navigation/nav-side-menu.tsx

Lines changed: 9 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ export default function NavSideMenu({
2424
<AnimatePresence>
2525
{isMenuOpen && (
2626
<MenuPanel>
27-
<div className="h-full w-full min-[768px]:py-2 min-[768px]:pr-1 min-[768px]:pl-2">
27+
<div className="h-full w-full min-[768px]:py-2 min-[768px]:pr-1 min-[768px]:pl-2 overflow-y-hidden">
2828
<div className="bg-content2 flex h-full w-full flex-col overflow-hidden shadow-md min-[768px]:rounded-xl">
2929
<div className="flex w-full items-center px-2 py-1 max-[768px]:justify-end">
3030
<Button
@@ -170,7 +170,7 @@ function PanelContent({
170170
}
171171

172172
return (
173-
<div className="relative h-full w-full px-4">
173+
<div className="relative h-full w-full grid grid-rows-[max-content_auto] overflow-y-hidden">
174174
<div className="flex w-full justify-center">
175175
<div className="w-fit">
176176
<Tabs
@@ -186,11 +186,13 @@ function PanelContent({
186186
/>
187187
</div>
188188
</div>
189-
{tabItems[selectedTabIndex]?.name === "Apps" ? (
190-
<AppExplorer />
191-
) : (
192-
<FileSystemExplorer setIsMenuOpen={setIsMenuOpen} />
193-
)}
189+
<div className="h-full w-full overflow-y-hidden">
190+
{tabItems[selectedTabIndex]?.name === "Apps" ? (
191+
<AppExplorer />
192+
) : (
193+
<FileSystemExplorer setIsMenuOpen={setIsMenuOpen} />
194+
)}
195+
</div>
194196
</div>
195197
);
196198
}

web/components/interface/navigation/nav.tsx

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,23 +1,23 @@
11
"use client";
22

3+
import { getPlatform } from "@/lib/platform-api/platform-checker";
4+
import { PlatformEnum } from "@/lib/types";
5+
import { useTheme } from "next-themes";
36
import { useContext, useEffect, useState } from "react";
47
import PasswordModal from "../../modals/password-modal";
5-
import { useTheme } from "next-themes";
6-
import NavSideMenu from "./nav-side-menu";
78
import { EditorContext } from "../../providers/editor-context-provider";
8-
import { getPlatform } from "@/lib/platform-api/platform-checker";
9-
import { PlatformEnum } from "@/lib/types";
109
import Loading from "../loading";
10+
import NavSideMenu from "./nav-side-menu";
1111

12-
import LoginModal from "../../modals/login-modal";
12+
import useAndroidManageStorageNotification from "@/lib/hooks/use-android-manage-storage-notification";
1313
import { useAuth } from "@/lib/hooks/use-auth";
14-
import WorkspaceSettingsModal from "../../modals/workspace-settings-model";
1514
import { useWorkspace } from "@/lib/hooks/use-workspace";
16-
import useAndroidManageStorageNotification from "@/lib/hooks/use-android-manage-storage-notification";
1715
import { SafeArea } from "@capacitor-community/safe-area";
16+
import AppInfoModal from "../../modals/app-info-modal";
17+
import LoginModal from "../../modals/login-modal";
1818
import SharingModal from "../../modals/sharing-modal";
19+
import WorkspaceSettingsModal from "../../modals/workspace-settings-model";
1920
import NavTopBar from "./nav-top-bar";
20-
import AppInfoModal from "../../modals/app-info-modal";
2121

2222
export default function Nav({ children }: { children: React.ReactNode }) {
2323
const [mounted, setMounted] = useState(false);
@@ -121,7 +121,7 @@ export default function Nav({ children }: { children: React.ReactNode }) {
121121
<AppInfoModal />
122122

123123
<div className="grid h-full w-full grid-cols-[max-content_auto]">
124-
<div className="h-full w-full">
124+
<div className="h-full w-full overflow-y-hidden">
125125
{isShowNavbar && (
126126
<NavSideMenu
127127
isMenuOpen={isMenuOpen}

web/components/views/view-area.tsx

Lines changed: 74 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { useTabViewManager } from "@/lib/hooks/use-tab-view-manager";
2-
import { AppViewConfig, CanvasViewConfig } from "@/lib/types";
2+
import { AppViewConfig, CanvasViewConfig, Extension } from "@/lib/types";
33
import { ViewModeEnum } from "@pulse-editor/shared-utils";
44
import { ReactFlowProvider } from "@xyflow/react";
55
import { useSearchParams } from "next/navigation";
@@ -27,6 +27,7 @@ export default function ViewArea() {
2727
selectTab,
2828
closeTabView,
2929
createTabView,
30+
createAppViewInCanvasView,
3031
deleteAppViewInCanvasView,
3132
activeTabView,
3233
} = useTabViewManager();
@@ -130,61 +131,83 @@ export default function ViewArea() {
130131
console.log("Tab views changed:", tabViews);
131132
}, [tabViews]);
132133

133-
if (tabViews.length === 0) {
134-
return <HomeView createNewCanvas={createNewCanvas} />;
135-
}
136-
137-
if (tabIndex < 0 || tabIndex >= tabViews.length) {
138-
return <div>No view selected</div>;
139-
}
140-
141134
return (
142135
<div
143-
className="grid h-full w-full grid-rows-1 gap-y-0.5 px-2 pt-17 data-[is-show-tabs=true]:grid-rows-[max-content_auto]"
144-
data-is-show-tabs={isShowTabs}
136+
className="w-full h-full"
137+
onDragOver={(e) => {
138+
e.preventDefault();
139+
}}
140+
onDrop={(e) => {
141+
e.preventDefault();
142+
const data = e.dataTransfer.getData("text/plain");
143+
console.log("Dropped item:", data);
144+
const ext: Extension = JSON.parse(data);
145+
const config: AppViewConfig = {
146+
app: ext.config.id,
147+
viewId: v4(),
148+
recommendedHeight: ext.config.recommendedHeight,
149+
recommendedWidth: ext.config.recommendedWidth,
150+
};
151+
createAppViewInCanvasView(config);
152+
}}
145153
>
146-
{isShowTabs && (
147-
<div className="border-default-border bg-content2 w-full rounded-lg py-0.5">
148-
<Tabs
149-
tabItems={tabItems}
150-
selectedItem={tabItems[tabIndex] ? tabItems[tabIndex] : undefined}
151-
setSelectedItem={(item) => {
152-
const index = tabItems.findIndex(
153-
(tab) => tab.name === item?.name,
154-
);
155-
selectTab(index !== -1 ? index : 0);
156-
}}
157-
isShowPagination={true}
158-
onTabClose={(item) => {
159-
const index = tabItems.findIndex(
160-
(tab) => tab.name === item?.name,
161-
);
162-
if (index !== -1) {
163-
closeTabView(tabViews[index]);
164-
}
165-
}}
166-
/>
167-
</div>
168-
)}
169-
<div className="h-full w-full">
170-
{tabViews.map((tabView, idx) => (
171-
<div
172-
key={tabView.config.viewId}
173-
data-is-active={idx === tabIndex}
174-
className="hidden h-full w-full data-[is-active=true]:block"
175-
>
176-
{tabView.type === ViewModeEnum.App ? (
177-
<MemoizedStandaloneAppView
178-
config={tabView.config as AppViewConfig}
154+
{tabViews.length === 0 ? (
155+
<HomeView createNewCanvas={createNewCanvas} />
156+
) : tabIndex < 0 || tabIndex >= tabViews.length ? (
157+
<div>No view selected</div>
158+
) : (
159+
<div
160+
className="grid h-full w-full grid-rows-1 gap-y-0.5 px-2 pt-17 data-[is-show-tabs=true]:grid-rows-[max-content_auto]"
161+
data-is-show-tabs={isShowTabs}
162+
>
163+
{isShowTabs && (
164+
<div className="border-default-border bg-content2 w-full rounded-lg py-0.5">
165+
<Tabs
166+
tabItems={tabItems}
167+
selectedItem={
168+
tabItems[tabIndex] ? tabItems[tabIndex] : undefined
169+
}
170+
setSelectedItem={(item) => {
171+
const index = tabItems.findIndex(
172+
(tab) => tab.name === item?.name,
173+
);
174+
selectTab(index !== -1 ? index : 0);
175+
}}
176+
isShowPagination={true}
177+
onTabClose={(item) => {
178+
const index = tabItems.findIndex(
179+
(tab) => tab.name === item?.name,
180+
);
181+
if (index !== -1) {
182+
closeTabView(tabViews[index]);
183+
}
184+
}}
179185
/>
180-
) : tabView.type === ViewModeEnum.Canvas ? (
181-
<MemoizedCanvasView config={tabView.config as CanvasViewConfig} />
182-
) : (
183-
<div>Unknown view type</div>
184-
)}
186+
</div>
187+
)}
188+
<div className="h-full w-full">
189+
{tabViews.map((tabView, idx) => (
190+
<div
191+
key={tabView.config.viewId}
192+
data-is-active={idx === tabIndex}
193+
className="hidden h-full w-full data-[is-active=true]:block"
194+
>
195+
{tabView.type === ViewModeEnum.App ? (
196+
<MemoizedStandaloneAppView
197+
config={tabView.config as AppViewConfig}
198+
/>
199+
) : tabView.type === ViewModeEnum.Canvas ? (
200+
<MemoizedCanvasView
201+
config={tabView.config as CanvasViewConfig}
202+
/>
203+
) : (
204+
<div>Unknown view type</div>
205+
)}
206+
</div>
207+
))}
185208
</div>
186-
))}
187-
</div>
209+
</div>
210+
)}
188211
</div>
189212
);
190213
}

web/lib/hooks/use-tab-view-manager.ts

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -277,11 +277,12 @@ export function useTabViewManager() {
277277
currentTab = await createTabView(ViewModeEnum.Canvas, {
278278
viewId: `canvas-${v4()}`,
279279
} as CanvasViewConfig);
280+
} else if (currentTab?.type !== ViewModeEnum.Canvas) {
281+
currentTab = await createTabView(ViewModeEnum.Canvas, {
282+
viewId: `canvas-${v4()}`,
283+
} as CanvasViewConfig);
280284
}
281285

282-
if (currentTab?.type !== ViewModeEnum.Canvas) {
283-
throw new Error("Current tab is not a canvas");
284-
}
285286
const newCanvasConfig: CanvasViewConfig = {
286287
...currentTab.config,
287288
nodes: [

web/lib/types.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -83,6 +83,7 @@ export type EditorStates = {
8383

8484
// Side menu panel
8585
isSideMenuOpen?: boolean;
86+
isMarketplaceOpen?: boolean;
8687
};
8788

8889
/**

0 commit comments

Comments
 (0)