Skip to content

Commit 8cb4e7f

Browse files
committed
enable connections
1 parent b15de15 commit 8cb4e7f

34 files changed

+676
-119
lines changed

convex/schema.ts

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ export const stacksValidator = v.object({
1212
sourceCodeUrl: v.optional(v.string()),
1313
websiteUrl: v.optional(v.string()),
1414
description: v.optional(v.string()),
15+
stackEdges: v.optional(v.array(v.any())),
1516
stackBlocks: v.array(
1617
v.object({
1718
id: v.string(),
@@ -50,6 +51,19 @@ export const stacksValidator = v.object({
5051
className: v.optional(v.string()),
5152
style: v.optional(v.any())
5253
})
54+
),
55+
blocksConfig: v.optional(
56+
v.object({
57+
compactMode: v.boolean(),
58+
snapToGrid: v.boolean(),
59+
enableConnections: v.boolean(),
60+
connectionsOrientation: v.union(
61+
v.literal('horizontal'),
62+
v.literal('vertical')
63+
),
64+
connectionsLineType: v.string(),
65+
animated: v.boolean()
66+
})
5367
)
5468
})
5569

src/app/hs/stacks/[stackId]/EditStack.tsx

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@ import StackFeedbackSettings from '@/app/hs/stacks/[stackId]/StackFeedbackSettin
3737
import Link from 'next/link'
3838
import { Button } from '@nextui-org/button'
3939
import FeedbacksCount from '@/app/hs/stacks/components/FeedbacksCount'
40+
import useBlocksConfig from '@/app/hs/stacks/components/blocks/hooks/useBlocksConfig'
4041

4142
interface EditStackProps {
4243
stackState: StackStateProps['stackState']
@@ -50,10 +51,10 @@ export default function EditStack({
5051
}: EditStackProps) {
5152
const router = useRouter()
5253
const [viewMode, setViewMode] = React.useState<string | number>('blocks')
53-
const { getNodes, validateBlocks, error } = useBlockNodes()
54+
const { getNodes, getEdges, validateBlocks, error } = useBlockNodes()
5455
const updateStack = useMutation(api.stack.updateStack)
5556
const deleteStack = useMutation(api.stack.deleteStack)
56-
57+
const { getBlocksConfig } = useBlocksConfig()
5758
const form = useForm<StackForm>({
5859
resolver: zodResolver(stackFormSchema),
5960
defaultValues: stackState
@@ -70,6 +71,8 @@ export default function EditStack({
7071
}
7172
}
7273
const nodes = getNodes()
74+
const edges = getEdges()
75+
const blocksConfig = getBlocksConfig()
7376
const values = form.getValues()
7477
const templateId = stack.templateId
7578
const coverImage = stack.coverImage
@@ -78,6 +81,8 @@ export default function EditStack({
7881
templateId,
7982
coverImage,
8083
stackBlocks: nodes,
84+
stackEdges: edges,
85+
blocksConfig,
8186
isPublic: stack.isPublic
8287
}
8388
await updateStack({ stackId, stack: updatedStack })
@@ -194,6 +199,7 @@ export default function EditStack({
194199
</div>
195200
<StackBlocks
196201
initialNodes={stackState.stackBlocks ?? []}
202+
initialEdges={stackState.stackEdges ?? []}
197203
hidden={viewMode !== 'blocks'}
198204
/>
199205

src/app/hs/stacks/[stackId]/EditStackView.tsx

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,9 +6,13 @@ import { api } from '~/convex/_generated/api'
66
import { Id } from '~/convex/_generated/dataModel'
77
import { StackState } from '@/app/hs/stacks/create/create.types'
88
import type { Node } from 'reactflow'
9-
import { BlockNodeData } from '@/app/hs/stacks/components/blocks/Blocks.types'
9+
import {
10+
BlockNodeData,
11+
BlocksConfig
12+
} from '@/app/hs/stacks/components/blocks/Blocks.types'
1013
import EditStack from '@/app/hs/stacks/[stackId]/EditStack'
1114
import PageDataLoading from '@/app/hs/components/ui/PageDataLoading'
15+
import useBlocksConfig from '@/app/hs/stacks/components/blocks/hooks/useBlocksConfig'
1216

1317
interface StackViewProps {
1418
stackId: Id<'stacks'>
@@ -20,13 +24,15 @@ export default function EditStackView({ stackId }: StackViewProps) {
2024
api.stack.getUserStack,
2125
shouldFetch ? { stackId } : 'skip'
2226
)
27+
const { setBlocksConfig } = useBlocksConfig()
2328
const [editStackState, setStackState] = React.useState<StackState>({
2429
name: '',
2530
projectTypes: [],
2631
sourceCodeUrl: '',
2732
websiteUrl: '',
2833
description: '',
2934
stackBlocks: [],
35+
stackEdges: [],
3036
isPublic: true,
3137
coverImage: ''
3238
})
@@ -40,11 +46,13 @@ export default function EditStackView({ stackId }: StackViewProps) {
4046
websiteUrl: stack.websiteUrl,
4147
description: stack.description,
4248
stackBlocks: stack.stackBlocks as Node<BlockNodeData>[],
49+
stackEdges: stack.stackEdges,
4350
isPublic: stack.isPublic,
4451
coverImage: stack.coverImage
4552
})
53+
stack?.blocksConfig && setBlocksConfig(stack.blocksConfig as BlocksConfig)
4654
}
47-
}, [stack])
55+
}, [stack, setBlocksConfig])
4856

4957
return (
5058
<>
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
import { proxy } from 'valtio'
2+
import { ConnectionLineType } from 'reactflow'
3+
import { BlocksConfig } from '@/app/hs/stacks/components/blocks/Blocks.types'
4+
5+
export const compactMode = proxy({ value: false })
6+
export const snapToGridEnabled = proxy({ value: false })
7+
export const enableConnections = proxy({ value: false })
8+
9+
export const connectionsOrientation = proxy<{
10+
value: BlocksConfig['connectionsOrientation']
11+
}>({ value: 'vertical' })
12+
13+
export const connectionsLineType = proxy<{
14+
value: BlocksConfig['connectionsLineType']
15+
}>({ value: ConnectionLineType.Bezier })
16+
17+
export const enableConnectionAnimation = proxy({ value: false })

src/app/hs/stacks/components/blocks/Blocks.types.ts

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
import { Doc } from '~/convex/_generated/dataModel'
22
import { WithoutSystemFields } from 'convex/server'
33

4+
import { ConnectionLineType } from 'reactflow'
5+
46
export type TechWithoutSystemFields = WithoutSystemFields<Doc<'tech'>>
57

68
export type BlockNodeData = {
@@ -24,3 +26,12 @@ export type TechWithRepoData =
2426
repoData?: any
2527
})
2628
| null
29+
30+
export type BlocksConfig = {
31+
compactMode: boolean
32+
snapToGrid: boolean
33+
enableConnections: boolean
34+
connectionsOrientation: 'horizontal' | 'vertical'
35+
connectionsLineType: ConnectionLineType
36+
animated: boolean
37+
}

src/app/hs/stacks/components/blocks/BlocksToolbar.tsx

Lines changed: 116 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,49 @@
1-
import { proxy, useSnapshot } from 'valtio'
2-
import { LucideExpand, LucideMagnet, LucideShrink } from 'lucide-react'
1+
import { useSnapshot } from 'valtio'
2+
import {
3+
LucideExpand,
4+
LucideMagnet,
5+
LucideMoveHorizontal,
6+
LucideMoveVertical,
7+
LucideShrink,
8+
LucideSparkles,
9+
LucideWorkflow
10+
} from 'lucide-react'
311
import { Button } from '@nextui-org/button'
4-
import { Tooltip } from '@nextui-org/react'
12+
import { Tab, Tabs, Tooltip } from '@nextui-org/react'
513
import { useTheme } from 'next-themes'
14+
import {
15+
compactMode,
16+
connectionsLineType,
17+
connectionsOrientation,
18+
enableConnectionAnimation,
19+
enableConnections,
20+
snapToGridEnabled
21+
} from '@/app/hs/stacks/components/blocks/Blocks.state'
22+
import { ConnectionLineType } from 'reactflow'
623

7-
export const compactMode = proxy({ value: false })
8-
export const snapToGridEnabled = proxy({ value: false })
9-
export default function BlocksToolbar() {
24+
interface BlocksToolbarProps {
25+
publishConnectionsConfigChange?: () => void
26+
onLineTypeChange?: (lineType: ConnectionLineType) => void
27+
onAnimatedChange?: (animated: boolean) => void
28+
viewOnly?: boolean
29+
}
30+
export default function BlocksToolbar({
31+
publishConnectionsConfigChange,
32+
onLineTypeChange,
33+
onAnimatedChange,
34+
viewOnly
35+
}: BlocksToolbarProps) {
1036
const { theme } = useTheme()
1137
const isCompactMode = useSnapshot(compactMode)
1238
const snapToGrid = useSnapshot(snapToGridEnabled)
39+
const withConnections = useSnapshot(enableConnections)
40+
const connOrientation = useSnapshot(connectionsOrientation)
41+
const lineType = useSnapshot(connectionsLineType)
42+
const connectionAnimation = useSnapshot(enableConnectionAnimation)
1343

1444
const activeColor = theme === 'dark' ? 'primary' : 'secondary'
1545
return (
16-
<div className="absolute right-4 top-4 z-10 bg-white dark:bg-black flex items-center gap-2 p-1 border-1 border-default-100 rounded-large">
46+
<div className="absolute right-4 top-4 z-10 bg-white dark:bg-black flex items-center flex-row-reverse gap-2 p-1 border-1 border-default-100 rounded-large">
1747
<Tooltip size="sm" content={'Compact mode'}>
1848
<Button
1949
onClick={() => {
@@ -46,6 +76,85 @@ export default function BlocksToolbar() {
4676
<LucideMagnet size={18} strokeWidth={1.5} />
4777
</Button>
4878
</Tooltip>
79+
{!viewOnly && (
80+
<>
81+
<Tooltip size="sm" content={'Enable connections'}>
82+
<Button
83+
onClick={() => {
84+
enableConnections.value = !withConnections.value
85+
publishConnectionsConfigChange?.()
86+
}}
87+
size="sm"
88+
radius="md"
89+
color={withConnections.value ? activeColor : 'default'}
90+
variant={withConnections.value ? 'flat' : 'light'}
91+
isIconOnly
92+
>
93+
<LucideWorkflow size={18} strokeWidth={1.5} />
94+
</Button>
95+
</Tooltip>
96+
{withConnections.value && (
97+
<div className="flex items-center flex-row-reverse gap-1">
98+
<div className="bg-white dark:bg-black flex items-center gap-2">
99+
<Tabs
100+
size="sm"
101+
variant="light"
102+
radius="lg"
103+
selectedKey={lineType.value}
104+
onSelectionChange={(key) => {
105+
const lineType = key as ConnectionLineType
106+
connectionsLineType.value = lineType
107+
onLineTypeChange?.(lineType)
108+
}}
109+
>
110+
<Tab key={ConnectionLineType.Step} title="Step" />
111+
<Tab key={ConnectionLineType.SmoothStep} title="Smooth" />
112+
<Tab key={ConnectionLineType.Straight} title="Straight" />
113+
<Tab key={ConnectionLineType.Bezier} title="Simple" />
114+
</Tabs>
115+
<Tooltip size="sm" content={'Animated'}>
116+
<Button
117+
onClick={() => {
118+
const animated = !connectionAnimation.value
119+
enableConnectionAnimation.value = animated
120+
onAnimatedChange?.(animated)
121+
}}
122+
size="sm"
123+
radius="md"
124+
color={connectionAnimation.value ? activeColor : 'default'}
125+
variant={connectionAnimation.value ? 'flat' : 'light'}
126+
isIconOnly
127+
>
128+
<LucideSparkles size={18} strokeWidth={1.5} />
129+
</Button>
130+
</Tooltip>
131+
<Tooltip size="sm" content={'Orientation'}>
132+
<Button
133+
onClick={() => {
134+
connectionsOrientation.value =
135+
connOrientation.value === 'vertical'
136+
? 'horizontal'
137+
: 'vertical'
138+
publishConnectionsConfigChange?.()
139+
}}
140+
size="sm"
141+
radius="md"
142+
color={activeColor}
143+
variant="flat"
144+
isIconOnly
145+
>
146+
{connOrientation.value === 'vertical' ? (
147+
<LucideMoveVertical size={18} />
148+
) : (
149+
<LucideMoveHorizontal size={18} />
150+
)}
151+
</Button>
152+
</Tooltip>
153+
</div>
154+
</div>
155+
)}
156+
</>
157+
)}
49158
</div>
50159
)
51160
}

src/app/hs/stacks/components/blocks/Flow.tsx

Lines changed: 27 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,15 @@ import React, { DragEventHandler } from 'react'
22
import ReactFlow, {
33
Node,
44
OnNodesChange,
5+
OnConnect,
56
Background,
67
BackgroundVariant,
78
BackgroundProps,
89
NodeChange,
9-
NodeDragHandler
10+
NodeDragHandler,
11+
Edge,
12+
OnEdgesChange,
13+
EdgeChange
1014
} from 'reactflow'
1115

1216
import 'reactflow/dist/style.css'
@@ -17,6 +21,7 @@ import CustomBlockNode from '@/app/hs/stacks/components/blocks/node-types/Custom
1721
import StackDetailsNode from '@/app/hs/stacks/components/blocks/node-types/StackDetailsNode'
1822
import ResizableGroupNode from '@/app/hs/stacks/components/blocks/node-types/ResizableGroupNode'
1923
import CustomGroupNode from '@/app/hs/stacks/components/blocks/node-types/CustomGroupNode'
24+
import CustomEdge from '@/app/hs/stacks/components/blocks/edge-types/CustomEdge'
2025

2126
const nodeTypes = {
2227
blockNode: BlockNode,
@@ -26,10 +31,16 @@ const nodeTypes = {
2631
customGroupNode: CustomGroupNode
2732
}
2833

34+
const edgeTypes = {
35+
customEdge: CustomEdge
36+
}
37+
2938
interface FlowProps<T> {
3039
nodes: Node[]
31-
setNodes: (nodes: Node[]) => void
40+
edges: Edge[]
41+
onEdgeConnect?: OnConnect
3242
onNodesChange: OnNodesChange
43+
onEdgesChange: OnEdgesChange
3344
onNodeDrag?: NodeDragHandler
3445
onNodeDragStop?: NodeDragHandler
3546
onDrop?: DragEventHandler
@@ -40,7 +51,10 @@ interface FlowProps<T> {
4051
}
4152
export default function Flow<T = Element>({
4253
nodes,
54+
edges,
55+
onEdgeConnect,
4356
onNodesChange,
57+
onEdgesChange,
4458
onNodeDrag,
4559
onNodeDragStop,
4660
onDrop,
@@ -64,17 +78,25 @@ export default function Flow<T = Element>({
6478

6579
useSetCenter()
6680

67-
const handleNodesChangechanges = (changes: NodeChange[]) => {
81+
const handleNodesChange = (changes: NodeChange[]) => {
6882
if (viewOnly && changes.some((c) => c.type === 'remove')) return
6983
onNodesChange(changes)
7084
}
7185

86+
const handleEdgesChange = (changes: EdgeChange[]) => {
87+
if (viewOnly && changes.some((c) => c.type === 'remove')) return
88+
onEdgesChange(changes)
89+
}
90+
7291
return (
7392
<ReactFlow
7493
nodes={nodes}
75-
edges={[]}
94+
edges={edges}
7695
nodeTypes={nodeTypes}
77-
onNodesChange={handleNodesChangechanges}
96+
edgeTypes={edgeTypes}
97+
onNodesChange={handleNodesChange}
98+
onEdgesChange={handleEdgesChange}
99+
onConnect={onEdgeConnect}
78100
onNodeDrag={onNodeDrag}
79101
onNodeDragStop={onNodeDragStop}
80102
onDrop={onDrop}

0 commit comments

Comments
 (0)