update: redefined empty state [wip].

This commit is contained in:
Darshan
2025-09-23 20:08:40 +05:30
parent 07f223fc1e
commit 62497943a5
4 changed files with 635 additions and 137 deletions
@@ -1,12 +1,14 @@
<script lang="ts">
import {
ActionMenu,
Button,
Icon,
Layout,
Spinner,
Spreadsheet,
Typography,
FloatingActionBar
FloatingActionBar,
Popover
} from '@appwrite.io/pink-svelte';
import { IconCalendar, IconFingerPrint, IconPlus } from '@appwrite.io/pink-icons-svelte';
import { isSmallViewport } from '$lib/stores/viewport';
@@ -14,33 +16,30 @@
import type { Column } from '$lib/helpers/types';
import { expandTabs } from '../table-[table]/store';
import SpreadsheetContainer from '../table-[table]/layout/spreadsheet.svelte';
import { onDestroy, onMount } from 'svelte';
import { debounce } from '$lib/helpers/debounce';
import { onDestroy, onMount, tick } from 'svelte';
import { sdk } from '$lib/stores/sdk';
import { page } from '$app/state';
import {
type ColumnInput,
mapSuggestedColumns,
mockSuggestions,
type SuggestedColumnSchema,
tableColumnSuggestions
tableColumnSuggestions,
basicColumnOptions
} from './store';
import { addNotification } from '$lib/stores/notifications';
import { Submit, trackError, trackEvent } from '$lib/actions/analytics';
import { sleep } from '$lib/helpers/promises';
import { invalidate } from '$app/navigation';
import { Dependencies } from '$lib/constants';
import type { Columns } from '../table-[table]/store';
const {
onColumnsFinalized = undefined
}: {
onColumnsFinalized?: (columns: SuggestedColumnSchema[]) => void | Promise<void>;
} = $props();
import { isDev } from '$lib/system';
import { columnOptions } from '../table-[table]/columns/store';
import Options from './options.svelte';
import { InputSelect, InputText } from '$lib/elements/forms';
/**
* flip this when you want to
* exclude or include the `$id` for colored overlay!
*/
const useFirstColumnAsId = false;
// can also be `__select_undefined` if `$id` needs to be covered on overlay!
const firstColumn = useFirstColumnAsId ? '$id' : '__select_undefined';
// No props needed - we handle column creation directly
let resizeObserver: ResizeObserver;
let spreadsheetContainer: HTMLElement;
@@ -49,6 +48,11 @@
let headerElement: HTMLElement | null = null;
let rangeOverlayEl: HTMLDivElement | null = null;
let customColumns = $state([]);
let showFloatingBar = $state(true);
let hasTransitioned = $state(false);
let scrollAnimationFrame: number | null = null;
let creatingColumns = $state(false);
const baseColProps = { draggable: false, resizable: false };
const findHorizontalScroller = (root: HTMLElement | null): HTMLElement | null => {
@@ -88,79 +92,115 @@
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;
}
if (!headerElement) return;
// hook the actual horizontal scroller once
if (!hScroller || !hScroller.isConnected) {
// TODO: @itznotabug, might not need this. check later.
const previousScrollLeft = hScroller?.scrollLeft || 0;
hScroller = findHorizontalScroller(headerElement);
if (hScroller) hScroller.addEventListener('scroll', debouncedRecalc, { passive: true });
if (hScroller) {
hScroller.addEventListener('scroll', recalcAllThrottled, { passive: true });
// Preserve scroll position when reconnecting to scroller
if (previousScrollLeft > 0) {
hScroller.scrollLeft = previousScrollLeft;
}
}
}
if (rangeOverlayEl) {
rangeOverlayEl.style.display = 'block';
}
if (customColumns.length === 0) {
spreadsheetContainer.style.setProperty('--group-left', '40px');
spreadsheetContainer.style.setProperty('--group-width', '100%');
return;
}
// Custom columns mode: calculate precise overlay bounds
const containerRect = spreadsheetContainer.getBoundingClientRect();
const getById = (id: string) =>
headerElement!.querySelector<HTMLElement>(
`[role="cell"][data-header="true"][data-column-id="${id}"]`
);
const firstColumnCell = getById(firstColumn);
// const emptyCell = getById('empty');
// const actionsCell = getById('actions');
// Calculate visible viewport bounds
const scrollerRect = hScroller ? hScroller.getBoundingClientRect() : containerRect;
const visibleRight = scrollerRect.right;
// 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;
// Get start boundary from $id column
// as its more reliable than first custom column
const idCell = getById('$id');
const startCell = getById(customColumns[0]?.key);
let endCell = getById(customColumns[customColumns.length - 1]?.key);
// we use a visible end column
if (endCell) {
const endRect = endCell.getBoundingClientRect();
if (endRect.left >= visibleRight) {
// Find the last column that's at least partially visible
for (let i = customColumns.length - 2; i >= 0; i--) {
const candidateCell = getById(customColumns[i]?.key);
if (
candidateCell &&
candidateCell.getBoundingClientRect().left < visibleRight
) {
endCell = candidateCell;
break;
}
}
}
startCell = node;
}
// end = cell right BEFORE actions;
// else right BEFORE empty; else last content cell
// const before = (element: HTMLElement | null) => {
// if (!element) return null;
// let previous = element.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);
// let endCell = before(emptyCell);
if (!startCell /* || !endCell*/) {
if (rangeOverlayEl) rangeOverlayEl.style.display = 'none';
if (!idCell || !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 - start.right));
// overlay positioning - start after selection column to avoid covering it
const idRect = idCell.getBoundingClientRect();
const selectionRect = spreadsheetContainer
.querySelector('[data-select="true"]')
?.getBoundingClientRect();
spreadsheetContainer.style.setProperty('--group-left', `${left}px`);
// spreadsheetContainer.style.setProperty('--group-width', `${width}px`);
// Start overlay after selection column if it exists, otherwise after $id
let startLeft = idRect.right;
if (selectionRect && selectionRect.right > idRect.right) {
startLeft = selectionRect.right;
}
// if (rangeOverlayEl) {
// rangeOverlayEl.style.display = width > 0 ? 'block' : 'none';
// }
const left = Math.round(startLeft - containerRect.left);
// maximum possible width of all custom columns
const totalFullWidth = customColumns.length * 180;
// get the actions column and use its left border as the boundary
const actionsCell = headerElement!.querySelector<HTMLElement>(
'[role="cell"][data-column-id="actions"]'
);
const rawVisibleWidth = Math.round(visibleRight - idRect.right);
let maxAllowedWidth = rawVisibleWidth;
if (actionsCell) {
const actionsRect = actionsCell.getBoundingClientRect();
const actionsLeft = actionsRect.left - containerRect.left;
maxAllowedWidth = Math.min(rawVisibleWidth, actionsLeft - left);
}
// Set overlay width to not exceed actions column boundary
const width = Math.min(totalFullWidth, maxAllowedWidth);
// Apply overlay positioning
spreadsheetContainer.style.setProperty('--group-left', `${left - 2}px`);
spreadsheetContainer.style.setProperty('--group-width', `${width + 2}px`);
};
const recalcAll = () => {
@@ -168,7 +208,39 @@
updateOverlayBounds();
};
const debouncedRecalc = debounce(recalcAll, 50);
/**
* Throttled version of recalcAll for scroll events to improve performance
*/
const recalcAllThrottled = () => {
if (scrollAnimationFrame !== null) return;
scrollAnimationFrame = requestAnimationFrame(() => {
recalcAll();
scrollAnimationFrame = null;
});
};
const customSuggestedColumns = $derived.by(() => {
return customColumns.map((col: SuggestedColumnSchema) => {
const columnOption = getColumnOption(col.type, col.format);
return {
id: col.key,
title: col.key,
type: col.type as Column['type'],
format: col.format,
required: col.required,
default: col.default,
size: col.size,
min: col.min,
max: col.max,
width: 180,
icon: columnOption?.icon,
draggable: false,
resizable: false
};
});
});
const getRowColumns = (): Column[] => [
{
@@ -179,39 +251,43 @@
icon: IconFingerPrint,
...baseColProps
},
{
id: '$createdAt',
title: '$createdAt',
type: 'datetime',
width: 180,
icon: IconCalendar,
...baseColProps
},
{
id: '$updatedAt',
title: '$updatedAt',
type: 'datetime',
width: 180,
icon: IconCalendar,
...baseColProps
},
...customSuggestedColumns,
...(customColumns.length === 0
? [
{
id: '$createdAt',
title: '$createdAt',
type: 'datetime' as Column['type'],
width: 180,
icon: IconCalendar,
...baseColProps
},
{
id: '$updatedAt',
title: '$updatedAt',
type: 'datetime' as Column['type'],
width: 180,
icon: IconCalendar,
...baseColProps
}
]
: []),
{
id: 'actions',
title: '',
type: 'string',
icon: IconPlus,
width: 832,
type: 'string' as Column['type'],
width: 40,
isAction: true,
...baseColProps
},
{ id: 'empty', title: '', type: 'string', ...baseColProps }
}
];
const spreadsheetColumns = getRowColumns();
const spreadsheetColumns = $derived(getRowColumns());
const emptyCells = $derived(($isSmallViewport ? 14 : 17) + (!$expandTabs ? 2 : 0));
onMount(async () => {
if (spreadsheetContainer) {
resizeObserver = new ResizeObserver(debouncedRecalc);
resizeObserver = new ResizeObserver(recalcAll);
resizeObserver.observe(spreadsheetContainer);
}
@@ -223,23 +299,31 @@
$tableColumnSuggestions.thinking = true;
try {
const suggestedColumns = (await sdk
.forProject(page.params.region, page.params.project)
.console.suggestColumns({
databaseId: page.params.database,
tableId: page.params.table,
context: $tableColumnSuggestions.context ?? undefined
})) as unknown as {
total: number;
columns: ColumnInput[];
};
await sleep(1250);
const suggestedColumns = isDev
? mockSuggestions
: ((await sdk
.forProject(page.params.region, page.params.project)
.console.suggestColumns({
databaseId: page.params.database,
tableId: page.params.table,
context: $tableColumnSuggestions.context ?? undefined
})) as unknown as {
total: number;
columns: ColumnInput[];
});
trackEvent(Submit.ColumnSuggestions, {
total: suggestedColumns.total,
tableName: $tableColumnSuggestions.table?.name ?? undefined
});
await onColumnsFinalized(mapSuggestedColumns(suggestedColumns.columns));
customColumns = mapSuggestedColumns(suggestedColumns.columns);
// Set hasTransitioned to disable future animations after initial state change
if (customColumns.length > 0) {
setTimeout(() => (hasTransitioned = true), 300); // After transition completes
}
} catch (error) {
addNotification({
type: 'error',
@@ -255,25 +339,186 @@
}
}
function onPopoverShowStateChanged(value: boolean) {
showFloatingBar = !value;
const currentScrollLeft = hScroller?.scrollLeft || 0;
tick().then(() => {
if (hScroller) {
hScroller.scrollLeft = currentScrollLeft;
}
});
}
function updateColumn(columnId: string, updates: Partial<SuggestedColumnSchema>) {
const columnIndex = customColumns.findIndex((col) => col.key === columnId);
if (columnIndex !== -1) {
customColumns[columnIndex] = { ...customColumns[columnIndex], ...updates };
}
}
function getColumn(columnId: string) {
return customColumns.find((col) => col.key === columnId);
}
function getColumnOption(type: string, format?: string | null) {
return columnOptions.find(
(option) => option.type === type && (format ? option.format === format : !option.format)
);
}
async function createColumns() {
creatingColumns = true;
const client = sdk.forProject(page.params.region, page.params.project);
try {
const results = [];
for (const column of customColumns) {
const baseParams = {
databaseId: page.params.database,
tableId: page.params.table,
key: column.key,
required: column.required || false
};
let columnResult: Columns;
switch (column.type) {
case 'string':
if (column.format) {
switch (column.format) {
case 'email':
columnResult =
await client.tablesDB.createEmailColumn(baseParams);
break;
case 'ip':
columnResult = await client.tablesDB.createIpColumn(baseParams);
break;
case 'url':
columnResult =
await client.tablesDB.createUrlColumn(baseParams);
break;
case 'enum':
columnResult = await client.tablesDB.createEnumColumn({
...baseParams,
elements: []
});
break;
default:
columnResult = await client.tablesDB.createStringColumn({
...baseParams,
size: column.size || 255,
xdefault: (column.default as string) || null
});
break;
}
} else {
columnResult = await client.tablesDB.createStringColumn({
...baseParams,
size: column.size || 255,
xdefault: (column.default as string) || null
});
}
break;
case 'integer':
columnResult = await client.tablesDB.createIntegerColumn({
...baseParams,
min: column.min,
max: column.max,
xdefault: (column.default as number) || null
});
break;
case 'double':
columnResult = await client.tablesDB.createFloatColumn({
...baseParams,
min: column.min,
max: column.max,
xdefault: (column.default as number) || null
});
break;
case 'boolean':
columnResult = await client.tablesDB.createBooleanColumn({
...baseParams,
xdefault: (column.default as boolean) || null
});
break;
case 'datetime':
columnResult = await client.tablesDB.createDatetimeColumn({
...baseParams,
xdefault: (column.default as string) || null
});
break;
case 'point':
columnResult = await client.tablesDB.createPointColumn(baseParams);
break;
case 'linestring':
columnResult = await client.tablesDB.createLineColumn(baseParams);
break;
case 'polygon':
columnResult = await client.tablesDB.createPolygonColumn(baseParams);
break;
}
results.push(columnResult);
}
await invalidate(Dependencies.TABLE);
// Reset state
customColumns = [];
$tableColumnSuggestions.context = null;
$tableColumnSuggestions.enabled = false;
addNotification({
type: 'success',
message: 'Columns created successfully'
});
trackEvent(Submit.ColumnCreate, { type: 'suggestions' });
} catch (error) {
trackError(error, Submit.ColumnCreate);
addNotification({
type: 'error',
message: error.message
});
} finally {
creatingColumns = false;
}
}
onDestroy(() => {
resizeObserver?.disconnect();
hScroller?.removeEventListener('scroll', debouncedRecalc);
hScroller?.removeEventListener('scroll', recalcAllThrottled);
if (scrollAnimationFrame) {
cancelAnimationFrame(scrollAnimationFrame);
}
});
</script>
<svelte:window on:resize={debouncedRecalc} on:scroll={debouncedRecalc} />
<svelte:window on:resize={recalcAll} on:scroll={recalcAll} />
<div bind:this={spreadsheetContainer} class="databases-spreadsheet spreadsheet-container-outer">
<div
bind:this={spreadsheetContainer}
class="databases-spreadsheet spreadsheet-container-outer"
class:custom-columns={customColumns.length > 0}>
<div>
<div
aria-hidden="true"
bind:this={rangeOverlayEl}
class="columns-range-overlay"
class:thinking={$tableColumnSuggestions.thinking}>
class:thinking={$tableColumnSuggestions.thinking}
class:no-transition={hasTransitioned && customColumns.length > 0}>
</div>
{#if $tableColumnSuggestions.thinking}
<div class="floating-action-wrapper">
<div class="floating-action-wrapper thinking">
<FloatingActionBar>
<svelte:fragment slot="start">
<Layout.Stack direction="row" gap="xxs" alignItems="center">
@@ -297,6 +542,41 @@
</svelte:fragment>
</FloatingActionBar>
</div>
{:else if customColumns.length > 0 && showFloatingBar}
<div class="floating-action-wrapper expanded">
<FloatingActionBar>
<svelte:fragment slot="start">
{#if creatingColumns}
<Spinner size="s" />
{/if}
<Typography.Text style="white-space: nowrap">
{creatingColumns
? 'Creating columns...'
: 'Review and edit suggested columns before applying'}
</Typography.Text>
</svelte:fragment>
<svelte:fragment slot="end">
{#if !creatingColumns}
<Layout.Stack direction="row" gap="xs" alignItems="center">
<Button.Button
size="xs"
variant="text"
on:click={() => {
customColumns = [];
$tableColumnSuggestions.context = null;
$tableColumnSuggestions.enabled = false;
}}
>Dismiss
</Button.Button>
<Button.Button size="xs" variant="primary" on:click={createColumns}
>Apply
</Button.Button>
</Layout.Stack>
{/if}
</svelte:fragment>
</FloatingActionBar>
</div>
{/if}
</div>
@@ -308,28 +588,130 @@
columns={spreadsheetColumns}
bottomActionClick={() => {}}>
<svelte:fragment slot="header" let:root>
{#each spreadsheetColumns as column (column.id)}
<Spreadsheet.Header.Cell
{root}
column={column.id}
icon={column.icon ?? undefined}>
{#if column.isAction}
{#each spreadsheetColumns as column, index (index)}
{#if column.isAction}
<Spreadsheet.Header.Cell column="actions" {root}>
<Button.Button icon variant="extra-compact">
<Icon icon={IconPlus} color="--fgcolor-neutral-primary" />
</Button.Button>
{:else if column.id === 'actions' || column.id === 'empty'}
{column.title}
{:else}
<Layout.Stack
gap="xs"
direction="row"
alignItems="center"
alignContent="center">
{column.title}
<SortButton disabled column={column.id} />
</Layout.Stack>
{/if}
</Spreadsheet.Header.Cell>
</Spreadsheet.Header.Cell>
{:else}
<Options onShowStateChanged={onPopoverShowStateChanged}>
{#snippet children(toggle)}
<Spreadsheet.Header.Cell
{root}
column={column.id}
on:contextmenu={toggle}>
<Layout.Stack
direction="row"
alignItems="center"
alignContent="center"
justifyContent="space-between">
<Layout.Stack
gap="xs"
direction="row"
alignItems="center"
alignContent="center">
{column.title}
<SortButton disabled column={column.id} />
</Layout.Stack>
<Popover let:toggle portal>
<Button.Button
size="xs"
variant="extra-compact"
on:click={toggle}>
<Icon
size="s"
color="--fgcolor-neutral-weak"
icon={column.icon ?? undefined} />
</Button.Button>
<div
let:toggle
slot="tooltip"
class="actions-menu-wrapper"
style:max-height="184px">
<ActionMenu.Root maxWidth="232px">
{#each basicColumnOptions as option}
<ActionMenu.Item.Button
on:click={() => {
toggle();
updateColumn(column.id, {
type: option.type,
format: option.format || null
});
}}>
<Layout.Stack
gap="s"
direction="row"
alignContent="center">
<Icon icon={option.icon} />
{option.name}
</Layout.Stack>
</ActionMenu.Item.Button>
{/each}
</ActionMenu.Root>
</div>
</Popover>
</Layout.Stack>
</Spreadsheet.Header.Cell>
{/snippet}
{#snippet tooltipChildren()}
{@const columnObj = getColumn(column.id)}
{#if columnObj}
{@const selectedOption = getColumnOption(
columnObj.type,
columnObj.format
)}
{@const ColumnComponent = selectedOption?.component}
<Layout.Stack gap="xl">
<Layout.Stack gap="xl">
<Layout.Stack direction="row">
<InputText
id="key"
label="Key"
placeholder="Enter key"
bind:value={columnObj.key}
autofocus
required
pattern="^[A-Za-z0-9][A-Za-z0-9._\-]*$" />
<InputSelect
id="type"
required
label="Type"
value={selectedOption?.name || 'String'}
on:change={(e) => {
const newOption = columnOptions.find(
(opt) => opt.name === e.detail
);
if (newOption) {
updateColumn(column.id, {
type: newOption.type,
format: newOption.format || null
});
}
}}
options={basicColumnOptions.map((col) => {
return {
label: col.name,
value: col.name,
leadingIcon: col.icon
};
})} />
</Layout.Stack>
</Layout.Stack>
{#if ColumnComponent}
<ColumnComponent data={columnObj} />
{/if}
</Layout.Stack>
{/if}
{/snippet}
</Options>
{/if}
{/each}
</svelte:fragment>
</Spreadsheet.Root>
@@ -338,7 +720,7 @@
<div
class="spreadsheet-fade-bottom"
data-collapsed-tabs={!$expandTabs}
style="height: var(--overlay-height)">
style="height: var(--overlay-height);">
</div>
</div>
@@ -347,27 +729,30 @@
width: 100%;
position: fixed;
overflow: hidden;
scrollbar-width: none;
&.custom-columns {
width: unset;
}
& :global([role='rowheader'] :nth-last-child(2) [role='presentation']) {
display: none;
}
.columns-range-overlay {
width: 100%;
position: absolute;
margin-block-start: 2px;
left: var(--group-left, 0px);
height: calc(var(--overlay-height, 60vh) + 36px);
border-radius: var(--border-radius-S, 8px);
width: var(--group-width, 100%);
height: 100%;
background: color-mix(in oklab, #fd366e 7%, transparent);
pointer-events: none;
z-index: 21;
overflow: hidden;
transition: all 0.3s cubic-bezier(0.25, 0.46, 0.45, 0.94);
/* border + inner hairline without double-counting */
box-shadow:
0 0 0 var(--border-width-L, 2px) #fd366e,
inset 0 0 0 1px color-mix(in oklab, #fe9567 20%, transparent);
&.no-transition {
transition: none !important;
}
/* pretty gradient wash (with fallback) */
background: rgba(253, 54, 110, 0.07);
@@ -380,6 +765,15 @@
}
&.thinking {
margin-block-start: 2px;
height: calc(100% - 4px);
border-radius: var(--border-radius-S, 8px);
box-shadow:
0 0 0 var(--border-width-l, 2px) #fd366e,
inset 0 0 0 1px color-mix(in oklab, #fe9567 20%, transparent);
transition: all 0.3s cubic-bezier(0.25, 0.46, 0.45, 0.94);
&::after {
content: '';
position: absolute;
@@ -405,15 +799,28 @@
); /* change this value if the firstColumn is changed for overlay logic.*/
}
& .floating-action-wrapper.expanded :global(:first-child) {
z-index: 21;
left: calc(50% - 80px);
max-width: 500px !important;
}
& :global(.spreadsheet-container) {
overflow-x: hidden;
overflow-y: hidden;
scrollbar-width: none;
}
& :global([data-select='true']) {
opacity: 0.85;
pointer-events: none;
}
/* alternative selector for header selection */
& :global(.sticky-header [data-select='true']) {
opacity: 1;
pointer-events: none;
}
}
.spreadsheet-fade-bottom {
@@ -430,6 +837,7 @@
display: flex;
justify-content: center;
transition: height 300ms cubic-bezier(0.4, 0, 0.2, 1);
pointer-events: none;
}
:global(.theme-dark) .spreadsheet-fade-bottom {
@@ -0,0 +1,24 @@
<script lang="ts">
import type { Snippet } from 'svelte';
import { Popover } from '@appwrite.io/pink-svelte';
let {
children,
tooltipChildren,
onShowStateChanged = null
}: {
children: Snippet<[toggle: (event: Event) => void]>;
tooltipChildren: Snippet<[toggle: (event: Event) => void]>;
onShowStateChanged?: (showing: boolean) => void;
} = $props();
</script>
<Popover let:toggle let:showing placement="bottom-start" portal>
{onShowStateChanged?.(showing)}
{@render children(toggle)}
<div slot="tooltip" let:toggle style:width="480px" style:padding="16px">
{@render tooltipChildren(toggle)}
</div>
</Popover>
@@ -1,4 +1,5 @@
import { writable } from 'svelte/store';
import { columnOptions } from '$routes/(console)/project-[region]-[project]/databases/database-[database]/table-[table]/columns/store';
export type TableColumnSuggestions = {
enabled: boolean;
@@ -30,6 +31,73 @@ export const tableColumnSuggestions = writable<TableColumnSuggestions>({
table: null
});
// TODO: @itznotabug, remove later, this is MOCK DATA ONLY.
export const mockSuggestions: { total: number; columns: ColumnInput[] } = {
total: 7,
columns: [
{
name: 'title',
type: 'string',
size: 255,
format: null,
required: true,
formatOptions: null
},
{
name: 'authorName',
type: 'string',
size: 128,
format: null,
required: true,
formatOptions: null
},
{
name: 'publishedYear',
type: 'integer',
size: null,
format: null,
required: true,
formatOptions: {
min: 1500,
max: null
}
},
{
name: 'genre',
type: 'string',
size: 64,
format: null,
required: false,
formatOptions: null,
default: null
},
{
name: 'isbn',
type: 'string',
size: 13,
required: false,
formatOptions: null,
default: null
},
{
name: 'language',
type: 'string',
size: 32,
format: null,
required: false,
formatOptions: null,
default: null
},
{
name: 'pageCount',
type: 'integer',
required: false,
min: 1,
max: 10000,
default: null
}
]
};
export type ColumnInput = {
name: string;
type: string;
@@ -63,3 +131,5 @@ export function mapSuggestedColumns<T extends ColumnInput>(columns: T[]): Sugges
format: col.format ?? null
}));
}
export const basicColumnOptions = columnOptions.slice(0, -1);
@@ -225,11 +225,7 @@
}} />
{/if}
{:else if $tableColumnSuggestions.enabled && $tableColumnSuggestions.table && $tableColumnSuggestions.table.id === page.params.table}
<SuggestionsEmptySheet
onColumnsFinalized={(columns) => {
showSuggestionsModal = true;
columnSuggestionsSchema = columns;
}} />
<SuggestionsEmptySheet />
{:else}
<EmptySheet
mode="rows"