diff --git a/src/main/frontend/app/routes/editor/editor.tsx b/src/main/frontend/app/routes/editor/editor.tsx index 6962942b..3412aa92 100644 --- a/src/main/frontend/app/routes/editor/editor.tsx +++ b/src/main/frontend/app/routes/editor/editor.tsx @@ -1,5 +1,7 @@ import RulerCrossPenIcon from '/icons/solar/Ruler Cross Pen.svg?react' import Editor, { type Monaco, type OnMount } from '@monaco-editor/react' +import prettier from 'prettier' +import parserHTML from 'prettier/plugins/html' import clsx from 'clsx' import XsdFeatures from 'monaco-xsd-code-completion/esm/XsdFeatures' import 'monaco-xsd-code-completion/src/style.css' @@ -159,6 +161,14 @@ function isConfigurationFile(fileExtension: string) { return fileExtension === 'xml' } +function prettierFormat(xml: string): Promise { + return prettier.format(xml, { + parser: 'html', + plugins: [parserHTML], + tabWidth: 3, + }) +} + export default function CodeEditor() { const theme = useTheme() const project = useProjectStore.getState().project @@ -217,7 +227,6 @@ export default function CodeEditor() { if (isConfigurationFile(fileExtension ?? '')) { saveConfiguration(project.name, configPath, updatedContent) .then(({ xmlContent }) => { - setFileContent(xmlContent) contentCacheRef.current.set(activeTabFilePath, { type: 'xml', content: xmlContent }) finishSaving() if (project.isGitRepository) refreshOpenDiffs(project.name) @@ -290,6 +299,18 @@ export default function CodeEditor() { ) }, []) + const runPrettierReformat = async () => { + const editor = editorReference.current + if (!editor) return + const model = editor.getModel() + if (!model) return + try { + if (model) model.setValue(await prettierFormat(model.getValue())) + } catch (error) { + console.error('Failed to reformat XML:', error) + } + } + const runSchemaValidation = useCallback( async (content: string) => { const monaco = monacoReference.current @@ -374,7 +395,6 @@ export default function CodeEditor() { xsdFeatures.addCompletion() xsdFeatures.addGenerateAction() - xsdFeatures.addReformatAction() fetchFrankConfigXsd() .then((xsdContent) => { @@ -422,6 +442,19 @@ export default function CodeEditor() { } }, }) + + editor.addAction({ + id: 'reformat-xml-prettier', + label: 'Reformat', + contextMenuGroupId: 'navigation', + contextMenuOrder: 3, + keybindings: [ + monacoReference.current.KeyMod.CtrlCmd | + monacoReference.current.KeyMod.Shift | + monacoReference.current.KeyCode.KeyR, + ], + run: runPrettierReformat, + }) } useEffect(() => { @@ -642,7 +675,7 @@ export default function CodeEditor() { scheduleSave() if (value && fileLanguage === 'xml') scheduleSchemaValidation(value) }} - options={{ automaticLayout: true, quickSuggestions: false }} + options={{ automaticLayout: true, quickSuggestions: false, tabSize: 3 }} /> diff --git a/src/main/frontend/app/routes/studio/canvas/flow.tsx b/src/main/frontend/app/routes/studio/canvas/flow.tsx index a50d5fc4..340542d6 100644 --- a/src/main/frontend/app/routes/studio/canvas/flow.tsx +++ b/src/main/frontend/app/routes/studio/canvas/flow.tsx @@ -173,7 +173,7 @@ function FlowCanvas() { await saveConfiguration(currentProject.name, configurationPath, updatedConfigXml) clearConfigurationCache(currentProject.name, configurationPath) useEditorTabStore.getState().refreshAllTabs() - if (currentProject.isGitRepository) refreshOpenDiffs(currentProject.name) + if (currentProject.isGitRepository) await refreshOpenDiffs(currentProject.name) setSaveStatus('saved') if (savedTimerRef.current) clearTimeout(savedTimerRef.current) diff --git a/src/main/java/org/frankframework/flow/adapter/AdapterService.java b/src/main/java/org/frankframework/flow/adapter/AdapterService.java index a91f2536..d9742461 100644 --- a/src/main/java/org/frankframework/flow/adapter/AdapterService.java +++ b/src/main/java/org/frankframework/flow/adapter/AdapterService.java @@ -84,7 +84,6 @@ public boolean updateAdapter(Path configurationFile, String adapterName, String String updatedXml = XmlConfigurationUtils.convertNodeToString(configDoc); Files.writeString(absConfigFile, updatedXml, StandardCharsets.UTF_8, StandardOpenOption.TRUNCATE_EXISTING); return true; - } catch (AdapterNotFoundException e) { throw e; } catch (Exception e) { diff --git a/src/main/java/org/frankframework/flow/configuration/ConfigurationController.java b/src/main/java/org/frankframework/flow/configuration/ConfigurationController.java index 15c038de..40f0913c 100644 --- a/src/main/java/org/frankframework/flow/configuration/ConfigurationController.java +++ b/src/main/java/org/frankframework/flow/configuration/ConfigurationController.java @@ -52,7 +52,7 @@ public ResponseEntity updateConfiguration( public ResponseEntity addConfiguration( @PathVariable String projectName, @RequestParam String name - ) throws ApiException, IOException { + ) throws ApiException, IOException, TransformerException, ParserConfigurationException, SAXException { String content = configurationService.addConfiguration(projectName, name); XmlDTO xmlDTO = new XmlDTO(content); return ResponseEntity.ok(xmlDTO); diff --git a/src/main/java/org/frankframework/flow/configuration/ConfigurationService.java b/src/main/java/org/frankframework/flow/configuration/ConfigurationService.java index e3d204b2..912bb787 100644 --- a/src/main/java/org/frankframework/flow/configuration/ConfigurationService.java +++ b/src/main/java/org/frankframework/flow/configuration/ConfigurationService.java @@ -49,14 +49,16 @@ public String updateConfiguration(String projectName, String filepath, String co throw new ApiException("Invalid file path: " + filepath, HttpStatus.NOT_FOUND); } - Document updatedDocument = XmlConfigurationUtils.insertFlowNamespace(content); - String updatedContent = XmlConfigurationUtils.convertNodeToString(updatedDocument); - fileSystemStorage.writeFile(absolutePath.toString(), updatedContent); - return updatedContent; + Document document = XmlConfigurationUtils.insertFlowNamespace(content); + String formatted = XmlConfigurationUtils.convertNodeToString(document); + + // Just write to the disk. ProjectService reads directly from disk now! + fileSystemStorage.writeFile(absolutePath.toString(), formatted); + return formatted; } - public String addConfiguration(String projectName, String configurationName) throws IOException, ApiException { + public String addConfiguration(String projectName, String configurationName) throws IOException, ApiException, TransformerException, ParserConfigurationException, SAXException { Project project = projectService.getProject(projectName); Path absProjectPath = fileSystemStorage.toAbsolutePath(project.getRootPath()); Path configDir = absProjectPath.resolve(CONFIGURATIONS_DIR).normalize(); @@ -71,8 +73,11 @@ public String addConfiguration(String projectName, String configurationName) thr } String defaultXml = loadDefaultConfigurationXml(); - fileSystemStorage.writeFile(filePath.toString(), defaultXml); - return defaultXml; + Document updatedDocument = XmlConfigurationUtils.insertFlowNamespace(defaultXml); + String updatedContent = XmlConfigurationUtils.convertNodeToString(updatedDocument); + fileSystemStorage.writeFile(filePath.toString(), updatedContent); + + return updatedContent; } private String loadDefaultConfigurationXml() throws IOException { diff --git a/src/test/java/org/frankframework/flow/configuration/ConfigurationServiceTest.java b/src/test/java/org/frankframework/flow/configuration/ConfigurationServiceTest.java index 2e4ef0f6..9fd40cfd 100644 --- a/src/test/java/org/frankframework/flow/configuration/ConfigurationServiceTest.java +++ b/src/test/java/org/frankframework/flow/configuration/ConfigurationServiceTest.java @@ -2,6 +2,7 @@ import static org.junit.jupiter.api.Assertions.*; import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.*; import java.io.IOException; @@ -111,8 +112,9 @@ void updateConfiguration_Success() throws Exception { configurationService.updateConfiguration("test", file.toString(), ""); - assertEquals("\n", Files.readString(file, StandardCharsets.UTF_8)); - verify(fileSystemStorage).writeFile(file.toString(), "\n"); + String result = Files.readString(file, StandardCharsets.UTF_8).trim(); + assertEquals("", result); + verify(fileSystemStorage).writeFile(eq(file.toString()), anyString()); } @Test