mirror of
https://github.com/appwrite/console.git
synced 2026-06-06 19:27:48 +00:00
feat: inline cell edits for arrays with options for advance edits.
fix: top spacing on mobile for side-sheets, make em default instead of passing a prop, which can be missed sometimes. fix: issue with non-persistent expanded tab after toggle.
This commit is contained in:
@@ -51,4 +51,5 @@
|
||||
on:input
|
||||
bind:value>
|
||||
<slot name="info" slot="info" />
|
||||
<slot name="end" slot="end" />
|
||||
</Input.Textarea>
|
||||
|
||||
+6
-15
@@ -33,7 +33,6 @@
|
||||
spreadsheetLoading,
|
||||
rowActivitySheet,
|
||||
spreadsheetRenderKey,
|
||||
columnsWidth,
|
||||
expandTabs
|
||||
} from './store';
|
||||
import { addSubPanel, registerCommands, updateCommandGroupRanks } from '$lib/commandCenter';
|
||||
@@ -244,14 +243,10 @@
|
||||
$spreadsheetLoading = true;
|
||||
$randomDataModalState.show = false;
|
||||
|
||||
let attributes = $table.columns;
|
||||
if (!attributes.length) {
|
||||
let columns = $table.columns;
|
||||
if (!columns.length) {
|
||||
try {
|
||||
attributes = await generateColumns(
|
||||
$project,
|
||||
page.params.database,
|
||||
page.params.table
|
||||
);
|
||||
columns = await generateColumns($project, page.params.database, page.params.table);
|
||||
|
||||
await invalidate(Dependencies.TABLE);
|
||||
} catch (e) {
|
||||
@@ -264,12 +259,12 @@
|
||||
}
|
||||
}
|
||||
|
||||
/* let the attributes be processed! */
|
||||
/* let the columns be processed! */
|
||||
await sleep(1250);
|
||||
|
||||
let rowIds = [];
|
||||
try {
|
||||
const { rows, ids } = generateFakeRecords(attributes, $randomDataModalState.value);
|
||||
const { rows, ids } = generateFakeRecords(columns, $randomDataModalState.value);
|
||||
|
||||
rowIds = ids;
|
||||
|
||||
@@ -311,7 +306,6 @@
|
||||
<slot />
|
||||
|
||||
<SideSheet
|
||||
spaced
|
||||
closeOnBlur
|
||||
title={$showCreateAttributeSheet.title}
|
||||
bind:show={$showCreateAttributeSheet.show}
|
||||
@@ -337,7 +331,6 @@
|
||||
</SideSheet>
|
||||
|
||||
<SideSheet
|
||||
spaced
|
||||
closeOnBlur
|
||||
title={$databaseColumnSheetOptions.title}
|
||||
bind:show={$databaseColumnSheetOptions.show}
|
||||
@@ -353,7 +346,6 @@
|
||||
</SideSheet>
|
||||
|
||||
<SideSheet
|
||||
spaced
|
||||
closeOnBlur
|
||||
title={$databaseRowSheetOptions.title}
|
||||
bind:show={$databaseRowSheetOptions.show}
|
||||
@@ -366,7 +358,6 @@
|
||||
</SideSheet>
|
||||
|
||||
<SideSheet
|
||||
spaced
|
||||
closeOnBlur
|
||||
title="Create index"
|
||||
bind:show={$showCreateIndexSheet.show}
|
||||
@@ -382,7 +373,7 @@
|
||||
externalColumnKey={$showCreateIndexSheet.column} />
|
||||
</SideSheet>
|
||||
|
||||
<SideSheet spaced title="Row activity" bind:show={$rowActivitySheet.show} closeOnBlur>
|
||||
<SideSheet title="Row activity" bind:show={$rowActivitySheet.show} closeOnBlur>
|
||||
<RowActivity />
|
||||
</SideSheet>
|
||||
|
||||
|
||||
+4
-1
@@ -138,7 +138,10 @@
|
||||
icon
|
||||
secondary
|
||||
disabled={!(hasColumns && hasValidColumns)}
|
||||
on:click={() => ($expandTabs = !$expandTabs)}>
|
||||
on:click={() => {
|
||||
$expandTabs = !$expandTabs;
|
||||
preferences.setTableHeaderExpanded($table.$id, $expandTabs);
|
||||
}}>
|
||||
<Icon icon={!$expandTabs ? IconChevronDown : IconChevronUp} size="s" />
|
||||
</Button>
|
||||
{/if}
|
||||
|
||||
+4
-4
@@ -128,8 +128,8 @@
|
||||
{#if selectedColumn?.type !== 'relationship'}
|
||||
<InputText
|
||||
id="key"
|
||||
label="Column Key"
|
||||
placeholder="Enter Key"
|
||||
label="Column key"
|
||||
placeholder="Enter key"
|
||||
bind:value={selectedColumn.key}
|
||||
autofocus />
|
||||
{/if}
|
||||
@@ -153,8 +153,8 @@
|
||||
{#if selectedColumn?.type !== 'relationship'}
|
||||
<InputText
|
||||
id="key"
|
||||
label="Attribute Key"
|
||||
placeholder="Enter Key"
|
||||
label="Column key"
|
||||
placeholder="Enter key"
|
||||
bind:value={selectedColumn.key}
|
||||
autofocus />
|
||||
{/if}
|
||||
|
||||
+37
-41
@@ -105,47 +105,43 @@
|
||||
</script>
|
||||
|
||||
{#if $createRow}
|
||||
<div class="sheet-container">
|
||||
<!-- TODO: add a ID badge-->
|
||||
<SideSheet
|
||||
spaced
|
||||
title={`${existingData ? 'Duplicate' : 'Create'} row`}
|
||||
bind:show={showSheet}
|
||||
closeOnBlur={false}
|
||||
submit={{
|
||||
text: 'Create',
|
||||
disabled: isSubmitting,
|
||||
onClick: () => create()
|
||||
}}>
|
||||
<Layout.Stack gap="xxl">
|
||||
<div bind:this={columnFormWrapper}>
|
||||
<ColumnForm
|
||||
columns={$createRow.columns}
|
||||
bind:customId={$createRow.id}
|
||||
bind:formValues={$createRow.row} />
|
||||
</div>
|
||||
<SideSheet
|
||||
title={`${existingData ? 'Duplicate' : 'Create'} row`}
|
||||
bind:show={showSheet}
|
||||
closeOnBlur={false}
|
||||
submit={{
|
||||
text: 'Create',
|
||||
disabled: isSubmitting,
|
||||
onClick: () => create()
|
||||
}}>
|
||||
<Layout.Stack gap="xxl">
|
||||
<div bind:this={columnFormWrapper}>
|
||||
<ColumnForm
|
||||
columns={$createRow.columns}
|
||||
bind:customId={$createRow.id}
|
||||
bind:formValues={$createRow.row} />
|
||||
</div>
|
||||
|
||||
<Layout.Stack gap="xl">
|
||||
<Typography.Text>
|
||||
Choose which permission scopes to grant your application. It is best
|
||||
practice to allow only the permissions you need to meet your project goals.
|
||||
</Typography.Text>
|
||||
{#if table.rowSecurity}
|
||||
<Alert.Inline status="info">
|
||||
<svelte:fragment slot="title">Row security is enabled</svelte:fragment>
|
||||
Users will be able to access this row if they have been granted
|
||||
<b>either row or table permissions</b>.
|
||||
</Alert.Inline>
|
||||
<Permissions bind:permissions={$createRow.permissions} />
|
||||
{:else}
|
||||
<Alert.Inline status="info">
|
||||
<svelte:fragment slot="title">Row security is disabled</svelte:fragment>
|
||||
If you want to assign row permissions, navigate to Table settings and enable
|
||||
row security. Otherwise, only table permissions will be used.
|
||||
</Alert.Inline>
|
||||
{/if}
|
||||
</Layout.Stack>
|
||||
<Layout.Stack gap="xl">
|
||||
<Typography.Text>
|
||||
Choose which permission scopes to grant your application. It is best practice to
|
||||
allow only the permissions you need to meet your project goals.
|
||||
</Typography.Text>
|
||||
{#if table.rowSecurity}
|
||||
<Alert.Inline status="info">
|
||||
<svelte:fragment slot="title">Row security is enabled</svelte:fragment>
|
||||
Users will be able to access this row if they have been granted
|
||||
<b>either row or table permissions</b>.
|
||||
</Alert.Inline>
|
||||
<Permissions bind:permissions={$createRow.permissions} />
|
||||
{:else}
|
||||
<Alert.Inline status="info">
|
||||
<svelte:fragment slot="title">Row security is disabled</svelte:fragment>
|
||||
If you want to assign row permissions, navigate to Table settings and enable
|
||||
row security. Otherwise, only table permissions will be used.
|
||||
</Alert.Inline>
|
||||
{/if}
|
||||
</Layout.Stack>
|
||||
</SideSheet>
|
||||
</div>
|
||||
</Layout.Stack>
|
||||
</SideSheet>
|
||||
{/if}
|
||||
|
||||
+16
-5
@@ -1,23 +1,28 @@
|
||||
<script lang="ts">
|
||||
import deepEqual from 'deep-equal';
|
||||
import type { Columns } from './store';
|
||||
import { onDestroy, onMount } from 'svelte';
|
||||
import type { Models } from '@appwrite.io/console';
|
||||
import ColumnItem from './row-[row]/columnItem.svelte';
|
||||
import { createEventDispatcher, onDestroy, onMount } from 'svelte';
|
||||
|
||||
let {
|
||||
column,
|
||||
row = $bindable(null),
|
||||
onChange = null,
|
||||
onRevert = null,
|
||||
openSideSheet = null,
|
||||
onRowStructureUpdate = null
|
||||
}: {
|
||||
row: Models.Row;
|
||||
column: Columns;
|
||||
openSideSheet?: () => void;
|
||||
onChange?: (row: Models.DefaultRow) => void;
|
||||
onRevert?: (row: Models.DefaultRow) => void;
|
||||
onRowStructureUpdate?: (row: Models.Row) => Promise<boolean>;
|
||||
} = $props();
|
||||
|
||||
let original: Models.Row;
|
||||
let wrapperEl: HTMLDivElement;
|
||||
const dispatch = createEventDispatcher();
|
||||
|
||||
onMount(() => {
|
||||
original = structuredClone(row);
|
||||
@@ -38,18 +43,24 @@
|
||||
const accepted = await onRowStructureUpdate(row);
|
||||
if (!accepted) {
|
||||
row = original;
|
||||
dispatch('revert', original);
|
||||
onRevert?.(original as Models.DefaultRow);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
$effect(() => {
|
||||
if (!deepEqual(original, row)) {
|
||||
dispatch('change', row);
|
||||
onChange?.(row as Models.DefaultRow);
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
<div style:width="100%" bind:this={wrapperEl}>
|
||||
<ColumnItem {column} fromSpreadsheet bind:formValues={row} label={undefined} editing />
|
||||
<ColumnItem
|
||||
{column}
|
||||
editing
|
||||
fromSpreadsheet
|
||||
label={undefined}
|
||||
bind:formValues={row}
|
||||
on:click={openSideSheet} />
|
||||
</div>
|
||||
|
||||
+8
@@ -8,6 +8,7 @@
|
||||
import { canWriteTables } from '$lib/stores/roles';
|
||||
import { expandTabs, table } from './store';
|
||||
import { Layout } from '@appwrite.io/pink-svelte';
|
||||
import { preferences } from '$lib/stores/preferences';
|
||||
|
||||
const databaseId = $derived(page.params.database);
|
||||
|
||||
@@ -65,6 +66,13 @@
|
||||
const isSpreadsheetPage = endings.some((end) => page.route.id?.endsWith(end));
|
||||
return !isSpreadsheetPage;
|
||||
});
|
||||
|
||||
$effect(() => {
|
||||
if (nonSheetPages) expandTabs.set(true);
|
||||
else {
|
||||
expandTabs.set(preferences.isTableHeaderExpanded(tableId));
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
<div class:nonSheetPages>
|
||||
|
||||
+2
-4
@@ -6,14 +6,12 @@
|
||||
let {
|
||||
show = $bindable(false),
|
||||
title,
|
||||
spaced = false,
|
||||
closeOnBlur = false,
|
||||
submit,
|
||||
children = null
|
||||
}: {
|
||||
show: boolean;
|
||||
title: string;
|
||||
spaced?: boolean;
|
||||
closeOnBlur?: boolean;
|
||||
submit?:
|
||||
| {
|
||||
@@ -26,7 +24,7 @@
|
||||
} = $props();
|
||||
</script>
|
||||
|
||||
<div class="sheet-container" class:spaced>
|
||||
<div class="sheet-container">
|
||||
<Sheet bind:open={show} {closeOnBlur}>
|
||||
<div slot="header" style:width="100%">
|
||||
<Layout.Stack direction="row" justifyContent="space-between" alignItems="center">
|
||||
@@ -79,7 +77,7 @@
|
||||
position: absolute;
|
||||
|
||||
@media (max-width: 768px) {
|
||||
&.spaced :global(aside header) {
|
||||
& :global(aside header) {
|
||||
margin-top: 6rem;
|
||||
}
|
||||
}
|
||||
|
||||
+6
-1
@@ -10,6 +10,7 @@
|
||||
|
||||
export let id: string;
|
||||
export let label: string;
|
||||
export let array: boolean | undefined = undefined;
|
||||
export let optionalText: string | undefined = undefined;
|
||||
export let value: string | number | boolean | null | string[];
|
||||
export let editing = false;
|
||||
@@ -50,17 +51,21 @@
|
||||
{label}
|
||||
{column}
|
||||
{limited}
|
||||
{array}
|
||||
{optionalText}
|
||||
bind:value />
|
||||
{:else}
|
||||
<!-- the `on:click` is from string > array mode for advanced edit button -->
|
||||
<svelte:component
|
||||
this={columnsTypeMap[column.type]}
|
||||
this={column.array ? columnsTypeMap['string'] : columnsTypeMap[column.type]}
|
||||
{id}
|
||||
{editing}
|
||||
{limited}
|
||||
{label}
|
||||
{column}
|
||||
{array}
|
||||
{optionalText}
|
||||
on:click
|
||||
bind:value />
|
||||
{/if}
|
||||
{/if}
|
||||
|
||||
+48
-17
@@ -6,11 +6,19 @@
|
||||
import Column from './column.svelte';
|
||||
import type { Columns } from '../store';
|
||||
|
||||
export let column: Columns;
|
||||
export let formValues: object = {};
|
||||
export let label: string;
|
||||
export let editing = false;
|
||||
export let fromSpreadsheet: boolean = false;
|
||||
let {
|
||||
column,
|
||||
formValues = $bindable({}),
|
||||
label,
|
||||
editing = false,
|
||||
fromSpreadsheet = false
|
||||
}: {
|
||||
column: Columns;
|
||||
formValues: object;
|
||||
label: string;
|
||||
editing?: boolean;
|
||||
fromSpreadsheet?: boolean;
|
||||
} = $props();
|
||||
|
||||
function removeArrayItem(key: string, index: number) {
|
||||
formValues = {
|
||||
@@ -48,18 +56,41 @@
|
||||
|
||||
{#if column.array}
|
||||
{#if formValues[column.key]?.length === 0}
|
||||
<Layout.Stack direction="row" alignContent="space-between">
|
||||
<Layout.Stack gap="xxs" direction="row" alignItems="center">
|
||||
<Typography.Text variant="m-500">{label}</Typography.Text>
|
||||
<Typography.Text variant="m-400" color="--fgcolor-neutral-tertiary">
|
||||
{getColumnType(column)}
|
||||
</Typography.Text>
|
||||
{#if fromSpreadsheet}
|
||||
<Column
|
||||
array
|
||||
{label}
|
||||
{column}
|
||||
{editing}
|
||||
id={column.key}
|
||||
limited={fromSpreadsheet}
|
||||
optionalText={getColumnType(column)}
|
||||
bind:value={formValues[column.key]}
|
||||
on:click />
|
||||
{:else}
|
||||
<Layout.Stack direction="row" alignContent="space-between">
|
||||
<Layout.Stack gap="xxs" direction="row" alignItems="center">
|
||||
<Typography.Text variant="m-500">{label}</Typography.Text>
|
||||
<Typography.Text variant="m-400" color="--fgcolor-neutral-tertiary">
|
||||
{getColumnType(column)}
|
||||
</Typography.Text>
|
||||
</Layout.Stack>
|
||||
<Button secondary on:click={() => addArrayItem(column.key)}>
|
||||
<Icon icon={IconPlus} slot="start" size="s" />
|
||||
Add item
|
||||
</Button>
|
||||
</Layout.Stack>
|
||||
<Button secondary on:click={() => addArrayItem(column.key)}>
|
||||
<Icon icon={IconPlus} slot="start" size="s" />
|
||||
Add item
|
||||
</Button>
|
||||
</Layout.Stack>
|
||||
{/if}
|
||||
{:else if fromSpreadsheet}
|
||||
<Column
|
||||
array
|
||||
{label}
|
||||
{column}
|
||||
{editing}
|
||||
id={column.key}
|
||||
limited={fromSpreadsheet}
|
||||
optionalText={getColumnType(column)}
|
||||
bind:value={formValues[column.key]} />
|
||||
{:else}
|
||||
<Layout.Stack>
|
||||
{#each [...(formValues[column.key]?.keys() ?? [])] as index}
|
||||
@@ -89,8 +120,8 @@
|
||||
{label}
|
||||
{editing}
|
||||
{column}
|
||||
limited={fromSpreadsheet}
|
||||
id={column.key}
|
||||
limited={fromSpreadsheet}
|
||||
optionalText={getColumnType(column)}
|
||||
bind:value={formValues[column.key]} />
|
||||
{/if}
|
||||
|
||||
+129
-15
@@ -1,38 +1,152 @@
|
||||
<script lang="ts">
|
||||
import { InputText, InputTextarea } from '$lib/elements/forms';
|
||||
import type { Models } from '@appwrite.io/console';
|
||||
import { Layout, Link } from '@appwrite.io/pink-svelte';
|
||||
import { InputText, InputTextarea } from '$lib/elements/forms';
|
||||
|
||||
export let id: string;
|
||||
export let label: string;
|
||||
export let value: string;
|
||||
export let limited: boolean = false;
|
||||
export let column: Models.ColumnString;
|
||||
let {
|
||||
id,
|
||||
label,
|
||||
value = $bindable(),
|
||||
array = false,
|
||||
limited = false,
|
||||
column
|
||||
}: {
|
||||
id: string;
|
||||
label: string;
|
||||
value: string | number | boolean | string[] | number[] | boolean[] | null;
|
||||
array?: boolean;
|
||||
limited?: boolean;
|
||||
column:
|
||||
| Models.ColumnString
|
||||
| Models.ColumnInteger
|
||||
| Models.ColumnFloat
|
||||
| Models.ColumnBoolean;
|
||||
} = $props();
|
||||
|
||||
$: autofocus = limited;
|
||||
$: maxlength = limited ? undefined : column.size;
|
||||
const autofocus = $derived(limited);
|
||||
const maxlength = $derived(
|
||||
limited
|
||||
? undefined
|
||||
: column.type === 'string'
|
||||
? (column as Models.ColumnString).size
|
||||
: undefined
|
||||
);
|
||||
|
||||
// TODO: do we need the nullable checkbox?
|
||||
$: nullable = !limited ? !column.required : false;
|
||||
const nullable = $derived(!limited ? !column.required : false);
|
||||
const columnSize = $derived('size' in column ? column.size : 0);
|
||||
|
||||
let stringValue = $state('');
|
||||
|
||||
function parseValue(str: string): number | boolean | string | null {
|
||||
const trimmed = str.trim();
|
||||
|
||||
if (!trimmed) return null;
|
||||
|
||||
switch (column.type) {
|
||||
case 'integer': {
|
||||
const int = parseInt(trimmed, 10);
|
||||
return isNaN(int) ? null : int;
|
||||
}
|
||||
|
||||
case 'double': {
|
||||
const float = parseFloat(trimmed);
|
||||
return isNaN(float) ? null : float;
|
||||
}
|
||||
|
||||
case 'boolean': {
|
||||
const lower = trimmed.toLowerCase();
|
||||
if (lower === 'true' || lower === '1') return true;
|
||||
if (lower === 'false' || lower === '0') return false;
|
||||
return null;
|
||||
}
|
||||
|
||||
case 'string':
|
||||
default:
|
||||
return trimmed;
|
||||
}
|
||||
}
|
||||
|
||||
$effect(() => {
|
||||
if (array && Array.isArray(value)) {
|
||||
stringValue = value.map(String).join(', ');
|
||||
} else if (value !== null && value !== undefined) {
|
||||
stringValue = String(value);
|
||||
} else {
|
||||
stringValue = '';
|
||||
}
|
||||
});
|
||||
|
||||
$effect(() => {
|
||||
if (array) {
|
||||
const newArray = stringValue
|
||||
.split(',')
|
||||
.map((item) => parseValue(item))
|
||||
.filter((item) => item !== null);
|
||||
|
||||
if (JSON.stringify(newArray) !== JSON.stringify(value)) {
|
||||
value = newArray as string[] | number[] | boolean[];
|
||||
}
|
||||
} else {
|
||||
const parsedValue = parseValue(stringValue);
|
||||
if (parsedValue !== value) {
|
||||
value = parsedValue;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
const getPlaceholder = () => {
|
||||
if (!array) {
|
||||
switch (column.type) {
|
||||
case 'integer':
|
||||
return 'Enter integer';
|
||||
case 'double':
|
||||
return 'Enter number';
|
||||
case 'boolean':
|
||||
return 'Enter true or false';
|
||||
case 'string':
|
||||
default:
|
||||
return 'Enter string';
|
||||
}
|
||||
} else {
|
||||
switch (column.type) {
|
||||
case 'integer':
|
||||
return 'Enter integers separated by commas';
|
||||
case 'double':
|
||||
return 'Enter numbers separated by commas';
|
||||
case 'boolean':
|
||||
return 'Enter true/false separated by commas';
|
||||
case 'string':
|
||||
default:
|
||||
return 'Enter strings separated by commas';
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
{#if column.size >= 50}
|
||||
{#if columnSize >= 50 || array}
|
||||
<InputTextarea
|
||||
{id}
|
||||
{label}
|
||||
bind:value
|
||||
{nullable}
|
||||
{maxlength}
|
||||
{autofocus}
|
||||
placeholder="Enter string"
|
||||
bind:value={stringValue}
|
||||
required={column.required}
|
||||
{nullable} />
|
||||
placeholder={getPlaceholder()}>
|
||||
<Layout.Stack direction="column" alignItems="flex-start" slot="end">
|
||||
{#if array}
|
||||
<Link.Button on:click size="s" variant="quiet">Advanced edit</Link.Button>
|
||||
{/if}
|
||||
</Layout.Stack>
|
||||
</InputTextarea>
|
||||
{:else}
|
||||
<InputText
|
||||
{id}
|
||||
{label}
|
||||
bind:value
|
||||
{nullable}
|
||||
{autofocus}
|
||||
{maxlength}
|
||||
bind:value={stringValue}
|
||||
placeholder="Enter string"
|
||||
required={column.required} />
|
||||
{/if}
|
||||
|
||||
+52
-30
@@ -3,7 +3,7 @@
|
||||
import { base } from '$app/paths';
|
||||
import { page } from '$app/state';
|
||||
import { Submit, trackError, trackEvent } from '$lib/actions/analytics';
|
||||
import { Alert, Confirm, Id, SortButton } from '$lib/components';
|
||||
import { Confirm, Id, SortButton } from '$lib/components';
|
||||
import { Dependencies, SPREADSHEET_PAGE_LIMIT } from '$lib/constants';
|
||||
import { Button as ConsoleButton, InputChoice, InputSelect } from '$lib/elements/forms';
|
||||
import { addNotification } from '$lib/stores/notifications';
|
||||
@@ -32,12 +32,12 @@
|
||||
rowActivitySheet,
|
||||
paginatedRows,
|
||||
paginatedRowsLoading,
|
||||
spreadsheetRenderKey,
|
||||
expandTabs
|
||||
spreadsheetRenderKey
|
||||
} from './store';
|
||||
import RelationshipsModal from './relationshipsModal.svelte';
|
||||
import type { Column, ColumnType } from '$lib/helpers/types';
|
||||
import {
|
||||
Alert,
|
||||
Tooltip,
|
||||
Spreadsheet,
|
||||
Table,
|
||||
@@ -53,6 +53,7 @@
|
||||
import { toLocaleDateTime } from '$lib/helpers/date';
|
||||
import DualTimeView from '$lib/components/dualTimeView.svelte';
|
||||
import {
|
||||
IconArrowSmRight,
|
||||
IconCalendar,
|
||||
IconDotsHorizontal,
|
||||
IconFingerPrint,
|
||||
@@ -62,6 +63,7 @@
|
||||
IconMail,
|
||||
IconPlus,
|
||||
IconRelationship,
|
||||
IconSwitchHorizontal,
|
||||
IconText,
|
||||
IconToggle,
|
||||
IconViewList
|
||||
@@ -98,7 +100,12 @@
|
||||
const emptyCellsLimit = $isSmallViewport ? 12 : 18;
|
||||
const SYSTEM_KEYS = new Set([
|
||||
'$tableId',
|
||||
'$databaseId'
|
||||
'$databaseId',
|
||||
'$permissions',
|
||||
'$createdAt',
|
||||
'$updatedAt',
|
||||
'$id',
|
||||
'$sequence'
|
||||
]); /* TODO: should be fixed at the sdk level! */
|
||||
|
||||
let selectedRows = [];
|
||||
@@ -138,7 +145,7 @@
|
||||
|
||||
const baseColumns = $table.columns.map((col) => ({
|
||||
id: col.key,
|
||||
title: col.key,
|
||||
title: col.array ? `${col.key} []` : col.key,
|
||||
type: col.type as ColumnType,
|
||||
hide: !!selectedColumnsToHide?.includes(col.key),
|
||||
array: col?.array,
|
||||
@@ -514,9 +521,10 @@
|
||||
Object.entries(row).filter(([key]) => !SYSTEM_KEYS.has(key))
|
||||
);
|
||||
|
||||
// TODO | BUG: related rows still have `system` columns atm!
|
||||
await sdk.forProject(page.params.region, page.params.project).grids.updateRow({
|
||||
databaseId,
|
||||
tableId,
|
||||
tableId: $table.$id,
|
||||
rowId: row.$id,
|
||||
data: onlyData,
|
||||
permissions: row.$permissions
|
||||
@@ -644,10 +652,6 @@
|
||||
$: totalPages = Math.ceil($rows.total / SPREADSHEET_PAGE_LIMIT) || 1;
|
||||
|
||||
$: rowSelection = !$spreadsheetLoading && !$paginatedRowsLoading ? true : ('disabled' as const);
|
||||
|
||||
expandTabs.subscribe((expanded) => {
|
||||
preferences.setTableHeaderExpanded(tableId, expanded);
|
||||
});
|
||||
</script>
|
||||
|
||||
<SpreadsheetContainer observeExpand bind:this={spreadsheetContainer}>
|
||||
@@ -867,14 +871,17 @@
|
||||
{/if}
|
||||
{/if}
|
||||
|
||||
<svelte:fragment slot="cell-editor">
|
||||
<svelte:fragment slot="cell-editor" let:close>
|
||||
<EditRowCell
|
||||
{row}
|
||||
column={rowColumn}
|
||||
row={paginatedRows.getItemAtVirtualIndex(index)}
|
||||
onRowStructureUpdate={updateRowContents}
|
||||
on:change={(row) => paginatedRows.update(index, row.detail)}
|
||||
on:revert={(row) =>
|
||||
paginatedRows.update(index, row.detail)} />
|
||||
onChange={(row) => paginatedRows.update(index, row)}
|
||||
onRevert={(row) => paginatedRows.update(index, row)}
|
||||
openSideSheet={() => {
|
||||
close(); /* closes the editor */
|
||||
onSelectSheetOption('update', null, 'row', row);
|
||||
}} />
|
||||
</svelte:fragment>
|
||||
</Spreadsheet.Cell>
|
||||
{/each}
|
||||
@@ -990,23 +997,24 @@
|
||||
<Table.Root
|
||||
let:root
|
||||
columns={[
|
||||
{ id: 'relation', width: 150 },
|
||||
{ id: 'setting', width: 150 },
|
||||
{ id: 'desc' }
|
||||
{ id: 'relation', width: 100 },
|
||||
{ id: 'setting', width: 100 },
|
||||
{ id: 'description', width: { min: $isSmallViewport ? 300 : 275 } }
|
||||
]}>
|
||||
<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} />
|
||||
<Table.Header.Cell column="description" {root}>Description</Table.Header.Cell>
|
||||
</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>
|
||||
<Icon icon={IconSwitchHorizontal} size="s" />
|
||||
{:else}
|
||||
<span class="icon-arrow-sm-right"></span>
|
||||
<Icon icon={IconArrowSmRight} size="s" />
|
||||
{/if}
|
||||
<span data-private>{attr.key}</span>
|
||||
</span>
|
||||
@@ -1014,25 +1022,27 @@
|
||||
<Table.Cell column="setting" {root}>
|
||||
{attr.onDelete}
|
||||
</Table.Cell>
|
||||
<Table.Cell column="desc" {root}>
|
||||
<Table.Cell column="description" {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>
|
||||
|
||||
<Layout.Stack direction="column" gap="m">
|
||||
<Alert.Inline>To change the selection edit the relationship settings.</Alert.Inline>
|
||||
|
||||
<ul>
|
||||
<InputChoice
|
||||
id="delete"
|
||||
label="Delete"
|
||||
showLabel={false}
|
||||
bind:value={deleteConfirmationChecked}>
|
||||
Delete {isSingle ? 'document' : 'documents'} from
|
||||
Delete {isSingle ? 'row' : 'rows'} from
|
||||
<span data-private>{$table.name}</span>
|
||||
</InputChoice>
|
||||
</ul>
|
||||
</div>
|
||||
</Layout.Stack>
|
||||
{:else}
|
||||
<p class="u-bold">This action is irreversible.</p>
|
||||
{/if}
|
||||
@@ -1066,19 +1076,31 @@
|
||||
}
|
||||
|
||||
:global(.floating-editor) {
|
||||
z-index: 3 !important;
|
||||
|
||||
& :global(:has(textarea)) {
|
||||
left: 0 !important;
|
||||
margin-inline-end: 1px;
|
||||
left: 2px !important;
|
||||
//margin-inline-end: 1px;
|
||||
}
|
||||
|
||||
/* TODO: not good! */
|
||||
& :global(textarea) {
|
||||
padding-inline: 9px;
|
||||
margin-block: -2.75px;
|
||||
min-height: 85px !important;
|
||||
}
|
||||
|
||||
/* TODO: not good! */
|
||||
& :global(:has(.link-wrapper) .input) {
|
||||
padding-bottom: 6px !important;
|
||||
}
|
||||
|
||||
& :global(:has(.link-wrapper) textarea) {
|
||||
min-height: 65px !important;
|
||||
}
|
||||
|
||||
& :global(.link-wrapper) {
|
||||
padding-inline: 9px;
|
||||
}
|
||||
|
||||
& :global(input[type='text']) {
|
||||
padding-inline: 8px !important;
|
||||
}
|
||||
|
||||
+2
-2
@@ -139,7 +139,7 @@ export const randomDataModalState = writable({
|
||||
export const spreadsheetLoading = writable(false);
|
||||
|
||||
export enum Deletion {
|
||||
'setNull' = 'Set rpw ID as NULL in all related rows',
|
||||
'setNull' = 'Set row ID as NULL in all related rows',
|
||||
'cascade' = 'All related rows will be deleted',
|
||||
'restrict' = 'Row can not be deleted'
|
||||
}
|
||||
@@ -151,7 +151,7 @@ export const rowActivitySheet = writable({
|
||||
row: null as Models.Row
|
||||
});
|
||||
|
||||
export const expandTabs = writable(true);
|
||||
export const expandTabs = writable(null);
|
||||
export const spreadsheetRenderKey = writable('initial');
|
||||
|
||||
export const paginatedRowsLoading = writable(false);
|
||||
|
||||
Reference in New Issue
Block a user