From 6a2f3c102443ea8d340d3c10d165f46843950a01 Mon Sep 17 00:00:00 2001 From: Harsh Mahajan <127186841+HarshMN2345@users.noreply.github.com> Date: Wed, 24 Sep 2025 18:30:14 +0530 Subject: [PATCH 01/52] feat: show CSV import error details with inline indicator and modal --- src/lib/components/csvImportBox.svelte | 69 ++++++++++++++++++++++++-- 1 file changed, 64 insertions(+), 5 deletions(-) diff --git a/src/lib/components/csvImportBox.svelte b/src/lib/components/csvImportBox.svelte index ec7a2c9a3..12b721e9f 100644 --- a/src/lib/components/csvImportBox.svelte +++ b/src/lib/components/csvImportBox.svelte @@ -8,7 +8,9 @@ import { getProjectId } from '$lib/helpers/project'; import { writable, type Writable } from 'svelte/store'; import { addNotification } from '$lib/stores/notifications'; - import { Layout, Typography } from '@appwrite.io/pink-svelte'; + import { Layout, Typography, Icon } from '@appwrite.io/pink-svelte'; + import { IconExclamationCircle } from '@appwrite.io/pink-icons-svelte'; + import { Modal, Code } from '$lib/components'; import { type Models, type Payload, Query } from '@appwrite.io/console'; // re-render the key for sheet UI. @@ -18,6 +20,7 @@ type ImportItem = { status: string; table?: string; + errors?: string[]; }; type ImportItemsMap = Map; @@ -112,7 +115,10 @@ if (shouldSkip) return items; const next = new Map(items); - next.set(importData.$id, { status, table: tableName ?? undefined }); + const errors = Array.isArray((importData as Payload).errors) + ? ((importData as Payload).errors as string[]) + : undefined; + next.set(importData.$id, { status, table: tableName ?? undefined, errors }); return next; }); @@ -138,7 +144,7 @@ return 60; case 'completed': case 'failed': - return 100; + return 60; default: return 30; } @@ -149,7 +155,7 @@ switch (status) { case 'completed': case 'failed': - return `Import to ${name} ${status}`; + return `Importing CSV file${name ? ` to ${name}` : ''}`; case 'processing': return `Importing CSV file${name ? ` to ${name}` : ''}`; default: @@ -179,6 +185,22 @@ $: isOpen = true; $: showCsvImportBox = $importItems.size > 0; + + let showDetails = false; + let selectedErrors: string[] = []; + + function openDetails(errors: string[] | undefined) { + selectedErrors = errors ?? []; + showDetails = true; + } + + $: parsedErrors = selectedErrors.map((err) => { + try { + return JSON.stringify(JSON.parse(err), null, 2); + } catch { + return err; + } + }); {#if showCsvImportBox} @@ -222,6 +244,25 @@ class:is-danger={value.status === 'failed'} style="--graph-size:{graphSize(value.status)}%"> + {#if value.status === 'failed'} + + + + There was an import issue. + + + + {/if} @@ -232,6 +273,14 @@ {/if} + + + + + + + + From 21940ef1dac5b3e31acf821f6bceaa048ac781f9 Mon Sep 17 00:00:00 2001 From: Harsh Mahajan <127186841+HarshMN2345@users.noreply.github.com> Date: Wed, 24 Sep 2025 18:40:49 +0530 Subject: [PATCH 02/52] lint issue --- src/lib/components/csvImportBox.svelte | 1 - 1 file changed, 1 deletion(-) diff --git a/src/lib/components/csvImportBox.svelte b/src/lib/components/csvImportBox.svelte index 12b721e9f..f3b18e2ad 100644 --- a/src/lib/components/csvImportBox.svelte +++ b/src/lib/components/csvImportBox.svelte @@ -324,7 +324,6 @@ } } - .link-button { background: none; border: none; From e9730d42074106c9ebe8f3ad5243cf8d45f16f0e Mon Sep 17 00:00:00 2001 From: Harsh Mahajan <127186841+HarshMN2345@users.noreply.github.com> Date: Wed, 24 Sep 2025 19:03:55 +0530 Subject: [PATCH 03/52] removed button and using Link --- src/lib/components/csvImportBox.svelte | 16 ++++------------ src/lib/elements/link.svelte | 12 +++++++++++- 2 files changed, 15 insertions(+), 13 deletions(-) diff --git a/src/lib/components/csvImportBox.svelte b/src/lib/components/csvImportBox.svelte index f3b18e2ad..e736e606c 100644 --- a/src/lib/components/csvImportBox.svelte +++ b/src/lib/components/csvImportBox.svelte @@ -16,6 +16,7 @@ // re-render the key for sheet UI. import { hash } from '$lib/helpers/string'; import { spreadsheetRenderKey } from '$routes/(console)/project-[region]-[project]/databases/database-[database]/table-[table]/store'; + import Link from '$lib/elements/link.svelte'; type ImportItem = { status: string; @@ -256,10 +257,10 @@ size="s" /> There was an import issue. - + >View details {/if} @@ -323,13 +324,4 @@ background-color: var(--bgcolor-error); } } - - .link-button { - background: none; - border: none; - padding: 0; - color: var(--fgcolor-error); - text-decoration: underline; - cursor: pointer; - } diff --git a/src/lib/elements/link.svelte b/src/lib/elements/link.svelte index 6e75e98a5..444a94d18 100644 --- a/src/lib/elements/link.svelte +++ b/src/lib/elements/link.svelte @@ -29,6 +29,7 @@ {#if href} {:else} - + {/if} From ae43a4d044eddb5c9e2b84dd2847d745db781c7d Mon Sep 17 00:00:00 2001 From: Harsh Mahajan <127186841+HarshMN2345@users.noreply.github.com> Date: Wed, 24 Sep 2025 19:17:21 +0530 Subject: [PATCH 04/52] remove reactive --- src/lib/components/csvImportBox.svelte | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/src/lib/components/csvImportBox.svelte b/src/lib/components/csvImportBox.svelte index e736e606c..8c0cd9f6b 100644 --- a/src/lib/components/csvImportBox.svelte +++ b/src/lib/components/csvImportBox.svelte @@ -189,19 +189,19 @@ let showDetails = false; let selectedErrors: string[] = []; + let parsedErrors: string[] = []; function openDetails(errors: string[] | undefined) { selectedErrors = errors ?? []; + parsedErrors = selectedErrors.map((err) => { + try { + return JSON.stringify(JSON.parse(err), null, 2); + } catch { + return err; + } + }); showDetails = true; } - - $: parsedErrors = selectedErrors.map((err) => { - try { - return JSON.stringify(JSON.parse(err), null, 2); - } catch { - return err; - } - }); {#if showCsvImportBox} From 594c281ed60949a7380ea8f8bf90132a72cae254 Mon Sep 17 00:00:00 2001 From: Darshan Date: Wed, 22 Oct 2025 15:11:36 +0530 Subject: [PATCH 05/52] feat: expand on shortcut. --- package.json | 4 +- pnpm-lock.yaml | 20 +- .../table-[table]/spreadsheet.svelte | 349 +++++++++++------- 3 files changed, 237 insertions(+), 136 deletions(-) diff --git a/package.json b/package.json index 9fa20b9dc..5459a00f4 100644 --- a/package.json +++ b/package.json @@ -24,9 +24,9 @@ "@ai-sdk/svelte": "^1.1.24", "@appwrite.io/console": "https://pkg.pr.new/appwrite-labs/cloud/@appwrite.io/console@636ed39", "@appwrite.io/pink-icons": "0.25.0", - "@appwrite.io/pink-icons-svelte": "https://pkg.vc/-/@appwrite/@appwrite.io/pink-icons-svelte@10305c4", + "@appwrite.io/pink-icons-svelte": "https://pkg.vc/-/@appwrite/@appwrite.io/pink-icons-svelte@71bdaf8", "@appwrite.io/pink-legacy": "^1.0.3", - "@appwrite.io/pink-svelte": "https://pkg.vc/-/@appwrite/@appwrite.io/pink-svelte@10305c4", + "@appwrite.io/pink-svelte": "https://pkg.vc/-/@appwrite/@appwrite.io/pink-svelte@71bdaf8", "@faker-js/faker": "^9.9.0", "@popperjs/core": "^2.11.8", "@sentry/sveltekit": "^8.38.0", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 26d4c94dd..5a0f2c6ae 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -18,14 +18,14 @@ importers: specifier: 0.25.0 version: 0.25.0 '@appwrite.io/pink-icons-svelte': - specifier: https://pkg.vc/-/@appwrite/@appwrite.io/pink-icons-svelte@10305c4 - version: https://pkg.vc/-/@appwrite/@appwrite.io/pink-icons-svelte@10305c4(svelte@5.25.3) + specifier: https://pkg.vc/-/@appwrite/@appwrite.io/pink-icons-svelte@71bdaf8 + version: https://pkg.vc/-/@appwrite/@appwrite.io/pink-icons-svelte@71bdaf8(svelte@5.25.3) '@appwrite.io/pink-legacy': specifier: ^1.0.3 version: 1.0.3 '@appwrite.io/pink-svelte': - specifier: https://pkg.vc/-/@appwrite/@appwrite.io/pink-svelte@10305c4 - version: https://pkg.vc/-/@appwrite/@appwrite.io/pink-svelte@10305c4(svelte@5.25.3) + specifier: https://pkg.vc/-/@appwrite/@appwrite.io/pink-svelte@71bdaf8 + version: https://pkg.vc/-/@appwrite/@appwrite.io/pink-svelte@71bdaf8(svelte@5.25.3) '@faker-js/faker': specifier: ^9.9.0 version: 9.9.0 @@ -269,8 +269,8 @@ packages: peerDependencies: svelte: ^4.0.0 - '@appwrite.io/pink-icons-svelte@https://pkg.vc/-/@appwrite/@appwrite.io/pink-icons-svelte@10305c4': - resolution: {tarball: https://pkg.vc/-/@appwrite/@appwrite.io/pink-icons-svelte@10305c4} + '@appwrite.io/pink-icons-svelte@https://pkg.vc/-/@appwrite/@appwrite.io/pink-icons-svelte@71bdaf8': + resolution: {tarball: https://pkg.vc/-/@appwrite/@appwrite.io/pink-icons-svelte@71bdaf8} version: 2.0.0-RC.1 peerDependencies: svelte: ^4.0.0 @@ -284,8 +284,8 @@ packages: '@appwrite.io/pink-legacy@1.0.3': resolution: {integrity: sha512-GGde5fmPhs+s6/3aFeMPc/kKADG/gTFkYQSy6oBN8pK0y0XNCLrZZgBv+EBbdhwdtqVEWXa0X85Mv9w7jcIlwQ==} - '@appwrite.io/pink-svelte@https://pkg.vc/-/@appwrite/@appwrite.io/pink-svelte@10305c4': - resolution: {tarball: https://pkg.vc/-/@appwrite/@appwrite.io/pink-svelte@10305c4} + '@appwrite.io/pink-svelte@https://pkg.vc/-/@appwrite/@appwrite.io/pink-svelte@71bdaf8': + resolution: {tarball: https://pkg.vc/-/@appwrite/@appwrite.io/pink-svelte@71bdaf8} version: 2.0.0-RC.2 peerDependencies: svelte: ^4.0.0 @@ -3709,7 +3709,7 @@ snapshots: dependencies: svelte: 5.25.3 - '@appwrite.io/pink-icons-svelte@https://pkg.vc/-/@appwrite/@appwrite.io/pink-icons-svelte@10305c4(svelte@5.25.3)': + '@appwrite.io/pink-icons-svelte@https://pkg.vc/-/@appwrite/@appwrite.io/pink-icons-svelte@71bdaf8(svelte@5.25.3)': dependencies: svelte: 5.25.3 @@ -3722,7 +3722,7 @@ snapshots: '@appwrite.io/pink-icons': 1.0.0 the-new-css-reset: 1.11.3 - '@appwrite.io/pink-svelte@https://pkg.vc/-/@appwrite/@appwrite.io/pink-svelte@10305c4(svelte@5.25.3)': + '@appwrite.io/pink-svelte@https://pkg.vc/-/@appwrite/@appwrite.io/pink-svelte@71bdaf8(svelte@5.25.3)': dependencies: '@appwrite.io/pink-icons-svelte': 2.0.0-RC.1(svelte@5.25.3) '@floating-ui/dom': 1.6.13 diff --git a/src/routes/(console)/project-[region]-[project]/databases/database-[database]/table-[table]/spreadsheet.svelte b/src/routes/(console)/project-[region]-[project]/databases/database-[database]/table-[table]/spreadsheet.svelte index 324a1e087..b63104a4c 100644 --- a/src/routes/(console)/project-[region]-[project]/databases/database-[database]/table-[table]/spreadsheet.svelte +++ b/src/routes/(console)/project-[region]-[project]/databases/database-[database]/table-[table]/spreadsheet.svelte @@ -53,6 +53,8 @@ Button, Layout, Link, + Popover, + Keyboard, Badge, FloatingActionBar, Icon, @@ -78,7 +80,8 @@ IconSwitchHorizontal, IconText, IconToggle, - IconViewList + IconViewList, + IconArrowExpand } from '@appwrite.io/pink-icons-svelte'; import type { HeaderCellAction, RowCellAction } from './sheetOptions.svelte'; import SheetOptions from './sheetOptions.svelte'; @@ -120,7 +123,9 @@ : 24; let selectedRows = []; + let showExpandIconForId: number | null = null; let spreadsheetContainer: SpreadsheetContainer; + let previouslyFocusedElement: Element | null = null; let currentPage = 1; let jumpToPageReactive = 0; @@ -747,6 +752,13 @@ $: totalPages = Math.ceil($rows.total / SPREADSHEET_PAGE_LIMIT) || 1; $: rowSelection = !$spreadsheetLoading && !$paginatedRowsLoading ? true : ('disabled' as const); + + $: if (!$databaseRowSheetOptions.show && previouslyFocusedElement) { + requestAnimationFrame(() => { + (previouslyFocusedElement as HTMLElement)?.focus(); + previouslyFocusedElement = null; + }); + } @@ -776,6 +788,14 @@ bottomActionTooltip={{ text: 'Create row', placement: 'top-end' + }} + expandKbdShortcut="Cmd+Enter" + on:expandKbdShortcut={({ detail }) => { + const focusedRowId = detail.rowId; + const focusedRow = $rows.rows.find((row) => row.$id === focusedRowId); + + previouslyFocusedElement = document.activeElement; + onSelectSheetOption('update', null, 'row', focusedRow); }}> {#each $tableColumns as column (column.id)} @@ -867,56 +887,136 @@ valueWithoutHover={row.$sequence}> {#each $tableColumns as { id: columnId, isEditable } (columnId)} {@const rowColumn = $columns.find((col) => col.key === columnId)} - - {#if columnId === '$id'} - {row.$id} - {:else if columnId === '$createdAt' || columnId === '$updatedAt'} - - {:else if columnId === 'actions'} - - onSelectSheetOption(option, null, 'row', row)} - onVisibilityChanged={(visible) => { - canShowDatetimePopover = !visible; - }}> - {#snippet children(toggle)} - - - - {/snippet} - - {:else if isRelationship(rowColumn)} - {@const args = getDisplayNamesForTable(row[columnId])} - {#if !isRelationshipToMany(rowColumn)} - {#if row[columnId]} - {@const displayValue = args - .map((arg) => row[columnId]?.[arg]) - .filter(Boolean) - .join(' | ')} + {#if columnId === '$id'} + + + + + Expand row + + + + + + + + + + + + {:else} + + {#if columnId === '$id'} + {row.$id} + {:else if columnId === '$createdAt' || columnId === '$updatedAt'} + + {:else if columnId === 'actions'} + + onSelectSheetOption(option, null, 'row', row)} + onVisibilityChanged={(visible) => { + canShowDatetimePopover = !visible; + }}> + {#snippet children(toggle)} + + + + {/snippet} + + {:else if isRelationship(rowColumn)} + {@const args = getDisplayNamesForTable(row[columnId])} + {#if !isRelationshipToMany(rowColumn)} + {#if row[columnId]} + {@const displayValue = args + .map((arg) => row[columnId]?.[arg]) + .filter(Boolean) + .join(' | ')} + + {#if displayValue} + { + $databaseRelatedRowSheetOptions.tableId = + row[columnId]?.['$tableId']; + $databaseRelatedRowSheetOptions.rows = + row[columnId]?.['$id']; + $databaseRelatedRowSheetOptions.show = true; + }}> + {displayValue} + + {:else} + + {/if} {:else} {/if} {:else} - + {@const itemsNum = row[columnId]?.length} + Items {/if} + {:else if isSpatialType(rowColumn) && row[columnId] !== null} + + {JSON.stringify(row[columnId])} + {:else} - {@const itemsNum = row[columnId]?.length} - Items - {/if} - {:else if isSpatialType(rowColumn) && row[columnId] !== null} - - {JSON.stringify(row[columnId])} - - {:else} - {@const value = row[columnId]} - {@const formatted = formatColumn(row[columnId])} - {@const isEmptyArray = formatted === 'Empty'} - {@const isDatetimeAttribute = rowColumn.type === 'datetime'} - {@const isEncryptedAttribute = - isString(rowColumn) && rowColumn.encrypt} - {#if isDatetimeAttribute} - - Timestamp - {toLocaleDateTime(value, true)} - - {:else if isEncryptedAttribute} - - {:else if formatted.length > 20} - + {@const value = row[columnId]} + {@const formatted = formatColumn(row[columnId])} + {@const isEmptyArray = formatted === 'Empty'} + {@const isDatetimeAttribute = rowColumn.type === 'datetime'} + {@const isEncryptedAttribute = + isString(rowColumn) && rowColumn.encrypt} + {#if isDatetimeAttribute} + + Timestamp + {toLocaleDateTime(value, true)} + + {:else if isEncryptedAttribute} + + {:else if formatted.length > 20} + + + {formatted} + + + {formatted} + + + {:else if formatted === 'null'} + + {:else if isEmptyArray} + + {:else} {formatted} - - {formatted} - - - {:else if formatted === 'null'} - - {:else if isEmptyArray} - - {:else} - - {formatted} - + {/if} {/if} - {/if} - - {@const isRelatedToMany = isRelationshipToMany(rowColumn)} - {@const hasItems = isRelatedToMany - ? row[columnId]?.length - : false} + + {@const isRelatedToMany = isRelationshipToMany(rowColumn)} + {@const hasItems = isRelatedToMany + ? row[columnId]?.length + : false} - paginatedRows.update(index, row)} - onRevert={(row) => paginatedRows.update(index, row)} - openSideSheet={() => { - close(); /* closes the editor */ + paginatedRows.update(index, row)} + onRevert={(row) => paginatedRows.update(index, row)} + openSideSheet={() => { + close(); /* closes the editor */ - if (isRelationshipToMany(rowColumn)) { - openSideSheetForRelationsToMany( - row[columnId], - rowColumn - ); - } else { - onSelectSheetOption('update', null, 'row', row); - } - }} /> - - + if (isRelationshipToMany(rowColumn)) { + openSideSheetForRelationsToMany( + row[columnId], + rowColumn + ); + } else { + onSelectSheetOption('update', null, 'row', row); + } + }} /> + + + {/if} {/each} {/if} From 75deddf49073e4f23551a127a6c9d7150f70c61c Mon Sep 17 00:00:00 2001 From: Darshan Date: Thu, 23 Oct 2025 10:47:11 +0530 Subject: [PATCH 06/52] update: increase the width of the $id column for button to show nicely. --- .../database-[database]/table-[table]/spreadsheet.svelte | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/routes/(console)/project-[region]-[project]/databases/database-[database]/table-[table]/spreadsheet.svelte b/src/routes/(console)/project-[region]-[project]/databases/database-[database]/table-[table]/spreadsheet.svelte index b63104a4c..d475c7836 100644 --- a/src/routes/(console)/project-[region]-[project]/databases/database-[database]/table-[table]/spreadsheet.svelte +++ b/src/routes/(console)/project-[region]-[project]/databases/database-[database]/table-[table]/spreadsheet.svelte @@ -175,8 +175,8 @@ { id: '$id', title: '$id', - width: getColumnWidth('$id', 225), - minimumWidth: 225, + width: getColumnWidth('$id', 250), + minimumWidth: 250, draggable: false, type: 'string', icon: IconFingerPrint, From dbebcd864a929509d625c7483be75456de3993a9 Mon Sep 17 00:00:00 2001 From: Darshan Date: Thu, 23 Oct 2025 10:50:49 +0530 Subject: [PATCH 07/52] fix: $id shouldn't have column-left or create-index options on header. --- .../database-[database]/table-[table]/sheetOptions.svelte | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/routes/(console)/project-[region]-[project]/databases/database-[database]/table-[table]/sheetOptions.svelte b/src/routes/(console)/project-[region]-[project]/databases/database-[database]/table-[table]/sheetOptions.svelte index 761178f79..71563a424 100644 --- a/src/routes/(console)/project-[region]-[project]/databases/database-[database]/table-[table]/sheetOptions.svelte +++ b/src/routes/(console)/project-[region]-[project]/databases/database-[database]/table-[table]/sheetOptions.svelte @@ -124,6 +124,11 @@ return false; } + // hide column-left and create-index for $id (first column, already indexed) + if (columnId === '$id' && ['column-left', 'create-index'].includes(item.action ?? '')) { + return false; + } + // hide sort options for relationship columns if (isRelationship(column) && ['sort-asc', 'sort-desc'].includes(item.action ?? '')) { return false; From cb3dcd9533473f21e8242e484b76ba199733f159 Mon Sep 17 00:00:00 2001 From: Darshan Date: Thu, 23 Oct 2025 10:57:21 +0530 Subject: [PATCH 08/52] Apply suggestion from @ItzNotABug --- .../database-[database]/table-[table]/spreadsheet.svelte | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/routes/(console)/project-[region]-[project]/databases/database-[database]/table-[table]/spreadsheet.svelte b/src/routes/(console)/project-[region]-[project]/databases/database-[database]/table-[table]/spreadsheet.svelte index d475c7836..e7d8c1688 100644 --- a/src/routes/(console)/project-[region]-[project]/databases/database-[database]/table-[table]/spreadsheet.svelte +++ b/src/routes/(console)/project-[region]-[project]/databases/database-[database]/table-[table]/spreadsheet.svelte @@ -1055,7 +1055,7 @@ text={formatted} /> {:else if formatted.length > 20} - + {formatted} From ba75515c3e9ada56117db236e7bd0ef10c059e46 Mon Sep 17 00:00:00 2001 From: Darshan Date: Thu, 23 Oct 2025 12:10:01 +0530 Subject: [PATCH 09/52] fix: unreachable condition. --- .../database-[database]/table-[table]/spreadsheet.svelte | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/src/routes/(console)/project-[region]-[project]/databases/database-[database]/table-[table]/spreadsheet.svelte b/src/routes/(console)/project-[region]-[project]/databases/database-[database]/table-[table]/spreadsheet.svelte index e7d8c1688..910c55e0e 100644 --- a/src/routes/(console)/project-[region]-[project]/databases/database-[database]/table-[table]/spreadsheet.svelte +++ b/src/routes/(console)/project-[region]-[project]/databases/database-[database]/table-[table]/spreadsheet.svelte @@ -962,10 +962,7 @@ {:else} - {#if columnId === '$id'} - {row.$id} - {:else if columnId === '$createdAt' || columnId === '$updatedAt'} + {#if columnId === '$createdAt' || columnId === '$updatedAt'} Date: Sat, 25 Oct 2025 19:05:44 +0530 Subject: [PATCH 10/52] feat: preliminary support for multi select table! --- package.json | 4 +- pnpm-lock.yaml | 20 +-- src/lib/components/index.ts | 1 + src/lib/components/multiSelectTable.svelte | 89 ++++++++++ .../sites/site-[site]/logs/table.svelte | 157 ++++++++---------- 5 files changed, 171 insertions(+), 100 deletions(-) create mode 100644 src/lib/components/multiSelectTable.svelte diff --git a/package.json b/package.json index e81d3d510..3baff6422 100644 --- a/package.json +++ b/package.json @@ -24,9 +24,9 @@ "@ai-sdk/svelte": "^1.1.24", "@appwrite.io/console": "https://pkg.pr.new/appwrite-labs/cloud/@appwrite.io/console@3996505", "@appwrite.io/pink-icons": "0.25.0", - "@appwrite.io/pink-icons-svelte": "https://pkg.vc/-/@appwrite/@appwrite.io/pink-icons-svelte@50b60cc", + "@appwrite.io/pink-icons-svelte": "https://pkg.vc/-/@appwrite/@appwrite.io/pink-icons-svelte@46f65c7", "@appwrite.io/pink-legacy": "^1.0.3", - "@appwrite.io/pink-svelte": "https://pkg.vc/-/@appwrite/@appwrite.io/pink-svelte@10305c4", + "@appwrite.io/pink-svelte": "https://pkg.vc/-/@appwrite/@appwrite.io/pink-svelte@46f65c7", "@faker-js/faker": "^9.9.0", "@popperjs/core": "^2.11.8", "@sentry/sveltekit": "^8.38.0", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 62a3e0c69..ba2055275 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -18,14 +18,14 @@ importers: specifier: 0.25.0 version: 0.25.0 '@appwrite.io/pink-icons-svelte': - specifier: https://pkg.vc/-/@appwrite/@appwrite.io/pink-icons-svelte@50b60cc - version: https://pkg.vc/-/@appwrite/@appwrite.io/pink-icons-svelte@50b60cc(svelte@5.25.3) + specifier: https://pkg.vc/-/@appwrite/@appwrite.io/pink-icons-svelte@46f65c7 + version: https://pkg.vc/-/@appwrite/@appwrite.io/pink-icons-svelte@46f65c7(svelte@5.25.3) '@appwrite.io/pink-legacy': specifier: ^1.0.3 version: 1.0.3 '@appwrite.io/pink-svelte': - specifier: https://pkg.vc/-/@appwrite/@appwrite.io/pink-svelte@10305c4 - version: https://pkg.vc/-/@appwrite/@appwrite.io/pink-svelte@10305c4(svelte@5.25.3) + specifier: https://pkg.vc/-/@appwrite/@appwrite.io/pink-svelte@46f65c7 + version: https://pkg.vc/-/@appwrite/@appwrite.io/pink-svelte@46f65c7(svelte@5.25.3) '@faker-js/faker': specifier: ^9.9.0 version: 9.9.0 @@ -269,8 +269,8 @@ packages: peerDependencies: svelte: ^4.0.0 - '@appwrite.io/pink-icons-svelte@https://pkg.vc/-/@appwrite/@appwrite.io/pink-icons-svelte@50b60cc': - resolution: {tarball: https://pkg.vc/-/@appwrite/@appwrite.io/pink-icons-svelte@50b60cc} + '@appwrite.io/pink-icons-svelte@https://pkg.vc/-/@appwrite/@appwrite.io/pink-icons-svelte@46f65c7': + resolution: {tarball: https://pkg.vc/-/@appwrite/@appwrite.io/pink-icons-svelte@46f65c7} version: 2.0.0-RC.1 peerDependencies: svelte: ^4.0.0 @@ -284,8 +284,8 @@ packages: '@appwrite.io/pink-legacy@1.0.3': resolution: {integrity: sha512-GGde5fmPhs+s6/3aFeMPc/kKADG/gTFkYQSy6oBN8pK0y0XNCLrZZgBv+EBbdhwdtqVEWXa0X85Mv9w7jcIlwQ==} - '@appwrite.io/pink-svelte@https://pkg.vc/-/@appwrite/@appwrite.io/pink-svelte@10305c4': - resolution: {tarball: https://pkg.vc/-/@appwrite/@appwrite.io/pink-svelte@10305c4} + '@appwrite.io/pink-svelte@https://pkg.vc/-/@appwrite/@appwrite.io/pink-svelte@46f65c7': + resolution: {tarball: https://pkg.vc/-/@appwrite/@appwrite.io/pink-svelte@46f65c7} version: 2.0.0-RC.2 peerDependencies: svelte: ^4.0.0 @@ -3709,7 +3709,7 @@ snapshots: dependencies: svelte: 5.25.3 - '@appwrite.io/pink-icons-svelte@https://pkg.vc/-/@appwrite/@appwrite.io/pink-icons-svelte@50b60cc(svelte@5.25.3)': + '@appwrite.io/pink-icons-svelte@https://pkg.vc/-/@appwrite/@appwrite.io/pink-icons-svelte@46f65c7(svelte@5.25.3)': dependencies: svelte: 5.25.3 @@ -3722,7 +3722,7 @@ snapshots: '@appwrite.io/pink-icons': 1.0.0 the-new-css-reset: 1.11.3 - '@appwrite.io/pink-svelte@https://pkg.vc/-/@appwrite/@appwrite.io/pink-svelte@10305c4(svelte@5.25.3)': + '@appwrite.io/pink-svelte@https://pkg.vc/-/@appwrite/@appwrite.io/pink-svelte@46f65c7(svelte@5.25.3)': dependencies: '@appwrite.io/pink-icons-svelte': 2.0.0-RC.1(svelte@5.25.3) '@floating-ui/dom': 1.6.13 diff --git a/src/lib/components/index.ts b/src/lib/components/index.ts index b9d6e57ed..f93405528 100644 --- a/src/lib/components/index.ts +++ b/src/lib/components/index.ts @@ -87,3 +87,4 @@ export { default as ExpirationInput } from './expirationInput.svelte'; export { default as EstimatedCard } from './estimatedCard.svelte'; export { default as SortButton, type SortDirection } from './sortButton.svelte'; export { default as SendVerificationEmailModal } from './account/sendVerificationEmailModal.svelte'; +export { default as MultiSelectionTable } from './multiSelectTable.svelte'; diff --git a/src/lib/components/multiSelectTable.svelte b/src/lib/components/multiSelectTable.svelte new file mode 100644 index 000000000..f89b2f999 --- /dev/null +++ b/src/lib/components/multiSelectTable.svelte @@ -0,0 +1,89 @@ + + + + + {@render header?.(root)} + + + {@render children(root)} + + +{#if allowSelection && selectedRows.length > 0} + + + + + {selectedRows.length > 1 ? `${resource}s` : resource} + selected + + + + + + + +{/if} + + { + await onDelete?.(selectedRows); + showConfirmDeletion = false; + }} +> +

