mirror of
https://github.com/windmill-labs/windmill.git
synced 2026-09-10 00:05:27 +00:00
fix(frontend): fix treeview (#2552)
* fix(frontend): fix wip * fix(frontend): improving home performance * slice number of items to show --------- Co-authored-by: Ruben Fiszel <ruben@rubenfiszel.com>
This commit is contained in:
committed by
GitHub
co-authored by
Ruben Fiszel
parent
1f9455a3d3
commit
ce34017467
@@ -27,7 +27,7 @@
|
||||
}
|
||||
</script>
|
||||
|
||||
<Menu {placement} let:close>
|
||||
<Menu {placement} let:close on:dropdownOpen on:dropdownClose>
|
||||
<Button
|
||||
nonCaptureEvent
|
||||
color="light"
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
|
||||
<script lang="ts">
|
||||
import { classNames } from '$lib/utils'
|
||||
import { onMount } from 'svelte'
|
||||
import { createEventDispatcher, onMount } from 'svelte'
|
||||
import { fade } from 'svelte/transition'
|
||||
|
||||
export let noMinW = false
|
||||
@@ -56,10 +56,12 @@
|
||||
'top-start': 'origin-bottom-left left-0 bottom-0',
|
||||
'top-end': 'origin-bottom-right right-0 bottom-0'
|
||||
}
|
||||
const dispatch = createEventDispatcher()
|
||||
</script>
|
||||
|
||||
<div class="relative {wrapperClasses}" bind:this={menu}>
|
||||
<!-- svelte-ignore a11y-click-events-have-key-events -->
|
||||
<!-- svelte-ignore a11y-no-static-element-interactions -->
|
||||
<div
|
||||
on:click={() => {
|
||||
if (!pointerDown) {
|
||||
@@ -68,6 +70,11 @@
|
||||
current = close
|
||||
}
|
||||
show = !show
|
||||
if (show) {
|
||||
dispatch('dropdownOpen')
|
||||
} else {
|
||||
dispatch('dropdownClose')
|
||||
}
|
||||
}
|
||||
}}
|
||||
on:pointerdown={() => {
|
||||
|
||||
@@ -35,6 +35,7 @@
|
||||
export let deploymentDrawer: DeployWorkspaceDrawer
|
||||
export let deleteConfirmedCallback: (() => void) | undefined
|
||||
export let depth: number = 0
|
||||
export let menuOpen: boolean = false
|
||||
|
||||
let {
|
||||
summary,
|
||||
@@ -66,8 +67,11 @@
|
||||
}
|
||||
</script>
|
||||
|
||||
<AppJsonEditor on:change bind:this={appExport} />
|
||||
<AppDeploymentHistory bind:this={appDeploymentHistory} />
|
||||
{#if menuOpen}
|
||||
<AppJsonEditor on:change bind:this={appExport} />
|
||||
<AppDeploymentHistory bind:this={appDeploymentHistory} />
|
||||
{/if}
|
||||
|
||||
<Row
|
||||
href={`/apps/get/${path}`}
|
||||
kind="app"
|
||||
@@ -250,6 +254,12 @@
|
||||
}
|
||||
]
|
||||
}}
|
||||
on:dropdownOpen={() => {
|
||||
menuOpen = true
|
||||
}}
|
||||
on:dropdownClose={() => {
|
||||
menuOpen = false
|
||||
}}
|
||||
/>
|
||||
</svelte:fragment>
|
||||
</Row>
|
||||
|
||||
@@ -36,6 +36,7 @@
|
||||
export let deploymentDrawer: DeployWorkspaceDrawer
|
||||
export let errorHandlerMuted: boolean
|
||||
export let depth: number = 0
|
||||
export let menuOpen: boolean = false
|
||||
|
||||
let { summary, path, extra_perms, canWrite, workspace_id, archived, draft_only, has_draft } = flow
|
||||
|
||||
@@ -67,7 +68,9 @@
|
||||
let scheduleEditor: ScheduleEditor
|
||||
</script>
|
||||
|
||||
<ScheduleEditor on:update={() => goto('/schedules')} bind:this={scheduleEditor} />
|
||||
{#if menuOpen}
|
||||
<ScheduleEditor on:update={() => goto('/schedules')} bind:this={scheduleEditor} />
|
||||
{/if}
|
||||
<Row
|
||||
href="/flows/get/{path}?workspace={$workspaceStore}"
|
||||
kind="flow"
|
||||
@@ -236,6 +239,12 @@
|
||||
}
|
||||
]
|
||||
}}
|
||||
on:dropdownOpen={() => {
|
||||
menuOpen = true
|
||||
}}
|
||||
on:dropdownClose={() => {
|
||||
menuOpen = false
|
||||
}}
|
||||
/>
|
||||
</svelte:fragment>
|
||||
</Row>
|
||||
|
||||
@@ -23,6 +23,7 @@
|
||||
export let deleteConfirmedCallback: (() => void) | undefined
|
||||
export let deploymentDrawer: DeployWorkspaceDrawer
|
||||
export let depth: number = 0
|
||||
export let menuOpen: boolean = false
|
||||
|
||||
let updateAppDrawer: Drawer
|
||||
|
||||
@@ -31,26 +32,27 @@
|
||||
const dispatch = createEventDispatcher()
|
||||
</script>
|
||||
|
||||
<Drawer bind:this={updateAppDrawer} size="800px">
|
||||
<DrawerContent title="Update app" on:close={() => updateAppDrawer?.toggleDrawer?.()}>
|
||||
<FileInput
|
||||
accept={'.js'}
|
||||
multiple={false}
|
||||
convertTo={'text'}
|
||||
iconSize={24}
|
||||
class="text-sm py-4"
|
||||
on:change={async ({ detail }) => {
|
||||
await RawAppService.updateRawApp({
|
||||
workspace: $workspaceStore ?? '',
|
||||
path,
|
||||
requestBody: { value: detail?.[0] }
|
||||
})
|
||||
goto(`/apps/get_raw/${version + 1}/${path}`)
|
||||
}}
|
||||
/>
|
||||
</DrawerContent>
|
||||
</Drawer>
|
||||
|
||||
{#if menuOpen}
|
||||
<Drawer bind:this={updateAppDrawer} size="800px">
|
||||
<DrawerContent title="Update app" on:close={() => updateAppDrawer?.toggleDrawer?.()}>
|
||||
<FileInput
|
||||
accept={'.js'}
|
||||
multiple={false}
|
||||
convertTo={'text'}
|
||||
iconSize={24}
|
||||
class="text-sm py-4"
|
||||
on:change={async ({ detail }) => {
|
||||
await RawAppService.updateRawApp({
|
||||
workspace: $workspaceStore ?? '',
|
||||
path,
|
||||
requestBody: { value: detail?.[0] }
|
||||
})
|
||||
goto(`/apps/get_raw/${version + 1}/${path}`)
|
||||
}}
|
||||
/>
|
||||
</DrawerContent>
|
||||
</Drawer>
|
||||
{/if}
|
||||
<Row
|
||||
href="/apps/get_raw/{version}/{path}"
|
||||
kind="raw_app"
|
||||
@@ -137,6 +139,12 @@
|
||||
}
|
||||
]
|
||||
}}
|
||||
on:dropdownOpen={() => {
|
||||
menuOpen = true
|
||||
}}
|
||||
on:dropdownClose={() => {
|
||||
menuOpen = false
|
||||
}}
|
||||
/>
|
||||
</svelte:fragment>
|
||||
</Row>
|
||||
|
||||
@@ -40,6 +40,7 @@
|
||||
export let errorHandlerMuted: boolean
|
||||
export let showCode: (path: string, summary: string) => void
|
||||
export let depth: number = 0
|
||||
export let menuOpen: boolean = false
|
||||
|
||||
let {
|
||||
summary,
|
||||
@@ -86,7 +87,9 @@
|
||||
const dlt: 'delete' = 'delete'
|
||||
</script>
|
||||
|
||||
<ScheduleEditor on:update={() => goto('/schedules')} bind:this={scheduleEditor} />
|
||||
{#if menuOpen}
|
||||
<ScheduleEditor on:update={() => goto('/schedules')} bind:this={scheduleEditor} />
|
||||
{/if}
|
||||
|
||||
<Row
|
||||
href="/scripts/get/{hash}?workspace={$workspaceStore}"
|
||||
@@ -283,6 +286,12 @@
|
||||
: [])
|
||||
]
|
||||
}}
|
||||
on:dropdownOpen={() => {
|
||||
menuOpen = true
|
||||
}}
|
||||
on:dropdownClose={() => {
|
||||
menuOpen = false
|
||||
}}
|
||||
/>
|
||||
</svelte:fragment>
|
||||
</Row>
|
||||
|
||||
@@ -19,6 +19,8 @@
|
||||
let shareModal: ShareModal
|
||||
let moveDrawer: MoveDrawer
|
||||
let deploymentDrawer: DeployWorkspaceDrawer
|
||||
|
||||
let menuOpen: boolean = false
|
||||
export let showCode: (path: string, summary: string) => void
|
||||
</script>
|
||||
|
||||
@@ -40,6 +42,7 @@
|
||||
{moveDrawer}
|
||||
{deploymentDrawer}
|
||||
{depth}
|
||||
bind:menuOpen
|
||||
{showCode}
|
||||
/>
|
||||
{:else if item.type == 'flow'}
|
||||
@@ -57,6 +60,7 @@
|
||||
{moveDrawer}
|
||||
{deploymentDrawer}
|
||||
{depth}
|
||||
bind:menuOpen
|
||||
/>
|
||||
{:else if item.type == 'app'}
|
||||
<AppRow
|
||||
@@ -69,6 +73,7 @@
|
||||
{shareModal}
|
||||
{deploymentDrawer}
|
||||
{depth}
|
||||
bind:menuOpen
|
||||
/>
|
||||
{:else if item.type == 'raw_app'}
|
||||
<RawAppRow
|
||||
@@ -81,49 +86,52 @@
|
||||
{shareModal}
|
||||
{deploymentDrawer}
|
||||
{depth}
|
||||
bind:menuOpen
|
||||
/>
|
||||
{/if}
|
||||
{/key}
|
||||
{/key}
|
||||
{/key}
|
||||
|
||||
<ConfirmationModal
|
||||
open={Boolean(deleteConfirmedCallback)}
|
||||
title="Remove"
|
||||
confirmationText="Remove"
|
||||
on:canceled={() => {
|
||||
deleteConfirmedCallback = undefined
|
||||
}}
|
||||
on:confirmed={() => {
|
||||
if (deleteConfirmedCallback) {
|
||||
deleteConfirmedCallback()
|
||||
}
|
||||
deleteConfirmedCallback = undefined
|
||||
}}
|
||||
>
|
||||
<div class="flex flex-col w-full space-y-4">
|
||||
<span>Are you sure you want to remove it?</span>
|
||||
<Alert type="info" title="Bypass confirmation">
|
||||
<div>
|
||||
You can press
|
||||
<Badge color="dark-gray">SHIFT</Badge>
|
||||
while removing to bypass confirmation.
|
||||
</div>
|
||||
</Alert>
|
||||
</div>
|
||||
</ConfirmationModal>
|
||||
{#if menuOpen}
|
||||
<ConfirmationModal
|
||||
open={Boolean(deleteConfirmedCallback)}
|
||||
title="Remove"
|
||||
confirmationText="Remove"
|
||||
on:canceled={() => {
|
||||
deleteConfirmedCallback = undefined
|
||||
}}
|
||||
on:confirmed={() => {
|
||||
if (deleteConfirmedCallback) {
|
||||
deleteConfirmedCallback()
|
||||
}
|
||||
deleteConfirmedCallback = undefined
|
||||
}}
|
||||
>
|
||||
<div class="flex flex-col w-full space-y-4">
|
||||
<span>Are you sure you want to remove it?</span>
|
||||
<Alert type="info" title="Bypass confirmation">
|
||||
<div>
|
||||
You can press
|
||||
<Badge color="dark-gray">SHIFT</Badge>
|
||||
while removing to bypass confirmation.
|
||||
</div>
|
||||
</Alert>
|
||||
</div>
|
||||
</ConfirmationModal>
|
||||
|
||||
<ShareModal
|
||||
bind:this={shareModal}
|
||||
on:change={() => {
|
||||
dispatch('reload')
|
||||
}}
|
||||
/>
|
||||
<ShareModal
|
||||
bind:this={shareModal}
|
||||
on:change={() => {
|
||||
dispatch('reload')
|
||||
}}
|
||||
/>
|
||||
|
||||
<DeployWorkspaceDrawer bind:this={deploymentDrawer} />
|
||||
<MoveDrawer
|
||||
bind:this={moveDrawer}
|
||||
on:update={() => {
|
||||
dispatch('reload')
|
||||
}}
|
||||
/>
|
||||
<DeployWorkspaceDrawer bind:this={deploymentDrawer} />
|
||||
<MoveDrawer
|
||||
bind:this={moveDrawer}
|
||||
on:update={() => {
|
||||
dispatch('reload')
|
||||
}}
|
||||
/>
|
||||
{/if}
|
||||
|
||||
@@ -390,21 +390,23 @@
|
||||
{:else}
|
||||
<div class="border rounded-md">
|
||||
{#if treeView}
|
||||
{#each (groupItems(items) ?? []).slice(0, nbDisplayed) as item}
|
||||
<TreeView
|
||||
{item}
|
||||
on:scriptChanged={loadScripts}
|
||||
on:flowChanged={loadFlows}
|
||||
on:appChanged={loadApps}
|
||||
on:rawAppChanged={loadRawApps}
|
||||
on:reload={() => {
|
||||
loadScripts()
|
||||
loadFlows()
|
||||
loadApps()
|
||||
loadRawApps()
|
||||
}}
|
||||
{showCode}
|
||||
/>
|
||||
{#each (groupItems(items) ?? []).slice(0, nbDisplayed) as item (item['folderName'] ?? 'user__' + item['username'])}
|
||||
{#if item}
|
||||
<TreeView
|
||||
{item}
|
||||
on:scriptChanged={loadScripts}
|
||||
on:flowChanged={loadFlows}
|
||||
on:appChanged={loadApps}
|
||||
on:rawAppChanged={loadRawApps}
|
||||
on:reload={() => {
|
||||
loadScripts()
|
||||
loadFlows()
|
||||
loadApps()
|
||||
loadRawApps()
|
||||
}}
|
||||
{showCode}
|
||||
/>
|
||||
{/if}
|
||||
{/each}
|
||||
{:else}
|
||||
{#each (items ?? []).slice(0, nbDisplayed) as item (item.type + '/' + item.path)}
|
||||
@@ -425,7 +427,7 @@
|
||||
{/each}
|
||||
{/if}
|
||||
</div>
|
||||
{#if items && items?.length > 30}
|
||||
{#if items && items?.length > 30 && !treeView && nbDisplayed < items.length}
|
||||
<span class="text-xs"
|
||||
>{nbDisplayed} items out of {items.length}
|
||||
<button class="ml-4" on:click={() => (nbDisplayed += 30)}>load 30 more</button></span
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
<svelte:options immutable={true} />
|
||||
|
||||
<script lang="ts">
|
||||
import { ChevronDown, ChevronUp, Folder, FolderTree, User } from 'lucide-svelte'
|
||||
import Item from './Item.svelte'
|
||||
@@ -6,7 +8,7 @@
|
||||
import { slide } from 'svelte/transition'
|
||||
import { pluralize } from '$lib/utils'
|
||||
|
||||
export let item: ItemType | FolderItem | UserItem | undefined
|
||||
export let item: ItemType | FolderItem | UserItem
|
||||
|
||||
export let depth: number = 0
|
||||
|
||||
@@ -15,111 +17,143 @@
|
||||
|
||||
let opened: boolean = true
|
||||
export let showCode: (path: string, summary: string) => void
|
||||
|
||||
let showMax = 30
|
||||
</script>
|
||||
|
||||
{#if item}
|
||||
{#if isFolder(item)}
|
||||
<div>
|
||||
<div class={twMerge('px-4 py-2 border-b w-full flex flex-row items-center justify-between')}>
|
||||
{#if isFolder(item)}
|
||||
<div>
|
||||
<div
|
||||
class={twMerge(
|
||||
'px-4 py-2 border-b border-t w-full flex flex-row items-center justify-between'
|
||||
)}
|
||||
>
|
||||
<div
|
||||
class={twMerge('flex flex-row items-center gap-4 text-sm font-semibold')}
|
||||
style={depth > 0 ? `padding-left: ${depth * 16}px;` : ''}
|
||||
>
|
||||
<div
|
||||
class={twMerge('flex flex-row items-center gap-4 text-sm font-semibold')}
|
||||
style={depth > 0 ? `padding-left: ${depth * 16}px;` : ''}
|
||||
class=" rounded-md p-1 flex justify-center items-center border bg-gray-50 border-gray-200 dark:bg-transparent dark:border-gray-900"
|
||||
>
|
||||
<div
|
||||
class=" rounded-md p-1 flex justify-center items-center border bg-gray-50 border-gray-200 dark:bg-transparent dark:border-gray-900"
|
||||
>
|
||||
{#if depth === 0}
|
||||
<Folder size={20} color="#aaa" />
|
||||
{:else}
|
||||
<FolderTree size={20} color="#aaa" />
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
<div>
|
||||
{#if depth === 0}f/{/if}{item.folderName}
|
||||
<div class="text-2xs font-normal text-secondary">
|
||||
({pluralize(item.items.length, ' item')})
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<button on:click={() => (opened = !opened)}>
|
||||
{#if opened}
|
||||
<ChevronUp size={20} />
|
||||
{#if depth === 0}
|
||||
<Folder size={20} color="#aaa" />
|
||||
{:else}
|
||||
<ChevronDown size={20} />
|
||||
<FolderTree size={20} color="#aaa" />
|
||||
{/if}
|
||||
</button>
|
||||
</div>
|
||||
{#if opened}
|
||||
<div transition:slide>
|
||||
{#each item.items as subItem}
|
||||
<svelte:self
|
||||
item={subItem}
|
||||
on:scriptChanged
|
||||
on:flowChanged
|
||||
on:appChanged
|
||||
on:rawAppChanged
|
||||
on:reload
|
||||
depth={depth + 1}
|
||||
/>
|
||||
{/each}
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
<div>
|
||||
{#if depth === 0}f/{/if}{item.folderName}
|
||||
<div class="text-2xs font-normal text-secondary">
|
||||
({pluralize(item.items.length, ' item')})
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<button on:click={() => (opened = !opened)}>
|
||||
{#if opened}
|
||||
<ChevronUp size={20} />
|
||||
{:else}
|
||||
<ChevronDown size={20} />
|
||||
{/if}
|
||||
</button>
|
||||
</div>
|
||||
{:else if isUser(item)}
|
||||
<div>
|
||||
<div class={twMerge('px-4 py-2 border-b w-full flex flex-row items-center justify-between')}>
|
||||
{#if opened}
|
||||
<div transition:slide>
|
||||
{#each item.items.slice(0, showMax) as subItem (subItem['path'] ?? 'folder__' + subItem['folderName'])}
|
||||
<svelte:self
|
||||
item={subItem}
|
||||
on:scriptChanged
|
||||
on:flowChanged
|
||||
on:appChanged
|
||||
on:rawAppChanged
|
||||
on:reload
|
||||
depth={depth + 1}
|
||||
/>
|
||||
{/each}
|
||||
{#if showMax < item.items.length}
|
||||
<div
|
||||
class="text-center text-sm text-secondary cursor-pointer hover:text-primary"
|
||||
on:click={() => {
|
||||
if (isFolder(item)) {
|
||||
showMax += Math.min(30, item.items.length - showMax)
|
||||
showMax = showMax
|
||||
console.log(showMax)
|
||||
}
|
||||
}}
|
||||
>
|
||||
Show more ({showMax}/{item.items.length})
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
{:else if isUser(item)}
|
||||
<div>
|
||||
<div class={twMerge('px-4 py-2 border-b w-full flex flex-row items-center justify-between')}>
|
||||
<div
|
||||
class={twMerge('flex flex-row items-center gap-4 text-sm font-semibold')}
|
||||
style={depth > 0 ? `padding-left: ${depth * 16}px;` : ''}
|
||||
>
|
||||
<div
|
||||
class={twMerge('flex flex-row items-center gap-4 text-sm font-semibold')}
|
||||
style={depth > 0 ? `padding-left: ${depth * 16}px;` : ''}
|
||||
class=" rounded-md p-1 flex justify-center items-center border bg-gray-50 border-gray-200 dark:bg-transparent dark:border-gray-900"
|
||||
>
|
||||
<div
|
||||
class=" rounded-md p-1 flex justify-center items-center border bg-gray-50 border-gray-200 dark:bg-transparent dark:border-gray-900"
|
||||
>
|
||||
<User size={20} color="#aaa" />
|
||||
</div>
|
||||
<User size={20} color="#aaa" />
|
||||
</div>
|
||||
|
||||
<div>
|
||||
u/{item.username}
|
||||
<div class="text-2xs font-normal text-secondary"
|
||||
>({pluralize(item.items.length, ' item')})</div
|
||||
>
|
||||
</div>
|
||||
<div>
|
||||
u/{item.username}
|
||||
<div class="text-2xs font-normal text-secondary"
|
||||
>({pluralize(item.items.length, ' item')})</div
|
||||
>
|
||||
</div>
|
||||
<button on:click={() => (opened = !opened)}>
|
||||
{#if opened}
|
||||
<ChevronUp size={20} />
|
||||
{:else}
|
||||
<ChevronDown size={20} />
|
||||
{/if}
|
||||
</button>
|
||||
</div>
|
||||
{#if opened}
|
||||
<div transition:slide>
|
||||
{#each item.items as subItem}
|
||||
<svelte:self
|
||||
item={subItem}
|
||||
on:scriptChanged
|
||||
on:flowChanged
|
||||
on:appChanged
|
||||
on:rawAppChanged
|
||||
on:reload
|
||||
depth={depth + 1}
|
||||
/>
|
||||
{/each}
|
||||
</div>
|
||||
{/if}
|
||||
<button on:click={() => (opened = !opened)}>
|
||||
{#if opened}
|
||||
<ChevronUp size={20} />
|
||||
{:else}
|
||||
<ChevronDown size={20} />
|
||||
{/if}
|
||||
</button>
|
||||
</div>
|
||||
{:else}
|
||||
<Item
|
||||
{item}
|
||||
on:scriptChanged
|
||||
on:flowChanged
|
||||
on:appChanged
|
||||
on:rawAppChanged
|
||||
on:reload
|
||||
{depth}
|
||||
{showCode}
|
||||
/>
|
||||
{/if}
|
||||
{#if opened}
|
||||
<div transition:slide>
|
||||
{#each item.items.slice(0, showMax) as subItem (subItem['path'] ?? 'folder__' + subItem['folderName'])}
|
||||
<svelte:self
|
||||
item={subItem}
|
||||
on:scriptChanged
|
||||
on:flowChanged
|
||||
on:appChanged
|
||||
on:rawAppChanged
|
||||
on:reload
|
||||
depth={depth + 1}
|
||||
/>
|
||||
{/each}
|
||||
{#if showMax < item.items.length}
|
||||
<!-- svelte-ignore a11y-click-events-have-key-events -->
|
||||
<!-- svelte-ignore a11y-no-static-element-interactions -->
|
||||
<div
|
||||
class="text-center text-sm text-secondary cursor-pointer py-2 hover:text-primary"
|
||||
on:click={() => {
|
||||
if (isUser(item)) {
|
||||
showMax += Math.min(30, item.items.length - showMax)
|
||||
}
|
||||
}}
|
||||
>
|
||||
Show more ({showMax}/{item.items.length})
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
{:else}
|
||||
<Item
|
||||
{item}
|
||||
{showCode}
|
||||
on:scriptChanged
|
||||
on:flowChanged
|
||||
on:appChanged
|
||||
on:rawAppChanged
|
||||
on:reload
|
||||
{depth}
|
||||
/>
|
||||
{/if}
|
||||
|
||||
Reference in New Issue
Block a user