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
44 changes: 44 additions & 0 deletions src/main/frontend/app/actions/navigationActions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,3 +33,47 @@ export function openInEditor(relativePath: string, filepath: string) {
setActiveTab(filepath)
useNavigationStore.getState().navigate('editor')
}

export function openInEditorAtElement(subtype: string, name: string | undefined, filepath: string) {
const editorStore = useEditorTabStore.getState()
const fileName = filepath.split(/[/\\]/).pop() ?? filepath

if (!editorStore.getTab(filepath)) {
editorStore.setTabData(filepath, {
name: fileName,
configurationPath: filepath,
})
}

editorStore.setPendingHighlight({ subtype, name })
editorStore.setActiveTab(filepath)
useNavigationStore.getState().navigate('editor')
}

export function openInStudioAtNode(
adapterName: string,
filepath: string,
adapterPosition: number,
subtype: string,
name: string,
) {
const { setTabData, setActiveTab, getTab } = useTabStore.getState()

const tabId = `${filepath}::${adapterName}::${adapterPosition}`

const existing = getTab(tabId)
if (existing) {
setTabData(tabId, { ...existing, pendingNodeSelection: { subtype, name } })
} else {
setTabData(tabId, {
name: adapterName,
configurationPath: filepath,
adapterPosition,
flowJson: {},
pendingNodeSelection: { subtype, name },
})
}

setActiveTab(tabId)
useNavigationStore.getState().navigate('studio')
}
24 changes: 24 additions & 0 deletions src/main/frontend/app/app.css
Original file line number Diff line number Diff line change
Expand Up @@ -139,6 +139,30 @@ body {
@apply border-l-4 border-yellow-400 bg-yellow-200/30 transition-colors;
}

.monaco-editor .frank-node-glyph {
cursor: pointer !important;
display: flex !important;
align-items: center;
justify-content: center;
opacity: 0.6;
transition: opacity 0.15s;
}

.monaco-editor .frank-node-glyph:hover {
opacity: 1;
}

.monaco-editor .frank-node-glyph::after {
content: '';
display: block;
width: 13px;
height: 13px;
margin-left: 5px;
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'%3E%3Crect x='1' y='1' width='14' height='14' rx='3' fill='none' stroke='%23888' stroke-width='1.5'/%3E%3Cpath d='M5.5 5.5l5 2.5-5 2.5V5.5z' fill='%23888'/%3E%3C/svg%3E");
background-size: 13px 13px;
background-repeat: no-repeat;
}

.monaco-editor .xml-lint.xml-lint--fatal-error {
border-color: #ff2424;
}
Expand Down
6 changes: 6 additions & 0 deletions src/main/frontend/app/components/flow/canvas-context-menu.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,11 @@ interface CanvasContextMenuProps {
onCut: () => void
onCopy: () => void
onPaste: () => void
onShowInEditor: () => void
hasSelection: boolean
hasGroupedSelection: boolean
hasClipboard: boolean
hasSingleNodeSelection: boolean
}