+ Are you sure you want to delete {selectedRows.length} + {selectedRows.length > 1 ? `${resource}s` : resource} +

+ +

This action is irreversible.

+
diff --git a/src/routes/(console)/project-[region]-[project]/sites/site-[site]/logs/table.svelte b/src/routes/(console)/project-[region]-[project]/sites/site-[site]/logs/table.svelte index 9d74a095d..d204eff19 100644 --- a/src/routes/(console)/project-[region]-[project]/sites/site-[site]/logs/table.svelte +++ b/src/routes/(console)/project-[region]-[project]/sites/site-[site]/logs/table.svelte @@ -1,8 +1,8 @@ - - + + {#snippet header(root)} {#each filteredColumns as { id, title }} {title} {/each} - - {#each logs.executions as log (log.$id)} - { - e.stopPropagation(); - openSheet = true; - selectedLogId = log.$id; - }}> - {#each filteredColumns as column} - - {#if column.id === '$id'} - {#key column.id} - {log.$id} - {/key} - {:else if column.id === 'deploymentId'} - {log.deploymentId} - {:else if column.id === 'requestMethod'} - - {log.requestMethod} - - {:else if column.id === 'duration'} - {#if ['processing', 'waiting'].includes(log.status)} - - {:else} - {calculateTime(log.duration)} + {/snippet} + + {#snippet children(root)} + {#each logs.executions as log (log.$id)} + { + e.stopPropagation(); + openSheet = true; + selectedLogId = log.$id; + }}> + {#each filteredColumns as column} + + {#if column.id === '$id'} + {#key column.id} + {log.$id} + {/key} + {:else if column.id === 'deploymentId'} + {log.deploymentId} + {:else if column.id === 'requestMethod'} + + {log.requestMethod} + + {:else if column.id === 'duration'} + {#if ['processing', 'waiting'].includes(log.status)} + + {:else} + {calculateTime(log.duration)} + {/if} + {:else if column.id === 'responseStatusCode'} +
+ +
+ {:else if column.id === 'requestPath'} + + {log.requestPath} + + {:else if column.id === '$createdAt'} + {/if} - {:else if column.id === 'responseStatusCode'} -
- -
- {:else if column.id === 'requestPath'} - - {log.requestPath} - - {:else if column.id === '$createdAt'} - - {/if} -
- {/each} -
- {/each} -
+ + {/each} + + {/each} + {/snippet} + - -{#if selectedRows.length > 0} - - - - - {selectedRows.length > 1 ? 'logs' : 'log'} - selected - - - - - - - -{/if} - - -

- Are you sure you want to delete {selectedRows.length} - {selectedRows.length > 1 ? 'logs' : 'log'}? -

- -

This action is irreversible.

-
From 09ff84ecd3357b01dda9e15d2c9e1b8257a934a3 Mon Sep 17 00:00:00 2001 From: Darshan Date: Sat, 25 Oct 2025 19:29:09 +0530 Subject: [PATCH 11/52] feat: multi-select table on tables delete. --- src/lib/components/multiSelectTable.svelte | 21 ++-- .../database-[database]/table.svelte | 113 +++++++----------- .../sites/site-[site]/logs/table.svelte | 7 +- 3 files changed, 60 insertions(+), 81 deletions(-) diff --git a/src/lib/components/multiSelectTable.svelte b/src/lib/components/multiSelectTable.svelte index f89b2f999..d2325ef10 100644 --- a/src/lib/components/multiSelectTable.svelte +++ b/src/lib/components/multiSelectTable.svelte @@ -2,10 +2,11 @@ import type { Snippet } from 'svelte'; import { Table, - FloatingActionBar, Badge, + Typography, + FloatingActionBar, type TableColumn, - type TableRootProps + type TableRootProps, } from '@appwrite.io/pink-svelte'; import { Button } from '$lib/elements/forms'; import { Confirm } from '$lib/components/index'; @@ -30,6 +31,7 @@ onCancel?: () => Promise | void; } = $props(); + let disableModal: boolean = $state(false); let selectedRows: string[] = $state([]); let showConfirmDeletion: boolean = $state(false); @@ -73,17 +75,20 @@ { + disableModal = true; await onDelete?.(selectedRows); + disableModal = false; showConfirmDeletion = false; - }} -> -

+ }}> + Are you sure you want to delete {selectedRows.length} - {selectedRows.length > 1 ? `${resource}s` : resource} -

+ {selectedRows.length > 1 ? `${resource}s` : resource}. + -

This action is irreversible.

