diff --git a/frontend/src/lib/components/FolderPicker.svelte b/frontend/src/lib/components/FolderPicker.svelte index 341b2c741a..f9e2e629f7 100644 --- a/frontend/src/lib/components/FolderPicker.svelte +++ b/frontend/src/lib/components/FolderPicker.svelte @@ -7,7 +7,11 @@ import Select from './select/Select.svelte' import TextInput from './text_input/TextInput.svelte' import Label from './Label.svelte' + import InputError from './InputError.svelte' import { tick } from 'svelte' + import { sendUserToast } from '$lib/toast' + + const VALID_FOLDER_NAME = /^[a-zA-Z_0-9]+$/ let folders: { name: string; write: boolean }[] = $state([]) let filterText: string = $state('') @@ -17,6 +21,8 @@ let viewFolder: Drawer | null = $state(null) let newFolderName: string = $state('') let folderCreated: string | undefined = $state(undefined) + let creating: boolean = $state(false) + let loadingFolders: boolean = $state(true) type Props = { folderName: string @@ -37,30 +43,37 @@ }: Props = $props() async function loadFolders(): Promise { - let initialFolders: { name: string; write: boolean }[] = [] - let initialFolder = '' - if (initialPath?.split('/')?.[0] == 'f') { - initialFolder = initialPath?.split('/')?.[1] - initialFolders.push({ name: initialFolder, write: true }) - } + loadingFolders = true + try { + let initialFolders: { name: string; write: boolean }[] = [] + let initialFolder = '' + if (initialPath?.split('/')?.[0] == 'f') { + initialFolder = initialPath?.split('/')?.[1] + initialFolders.push({ name: initialFolder, write: true }) + } - const excludedFolders = [initialFolder, 'app_groups', 'app_custom', 'app_themes'] + const excludedFolders = [initialFolder, 'app_groups', 'app_custom', 'app_themes'] - folders = initialFolders.concat( - ( - await FolderService.listFolderNames({ - workspace: $workspaceStore! - }) + folders = initialFolders.concat( + ( + await FolderService.listFolderNames({ + workspace: $workspaceStore! + }) + ) + .filter((x) => !excludedFolders.includes(x)) + .map((x) => ({ + name: x, + write: + $userStore?.folders?.includes(x) == true || + ($userStore?.is_admin ?? false) || + ($userStore?.is_super_admin ?? false) + })) ) - .filter((x) => !excludedFolders.includes(x)) - .map((x) => ({ - name: x, - write: - $userStore?.folders?.includes(x) == true || - ($userStore?.is_admin ?? false) || - ($userStore?.is_super_admin ?? false) - })) - ) + } catch (e) { + sendUserToast(`Could not load folders: ${e}`, true) + } finally { + loadingFolders = false + } } async function openCreateFolder() { @@ -72,14 +85,24 @@ } async function addFolder() { - await FolderService.createFolder({ - workspace: $workspaceStore ?? '', - requestBody: { name: newFolderName } - }) - folderCreated = newFolderName - $userStore?.folders?.push(newFolderName) - folderName = newFolderName - loadFolders() + if (nameError || !newFolderName || creating) return + creating = true + try { + await FolderService.createFolder({ + workspace: $workspaceStore ?? '', + requestBody: { name: newFolderName } + }) + folderCreated = newFolderName + if ($userStore) { + $userStore.folders = [...($userStore.folders ?? []), newFolderName] + } + folderName = newFolderName + loadFolders() + } catch (e) { + sendUserToast(`Could not create folder: ${e}`, true) + } finally { + creating = false + } } let selectItems = $derived( @@ -90,6 +113,16 @@ })) ) + let nameError = $derived( + !newFolderName + ? '' + : !VALID_FOLDER_NAME.test(newFolderName) + ? 'Folder name can only contain alphanumeric characters and underscores' + : folders.some((f) => f.name === newFolderName) + ? 'A folder with this name already exists' + : '' + ) + let noMatchingItems = $derived( filterText && !selectItems.some((item) => item.label.toLowerCase().includes(filterText.toLowerCase())) @@ -120,20 +153,29 @@
- +
{/if} @@ -153,12 +195,15 @@ bind:open={selectOpen} items={selectItems} disabled={disabled || disableEditing} + loading={loadingFolders} {size} placeholder="Select folder" > {#snippet bottomSnippet({ close })}