diff --git a/web/components/interface/navigation/nav-side-menu.tsx b/web/components/interface/navigation/nav-side-menu.tsx index 8872183..213c139 100644 --- a/web/components/interface/navigation/nav-side-menu.tsx +++ b/web/components/interface/navigation/nav-side-menu.tsx @@ -113,6 +113,11 @@ function PanelContent({ useState(false); const tabItems: TabItem[] = [ + { + name: "Projects", + description: "List of projects", + icon: "folder", + }, { name: "Apps", description: "List of apps", @@ -145,27 +150,6 @@ function PanelContent({ ); } - // Pick project if no project is opened - else if (!editorContext?.editorStates.project) { - return ( -
-

View Projects

- - - -
- ); - } return (
@@ -187,10 +171,27 @@ function PanelContent({
{tabItems[selectedTabIndex]?.name === "Apps" ? ( - ) : ( + ) : tabItems[selectedTabIndex]?.name === "Workspace" ? ( + ) : ( +
+

View Projects

+ + +
)}
+
); } diff --git a/web/components/views/canvas/canvas-node-control.tsx b/web/components/views/canvas/canvas-node-control.tsx deleted file mode 100644 index 4c72217..0000000 --- a/web/components/views/canvas/canvas-node-control.tsx +++ /dev/null @@ -1,104 +0,0 @@ -import Icon from "@/components/misc/icon"; -import { isMobile } from "@/lib/platform-api/platform-checker"; -import { Button } from "@heroui/react"; -import { NodeResizeControl } from "@xyflow/react"; - -export default function CanvasNodeControl({ - controlActions, - setIsResizing, - isShowingWorkflowConnector, - setIsShowingWorkflowConnector, -}: { - controlActions: Record void) | undefined>; - setIsResizing: (resizing: boolean) => void; - isShowingWorkflowConnector: boolean; - setIsShowingWorkflowConnector: (showing: boolean) => void; -}) { - return ( - <> - - - - -
-
- {/* Popover is interfering with the drag area... */} - setIsResizing(true)} - onResizeEnd={isMobile() ? undefined : () => setIsResizing(false)} - autoScale={false} - > - - - - - - - - -
-
- - - - ); -} diff --git a/web/components/views/canvas/canvas-node-view-layout.tsx b/web/components/views/canvas/canvas-node-view-layout.tsx deleted file mode 100644 index 8f08c1d..0000000 --- a/web/components/views/canvas/canvas-node-view-layout.tsx +++ /dev/null @@ -1,98 +0,0 @@ -import { isMobile } from "@/lib/platform-api/platform-checker"; -import { Popover, PopoverContent, PopoverTrigger } from "@heroui/react"; -import { NodeResizer } from "@xyflow/react"; -import { useState } from "react"; -import CanvasNodeControl from "./canvas-node-control"; - -export default function CanvasNodeViewLayout({ - height = "100%", - width = "100%", - children, - controlActions = {}, -}: { - height?: string; - width?: string; - children: React.ReactNode; - controlActions?: Record void) | undefined>; -}) { - const [isShowingMenu, setIsShowingMenu] = useState(false); - const [isResizing, setIsResizing] = useState(false); - const [isShowingWorkflowConnector, setIsShowingWorkflowConnector] = - useState(false); - - return ( -
-
-
{ - e.preventDefault(); - setIsShowingMenu((prev) => !prev); - }} - > -
- - - -
-
- -
- -
-
-
-
-
- - {isShowingWorkflowConnector && ( - <> - {/* Input Area */} -
- - {/* Output Area */} -
- - )} - - setIsResizing(true)} - onResizeEnd={isMobile() ? undefined : () => setIsResizing(false)} - lineStyle={{ - borderColor: "transparent", - borderWidth: 6, - }} - handleStyle={{ - background: "transparent", - borderColor: "transparent", - width: 12, - height: 12, - zIndex: 40, - }} - /> - -
- { - // Add a temporary div overlay to prevent interaction while resizing - isResizing && ( -
- ) - } - {children} -
-
- ); -} diff --git a/web/components/views/canvas/canvas-view.tsx b/web/components/views/canvas/canvas-view.tsx index 046e326..92720a0 100644 --- a/web/components/views/canvas/canvas-view.tsx +++ b/web/components/views/canvas/canvas-view.tsx @@ -6,35 +6,23 @@ import { addEdge, applyEdgeChanges, applyNodeChanges, + Background, + BackgroundVariant, + Connection, EdgeChange, NodeChange, ReactFlow, Edge as ReactFlowEdge, Node as ReactFlowNode, + reconnectEdge, useReactFlow, useViewport, } from "@xyflow/react"; import "@xyflow/react/dist/style.css"; import { useCallback, useEffect, useRef, useState } from "react"; import Icon from "../../misc/icon"; -import AppNode from "./nodes/app-node"; - -// const initialNodes = [ -// { -// id: "n1", -// position: { x: 200, y: 0 }, -// data: { -// label: "Node 1", -// config: { -// viewId: v4(), -// app: "https://cdn.pulse-editor.com/extension/spin_wheel/0.0.1/", -// }, -// }, -// type: "appNode", -// }, -// { id: "n2", position: { x: 0, y: 100 }, data: { label: "Node 2" } }, -// ]; -// const initialEdges = [{ id: "n1-n2", source: "n1", target: "n2" }]; +import AppNode from "./nodes/app-node/app-node"; +import "./theme.css"; const appInfo: AppInfoModalContent = { name: "Pulse Editor", @@ -71,8 +59,10 @@ export default function CanvasView({ config }: { config?: CanvasViewConfig }) { setNodes((nodesSnapshot) => applyNodeChanges(changes, nodesSnapshot)); }, []); const onEdgesChange = useCallback( - (changes: EdgeChange<{ id: string; source: string; target: string }>[]) => - setEdges((edgesSnapshot) => applyEdgeChanges(changes, edgesSnapshot)), + (changes: EdgeChange<{ id: string; source: string; target: string }>[]) => { + console.log("Edge changes:", changes); + setEdges((edgesSnapshot) => applyEdgeChanges(changes, edgesSnapshot)); + }, [], ); const onConnect = useCallback( @@ -80,7 +70,13 @@ export default function CanvasView({ config }: { config?: CanvasViewConfig }) { setEdges((edgesSnapshot) => addEdge(params, edgesSnapshot)), [], ); + const onReconnect = useCallback( + (oldEdge: ReactFlowEdge, newConnection: Connection) => + setEdges((els) => reconnectEdge(oldEdge, newConnection, els)), + [], + ); + /* Node creator functions */ const createAppNode = useCallback((props: any) => { return ; }, []); @@ -221,7 +217,7 @@ export default function CanvasView({ config }: { config?: CanvasViewConfig }) { return (
+ deleteKeyCode={["Delete", "Backspace"]} + onReconnect={onReconnect} + defaultEdgeOptions={{ + markerEnd: { + type: "arrowclosed", + width: 20, + height: 20, + }, + }} + > + + + + + +
+
+ {/* Popover is interfering with the drag area... */} + setIsResizing(true)} + // onResizeEnd={isMobile() ? undefined : () => setIsResizing(false)} + autoScale={false} + > + + + + + + + + +
+
+ + + + ); +} diff --git a/web/components/views/canvas/nodes/app-node/node-handle.tsx b/web/components/views/canvas/nodes/app-node/node-handle.tsx new file mode 100644 index 0000000..d45995b --- /dev/null +++ b/web/components/views/canvas/nodes/app-node/node-handle.tsx @@ -0,0 +1,25 @@ +import { Handle, HandleType, Position } from "@xyflow/react"; + +export default function NodeHandle({ + id, + displayName, + position, + type, +}: { + id: string; + displayName: string; + position: Position; + type: HandleType; +}) { + return ( +
+ +

{displayName}

+
+ ); +} diff --git a/web/components/views/canvas/nodes/backend-node.tsx b/web/components/views/canvas/nodes/backend-node/backend-node.tsx similarity index 88% rename from web/components/views/canvas/nodes/backend-node.tsx rename to web/components/views/canvas/nodes/backend-node/backend-node.tsx index 7f8a120..f4fad39 100644 --- a/web/components/views/canvas/nodes/backend-node.tsx +++ b/web/components/views/canvas/nodes/backend-node/backend-node.tsx @@ -1,8 +1,8 @@ import { AppViewConfig } from "@/lib/types"; import { Node } from "@xyflow/react"; -import BaseAppView from "../../base/base-app-view"; import { memo } from "react"; -import CanvasNodeViewLayout from "../canvas-node-view-layout"; +import CanvasNodeViewLayout from "../app-node/layout"; +import BaseAppView from "@/components/views/base/base-app-view"; /* Runs backend part of pulse app. */ const BackendNode = memo((props: any) => { @@ -15,6 +15,7 @@ const BackendNode = memo((props: any) => { return ( diff --git a/web/components/views/canvas/theme.css b/web/components/views/canvas/theme.css new file mode 100644 index 0000000..dc350ad --- /dev/null +++ b/web/components/views/canvas/theme.css @@ -0,0 +1,66 @@ +.react-flow__node-appNode.selected .aura { + box-shadow: + 4px 0 15px rgba(42, 138, 246, 0.3), + -4px 0 15px rgba(233, 42, 103, 0.3); +} + +.react-flow__node-appNode .aura { + box-shadow: none; +} + +.react-flow__node-appNode .wrapper { + overflow: hidden; + padding: 3px; + margin-left: -3px; + margin-top: -3px; + box-sizing: content-box; + flex-grow: 1; + height: 100%; + width: 100%; +} + +.gradient:before { + content: ""; + position: absolute; + inset: -50%; /* makes it bigger than the box */ + border-radius: 50%; /* keep it circular */ + background: conic-gradient( + from -160deg at 50% 50%, + #e92a67 0deg, + #a853ba 120deg, + #2a8af6 240deg, + #e92a67 360deg + ); + z-index: -1; +} + +.react-flow__node-appNode .running.gradient:before { + content: ""; + background: conic-gradient( + from -160deg at 50% 50%, + #e92a6700 0deg, + #ff0035cc 20deg, + #a853ba 126deg, + #2a8af6cc 232deg, + #2a8af600 340deg + ); + animation: spinner 4s linear infinite; + transform: rotate(0deg); + z-index: -1; +} + +@keyframes spinner { + 100% { + transform: rotate(-360deg); + } +} + +.react-flow__node:focus { + outline: none; +} + +.react-flow__edge .react-flow__edge-path { + stroke: url(#edge-gradient); + stroke-width: 2; + stroke-opacity: 0.75; +} diff --git a/web/components/views/console-panel/console-panel-view.tsx b/web/components/views/console-panel/console-panel-view.tsx index 64d95e1..87d61c2 100644 --- a/web/components/views/console-panel/console-panel-view.tsx +++ b/web/components/views/console-panel/console-panel-view.tsx @@ -17,7 +17,7 @@ import { ExtensionTypeEnum, ViewModel } from "@pulse-editor/shared-utils"; import { AnimatePresence, motion } from "framer-motion"; import { v4 } from "uuid"; import SandboxAppLoader from "../../app-loaders/sandbox-app-loader"; -import AppViewLayout from "../standalone-app/app-view-layout"; +import AppViewLayout from "../standalone-app/layout"; function ConsoleNavBar({ consoles, diff --git a/web/components/views/standalone-app/app-control.tsx b/web/components/views/standalone-app/app-control.tsx deleted file mode 100644 index 6d07b74..0000000 --- a/web/components/views/standalone-app/app-control.tsx +++ /dev/null @@ -1,3 +0,0 @@ -export default function AppControl() { - return <>; -} diff --git a/web/components/views/standalone-app/app-view-layout.tsx b/web/components/views/standalone-app/layout.tsx similarity index 97% rename from web/components/views/standalone-app/app-view-layout.tsx rename to web/components/views/standalone-app/layout.tsx index 87c1f01..994d94f 100644 --- a/web/components/views/standalone-app/app-view-layout.tsx +++ b/web/components/views/standalone-app/layout.tsx @@ -1,6 +1,5 @@ import { Popover, PopoverContent, PopoverTrigger } from "@heroui/react"; import { useState } from "react"; -import AppControl from "./app-control"; export default function AppViewLayout({ height = "100%", @@ -52,3 +51,7 @@ export default function AppViewLayout({
); } + +function AppControl() { + return <>; +} diff --git a/web/components/views/standalone-app/standalone-app-view.tsx b/web/components/views/standalone-app/standalone-app-view.tsx index 0cd8c8d..df72adf 100644 --- a/web/components/views/standalone-app/standalone-app-view.tsx +++ b/web/components/views/standalone-app/standalone-app-view.tsx @@ -1,6 +1,6 @@ import { AppViewConfig } from "@/lib/types"; import BaseAppView from "../base/base-app-view"; -import AppViewLayout from "./app-view-layout"; +import AppViewLayout from "./layout"; export default function StandaloneAppView({ config, diff --git a/web/components/views/view-area.tsx b/web/components/views/view-area.tsx index 39d9013..7e24fa9 100644 --- a/web/components/views/view-area.tsx +++ b/web/components/views/view-area.tsx @@ -157,40 +157,47 @@ export default function ViewArea() {
No view selected
) : (
{isShowTabs && ( -
- { - const index = tabItems.findIndex( - (tab) => tab.name === item?.name, - ); - selectTab(index !== -1 ? index : 0); - }} - isShowPagination={true} - onTabClose={(item) => { - const index = tabItems.findIndex( - (tab) => tab.name === item?.name, - ); - if (index !== -1) { - closeTabView(tabViews[index]); +
+
+ + setSelectedItem={(item) => { + const index = tabItems.findIndex( + (tab) => tab.name === item?.name, + ); + selectTab(index !== -1 ? index : 0); + }} + isShowPagination={true} + onTabClose={(item) => { + const index = tabItems.findIndex( + (tab) => tab.name === item?.name, + ); + if (index !== -1) { + closeTabView(tabViews[index]); + } + }} + /> +
)} -
+
{tabViews.map((tabView, idx) => (
{tabView.type === ViewModeEnum.App ? (