+ This action is irreversible.
diff --git a/src/routes/(console)/project-[region]-[project]/databases/database-[database]/table.svelte b/src/routes/(console)/project-[region]-[project]/databases/database-[database]/table.svelte index 28e5c2362..19cf12b52 100644 --- a/src/routes/(console)/project-[region]-[project]/databases/database-[database]/table.svelte +++ b/src/routes/(console)/project-[region]-[project]/databases/database-[database]/table.svelte @@ -1,34 +1,26 @@ - - + {onDelete}> + {#snippet header(root)} {#each $tableViewColumns as { id, title }} {title} {/each} - - {#each data.tables.tables as table (table.$id)} - - {#each $tableViewColumns as column} - - {#if column.id === '$id'} - {#key $tableViewColumns} - {table.$id} - {/key} - {:else if column.id === 'name'} - {table.name} - {:else} - - {/if} - - {/each} - - {/each} - + {/snippet} -{#if selectedTables.length > 0} - - - - - {selectedTables.length > 1 ? 'tables' : 'table'} - selected - - - - - - - -{/if} - - - - Are you sure you want to delete {selectedTables.length} - {selectedTables.length > 1 ? 'tables' : 'table'}? - - + {#snippet children(root)} + {#each data.tables.tables as table (table.$id)} + + {#each $tableViewColumns as column} + + {#if column.id === '$id'} + {#key $tableViewColumns} + {table.$id} + {/key} + {:else if column.id === 'name'} + {table.name} + {:else} + + {/if} + + {/each} + + {/each} + {/snippet} + diff --git a/src/routes/(console)/project-[region]-[project]/sites/site-[site]/logs/table.svelte b/src/routes/(console)/project-[region]-[project]/sites/site-[site]/logs/table.svelte index d204eff19..b530e4d2c 100644 --- a/src/routes/(console)/project-[region]-[project]/sites/site-[site]/logs/table.svelte +++ b/src/routes/(console)/project-[region]-[project]/sites/site-[site]/logs/table.svelte @@ -54,12 +54,7 @@ } - + {#snippet header(root)} {#each filteredColumns as { id, title }} {title} From ca042753aecc5d91a2b316c38947bbb257d36a6a Mon Sep 17 00:00:00 2001 From: Darshan Date: Sun, 26 Oct 2025 12:08:47 +0530 Subject: [PATCH 12/52] change: structure a bit. --- src/lib/components/index.ts | 1 + src/lib/components/multiSelectTable.svelte | 65 ++++++++++++------- .../database-[database]/table.svelte | 16 ++--- .../sites/site-[site]/logs/table.svelte | 19 +++--- 4 files changed, 61 insertions(+), 40 deletions(-) diff --git a/src/lib/components/index.ts b/src/lib/components/index.ts index f93405528..8d99efda7 100644 --- a/src/lib/components/index.ts +++ b/src/lib/components/index.ts @@ -88,3 +88,4 @@ export { default as EstimatedCard } from './estimatedCard.svelte'; export { default as SortButton, type SortDirection } from './sortButton.svelte'; export { default as SendVerificationEmailModal } from './account/sendVerificationEmailModal.svelte'; export { default as MultiSelectionTable } from './multiSelectTable.svelte'; +export * from './multiSelectTable.svelte'; diff --git a/src/lib/components/multiSelectTable.svelte b/src/lib/components/multiSelectTable.svelte index d2325ef10..31ed31df1 100644 --- a/src/lib/components/multiSelectTable.svelte +++ b/src/lib/components/multiSelectTable.svelte @@ -1,3 +1,7 @@ + + @@ -66,29 +71,45 @@ if (confirmDeletion) { showConfirmDeletion = true; } else { - await onDelete?.(selectedRows); + const state = await onDelete?.(selectedRows); + if (typeof state === 'string') { + // user should handle error on their own! + } else { + selectedRows = []; + } } }}>Delete {/if} - { - disableModal = true; - await onDelete?.(selectedRows); - disableModal = false; - showConfirmDeletion = false; - }}> - - Are you sure you want to delete {selectedRows.length} - {selectedRows.length > 1 ? `${resource}s` : resource}. - +{#if allowSelection} + { + disableModal = true; + onDeleteError = null; - This action is irreversible. - + const state = await onDelete?.(selectedRows); + if (typeof state === 'string') { + disableModal = false; + onDeleteError = state || `Failed to delete ${resource}s`; + } else { + selectedRows = []; + disableModal = false; + showConfirmDeletion = false; + } + }}> + + Are you sure you want to delete {selectedRows.length} + {selectedRows.length > 1 ? `${resource}s` : resource}. + + + This action is irreversible. + +{/if} diff --git a/src/routes/(console)/project-[region]-[project]/databases/database-[database]/table.svelte b/src/routes/(console)/project-[region]-[project]/databases/database-[database]/table.svelte index 19cf12b52..bf3db7853 100644 --- a/src/routes/(console)/project-[region]-[project]/databases/database-[database]/table.svelte +++ b/src/routes/(console)/project-[region]-[project]/databases/database-[database]/table.svelte @@ -3,7 +3,7 @@ import { resolve } from '$app/paths'; import { page } from '$app/state'; import { Submit, trackError, trackEvent } from '$lib/actions/analytics'; - import { Id, MultiSelectionTable } from '$lib/components'; + import { Id, MultiSelectionTable, type DeleteOperationState } from '$lib/components'; import { Dependencies } from '$lib/constants'; import DualTimeView from '$lib/components/dualTimeView.svelte'; import { addNotification } from '$lib/stores/notifications'; @@ -17,7 +17,7 @@ let { data }: PageProps = $props(); - async function onDelete(selectedTables: string[]) { + async function onDelete(selectedTables: string[]): Promise { const promises = selectedTables.map((tableId) => sdk.forProject(page.params.region, page.params.project).tablesDB.deleteTable({ databaseId: page.params.database, @@ -26,19 +26,19 @@ ); try { await Promise.all(promises); + await invalidate(Dependencies.TABLES); + subNavigation.update(); + trackEvent(Submit.TableDelete); addNotification({ type: 'success', message: `${selectedTables.length} table${selectedTables.length > 1 ? 's' : ''} deleted` }); - await invalidate(Dependencies.TABLES); - subNavigation.update(); + + return true; } catch (error) { - addNotification({ - type: 'error', - message: error.message - }); trackError(error, Submit.TableDelete); + return error.message; } } diff --git a/src/routes/(console)/project-[region]-[project]/sites/site-[site]/logs/table.svelte b/src/routes/(console)/project-[region]-[project]/sites/site-[site]/logs/table.svelte index b530e4d2c..57ef3a2cc 100644 --- a/src/routes/(console)/project-[region]-[project]/sites/site-[site]/logs/table.svelte +++ b/src/routes/(console)/project-[region]-[project]/sites/site-[site]/logs/table.svelte @@ -1,5 +1,5 @@ From e210612d19ca871109d60d2b755375226409bd9c Mon Sep 17 00:00:00 2001 From: Darshan Date: Sun, 26 Oct 2025 12:31:57 +0530 Subject: [PATCH 13/52] add: auto notify support. add: `deleteContent` snippet support. add: `multiSelectTable` to functions. --- src/lib/components/multiSelectTable.svelte | 33 +- .../database-[database]/table.svelte | 5 - .../function-[function]/table.svelte | 314 ++++++++---------- .../sites/site-[site]/logs/table.svelte | 5 - 4 files changed, 173 insertions(+), 184 deletions(-) diff --git a/src/lib/components/multiSelectTable.svelte b/src/lib/components/multiSelectTable.svelte index 31ed31df1..e87daa25f 100644 --- a/src/lib/components/multiSelectTable.svelte +++ b/src/lib/components/multiSelectTable.svelte @@ -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 | number; header: Snippet<[root: TableRootProps]>; children: Snippet<[root: TableRootProps]>; + deleteContent?: Snippet<[count: number]>; onDelete?: (selectedRows: string[]) => Promise | DeleteOperationState; onCancel?: () => Promise | 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` + }); + } @@ -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; } }}> - Are you sure you want to delete {selectedRows.length} - {selectedRows.length > 1 ? `${resource}s` : resource}. + {@const selectionCount = selectedRows.length} + {#if deleteContent} + + {@render deleteContent(selectionCount)} + {:else} + Are you sure you want to delete {selectionCount} + {selectionCount > 1 ? `${resource}s` : resource}. + {/if} This action is irreversible. diff --git a/src/routes/(console)/project-[region]-[project]/databases/database-[database]/table.svelte b/src/routes/(console)/project-[region]-[project]/databases/database-[database]/table.svelte index bf3db7853..cdfcf1cc3 100644 --- a/src/routes/(console)/project-[region]-[project]/databases/database-[database]/table.svelte +++ b/src/routes/(console)/project-[region]-[project]/databases/database-[database]/table.svelte @@ -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); diff --git a/src/routes/(console)/project-[region]-[project]/functions/function-[function]/table.svelte b/src/routes/(console)/project-[region]-[project]/functions/function-[function]/table.svelte index eecafbd5a..40c2eff46 100644 --- a/src/routes/(console)/project-[region]-[project]/functions/function-[function]/table.svelte +++ b/src/routes/(console)/project-[region]-[project]/functions/function-[function]/table.svelte @@ -1,5 +1,5 @@ - - + {#snippet header(root)} {#each columns as { id, title }} {title} {/each} - - {#each data.deploymentList.deployments as deployment (deployment.$id)} - - {#each columns as column} - - {#if column.id === '$id'} - {#key column.id} - {deployment.$id} - {/key} - {:else if column.id === 'status'} - {@const status = deployment.status} + {/snippet} - {#if data?.activeDeployment?.$id === deployment?.$id} - - {:else} - - {/if} - {:else if column.id === 'type'} - - {:else if column.id === '$updatedAt'} - - {:else if column.id === 'buildDuration'} - {#if ['waiting'].includes(deployment.status)} - - - {:else if ['processing', 'building'].includes(deployment.status)} - - {: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} - - {/each} - - - + {#snippet children(root)} + {#each data.deploymentList.deployments as deployment (deployment.$id)} + + {#each columns as column} + + {#if column.id === '$id'} + {#key column.id} + {deployment.$id} + {/key} + {:else if column.id === 'status'} + {@const status = deployment.status} - - - -
+ {#if data?.activeDeployment?.$id === deployment?.$id} + + {:else} + + {/if} + {:else if column.id === 'type'} + + {:else if column.id === '$updatedAt'} + + {:else if column.id === 'buildDuration'} + {#if ['waiting'].includes(deployment.status)} + - + {:else if ['processing', 'building'].includes(deployment.status)} + + {: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} + + {/each} + + + + + + + +
+ { + selectedDeployment = deployment; + showRedeploy = true; + toggle(); + trackEvent(Click.FunctionsRedeployClick); + }} + style="width: 100%"> + Redeploy + +
+
Source is empty
+
+ {#if deployment.status === 'ready' && deployment.$id !== $func.deploymentId} { selectedDeployment = deployment; - showRedeploy = true; + showActivate = true; toggle(); - trackEvent(Click.FunctionsRedeployClick); - }} - style="width: 100%"> - Redeploy + }}> + Activate -
-
Source is empty
-
- {#if deployment.status === 'ready' && deployment.$id !== $func.deploymentId} - { - selectedDeployment = deployment; - showActivate = true; - toggle(); - }}> - Activate - - {/if} + {/if} - + - {#if deployment.status === 'processing' || deployment.status === 'building' || deployment.status === 'waiting'} - { - selectedDeployment = deployment; - toggle(); + {#if deployment.status === 'processing' || deployment.status === 'building' || deployment.status === 'waiting'} + { + selectedDeployment = deployment; + toggle(); - showCancel = true; - trackEvent(Click.FunctionsDeploymentCancelClick); - }}> - Cancel - - {/if} - {#if deployment.status !== 'building' && deployment.status !== 'processing' && deployment.status !== 'waiting'} - { - selectedDeployment = deployment; - toggle(); + showCancel = true; + trackEvent(Click.FunctionsDeploymentCancelClick); + }}> + Cancel + + {/if} + {#if deployment.status !== 'building' && deployment.status !== 'processing' && deployment.status !== 'waiting'} + { + selectedDeployment = deployment; + toggle(); - showDelete = true; - trackEvent(Click.FunctionsDeploymentDeleteClick); - }}> - Delete - - {/if} -
-
-
-
-
- {/each} -
+ showDelete = true; + trackEvent(Click.FunctionsDeploymentDeleteClick); + }}> + Delete + + {/if} + + + + + + {/each} + {/snippet} + + {#snippet deleteContent(count)} +

+ Are you sure you want to delete {count} + {count > 1 ? 'deployments' : 'deployment'} from your function - + {page.data.function.name}? +

+ {/snippet} +
{#if selectedDeployment} - + {/if} - -{#if selectedRows.length > 0} - - - - - {selectedRows.length > 1 ? 'deployments' : 'deployment'} - selected - - - - - - - -{/if} - - -

- Are you sure you want to delete {selectedRows.length} - {selectedRows.length > 1 ? 'deployments' : 'deployment'} from your function - - {$func.name}? -

- -

This action is irreversible.

-
diff --git a/src/routes/(console)/project-[region]-[project]/sites/site-[site]/logs/table.svelte b/src/routes/(console)/project-[region]-[project]/sites/site-[site]/logs/table.svelte index 57ef3a2cc..1ce01bb89 100644 --- a/src/routes/(console)/project-[region]-[project]/sites/site-[site]/logs/table.svelte +++ b/src/routes/(console)/project-[region]-[project]/sites/site-[site]/logs/table.svelte @@ -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) { From 8a8513f7e861220611809da1acf18c0e84ce9722 Mon Sep 17 00:00:00 2001 From: Darshan Date: Sun, 26 Oct 2025 12:41:50 +0530 Subject: [PATCH 14/52] add: `multiSelectTable` to function executions. --- .../executions/table.svelte | 208 +++++++----------- .../function-[function]/table.svelte | 18 +- 2 files changed, 89 insertions(+), 137 deletions(-) diff --git a/src/routes/(console)/project-[region]-[project]/functions/function-[function]/executions/table.svelte b/src/routes/(console)/project-[region]-[project]/functions/function-[function]/executions/table.svelte index b73c0a7f6..cbb6d0042 100644 --- a/src/routes/(console)/project-[region]-[project]/functions/function-[function]/executions/table.svelte +++ b/src/routes/(console)/project-[region]-[project]/functions/function-[function]/executions/table.svelte @@ -1,16 +1,9 @@ - - + + {#snippet header(root)} {#each columns as { id, title }} {title} {/each} - - {#each executions.executions as log (log.$id)} - { - e.stopPropagation(); - open = true; - selectedLogId = log.$id; - }}> - {#each columns as column} - - {#if column.id === '$id'} - {#key column.id} - {log.$id} - {/key} - {:else if column.id === 'deploymentId'} - {log.deploymentId} - {:else if column.id === '$createdAt'} - - {:else if column.id === 'requestPath'} - - {log.requestPath} - - {:else if column.id === 'responseStatusCode'} - - {:else if column.id === 'requestMethod'} - - {log.requestMethod} - - {:else if column.id === 'trigger'} - {capitalize(log.trigger)} - {:else if column.id === 'status'} - {@const status = log.status} - -
- - -
- - {`Scheduled to execute on ${toLocaleDateTime(log.scheduledAt)}`} - -
- {:else if column.id === 'duration'} - {#if ['processing', 'waiting'].includes(log.status)} - - {:else} - {calculateTime(log.duration)} + {/snippet} + + {#snippet children(root)} + {#each executions.executions as log (log.$id)} + { + e.stopPropagation(); + open = true; + selectedLogId = log.$id; + }}> + {#each columns as column} + + {#if column.id === '$id'} + {#key column.id} + {log.$id} + {/key} + {:else if column.id === 'deploymentId'} + {log.deploymentId} + {:else if column.id === '$createdAt'} + + {:else if column.id === 'requestPath'} + + {log.requestPath} + + {:else if column.id === 'responseStatusCode'} + + {:else if column.id === 'requestMethod'} + + {log.requestMethod} + + {:else if column.id === 'trigger'} + {capitalize(log.trigger)} + {:else if column.id === 'status'} + {@const status = log.status} + +
+ + +
+ + {`Scheduled to execute on ${toLocaleDateTime(log.scheduledAt)}`} + +
+ {:else if column.id === 'duration'} + {#if ['processing', 'waiting'].includes(log.status)} + + {:else} + {calculateTime(log.duration)} + {/if} {/if} - {/if} -
- {/each} -
- {/each} -
+ + {/each} + + {/each} + {/snippet} +
- -{#if selectedRows.length > 0} - - - - - {selectedRows.length > 1 ? 'executions' : 'execution'} - selected - - - - - - - -{/if} - - -

- Are you sure you want to delete {selectedRows.length} - {selectedRows.length > 1 ? 'executions' : 'execution'}? -

- -

This action is irreversible.

-
diff --git a/src/routes/(console)/project-[region]-[project]/functions/function-[function]/table.svelte b/src/routes/(console)/project-[region]-[project]/functions/function-[function]/table.svelte index 40c2eff46..3296345f1 100644 --- a/src/routes/(console)/project-[region]-[project]/functions/function-[function]/table.svelte +++ b/src/routes/(console)/project-[region]-[project]/functions/function-[function]/table.svelte @@ -14,13 +14,7 @@ import { Dependencies } from '$lib/constants'; import Cancel from './(modals)/cancelDeploymentModal.svelte'; import { base } from '$app/paths'; - import { - ActionMenu, - Icon, - Status, - Table, - Tooltip - } from '@appwrite.io/pink-svelte'; + import { ActionMenu, Icon, Status, Table, Tooltip } from '@appwrite.io/pink-svelte'; import { Click, Submit, trackError, trackEvent } from '$lib/actions/analytics'; import { IconDotsHorizontal, @@ -40,12 +34,12 @@ let { data, - columns, + columns }: { - data: PageData, - columns: Column[] - } = $props() - + data: PageData; + columns: Column[]; + } = $props(); + let showDelete = $state(false); let showCancel = $state(false); let showActivate = $state(false); From b3eb92c12a451d3b1cdb3e277927393d08281f88 Mon Sep 17 00:00:00 2001 From: Darshan Date: Sun, 26 Oct 2025 12:54:56 +0530 Subject: [PATCH 15/52] add: `multiSelectTable` to function messaging. update: make `confirmDeletion`'s default value as `true`. --- src/lib/components/multiSelectTable.svelte | 2 +- .../database-[database]/table.svelte | 1 - .../executions/table.svelte | 2 +- .../function-[function]/table.svelte | 1 - .../messaging/+page.svelte | 194 ++++++++---------- .../sites/site-[site]/logs/table.svelte | 2 +- 6 files changed, 85 insertions(+), 117 deletions(-) diff --git a/src/lib/components/multiSelectTable.svelte b/src/lib/components/multiSelectTable.svelte index e87daa25f..2e6eac302 100644 --- a/src/lib/components/multiSelectTable.svelte +++ b/src/lib/components/multiSelectTable.svelte @@ -20,7 +20,7 @@ columns, resource, allowSelection = true, - confirmDeletion = false, + confirmDeletion = true, showSuccessNotification = true, header, children, diff --git a/src/routes/(console)/project-[region]-[project]/databases/database-[database]/table.svelte b/src/routes/(console)/project-[region]-[project]/databases/database-[database]/table.svelte index cdfcf1cc3..473cc08a3 100644 --- a/src/routes/(console)/project-[region]-[project]/databases/database-[database]/table.svelte +++ b/src/routes/(console)/project-[region]-[project]/databases/database-[database]/table.svelte @@ -52,7 +52,6 @@ diff --git a/src/routes/(console)/project-[region]-[project]/functions/function-[function]/executions/table.svelte b/src/routes/(console)/project-[region]-[project]/functions/function-[function]/executions/table.svelte index cbb6d0042..5205f9f20 100644 --- a/src/routes/(console)/project-[region]-[project]/functions/function-[function]/executions/table.svelte +++ b/src/routes/(console)/project-[region]-[project]/functions/function-[function]/executions/table.svelte @@ -50,7 +50,7 @@ } - + {#snippet header(root)} {#each columns as { id, title }} {title} diff --git a/src/routes/(console)/project-[region]-[project]/functions/function-[function]/table.svelte b/src/routes/(console)/project-[region]-[project]/functions/function-[function]/table.svelte index 3296345f1..65e10e1f3 100644 --- a/src/routes/(console)/project-[region]-[project]/functions/function-[function]/table.svelte +++ b/src/routes/(console)/project-[region]-[project]/functions/function-[function]/table.svelte @@ -72,7 +72,6 @@ diff --git a/src/routes/(console)/project-[region]-[project]/messaging/+page.svelte b/src/routes/(console)/project-[region]-[project]/messaging/+page.svelte index 6f009719b..75771028c 100644 --- a/src/routes/(console)/project-[region]-[project]/messaging/+page.svelte +++ b/src/routes/(console)/project-[region]-[project]/messaging/+page.svelte @@ -1,7 +1,15 @@ - + {#snippet header(root)} {#each filteredColumns as { id, title }} {title} From 1d55d26722671ca3360a9e6be1cfae561348cfe5 Mon Sep 17 00:00:00 2001 From: Darshan Date: Sun, 26 Oct 2025 13:03:27 +0530 Subject: [PATCH 16/52] add: `multiSelectTable` to auth > team > memberships. update: add `deleteContentNotice` support. --- src/lib/components/multiSelectTable.svelte | 17 +- .../teams/team-[team]/members/+page.svelte | 179 +++++++----------- .../messaging/+page.svelte | 6 +- 3 files changed, 82 insertions(+), 120 deletions(-) diff --git a/src/lib/components/multiSelectTable.svelte b/src/lib/components/multiSelectTable.svelte index 2e6eac302..56b75080e 100644 --- a/src/lib/components/multiSelectTable.svelte +++ b/src/lib/components/multiSelectTable.svelte @@ -26,7 +26,8 @@ children, onDelete, onCancel, - deleteContent + deleteContent, + deleteContentNotice }: { resource: string; allowSelection?: boolean; @@ -36,6 +37,7 @@ header: Snippet<[root: TableRootProps]>; children: Snippet<[root: TableRootProps]>; deleteContent?: Snippet<[count: number]>; + deleteContentNotice?: Snippet; onDelete?: (selectedRows: string[]) => Promise | DeleteOperationState; onCancel?: () => Promise | void; } = $props(); @@ -129,14 +131,21 @@ {@const selectionCount = selectedRows.length} {#if deleteContent} - + {@render deleteContent(selectionCount)} {:else} Are you sure you want to delete {selectionCount} - {selectionCount > 1 ? `${resource}s` : resource}. + {selectionCount > 1 ? `${resource}s` : resource}? {/if} - This action is irreversible. + + {#if deleteContentNotice} + + {@render deleteContentNotice()} + {:else} + This action is irreversible. + {/if} + {/if} diff --git a/src/routes/(console)/project-[region]-[project]/auth/teams/team-[team]/members/+page.svelte b/src/routes/(console)/project-[region]-[project]/auth/teams/team-[team]/members/+page.svelte index 629c5bdda..02266b199 100644 --- a/src/routes/(console)/project-[region]-[project]/auth/teams/team-[team]/members/+page.svelte +++ b/src/routes/(console)/project-[region]-[project]/auth/teams/team-[team]/members/+page.svelte @@ -1,6 +1,13 @@ @@ -85,71 +62,61 @@ {#if data.memberships.total} - - + {#snippet header(root)} Name Roles Joined - - {#each data.memberships.memberships as membership (membership.$id)} - {@const username = membership.userName ? membership.userName : '-'} - - - - - {username} - - - - {membership.roles} - - - - - - - - - {/each} - + {/snippet} - {#if selectedMemberships.length > 0} - - - - - {selectedMemberships.length > 1 ? 'memberships' : 'membership'} - selected - - - - - - - - {/if} + {#snippet children(root)} + {#each data.memberships.memberships as membership (membership.$id)} + {@const username = membership.userName ? membership.userName : '-'} + + + + + {username} + + + + {membership.roles} + + + + + + + + + {/each} + {/snippet} + + {#snippet deleteContentNotice()} + This action is irreversible and will remove the selected members from this team. + {/snippet} + - - invalidate(Dependencies.MEMBERSHIPS)} /> + invalidate(Dependencies.MEMBERSHIPS)} /> - - - Are you sure you want to delete {selectedMemberships.length} - {selectedMemberships.length > 1 ? 'memberships' : 'membership'}? - - - This action is irreversible and will remove the selected members from this team. - - + invalidate(Dependencies.MEMBERSHIPS)} /> diff --git a/src/routes/(console)/project-[region]-[project]/messaging/+page.svelte b/src/routes/(console)/project-[region]-[project]/messaging/+page.svelte index 75771028c..56a5eab70 100644 --- a/src/routes/(console)/project-[region]-[project]/messaging/+page.svelte +++ b/src/routes/(console)/project-[region]-[project]/messaging/+page.svelte @@ -27,11 +27,7 @@ import type { Column } from '$lib/helpers/types'; import { writable } from 'svelte/store'; import { canWriteMessages } from '$lib/stores/roles'; - import { - Layout, - Link, - Table, - } from '@appwrite.io/pink-svelte'; + import { Layout, Link, Table } from '@appwrite.io/pink-svelte'; import { onDestroy, onMount } from 'svelte'; import { stopPolling, pollMessagesStatus } from './helper'; From baff61e4bf4d8a11e749885d31a9605bd40e6eb4 Mon Sep 17 00:00:00 2001 From: Darshan Date: Sun, 26 Oct 2025 13:08:45 +0530 Subject: [PATCH 17/52] add: `multiSelectTable` to auth > user > targets. --- .../auth/user-[user]/targets/table.svelte | 142 +++++++----------- 1 file changed, 54 insertions(+), 88 deletions(-) diff --git a/src/routes/(console)/project-[region]-[project]/auth/user-[user]/targets/table.svelte b/src/routes/(console)/project-[region]-[project]/auth/user-[user]/targets/table.svelte index 09aa63619..f06ed2ab4 100644 --- a/src/routes/(console)/project-[region]-[project]/auth/user-[user]/targets/table.svelte +++ b/src/routes/(console)/project-[region]-[project]/auth/user-[user]/targets/table.svelte @@ -1,6 +1,5 @@ - - + + {#snippet header(root)} {#each $columns as { id, title }} {title} {/each} - - {#each data.targets.targets as target (target.$id)} - {@const provider = data.providersById[target.providerId]} - - {#each $columns as column} - - {#if column.id === '$id'} - {#key $columns} - - {target[column.id]} - - {/key} - {:else if column.id === 'target'} - {#if target.providerType === MessagingProviderType.Push} - {target.name} + {/snippet} + + {#snippet children(root)} + {#each data.targets.targets as target (target.$id)} + {@const provider = data.providersById[target.providerId]} + + {#each $columns as column} + + {#if column.id === '$id'} + {#key $columns} + + {target[column.id]} + + {/key} + {:else if column.id === 'target'} + {#if target.providerType === MessagingProviderType.Push} + {target.name} + {:else} + {target.identifier} + {/if} + {:else if column.id === 'providerType'} + + {:else if column.id === 'provider'} + {#if provider} + + {/if} + {:else if column.id === '$createdAt'} + {:else} - {target.identifier} + {target[column.id]} {/if} - {:else if column.id === 'providerType'} - - {:else if column.id === 'provider'} - {#if provider} - - {/if} - {:else if column.id === '$createdAt'} - - {:else} - {target[column.id]} - {/if} - - {/each} - - {/each} - - -{#if selectedIds.length > 0} - - - - - {selectedIds.length > 1 ? 'targets' : 'target'} - selected - - - - - - - -{/if} - - - - Are you sure you want to delete {selectedIds.length} - {selectedIds.length > 1 ? 'targets' : 'target'}? - - + + {/each} + + {/each} + {/snippet} + From 901cdd535c8931e14da4763772b6ee659c407182 Mon Sep 17 00:00:00 2001 From: Darshan Date: Sun, 26 Oct 2025 13:13:04 +0530 Subject: [PATCH 18/52] migrate: existing `MultiSelectionTable` users to svelte5 runes! --- .../teams/team-[team]/members/+page.svelte | 28 +++++++++---------- .../messaging/+page.svelte | 14 ++++++---- 2 files changed, 21 insertions(+), 21 deletions(-) diff --git a/src/routes/(console)/project-[region]-[project]/auth/teams/team-[team]/members/+page.svelte b/src/routes/(console)/project-[region]-[project]/auth/teams/team-[team]/members/+page.svelte index 02266b199..653a2cad2 100644 --- a/src/routes/(console)/project-[region]-[project]/auth/teams/team-[team]/members/+page.svelte +++ b/src/routes/(console)/project-[region]-[project]/auth/teams/team-[team]/members/+page.svelte @@ -14,7 +14,7 @@ import { invalidate } from '$app/navigation'; import { base } from '$app/paths'; import DualTimeView from '$lib/components/dualTimeView.svelte'; - import type { PageData } from './$types'; + import type { PageProps } from './$types'; import CreateMember from '../createMembership.svelte'; import DeleteMembership from '../deleteMembership.svelte'; import { Dependencies } from '$lib/constants'; @@ -23,22 +23,19 @@ import { IconPlus } from '@appwrite.io/pink-icons-svelte'; import { sdk } from '$lib/stores/sdk'; - export let data: PageData; + const { data }: PageProps = $props(); - let showCreate = false; - let showDelete = false; - let selectedMembership: Models.Membership; - - const region = page.params.region; - const project = page.params.project; + let showCreate = $state(false); + let showDelete = $state(false); + let selectedMembership: Models.Membership | null = $state(null); async function handleBulkDelete(selectedRows: string[]): Promise { - const promises = selectedRows.map((membershipId) => - sdk.forProject(page.params.region, page.params.project).teams.deleteMembership({ + const promises = selectedRows.map((membershipId) => { + return sdk.forProject(page.params.region, page.params.project).teams.deleteMembership({ teamId: page.params.team, membershipId - }) - ); + }); + }); try { await Promise.all(promises); @@ -83,7 +80,7 @@ {@const username = membership.userName ? membership.userName : '-'} @@ -101,9 +98,10 @@ - - - -{/if} - - - - Are you sure you want to delete {selectedIds.length} - {selectedIds.length > 1 ? 'identities' : 'identity'}? - - + + {/each} + + {/each} + {/snippet} + From c68fd85eeff2e416c694a34c3f0442d379d663e3 Mon Sep 17 00:00:00 2001 From: Darshan Date: Sun, 26 Oct 2025 14:07:34 +0530 Subject: [PATCH 20/52] add: `multiSelectTable` to database backups. --- src/lib/components/multiSelectTable.svelte | 166 +++---- .../database-[database]/backups/+page.svelte | 7 +- .../database-[database]/backups/table.svelte | 419 +++++++++--------- 3 files changed, 299 insertions(+), 293 deletions(-) diff --git a/src/lib/components/multiSelectTable.svelte b/src/lib/components/multiSelectTable.svelte index c79c496f3..7ccc0ba82 100644 --- a/src/lib/components/multiSelectTable.svelte +++ b/src/lib/components/multiSelectTable.svelte @@ -23,6 +23,7 @@ allowSelection = true, confirmDeletion = true, showSuccessNotification = true, + computeKey = 'multiSelectionTable', header, children, onDelete, @@ -33,6 +34,7 @@ resource: string; allowSelection?: boolean; confirmDeletion?: boolean; + computeKey?: string | number; showSuccessNotification?: boolean; columns: Array | number; header: Snippet<[root: TableRootProps]>; @@ -72,90 +74,92 @@ } - - - {@render header?.(root)} - - - {@render children(root)} - - -{#if allowSelection && selectedRows.length > 0} - - - - - {selectedRows.length > 1 ? getPluralResource() : resource} - selected - +{#key computeKey} + + + {@render header?.(root)} - - - + - - -{/if} + }}>Delete + + + {/if} -{#if allowSelection} - { - disableModal = true; - onDeleteError = null; + {#if allowSelection} + { + disableModal = true; + onDeleteError = null; - const state = await onDelete?.(selectedRows); - if (typeof state === 'string') { - disableModal = false; - onDeleteError = state || `Failed to delete ${resource}s`; - } else { - notifySuccess(); - selectedRows = []; - disableModal = false; - showConfirmDeletion = false; - } - }}> - - {@const selectionCount = selectedRows.length} - {#if deleteContent} - - {@render deleteContent(selectionCount)} - {:else} - Are you sure you want to delete {selectionCount} - {selectionCount > 1 ? getPluralResource() : resource}? - {/if} - + const state = await onDelete?.(selectedRows); + if (typeof state === 'string') { + disableModal = false; + onDeleteError = state || `Failed to delete ${resource}s`; + } else { + notifySuccess(); + selectedRows = []; + disableModal = false; + showConfirmDeletion = false; + } + }}> + + {@const selectionCount = selectedRows.length} + {#if deleteContent} + + {@render deleteContent(selectionCount)} + {:else} + Are you sure you want to delete {selectionCount} + {selectionCount > 1 ? getPluralResource() : resource}? + {/if} + - - {#if deleteContentNotice} - - {@render deleteContentNotice()} - {:else} - This action is irreversible. - {/if} - - -{/if} + + {#if deleteContentNotice} + + {@render deleteContentNotice()} + {:else} + This action is irreversible. + {/if} + + + {/if} +{/key} diff --git a/src/routes/(console)/project-[region]-[project]/databases/database-[database]/backups/+page.svelte b/src/routes/(console)/project-[region]-[project]/databases/database-[database]/backups/+page.svelte index 6c6a8de18..b9c1c57c8 100644 --- a/src/routes/(console)/project-[region]-[project]/databases/database-[database]/backups/+page.svelte +++ b/src/routes/(console)/project-[region]-[project]/databases/database-[database]/backups/+page.svelte @@ -69,11 +69,11 @@ await sdk .forProject(page.params.region, page.params.project) .backups.createArchive(['databases'], data.database.$id); + await invalidate(Dependencies.BACKUPS); addNotification({ type: 'success', message: 'Database backup has started' }); - invalidate(Dependencies.BACKUPS); trackEvent('click_manual_submit'); showFeedbackNotification(); } catch (error) { @@ -86,7 +86,7 @@ } }; - const trackEvents = (policies) => { + const trackEvents = (policies: UserBackupPolicy[]) => { policies.forEach((policy) => { let actualDay = null; const monthlyBackupFrequency = policy.monthlyBackupFrequency; @@ -139,7 +139,6 @@ ? `Backup policies have been created` : `${totalPolicies[0].label} policy has been created`; - // TODO: html isn't yet supported on Toast. addNotification({ isHtml: true, type: 'success', @@ -148,7 +147,7 @@ trackEvents(totalPolicies); - invalidate(Dependencies.BACKUPS); + await invalidate(Dependencies.BACKUPS); showFeedbackNotification(); } catch (err) { addNotification({ diff --git a/src/routes/(console)/project-[region]-[project]/databases/database-[database]/backups/table.svelte b/src/routes/(console)/project-[region]-[project]/databases/database-[database]/backups/table.svelte index 689afe74f..f9f5b7d0e 100644 --- a/src/routes/(console)/project-[region]-[project]/databases/database-[database]/backups/table.svelte +++ b/src/routes/(console)/project-[region]-[project]/databases/database-[database]/backups/table.svelte @@ -1,5 +1,11 @@ - - + + {#snippet header(root)} {#each $columns as column} {column.title} {/each} - + {/snippet} - {#each data.backups.archives as backup, index} - {@const policy = policyDetails(backup.policyId)} - {@const retainedUntil = new Date( - new Date(policy?.$createdAt).getTime() + policy?.retention * 24 * 60 * 60 * 1000 - )} - {@const formattedRetainedUntil = `${retainedUntil.getDate()} ${retainedUntil.toLocaleString('en-US', { month: 'short' })}, ${retainedUntil.getFullYear()} ${retainedUntil.toLocaleTimeString('en-US', { hour12: false })}`} - - - - {cleanBackupName(backup)} - - - - {#if backup.status === 'completed'} - {calculateSize(backup.size)} - {:else} - - - {/if} - - - {@const backupStatus = getBackupStatus(backup)} - - - - - - -
- - - {policy?.name || 'Manual'} - - {policy - ? `Retained until: ${formattedRetainedUntil}` - : `Retained forever`} - -
-
- -
- - - - - {#if backup.status === 'completed'} + {#snippet children(root)} + {#each data.backups.archives as backup, index} + {@const policy = getPolicyDetails(backup.policyId)} + {@const retainedUntil = new Date( + new Date(policy?.$createdAt).getTime() + policy?.retention * 24 * 60 * 60 * 1000 + )} + {@const formattedRetainedUntil = `${retainedUntil.getDate()} ${retainedUntil.toLocaleString('en-US', { month: 'short' })}, ${retainedUntil.getFullYear()} ${retainedUntil.toLocaleTimeString('en-US', { hour12: false })}`} + + + + {getCleanBackupName(backup)} + + + + {#if backup.status === 'completed'} + {calculateSize(backup.size)} + {:else} + - + {/if} + + + {@const backupStatus = getBackupStatus(backup)} + + + + + + +
+ + + {policy?.name || 'Manual'} + + {policy + ? `Retained until: ${formattedRetainedUntil}` + : `Retained forever`} + +
+
+ +
+ + + + + {#if backup.status === 'completed'} + { + toggle(e); + showRestore = true; + selectedBackup = backup; + showDropdown[index] = false; + trackEvent(Click.BackupRestoreClick); + }}> + Restore + + {/if} { toggle(e); - showRestore = true; + copy(backup.$id); + showDropdown[index] = false; + trackEvent(Click.BackupCopyIdClick); + }}> + Copy ID + + { + toggle(e); + showDelete = true; selectedBackup = backup; showDropdown[index] = false; - trackEvent(Click.BackupRestoreClick); + trackEvent(Click.BackupDeleteClick); }}> - Restore + Delete - {/if} - { - toggle(e); - copy(backup.$id); - showDropdown[index] = false; - trackEvent(Click.BackupCopyIdClick); - }}> - Copy ID - - { - toggle(e); - showDelete = true; - selectedBackup = backup; - showDropdown[index] = false; - trackEvent(Click.BackupDeleteClick); - }}> - Delete - - - - -
-
-
- {/each} - - -{#if selectedBackups.length > 0} - - - - - {selectedBackups.length > 1 ? 'backups' : 'backup'} - selected - - - - - - - -{/if} +
+
+
+
+
+
+ {/each} + {/snippet} +
+ + onSubmit={async () => { + await deleteBackups([selectedBackup.$id]); + }}> - Are you sure you want to delete - {#if selectedBackups.length} - {selectedBackups.length} {selectedBackups.length > 1 ? 'backups' : 'backup'}? - {:else} - the {cleanBackupName(selectedBackup)} backup? - {/if} -
This action is irreversible. + Are you sure you want to delete the {getCleanBackupName(selectedBackup)} backup?
+ + This action is irreversible.
- {cleanBackupName(selectedBackup)} + {getCleanBackupName(selectedBackup)} @@ -374,6 +377,6 @@ - + From ad67a511e43ff410f0aef2211d921dce6166b64a Mon Sep 17 00:00:00 2001 From: Darshan Date: Sun, 26 Oct 2025 14:15:49 +0530 Subject: [PATCH 21/52] add: `multiSelectTable` to messaging > providers. --- .../messaging/providers/table.svelte | 164 +++++++----------- 1 file changed, 60 insertions(+), 104 deletions(-) diff --git a/src/routes/(console)/project-[region]-[project]/messaging/providers/table.svelte b/src/routes/(console)/project-[region]-[project]/messaging/providers/table.svelte index b65093f6e..5f2a0ddf8 100644 --- a/src/routes/(console)/project-[region]-[project]/messaging/providers/table.svelte +++ b/src/routes/(console)/project-[region]-[project]/messaging/providers/table.svelte @@ -1,134 +1,90 @@ - - + onDelete={handleDelete} + allowSelection={$canWriteProviders}> + {#snippet header(root)} {#each $columns as { id, title }} {title} {/each} - - {#each data.providers.providers as provider (provider.$id)} - - {#each $columns as column} - - {#if column.id === '$id'} - {#key $columns} - {provider.$id} - {/key} - {:else if column.id === 'provider'} - - {:else if column.id === 'type'} - - {:else if column.id === 'enabled'} - - - {#if provider.enabled} - - {/if} - - - {:else} - {provider[column.id]} - {/if} - - {/each} - - {/each} - - -{#if selectedIds.length > 0} - - - - - {selectedIds.length > 1 ? 'providers' : 'provider'} - selected - - - - - - - -{/if} - - - - Are you sure you want to delete {selectedIds.length} - {selectedIds.length > 1 ? 'providers' : 'provider'}? - - + + {#each $columns as column} + + {#if column.id === '$id'} + {#key $columns} + {provider.$id} + {/key} + {:else if column.id === 'provider'} + + {:else if column.id === 'type'} + + {:else if column.id === 'enabled'} + + + {#if provider.enabled} + + {/if} + + + {:else} + {provider[column.id]} + {/if} + + {/each} + + {/each} + {/snippet} +
From 94ffa90cd49398e2d4204b8763824c4b10d81d94 Mon Sep 17 00:00:00 2001 From: Darshan Date: Sun, 26 Oct 2025 14:23:13 +0530 Subject: [PATCH 22/52] add: `multiSelectTable` to sites > deployments. --- .../site-[site]/deployments/table.svelte | 198 ++++++++---------- 1 file changed, 84 insertions(+), 114 deletions(-) diff --git a/src/routes/(console)/project-[region]-[project]/sites/site-[site]/deployments/table.svelte b/src/routes/(console)/project-[region]-[project]/sites/site-[site]/deployments/table.svelte index 9bb700a5a..771ca9967 100644 --- a/src/routes/(console)/project-[region]-[project]/sites/site-[site]/deployments/table.svelte +++ b/src/routes/(console)/project-[region]-[project]/sites/site-[site]/deployments/table.svelte @@ -1,5 +1,5 @@ - - + {#snippet header(root)} {#each $columns as { id, title }} {title} {/each} - - {#each data.deploymentList.deployments as deployment (deployment.$id)} - - {#each $columns as column} - - {#if column.id === '$id'} - {#key column.id} - {deployment.$id} - {/key} - {:else if column.id === 'status'} - {@const status = deployment.status} - {#if data?.activeDeployment?.$id === deployment?.$id} - - {:else} - + {/snippet} + + {#snippet children(root)} + {#each data.deploymentList.deployments as deployment (deployment.$id)} + + {#each $columns as column} + + {#if column.id === '$id'} + {#key column.id} + {deployment.$id} + {/key} + {:else if column.id === 'status'} + {@const status = deployment.status} + {#if data?.activeDeployment?.$id === deployment?.$id} + + {:else} + + {/if} + {:else if column.id === 'type'} + + {:else if column.id === '$updatedAt'} + + {:else if column.id === 'buildDuration'} + {#if ['waiting'].includes(deployment.status)} + - + {:else if ['processing', 'building'].includes(deployment.status)} + + {:else} + {formatTimeDetailed(deployment.buildDuration)} + {/if} + {:else if column.id === 'totalSize'} + {calculateSize(deployment?.totalSize ?? 0)} + {:else if column.id === 'sourceSize'} + {calculateSize(deployment.sourceSize)} + {:else if column.id === 'buildSize'} + {calculateSize(deployment.buildSize)} {/if} - {:else if column.id === 'type'} - - {:else if column.id === '$updatedAt'} - - {:else if column.id === 'buildDuration'} - {#if ['waiting'].includes(deployment.status)} - - - {:else if ['processing', 'building'].includes(deployment.status)} - - {:else} - {formatTimeDetailed(deployment.buildDuration)} - {/if} - {:else if column.id === 'totalSize'} - {calculateSize(deployment?.totalSize ?? 0)} - {:else if column.id === 'sourceSize'} - {calculateSize(deployment.sourceSize)} - {:else if column.id === 'buildSize'} - {calculateSize(deployment.buildSize)} - {/if} + + {/each} + + + + - {/each} - - - - - - - {/each} - + + {/each} + {/snippet} + + {#snippet deleteContent(count)} + Are you sure you want to delete {count} + {count > 1 ? 'deployments' : 'deployment'} from your site - + {page.data.site.name}? + {/snippet} +
{#if selectedDeployment} @@ -154,33 +154,3 @@ selectedDeploymentId={selectedDeployment.$id} bind:show={showActivate} /> {/if} - -{#if selectedRows.length > 0} - - - - - {selectedRows.length > 1 ? 'deployments' : 'deployment'} - selected - - - - - - - -{/if} - - -

- Are you sure you want to delete {selectedRows.length} - {selectedRows.length > 1 ? 'deployments' : 'deployment'} from your site - - {$site.name}? -

- -

This action is irreversible.

-
From 5b53e45cf2f37fa0810f5cf67b239b0c1df11eb9 Mon Sep 17 00:00:00 2001 From: Darshan Date: Sun, 26 Oct 2025 14:37:06 +0530 Subject: [PATCH 23/52] add: `multiSelectTable` to auth. add: `multiSelectTable` to messaging > topics. --- .../auth/+page.svelte | 260 ++++++++---------- .../messaging/topics/table.svelte | 135 ++++----- 2 files changed, 164 insertions(+), 231 deletions(-) diff --git a/src/routes/(console)/project-[region]-[project]/auth/+page.svelte b/src/routes/(console)/project-[region]-[project]/auth/+page.svelte index 80f6ed5cc..1423a9ed3 100644 --- a/src/routes/(console)/project-[region]-[project]/auth/+page.svelte +++ b/src/routes/(console)/project-[region]-[project]/auth/+page.svelte @@ -9,8 +9,10 @@ import { AvatarInitials, Copy, + type DeleteOperationState, Empty, EmptySearch, + MultiSelectionTable, PaginationWithLimit, SearchQuery } from '$lib/components'; @@ -20,14 +22,7 @@ import type { Models } from '@appwrite.io/console'; import { writable } from 'svelte/store'; import Create from './createUser.svelte'; - import { - Badge, - Icon, - Table, - Layout, - Typography, - FloatingActionBar - } from '@appwrite.io/pink-svelte'; + import { Badge, Icon, Table, Layout, Typography } from '@appwrite.io/pink-svelte'; import { Tag } from '@appwrite.io/pink-svelte'; import { IconDuplicate, IconPlus } from '@appwrite.io/pink-icons-svelte'; import { canWriteUsers } from '$lib/stores/roles'; @@ -37,11 +32,11 @@ import { sdk } from '$lib/stores/sdk'; import { Submit, trackError, trackEvent } from '$lib/actions/analytics'; import { Dependencies } from '$lib/constants'; - import { addNotification } from '$lib/stores/notifications'; import { invalidate } from '$app/navigation'; - import Confirm from '$lib/components/confirm.svelte'; - export let data; + import type { PageProps } from './$types'; + + let { data }: PageProps = $props(); const columns = writable([ { id: '$id', title: 'User ID', type: 'string', width: 200 }, @@ -59,44 +54,27 @@ } ]); - let selectedUsers: string[] = []; - let showDelete = false; - let deleting = false; - async function userCreated(event: CustomEvent>>) { await goto( `${base}/project-${page.params.region}-${page.params.project}/auth/user-${event.detail.$id}` ); } - async function handleDelete() { - showDelete = false; - deleting = true; - - const promises = selectedUsers.map((userId) => - sdk.forProject(page.params.region, page.params.project).users.delete(userId) - ); + async function handleDelete(selectedRows: string[]): Promise { + const promises = selectedRows.map((userId) => { + return sdk.forProject(page.params.region, page.params.project).users.delete({ userId }); + }); try { await Promise.all(promises); - trackEvent(Submit.UserDelete, { - total: selectedUsers.length - }); - addNotification({ - type: 'success', - message: `${selectedUsers.length} user${selectedUsers.length > 1 ? 's' : ''} deleted` - }); - invalidate(Dependencies.USERS); + + trackEvent(Submit.UserDelete, { total: selectedRows.length }); + return true; } catch (error) { - addNotification({ - type: 'error', - message: error.message - }); trackError(error, Submit.UserDelete); + return error.message; } finally { - selectedUsers = []; - showDelete = false; - deleting = false; + await invalidate(Dependencies.USERS); } } @@ -116,101 +94,111 @@ {#if data.users.total} - - + onDelete={handleDelete} + allowSelection={$canWriteUsers}> + {#snippet header(root)} {#each $columns as { id, title } (id)} {title} {/each} - - {#each data.users.users as user} - - {#each $columns as { id } (id)} - - {#if id === '$id'} - - - - {user.$id} - - - {:else if id === 'name'} - - {#if user.email || user.phone} - {#if user.name} - + {/snippet} + + {#snippet children(root)} + {#each data.users.users as user} + + {#each $columns as { id } (id)} + + {#if id === '$id'} + + + + {user.$id} + + + {:else if id === 'name'} + + {#if user.email || user.phone} + {#if user.name} + + + {user.name} + + {:else} +
+ +
+ {/if} + {:else} +
+ +
{user.name} - {:else} -
- -
{/if} +
+ {:else if id === 'identifiers'} + + {user.email && user.phone + ? [user.email, user.phone].join(',') + : user.email || user.phone} + + {:else if id === 'status'} + {#if user.status} + {@const success = + user.emailVerification || user.phoneVerification} + {:else} -
- -
- - {user.name} - + + {/if} + {:else if id === 'labels'} + + {user.labels.join(', ')} + + {:else if id === 'joined'} + + {:else if id === 'lastActivity'} + {#if user.accessedAt} + + {:else} + never {/if} -
- {:else if id === 'identifiers'} - - {user.email && user.phone - ? [user.email, user.phone].join(',') - : user.email || user.phone} - - {:else if id === 'status'} - {#if user.status} - {@const success = - user.emailVerification || user.phoneVerification} - {:else} - + {user[id]} {/if} - {:else if id === 'labels'} - - {user.labels.join(', ')} - - {:else if id === 'joined'} - - {:else if id === 'lastActivity'} - {#if user.accessedAt} - - {:else} - never - {/if} - {:else} - {user[id]} - {/if} -
- {/each} -
- {/each} -
+ + {/each} + + {/each} + {/snippet} + + {#snippet deleteContentNotice()} + This action is irreversible and will permanently remove the selected users and all + their data. + {/snippet} + + secondary + href={`${base}/project-${page.params.region}-${page.params.project}/auth`} + >Clear Search {:else} showCreateUser.set(true)} /> {/if} - - {#if selectedUsers.length > 0} - - - - - {selectedUsers.length > 1 ? 'users' : 'user'} - selected - - - - - - - - {/if} - - - - Are you sure you want to delete {selectedUsers.length} - {selectedUsers.length > 1 ? 'users' : 'user'}? - - - This action is irreversible and will permanently remove the selected users and all their - data. - - diff --git a/src/routes/(console)/project-[region]-[project]/messaging/topics/table.svelte b/src/routes/(console)/project-[region]-[project]/messaging/topics/table.svelte index b4d653169..4dde3847e 100644 --- a/src/routes/(console)/project-[region]-[project]/messaging/topics/table.svelte +++ b/src/routes/(console)/project-[region]-[project]/messaging/topics/table.svelte @@ -1,9 +1,7 @@ - - + + {#snippet header(root)} {#each columns as { id, title }} {title} {/each} - - {#each data.topics.topics as topic (topic.$id)} - - {#each columns as column (column.id)} - - {#if column.id === '$id'} - {#key column.id} - {topic.$id} - {/key} - {:else if column.type === 'datetime'} - {#if topic[column.id]} - - {:else}-{/if} - {:else if column.id === 'total'} - {topic.emailTotal + topic.smsTotal + topic.pushTotal} - {:else} - {topic[column.id]} - {/if} - - {/each} - - {/each} - + {/snippet} -{#if selectedIds.length > 0} - - - - - {selectedIds.length > 1 ? 'topics' : 'topic'} - selected - - - - - - - -{/if} - - - - Are you sure you want to delete {selectedIds.length} - {selectedIds.length > 1 ? 'topics' : 'topic'}? - - + {#snippet children(root)} + {#each data.topics.topics as topic (topic.$id)} + + {#each columns as column (column.id)} + + {#if column.id === '$id'} + {#key column.id} + {topic.$id} + {/key} + {:else if column.type === 'datetime'} + {#if topic[column.id]} + + {:else}-{/if} + {:else if column.id === 'total'} + {topic.emailTotal + topic.smsTotal + topic.pushTotal} + {:else} + {topic[column.id]} + {/if} + + {/each} + + {/each} + {/snippet} + From 877c73d8e2ac830d0278e4dd93aa4f4a2248cbf8 Mon Sep 17 00:00:00 2001 From: Darshan Date: Sun, 26 Oct 2025 14:44:16 +0530 Subject: [PATCH 24/52] add: `multiSelectTable` to auth > teams. --- .../auth/teams/+page.svelte | 155 +++++++----------- 1 file changed, 58 insertions(+), 97 deletions(-) diff --git a/src/routes/(console)/project-[region]-[project]/auth/teams/+page.svelte b/src/routes/(console)/project-[region]-[project]/auth/teams/+page.svelte index 3a64c2500..05a321591 100644 --- a/src/routes/(console)/project-[region]-[project]/auth/teams/+page.svelte +++ b/src/routes/(console)/project-[region]-[project]/auth/teams/+page.svelte @@ -10,7 +10,9 @@ EmptySearch, AvatarInitials, SearchQuery, - PaginationWithLimit + PaginationWithLimit, + type DeleteOperationState, + MultiSelectionTable } from '$lib/components'; import Create from '../createTeam.svelte'; import { goto } from '$app/navigation'; @@ -19,31 +21,16 @@ import type { Models } from '@appwrite.io/console'; import { writable } from 'svelte/store'; import { canWriteTeams } from '$lib/stores/roles'; - import { - Icon, - Layout, - Table, - FloatingActionBar, - Badge, - Typography - } from '@appwrite.io/pink-svelte'; + import { Icon, Layout, Table } from '@appwrite.io/pink-svelte'; import { IconPlus } from '@appwrite.io/pink-icons-svelte'; import DualTimeView from '$lib/components/dualTimeView.svelte'; import { sdk } from '$lib/stores/sdk'; import { Submit, trackError, trackEvent } from '$lib/actions/analytics'; import { Dependencies } from '$lib/constants'; - import { addNotification } from '$lib/stores/notifications'; import { invalidate } from '$app/navigation'; - import Confirm from '$lib/components/confirm.svelte'; + import type { PageProps } from './$types'; - export let data; - - const region = page.params.region; - const project = page.params.project; - - let selectedTeams: string[] = []; - let showDelete = false; - let deleting = false; + let { data }: PageProps = $props(); const columns = writable([ { id: 'name', title: 'Name', type: 'string', width: { min: 200, max: 300 } }, @@ -52,37 +39,26 @@ ]); const teamCreated = async (event: CustomEvent>>) => { - await goto(`${base}/project-${region}-${project}/auth/teams/team-${event.detail.$id}`); + await goto( + `${base}/project-${page.params.region}-${page.params.project}/auth/teams/team-${event.detail.$id}` + ); }; - async function handleDelete() { - showDelete = false; - deleting = true; - - const promises = selectedTeams.map((teamId) => - sdk.forProject(page.params.region, page.params.project).teams.delete(teamId) - ); + async function handleDelete(selectedRows: string[]): Promise { + const promises = selectedRows.map((teamId) => { + return sdk.forProject(page.params.region, page.params.project).teams.delete({ teamId }); + }); try { await Promise.all(promises); - trackEvent(Submit.TeamDelete, { - total: selectedTeams.length - }); - addNotification({ - type: 'success', - message: `${selectedTeams.length} team${selectedTeams.length > 1 ? 's' : ''} deleted` - }); - invalidate(Dependencies.TEAMS); + + trackEvent(Submit.TeamDelete, { total: selectedRows.length }); + return true; } catch (error) { - addNotification({ - type: 'error', - message: error.message - }); trackError(error, Submit.TeamDelete); + return error.message; } finally { - selectedTeams = []; - showDelete = false; - deleting = false; + await invalidate(Dependencies.TEAMS); } } @@ -101,54 +77,47 @@ {#if data.teams.total} - - + onDelete={handleDelete} + allowSelection={$canWriteTeams}> + {#snippet header(root)} {#each $columns as { id, title }} {title} {/each} - - {#each data.teams.teams as team (team.$id)} - - {#each $columns as column} - - {#if column.id === 'name'} - - - {team.name} - - {:else if column.id === 'members'} - {team.total} members - {:else if column.id === 'created'} - - {/if} - - {/each} - - {/each} - + {/snippet} - {#if selectedTeams.length > 0} - - - - - {selectedTeams.length > 1 ? 'teams' : 'team'} - selected - - - - - - - - {/if} + {#snippet children(root)} + {@const TableRowComponent = $canWriteTeams ? Table.Row.Link : Table.Row.Base} + {#each data.teams.teams as team (team.$id)} + {@const href = $canWriteTeams + ? `${base}/project-${page.params.region}-${page.params.project}/auth/teams/team-${team.$id}` + : undefined} + + {#each $columns as column} + + {#if column.id === 'name'} + + + {team.name} + + {:else if column.id === 'members'} + {team.total} members + {:else if column.id === 'created'} + + {/if} + + {/each} + + {/each} + {/snippet} + + {#snippet deleteContentNotice()} + This action is irreversible and will permanently remove the selected teams and all + their memberships. + {/snippet} + {:else if data.search} - @@ -172,14 +144,3 @@ - - - - Are you sure you want to delete {selectedTeams.length} - {selectedTeams.length > 1 ? 'teams' : 'team'}? - - - This action is irreversible and will permanently remove the selected teams and all their - memberships. - - From 1b911b169d673d755a1ae72447afe772051d34f4 Mon Sep 17 00:00:00 2001 From: Darshan Date: Sun, 26 Oct 2025 14:49:07 +0530 Subject: [PATCH 25/52] add: `multiSelectTable` to auth > user > memberships. --- .../auth/user-[user]/memberships/+page.svelte | 174 +++++++----------- 1 file changed, 63 insertions(+), 111 deletions(-) diff --git a/src/routes/(console)/project-[region]-[project]/auth/user-[user]/memberships/+page.svelte b/src/routes/(console)/project-[region]-[project]/auth/user-[user]/memberships/+page.svelte index 0deaed8da..a78be3f4f 100644 --- a/src/routes/(console)/project-[region]-[project]/auth/user-[user]/memberships/+page.svelte +++ b/src/routes/(console)/project-[region]-[project]/auth/user-[user]/memberships/+page.svelte @@ -1,50 +1,36 @@ {#if data.memberships.total} - - + {#snippet header(root)} Name Roles Joined - - {#each data.memberships.memberships as membership} - - - - - {membership.teamName ? membership.teamName : 'n/a'} - - - - {membership.roles} - - - - - - - - - {/each} - + {/snippet} + + {#snippet children(root)} + {#each data.memberships.memberships as membership} + + + + + {membership.teamName ? membership.teamName : 'n/a'} + + + + {membership.roles} + + + + + + + + + {/each} + {/snippet} + + {#snippet deleteContentNotice()} + This action is irreversible and will remove the user from the selected teams. + {/snippet} + {:else} {/if} - - {#if selectedMemberships.length > 0} - - - - - {selectedMemberships.length > 1 ? 'memberships' : 'membership'} - selected - - - - - - - - {/if} - - - - Are you sure you want to delete {selectedMemberships.length} - {selectedMemberships.length > 1 ? 'memberships' : 'membership'}? - - - This action is irreversible and will remove the user from the selected teams. - - From 06eb186c1b9e6a522e1b62a368bb7f720a1ab527 Mon Sep 17 00:00:00 2001 From: Darshan Date: Sun, 26 Oct 2025 14:59:39 +0530 Subject: [PATCH 26/52] add: `multiSelectTable` to messaging > topics > topic. --- .../topics/topic-[topic]/table.svelte | 164 +++++++----------- 1 file changed, 63 insertions(+), 101 deletions(-) diff --git a/src/routes/(console)/project-[region]-[project]/messaging/topics/topic-[topic]/table.svelte b/src/routes/(console)/project-[region]-[project]/messaging/topics/topic-[topic]/table.svelte index a42fcfd07..c062b6dce 100644 --- a/src/routes/(console)/project-[region]-[project]/messaging/topics/topic-[topic]/table.svelte +++ b/src/routes/(console)/project-[region]-[project]/messaging/topics/topic-[topic]/table.svelte @@ -2,10 +2,8 @@ import { invalidate } from '$app/navigation'; import { base } from '$app/paths'; import { Submit, trackError, trackEvent } from '$lib/actions/analytics'; - import { Id } from '$lib/components'; + import { type DeleteOperationState, Id, MultiSelectionTable } from '$lib/components'; import { Dependencies } from '$lib/constants'; - import { Button } from '$lib/elements/forms'; - import { addNotification } from '$lib/stores/notifications'; import type { PageData } from './$types'; import ProviderType from '../../providerType.svelte'; import DualTimeView from '$lib/components/dualTimeView.svelte'; @@ -15,21 +13,26 @@ import { targetsById } from '../../store'; import { MessagingProviderType, type Models } from '@appwrite.io/console'; import type { Column } from '$lib/helpers/types'; - import { Badge, FloatingActionBar, Table, Typography } from '@appwrite.io/pink-svelte'; - import Confirm from '$lib/components/confirm.svelte'; + import { Table } from '@appwrite.io/pink-svelte'; - export let columns: Column[]; - export let data: PageData; + let { + data, + columns + }: { + data: PageData; + columns: Column[]; + } = $props(); - let subscribers: Record = {}; - let selectedIds: string[] = []; - let selected: Record = {}; - let showDelete = false; - let deleting = false; + const subscribers = $derived.by(() => { + const record: Record = {}; + for (const subscriber of data.subscribers.subscribers) { + record[subscriber.$id] = subscriber; + } - async function handleDelete() { - showDelete = false; + return record; + }); + async function handleDelete(selectedRows: string[]): Promise { async function deleteSubscriber(subscriberId: string) { await sdk .forProject(page.params.region, page.params.project) @@ -37,111 +40,70 @@ topicId: page.params.topic, subscriberId }); + const { target } = subscribers[subscriberId]; const { [target.$id]: _, ...rest } = $targetsById; $targetsById = rest; } - const promises = selectedIds.map((id) => deleteSubscriber(id)); + const promises = selectedRows.map((id) => deleteSubscriber(id)); try { await Promise.all(promises); - trackEvent(Submit.MessagingTopicSubscriberDelete, { - total: selectedIds.length - }); - addNotification({ - type: 'success', - message: `${selectedIds.length} subscriber${ - selectedIds.length > 1 ? 's' : '' - } deleted` - }); - invalidate(Dependencies.MESSAGING_TOPIC_SUBSCRIBERS); + + trackEvent(Submit.MessagingTopicSubscriberDelete, { total: selectedRows.length }); + return true; } catch (error) { - addNotification({ - type: 'error', - message: error.message - }); trackError(error, Submit.MessagingTopicSubscriberDelete); + return error.message; } finally { - selectedIds = []; - showDelete = false; + await invalidate(Dependencies.MESSAGING_TOPIC_SUBSCRIBERS); } } - - $: data.subscribers.subscribers.forEach((s) => { - subscribers[s.$id] = s; - }); - $: selectedIds.forEach((id) => { - selected[id] = subscribers[id]; - }); - - + + {#snippet header(root)} {#each columns as { id, title }} {title} {/each} - - {#each data.subscribers.subscribers as subscriber (subscriber.$id)} - {@const target = subscriber.target} - - {#each columns as column} - - {#if column.id === '$id'} - {#key column.id} - - {subscriber.$id} + {/snippet} + + {#snippet children(root)} + {#each data.subscribers.subscribers as subscriber (subscriber.$id)} + {@const target = subscriber.target} + + {#each columns as column} + + {#if column.id === '$id'} + {#key column.id} + + {subscriber.$id} + + {/key} + {:else if column.id === 'targetId'} + + {subscriber[column.id]} - {/key} - {:else if column.id === 'targetId'} - - {subscriber[column.id]} - - {:else if column.id === 'target'} - {#if target.providerType === MessagingProviderType.Push} - {target.name} + {:else if column.id === 'target'} + {#if target.providerType === MessagingProviderType.Push} + {target.name} + {:else} + {target.identifier} + {/if} + {:else if column.id === 'type'} + + {:else if column.id === '$createdAt'} + {:else} - {target.identifier} + {subscriber[column.id]} {/if} - {:else if column.id === 'type'} - - {:else if column.id === '$createdAt'} - - {:else} - {subscriber[column.id]} - {/if} - - {/each} - - {/each} - - -{#if selectedIds.length > 0} - - - - - {selectedIds.length > 1 ? 'subscribers' : 'subscriber'} - selected - - - - - - - -{/if} - - - - Are you sure you want to delete {selectedIds.length} - {selectedIds.length > 1 ? 'subscribers' : 'subscriber'}? - - + + {/each} + + {/each} + {/snippet} + From 5058d943d3f0b36d04c8129fc74c78e015c057ea Mon Sep 17 00:00:00 2001 From: Darshan Date: Sun, 26 Oct 2025 15:13:37 +0530 Subject: [PATCH 27/52] add: `multiSelectTable` to keys/devKeys > table component. --- src/lib/components/multiSelectTable.svelte | 4 +- .../overview/(components)/deleteBatch.svelte | 7 +- .../overview/(components)/table.svelte | 130 +++++++++--------- 3 files changed, 72 insertions(+), 69 deletions(-) diff --git a/src/lib/components/multiSelectTable.svelte b/src/lib/components/multiSelectTable.svelte index 7ccc0ba82..8479f72a7 100644 --- a/src/lib/components/multiSelectTable.svelte +++ b/src/lib/components/multiSelectTable.svelte @@ -66,7 +66,7 @@ // this is kept very basic! function getPluralResource() { - if (resource.endsWith('y')) { + if (resource.endsWith('ty')) { return `${resource}ies`; } @@ -118,7 +118,7 @@ {/if} - {#if allowSelection} + {#if allowSelection && confirmDeletion} - + 1 ? 's' : ''}`}>

Are you sure you want to delete {keyIds.length} {label} key{keyIds.length > 1 ? 's' : ''}? diff --git a/src/routes/(console)/project-[region]-[project]/overview/(components)/table.svelte b/src/routes/(console)/project-[region]-[project]/overview/(components)/table.svelte index 28bf1a389..008aeaa26 100644 --- a/src/routes/(console)/project-[region]-[project]/overview/(components)/table.svelte +++ b/src/routes/(console)/project-[region]-[project]/overview/(components)/table.svelte @@ -2,27 +2,32 @@ import { base } from '$app/paths'; import { page } from '$app/state'; import { goto } from '$app/navigation'; - import { Empty } from '$lib/components'; - import { Button } from '$lib/elements/forms'; + import { Empty, MultiSelectionTable } from '$lib/components'; import { canWriteKeys } from '$lib/stores/roles'; import type { Models } from '@appwrite.io/console'; import { diffDays } from '$lib/helpers/date'; import DualTimeView from '$lib/components/dualTimeView.svelte'; import { devKeyColumns, keyColumns, showDevKeysCreateModal } from '../store'; - import { Badge, FloatingActionBar, Layout, Table } from '@appwrite.io/pink-svelte'; + import { Badge, Layout, Table } from '@appwrite.io/pink-svelte'; import DeleteBatch from './deleteBatch.svelte'; import { capitalize } from '$lib/helpers/string'; import { getEffectiveScopes } from '../api-keys/scopes.svelte'; - export let keyType: 'api' | 'dev' = 'api'; - export let keys: Models.KeyList | Models.DevKeyList; + let { + keyType = 'api', + keys + }: { + keyType: 'api' | 'dev'; + keys: Models.KeyList | Models.DevKeyList; + } = $props(); - let showDeleteModal = false; - let selectedRows: string[] = []; + let selectedKeys = $state([]); + let showDeleteModal = $state(false); const isApiKey = keyType === 'api'; const label = isApiKey ? 'API' : 'dev'; const slug = isApiKey ? 'api-keys' : 'dev-keys'; + const columns = isApiKey ? $keyColumns : $devKeyColumns; function getApiKeyScopeCount(key: Models.Key | Models.DevKey) { const apiKey = key as Models.Key; @@ -53,55 +58,65 @@ else return 'Dev keys allow bypassing rate limits and CORS errors in your development environment.'; } - - const columns = isApiKey ? $keyColumns : $devKeyColumns; {#if keys.total} - - + { + showDeleteModal = true; + selectedKeys = selectedRows; + }}> + {#snippet header(root)} {#each columns as column} {column.title} {/each} - - {#each getKeys() as key (key.$id)} - - - {key.name} - - - {#if key.accessedAt} - - {:else} - never - {/if} - - - {@const expiration = getExpiryDetails(key)} - - {#if key.expire} - + {/snippet} + + {#snippet children(root)} + {#each getKeys() as key (key.$id)} + + + {key.name} + + + {#if key.accessedAt} + {:else} never {/if} - - {#if expiration.status} - - {/if} - - - {#if isApiKey} - - {getApiKeyScopeCount(key)} Scopes - {/if} - - {/each} - + + {@const expiration = getExpiryDetails(key)} + + {#if key.expire} + + {:else} + never + {/if} + + {#if expiration.status} + + {/if} + + + {#if isApiKey} + + {getApiKeyScopeCount(key)} Scopes + + {/if} + + {/each} + {/snippet} + {:else} { + on:click={async () => { if (isApiKey) { - goto( + await goto( `${base}/project-${page.params.region}-${page.params.project}/overview/${slug}/create` ); } else { @@ -120,21 +135,4 @@ }} /> {/if} -{#if selectedRows.length > 0} - - - - - {capitalize(label)} - {selectedRows.length > 1 ? 'keys' : 'key'} - selected - - - - - - - -{/if} - - + From d9d27614719f0038a7f39fb599af261b61cf4fe4 Mon Sep 17 00:00:00 2001 From: Darshan Date: Sun, 26 Oct 2025 15:22:04 +0530 Subject: [PATCH 28/52] add: `multiSelectTable` to storage > bucket. --- .../storage/bucket-[bucket]/+page.svelte | 282 ++++++++---------- 1 file changed, 121 insertions(+), 161 deletions(-) diff --git a/src/routes/(console)/project-[region]-[project]/storage/bucket-[bucket]/+page.svelte b/src/routes/(console)/project-[region]-[project]/storage/bucket-[bucket]/+page.svelte index a4ae7b082..d3a08759b 100644 --- a/src/routes/(console)/project-[region]-[project]/storage/bucket-[bucket]/+page.svelte +++ b/src/routes/(console)/project-[region]-[project]/storage/bucket-[bucket]/+page.svelte @@ -3,28 +3,25 @@ import { base } from '$app/paths'; import { page } from '$app/state'; import { Submit, trackError, trackEvent } from '$lib/actions/analytics'; - import { Avatar, Empty, EmptySearch, PaginationWithLimit, SearchQuery } from '$lib/components'; + import { + Avatar, + type DeleteOperationState, + Empty, + EmptySearch, + MultiSelectionTable, + PaginationWithLimit, + SearchQuery + } from '$lib/components'; import { Dependencies } from '$lib/constants'; import { Badge } from '@appwrite.io/pink-svelte'; import { Button } from '$lib/elements/forms'; import { calculateSize } from '$lib/helpers/sizeConvertion'; import { Container } from '$lib/layout'; import { ImageFormat, type Models } from '@appwrite.io/console'; - import { addNotification } from '$lib/stores/notifications'; import { uploader } from '$lib/stores/uploader'; import { sdk } from '$lib/stores/sdk.js'; import DeleteFile from './deleteFile.svelte'; - import { bucket } from './store'; - import Confirm from '$lib/components/confirm.svelte'; - import { - Layout, - Table, - Icon, - Popover, - ActionMenu, - FloatingActionBar, - Typography - } from '@appwrite.io/pink-svelte'; + import { Layout, Table, Icon, Popover, ActionMenu } from '@appwrite.io/pink-svelte'; import { onMount } from 'svelte'; import DualTimeView from '$lib/components/dualTimeView.svelte'; import { @@ -34,23 +31,20 @@ IconTrash } from '@appwrite.io/pink-icons-svelte'; import { isSmallViewport } from '$lib/stores/viewport'; + import type { PageProps } from './$types'; - export let data; + const { data }: PageProps = $props(); - let showDelete = false; - let selectedFile: Models.File = null; - let selectedFiles: string[] = []; - let showBulkDelete = false; - let deleting = false; - - const bucketId = page.params.bucket; + let showDelete = $state(false); + let isUploading = $state(false); + let selectedFile: Models.File | null = $state(null); function getPreview(fileId: string) { return ( sdk .forProject(page.params.region, page.params.project) .storage.getFilePreview({ - bucketId, + bucketId: page.params.bucket, fileId, height: 128, width: 128, @@ -71,42 +65,25 @@ showDelete = true; } - async function handleBulkDelete() { - showBulkDelete = false; - deleting = true; - - const promises = selectedFiles.map((fileId) => - sdk.forProject(page.params.region, page.params.project).storage.deleteFile({ - bucketId, + async function handleBulkDelete(selectedRows: string[]): Promise { + const promises = selectedRows.map((fileId) => { + return sdk.forProject(page.params.region, page.params.project).storage.deleteFile({ + bucketId: page.params.bucket, fileId - }) - ); + }); + }); try { await Promise.all(promises); - trackEvent(Submit.FileDelete, { - total: selectedFiles.length - }); - addNotification({ - type: 'success', - message: `${selectedFiles.length} file${selectedFiles.length > 1 ? 's' : ''} deleted` - }); - invalidate(Dependencies.FILES); + trackEvent(Submit.FileDelete, { total: selectedRows.length }); } catch (error) { - addNotification({ - type: 'error', - message: error.message - }); trackError(error, Submit.FileDelete); + return error.message; } finally { - selectedFiles = []; - showBulkDelete = false; - deleting = false; + await invalidate(Dependencies.FILES); } } - let isUploading = false; - const beforeunload = (event: BeforeUnloadEvent) => { // legacy browser **may** support showing a custom message. const message = 'An upload is in progress. Are you sure you want to leave?'; @@ -147,10 +124,10 @@ {#if data.files.total} - - + {#snippet header(root)} Filename Type Size Created - - {#each data.files.files as file} - {#if file.chunksTotal / file.chunksUploaded !== 1} - - - - - {file.name} -

- + {/snippet} + + {#snippet children(root)} + {#each data.files.files as file} + {#if file.chunksTotal / file.chunksUploaded !== 1} + + + + + {file.name} +
+ +
+
+
+ {file.mimeType} + + {calculateSize(file.sizeOriginal)} + + + + + +
+
- -
- {file.mimeType} - - {calculateSize(file.sizeOriginal)} - - - - - -
- -
-
-
- {:else} - {@const href = `${base}/project-${page.params.region}-${page.params.project}/storage/bucket-${bucketId}/file-${file.$id}`} - - -
- - {file.name} -
-
- {file.mimeType} - - {calculateSize(file.sizeOriginal)} - - - - - - - - - - Update - - + + {:else} + {@const href = `${base}/project-${page.params.region}-${page.params.project}/storage/bucket-${page.params.bucket}/file-${file.$id}`} + + +
+ + {file.name} +
+
+ {file.mimeType} + + {calculateSize(file.sizeOriginal)} + + + + + + + + + + Update + + { + e.stopPropagation(); + e.preventDefault(); + selectedFile = file; + showDelete = true; + }}> + Delete + + + + +
+ {/if} + {/each} + {/snippet} + + {#snippet deleteContentNotice()} + This action is irreversible and will permanently remove the selected files. + {/snippet} + + href={`${base}/project-${page.params.region}-${page.params.project}/storage/bucket-${page.params.bucket}`}> Clear Search @@ -268,39 +258,9 @@ allowCreate on:click={() => goto( - `${base}/project-${page.params.region}-${page.params.project}/storage/bucket-${bucketId}/create` + `${base}/project-${page.params.region}-${page.params.project}/storage/bucket-${page.params.bucket}/create` )} /> {/if} - - {#if selectedFiles.length > 0} - - - - - {selectedFiles.length > 1 ? 'files' : 'file'} - selected - - - - - - - - {/if} - - - - Are you sure you want to delete {selectedFiles.length} - {selectedFiles.length > 1 ? 'files' : 'file'} from {$bucket?.name}? - - - This action is irreversible and will permanently remove the selected files. - - From 930dd963dc27863a2eb1887db43af1be7d0dae87 Mon Sep 17 00:00:00 2001 From: Darshan Date: Sun, 26 Oct 2025 15:31:09 +0530 Subject: [PATCH 29/52] update: simplify type. --- src/lib/components/multiSelectTable.svelte | 9 ++++----- .../project-[region]-[project]/auth/+page.svelte | 4 +--- .../project-[region]-[project]/auth/teams/+page.svelte | 4 +--- .../auth/teams/team-[team]/members/+page.svelte | 7 +++---- .../auth/user-[user]/identities/table.svelte | 7 +++---- .../auth/user-[user]/memberships/+page.svelte | 4 +--- .../auth/user-[user]/targets/table.svelte | 7 +++---- .../databases/database-[database]/backups/table.svelte | 5 +++-- .../databases/database-[database]/table.svelte | 9 ++++----- .../function-[function]/executions/table.svelte | 7 +++---- .../functions/function-[function]/table.svelte | 7 +++---- .../project-[region]-[project]/messaging/+page.svelte | 7 +++---- .../messaging/providers/table.svelte | 7 +++---- .../messaging/topics/table.svelte | 4 +--- .../messaging/topics/topic-[topic]/table.svelte | 4 +--- .../sites/site-[site]/deployments/table.svelte | 4 +--- .../sites/site-[site]/logs/table.svelte | 7 +++---- .../storage/bucket-[bucket]/+page.svelte | 2 +- 18 files changed, 42 insertions(+), 63 deletions(-) diff --git a/src/lib/components/multiSelectTable.svelte b/src/lib/components/multiSelectTable.svelte index 8479f72a7..190cbfab8 100644 --- a/src/lib/components/multiSelectTable.svelte +++ b/src/lib/components/multiSelectTable.svelte @@ -1,6 +1,5 @@ diff --git a/src/routes/(console)/project-[region]-[project]/auth/user-[user]/identities/table.svelte b/src/routes/(console)/project-[region]-[project]/auth/user-[user]/identities/table.svelte index 66dbd6893..5d9e956af 100644 --- a/src/routes/(console)/project-[region]-[project]/auth/user-[user]/identities/table.svelte +++ b/src/routes/(console)/project-[region]-[project]/auth/user-[user]/identities/table.svelte @@ -30,13 +30,12 @@ try { await Promise.all(promises); - await invalidate(Dependencies.USER_IDENTITIES); - trackEvent(Submit.UserIdentityDelete, { total: selectedRows.length }); - return true; } catch (error) { trackError(error, Submit.UserIdentityDelete); - return error.message; + return error; + } finally { + await invalidate(Dependencies.USER_IDENTITIES); } } diff --git a/src/routes/(console)/project-[region]-[project]/auth/user-[user]/memberships/+page.svelte b/src/routes/(console)/project-[region]-[project]/auth/user-[user]/memberships/+page.svelte index a78be3f4f..6dd989a88 100644 --- a/src/routes/(console)/project-[region]-[project]/auth/user-[user]/memberships/+page.svelte +++ b/src/routes/(console)/project-[region]-[project]/auth/user-[user]/memberships/+page.svelte @@ -39,12 +39,10 @@ try { await Promise.all(promises); - trackEvent(Submit.MembershipUpdate, { total: selectedRows.length }); - return true; } catch (error) { trackError(error, Submit.MembershipUpdate); - return error.message; + return error; } finally { await invalidate(Dependencies.MEMBERSHIPS); } diff --git a/src/routes/(console)/project-[region]-[project]/auth/user-[user]/targets/table.svelte b/src/routes/(console)/project-[region]-[project]/auth/user-[user]/targets/table.svelte index f06ed2ab4..e71859a43 100644 --- a/src/routes/(console)/project-[region]-[project]/auth/user-[user]/targets/table.svelte +++ b/src/routes/(console)/project-[region]-[project]/auth/user-[user]/targets/table.svelte @@ -28,13 +28,12 @@ try { await Promise.all(promises); - await invalidate(Dependencies.USER_TARGETS); - trackEvent(Submit.UserTargetDelete, { total: selectedRows.length }); - return true; } catch (error) { trackError(error, Submit.UserTargetDelete); - return error.message; + return error; + } finally { + await invalidate(Dependencies.USER_TARGETS); } } diff --git a/src/routes/(console)/project-[region]-[project]/databases/database-[database]/backups/table.svelte b/src/routes/(console)/project-[region]-[project]/databases/database-[database]/backups/table.svelte index f9f5b7d0e..fc1fcca7e 100644 --- a/src/routes/(console)/project-[region]-[project]/databases/database-[database]/backups/table.svelte +++ b/src/routes/(console)/project-[region]-[project]/databases/database-[database]/backups/table.svelte @@ -116,7 +116,6 @@ try { await Promise.all(promises); - await invalidate(Dependencies.BACKUPS); if (selectedBackup) { addNotification({ @@ -131,13 +130,15 @@ message: error.message }); } else { - return error.message; + return error; } } finally { if (selectedBackup) { showDelete = false; selectedBackup = null; } + + await invalidate(Dependencies.BACKUPS); } } diff --git a/src/routes/(console)/project-[region]-[project]/databases/database-[database]/table.svelte b/src/routes/(console)/project-[region]-[project]/databases/database-[database]/table.svelte index 473cc08a3..958aadec3 100644 --- a/src/routes/(console)/project-[region]-[project]/databases/database-[database]/table.svelte +++ b/src/routes/(console)/project-[region]-[project]/databases/database-[database]/table.svelte @@ -26,14 +26,13 @@ ); try { await Promise.all(promises); - await invalidate(Dependencies.TABLES); - subNavigation.update(); - trackEvent(Submit.TableDelete); - return true; } catch (error) { trackError(error, Submit.TableDelete); - return error.message; + return error; + } finally { + await invalidate(Dependencies.TABLES); + subNavigation.update(); } } diff --git a/src/routes/(console)/project-[region]-[project]/functions/function-[function]/executions/table.svelte b/src/routes/(console)/project-[region]-[project]/functions/function-[function]/executions/table.svelte index 5205f9f20..4d5c2d06f 100644 --- a/src/routes/(console)/project-[region]-[project]/functions/function-[function]/executions/table.svelte +++ b/src/routes/(console)/project-[region]-[project]/functions/function-[function]/executions/table.svelte @@ -39,13 +39,12 @@ try { await Promise.all(promises); - await invalidate(Dependencies.EXECUTIONS); - trackEvent(Submit.ExecutionDelete); - return true; } catch (error) { trackError(error, Submit.ExecutionDelete); - return error.message; + return error; + } finally { + await invalidate(Dependencies.EXECUTIONS); } } diff --git a/src/routes/(console)/project-[region]-[project]/functions/function-[function]/table.svelte b/src/routes/(console)/project-[region]-[project]/functions/function-[function]/table.svelte index 65e10e1f3..c4c1d1743 100644 --- a/src/routes/(console)/project-[region]-[project]/functions/function-[function]/table.svelte +++ b/src/routes/(console)/project-[region]-[project]/functions/function-[function]/table.svelte @@ -59,13 +59,12 @@ ); try { await Promise.all(promises); - await invalidate(Dependencies.DEPLOYMENTS); - trackEvent(Submit.DeploymentDelete); - return true; } catch (error) { trackError(error, Submit.DeploymentDelete); - return error.message; + return error; + } finally { + await invalidate(Dependencies.DEPLOYMENTS); } } diff --git a/src/routes/(console)/project-[region]-[project]/messaging/+page.svelte b/src/routes/(console)/project-[region]-[project]/messaging/+page.svelte index 2e02d5e50..6d1faed8c 100644 --- a/src/routes/(console)/project-[region]-[project]/messaging/+page.svelte +++ b/src/routes/(console)/project-[region]-[project]/messaging/+page.svelte @@ -109,13 +109,12 @@ try { await Promise.all(promises); - await invalidate(Dependencies.MESSAGING_MESSAGES); - trackEvent(Submit.MessagingMessageDelete, { total: selected.length }); - return true; } catch (error) { trackError(error, Submit.MessagingMessageDelete); - return error.message; + return error; + } finally { + await invalidate(Dependencies.MESSAGING_MESSAGES); } } diff --git a/src/routes/(console)/project-[region]-[project]/messaging/providers/table.svelte b/src/routes/(console)/project-[region]-[project]/messaging/providers/table.svelte index 5f2a0ddf8..a54178b3d 100644 --- a/src/routes/(console)/project-[region]-[project]/messaging/providers/table.svelte +++ b/src/routes/(console)/project-[region]-[project]/messaging/providers/table.svelte @@ -29,13 +29,12 @@ try { await Promise.all(promises); - await invalidate(Dependencies.MESSAGING_PROVIDERS); - trackEvent(Submit.MessagingProviderDelete, { total: selectedRows.length }); - return true; } catch (error) { trackError(error, Submit.MessagingProviderDelete); - return error.message; + return error; + } finally { + await invalidate(Dependencies.MESSAGING_PROVIDERS); } } diff --git a/src/routes/(console)/project-[region]-[project]/messaging/topics/table.svelte b/src/routes/(console)/project-[region]-[project]/messaging/topics/table.svelte index 4dde3847e..f5525754e 100644 --- a/src/routes/(console)/project-[region]-[project]/messaging/topics/table.svelte +++ b/src/routes/(console)/project-[region]-[project]/messaging/topics/table.svelte @@ -29,12 +29,10 @@ try { await Promise.all(promises); - trackEvent(Submit.MessagingTopicDelete, { total: selectedRows.length }); - return true; } catch (error) { trackError(error, Submit.MessagingTopicDelete); - return error.message; + return error; } finally { await invalidate(Dependencies.MESSAGING_TOPICS); } diff --git a/src/routes/(console)/project-[region]-[project]/messaging/topics/topic-[topic]/table.svelte b/src/routes/(console)/project-[region]-[project]/messaging/topics/topic-[topic]/table.svelte index c062b6dce..c9a64e9af 100644 --- a/src/routes/(console)/project-[region]-[project]/messaging/topics/topic-[topic]/table.svelte +++ b/src/routes/(console)/project-[region]-[project]/messaging/topics/topic-[topic]/table.svelte @@ -50,12 +50,10 @@ try { await Promise.all(promises); - trackEvent(Submit.MessagingTopicSubscriberDelete, { total: selectedRows.length }); - return true; } catch (error) { trackError(error, Submit.MessagingTopicSubscriberDelete); - return error.message; + return error; } finally { await invalidate(Dependencies.MESSAGING_TOPIC_SUBSCRIBERS); } diff --git a/src/routes/(console)/project-[region]-[project]/sites/site-[site]/deployments/table.svelte b/src/routes/(console)/project-[region]-[project]/sites/site-[site]/deployments/table.svelte index 771ca9967..7403cf07d 100644 --- a/src/routes/(console)/project-[region]-[project]/sites/site-[site]/deployments/table.svelte +++ b/src/routes/(console)/project-[region]-[project]/sites/site-[site]/deployments/table.svelte @@ -45,12 +45,10 @@ ); try { await Promise.all(promises); - trackEvent(Submit.DeploymentDelete); - return true; } catch (error) { trackError(error, Submit.DeploymentDelete); - return error.message; + return error; } finally { await Promise.all([ invalidate(Dependencies.DEPLOYMENTS), diff --git a/src/routes/(console)/project-[region]-[project]/sites/site-[site]/logs/table.svelte b/src/routes/(console)/project-[region]-[project]/sites/site-[site]/logs/table.svelte index 816d7c03f..24c6f957e 100644 --- a/src/routes/(console)/project-[region]-[project]/sites/site-[site]/logs/table.svelte +++ b/src/routes/(console)/project-[region]-[project]/sites/site-[site]/logs/table.svelte @@ -37,13 +37,12 @@ try { await Promise.all(promises); - await invalidate(Dependencies.EXECUTIONS); - trackEvent(Submit.LogDelete); - return true; } catch (error) { trackError(error, Submit.LogDelete); - return error.message; + return error; + } finally { + await invalidate(Dependencies.EXECUTIONS); } } diff --git a/src/routes/(console)/project-[region]-[project]/storage/bucket-[bucket]/+page.svelte b/src/routes/(console)/project-[region]-[project]/storage/bucket-[bucket]/+page.svelte index d3a08759b..8caf3c05a 100644 --- a/src/routes/(console)/project-[region]-[project]/storage/bucket-[bucket]/+page.svelte +++ b/src/routes/(console)/project-[region]-[project]/storage/bucket-[bucket]/+page.svelte @@ -78,7 +78,7 @@ trackEvent(Submit.FileDelete, { total: selectedRows.length }); } catch (error) { trackError(error, Submit.FileDelete); - return error.message; + return error; } finally { await invalidate(Dependencies.FILES); } From ab03ba8c985bf3b941fd6e59168c8d9b1739f6ab Mon Sep 17 00:00:00 2001 From: Darshan Date: Sun, 26 Oct 2025 15:44:17 +0530 Subject: [PATCH 30/52] fix: tests. --- .../project-[region]-[project]/auth/+page.svelte | 2 +- .../project-[region]-[project]/auth/teams/+page.svelte | 2 +- .../auth/user-[user]/memberships/+page.svelte | 3 ++- .../databases/database-[database]/table.svelte | 9 ++++++--- .../overview/(components)/table.svelte | 2 +- .../sites/site-[site]/deployments/table.svelte | 1 - .../sites/site-[site]/logs/table.svelte | 1 - 7 files changed, 11 insertions(+), 9 deletions(-) diff --git a/src/routes/(console)/project-[region]-[project]/auth/+page.svelte b/src/routes/(console)/project-[region]-[project]/auth/+page.svelte index 68344d237..391274650 100644 --- a/src/routes/(console)/project-[region]-[project]/auth/+page.svelte +++ b/src/routes/(console)/project-[region]-[project]/auth/+page.svelte @@ -1,4 +1,4 @@ - diff --git a/src/routes/(console)/project-[region]-[project]/auth/teams/+page.svelte b/src/routes/(console)/project-[region]-[project]/auth/teams/+page.svelte index 853d8515f..18b2eb2d9 100644 --- a/src/routes/(console)/project-[region]-[project]/auth/teams/+page.svelte +++ b/src/routes/(console)/project-[region]-[project]/auth/teams/+page.svelte @@ -1,4 +1,4 @@ - diff --git a/src/routes/(console)/project-[region]-[project]/auth/user-[user]/memberships/+page.svelte b/src/routes/(console)/project-[region]-[project]/auth/user-[user]/memberships/+page.svelte index 6dd989a88..0c0b5525b 100644 --- a/src/routes/(console)/project-[region]-[project]/auth/user-[user]/memberships/+page.svelte +++ b/src/routes/(console)/project-[region]-[project]/auth/user-[user]/memberships/+page.svelte @@ -89,7 +89,8 @@
- {#each [...$importItems.entries()] as [key, value] (key)} + {#each [...importItems.entries()] as [key, value] (key)}
  • @@ -259,7 +260,7 @@ There was an import issue. openDetails(value.errors)} + onclick={() => openDetails(value.errors)} >View details @@ -277,7 +278,11 @@ - + From 1d6873b8c8443cbe9d6c7320ba7530700d3e5bc9 Mon Sep 17 00:00:00 2001 From: Harsh Mahajan <127186841+HarshMN2345@users.noreply.github.com> Date: Tue, 28 Oct 2025 12:22:27 +0530 Subject: [PATCH 42/52] Update src/lib/components/csvImportBox.svelte Co-authored-by: Darshan --- src/lib/components/csvImportBox.svelte | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/src/lib/components/csvImportBox.svelte b/src/lib/components/csvImportBox.svelte index b44b9ae4c..67435f86e 100644 --- a/src/lib/components/csvImportBox.svelte +++ b/src/lib/components/csvImportBox.svelte @@ -148,10 +148,9 @@ return 30; case 'uploading': return 60; - case 'completed': - return 100; - case 'failed': - return 100; + case 'completed': + case 'failed': + return 100; default: return 30; } From 61e7f58cd2676cb9d15a921829c21f4026a50034 Mon Sep 17 00:00:00 2001 From: Harsh Mahajan <127186841+HarshMN2345@users.noreply.github.com> Date: Tue, 28 Oct 2025 12:32:47 +0530 Subject: [PATCH 43/52] fix: more permissions ui improvements --- .../components/permissions/permissions.svelte | 27 +++++-------------- src/lib/components/permissions/row.svelte | 15 +++++++---- src/lib/components/permissions/team.svelte | 2 +- src/lib/components/permissions/user.svelte | 4 +-- 4 files changed, 20 insertions(+), 28 deletions(-) diff --git a/src/lib/components/permissions/permissions.svelte b/src/lib/components/permissions/permissions.svelte index 135f1506d..46b6a9bb3 100644 --- a/src/lib/components/permissions/permissions.svelte +++ b/src/lib/components/permissions/permissions.svelte @@ -113,26 +113,12 @@ }, []); } - function sortRoles([a]: [string, Permission], [b]: [string, Permission]) { - if ((a === 'any') !== (b === 'any')) { - return a === 'any' ? -1 : 1; - } - if ((a === 'users') !== (b === 'users')) { - return a === 'users' ? -1 : 1; - } - if ((a === 'guests') !== (b === 'guests')) { - return a === 'guests' ? -1 : 1; - } - - return a.localeCompare(b); - } - const columns: PinkColumn[] = [ - { id: 'role', width: { min: 80 } }, - { id: 'create', width: { min: 80 }, hide: !withCreate }, - { id: 'read', width: { min: 80 } }, - { id: 'update', width: { min: 80 } }, - { id: 'delete', width: { min: 80 } }, + { id: 'role', width: { min: 220 } }, + { id: 'create', width: { min: 64 }, hide: !withCreate }, + { id: 'read', width: { min: 64 } }, + { id: 'update', width: { min: 64 } }, + { id: 'delete', width: { min: 64 } }, { id: 'action', width: 40 } ]; @@ -150,13 +136,14 @@ Delete - {#each [...$groups].sort(sortRoles) as [role, permission] (role)} + {#each [...$groups] as [role, permission] (role)} togglePermission(role, 'create')} /> diff --git a/src/lib/components/permissions/row.svelte b/src/lib/components/permissions/row.svelte index dc2323849..a8c6b5080 100644 --- a/src/lib/components/permissions/row.svelte +++ b/src/lib/components/permissions/row.svelte @@ -140,6 +140,9 @@ {:else}
{:else} - + Need a hand? Learn more in our {:else} - + Need a hand? Learn more in our documentation Date: Tue, 28 Oct 2025 12:57:22 +0530 Subject: [PATCH 44/52] removed reactive dependency --- src/lib/components/csvImportBox.svelte | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/src/lib/components/csvImportBox.svelte b/src/lib/components/csvImportBox.svelte index 67435f86e..9565fa2cd 100644 --- a/src/lib/components/csvImportBox.svelte +++ b/src/lib/components/csvImportBox.svelte @@ -1,4 +1,5 @@ @@ -456,7 +458,6 @@ - + {#key currentRowId} + + {/key} { if (row) { work = initWork(); - focusFirstInput(); + if (autoFocus) { + focusFirstInput(); + } } else { work = null; } diff --git a/src/routes/(console)/project-[region]-[project]/databases/database-[database]/table-[table]/spreadsheet.svelte b/src/routes/(console)/project-[region]-[project]/databases/database-[database]/table-[table]/spreadsheet.svelte index 910c55e0e..71f9bda02 100644 --- a/src/routes/(console)/project-[region]-[project]/databases/database-[database]/table-[table]/spreadsheet.svelte +++ b/src/routes/(console)/project-[region]-[project]/databases/database-[database]/table-[table]/spreadsheet.svelte @@ -542,9 +542,16 @@ } } else if (type === 'row') { if (action === 'update') { - $databaseRowSheetOptions.show = true; - $databaseRowSheetOptions.row = row; - $databaseRowSheetOptions.title = 'Update row'; + databaseRowSheetOptions.update((opts) => { + const wasOpen = opts.show; + return { + ...opts, + row, + show: true, + title: 'Update row', + autoFocus: !wasOpen, + }; + }); } if (action === 'duplicate-row') { @@ -753,11 +760,15 @@ $: rowSelection = !$spreadsheetLoading && !$paginatedRowsLoading ? true : ('disabled' as const); - $: if (!$databaseRowSheetOptions.show && previouslyFocusedElement) { - requestAnimationFrame(() => { - (previouslyFocusedElement as HTMLElement)?.focus(); - previouslyFocusedElement = null; - }); + $: if (!$databaseRowSheetOptions.show) { + $databaseRowSheetOptions.autoFocus = true; + + if (previouslyFocusedElement) { + requestAnimationFrame(() => { + (previouslyFocusedElement as HTMLElement)?.focus(); + previouslyFocusedElement = null; + }); + } } diff --git a/src/routes/(console)/project-[region]-[project]/databases/database-[database]/table-[table]/store.ts b/src/routes/(console)/project-[region]-[project]/databases/database-[database]/table-[table]/store.ts index f5679dfb6..245080513 100644 --- a/src/routes/(console)/project-[region]-[project]/databases/database-[database]/table-[table]/store.ts +++ b/src/routes/(console)/project-[region]-[project]/databases/database-[database]/table-[table]/store.ts @@ -64,12 +64,14 @@ export const databaseRowSheetOptions = writable< DatabaseSheetOptions & { row: Models.Row; rowId?: string; + autoFocus?: boolean; } >({ title: null, show: false, row: null, - rowId: null // for loading from a given id + rowId: null, // for loading from a given id + autoFocus: true }); export const databaseRelatedRowSheetOptions = writable< From c71c8d09985a0312496eb7aa191421875d0699bd Mon Sep 17 00:00:00 2001 From: Darshan Date: Tue, 28 Oct 2025 18:20:39 +0530 Subject: [PATCH 50/52] remove: excessive changes --- .../table-[table]/+layout.svelte | 3 +-- .../table-[table]/rows/edit.svelte | 8 ++------ .../table-[table]/spreadsheet.svelte | 18 ++++++------------ .../database-[database]/table-[table]/store.ts | 4 +--- 4 files changed, 10 insertions(+), 23 deletions(-) diff --git a/src/routes/(console)/project-[region]-[project]/databases/database-[database]/table-[table]/+layout.svelte b/src/routes/(console)/project-[region]-[project]/databases/database-[database]/table-[table]/+layout.svelte index 419840f2e..cfefc3a7f 100644 --- a/src/routes/(console)/project-[region]-[project]/databases/database-[database]/table-[table]/+layout.svelte +++ b/src/routes/(console)/project-[region]-[project]/databases/database-[database]/table-[table]/+layout.svelte @@ -475,8 +475,7 @@ + bind:rowId={$databaseRowSheetOptions.rowId} /> {/key} diff --git a/src/routes/(console)/project-[region]-[project]/databases/database-[database]/table-[table]/rows/edit.svelte b/src/routes/(console)/project-[region]-[project]/databases/database-[database]/table-[table]/rows/edit.svelte index 738b4fab9..03b95bea6 100644 --- a/src/routes/(console)/project-[region]-[project]/databases/database-[database]/table-[table]/rows/edit.svelte +++ b/src/routes/(console)/project-[region]-[project]/databases/database-[database]/table-[table]/rows/edit.svelte @@ -19,12 +19,10 @@ let { row = $bindable(), - rowId = $bindable(null), - autoFocus = true + rowId = $bindable(null) }: { row?: Models.Row | null; rowId?: string | null; - autoFocus?: boolean; } = $props(); let loading = $state(false); @@ -78,9 +76,7 @@ $effect(() => { if (row) { work = initWork(); - if (autoFocus) { - focusFirstInput(); - } + requestAnimationFrame(() => focusFirstInput()); } else { work = null; } diff --git a/src/routes/(console)/project-[region]-[project]/databases/database-[database]/table-[table]/spreadsheet.svelte b/src/routes/(console)/project-[region]-[project]/databases/database-[database]/table-[table]/spreadsheet.svelte index 71f9bda02..ecf308d49 100644 --- a/src/routes/(console)/project-[region]-[project]/databases/database-[database]/table-[table]/spreadsheet.svelte +++ b/src/routes/(console)/project-[region]-[project]/databases/database-[database]/table-[table]/spreadsheet.svelte @@ -543,13 +543,11 @@ } else if (type === 'row') { if (action === 'update') { databaseRowSheetOptions.update((opts) => { - const wasOpen = opts.show; return { ...opts, row, show: true, - title: 'Update row', - autoFocus: !wasOpen, + title: 'Update row' }; }); } @@ -760,15 +758,11 @@ $: rowSelection = !$spreadsheetLoading && !$paginatedRowsLoading ? true : ('disabled' as const); - $: if (!$databaseRowSheetOptions.show) { - $databaseRowSheetOptions.autoFocus = true; - - if (previouslyFocusedElement) { - requestAnimationFrame(() => { - (previouslyFocusedElement as HTMLElement)?.focus(); - previouslyFocusedElement = null; - }); - } + $: if (!$databaseRowSheetOptions.show && previouslyFocusedElement) { + requestAnimationFrame(() => { + (previouslyFocusedElement as HTMLElement)?.focus(); + previouslyFocusedElement = null; + }); } diff --git a/src/routes/(console)/project-[region]-[project]/databases/database-[database]/table-[table]/store.ts b/src/routes/(console)/project-[region]-[project]/databases/database-[database]/table-[table]/store.ts index 245080513..f5679dfb6 100644 --- a/src/routes/(console)/project-[region]-[project]/databases/database-[database]/table-[table]/store.ts +++ b/src/routes/(console)/project-[region]-[project]/databases/database-[database]/table-[table]/store.ts @@ -64,14 +64,12 @@ export const databaseRowSheetOptions = writable< DatabaseSheetOptions & { row: Models.Row; rowId?: string; - autoFocus?: boolean; } >({ title: null, show: false, row: null, - rowId: null, // for loading from a given id - autoFocus: true + rowId: null // for loading from a given id }); export const databaseRelatedRowSheetOptions = writable< From 32cba4623aed3a38cc9236bd6725c3d437b761eb Mon Sep 17 00:00:00 2001 From: Darshan Date: Tue, 28 Oct 2025 18:59:22 +0530 Subject: [PATCH 51/52] Update src/routes/(console)/project-[region]-[project]/databases/database-[database]/table-[table]/sheetOptions.svelte Co-authored-by: Torsten Dittmann --- .../database-[database]/table-[table]/sheetOptions.svelte | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/routes/(console)/project-[region]-[project]/databases/database-[database]/table-[table]/sheetOptions.svelte b/src/routes/(console)/project-[region]-[project]/databases/database-[database]/table-[table]/sheetOptions.svelte index 71563a424..798c19236 100644 --- a/src/routes/(console)/project-[region]-[project]/databases/database-[database]/table-[table]/sheetOptions.svelte +++ b/src/routes/(console)/project-[region]-[project]/databases/database-[database]/table-[table]/sheetOptions.svelte @@ -125,7 +125,7 @@ } // hide column-left and create-index for $id (first column, already indexed) - if (columnId === '$id' && ['column-left', 'create-index'].includes(item.action ?? '')) { + if (columnId === '$id' && ['column-left', 'create-index'].includes(item?.action)) { return false; } From 982daecab2a144b06fa476c294f1af15a844a395 Mon Sep 17 00:00:00 2001 From: Darshan Date: Tue, 28 Oct 2025 19:01:07 +0530 Subject: [PATCH 52/52] address comment. --- .../database-[database]/table-[table]/sheetOptions.svelte | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/routes/(console)/project-[region]-[project]/databases/database-[database]/table-[table]/sheetOptions.svelte b/src/routes/(console)/project-[region]-[project]/databases/database-[database]/table-[table]/sheetOptions.svelte index 798c19236..3fdf50d14 100644 --- a/src/routes/(console)/project-[region]-[project]/databases/database-[database]/table-[table]/sheetOptions.svelte +++ b/src/routes/(console)/project-[region]-[project]/databases/database-[database]/table-[table]/sheetOptions.svelte @@ -117,10 +117,10 @@ if (type === 'header') { if (isSequence) { - return ['sort-asc', 'sort-desc'].includes(item.action ?? ''); + return ['sort-asc', 'sort-desc'].includes(item?.action); } - if (['delete', 'update', 'duplicate-header'].includes(item.action) && isSystemColumn) { + if (['delete', 'update', 'duplicate-header'].includes(item?.action) && isSystemColumn) { return false; } @@ -130,7 +130,7 @@ } // hide sort options for relationship columns - if (isRelationship(column) && ['sort-asc', 'sort-desc'].includes(item.action ?? '')) { + if (isRelationship(column) && ['sort-asc', 'sort-desc'].includes(item?.action)) { return false; } }