function formatShortcut(shortcutId: string): string | null {
Expand All @@ -33,9 +35,11 @@ export default function CanvasContextMenu({
onCut,
onCopy,
onPaste,
onShowInEditor,
hasSelection,
hasGroupedSelection,
hasClipboard,
hasSingleNodeSelection,
}: CanvasContextMenuProps) {
const menuRef = useRef<HTMLDivElement>(null)
useContextMenuDismiss(menuRef, onClose)
Expand Down Expand Up @@ -71,6 +75,8 @@ export default function CanvasContextMenu({
>
{menuItem('Add Note', onAddNote, true)}
<div className="bg-border my-1 h-px" />
{menuItem('Show in Editor', onShowInEditor, hasSingleNodeSelection, 'studio.show-in-editor')}
<div className="bg-border my-1 h-px" />
{menuItem('Group', onGroup, hasSelection, 'studio.group')}
{menuItem('Ungroup', onUngroup, hasGroupedSelection, 'studio.ungroup')}
<div className="bg-border my-1 h-px" />
Expand Down
110 changes: 107 additions & 3 deletions src/main/frontend/app/routes/editor/editor.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -36,10 +36,13 @@
extractFlowElements,
findAdapterIndexAtOffset,
findAdaptersInXml,
findElementRangeInXml,
findFrankElementsForGlyphs,
findFlowElementsStartLine,
lineToOffset,
wrapFlowXml,
} from './xml-utils'
import { openInStudioAtNode } from '~/actions/navigationActions'

type LeftTab = 'files' | 'git'
type SaveStatus = 'idle' | 'saving' | 'saved'
Expand Down Expand Up @@ -240,7 +243,7 @@
]
}

export default function CodeEditor() {

Check warning on line 246 in src/main/frontend/app/routes/editor/editor.tsx

View workflow job for this annotation

GitHub Actions / Build & Run All Tests

Refactor this function to reduce its Cognitive Complexity from 16 to the 15 allowed
const theme = useTheme()
const project = useProjectStore.getState().project
const [activeTabFilePath, setActiveTabFilePath] = useState<string>(useEditorTabStore.getState().activeTabFilePath)
Expand All @@ -255,12 +258,19 @@
const xsdContentRef = useRef<string | null>(null)
const errorDecorationsRef = useRef<{ clear: () => void } | null>(null)
const flowDecorationsRef = useRef<IEditorDecorationsCollection | null>(null)
const highlightDecorationsRef = useRef<IEditorDecorationsCollection | null>(null)
const frankGlyphsDecorationsRef = useRef<IEditorDecorationsCollection | null>(null)
const frankElementsRef = useRef<ReturnType<typeof findFrankElementsForGlyphs>>([])
const debounceTimerRef = useRef<ReturnType<typeof setTimeout> | null>(null)
const savedTimerRef = useRef<ReturnType<typeof setTimeout> | null>(null)
const validationTimerRef = useRef<ReturnType<typeof setTimeout> | null>(null)
const validationCounterRef = useRef(0)
const contentCacheRef = useRef<Map<string, CachedFile>>(new Map())

const [pendingHighlight, setPendingHighlightLocal] = useState<{ subtype: string; name?: string } | null>(
() => useEditorTabStore.getState().pendingHighlight,
)

const activeTab = useEditorTabStore(
useShallow((state) => {
const tab = state.activeTabFilePath ? state.tabs[state.activeTabFilePath] : undefined
Expand Down Expand Up @@ -301,6 +311,31 @@
}
}, [fileLanguage])

const applyFrankGlyphs = useCallback(
(content: string) => {
const editor = editorReference.current
if (!editor || fileLanguage !== 'xml') return

const elements = findFrankElementsForGlyphs(content)
frankElementsRef.current = elements

const decorations = elements.map((element) => ({
range: { startLineNumber: element.startLine, startColumn: 1, endLineNumber: element.startLine, endColumn: 1 },
options: {
glyphMarginClassName: 'frank-node-glyph',
glyphMarginHoverMessage: { value: `Open **${element.name}** in Studio` },
},
}))

if (frankGlyphsDecorationsRef.current) {
frankGlyphsDecorationsRef.current.set(decorations)
} else if (decorations.length > 0) {
frankGlyphsDecorationsRef.current = editor.createDecorationsCollection(decorations)
}
},
[fileLanguage],
)

const performSave = useCallback(
(content?: string) => {
if (!project || !activeTabFilePath || isDiffTab) return
Expand Down Expand Up @@ -482,8 +517,11 @@
const handleEditorMount: OnMount = (editor, monacoInstance) => {
editorReference.current = editor
monacoReference.current = monacoInstance
frankGlyphsDecorationsRef.current = null
setEditorMounted(true)

editor.updateOptions({ glyphMargin: true })

applyFlowHighlighter()

editor.addAction({
Expand Down Expand Up @@ -513,6 +551,32 @@
],
run: runReformat,
})

editor.onMouseDown((event) => {
if (highlightDecorationsRef.current) {
highlightDecorationsRef.current.clear()
highlightDecorationsRef.current = null
}

Comment thread
stijnpotters1 marked this conversation as resolved.
if (event.target.type === monacoInstance.editor.MouseTargetType.GUTTER_GLYPH_MARGIN) {
const lineNumber = event.target.position?.lineNumber
if (!lineNumber) return

const editorTab = useEditorTabStore.getState().getTab(useEditorTabStore.getState().activeTabFilePath)
if (!editorTab) return

const element = frankElementsRef.current.find((element) => element.startLine === lineNumber)
if (!element) return

openInStudioAtNode(
element.adapterName,
editorTab.configurationPath,
element.adapterPosition,
element.subtype,
element.name,
)
}
})
}

useEffect(() => {
Expand Down Expand Up @@ -591,10 +655,13 @@
errorDecorationsRef.current.clear()
errorDecorationsRef.current = null
}
// Also clear flow decorations when switching files
if (flowDecorationsRef.current) {
flowDecorationsRef.current.set([])
}
if (frankGlyphsDecorationsRef.current) {
frankGlyphsDecorationsRef.current.clear()
frankGlyphsDecorationsRef.current = null
}
const monaco = monacoReference.current
const editor = editorReference.current
if (monaco && editor) {
Expand All @@ -606,9 +673,14 @@
useEffect(() => {
if (!fileContent || !xsdLoaded || isDiffTab || fileLanguage !== 'xml') return
runSchemaValidation(fileContent)
applyFlowHighlighter() // Refresh highlighter when schema is loaded or content changes
applyFlowHighlighter()
}, [fileContent, xsdLoaded, isDiffTab, runSchemaValidation, fileLanguage, applyFlowHighlighter])

useEffect(() => {
if (!fileContent || !editorMounted || isDiffTab || fileLanguage !== 'xml') return
applyFrankGlyphs(fileContent)
}, [fileContent, editorMounted, isDiffTab, fileLanguage, applyFrankGlyphs])

useEffect(() => {
if (!fileContent || !activeTabFilePath || !editorReference.current || isDiffTab) return

Expand Down Expand Up @@ -636,6 +708,37 @@
return () => clearTimeout(timeout)
}, [fileContent, activeTabFilePath, isDiffTab])

useEffect(() => {
return useEditorTabStore.subscribe(
(state) => state.pendingHighlight,
(highlight) => setPendingHighlightLocal(highlight),
)
}, [])

useEffect(() => {
if (!pendingHighlight || !fileContent || !editorReference.current || isDiffTab) return

const editor = editorReference.current
const range = findElementRangeInXml(fileContent, pendingHighlight.subtype, pendingHighlight.name)

useEditorTabStore.getState().setPendingHighlight(null)

if (!range) return

editor.revealLineNearTop(range.startLine)
editor.setPosition({ lineNumber: range.startLine, column: 1 })
editor.focus()

highlightDecorationsRef.current?.clear()

highlightDecorationsRef.current = editor.createDecorationsCollection([
{
range: { startLineNumber: range.startLine, startColumn: 1, endLineNumber: range.endLine, endColumn: 1 },
options: { isWholeLine: true, className: 'highlight-line' },
},
])
}, [pendingHighlight, fileContent, isDiffTab, editorMounted])

const handleOpenInStudio = useCallback(() => {
const editorTab = useEditorTabStore.getState().getTab(activeTabFilePath)
if (!editorTab) return
Expand Down Expand Up @@ -737,7 +840,7 @@
scheduleSave()
if (value && fileLanguage === 'xml') {
scheduleSchemaValidation(value)
applyFlowHighlighter() // Real-time highlight updates
applyFlowHighlighter()
}
}}
options={{
Expand All @@ -746,6 +849,7 @@
tabSize: 2,
insertSpaces: true,
detectIndentation: false,
glyphMargin: true,
}}
/>
</div>
Expand Down
Loading
Loading