mirror of
https://github.com/appwrite/console.git
synced 2026-06-06 19:27:48 +00:00
updates.
This commit is contained in:
+2
-1
@@ -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@363dcac",
|
||||
"@appwrite.io/pink-svelte": "https://pkg.vc/-/@appwrite/@appwrite.io/pink-svelte@b023951",
|
||||
"@faker-js/faker": "^9.9.0",
|
||||
"@popperjs/core": "^2.11.8",
|
||||
"@sentry/sveltekit": "^8.38.0",
|
||||
@@ -56,6 +56,7 @@
|
||||
"@sveltejs/adapter-static": "^3.0.8",
|
||||
"@sveltejs/kit": "^2.20.2",
|
||||
"@sveltejs/vite-plugin-svelte": "^5.0.3",
|
||||
"@tanstack/svelte-virtual": "^3.13.12",
|
||||
"@testing-library/dom": "^10.4.0",
|
||||
"@testing-library/jest-dom": "^6.6.3",
|
||||
"@testing-library/svelte": "^5.2.8",
|
||||
|
||||
Generated
+8
-5
@@ -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@363dcac
|
||||
version: https://pkg.vc/-/@appwrite/@appwrite.io/pink-svelte@363dcac(svelte@5.25.3)
|
||||
specifier: https://pkg.vc/-/@appwrite/@appwrite.io/pink-svelte@b023951
|
||||
version: https://pkg.vc/-/@appwrite/@appwrite.io/pink-svelte@b023951(svelte@5.25.3)
|
||||
'@faker-js/faker':
|
||||
specifier: ^9.9.0
|
||||
version: 9.9.0
|
||||
@@ -108,6 +108,9 @@ importers:
|
||||
'@sveltejs/vite-plugin-svelte':
|
||||
specifier: ^5.0.3
|
||||
version: 5.0.3(svelte@5.25.3)(vite@7.0.6(@types/node@22.13.14)(sass@1.86.0))
|
||||
'@tanstack/svelte-virtual':
|
||||
specifier: ^3.13.12
|
||||
version: 3.13.12(svelte@5.25.3)
|
||||
'@testing-library/dom':
|
||||
specifier: ^10.4.0
|
||||
version: 10.4.0
|
||||
@@ -284,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@363dcac':
|
||||
resolution: {tarball: https://pkg.vc/-/@appwrite/@appwrite.io/pink-svelte@363dcac}
|
||||
'@appwrite.io/pink-svelte@https://pkg.vc/-/@appwrite/@appwrite.io/pink-svelte@b023951':
|
||||
resolution: {tarball: https://pkg.vc/-/@appwrite/@appwrite.io/pink-svelte@b023951}
|
||||
version: 2.0.0-RC.2
|
||||
peerDependencies:
|
||||
svelte: ^4.0.0
|
||||
@@ -3828,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@363dcac(svelte@5.25.3)':
|
||||
'@appwrite.io/pink-svelte@https://pkg.vc/-/@appwrite/@appwrite.io/pink-svelte@b023951(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
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
export const PAGE_LIMIT = 12; // default page limit
|
||||
export const SPREADSHEET_PAGE_LIMIT = 50; // default sheet page limit
|
||||
export const CARD_LIMIT = 6; // default card limit
|
||||
export const INTERVAL = 5 * 60000; // default interval to check for feedback
|
||||
export const NEW_DEV_PRO_UPGRADE_COUPON = 'appw50';
|
||||
|
||||
@@ -17,11 +17,10 @@
|
||||
const buttonSize = $derived(collapsed ? 'xs' : 's');
|
||||
|
||||
const expandedSize = $derived(
|
||||
$isSmallViewport ? 'var(--font-size-xl)' : 'var(--font-size-xxxl)'
|
||||
$isSmallViewport ? 'var(--font-size-m)' : 'var(--font-size-xxxl)'
|
||||
);
|
||||
|
||||
const collapsedSize = $derived($isSmallViewport ? 'var(--font-size-l)' : 'var(--font-size-xl)');
|
||||
const currentFontSize = $derived(collapsed ? collapsedSize : expandedSize);
|
||||
const currentFontSize = $derived(collapsed ? 'var(--font-size-l)' : expandedSize);
|
||||
|
||||
const currentLineHeight = $derived(collapsed ? '130%' : '140%');
|
||||
const currentLetterSpacing = $derived(collapsed ? '0' : '-0.144px');
|
||||
|
||||
@@ -33,6 +33,7 @@
|
||||
|
||||
<style>
|
||||
.overlap-cover {
|
||||
z-index: 1;
|
||||
margin-block-start: -3.5rem;
|
||||
}
|
||||
:global(.console-container) {
|
||||
|
||||
+1
-1
@@ -91,7 +91,7 @@
|
||||
bind:id
|
||||
required={false}
|
||||
autofocus={false}
|
||||
name="Collection"
|
||||
name="Table"
|
||||
on:input={() => {
|
||||
if (!touchedId) {
|
||||
touchedId = true;
|
||||
|
||||
+1
-1
@@ -222,8 +222,8 @@
|
||||
}
|
||||
|
||||
li {
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
position: relative;
|
||||
padding-inline-end: 0.5rem;
|
||||
margin-inline-start: 0.5rem;
|
||||
}
|
||||
|
||||
+2
-3
@@ -1,4 +1,4 @@
|
||||
import { Dependencies /*PAGE_LIMIT*/ } from '$lib/constants';
|
||||
import { Dependencies, SPREADSHEET_PAGE_LIMIT } from '$lib/constants';
|
||||
import { getLimit, getPage, getQuery, getView, pageToOffset, View } from '$lib/helpers/load';
|
||||
import { sdk } from '$lib/stores/sdk';
|
||||
import { type Models, Query } from '@appwrite.io/console';
|
||||
@@ -13,8 +13,7 @@ export const load: PageLoad = async ({ params, depends, url, route, parent }) =>
|
||||
depends(Dependencies.ROWS);
|
||||
|
||||
const page = getPage(url);
|
||||
// TODO: apply pagination
|
||||
const limit = getLimit(url, route, 96 /*PAGE_LIMIT*/);
|
||||
const limit = getLimit(url, route, SPREADSHEET_PAGE_LIMIT);
|
||||
const view = getView(url, route, View.Grid);
|
||||
const offset = pageToOffset(page, limit);
|
||||
const query = getQuery(url);
|
||||
|
||||
-2
@@ -81,8 +81,6 @@
|
||||
});
|
||||
</script>
|
||||
|
||||
<svelte:window on:resize={resizeSheet} />
|
||||
|
||||
<div bind:this={spreadsheetWrapper} class="spreadsheet-wrapper" style:height={spreadsheetHeight}>
|
||||
{@render children()}
|
||||
</div>
|
||||
|
||||
+293
-145
@@ -4,8 +4,8 @@
|
||||
import { page } from '$app/state';
|
||||
import { Submit, trackError, trackEvent } from '$lib/actions/analytics';
|
||||
import { Alert, Confirm, Id, SortButton } from '$lib/components';
|
||||
import { Dependencies } from '$lib/constants';
|
||||
import { Button as ConsoleButton, InputChoice } from '$lib/elements/forms';
|
||||
import { Dependencies, SPREADSHEET_PAGE_LIMIT } from '$lib/constants';
|
||||
import { Button as ConsoleButton, InputChoice, InputSelect } from '$lib/elements/forms';
|
||||
import { addNotification } from '$lib/stores/notifications';
|
||||
import { preferences } from '$lib/stores/preferences';
|
||||
import { sdk } from '$lib/stores/sdk';
|
||||
@@ -29,8 +29,9 @@
|
||||
showCreateIndexSheet,
|
||||
type SortState,
|
||||
Deletion,
|
||||
scrollStore,
|
||||
rowActivitySheet
|
||||
rowActivitySheet,
|
||||
paginatedRows,
|
||||
paginatedRowsLoading
|
||||
} from './store';
|
||||
import RelationshipsModal from './relationshipsModal.svelte';
|
||||
import type { Column, ColumnType } from '$lib/helpers/types';
|
||||
@@ -70,8 +71,10 @@
|
||||
import type { HeaderCellAction, RowCellAction } from './sheetOptions.svelte';
|
||||
import { copy } from '$lib/helpers/copy';
|
||||
import { debounce } from '$lib/helpers/debounce';
|
||||
import { writable } from 'svelte/store';
|
||||
import { hash } from '$lib/helpers/string';
|
||||
import { writable } from 'svelte/store';
|
||||
import { pageToOffset } from '$lib/helpers/load';
|
||||
import { sleep } from '$lib/helpers/promises';
|
||||
|
||||
export let data: PageData;
|
||||
export let showRecordsCreateSheet: {
|
||||
@@ -80,6 +83,11 @@
|
||||
};
|
||||
|
||||
$: rows = writable(data.rows);
|
||||
$: if (rows) {
|
||||
paginatedRows.clear();
|
||||
paginatedRows.setPage(1, $rows.rows);
|
||||
}
|
||||
|
||||
const tableId = page.params.table;
|
||||
const databaseId = page.params.database;
|
||||
const organizationId = data.organization.$id ?? data.project.teamId;
|
||||
@@ -100,6 +108,9 @@
|
||||
|
||||
let spreadsheetContainer: SpreadsheetContainer;
|
||||
|
||||
let currentPage = 1;
|
||||
let jumpToPageReactive = 0;
|
||||
|
||||
let showDelete = false;
|
||||
let showColumnDelete = false;
|
||||
let deleteConfirmationChecked = false;
|
||||
@@ -113,7 +124,10 @@
|
||||
makeTableColumns();
|
||||
sortState.set(data.currentSort as SortState);
|
||||
|
||||
window.scrollTo($scrollStore, 0);
|
||||
if (data.rows.rows.length) {
|
||||
paginatedRows.clear();
|
||||
paginatedRows.setPage(1, data.rows.rows);
|
||||
}
|
||||
});
|
||||
|
||||
onDestroy(() => ($showCreateAttributeSheet.show = false));
|
||||
@@ -330,13 +344,30 @@
|
||||
showDelete = false;
|
||||
try {
|
||||
if (selectedRowForDelete) {
|
||||
await sdk
|
||||
.forProject(page.params.region, page.params.project)
|
||||
.grids.deleteRow(databaseId, tableId, selectedRowForDelete);
|
||||
await sdk.forProject(page.params.region, page.params.project).grids.deleteRow({
|
||||
databaseId,
|
||||
tableId,
|
||||
rowId: selectedRowForDelete
|
||||
});
|
||||
} else {
|
||||
await sdk
|
||||
.forProject(page.params.region, page.params.project)
|
||||
.grids.deleteRows(databaseId, tableId, [Query.equal('$id', selectedRows)]);
|
||||
if (selectedRows.length) {
|
||||
const batches: string[][] = [];
|
||||
for (let i = 0; i < selectedRows.length; i += 100) {
|
||||
batches.push(selectedRows.slice(i, i + 100));
|
||||
}
|
||||
|
||||
await Promise.all(
|
||||
batches.map((batch) =>
|
||||
sdk
|
||||
.forProject(page.params.region, page.params.project)
|
||||
.grids.deleteRows({
|
||||
databaseId,
|
||||
tableId,
|
||||
queries: [Query.equal('$id', batch)]
|
||||
})
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
await invalidate(Dependencies.ROWS);
|
||||
@@ -501,6 +532,64 @@
|
||||
}
|
||||
}
|
||||
|
||||
async function loadPage(pageNumber: number): Promise<boolean> {
|
||||
const totalPages = Math.ceil(data.rows.total / SPREADSHEET_PAGE_LIMIT);
|
||||
if (
|
||||
pageNumber < 1 ||
|
||||
pageNumber === currentPage ||
|
||||
paginatedRows.hasPage(pageNumber) ||
|
||||
pageNumber > totalPages
|
||||
)
|
||||
return false;
|
||||
|
||||
console.log(`pageNumber: ${pageNumber}`);
|
||||
|
||||
$paginatedRowsLoading = true;
|
||||
await sleep(500);
|
||||
const loadedRows = await sdk
|
||||
.forProject(page.params.region, page.params.project)
|
||||
.grids.listRows({
|
||||
databaseId,
|
||||
tableId,
|
||||
queries: [
|
||||
Query.orderDesc(''),
|
||||
Query.limit(SPREADSHEET_PAGE_LIMIT),
|
||||
Query.offset(pageToOffset(pageNumber, SPREADSHEET_PAGE_LIMIT))
|
||||
]
|
||||
});
|
||||
|
||||
paginatedRows.setPage(pageNumber, loadedRows.rows);
|
||||
$paginatedRowsLoading = false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
console.log(`$paginatedRowsLoading: ${$paginatedRowsLoading}`);
|
||||
|
||||
async function handleGoToPage(targetPageNum: number): Promise<void> {
|
||||
if (targetPageNum < 1 || targetPageNum > totalPages) return;
|
||||
|
||||
if (!paginatedRows.hasPage(targetPageNum)) {
|
||||
paginatedRows.setMaxPage(targetPageNum);
|
||||
$paginatedRowsLoading = true;
|
||||
|
||||
const loadedRows = await sdk
|
||||
.forProject(page.params.region, page.params.project)
|
||||
.grids.listRows({
|
||||
databaseId,
|
||||
tableId,
|
||||
queries: [
|
||||
Query.orderDesc(''),
|
||||
Query.limit(SPREADSHEET_PAGE_LIMIT),
|
||||
Query.offset(pageToOffset(targetPageNum, SPREADSHEET_PAGE_LIMIT))
|
||||
]
|
||||
});
|
||||
|
||||
paginatedRows.setPage(targetPageNum, loadedRows.rows);
|
||||
$paginatedRowsLoading = false;
|
||||
}
|
||||
}
|
||||
|
||||
const saveColumnWidthsToPreferences = debounce(
|
||||
(column: { columnId: string; newWidth: number; fixedWidth: number }) => {
|
||||
preferences.saveColumnWidths(organizationId, tableId, {
|
||||
@@ -531,7 +620,9 @@
|
||||
) as Models.ColumnRelationship[];
|
||||
|
||||
$: emptyCellsCount =
|
||||
$rows.rows.length >= emptyCellsLimit ? 0 : emptyCellsLimit - $rows.rows.length;
|
||||
$paginatedRows.virtualLength >= emptyCellsLimit
|
||||
? 0
|
||||
: emptyCellsLimit - $paginatedRows.virtualLength;
|
||||
|
||||
$: canShowDatetimePopover = true;
|
||||
|
||||
@@ -544,10 +635,9 @@
|
||||
tick().then(() => spreadsheetContainer?.resizeSheet());
|
||||
}
|
||||
|
||||
$: spreadsheetKey = hash(
|
||||
$rows.rows.map((row) => row.$id),
|
||||
'#'
|
||||
);
|
||||
$: spreadsheetKey = hash($paginatedRows.items.map((item) => item.$id).toString(), '#');
|
||||
|
||||
$: totalPages = Math.ceil($rows.total / SPREADSHEET_PAGE_LIMIT) || 1;
|
||||
</script>
|
||||
|
||||
<SpreadsheetContainer observeExpand bind:this={spreadsheetContainer}>
|
||||
@@ -558,13 +648,22 @@
|
||||
useVirtualizer
|
||||
keyboardNavigation
|
||||
bind:selectedRows
|
||||
rowCount={$rows.rows.length}
|
||||
bind:columns={$tableColumns}
|
||||
loading={$spreadsheetLoading}
|
||||
emptyCells={emptyCellsCount}
|
||||
rowCount={$paginatedRows.virtualLength}
|
||||
bottomActionClick={() => (showRecordsCreateSheet.show = true)}
|
||||
on:columnsSwap={(order) => saveColumnsOrder(order.detail)}
|
||||
on:columnsResize={(resize) => saveColumnsWidth(resize.detail)}>
|
||||
on:columnsResize={(resize) => saveColumnsWidth(resize.detail)}
|
||||
bind:currentPage
|
||||
nextPageTriggerOffset={2}
|
||||
paginationBufferSpace={35}
|
||||
jumpToPageNumber={jumpToPageReactive}
|
||||
loadingMore={$paginatedRowsLoading}
|
||||
itemsPerPage={SPREADSHEET_PAGE_LIMIT}
|
||||
loadNextPage={loadPage}
|
||||
loadPreviousPage={loadPage}
|
||||
goToPage={handleGoToPage}>
|
||||
<svelte:fragment slot="header" let:root>
|
||||
{#each $tableColumns as column (column.id)}
|
||||
{#if column.isAction}
|
||||
@@ -615,135 +714,147 @@
|
||||
</svelte:fragment>
|
||||
|
||||
<svelte:fragment slot="rows" let:root let:item let:index>
|
||||
{@const row = $rows.rows[item.index]}
|
||||
<Spreadsheet.Row.Base {root} {index} id={row?.$id} virtualItem={item}>
|
||||
{#each $tableColumns as { id: columnId, isEditable } (columnId)}
|
||||
{@const rowColumn = $columns.find((col) => col.key === columnId)}
|
||||
<Spreadsheet.Cell
|
||||
{root}
|
||||
{isEditable}
|
||||
column={columnId}
|
||||
value={$rows.rows[item.index][columnId]}>
|
||||
{#if columnId === '$sequence'}
|
||||
<Id value={row.$sequence.toString()} tooltipPortal
|
||||
>{row.$sequence}</Id>
|
||||
{:else if columnId === '$id'}
|
||||
<Id value={row.$id} tooltipPortal tooltipDelay={200}>{row.$id}</Id>
|
||||
{:else if columnId === '$createdAt' || columnId === '$updatedAt'}
|
||||
<DualTimeView
|
||||
time={row[columnId]}
|
||||
canShowPopover={canShowDatetimePopover} />
|
||||
{:else if columnId === 'actions'}
|
||||
<SheetOptions
|
||||
type="row"
|
||||
column={rowColumn}
|
||||
onSelect={(option) =>
|
||||
onSelectSheetOption(option, null, 'row', row)}
|
||||
onVisibilityChanged={(visible) => {
|
||||
canShowDatetimePopover = !visible;
|
||||
}}>
|
||||
{#snippet children(toggle)}
|
||||
<Button.Button
|
||||
icon
|
||||
variant="extra-compact"
|
||||
on:click={toggle}>
|
||||
<Icon
|
||||
icon={IconDotsHorizontal}
|
||||
color="--fgcolor-neutral-primary" />
|
||||
</Button.Button>
|
||||
{/snippet}
|
||||
</SheetOptions>
|
||||
{:else if isRelationship(rowColumn)}
|
||||
{@const args = displayNames?.[rowColumn.relatedTable] ?? ['$id']}
|
||||
{#if !isRelationshipToMany(rowColumn)}
|
||||
{#if row[columnId]}
|
||||
{@const related = row[columnId]}
|
||||
<Link.Button
|
||||
variant="muted"
|
||||
on:click={(e) => {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
// TODO: open sheet maybes
|
||||
goto(
|
||||
`${base}/project-${page.params.region}-${page.params.project}/databases/database-${databaseId}/table/${rowColumn.relatedTable}/row/${related.$id}`
|
||||
);
|
||||
}}>
|
||||
{#each args as arg, i}
|
||||
{#if arg !== undefined}
|
||||
{#if i} |{/if}
|
||||
<span class="text" data-private
|
||||
>{related?.[arg]}</span>
|
||||
{/if}
|
||||
{/each}
|
||||
</Link.Button>
|
||||
{@const row = $paginatedRows.getItemAtVirtualIndex(index)}
|
||||
{#if row === null}
|
||||
<Spreadsheet.Row.Base {root} virtualItem={item} {index} id={`loading-${index}`}>
|
||||
{#each $tableColumns as col}
|
||||
<Spreadsheet.Cell
|
||||
column={col.id}
|
||||
isEditable={false}
|
||||
root={{ ...root, loading: true }}
|
||||
id={`loading-${index}-${col.id}`} />
|
||||
{/each}
|
||||
</Spreadsheet.Row.Base>
|
||||
{:else}
|
||||
<Spreadsheet.Row.Base {root} {index} id={row?.$id} virtualItem={item}>
|
||||
{#each $tableColumns as { id: columnId, isEditable } (columnId)}
|
||||
{@const rowColumn = $columns.find((col) => col.key === columnId)}
|
||||
<Spreadsheet.Cell {root} {isEditable} column={columnId}>
|
||||
{#if columnId === '$sequence'}
|
||||
<Id value={row.$sequence.toString()} tooltipPortal
|
||||
>{row.$sequence}</Id>
|
||||
{:else if columnId === '$id'}
|
||||
<Id value={row.$id} tooltipPortal tooltipDelay={200}
|
||||
>{row.$id}</Id>
|
||||
{:else if columnId === '$createdAt' || columnId === '$updatedAt'}
|
||||
<DualTimeView
|
||||
time={row[columnId]}
|
||||
canShowPopover={canShowDatetimePopover} />
|
||||
{:else if columnId === 'actions'}
|
||||
<SheetOptions
|
||||
type="row"
|
||||
column={rowColumn}
|
||||
onSelect={(option) =>
|
||||
onSelectSheetOption(option, null, 'row', row)}
|
||||
onVisibilityChanged={(visible) => {
|
||||
canShowDatetimePopover = !visible;
|
||||
}}>
|
||||
{#snippet children(toggle)}
|
||||
<Button.Button
|
||||
icon
|
||||
variant="extra-compact"
|
||||
on:click={toggle}>
|
||||
<Icon
|
||||
icon={IconDotsHorizontal}
|
||||
color="--fgcolor-neutral-primary" />
|
||||
</Button.Button>
|
||||
{/snippet}
|
||||
</SheetOptions>
|
||||
{:else if isRelationship(rowColumn)}
|
||||
{@const args = displayNames?.[rowColumn.relatedTable] ?? [
|
||||
'$id'
|
||||
]}
|
||||
{#if !isRelationshipToMany(rowColumn)}
|
||||
{#if row[columnId]}
|
||||
{@const related = row[columnId]}
|
||||
<Link.Button
|
||||
variant="muted"
|
||||
on:click={(e) => {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
// TODO: open sheet maybes
|
||||
goto(
|
||||
`${base}/project-${page.params.region}-${page.params.project}/databases/database-${databaseId}/table/${rowColumn.relatedTable}/row/${related.$id}`
|
||||
);
|
||||
}}>
|
||||
{#each args as arg, i}
|
||||
{#if arg !== undefined}
|
||||
{#if i} |{/if}
|
||||
<span class="text" data-private
|
||||
>{related?.[arg]}</span>
|
||||
{/if}
|
||||
{/each}
|
||||
</Link.Button>
|
||||
{:else}
|
||||
<span class="text">n/a</span>
|
||||
{/if}
|
||||
{:else}
|
||||
<span class="text">n/a</span>
|
||||
{@const itemsNum = row[columnId]?.length}
|
||||
<Button.Button
|
||||
variant="extra-compact"
|
||||
disabled={!itemsNum}
|
||||
badge={itemsNum ?? 0}
|
||||
on:click={(e) => {
|
||||
e.stopPropagation();
|
||||
e.preventDefault();
|
||||
relationshipData = row[columnId];
|
||||
showRelationships = true;
|
||||
selectedRelationship = rowColumn;
|
||||
}}>
|
||||
Items
|
||||
</Button.Button>
|
||||
{/if}
|
||||
{:else}
|
||||
{@const itemsNum = row[columnId]?.length}
|
||||
<Button.Button
|
||||
variant="extra-compact"
|
||||
disabled={!itemsNum}
|
||||
badge={itemsNum ?? 0}
|
||||
on:click={(e) => {
|
||||
e.stopPropagation();
|
||||
e.preventDefault();
|
||||
relationshipData = row[columnId];
|
||||
showRelationships = true;
|
||||
selectedRelationship = rowColumn;
|
||||
}}>
|
||||
Items
|
||||
</Button.Button>
|
||||
{/if}
|
||||
{:else}
|
||||
{@const value = row[columnId]}
|
||||
{@const formatted = formatColumn(row[columnId])}
|
||||
{@const isDatetimeAttribute = rowColumn.type === 'datetime'}
|
||||
{@const isEncryptedAttribute =
|
||||
isString(rowColumn) && rowColumn.encrypt}
|
||||
{#if isDatetimeAttribute}
|
||||
<DualTimeView time={value}>
|
||||
<span slot="title">Timestamp</span>
|
||||
{toLocaleDateTime(value, true)}
|
||||
</DualTimeView>
|
||||
{:else if isEncryptedAttribute}
|
||||
<button on:click={(e) => e.preventDefault()}>
|
||||
<InteractiveText
|
||||
copy={false}
|
||||
variant="secret"
|
||||
isVisible={false}
|
||||
text={formatted} />
|
||||
</button>
|
||||
{:else if formatted.length > 20}
|
||||
<Tooltip placement="bottom" portal disabled>
|
||||
{@const value = row[columnId]}
|
||||
{@const formatted = formatColumn(row[columnId])}
|
||||
{@const isDatetimeAttribute = rowColumn.type === 'datetime'}
|
||||
{@const isEncryptedAttribute =
|
||||
isString(rowColumn) && rowColumn.encrypt}
|
||||
{#if isDatetimeAttribute}
|
||||
<DualTimeView time={value}>
|
||||
<span slot="title">Timestamp</span>
|
||||
{toLocaleDateTime(value, true)}
|
||||
</DualTimeView>
|
||||
{:else if isEncryptedAttribute}
|
||||
<button on:click={(e) => e.preventDefault()}>
|
||||
<InteractiveText
|
||||
copy={false}
|
||||
variant="secret"
|
||||
isVisible={false}
|
||||
text={formatted} />
|
||||
</button>
|
||||
{:else if formatted.length > 20}
|
||||
<Tooltip placement="bottom" portal disabled>
|
||||
<Typography.Text truncate>
|
||||
{formatted}
|
||||
</Typography.Text>
|
||||
<span
|
||||
slot="tooltip"
|
||||
style:white-space="pre-wrap"
|
||||
style:word-break="break-word">
|
||||
{formatted}
|
||||
</span>
|
||||
</Tooltip>
|
||||
{:else if formatted === 'null'}
|
||||
<Badge variant="secondary" content="NULL" size="xs" />
|
||||
{:else}
|
||||
<Typography.Text truncate>
|
||||
{formatted}
|
||||
</Typography.Text>
|
||||
<span
|
||||
slot="tooltip"
|
||||
style:white-space="pre-wrap"
|
||||
style:word-break="break-word">
|
||||
{formatted}
|
||||
</span>
|
||||
</Tooltip>
|
||||
{:else if formatted === 'null'}
|
||||
<Badge variant="secondary" content="NULL" size="xs" />
|
||||
{:else}
|
||||
<Typography.Text truncate>
|
||||
{formatted}
|
||||
</Typography.Text>
|
||||
{/if}
|
||||
{/if}
|
||||
{/if}
|
||||
|
||||
<svelte:fragment slot="cell-editor">
|
||||
<EditCellRow
|
||||
column={rowColumn}
|
||||
bind:row={$rows.rows[item.index]}
|
||||
onRowStructureUpdate={updateRowContents} />
|
||||
</svelte:fragment>
|
||||
</Spreadsheet.Cell>
|
||||
{/each}
|
||||
</Spreadsheet.Row.Base>
|
||||
<svelte:fragment slot="cell-editor">
|
||||
<!-- TODO: cannot bind here as well, this is breaking -->
|
||||
<EditCellRow
|
||||
column={rowColumn}
|
||||
row={$paginatedRows.items[index]}
|
||||
onRowStructureUpdate={updateRowContents} />
|
||||
</svelte:fragment>
|
||||
</Spreadsheet.Cell>
|
||||
{/each}
|
||||
</Spreadsheet.Row.Base>
|
||||
{/if}
|
||||
</svelte:fragment>
|
||||
|
||||
<svelte:fragment slot="footer">
|
||||
@@ -752,11 +863,48 @@
|
||||
alignContent="center"
|
||||
alignItems="center"
|
||||
justifyContent="space-between">
|
||||
<Typography.Text variant="m-400" color="--fgcolor-neutral-secondary">
|
||||
{selectedRows.length
|
||||
? `${selectedRows.length} row${selectedRows.length === 1 ? '' : 's'} selected`
|
||||
: `${$rows.total} row${$rows.total === 1 ? '' : 's'}`}
|
||||
</Typography.Text>
|
||||
<Layout.Stack
|
||||
gap="l"
|
||||
direction="row"
|
||||
alignItems="center"
|
||||
alignContent="center"
|
||||
inline>
|
||||
<Typography.Text variant="m-400" color="--fgcolor-neutral-secondary">
|
||||
<span style:white-space="nowrap">
|
||||
{selectedRows.length
|
||||
? `${selectedRows.length} row${selectedRows.length === 1 ? '' : 's'} selected`
|
||||
: `${$rows.total} row${$rows.total === 1 ? '' : 's'}`}
|
||||
</span>
|
||||
</Typography.Text>
|
||||
|
||||
<div
|
||||
style:height="20px"
|
||||
style:width="1px"
|
||||
style:background-color="var(--border-neutral)">
|
||||
</div>
|
||||
|
||||
<Layout.Stack
|
||||
gap="xs"
|
||||
direction="row"
|
||||
alignItems="center"
|
||||
alignContent="center">
|
||||
<span style:white-space="nowrap"> Page </span>
|
||||
|
||||
<InputSelect
|
||||
id="pagination"
|
||||
value={currentPage}
|
||||
placeholder="Page"
|
||||
options={Array.from({ length: totalPages }, (_, i) => ({
|
||||
label: `${i + 1}`,
|
||||
value: i + 1
|
||||
}))}
|
||||
on:change={(e) => (jumpToPageReactive = Number(e.detail))} />
|
||||
|
||||
<span style:white-space="nowrap">
|
||||
out of {totalPages}
|
||||
</span>
|
||||
</Layout.Stack>
|
||||
</Layout.Stack>
|
||||
|
||||
<div style:margin-right="var(--space-6)">
|
||||
<Button.Button
|
||||
|
||||
+6
@@ -3,6 +3,8 @@ import type { Column } from '$lib/helpers/types';
|
||||
import type { Models } from '@appwrite.io/console';
|
||||
import { derived, writable } from 'svelte/store';
|
||||
import type { SortDirection } from '$lib/components';
|
||||
import { SPREADSHEET_PAGE_LIMIT } from '$lib/constants';
|
||||
import { createSparsePagedDataStore } from '@appwrite.io/pink-svelte';
|
||||
|
||||
export type Columns =
|
||||
| Models.ColumnBoolean
|
||||
@@ -150,3 +152,7 @@ export const rowActivitySheet = writable({
|
||||
});
|
||||
|
||||
export const expandTabs = writable(true);
|
||||
|
||||
// Paginated rows stores
|
||||
export const paginatedRowsLoading = writable(false);
|
||||
export const paginatedRows = createSparsePagedDataStore<Models.DefaultRow>(SPREADSHEET_PAGE_LIMIT);
|
||||
|
||||
Reference in New Issue
Block a user