From bd6e156ca340155431a0440cf44991187b24be3e Mon Sep 17 00:00:00 2001 From: Darshan Date: Mon, 18 Aug 2025 16:10:49 +0530 Subject: [PATCH] update: ID > `$id` fix: displayNames logic <> preferences. fix: edit related rows in the side sheet. fix: dynamic height on empty sheet's overlay. fix: filter checkbox and action item being a bit independent. --- src/lib/components/columnSelector.svelte | 5 +- src/lib/stores/preferences.ts | 42 ++-- .../project-[region]-[project]/+layout.ts | 3 +- .../table-[table]/+layout.svelte | 21 +- .../table-[table]/editRelatedRow.svelte | 174 ++++++++++++++ .../table-[table]/editRow.svelte | 27 +-- .../table-[table]/layout/emptySheet.svelte | 212 ++++++++++-------- .../table-[table]/layout/spreadsheet.svelte | 10 +- .../row-[row]/columns/relationship.svelte | 83 +++---- .../table-[table]/settings/deleteTable.svelte | 6 +- .../table-[table]/settings/displayName.svelte | 18 +- .../table-[table]/spreadsheet.svelte | 96 ++++---- .../table-[table]/store.ts | 21 ++ 13 files changed, 488 insertions(+), 230 deletions(-) create mode 100644 src/routes/(console)/project-[region]-[project]/databases/database-[database]/table-[table]/editRelatedRow.svelte diff --git a/src/lib/components/columnSelector.svelte b/src/lib/components/columnSelector.svelte index 17d43482f..8b7433fc6 100644 --- a/src/lib/components/columnSelector.svelte +++ b/src/lib/components/columnSelector.svelte @@ -233,7 +233,10 @@ ? false : visibleRealColumns.length <= 1 && !column.hide}> - + toggleColumn(column)} /> {column.title} diff --git a/src/lib/stores/preferences.ts b/src/lib/stores/preferences.ts index ab0a2e37b..4fd1a3e3b 100644 --- a/src/lib/stores/preferences.ts +++ b/src/lib/stores/preferences.ts @@ -208,31 +208,37 @@ function createPreferences() { loadTeamPrefs: loadTeamPreferences, - getDisplayNames: () => { - return preferences?.displayNames ?? {}; + getDisplayNames: (tableId: string) => { + return teamPreferences?.displayNames?.[tableId]; }, - setDisplayNames: async (tableId: string, names: TeamPreferences['names']) => { - await updateAndSync((n) => { - if (!n?.displayNames) { - n ??= {}; - n.displayNames ??= {}; - } + setDisplayNames: async ( + orgId: string, + tableId: string, + displayNames: TeamPreferences['names'] + ) => { + if (!teamPreferences.displayNames) { + teamPreferences.displayNames = {}; + } - n.displayNames[tableId] = names; - return n; + teamPreferences.displayNames[tableId] = displayNames; + + await sdk.forConsole.teams.updatePrefs({ + teamId: orgId, + prefs: teamPreferences }); }, - deleteDisplayNames: async (tableId: string) => { - await updateAndSync((n) => { - if (!n?.displayNames) { - n ??= {}; - n.displayNames ??= {}; - } + deleteDisplayNames: async (orgId: string, tableId: string) => { + if (!teamPreferences?.displayNames) { + return; + } - delete n.displayNames[tableId]; - return n; + delete teamPreferences.displayNames[tableId]; + + await sdk.forConsole.teams.updatePrefs({ + teamId: orgId, + prefs: teamPreferences }); }, diff --git a/src/routes/(console)/project-[region]-[project]/+layout.ts b/src/routes/(console)/project-[region]-[project]/+layout.ts index 69afdf0e2..2d7c9dbc9 100644 --- a/src/routes/(console)/project-[region]-[project]/+layout.ts +++ b/src/routes/(console)/project-[region]-[project]/+layout.ts @@ -56,7 +56,8 @@ export const load: LayoutLoad = async ({ params, depends, parent }) => { }); } - preferences.loadTeamPrefs(project.teamId); + // should be awaited for `displayNames`! + await preferences.loadTeamPrefs(project.teamId); if (isCloud && scopes.includes('billing.read')) { loadFailedInvoices(project.teamId); 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 0efe9710f..a3a69ebed 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 @@ -33,7 +33,8 @@ spreadsheetLoading, rowActivitySheet, spreadsheetRenderKey, - expandTabs + expandTabs, + databaseRelatedRowSheetOptions } from './store'; import { addSubPanel, registerCommands, updateCommandGroupRanks } from '$lib/commandCenter'; import CreateColumn from './createColumn.svelte'; @@ -46,6 +47,7 @@ import { IconEye, IconLockClosed, IconPlus, IconPuzzle } from '@appwrite.io/pink-icons-svelte'; import SideSheet from './layout/sidesheet.svelte'; import EditRow from './editRow.svelte'; + import EditRelatedRow from './editRelatedRow.svelte'; import EditColumn from './columns/edit.svelte'; import RowActivity from './rowActivity.svelte'; import { Dialog, Layout, Typography } from '@appwrite.io/pink-svelte'; @@ -58,6 +60,8 @@ import { preferences } from '$lib/stores/preferences'; let editRow: EditRow; + let editRelatedRow: EditRelatedRow; + let createIndex: CreateIndex; let createColumn: CreateColumn; let selectedOption: Option['name'] = 'String'; @@ -357,6 +361,21 @@ + await editRelatedRow?.update() + }}> + + + + import { symmetricDifference } from '$lib/helpers/array'; + import { Submit, trackEvent, trackError } from '$lib/actions/analytics'; + import { page } from '$app/state'; + import { sdk } from '$lib/stores/sdk'; + import { addNotification } from '$lib/stores/notifications'; + import { type Writable, writable } from 'svelte/store'; + import { type Models } from '@appwrite.io/console'; + import { Dependencies } from '$lib/constants'; + import { invalidate } from '$app/navigation'; + import { type Columns, PROHIBITED_ROW_KEYS } from './store'; + import ColumnItem from './row-[row]/columnItem.svelte'; + import { + buildWildcardColumnsQuery, + isRelationship, + isRelationshipToMany + } from './row-[row]/columns/store'; + import { Layout, Skeleton } from '@appwrite.io/pink-svelte'; + import { deepClone } from '$lib/helpers/object'; + + const databaseId = page.params.database; + + let { + rowId, + tableId + }: { + rowId: string; + tableId: string; + } = $props(); + + let loading = $state(false); + let fetchedRow = $state(null); + let relatedTable = $state(null); + + let work = $state | null>(null); + let columnFormWrapper = $state(null); + + async function loadRelatedRow() { + loading = true; + + try { + relatedTable = + page.data.tables?.[tableId] ?? + (await sdk.forProject(page.params.region, page.params.project).grids.getTable({ + databaseId, + tableId: tableId + })); + + fetchedRow = await sdk + .forProject(page.params.region, page.params.project) + .grids.getRow({ + databaseId, + tableId: tableId, + rowId: rowId, + queries: buildWildcardColumnsQuery(relatedTable) + }); + + const filteredKeys = Object.keys(fetchedRow).filter((key) => { + return !PROHIBITED_ROW_KEYS.includes(key); + }); + + const workingData = filteredKeys.reduce((obj, key) => { + obj[key] = fetchedRow[key]; + return obj; + }, {}); + + work = writable(deepClone(workingData as Models.Row)); + } catch (error) { + addNotification({ + message: error.message, + type: 'error' + }); + trackError(error, Submit.RowUpdate); + } finally { + loading = false; + } + } + + $effect(() => { + if (rowId && tableId) { + loadRelatedRow().then(() => { + focusFirstInput(); + }); + } + }); + + function compareColumns(column: Columns, $work: Models.Row, originalRow: Models.Row) { + if (!column) { + return false; + } + + const workColumn = $work?.[column.key]; + const currentColumn = originalRow?.[column.key]; + + if (column.array) { + return !symmetricDifference(Array.from(workColumn), Array.from(currentColumn)).length; + } + + if (isRelationship(column)) { + if (isRelationshipToMany(column as Models.ColumnRelationship)) { + const workIds = workColumn.map((doc: string | Record) => + typeof doc === 'string' ? doc : doc.$id + ); + + const relatedIds = currentColumn.map((doc: string | Record) => + typeof doc === 'string' ? doc : doc.$id + ); + return !symmetricDifference(workIds, relatedIds).length; + } else { + const workId = typeof workColumn === 'string' ? workColumn : workColumn?.$id; + const relatedId = + typeof currentColumn === 'string' ? currentColumn : currentColumn?.$id; + + return workId === relatedId; + } + } + + return workColumn === currentColumn; + } + + export async function update() { + try { + await sdk.forProject(page.params.region, page.params.project).grids.updateRow({ + databaseId, + tableId: relatedTable.$id, + rowId: fetchedRow.$id, + data: $work, + permissions: $work.$permissions + }); + + invalidate(Dependencies.ROW); + trackEvent(Submit.RowUpdate); + addNotification({ + message: 'Related row has been updated', + type: 'success' + }); + } catch (error) { + addNotification({ + message: error.message, + type: 'error' + }); + trackError(error, Submit.RowUpdate); + } + } + + export function isDisabled(): boolean { + if (!work || !relatedTable?.columns?.length || !fetchedRow) return true; + + return relatedTable.columns.every((column) => compareColumns(column, $work, fetchedRow)); + } + + function focusFirstInput() { + const firstInput = columnFormWrapper?.querySelector( + 'input:not([disabled]):not([readonly]), textarea:not([disabled]):not([readonly])' + ); + + firstInput?.focus({ preventScroll: true }); + } + + +{#if loading} +
+ +
+{:else if relatedTable?.columns?.length && work} +
+ + {#each relatedTable.columns as column} + {@const label = column.key} + + {/each} + +
+{/if} diff --git a/src/routes/(console)/project-[region]-[project]/databases/database-[database]/table-[table]/editRow.svelte b/src/routes/(console)/project-[region]-[project]/databases/database-[database]/table-[table]/editRow.svelte index c52825a3e..519c6966b 100644 --- a/src/routes/(console)/project-[region]-[project]/databases/database-[database]/table-[table]/editRow.svelte +++ b/src/routes/(console)/project-[region]-[project]/databases/database-[database]/table-[table]/editRow.svelte @@ -8,7 +8,7 @@ import type { Models } from '@appwrite.io/console'; import { Dependencies } from '$lib/constants'; import { invalidate } from '$app/navigation'; - import { table, type Columns } from './store'; + import { table, type Columns, PROHIBITED_ROW_KEYS } from './store'; import ColumnItem from './row-[row]/columnItem.svelte'; import { isRelationship, isRelationshipToMany } from './row-[row]/columns/store'; import { Layout } from '@appwrite.io/pink-svelte'; @@ -27,17 +27,8 @@ let columnFormWrapper = $state(null); function initWork() { - const prohibitedKeys = [ - '$id', - '$collection', - '$tableId', - '$databaseId', - '$createdAt', - '$updatedAt' - ]; - const filteredKeys = Object.keys(row).filter((key) => { - return !prohibitedKeys.includes(key); + return !PROHIBITED_ROW_KEYS.includes(key); }); const result = filteredKeys.reduce((obj, key) => { @@ -55,7 +46,7 @@ } }); - function compareAttributes(column: Columns, $work: Models.Row, $doc: Models.Row) { + function compareColumns(column: Columns, $work: Models.Row, $doc: Models.Row) { if (!column) { return false; } @@ -91,9 +82,13 @@ export async function update() { try { - await sdk - .forProject(page.params.region, page.params.project) - .grids.updateRow(databaseId, tableId, row.$id, $work, $work.$permissions); + await sdk.forProject(page.params.region, page.params.project).grids.updateRow({ + databaseId, + tableId, + rowId: row.$id, + data: $work, + permissions: $work.$permissions + }); invalidate(Dependencies.ROW); trackEvent(Submit.RowUpdate); @@ -113,7 +108,7 @@ export function isDisabled(): boolean { if (!work || !$table?.columns?.length) return true; - return $table.columns.every((attribute) => compareAttributes(attribute, $work, row)); + return $table.columns.every((column) => compareColumns(column, $work, row)); } function focusFirstInput() { diff --git a/src/routes/(console)/project-[region]-[project]/databases/database-[database]/table-[table]/layout/emptySheet.svelte b/src/routes/(console)/project-[region]-[project]/databases/database-[database]/table-[table]/layout/emptySheet.svelte index e283e4154..23bb3fd77 100644 --- a/src/routes/(console)/project-[region]-[project]/databases/database-[database]/table-[table]/layout/emptySheet.svelte +++ b/src/routes/(console)/project-[region]-[project]/databases/database-[database]/table-[table]/layout/emptySheet.svelte @@ -7,13 +7,7 @@ Spreadsheet, Typography } from '@appwrite.io/pink-svelte'; - - import { - IconCalendar, - IconFingerPrint, - IconHashtag, - IconPlus - } from '@appwrite.io/pink-icons-svelte'; + import { IconCalendar, IconFingerPrint, IconPlus } from '@appwrite.io/pink-icons-svelte'; import { isSmallViewport } from '$lib/stores/viewport'; import { SortButton } from '$lib/components'; import type { Column } from '$lib/helpers/types'; @@ -24,6 +18,8 @@ spreadsheetLoading, expandTabs } from '../store'; + import { onMount, tick } from 'svelte'; + import SpreadsheetContainer from './spreadsheet.svelte'; type Mode = 'rows' | 'indexes'; @@ -50,8 +46,39 @@ }; }>(); + let spreadsheetContainer: HTMLElement; + let headerElement: HTMLElement | null = null; + let dynamicOverlayHeight = $state('70.5vh'); + + let spreadsheetRootContainer: SpreadsheetContainer; + const baseColProps = { draggable: false, resizable: false }; + const updateOverlayHeight = () => { + tick().then(() => { + spreadsheetRootContainer?.resizeSheet(false, true); + }); + + if (!spreadsheetContainer) return; + + if (!headerElement || !headerElement.isConnected) { + headerElement = spreadsheetContainer.querySelector('[role="rowheader"]'); + } + + if (headerElement) { + const headerRect = headerElement.getBoundingClientRect(); + const viewportHeight = window.innerHeight; + const dynamicHeight = viewportHeight - headerRect.bottom; + + dynamicOverlayHeight = `${dynamicHeight}px`; + if (!$expandTabs) { + dynamicOverlayHeight = `calc(${dynamicHeight}px - 89px)`; + } + } + }; + + onMount(updateOverlayHeight); + const getCustomColumns = (): Column[] => customColumns.map((col: Column) => ({ ...col, @@ -61,18 +88,9 @@ })); const getRowColumns = (): Column[] => [ - { - id: '$sequence', - title: 'Sequence', - type: 'string', - width: 150, - isPrimary: true, - icon: IconHashtag, - ...baseColProps - }, { id: '$id', - title: 'ID', + title: '$id', type: 'string', width: 180, icon: IconFingerPrint, @@ -81,7 +99,7 @@ ...getCustomColumns(), { id: '$createdAt', - title: 'Created', + title: '$createdAt', type: 'datetime', width: 180, icon: IconCalendar, @@ -89,7 +107,7 @@ }, { id: '$updatedAt', - title: 'Updated', + title: '$updatedAt', type: 'datetime', width: 180, icon: IconCalendar, @@ -127,82 +145,89 @@ const spreadsheetColumns = $derived(mode === 'rows' ? getRowColumns() : getIndexesColumns()); - const emptyCells = $derived($isSmallViewport ? 14 : 17); - const fixedHeight = $derived($isSmallViewport ? '60.75vh' : '74.35vh'); + const fixedHeight = $derived($expandTabs ? 'calc(100% - 89px)' : '100%'); + const emptyCells = $derived(($isSmallViewport ? 14 : 17) + (!$expandTabs ? 2 : 0)); -
- { - /* @ignore: only for showing the `+` button on footer */ - }}> - - {#each spreadsheetColumns as column (column.id)} - {@const columnActionsById = column.id === 'actions'} - -
{ - if (columnActionsById && mode === 'rows') { - $showCreateAttributeSheet.show = true; - $showCreateAttributeSheet.title = 'Create column'; - $showCreateAttributeSheet.columns = $tableColumns; - $showCreateAttributeSheet.columnsOrder = $columnsOrder; - } - }}> - - {#if column.isAction} - { - console.log('dank'); - }}> - - - {:else if column.id === 'actions' || column.id === 'empty'} - {column.title} - {:else} - + + +
+ + { + /* @ignore: only for showing the `+` button on footer */ + }}> + + {#each spreadsheetColumns as column (column.id)} + {@const columnActionsById = column.id === 'actions'} + +
{ + if (columnActionsById && mode === 'rows') { + $showCreateAttributeSheet.show = true; + $showCreateAttributeSheet.title = 'Create column'; + $showCreateAttributeSheet.columns = $tableColumns; + $showCreateAttributeSheet.columnsOrder = $columnsOrder; + } + }}> + + {#if column.isAction} + { + console.log('dank'); + }}> + + + {:else if column.id === 'actions' || column.id === 'empty'} {column.title} + {:else} + + {column.title} - - - {/if} - -
- {/each} -
+ + + {/if} + +
+ {/each} + - - {#if $spreadsheetLoading} - - - - {/if} - - + + {#if $spreadsheetLoading} + + + + {/if} + + + {#if !$spreadsheetLoading} -
+
{title ?? `You have no ${mode} yet`} @@ -293,7 +318,6 @@ .spreadsheet-fade-bottom { bottom: 0; width: 100%; - height: 70.5vh; position: fixed; background: linear-gradient( 180deg, @@ -302,20 +326,22 @@ #ffffff 100% ); z-index: 20; - display: flex; + display: none; justify-content: center; transition: height 300ms cubic-bezier(0.4, 0, 0.2, 1); + height: var(--dynamic-overlay-height, 70.5vh); + &[data-collapsed-tabs='true'] { - height: 79.1vh !important; + height: calc(var(--dynamic-overlay-height, 79.1vh) + 8.6vh); } @media (max-width: 1024px) { - height: 63.35vh; + height: var(--dynamic-overlay-height, 63.35vh); } @media (min-width: 1024px) { - height: 70.35vh; + height: var(--dynamic-overlay-height, 70.35vh); } } diff --git a/src/routes/(console)/project-[region]-[project]/databases/database-[database]/table-[table]/layout/spreadsheet.svelte b/src/routes/(console)/project-[region]-[project]/databases/database-[database]/table-[table]/layout/spreadsheet.svelte index ff6757ebb..ce993d8af 100644 --- a/src/routes/(console)/project-[region]-[project]/databases/database-[database]/table-[table]/layout/spreadsheet.svelte +++ b/src/routes/(console)/project-[region]-[project]/databases/database-[database]/table-[table]/layout/spreadsheet.svelte @@ -1,6 +1,6 @@ - {#if isRelationshipToMany(column)} - - - {label} - - {optionalText} - - + {#if !limited} + + + {label} + + {optionalText} + + - {#if showTopAddButton} - - {/if} - + {#if showTopAddButton} + + {/if} + + {/if} @@ -200,19 +204,22 @@ - {#if relatedList[actualIndex]} -
- -
+ {#if !limited} + {#if relatedList[actualIndex]} +
+ +
+ {/if} {/if}
{/each} @@ -263,7 +270,7 @@ {/if}
- {#if showBottomAddButton} + {#if showBottomAddButton && !limited}