mirror of
https://github.com/appwrite/console.git
synced 2026-06-06 19:27:48 +00:00
right click actions
This commit is contained in:
@@ -198,7 +198,16 @@ export enum Click {
|
||||
WebsiteOpenClick = 'click_open_website',
|
||||
CopyPromptStarterKitClick = 'click_copy_prompt_starter_kit',
|
||||
OpenInCursorClick = 'click_open_in_cursor',
|
||||
OpenInLovableClick = 'click_open_in_lovable'
|
||||
OpenInLovableClick = 'click_open_in_lovable',
|
||||
RowCopyUrl = 'click_row_copy_url',
|
||||
RowCopyJson = 'click_row_copy_json',
|
||||
RowCopySnippet = 'click_row_copy_snippet',
|
||||
RowContextMenuOpen = 'click_row_context_menu_open',
|
||||
RowUpdate = 'click_row_update',
|
||||
RowDuplicate = 'click_row_duplicate',
|
||||
RowDelete = 'click_row_delete',
|
||||
RowPermissions = 'click_row_permissions',
|
||||
RowActivity = 'click_row_activity'
|
||||
}
|
||||
|
||||
export enum Submit {
|
||||
|
||||
+159
@@ -0,0 +1,159 @@
|
||||
<script lang="ts">
|
||||
import { createContextMenu, melt } from '@melt-ui/svelte';
|
||||
import { Card, ActionMenu, Divider } from '@appwrite.io/pink-svelte';
|
||||
import type { ComponentType } from 'svelte';
|
||||
import {
|
||||
IconPencil,
|
||||
IconDuplicate,
|
||||
IconKey,
|
||||
IconChartBar,
|
||||
IconLink,
|
||||
IconTrash,
|
||||
IconClipboardCopy,
|
||||
IconCode,
|
||||
IconChevronRight
|
||||
} from '@appwrite.io/pink-icons-svelte';
|
||||
import type { RowCellAction } from './sheetOptions.svelte';
|
||||
import { trackEvent, Click } from '$lib/actions/analytics';
|
||||
|
||||
export let onSelect: (action: RowCellAction) => void;
|
||||
|
||||
const {
|
||||
elements: { menu, trigger, item, separator },
|
||||
builders: { createSubmenu }
|
||||
} = createContextMenu({
|
||||
positioning: {
|
||||
placement: 'right-start',
|
||||
gutter: 2,
|
||||
sameWidth: false
|
||||
},
|
||||
onOpenChange: ({ next }) => {
|
||||
if (next) {
|
||||
trackEvent(Click.RowContextMenuOpen);
|
||||
}
|
||||
return next;
|
||||
}
|
||||
});
|
||||
|
||||
const {
|
||||
elements: { subMenu: copySubMenu, subTrigger: copySubTrigger }
|
||||
} = createSubmenu({
|
||||
positioning: {
|
||||
placement: 'right-start',
|
||||
gutter: 8
|
||||
},
|
||||
arrowSize: 0
|
||||
});
|
||||
|
||||
const menuItems: {
|
||||
label?: string;
|
||||
icon?: ComponentType;
|
||||
action?: RowCellAction;
|
||||
subMenu?: string;
|
||||
divider?: boolean;
|
||||
danger?: boolean;
|
||||
}[] = [
|
||||
{ label: 'Update row', icon: IconPencil, action: 'update' },
|
||||
{ label: 'Duplicate row', icon: IconDuplicate, action: 'duplicate-row' },
|
||||
{ divider: true },
|
||||
{ label: 'Manage permissions', icon: IconKey, action: 'permissions' },
|
||||
{ label: 'View activity', icon: IconChartBar, action: 'activity' },
|
||||
{ divider: true },
|
||||
{ label: 'Copy', icon: IconDuplicate, subMenu: 'copy' },
|
||||
{ divider: true },
|
||||
{ label: 'Delete', icon: IconTrash, action: 'delete', danger: true }
|
||||
];
|
||||
|
||||
const copyMenuItems: { label: string; icon: ComponentType; action: RowCellAction }[] = [
|
||||
{ label: 'URL', icon: IconLink, action: 'copy-url' },
|
||||
{ label: 'JSON', icon: IconClipboardCopy, action: 'copy-json' },
|
||||
{ label: 'Code snippet', icon: IconCode, action: 'copy-snippet' }
|
||||
];
|
||||
</script>
|
||||
|
||||
<div use:melt={$trigger} style="display: contents;">
|
||||
<slot />
|
||||
</div>
|
||||
|
||||
<div class="menu" use:melt={$menu}>
|
||||
<Card.Base padding="none">
|
||||
<div class="action-menu-root">
|
||||
<ActionMenu.Root>
|
||||
{#each menuItems as menuItem, index}
|
||||
{#if menuItem.divider}
|
||||
{@const isLastDivider = index === menuItems.length - 2}
|
||||
<div
|
||||
use:melt={$separator}
|
||||
style:margin-inline="-1rem"
|
||||
style:padding-block-start="0.5rem"
|
||||
style:padding-block-end={isLastDivider ? '0.25rem' : '0.5rem'}>
|
||||
<Divider />
|
||||
</div>
|
||||
{:else if menuItem.subMenu === 'copy'}
|
||||
<!-- Copy Submenu Trigger -->
|
||||
<div use:melt={$copySubTrigger} class="sub-trigger">
|
||||
<ActionMenu.Root>
|
||||
<ActionMenu.Item.Button
|
||||
leadingIcon={menuItem.icon}
|
||||
trailingIcon={IconChevronRight}>
|
||||
{menuItem.label}
|
||||
</ActionMenu.Item.Button>
|
||||
</ActionMenu.Root>
|
||||
</div>
|
||||
{:else}
|
||||
<div
|
||||
use:melt={$item}
|
||||
style:display="contents"
|
||||
on:m-click={() => onSelect(menuItem.action as RowCellAction)}>
|
||||
<ActionMenu.Item.Button
|
||||
leadingIcon={menuItem.icon}
|
||||
status={menuItem.danger ? 'danger' : undefined}>
|
||||
{menuItem.label}
|
||||
</ActionMenu.Item.Button>
|
||||
</div>
|
||||
{/if}
|
||||
{/each}
|
||||
</ActionMenu.Root>
|
||||
</div>
|
||||
|
||||
<!-- Copy Submenu (outside ActionMenu.Root, inside Card.Base) -->
|
||||
<div class="menu submenu" use:melt={$copySubMenu}>
|
||||
<Card.Base padding="none">
|
||||
<div class="action-menu-root">
|
||||
{#each copyMenuItems as copyItem}
|
||||
<div use:melt={$item} on:m-click={() => onSelect(copyItem.action)}>
|
||||
<ActionMenu.Root>
|
||||
<ActionMenu.Item.Button leadingIcon={copyItem.icon}>
|
||||
{copyItem.label}
|
||||
</ActionMenu.Item.Button>
|
||||
</ActionMenu.Root>
|
||||
</div>
|
||||
{/each}
|
||||
</div>
|
||||
</Card.Base>
|
||||
</div>
|
||||
</Card.Base>
|
||||
</div>
|
||||
|
||||
<style>
|
||||
.menu {
|
||||
z-index: 50;
|
||||
min-width: 220px;
|
||||
outline: none;
|
||||
border-radius: var(--border-radius-m);
|
||||
box-shadow: var(--shadow-large);
|
||||
}
|
||||
|
||||
.submenu {
|
||||
margin-inline: -4px;
|
||||
margin-block: -4px;
|
||||
}
|
||||
|
||||
.action-menu-root {
|
||||
margin-inline-start: calc(var(--space-2) * -1);
|
||||
|
||||
& :global(:first-child) {
|
||||
overflow: visible;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
+1
-1
@@ -16,7 +16,7 @@
|
||||
| 'activity'
|
||||
| 'copy-url'
|
||||
| 'copy-json'
|
||||
// | 'copy-snippet'
|
||||
| 'copy-snippet'
|
||||
| 'delete';
|
||||
</script>
|
||||
|
||||
|
||||
+271
-219
@@ -1,7 +1,7 @@
|
||||
<script lang="ts">
|
||||
import { page } from '$app/state';
|
||||
import { goto, invalidate } from '$app/navigation';
|
||||
import { Submit, trackError, trackEvent } from '$lib/actions/analytics';
|
||||
import { Submit, trackError, trackEvent, Click } from '$lib/actions/analytics';
|
||||
import { Confirm, Id, SortButton } from '$lib/components';
|
||||
import { Dependencies, SPREADSHEET_PAGE_LIMIT } from '$lib/constants';
|
||||
import { Button as ConsoleButton, InputSelect } from '$lib/elements/forms';
|
||||
@@ -96,6 +96,7 @@
|
||||
import { formatNumberWithCommas } from '$lib/helpers/numbers';
|
||||
import { chunks } from '$lib/helpers/array';
|
||||
import { mapToQueryParams } from '$lib/components/filters/store';
|
||||
import RowContextMenu from './rowContextMenu.svelte';
|
||||
|
||||
export let data: PageData;
|
||||
export let showRowCreateSheet: {
|
||||
@@ -504,7 +505,8 @@
|
||||
action: HeaderCellAction | RowCellAction,
|
||||
columnId: string,
|
||||
type: 'header' | 'row',
|
||||
row: Models.Row | null = null
|
||||
row: Models.Row | null = null,
|
||||
source: string = 'unknown'
|
||||
) {
|
||||
if (type === 'header') {
|
||||
if (action === 'update') {
|
||||
@@ -548,6 +550,7 @@
|
||||
}
|
||||
} else if (type === 'row') {
|
||||
if (action === 'update') {
|
||||
trackEvent(Click.RowUpdate, { source });
|
||||
databaseRowSheetOptions.update((opts) => {
|
||||
const rowIndex = rowIndexMap.get(row.$id) ?? -1;
|
||||
return {
|
||||
@@ -562,6 +565,7 @@
|
||||
}
|
||||
|
||||
if (action === 'duplicate-row') {
|
||||
trackEvent(Click.RowDuplicate, { source });
|
||||
/**
|
||||
* remove dates because
|
||||
* console can override timestamps!
|
||||
@@ -573,6 +577,7 @@
|
||||
}
|
||||
|
||||
if (action === 'permissions') {
|
||||
trackEvent(Click.RowPermissions, { source });
|
||||
$rowPermissionSheet.row = row;
|
||||
$rowPermissionSheet.show = true;
|
||||
}
|
||||
@@ -580,6 +585,7 @@
|
||||
if (action === 'copy-url') {
|
||||
try {
|
||||
await copy(buildRowUrl(row.$id));
|
||||
trackEvent(Click.RowCopyUrl, { source });
|
||||
addNotification({
|
||||
type: 'success',
|
||||
message: 'Row url copied'
|
||||
@@ -596,6 +602,7 @@
|
||||
const stringified = JSON.stringify(row, null, 2);
|
||||
try {
|
||||
await copy(stringified);
|
||||
trackEvent(Click.RowCopyJson, { source });
|
||||
addNotification({
|
||||
type: 'success',
|
||||
message: 'Row copied'
|
||||
@@ -608,12 +615,35 @@
|
||||
}
|
||||
}
|
||||
|
||||
if (action === 'copy-snippet') {
|
||||
const snippet = `const result = await databases.getDocument(
|
||||
'${databaseId}', // databaseId
|
||||
'${tableId}', // collectionId
|
||||
'${row.$id}', // documentId
|
||||
);`;
|
||||
try {
|
||||
await copy(snippet);
|
||||
trackEvent(Click.RowCopySnippet, { source });
|
||||
addNotification({
|
||||
type: 'success',
|
||||
message: 'Code snippet copied'
|
||||
});
|
||||
} catch (e) {
|
||||
addNotification({
|
||||
type: 'error',
|
||||
message: e.message
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
if (action === 'delete') {
|
||||
trackEvent(Click.RowDelete, { source });
|
||||
showDelete = true;
|
||||
selectedRowForDelete = row.$id;
|
||||
}
|
||||
|
||||
if (action === 'activity') {
|
||||
trackEvent(Click.RowActivity, { source });
|
||||
$rowActivitySheet.row = row;
|
||||
$rowActivitySheet.show = true;
|
||||
}
|
||||
@@ -891,141 +921,158 @@
|
||||
{/each}
|
||||
</Spreadsheet.Row.Base>
|
||||
{:else}
|
||||
<Spreadsheet.Row.Base
|
||||
{root}
|
||||
{index}
|
||||
id={row?.$id}
|
||||
virtualItem={item}
|
||||
select={rowSelection}
|
||||
hoverEffect
|
||||
showSelectOnHover
|
||||
valueWithoutHover={row.$sequence}>
|
||||
{#each $tableColumns as { id: columnId, isEditable, hide } (columnId)}
|
||||
{@const rowColumn = $columns.find((col) => col.key === columnId)}
|
||||
{#if columnId === '$id' && !hide}
|
||||
<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>
|
||||
<RowContextMenu
|
||||
onSelect={(action) => {
|
||||
$databaseRowSheetOptions.autoFocus = true;
|
||||
onSelectSheetOption(action, null, 'row', row, 'context-menu');
|
||||
}}>
|
||||
<Spreadsheet.Row.Base
|
||||
{root}
|
||||
{index}
|
||||
id={row?.$id}
|
||||
virtualItem={item}
|
||||
select={rowSelection}
|
||||
hoverEffect
|
||||
showSelectOnHover
|
||||
valueWithoutHover={row.$sequence}>
|
||||
{#each $tableColumns as { id: columnId, isEditable, hide } (columnId)}
|
||||
{@const rowColumn = $columns.find((col) => col.key === columnId)}
|
||||
{#if columnId === '$id' && !hide}
|
||||
<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>
|
||||
|
||||
<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;
|
||||
$databaseRowSheetOptions.autoFocus = false;
|
||||
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
|
||||
<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;
|
||||
$databaseRowSheetOptions.autoFocus = false;
|
||||
onSelectSheetOption(
|
||||
'update',
|
||||
null,
|
||||
'row',
|
||||
row
|
||||
);
|
||||
}}>
|
||||
<Icon icon={IconArrowExpand} size="s" />
|
||||
</Button.Button>
|
||||
</button>
|
||||
|
||||
<svelte:fragment slot="tooltip">
|
||||
<Layout.Stack
|
||||
inline
|
||||
gap="xxxs"
|
||||
gap="xxs"
|
||||
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) => {
|
||||
$databaseRowSheetOptions.autoFocus = true;
|
||||
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(' | ')}
|
||||
alignContent="center"
|
||||
style="padding: var(--gap-XS, 6px) var(--gap-S, 8px);">
|
||||
Expand row
|
||||
|
||||
{#if displayValue}
|
||||
<Link.Button
|
||||
variant="muted"
|
||||
on:click={() => {
|
||||
$databaseRelatedRowSheetOptions.tableId =
|
||||
row[columnId]?.['$tableId'];
|
||||
$databaseRelatedRowSheetOptions.rows =
|
||||
row[columnId]?.['$id'];
|
||||
$databaseRelatedRowSheetOptions.show = true;
|
||||
}}>
|
||||
{displayValue}
|
||||
</Link.Button>
|
||||
<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) => {
|
||||
$databaseRowSheetOptions.autoFocus = true;
|
||||
onSelectSheetOption(
|
||||
option,
|
||||
null,
|
||||
'row',
|
||||
row,
|
||||
'button-menu'
|
||||
);
|
||||
}}
|
||||
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"
|
||||
@@ -1033,106 +1080,111 @@
|
||||
size="xs" />
|
||||
{/if}
|
||||
{: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>
|
||||
<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" />
|
||||
{/if}
|
||||
{: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>
|
||||
{: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={async (row) => {
|
||||
const success = await updateRowContents(row);
|
||||
if (success) {
|
||||
// database update succeeded!
|
||||
paginatedRows.update(index, row);
|
||||
}
|
||||
return success;
|
||||
}}
|
||||
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={async (row) => {
|
||||
const success = await updateRowContents(row);
|
||||
if (success) {
|
||||
// database update succeeded!
|
||||
paginatedRows.update(index, row);
|
||||
}
|
||||
return success;
|
||||
}}
|
||||
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 {
|
||||
$databaseRowSheetOptions.autoFocus = true;
|
||||
onSelectSheetOption('update', null, 'row', row);
|
||||
}
|
||||
}} />
|
||||
</svelte:fragment>
|
||||
</Spreadsheet.Cell>
|
||||
{/if}
|
||||
{/each}
|
||||
</Spreadsheet.Row.Base>
|
||||
if (isRelationshipToMany(rowColumn)) {
|
||||
openSideSheetForRelationsToMany(
|
||||
row[columnId],
|
||||
rowColumn
|
||||
);
|
||||
} else {
|
||||
$databaseRowSheetOptions.autoFocus = true;
|
||||
onSelectSheetOption(
|
||||
'update',
|
||||
null,
|
||||
'row',
|
||||
row
|
||||
);
|
||||
}
|
||||
}} />
|
||||
</svelte:fragment>
|
||||
</Spreadsheet.Cell>
|
||||
{/if}
|
||||
{/each}
|
||||
</Spreadsheet.Row.Base>
|
||||
</RowContextMenu>
|
||||
{/if}
|
||||
</svelte:fragment>
|
||||
|
||||
|
||||
Reference in New Issue
Block a user