Skip to content

Commit 3e8f15f

Browse files
committed
Add a warning modal on navigating away from a running flow
1 parent b67dab9 commit 3e8f15f

1 file changed

Lines changed: 73 additions & 42 deletions

File tree

‎packages/client/src/flow/flow.tsx‎

Lines changed: 73 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import { enumFromJson, isEnumJson } from '@bufbuild/protobuf';
22
import { createClient } from '@connectrpc/connect';
3-
import { createFileRoute, useMatchRoute, useNavigate, useRouteContext } from '@tanstack/react-router';
3+
import { createFileRoute, useBlocker, useMatchRoute, useNavigate, useRouteContext } from '@tanstack/react-router';
44
import {
55
Background,
66
BackgroundVariant,
@@ -19,7 +19,7 @@ import { Array, Boolean, HashMap, Match, MutableHashMap, Option, pipe, Predicate
1919
import { Ulid } from 'id128';
2020
import { PropsWithChildren, Suspense, use, useCallback, useMemo, useRef, useState } from 'react';
2121
import { useDrop } from 'react-aria';
22-
import { MenuTrigger, useDragAndDrop } from 'react-aria-components';
22+
import { Dialog, MenuTrigger, useDragAndDrop } from 'react-aria-components';
2323
import { FiClock, FiMinus, FiMoreHorizontal, FiPlus, FiStopCircle, FiX } from 'react-icons/fi';
2424
import { Panel, PanelGroup } from 'react-resizable-panels';
2525
import { Example } from '@the-dev-tools/spec/collection/item/example/v1/example_pb';
@@ -55,6 +55,7 @@ import { Button, ButtonAsLink } from '@the-dev-tools/ui/button';
5555
import { DataTable, useReactTable } from '@the-dev-tools/ui/data-table';
5656
import { PlayCircleIcon, Spinner } from '@the-dev-tools/ui/icons';
5757
import { Menu, MenuItem, useContextMenuState } from '@the-dev-tools/ui/menu';
58+
import { Modal } from '@the-dev-tools/ui/modal';
5859
import { PanelResizeHandle } from '@the-dev-tools/ui/resizable-panel';
5960
import { Separator } from '@the-dev-tools/ui/separator';
6061
import { tw } from '@the-dev-tools/ui/tailwind-literal';
@@ -445,6 +446,12 @@ const ActionBar = () => {
445446

446447
const makeNode = useMakeNode();
447448

449+
const { proceed, reset, status } = useBlocker({
450+
disabled: !controller,
451+
shouldBlockFn: (_) => _.current.pathname !== _.next.pathname,
452+
withResolver: true,
453+
});
454+
448455
const onRun = async () => {
449456
const controller = new AbortController();
450457
setController(controller);
@@ -531,48 +538,72 @@ const ActionBar = () => {
531538
};
532539

533540
return (
534-
<RFPanel
535-
className={tw`mb-4 flex items-center gap-2 rounded-lg bg-slate-900 p-1 shadow-sm`}
536-
position='bottom-center'
537-
>
538-
{/* <Button variant='ghost dark' className={tw`p-1`}>
539-
<TextBoxIcon className={tw`size-5 text-slate-300`} />
540-
</Button> */}
541-
542-
{/* <Button variant='ghost dark' className={tw`p-1`}>
543-
<ChatAddIcon className={tw`size-5 text-slate-300`} />
544-
</Button> */}
545-
546-
{/* <div className={tw`mx-2 h-5 w-px bg-white/20`} /> */}
547-
548-
<Button
549-
className={tw`px-1.5 py-1`}
550-
onPress={async () => {
551-
const { domNode } = storeApi.getState();
552-
if (!domNode) return;
553-
const box = domNode.getBoundingClientRect();
554-
const position = flow.screenToFlowPosition({ x: box.x + box.width / 2, y: box.y + box.height * 0.1 });
555-
const node = await makeNode({ kind: NodeKind.NO_OP, noOp: NodeNoOpKind.CREATE, position });
556-
pipe(node, Node.fromDTO, flow.addNodes);
557-
}}
558-
variant='ghost dark'
541+
<>
542+
<Modal isOpen={status === 'blocked'} modalClassName={tw`h-auto w-sm`}>
543+
<Dialog className={tw`grid grid-cols-2 gap-4 p-6`}>
544+
<div className={tw`col-span-full`}>
545+
Leaving the flow will stop the execution, are you sure you want to proceed?
546+
</div>
547+
548+
<Button onPress={() => reset?.()} variant='secondary'>
549+
Cancel
550+
</Button>
551+
552+
<Button
553+
onPress={() => {
554+
onStop();
555+
proceed?.();
556+
}}
557+
variant='primary'
558+
>
559+
Continue
560+
</Button>
561+
</Dialog>
562+
</Modal>
563+
564+
<RFPanel
565+
className={tw`mb-4 flex items-center gap-2 rounded-lg bg-slate-900 p-1 shadow-sm`}
566+
position='bottom-center'
559567
>
560-
<FiPlus className={tw`size-5 text-slate-300`} />
561-
Add Node
562-
</Button>
563-
564-
{controller ? (
565-
<Button onPress={onStop} variant='primary'>
566-
<FiStopCircle className={tw`size-4`} />
567-
Stop
568-
</Button>
569-
) : (
570-
<Button onPress={onRun} variant='primary'>
571-
<PlayCircleIcon className={tw`size-4`} />
572-
Run
568+
{/* <Button variant='ghost dark' className={tw`p-1`}>
569+
<TextBoxIcon className={tw`size-5 text-slate-300`} />
570+
</Button> */}
571+
572+
{/* <Button variant='ghost dark' className={tw`p-1`}>
573+
<ChatAddIcon className={tw`size-5 text-slate-300`} />
574+
</Button> */}
575+
576+
{/* <div className={tw`mx-2 h-5 w-px bg-white/20`} /> */}
577+
578+
<Button
579+
className={tw`px-1.5 py-1`}
580+
onPress={async () => {
581+
const { domNode } = storeApi.getState();
582+
if (!domNode) return;
583+
const box = domNode.getBoundingClientRect();
584+
const position = flow.screenToFlowPosition({ x: box.x + box.width / 2, y: box.y + box.height * 0.1 });
585+
const node = await makeNode({ kind: NodeKind.NO_OP, noOp: NodeNoOpKind.CREATE, position });
586+
pipe(node, Node.fromDTO, flow.addNodes);
587+
}}
588+
variant='ghost dark'
589+
>
590+
<FiPlus className={tw`size-5 text-slate-300`} />
591+
Add Node
573592
</Button>
574-
)}
575-
</RFPanel>
593+
594+
{controller ? (
595+
<Button onPress={onStop} variant='primary'>
596+
<FiStopCircle className={tw`size-4`} />
597+
Stop
598+
</Button>
599+
) : (
600+
<Button onPress={onRun} variant='primary'>
601+
<PlayCircleIcon className={tw`size-4`} />
602+
Run
603+
</Button>
604+
)}
605+
</RFPanel>
606+
</>
576607
);
577608
};
578609

0 commit comments

Comments
 (0)