mirror of
https://github.com/shishantbiswas/bknd.git
synced 2026-03-17 21:06:04 +00:00
210 lines
6.1 KiB
TypeScript
210 lines
6.1 KiB
TypeScript
import { MarkerType, type Node } from "@xyflow/react";
|
|
import type { TAppFlowSchema, TAppFlowTriggerSchema } from "flows/AppFlows";
|
|
import { atom, useAtom, useAtomValue, useSetAtom } from "jotai";
|
|
import { selectAtom } from "jotai/utils";
|
|
import { isEqual } from "lodash-es";
|
|
import type { ModuleSchemas } from "modules/ModuleManager";
|
|
import { createContext, useCallback, useContext, useEffect } from "react";
|
|
import { useBknd } from "ui/client/bknd";
|
|
|
|
export type TFlowNodeData = {
|
|
label: string;
|
|
type: string;
|
|
last?: boolean;
|
|
start?: boolean;
|
|
responding?: boolean;
|
|
};
|
|
|
|
export type FlowContextType = {
|
|
name?: string;
|
|
data?: TAppFlowSchema;
|
|
schema: ModuleSchemas["flows"]["properties"]["flows"];
|
|
actions: {
|
|
flow: {
|
|
setName: (name: string) => Promise<any>;
|
|
};
|
|
trigger: {
|
|
update: (trigger: TAppFlowTriggerSchema) => Promise<any>;
|
|
};
|
|
task: {
|
|
create: (type: string, defaults?: object) => Promise<any>;
|
|
update: (name: string, params: any) => Promise<any>;
|
|
};
|
|
};
|
|
};
|
|
|
|
export type TFlowState = {
|
|
dirty: boolean;
|
|
name?: string;
|
|
flow?: TAppFlowSchema;
|
|
};
|
|
export const flowStateAtom = atom<TFlowState>({
|
|
dirty: false,
|
|
name: undefined,
|
|
flow: undefined
|
|
});
|
|
|
|
const FlowCanvasContext = createContext<FlowContextType>(undefined!);
|
|
|
|
const DEFAULT_FLOW = { trigger: {}, tasks: {}, connections: {} };
|
|
export function FlowCanvasProvider({ children, name }: { children: any; name?: string }) {
|
|
//const [dirty, setDirty] = useState(false);
|
|
const setFlowState = useSetAtom(flowStateAtom);
|
|
const s = useBknd();
|
|
const data = name ? (s.config.flows.flows[name] as TAppFlowSchema) : undefined;
|
|
const schema = s.schema.flows.properties.flows;
|
|
|
|
useEffect(() => {
|
|
if (name) {
|
|
setFlowState({ dirty: false, name, flow: data });
|
|
}
|
|
}, [name]);
|
|
|
|
const actions = {
|
|
flow: {
|
|
setName: async (name: string) => {
|
|
console.log("set name", name);
|
|
setFlowState((state) => ({ ...state, name, dirty: true }));
|
|
}
|
|
},
|
|
trigger: {
|
|
update: async (trigger: TAppFlowTriggerSchema | any) => {
|
|
console.log("update trigger", trigger);
|
|
setFlowState((state) => {
|
|
const flow = state.flow || DEFAULT_FLOW;
|
|
return { ...state, dirty: true, flow: { ...flow, trigger } };
|
|
});
|
|
//return s.actions.patch("flows", `flows.flows.${name}`, { trigger });
|
|
}
|
|
},
|
|
task: {
|
|
create: async (name: string, defaults: object = {}) => {
|
|
console.log("create task", name, defaults);
|
|
setFlowState((state) => {
|
|
const flow = state.flow || (DEFAULT_FLOW as any);
|
|
const tasks = { ...flow.tasks, [name]: defaults };
|
|
return { ...state, dirty: true, flow: { ...flow, tasks } };
|
|
});
|
|
},
|
|
|
|
update: async (name: string, params: any) => {
|
|
console.log("update task", name, params);
|
|
setFlowState((state) => {
|
|
const flow = state.flow || (DEFAULT_FLOW as any);
|
|
const task = { ...state.flow?.tasks?.[name], params };
|
|
return {
|
|
...state,
|
|
dirty: true,
|
|
flow: { ...flow, tasks: { ...flow.tasks, [name]: task } }
|
|
};
|
|
});
|
|
//return s.actions.patch("flows", `flows.flows.${name}.tasks.${name}`, task);
|
|
}
|
|
}
|
|
};
|
|
|
|
return (
|
|
<FlowCanvasContext.Provider value={{ name, data, schema, actions }}>
|
|
{children}
|
|
</FlowCanvasContext.Provider>
|
|
);
|
|
}
|
|
|
|
export function useFlowCanvas() {
|
|
return useContext(FlowCanvasContext);
|
|
}
|
|
|
|
export function useFlowCanvasState() {
|
|
return useAtomValue(flowStateAtom);
|
|
}
|
|
|
|
export function useFlowSelector<Reduced = TFlowState>(
|
|
selector: (state: TFlowState) => Reduced,
|
|
equalityFn: (a: any, b: any) => boolean = isEqual
|
|
) {
|
|
const selected = selectAtom(flowStateAtom, useCallback(selector, []), equalityFn);
|
|
return useAtom(selected)[0];
|
|
}
|
|
|
|
export function flowToNodes(flow: TAppFlowSchema, name: string): Node<TFlowNodeData>[] {
|
|
const nodes: Node<TFlowNodeData>[] = [
|
|
{
|
|
id: "trigger",
|
|
data: { label: name, type: flow.trigger.type },
|
|
type: "trigger",
|
|
dragHandle: ".drag-handle",
|
|
position: { x: 0, y: 0 }
|
|
}
|
|
];
|
|
|
|
let i = 1;
|
|
const count = Object.keys(flow.tasks ?? {}).length;
|
|
for (const [name, task] of Object.entries(flow.tasks ?? {})) {
|
|
const last = i === count;
|
|
const start = i === 1;
|
|
const responding = last;
|
|
|
|
nodes.push({
|
|
id: `task-${name}`,
|
|
data: { label: name, type: task.type, last, start, responding },
|
|
type: "task",
|
|
dragHandle: ".drag-handle",
|
|
// @todo: this is currently static
|
|
position: { x: 450 * i + (i - 1) * 64, y: 0 }
|
|
});
|
|
i++;
|
|
}
|
|
|
|
/*nodes.push({
|
|
id: "select",
|
|
data: { label: "Select", type: "select" },
|
|
type: "select",
|
|
position: { x: 500 * i, y: 0 }
|
|
});*/
|
|
|
|
return nodes;
|
|
}
|
|
|
|
export function flowToEdges(flow: TAppFlowSchema) {
|
|
const tasks = Object.entries(flow.tasks ?? {});
|
|
if (tasks.length === 0) return [];
|
|
|
|
const edges =
|
|
tasks.length >= 1
|
|
? [
|
|
{
|
|
id: "trigger-task",
|
|
source: "trigger",
|
|
target: `task-${tasks[0]?.[0]}`,
|
|
//type: "smoothstep",
|
|
style: {
|
|
strokeWidth: 2
|
|
},
|
|
markerEnd: {
|
|
type: MarkerType.ArrowClosed,
|
|
width: 10,
|
|
height: 10
|
|
}
|
|
}
|
|
]
|
|
: [];
|
|
|
|
for (const [id, connection] of Object.entries(flow.connections ?? {})) {
|
|
edges.push({
|
|
id,
|
|
source: "task-" + connection.source,
|
|
target: "task-" + connection.target,
|
|
style: {
|
|
strokeWidth: 2
|
|
},
|
|
markerEnd: {
|
|
type: MarkerType.ArrowClosed,
|
|
width: 10,
|
|
height: 10
|
|
}
|
|
});
|
|
}
|
|
|
|
return edges;
|
|
}
|