Skip to content

Commit 6d005c3

Browse files
authored
File api for non configuration files & monaco file language support (#411)
1 parent 3cbd936 commit 6d005c3

39 files changed

Lines changed: 1049 additions & 941 deletions

src/main/frontend/app/components/file-structure/name-input-dialog.tsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ const namePatterns: Record<string, RegExp> = {
88
'Cannot contain /': /^[^/]*$/,
99
'Cannot contain \\': /^[^\\]*$/,
1010
'Cannot contain ..': /^(?!.*\.\.).*$/,
11+
'Must end with:\n.xml, .json, .yaml, .yml, or .properties': /^(.*\.(xml|json|yaml|yml|properties))?$/i,
1112
}
1213

1314
interface NameInputDialogProps {

src/main/frontend/app/components/file-structure/use-file-tree-context-menu.ts

Lines changed: 24 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,11 @@
1-
import { useCallback, useRef, useState } from 'react'
1+
import React, { useCallback, useRef, useState } from 'react'
22
import type { TreeItemIndex } from 'react-complex-tree'
3-
import {
4-
createFileInProject,
5-
createFolderInProject,
6-
renameInProject,
7-
deleteInProject,
8-
} from '~/services/file-tree-service'
3+
import { createFile, deleteFile, renameFile } from '~/services/file-service'
4+
import { createFolderInProject } from '~/services/file-tree-service'
95
import { clearConfigurationCache } from '~/services/configuration-service'
106
import useTabStore from '~/stores/tab-store'
117
import useEditorTabStore from '~/stores/editor-tab-store'
12-
import { showErrorToastFrom } from '~/components/toast'
8+
import { showErrorToast, showErrorToastFrom } from '~/components/toast'
139

1410
export interface ContextMenuState {
1511
position: { x: number; y: number }
@@ -47,15 +43,19 @@ interface UseFileTreeContextMenuOptions {
4743
onAfterDelete?: (path: string) => void
4844
}
4945

46+
const ALLOWED_EXTENSIONS = ['.xml', '.json', '.yaml', '.yml', '.properties']
47+
5048
export function getParentItemId(itemId: TreeItemIndex): TreeItemIndex {
5149
const str = String(itemId)
5250
const lastSlash = str.lastIndexOf('/')
5351
return lastSlash > 0 ? str.slice(0, Math.max(0, lastSlash)) : 'root'
5452
}
5553

56-
function ensureXmlExtension(name: string): string {
57-
if (name.includes('.')) return name
58-
return `${name}.xml`
54+
function ensureHasCorrectExtension(name: string): boolean {
55+
const dotIndex = name.lastIndexOf('.')
56+
if (dotIndex === -1) return false
57+
const extension = name.slice(dotIndex)
58+
return ALLOWED_EXTENSIONS.includes(extension.toLowerCase())
5959
}
6060

6161
function buildNewPath(oldPath: string, newName: string): string {
@@ -117,9 +117,13 @@ export function useFileTreeContextMenu({
117117
setNameDialog({
118118
title: 'New File',
119119
onSubmit: async (name: string) => {
120-
const fileName = ensureXmlExtension(name)
120+
if (!ensureHasCorrectExtension(name)) {
121+
showErrorToast(`Filename must have one of the following extensions: ${ALLOWED_EXTENSIONS.join(', ')}`)
122+
return
123+
}
124+
121125
try {
122-
await createFileInProject(projectName, parentPath, fileName)
126+
await createFile(projectName, `${parentPath}/${name}`)
123127
await dataProvider.reloadDirectory(parentItemId)
124128
} catch (error) {
125129
showErrorToastFrom('Failed to create file', error)
@@ -143,7 +147,7 @@ export function useFileTreeContextMenu({
143147
title: 'New Folder',
144148
onSubmit: async (name: string) => {
145149
try {
146-
await createFolderInProject(projectName, parentPath, name)
150+
await createFolderInProject(projectName, `${parentPath}/${name}`)
147151
await dataProvider.reloadDirectory(parentItemId)
148152
} catch (error) {
149153
showErrorToastFrom('Failed to create folder', error)
@@ -171,9 +175,13 @@ export function useFileTreeContextMenu({
171175
if (newName === oldName) {
172176
setNameDialog(null)
173177
return
178+
} else if (!ensureHasCorrectExtension(newName)) {
179+
showErrorToast(`Filename must have one of the following extensions: ${ALLOWED_EXTENSIONS.join(', ')}`)
180+
return
174181
}
182+
175183
try {
176-
await renameInProject(projectName, oldPath, newName)
184+
await renameFile(projectName, `${oldPath}`, `${oldPath}`.replace(oldName, newName))
177185
clearConfigurationCache(projectName, oldPath)
178186
const newPath = buildNewPath(oldPath, newName)
179187
useTabStore.getState().renameTabsForConfig(oldPath, newPath)
@@ -209,7 +217,7 @@ export function useFileTreeContextMenu({
209217
if (!deleteTarget || !projectName || !dataProvider) return
210218

211219
try {
212-
await deleteInProject(projectName, deleteTarget.path)
220+
await deleteFile(projectName, deleteTarget.path)
213221
clearConfigurationCache(projectName, deleteTarget.path)
214222
useTabStore.getState().removeTabsForConfig(deleteTarget.path)
215223
useEditorTabStore.getState().refreshAllTabs()

src/main/frontend/app/components/file-structure/use-studio-context-menu.ts

Lines changed: 7 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,9 @@
11
import { useCallback, useRef, useState } from 'react'
22
import type { TreeItemIndex } from 'react-complex-tree'
3-
import {
4-
createFileInProject,
5-
createFolderInProject,
6-
renameInProject,
7-
deleteInProject,
8-
} from '~/services/file-tree-service'
3+
import { deleteFile, renameFile } from '~/services/file-service'
4+
import { createFolderInProject } from '~/services/file-tree-service'
95
import { createAdapter, renameAdapter, deleteAdapter } from '~/services/adapter-service'
10-
import { clearConfigurationCache } from '~/services/configuration-service'
6+
import { clearConfigurationCache, createConfiguration } from '~/services/configuration-service'
117
import useTabStore from '~/stores/tab-store'
128
import { showErrorToastFrom } from '~/components/toast'
139
import type { StudioItemData, StudioFolderData, StudioAdapterData } from './studio-files-data-provider'
@@ -156,7 +152,7 @@ export function useStudioContextMenu({ projectName, dataProvider }: UseStudioCon
156152
onSubmit: async (name: string) => {
157153
const fileName = ensureXmlExtension(name)
158154
try {
159-
await createFileInProject(projectName, menu.folderPath, fileName)
155+
await createConfiguration(projectName, `${menu.folderPath}/${fileName}`)
160156
await dataProvider.reloadDirectory('root')
161157
} catch (error) {
162158
showErrorToastFrom('Failed to create configuration', error)
@@ -200,7 +196,7 @@ export function useStudioContextMenu({ projectName, dataProvider }: UseStudioCon
200196
title: 'New Folder',
201197
onSubmit: async (name: string) => {
202198
try {
203-
await createFolderInProject(projectName, menu.folderPath, name)
199+
await createFolderInProject(projectName, `${menu.folderPath}/${name}`)
204200
await dataProvider.reloadDirectory('root')
205201
} catch (error) {
206202
showErrorToastFrom('Failed to create folder', error)
@@ -232,7 +228,7 @@ export function useStudioContextMenu({ projectName, dataProvider }: UseStudioCon
232228
await renameAdapter(projectName, oldName, newName, menu.path)
233229
} else {
234230
const finalName = menu.itemType === 'configuration' ? ensureXmlExtension(newName) : newName
235-
await renameInProject(projectName, menu.path, finalName)
231+
await renameFile(projectName, `${menu.path}/${oldName}`, `${menu.path}/${newName}`)
236232
clearConfigurationCache(projectName, menu.path)
237233
const newPath = `${getParentDir(menu.path)}/${finalName}`
238234
useTabStore.getState().renameTabsForConfig(menu.path, newPath)
@@ -270,7 +266,7 @@ export function useStudioContextMenu({ projectName, dataProvider }: UseStudioCon
270266
await deleteAdapter(projectName, deleteTarget.name, deleteTarget.path)
271267
removeAdapterTab(deleteTarget.path, deleteTarget.name)
272268
} else {
273-
await deleteInProject(projectName, deleteTarget.path)
269+
await deleteFile(projectName, deleteTarget.path)
274270
clearConfigurationCache(projectName, deleteTarget.path)
275271
useTabStore.getState().removeTabsForConfig(deleteTarget.path)
276272
}

src/main/frontend/app/components/inputs/validatedInput.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -78,7 +78,7 @@ export default function ValidatedInput({
7878
return (
7979
<div key={check} className="flex items-center gap-1">
8080
<Icon className={clsx('h-4 w-4', satisfied ? 'fill-green-500' : 'fill-red-500')} />
81-
<span>{check}</span>
81+
<span className="whitespace-pre">{check}</span>
8282
</div>
8383
)
8484
})}

src/main/frontend/app/routes/configurations/add-configuration-modal.tsx

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
1-
import { useEffect, useState } from 'react'
1+
import React, { useEffect, useState } from 'react'
2+
import { createConfiguration } from '~/services/configuration-service'
23
import { useProjectStore } from '~/stores/project-store'
34
import type { Project } from '~/types/project.types'
45
import Button from '~/components/inputs/button'
56
import DirectoryPicker from '~/components/directory-picker/directory-picker'
6-
import { createFileInProject } from '~/services/file-tree-service'
77
import { fetchProject } from '~/services/project-service'
88

99
interface AddConfigurationModalProperties {
@@ -50,7 +50,7 @@ export default function AddConfigurationModal({
5050
configname = `${configname}.xml`
5151
}
5252

53-
await createFileInProject(currentProject.name, rootLocationName, configname)
53+
await createConfiguration(currentProject.name, `${rootLocationName}/${configname}`)
5454
const updatedProject = await fetchProject(currentProject.name)
5555
setProject(updatedProject)
5656
onSuccess?.()
@@ -92,7 +92,7 @@ export default function AddConfigurationModal({
9292
className="bg-background/50 absolute inset-0 z-50 flex items-center justify-center"
9393
onClick={handleClickedOutside}
9494
>
95-
<div className="bg-background border-border relative h-[400px] w-1/3 min-w-[800px] rounded-lg border p-6 shadow-lg">
95+
<div className="bg-background border-border relative h-100 w-1/3 min-w-200 rounded-lg border p-6 shadow-lg">
9696
<h2 className="mb-4 text-lg font-semibold">Add Configuration</h2>
9797
<p className="mb-4">Add a new configuration file.</p>
9898

src/main/frontend/app/routes/configurations/configuration-manager.tsx

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import { deleteFile } from '~/services/file-service'
12
import { useProjectStore } from '~/stores/project-store'
23
import ConfigurationTile from './configuration-tile'
34
import ArrowLeftIcon from '/icons/solar/Alt Arrow Left.svg?react'
@@ -7,7 +8,7 @@ import { useState, useEffect, useCallback, type ChangeEvent, useMemo } from 'rea
78
import AddConfigurationModal from './add-configuration-modal'
89
import LoadingSpinner from '~/components/loading-spinner'
910
import type { FileTreeNode } from '~/types/filesystem.types'
10-
import { deleteInProject, fetchProjectTree } from '~/services/file-tree-service'
11+
import { fetchProjectTree } from '~/services/file-tree-service'
1112
import Button from '~/components/inputs/button'
1213
import Search from '~/components/search/search'
1314
import { toRelativePath } from '~/utils/path-utils'
@@ -104,7 +105,7 @@ export default function ConfigurationManager() {
104105

105106
const handleDelete = async (filepath: string) => {
106107
if (!currentProject?.name) return
107-
await deleteInProject(currentProject.name, filepath)
108+
await deleteFile(currentProject.name, filepath)
108109
loadTree()
109110
}
110111

0 commit comments

Comments
 (0)