diff --git a/frontend/src/lib/components/common/table/AppRow.svelte b/frontend/src/lib/components/common/table/AppRow.svelte index 7e7beee6f2..494a2a8603 100644 --- a/frontend/src/lib/components/common/table/AppRow.svelte +++ b/frontend/src/lib/components/common/table/AppRow.svelte @@ -34,6 +34,7 @@ export let moveDrawer: MoveDrawer export let deploymentDrawer: DeployWorkspaceDrawer export let deleteConfirmedCallback: (() => void) | undefined + export let depth: number = 0 let { summary, @@ -77,6 +78,7 @@ {starred} on:change canFavorite={!draft_only} + {depth} > {#if execution_mode == 'anonymous'} diff --git a/frontend/src/lib/components/common/table/FlowRow.svelte b/frontend/src/lib/components/common/table/FlowRow.svelte index a4be4b3fc3..31455715db 100644 --- a/frontend/src/lib/components/common/table/FlowRow.svelte +++ b/frontend/src/lib/components/common/table/FlowRow.svelte @@ -35,6 +35,7 @@ export let deleteConfirmedCallback: (() => void) | undefined export let deploymentDrawer: DeployWorkspaceDrawer export let errorHandlerMuted: boolean + export let depth: number = 0 let { summary, path, extra_perms, canWrite, workspace_id, archived, draft_only, has_draft } = flow @@ -78,6 +79,7 @@ {errorHandlerMuted} on:change canFavorite={!draft_only} + {depth} > {#if archived} diff --git a/frontend/src/lib/components/common/table/RawAppRow.svelte b/frontend/src/lib/components/common/table/RawAppRow.svelte index 4d41d0bc62..b280a99fc5 100644 --- a/frontend/src/lib/components/common/table/RawAppRow.svelte +++ b/frontend/src/lib/components/common/table/RawAppRow.svelte @@ -22,6 +22,7 @@ export let moveDrawer: MoveDrawer export let deleteConfirmedCallback: (() => void) | undefined export let deploymentDrawer: DeployWorkspaceDrawer + export let depth: number = 0 let updateAppDrawer: Drawer @@ -60,6 +61,7 @@ {starred} on:change canFavorite={true} + {depth} > diff --git a/frontend/src/lib/components/common/table/Row.svelte b/frontend/src/lib/components/common/table/Row.svelte index 7c60ac4e43..863421eb95 100644 --- a/frontend/src/lib/components/common/table/Row.svelte +++ b/frontend/src/lib/components/common/table/Row.svelte @@ -3,6 +3,7 @@ import { createEventDispatcher } from 'svelte' import RowIcon from './RowIcon.svelte' import { BellOff } from 'lucide-svelte' + import { twMerge } from 'tailwind-merge' export let marked: string | undefined export let starred: boolean @@ -16,11 +17,15 @@ export let path: string export let href: string export let workspaceId: string + export let depth: number = 0
0 ? '!rounded-none' : '' + )} + style={depth > 0 ? `padding-left: ${depth * 32}px;` : ''} >
diff --git a/frontend/src/lib/components/common/table/ScriptRow.svelte b/frontend/src/lib/components/common/table/ScriptRow.svelte index 76e6cddfaa..dc5bbda9c5 100644 --- a/frontend/src/lib/components/common/table/ScriptRow.svelte +++ b/frontend/src/lib/components/common/table/ScriptRow.svelte @@ -37,6 +37,7 @@ export let deploymentDrawer: DeployWorkspaceDrawer export let deleteConfirmedCallback: (() => void) | undefined export let errorHandlerMuted: boolean + export let depth: number = 0 let { summary, @@ -96,6 +97,7 @@ workspaceId={$workspaceStore ?? ''} on:change canFavorite={!draft_only} + {depth} > {#if lock_error_logs} diff --git a/frontend/src/lib/components/home/Item.svelte b/frontend/src/lib/components/home/Item.svelte new file mode 100644 index 0000000000..c2d75a66e2 --- /dev/null +++ b/frontend/src/lib/components/home/Item.svelte @@ -0,0 +1,127 @@ + + +{#key item.summary} + {#key item.starred} + {#key item.has_draft} + {#if item.type == 'script'} + dispatch('scriptChanged')} + script={item} + errorHandlerMuted={item.ws_error_handler_muted === undefined || + item.ws_error_handler_muted === null + ? false + : item.ws_error_handler_muted} + {shareModal} + {moveDrawer} + {deploymentDrawer} + {depth} + /> + {:else if item.type == 'flow'} + dispatch('flowChanged')} + flow={item} + errorHandlerMuted={item.ws_error_handler_muted === undefined || + item.ws_error_handler_muted === null + ? false + : item.ws_error_handler_muted} + {shareModal} + {moveDrawer} + {deploymentDrawer} + {depth} + /> + {:else if item.type == 'app'} + dispatch('appChanged')} + app={item} + {moveDrawer} + {shareModal} + {deploymentDrawer} + {depth} + /> + {:else if item.type == 'raw_app'} + dispatch('rawAppChanged')} + app={item} + {moveDrawer} + {shareModal} + {deploymentDrawer} + {depth} + /> + {/if} + {/key} + {/key} +{/key} + + { + deleteConfirmedCallback = undefined + }} + on:confirmed={() => { + if (deleteConfirmedCallback) { + deleteConfirmedCallback() + } + deleteConfirmedCallback = undefined + }} +> +
+ Are you sure you want to remove it? + +
+ You can press + SHIFT + while removing to bypass confirmation. +
+
+
+
+ + { + dispatch('reload') + }} +/> + + + { + dispatch('reload') + }} +/> diff --git a/frontend/src/lib/components/home/ItemsList.svelte b/frontend/src/lib/components/home/ItemsList.svelte index b1a088d828..62c3208792 100644 --- a/frontend/src/lib/components/home/ItemsList.svelte +++ b/frontend/src/lib/components/home/ItemsList.svelte @@ -1,7 +1,6 @@ @@ -267,27 +256,6 @@ {opts} /> - { - loadScripts() - loadApps() - loadFlows() - loadRawApps() - }} -/> - - - { - loadScripts() - loadApps() - loadFlows() - loadRawApps() - }} -/> -
@@ -371,8 +339,9 @@
{/if} {#if !loading} -
+
+
{/if}
@@ -387,69 +356,39 @@ {:else}
- - {#each (items ?? []).slice(0, nbDisplayed) as item (item.type + '/' + item.path)} - {#key item.summary} - {#key item.starred} - {#key item.has_draft} - {#if item.type == 'script'} - - {:else if item.type == 'flow'} - - {:else if item.type == 'app'} - - {:else if item.type == 'raw_app'} - - {/if} - {/key} - {/key} - {/key} - {/each} - + {#if treeView} + {#each (groupItems(items) ?? []).slice(0, nbDisplayed) as item} + { + loadScripts() + loadFlows() + loadApps() + loadRawApps() + }} + /> + {/each} + {:else} + {#each (items ?? []).slice(0, nbDisplayed) as item (item.type + '/' + item.path)} + { + loadScripts() + loadFlows() + loadApps() + loadRawApps() + }} + /> + {/each} + {/if}
{#if items && items?.length > 30} - - { - deleteConfirmedCallback = undefined - }} - on:confirmed={() => { - if (deleteConfirmedCallback) { - deleteConfirmedCallback() - } - deleteConfirmedCallback = undefined - }} -> -
- Are you sure you want to remove it? - -
- You can press - SHIFT - while removing to bypass confirmation. -
-
-
-
diff --git a/frontend/src/lib/components/home/TreeView.svelte b/frontend/src/lib/components/home/TreeView.svelte new file mode 100644 index 0000000000..2b386de10a --- /dev/null +++ b/frontend/src/lib/components/home/TreeView.svelte @@ -0,0 +1,115 @@ + + +{#if item} + {#if isFolder(item)} +
+
+
0 ? `padding-left: ${depth * 16}px;` : ''} + > +
+ {#if depth === 0} + + {:else} + + {/if} +
+ +
+ {#if depth === 0}f/{/if}{item.folderName} +
+ ({pluralize(item.items.length, ' item')}) +
+
+
+ +
+ {#if opened} +
+ {#each item.items as subItem} + + {/each} +
+ {/if} +
+ {:else if isUser(item)} +
+
+
0 ? `padding-left: ${depth * 16}px;` : ''} + > +
+ +
+ +
+ u/{item.username} +
({pluralize(item.items.length, ' item')})
+
+
+ +
+ {#if opened} +
+ {#each item.items as subItem} + + {/each} +
+ {/if} +
+ {:else} + + {/if} +{/if} diff --git a/frontend/src/lib/components/home/treeViewUtils.ts b/frontend/src/lib/components/home/treeViewUtils.ts new file mode 100644 index 0000000000..62caec3344 --- /dev/null +++ b/frontend/src/lib/components/home/treeViewUtils.ts @@ -0,0 +1,130 @@ +import type { ListableApp, Script, Flow, ListableRawApp } from '$lib/gen' +type TableItem = T & { + canWrite: boolean + marked?: string + type?: U + time?: number + starred?: boolean + has_draft?: boolean +} + +type TableScript = TableItem +type TableFlow = TableItem +type TableApp = TableItem +type TableRawApp = TableItem + +export type ItemType = TableScript | TableFlow | TableApp | TableRawApp + +export interface FolderItem { + folderName: string + items: (ItemType | FolderItem)[] +} + +export type UserItem = { + username: string + items: (ItemType | FolderItem)[] +} + +function insertItemInFolder( + root: (ItemType | FolderItem | UserItem)[], + item: ItemType, + path: string[] +) { + let currentLevel = root + + path.forEach((folderName, index) => { + if (index === path.length - 1) { + currentLevel.push(item) + } else { + let folder = currentLevel.find((f) => 'folderName' in f && f.folderName === folderName) as + | FolderItem + | undefined + + if (!folder) { + folder = { folderName: folderName, items: [] } + currentLevel.push(folder) + } + currentLevel = folder.items + } + }) +} + +export function groupItems(items: ItemType[] | undefined): (ItemType | FolderItem | UserItem)[] { + if (!items) { + return [] + } + + const root: (ItemType | FolderItem | UserItem)[] = [] + + items.forEach((item) => { + const pathSplit = item.path.split('/') + if (pathSplit[0] === 'u') { + const username = pathSplit[1] + let userItem = root.find((f): f is UserItem => 'username' in f && f.username === username) as + | UserItem + | undefined + + if (!userItem) { + userItem = { username, items: [] } + root.push(userItem) + } + + if (pathSplit.length > 2) { + insertItemInFolder(userItem.items, item, pathSplit.slice(2)) + } else { + userItem.items.push(item) + } + } else if (pathSplit[0] === 'f') { + insertItemInFolder(root, item, pathSplit.slice(1)) + } + }) + + root.sort((a, b) => { + if ('username' in a && 'folderName' in b) { + return -1 + } + if ('folderName' in a && 'username' in b) { + return 1 + } + return 0 + }) + + sortGroup(root) + + return root +} + +function sortGroup(group: (ItemType | FolderItem | UserItem)[]) { + group.forEach((item) => { + if ('items' in item) { + item.items.sort((a, b) => { + if (isItemType(a) && isItemType(b)) { + if (a.starred && !b.starred) return -1 + if (!a.starred && b.starred) return 1 + return getModifiedAt(b) - getModifiedAt(a) + } + return 0 + }) + + sortGroup(item.items) + } + }) +} + +function isItemType(item: ItemType | FolderItem | UserItem): item is ItemType { + return 'type' in item +} + +function getModifiedAt(item: ItemType): number { + if (item.type === 'app') { + return new Date(item.edited_at).getTime() || 0 + } else if (item.type === 'script') { + return new Date(item.created_at).getTime() || 0 + } else if (item.type === 'flow') { + return new Date(item.edited_at).getTime() || 0 + } else if (item.type === 'raw_app') { + return new Date(item.edited_at).getTime() || 0 + } + + return 0 +}