From 3ed568d777b33f37f6f5a44d012eb0dffb6aa885 Mon Sep 17 00:00:00 2001 From: Darshan Date: Tue, 16 Sep 2025 19:42:34 +0530 Subject: [PATCH] add: some nice stuff. --- package.json | 2 +- pnpm-lock.yaml | 10 +- .../database-[database]/createTable.svelte | 8 +- .../database-[database]/icon/ai.svelte | 19 +- .../databases/database-[database]/store.ts | 12 + .../database-[database]/subNavigation.svelte | 5 +- .../database-[database]/suggestions.svelte | 39 +- .../table-[table]/+page.svelte | 4 + .../layout/suggestionsEmptySheet.svelte | 468 ++++++++++++++++++ 9 files changed, 524 insertions(+), 43 deletions(-) create mode 100644 src/routes/(console)/project-[region]-[project]/databases/database-[database]/table-[table]/layout/suggestionsEmptySheet.svelte diff --git a/package.json b/package.json index cd4e9b060..183d358a8 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@6031134", + "@appwrite.io/console": "https://pkg.pr.new/appwrite-labs/cloud/@appwrite.io/console@2407", "@appwrite.io/pink-icons": "0.25.0", "@appwrite.io/pink-icons-svelte": "https://pkg.vc/-/@appwrite/@appwrite.io/pink-icons-svelte@f4da718", "@appwrite.io/pink-legacy": "^1.0.3", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 747776222..3a65d67e9 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@6031134 - version: https://pkg.pr.new/appwrite-labs/cloud/@appwrite.io/console@6031134 + specifier: https://pkg.pr.new/appwrite-labs/cloud/@appwrite.io/console@2407 + version: https://pkg.pr.new/appwrite-labs/cloud/@appwrite.io/console@2407 '@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@6031134': - resolution: {tarball: https://pkg.pr.new/appwrite-labs/cloud/@appwrite.io/console@6031134} + '@appwrite.io/console@https://pkg.pr.new/appwrite-labs/cloud/@appwrite.io/console@2407': + resolution: {tarball: https://pkg.pr.new/appwrite-labs/cloud/@appwrite.io/console@2407} version: 1.10.0 '@appwrite.io/pink-icons-svelte@2.0.0-RC.1': @@ -3700,7 +3700,7 @@ snapshots: '@analytics/type-utils@0.6.2': {} - '@appwrite.io/console@https://pkg.pr.new/appwrite-labs/cloud/@appwrite.io/console@6031134': {} + '@appwrite.io/console@https://pkg.pr.new/appwrite-labs/cloud/@appwrite.io/console@2407': {} '@appwrite.io/pink-icons-svelte@2.0.0-RC.1(svelte@5.25.3)': dependencies: diff --git a/src/routes/(console)/project-[region]-[project]/databases/database-[database]/createTable.svelte b/src/routes/(console)/project-[region]-[project]/databases/database-[database]/createTable.svelte index 2a5f509eb..e6d7dd08e 100644 --- a/src/routes/(console)/project-[region]-[project]/databases/database-[database]/createTable.svelte +++ b/src/routes/(console)/project-[region]-[project]/databases/database-[database]/createTable.svelte @@ -10,7 +10,6 @@ import { createEventDispatcher } from 'svelte'; import { subNavigation } from '$lib/stores/database'; import Suggestions from './suggestions.svelte'; - import type { TableColumnSuggestions } from './suggestions.svelte'; let { showCreate = $bindable(false) @@ -26,11 +25,6 @@ let touchedId = $state(false); let error: string = $state(null); - let suggestions: TableColumnSuggestions = $state({ - enabled: false, - context: null - }) - const create = async () => { error = null; try { @@ -111,7 +105,7 @@ } }} /> - + diff --git a/src/routes/(console)/project-[region]-[project]/databases/database-[database]/icon/ai.svelte b/src/routes/(console)/project-[region]-[project]/databases/database-[database]/icon/ai.svelte index 07614291c..27bc31198 100644 --- a/src/routes/(console)/project-[region]-[project]/databases/database-[database]/icon/ai.svelte +++ b/src/routes/(console)/project-[region]-[project]/databases/database-[database]/icon/ai.svelte @@ -1,5 +1,8 @@ - -
+ + + -
+ diff --git a/src/routes/(console)/project-[region]-[project]/databases/database-[database]/store.ts b/src/routes/(console)/project-[region]-[project]/databases/database-[database]/store.ts index 36e755d77..c06d8be46 100644 --- a/src/routes/(console)/project-[region]-[project]/databases/database-[database]/store.ts +++ b/src/routes/(console)/project-[region]-[project]/databases/database-[database]/store.ts @@ -4,9 +4,21 @@ import type { Models } from '@appwrite.io/console'; import { derived, writable } from 'svelte/store'; import { IconChartBar, IconCloudUpload, IconCog } from '@appwrite.io/pink-icons-svelte'; +export type TableColumnSuggestions = { + enabled: boolean; + thinking: boolean; + context?: string | null; +}; + export const database = derived(page, ($page) => $page.data.database as Models.Database); export const showCreateTable = writable(false); +export const tableColumnSuggestions = writable({ + enabled: false, + context: null, + thinking: false +}); + export const tableViewColumns = writable([ { id: '$id', title: 'Table ID', type: 'string', width: 200 }, { id: 'name', title: 'Name', type: 'string', width: { min: 120 } }, 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 864a133cc..a9d1abc29 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 @@ -145,7 +145,10 @@ - +
diff --git a/src/routes/(console)/project-[region]-[project]/databases/database-[database]/suggestions.svelte b/src/routes/(console)/project-[region]-[project]/databases/database-[database]/suggestions.svelte index 38cfbec08..0baab1891 100644 --- a/src/routes/(console)/project-[region]-[project]/databases/database-[database]/suggestions.svelte +++ b/src/routes/(console)/project-[region]-[project]/databases/database-[database]/suggestions.svelte @@ -1,19 +1,14 @@ @@ -26,11 +21,12 @@ Smart column suggestions - +
+ +
@@ -39,17 +35,22 @@
- {#if suggestions.enabled} + {#if $tableColumnSuggestions.enabled}
+ bind:value={$tableColumnSuggestions.context} + placeholder="Optional: Add context to improve suggestions" />
{/if} + + 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 7a52f59e9..f52a0eb0a 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 @@ -29,8 +29,10 @@ import { IconChevronDown, IconChevronUp, IconPlus } from '@appwrite.io/pink-icons-svelte'; import type { Models } from '@appwrite.io/console'; import EmptySheet from './layout/emptySheet.svelte'; + import SuggestionsEmptySheet from './layout/suggestionsEmptySheet.svelte'; import CreateRow from './rows/create.svelte'; import { onDestroy } from 'svelte'; + import { tableColumnSuggestions } from '../store'; export let data: PageData; @@ -215,6 +217,8 @@ } }} /> {/if} + {:else if $tableColumnSuggestions.thinking} + {:else} + import { + Button, + Icon, + Layout, + Spinner, + Spreadsheet, + Typography, + FloatingActionBar + } from '@appwrite.io/pink-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'; + import { expandTabs } from '../store'; + import SpreadsheetContainer from './spreadsheet.svelte'; + import { onDestroy, onMount } from 'svelte'; + import { debounce } from '$lib/helpers/debounce'; + import { tableColumnSuggestions } from '../../store'; + + const { customColumns = [] }: { customColumns?: Column[] } = $props(); + + // can also be `__select_undefined` if `$id` needs to be covered on overlay! + const firstColumn = '$id'; + + let resizeObserver: ResizeObserver; + let spreadsheetContainer: HTMLElement; + + let hScroller: HTMLElement | null = null; + let headerElement: HTMLElement | null = null; + let rangeOverlayEl: HTMLDivElement | null = null; + + const baseColProps = { draggable: false, resizable: false }; + + const findHorizontalScroller = (root: HTMLElement | null): HTMLElement | null => { + let el = root as HTMLElement | null; + while (el && el !== document.body) { + const st = getComputedStyle(el); + if ( + (st.overflowX === 'auto' || st.overflowX === 'scroll') && + el.scrollWidth > el.clientWidth + ) { + return el; + } + el = el.parentElement as HTMLElement | null; + } + return null; + }; + + const updateOverlayHeight = () => { + if (!spreadsheetContainer) return; + if (!headerElement || !headerElement.isConnected) { + headerElement = spreadsheetContainer.querySelector('[role="rowheader"]'); + } + if (!headerElement) return; + + const headerRect = headerElement.getBoundingClientRect(); + const containerRect = spreadsheetContainer.getBoundingClientRect(); + const viewportHeight = window.innerHeight; + const dynamicHeightPx = viewportHeight - headerRect.bottom; + const overlayTop = Math.round(headerRect.bottom - containerRect.top); + const overlayHeight = $expandTabs + ? `${dynamicHeightPx}px` + : `calc(${dynamicHeightPx}px - 89px)`; + + spreadsheetContainer.style.setProperty('--overlay-top', `${overlayTop}px`); + spreadsheetContainer.style.setProperty('--overlay-height', overlayHeight); + }; + + // Measure first content header + // and the cell before `actions`|`empty` + const updateOverlayBounds = () => { + if (!spreadsheetContainer) return; + if (!headerElement || !headerElement.isConnected) { + headerElement = spreadsheetContainer.querySelector('[role="rowheader"]'); + if (!headerElement) return; + } + + // hook the actual horizontal scroller once + if (!hScroller || !hScroller.isConnected) { + hScroller = findHorizontalScroller(headerElement); + if (hScroller) hScroller.addEventListener('scroll', debouncedRecalc, { passive: true }); + } + + const containerRect = spreadsheetContainer.getBoundingClientRect(); + const getById = (id: string) => + headerElement!.querySelector( + `[role="cell"][data-header="true"][data-column-id="${id}"]` + ); + + const firstColumnCell = getById(firstColumn); + const actionsCell = getById('actions'); + const emptyCell = getById('empty'); + + // start = first content cell after `firstColumn` + // (skip actions/empty if they were there) + let startCell: HTMLElement | null = null; + if (firstColumnCell) { + let node = firstColumnCell.nextElementSibling as HTMLElement | null; + while ( + node && + (node.dataset.columnId === 'actions' || node.dataset.columnId === 'empty') + ) { + node = node.nextElementSibling as HTMLElement | null; + } + startCell = node; + } + + // end = cell right BEFORE actions; + // else right BEFORE empty; else last content cell + const before = (el: HTMLElement | null) => { + if (!el) return null; + let previous = el.previousElementSibling as HTMLElement | null; + while ( + previous && + (previous.dataset.columnId === firstColumn || + previous.getAttribute('data-select') === 'true') + ) { + previous = previous.previousElementSibling as HTMLElement | null; + } + return previous; + }; + + let endCell = before(actionsCell) ?? before(emptyCell); + if (!endCell) { + const cells = Array.from( + headerElement.querySelectorAll('[role="cell"][data-header="true"]') + ); + for (let index = cells.length - 1; index >= 0; index--) { + const cell = cells[index]; + const cellId = cell.dataset.columnId; + const isSelect = cell.getAttribute('data-select') === 'true'; + if (!isSelect && cellId !== firstColumn && cellId !== 'actions' && cellId !== 'empty') { + endCell = cell; + break; + } + } + } + + if (!startCell || !endCell) { + if (rangeOverlayEl) rangeOverlayEl.style.display = 'none'; + return; + } + + const start = startCell.getBoundingClientRect(); + const end = endCell.getBoundingClientRect(); + const left = Math.round(start.left - containerRect.left); + const width = Math.max(0, Math.round(end.right - end.left)); + + spreadsheetContainer.style.setProperty('--group-left', `${left}px`); + spreadsheetContainer.style.setProperty('--group-width', `${width}px`); + + if (rangeOverlayEl) rangeOverlayEl.style.display = width > 0 ? 'block' : 'none'; + }; + + const recalcAll = () => { + updateOverlayHeight(); + updateOverlayBounds(); + }; + + const debouncedRecalc = debounce(recalcAll, 50); + + onMount(() => { + if (spreadsheetContainer) { + resizeObserver = new ResizeObserver(debouncedRecalc); + resizeObserver.observe(spreadsheetContainer); + } + + requestAnimationFrame(recalcAll); + }); + + onDestroy(() => { + resizeObserver?.disconnect(); + hScroller?.removeEventListener('scroll', debouncedRecalc); + }); + + const getCustomColumns = (): Column[] => + customColumns.map((col: Column) => ({ ...col, width: 180, hide: false, ...baseColProps })); + + const getRowColumns = (): Column[] => [ + { + id: '$id', + title: '$id', + type: 'string', + width: 180, + icon: IconFingerPrint, + ...baseColProps + }, + ...getCustomColumns(), + { + id: '$createdAt', + title: '$createdAt', + type: 'datetime', + width: 180, + icon: IconCalendar, + ...baseColProps + }, + { + id: '$updatedAt', + title: '$updatedAt', + type: 'datetime', + width: 180, + icon: IconCalendar, + ...baseColProps + }, + { + id: 'actions', + title: '', + type: 'string', + icon: IconPlus, + width: customColumns.length ? 555 : 832, + ...baseColProps + }, + { id: 'empty', title: '', type: 'string', ...baseColProps } + ]; + + const spreadsheetColumns = getRowColumns(); + const emptyCells = $derived(($isSmallViewport ? 14 : 17) + (!$expandTabs ? 2 : 0)); + + + + +
+
+ + + {#if $tableColumnSuggestions.thinking} +
+ + + + + + Thinking of column suggestions + + + + + { + $tableColumnSuggestions.context = null; + $tableColumnSuggestions.enabled = false; + $tableColumnSuggestions.thinking = false; + }} + >Cancel + + + +
+ {/if} +
+ + + {}}> + + {#each spreadsheetColumns as column (column.id)} + + {#if column.isAction} + + + + {:else if column.id === 'actions' || column.id === 'empty'} + {column.title} + {:else} + + {column.title} + + + {/if} + + {/each} + + + + +
+
+
+ +