mirror of
https://github.com/shishantbiswas/bknd.git
synced 2026-03-17 12:56:05 +00:00
public commit
This commit is contained in:
64
app/src/ui/routes/flows_old/_flows.root.tsx
Normal file
64
app/src/ui/routes/flows_old/_flows.root.tsx
Normal file
@@ -0,0 +1,64 @@
|
||||
import { IconHierarchy2 } from "@tabler/icons-react";
|
||||
import { ReactFlowProvider } from "@xyflow/react";
|
||||
import { ucFirstAllSnakeToPascalWithSpaces } from "core/utils";
|
||||
import { TbSettings } from "react-icons/tb";
|
||||
import { useLocation } from "wouter";
|
||||
import { useBknd } from "../../client/BkndProvider";
|
||||
import { useFlows } from "../../client/schema/flows/use-flows";
|
||||
import { IconButton } from "../../components/buttons/IconButton";
|
||||
import { Empty } from "../../components/display/Empty";
|
||||
import { SearchInput } from "../../components/form/SearchInput";
|
||||
import { Link } from "../../components/wouter/Link";
|
||||
import { useBrowserTitle } from "../../hooks/use-browser-title";
|
||||
import * as AppShell from "../../layouts/AppShell/AppShell";
|
||||
|
||||
export function FlowsRoot({ children }) {
|
||||
return <ReactFlowProvider>{children}</ReactFlowProvider>;
|
||||
}
|
||||
|
||||
export function FlowsEmpty() {
|
||||
const { app } = useBknd();
|
||||
useBrowserTitle(["Flows"]);
|
||||
const [, navigate] = useLocation();
|
||||
const { flows } = useFlows();
|
||||
|
||||
return (
|
||||
<>
|
||||
<AppShell.Sidebar>
|
||||
<AppShell.SectionHeader
|
||||
right={
|
||||
<Link href={app.getSettingsPath(["flows"])}>
|
||||
<IconButton Icon={TbSettings} />
|
||||
</Link>
|
||||
}
|
||||
>
|
||||
Flows
|
||||
</AppShell.SectionHeader>
|
||||
<AppShell.Scrollable initialOffset={96}>
|
||||
<div className="flex flex-col flex-grow p-3 gap-3">
|
||||
<div>
|
||||
<SearchInput placeholder="Search flows" />
|
||||
</div>
|
||||
<nav className="flex flex-col flex-1 gap-1">
|
||||
{flows.map((flow) => (
|
||||
<AppShell.SidebarLink key={flow.name} as={Link} href={`/flow/${flow.name}`}>
|
||||
{ucFirstAllSnakeToPascalWithSpaces(flow.name)}
|
||||
</AppShell.SidebarLink>
|
||||
))}
|
||||
</nav>
|
||||
</div>
|
||||
</AppShell.Scrollable>
|
||||
</AppShell.Sidebar>
|
||||
<main className="flex flex-col flex-grow">
|
||||
<Empty
|
||||
Icon={IconHierarchy2}
|
||||
title="No flow selected"
|
||||
description="Please select a flow from the left sidebar or create a new one
|
||||
to continue."
|
||||
buttonText="Create Flow"
|
||||
buttonOnClick={() => navigate(app.getSettingsPath(["flows"]))}
|
||||
/>
|
||||
</main>
|
||||
</>
|
||||
);
|
||||
}
|
||||
202
app/src/ui/routes/flows_old/flow.$key.tsx
Normal file
202
app/src/ui/routes/flows_old/flow.$key.tsx
Normal file
@@ -0,0 +1,202 @@
|
||||
import { type Edge, type Node, useOnSelectionChange } from "@xyflow/react";
|
||||
import { type Execution, ExecutionState, type Flow, type Task } from "flows";
|
||||
import { useEffect, useState } from "react";
|
||||
import {
|
||||
TbArrowLeft,
|
||||
TbChevronDown,
|
||||
TbChevronUp,
|
||||
TbDots,
|
||||
TbPlayerPlayFilled,
|
||||
TbSettings
|
||||
} from "react-icons/tb";
|
||||
import { twMerge } from "tailwind-merge";
|
||||
import FlowCanvas from "ui/modules/flows/components/FlowCanvas";
|
||||
import { TaskForm } from "ui/modules/flows/components/form/TaskForm";
|
||||
import { useLocation } from "wouter";
|
||||
import { useBknd } from "../../client/BkndProvider";
|
||||
import { Button } from "../../components/buttons/Button";
|
||||
import { IconButton } from "../../components/buttons/IconButton";
|
||||
import { Dropdown } from "../../components/overlay/Dropdown";
|
||||
import { useFlow } from "../../container/use-flows";
|
||||
import * as AppShell from "../../layouts/AppShell/AppShell";
|
||||
import { SectionHeader } from "../../layouts/AppShell/AppShell";
|
||||
|
||||
export function FlowEdit({ params }) {
|
||||
const { app } = useBknd();
|
||||
const { color_scheme: theme } = app.getAdminConfig();
|
||||
const { basepath } = app.getAdminConfig();
|
||||
const prefix = `~/${basepath}/settings`.replace(/\/+/g, "/");
|
||||
const [location, navigate] = useLocation();
|
||||
const [execution, setExecution] = useState<Execution>();
|
||||
const [selectedNodes, setSelectedNodes] = useState<Node[]>([]);
|
||||
const [selectedEdges, setSelectedEdges] = useState<Edge[]>([]);
|
||||
|
||||
console.log("key", params, params.flow);
|
||||
const { flow } = useFlow(params.flow);
|
||||
console.log("--flow", flow);
|
||||
|
||||
async function handleRun() {
|
||||
console.log("Running flow", flow);
|
||||
const execution = flow.createExecution();
|
||||
setExecution(execution);
|
||||
|
||||
// delay a bit before starting
|
||||
execution.emgr.onEvent(
|
||||
ExecutionState,
|
||||
async (event) => {
|
||||
if (event.params.state === "started") {
|
||||
await new Promise((resolve) => setTimeout(resolve, 100));
|
||||
}
|
||||
},
|
||||
"sync"
|
||||
);
|
||||
|
||||
execution.subscribe(async (event) => {
|
||||
console.log("[event]", event);
|
||||
});
|
||||
await new Promise((resolve) => setTimeout(resolve, 100));
|
||||
|
||||
execution.start();
|
||||
}
|
||||
|
||||
function goBack(state?: Record<string, any>) {
|
||||
window.history.go(-1);
|
||||
}
|
||||
|
||||
useOnSelectionChange({
|
||||
onChange: ({ nodes, edges }) => {
|
||||
setSelectedNodes(nodes);
|
||||
setSelectedEdges(edges);
|
||||
}
|
||||
});
|
||||
|
||||
return (
|
||||
<>
|
||||
<AppShell.Sidebar>
|
||||
<AppShell.SectionHeader
|
||||
right={
|
||||
<a href="#" className="link p-1 rounded-md hover:bg-primary/5 flex items-center">
|
||||
<TbSettings size={20} />
|
||||
</a>
|
||||
}
|
||||
>
|
||||
Tasks
|
||||
</AppShell.SectionHeader>
|
||||
<AppShell.Scrollable initialOffset={96}>
|
||||
<Sidebar edges={selectedEdges} nodes={selectedNodes} flow={flow} />
|
||||
</AppShell.Scrollable>
|
||||
</AppShell.Sidebar>
|
||||
<main className="flex flex-col flex-grow">
|
||||
<SectionHeader
|
||||
right={
|
||||
<>
|
||||
<Dropdown
|
||||
items={[
|
||||
{
|
||||
label: "Settings",
|
||||
onClick: () => navigate(`${prefix}/flows/flows/${flow.name}`)
|
||||
}
|
||||
]}
|
||||
position="bottom-end"
|
||||
>
|
||||
<IconButton Icon={TbDots} />
|
||||
</Dropdown>
|
||||
<Button variant="primary" IconLeft={TbPlayerPlayFilled} onClick={handleRun}>
|
||||
Run
|
||||
</Button>
|
||||
</>
|
||||
}
|
||||
className="pl-3"
|
||||
>
|
||||
<AppShell.SectionHeaderTitle className="flex flex-row items-center gap-2">
|
||||
<IconButton
|
||||
onClick={goBack}
|
||||
Icon={TbArrowLeft}
|
||||
variant="default"
|
||||
size="lg"
|
||||
className="mr-1"
|
||||
/>
|
||||
<div className="truncate">
|
||||
<span className="text-primary/60">Flow / </span>
|
||||
{flow.name}
|
||||
</div>
|
||||
</AppShell.SectionHeaderTitle>
|
||||
</SectionHeader>
|
||||
<div className="w-full h-full">
|
||||
<FlowCanvas flow={flow} execution={execution} options={{ theme }} key={theme} />
|
||||
</div>
|
||||
</main>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
function Sidebar({ nodes, edges, flow }: { flow: Flow; nodes: Node[]; edges: Edge[] }) {
|
||||
const selectedNode = nodes?.[0];
|
||||
// @ts-ignore
|
||||
const selectedTask: Task | undefined = selectedNode?.data?.task;
|
||||
|
||||
useEffect(() => {
|
||||
console.log("-- selected", selectedTask);
|
||||
}, [selectedTask]);
|
||||
|
||||
const tasks = flow.getSequence().flat();
|
||||
|
||||
const Header = ({ onClick, opened, task }) => (
|
||||
<div
|
||||
className={twMerge(
|
||||
"flex flex-row pl-5 pr-3 py-3 border-muted border-b cursor-pointer justify-between items-center font-bold",
|
||||
opened && "bg-primary/5"
|
||||
)}
|
||||
onClick={onClick}
|
||||
>
|
||||
{task.name}
|
||||
{opened ? <TbChevronUp size={18} /> : <TbChevronDown size={18} />}
|
||||
</div>
|
||||
);
|
||||
|
||||
return (
|
||||
<div className="flex flex-col flex-grow">
|
||||
{tasks.map((task) => {
|
||||
const open = task.name === selectedTask?.name;
|
||||
return (
|
||||
<Collapsible
|
||||
key={task.name}
|
||||
className="flex flex-col"
|
||||
header={(props) => <Header {...props} task={task} />}
|
||||
open={open}
|
||||
>
|
||||
<div className="flex flex-col pl-5 pr-3 py-3">
|
||||
<TaskForm task={task} onChange={console.log} />
|
||||
</div>
|
||||
</Collapsible>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
type CollapsibleProps = {
|
||||
header: (props: any) => any;
|
||||
className?: string;
|
||||
children: React.ReactNode;
|
||||
open?: boolean;
|
||||
};
|
||||
|
||||
function Collapsible({ header, children, open = false, className }: CollapsibleProps) {
|
||||
const [opened, setOpened] = useState(open);
|
||||
|
||||
function toggle() {
|
||||
setOpened((prev) => !prev);
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
setOpened(open);
|
||||
}, [open]);
|
||||
|
||||
return (
|
||||
<div className={twMerge("flex flex-col", className)}>
|
||||
{header?.({ onClick: toggle, opened })}
|
||||
{opened && children}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
12
app/src/ui/routes/flows_old/index.tsx
Normal file
12
app/src/ui/routes/flows_old/index.tsx
Normal file
@@ -0,0 +1,12 @@
|
||||
import { Route } from "wouter";
|
||||
import { FlowsEmpty, FlowsRoot } from "./_flows.root";
|
||||
import { FlowEdit } from "./flow.$key";
|
||||
|
||||
export default function FlowRoutes() {
|
||||
return (
|
||||
<FlowsRoot>
|
||||
<Route path="/" component={FlowsEmpty} />
|
||||
<Route path="/flow/:flow" component={FlowEdit} />
|
||||
</FlowsRoot>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user