Merge remote-tracking branch 'origin/spreadsheet' into dat-606

This commit is contained in:
ArnabChatterjee20k
2025-08-18 10:20:47 +05:30
15 changed files with 298 additions and 191 deletions
+1 -1
View File
@@ -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",
+5 -5
View File
@@ -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
+6 -5
View File
@@ -20,7 +20,8 @@
ui = 'legacy',
allowNoColumns = false,
showAnyway = false,
children
children,
onPreferencesUpdated = null
}: {
columns: Writable<Column[]>;
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</Button>
</Layout.Stack>
{:else}
<div style:padding-inline="0.6rem">
<Typography.Text>
No column named "{search}" was found
</Typography.Text>
<div style:padding-inline="0.6rem" style:padding-block-end="0.5rem">
<Typography.Text>No results found</Typography.Text>
</div>
{/if}
</Layout.Stack>
+56 -13
View File
@@ -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<Column[]>;
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<Column[]>;
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);
});
</script>
{#if !hideColumns && view === View.Table}
<ColumnSelector {columns} {isCustomTable} {allowNoColumns} {ui} {showAnyway}>
<ColumnSelector
{ui}
{columns}
{showAnyway}
{isCustomTable}
{allowNoColumns}
onPreferencesUpdated={updateBadgeState}>
{#snippet children(toggle, selectedColumnsNumber)}
<Button.Button
size="s"
icon={onlyIcon}
onclick={toggle}
variant="secondary"
class={onlyIcon && !$isSmallViewport ? 'width-fix' : undefined}
disabled={!$columns.length && showAnyway}
badge={selectedColumnsNumber.toString()}
on:click={toggle}>
class={onlyIcon && !$isSmallViewport ? 'width-fix' : undefined}
badge={showCountBadge ? selectedColumnsNumber.toString() : undefined}>
<Icon slot="start" icon={IconViewBoards} />
</Button.Button>
{/snippet}
+1 -1
View File
@@ -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);
+6 -6
View File
@@ -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 } } : {}) }
];
</script>
<!-- TODO: only the databases page -->
<Container expanded={databasesScreen} slotSpacing={databasesScreen} {databasesScreen}>
{#if logs.total}
<div>
+2 -9
View File
@@ -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)');
</script>
<Layout.Stack justifyContent="center" alignItems="center" direction="row" gap="xs" inline>
{#if href}
{#if href && !$isSmallViewport}
<span style:position="relative">
<Button.Anchor size={buttonSize} icon variant="text" {href} aria-label="page back">
<Icon icon={IconChevronLeft} />
+20 -4
View File
@@ -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<void> {
@@ -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<T>(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;
});
}
};
}
@@ -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 @@
</a>
<div class="collection-content">
{#if tables?.total}
<ul class="drop-list u-margin-inline-start-8 u-margin-block-start-8">
<ul class="drop-list u-margin-inline-start-8 u-margin-block-start-4">
{#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}
<Layout.Stack gap="m" direction="row" alignItems="center">
<Layout.Stack gap="s" direction="row" alignItems="center">
<li
class:is-last={isLast}
class:is-first={isFirst}
@@ -99,7 +99,10 @@
</div>
{/if}
<Layout.Stack alignItems="center" direction="row" style="gap: 3px;">
<Layout.Stack
alignItems="center"
direction="row"
style="gap: 3px; margin-block-start: 8px;">
<Icon icon={IconPlus} size="s" />
<Button
compact
@@ -120,18 +123,18 @@
<Icon icon={IconDatabase} size="s" color="--neutral-300" />
<Link.Anchor
href={`${base}/project-${region}-${project}/databases/database-${databaseId}`}
variant="quiet-muted">{data.database.name}</Link.Anchor>
variant="quiet-muted">
{data.database.name}
</Link.Anchor>
<span style:margin-left="8px">/</span>
<button
type="button"
class="trigger"
aria-label="Open collections"
onclick={() => {
openBottomSheet = !openBottomSheet;
}}>
<span class="orgName">{selectedTable?.name}</span>
<Button text extraCompact on:click={() => (openBottomSheet = !openBottomSheet)}>
<Typography.Text variant="m-400">
{selectedTable?.name}
</Typography.Text>
<Icon icon={IconChevronDown} size="s" />
</button>
</Button>
</Layout.Stack>
</div>
</Navbar.Base>
@@ -166,17 +169,16 @@
<style lang="scss">
.list-container {
display: flex;
flex-direction: column;
height: 100%;
display: flex;
min-height: 0;
flex-direction: column;
}
.database-name {
margin-block-end: 8px;
font-size: var(--font-size-sm);
color: var(--fgcolor-neutral-secondary);
margin-block-end: 2px;
}
.collection-content {
@@ -212,6 +214,7 @@
.drop-list {
flex: 1;
gap: 8px;
padding-left: 4px;
position: relative;
font-size: var(--font-size-sm);
@@ -228,12 +231,12 @@
// first item
&:has(li.is-first)::before {
top: 0.9rem;
top: 0.8rem;
}
// last item
&:has(li.is-last)::before {
bottom: 0.9rem;
bottom: 0.85rem;
}
// the only item
@@ -72,8 +72,6 @@
const tableId = page.params.table;
onMount(() => {
columnsOrder.set(preferences.getColumnOrder(tableId));
columnsWidth.set(preferences.getColumnWidths(tableId));
expandTabs.set(preferences.isTableHeaderExpanded(tableId));
return realtime
@@ -384,7 +382,7 @@
externalColumnKey={$showCreateIndexSheet.column} />
</SideSheet>
<SideSheet title="Row activity" bind:show={$rowActivitySheet.show} closeOnBlur>
<SideSheet spaced title="Row activity" bind:show={$rowActivitySheet.show} closeOnBlur>
<RowActivity />
</SideSheet>
@@ -102,6 +102,7 @@
let showDelete = $state(false);
let selectedColumns = $state([]);
let selectedColumn: Columns = $state(null);
let columnIndexMap: Record<string, boolean> = $state({});
let columnsOrder = $state([]);
const tableId = page.params.table;
@@ -161,6 +162,18 @@
tick().then(() => spreadsheetContainer?.resizeSheet());
}
});
$effect(() => {
if (!$showCreateIndexSheet.show && $showCreateIndexSheet.column) {
const columnKey = $showCreateIndexSheet.column;
const isActuallyIndexed = $indexes.some((index) => index.columns.includes(columnKey));
if (!isActuallyIndexed) {
columnIndexMap[columnKey] = false;
}
}
});
</script>
<Container
@@ -275,17 +288,34 @@
</Layout.Stack>
{@const minMaxSize = getMinMaxSizeForColumn(column)}
{#if minMaxSize}
<Typography.Caption variant="400">
<Typography.Caption
variant="400"
color="--fgcolor-neutral-tertiary">
{minMaxSize}
</Typography.Caption>
{/if}
</Layout.Stack>
</Spreadsheet.Cell>
<Spreadsheet.Cell column="indexed" {root} isEditable={false}>
{@const isIndexed = $indexes.some((index) =>
{@const isActuallyIndexed = $indexes.some((index) =>
index.columns.includes(column.key)
)}
<Selector.Checkbox size="s" checked={isIndexed} disabled />
{@const checked = isActuallyIndexed || !!columnIndexMap[column.key]}
<Selector.Checkbox
size="s"
{checked}
disabled={isActuallyIndexed}
on:change={(e) => {
if (!isActuallyIndexed) {
if (e.detail) {
columnIndexMap[column.key] = true;
$showCreateIndexSheet.show = true;
$showCreateIndexSheet.column = column.key;
}
}
}} />
</Spreadsheet.Cell>
<Spreadsheet.Cell column="default" {root} isEditable={false}>
{@const _default =
@@ -1,6 +1,7 @@
<script lang="ts">
import { invalidate } from '$app/navigation';
import { base } from '$app/paths';
import { page } from '$app/state';
import { goto, invalidate } from '$app/navigation';
import { Submit, trackError, trackEvent } from '$lib/actions/analytics';
import { Dependencies } from '$lib/constants';
import { Button, InputNumber, InputSelect, InputText } from '$lib/elements/forms';
@@ -63,6 +64,11 @@
const addColumnDisabled = $derived(!columnList.at(-1)?.value || !columnList.at(-1)?.order);
const isOnIndexesPage = $derived(page.route.id?.endsWith('/indexes'));
const navigatorPathToIndexes = $derived(
`${base}/project-${page.params.region}-${page.params.project}/databases/database-${databaseId}/table-${$table?.$id}/indexes`
);
$effect(() => {
if (showCreateIndex) {
initialize();
@@ -80,15 +86,15 @@
}
try {
await sdk.forProject(page.params.region, page.params.project).grids.createIndex(
await sdk.forProject(page.params.region, page.params.project).grids.createIndex({
databaseId,
$table.$id,
tableId: $table.$id,
key,
selectedType,
columnList.map((a) => a.value),
columnList.map((a) => a.order),
columnList.map((a) => (a.length ? Number(a.length) : null))
);
type: selectedType,
columns: columnList.map((a) => a.value),
orders: columnList.map((a) => a.order),
lengths: columnList.map((a) => (a.length ? Number(a.length) : null))
});
await Promise.allSettled([
invalidate(Dependencies.TABLE),
@@ -96,8 +102,16 @@
]);
addNotification({
message: 'Creating index',
type: 'success'
message: 'Index is being created',
type: 'success',
buttons: !isOnIndexesPage
? [
{
name: 'View index',
method: () => goto(navigatorPathToIndexes)
}
]
: undefined
});
trackEvent(Submit.IndexCreate);
showCreateIndex = false;
@@ -179,9 +193,10 @@
</Button>
</div>
{:else}
<div style:margin-top="2.05rem">
<div style:margin-top="27.6px" class="x-button-holder">
<Button
icon
size="s"
secondary
disabled={columnList.length <= 1}
on:click={() => {
@@ -200,3 +215,10 @@
</Button>
</div>
</Layout.Stack>
<style lang="scss">
.x-button-holder :global(button) {
width: 34px;
height: 34px;
}
</style>
@@ -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 @@
});
</script>
<svelte:window on:resize={() => resizeSheet(true)} />
<div bind:this={spreadsheetWrapper} class="spreadsheet-wrapper" style:height={spreadsheetHeight}>
{@render children()}
</div>
@@ -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 @@
<Activity
{limit}
{offset}
insideSideSheet
on:page={loadRowLogs}
logs={rowActivityLogs}
databasesScreen
useCreateLinkForPagination={false} />
</div>
{/if}
<style lang="scss">
.row-activity-wrapper :global(.console-container) {
margin-inline: unset;
padding-block-start: 1.25rem !important;
.row-activity-wrapper {
& :global(.console-container) {
margin-inline: unset;
padding-inline-end: unset;
@media (max-width: 768px) {
margin-block: 0.5rem;
}
}
}
</style>
@@ -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<void> {
jumpToPageReactive = 0;
if (targetPageNum < 1 || targetPageNum > totalPages) return;
if (!$paginatedRows.hasPage(targetPageNum)) {
@@ -742,8 +729,8 @@
{#if row === null}
<Spreadsheet.Row.Base
{root}
virtualItem={item}
{index}
virtualItem={item}
id={`loading-${index}`}
select={rowSelection}>
{#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)}
<Spreadsheet.Cell {root} {isEditable} column={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 @@
</Tooltip>
{:else if formatted === 'null'}
<Badge variant="secondary" content="NULL" size="xs" />
{:else if isEmptyArray}
<Badge variant="secondary" content={formatted} size="xs" />
{:else}
<Typography.Text truncate>
{formatted}
@@ -878,7 +870,7 @@
<svelte:fragment slot="cell-editor">
<EditRowCell
column={rowColumn}
row={paginatedRows.items[index]}
row={paginatedRows.getItemAtVirtualIndex(index)}
onRowStructureUpdate={updateRowContents}
on:change={(row) => paginatedRows.update(index, row.detail)}
on:revert={(row) =>
@@ -985,65 +977,65 @@
title={selectedRows.length === 1 ? 'Delete Row' : 'Delete Rows'}>
{@const isSingle = selectedRowForDelete !== null}
<div>
<p>
{#if isSingle}
Are you sure you want to delete this document from <b>{$table.name}</b>?
Are you sure you want to delete this row from <b>{$table.name}</b>?
{:else}
Are you sure you want to delete <b>{selectedRows.length}</b>
{selectedRows.length > 1 ? 'rows' : 'row'} from <b>{$table.name}</b>?
{/if}
</p>
{#if relatedColumns?.length}
<Table.Root
let:root
columns={[
{ id: 'relation', width: 150 },
{ id: 'setting', width: 150 },
{ id: 'desc' }
]}>
<svelte:fragment slot="header" let:root>
<Table.Header.Cell column="relation" {root}>Relation</Table.Header.Cell>
<Table.Header.Cell column="setting" {root}>Setting</Table.Header.Cell>
<Table.Header.Cell column="desc" {root} />
</svelte:fragment>
{#each relatedColumns as attr}
<Table.Row.Base {root}>
<Table.Cell column="relation" {root}>
<span class="u-flex u-cross-center u-gap-8">
{#if attr.twoWay}
<span class="icon-switch-horizontal"></span>
{:else}
<span class="icon-arrow-sm-right"></span>
{/if}
<span data-private>{attr.key}</span>
</span>
</Table.Cell>
<Table.Cell column="setting" {root}>
{attr.onDelete}
</Table.Cell>
<Table.Cell column="desc" {root}>
{Deletion[attr.onDelete]}
</Table.Cell>
</Table.Row.Base>
{/each}
</Table.Root>
<div class="u-flex u-flex-vertical u-gap-16">
<Alert>To change the selection edit the relationship settings.</Alert>
<ul>
<InputChoice
id="delete"
label="Delete"
showLabel={false}
bind:value={deleteConfirmationChecked}>
Delete {isSingle ? 'document' : 'documents'} from
<span data-private>{$table.name}</span>
</InputChoice>
</ul>
</div>
{:else}
<p class="u-bold">This action is irreversible.</p>
{/if}
</div>
{#if relatedColumns?.length}
<Table.Root
let:root
columns={[
{ id: 'relation', width: 150 },
{ id: 'setting', width: 150 },
{ id: 'desc' }
]}>
<svelte:fragment slot="header" let:root>
<Table.Header.Cell column="relation" {root}>Relation</Table.Header.Cell>
<Table.Header.Cell column="setting" {root}>Setting</Table.Header.Cell>
<Table.Header.Cell column="desc" {root} />
</svelte:fragment>
{#each relatedColumns as attr}
<Table.Row.Base {root}>
<Table.Cell column="relation" {root}>
<span class="u-flex u-cross-center u-gap-8">
{#if attr.twoWay}
<span class="icon-switch-horizontal"></span>
{:else}
<span class="icon-arrow-sm-right"></span>
{/if}
<span data-private>{attr.key}</span>
</span>
</Table.Cell>
<Table.Cell column="setting" {root}>
{attr.onDelete}
</Table.Cell>
<Table.Cell column="desc" {root}>
{Deletion[attr.onDelete]}
</Table.Cell>
</Table.Row.Base>
{/each}
</Table.Root>
<div class="u-flex u-flex-vertical u-gap-16">
<Alert>To change the selection edit the relationship settings.</Alert>
<ul>
<InputChoice
id="delete"
label="Delete"
showLabel={false}
bind:value={deleteConfirmationChecked}>
Delete {isSingle ? 'document' : 'documents'} from
<span data-private>{$table.name}</span>
</InputChoice>
</ul>
</div>
{:else}
<p class="u-bold">This action is irreversible.</p>
{/if}
</Confirm>
<Confirm
@@ -1087,7 +1079,7 @@
}
/* TODO: not good! */
& :global(input) {
& :global(input[type='text']) {
padding-inline: 8px !important;
}