mirror of
https://github.com/appwrite/console.git
synced 2026-06-06 19:27:48 +00:00
Merge pull request #2493 from appwrite/spreadsheet-kbd-expand
This commit is contained in:
+10
-7
@@ -400,6 +400,8 @@
|
||||
$: if (!$showCreateColumnSheet.show) {
|
||||
createMoreColumns = false;
|
||||
}
|
||||
|
||||
$: currentRowId = $databaseRowSheetOptions.row?.$id ?? $databaseRowSheetOptions.rowId;
|
||||
</script>
|
||||
|
||||
<svelte:head>
|
||||
@@ -456,7 +458,6 @@
|
||||
</SideSheet>
|
||||
|
||||
<SideSheet
|
||||
closeOnBlur
|
||||
title={$databaseRowSheetOptions.title}
|
||||
bind:show={$databaseRowSheetOptions.show}
|
||||
submit={{
|
||||
@@ -467,13 +468,15 @@
|
||||
topAction={{
|
||||
mode: 'copy-tag',
|
||||
text: 'Row URL',
|
||||
show: !!($databaseRowSheetOptions.rowId ?? $databaseRowSheetOptions.row?.$id),
|
||||
value: buildRowUrl($databaseRowSheetOptions.rowId ?? $databaseRowSheetOptions.row?.$id)
|
||||
show: !!currentRowId,
|
||||
value: buildRowUrl(currentRowId)
|
||||
}}>
|
||||
<EditRow
|
||||
bind:this={editRow}
|
||||
bind:row={$databaseRowSheetOptions.row}
|
||||
bind:rowId={$databaseRowSheetOptions.rowId} />
|
||||
{#key currentRowId}
|
||||
<EditRow
|
||||
bind:this={editRow}
|
||||
bind:row={$databaseRowSheetOptions.row}
|
||||
bind:rowId={$databaseRowSheetOptions.rowId} />
|
||||
{/key}
|
||||
</SideSheet>
|
||||
|
||||
<SideSheet
|
||||
|
||||
+1
-1
@@ -76,7 +76,7 @@
|
||||
$effect(() => {
|
||||
if (row) {
|
||||
work = initWork();
|
||||
focusFirstInput();
|
||||
requestAnimationFrame(() => focusFirstInput());
|
||||
} else {
|
||||
work = null;
|
||||
}
|
||||
|
||||
+8
-3
@@ -117,15 +117,20 @@
|
||||
|
||||
if (type === 'header') {
|
||||
if (isSequence) {
|
||||
return ['sort-asc', 'sort-desc'].includes(item.action ?? '');
|
||||
return ['sort-asc', 'sort-desc'].includes(item?.action);
|
||||
}
|
||||
|
||||
if (['delete', 'update', 'duplicate-header'].includes(item.action) && isSystemColumn) {
|
||||
if (['delete', 'update', 'duplicate-header'].includes(item?.action) && isSystemColumn) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// hide column-left and create-index for $id (first column, already indexed)
|
||||
if (columnId === '$id' && ['column-left', 'create-index'].includes(item?.action)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// hide sort options for relationship columns
|
||||
if (isRelationship(column) && ['sort-asc', 'sort-desc'].includes(item.action ?? '')) {
|
||||
if (isRelationship(column) && ['sort-asc', 'sort-desc'].includes(item?.action)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
+232
-129
@@ -53,6 +53,8 @@
|
||||
Button,
|
||||
Layout,
|
||||
Link,
|
||||
Popover,
|
||||
Keyboard,
|
||||
Badge,
|
||||
FloatingActionBar,
|
||||
Icon,
|
||||
@@ -78,7 +80,8 @@
|
||||
IconSwitchHorizontal,
|
||||
IconText,
|
||||
IconToggle,
|
||||
IconViewList
|
||||
IconViewList,
|
||||
IconArrowExpand
|
||||
} from '@appwrite.io/pink-icons-svelte';
|
||||
import type { HeaderCellAction, RowCellAction } from './sheetOptions.svelte';
|
||||
import SheetOptions from './sheetOptions.svelte';
|
||||
@@ -120,7 +123,9 @@
|
||||
: 24;
|
||||
|
||||
let selectedRows = [];
|
||||
let showExpandIconForId: number | null = null;
|
||||
let spreadsheetContainer: SpreadsheetContainer;
|
||||
let previouslyFocusedElement: Element | null = null;
|
||||
|
||||
let currentPage = 1;
|
||||
let jumpToPageReactive = 0;
|
||||
@@ -170,8 +175,8 @@
|
||||
{
|
||||
id: '$id',
|
||||
title: '$id',
|
||||
width: getColumnWidth('$id', 225),
|
||||
minimumWidth: 225,
|
||||
width: getColumnWidth('$id', 250),
|
||||
minimumWidth: 250,
|
||||
draggable: false,
|
||||
type: 'string',
|
||||
icon: IconFingerPrint,
|
||||
@@ -537,9 +542,14 @@
|
||||
}
|
||||
} else if (type === 'row') {
|
||||
if (action === 'update') {
|
||||
$databaseRowSheetOptions.show = true;
|
||||
$databaseRowSheetOptions.row = row;
|
||||
$databaseRowSheetOptions.title = 'Update row';
|
||||
databaseRowSheetOptions.update((opts) => {
|
||||
return {
|
||||
...opts,
|
||||
row,
|
||||
show: true,
|
||||
title: 'Update row'
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
if (action === 'duplicate-row') {
|
||||
@@ -747,6 +757,13 @@
|
||||
$: totalPages = Math.ceil($rows.total / SPREADSHEET_PAGE_LIMIT) || 1;
|
||||
|
||||
$: rowSelection = !$spreadsheetLoading && !$paginatedRowsLoading ? true : ('disabled' as const);
|
||||
|
||||
$: if (!$databaseRowSheetOptions.show && previouslyFocusedElement) {
|
||||
requestAnimationFrame(() => {
|
||||
(previouslyFocusedElement as HTMLElement)?.focus();
|
||||
previouslyFocusedElement = null;
|
||||
});
|
||||
}
|
||||
</script>
|
||||
|
||||
<SpreadsheetContainer bind:this={spreadsheetContainer}>
|
||||
@@ -776,6 +793,14 @@
|
||||
bottomActionTooltip={{
|
||||
text: 'Create row',
|
||||
placement: 'top-end'
|
||||
}}
|
||||
expandKbdShortcut="Cmd+Enter"
|
||||
on:expandKbdShortcut={({ detail }) => {
|
||||
const focusedRowId = detail.rowId;
|
||||
const focusedRow = $rows.rows.find((row) => row.$id === focusedRowId);
|
||||
|
||||
previouslyFocusedElement = document.activeElement;
|
||||
onSelectSheetOption('update', null, 'row', focusedRow);
|
||||
}}>
|
||||
<svelte:fragment slot="header" let:root>
|
||||
{#each $tableColumns as column (column.id)}
|
||||
@@ -867,56 +892,133 @@
|
||||
valueWithoutHover={row.$sequence}>
|
||||
{#each $tableColumns as { id: columnId, isEditable } (columnId)}
|
||||
{@const rowColumn = $columns.find((col) => col.key === columnId)}
|
||||
<Spreadsheet.Cell {root} {isEditable} column={columnId}>
|
||||
{#if columnId === '$id'}
|
||||
<Id value={row.$id} tooltipPortal tooltipDelay={200}
|
||||
>{row.$id}</Id>
|
||||
{:else if columnId === '$createdAt' || columnId === '$updatedAt'}
|
||||
<DualTimeView
|
||||
showDatetime
|
||||
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 = getDisplayNamesForTable(row[columnId])}
|
||||
{#if !isRelationshipToMany(rowColumn)}
|
||||
{#if row[columnId]}
|
||||
{@const displayValue = args
|
||||
.map((arg) => row[columnId]?.[arg])
|
||||
.filter(Boolean)
|
||||
.join(' | ')}
|
||||
{#if columnId === '$id'}
|
||||
<button
|
||||
on:mouseenter={() => {
|
||||
showExpandIconForId = index;
|
||||
}}
|
||||
on:mouseleave={() => {
|
||||
showExpandIconForId = null;
|
||||
}}>
|
||||
<Spreadsheet.Cell {root} {isEditable} column={columnId}>
|
||||
<Layout.Stack
|
||||
gap="none"
|
||||
direction="row"
|
||||
alignItems="center"
|
||||
alignContent="center"
|
||||
justifyContent="space-between">
|
||||
<Id value={row.$id} tooltipPortal tooltipDelay={200}
|
||||
>{row.$id}</Id>
|
||||
|
||||
{#if displayValue}
|
||||
<Link.Button
|
||||
variant="muted"
|
||||
on:click={() => {
|
||||
$databaseRelatedRowSheetOptions.tableId =
|
||||
row[columnId]?.['$tableId'];
|
||||
$databaseRelatedRowSheetOptions.rows =
|
||||
row[columnId]?.['$id'];
|
||||
$databaseRelatedRowSheetOptions.show = true;
|
||||
}}>
|
||||
{displayValue}
|
||||
</Link.Button>
|
||||
<Popover let:show let:hide portal padding="none">
|
||||
{@const opacityValue =
|
||||
showExpandIconForId === index ? '1' : '0'}
|
||||
<button
|
||||
on:mouseenter={show}
|
||||
on:mouseleave={hide}
|
||||
style:opacity={opacityValue}
|
||||
style:transition="opacity 225ms ease-in-out">
|
||||
<Button.Button
|
||||
size="xs"
|
||||
icon
|
||||
variant="secondary"
|
||||
on:click={() => {
|
||||
hide();
|
||||
previouslyFocusedElement =
|
||||
document.activeElement;
|
||||
onSelectSheetOption(
|
||||
'update',
|
||||
null,
|
||||
'row',
|
||||
row
|
||||
);
|
||||
}}>
|
||||
<Icon icon={IconArrowExpand} size="s" />
|
||||
</Button.Button>
|
||||
</button>
|
||||
|
||||
<svelte:fragment slot="tooltip">
|
||||
<Layout.Stack
|
||||
inline
|
||||
gap="xxs"
|
||||
direction="row"
|
||||
alignItems="center"
|
||||
alignContent="center"
|
||||
style="padding: var(--gap-XS, 6px) var(--gap-S, 8px);">
|
||||
Expand row
|
||||
|
||||
<Layout.Stack
|
||||
inline
|
||||
gap="xxxs"
|
||||
direction="row"
|
||||
alignItems="center"
|
||||
alignContent="center">
|
||||
<Keyboard size="s" key="⌘" />
|
||||
<Keyboard
|
||||
key={'Enter'}
|
||||
autoWidth
|
||||
size="s" />
|
||||
</Layout.Stack>
|
||||
</Layout.Stack>
|
||||
</svelte:fragment>
|
||||
</Popover>
|
||||
</Layout.Stack>
|
||||
</Spreadsheet.Cell>
|
||||
</button>
|
||||
{:else}
|
||||
<Spreadsheet.Cell {root} {isEditable} column={columnId}>
|
||||
{#if columnId === '$createdAt' || columnId === '$updatedAt'}
|
||||
<DualTimeView
|
||||
showDatetime
|
||||
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 = getDisplayNamesForTable(row[columnId])}
|
||||
{#if !isRelationshipToMany(rowColumn)}
|
||||
{#if row[columnId]}
|
||||
{@const displayValue = args
|
||||
.map((arg) => row[columnId]?.[arg])
|
||||
.filter(Boolean)
|
||||
.join(' | ')}
|
||||
|
||||
{#if displayValue}
|
||||
<Link.Button
|
||||
variant="muted"
|
||||
on:click={() => {
|
||||
$databaseRelatedRowSheetOptions.tableId =
|
||||
row[columnId]?.['$tableId'];
|
||||
$databaseRelatedRowSheetOptions.rows =
|
||||
row[columnId]?.['$id'];
|
||||
$databaseRelatedRowSheetOptions.show = true;
|
||||
}}>
|
||||
{displayValue}
|
||||
</Link.Button>
|
||||
{:else}
|
||||
<Badge
|
||||
variant="secondary"
|
||||
content="NULL"
|
||||
size="xs" />
|
||||
{/if}
|
||||
{:else}
|
||||
<Badge
|
||||
variant="secondary"
|
||||
@@ -924,89 +1026,90 @@
|
||||
size="xs" />
|
||||
{/if}
|
||||
{:else}
|
||||
<Badge variant="secondary" content="NULL" size="xs" />
|
||||
{@const itemsNum = row[columnId]?.length}
|
||||
Items <Badge
|
||||
content={itemsNum}
|
||||
variant="secondary"
|
||||
size="s" />
|
||||
{/if}
|
||||
{:else if isSpatialType(rowColumn) && row[columnId] !== null}
|
||||
<Typography.Text truncate>
|
||||
{JSON.stringify(row[columnId])}
|
||||
</Typography.Text>
|
||||
{:else}
|
||||
{@const itemsNum = row[columnId]?.length}
|
||||
Items <Badge
|
||||
content={itemsNum}
|
||||
variant="secondary"
|
||||
size="s" />
|
||||
{/if}
|
||||
{:else if isSpatialType(rowColumn) && row[columnId] !== null}
|
||||
<Typography.Text truncate>
|
||||
{JSON.stringify(row[columnId])}
|
||||
</Typography.Text>
|
||||
{: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}
|
||||
{#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 isEmptyArray = formatted === 'Empty'}
|
||||
{@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>
|
||||
<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 if isEmptyArray}
|
||||
<Badge
|
||||
variant="secondary"
|
||||
content={formatted}
|
||||
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 if isEmptyArray}
|
||||
<Badge variant="secondary" content={formatted} size="xs" />
|
||||
{:else}
|
||||
<Typography.Text truncate>
|
||||
{formatted}
|
||||
</Typography.Text>
|
||||
{/if}
|
||||
{/if}
|
||||
{/if}
|
||||
|
||||
<svelte:fragment slot="cell-editor" let:close>
|
||||
{@const isRelatedToMany = isRelationshipToMany(rowColumn)}
|
||||
{@const hasItems = isRelatedToMany
|
||||
? row[columnId]?.length
|
||||
: false}
|
||||
<svelte:fragment slot="cell-editor" let:close>
|
||||
{@const isRelatedToMany = isRelationshipToMany(rowColumn)}
|
||||
{@const hasItems = isRelatedToMany
|
||||
? row[columnId]?.length
|
||||
: false}
|
||||
|
||||
<EditRowCell
|
||||
{row}
|
||||
column={rowColumn}
|
||||
onRowStructureUpdate={updateRowContents}
|
||||
noInlineEdit={isRelatedToMany && hasItems}
|
||||
onChange={(row) => paginatedRows.update(index, row)}
|
||||
onRevert={(row) => paginatedRows.update(index, row)}
|
||||
openSideSheet={() => {
|
||||
close(); /* closes the editor */
|
||||
<EditRowCell
|
||||
{row}
|
||||
column={rowColumn}
|
||||
onRowStructureUpdate={updateRowContents}
|
||||
noInlineEdit={isRelatedToMany && hasItems}
|
||||
onChange={(row) => paginatedRows.update(index, row)}
|
||||
onRevert={(row) => paginatedRows.update(index, row)}
|
||||
openSideSheet={() => {
|
||||
close(); /* closes the editor */
|
||||
|
||||
if (isRelationshipToMany(rowColumn)) {
|
||||
openSideSheetForRelationsToMany(
|
||||
row[columnId],
|
||||
rowColumn
|
||||
);
|
||||
} else {
|
||||
onSelectSheetOption('update', null, 'row', row);
|
||||
}
|
||||
}} />
|
||||
</svelte:fragment>
|
||||
</Spreadsheet.Cell>
|
||||
if (isRelationshipToMany(rowColumn)) {
|
||||
openSideSheetForRelationsToMany(
|
||||
row[columnId],
|
||||
rowColumn
|
||||
);
|
||||
} else {
|
||||
onSelectSheetOption('update', null, 'row', row);
|
||||
}
|
||||
}} />
|
||||
</svelte:fragment>
|
||||
</Spreadsheet.Cell>
|
||||
{/if}
|
||||
{/each}
|
||||
</Spreadsheet.Row.Base>
|
||||
{/if}
|
||||
|
||||
Reference in New Issue
Block a user