diff --git a/package.json b/package.json index 564746784..8dff8e36a 100644 --- a/package.json +++ b/package.json @@ -26,7 +26,7 @@ "@appwrite.io/pink-icons": "0.25.0", "@appwrite.io/pink-icons-svelte": "^2.0.0-RC.1", "@appwrite.io/pink-legacy": "^1.0.3", - "@appwrite.io/pink-svelte": "https://pkg.vc/-/@appwrite/@appwrite.io/pink-svelte@0fed23b", + "@appwrite.io/pink-svelte": "https://pkg.vc/-/@appwrite/@appwrite.io/pink-svelte@7a52c75", "@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 752cd33a1..5fb9a4fd7 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -24,8 +24,8 @@ importers: specifier: ^1.0.3 version: 1.0.3 '@appwrite.io/pink-svelte': - specifier: https://pkg.vc/-/@appwrite/@appwrite.io/pink-svelte@0fed23b - version: https://pkg.vc/-/@appwrite/@appwrite.io/pink-svelte@0fed23b(svelte@5.25.3) + specifier: https://pkg.vc/-/@appwrite/@appwrite.io/pink-svelte@7a52c75 + version: https://pkg.vc/-/@appwrite/@appwrite.io/pink-svelte@7a52c75(svelte@5.25.3) '@faker-js/faker': specifier: ^9.9.0 version: 9.9.0 @@ -287,8 +287,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@0fed23b': - resolution: {tarball: https://pkg.vc/-/@appwrite/@appwrite.io/pink-svelte@0fed23b} + '@appwrite.io/pink-svelte@https://pkg.vc/-/@appwrite/@appwrite.io/pink-svelte@7a52c75': + resolution: {tarball: https://pkg.vc/-/@appwrite/@appwrite.io/pink-svelte@7a52c75} version: 2.0.0-RC.2 peerDependencies: svelte: ^4.0.0 @@ -3831,7 +3831,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@0fed23b(svelte@5.25.3)': + '@appwrite.io/pink-svelte@https://pkg.vc/-/@appwrite/@appwrite.io/pink-svelte@7a52c75(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/columnSelector.svelte b/src/lib/components/columnSelector.svelte index 6092e1eea..17d43482f 100644 --- a/src/lib/components/columnSelector.svelte +++ b/src/lib/components/columnSelector.svelte @@ -20,7 +20,8 @@ ui = 'legacy', allowNoColumns = false, showAnyway = false, - children + children, + onPreferencesUpdated = null }: { columns: Writable; isCustomTable?: boolean; @@ -28,6 +29,7 @@ ui?: 'legacy' | 'new'; showAnyway?: boolean; children: Snippet<[toggle: () => void, selectedColumnsNumber: number]>; + onPreferencesUpdated?: () => void; } = $props(); let search = $state(''); @@ -60,6 +62,7 @@ const shownColumns = $columns.filter((n) => n.hide === true).map((n) => n.id); if (isCustomTable) { + onPreferencesUpdated?.(); preferences.setCustomTableColumns(page.params.table, shownColumns); } else { preferences.setColumns(shownColumns); @@ -211,10 +214,8 @@ >Deselect all {:else} -
- - No column named "{search}" was found - +
+ No results found
{/if} diff --git a/src/lib/components/viewSelector.svelte b/src/lib/components/viewSelector.svelte index 43fe2d773..7523c6f67 100644 --- a/src/lib/components/viewSelector.svelte +++ b/src/lib/components/viewSelector.svelte @@ -7,29 +7,72 @@ import ViewToggle from './viewToggle.svelte'; import ColumnSelector from './columnSelector.svelte'; import { isSmallViewport } from '$lib/stores/viewport'; + import { preferences } from '$lib/stores/preferences'; + import { page } from '$app/state'; + import { hash } from '$lib/helpers/string'; - export let onlyIcon = false; - export let ui: 'legacy' | 'new' = 'legacy'; - export let columns: Writable; - export let view: View; - export let isCustomTable = false; - export let hideView = false; - export let hideColumns = false; - export let allowNoColumns = false; - export let showAnyway = false; + interface Props { + onlyIcon?: boolean; + ui?: 'legacy' | 'new'; + columns: Writable; + view: View; + isCustomTable?: boolean; + hideView?: boolean; + hideColumns?: boolean; + allowNoColumns?: boolean; + showAnyway?: boolean; + } + + let { + onlyIcon = false, + ui = 'legacy', + columns, + view = $bindable(), + isCustomTable = false, + hideView = false, + hideColumns = false, + allowNoColumns = false, + showAnyway = false + }: Props = $props(); + + let showCountBadge = $state(false); + const preferenceKey = $derived(getPreferenceKey()); + + function getPreferenceKey(): string { + const tableId = page.params.table; + const organizationId = page.data.organization?.$id ?? page.data.project?.teamId; + return hash([organizationId, tableId].filter(Boolean).join('#')); + } + + function updateBadgeState() { + if (!preferences.getKey(preferenceKey, false)) { + showCountBadge = true; + preferences.setKey(preferenceKey, true); + } + } + + $effect(() => { + showCountBadge = !onlyIcon || !!preferences.getKey(preferenceKey, false); + }); {#if !hideColumns && view === View.Table} - + {#snippet children(toggle, selectedColumnsNumber)} + class={onlyIcon && !$isSmallViewport ? 'width-fix' : undefined} + badge={showCountBadge ? selectedColumnsNumber.toString() : undefined}> {/snippet} diff --git a/src/lib/helpers/faker.ts b/src/lib/helpers/faker.ts index 4ef272745..9ff49c87c 100644 --- a/src/lib/helpers/faker.ts +++ b/src/lib/helpers/faker.ts @@ -149,7 +149,7 @@ function generateSingleValue(column: Columns): string | number | boolean | null return faker.internet.url(); } } - return '' + return ''; } else { const stringAttr = column as Models.ColumnString; const maxLength = Math.min(stringAttr.size ?? 255, 1000); diff --git a/src/lib/layout/activity.svelte b/src/lib/layout/activity.svelte index f20c7963d..31bf34ef3 100644 --- a/src/lib/layout/activity.svelte +++ b/src/lib/layout/activity.svelte @@ -10,19 +10,19 @@ export let limit = 0; export let offset = 0; export let logs: Models.LogList; + export let insideSideSheet = false; export let databasesScreen = false; export let useCreateLinkForPagination = true; const columns: PinkColumn[] = [ - { id: 'user' }, - { id: 'event' }, - { id: 'location' }, - { id: 'ip' }, - { id: 'date' } + { id: 'user', ...(insideSideSheet ? { width: 140 } : {}) }, + { id: 'event', ...(insideSideSheet ? { width: 125 } : {}) }, + { id: 'location', ...(insideSideSheet ? { width: 100 } : {}) }, + { id: 'ip', ...(insideSideSheet ? { width: { min: 150 } } : {}) }, + { id: 'date', ...(insideSideSheet ? { width: { min: 200 } } : {}) } ]; - {#if logs.total}
diff --git a/src/lib/layout/animatedTitle.svelte b/src/lib/layout/animatedTitle.svelte index 05e333d68..867c3212b 100644 --- a/src/lib/layout/animatedTitle.svelte +++ b/src/lib/layout/animatedTitle.svelte @@ -15,20 +15,13 @@ } = $props(); const buttonSize = $derived(collapsed ? 'xs' : 's'); - - const expandedSize = $derived( - $isSmallViewport ? 'var(--font-size-m)' : 'var(--font-size-xxxl)' - ); - - // const currentFontSize = $derived(collapsed ? 'var(--font-size-l)' : expandedSize); - const currentFontSize = $derived(collapsed ? 'var(--font-size-l)' : 'var(--font-size-xxxl)'); - const currentLineHeight = $derived(collapsed ? '130%' : '140%'); const currentLetterSpacing = $derived(collapsed ? '0' : '-0.144px'); + const currentFontSize = $derived(collapsed ? 'var(--font-size-l)' : 'var(--font-size-xxxl)'); - {#if href} + {#if href && !$isSmallViewport} diff --git a/src/lib/stores/preferences.ts b/src/lib/stores/preferences.ts index 0dbff31b6..0cce4994a 100644 --- a/src/lib/stores/preferences.ts +++ b/src/lib/stores/preferences.ts @@ -46,6 +46,9 @@ type ConsolePreferencesStore = { columnWidths?: { [key: string]: TeamPreferences['widths']; }; + miscellaneous?: { + [key: string]: string | number | boolean; + }; } & { hideAiDisclaimer?: boolean }; async function updateConsolePreferences(store: ConsolePreferencesStore): Promise { @@ -191,8 +194,6 @@ function createPreferences() { n.tables ??= {}; n.tables[tableId] = Array.from(new Set(columns)); - // let's not double save - // n.collections[tableId] = Array.from(new Set(columns)); return n; }), @@ -238,8 +239,8 @@ function createPreferences() { }); }, - getColumnOrder(collectionId: string): TeamPreferences['order'] { - return teamPreferences?.columnOrder?.[collectionId] ?? []; + getColumnOrder(tableId: string): TeamPreferences['order'] { + return teamPreferences?.columnOrder?.[tableId] ?? []; }, async saveColumnOrder( @@ -299,6 +300,21 @@ function createPreferences() { }); }, + getKey(key: string, _default: T): T { + return (preferences?.miscellaneous?.[key] ?? _default) as T; + }, + + async setKey(key: string, value: string | number | boolean) { + await updateAndSync((n) => { + if (!n?.miscellaneous) { + n ??= {}; + n.miscellaneous ??= {}; + } + + n.miscellaneous[key] = value; + return n; + }); + } }; } diff --git a/src/routes/(console)/project-[region]-[project]/databases/database-[database]/subNavigation.svelte b/src/routes/(console)/project-[region]-[project]/databases/database-[database]/subNavigation.svelte index e8485f487..765ea3ae1 100644 --- a/src/routes/(console)/project-[region]-[project]/databases/database-[database]/subNavigation.svelte +++ b/src/routes/(console)/project-[region]-[project]/databases/database-[database]/subNavigation.svelte @@ -4,7 +4,7 @@ import { showCreate } from './store'; import type { PageData } from './$types'; import { showSubNavigation } from '$lib/stores/layout'; - import { Icon, Sidebar, Navbar, Layout, Link } from '@appwrite.io/pink-svelte'; + import { Icon, Sidebar, Navbar, Layout, Link, Typography } from '@appwrite.io/pink-svelte'; import { IconChevronDown, IconDatabase, @@ -55,14 +55,14 @@
{#if tables?.total} -
    +
      {#each sortedTables as table, index} {@const href = `${base}/project-${region}-${project}/databases/database-${databaseId}/table-${table.$id}`} {@const isSelected = tableId === table.$id} {@const isFirst = index === 0} {@const isLast = index === sortedTables.length - 1} - +
    • {/if} - + +
@@ -166,17 +169,16 @@ 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 ddb870828..ff6757ebb 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 @@ -26,7 +26,7 @@ } /** adjust height to fill remaining viewport space */ - export function resizeSheet(): void { + export function resizeSheet(fromResize: boolean = false): void { if (!spreadsheetWrapper) return; clearTimeout(resizeTimeout); @@ -36,7 +36,7 @@ base = window.innerHeight - rect.top; let headerHeightDiff = 0; - if (observeExpand && !isFirstMount) { + if (observeExpand && !isFirstMount && !fromResize) { /** * 16px from padding top * 08px from padding bottom @@ -60,6 +60,7 @@ export function restoreGridSheetScroll(): void { if (initSpreadsheetGridContainer() && spreadsheetGridContainer.scrollWidth > 0) { + spreadsheetGridContainer.scrollTop = 0; spreadsheetGridContainer.scrollLeft = $scrollStore; } } @@ -81,6 +82,8 @@ }); + resizeSheet(true)} /> +
{@render children()}
diff --git a/src/routes/(console)/project-[region]-[project]/databases/database-[database]/table-[table]/rowActivity.svelte b/src/routes/(console)/project-[region]-[project]/databases/database-[database]/table-[table]/rowActivity.svelte index 69060a955..a6ad658fb 100644 --- a/src/routes/(console)/project-[region]-[project]/databases/database-[database]/table-[table]/rowActivity.svelte +++ b/src/routes/(console)/project-[region]-[project]/databases/database-[database]/table-[table]/rowActivity.svelte @@ -6,7 +6,7 @@ import { rowActivitySheet } from './store'; import { pageToOffset } from '$lib/helpers/load'; import { type Models, Query } from '@appwrite.io/console'; - import { Layout, Skeleton } from '@appwrite.io/pink-svelte'; + import { Skeleton } from '@appwrite.io/pink-svelte'; let limit = 25; /* default */ let offset = $state(0); @@ -47,16 +47,22 @@
{/if} 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 6902121d2..494a27a24 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 @@ -96,7 +96,6 @@ const minimumWidth = 168; const emptyCellsLimit = $isSmallViewport ? 12 : 18; - const selected = preferences.getCustomTableColumns(page.params.table); const SYSTEM_KEYS = new Set([ '$tableId', '$databaseId' @@ -120,6 +119,8 @@ onMount(async () => { displayNames = preferences.getDisplayNames(); + columnsOrder.set(preferences.getColumnOrder(tableId)); + columnsWidth.set(preferences.getColumnWidths(tableId)); makeTableColumns(); sortState.set(data.currentSort as SortState); @@ -133,11 +134,13 @@ onDestroy(() => ($showCreateAttributeSheet.show = false)); function makeTableColumns() { + const selectedColumnsToHide = preferences.getCustomTableColumns(tableId); + const baseColumns = $table.columns.map((col) => ({ id: col.key, title: col.key, type: col.type as ColumnType, - hide: !!selected?.includes(col.key), + hide: !!selectedColumnsToHide?.includes(col.key), array: col?.array, width: getColumnWidth(col.key, { min: minimumWidth }), minimumWidth: minimumWidth, @@ -158,7 +161,7 @@ icon: IconFingerPrint, isEditable: false, isPrimary: false, - hide: !!selected?.includes('$id') + hide: !!selectedColumnsToHide?.includes('$id') }, { id: '$createdAt', @@ -169,7 +172,7 @@ type: 'datetime', icon: IconCalendar, isEditable: false, - hide: !!selected?.includes('$createdAt') + hide: !!selectedColumnsToHide?.includes('$createdAt') }, { id: '$updatedAt', @@ -180,17 +183,18 @@ type: 'datetime', icon: IconCalendar, isEditable: false, - hide: !!selected?.includes('$updatedAt') + hide: !!selectedColumnsToHide?.includes('$updatedAt') }, { id: 'actions', title: '', - width: getColumnWidth('actions', 40), + width: 40, isAction: true, draggable: false, type: 'string', resizable: false, - isEditable: false + isEditable: false, + hide: false } ]; @@ -198,32 +202,15 @@ staticColumns[0], ...baseColumns, staticColumns[1], - staticColumns[2], - staticColumns[3] + staticColumns[2] ]; - const visibleNonAction = groupedColumns.filter((c) => !c.hide && !c.isAction); - if (visibleNonAction.length === 1) { - const only = visibleNonAction[0]; - if (typeof only.width === 'number') { - only.width = { - min: only.width - }; - } - } + const actionsColumn = staticColumns[3]; - tableColumns.set( - reorderItems( - [ - staticColumns[0], - ...baseColumns, - staticColumns[1], - staticColumns[2], - staticColumns[3] - ], - $columnsOrder - ) - ); + const reorderedNonActions = reorderItems(groupedColumns, $columnsOrder); + const finalColumns = [...reorderedNonActions, actionsColumn]; + + tableColumns.set(finalColumns); } function getColumnWidth( @@ -264,18 +251,14 @@ } function formatArray(array: unknown[]) { - if (array.length === 0) return '[ ]'; + if (array.length === 0) return 'Empty'; let formattedFields: string[] = []; for (const item of array) { - if (typeof item === 'string') { - formattedFields.push(`"${item}"`); - } else { - formattedFields.push(`${item}`); - } + formattedFields.push(`${item}`); } - return `[${formattedFields.join(', ')}]`; + return `${formattedFields.join(', ')}`; } function formatColumn(column: unknown) { @@ -342,9 +325,6 @@ ); } - // trigger ui update with randomized key! - spreadsheetRenderKey.set(`sorted#${Date.now()}`); - // save > navigate > restore! spreadsheetContainer.saveGridSheetScroll(); await goto(`${url.pathname}${url.search}`); @@ -411,9 +391,11 @@ showColumnDelete = false; try { - await sdk - .forProject(page.params.region, page.params.project) - .grids.deleteColumn(databaseId, tableId, $databaseColumnSheetOptions.column.key); + await sdk.forProject(page.params.region, page.params.project).grids.deleteColumn({ + databaseId, + tableId, + key: $databaseColumnSheetOptions.column.key + }); if ($columnsOrder.includes($databaseColumnSheetOptions.column.key)) { const updatedOrder = $columnsOrder.filter( @@ -532,9 +514,13 @@ Object.entries(row).filter(([key]) => !SYSTEM_KEYS.has(key)) ); - await sdk - .forProject(page.params.region, page.params.project) - .grids.updateRow(databaseId, tableId, row.$id, onlyData, row.$permissions); + await sdk.forProject(page.params.region, page.params.project).grids.updateRow({ + databaseId, + tableId, + rowId: row.$id, + data: onlyData, + permissions: row.$permissions + }); invalidate(Dependencies.ROW); trackEvent(Submit.RowUpdate); @@ -586,6 +572,7 @@ } async function handleGoToPage(targetPageNum: number): Promise { + jumpToPageReactive = 0; if (targetPageNum < 1 || targetPageNum > totalPages) return; if (!$paginatedRows.hasPage(targetPageNum)) { @@ -742,8 +729,8 @@ {#if row === null} {#each $tableColumns as col} @@ -760,7 +747,9 @@ {index} id={row?.$id} virtualItem={item} - select={rowSelection}> + select={rowSelection} + showSelectOnHover + valueWithoutHover={row.$sequence}> {#each $tableColumns as { id: columnId, isEditable } (columnId)} {@const rowColumn = $columns.find((col) => col.key === columnId)} @@ -838,6 +827,7 @@ {: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} @@ -868,6 +858,8 @@ {:else if formatted === 'null'} + {:else if isEmptyArray} + {:else} {formatted} @@ -878,7 +870,7 @@ paginatedRows.update(index, row.detail)} on:revert={(row) => @@ -985,65 +977,65 @@ title={selectedRows.length === 1 ? 'Delete Row' : 'Delete Rows'}> {@const isSingle = selectedRowForDelete !== null} -
+

{#if isSingle} - Are you sure you want to delete this document from {$table.name}? + Are you sure you want to delete this row from {$table.name}? {:else} Are you sure you want to delete {selectedRows.length} {selectedRows.length > 1 ? 'rows' : 'row'} from {$table.name}? {/if} +

- {#if relatedColumns?.length} - - - Relation - Setting - - - {#each relatedColumns as attr} - - - - {#if attr.twoWay} - - {:else} - - {/if} - {attr.key} - - - - {attr.onDelete} - - - {Deletion[attr.onDelete]} - - - {/each} - -
- To change the selection edit the relationship settings. -
    - - Delete {isSingle ? 'document' : 'documents'} from - {$table.name} - -
-
- {:else} -

This action is irreversible.

- {/if} -
+ {#if relatedColumns?.length} + + + Relation + Setting + + + {#each relatedColumns as attr} + + + + {#if attr.twoWay} + + {:else} + + {/if} + {attr.key} + + + + {attr.onDelete} + + + {Deletion[attr.onDelete]} + + + {/each} + +
+ To change the selection edit the relationship settings. +
    + + Delete {isSingle ? 'document' : 'documents'} from + {$table.name} + +
+
+ {:else} +

This action is irreversible.

+ {/if}