@motiadev/workbench 0.0.1
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/README.md +50 -0
- package/components.json +21 -0
- package/dist/.empty +0 -0
- package/dist/assets/index-DGmArPOa.css +1 -0
- package/dist/assets/index-hQsWtfVb.js +182 -0
- package/dist/index.html +20 -0
- package/eslint.config.js +28 -0
- package/index.html +19 -0
- package/index.tsx +10 -0
- package/middleware.ts +46 -0
- package/package.json +56 -0
- package/postcss.config.js +6 -0
- package/public/.empty +0 -0
- package/src/assets/.empty +0 -0
- package/src/components/app-sidebar.tsx +55 -0
- package/src/components/log-console.tsx +76 -0
- package/src/components/log-level-badge.tsx +12 -0
- package/src/components/ui/badge.tsx +31 -0
- package/src/components/ui/button.tsx +47 -0
- package/src/components/ui/collapsible.tsx +9 -0
- package/src/components/ui/dialog.tsx +120 -0
- package/src/components/ui/input.tsx +21 -0
- package/src/components/ui/label.tsx +26 -0
- package/src/components/ui/select.tsx +157 -0
- package/src/components/ui/separator.tsx +22 -0
- package/src/components/ui/sheet.tsx +106 -0
- package/src/components/ui/sidebar.tsx +637 -0
- package/src/components/ui/skeleton.tsx +7 -0
- package/src/components/ui/switch.tsx +27 -0
- package/src/components/ui/table.tsx +76 -0
- package/src/components/ui/textarea.tsx +22 -0
- package/src/components/ui/tooltip.tsx +32 -0
- package/src/hooks/use-list-flows.tsx +20 -0
- package/src/hooks/use-log-listener.tsx +32 -0
- package/src/hooks/use-mobile.tsx +19 -0
- package/src/index.css +190 -0
- package/src/lib/utils.ts +6 -0
- package/src/main.tsx +28 -0
- package/src/publicComponents/api-node.tsx +28 -0
- package/src/publicComponents/base-handle.tsx +43 -0
- package/src/publicComponents/base-node.tsx +57 -0
- package/src/publicComponents/emits.tsx +22 -0
- package/src/publicComponents/event-node.tsx +36 -0
- package/src/publicComponents/node-props.tsx +15 -0
- package/src/publicComponents/noop-node.tsx +21 -0
- package/src/publicComponents/subscribe.tsx +19 -0
- package/src/route-wrapper.tsx +9 -0
- package/src/routeTree.gen.ts +109 -0
- package/src/routes/__root.tsx +26 -0
- package/src/routes/flow/$id.tsx +21 -0
- package/src/routes/index.tsx +13 -0
- package/src/stores/use-logs.ts +22 -0
- package/src/views/flow/arrow-head.tsx +13 -0
- package/src/views/flow/base-edge.tsx +44 -0
- package/src/views/flow/flow-loader.tsx +3 -0
- package/src/views/flow/flow-view.tsx +72 -0
- package/src/views/flow/hooks/use-get-flow-state.tsx +109 -0
- package/src/views/flow/hooks/use-organize-nodes.ts +60 -0
- package/src/views/flow/legend.tsx +59 -0
- package/src/views/flow/node-organizer.tsx +70 -0
- package/src/views/flow/nodes/api-flow-node.tsx +6 -0
- package/src/views/flow/nodes/event-flow-node.tsx +6 -0
- package/src/views/flow/nodes/json-schema-form.tsx +110 -0
- package/src/views/flow/nodes/language-indicator.tsx +74 -0
- package/src/views/flow/nodes/nodes.types.ts +36 -0
- package/src/views/flow/nodes/noop-flow-node.tsx +6 -0
- package/src/vite-env.d.ts +1 -0
- package/tailwind.config.ts +75 -0
- package/tsconfig.app.json +30 -0
- package/tsconfig.json +13 -0
- package/tsconfig.node.json +22 -0
- package/tsconfig.node.tsbuildinfo +1 -0
- package/vite.config.ts +14 -0
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
import * as React from 'react'
|
|
2
|
+
|
|
3
|
+
import { cn } from '@/lib/utils'
|
|
4
|
+
|
|
5
|
+
const Table = React.forwardRef<HTMLTableElement, React.HTMLAttributes<HTMLTableElement>>(
|
|
6
|
+
({ className, ...props }, ref) => (
|
|
7
|
+
<div className="relative w-full overflow-auto">
|
|
8
|
+
<table ref={ref} className={cn('w-full caption-bottom text-sm', className)} {...props} />
|
|
9
|
+
</div>
|
|
10
|
+
),
|
|
11
|
+
)
|
|
12
|
+
Table.displayName = 'Table'
|
|
13
|
+
|
|
14
|
+
const TableHeader = React.forwardRef<HTMLTableSectionElement, React.HTMLAttributes<HTMLTableSectionElement>>(
|
|
15
|
+
({ className, ...props }, ref) => <thead ref={ref} className={cn('[&_tr]:border-b', className)} {...props} />,
|
|
16
|
+
)
|
|
17
|
+
TableHeader.displayName = 'TableHeader'
|
|
18
|
+
|
|
19
|
+
const TableBody = React.forwardRef<HTMLTableSectionElement, React.HTMLAttributes<HTMLTableSectionElement>>(
|
|
20
|
+
({ className, ...props }, ref) => (
|
|
21
|
+
<tbody ref={ref} className={cn('[&_tr:last-child]:border-0', className)} {...props} />
|
|
22
|
+
),
|
|
23
|
+
)
|
|
24
|
+
TableBody.displayName = 'TableBody'
|
|
25
|
+
|
|
26
|
+
const TableFooter = React.forwardRef<HTMLTableSectionElement, React.HTMLAttributes<HTMLTableSectionElement>>(
|
|
27
|
+
({ className, ...props }, ref) => (
|
|
28
|
+
<tfoot ref={ref} className={cn('border-t bg-muted/50 font-medium [&>tr]:last:border-b-0', className)} {...props} />
|
|
29
|
+
),
|
|
30
|
+
)
|
|
31
|
+
TableFooter.displayName = 'TableFooter'
|
|
32
|
+
|
|
33
|
+
const TableRow = React.forwardRef<HTMLTableRowElement, React.HTMLAttributes<HTMLTableRowElement>>(
|
|
34
|
+
({ className, ...props }, ref) => (
|
|
35
|
+
<tr
|
|
36
|
+
ref={ref}
|
|
37
|
+
className={cn('border-b transition-colors hover:bg-muted/50 data-[state=selected]:bg-muted', className)}
|
|
38
|
+
{...props}
|
|
39
|
+
/>
|
|
40
|
+
),
|
|
41
|
+
)
|
|
42
|
+
TableRow.displayName = 'TableRow'
|
|
43
|
+
|
|
44
|
+
const TableHead = React.forwardRef<HTMLTableCellElement, React.ThHTMLAttributes<HTMLTableCellElement>>(
|
|
45
|
+
({ className, ...props }, ref) => (
|
|
46
|
+
<th
|
|
47
|
+
ref={ref}
|
|
48
|
+
className={cn(
|
|
49
|
+
'h-10 px-2 text-left align-middle font-medium text-muted-foreground [&:has([role=checkbox])]:pr-0 [&>[role=checkbox]]:translate-y-[2px]',
|
|
50
|
+
className,
|
|
51
|
+
)}
|
|
52
|
+
{...props}
|
|
53
|
+
/>
|
|
54
|
+
),
|
|
55
|
+
)
|
|
56
|
+
TableHead.displayName = 'TableHead'
|
|
57
|
+
|
|
58
|
+
const TableCell = React.forwardRef<HTMLTableCellElement, React.TdHTMLAttributes<HTMLTableCellElement>>(
|
|
59
|
+
({ className, ...props }, ref) => (
|
|
60
|
+
<td
|
|
61
|
+
ref={ref}
|
|
62
|
+
className={cn('p-2 align-middle [&:has([role=checkbox])]:pr-0 [&>[role=checkbox]]:translate-y-[2px]', className)}
|
|
63
|
+
{...props}
|
|
64
|
+
/>
|
|
65
|
+
),
|
|
66
|
+
)
|
|
67
|
+
TableCell.displayName = 'TableCell'
|
|
68
|
+
|
|
69
|
+
const TableCaption = React.forwardRef<HTMLTableCaptionElement, React.HTMLAttributes<HTMLTableCaptionElement>>(
|
|
70
|
+
({ className, ...props }, ref) => (
|
|
71
|
+
<caption ref={ref} className={cn('mt-4 text-sm text-muted-foreground', className)} {...props} />
|
|
72
|
+
),
|
|
73
|
+
)
|
|
74
|
+
TableCaption.displayName = 'TableCaption'
|
|
75
|
+
|
|
76
|
+
export { Table, TableHeader, TableBody, TableFooter, TableHead, TableRow, TableCell, TableCaption }
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import * as React from "react"
|
|
2
|
+
|
|
3
|
+
import { cn } from "@/lib/utils"
|
|
4
|
+
|
|
5
|
+
const Textarea = React.forwardRef<
|
|
6
|
+
HTMLTextAreaElement,
|
|
7
|
+
React.ComponentProps<"textarea">
|
|
8
|
+
>(({ className, ...props }, ref) => {
|
|
9
|
+
return (
|
|
10
|
+
<textarea
|
|
11
|
+
className={cn(
|
|
12
|
+
"flex min-h-[60px] w-full rounded-md border border-input bg-transparent px-3 py-2 text-base shadow-sm placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:cursor-not-allowed disabled:opacity-50 md:text-sm",
|
|
13
|
+
className
|
|
14
|
+
)}
|
|
15
|
+
ref={ref}
|
|
16
|
+
{...props}
|
|
17
|
+
/>
|
|
18
|
+
)
|
|
19
|
+
})
|
|
20
|
+
Textarea.displayName = "Textarea"
|
|
21
|
+
|
|
22
|
+
export { Textarea }
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
'use client'
|
|
2
|
+
|
|
3
|
+
import * as React from 'react'
|
|
4
|
+
import * as TooltipPrimitive from '@radix-ui/react-tooltip'
|
|
5
|
+
|
|
6
|
+
import { cn } from '@/lib/utils'
|
|
7
|
+
|
|
8
|
+
const TooltipProvider = TooltipPrimitive.Provider
|
|
9
|
+
|
|
10
|
+
const Tooltip = TooltipPrimitive.Root
|
|
11
|
+
|
|
12
|
+
const TooltipTrigger = TooltipPrimitive.Trigger
|
|
13
|
+
|
|
14
|
+
const TooltipContent = React.forwardRef<
|
|
15
|
+
React.ElementRef<typeof TooltipPrimitive.Content>,
|
|
16
|
+
React.ComponentPropsWithoutRef<typeof TooltipPrimitive.Content>
|
|
17
|
+
>(({ className, sideOffset = 4, ...props }, ref) => (
|
|
18
|
+
<TooltipPrimitive.Portal>
|
|
19
|
+
<TooltipPrimitive.Content
|
|
20
|
+
ref={ref}
|
|
21
|
+
sideOffset={sideOffset}
|
|
22
|
+
className={cn(
|
|
23
|
+
'z-50 overflow-hidden rounded-md bg-primary px-3 py-1.5 text-xs text-primary-foreground animate-in fade-in-0 zoom-in-95 data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=closed]:zoom-out-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2',
|
|
24
|
+
className,
|
|
25
|
+
)}
|
|
26
|
+
{...props}
|
|
27
|
+
/>
|
|
28
|
+
</TooltipPrimitive.Portal>
|
|
29
|
+
))
|
|
30
|
+
TooltipContent.displayName = TooltipPrimitive.Content.displayName
|
|
31
|
+
|
|
32
|
+
export { Tooltip, TooltipTrigger, TooltipContent, TooltipProvider }
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { useEffect, useState } from 'react'
|
|
2
|
+
|
|
3
|
+
type Flow = {
|
|
4
|
+
id: string
|
|
5
|
+
name: string
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
export const useListFlows = () => {
|
|
9
|
+
const [isLoading, setIsLoading] = useState(true)
|
|
10
|
+
const [flows, setFlows] = useState<Flow[]>([])
|
|
11
|
+
|
|
12
|
+
useEffect(() => {
|
|
13
|
+
fetch('/flows')
|
|
14
|
+
.then((res) => res.json())
|
|
15
|
+
.then(setFlows)
|
|
16
|
+
.finally(() => setIsLoading(false))
|
|
17
|
+
}, [])
|
|
18
|
+
|
|
19
|
+
return { flows, isLoading }
|
|
20
|
+
}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { Log, useLogs } from '@/stores/use-logs'
|
|
2
|
+
import { useState, useEffect } from 'react'
|
|
3
|
+
import { io } from 'socket.io-client'
|
|
4
|
+
|
|
5
|
+
type UseWebSocketReturn = {
|
|
6
|
+
isConnected: boolean
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
const socket = io('/')
|
|
10
|
+
|
|
11
|
+
export const useLogListener = (): UseWebSocketReturn => {
|
|
12
|
+
const [isConnected, setIsConnected] = useState(socket.connected)
|
|
13
|
+
const addLog = useLogs((state) => state.addLog)
|
|
14
|
+
|
|
15
|
+
useEffect(() => {
|
|
16
|
+
const onConnect = () => setIsConnected(true)
|
|
17
|
+
const onDisconnect = () => setIsConnected(false)
|
|
18
|
+
const onLog = (log: Log) => addLog(log)
|
|
19
|
+
|
|
20
|
+
socket.on('connect', onConnect)
|
|
21
|
+
socket.on('disconnect', onDisconnect)
|
|
22
|
+
socket.on('log', onLog)
|
|
23
|
+
|
|
24
|
+
return () => {
|
|
25
|
+
socket.off('connect', onConnect)
|
|
26
|
+
socket.off('disconnect', onDisconnect)
|
|
27
|
+
socket.off('log', onLog)
|
|
28
|
+
}
|
|
29
|
+
}, [addLog])
|
|
30
|
+
|
|
31
|
+
return { isConnected }
|
|
32
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import * as React from 'react'
|
|
2
|
+
|
|
3
|
+
const MOBILE_BREAKPOINT = 768
|
|
4
|
+
|
|
5
|
+
export function useIsMobile() {
|
|
6
|
+
const [isMobile, setIsMobile] = React.useState<boolean | undefined>(undefined)
|
|
7
|
+
|
|
8
|
+
React.useEffect(() => {
|
|
9
|
+
const mql = window.matchMedia(`(max-width: ${MOBILE_BREAKPOINT - 1}px)`)
|
|
10
|
+
const onChange = () => {
|
|
11
|
+
setIsMobile(window.innerWidth < MOBILE_BREAKPOINT)
|
|
12
|
+
}
|
|
13
|
+
mql.addEventListener('change', onChange)
|
|
14
|
+
setIsMobile(window.innerWidth < MOBILE_BREAKPOINT)
|
|
15
|
+
return () => mql.removeEventListener('change', onChange)
|
|
16
|
+
}, [])
|
|
17
|
+
|
|
18
|
+
return !!isMobile
|
|
19
|
+
}
|
package/src/index.css
ADDED
|
@@ -0,0 +1,190 @@
|
|
|
1
|
+
@tailwind base;
|
|
2
|
+
@tailwind components;
|
|
3
|
+
@tailwind utilities;
|
|
4
|
+
|
|
5
|
+
html, body, div, span, applet, object, iframe,
|
|
6
|
+
h1, h2, h3, h4, h5, h6, p, blockquote, pre,
|
|
7
|
+
a, abbr, acronym, address, big, cite, code,
|
|
8
|
+
del, dfn, em, img, ins, kbd, q, s, samp,
|
|
9
|
+
small, strike, strong, sub, sup, tt, var,
|
|
10
|
+
b, u, i, center,
|
|
11
|
+
dl, dt, dd, ol, ul, li,
|
|
12
|
+
fieldset, form, label, legend,
|
|
13
|
+
table, caption, tbody, tfoot, thead, tr, th, td,
|
|
14
|
+
article, aside, canvas, details, embed,
|
|
15
|
+
figure, figcaption, footer, header, hgroup,
|
|
16
|
+
menu, nav, output, ruby, section, summary,
|
|
17
|
+
time, mark, audio, video {
|
|
18
|
+
margin: 0;
|
|
19
|
+
padding: 0;
|
|
20
|
+
border: 0;
|
|
21
|
+
font: inherit;
|
|
22
|
+
vertical-align: baseline;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/* HTML5 display-role reset for older browsers */
|
|
26
|
+
article, aside, details, figcaption, figure,
|
|
27
|
+
footer, header, hgroup, menu, nav, section {
|
|
28
|
+
display: block;
|
|
29
|
+
}
|
|
30
|
+
body {
|
|
31
|
+
line-height: 1;
|
|
32
|
+
}
|
|
33
|
+
ol, ul {
|
|
34
|
+
list-style: none;
|
|
35
|
+
}
|
|
36
|
+
blockquote, q {
|
|
37
|
+
quotes: none;
|
|
38
|
+
}
|
|
39
|
+
blockquote:before, blockquote:after,
|
|
40
|
+
q:before, q:after {
|
|
41
|
+
content: '';
|
|
42
|
+
content: none;
|
|
43
|
+
}
|
|
44
|
+
table {
|
|
45
|
+
border-collapse: collapse;
|
|
46
|
+
border-spacing: 0;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
:root {
|
|
50
|
+
font-family: Inter, system-ui, Avenir, Helvetica, Arial, sans-serif;
|
|
51
|
+
line-height: 1.5;
|
|
52
|
+
font-size: 16px;
|
|
53
|
+
|
|
54
|
+
color-scheme: light dark;
|
|
55
|
+
color: rgba(255, 255, 255, 0.87);
|
|
56
|
+
background-color: #242424;
|
|
57
|
+
|
|
58
|
+
font-synthesis: none;
|
|
59
|
+
text-rendering: optimizeLegibility;
|
|
60
|
+
-webkit-font-smoothing: antialiased;
|
|
61
|
+
-moz-osx-font-smoothing: grayscale;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
:root {
|
|
65
|
+
width: 100%;
|
|
66
|
+
color-scheme: light dark;
|
|
67
|
+
font-synthesis: none;
|
|
68
|
+
text-rendering: optimizeLegibility;
|
|
69
|
+
-webkit-font-smoothing: antialiased;
|
|
70
|
+
-moz-osx-font-smoothing: grayscale;
|
|
71
|
+
font-family: 'PT Sans', serif;
|
|
72
|
+
font-optical-sizing: auto;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
body {
|
|
76
|
+
margin: 0;
|
|
77
|
+
place-items: center;
|
|
78
|
+
min-height: 100dvh;
|
|
79
|
+
width: 100%;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
button, textarea {
|
|
84
|
+
font-family: 'PT Sans', serif;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
strong {
|
|
88
|
+
font-weight: 800;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
body, #root {
|
|
92
|
+
width: 100dvw;
|
|
93
|
+
height: 100dvh;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
.react-flow__attribution {
|
|
97
|
+
display: none;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
@layer base {
|
|
101
|
+
:root {
|
|
102
|
+
--background: 0 0% 100%;
|
|
103
|
+
--foreground: 224 71.4% 4.1%;
|
|
104
|
+
--card: 0 0% 100%;
|
|
105
|
+
--card-foreground: 224 71.4% 4.1%;
|
|
106
|
+
--popover: 0 0% 100%;
|
|
107
|
+
--popover-foreground: 224 71.4% 4.1%;
|
|
108
|
+
--primary: 220.9 39.3% 11%;
|
|
109
|
+
--primary-foreground: 210 20% 98%;
|
|
110
|
+
--secondary: 220 14.3% 95.9%;
|
|
111
|
+
--secondary-foreground: 220.9 39.3% 11%;
|
|
112
|
+
--muted: 220 14.3% 95.9%;
|
|
113
|
+
--muted-foreground: 220 8.9% 46.1%;
|
|
114
|
+
--accent: 220 14.3% 95.9%;
|
|
115
|
+
--accent-foreground: 220.9 39.3% 11%;
|
|
116
|
+
--destructive: 0 84.2% 60.2%;
|
|
117
|
+
--destructive-foreground: 210 20% 98%;
|
|
118
|
+
--border: 220 13% 91%;
|
|
119
|
+
--input: 220 13% 91%;
|
|
120
|
+
--ring: 224 71.4% 4.1%;
|
|
121
|
+
--chart-1: 12 76% 61%;
|
|
122
|
+
--chart-2: 173 58% 39%;
|
|
123
|
+
--chart-3: 197 37% 24%;
|
|
124
|
+
--chart-4: 43 74% 66%;
|
|
125
|
+
--chart-5: 27 87% 67%;
|
|
126
|
+
--radius: 0.5rem;
|
|
127
|
+
--sidebar-background: 0 0% 98%;
|
|
128
|
+
--sidebar-foreground: 240 5.3% 26.1%;
|
|
129
|
+
--sidebar-primary: 240 5.9% 10%;
|
|
130
|
+
--sidebar-primary-foreground: 0 0% 98%;
|
|
131
|
+
--sidebar-accent: 240 4.8% 95.9%;
|
|
132
|
+
--sidebar-accent-foreground: 240 5.9% 10%;
|
|
133
|
+
--sidebar-border: 220 13% 91%;
|
|
134
|
+
--sidebar-ring: 217.2 91.2% 59.8%;
|
|
135
|
+
}
|
|
136
|
+
.dark {
|
|
137
|
+
--background: 224 71.4% 4.1%;
|
|
138
|
+
--foreground: 210 20% 98%;
|
|
139
|
+
--card: 224 71.4% 4.1%;
|
|
140
|
+
--card-foreground: 210 20% 98%;
|
|
141
|
+
--popover: 224 71.4% 4.1%;
|
|
142
|
+
--popover-foreground: 210 20% 98%;
|
|
143
|
+
--primary: 210 20% 98%;
|
|
144
|
+
--primary-foreground: 220.9 39.3% 11%;
|
|
145
|
+
--secondary: 215 27.9% 16.9%;
|
|
146
|
+
--secondary-foreground: 210 20% 98%;
|
|
147
|
+
--muted: 215 27.9% 16.9%;
|
|
148
|
+
--muted-foreground: 217.9 10.6% 64.9%;
|
|
149
|
+
--accent: 215 27.9% 16.9%;
|
|
150
|
+
--accent-foreground: 210 20% 98%;
|
|
151
|
+
--destructive: 0 62.8% 30.6%;
|
|
152
|
+
--destructive-foreground: 210 20% 98%;
|
|
153
|
+
--border: 215 27.9% 16.9%;
|
|
154
|
+
--input: 215 27.9% 16.9%;
|
|
155
|
+
--ring: 216 12.2% 83.9%;
|
|
156
|
+
--chart-1: 220 70% 50%;
|
|
157
|
+
--chart-2: 160 60% 45%;
|
|
158
|
+
--chart-3: 30 80% 55%;
|
|
159
|
+
--chart-4: 280 65% 60%;
|
|
160
|
+
--chart-5: 340 75% 55%;
|
|
161
|
+
--sidebar-background: 240 5.9% 10%;
|
|
162
|
+
--sidebar-foreground: 240 4.8% 95.9%;
|
|
163
|
+
--sidebar-primary: 224.3 76.3% 48%;
|
|
164
|
+
--sidebar-primary-foreground: 0 0% 100%;
|
|
165
|
+
--sidebar-accent: 240 3.7% 15.9%;
|
|
166
|
+
--sidebar-accent-foreground: 240 4.8% 95.9%;
|
|
167
|
+
--sidebar-border: 240 3.7% 15.9%;
|
|
168
|
+
--sidebar-ring: 217.2 91.2% 59.8%;
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
@layer base {
|
|
173
|
+
* {
|
|
174
|
+
@apply border-border;
|
|
175
|
+
}
|
|
176
|
+
body {
|
|
177
|
+
@apply bg-background text-foreground;
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
@keyframes flowDash {
|
|
182
|
+
0% { stroke-dashoffset: 0; }
|
|
183
|
+
100% { stroke-dashoffset: -20; }
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
.edge-animated {
|
|
187
|
+
stroke-dasharray: 5; /* length of dash pattern */
|
|
188
|
+
stroke-linecap: round; /* round the dash ends */
|
|
189
|
+
animation: flowDash 1s linear infinite;
|
|
190
|
+
}
|
package/src/lib/utils.ts
ADDED
package/src/main.tsx
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { StrictMode } from 'react'
|
|
2
|
+
import { createRoot } from 'react-dom/client'
|
|
3
|
+
import './index.css'
|
|
4
|
+
import { RouterProvider, createRouter } from '@tanstack/react-router'
|
|
5
|
+
|
|
6
|
+
// Import the generated route tree
|
|
7
|
+
import { routeTree } from './routeTree.gen'
|
|
8
|
+
|
|
9
|
+
// Create a new router instance
|
|
10
|
+
const router = createRouter({ routeTree })
|
|
11
|
+
|
|
12
|
+
// Register the router instance for type safety
|
|
13
|
+
declare module '@tanstack/react-router' {
|
|
14
|
+
interface Register {
|
|
15
|
+
router: typeof router
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
// Render the app
|
|
20
|
+
const rootElement = document.getElementById('root')!
|
|
21
|
+
if (!rootElement.innerHTML) {
|
|
22
|
+
const root = createRoot(rootElement)
|
|
23
|
+
root.render(
|
|
24
|
+
<StrictMode>
|
|
25
|
+
<RouterProvider router={router} />
|
|
26
|
+
</StrictMode>,
|
|
27
|
+
)
|
|
28
|
+
}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { Webhook } from 'lucide-react'
|
|
2
|
+
import { PropsWithChildren } from 'react'
|
|
3
|
+
import { BaseNode } from './base-node'
|
|
4
|
+
import { Emits } from './emits'
|
|
5
|
+
import { ApiNodeProps } from './node-props'
|
|
6
|
+
|
|
7
|
+
type Props = PropsWithChildren<ApiNodeProps & { excludePubsub?: boolean }>
|
|
8
|
+
|
|
9
|
+
export const ApiNode = ({ data, children, excludePubsub }: Props) => {
|
|
10
|
+
return (
|
|
11
|
+
<BaseNode
|
|
12
|
+
variant="api"
|
|
13
|
+
title={data.name}
|
|
14
|
+
disableSourceHandle={!data.emits?.length}
|
|
15
|
+
disableTargetHandle={!data.subscribes?.length}
|
|
16
|
+
>
|
|
17
|
+
{data.description && <div className="text-sm max-w-[300px] text-white/60">{data.description}</div>}
|
|
18
|
+
{children}
|
|
19
|
+
{data.webhookUrl && (
|
|
20
|
+
<div className="flex gap-1 items-center text-xs text-white/60">
|
|
21
|
+
<Webhook className="w-3 h-3 text-white/40" />
|
|
22
|
+
<div className="font-mono">{data.webhookUrl}</div>
|
|
23
|
+
</div>
|
|
24
|
+
)}
|
|
25
|
+
{!excludePubsub && <Emits emits={data.emits} />}
|
|
26
|
+
</BaseNode>
|
|
27
|
+
)
|
|
28
|
+
}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import React, { HTMLAttributes } from 'react'
|
|
2
|
+
import { HandleProps, Position, Handle as RFHandle } from '@xyflow/react'
|
|
3
|
+
import clsx from 'clsx'
|
|
4
|
+
|
|
5
|
+
type Props = HandleProps &
|
|
6
|
+
Omit<HTMLAttributes<HTMLDivElement>, 'id'> & {
|
|
7
|
+
isHidden?: boolean
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export const BaseHandle: React.FC<Props> = (props) => {
|
|
11
|
+
const { isHidden, position, ...rest } = props
|
|
12
|
+
|
|
13
|
+
return (
|
|
14
|
+
<div
|
|
15
|
+
className={clsx(
|
|
16
|
+
'absolute w-1 h-1',
|
|
17
|
+
position === Position.Top && '-top-[10px]',
|
|
18
|
+
position === Position.Bottom && '-bottom-[10px]',
|
|
19
|
+
'left-1/2 -ml-[2px]',
|
|
20
|
+
isHidden && 'hidden',
|
|
21
|
+
)}
|
|
22
|
+
>
|
|
23
|
+
<RFHandle
|
|
24
|
+
{...rest}
|
|
25
|
+
position={position}
|
|
26
|
+
className="
|
|
27
|
+
!static
|
|
28
|
+
!w-1
|
|
29
|
+
!h-1
|
|
30
|
+
!min-w-[6px]
|
|
31
|
+
!min-h-[6px]
|
|
32
|
+
!p-0
|
|
33
|
+
!border-none
|
|
34
|
+
!bg-[#666666]
|
|
35
|
+
!transform-none
|
|
36
|
+
!rounded-full
|
|
37
|
+
!outline-none
|
|
38
|
+
!shadow-none
|
|
39
|
+
"
|
|
40
|
+
/>
|
|
41
|
+
</div>
|
|
42
|
+
)
|
|
43
|
+
}
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import { cn } from '@/lib/utils'
|
|
2
|
+
import { Position } from '@xyflow/react'
|
|
3
|
+
import { cva, type VariantProps } from 'class-variance-authority'
|
|
4
|
+
import { PropsWithChildren } from 'react'
|
|
5
|
+
import { BaseHandle } from './base-handle'
|
|
6
|
+
|
|
7
|
+
const baseNodeVariants = cva('relative flex flex-col min-w-[300px] rounded-md overflow-hidden font-mono')
|
|
8
|
+
|
|
9
|
+
const baseBackgroundVariants = cva('absolute -inset-[1px] rounded-md bg-gradient-to-r', {
|
|
10
|
+
variants: {
|
|
11
|
+
variant: {
|
|
12
|
+
event: 'from-teal-500/20 to-teal-400/10',
|
|
13
|
+
api: 'from-blue-500/20 to-blue-400/10',
|
|
14
|
+
noop: 'from-white/20 to-white/10',
|
|
15
|
+
},
|
|
16
|
+
},
|
|
17
|
+
})
|
|
18
|
+
|
|
19
|
+
type Props = PropsWithChildren<{
|
|
20
|
+
variant?: VariantProps<typeof baseBackgroundVariants>['variant']
|
|
21
|
+
title: string
|
|
22
|
+
headerChildren?: React.ReactNode
|
|
23
|
+
className?: string
|
|
24
|
+
disableSourceHandle?: boolean
|
|
25
|
+
disableTargetHandle?: boolean
|
|
26
|
+
}>
|
|
27
|
+
|
|
28
|
+
const HeaderBar = ({ text, children }: { text: string; children?: React.ReactNode }) => (
|
|
29
|
+
<div className="px-3 py-1 border-b border-white/20 bg-black/30 text-xs text-white/70 flex justify-between items-center">
|
|
30
|
+
<span>{text}</span>
|
|
31
|
+
{children}
|
|
32
|
+
</div>
|
|
33
|
+
)
|
|
34
|
+
|
|
35
|
+
export const BaseNode = (props: Props) => {
|
|
36
|
+
const { title, variant, className, children, disableSourceHandle, disableTargetHandle, headerChildren } = props
|
|
37
|
+
|
|
38
|
+
return (
|
|
39
|
+
<div className="group relative">
|
|
40
|
+
{/* Border container */}
|
|
41
|
+
<div className={cn(baseBackgroundVariants({ variant }))} />
|
|
42
|
+
|
|
43
|
+
{/* Main node content */}
|
|
44
|
+
<div className={cn(baseNodeVariants(), className)}>
|
|
45
|
+
<HeaderBar text={title} children={headerChildren} />
|
|
46
|
+
<div className="p-4 space-y-3">{children}</div>
|
|
47
|
+
</div>
|
|
48
|
+
|
|
49
|
+
{/* Connection points */}
|
|
50
|
+
{!disableTargetHandle && <BaseHandle type="target" position={Position.Top} />}
|
|
51
|
+
{!disableSourceHandle && <BaseHandle type="source" position={Position.Bottom} />}
|
|
52
|
+
|
|
53
|
+
{/* Stacked card effect */}
|
|
54
|
+
<div className="absolute inset-0 -z-10 translate-y-1 translate-x-1 bg-black/20 rounded-md border border-white/5" />
|
|
55
|
+
</div>
|
|
56
|
+
)
|
|
57
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { Send } from 'lucide-react'
|
|
2
|
+
import { EventNodeData } from '../views/flow/nodes/nodes.types'
|
|
3
|
+
|
|
4
|
+
const toType = (emit: string | { type: string; label?: string; conditional?: boolean }) =>
|
|
5
|
+
typeof emit === 'string' ? emit : emit.type
|
|
6
|
+
|
|
7
|
+
export const Emits: React.FC<{ emits: EventNodeData['emits'] }> = ({ emits }) => {
|
|
8
|
+
return (
|
|
9
|
+
<>
|
|
10
|
+
{emits.map((emit) => (
|
|
11
|
+
<div
|
|
12
|
+
key={toType(emit)}
|
|
13
|
+
className="flex gap-2 items-center text-xs text-white/60"
|
|
14
|
+
data-testid={`emits__${toType(emit)}`}
|
|
15
|
+
>
|
|
16
|
+
<Send className="w-3 h-3 text-white/40" />
|
|
17
|
+
<div className="font-mono tracking-wider">{toType(emit)}</div>
|
|
18
|
+
</div>
|
|
19
|
+
))}
|
|
20
|
+
</>
|
|
21
|
+
)
|
|
22
|
+
}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import { PropsWithChildren } from 'react'
|
|
2
|
+
import { LanguageIndicator } from '../views/flow/nodes/language-indicator'
|
|
3
|
+
import { BaseNode } from './base-node'
|
|
4
|
+
import { Emits } from './emits'
|
|
5
|
+
import { EventNodeProps } from './node-props'
|
|
6
|
+
import { Subscribe } from './subscribe'
|
|
7
|
+
|
|
8
|
+
type Props = PropsWithChildren<
|
|
9
|
+
EventNodeProps & {
|
|
10
|
+
excludePubsub?: boolean
|
|
11
|
+
className?: string
|
|
12
|
+
}
|
|
13
|
+
>
|
|
14
|
+
|
|
15
|
+
export const EventNode = (props: Props) => {
|
|
16
|
+
const { data, excludePubsub, children } = props
|
|
17
|
+
|
|
18
|
+
return (
|
|
19
|
+
<BaseNode
|
|
20
|
+
variant="event"
|
|
21
|
+
title={data.name}
|
|
22
|
+
disableSourceHandle={!data.emits.length}
|
|
23
|
+
disableTargetHandle={!data.subscribes.length}
|
|
24
|
+
headerChildren={<LanguageIndicator language={data.language} />}
|
|
25
|
+
>
|
|
26
|
+
{data.description && <div className="text-sm max-w-[300px] text-white/60">{data.description}</div>}
|
|
27
|
+
{children}
|
|
28
|
+
{!excludePubsub && (
|
|
29
|
+
<div className="space-y-2 pt-2 border-t border-white/10">
|
|
30
|
+
<Subscribe data={data} />
|
|
31
|
+
</div>
|
|
32
|
+
)}
|
|
33
|
+
<Emits emits={data.emits} />
|
|
34
|
+
</BaseNode>
|
|
35
|
+
)
|
|
36
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { EventNodeData, ApiNodeData, NoopNodeData } from '../views/flow/nodes/nodes.types'
|
|
2
|
+
|
|
3
|
+
export type BaseNodeProps = EventNodeProps | NoopNodeProps | ApiNodeProps
|
|
4
|
+
|
|
5
|
+
export type EventNodeProps = {
|
|
6
|
+
data: EventNodeData
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export type NoopNodeProps = {
|
|
10
|
+
data: NoopNodeData
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export type ApiNodeProps = {
|
|
14
|
+
data: ApiNodeData
|
|
15
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { PropsWithChildren } from 'react'
|
|
2
|
+
import { NoopNodeData } from '../views/flow/nodes/nodes.types'
|
|
3
|
+
import { BaseNode } from './base-node'
|
|
4
|
+
|
|
5
|
+
type Props = PropsWithChildren<{
|
|
6
|
+
data: NoopNodeData
|
|
7
|
+
}>
|
|
8
|
+
|
|
9
|
+
export const NoopNode = ({ data, children }: Props) => {
|
|
10
|
+
return (
|
|
11
|
+
<BaseNode
|
|
12
|
+
variant="noop"
|
|
13
|
+
title={data.name}
|
|
14
|
+
disableSourceHandle={!data.emits?.length}
|
|
15
|
+
disableTargetHandle={!data.subscribes?.length}
|
|
16
|
+
>
|
|
17
|
+
{data.description && <div className="text-sm max-w-[300px] text-white/60">{data.description}</div>}
|
|
18
|
+
{children}
|
|
19
|
+
</BaseNode>
|
|
20
|
+
)
|
|
21
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { Eye } from 'lucide-react'
|
|
2
|
+
import { EventNodeData } from '../views/flow/nodes/nodes.types'
|
|
3
|
+
|
|
4
|
+
export const Subscribe: React.FC<{ data: EventNodeData }> = ({ data }) => {
|
|
5
|
+
return (
|
|
6
|
+
<>
|
|
7
|
+
{data.subscribes.map((subscribe) => (
|
|
8
|
+
<div
|
|
9
|
+
key={subscribe}
|
|
10
|
+
className="flex gap-2 items-center text-xs text-white/60"
|
|
11
|
+
data-testid={`subscribes__${subscribe}`}
|
|
12
|
+
>
|
|
13
|
+
<Eye className="w-3 h-3 text-white/40" />
|
|
14
|
+
<div className="font-mono tracking-wider">{subscribe}</div>
|
|
15
|
+
</div>
|
|
16
|
+
))}
|
|
17
|
+
</>
|
|
18
|
+
)
|
|
19
|
+
}
|