From 39737943e51758d808d2499c98d6a1343cae9a94 Mon Sep 17 00:00:00 2001 From: Darshan Date: Fri, 26 Sep 2025 14:03:10 +0530 Subject: [PATCH 01/28] improve: notifications system. --- package.json | 4 ++-- pnpm-lock.yaml | 20 +++++++++---------- src/lib/layout/notifications.svelte | 5 ++++- src/lib/stores/notifications.ts | 4 +++- .../database-[database]/backups/+page.svelte | 3 ++- .../backups/components/questionIcon.svelte | 6 ++++++ 6 files changed, 27 insertions(+), 15 deletions(-) create mode 100644 src/routes/(console)/project-[region]-[project]/databases/database-[database]/backups/components/questionIcon.svelte diff --git a/package.json b/package.json index eebc54597..6412b8e09 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@f08cb74", "@appwrite.io/pink-icons": "0.25.0", - "@appwrite.io/pink-icons-svelte": "https://pkg.vc/-/@appwrite/@appwrite.io/pink-icons-svelte@077179c", + "@appwrite.io/pink-icons-svelte": "https://pkg.vc/-/@appwrite/@appwrite.io/pink-icons-svelte@8f82877", "@appwrite.io/pink-legacy": "^1.0.3", - "@appwrite.io/pink-svelte": "https://pkg.vc/-/@appwrite/@appwrite.io/pink-svelte@077179c", + "@appwrite.io/pink-svelte": "https://pkg.vc/-/@appwrite/@appwrite.io/pink-svelte@8f82877", "@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 3a4161ed8..6dc1891c4 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@077179c - version: https://pkg.vc/-/@appwrite/@appwrite.io/pink-icons-svelte@077179c(svelte@5.25.3) + specifier: https://pkg.vc/-/@appwrite/@appwrite.io/pink-icons-svelte@8f82877 + version: https://pkg.vc/-/@appwrite/@appwrite.io/pink-icons-svelte@8f82877(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@077179c - version: https://pkg.vc/-/@appwrite/@appwrite.io/pink-svelte@077179c(svelte@5.25.3) + specifier: https://pkg.vc/-/@appwrite/@appwrite.io/pink-svelte@8f82877 + version: https://pkg.vc/-/@appwrite/@appwrite.io/pink-svelte@8f82877(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@077179c': - resolution: {tarball: https://pkg.vc/-/@appwrite/@appwrite.io/pink-icons-svelte@077179c} + '@appwrite.io/pink-icons-svelte@https://pkg.vc/-/@appwrite/@appwrite.io/pink-icons-svelte@8f82877': + resolution: {tarball: https://pkg.vc/-/@appwrite/@appwrite.io/pink-icons-svelte@8f82877} 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@077179c': - resolution: {tarball: https://pkg.vc/-/@appwrite/@appwrite.io/pink-svelte@077179c} + '@appwrite.io/pink-svelte@https://pkg.vc/-/@appwrite/@appwrite.io/pink-svelte@8f82877': + resolution: {tarball: https://pkg.vc/-/@appwrite/@appwrite.io/pink-svelte@8f82877} 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@077179c(svelte@5.25.3)': + '@appwrite.io/pink-icons-svelte@https://pkg.vc/-/@appwrite/@appwrite.io/pink-icons-svelte@8f82877(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@077179c(svelte@5.25.3)': + '@appwrite.io/pink-svelte@https://pkg.vc/-/@appwrite/@appwrite.io/pink-svelte@8f82877(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/layout/notifications.svelte b/src/lib/layout/notifications.svelte index 5464edb31..b6efecd76 100644 --- a/src/lib/layout/notifications.svelte +++ b/src/lib/layout/notifications.svelte @@ -10,13 +10,16 @@ {#each $notifications as notification (notification.id)} { return { label: button.name, - onClick: button.method + onClick: button.method, + isHtml: button.isHtml }; })} on:dismiss={() => dismissNotification(notification.id)} /> diff --git a/src/lib/stores/notifications.ts b/src/lib/stores/notifications.ts index 97f6cfb36..bc78772c9 100644 --- a/src/lib/stores/notifications.ts +++ b/src/lib/stores/notifications.ts @@ -1,4 +1,5 @@ import { writable } from 'svelte/store'; +import type { ComponentType } from 'svelte'; export type Notification = { id: number; @@ -7,7 +8,7 @@ export type Notification = { timeout?: number; message: string; title?: string; - icon?: string; + icon?: ComponentType; buttons?: Buttons[]; isHtml?: boolean; }; @@ -15,6 +16,7 @@ export type Notification = { export type Buttons = { method: () => void | Promise; name: string; + isHtml?: boolean; }; let counter = 0; 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 9fb56506a..6c6a8de18 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 @@ -23,6 +23,7 @@ import { trackEvent } from '$lib/actions/analytics'; import { Layout, Typography } from '@appwrite.io/pink-svelte'; import { page } from '$app/state'; + import IconQuestionMarkCircle from './components/questionIcon.svelte'; let policyCreateError: string; let totalPolicies: UserBackupPolicy[] = []; @@ -40,7 +41,7 @@ if (parsedCounter === showOnCount || !counter) { addNotification({ type: 'info', - icon: 'question-mark-circle', + icon: IconQuestionMarkCircle, message: 'How was your experience with our new Backups feature? Give us your feedback and help us improve!', timeout: 15000, diff --git a/src/routes/(console)/project-[region]-[project]/databases/database-[database]/backups/components/questionIcon.svelte b/src/routes/(console)/project-[region]-[project]/databases/database-[database]/backups/components/questionIcon.svelte new file mode 100644 index 000000000..98e32e2bd --- /dev/null +++ b/src/routes/(console)/project-[region]-[project]/databases/database-[database]/backups/components/questionIcon.svelte @@ -0,0 +1,6 @@ + + + From 101416b19e4efc4af7dffe7cbceabd8eb9906082 Mon Sep 17 00:00:00 2001 From: Darshan Date: Fri, 26 Sep 2025 19:25:57 +0530 Subject: [PATCH 02/28] feat: index suggestions. --- package.json | 2 +- pnpm-lock.yaml | 10 +- src/lib/stores/notifications.ts | 37 ++- .../(suggestions)/empty.svelte | 39 ++- .../(suggestions)/icon/ai.svelte | 17 +- .../(suggestions)/icon/aiNotification.svelte | 5 + .../(suggestions)/indexes.svelte | 293 ++++++++++++++++++ .../(suggestions)/store.ts | 17 + .../table-[table]/+layout.svelte | 4 + .../table-[table]/indexes/createIndex.svelte | 2 +- 10 files changed, 405 insertions(+), 21 deletions(-) create mode 100644 src/routes/(console)/project-[region]-[project]/databases/database-[database]/(suggestions)/icon/aiNotification.svelte create mode 100644 src/routes/(console)/project-[region]-[project]/databases/database-[database]/(suggestions)/indexes.svelte diff --git a/package.json b/package.json index 6412b8e09..496818696 100644 --- a/package.json +++ b/package.json @@ -22,7 +22,7 @@ }, "dependencies": { "@ai-sdk/svelte": "^1.1.24", - "@appwrite.io/console": "https://pkg.pr.new/appwrite-labs/cloud/@appwrite.io/console@f08cb74", + "@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@8f82877", "@appwrite.io/pink-legacy": "^1.0.3", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 6dc1891c4..827befa27 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -12,8 +12,8 @@ importers: specifier: ^1.1.24 version: 1.1.24(svelte@5.25.3)(zod@3.24.3) '@appwrite.io/console': - specifier: https://pkg.pr.new/appwrite-labs/cloud/@appwrite.io/console@f08cb74 - version: https://pkg.pr.new/appwrite-labs/cloud/@appwrite.io/console@f08cb74 + specifier: https://pkg.pr.new/appwrite-labs/cloud/@appwrite.io/console@636ed39 + version: https://pkg.pr.new/appwrite-labs/cloud/@appwrite.io/console@636ed39 '@appwrite.io/pink-icons': specifier: 0.25.0 version: 0.25.0 @@ -260,8 +260,8 @@ packages: '@analytics/type-utils@0.6.2': resolution: {integrity: sha512-TD+xbmsBLyYy/IxFimW/YL/9L2IEnM7/EoV9Aeh56U64Ify8o27HJcKjo38XY9Tcn0uOq1AX3thkKgvtWvwFQg==} - '@appwrite.io/console@https://pkg.pr.new/appwrite-labs/cloud/@appwrite.io/console@f08cb74': - resolution: {tarball: https://pkg.pr.new/appwrite-labs/cloud/@appwrite.io/console@f08cb74} + '@appwrite.io/console@https://pkg.pr.new/appwrite-labs/cloud/@appwrite.io/console@636ed39': + resolution: {tarball: https://pkg.pr.new/appwrite-labs/cloud/@appwrite.io/console@636ed39} version: 1.10.0 '@appwrite.io/pink-icons-svelte@2.0.0-RC.1': @@ -3703,7 +3703,7 @@ snapshots: '@analytics/type-utils@0.6.2': {} - '@appwrite.io/console@https://pkg.pr.new/appwrite-labs/cloud/@appwrite.io/console@f08cb74': {} + '@appwrite.io/console@https://pkg.pr.new/appwrite-labs/cloud/@appwrite.io/console@636ed39': {} '@appwrite.io/pink-icons-svelte@2.0.0-RC.1(svelte@5.25.3)': dependencies: diff --git a/src/lib/stores/notifications.ts b/src/lib/stores/notifications.ts index bc78772c9..c250820f7 100644 --- a/src/lib/stores/notifications.ts +++ b/src/lib/stores/notifications.ts @@ -3,7 +3,7 @@ import type { ComponentType } from 'svelte'; export type Notification = { id: number; - type: 'success' | 'error' | 'info' | 'warning'; + type?: 'success' | 'error' | 'info' | 'warning'; dismissible?: boolean; timeout?: number; message: string; @@ -21,28 +21,47 @@ export type Buttons = { let counter = 0; +const activeTimeouts = new Map(); + export const notifications = writable([]); export const dismissNotification = (id: number) => { + const timeoutId = activeTimeouts.get(id); + + if (timeoutId) { + clearTimeout(timeoutId); + activeTimeouts.delete(id); + } + notifications.update((all) => all.filter((t) => t.id !== id)); }; export const dismissAllNotifications = () => { + activeTimeouts.forEach((timeoutId) => clearTimeout(timeoutId)); + activeTimeouts.clear(); + notifications.set([]); }; -export const addNotification = (notification: Omit) => { - const defaults = { +export const addNotification = (notification: Omit): number => { + const defaults: Notification = { id: counter++, type: 'info', dismissible: true, - timeout: 6000 + timeout: 6000, + ...notification }; - const n = { ...defaults, ...notification }; - notifications.update((all) => { - return [n, ...all.slice(0, 4)]; - }); + notifications.update((all) => [defaults, ...all.slice(0, 4)]); - if (n.timeout) setTimeout(() => dismissNotification(n.id), n.timeout); + if (defaults.timeout) { + const timeoutId = setTimeout(() => { + activeTimeouts.delete(defaults.id); + dismissNotification(defaults.id); + }, defaults.timeout); + + activeTimeouts.set(defaults.id, timeoutId); + } + + return defaults.id; }; diff --git a/src/routes/(console)/project-[region]-[project]/databases/database-[database]/(suggestions)/empty.svelte b/src/routes/(console)/project-[region]-[project]/databases/database-[database]/(suggestions)/empty.svelte index 38ee34a9a..7a2272d95 100644 --- a/src/routes/(console)/project-[region]-[project]/databases/database-[database]/(suggestions)/empty.svelte +++ b/src/routes/(console)/project-[region]-[project]/databases/database-[database]/(suggestions)/empty.svelte @@ -25,9 +25,10 @@ tableColumnSuggestions, basicColumnOptions, mockSuggestions, - createTableRequest + createTableRequest, + showIndexesSuggestions } from './store'; - import { addNotification } from '$lib/stores/notifications'; + import { addNotification, dismissNotification } from '$lib/stores/notifications'; import { Submit, trackError, trackEvent } from '$lib/actions/analytics'; import { sleep } from '$lib/helpers/promises'; import { invalidate, beforeNavigate, goto } from '$app/navigation'; @@ -43,6 +44,8 @@ import { Confirm } from '$lib/components'; import { VARS } from '$lib/system'; + import IconAINotification from './icon/aiNotification.svelte'; + let resizeObserver: ResizeObserver; let spreadsheetContainer: HTMLElement; @@ -63,6 +66,8 @@ let creatingColumns = $state(false); const baseColProps = { draggable: false, resizable: false }; + const NOTIFICATION_AND_MOCK_DELAY = 1250; + const getColumnWidth = (columnKey: string) => Math.max(180, columnKey.length * 8 + 60); const safeNumericValue = (value: number | undefined) => value !== undefined && isWithinSafeRange(value) ? value : undefined; @@ -402,7 +407,7 @@ try { if (VARS.MOCK_AI_SUGGESTIONS) { /* animation */ - await sleep(1250); + await sleep(NOTIFICATION_AND_MOCK_DELAY); suggestedColumns = mockSuggestions; } else { suggestedColumns = (await sdk @@ -410,7 +415,8 @@ .console.suggestColumns({ databaseId: page.params.database, tableId: page.params.table, - context: $tableColumnSuggestions.context ?? undefined + context: $tableColumnSuggestions.context ?? undefined, + min: 6 // TODO: to not break the sheet layout's width! })) as unknown as { total: number; columns: ColumnInput[]; @@ -483,6 +489,28 @@ return !['$id', '$createdAt', '$updatedAt', 'actions'].includes(id); } + function showIndexSuggestionsNotification() { + setTimeout(() => { + const notifId = addNotification({ + isHtml: true, + title: 'Next step: Add indexes', + message: 'See suggested indexes based on your columns', + dismissible: true, + icon: IconAINotification, + timeout: 10000, // ten seconds + buttons: [ + { + name: 'Create indexes', + method: () => { + dismissNotification(notifId); + showIndexesSuggestions.update(() => true); + } + } + ] + }); + }, NOTIFICATION_AND_MOCK_DELAY); + } + async function createColumns() { creatingColumns = true; const client = sdk.forProject(page.params.region, page.params.project); @@ -592,6 +620,9 @@ message: 'Columns created successfully' }); + // show index notification! + showIndexSuggestionsNotification(); + trackEvent(Submit.ColumnCreate, { type: 'suggestions' }); } catch (error) { trackError(error, Submit.ColumnCreate); diff --git a/src/routes/(console)/project-[region]-[project]/databases/database-[database]/(suggestions)/icon/ai.svelte b/src/routes/(console)/project-[region]-[project]/databases/database-[database]/(suggestions)/icon/ai.svelte index 27bc31198..1d51db168 100644 --- a/src/routes/(console)/project-[region]-[project]/databases/database-[database]/(suggestions)/icon/ai.svelte +++ b/src/routes/(console)/project-[region]-[project]/databases/database-[database]/(suggestions)/icon/ai.svelte @@ -1,8 +1,18 @@ - + diff --git a/src/routes/(console)/project-[region]-[project]/databases/database-[database]/(suggestions)/icon/aiNotification.svelte b/src/routes/(console)/project-[region]-[project]/databases/database-[database]/(suggestions)/icon/aiNotification.svelte new file mode 100644 index 000000000..1fe702004 --- /dev/null +++ b/src/routes/(console)/project-[region]-[project]/databases/database-[database]/(suggestions)/icon/aiNotification.svelte @@ -0,0 +1,5 @@ + + + diff --git a/src/routes/(console)/project-[region]-[project]/databases/database-[database]/(suggestions)/indexes.svelte b/src/routes/(console)/project-[region]-[project]/databases/database-[database]/(suggestions)/indexes.svelte new file mode 100644 index 000000000..365e8f05f --- /dev/null +++ b/src/routes/(console)/project-[region]-[project]/databases/database-[database]/(suggestions)/indexes.svelte @@ -0,0 +1,293 @@ + + + + + {#await loadIndexSuggestions()} + {#each Array(3) as _, index} + {@const firstItem = index === 0} + + {@render fieldSkeleton({ label: 'Key', showLabel: firstItem })} + {@render fieldSkeleton({ label: 'Type', showLabel: firstItem })} + {@render fieldSkeleton({ label: 'Order', showLabel: firstItem })} + + +
+ +
+
+ {/each} + + + + + {:then suggestedIndexes} + {#each suggestedIndexes as index, count} + {@const firstItem = count === 0} + + + + + + + + + +
+ +
+
+ {/each} + + {#if indexes.length < MAX_INDEXES} + + + + {/if} + {/await} +
+ + + + + + + + + + + + +{#snippet fieldSkeleton({ label, showLabel })} + + {#if showLabel} + {label} + {/if} + + +{/snippet} diff --git a/src/routes/(console)/project-[region]-[project]/databases/database-[database]/(suggestions)/store.ts b/src/routes/(console)/project-[region]-[project]/databases/database-[database]/(suggestions)/store.ts index e0432b6f2..b8c022bc4 100644 --- a/src/routes/(console)/project-[region]-[project]/databases/database-[database]/(suggestions)/store.ts +++ b/src/routes/(console)/project-[region]-[project]/databases/database-[database]/(suggestions)/store.ts @@ -1,4 +1,5 @@ import { writable } from 'svelte/store'; +import { IndexType } from '@appwrite.io/console'; import { columnOptions } from '../table-[table]/columns/store'; export type TableColumnSuggestions = { @@ -24,6 +25,20 @@ export type SuggestedColumnSchema = { format?: string | null; }; +export enum IndexOrder { + ASC = 'ASC', + DESC = 'DESC', + NONE = null +} + +export type SuggestedIndexSchema = { + key: string; + type: IndexType; + orders: IndexOrder; + columns: string[]; + lengths?: number[] | undefined; +}; + export const tableColumnSuggestions = writable({ enabled: false, context: null, @@ -33,6 +48,8 @@ export const tableColumnSuggestions = writable({ export const createTableRequest = writable(false); +export const showIndexesSuggestions = writable(false); + export const mockSuggestions: { total: number; columns: ColumnInput[] } = { total: 7, columns: [ 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 fcc995b71..74d71ee07 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 @@ -64,6 +64,8 @@ import { chunks } from '$lib/helpers/array'; import { Submit, trackEvent } from '$lib/actions/analytics'; + import IndexesSuggestions from '../(suggestions)/indexes.svelte'; + let editRow: EditRow; let editRelatedRow: EditRelatedRow; let editRowPermissions: EditRowPermissions; @@ -470,3 +472,5 @@ + + diff --git a/src/routes/(console)/project-[region]-[project]/databases/database-[database]/table-[table]/indexes/createIndex.svelte b/src/routes/(console)/project-[region]-[project]/databases/database-[database]/table-[table]/indexes/createIndex.svelte index 85c5b518a..cb19528dc 100644 --- a/src/routes/(console)/project-[region]-[project]/databases/database-[database]/table-[table]/indexes/createIndex.svelte +++ b/src/routes/(console)/project-[region]-[project]/databases/database-[database]/table-[table]/indexes/createIndex.svelte @@ -154,7 +154,7 @@ ] : undefined }); - trackEvent(Submit.IndexCreate); + trackEvent(Submit.IndexCreate, { type: 'manual' }); showCreateIndex = false; } catch (err) { addNotification({ From 8f577e9ecdf6220cc2d9bfedf1d4b4e1c3e685a0 Mon Sep 17 00:00:00 2001 From: Darshan Date: Sat, 27 Sep 2025 12:29:00 +0530 Subject: [PATCH 03/28] update: index suggestions mobile views with accordion. --- .../(suggestions)/indexes.svelte | 409 +++++++++++++----- 1 file changed, 291 insertions(+), 118 deletions(-) diff --git a/src/routes/(console)/project-[region]-[project]/databases/database-[database]/(suggestions)/indexes.svelte b/src/routes/(console)/project-[region]-[project]/databases/database-[database]/(suggestions)/indexes.svelte index 365e8f05f..9b1face53 100644 --- a/src/routes/(console)/project-[region]-[project]/databases/database-[database]/(suggestions)/indexes.svelte +++ b/src/routes/(console)/project-[region]-[project]/databases/database-[database]/(suggestions)/indexes.svelte @@ -1,5 +1,5 @@ - - - {#await loadIndexSuggestions()} - {#each Array(3) as _, index} - {@const firstItem = index === 0} - - {@render fieldSkeleton({ label: 'Key', showLabel: firstItem })} - {@render fieldSkeleton({ label: 'Type', showLabel: firstItem })} - {@render fieldSkeleton({ label: 'Order', showLabel: firstItem })} - +{#if !$isSmallViewport} + { + await applySuggestedIndexes(); + }}> + + {#await loadIndexSuggestions()} + {#each Array(3) as _, index} + {@const firstItem = index === 0} + + {@render fieldSkeleton({ label: 'Key', showLabel: firstItem })} + {@render fieldSkeleton({ label: 'Type', showLabel: firstItem })} + {@render fieldSkeleton({ label: 'Order', showLabel: firstItem })} + -
- -
-
- {/each} +
+ +
+
+ {/each} - - - - {:then suggestedIndexes} - {#each suggestedIndexes as index, count} - {@const firstItem = count === 0} - - + {@render addIndexButton()} + {/await} + - + + + + - - - - -
- -
-
- {/each} - - {#if indexes.length < MAX_INDEXES} - - - {/if} - {/await} -
- - - - - - - - - -
+ +
+{/if} -{#snippet fieldSkeleton({ label, showLabel })} +{#if $isSmallViewport} + await applySuggestedIndexes() + }}> + {#if modalError} + + {modalError} + + {/if} + + {#await loadIndexSuggestions()} + + {#each Array(3) as _} + {@render fieldSkeleton({ + label: undefined, + showLabel: false, + isDesktop: false + })} + {/each} + + {:then suggestedIndexes} + + {#each suggestedIndexes as index, count} + + {@render indexEditForm({ index, count, isDesktop: false })} + + {/each} + + {@render addIndexButton()} + + {/await} + +{/if} + +{#snippet fieldSkeleton({ label, showLabel, isDesktop = true })} {#if showLabel} {label} {/if} - + {/snippet} + +{#snippet indexEditForm({ index, count, isDesktop = true })} + {@const firstItem = count === 0} + {#if isDesktop} + + + + + + + + {@render removeIndexButton({ count, isDesktop: true })} + + {:else} + + + + + + + + + + + {@render removeIndexButton({ count, isDesktop: false })} + + {/if} +{/snippet} + +{#snippet addIndexButton()} + {#if indexes.length < MAX_INDEXES} + + + + {/if} +{/snippet} + +{#snippet removeIndexButton({ count, isDesktop = true })} + {#if isDesktop} +
+ +
+ {:else if indexes.length > 1} + + + + {/if} +{/snippet} From e1d526c3f62dc3a7a31356be6d8625ede75e41e5 Mon Sep 17 00:00:00 2001 From: Darshan Date: Sat, 27 Sep 2025 12:29:27 +0530 Subject: [PATCH 04/28] update: misc. styles --- .../database-[database]/(suggestions)/empty.svelte | 9 ++++++--- .../database-[database]/(suggestions)/input.svelte | 6 +++++- 2 files changed, 11 insertions(+), 4 deletions(-) diff --git a/src/routes/(console)/project-[region]-[project]/databases/database-[database]/(suggestions)/empty.svelte b/src/routes/(console)/project-[region]-[project]/databases/database-[database]/(suggestions)/empty.svelte index 7a2272d95..19c8c72a4 100644 --- a/src/routes/(console)/project-[region]-[project]/databases/database-[database]/(suggestions)/empty.svelte +++ b/src/routes/(console)/project-[region]-[project]/databases/database-[database]/(suggestions)/empty.svelte @@ -863,9 +863,12 @@ - + Thinking of column suggestions - + @@ -990,7 +993,7 @@ transition: box-shadow 0.5s cubic-bezier(0.25, 0.46, 0.45, 0.94); } - & :global(.column-resizer-disabled) { + & :global([role='cell']:not([data-column-id='actions']) .column-resizer-disabled) { border-left: var(--border-width-s, 1px) solid rgba(253, 54, 110, 0.24) !important; transition: border-color 0.5s cubic-bezier(0.25, 0.46, 0.45, 0.94); } diff --git a/src/routes/(console)/project-[region]-[project]/databases/database-[database]/(suggestions)/input.svelte b/src/routes/(console)/project-[region]-[project]/databases/database-[database]/(suggestions)/input.svelte index b663588e9..bf5f63109 100644 --- a/src/routes/(console)/project-[region]-[project]/databases/database-[database]/(suggestions)/input.svelte +++ b/src/routes/(console)/project-[region]-[project]/databases/database-[database]/(suggestions)/input.svelte @@ -5,6 +5,7 @@ import { InputTextarea } from '$lib/elements/forms'; import { Card, Layout, Selector, Typography } from '@appwrite.io/pink-svelte'; import { onMount } from 'svelte'; + import { isSmallViewport } from '$lib/stores/viewport'; onMount(() => { // enable by default! @@ -14,7 +15,10 @@ - + From c08145d9efdf97ce3f3b4f8c4c31a9e5157af381 Mon Sep 17 00:00:00 2001 From: Darshan Date: Sat, 27 Sep 2025 12:30:29 +0530 Subject: [PATCH 05/28] update: make spreadsheet cols on columns and index sheets as non-resizable. --- .../table-[table]/columns/+page.svelte | 22 ++++++++++++++----- .../table-[table]/indexes/+page.svelte | 8 +++---- 2 files changed, 20 insertions(+), 10 deletions(-) diff --git a/src/routes/(console)/project-[region]-[project]/databases/database-[database]/table-[table]/columns/+page.svelte b/src/routes/(console)/project-[region]-[project]/databases/database-[database]/table-[table]/columns/+page.svelte index b767d0cdf..9936f4e41 100644 --- a/src/routes/(console)/project-[region]-[project]/databases/database-[database]/table-[table]/columns/+page.svelte +++ b/src/routes/(console)/project-[region]-[project]/databases/database-[database]/table-[table]/columns/+page.svelte @@ -226,9 +226,9 @@ bind:selectedRows={selectedColumns} columns={[ // more size until we decide if we want a new column! - { id: 'key', width: { min: $isSmallViewport ? 250 : 200 } }, - { id: 'indexed', width: { min: 150 } }, - { id: 'default', width: { min: 200 } }, + { id: 'key', width: { min: 300 }, resizable: false }, + { id: 'indexed', width: { min: 150 }, resizable: false }, + { id: 'default', width: { min: 200 }, resizable: false }, { id: 'actions', width: 40, isAction: true } ]} bottomActionClick={() => ($showCreateColumnSheet.show = true)}> @@ -269,13 +269,13 @@ direction="row" alignItems="center" gap="xxs"> - + {#if column.key === '$id' || column.key === '$sequence' || column.key === '$createdAt' || column.key === '$updatedAt'} - {column['name']} + {column.key} {:else} {column.key} {column.array ? '[]' : undefined} {/if} - + {#if isString(column) && column.encrypt} {/if} + + diff --git a/src/routes/(console)/project-[region]-[project]/databases/database-[database]/table-[table]/indexes/+page.svelte b/src/routes/(console)/project-[region]-[project]/databases/database-[database]/table-[table]/indexes/+page.svelte index 53a4327e9..632cefcdf 100644 --- a/src/routes/(console)/project-[region]-[project]/databases/database-[database]/table-[table]/indexes/+page.svelte +++ b/src/routes/(console)/project-[region]-[project]/databases/database-[database]/table-[table]/indexes/+page.svelte @@ -53,11 +53,11 @@ let showOverview = $state(false); let columns = $state([ - { id: 'key' }, - { id: 'type' }, - { id: 'columns' }, + { id: 'key', width: { min: $isSmallViewport ? 250 : 200 }, resizable: false }, + { id: 'type', width: 120, resizable: false }, + { id: 'columns', width: { min: 200, resizable: false } }, // { id: 'orders' }, // design doesn't have orders atm - { id: 'lengths' }, + { id: 'lengths', width: { min: 180, resizable: false } }, { id: 'actions', width: 40, isAction: true } ]); From be1747179512e7649c95fe431b9936d9c39a86ec Mon Sep 17 00:00:00 2001 From: Darshan Date: Sat, 27 Sep 2025 12:30:58 +0530 Subject: [PATCH 06/28] add: icons to select columns on index creation. --- .../table-[table]/indexes/createIndex.svelte | 23 +++++++++++++++---- 1 file changed, 18 insertions(+), 5 deletions(-) diff --git a/src/routes/(console)/project-[region]-[project]/databases/database-[database]/table-[table]/indexes/createIndex.svelte b/src/routes/(console)/project-[region]-[project]/databases/database-[database]/table-[table]/indexes/createIndex.svelte index cb19528dc..ff95b6af5 100644 --- a/src/routes/(console)/project-[region]-[project]/databases/database-[database]/table-[table]/indexes/createIndex.svelte +++ b/src/routes/(console)/project-[region]-[project]/databases/database-[database]/table-[table]/indexes/createIndex.svelte @@ -12,8 +12,9 @@ import { isRelationship, isSpatialType } from '../rows/store'; import { table, indexes } from '../store'; import { Icon, Layout } from '@appwrite.io/pink-svelte'; - import { IconPlus, IconX } from '@appwrite.io/pink-icons-svelte'; + import { IconCalendar, IconFingerPrint, IconPlus, IconX } from '@appwrite.io/pink-icons-svelte'; import { isSmallViewport } from '$lib/stores/viewport'; + import { columnOptions as baseColumnOptions } from '../columns/store'; let { showCreateIndex = $bindable(false), @@ -37,7 +38,11 @@ } return !isRelationship(column) && !isSpatialType(column); // keep non-relationship and non-spatial }) - .map((column) => ({ value: column.key, label: column.key })) + .map((column) => ({ + value: column.key, + label: column.key, + leadingIcon: baseColumnOptions.find((option) => option.type === column.type).icon + })) ); let columnList = $state([{ value: '', order: '', length: null }]); @@ -196,9 +201,17 @@ ...(selectedType === IndexType.Spatial ? [] : [ - { value: '$id', label: '$id' }, - { value: '$createdAt', label: '$createdAt' }, - { value: '$updatedAt', label: '$updatedAt' } + { value: '$id', label: '$id', leadingIcon: IconFingerPrint }, + { + value: '$createdAt', + label: '$createdAt', + leadingIcon: IconCalendar + }, + { + value: '$updatedAt', + label: '$updatedAt', + leadingIcon: IconCalendar + } ]), ...columnOptions ]} From 4bb90aa7f576dc3d82b3b017c55a31df6729b0b9 Mon Sep 17 00:00:00 2001 From: Darshan Date: Sat, 27 Sep 2025 12:32:27 +0530 Subject: [PATCH 07/28] fix: icon in footer shown above floating action bars. --- src/lib/layout/footer.svelte | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/lib/layout/footer.svelte b/src/lib/layout/footer.svelte index 2e4f9819c..54e8ec053 100644 --- a/src/lib/layout/footer.svelte +++ b/src/lib/layout/footer.svelte @@ -181,6 +181,11 @@ &.hide { display: none; } + + & :global(i) { + // because the `IconCloud` shows above floating action bars. + position: unset; + } } :global(main:has(.sub-navigation)) footer { From f2ff9d61c866ea027abea9a1171d4165c9fe6ff2 Mon Sep 17 00:00:00 2001 From: Darshan Date: Sat, 27 Sep 2025 12:33:34 +0530 Subject: [PATCH 08/28] fix: selection count not updated on indexes deletions. --- .../table-[table]/indexes/deleteIndex.svelte | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/routes/(console)/project-[region]-[project]/databases/database-[database]/table-[table]/indexes/deleteIndex.svelte b/src/routes/(console)/project-[region]-[project]/databases/database-[database]/table-[table]/indexes/deleteIndex.svelte index 956bb1b6a..5916d398a 100644 --- a/src/routes/(console)/project-[region]-[project]/databases/database-[database]/table-[table]/indexes/deleteIndex.svelte +++ b/src/routes/(console)/project-[region]-[project]/databases/database-[database]/table-[table]/indexes/deleteIndex.svelte @@ -34,8 +34,6 @@ }) ) ); - await invalidate(Dependencies.TABLE); - showDelete = false; addNotification({ type: 'success', message: @@ -43,7 +41,13 @@ ? 'Index has been deleted' : `${selectedKeys.length} indexes have been deleted` }); + trackEvent(Submit.IndexDelete); + + await invalidate(Dependencies.TABLE); + + showDelete = false; + selectedIndex = Array.isArray(selectedIndex) ? [] : null; } catch (e) { error = e.message; trackError(e, Submit.IndexDelete); From b0403c59628f48cc90eb152199a1235a439d7d2c Mon Sep 17 00:00:00 2001 From: Darshan Date: Sat, 27 Sep 2025 12:43:00 +0530 Subject: [PATCH 09/28] misc: comment update. --- .../database-[database]/(suggestions)/indexes.svelte | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/routes/(console)/project-[region]-[project]/databases/database-[database]/(suggestions)/indexes.svelte b/src/routes/(console)/project-[region]-[project]/databases/database-[database]/(suggestions)/indexes.svelte index 9b1face53..3be1fa5b7 100644 --- a/src/routes/(console)/project-[region]-[project]/databases/database-[database]/(suggestions)/indexes.svelte +++ b/src/routes/(console)/project-[region]-[project]/databases/database-[database]/(suggestions)/indexes.svelte @@ -144,7 +144,7 @@ // prepare lengths array let lengths: (number | null)[]; if (index.type === IndexType.Key) { - // Only validate if it's a key index + // only validate if it's a key index lengths = index.columns.map((columnKey, i) => { const column = columnMap.get(columnKey); if (column?.type === 'string') { @@ -188,10 +188,10 @@ for (const [_, index] of indexes.entries()) { try { - // Prepare and validate index data + // prepare and validate index data const { orders, lengths } = prepareIndexForCreation(index, columnMap); - // Generate unique key name for the index + // generate unique key name for the index index.key = generateUniqueIndexKey(index, usedKeys); await sdkClient.tablesDB.createIndex({ From 34aaf77b7c23c4e0d7b5bd8318121f639a84a87c Mon Sep 17 00:00:00 2001 From: Darshan Date: Sat, 27 Sep 2025 12:57:22 +0530 Subject: [PATCH 10/28] fix: modal not dismissed and content reloading due to inlined svelte `await`. --- .../(suggestions)/indexes.svelte | 17 +++++++++++++---- 1 file changed, 13 insertions(+), 4 deletions(-) diff --git a/src/routes/(console)/project-[region]-[project]/databases/database-[database]/(suggestions)/indexes.svelte b/src/routes/(console)/project-[region]-[project]/databases/database-[database]/(suggestions)/indexes.svelte index 3be1fa5b7..3e87b117d 100644 --- a/src/routes/(console)/project-[region]-[project]/databases/database-[database]/(suggestions)/indexes.svelte +++ b/src/routes/(console)/project-[region]-[project]/databases/database-[database]/(suggestions)/indexes.svelte @@ -169,6 +169,17 @@ return { orders, lengths }; } + async function closeAndInvalidate() { + // close modal/sheet. + $showIndexesSuggestions = false; + + /** + * Invalidate dependencies after modal/sheet close, + * because otherwise the `await` block will re-run in the modal. + */ + await invalidate(Dependencies.TABLE); + } + async function applySuggestedIndexes(): Promise { modalError = null; creatingIndexes = true; @@ -228,8 +239,7 @@ type: 'success' }); - // invalidate dependencies. - await invalidate(Dependencies.TABLE); + await closeAndInvalidate(); } else if (successCount > 0) { // some succeeded, some failed addNotification({ @@ -237,8 +247,7 @@ type: 'warning' }); - // invalidate dependencies. - await invalidate(Dependencies.TABLE); + await closeAndInvalidate(); } else { // all failed addNotification({ From 23b410cfbcb176fb70185e3208727bf1ab6705e4 Mon Sep 17 00:00:00 2001 From: Darshan Date: Sat, 27 Sep 2025 13:10:04 +0530 Subject: [PATCH 11/28] update: button states when loading the suggestions. --- .../(suggestions)/indexes.svelte | 25 ++++++++++++++----- .../table-[table]/layout/sidesheet.svelte | 17 +++++++++++-- 2 files changed, 34 insertions(+), 8 deletions(-) diff --git a/src/routes/(console)/project-[region]-[project]/databases/database-[database]/(suggestions)/indexes.svelte b/src/routes/(console)/project-[region]-[project]/databases/database-[database]/(suggestions)/indexes.svelte index 3e87b117d..a5f7e72b1 100644 --- a/src/routes/(console)/project-[region]-[project]/databases/database-[database]/(suggestions)/indexes.svelte +++ b/src/routes/(console)/project-[region]-[project]/databases/database-[database]/(suggestions)/indexes.svelte @@ -30,6 +30,7 @@ let modalError = $state(null); let creatingIndexes = $state(false); + let loadingSuggestions = $state(false); let indexes = $state([]); let columnOptions: Array<{ value: string; @@ -61,6 +62,7 @@ async function loadIndexSuggestions(): Promise { modalError = null; + loadingSuggestions = true; if (VARS.MOCK_AI_SUGGESTIONS) { await sleep(1250); @@ -93,6 +95,8 @@ makeColumnOptions(); + loadingSuggestions = false; + return indexes; } @@ -313,14 +317,17 @@ - + @@ -337,9 +344,10 @@ bind:show={$showIndexesSuggestions} submit={{ text: 'Create', - disabled: indexes.length === 0 || creatingIndexes, + disabled: indexes.length === 0 || creatingIndexes || loadingSuggestions, onClick: async () => await applySuggestedIndexes() - }}> + }} + cancel={{ disabled: loadingSuggestions }}> {#if modalError} {modalError} @@ -447,7 +455,12 @@ {#snippet addIndexButton()} {#if indexes.length < MAX_INDEXES} - diff --git a/src/routes/(console)/project-[region]-[project]/databases/database-[database]/table-[table]/layout/sidesheet.svelte b/src/routes/(console)/project-[region]-[project]/databases/database-[database]/table-[table]/layout/sidesheet.svelte index 68b981d3a..1665e2a42 100644 --- a/src/routes/(console)/project-[region]-[project]/databases/database-[database]/table-[table]/layout/sidesheet.svelte +++ b/src/routes/(console)/project-[region]-[project]/databases/database-[database]/table-[table]/layout/sidesheet.svelte @@ -11,6 +11,7 @@ title, closeOnBlur = false, submit, + cancel, children = null, footer = null, titleBadge = null, @@ -36,6 +37,12 @@ onClick?: () => boolean | void | Promise; } | undefined; + cancel?: + | { + text?: string; + disabled?: boolean; + } + | undefined; children?: Snippet; footer?: Snippet | null; } = $props(); @@ -110,8 +117,14 @@ {#if footer} {@render footer?.()} {/if} - + + + + on:click={() => { + if (indexes.length > 0 && !creatingIndexes) { + confirmDismiss = true; + } else { + $showIndexesSuggestions = false; + } + }}>Cancel + on:click={() => { + if (cancel?.onClick) { + cancel.onClick(); + } else { + show = false; + } + }}>{cancel?.text ?? 'Cancel'} + + {/if} + + + {#if $tableColumnSuggestions.enabled && featureActive}
diff --git a/src/routes/(console)/project-[region]-[project]/databases/database-[database]/table-[table]/+page.svelte b/src/routes/(console)/project-[region]-[project]/databases/database-[database]/table-[table]/+page.svelte index b6d825c4f..4889cfe4d 100644 --- a/src/routes/(console)/project-[region]-[project]/databases/database-[database]/table-[table]/+page.svelte +++ b/src/routes/(console)/project-[region]-[project]/databases/database-[database]/table-[table]/+page.svelte @@ -31,6 +31,7 @@ import EmptySheet from './layout/emptySheet.svelte'; import CreateRow from './rows/create.svelte'; import { onDestroy } from 'svelte'; + import { isCloud } from '$lib/system'; import { Empty as SuggestionsEmptySheet, tableColumnSuggestions } from '../(suggestions)'; export let data: PageData; @@ -75,6 +76,12 @@ $: hasColumns = !!$table.columns.length; $: hasValidColumns = $table?.columns?.some((col) => col.status === 'available'); + $: canShowSuggestionsSheet = + // enabled, has table details + // and it matches current table + $tableColumnSuggestions.enabled && + $tableColumnSuggestions.table && + $tableColumnSuggestions.table.id === page.params.table; async function onSelect(file: Models.File, localFile = false) { $isCsvImportInProgress = true; @@ -216,7 +223,7 @@ } }} /> {/if} - {:else if $tableColumnSuggestions.enabled && $tableColumnSuggestions.table && $tableColumnSuggestions.table.id === page.params.table} + {:else if isCloud && canShowSuggestionsSheet} {:else}