mirror of
https://github.com/appwrite/console.git
synced 2026-06-06 19:27:48 +00:00
Merge pull request #2538 from appwrite/add-arrow-navs
This commit is contained in:
+97
-3
@@ -47,14 +47,21 @@
|
||||
import { page } from '$app/state';
|
||||
import { base } from '$app/paths';
|
||||
import { canWriteTables } from '$lib/stores/roles';
|
||||
import { IconEye, IconLockClosed, IconPlus, IconPuzzle } from '@appwrite.io/pink-icons-svelte';
|
||||
import {
|
||||
IconChevronDown,
|
||||
IconChevronUp,
|
||||
IconEye,
|
||||
IconLockClosed,
|
||||
IconPlus,
|
||||
IconPuzzle
|
||||
} from '@appwrite.io/pink-icons-svelte';
|
||||
import SideSheet from './layout/sidesheet.svelte';
|
||||
import EditRow from './rows/edit.svelte';
|
||||
import EditRelatedRow from './rows/editRelated.svelte';
|
||||
import EditColumn from './columns/edit.svelte';
|
||||
import RowActivity from './rows/activity.svelte';
|
||||
import EditRowPermissions from './rows/editPermissions.svelte';
|
||||
import { Dialog, Layout, Typography, Selector } from '@appwrite.io/pink-svelte';
|
||||
import { Dialog, Layout, Typography, Selector, Icon } from '@appwrite.io/pink-svelte';
|
||||
import { Button, Seekbar } from '$lib/elements/forms';
|
||||
import { generateFakeRecords, generateColumns } from '$lib/helpers/faker';
|
||||
import { addNotification } from '$lib/stores/notifications';
|
||||
@@ -65,6 +72,7 @@
|
||||
import { chunks } from '$lib/helpers/array';
|
||||
import { Submit, trackEvent } from '$lib/actions/analytics';
|
||||
|
||||
import { isTabletViewport } from '$lib/stores/viewport';
|
||||
import IndexesSuggestions from '../(suggestions)/indexes.svelte';
|
||||
|
||||
let editRow: EditRow;
|
||||
@@ -78,6 +86,33 @@
|
||||
|
||||
let columnCreationHandler: ((response: RealtimeResponse) => void) | null = null;
|
||||
|
||||
// manual management of focus is needed!
|
||||
const autoFocusAction = (node: HTMLElement, shouldFocus: boolean) => {
|
||||
const button = node.querySelector('button');
|
||||
if (!button) return;
|
||||
|
||||
const handleBlur = () => button.classList.remove('focus-visible');
|
||||
const applyFocus = (focus: boolean) => {
|
||||
if (focus) {
|
||||
button.classList.add('focus-visible');
|
||||
button.focus();
|
||||
} else {
|
||||
button.classList.remove('focus-visible');
|
||||
}
|
||||
};
|
||||
|
||||
button.addEventListener('blur', handleBlur);
|
||||
applyFocus(shouldFocus);
|
||||
|
||||
return {
|
||||
update: applyFocus,
|
||||
destroy() {
|
||||
button.removeEventListener('blur', handleBlur);
|
||||
button.classList.remove('focus-visible');
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
onMount(() => {
|
||||
expandTabs.set(preferences.getKey('tableHeaderExpanded', true));
|
||||
|
||||
@@ -448,11 +483,63 @@
|
||||
show: !!currentRowId,
|
||||
value: buildRowUrl(currentRowId)
|
||||
}}>
|
||||
{#snippet topEndActions()}
|
||||
{@const rows = $databaseRowSheetOptions.rows ?? []}
|
||||
{@const currentIndex = $databaseRowSheetOptions.rowIndex ?? -1}
|
||||
{@const isFirstRow = currentIndex <= 0}
|
||||
{@const isLastRow = currentIndex >= rows.length - 1}
|
||||
|
||||
{#if !$isTabletViewport}
|
||||
{@const shouldFocusPrev = !$databaseRowSheetOptions.autoFocus && !isFirstRow}
|
||||
{@const shouldFocusNext =
|
||||
!$databaseRowSheetOptions.autoFocus && isFirstRow && !isLastRow}
|
||||
|
||||
<div use:autoFocusAction={shouldFocusPrev} class:nav-button-wrapper={shouldFocusPrev}>
|
||||
<Button
|
||||
icon
|
||||
text
|
||||
size="xs"
|
||||
on:click={() => {
|
||||
if (currentIndex > 0) {
|
||||
databaseRowSheetOptions.update((opts) => ({
|
||||
...opts,
|
||||
row: rows[currentIndex - 1],
|
||||
rowIndex: currentIndex - 1
|
||||
}));
|
||||
}
|
||||
}}
|
||||
disabled={isFirstRow}>
|
||||
<Icon icon={IconChevronUp} />
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
<div use:autoFocusAction={shouldFocusNext} class:nav-button-wrapper={shouldFocusNext}>
|
||||
<Button
|
||||
icon
|
||||
text
|
||||
size="xs"
|
||||
on:click={() => {
|
||||
if (currentIndex < rows.length - 1) {
|
||||
databaseRowSheetOptions.update((opts) => ({
|
||||
...opts,
|
||||
row: rows[currentIndex + 1],
|
||||
rowIndex: currentIndex + 1
|
||||
}));
|
||||
}
|
||||
}}
|
||||
disabled={isLastRow}>
|
||||
<Icon icon={IconChevronDown} />
|
||||
</Button>
|
||||
</div>
|
||||
{/if}
|
||||
{/snippet}
|
||||
|
||||
{#key currentRowId}
|
||||
<EditRow
|
||||
bind:this={editRow}
|
||||
bind:row={$databaseRowSheetOptions.row}
|
||||
bind:rowId={$databaseRowSheetOptions.rowId} />
|
||||
bind:rowId={$databaseRowSheetOptions.rowId}
|
||||
autoFocus={$databaseRowSheetOptions.autoFocus} />
|
||||
{/key}
|
||||
</SideSheet>
|
||||
|
||||
@@ -522,3 +609,10 @@
|
||||
</Dialog>
|
||||
|
||||
<IndexesSuggestions />
|
||||
|
||||
<style lang="scss">
|
||||
// not the best solution but needed!
|
||||
.nav-button-wrapper :global(button.focus-visible) {
|
||||
outline: var(--border-width-l) solid var(--border-focus);
|
||||
}
|
||||
</style>
|
||||
|
||||
+9
-1
@@ -18,6 +18,7 @@
|
||||
footer = null,
|
||||
titleBadge = null,
|
||||
topAction = null,
|
||||
topEndActions = null,
|
||||
...restProps
|
||||
}: {
|
||||
show: boolean;
|
||||
@@ -48,7 +49,8 @@
|
||||
}
|
||||
| undefined;
|
||||
children?: Snippet;
|
||||
footer?: Snippet | null;
|
||||
footer?: Snippet;
|
||||
topEndActions?: Snippet;
|
||||
} & HTMLAttributes<HTMLDivElement> = $props();
|
||||
|
||||
let form: Form;
|
||||
@@ -88,6 +90,12 @@
|
||||
{/if}
|
||||
{/if}
|
||||
</Layout.Stack>
|
||||
|
||||
{#if topEndActions}
|
||||
<Layout.Stack direction="row" gap="xs" alignItems="center" inline>
|
||||
{@render topEndActions()}
|
||||
</Layout.Stack>
|
||||
{/if}
|
||||
</Layout.Stack>
|
||||
</div>
|
||||
|
||||
|
||||
+17
-3
@@ -10,19 +10,27 @@
|
||||
import { invalidate } from '$app/navigation';
|
||||
import { table, type Columns, PROHIBITED_ROW_KEYS } from '../store';
|
||||
import ColumnItem from './columns/columnItem.svelte';
|
||||
import { buildWildcardColumnsQuery, isRelationship, isRelationshipToMany } from './store';
|
||||
import {
|
||||
buildWildcardColumnsQuery,
|
||||
isRelationship,
|
||||
isRelationshipToMany,
|
||||
isSpatialType
|
||||
} from './store';
|
||||
import { Layout, Skeleton } from '@appwrite.io/pink-svelte';
|
||||
import { deepClone } from '$lib/helpers/object';
|
||||
import deepEqual from 'deep-equal';
|
||||
|
||||
const tableId = page.params.table;
|
||||
const databaseId = page.params.database;
|
||||
|
||||
let {
|
||||
row = $bindable(),
|
||||
rowId = $bindable(null)
|
||||
rowId = $bindable(null),
|
||||
autoFocus = true
|
||||
}: {
|
||||
row?: Models.Row | null;
|
||||
rowId?: string | null;
|
||||
autoFocus?: boolean;
|
||||
} = $props();
|
||||
|
||||
let loading = $state(false);
|
||||
@@ -76,7 +84,9 @@
|
||||
$effect(() => {
|
||||
if (row) {
|
||||
work = initWork();
|
||||
requestAnimationFrame(() => focusFirstInput());
|
||||
if (autoFocus) {
|
||||
requestAnimationFrame(() => focusFirstInput());
|
||||
}
|
||||
} else {
|
||||
work = null;
|
||||
}
|
||||
@@ -90,6 +100,10 @@
|
||||
const workColumn = $work?.[column.key];
|
||||
const currentColumn = $doc?.[column.key];
|
||||
|
||||
if (isSpatialType(column)) {
|
||||
return deepEqual(workColumn, currentColumn);
|
||||
}
|
||||
|
||||
if (column.array) {
|
||||
return !symmetricDifference(Array.from(workColumn), Array.from(currentColumn)).length;
|
||||
}
|
||||
|
||||
+15
-4
@@ -112,6 +112,9 @@
|
||||
spreadsheetRenderKey.set(hash(Date.now().toString()));
|
||||
}
|
||||
|
||||
// create index map for O(1) row lookups, reactive!
|
||||
$: rowIndexMap = new Map($paginatedRows.items.map((row, index) => [row.$id, index]));
|
||||
|
||||
const tableId = page.params.table;
|
||||
const databaseId = page.params.database;
|
||||
const organizationId = data.organization.$id ?? data.project.teamId;
|
||||
@@ -546,11 +549,14 @@
|
||||
} else if (type === 'row') {
|
||||
if (action === 'update') {
|
||||
databaseRowSheetOptions.update((opts) => {
|
||||
const rowIndex = rowIndexMap.get(row.$id) ?? -1;
|
||||
return {
|
||||
...opts,
|
||||
row,
|
||||
rowIndex,
|
||||
show: true,
|
||||
title: 'Update row'
|
||||
title: 'Update row',
|
||||
rows: $paginatedRows.items
|
||||
};
|
||||
});
|
||||
}
|
||||
@@ -800,9 +806,10 @@
|
||||
expandKbdShortcut="Cmd+Enter"
|
||||
on:expandKbdShortcut={({ detail }) => {
|
||||
const focusedRowId = detail.rowId;
|
||||
const focusedRow = $rows.rows.find((row) => row.$id === focusedRowId);
|
||||
const focusedRow = $paginatedRows.items.find((row) => row.$id === focusedRowId);
|
||||
|
||||
previouslyFocusedElement = document.activeElement;
|
||||
$databaseRowSheetOptions.autoFocus = false;
|
||||
onSelectSheetOption('update', null, 'row', focusedRow);
|
||||
}}>
|
||||
<svelte:fragment slot="header" let:root>
|
||||
@@ -929,6 +936,7 @@
|
||||
hide();
|
||||
previouslyFocusedElement =
|
||||
document.activeElement;
|
||||
$databaseRowSheetOptions.autoFocus = false;
|
||||
onSelectSheetOption(
|
||||
'update',
|
||||
null,
|
||||
@@ -979,8 +987,10 @@
|
||||
<SheetOptions
|
||||
type="row"
|
||||
column={rowColumn}
|
||||
onSelect={(option) =>
|
||||
onSelectSheetOption(option, null, 'row', row)}
|
||||
onSelect={(option) => {
|
||||
$databaseRowSheetOptions.autoFocus = true;
|
||||
onSelectSheetOption(option, null, 'row', row);
|
||||
}}
|
||||
onVisibilityChanged={(visible) => {
|
||||
canShowDatetimePopover = !visible;
|
||||
}}>
|
||||
@@ -1107,6 +1117,7 @@
|
||||
rowColumn
|
||||
);
|
||||
} else {
|
||||
$databaseRowSheetOptions.autoFocus = true;
|
||||
onSelectSheetOption('update', null, 'row', row);
|
||||
}
|
||||
}} />
|
||||
|
||||
+7
-1
@@ -70,12 +70,18 @@ export const databaseRowSheetOptions = writable<
|
||||
DatabaseSheetOptions & {
|
||||
row: Models.Row;
|
||||
rowId?: string;
|
||||
rows: Models.Row[];
|
||||
rowIndex?: number;
|
||||
autoFocus?: boolean;
|
||||
}
|
||||
>({
|
||||
title: null,
|
||||
show: false,
|
||||
row: null,
|
||||
rowId: null // for loading from a given id
|
||||
rowId: null, // for loading from a given id
|
||||
rows: [],
|
||||
rowIndex: -1,
|
||||
autoFocus: true
|
||||
});
|
||||
|
||||
export const databaseRelatedRowSheetOptions = writable<
|
||||
|
||||
Reference in New Issue
Block a user