feat: document bulk deletion

This commit is contained in:
tglide
2023-07-28 21:35:59 +01:00
parent 382b473ceb
commit 8d8d690fe1
2 changed files with 187 additions and 2 deletions
+2 -1
View File
@@ -2,6 +2,7 @@
import type { Action } from 'svelte/action';
export let isSticky = false;
export let noMargin = false;
const hasOverflow: Action<HTMLDivElement, (value: boolean) => void> = (node, callback) => {
const observer = new ResizeObserver((entries) => {
@@ -22,7 +23,7 @@
let isOverflowing = false;
</script>
<div class="table-with-scroll u-margin-block-start-32" data-private>
<div class="table-with-scroll {noMargin ? '' : 'u-margin-block-start-32'}" data-private>
<div class="table-wrapper" use:hasOverflow={(v) => (isOverflowing = v)}>
<table class="table" class:is-sticky-scroll={isSticky && isOverflowing}>
<slot />
@@ -1,5 +1,5 @@
<script lang="ts">
import { goto } from '$app/navigation';
import { goto, invalidate } from '$app/navigation';
import { base } from '$app/paths';
import { page } from '$app/stores';
import { tooltip } from '$lib/actions/tooltip';
@@ -8,7 +8,9 @@
TableBody,
TableCell,
TableCellHead,
TableCellText,
TableHeader,
TableRow,
TableRowLink,
TableScroll
} from '$lib/elements/table';
@@ -19,11 +21,24 @@
import { isRelationship, isRelationshipToMany } from './document-[document]/attributes/store';
import RelationshipsModal from './relationshipsModal.svelte';
import { attributes, collection, columns } from './store';
import InputCheckbox from '$lib/elements/forms/inputCheckbox.svelte';
import { isHTMLInputElement } from '$lib/helpers/types';
import { toggle } from '$lib/helpers/array';
import { sdk } from '$lib/stores/sdk';
import { Submit, trackError, trackEvent } from '$lib/actions/analytics';
import { addNotification } from '$lib/stores/notifications';
import { Dependencies } from '$lib/constants';
import FloatingActionBar from '$lib/components/floatingActionBar.svelte';
import Button from '$lib/elements/forms/button.svelte';
import Modal from '$lib/components/modal.svelte';
import Alert from '$lib/components/alert.svelte';
import InputChoice from '$lib/elements/forms/inputChoice.svelte';
export let data: PageData;
const projectId = $page.params.project;
const databaseId = $page.params.database;
const collectionId = $page.params.collection;
let showRelationships = false;
let selectedRelationship: Models.AttributeRelationship = null;
let relationshipData: [];
@@ -81,10 +96,64 @@
show: selected?.includes(attribute.key) ?? true
}))
);
let selectedDb: string[] = [];
let showDelete = false;
let deleting = false;
async function handleDelete() {
showDelete = false;
const promises = selectedDb.map((documentId) =>
sdk.forProject.databases.deleteDocument(databaseId, collectionId, documentId)
);
try {
await Promise.all(promises);
trackEvent(Submit.DocumentDelete);
addNotification({
type: 'success',
message: `${selected.length} document${selected.length > 1 ? 's' : ''} deleted`
});
invalidate(Dependencies.DOCUMENTS);
} catch (error) {
addNotification({
type: 'error',
message: error.message
});
trackError(error, Submit.DocumentDelete);
} finally {
selected = [];
showDelete = false;
}
}
enum Deletion {
'setNull' = 'Set document ID as NULL in all related documents',
'cascade' = 'All related documents will be deleted',
'restrict' = 'Document can not be deleted'
}
$: relAttributes = $attributes?.filter((attribute) =>
isRelationship(attribute)
) as Models.AttributeRelationship[];
let checked = false;
</script>
<TableScroll isSticky>
<TableHeader>
<TableCellHead width={10}>
<InputCheckbox
id="select-all"
indeterminate={selectedDb.length > 0 && selectedDb.length < data.documents.total}
value={selectedDb.length === data.documents.total}
on:click={(e) => {
if (!isHTMLInputElement(e.target)) return;
selectedDb = e.target.checked
? data.documents.documents.map((database) => database.$id)
: [];
}} />
</TableCellHead>
<TableCellHead width={150} eyebrow={false}>Document ID</TableCellHead>
{#each $columns.filter((n) => n.show) as column}
{#if column.show}
@@ -96,6 +165,26 @@
{#each data.documents.documents as document}
<TableRowLink
href={`${base}/console/project-${projectId}/databases/database-${databaseId}/collection-${$collection.$id}/document-${document.$id}`}>
<TableCell>
<InputCheckbox
id="select-{document.$id}"
value={selectedDb.includes(document.$id)}
on:click={(e) => {
// Prevent the link from being followed
e.preventDefault();
const el = e.currentTarget;
if (!isHTMLInputElement(el)) return;
selectedDb = toggle(selectedDb, document.$id);
// Hack to make sure the checkbox is checked, independent of the
// preventDefault() call above
window.setTimeout(() => {
el.checked = selectedDb.includes(document.$id);
});
}} />
</TableCell>
<TableCell width={150}>
<Id value={document.$id}>
{document.$id}
@@ -165,3 +254,98 @@
</TableScroll>
<RelationshipsModal bind:show={showRelationships} {selectedRelationship} data={relationshipData} />
<FloatingActionBar show={selectedDb.length > 0}>
<div class="u-flex u-cross-center u-main-space-between actions">
<div class="u-flex u-cross-center u-gap-8">
<span class="indicator body-text-2 u-bold">{selectedDb.length}</span>
<span>
{selected.length > 1 ? 'documents' : 'document'} selected
</span>
</div>
<div class="u-flex u-cross-center u-gap-8">
<Button text on:click={() => (selected = [])}>Cancel</Button>
<Button secondary on:click={() => (showDelete = true)}>Delete selection</Button>
</div>
</div>
</FloatingActionBar>
<Modal
icon="exclamation"
state="warning"
bind:show={showDelete}
onSubmit={handleDelete}
headerDivider={false}
closable={!deleting}>
<svelte:fragment slot="header">Delete Documents</svelte:fragment>
<div>
<p class="text" data-private>
Are you sure you want to delete <b>{selectedDb.length}</b>
{selectedDb.length > 1 ? 'documents' : 'document'}?
</p>
{#if relAttributes?.length}
<TableScroll noMargin>
<TableHeader>
<TableCellHead width={50}>Relation</TableCellHead>
<TableCellHead width={50}>Setting</TableCellHead>
<TableCellHead width={200} />
</TableHeader>
<TableBody>
{#each relAttributes as attr}
<TableRow>
<TableCell title="relation">
<span class="u-flex u-cross-center u-gap-8">
{#if attr.twoWay}
<span class="icon-switch-horizontal" />
{:else}
<span class="icon-arrow-sm-right" />
{/if}
<span data-private>{attr.key}</span>
</span>
</TableCell>
<TableCellText title="Settings">
{attr.onDelete}
</TableCellText>
<TableCellText title="description">
{Deletion[attr.onDelete]}
</TableCellText>
</TableRow>
{/each}
</TableBody>
</TableScroll>
<div class="u-flex u-flex-vertical u-gap-16">
<Alert>To change the selection edit the relationship settings.</Alert>
<InputChoice id="delete" label="Delete" showLabel={false} bind:value={checked}>
Delete document from <span data-private>{$collection.name}</span>
</InputChoice>
</div>
{:else}
<p class="u-bold">This action is irreversible.</p>
{/if}
</div>
<svelte:fragment slot="footer">
<Button text on:click={() => (showDelete = false)} disabled={deleting}>Cancel</Button>
<Button secondary submit disabled={deleting || (relAttributes?.length && !checked)}
>Delete</Button>
</svelte:fragment>
</Modal>
<style lang="scss">
.actions {
width: 31.25rem;
.indicator {
border-radius: 0.25rem;
background: hsl(var(--color-information-100));
color: hsl(var(--color-neutral-0));
padding: 0rem 0.375rem;
display: inline-block;
}
}
</style>