Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 14 additions & 0 deletions src/main/frontend/app/app.css
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,13 @@
--sticky-color-purple: #e9d5ff;
--sticky-color-orange: #fed7aa;

--group-color-blue: #93c5fd;
--group-color-violet: #c4b5fd;
--group-color-rose: #fda4af;
--group-color-green: #86efac;
--group-color-amber: #fcd34d;
--group-color-cyan: #67e8f9;

/* Palette Styling */
--palette-pipes: #68d250;
--palette-listeners: #d250bf;
Expand Down Expand Up @@ -110,6 +117,13 @@
--sticky-color-purple: #581c87;
--sticky-color-orange: #9a3412;

--group-color-blue: #1d4ed8;
--group-color-violet: #6d28d9;
--group-color-rose: #be123c;
--group-color-green: #15803d;
--group-color-amber: #b45309;
--group-color-cyan: #0e7490;

--palette-pipes: #136502;
--palette-listeners: #853279;
--palette-senders: #1c7c6a;
Expand Down
50 changes: 46 additions & 4 deletions src/main/frontend/app/routes/studio/canvas/flow.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -122,6 +122,7 @@
setDropSuccessful,
setIsMultiSelect,
setSelectedStickyId,
setSelectedGroupId,
} = useNodeContextStore(
useShallow((s) => ({
isEditing: s.isEditing,
Expand All @@ -139,6 +140,7 @@
setIsMultiSelect: s.setIsMultiSelect,
setSelectedStickyId: s.setSelectedStickyId,
selectedStickyId: s.selectedStickyId,
setSelectedGroupId: s.setSelectedGroupId,
})),
)
const { elements } = useFFDoc()
Expand Down Expand Up @@ -232,7 +234,7 @@
showErrorToast(`Failed to save XML: ${error instanceof Error ? error.message : error}`)
setSaveStatus('idle')
}
}, [project])

Check warning on line 237 in src/main/frontend/app/routes/studio/canvas/flow.tsx

View workflow job for this annotation

GitHub Actions / Build & Run All Tests

React Hook useCallback has an unnecessary dependency: 'project'. Either exclude it or remove the dependency array

const autosaveEnabled = useSettingsStore((s) => s.general.autoSave.enabled)
const autosaveDelay = useSettingsStore((s) => s.general.autoSave.delayMs)
Expand Down Expand Up @@ -461,24 +463,28 @@

if (fullySelectedGroupIds.length > 1) {
handleMultiGroupMerge(fullySelectedGroupIds, selectedNodes)
showNodeContextMenu(true)
return
}

if (allSelectedInSameGroup(selectedNodes)) return

if (shouldMergeUngroupedIntoGroup(selectedNodes)) {
handleMergeUngroupedIntoGroup(selectedNodes)
showNodeContextMenu(true)
return
}

groupNodes(selectedNodes, nodes)
showNodeContextMenu(true)
}, [
nodes,
allSelectedInSameGroup,
getFullySelectedGroupIds,
handleMergeUngroupedIntoGroup,
handleMultiGroupMerge,
shouldMergeUngroupedIntoGroup,
showNodeContextMenu,
])

const copySelection = useCallback(() => {
Expand Down Expand Up @@ -652,6 +658,14 @@

if (node.type === 'stickyNote') {
setSelectedStickyId(node.id)
setSelectedGroupId(null)
showNodeContextMenu(true)
return
}

if (node.type === 'groupNode') {
setSelectedGroupId(node.id)
setSelectedStickyId(null)
showNodeContextMenu(true)
return
}
Expand All @@ -661,12 +675,21 @@
if (frankElement) {
deselectOtherNodes(node.id)
setSelectedStickyId(null)
setSelectedGroupId(null)
applyNodeContext(node, frankElement)
showNodeContextMenu(true)
}
}
},
[isDirty, lookupFrankElement, deselectOtherNodes, applyNodeContext, showNodeContextMenu, setSelectedStickyId],
[
isDirty,
lookupFrankElement,
deselectOtherNodes,
applyNodeContext,
showNodeContextMenu,
setSelectedStickyId,
setSelectedGroupId,
],
)

