Skip to main content
Glama
index.tsx9.9 kB
import { useMutation } from '@tanstack/react-query'; import { useEffect, useRef, useState } from 'react'; import { ImperativePanelHandle } from 'react-resizable-panels'; import { LeftSideBarType, RightSideBarType, useBuilderStateContext, useShowBuilderIsSavingWarningBeforeLeaving, useSwitchToDraft, } from '@/app/builder/builder-hooks'; import { DataSelector } from '@/app/builder/data-selector'; import { CanvasControls } from '@/app/builder/flow-canvas/canvas-controls'; import { StepSettingsProvider } from '@/app/builder/step-settings/step-settings-context'; import { ChatDrawer } from '@/app/routes/chat/chat-drawer'; import { ShowPoweredBy } from '@/components/show-powered-by'; import { useSocket } from '@/components/socket-provider'; import { ResizableHandle, ResizablePanel, ResizablePanelGroup, } from '@/components/ui/resizable-panel'; import { RunDetailsBar } from '@/features/flow-runs/components/run-details-bar'; import { flowRunsApi } from '@/features/flow-runs/lib/flow-runs-api'; import { piecesHooks } from '@/features/pieces/lib/pieces-hooks'; import { platformHooks } from '@/hooks/platform-hooks'; import { FlowAction, FlowActionType, FlowTrigger, FlowTriggerType, FlowVersionState, WebsocketClientEvent, flowStructureUtil, isNil, } from '@activepieces/shared'; import { cn, useElementSize } from '../../lib/utils'; import { BuilderHeader } from './builder-header/builder-header'; import { FlowCanvas } from './flow-canvas'; import { LEFT_SIDEBAR_ID } from './flow-canvas/utils/consts'; import { FlowVersionsList } from './flow-versions'; import { FlowRunDetails } from './run-details'; import { RunsList } from './run-list'; import { StepSettingsContainer } from './step-settings'; const minWidthOfSidebar = 'min-w-[max(20vw,400px)]'; const animateResizeClassName = `transition-all duration-200`; const useAnimateSidebar = ( sidebarValue: LeftSideBarType | RightSideBarType, ) => { const handleRef = useRef<ImperativePanelHandle>(null); const sidebarClosed = [LeftSideBarType.NONE, RightSideBarType.NONE].includes( sidebarValue, ); useEffect(() => { const sidebarSize = handleRef.current?.getSize() ?? 0; if (sidebarClosed) { handleRef.current?.resize(0); } else if (sidebarSize === 0) { handleRef.current?.resize(25); } }, [handleRef, sidebarValue, sidebarClosed]); return handleRef; }; const BuilderPage = () => { const { platform } = platformHooks.useCurrentPlatform(); const [setRun, flowVersion, leftSidebar, rightSidebar, run, selectedStep] = useBuilderStateContext((state) => [ state.setRun, state.flowVersion, state.leftSidebar, state.rightSidebar, state.run, state.selectedStep, ]); useShowBuilderIsSavingWarningBeforeLeaving(); const { memorizedSelectedStep } = useBuilderStateContext((state) => { const flowVersion = state.flowVersion; if (isNil(state.selectedStep) || isNil(flowVersion)) { return { memorizedSelectedStep: undefined, }; } const step = flowStructureUtil.getStep( state.selectedStep, flowVersion.trigger, ); return { memorizedSelectedStep: step, }; }); const middlePanelRef = useRef<HTMLDivElement>(null); const middlePanelSize = useElementSize(middlePanelRef); const [isDraggingHandle, setIsDraggingHandle] = useState(false); const rightHandleRef = useAnimateSidebar(rightSidebar); const leftHandleRef = useAnimateSidebar(leftSidebar); const rightSidePanelRef = useRef<HTMLDivElement>(null); const { pieceModel, refetch: refetchPiece } = piecesHooks.usePieceModelForStepSettings({ name: memorizedSelectedStep?.settings.pieceName, version: memorizedSelectedStep?.settings.pieceVersion, enabled: memorizedSelectedStep?.type === FlowActionType.PIECE || memorizedSelectedStep?.type === FlowTriggerType.PIECE, getExactVersion: flowVersion.state === FlowVersionState.LOCKED, }); const socket = useSocket(); const { mutate: fetchAndUpdateRun } = useMutation({ mutationFn: flowRunsApi.getPopulated, }); useEffect(() => { socket.on(WebsocketClientEvent.REFRESH_PIECE, () => { refetchPiece(); }); socket.on(WebsocketClientEvent.FLOW_RUN_PROGRESS, (data) => { const runId = data?.runId; if (run && run?.id === runId) { fetchAndUpdateRun(runId, { onSuccess: (run) => { setRun(run, flowVersion); }, }); } }); return () => { socket.removeAllListeners(WebsocketClientEvent.REFRESH_PIECE); socket.removeAllListeners(WebsocketClientEvent.FLOW_RUN_PROGRESS); }; }, [socket.id, run?.id]); const { switchToDraft, isSwitchingToDraftPending } = useSwitchToDraft(); const [hasCanvasBeenInitialised, setHasCanvasBeenInitialised] = useState(false); return ( <div className="flex h-full w-full flex-col relative"> <div className="z-50"> <BuilderHeader /> </div> <ResizablePanelGroup direction="horizontal"> <ResizablePanel id="left-sidebar" defaultSize={0} minSize={0} maxSize={39} order={1} ref={leftHandleRef} className={cn('min-w-0 z-20 ', { [minWidthOfSidebar]: leftSidebar !== LeftSideBarType.NONE, [animateResizeClassName]: !isDraggingHandle, })} > <div id={LEFT_SIDEBAR_ID} className="w-full h-full"> {leftSidebar === LeftSideBarType.RUNS && <RunsList />} {leftSidebar === LeftSideBarType.RUN_DETAILS && <FlowRunDetails />} </div> </ResizablePanel> <ResizableHandle onDragging={setIsDraggingHandle} withHandle={leftSidebar !== LeftSideBarType.NONE} className={ leftSidebar === LeftSideBarType.NONE ? 'bg-transparent' : '' } /> <ResizablePanel defaultSize={100} order={2} id="flow-canvas"> <div ref={middlePanelRef} className="relative h-full w-full"> <FlowCanvas setHasCanvasBeenInitialised={setHasCanvasBeenInitialised} ></FlowCanvas> <RunDetailsBar run={run} isLoading={isSwitchingToDraftPending} exitRun={() => { socket.removeAllListeners( WebsocketClientEvent.FLOW_RUN_PROGRESS, ); switchToDraft(); }} /> {middlePanelRef.current && middlePanelRef.current.clientWidth > 0 && ( <CanvasControls canvasHeight={middlePanelRef.current?.clientHeight ?? 0} canvasWidth={middlePanelRef.current?.clientWidth ?? 0} hasCanvasBeenInitialised={hasCanvasBeenInitialised} selectedStep={selectedStep} ></CanvasControls> )} <ShowPoweredBy position="absolute" show={platform?.plan.showPoweredBy} /> <DataSelector parentHeight={middlePanelSize.height} parentWidth={middlePanelSize.width} ></DataSelector> </div> </ResizablePanel> <ResizableHandle disabled={rightSidebar === RightSideBarType.NONE} withHandle={rightSidebar !== RightSideBarType.NONE} onDragging={setIsDraggingHandle} className={ rightSidebar === RightSideBarType.NONE ? 'bg-transparent' : '' } /> <ResizablePanel ref={rightHandleRef} id="right-sidebar" defaultSize={0} minSize={0} maxSize={60} order={3} className={cn('min-w-0 bg-background z-30', { [minWidthOfSidebar]: rightSidebar !== RightSideBarType.NONE, [animateResizeClassName]: !isDraggingHandle, })} > <div ref={rightSidePanelRef} className="h-full w-full"> {rightSidebar === RightSideBarType.PIECE_SETTINGS && memorizedSelectedStep && ( <StepSettingsProvider pieceModel={pieceModel} selectedStep={memorizedSelectedStep} key={constructContainerKey({ flowVersionId: flowVersion.id, step: memorizedSelectedStep, hasPieceModelLoaded: !!pieceModel, })} > <StepSettingsContainer /> </StepSettingsProvider> )} {rightSidebar === RightSideBarType.VERSIONS && <FlowVersionsList />} </div> </ResizablePanel> </ResizablePanelGroup> <ChatDrawer /> </div> ); }; BuilderPage.displayName = 'BuilderPage'; export { BuilderPage }; function constructContainerKey({ flowVersionId, step, hasPieceModelLoaded, }: { flowVersionId: string; step?: FlowAction | FlowTrigger; hasPieceModelLoaded: boolean; }) { const stepName = step?.name; const triggerOrActionName = step?.type === FlowTriggerType.PIECE ? step?.settings.triggerName : step?.settings.actionName; const pieceName = step?.type === FlowTriggerType.PIECE || step?.type === FlowActionType.PIECE ? step?.settings.pieceName : undefined; //we need to re-render the step settings form when the step is skipped, so when the user edits the settings after setting it to skipped the changes are reflected in the update request const isSkipped = step?.type != FlowTriggerType.EMPTY && step?.type != FlowTriggerType.PIECE && step?.skip; return `${flowVersionId}-${stepName ?? ''}-${triggerOrActionName ?? ''}-${ pieceName ?? '' }-${'skipped-' + !!isSkipped}-${ hasPieceModelLoaded ? 'loaded' : 'not-loaded' }`; }

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/activepieces/activepieces'

If you have feedback or need assistance with the MCP directory API, please join our Discord server