skeletons for home search

This commit is contained in:
Ruben Fiszel
2022-12-03 11:03:47 +01:00
parent 2071a06668
commit 0ec465ca7e
4 changed files with 155 additions and 125 deletions
+60 -62
View File
@@ -60,72 +60,70 @@
{#each new Array(6) as _}
<Skeleton layout={[[2], 0.7]} />
{/each}
{:else}
{#if !items?.length}
<div class="text-center text-sm text-gray-600 mt-2">
{@html noItemMessage}
</div>
{:else if filteredItems?.length}
<ul class="divide-y divide-gray-200">
{#each filteredItems as obj}
<li class="flex flex-row w-full">
<button
class="py-4 px-1 gap-1 flex flex-row grow border border-gray-300 border-opacity-0
{:else if !items?.length}
<div class="text-center text-sm text-gray-600 mt-2">
{@html noItemMessage}
</div>
{:else if filteredItems?.length}
<ul class="divide-y divide-gray-200">
{#each filteredItems as obj}
<li class="flex flex-row w-full">
<button
class="py-4 px-1 gap-1 flex flex-row grow border border-gray-300 border-opacity-0
hover:bg-white hover:border-opacity-100 text-black"
on:click={() => {
if (closeOnClick) {
drawer.closeDrawer()
}
pickCallback(obj['path'], obj[extraField])
}}
>
{#if `app` in obj}
<div class="mr-2 text-sm text-left truncate w-24">
<IconedResourceType after={true} silent={false} name={obj['app']} />
</div>
{/if}
{#if `resource_type` in obj}
<div class="mr-2 truncate text-left w-24 text-sm">
<IconedResourceType after={true} name={obj['resource_type']} />
</div>
{/if}
<div class="flex flex-col">
<div class="text-sm font-semibold flex flex-col">
<span class="mr-2 text-left">{obj[extraField] ?? ''}</span>
{#if extraField != 'path'}
<span class="font-normal text-xs text-left italic overflow-hidden"
>{obj['path'] ?? ''}</span
>
{/if}
</div>
{#if extraField != 'description'}
<div class="text-xs font-light italic text-left">{obj['description'] ?? ''}</div
on:click={() => {
if (closeOnClick) {
drawer.closeDrawer()
}
pickCallback(obj['path'], obj[extraField])
}}
>
{#if `app` in obj}
<div class="mr-2 text-sm text-left truncate w-24">
<IconedResourceType after={true} silent={false} name={obj['app']} />
</div>
{/if}
{#if `resource_type` in obj}
<div class="mr-2 truncate text-left w-24 text-sm">
<IconedResourceType after={true} name={obj['resource_type']} />
</div>
{/if}
<div class="flex flex-col">
<div class="text-sm font-semibold flex flex-col">
<span class="mr-2 text-left">{obj[extraField] ?? ''}</span>
{#if extraField != 'path'}
<span class="font-normal text-xs text-left italic overflow-hidden"
>{obj['path'] ?? ''}</span
>
{/if}
</div>
</button>
{#if buttons}
<div class="flex flex-row">
{#each Object.entries(buttons) as [name, button]}
<button
class="py-4 px-1 gap-1 flex flex-row grow text-black"
on:click={() => {
button(obj['path'] ?? '')
}}
>
{name}
</button>
{/each}
</div>
{/if}
</li>
{/each}
</ul>
{:else}
<div class="text-center text-sm text-gray-600 mt-2">
{@html emptySearchMessage}
</div>
{/if}
{#if extraField != 'description'}
<div class="text-xs font-light italic text-left">{obj['description'] ?? ''}</div
>
{/if}
</div>
</button>
{#if buttons}
<div class="flex flex-row">
{#each Object.entries(buttons) as [name, button]}
<button
class="py-4 px-1 gap-1 flex flex-row grow text-black"
on:click={() => {
button(obj['path'] ?? '')
}}
>
{name}
</button>
{/each}
</div>
{/if}
</li>
{/each}
</ul>
{:else}
<div class="text-center text-sm text-gray-600 mt-2">
{@html emptySearchMessage}
</div>
{/if}
</div>
<span slot="submission" class="mr-2">
@@ -2,18 +2,18 @@
import uFuzzy from '@leeoniya/ufuzzy'
export let filter: string = ''
export let items: any[]
export let items: any[] | undefined
export let f: (item: any) => string
export let filteredItems: (any & { marked: string })[]
export let filteredItems: (any & { marked: string })[] | undefined
export let opts: uFuzzy.Options = {}
let uf = new uFuzzy(opts)
$: plaintextItems = items.map((item) => f(item))
$: plaintextItems = items?.map((item) => f(item)) ?? []
$: plaintextItems && filter != undefined && setTimeout(() => filterItems(), 0)
function filterItems() {
if (filter.length == 0) {
if (items == undefined || filter.length == 0) {
filteredItems = items
return
}
+39 -19
View File
@@ -18,6 +18,7 @@
Button,
Drawer,
DrawerContent,
Skeleton,
Tab,
Tabs,
ToggleButton,
@@ -63,9 +64,9 @@
type TableFlow = TableItem<Flow, 'flow'>
type TableApp = TableItem<ListableApp, 'app'>
let scripts: TableScript[] = []
let flows: TableFlow[] = []
let apps: TableApp[] = []
let scripts: TableScript[] | undefined
let flows: TableFlow[] | undefined
let apps: TableApp[] | undefined
let filteredItems: (TableScript | TableFlow | TableApp)[] = []
@@ -143,24 +144,37 @@
new Set(filteredItems?.map((x) => x.path.split('/').slice(0, 2).join('/')) ?? [])
).sort()
let combinedItems: (TableScript | TableFlow | TableApp)[] = []
let combinedItems: (TableScript | TableFlow | TableApp)[] | undefined = undefined
$: combinedItems = [
...flows.map((x) => ({ ...x, type: 'flow' as 'flow', time: new Date(x.edited_at).getTime() })),
...scripts.map((x) => ({
...x,
type: 'script' as 'script',
time: new Date(x.created_at).getTime()
})),
...apps.map((x) => ({ ...x, type: 'app' as 'app', time: new Date(x.edited_at).getTime() }))
].sort((a, b) => (a.starred != b.starred ? (a.starred ? -1 : 1) : a.time - b.time > 0 ? -1 : 1))
$: combinedItems =
flows == undefined || scripts == undefined || apps == undefined
? undefined
: [
...flows.map((x) => ({
...x,
type: 'flow' as 'flow',
time: new Date(x.edited_at).getTime()
})),
...scripts.map((x) => ({
...x,
type: 'script' as 'script',
time: new Date(x.created_at).getTime()
})),
...apps.map((x) => ({
...x,
type: 'app' as 'app',
time: new Date(x.edited_at).getTime()
}))
].sort((a, b) =>
a.starred != b.starred ? (a.starred ? -1 : 1) : a.time - b.time > 0 ? -1 : 1
)
$: preFilteredItems =
ownerFilter != undefined
? combinedItems.filter(
? combinedItems?.filter(
(x) => x.path.startsWith(ownerFilter ?? '') && (x.type == itemKind || itemKind == 'all')
)
: combinedItems.filter((x) => x.type == itemKind || itemKind == 'all')
: combinedItems?.filter((x) => x.type == itemKind || itemKind == 'all')
let ownerFilter: string | undefined = undefined
@@ -220,8 +234,8 @@
start[ia] - start[ib] ||
// alphabetic
cmp(haystack[idx[ia]], haystack[idx[ib]]) +
(preFilteredItems[idx[ib]].starred ? 100 : 0) -
(preFilteredItems[idx[ia]].starred ? 100 : 0)
(preFilteredItems?.[idx[ib]]?.starred ? 100 : 0) -
(preFilteredItems?.[idx[ia]]?.starred ? 100 : 0)
)
return sortResult
}
@@ -448,7 +462,13 @@
</div>
{/if}
<div>
{#if filteredItems.length === 0}
{#if filteredItems == undefined}
<div class="mt-4" />
<Skeleton layout={[[2], 1]} />
{#each new Array(6) as _}
<Skeleton layout={[[4], 0.5]} />
{/each}
{:else if filteredItems.length === 0}
<div class="flex justify-center items-center h-48">
<div class="text-gray-500 text-center">
<div class="text-2xl font-bold">No items found</div>
@@ -487,7 +507,7 @@
{/if}
</VirtualList>
</div>
<span class="text-xs">{pluralize(items.length, 'item')}</span>
<span class="text-xs">{pluralize(items?.length ?? 0, 'item')}</span>
<span class="text-xs">{`(${start} - ${end})`}</span>
{/if}
</div>
+52 -40
View File
@@ -24,14 +24,14 @@
import { goto } from '$app/navigation'
import InviteUser from '$lib/components/InviteUser.svelte'
import ScriptPicker from '$lib/components/ScriptPicker.svelte'
import { Button } from '$lib/components/common'
import { Button, Skeleton } from '$lib/components/common'
import Tooltip from '$lib/components/Tooltip.svelte'
import { faScroll, faWind } from '@fortawesome/free-solid-svg-icons'
import SearchItems from '$lib/components/SearchItems.svelte'
let users: User[] = []
let users: User[] | undefined = undefined
let invites: WorkspaceInvite[] = []
let filteredUsers: User[] = []
let filteredUsers: User[] | undefined = undefined
let userFilter = ''
let scriptPath: string
let initialPath: string
@@ -120,44 +120,56 @@
<th colspan="3">jobs &amp; flows (<abbr title="past two weeks">2w</abbr>)</th>
</tr>
<tbody slot="body">
{#each filteredUsers as { email, username, is_admin, usage } (email)}
<tr class="border">
<td>{email}</td>
<td>{username}</td>
<td>{is_admin ? 'admin' : 'user'}</td>
<td>{usage?.jobs}</td>
<td>{usage?.flows}</td>
<td>{msToSec(usage?.duration_ms)}s</td>
<td class="whitespace-nowrap"
><button
class="ml-2 text-red-500"
on:click={async () => {
await UserService.deleteUser({
workspace: $workspaceStore ?? '',
username
})
sendUserToast('User removed')
listUsers()
}}>remove</button
{#if filteredUsers}
{#each filteredUsers as { email, username, is_admin, usage } (email)}
<tr class="border">
<td>{email}</td>
<td>{username}</td>
<td>{is_admin ? 'admin' : 'user'}</td>
<td>{usage?.jobs}</td>
<td>{usage?.flows}</td>
<td>{msToSec(usage?.duration_ms)}s</td>
<td class="whitespace-nowrap"
><button
class="ml-2 text-red-500"
on:click={async () => {
await UserService.deleteUser({
workspace: $workspaceStore ?? '',
username
})
sendUserToast('User removed')
listUsers()
}}>remove</button
>
-
<button
class="text-blue-500"
on:click={async () => {
await UserService.updateUser({
workspace: $workspaceStore ?? '',
username,
requestBody: {
is_admin: !is_admin
}
})
sendUserToast('User updated')
listUsers()
}}>{is_admin ? 'demote' : 'promote'}</button
></td
>
-
<button
class="text-blue-500"
on:click={async () => {
await UserService.updateUser({
workspace: $workspaceStore ?? '',
username,
requestBody: {
is_admin: !is_admin
}
})
sendUserToast('User updated')
listUsers()
}}>{is_admin ? 'demote' : 'promote'}</button
></td
>
</tr>
{/each}
</tr>
{/each}
{:else}
{#each new Array(6) as _}
<tr class="border">
{#each new Array(4) as _}
<td>
<Skeleton layout={[[2]]} />
</td>
{/each}
</tr>
{/each}
{/if}
</tbody>
</TableCustom>
</div>