add: auto notify support.

add: `deleteContent` snippet support.
add: `multiSelectTable` to functions.
This commit is contained in:
Darshan
2025-10-26 12:31:57 +05:30
parent ca042753ae
commit e210612d19
4 changed files with 173 additions and 184 deletions
+30 -3
View File
@@ -14,23 +14,28 @@
} from '@appwrite.io/pink-svelte';
import { Button } from '$lib/elements/forms';
import { Confirm } from '$lib/components/index';
import { addNotification } from '$lib/stores/notifications';
let {
columns,
resource,
allowSelection = true,
confirmDeletion = false,
showSuccessNotification = true,
header,
children,
onDelete,
onCancel
onCancel,
deleteContent
}: {
resource: string;
allowSelection?: boolean;
confirmDeletion?: boolean;
showSuccessNotification?: boolean;
columns: Array<TableColumn> | number;
header: Snippet<[root: TableRootProps]>;
children: Snippet<[root: TableRootProps]>;
deleteContent?: Snippet<[count: number]>;
onDelete?: (selectedRows: string[]) => Promise<DeleteOperationState> | DeleteOperationState;
onCancel?: () => Promise<void> | void;
} = $props();
@@ -39,6 +44,20 @@
let disableModal: boolean = $state(false);
let onDeleteError: string | null = $state(null);
let showConfirmDeletion: boolean = $state(false);
function notifySuccess() {
if (!showSuccessNotification) return;
const count = selectedRows.length;
if (count === 0) return;
const label = `${resource}${count > 1 ? 's' : ''}`;
addNotification({
type: 'success',
message: `${count} ${label} deleted`
});
}
</script>
<Table.Root let:root {columns} {allowSelection} bind:selectedRows>
@@ -75,6 +94,7 @@
if (typeof state === 'string') {
// user should handle error on their own!
} else {
notifySuccess();
selectedRows = [];
}
}
@@ -100,14 +120,21 @@
disableModal = false;
onDeleteError = state || `Failed to delete ${resource}s`;
} else {
notifySuccess();
selectedRows = [];
disableModal = false;
showConfirmDeletion = false;
}
}}>
<Typography.Text>
Are you sure you want to delete <strong>{selectedRows.length}</strong>
{selectedRows.length > 1 ? `${resource}s` : resource}.
{@const selectionCount = selectedRows.length}
{#if deleteContent}
<!-- because some show extra info -->
{@render deleteContent(selectionCount)}
{:else}
Are you sure you want to delete <strong>{selectionCount}</strong>
{selectionCount > 1 ? `${resource}s` : resource}.
{/if}
</Typography.Text>
<Typography.Text variant="m-500">This action is irreversible.</Typography.Text>
@@ -30,11 +30,6 @@
subNavigation.update();
trackEvent(Submit.TableDelete);
addNotification({
type: 'success',
message: `${selectedTables.length} table${selectedTables.length > 1 ? 's' : ''} deleted`
});
return true;
} catch (error) {
trackError(error, Submit.TableDelete);
@@ -1,5 +1,5 @@
<script lang="ts">
import { Confirm, Id } from '$lib/components';
import { type DeleteOperationState, Id, MultiSelectionTable } from '$lib/components';
import type { PageData } from './$types';
import { type Models } from '@appwrite.io/console';
import type { Column } from '$lib/helpers/types';
@@ -16,8 +16,6 @@
import { base } from '$app/paths';
import {
ActionMenu,
Badge,
FloatingActionBar,
Icon,
Status,
Table,
@@ -39,28 +37,26 @@
import DownloadActionMenuItem from './(components)/downloadActionMenuItem.svelte';
import { Menu } from '$lib/components/menu';
import { sdk } from '$lib/stores/sdk';
import { addNotification } from '$lib/stores/notifications';
export let columns: Column[];
export let data: PageData;
let showDelete = false;
let showActivate = false;
let showRedeploy = false;
let showCancel = false;
let selectedDeployment: Models.Deployment = null;
let {
data,
columns,
}: {
data: PageData,
columns: Column[]
} = $props()
let showDelete = $state(false);
let showCancel = $state(false);
let showActivate = $state(false);
let showRedeploy = $state(false);
let selectedDeployment: Models.Deployment | null = $state(null);
function handleActivate() {
invalidate(Dependencies.DEPLOYMENTS);
}
let selectedRows = [];
let showBatchDeletion = false;
async function deleteDeployments() {
showBatchDeletion = false;
async function deleteDeployments(selectedRows: string[]): Promise<DeleteOperationState> {
const promises = selectedRows.map((deploymentId) =>
sdk.forProject(page.params.region, page.params.project).functions.deleteDeployment({
functionId: page.params.function,
@@ -69,187 +65,163 @@
);
try {
await Promise.all(promises);
await invalidate(Dependencies.DEPLOYMENTS);
trackEvent(Submit.DeploymentDelete);
addNotification({
type: 'success',
message: `${selectedRows.length} deployment${selectedRows.length > 1 ? 's' : ''} deleted`
});
return true;
} catch (error) {
addNotification({
type: 'error',
message: error.message
});
trackError(error, Submit.DeploymentDelete);
} finally {
selectedRows = [];
showBatchDeletion = false;
invalidate(Dependencies.DEPLOYMENTS);
return error.message;
}
}
</script>
<Table.Root
let:root
<MultiSelectionTable
allowSelection
bind:selectedRows
confirmDeletion
resource="deployment"
onDelete={deleteDeployments}
columns={[...columns, { id: 'actions', width: 40 }]}>
<svelte:fragment slot="header" let:root>
{#snippet header(root)}
{#each columns as { id, title }}
<Table.Header.Cell column={id} {root}>
{title}
</Table.Header.Cell>
{/each}
<Table.Header.Cell column="actions" {root} />
</svelte:fragment>
{#each data.deploymentList.deployments as deployment (deployment.$id)}
<Table.Row.Link
{root}
id={deployment.$id}
href={`${base}/project-${page.params.region}-${page.params.project}/functions/function-${page.params.function}/deployment-${deployment.$id}`}>
{#each columns as column}
<Table.Cell column={column.id} {root}>
{#if column.id === '$id'}
{#key column.id}
<Id value={deployment.$id}>{deployment.$id}</Id>
{/key}
{:else if column.id === 'status'}
{@const status = deployment.status}
{/snippet}
{#if data?.activeDeployment?.$id === deployment?.$id}
<Status status="complete" label="Active" />
{:else}
<Status
status={deploymentStatusConverter(status)}
label={capitalize(status)} />
{/if}
{:else if column.id === 'type'}
<DeploymentSource {deployment} />
{:else if column.id === '$updatedAt'}
<DeploymentCreatedBy {deployment} />
{:else if column.id === 'buildDuration'}
{#if ['waiting'].includes(deployment.status)}
-
{:else if ['processing', 'building'].includes(deployment.status)}
<span use:timer={{ start: deployment.$createdAt }}></span>
{:else}
{formatTimeDetailed(deployment.buildDuration)}
{/if}
{:else if column.id === 'totalSize'}
{calculateSize(deployment.totalSize)}
{:else if column.id === 'sourceSize'}
{calculateSize(deployment.sourceSize)}
{:else if column.id === 'buildSize'}
{calculateSize(deployment.buildSize)}
{/if}
</Table.Cell>
{/each}
<Table.Cell column="actions" {root}>
<Menu>
<Button text icon size="s">
<Icon size="s" icon={IconDotsHorizontal} />
</Button>
{#snippet children(root)}
{#each data.deploymentList.deployments as deployment (deployment.$id)}
<Table.Row.Link
{root}
id={deployment.$id}
href={`${base}/project-${page.params.region}-${page.params.project}/functions/function-${page.params.function}/deployment-${deployment.$id}`}>
{#each columns as column}
<Table.Cell column={column.id} {root}>
{#if column.id === '$id'}
{#key column.id}
<Id value={deployment.$id}>{deployment.$id}</Id>
{/key}
{:else if column.id === 'status'}
{@const status = deployment.status}
<svelte:fragment slot="menu" let:toggle>
<ActionMenu.Root>
<Tooltip disabled={deployment.sourceSize !== 0} placement={'bottom'}>
<div>
{#if data?.activeDeployment?.$id === deployment?.$id}
<Status status="complete" label="Active" />
{:else}
<Status
status={deploymentStatusConverter(status)}
label={capitalize(status)} />
{/if}
{:else if column.id === 'type'}
<DeploymentSource {deployment} />
{:else if column.id === '$updatedAt'}
<DeploymentCreatedBy {deployment} />
{:else if column.id === 'buildDuration'}
{#if ['waiting'].includes(deployment.status)}
-
{:else if ['processing', 'building'].includes(deployment.status)}
<span use:timer={{ start: deployment.$createdAt }}></span>
{:else}
{formatTimeDetailed(deployment.buildDuration)}
{/if}
{:else if column.id === 'totalSize'}
{calculateSize(deployment.totalSize)}
{:else if column.id === 'sourceSize'}
{calculateSize(deployment.sourceSize)}
{:else if column.id === 'buildSize'}
{calculateSize(deployment.buildSize)}
{/if}
</Table.Cell>
{/each}
<Table.Cell column="actions" {root}>
<Menu>
<Button text icon size="s">
<Icon size="s" icon={IconDotsHorizontal} />
</Button>
<svelte:fragment slot="menu" let:toggle>
<ActionMenu.Root>
<Tooltip
disabled={deployment.sourceSize !== 0}
placement={'bottom'}>
<div>
<ActionMenu.Item.Button
trailingIcon={IconRefresh}
disabled={deployment.sourceSize === 0}
on:click={() => {
selectedDeployment = deployment;
showRedeploy = true;
toggle();
trackEvent(Click.FunctionsRedeployClick);
}}
style="width: 100%">
Redeploy
</ActionMenu.Item.Button>
</div>
<div slot="tooltip">Source is empty</div>
</Tooltip>
{#if deployment.status === 'ready' && deployment.$id !== $func.deploymentId}
<ActionMenu.Item.Button
trailingIcon={IconRefresh}
disabled={deployment.sourceSize === 0}
trailingIcon={IconLightningBolt}
on:click={() => {
selectedDeployment = deployment;
showRedeploy = true;
showActivate = true;
toggle();
trackEvent(Click.FunctionsRedeployClick);
}}
style="width: 100%">
Redeploy
}}>
Activate
</ActionMenu.Item.Button>
</div>
<div slot="tooltip">Source is empty</div>
</Tooltip>
{#if deployment.status === 'ready' && deployment.$id !== $func.deploymentId}
<ActionMenu.Item.Button
trailingIcon={IconLightningBolt}
on:click={() => {
selectedDeployment = deployment;
showActivate = true;
toggle();
}}>
Activate
</ActionMenu.Item.Button>
{/if}
{/if}
<DownloadActionMenuItem {deployment} {toggle} />
<DownloadActionMenuItem {deployment} {toggle} />
{#if deployment.status === 'processing' || deployment.status === 'building' || deployment.status === 'waiting'}
<ActionMenu.Item.Button
trailingIcon={IconXCircle}
on:click={() => {
selectedDeployment = deployment;
toggle();
{#if deployment.status === 'processing' || deployment.status === 'building' || deployment.status === 'waiting'}
<ActionMenu.Item.Button
trailingIcon={IconXCircle}
on:click={() => {
selectedDeployment = deployment;
toggle();
showCancel = true;
trackEvent(Click.FunctionsDeploymentCancelClick);
}}>
Cancel
</ActionMenu.Item.Button>
{/if}
{#if deployment.status !== 'building' && deployment.status !== 'processing' && deployment.status !== 'waiting'}
<ActionMenu.Item.Button
trailingIcon={IconTrash}
status="danger"
on:click={() => {
selectedDeployment = deployment;
toggle();
showCancel = true;
trackEvent(Click.FunctionsDeploymentCancelClick);
}}>
Cancel
</ActionMenu.Item.Button>
{/if}
{#if deployment.status !== 'building' && deployment.status !== 'processing' && deployment.status !== 'waiting'}
<ActionMenu.Item.Button
trailingIcon={IconTrash}
status="danger"
on:click={() => {
selectedDeployment = deployment;
toggle();
showDelete = true;
trackEvent(Click.FunctionsDeploymentDeleteClick);
}}>
Delete
</ActionMenu.Item.Button>
{/if}
</ActionMenu.Root>
</svelte:fragment>
</Menu>
</Table.Cell>
</Table.Row.Link>
{/each}
</Table.Root>
showDelete = true;
trackEvent(Click.FunctionsDeploymentDeleteClick);
}}>
Delete
</ActionMenu.Item.Button>
{/if}
</ActionMenu.Root>
</svelte:fragment>
</Menu>
</Table.Cell>
</Table.Row.Link>
{/each}
{/snippet}
{#snippet deleteContent(count)}
<p>
Are you sure you want to delete <strong>{count}</strong>
{count > 1 ? 'deployments' : 'deployment'} from your function -
<strong>{page.data.function.name}</strong>?
</p>
{/snippet}
</MultiSelectionTable>
{#if selectedDeployment}
<Delete {selectedDeployment} bind:showDelete />
<Activate {selectedDeployment} bind:showActivate on:activated={handleActivate} />
<Cancel {selectedDeployment} bind:showCancel />
<RedeployModal {selectedDeployment} bind:show={showRedeploy} />
<Activate {selectedDeployment} bind:showActivate on:activated={handleActivate} />
{/if}
{#if selectedRows.length > 0}
<FloatingActionBar>
<svelte:fragment slot="start">
<Badge content={selectedRows.length.toString()} />
<span style="white-space: nowrap">
{selectedRows.length > 1 ? 'deployments' : 'deployment'}
selected
</span>
</svelte:fragment>
<svelte:fragment slot="end">
<Button text on:click={() => (selectedRows = [])}>Cancel</Button>
<Button secondary on:click={() => (showBatchDeletion = true)}>Delete</Button>
</svelte:fragment>
</FloatingActionBar>
{/if}
<Confirm
title="Delete deployments"
bind:open={showBatchDeletion}
confirmDeletion
onSubmit={deleteDeployments}>
<p>
Are you sure you want to delete <strong>{selectedRows.length}</strong>
{selectedRows.length > 1 ? 'deployments' : 'deployment'} from your function -
<strong>{$func.name}</strong>?
</p>
<p class="u-bold">This action is irreversible.</p>
</Confirm>
@@ -39,11 +39,6 @@
await Promise.all(promises);
await invalidate(Dependencies.EXECUTIONS);
addNotification({
type: 'success',
message: `${selectedRows.length} log${selectedRows.length > 1 ? 's' : ''} deleted`
});
trackEvent(Submit.LogDelete);
return true;
} catch (error) {