const handleNodeDoubleClick = useCallback(
Expand Down Expand Up @@ -703,21 +726,31 @@
const handleEdgeClick = useCallback(() => {
setIsMultiSelect(false)
setSelectedStickyId(null)
setSelectedGroupId(null)
showNodeContextMenu(false)
setIsEditing(false)
}, [setIsMultiSelect, setSelectedStickyId, showNodeContextMenu, setIsEditing])
}, [setIsMultiSelect, setSelectedStickyId, setSelectedGroupId, showNodeContextMenu, setIsEditing])

const handleSelectionChange = useCallback(
({ nodes: selectedNodes }: { nodes: FlowNode[] }) => {
const frankNodes = selectedNodes.filter((n) => isFrankNode(n))

if (frankNodes.length > 1) {
const firstParent = frankNodes[0]?.parentId
const allInSameGroup = Boolean(firstParent) && frankNodes.every((n) => n.parentId === firstParent)

setIsMultiSelect(true)
setSelectedStickyId(null)
showNodeContextMenu(false)
setSelectedGroupId(null)
setIsEditing(false)
setParentId(null)
setChildParentId(null)

if (allInSameGroup) {
showNodeContextMenu(true)
} else {
showNodeContextMenu(false)
}
return
}

Expand All @@ -727,6 +760,7 @@
const frankElement = lookupFrankElement((frankNodes[0] as FrankNodeType).data.subtype)
if (!frankElement) return
setSelectedStickyId(null)
setSelectedGroupId(null)
applyNodeContext(frankNodes[0] as FrankNodeType, frankElement)
showContextIfSidebarOpen()
}
Expand All @@ -736,6 +770,7 @@
setIsEditing,
setIsMultiSelect,
setSelectedStickyId,
setSelectedGroupId,
setParentId,
setChildParentId,
lookupFrankElement,
Expand Down Expand Up @@ -1066,13 +1101,19 @@
const unsub = useFlowStore.subscribe(
(state) => state.nodes,
(nodes) => {
const { selectedStickyId, setSelectedStickyId, setIsEditing } = useNodeContextStore.getState()
const { selectedStickyId, setSelectedStickyId, selectedGroupId, setSelectedGroupId, setIsEditing } =
useNodeContextStore.getState()

if (selectedStickyId && !nodes.some((node) => node.id === selectedStickyId)) {
setSelectedStickyId(null)
showNodeContextMenu(false)
setIsEditing(false)
}

if (selectedGroupId && !nodes.some((node) => node.id === selectedGroupId)) {
setSelectedGroupId(null)
showNodeContextMenu(false)
}
},
)
return () => unsub()
Expand Down Expand Up @@ -1158,6 +1199,7 @@
onPaneClick={() => {
setContextMenu(null)
setSelectedStickyId(null)
setSelectedGroupId(null)
if (!isDirty) {
showNodeContextMenu(false)
setIsEditing(false)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -312,7 +312,7 @@

addChild(properties.id, child)
},
[

Check warning on line 315 in src/main/frontend/app/routes/studio/canvas/nodetypes/frank-node.tsx

View workflow job for this annotation

GitHub Actions / Build & Run All Tests

React Hook useCallback has missing dependencies: 'setAttributes' and 'setNodeId'. Either include them or remove the dependency array
properties.id,
addChild,
setIsNewNode,
Expand Down Expand Up @@ -492,9 +492,7 @@
{properties.data.attributes &&
Object.entries(properties.data.attributes).map(([key, value]) => (
<div key={key} className="my-1 w-full max-w-full px-1">
<p className="text-gray-1000 overflow-hidden text-sm font-bold text-ellipsis whitespace-nowrap">
{key}
</p>
<p className="text-gray-1000 overflow-hidden text-sm font-bold text-ellipsis whitespace-nowrap">{key}</p>
<p className="overflow-hidden text-sm text-ellipsis whitespace-nowrap">{value}</p>
</div>
))}
Expand Down
84 changes: 22 additions & 62 deletions src/main/frontend/app/routes/studio/canvas/nodetypes/group-node.tsx
Original file line number Diff line number Diff line change
@@ -1,42 +1,38 @@
import { type Node, type NodeProps, NodeResizeControl } from '@xyflow/react'
import { useState, type ChangeEvent } from 'react'
import { useState } from 'react'
import { ResizeIcon } from '~/routes/studio/canvas/nodetypes/frank-node'
import useFlowStore from '~/stores/flow-store'

export const GROUP_COLORS = [
{ label: 'Blue', value: 'var(--group-color-blue)' },
{ label: 'Violet', value: 'var(--group-color-violet)' },
{ label: 'Rose', value: 'var(--group-color-rose)' },
{ label: 'Green', value: 'var(--group-color-green)' },
{ label: 'Amber', value: 'var(--group-color-amber)' },
{ label: 'Cyan', value: 'var(--group-color-cyan)' },
]

export const GROUP_DEFAULT_COLOR = 'var(--group-color-blue)'

export type GroupNode = Node<{
label: string
description?: string
color?: string
width: number
height: number
childrenNames?: string[]
}>

export default function GroupNodeComponent({ id, data, selected }: NodeProps<GroupNode>) {
export default function GroupNodeComponent({ data, selected }: NodeProps<GroupNode>) {
const [dimensions, setDimensions] = useState({
width: data.width,
height: data.height,
})
const [isEditing, setIsEditing] = useState(false)
const [label, setLabel] = useState(data.label)
const { setGroupnodeLabel } = useFlowStore.getState()

const handleBlur = () => setIsEditing(false)
const handleClick = () => setIsEditing(true)

const handleSave = (event: ChangeEvent<HTMLInputElement>) => {
setGroupnodeLabel(id, event.target.value)
setLabel(event.target.value)
}

const handleKeyDown = (event: React.KeyboardEvent<HTMLInputElement>) => {
if (event.key === 'Enter') {
handleBlur()
}
}
const color = data.color || GROUP_DEFAULT_COLOR

return (
<>
<NodeResizeControl
onResize={(event, resizeData) => {
onResize={(_event, resizeData) => {
setDimensions({
width: resizeData.width,
height: resizeData.height,
Expand All @@ -47,43 +43,26 @@ export default function GroupNodeComponent({ id, data, selected }: NodeProps<Gro
<ResizeIcon color="black" />
</NodeResizeControl>
<div
className={`cursor-default rounded border bg-pink-300/25 ${selected ? 'border-blue-500' : 'border-border'}`}
className={`cursor-default rounded border ${selected ? 'border-blue-500' : 'border-border'}`}
style={{
width: dimensions.width,
height: dimensions.height,
backgroundColor: `color-mix(in srgb, ${color} 25%, transparent)`,
}}
>
<div
className="drag-handle relative max-h-1/2 cursor-move bg-pink-300 p-1"
className="drag-handle relative max-h-1/2 cursor-move p-1"
style={{
pointerEvents: 'all',
backgroundColor: color,
}}
>
<div className="absolute top-1/2 left-1/2 -translate-x-1/2 -translate-y-1/2">
<HamburgerMenu />
</div>

<div className="flex max-w-1/2 gap-1 px-2 py-1 text-sm font-bold">
{isEditing ? (
<input
type="text"
value={label}
onChange={handleSave}
onKeyDown={handleKeyDown}
onBlur={handleBlur}
autoFocus
className="nodrag rounded border px-1 text-sm"
/>
) : (
<>
<div className="cursor-pointer" onClick={handleClick}>
{label}
</div>
<div onClick={handleClick} className="flex-shrink-0 cursor-pointer self-start">
<PenIcon />
</div>
</>
)}
<div>{data.label}</div>
</div>
</div>
</div>
Expand All @@ -107,22 +86,3 @@ function HamburgerMenu() {
</svg>
)
}

function PenIcon() {
return (
<svg
xmlns="http://www.w3.org/2000/svg"
className="ml-2 h-4 w-4"
fill="none"
viewBox="0 0 24 24"
stroke="currentColor"
strokeWidth={2}
>
<path
strokeLinecap="round"
strokeLinejoin="round"
d="M11 5H6a2 2 0 00-2 2v11.5A1.5 1.5 0 005.5 20H17a2 2 0 002-2v-5M18.5 2.5a2.121 2.121 0 013 3L12 15l-4 1 1-4 9.5-9.5z"
/>
</svg>
)
}
Loading
Loading