Skip to content
Open
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
2,511 changes: 1,176 additions & 1,335 deletions pnpm-lock.yaml

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,7 @@ export default function ProjectLanding() {
setIsDiscovering(true)
fetchInstanceConfigurations()
.then((ffInstance) => {
if (!ffInstance) return
setFFInstanceName(ffInstance.name)
setFFConfiguration(ffInstance.configurations)
})
Expand Down

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { BaseEdge, EdgeLabelRenderer, getBezierPath, type Position, useReactFlow, useStore } from '@xyflow/react'
import { type MouseEvent, useEffect, useRef, useState } from 'react'
import useFlowStore from '~/stores/flow-store'
import { FlowConfig, getCompactLabelScale } from '~/routes/studio/canvas/flow.config'
import { FlowConfig, getCompactLabelScale } from '~/routes/studio/canvas-flow/flow.config'
import { getEdgeLabelPositions } from '~/utils/edge-label-utils'
import EdgeLabel from './edge-label'

Expand Down
File renamed without changes.
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,8 @@ import {
useUpdateNodeInternals,
} from '@xyflow/react'
import { useEffect } from 'react'
import { ResizeIcon } from '~/routes/studio/canvas/nodetypes/frank-node'
import { FlowConfig } from '~/routes/studio/canvas/flow.config'
import { ResizeIcon } from '~/routes/studio/canvas-flow/nodetypes/frank-node'
import { FlowConfig } from '~/routes/studio/canvas-flow/flow.config'
import { useSettingsStore } from '~/stores/settings-store'
import { NodeHeader } from './components/node-header'
import ZoomedOutNode from './zoomed-out-node'
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,9 @@
import { useCallback, useEffect, useLayoutEffect, useMemo, useRef, useState } from 'react'
import { useShallow } from 'zustand/react/shallow'
import useFlowStore, { isExitNode, isFrankNode } from '~/stores/flow-store'
import { CustomHandle } from '~/routes/studio/canvas/nodetypes/components/handle'
import { FlowConfig } from '~/routes/studio/canvas/flow.config'
import { useNodeContextMenu } from '~/routes/studio/canvas/node-context-menu-context'
import { CustomHandle } from '~/routes/studio/canvas-flow/nodetypes/components/handle'
import { FlowConfig } from '~/routes/studio/canvas-flow/flow.config'
import { useNodeContextMenu } from '~/routes/studio/canvas-flow/node-context-menu-context'
import useNodeContextStore from '~/stores/node-context-store'
import { getElementTypeFromName } from '~/routes/studio/node-translator-module'
import { useSettingsStore } from '~/stores/settings-store'
Expand Down Expand Up @@ -391,7 +391,7 @@

addChild(properties.id, child)
},
[

Check warning on line 394 in src/main/frontend/app/routes/studio/canvas-flow/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

Check warning on line 394 in src/main/frontend/app/routes/studio/canvas-flow/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
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { type Node, type NodeProps, NodeResizeControl } from '@xyflow/react'
import { useState } from 'react'
import { ResizeIcon } from '~/routes/studio/canvas/nodetypes/frank-node'
import { ResizeIcon } from '~/routes/studio/canvas-flow/nodetypes/frank-node'

export const GROUP_COLORS = [
{ label: 'Blue', value: 'var(--group-color-blue)' },
Expand Down
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
import { type Node, type NodeProps, NodeResizeControl, useUpdateNodeInternals } from '@xyflow/react'
import { FlowConfig } from '~/routes/studio/canvas/flow.config'
import { ResizeIcon } from '~/routes/studio/canvas/nodetypes/frank-node'
import { FlowConfig } from '~/routes/studio/canvas-flow/flow.config'
import { ResizeIcon } from '~/routes/studio/canvas-flow/nodetypes/frank-node'
import { useEffect, useLayoutEffect, useRef, useState } from 'react'
import useFlowStore from '~/stores/flow-store'
import { useNodeContextMenu } from '~/routes/studio/canvas/node-context-menu-context'
import { useNodeContextMenu } from '~/routes/studio/canvas-flow/node-context-menu-context'
import useNodeContextStore from '~/stores/node-context-store'

export const STICKY_NOTE_COLORS = [
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { Handle, Position, useStore } from '@xyflow/react'
import { FlowConfig, getCompactLabelScale } from '~/routes/studio/canvas/flow.config'
import { FlowConfig, getCompactLabelScale } from '~/routes/studio/canvas-flow/flow.config'

const COMPACT_INITIALS_BOX_SIZE = 160
const COMPACT_PADDING_TOP = 8
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import { useJavadocTransform } from '@frankframework/doc-library-react'
import ContextInputField from './context-input-field'
import type { Attribute, Elements } from '@frankframework/doc-library-core'
import type { DeprecatedInfo } from './deprecated-list-popover'
import { DeprecatedPopover } from '../canvas/nodetypes/components/deprecated-popover'
import { DeprecatedPopover } from '~/routes/studio/canvas-flow/nodetypes/components/deprecated-popover'

export type ContextInputProperties = {
id: string
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import useFlowStore, { isGroupNode } from '~/stores/flow-store'
import { GROUP_COLORS, GROUP_DEFAULT_COLOR } from '~/routes/studio/canvas/nodetypes/group-node'
import { GROUP_COLORS, GROUP_DEFAULT_COLOR } from '~/routes/studio/canvas-flow/nodetypes/group-node'
import { ALL_SHORTCUTS, formatShortcutParts, useShortcutStore } from '~/stores/shortcut-store'
import Button from '~/components/inputs/button'
import Input from '~/components/inputs/input'
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import useFlowStore, { isFrankNode, isStickyNote } from '~/stores/flow-store'
import { STICKY_NOTE_COLORS } from '~/routes/studio/canvas/nodetypes/sticky-note'
import { STICKY_NOTE_COLORS } from '~/routes/studio/canvas-flow/nodetypes/sticky-note'
import { useShallow } from 'zustand/react/shallow'
import Dropdown from '~/components/inputs/dropdown'

Expand Down
8 changes: 4 additions & 4 deletions src/main/frontend/app/routes/studio/flow-to-xml-parser.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
import type { FlowNode } from '~/routes/studio/canvas/flow'
import type { FlowNode } from '~/routes/studio/canvas-flow/canvas-flow'
import type { Edge } from '@xyflow/react'
import type { ChildNode } from './canvas/nodetypes/child-node'
import type { ChildNode } from '~/routes/studio/canvas-flow/nodetypes/child-node'
import { getAdapter } from '~/services/adapter-service'
import { FlowConfig } from './canvas/flow.config'
import { FlowConfig } from '~/routes/studio/canvas-flow/flow.config'
import { isGroupNode, isFrankNode, isStickyNote } from '~/stores/flow-store'
import type { GroupNode } from './canvas/nodetypes/group-node'
import type { GroupNode } from '~/routes/studio/canvas-flow/nodetypes/group-node'

type ReactFlowJson = {
nodes: FlowNode[]
Expand Down
119 changes: 119 additions & 0 deletions src/main/frontend/app/routes/studio/right-panel-content.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,119 @@
import { useShallow } from 'zustand/react/shallow'
import Button from '~/components/inputs/button'
import type { StickyNote } from '~/routes/studio/canvas-flow/nodetypes/sticky-note'
import GroupContext from '~/routes/studio/context/group-context'
import NodeContext from '~/routes/studio/context/node-context'
import StickyNoteContext from '~/routes/studio/context/sticky-note-context'
import StudioContext from '~/routes/studio/context/studio-context'
import useFlowStore, { isStickyNote } from '~/stores/flow-store'
import { ALL_SHORTCUTS, formatShortcutParts, useShortcutStore } from '~/stores/shortcut-store'

type RightPanelProps = {
isMultiSelect: boolean
selectedStickyId: string | null
selectedGroupId: string | null
showNodeContext: boolean
nodeId: number
editingSubtype: string | null
onShowNodeContext: (visible: boolean) => void
}

export default function RightPanelContent({
isMultiSelect,
selectedStickyId,
selectedGroupId,
showNodeContext,
nodeId,
onShowNodeContext,
}: RightPanelProps) {
const showPalette = !isMultiSelect && !selectedStickyId && !selectedGroupId && !showNodeContext

return (
<>
{isMultiSelect && <MultiSelectPanel />}
{!isMultiSelect && selectedStickyId && <StickyNoteContext nodeId={selectedStickyId} />}
{!isMultiSelect && !selectedStickyId && selectedGroupId && <GroupContext nodeId={selectedGroupId} />}
{!isMultiSelect && !selectedStickyId && !selectedGroupId && showNodeContext && (
<div className="flex min-h-0 flex-1 flex-col">
<AttachedNotesPanel nodeId={nodeId} />
<NodeContext nodeId={nodeId} setShowNodeContext={onShowNodeContext} />
</div>
)}

<div className={showPalette ? 'contents' : 'hidden'}>
<StudioContext />
</div>
</>
)
}

function MultiSelectPanel() {
const { allInSameGroup, groupId } = useFlowStore(
useShallow((state) => {
const selected = state.nodes.filter((node) => node.selected)
if (selected.length < 2) return { allInSameGroup: false, groupId: null }

const content = selected.filter((node) => node.type === 'frankNode' || node.type === 'exitNode')
if (content.length < 2) return { allInSameGroup: false, groupId: null }

const firstParent = content[0].parentId
const allSame = Boolean(firstParent) && content.every((node) => node.parentId === firstParent)
return { allInSameGroup: allSame, groupId: allSame ? (firstParent ?? null) : null }
}),
)

const platform = useShortcutStore((shortcut) => shortcut.platform)
const groupDef = ALL_SHORTCUTS.find((shortCut) => shortCut.id === 'studio.group')!
const groupParts = formatShortcutParts(groupDef, platform)
const triggerGroup = () => useShortcutStore.getState().shortcuts.get('studio.group')?.handler?.()

if (allInSameGroup && groupId) {
return <GroupContext nodeId={groupId} />
}

return (
<div className="p-4">
<Button onClick={triggerGroup} className="flex w-full items-center justify-between gap-2 px-4 py-2 text-sm">
<span>Group</span>
<span className="flex gap-1">
{groupParts.map((part) => (
<kbd key={part} className="rounded border border-current/40 bg-current/10 px-1.5 py-0.5 font-mono text-xs">
{part}
</kbd>
))}
</span>
</Button>
</div>
)
}

function AttachedNotesPanel({ nodeId }: { nodeId: number }) {
const attachedNotes = useFlowStore(
useShallow((state) =>
state.nodes.filter(
(node) => isStickyNote(node) && (node as StickyNote).data.attachedToNodeId === nodeId.toString(),
),
),
) as StickyNote[]

if (attachedNotes.length === 0) return null

return (
<div className="border-b-border border-b px-4 pt-4 pb-3">
<div className="text-foreground-muted mb-2 text-xs font-semibold tracking-wide uppercase">
{attachedNotes.length === 1 ? 'Note' : 'Notes'}
</div>
<div className="flex flex-col gap-2">
{attachedNotes.map((note) => (
<div
key={note.id}
className="rounded-lg p-2 text-xs leading-snug wrap-break-word whitespace-pre-wrap"
style={{ background: note.data.color ?? '#fef08a' }}
>
{note.data.content || <span className="opacity-40">Empty note</span>}
</div>
))}
</div>
</div>
)
}
136 changes: 12 additions & 124 deletions src/main/frontend/app/routes/studio/studio.tsx
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
import { useCallback, useEffect, useState } from 'react'
import { ReactFlowProvider } from '@xyflow/react'
import React, { useCallback, useEffect, useState } from 'react'
import { useNavigate } from 'react-router'
import StudioTabs from '~/components/tabs/studio-tabs'
import StudioFileStructure from '~/components/file-structure/studio-file-structure'
import StudioContext from '~/routes/studio/context/studio-context'
import Flow from '~/routes/studio/canvas/flow'
import NodeContext from '~/routes/studio/context/node-context'
import StickyNoteContext from '~/routes/studio/context/sticky-note-context'
import FlowCanvas from '~/routes/studio/canvas-flow/canvas-flow'
import { NodeContextMenuContext } from '~/routes/studio/canvas-flow/node-context-menu-context'
import RightPanelContent from '~/routes/studio/right-panel-content'
import useFlowStore from '~/stores/flow-store'
import useNodeContextStore from '~/stores/node-context-store'
import SidebarContentClose from '~/components/sidebars-layout/sidebar-content-close'
import SidebarHeader from '~/components/sidebars-layout/sidebar-header'
Expand All @@ -15,21 +16,6 @@ import useTabStore from '~/stores/tab-store'
import { useShallow } from 'zustand/react/shallow'
import { openInEditor } from '~/actions/navigationActions'
import { getBaseName } from '~/utils/path-utils'
import Button from '~/components/inputs/button'
import useFlowStore, { isStickyNote } from '~/stores/flow-store'
import type { StickyNote } from '~/routes/studio/canvas/nodetypes/sticky-note'
import { ALL_SHORTCUTS, formatShortcutParts, useShortcutStore } from '~/stores/shortcut-store'
import GroupContext from '~/routes/studio/context/group-context'

type RightPanelProps = {
isMultiSelect: boolean
selectedStickyId: string | null
selectedGroupId: string | null
showNodeContext: boolean
nodeId: number
editingSubtype: string | null
onShowNodeContext: (visible: boolean) => void
}

function getRightPanelTitle(
isMultiSelect: boolean,
Expand All @@ -46,106 +32,6 @@ function getRightPanelTitle(
return 'Palette'
}

function MultiSelectPanel() {
const { allInSameGroup, groupId } = useFlowStore(
useShallow((state) => {
const selected = state.nodes.filter((node) => node.selected)
if (selected.length < 2) return { allInSameGroup: false, groupId: null }

const content = selected.filter((node) => node.type === 'frankNode' || node.type === 'exitNode')
if (content.length < 2) return { allInSameGroup: false, groupId: null }

const firstParent = content[0].parentId
const allSame = Boolean(firstParent) && content.every((node) => node.parentId === firstParent)
return { allInSameGroup: allSame, groupId: allSame ? (firstParent ?? null) : null }
}),
)

const platform = useShortcutStore((shortcut) => shortcut.platform)
const groupDef = ALL_SHORTCUTS.find((shortCut) => shortCut.id === 'studio.group')!
const groupParts = formatShortcutParts(groupDef, platform)
const triggerGroup = () => useShortcutStore.getState().shortcuts.get('studio.group')?.handler?.()

if (allInSameGroup && groupId) {
return <GroupContext nodeId={groupId} />
}

return (
<div className="p-4">
<Button onClick={triggerGroup} className="flex w-full items-center justify-between gap-2 px-4 py-2 text-sm">
<span>Group</span>
<span className="flex gap-1">
{groupParts.map((part) => (
<kbd key={part} className="rounded border border-current/40 bg-current/10 px-1.5 py-0.5 font-mono text-xs">
{part}
</kbd>
))}
</span>
</Button>
</div>
)
}

function AttachedNotesPanel({ nodeId }: { nodeId: number }) {
const attachedNotes = useFlowStore(
useShallow((state) =>
state.nodes.filter(
(node) => isStickyNote(node) && (node as StickyNote).data.attachedToNodeId === nodeId.toString(),
),
),
) as StickyNote[]

if (attachedNotes.length === 0) return null

return (
<div className="border-b-border border-b px-4 pt-4 pb-3">
<div className="text-foreground-muted mb-2 text-xs font-semibold tracking-wide uppercase">
{attachedNotes.length === 1 ? 'Note' : 'Notes'}
</div>
<div className="flex flex-col gap-2">
{attachedNotes.map((note) => (
<div
key={note.id}
className="rounded-lg p-2 text-xs leading-snug wrap-break-word whitespace-pre-wrap"
style={{ background: note.data.color ?? '#fef08a' }}
>
{note.data.content || <span className="opacity-40">Empty note</span>}
</div>
))}
</div>
</div>
)
}

function RightPanelContent({
isMultiSelect,
selectedStickyId,
selectedGroupId,
showNodeContext,
nodeId,
onShowNodeContext,
}: RightPanelProps) {
const showPalette = !isMultiSelect && !selectedStickyId && !selectedGroupId && !showNodeContext

return (
<>
{isMultiSelect && <MultiSelectPanel />}
{!isMultiSelect && selectedStickyId && <StickyNoteContext nodeId={selectedStickyId} />}
{!isMultiSelect && !selectedStickyId && selectedGroupId && <GroupContext nodeId={selectedGroupId} />}
{!isMultiSelect && !selectedStickyId && !selectedGroupId && showNodeContext && (
<div className="flex min-h-0 flex-1 flex-col">
<AttachedNotesPanel nodeId={nodeId} />
<NodeContext nodeId={nodeId} setShowNodeContext={onShowNodeContext} />
</div>
)}

<div className={showPalette ? 'contents' : 'hidden'}>
<StudioContext />
</div>
</>
)
}

export default function Studio() {
const setVisibility = useSidebarStore((state) => state.setVisible)
const [showNodeContext, setShowNodeContext] = useState(false)
Expand All @@ -169,7 +55,7 @@ export default function Studio() {
const { activeTab, activeTabPath } = useTabStore(
useShallow((state) => ({
activeTab: state.activeTab,
activeTabPath: state.activeTab ? state.tabs[state.activeTab]?.configurationPath : undefined,
activeTabPath: state.activeTab ? state.tabs[state.activeTab]?.configurationPath : null,
})),
)

Expand Down Expand Up @@ -233,9 +119,11 @@ export default function Studio() {
</div>

{activeTab ? (
<>
<Flow showNodeContextMenu={handleShowNodeContext} onOpenInEditor={handleOpenInEditor} />
</>
<NodeContextMenuContext.Provider value={handleShowNodeContext}>
<ReactFlowProvider>
<FlowCanvas onOpenInEditor={handleOpenInEditor} />
</ReactFlowProvider>
</NodeContextMenuContext.Provider>
) : (
<div className="text-foreground-muted flex h-full flex-col items-center justify-center p-8 text-center">
<h2 className="mb-2 text-xl font-semibold">No adapter selected</h2>
Expand Down
Loading
Loading