@motiadev/workbench 0.0.29 → 0.0.30
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/dist/index.html +1 -1
- package/dist/src/components/app-sidebar.js +2 -2
- package/dist/src/components/logs/log-console.js +1 -1
- package/dist/src/components/logs/log-detail.js +1 -1
- package/dist/src/components/logs/log-field.js +7 -3
- package/dist/src/components/logs/logs.js +1 -1
- package/dist/src/components/states/state-detail.js +2 -4
- package/dist/src/components/states/state-value.d.ts +8 -0
- package/dist/src/components/states/state-value.js +51 -0
- package/dist/src/components/states/states.js +1 -1
- package/dist/src/components/ui/badge.js +2 -2
- package/dist/src/components/ui/button.d.ts +1 -1
- package/dist/src/components/ui/sidebar.d.ts +11 -55
- package/dist/src/components/ui/sidebar.js +14 -220
- package/dist/src/components/ui/table.js +1 -1
- package/dist/src/hooks/use-debounced.d.ts +1 -0
- package/dist/src/hooks/use-debounced.js +20 -0
- package/dist/src/index.css +17 -4
- package/dist/src/publicComponents/api-node.d.ts +2 -4
- package/dist/src/publicComponents/api-node.js +3 -3
- package/dist/src/publicComponents/base-handle.d.ts +1 -0
- package/dist/src/publicComponents/base-handle.js +5 -2
- package/dist/src/publicComponents/base-node.d.ts +3 -2
- package/dist/src/publicComponents/base-node.js +13 -9
- package/dist/src/publicComponents/colorMap.d.ts +6 -0
- package/dist/src/publicComponents/colorMap.js +6 -0
- package/dist/src/publicComponents/components/header-bar.d.ts +11 -0
- package/dist/src/publicComponents/components/header-bar.js +15 -0
- package/dist/src/publicComponents/cron-node.js +2 -2
- package/dist/src/publicComponents/emits.js +2 -2
- package/dist/src/publicComponents/event-node.d.ts +0 -1
- package/dist/src/publicComponents/event-node.js +3 -5
- package/dist/src/publicComponents/node-details.d.ts +17 -0
- package/dist/src/publicComponents/node-details.js +19 -0
- package/dist/src/publicComponents/subscribe.d.ts +1 -2
- package/dist/src/publicComponents/subscribe.js +2 -2
- package/dist/src/route-wrapper.js +2 -2
- package/dist/src/routes/flow.js +8 -4
- package/dist/src/routes/index.js +3 -2
- package/dist/src/views/flow/base-edge.js +3 -3
- package/dist/src/views/flow/flow-view.d.ts +7 -2
- package/dist/src/views/flow/flow-view.js +18 -5
- package/dist/src/views/flow/hooks/use-get-flow-state.d.ts +7 -3
- package/dist/src/views/flow/hooks/use-get-flow-state.js +9 -5
- package/dist/src/views/flow/hooks/use-save-workflow-config.d.ts +3 -0
- package/dist/src/views/flow/hooks/use-save-workflow-config.js +23 -0
- package/dist/src/views/flow/legend.js +46 -46
- package/dist/src/views/flow/node-organizer.js +3 -1
- package/dist/src/views/flow/nodes/language-indicator.js +7 -7
- package/dist/src/views/flow/nodes/nodes.types.d.ts +21 -7
- package/dist/tsconfig.app.tsbuildinfo +1 -1
- package/dist/tsconfig.node.tsbuildinfo +1 -1
- package/package.json +1 -1
- package/dist/src/components/states/state-field.d.ts +0 -8
- package/dist/src/components/states/state-field.js +0 -16
package/dist/src/index.css
CHANGED
|
@@ -149,7 +149,7 @@ table {
|
|
|
149
149
|
text-rendering: optimizeLegibility;
|
|
150
150
|
-webkit-font-smoothing: antialiased;
|
|
151
151
|
-moz-osx-font-smoothing: grayscale;
|
|
152
|
-
font-family: '
|
|
152
|
+
font-family: 'DM Sans', serif;
|
|
153
153
|
font-optical-sizing: auto;
|
|
154
154
|
}
|
|
155
155
|
|
|
@@ -162,7 +162,7 @@ body {
|
|
|
162
162
|
|
|
163
163
|
button,
|
|
164
164
|
textarea {
|
|
165
|
-
font-family: '
|
|
165
|
+
font-family: 'DM Sans', serif;
|
|
166
166
|
}
|
|
167
167
|
|
|
168
168
|
strong {
|
|
@@ -216,7 +216,8 @@ body,
|
|
|
216
216
|
--sidebar-ring: 217.2 91.2% 59.8%;
|
|
217
217
|
}
|
|
218
218
|
.dark {
|
|
219
|
-
--background:
|
|
219
|
+
--background: 249 23% 11%;
|
|
220
|
+
--border: 240 18% 18%;
|
|
220
221
|
--foreground: 210 20% 98%;
|
|
221
222
|
--card: 224 71.4% 4.1%;
|
|
222
223
|
--card-foreground: 210 20% 98%;
|
|
@@ -232,7 +233,6 @@ body,
|
|
|
232
233
|
--accent-foreground: 210 20% 98%;
|
|
233
234
|
--destructive: 0 62.8% 30.6%;
|
|
234
235
|
--destructive-foreground: 210 20% 98%;
|
|
235
|
-
--border: 215 27.9% 16.9%;
|
|
236
236
|
--input: 215 27.9% 16.9%;
|
|
237
237
|
--ring: 216 12.2% 83.9%;
|
|
238
238
|
--chart-1: 220 70% 50%;
|
|
@@ -249,6 +249,19 @@ body,
|
|
|
249
249
|
--sidebar-border: 240 3.7% 15.9%;
|
|
250
250
|
--sidebar-ring: 217.2 91.2% 59.8%;
|
|
251
251
|
}
|
|
252
|
+
|
|
253
|
+
.text-md {
|
|
254
|
+
font-size: 14px;
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
.text-lg {
|
|
258
|
+
font-size: 16px !important;
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
* {
|
|
262
|
+
-webkit-font-smoothing: antialiased;
|
|
263
|
+
-moz-osx-font-smoothing: grayscale;
|
|
264
|
+
}
|
|
252
265
|
}
|
|
253
266
|
|
|
254
267
|
@layer base {
|
|
@@ -1,7 +1,5 @@
|
|
|
1
1
|
import { PropsWithChildren } from 'react';
|
|
2
2
|
import { ApiNodeProps } from './node-props';
|
|
3
|
-
type Props = PropsWithChildren<ApiNodeProps
|
|
4
|
-
|
|
5
|
-
}>;
|
|
6
|
-
export declare const ApiNode: ({ data, children, excludePubsub }: Props) => import("react/jsx-runtime").JSX.Element;
|
|
3
|
+
type Props = PropsWithChildren<ApiNodeProps>;
|
|
4
|
+
export declare const ApiNode: ({ data, children }: Props) => import("react/jsx-runtime").JSX.Element;
|
|
7
5
|
export {};
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
2
|
import { Webhook } from 'lucide-react';
|
|
3
3
|
import { BaseNode } from './base-node';
|
|
4
|
-
import {
|
|
5
|
-
export const ApiNode = ({ data, children
|
|
6
|
-
return (_jsxs(BaseNode, { variant: "api", title: data.name, disableSourceHandle: !data.emits?.length && !data.virtualEmits?.length, disableTargetHandle: !data.subscribes?.length && !data.virtualSubscribes?.length, children: [data.description && _jsx("div", { className: "text-sm
|
|
4
|
+
import { DetailItem, NodeDetails } from './node-details';
|
|
5
|
+
export const ApiNode = ({ data, children }) => {
|
|
6
|
+
return (_jsxs(BaseNode, { variant: "api", title: data.name, language: data.language, disableSourceHandle: !data.emits?.length && !data.virtualEmits?.length, disableTargetHandle: !data.subscribes?.length && !data.virtualSubscribes?.length, children: [data.description && _jsx("div", { className: "text-sm text-white/60", children: data.description }), children, data.webhookUrl && (_jsxs("div", { className: "flex gap-1 items-center text-xs text-white/60", children: [_jsx(Webhook, { className: "w-3 h-3 text-white/40" }), _jsx("div", { className: "font-mono", children: data.webhookUrl })] })), _jsx(NodeDetails, { type: "api", name: data.name, subscribes: data.subscribes, emits: data.emits, description: data.description, language: data.language, children: _jsx(DetailItem, { label: "Webhook URL", children: _jsxs("div", { className: "flex gap-1 items-center text-xs text-white/60", children: [_jsx(Webhook, { className: "w-3 h-3 text-white/40" }), _jsx("div", { className: "font-mono", children: data.webhookUrl })] }) }) })] }));
|
|
7
7
|
};
|
|
@@ -2,6 +2,7 @@ import React, { HTMLAttributes } from 'react';
|
|
|
2
2
|
import { HandleProps } from '@xyflow/react';
|
|
3
3
|
type Props = HandleProps & Omit<HTMLAttributes<HTMLDivElement>, 'id'> & {
|
|
4
4
|
isHidden?: boolean;
|
|
5
|
+
variant?: string | null;
|
|
5
6
|
};
|
|
6
7
|
export declare const BaseHandle: React.FC<Props>;
|
|
7
8
|
export {};
|
|
@@ -1,7 +1,10 @@
|
|
|
1
1
|
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
2
|
import { Position, Handle as RFHandle } from '@xyflow/react';
|
|
3
3
|
import clsx from 'clsx';
|
|
4
|
+
import { colorMap } from './colorMap';
|
|
4
5
|
export const BaseHandle = (props) => {
|
|
5
|
-
const { isHidden, position, ...rest } = props;
|
|
6
|
-
return (_jsx("div", { className: clsx('absolute w-
|
|
6
|
+
const { isHidden, position, variant, ...rest } = props;
|
|
7
|
+
return (_jsx("div", { className: clsx('absolute w-[6px] h-[6px]', position === Position.Top && '-top-[20px]', position === Position.Bottom && '-bottom-[20px]', 'left-1/2 -ml-[2px]', isHidden && 'hidden'), children: _jsx(RFHandle, { ...rest, position: position, style: {
|
|
8
|
+
background: colorMap[variant],
|
|
9
|
+
}, className: "\n !static\n !w-[6px]\n !h-[6px]\n !min-w-[6px]\n !min-h-[6px]\n !p-0\n !border-none\n !transform-none\n !rounded-full\n !outline-none\n !shadow-none\n " }) }));
|
|
7
10
|
};
|
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
import { type VariantProps } from 'class-variance-authority';
|
|
2
2
|
import React, { PropsWithChildren } from 'react';
|
|
3
|
-
declare const
|
|
3
|
+
declare const baseDot: (props?: ({
|
|
4
4
|
variant?: "event" | "api" | "noop" | "cron" | null | undefined;
|
|
5
5
|
} & import("class-variance-authority/types").ClassProp) | undefined) => string;
|
|
6
6
|
type Props = PropsWithChildren<{
|
|
7
|
-
variant?: VariantProps<typeof baseBackgroundVariants>['variant'];
|
|
8
7
|
title: string;
|
|
8
|
+
variant: VariantProps<typeof baseDot>['variant'];
|
|
9
|
+
language?: string;
|
|
9
10
|
headerChildren?: React.ReactNode;
|
|
10
11
|
className?: string;
|
|
11
12
|
disableSourceHandle?: boolean;
|
|
@@ -3,19 +3,23 @@ import { cn } from '@/lib/utils';
|
|
|
3
3
|
import { Position } from '@xyflow/react';
|
|
4
4
|
import { cva } from 'class-variance-authority';
|
|
5
5
|
import { BaseHandle } from './base-handle';
|
|
6
|
-
|
|
7
|
-
|
|
6
|
+
import { LanguageIndicator } from '../views/flow/nodes/language-indicator';
|
|
7
|
+
import { colorMap } from './colorMap';
|
|
8
|
+
const baseDot = cva('w-[6px] h-[6px] rounded-full', {
|
|
8
9
|
variants: {
|
|
9
10
|
variant: {
|
|
10
|
-
event: '
|
|
11
|
-
api: '
|
|
12
|
-
noop: '
|
|
13
|
-
cron: '
|
|
11
|
+
event: 'bg-[rgba(0,117,255,1)]',
|
|
12
|
+
api: 'bg-[rgba(189,255,0,1)]',
|
|
13
|
+
noop: 'bg-[rgba(255,49,234,1)]',
|
|
14
|
+
cron: 'bg-[rgba(255,113,11,1)]',
|
|
14
15
|
},
|
|
15
16
|
},
|
|
16
17
|
});
|
|
17
|
-
const
|
|
18
|
+
const Dot = ({ variant }) => (_jsx("div", { className: cn(baseDot({ variant })) }));
|
|
19
|
+
const HeaderBar = ({ text, variant, children, }) => (_jsxs("div", { className: "text-sm text-white flex justify-between items-center gap-4", children: [_jsxs("div", { className: "flex items-center gap-2", children: [_jsx(Dot, { variant: variant }), _jsx("span", { children: text })] }), children] }));
|
|
18
20
|
export const BaseNode = (props) => {
|
|
19
|
-
const { title, variant,
|
|
20
|
-
return (
|
|
21
|
+
const { title, variant, children, disableSourceHandle, disableTargetHandle, language } = props;
|
|
22
|
+
return (_jsx("div", { className: "p-[1px] rounded-lg shadow-[0px_7px_14px_0px_rgba(7,0,23,0.98)] max-w-[350px] ", style: {
|
|
23
|
+
background: `linear-gradient(100.74deg, rgba(0, 71, 255, 0) -2.15%, ${colorMap[variant]} 45.08%, rgba(0, 71, 255, 0) 96.79%)`,
|
|
24
|
+
}, children: _jsx("div", { className: "rounded-lg bg-[#060014] p-4", children: _jsxs("div", { className: "group relative", children: [_jsx(HeaderBar, { text: title, variant: variant, children: _jsx(LanguageIndicator, { language: language }) }), _jsx("div", { className: "pt-4 space-y-3", children: children }), !disableTargetHandle && _jsx(BaseHandle, { type: "target", position: Position.Top, variant: variant }), !disableSourceHandle && _jsx(BaseHandle, { type: "source", position: Position.Bottom, variant: variant }), _jsx("div", { className: "absolute inset-0 -z-10 translate-y-1 translate-x-1 bg-black/20 rounded-md border border-white/5" })] }) }) }));
|
|
21
25
|
};
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { type VariantProps } from 'class-variance-authority';
|
|
2
|
+
import React from 'react';
|
|
3
|
+
declare const baseDot: (props?: ({
|
|
4
|
+
variant?: "event" | "api" | "noop" | "cron" | null | undefined;
|
|
5
|
+
} & import("class-variance-authority/types").ClassProp) | undefined) => string;
|
|
6
|
+
export declare const HeaderBar: ({ text, variant, children, }: {
|
|
7
|
+
text: string;
|
|
8
|
+
variant: VariantProps<typeof baseDot>["variant"];
|
|
9
|
+
children?: React.ReactNode;
|
|
10
|
+
}) => import("react/jsx-runtime").JSX.Element;
|
|
11
|
+
export {};
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { cn } from '@/lib/utils';
|
|
3
|
+
import { cva } from 'class-variance-authority';
|
|
4
|
+
const baseDot = cva('w-[6px] h-[6px] rounded-full', {
|
|
5
|
+
variants: {
|
|
6
|
+
variant: {
|
|
7
|
+
event: 'bg-[rgba(0,117,255,1)]',
|
|
8
|
+
api: 'bg-[rgba(189,255,0,1)]',
|
|
9
|
+
noop: 'bg-[rgba(255,49,234,1)]',
|
|
10
|
+
cron: 'bg-[rgba(255,113,11,1)]',
|
|
11
|
+
},
|
|
12
|
+
},
|
|
13
|
+
});
|
|
14
|
+
const Dot = ({ variant }) => (_jsx("div", { className: cn(baseDot({ variant })) }));
|
|
15
|
+
export const HeaderBar = ({ text, variant, children, }) => (_jsxs("div", { className: "text-sm text-white flex justify-between items-center gap-4", children: [_jsxs("div", { className: "flex items-center gap-2", children: [_jsx(Dot, { variant: variant }), _jsx("span", { children: text })] }), children] }));
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
2
|
import { BaseNode } from './base-node';
|
|
3
|
-
import { Emits } from './emits';
|
|
4
3
|
import { Clock } from 'lucide-react';
|
|
4
|
+
import { NodeDetails, DetailItem } from './node-details';
|
|
5
5
|
export const CronNode = ({ data }) => {
|
|
6
|
-
return (_jsxs(BaseNode, { variant: "cron", title: data.name, headerChildren: _jsx(Clock, { className: "w-4 h-4 text-purple-400" }), disableTargetHandle: !data.virtualSubscribes?.length, disableSourceHandle: !data.virtualEmits?.length, children: [_jsx("div", { className: "text-sm text-white/70", children: data.description }), _jsxs("div", { className: "text-xs text-white/50 flex items-center gap-2", children: [_jsx(Clock, { className: "w-3 h-3" }), " ", data.cronExpression] }), _jsx(
|
|
6
|
+
return (_jsxs(BaseNode, { variant: "cron", title: data.name, language: data.language, headerChildren: _jsx(Clock, { className: "w-4 h-4 text-purple-400" }), disableTargetHandle: !data.virtualSubscribes?.length, disableSourceHandle: !data.virtualEmits?.length && !data.emits?.length, children: [_jsx("div", { className: "text-sm text-white/70", children: data.description }), _jsxs("div", { className: "text-xs text-white/50 flex items-center gap-2", children: [_jsx(Clock, { className: "w-3 h-3" }), " ", data.cronExpression] }), _jsx(NodeDetails, { type: "cron", name: data.name, description: data.description, language: data.language, children: _jsx(DetailItem, { label: "Cron Expression", children: _jsx("div", { className: "flex gap-1 items-center text-xs text-white/60", children: _jsxs("div", { className: "text-xs text-white/50 flex items-center gap-2", children: [_jsx(Clock, { className: "w-3 h-3" }), " ", data.cronExpression] }) }) }) })] }));
|
|
7
7
|
};
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
2
2
|
import { Send } from 'lucide-react';
|
|
3
|
-
const
|
|
3
|
+
const toTopic = (emit) => typeof emit === 'string' ? emit : emit.topic;
|
|
4
4
|
export const Emits = ({ emits }) => {
|
|
5
|
-
return (_jsx(_Fragment, { children: emits.map((emit) => (_jsxs("div", { className: "flex gap-2 items-center text-xs text-white/60", "data-testid": `emits__${
|
|
5
|
+
return (_jsx(_Fragment, { children: emits.map((emit) => (_jsxs("div", { className: "flex gap-2 items-center text-xs text-white/60", "data-testid": `emits__${toTopic(emit)}`, children: [_jsx(Send, { className: "w-4 h-4 text-white/40" }), _jsx("div", { className: "font-mono tracking-wider", children: toTopic(emit) })] }, toTopic(emit)))) }));
|
|
6
6
|
};
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
import { PropsWithChildren } from 'react';
|
|
2
2
|
import { EventNodeProps } from './node-props';
|
|
3
3
|
type Props = PropsWithChildren<EventNodeProps & {
|
|
4
|
-
excludePubsub?: boolean;
|
|
5
4
|
className?: string;
|
|
6
5
|
}>;
|
|
7
6
|
export declare const EventNode: (props: Props) => import("react/jsx-runtime").JSX.Element;
|
|
@@ -1,9 +1,7 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
-
import { LanguageIndicator } from '../views/flow/nodes/language-indicator';
|
|
3
2
|
import { BaseNode } from './base-node';
|
|
4
|
-
import {
|
|
5
|
-
import { Subscribe } from './subscribe';
|
|
3
|
+
import { NodeDetails } from './node-details';
|
|
6
4
|
export const EventNode = (props) => {
|
|
7
|
-
const { data,
|
|
8
|
-
return (_jsxs(BaseNode, { variant: "event", title: data.name, disableSourceHandle: !data.emits.length && !data.virtualEmits?.length, disableTargetHandle: !data.subscribes.length && !data.virtualSubscribes?.length,
|
|
5
|
+
const { data, children } = props;
|
|
6
|
+
return (_jsxs(BaseNode, { variant: "event", title: data.name, language: data.language, disableSourceHandle: !data.emits.length && !data.virtualEmits?.length, disableTargetHandle: !data.subscribes.length && !data.virtualSubscribes?.length, children: [children, _jsx(NodeDetails, { type: "event", name: data.name, subscribes: data.subscribes, emits: data.emits, description: data.description, language: data.language })] }));
|
|
9
7
|
};
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import React, { PropsWithChildren } from 'react';
|
|
2
|
+
type Props = PropsWithChildren<{
|
|
3
|
+
name: string;
|
|
4
|
+
type: 'event' | 'api' | 'noop' | 'cron';
|
|
5
|
+
subscribes?: string[];
|
|
6
|
+
emits?: Array<string | {
|
|
7
|
+
topic: string;
|
|
8
|
+
label?: string;
|
|
9
|
+
}>;
|
|
10
|
+
description?: string;
|
|
11
|
+
language?: string;
|
|
12
|
+
}>;
|
|
13
|
+
export declare const DetailItem: React.FC<PropsWithChildren<{
|
|
14
|
+
label: string;
|
|
15
|
+
}>>;
|
|
16
|
+
export declare const NodeDetails: React.FC<Props>;
|
|
17
|
+
export {};
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { ChevronRight } from 'lucide-react';
|
|
3
|
+
import { DialogDescription, DialogHeader, DialogTitle, DialogTrigger } from '../components/ui/dialog';
|
|
4
|
+
import { DialogContent } from '../components/ui/dialog';
|
|
5
|
+
import { Dialog } from '../components/ui/dialog';
|
|
6
|
+
import { Label } from '../components/ui/label';
|
|
7
|
+
import { LanguageIndicator } from '../views/flow/nodes/language-indicator';
|
|
8
|
+
import { Emits } from './emits';
|
|
9
|
+
import { Subscribe } from './subscribe';
|
|
10
|
+
import { colorMap } from './colorMap';
|
|
11
|
+
import { HeaderBar } from './components/header-bar';
|
|
12
|
+
export const DetailItem = (props) => {
|
|
13
|
+
const { label, children } = props;
|
|
14
|
+
return (_jsxs("div", { className: "flex flex-col gap-2", children: [_jsx(Label, { className: "text-white", children: label }), children] }));
|
|
15
|
+
};
|
|
16
|
+
export const NodeDetails = (props) => {
|
|
17
|
+
const { name, type, subscribes, emits, description, language, children } = props;
|
|
18
|
+
return (_jsxs(Dialog, { children: [_jsx(DialogTrigger, { asChild: true, children: _jsx("div", { className: "flex justify-end gap-2", children: _jsx("div", { className: "border border-solid border-white/10 p-1 rounded-md cursor-pointer", children: _jsx(ChevronRight, { className: "w-4 h-4" }) }) }) }), _jsxs(DialogContent, { className: "border border-solid", style: { borderColor: colorMap[type] }, children: [_jsx(DialogHeader, { children: _jsx(DialogTitle, { children: _jsx(HeaderBar, { variant: type, text: name }) }) }), _jsx(DialogDescription, { children: _jsxs("div", { className: "flex flex-col gap-6", children: [description && (_jsx(DetailItem, { label: "Description", children: _jsx("span", { className: "text-sm text-white/60", children: description }) })), _jsx(DetailItem, { label: "Language", children: _jsxs("div", { className: "flex items-center gap-2", children: [_jsx(LanguageIndicator, { language: language }), _jsx("span", { className: "capitalize", children: language })] }) }), subscribes && (_jsx(DetailItem, { label: "Subscribes", children: _jsx(Subscribe, { subscribes: subscribes }) })), emits && (_jsx(DetailItem, { label: "Emits", children: _jsx(Emits, { emits: emits }) })), children] }) })] })] }));
|
|
19
|
+
};
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
2
2
|
import { Eye } from 'lucide-react';
|
|
3
|
-
export const Subscribe = ({
|
|
4
|
-
return (_jsx(_Fragment, { children:
|
|
3
|
+
export const Subscribe = ({ subscribes }) => {
|
|
4
|
+
return (_jsx(_Fragment, { children: subscribes.map((subscribe) => (_jsxs("div", { className: "flex gap-2 items-center text-xs text-white/60", "data-testid": `subscribes__${subscribe}`, children: [_jsx(Eye, { className: "w-4 h-4 text-white/40" }), _jsx("div", { className: "font-mono tracking-wider", children: subscribe })] }, subscribe))) }));
|
|
5
5
|
};
|
|
@@ -1,4 +1,4 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
2
|
import { AppSidebar } from './components/app-sidebar';
|
|
3
|
-
import {
|
|
4
|
-
export const RouteWrapper = ({ children }) => (_jsxs(
|
|
3
|
+
import { ReactFlowProvider } from '@xyflow/react';
|
|
4
|
+
export const RouteWrapper = ({ children }) => (_jsx("div", { className: "flex flex-row", children: _jsxs(ReactFlowProvider, { children: [_jsx(AppSidebar, {}), _jsx("div", { className: "flex-1", children: children })] }) }));
|
package/dist/src/routes/flow.js
CHANGED
|
@@ -7,14 +7,18 @@ export const Flow = () => {
|
|
|
7
7
|
const { id } = useParams();
|
|
8
8
|
const flowId = id;
|
|
9
9
|
const [flow, setFlow] = useState(null);
|
|
10
|
+
const [flowConfig, setFlowConfig] = useState(null);
|
|
10
11
|
const fetchFlow = useCallback(() => {
|
|
11
|
-
fetch(`/flows/${flowId}`)
|
|
12
|
-
.then((
|
|
13
|
-
.then((flow) =>
|
|
12
|
+
Promise.all([fetch(`/flows/${flowId}`), fetch(`/flows/${flowId}/config`)])
|
|
13
|
+
.then(([flowRes, configRes]) => Promise.all([flowRes.json(), configRes.json()]))
|
|
14
|
+
.then(([flow, config]) => {
|
|
15
|
+
setFlow(flow);
|
|
16
|
+
setFlowConfig(config);
|
|
17
|
+
});
|
|
14
18
|
}, [flowId]);
|
|
15
19
|
useEffect(fetchFlow, [fetchFlow]);
|
|
16
20
|
useFlowUpdateListener(flowId, fetchFlow);
|
|
17
21
|
if (!flow)
|
|
18
22
|
return null;
|
|
19
|
-
return (_jsx("div", { className: "w-
|
|
23
|
+
return (_jsx("div", { className: "w-full h-screen", children: _jsx(FlowView, { flow: flow, flowConfig: flowConfig }) }));
|
|
20
24
|
};
|
package/dist/src/routes/index.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import { jsx as _jsx } from "react/jsx-runtime";
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { Button } from '../components/ui/button';
|
|
2
3
|
export const Index = () => {
|
|
3
|
-
return (
|
|
4
|
+
return (_jsxs("div", { className: "flex flex-col items-center justify-center w-full h-screen gap-10 bg-gradient-to-r from-black to-gray-900", children: [_jsx("h1", { className: "text-5xl font-extrabold max-w-[600px] text-center", children: "Code-first framework for intelligent workflows" }), _jsx("div", { className: "max-w-[600px] text-center text-xl font-medium text-gray-400", children: "Write in any language. Automate anything. From AI agents to backend automation, Motia runs event-driven workflows with zero overhead." }), _jsx("div", { className: "p-[1px] min-w-[600px] rounded-lg shadow-[0px_7px_14px_0px_rgba(7,0,23,0.98),0px_26px_26px_0px_rgba(7,0,23,0.85),0px_59px_35px_0px_rgba(7,0,23,0.5),0px_105px_42px_0px_rgba(7,0,23,0.15),0px_164px_46px_0px_rgba(7,0,23,0.02)]", children: _jsx("div", { className: "rounded-lg bg-black p-8 font-semibold text-xl min-h-[100px] flex items-center", children: _jsxs("div", { className: "flex items-center gap-2 font-mono", children: [_jsx("span", { className: "text-[rgb(0,117,255)]", children: "$" }), _jsx("span", { className: "text-white", children: "npx motia generate step" })] }) }) }), _jsxs("div", { className: "flex flex-col gap-8 items-center", children: [_jsx("span", { className: "text-gray-400 text-xl", children: "or" }), _jsx("a", { href: "https://motia.dev/docs", target: "_blank", children: _jsx(Button, { size: "lg", className: "text-xl py-6 px-8", children: "Read developer docs" }) })] })] }));
|
|
4
5
|
};
|
|
@@ -5,7 +5,7 @@ import { cn } from '@/lib/utils';
|
|
|
5
5
|
const labelVariants = cva('absolute pointer-events-all text-cs border p-1 px-2', {
|
|
6
6
|
variants: {
|
|
7
7
|
color: {
|
|
8
|
-
default: 'border-[#b3b3b3] bg-
|
|
8
|
+
default: 'border-[#b3b3b3] bg-[#060014] text-gray-100 font-semibold border-solid rounded-full',
|
|
9
9
|
conditional: 'bg-amber-300 border-amber-950 text-amber-950 border-solid font-semibold italic rounded-lg',
|
|
10
10
|
},
|
|
11
11
|
},
|
|
@@ -28,8 +28,8 @@ export const BaseEdge = (props) => {
|
|
|
28
28
|
offset: 10,
|
|
29
29
|
});
|
|
30
30
|
return (_jsxs(_Fragment, { children: [_jsx(BaseReactFlowEdge, { path: edgePath, style: {
|
|
31
|
-
stroke: data?.variant === 'virtual' ? 'rgb(
|
|
32
|
-
strokeWidth:
|
|
31
|
+
stroke: data?.variant === 'virtual' ? 'rgb(111, 111, 111)' : '#0094FF',
|
|
32
|
+
strokeWidth: 2,
|
|
33
33
|
shapeRendering: 'geometricPrecision',
|
|
34
34
|
fill: 'none',
|
|
35
35
|
mixBlendMode: 'screen',
|
|
@@ -1,8 +1,13 @@
|
|
|
1
|
-
import '@xyflow/react/dist/style.css';
|
|
2
1
|
import React from 'react';
|
|
3
|
-
import {
|
|
2
|
+
import { EdgeData, NodeData } from './nodes/nodes.types';
|
|
3
|
+
import { FlowConfigResponse, FlowResponse } from './hooks/use-get-flow-state';
|
|
4
|
+
import { Node as ReactFlowNode, Edge as ReactFlowEdge } from '@xyflow/react';
|
|
5
|
+
import '@xyflow/react/dist/style.css';
|
|
6
|
+
export type FlowNode = ReactFlowNode<NodeData>;
|
|
7
|
+
export type FlowEdge = ReactFlowEdge<EdgeData>;
|
|
4
8
|
type Props = {
|
|
5
9
|
flow: FlowResponse;
|
|
10
|
+
flowConfig: FlowConfigResponse;
|
|
6
11
|
};
|
|
7
12
|
export declare const FlowView: React.FC<Props>;
|
|
8
13
|
export {};
|
|
@@ -1,20 +1,25 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
// packages/workbench/src/views/flow/flow-view.tsx
|
|
2
3
|
import { LogConsole } from '@/components/logs/log-console';
|
|
3
|
-
import { Background, BackgroundVariant, ReactFlow } from '@xyflow/react';
|
|
4
|
-
import '@xyflow/react/dist/style.css';
|
|
4
|
+
import { Background, BackgroundVariant, ReactFlow, useReactFlow } from '@xyflow/react';
|
|
5
5
|
import { useCallback, useEffect, useState } from 'react';
|
|
6
6
|
import { ArrowHead } from './arrow-head';
|
|
7
7
|
import { BaseEdge } from './base-edge';
|
|
8
8
|
import { FlowLoader } from './flow-loader';
|
|
9
9
|
import { useGetFlowState } from './hooks/use-get-flow-state';
|
|
10
10
|
import { Legend } from './legend';
|
|
11
|
+
import { useSaveWorkflowConfig } from './hooks/use-save-workflow-config';
|
|
11
12
|
import { NodeOrganizer } from './node-organizer';
|
|
13
|
+
import { useDebounced } from '@/hooks/use-debounced';
|
|
14
|
+
import '@xyflow/react/dist/style.css';
|
|
12
15
|
const edgeTypes = {
|
|
13
16
|
base: BaseEdge,
|
|
14
17
|
};
|
|
15
|
-
export const FlowView = ({ flow }) => {
|
|
16
|
-
const { nodes, edges, onNodesChange, onEdgesChange, nodeTypes } = useGetFlowState(flow);
|
|
18
|
+
export const FlowView = ({ flow, flowConfig }) => {
|
|
19
|
+
const { nodes, edges, onNodesChange, onEdgesChange, nodeTypes } = useGetFlowState(flow, flowConfig);
|
|
17
20
|
const [initialized, setInitialized] = useState(false);
|
|
21
|
+
const { getNodes, getEdges } = useReactFlow();
|
|
22
|
+
const { saveConfig } = useSaveWorkflowConfig(flow.id);
|
|
18
23
|
const [hoveredType, setHoveredType] = useState(null);
|
|
19
24
|
useEffect(() => setInitialized(false), [flow]);
|
|
20
25
|
const onInitialized = useCallback(() => {
|
|
@@ -39,8 +44,16 @@ export const FlowView = ({ flow }) => {
|
|
|
39
44
|
...edge,
|
|
40
45
|
className: getClassName(), // No argument means it's an edge
|
|
41
46
|
}));
|
|
47
|
+
const saveFlowConfig = useCallback(() => {
|
|
48
|
+
return saveConfig({ steps: getNodes(), edges: getEdges() });
|
|
49
|
+
}, [saveConfig, getNodes, getEdges]);
|
|
50
|
+
const debouncedSaveConfig = useDebounced(saveFlowConfig);
|
|
51
|
+
const onNodesChangeHandler = useCallback((changes) => {
|
|
52
|
+
onNodesChange(changes);
|
|
53
|
+
debouncedSaveConfig();
|
|
54
|
+
}, [onNodesChange, debouncedSaveConfig]);
|
|
42
55
|
if (!nodeTypes) {
|
|
43
56
|
return null;
|
|
44
57
|
}
|
|
45
|
-
return (_jsxs("div", { className: "w-full h-full relative bg-black", children: [!initialized && _jsx(FlowLoader, {}), _jsx(Legend, { onHover: setHoveredType }), _jsxs(ReactFlow, { nodes: nodesWithHighlights, edges: edgesWithHighlights, nodeTypes: nodeTypes, edgeTypes: edgeTypes, onNodesChange:
|
|
58
|
+
return (_jsxs("div", { className: "w-full h-full relative bg-black", children: [!initialized && _jsx(FlowLoader, {}), _jsx(Legend, { onHover: setHoveredType }), _jsxs(ReactFlow, { nodes: nodesWithHighlights, edges: edgesWithHighlights, nodeTypes: nodeTypes, edgeTypes: edgeTypes, onNodesChange: onNodesChangeHandler, onEdgesChange: onEdgesChange, children: [_jsx(Background, { variant: BackgroundVariant.Dots, gap: 20, size: 1, color: "#99a", bgColor: "#1d1c2a" }), _jsx(NodeOrganizer, { onInitialized: onInitialized }), _jsx("svg", { children: _jsx("defs", { children: _jsx(ArrowHead, { color: "#B3B3B3", id: "arrowhead" }) }) })] }), _jsx(LogConsole, {})] }));
|
|
46
59
|
};
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { Edge, Node } from '@xyflow/react';
|
|
2
2
|
import React from 'react';
|
|
3
|
-
import type { EdgeData, NodeData } from '../nodes/nodes.types';
|
|
3
|
+
import type { EdgeData, FlowNodeData, NodeData } from '../nodes/nodes.types';
|
|
4
4
|
type Emit = string | {
|
|
5
|
-
|
|
5
|
+
topic: string;
|
|
6
6
|
label?: string;
|
|
7
7
|
};
|
|
8
8
|
type FlowStep = {
|
|
@@ -24,13 +24,17 @@ export type FlowResponse = {
|
|
|
24
24
|
steps: FlowStep[];
|
|
25
25
|
edges: FlowEdge[];
|
|
26
26
|
};
|
|
27
|
+
export type FlowConfigResponse = {
|
|
28
|
+
steps: FlowNodeData[];
|
|
29
|
+
edges: FlowEdge[];
|
|
30
|
+
};
|
|
27
31
|
type FlowEdge = {
|
|
28
32
|
id: string;
|
|
29
33
|
source: string;
|
|
30
34
|
target: string;
|
|
31
35
|
data: EdgeData;
|
|
32
36
|
};
|
|
33
|
-
export declare const useGetFlowState: (flow: FlowResponse) => {
|
|
37
|
+
export declare const useGetFlowState: (flow: FlowResponse, flowConfig: FlowConfigResponse) => {
|
|
34
38
|
nodes: Node<NodeData>[];
|
|
35
39
|
edges: Edge<EdgeData>[];
|
|
36
40
|
onNodesChange: import("@xyflow/react").OnNodesChange<Node<NodeData>>;
|
|
@@ -4,7 +4,11 @@ import { ApiFlowNode } from '../nodes/api-flow-node';
|
|
|
4
4
|
import { NoopFlowNode } from '../nodes/noop-flow-node';
|
|
5
5
|
import { EventFlowNode } from '../nodes/event-flow-node';
|
|
6
6
|
import { CronNode } from '@/publicComponents/cron-node';
|
|
7
|
-
|
|
7
|
+
const getNodePosition = (flowConfig, stepName) => {
|
|
8
|
+
const configStep = flowConfig?.steps?.find((step) => step.data?.name === stepName);
|
|
9
|
+
return configStep?.position || { x: 0, y: 0 };
|
|
10
|
+
};
|
|
11
|
+
async function importFlow(flow, flowConfig) {
|
|
8
12
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
9
13
|
const nodeTypes = {
|
|
10
14
|
event: EventFlowNode,
|
|
@@ -23,7 +27,7 @@ async function importFlow(flow) {
|
|
|
23
27
|
const nodes = flow.steps.map((step) => ({
|
|
24
28
|
id: step.id,
|
|
25
29
|
type: step.nodeComponentPath ? step.nodeComponentPath : step.type,
|
|
26
|
-
position:
|
|
30
|
+
position: getNodePosition(flowConfig, step.name),
|
|
27
31
|
data: step,
|
|
28
32
|
language: step.language,
|
|
29
33
|
}));
|
|
@@ -34,7 +38,7 @@ async function importFlow(flow) {
|
|
|
34
38
|
}));
|
|
35
39
|
return { nodes, edges, nodeTypes };
|
|
36
40
|
}
|
|
37
|
-
export const useGetFlowState = (flow) => {
|
|
41
|
+
export const useGetFlowState = (flow, flowConfig) => {
|
|
38
42
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
39
43
|
const [nodeTypes, setNodeTypes] = useState();
|
|
40
44
|
const [nodes, setNodes, onNodesChange] = useNodesState([]);
|
|
@@ -42,11 +46,11 @@ export const useGetFlowState = (flow) => {
|
|
|
42
46
|
useEffect(() => {
|
|
43
47
|
if (!flow)
|
|
44
48
|
return;
|
|
45
|
-
importFlow(flow).then(({ nodes, edges, nodeTypes }) => {
|
|
49
|
+
importFlow(flow, flowConfig).then(({ nodes, edges, nodeTypes }) => {
|
|
46
50
|
setNodes(nodes);
|
|
47
51
|
setEdges(edges);
|
|
48
52
|
setNodeTypes(nodeTypes);
|
|
49
53
|
});
|
|
50
|
-
}, [flow, setNodes, setEdges, setNodeTypes]);
|
|
54
|
+
}, [flow, flowConfig, setNodes, setEdges, setNodeTypes]);
|
|
51
55
|
return { nodes, edges, onNodesChange, onEdgesChange, nodeTypes };
|
|
52
56
|
};
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { useCallback } from 'react';
|
|
2
|
+
export const useSaveWorkflowConfig = (flowId) => {
|
|
3
|
+
const saveConfig = useCallback(async (config) => {
|
|
4
|
+
try {
|
|
5
|
+
const response = await fetch(`/flows/${flowId}/config`, {
|
|
6
|
+
method: 'POST',
|
|
7
|
+
headers: {
|
|
8
|
+
'Content-Type': 'application/json',
|
|
9
|
+
},
|
|
10
|
+
body: JSON.stringify(config),
|
|
11
|
+
});
|
|
12
|
+
if (!response.ok) {
|
|
13
|
+
throw new Error(`Failed to save config: ${response.statusText}`);
|
|
14
|
+
}
|
|
15
|
+
return await response.json();
|
|
16
|
+
}
|
|
17
|
+
catch (error) {
|
|
18
|
+
console.error('Error saving workflow config:', error);
|
|
19
|
+
throw error;
|
|
20
|
+
}
|
|
21
|
+
}, [flowId]);
|
|
22
|
+
return { saveConfig };
|
|
23
|
+
};
|