improve folder UX

This commit is contained in:
Ruben Fiszel
2022-12-20 10:47:29 +01:00
parent d2cbf72c76
commit fb7e8681b4
7 changed files with 312 additions and 98 deletions
+37
View File
@@ -3733,6 +3733,43 @@ paths:
schema:
$ref: "#/components/schemas/Folder"
/w/{workspace}/folders/getusage/{name}:
get:
summary: get folder usage
operationId: getFolderUsage
tags:
- folder
parameters:
- $ref: "#/components/parameters/WorkspaceId"
- $ref: "#/components/parameters/Name"
responses:
"200":
description: folder
content:
application/json:
schema:
type: object
properties:
scripts:
type: number
flows:
type: number
apps:
type: number
resources:
type: number
variables:
type: number
schedules:
type: number
required:
- scripts
- flows
- apps
- resources
- variables
- schedules
/w/{workspace}/folders/addowner/{name}:
post:
summary: add owner to folder
+82
View File
@@ -31,6 +31,7 @@ pub fn workspaced_service() -> Router {
.route("/listnames", get(list_foldernames))
.route("/create", post(create_folder))
.route("/get/:name", get(get_folder))
.route("/getusage/:name", get(get_folder_usage))
.route("/delete/:name", delete(delete_folder))
.route("/addowner/:name", post(add_owner))
.route("/removeowner/:name", post(remove_owner))
@@ -168,6 +169,87 @@ async fn get_folder(
Ok(Json(folder))
}
#[derive(Serialize)]
struct FolderUsage {
pub scripts: i64,
pub schedules: i64,
pub flows: i64,
pub apps: i64,
pub resources: i64,
pub variables: i64,
}
async fn get_folder_usage(
authed: Authed,
Extension(user_db): Extension<UserDB>,
Path((w_id, name)): Path<(String, String)>,
) -> JsonResult<FolderUsage> {
let mut tx = user_db.begin(&authed).await?;
let scripts = sqlx::query_scalar!(
"SELECT count(path) FROM script WHERE path LIKE 'f/' || $1 || '%' AND archived IS false AND workspace_id = $2",
name,
w_id
)
.fetch_one(&mut tx)
.await?
.unwrap_or(0);
let schedules = sqlx::query_scalar!(
"SELECT count(path) FROM schedule WHERE path LIKE 'f/' || $1 || '%' AND workspace_id = $2",
name,
w_id
)
.fetch_one(&mut tx)
.await?
.unwrap_or(0);
let flows = sqlx::query_scalar!(
"SELECT count(path) FROM flow WHERE path LIKE 'f/' || $1 || '%' AND archived IS false AND workspace_id = $2",
name,
w_id
)
.fetch_one(&mut tx)
.await?
.unwrap_or(0);
let apps = sqlx::query_scalar!(
"SELECT count(path) FROM app WHERE path LIKE 'f/' || $1 || '%' AND workspace_id = $2",
name,
w_id
)
.fetch_one(&mut tx)
.await?
.unwrap_or(0);
let resources = sqlx::query_scalar!(
"SELECT count(path) FROM resource WHERE path LIKE 'f/' || $1 || '%' AND workspace_id = $2",
name,
w_id
)
.fetch_one(&mut tx)
.await?
.unwrap_or(0);
let variables = sqlx::query_scalar!(
"SELECT count(path) FROM variable WHERE path LIKE 'f/' || $1 || '%' AND workspace_id = $2",
name,
w_id
)
.fetch_one(&mut tx)
.await?
.unwrap_or(0);
tx.commit().await?;
Ok(Json(FolderUsage {
scripts,
flows,
schedules,
apps,
resources,
variables,
}))
}
async fn delete_folder(
authed: Authed,
Extension(user_db): Extension<UserDB>,
@@ -0,0 +1,15 @@
<script lang="ts">
import Popover from './Popover.svelte'
export let members: string[]
</script>
<Popover
><div class="inline-flex gap-1 items-end"
><span class="text-gray-500 text-xs mb-0.5">({members.length})</span>
<div class="max-w-xs truncate"
><span class="text-gray-600 text-xs ">{members?.join(', ')}</span></div
></div
>
<span slot="text">{members?.join(', ')}</span></Popover
>
@@ -0,0 +1,22 @@
<script lang="ts">
import { FolderService, GroupService } from '$lib/gen'
import { workspaceStore } from '$lib/stores'
export let name: string
$: $workspaceStore && loadUsage()
let usage: Record<string, number> = {}
async function loadUsage() {
usage = await FolderService.getFolderUsage({ workspace: $workspaceStore!, name })
}
</script>
<div class="flex flex-col text-xs text-gray-600">
{#each Object.entries(usage) as [k, v]}
<div>
{k}: {v}
</div>
{/each}
</div>
@@ -0,0 +1,27 @@
<script lang="ts">
import { GroupService } from '$lib/gen'
import { workspaceStore } from '$lib/stores'
import Popover from './Popover.svelte'
export let name: string
$: $workspaceStore && loadMembers()
let members: string[] | undefined = []
async function loadMembers() {
members = (await GroupService.getGroup({ workspace: $workspaceStore!, name })).members
}
</script>
{#if members}
<Popover
><div class="inline-flex gap-1 items-end"
><span class="text-gray-500 text-xs mb-0.5">({members.length})</span>
<div class="max-w-xs truncate"
><span class="text-gray-600 text-xs ">{members?.join(', ')}</span></div
></div
>
<span slot="text">{members?.join(', ')}</span></Popover
>
{/if}
+75 -52
View File
@@ -19,12 +19,14 @@
import TableCustom from '$lib/components/TableCustom.svelte'
import { userStore, workspaceStore } from '$lib/stores'
import { faEdit, faPlus, faTrash } from '@fortawesome/free-solid-svg-icons'
import { Button, Drawer, DrawerContent } from '$lib/components/common'
import { Button, Drawer, DrawerContent, Skeleton } from '$lib/components/common'
import FolderInfo from '$lib/components/FolderInfo.svelte'
import FolderUsageInfo from '$lib/components/FolderUsageInfo.svelte'
type FolderW = Folder & { canWrite: boolean }
let newFolderName: string = ''
let folders: FolderW[] = []
let folders: FolderW[] | undefined = undefined
let folderDrawer: Drawer
async function loadFolders(): Promise<void> {
@@ -57,6 +59,14 @@
}
let editFolderName: string = ''
function computeMembers(owners: string[], extra_perms: Record<string, any>) {
const members = new Set(owners)
for (const [user, _] of Object.entries(extra_perms)) {
members.add(user)
}
return Array.from(members)
}
</script>
<Drawer bind:this={folderDrawer}>
@@ -87,59 +97,72 @@
<TableCustom>
<tr slot="header-row">
<th>Name</th>
<th />
<th>Usage</th>
<th>Participants</th>
</tr>
<tbody slot="body">
{#if folders.length === 0}
<tr>
<td colspan="2" class="text-gray-600 mt-2"> No folders yet, create one! </td>
</tr>
{#if folders === undefined}
{#each new Array(6) as _}
<Skeleton layout={[0.25, [2], 0.25]} />
{/each}
{:else}
{#if folders.length === 0}
<tr>
<td colspan="4" class="text-gray-600 mt-2"> No folders yet, create one! </td>
</tr>
{/if}
{#each folders as { name, extra_perms, owners, canWrite }}
<tr>
<td>
<a
href="#{name}"
on:click={() => {
editFolderName = name
folderDrawer.openDrawer()
}}
>{name}
</a>
<div>
<SharedBadge {canWrite} extraPerms={extra_perms} />
</div>
</td>
<td><FolderUsageInfo {name} /></td>
<td><FolderInfo members={computeMembers(owners, extra_perms)} /></td>
<td>
<Dropdown
placement="bottom-end"
dropdownItems={[
{
displayName: 'Manage folder',
icon: faEdit,
disabled: !canWrite,
action: () => {
editFolderName = name
folderDrawer.openDrawer()
}
},
{
displayName: 'Delete',
icon: faTrash,
type: 'delete',
disabled: !canWrite,
action: async () => {
await FolderService.deleteFolder({
workspace: $workspaceStore ?? '',
name
})
loadFolders()
}
}
]}
/>
</td>
</tr>
{/each}
{/if}
{#each folders as { name, extra_perms, canWrite }}
<tr>
<td>
<a
href="#{name}"
on:click={() => {
editFolderName = name
folderDrawer.openDrawer()
}}
>{name}
</a>
<div>
<SharedBadge {canWrite} extraPerms={extra_perms} />
</div>
</td>
<td>
<Dropdown
placement="bottom-end"
dropdownItems={[
{
displayName: 'Manage folder',
icon: faEdit,
disabled: !canWrite,
action: () => {
editFolderName = name
folderDrawer.openDrawer()
}
},
{
displayName: 'Delete',
icon: faTrash,
type: 'delete',
disabled: !canWrite,
action: async () => {
await FolderService.deleteFolder({ workspace: $workspaceStore ?? '', name })
loadFolders()
}
}
]}
/>
</td>
</tr>
{/each}
</tbody>
</TableCustom>
</div>
+54 -46
View File
@@ -16,16 +16,16 @@
import GroupEditor from '$lib/components/GroupEditor.svelte'
import PageHeader from '$lib/components/PageHeader.svelte'
import SharedBadge from '$lib/components/SharedBadge.svelte'
import ShareModal from '$lib/components/ShareModal.svelte'
import TableCustom from '$lib/components/TableCustom.svelte'
import { userStore, workspaceStore } from '$lib/stores'
import { faEdit, faPlus, faShare, faTrash } from '@fortawesome/free-solid-svg-icons'
import { Button, Drawer, DrawerContent } from '$lib/components/common'
import { Button, Drawer, DrawerContent, Skeleton } from '$lib/components/common'
import GroupInfo from '$lib/components/GroupInfo.svelte'
type GroupW = Group & { canWrite: boolean }
let newGroupName: string = ''
let groups: GroupW[] = []
let groups: GroupW[] | undefined = undefined
let groupDrawer: Drawer
async function loadGroups(): Promise<void> {
@@ -89,54 +89,62 @@
<tr slot="header-row">
<th>Name</th>
<th>Summary</th>
<th>Members</th>
<th />
</tr>
<tbody slot="body">
{#each groups as { name, summary, extra_perms, canWrite }}
<tr>
<td>
<a
href="#{name}"
on:click={() => {
editGroupName = name
groupDrawer.openDrawer()
}}
>{name}
</a>
<div>
<SharedBadge {canWrite} extraPerms={extra_perms} />
</div>
</td>
<td>{summary ?? ''}</td>
<td>
<Dropdown
placement="bottom-end"
dropdownItems={[
{
displayName: 'Manage group',
icon: faEdit,
disabled: !canWrite,
action: () => {
editGroupName = name
groupDrawer.openDrawer()
}
},
{
displayName: 'Delete',
{#if groups === undefined}
{#each new Array(6) as _}
<Skeleton layout={[0.25, [2], 0.25]} />
{/each}
{:else}
{#each groups as { name, summary, extra_perms, canWrite }}
<tr>
<td>
<a
href="#{name}"
on:click={() => {
editGroupName = name
groupDrawer.openDrawer()
}}
>{name}
</a>
<div>
<SharedBadge {canWrite} extraPerms={extra_perms} />
</div>
</td>
<td>{summary ?? ''}</td>
<td><GroupInfo {name} /></td>
<td>
<Dropdown
placement="bottom-end"
dropdownItems={[
{
displayName: 'Manage group',
icon: faEdit,
disabled: !canWrite,
action: () => {
editGroupName = name
groupDrawer.openDrawer()
}
},
{
displayName: 'Delete',
icon: faTrash,
type: 'delete',
disabled: !canWrite,
action: async () => {
await GroupService.deleteGroup({ workspace: $workspaceStore ?? '', name })
loadGroups()
icon: faTrash,
type: 'delete',
disabled: !canWrite,
action: async () => {
await GroupService.deleteGroup({ workspace: $workspaceStore ?? '', name })
loadGroups()
}
}
}
]}
/>
</td>
</tr>
{/each}
]}
/>
</td>
</tr>
{/each}
{/if}
</tbody>
</TableCustom>
</div>