mirror of
https://github.com/appwrite/console.git
synced 2026-06-06 19:27:48 +00:00
updates: to spreadsheet.
This commit is contained in:
@@ -1,10 +1,10 @@
|
||||
import { goto } from '$app/navigation';
|
||||
import { derived, get, writable } from 'svelte/store';
|
||||
import { page } from '$app/stores';
|
||||
import deepEqual from 'deep-equal';
|
||||
import type { Column, ColumnType } from '$lib/helpers/types';
|
||||
import { Query } from '@appwrite.io/console';
|
||||
import { toLocaleDateTime } from '$lib/helpers/date';
|
||||
import { derived, get, writable } from 'svelte/store';
|
||||
import type { SheetColumn, SheetColumnType } from '$lib/helpers/types';
|
||||
|
||||
export type TagValue = {
|
||||
tag: string;
|
||||
@@ -14,7 +14,7 @@ export type TagValue = {
|
||||
export type Operator = {
|
||||
toTag: (attribute: string, input?: string | number | string[], type?: string) => TagValue;
|
||||
toQuery: (attribute: string, input?: string | number | string[]) => string;
|
||||
types: ColumnType[];
|
||||
types: SheetColumnType[];
|
||||
hideInput?: boolean;
|
||||
};
|
||||
|
||||
@@ -33,7 +33,7 @@ function initQueries(initialValue = new Map<TagValue, string>()) {
|
||||
|
||||
type AddFilterArgs = {
|
||||
operator: Operator;
|
||||
column: Column;
|
||||
column: SheetColumn;
|
||||
value: string | number | string[];
|
||||
};
|
||||
|
||||
@@ -91,14 +91,14 @@ export const tags = derived(queries, ($queries) => Array.from($queries.keys()));
|
||||
|
||||
/* eslint @typescript-eslint/no-explicit-any: 'off' */
|
||||
export function addFilter(
|
||||
columns: Column[],
|
||||
columns: SheetColumn[],
|
||||
columnId: string,
|
||||
operatorKey: string,
|
||||
value: any, // We cast to any to not cause type errors in the input components
|
||||
arrayValues: string[] = []
|
||||
) {
|
||||
const operator = operatorKey ? operators[operatorKey] : null;
|
||||
const column = columns.find((c) => c.id === columnId) as Column;
|
||||
const column = columns.find((c) => c.id === columnId) as SheetColumn;
|
||||
if (!column || !operator) return;
|
||||
if (column.array) {
|
||||
queries.addFilter({ column, operator, value: arrayValues });
|
||||
@@ -135,7 +135,7 @@ const operatorsDefault = new Map<
|
||||
ValidOperators,
|
||||
{
|
||||
query: (attr: string, input: string | number | string[]) => string;
|
||||
types: ColumnType[];
|
||||
types: SheetColumnType[];
|
||||
hideInput?: boolean;
|
||||
}
|
||||
>([
|
||||
|
||||
+27
-8
@@ -20,11 +20,12 @@
|
||||
import { addNotification } from '$lib/stores/notifications';
|
||||
import { Click, Submit, trackError, trackEvent } from '$lib/actions/analytics';
|
||||
import { isSmallViewport } from '$lib/stores/viewport';
|
||||
import { base } from '$app/paths';
|
||||
import { IconPlus } from '@appwrite.io/pink-icons-svelte';
|
||||
import type { Models } from '@appwrite.io/console';
|
||||
import EmptySheet from './emptySheet.svelte';
|
||||
import EmptySheet from './layout/emptySheet.svelte';
|
||||
import CreateRecord from './createRecord.svelte';
|
||||
import { goto } from '$app/navigation';
|
||||
import { base } from '$app/paths';
|
||||
|
||||
export let data: PageData;
|
||||
|
||||
@@ -135,7 +136,7 @@
|
||||
{#if hasAttributes && hasValidAttributes}
|
||||
{#if data.documents.total}
|
||||
<Divider />
|
||||
<SpreadSheet {data} />
|
||||
<SpreadSheet {data} bind:showRecordsCreateSheet />
|
||||
{:else if $hasPageQueries}
|
||||
<EmptySearch hidePages>
|
||||
<div class="common-section">
|
||||
@@ -159,10 +160,30 @@
|
||||
</div>
|
||||
</EmptySearch>
|
||||
{:else}
|
||||
<EmptySheet on:record={() => (showRecordsCreateSheet = true)} />
|
||||
<EmptySheet
|
||||
mode="records"
|
||||
cta={{
|
||||
primary: {
|
||||
onClick: () => {
|
||||
showRecordsCreateSheet = true;
|
||||
}
|
||||
}
|
||||
}} />
|
||||
{/if}
|
||||
{:else}
|
||||
<EmptySheet on:record={() => (showRecordsCreateSheet = true)} />
|
||||
<EmptySheet
|
||||
mode="records"
|
||||
title="You have no columns yet"
|
||||
cta={{
|
||||
primary: {
|
||||
text: 'Create column',
|
||||
onClick: async () => {
|
||||
await goto(
|
||||
`${base}/project-${page.params.region}-${page.params.project}/databases/database-${page.params.database}/collection-${page.params.collection}/attributes`
|
||||
);
|
||||
}
|
||||
}
|
||||
}} />
|
||||
{/if}
|
||||
</div>
|
||||
{/key}
|
||||
@@ -186,6 +207,4 @@
|
||||
}} />
|
||||
{/if}
|
||||
|
||||
{#if showRecordsCreateSheet}
|
||||
<CreateRecord collection={$collection} bind:showSheet={showRecordsCreateSheet} />
|
||||
{/if}
|
||||
<CreateRecord collection={$collection} bind:showSheet={showRecordsCreateSheet} />
|
||||
|
||||
+2
-1
@@ -8,7 +8,8 @@ import { queries, queryParamToMap } from '$lib/components/filters';
|
||||
export const load: PageLoad = async ({ params, depends, url, route }) => {
|
||||
depends(Dependencies.DOCUMENTS);
|
||||
const page = getPage(url);
|
||||
const limit = getLimit(url, route, 18 /*PAGE_LIMIT*/);
|
||||
// TODO: apply pagination
|
||||
const limit = getLimit(url, route, 96 /*PAGE_LIMIT*/);
|
||||
const view = getView(url, route, View.Grid);
|
||||
const offset = pageToOffset(page, limit);
|
||||
const query = getQuery(url);
|
||||
|
||||
+192
-165
@@ -1,7 +1,4 @@
|
||||
<script lang="ts">
|
||||
import { base } from '$app/paths';
|
||||
import { page } from '$app/state';
|
||||
import { Empty } from '$lib/components';
|
||||
import { Button } from '$lib/elements/forms';
|
||||
import { Container } from '$lib/layout';
|
||||
import { canWriteCollections } from '$lib/stores/roles';
|
||||
@@ -12,7 +9,8 @@
|
||||
Layout,
|
||||
Link,
|
||||
Popover,
|
||||
Table,
|
||||
Selector,
|
||||
Spreadsheet,
|
||||
Tooltip,
|
||||
Typography
|
||||
} from '@appwrite.io/pink-svelte';
|
||||
@@ -20,10 +18,10 @@
|
||||
import { isRelationship, isString } from '../document-[document]/attributes/store';
|
||||
import FailedModal from '../failedModal.svelte';
|
||||
import CreateIndex from '../indexes/createIndex.svelte';
|
||||
import { attributes, type Attributes, isCsvImportInProgress } from '../store';
|
||||
import { attributes, type Attributes, indexes, isCsvImportInProgress } from '../store';
|
||||
import CreateAttributeDropdown from './createAttributeDropdown.svelte';
|
||||
import Delete from './deleteAttribute.svelte';
|
||||
import Edit from './edit.svelte';
|
||||
import EditAttribute from './edit.svelte';
|
||||
import { attributeOptions, type Option } from './store';
|
||||
import {
|
||||
IconArrowSmRight,
|
||||
@@ -40,19 +38,23 @@
|
||||
import type { ComponentProps } from 'svelte';
|
||||
import { Click, trackEvent } from '$lib/actions/analytics';
|
||||
import CsvDisabled from '../csvDisabled.svelte';
|
||||
|
||||
const databaseId = page.params.database;
|
||||
import { isSmallViewport } from '$lib/stores/viewport';
|
||||
import SideSheet from '../layout/sidesheet.svelte';
|
||||
import SpreadsheetContainer from '../layout/spreadsheet.svelte';
|
||||
import EmptySheet from '../layout/emptySheet.svelte';
|
||||
|
||||
let showDropdown = [];
|
||||
let selectedOption: Option['name'] = null;
|
||||
let selectedAttribute: Attributes = null;
|
||||
let showCreate = false;
|
||||
let showDelete = false;
|
||||
let showEdit = false;
|
||||
let showCreateIndex = false;
|
||||
let showFailed = false;
|
||||
let error = '';
|
||||
|
||||
let showEdit = false;
|
||||
let editAttribute: EditAttribute;
|
||||
|
||||
const attributeFormatIcon = {
|
||||
ip: IconLocationMarker,
|
||||
url: IconLink,
|
||||
@@ -72,181 +74,197 @@
|
||||
return undefined;
|
||||
}
|
||||
}
|
||||
|
||||
const emptyCellsLimit = $isSmallViewport ? 14 : 16;
|
||||
|
||||
$: emptyCellsCount =
|
||||
$attributes.length >= emptyCellsLimit ? 0 : emptyCellsLimit - $attributes.length;
|
||||
</script>
|
||||
|
||||
<Container>
|
||||
<Layout.Stack direction="row" justifyContent="space-between">
|
||||
<Typography.Title>Attributes</Typography.Title>
|
||||
<Container expanded style="background: var(--bgcolor-neutral-primary)">
|
||||
<Layout.Stack direction="row" justifyContent="flex-end">
|
||||
<!-- TODO: filters? -->
|
||||
{#if $canWriteCollections}
|
||||
<CreateAttributeDropdown bind:selectedOption bind:showCreate />
|
||||
{/if}
|
||||
</Layout.Stack>
|
||||
</Container>
|
||||
|
||||
<div class="databases-spreadsheet">
|
||||
{#if $attributes.length}
|
||||
<Table.Root
|
||||
let:root
|
||||
columns={[
|
||||
{ id: 'key' },
|
||||
{ id: 'type' },
|
||||
{ id: 'default' },
|
||||
{ id: 'actions', width: 40 }
|
||||
]}>
|
||||
<svelte:fragment slot="header" let:root>
|
||||
<Table.Header.Cell column="key" {root}>Key</Table.Header.Cell>
|
||||
<Table.Header.Cell column="type" {root}>Type</Table.Header.Cell>
|
||||
<Table.Header.Cell column="default" {root}>Default value</Table.Header.Cell>
|
||||
<Table.Header.Cell column="actions" {root} />
|
||||
</svelte:fragment>
|
||||
{#each $attributes as attribute, index}
|
||||
{@const option = attributeOptions.find((option) => option.type === attribute.type)}
|
||||
<Table.Row.Base {root}>
|
||||
<Table.Cell column="key" {root}>
|
||||
<Layout.Stack direction="row" alignItems="center">
|
||||
{#if isRelationship(attribute)}
|
||||
<Icon
|
||||
size="s"
|
||||
icon={attribute?.twoWay
|
||||
? IconSwitchHorizontal
|
||||
: IconArrowSmRight} />
|
||||
{:else if 'format' in attribute && attribute.format}
|
||||
{@const icon = attributeFormatIcon[attribute?.format]}
|
||||
<Icon {icon} size="s" />
|
||||
{:else}
|
||||
<Icon icon={option.icon} size="s" />
|
||||
{/if}
|
||||
<Layout.Stack direction="row" alignItems="center" gap="s">
|
||||
<Layout.Stack inline direction="row" alignItems="center" gap="xxs">
|
||||
<span class="text u-trim-1" data-private>{attribute.key}</span>
|
||||
{#if isString(attribute) && attribute.encrypt}
|
||||
<Tooltip>
|
||||
<Icon
|
||||
size="s"
|
||||
icon={IconLockClosed}
|
||||
color="--fgcolor-neutral-tertiary" />
|
||||
<div slot="tooltip">Encrypted</div>
|
||||
</Tooltip>
|
||||
<SpreadsheetContainer>
|
||||
<Spreadsheet.Root
|
||||
let:root
|
||||
height="100%"
|
||||
emptyCells={emptyCellsCount}
|
||||
columns={[
|
||||
{ id: 'key', width: { min: 200 } },
|
||||
{ id: 'indexed', width: { min: 150 } },
|
||||
{ id: 'default', width: { min: 200 } },
|
||||
{ id: 'actions', width: 40, isAction: true }
|
||||
]}>
|
||||
<svelte:fragment slot="header" let:root>
|
||||
<Spreadsheet.Header.Cell column="key" {root}
|
||||
>Column name</Spreadsheet.Header.Cell>
|
||||
<Spreadsheet.Header.Cell column="indexed" {root}
|
||||
>Indexed</Spreadsheet.Header.Cell>
|
||||
<Spreadsheet.Header.Cell column="default" {root}
|
||||
>Default value</Spreadsheet.Header.Cell>
|
||||
<Spreadsheet.Header.Cell column="actions" {root} />
|
||||
</svelte:fragment>
|
||||
|
||||
{#each $attributes as attribute, index}
|
||||
{@const option = attributeOptions.find(
|
||||
(option) => option.type === attribute.type
|
||||
)}
|
||||
<Spreadsheet.Row.Base {root}>
|
||||
<Spreadsheet.Cell column="key" {root} isEditable={false}>
|
||||
<Layout.Stack direction="row" alignItems="center">
|
||||
{#if isRelationship(attribute)}
|
||||
<Icon
|
||||
size="s"
|
||||
icon={attribute?.twoWay
|
||||
? IconSwitchHorizontal
|
||||
: IconArrowSmRight} />
|
||||
{:else if 'format' in attribute && attribute.format}
|
||||
{@const icon = attributeFormatIcon[attribute?.format]}
|
||||
<Icon {icon} size="s" />
|
||||
{:else}
|
||||
<Icon icon={option.icon} size="s" />
|
||||
{/if}
|
||||
<Layout.Stack direction="row" alignItems="center" gap="s">
|
||||
<Layout.Stack
|
||||
inline
|
||||
direction="row"
|
||||
alignItems="center"
|
||||
gap="xxs">
|
||||
<span class="text u-trim-1" data-private
|
||||
>{attribute.key}</span>
|
||||
{#if isString(attribute) && attribute.encrypt}
|
||||
<Tooltip>
|
||||
<Icon
|
||||
size="s"
|
||||
icon={IconLockClosed}
|
||||
color="--fgcolor-neutral-tertiary" />
|
||||
<div slot="tooltip">Encrypted</div>
|
||||
</Tooltip>
|
||||
{/if}
|
||||
</Layout.Stack>
|
||||
{#if attribute.status !== 'available'}
|
||||
<Badge
|
||||
size="s"
|
||||
variant="secondary"
|
||||
content={attribute.status}
|
||||
type={getAttributeStatusBadge(attribute.status)} />
|
||||
{#if attribute.error}
|
||||
<Link.Button
|
||||
variant="muted"
|
||||
on:click={(e) => {
|
||||
e.preventDefault();
|
||||
error = attribute.error;
|
||||
showFailed = true;
|
||||
}}>Details</Link.Button>
|
||||
{/if}
|
||||
{:else if attribute.required}
|
||||
<Badge size="xs" variant="secondary" content="required" />
|
||||
{/if}
|
||||
</Layout.Stack>
|
||||
{#if attribute.status !== 'available'}
|
||||
<Badge
|
||||
size="s"
|
||||
variant="secondary"
|
||||
content={attribute.status}
|
||||
type={getAttributeStatusBadge(attribute.status)} />
|
||||
{#if attribute.error}
|
||||
<Link.Button
|
||||
variant="muted"
|
||||
on:click={(e) => {
|
||||
e.preventDefault();
|
||||
error = attribute.error;
|
||||
showFailed = true;
|
||||
}}>Details</Link.Button>
|
||||
{/if}
|
||||
{:else if attribute.required}
|
||||
<Badge size="xs" variant="secondary" content="required" />
|
||||
{/if}
|
||||
</Layout.Stack>
|
||||
</Layout.Stack>
|
||||
</Table.Cell>
|
||||
<Table.Cell column="type" {root}>
|
||||
{#if 'format' in attribute && attribute.format}
|
||||
<span class="u-capitalize">{attribute.format}</span>
|
||||
{:else}
|
||||
<p>
|
||||
<span class="u-capitalize">{attribute.type}</span>
|
||||
{#if isRelationship(attribute)}
|
||||
<span>
|
||||
with <a
|
||||
href={`${base}/project-${page.params.region}-${page.params.project}/databases/database-${databaseId}/collection-${attribute?.relatedCollection}`}
|
||||
><b data-private>{attribute?.key}</b></a>
|
||||
</span>
|
||||
{/if}
|
||||
</p>
|
||||
{/if}
|
||||
<span>
|
||||
{attribute.array ? '[]' : ''}
|
||||
</span>
|
||||
</Table.Cell>
|
||||
<Table.Cell column="default" {root}>
|
||||
{attribute?.default !== null && attribute?.default !== undefined
|
||||
? attribute?.default
|
||||
: '-'}
|
||||
</Table.Cell>
|
||||
<Table.Cell column="actions" {root}>
|
||||
{#if $isCsvImportInProgress}
|
||||
<CsvDisabled>
|
||||
<Button disabled text icon ariaLabel="more options">
|
||||
<Icon icon={IconDotsHorizontal} size="s" />
|
||||
</Button>
|
||||
</CsvDisabled>
|
||||
{:else}
|
||||
<Popover let:toggle padding="none" placement="bottom-end">
|
||||
<Button text icon ariaLabel="more options" on:click={toggle}>
|
||||
<Icon icon={IconDotsHorizontal} size="s" />
|
||||
</Button>
|
||||
<ActionMenu.Root slot="tooltip" let:toggle>
|
||||
<ActionMenu.Item.Button
|
||||
leadingIcon={IconPencil}
|
||||
on:click={(event) => {
|
||||
toggle(event);
|
||||
showEdit = true;
|
||||
selectedAttribute = attribute;
|
||||
showDropdown[index] = false;
|
||||
}}>
|
||||
Update
|
||||
</ActionMenu.Item.Button>
|
||||
{#if !isRelationship(attribute)}
|
||||
</Spreadsheet.Cell>
|
||||
<Spreadsheet.Cell column="indexed" {root}>
|
||||
{@const isIndexed = $indexes.some((index) =>
|
||||
index.attributes.includes(attribute.key)
|
||||
)}
|
||||
<Selector.Checkbox size="s" checked={isIndexed} disabled />
|
||||
</Spreadsheet.Cell>
|
||||
<Spreadsheet.Cell column="default" {root}>
|
||||
{@const _default =
|
||||
attribute?.default !== null && attribute?.default !== undefined
|
||||
? attribute?.default
|
||||
: null}
|
||||
|
||||
{#if _default === null}
|
||||
<Badge variant="secondary" content="NULL" size="xs" />
|
||||
{:else}
|
||||
{_default}
|
||||
{/if}
|
||||
</Spreadsheet.Cell>
|
||||
<Spreadsheet.Cell column="actions" {root}>
|
||||
{#if $isCsvImportInProgress}
|
||||
<CsvDisabled>
|
||||
<Button disabled text icon ariaLabel="more options">
|
||||
<Icon icon={IconDotsHorizontal} size="s" />
|
||||
</Button>
|
||||
</CsvDisabled>
|
||||
{:else}
|
||||
<!-- TODO: no portal, rather see if we can fix the cell -->
|
||||
<Popover let:toggle padding="none" placement="bottom-end" portal>
|
||||
<Button text icon ariaLabel="more options" on:click={toggle}>
|
||||
<Icon icon={IconDotsHorizontal} size="s" />
|
||||
</Button>
|
||||
<ActionMenu.Root slot="tooltip" let:toggle>
|
||||
<ActionMenu.Item.Button
|
||||
leadingIcon={IconPlus}
|
||||
leadingIcon={IconPencil}
|
||||
on:click={(event) => {
|
||||
toggle(event);
|
||||
showEdit = true;
|
||||
selectedAttribute = attribute;
|
||||
showCreateIndex = true;
|
||||
showDropdown[index] = false;
|
||||
}}>
|
||||
Create index
|
||||
Update
|
||||
</ActionMenu.Item.Button>
|
||||
{/if}
|
||||
{#if attribute.status !== 'processing'}
|
||||
<ActionMenu.Item.Button
|
||||
leadingIcon={IconTrash}
|
||||
on:click={(event) => {
|
||||
toggle(event);
|
||||
showDelete = true;
|
||||
showDropdown[index] = false;
|
||||
selectedAttribute = attribute;
|
||||
trackEvent(Click.DatabaseAttributeDelete);
|
||||
}}>
|
||||
Delete
|
||||
</ActionMenu.Item.Button>
|
||||
{/if}
|
||||
</ActionMenu.Root>
|
||||
</Popover>
|
||||
{/if}
|
||||
</Table.Cell>
|
||||
</Table.Row.Base>
|
||||
{/each}
|
||||
</Table.Root>
|
||||
{#if !isRelationship(attribute)}
|
||||
<ActionMenu.Item.Button
|
||||
leadingIcon={IconPlus}
|
||||
on:click={(event) => {
|
||||
toggle(event);
|
||||
selectedAttribute = attribute;
|
||||
showCreateIndex = true;
|
||||
showDropdown[index] = false;
|
||||
}}>
|
||||
Create index
|
||||
</ActionMenu.Item.Button>
|
||||
{/if}
|
||||
{#if attribute.status !== 'processing'}
|
||||
<ActionMenu.Item.Button
|
||||
status="danger"
|
||||
leadingIcon={IconTrash}
|
||||
on:click={(event) => {
|
||||
toggle(event);
|
||||
showDelete = true;
|
||||
showDropdown[index] = false;
|
||||
selectedAttribute = attribute;
|
||||
trackEvent(Click.DatabaseAttributeDelete);
|
||||
}}>
|
||||
Delete
|
||||
</ActionMenu.Item.Button>
|
||||
{/if}
|
||||
</ActionMenu.Root>
|
||||
</Popover>
|
||||
{/if}
|
||||
</Spreadsheet.Cell>
|
||||
</Spreadsheet.Row.Base>
|
||||
{/each}
|
||||
|
||||
<svelte:fragment slot="footer">
|
||||
<Layout.Stack
|
||||
direction="row"
|
||||
alignContent="center"
|
||||
alignItems="center"
|
||||
justifyContent="space-between">
|
||||
<Typography.Text variant="m-400" color="--fgcolor-neutral-secondary">
|
||||
{$attributes.length} columns
|
||||
</Typography.Text>
|
||||
</Layout.Stack>
|
||||
</svelte:fragment>
|
||||
</Spreadsheet.Root>
|
||||
</SpreadsheetContainer>
|
||||
{:else}
|
||||
<Empty single target="attribute">
|
||||
<svelte:fragment slot="actions">
|
||||
<Button
|
||||
external
|
||||
href="https://appwrite.io/docs/products/databases/collections#attributes"
|
||||
text
|
||||
event="empty_documentation"
|
||||
ariaLabel={`create {target}`}>Documentation</Button>
|
||||
{#if $canWriteCollections}
|
||||
<CreateAttributeDropdown bind:selectedOption bind:showCreate let:toggle>
|
||||
<Button secondary event="create_attribute" on:click={toggle}>
|
||||
Create attribute
|
||||
</Button>
|
||||
</CreateAttributeDropdown>
|
||||
{/if}
|
||||
</svelte:fragment>
|
||||
</Empty>
|
||||
<!-- TODO: handle these cases -->
|
||||
<!--{#if $canWriteCollections}-->
|
||||
<EmptySheet mode="columns" />
|
||||
{/if}
|
||||
</Container>
|
||||
</div>
|
||||
|
||||
{#if showCreate}
|
||||
<Create bind:showCreate bind:selectedOption />
|
||||
@@ -255,8 +273,17 @@
|
||||
<Delete bind:showDelete {selectedAttribute} />
|
||||
{/if}
|
||||
{#if showEdit}
|
||||
<Edit bind:showEdit {selectedAttribute} />
|
||||
<SideSheet
|
||||
title="Edit column"
|
||||
bind:show={showEdit}
|
||||
submit={{
|
||||
text: 'Update',
|
||||
onClick: async () => await editAttribute.submit()
|
||||
}}>
|
||||
<EditAttribute showEdit isModal={false} {selectedAttribute} bind:this={editAttribute} />
|
||||
</SideSheet>
|
||||
{/if}
|
||||
|
||||
{#if showCreateIndex}
|
||||
<CreateIndex bind:showCreateIndex externalAttribute={selectedAttribute} />
|
||||
{/if}
|
||||
|
||||
+3
-3
@@ -6,8 +6,8 @@
|
||||
import CsvDisabled from '../csvDisabled.svelte';
|
||||
import { isCsvImportInProgress } from '../store';
|
||||
|
||||
export let selectedOption: Option['name'] = null;
|
||||
export let showCreate = false;
|
||||
export let selectedOption: Option['name'] = null;
|
||||
</script>
|
||||
|
||||
{#if $isCsvImportInProgress}
|
||||
@@ -20,9 +20,9 @@
|
||||
{:else}
|
||||
<Popover let:toggle padding="none" placement="bottom-start">
|
||||
<slot {toggle}>
|
||||
<Button on:click={toggle} event="create_attribute">
|
||||
<Button secondary on:click={toggle} event="create_attribute">
|
||||
<Icon icon={IconPlus} slot="start" size="s" />
|
||||
Create attribute
|
||||
Create column
|
||||
</Button>
|
||||
</slot>
|
||||
<ActionMenu.Root slot="tooltip">
|
||||
|
||||
+2
-1
@@ -43,7 +43,7 @@
|
||||
}
|
||||
}) as Option;
|
||||
|
||||
async function submit() {
|
||||
export async function submit() {
|
||||
try {
|
||||
await option.update(databaseId, collectionId, selectedAttribute, originalKey);
|
||||
await invalidate(Dependencies.COLLECTION);
|
||||
@@ -58,6 +58,7 @@
|
||||
type: 'success',
|
||||
message: `Attribute ${selectedAttribute.key} has been updated`
|
||||
});
|
||||
|
||||
showEdit = false;
|
||||
trackEvent(Submit.AttributeUpdate);
|
||||
|
||||
|
||||
-135
@@ -1,135 +0,0 @@
|
||||
<script lang="ts">
|
||||
import { base } from '$app/paths';
|
||||
import { page } from '$app/state';
|
||||
import { Wizard } from '$lib/layout';
|
||||
import { Alert, Fieldset, Layout, Sheet, Typography } from '@appwrite.io/pink-svelte';
|
||||
import Button from '$lib/elements/forms/button.svelte';
|
||||
import Form from '$lib/elements/forms/form.svelte';
|
||||
import { sdk } from '$lib/stores/sdk';
|
||||
import { goto } from '$app/navigation';
|
||||
import { Submit, trackError, trackEvent } from '$lib/actions/analytics';
|
||||
import { addNotification } from '$lib/stores/notifications';
|
||||
import { writable } from 'svelte/store';
|
||||
import AttributeForm from '../document-[document]/attributeForm.svelte';
|
||||
import { Permissions } from '$lib/components/permissions';
|
||||
import type { PageData } from './$types';
|
||||
import type { Attributes } from '../store';
|
||||
import { ID } from '@appwrite.io/console';
|
||||
|
||||
export let data: PageData;
|
||||
|
||||
let showExitModal = false;
|
||||
let formComponent: Form;
|
||||
let isSubmitting = writable(false);
|
||||
|
||||
type CreateDocument = {
|
||||
id?: string;
|
||||
document: object;
|
||||
permissions: string[];
|
||||
attributes: Attributes[];
|
||||
};
|
||||
|
||||
function createDocumentWritable() {
|
||||
const availableAttributes = (data.collection.attributes as unknown as Attributes[]).filter(
|
||||
(a) => a.status === 'available'
|
||||
);
|
||||
const initial = {
|
||||
id: null,
|
||||
document: availableAttributes.reduce((acc, attr) => {
|
||||
acc[attr.key] = attr.array ? [] : null;
|
||||
|
||||
return acc;
|
||||
}, {}),
|
||||
permissions: [],
|
||||
attributes: availableAttributes
|
||||
};
|
||||
|
||||
const store = writable<CreateDocument>({ ...initial });
|
||||
|
||||
return store;
|
||||
}
|
||||
|
||||
const createDocument = createDocumentWritable();
|
||||
|
||||
async function create() {
|
||||
try {
|
||||
const { $id } = await sdk
|
||||
.forProject(page.params.region, page.params.project)
|
||||
.databases.createDocument(
|
||||
page.params.database,
|
||||
page.params.collection,
|
||||
$createDocument.id ?? ID.unique(),
|
||||
$createDocument.document,
|
||||
$createDocument.permissions
|
||||
);
|
||||
|
||||
addNotification({
|
||||
message: 'Document has been created',
|
||||
type: 'success'
|
||||
});
|
||||
trackEvent(Submit.DocumentCreate, {
|
||||
customId: !!$createDocument.id
|
||||
});
|
||||
goto(
|
||||
`${base}/project-${page.params.region}-${page.params.project}/databases/database-${page.params.database}/collection-${page.params.collection}/document-${$id}`
|
||||
);
|
||||
} catch (error) {
|
||||
addNotification({
|
||||
message: error.message,
|
||||
type: 'error'
|
||||
});
|
||||
trackError(error, Submit.DocumentCreate);
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<Sheet bind:open={showExitModal}>
|
||||
<div slot="header">Create document</div>
|
||||
|
||||
<Form bind:this={formComponent} onSubmit={create} bind:isSubmitting>
|
||||
<Layout.Stack gap="xxl">
|
||||
<Fieldset legend="Data">
|
||||
<AttributeForm
|
||||
bind:formValues={$createDocument.document}
|
||||
attributes={$createDocument.attributes}
|
||||
bind:customId={$createDocument.id} />
|
||||
</Fieldset>
|
||||
|
||||
<Fieldset legend="Permissions">
|
||||
<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 data.collection.documentSecurity}
|
||||
<Alert.Inline status="info">
|
||||
<svelte:fragment slot="title"
|
||||
>Document security is enabled</svelte:fragment>
|
||||
Users will be able to access this document if they have been granted
|
||||
<b>either document or collection permissions</b>.
|
||||
</Alert.Inline>
|
||||
<Permissions bind:permissions={$createDocument.permissions} />
|
||||
{:else}
|
||||
<Alert.Inline status="info">
|
||||
<svelte:fragment slot="title"
|
||||
>Document security is disabled</svelte:fragment>
|
||||
If you want to assign document permissions, navigate to Collection settings
|
||||
and enable document security. Otherwise, only collection permissions will
|
||||
be used.
|
||||
</Alert.Inline>
|
||||
{/if}
|
||||
</Layout.Stack>
|
||||
</Fieldset>
|
||||
</Layout.Stack>
|
||||
</Form>
|
||||
|
||||
<!-- <svelte:fragment slot="footer">-->
|
||||
<!-- <Button fullWidthMobile secondary on:click={() => (showExitModal = true)}>Cancel</Button>-->
|
||||
<!-- <Button-->
|
||||
<!-- fullWidthMobile-->
|
||||
<!-- on:click={() => formComponent.triggerSubmit()}-->
|
||||
<!-- disabled={$isSubmitting}>-->
|
||||
<!-- Create-->
|
||||
<!-- </Button>-->
|
||||
<!-- </svelte:fragment>-->
|
||||
</Sheet>
|
||||
+45
-96
@@ -1,7 +1,5 @@
|
||||
<script lang="ts">
|
||||
import { page } from '$app/state';
|
||||
import Form from '$lib/elements/forms/form.svelte';
|
||||
import Button from '$lib/elements/forms/button.svelte';
|
||||
import { sdk } from '$lib/stores/sdk';
|
||||
import { Submit, trackError, trackEvent } from '$lib/actions/analytics';
|
||||
import { addNotification } from '$lib/stores/notifications';
|
||||
@@ -10,15 +8,15 @@
|
||||
import { Permissions } from '$lib/components/permissions';
|
||||
import type { Attributes } from './store';
|
||||
import { ID, type Models } from '@appwrite.io/console';
|
||||
import { Alert, Divider, Layout, Sheet, Typography } from '@appwrite.io/pink-svelte';
|
||||
import { Alert, Layout, Typography } from '@appwrite.io/pink-svelte';
|
||||
import { invalidate } from '$app/navigation';
|
||||
import { Dependencies } from '$lib/constants';
|
||||
import SideSheet from './layout/sidesheet.svelte';
|
||||
|
||||
export let showSheet = false;
|
||||
export let collection: Models.Collection;
|
||||
|
||||
let formComponent: Form;
|
||||
let isSubmitting = writable(false);
|
||||
let isSubmitting = false;
|
||||
|
||||
type CreateDocument = {
|
||||
id?: string;
|
||||
@@ -48,6 +46,8 @@
|
||||
const createDocument = createDocumentWritable();
|
||||
|
||||
async function create() {
|
||||
isSubmitting = true;
|
||||
|
||||
try {
|
||||
await sdk
|
||||
.forProject(page.params.region, page.params.project)
|
||||
@@ -74,102 +74,51 @@
|
||||
type: 'error'
|
||||
});
|
||||
trackError(error, Submit.DocumentCreate);
|
||||
} finally {
|
||||
isSubmitting = false;
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<div class="sheet-container">
|
||||
<Sheet bind:open={showSheet} closeOnBlur={false}>
|
||||
<svelte:fragment slot="header">
|
||||
<Layout.Stack>
|
||||
Create document
|
||||
<!-- TODO: add a ID badge-->
|
||||
<SideSheet
|
||||
spaced
|
||||
title="Create record"
|
||||
bind:show={showSheet}
|
||||
closeOnBlur={false}
|
||||
submit={{
|
||||
text: 'Create',
|
||||
disabled: isSubmitting,
|
||||
onClick: () => create()
|
||||
}}>
|
||||
<Layout.Stack gap="xxl">
|
||||
<AttributeForm
|
||||
bind:formValues={$createDocument.document}
|
||||
attributes={$createDocument.attributes}
|
||||
bind:customId={$createDocument.id} />
|
||||
|
||||
<!-- TODO: add a ID badge-->
|
||||
<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 collection.documentSecurity}
|
||||
<Alert.Inline status="info">
|
||||
<svelte:fragment slot="title">Document security is enabled</svelte:fragment>
|
||||
Users will be able to access this document if they have been granted
|
||||
<b>either document or collection permissions</b>.
|
||||
</Alert.Inline>
|
||||
<Permissions bind:permissions={$createDocument.permissions} />
|
||||
{:else}
|
||||
<Alert.Inline status="info">
|
||||
<svelte:fragment slot="title"
|
||||
>Document security is disabled</svelte:fragment>
|
||||
If you want to assign document permissions, navigate to Collection settings and
|
||||
enable document security. Otherwise, only collection permissions will be used.
|
||||
</Alert.Inline>
|
||||
{/if}
|
||||
</Layout.Stack>
|
||||
</svelte:fragment>
|
||||
|
||||
<Form bind:this={formComponent} onSubmit={create} bind:isSubmitting>
|
||||
<Layout.Stack direction="column" justifyContent="space-evenly">
|
||||
<Layout.Stack gap="xxl">
|
||||
<AttributeForm
|
||||
bind:formValues={$createDocument.document}
|
||||
attributes={$createDocument.attributes}
|
||||
bind:customId={$createDocument.id} />
|
||||
|
||||
<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 collection.documentSecurity}
|
||||
<Alert.Inline status="info">
|
||||
<svelte:fragment slot="title"
|
||||
>Document security is enabled</svelte:fragment>
|
||||
Users will be able to access this document if they have been granted
|
||||
<b>either document or collection permissions</b>.
|
||||
</Alert.Inline>
|
||||
<Permissions bind:permissions={$createDocument.permissions} />
|
||||
{:else}
|
||||
<Alert.Inline status="info">
|
||||
<svelte:fragment slot="title"
|
||||
>Document security is disabled</svelte:fragment>
|
||||
If you want to assign document permissions, navigate to Collection settings
|
||||
and enable document security. Otherwise, only collection permissions
|
||||
will be used.
|
||||
</Alert.Inline>
|
||||
{/if}
|
||||
</Layout.Stack>
|
||||
</Layout.Stack>
|
||||
|
||||
<div class="sheet-footer">
|
||||
<Layout.Stack gap="l">
|
||||
<Divider />
|
||||
|
||||
<div class="sheet-footer-actions">
|
||||
<Layout.Stack gap="m" direction="row" justifyContent="flex-end">
|
||||
<Button
|
||||
size="s"
|
||||
secondary
|
||||
fullWidthMobile
|
||||
on:click={() => (showSheet = false)}>Cancel</Button>
|
||||
|
||||
<Button
|
||||
size="s"
|
||||
fullWidthMobile
|
||||
disabled={$isSubmitting}
|
||||
on:click={() => formComponent.triggerSubmit()}>
|
||||
Create
|
||||
</Button>
|
||||
</Layout.Stack>
|
||||
</div>
|
||||
</Layout.Stack>
|
||||
</div>
|
||||
</Layout.Stack>
|
||||
</Form>
|
||||
</Sheet>
|
||||
</Layout.Stack>
|
||||
</SideSheet>
|
||||
</div>
|
||||
|
||||
<style>
|
||||
.sheet-container {
|
||||
top: 0;
|
||||
position: absolute;
|
||||
|
||||
& :global(aside header) {
|
||||
@media (max-width: 768px) {
|
||||
margin-top: 6rem;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.sheet-footer {
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
position: absolute;
|
||||
|
||||
& .sheet-footer-actions {
|
||||
padding: var(--space-8);
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
-173
@@ -1,173 +0,0 @@
|
||||
<script lang="ts">
|
||||
import { createEventDispatcher } from 'svelte';
|
||||
import {
|
||||
Button,
|
||||
Icon,
|
||||
Layout,
|
||||
Spreadsheet,
|
||||
Tooltip,
|
||||
Typography
|
||||
} from '@appwrite.io/pink-svelte';
|
||||
import { IconCalendar, IconFingerPrint, IconPlus } from '@appwrite.io/pink-icons-svelte';
|
||||
|
||||
const staticColumns = [
|
||||
{
|
||||
id: '$id',
|
||||
title: 'ID',
|
||||
width: 200,
|
||||
draggable: false,
|
||||
type: 'string',
|
||||
icon: IconFingerPrint
|
||||
},
|
||||
{
|
||||
id: '$createdAt',
|
||||
title: 'createdAt',
|
||||
width: { min: 180 },
|
||||
draggable: false,
|
||||
type: 'datetime',
|
||||
icon: IconCalendar
|
||||
},
|
||||
{
|
||||
id: '$updatedAt',
|
||||
title: 'updatedAt',
|
||||
width: { min: 180 },
|
||||
draggable: false,
|
||||
type: 'datetime',
|
||||
icon: IconCalendar
|
||||
},
|
||||
{
|
||||
id: 'actions',
|
||||
title: '',
|
||||
width: 555,
|
||||
draggable: false,
|
||||
type: 'string',
|
||||
icon: IconPlus
|
||||
},
|
||||
{
|
||||
id: 'empty',
|
||||
title: '',
|
||||
width: 40,
|
||||
isAction: true,
|
||||
draggable: false,
|
||||
type: 'string'
|
||||
}
|
||||
];
|
||||
|
||||
const dispatch = createEventDispatcher();
|
||||
</script>
|
||||
|
||||
<div class="spreadsheet-container-outer">
|
||||
<Spreadsheet.Root allowSelection emptyCells={20} height="fit-content" columns={staticColumns}>
|
||||
<svelte:fragment slot="header" let:root>
|
||||
{#each staticColumns as column (column.id)}
|
||||
{#if column.isAction}
|
||||
<Spreadsheet.Header.Cell column="actions" {root}>
|
||||
<Button.Button icon variant="extra-compact" on:click={() => {}}>
|
||||
<Icon icon={IconPlus} color="--fgcolor-neutral-primary" />
|
||||
</Button.Button>
|
||||
</Spreadsheet.Header.Cell>
|
||||
{:else}
|
||||
<Spreadsheet.Header.Cell
|
||||
{root}
|
||||
column={column.id}
|
||||
icon={column.icon ?? undefined}>
|
||||
{column.title}
|
||||
</Spreadsheet.Header.Cell>
|
||||
{/if}
|
||||
{/each}
|
||||
</svelte:fragment>
|
||||
</Spreadsheet.Root>
|
||||
|
||||
<div class="spreadsheet-fade-bottom">
|
||||
<div class="empty-actions">
|
||||
<Layout.Stack gap="xl">
|
||||
<Typography.Title>You have no records yet</Typography.Title>
|
||||
|
||||
<Layout.Stack>
|
||||
<Button.Button
|
||||
icon
|
||||
size="s"
|
||||
variant="secondary"
|
||||
on:click={() => dispatch('record')}>
|
||||
<Icon icon={IconPlus} size="s" />
|
||||
Create record
|
||||
</Button.Button>
|
||||
|
||||
<Tooltip>
|
||||
<Button.Button
|
||||
size="s"
|
||||
variant="secondary"
|
||||
on:click={() => dispatch('random')}>
|
||||
Generate random data
|
||||
</Button.Button>
|
||||
|
||||
<span slot="tooltip"> Yet to be added </span>
|
||||
</Tooltip>
|
||||
</Layout.Stack>
|
||||
</Layout.Stack>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<style lang="scss">
|
||||
.spreadsheet-container-outer {
|
||||
width: 100%;
|
||||
overflow: hidden;
|
||||
position: fixed;
|
||||
|
||||
@media (max-width: 768px) {
|
||||
position: unset;
|
||||
}
|
||||
|
||||
& :global(.spreadsheet-container) {
|
||||
overflow-x: hidden;
|
||||
overflow-y: hidden;
|
||||
}
|
||||
|
||||
& :global([data-select='true']) {
|
||||
opacity: 0.85;
|
||||
pointer-events: none;
|
||||
}
|
||||
}
|
||||
|
||||
.spreadsheet-fade-bottom {
|
||||
bottom: 0;
|
||||
width: 100%;
|
||||
height: 70vh;
|
||||
position: fixed;
|
||||
background: linear-gradient(
|
||||
180deg,
|
||||
rgba(255, 255, 255, 0) 0%,
|
||||
rgba(255, 255, 255, 0.855) 21%,
|
||||
#ffffff 100%
|
||||
);
|
||||
|
||||
z-index: 20;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
|
||||
@media (max-width: 768px) {
|
||||
height: 50vh;
|
||||
}
|
||||
}
|
||||
|
||||
:global(.theme-dark) .spreadsheet-fade-bottom {
|
||||
background: linear-gradient(
|
||||
180deg,
|
||||
rgba(25, 25, 28, 0.38) 13%,
|
||||
rgba(25, 25, 28, 0.7) 21%,
|
||||
rgba(25, 25, 28, 0.95) 38%,
|
||||
var(--bgcolor-neutral-default, #19191c) 100%
|
||||
);
|
||||
}
|
||||
|
||||
.empty-actions {
|
||||
bottom: 30%;
|
||||
position: fixed;
|
||||
|
||||
@media (min-width: 1024px) {
|
||||
left: 55%;
|
||||
bottom: 40%;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
+115
-109
@@ -1,5 +1,4 @@
|
||||
<script lang="ts">
|
||||
import { Empty } from '$lib/components';
|
||||
import { Container } from '$lib/layout';
|
||||
import { collection } from '../store';
|
||||
import Delete from './deleteIndex.svelte';
|
||||
@@ -8,7 +7,7 @@
|
||||
import CreateAttribute from '../createAttribute.svelte';
|
||||
import type { Models } from '@appwrite.io/console';
|
||||
import { Button } from '$lib/elements/forms';
|
||||
import CreateAttributeDropdown from '../attributes/createAttributeDropdown.svelte';
|
||||
// import CreateAttributeDropdown from '../attributes/createAttributeDropdown.svelte';
|
||||
import type { Option } from '../attributes/store';
|
||||
import FailedModal from '../failedModal.svelte';
|
||||
import { canWriteCollections } from '$lib/stores/roles';
|
||||
@@ -19,8 +18,7 @@
|
||||
Layout,
|
||||
Link,
|
||||
Popover,
|
||||
Table,
|
||||
Typography
|
||||
Spreadsheet
|
||||
} from '@appwrite.io/pink-svelte';
|
||||
import {
|
||||
IconDotsHorizontal,
|
||||
@@ -30,6 +28,11 @@
|
||||
} from '@appwrite.io/pink-icons-svelte';
|
||||
import type { ComponentProps } from 'svelte';
|
||||
import { Click, trackEvent } from '$lib/actions/analytics';
|
||||
import EmptySheet from '../layout/emptySheet.svelte';
|
||||
import SpreadsheetContainer from '../layout/spreadsheet.svelte';
|
||||
import { goto } from '$app/navigation';
|
||||
import { base } from '$app/paths';
|
||||
import { page } from '$app/state';
|
||||
|
||||
export let data;
|
||||
|
||||
@@ -56,11 +59,11 @@
|
||||
}
|
||||
</script>
|
||||
|
||||
<Container>
|
||||
<Layout.Stack direction="row" justifyContent="space-between">
|
||||
<Typography.Title>Indexes</Typography.Title>
|
||||
<Container expanded style="background: var(--bgcolor-neutral-primary)">
|
||||
<Layout.Stack direction="row" justifyContent="flex-end">
|
||||
{#if $canWriteCollections}
|
||||
<Button
|
||||
secondary
|
||||
event="create_index"
|
||||
disabled={!$collection?.attributes?.length}
|
||||
on:click={() => (showCreateIndex = true)}>
|
||||
@@ -69,116 +72,119 @@
|
||||
</Button>
|
||||
{/if}
|
||||
</Layout.Stack>
|
||||
</Container>
|
||||
|
||||
<div class="databases-spreadsheet">
|
||||
{#if data.collection?.attributes?.length}
|
||||
{#if data.collection.indexes.length}
|
||||
<Table.Root
|
||||
let:root
|
||||
columns={[
|
||||
{ id: 'key' },
|
||||
{ id: 'type' },
|
||||
{ id: 'attributes' },
|
||||
{ id: 'orders' },
|
||||
{ id: 'lengths' },
|
||||
{ id: 'actions', width: 40 }
|
||||
]}>
|
||||
<svelte:fragment slot="header" let:root>
|
||||
<Table.Header.Cell column="key" {root}>Key</Table.Header.Cell>
|
||||
<Table.Header.Cell column="type" {root}>Type</Table.Header.Cell>
|
||||
<Table.Header.Cell column="attributes" {root}>Attributes</Table.Header.Cell>
|
||||
<Table.Header.Cell column="orders" {root}>Orders</Table.Header.Cell>
|
||||
<Table.Header.Cell column="lengths" {root}>Lengths</Table.Header.Cell>
|
||||
<Table.Header.Cell column="actions" {root} />
|
||||
</svelte:fragment>
|
||||
{#each data.collection.indexes as index}
|
||||
<Table.Row.Base {root}>
|
||||
<Table.Cell column="key" {root}>
|
||||
<Layout.Stack direction="row" alignItems="center">
|
||||
{index.key}
|
||||
{#if index.status !== 'available'}
|
||||
<Badge
|
||||
size="s"
|
||||
variant="secondary"
|
||||
content={index.status}
|
||||
type={getAttributeStatusBadge(index.status)} />
|
||||
{#if index.error}
|
||||
<Link.Button
|
||||
variant="muted"
|
||||
on:click={(e) => {
|
||||
e.preventDefault();
|
||||
error = index.error;
|
||||
showFailed = true;
|
||||
}}>Details</Link.Button>
|
||||
<SpreadsheetContainer>
|
||||
<Spreadsheet.Root
|
||||
let:root
|
||||
columns={[
|
||||
{ id: 'key' },
|
||||
{ id: 'type' },
|
||||
{ id: 'attributes' },
|
||||
{ id: 'orders' },
|
||||
{ id: 'lengths' },
|
||||
{ id: 'actions', width: 40 }
|
||||
]}>
|
||||
<svelte:fragment slot="header" let:root>
|
||||
<Spreadsheet.Header.Cell column="key" {root}>Key</Spreadsheet.Header.Cell>
|
||||
<Spreadsheet.Header.Cell column="type" {root}>Type</Spreadsheet.Header.Cell>
|
||||
<Spreadsheet.Header.Cell column="attributes" {root}
|
||||
>Attributes</Spreadsheet.Header.Cell>
|
||||
<Spreadsheet.Header.Cell column="orders" {root}
|
||||
>Orders</Spreadsheet.Header.Cell>
|
||||
<Spreadsheet.Header.Cell column="lengths" {root}
|
||||
>Lengths</Spreadsheet.Header.Cell>
|
||||
<Spreadsheet.Header.Cell column="actions" {root} />
|
||||
</svelte:fragment>
|
||||
{#each data.collection.indexes as index}
|
||||
<Spreadsheet.Row.Base {root}>
|
||||
<Spreadsheet.Cell column="key" {root}>
|
||||
<Layout.Stack direction="row" alignItems="center">
|
||||
{index.key}
|
||||
{#if index.status !== 'available'}
|
||||
<Badge
|
||||
size="s"
|
||||
variant="secondary"
|
||||
content={index.status}
|
||||
type={getAttributeStatusBadge(index.status)} />
|
||||
{#if index.error}
|
||||
<Link.Button
|
||||
variant="muted"
|
||||
on:click={(e) => {
|
||||
e.preventDefault();
|
||||
error = index.error;
|
||||
showFailed = true;
|
||||
}}>Details</Link.Button>
|
||||
{/if}
|
||||
{/if}
|
||||
{/if}
|
||||
</Layout.Stack>
|
||||
</Table.Cell>
|
||||
<Table.Cell column="type" {root}>{index.type}</Table.Cell>
|
||||
<Table.Cell column="attributes" {root}>
|
||||
{index.attributes}
|
||||
</Table.Cell>
|
||||
<Table.Cell column="orders" {root}>
|
||||
{index.orders}
|
||||
</Table.Cell>
|
||||
<Table.Cell column="lengths" {root}>
|
||||
{index.lengths}
|
||||
</Table.Cell>
|
||||
<Table.Cell column="actions" {root}>
|
||||
<Popover let:toggle padding="none" placement="bottom-end">
|
||||
<Button text icon ariaLabel="more options" on:click={toggle}>
|
||||
<Icon icon={IconDotsHorizontal} size="s" />
|
||||
</Button>
|
||||
<ActionMenu.Root slot="tooltip">
|
||||
<ActionMenu.Item.Button
|
||||
leadingIcon={IconEye}
|
||||
on:click={() => {
|
||||
selectedIndex = index;
|
||||
showOverview = true;
|
||||
}}>Overview</ActionMenu.Item.Button>
|
||||
<ActionMenu.Item.Button
|
||||
leadingIcon={IconTrash}
|
||||
on:click={() => {
|
||||
selectedIndex = index;
|
||||
showDelete = true;
|
||||
trackEvent(Click.DatabaseIndexDelete);
|
||||
}}>Delete</ActionMenu.Item.Button>
|
||||
</ActionMenu.Root>
|
||||
</Popover>
|
||||
</Table.Cell>
|
||||
</Table.Row.Base>
|
||||
{/each}
|
||||
</Table.Root>
|
||||
</Layout.Stack>
|
||||
</Spreadsheet.Cell>
|
||||
<Spreadsheet.Cell column="type" {root}>{index.type}</Spreadsheet.Cell>
|
||||
<Spreadsheet.Cell column="attributes" {root}>
|
||||
{index.attributes}
|
||||
</Spreadsheet.Cell>
|
||||
<Spreadsheet.Cell column="orders" {root}>
|
||||
{index.orders}
|
||||
</Spreadsheet.Cell>
|
||||
<Spreadsheet.Cell column="lengths" {root}>
|
||||
{index.lengths}
|
||||
</Spreadsheet.Cell>
|
||||
<Spreadsheet.Cell column="actions" {root}>
|
||||
<Popover let:toggle padding="none" placement="bottom-end">
|
||||
<Button text icon ariaLabel="more options" on:click={toggle}>
|
||||
<Icon icon={IconDotsHorizontal} size="s" />
|
||||
</Button>
|
||||
<ActionMenu.Root slot="tooltip">
|
||||
<ActionMenu.Item.Button
|
||||
leadingIcon={IconEye}
|
||||
on:click={() => {
|
||||
selectedIndex = index;
|
||||
showOverview = true;
|
||||
}}>Overview</ActionMenu.Item.Button>
|
||||
<ActionMenu.Item.Button
|
||||
leadingIcon={IconTrash}
|
||||
on:click={() => {
|
||||
selectedIndex = index;
|
||||
showDelete = true;
|
||||
trackEvent(Click.DatabaseIndexDelete);
|
||||
}}>Delete</ActionMenu.Item.Button>
|
||||
</ActionMenu.Root>
|
||||
</Popover>
|
||||
</Spreadsheet.Cell>
|
||||
</Spreadsheet.Row.Base>
|
||||
{/each}
|
||||
</Spreadsheet.Root>
|
||||
</SpreadsheetContainer>
|
||||
{:else}
|
||||
<Empty
|
||||
allowCreate={$canWriteCollections}
|
||||
single
|
||||
href="https://appwrite.io/docs/products/databases/collections#indexes"
|
||||
target="index"
|
||||
on:click={() => (showCreateIndex = true)} />
|
||||
<EmptySheet
|
||||
mode="indexes"
|
||||
cta={{
|
||||
primary: {
|
||||
onClick: () => (showCreateIndex = true),
|
||||
disabled: !$collection?.attributes?.length
|
||||
}
|
||||
}} />
|
||||
{/if}
|
||||
{:else}
|
||||
<Empty single target="attribute">
|
||||
<svelte:fragment slot="actions">
|
||||
<Button
|
||||
external
|
||||
href="https://appwrite.io/docs/products/databases/collections#attributes"
|
||||
text
|
||||
event="empty_documentation"
|
||||
ariaLabel={`create {target}`}>Documentation</Button>
|
||||
{#if $canWriteCollections}
|
||||
<CreateAttributeDropdown
|
||||
bind:selectedOption={selectedAttribute}
|
||||
bind:showCreate={showCreateAttribute}
|
||||
let:toggle>
|
||||
<Button secondary event="create_attribute" on:click={toggle}>
|
||||
Create attribute
|
||||
</Button>
|
||||
</CreateAttributeDropdown>
|
||||
{/if}
|
||||
</svelte:fragment>
|
||||
</Empty>
|
||||
<!-- todo: show create attribute if one doesn't exist -->
|
||||
<EmptySheet
|
||||
mode="indexes"
|
||||
title="You have no columns yet"
|
||||
cta={{
|
||||
primary: {
|
||||
text: 'Create columns',
|
||||
onClick: async () => {
|
||||
await goto(
|
||||
`${base}/project-${page.params.region}-${page.params.project}/databases/database-${page.params.database}/collection-${page.params.collection}/attributes`
|
||||
);
|
||||
}
|
||||
}
|
||||
}} />
|
||||
{/if}
|
||||
</Container>
|
||||
</div>
|
||||
|
||||
<Create bind:showCreateIndex />
|
||||
|
||||
|
||||
+241
@@ -0,0 +1,241 @@
|
||||
<script lang="ts">
|
||||
import {
|
||||
Button,
|
||||
Icon,
|
||||
Layout,
|
||||
Spreadsheet,
|
||||
Tooltip,
|
||||
Typography
|
||||
} from '@appwrite.io/pink-svelte';
|
||||
import type { SheetColumn } from '$lib/helpers/types';
|
||||
import { IconCalendar, IconFingerPrint, IconPlus } from '@appwrite.io/pink-icons-svelte';
|
||||
import { isSmallViewport } from '$lib/stores/viewport';
|
||||
|
||||
type Mode = 'records' | 'columns' | 'indexes';
|
||||
|
||||
type ColumnsMap = Record<Mode, SheetColumn[]>;
|
||||
|
||||
type CTA = {
|
||||
text?: string;
|
||||
disabled?: boolean;
|
||||
onClick?: () => void;
|
||||
};
|
||||
|
||||
export let mode: Mode;
|
||||
export let title: string | undefined = undefined;
|
||||
export let cta:
|
||||
| {
|
||||
primary?: CTA;
|
||||
random?: CTA;
|
||||
}
|
||||
| undefined = undefined;
|
||||
|
||||
function makeColumns(...middle: SheetColumn[]): SheetColumn[] {
|
||||
return [
|
||||
{
|
||||
id: '$id',
|
||||
title: 'ID',
|
||||
type: 'string',
|
||||
width: 250,
|
||||
icon: IconFingerPrint
|
||||
},
|
||||
...middle,
|
||||
{
|
||||
id: 'actions',
|
||||
title: '',
|
||||
width: 555,
|
||||
draggable: false,
|
||||
type: 'string',
|
||||
icon: IconPlus
|
||||
},
|
||||
{
|
||||
id: 'empty',
|
||||
title: '',
|
||||
width: 40,
|
||||
isAction: true,
|
||||
draggable: false,
|
||||
type: 'string'
|
||||
}
|
||||
];
|
||||
}
|
||||
|
||||
const columnsMap: ColumnsMap = {
|
||||
records: makeColumns(
|
||||
{
|
||||
id: '$createdAt',
|
||||
title: 'Created',
|
||||
type: 'datetime',
|
||||
width: { min: 180 },
|
||||
draggable: false,
|
||||
icon: IconCalendar
|
||||
},
|
||||
{
|
||||
id: '$updatedAt',
|
||||
title: 'Updated',
|
||||
type: 'datetime',
|
||||
width: { min: 180 },
|
||||
draggable: false,
|
||||
icon: IconCalendar
|
||||
}
|
||||
),
|
||||
columns: makeColumns(
|
||||
{
|
||||
id: 'indexed',
|
||||
title: 'Indexed',
|
||||
type: 'boolean',
|
||||
width: { min: 180 },
|
||||
draggable: false,
|
||||
isAction: false
|
||||
},
|
||||
{
|
||||
id: 'default',
|
||||
title: 'Default',
|
||||
type: 'string',
|
||||
width: { min: 180 },
|
||||
draggable: false,
|
||||
isAction: false
|
||||
}
|
||||
),
|
||||
indexes: makeColumns(
|
||||
{
|
||||
id: 'type',
|
||||
title: 'Type',
|
||||
type: 'string',
|
||||
width: { min: 150 },
|
||||
draggable: false,
|
||||
isAction: false
|
||||
},
|
||||
{
|
||||
id: 'attributes',
|
||||
title: 'Columns',
|
||||
type: 'string',
|
||||
width: { min: 200 },
|
||||
draggable: false,
|
||||
isAction: false
|
||||
}
|
||||
)
|
||||
};
|
||||
|
||||
$: spreadsheetColumns = columnsMap[mode];
|
||||
</script>
|
||||
|
||||
<div class="spreadsheet-container-outer">
|
||||
<Spreadsheet.Root
|
||||
allowSelection
|
||||
emptyCells={20}
|
||||
height="fit-content"
|
||||
columns={spreadsheetColumns}>
|
||||
<svelte:fragment slot="header" let:root>
|
||||
{#each spreadsheetColumns as column (column.id)}
|
||||
<Spreadsheet.Header.Cell {root} column={column.id} icon={column.icon ?? undefined}>
|
||||
{#if column.isAction}
|
||||
<Button.Button icon variant="extra-compact">
|
||||
<Icon icon={IconPlus} color="--fgcolor-neutral-primary" />
|
||||
</Button.Button>
|
||||
{:else}
|
||||
{column.title}
|
||||
{/if}
|
||||
</Spreadsheet.Header.Cell>
|
||||
{/each}
|
||||
</svelte:fragment>
|
||||
</Spreadsheet.Root>
|
||||
|
||||
<div class="spreadsheet-fade-bottom">
|
||||
<div class="empty-actions">
|
||||
<Layout.Stack gap="xl" alignItems="center">
|
||||
<Typography.Title>{title ?? `You have no ${mode} yet`}</Typography.Title>
|
||||
|
||||
<Layout.Stack
|
||||
inline
|
||||
alignItems="center"
|
||||
direction={$isSmallViewport ? 'column' : 'row'}
|
||||
gap="s">
|
||||
<Button.Button
|
||||
icon
|
||||
size="s"
|
||||
variant="secondary"
|
||||
disabled={cta?.primary?.disabled}
|
||||
on:click={cta?.primary?.onClick}>
|
||||
<Icon icon={IconPlus} size="s" />
|
||||
{cta?.primary?.text ?? `Create ${mode}`}
|
||||
</Button.Button>
|
||||
|
||||
{#if mode === 'records'}
|
||||
<Tooltip>
|
||||
<Button.Button
|
||||
disabled={cta?.random?.disabled}
|
||||
size="s"
|
||||
variant="secondary"
|
||||
on:click={cta?.random.onClick}>
|
||||
{cta?.random?.text ?? `Generate random data`}
|
||||
</Button.Button>
|
||||
<span slot="tooltip">Yet to be added</span>
|
||||
</Tooltip>
|
||||
{/if}
|
||||
</Layout.Stack>
|
||||
</Layout.Stack>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<style lang="scss">
|
||||
.spreadsheet-container-outer {
|
||||
width: 100%;
|
||||
overflow: hidden;
|
||||
position: fixed;
|
||||
|
||||
@media (max-width: 768px) {
|
||||
position: unset;
|
||||
}
|
||||
|
||||
& :global(.spreadsheet-container) {
|
||||
overflow-x: hidden;
|
||||
overflow-y: hidden;
|
||||
}
|
||||
|
||||
& :global([data-select='true']) {
|
||||
opacity: 0.85;
|
||||
pointer-events: none;
|
||||
}
|
||||
}
|
||||
|
||||
.spreadsheet-fade-bottom {
|
||||
bottom: 0;
|
||||
width: 100%;
|
||||
height: 70vh;
|
||||
position: fixed;
|
||||
background: linear-gradient(
|
||||
180deg,
|
||||
rgba(255, 255, 255, 0) 0%,
|
||||
rgba(255, 255, 255, 0.855) 21%,
|
||||
#ffffff 100%
|
||||
);
|
||||
z-index: 20;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
|
||||
@media (max-width: 768px) {
|
||||
height: 50vh;
|
||||
}
|
||||
}
|
||||
|
||||
:global(.theme-dark) .spreadsheet-fade-bottom {
|
||||
background: linear-gradient(
|
||||
180deg,
|
||||
rgba(25, 25, 28, 0.38) 13%,
|
||||
rgba(25, 25, 28, 0.7) 21%,
|
||||
rgba(25, 25, 28, 0.95) 38%,
|
||||
var(--bgcolor-neutral-default, #19191c) 100%
|
||||
);
|
||||
}
|
||||
|
||||
.empty-actions {
|
||||
bottom: 30%;
|
||||
position: fixed;
|
||||
|
||||
@media (min-width: 1024px) {
|
||||
left: 55%; /* TODO: if both sidebars open, use 55%, else 50% */
|
||||
bottom: 40%;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
+91
@@ -0,0 +1,91 @@
|
||||
<script lang="ts">
|
||||
import type { Snippet } from 'svelte';
|
||||
import { Button } from '$lib/elements/forms';
|
||||
import { Divider, Layout, Sheet, Typography } from '@appwrite.io/pink-svelte';
|
||||
|
||||
let {
|
||||
show = $bindable(false),
|
||||
title,
|
||||
spaced = false,
|
||||
closeOnBlur = false,
|
||||
submit,
|
||||
children
|
||||
}: {
|
||||
show: boolean;
|
||||
title: string;
|
||||
spaced?: boolean;
|
||||
closeOnBlur?: boolean;
|
||||
submit: {
|
||||
text: string;
|
||||
disabled?: boolean;
|
||||
onClick: () => void | Promise<void>;
|
||||
};
|
||||
children: Snippet;
|
||||
} = $props();
|
||||
</script>
|
||||
|
||||
<div class="sheet-container" class:spaced>
|
||||
<Sheet bind:open={show} {closeOnBlur}>
|
||||
<div slot="header" style:width="100%">
|
||||
<Layout.Stack direction="row" justifyContent="space-between" alignItems="center">
|
||||
<Layout.Stack direction="row" gap="m" alignItems="center">
|
||||
<Typography.Text variant="m-400">{title}</Typography.Text>
|
||||
</Layout.Stack>
|
||||
</Layout.Stack>
|
||||
</div>
|
||||
|
||||
<Layout.Stack direction="column" justifyContent="space-evenly">
|
||||
<Layout.Stack gap="xl">
|
||||
{@render children()}
|
||||
</Layout.Stack>
|
||||
|
||||
<div class="sheet-footer">
|
||||
<Layout.Stack gap="l">
|
||||
<Divider />
|
||||
|
||||
<div class="sheet-footer-actions">
|
||||
<Layout.Stack gap="m" direction="row" justifyContent="flex-end">
|
||||
<Button size="s" secondary on:click={() => (show = false)}
|
||||
>Cancel</Button>
|
||||
|
||||
<Button
|
||||
size="s"
|
||||
disabled={submit.disabled}
|
||||
on:click={async () => {
|
||||
await submit.onClick();
|
||||
show = false;
|
||||
}}>
|
||||
{submit.text}
|
||||
</Button>
|
||||
</Layout.Stack>
|
||||
</div>
|
||||
</Layout.Stack>
|
||||
</div>
|
||||
</Layout.Stack>
|
||||
</Sheet>
|
||||
</div>
|
||||
|
||||
<style lang="scss">
|
||||
.sheet-container {
|
||||
top: 0;
|
||||
position: absolute;
|
||||
|
||||
@media (max-width: 768px) {
|
||||
&.spaced :global(aside header) {
|
||||
margin-top: 6rem;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.sheet-footer {
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
position: absolute;
|
||||
|
||||
& .sheet-footer-actions {
|
||||
padding-inline: var(--space-8);
|
||||
padding-block-end: var(--space-6);
|
||||
}
|
||||
}
|
||||
</style>
|
||||
+24
@@ -0,0 +1,24 @@
|
||||
<script lang="ts">
|
||||
import { onMount } from 'svelte';
|
||||
|
||||
let spreadsheetHeight = '75vh';
|
||||
let spreadsheetWrapper: HTMLDivElement;
|
||||
|
||||
function resizeSheet() {
|
||||
if (!spreadsheetWrapper) return;
|
||||
const rect = spreadsheetWrapper.getBoundingClientRect();
|
||||
spreadsheetHeight = window.innerHeight - rect.top + 'px';
|
||||
}
|
||||
|
||||
onMount(resizeSheet);
|
||||
</script>
|
||||
|
||||
<svelte:window on:resize={resizeSheet} />
|
||||
|
||||
<div
|
||||
bind:this={spreadsheetWrapper}
|
||||
style:height={spreadsheetHeight}
|
||||
style:position="relative"
|
||||
style:width="100%">
|
||||
<slot />
|
||||
</div>
|
||||
+25
-78
@@ -31,9 +31,7 @@
|
||||
FloatingActionBar,
|
||||
Icon,
|
||||
InteractiveText,
|
||||
Typography,
|
||||
Sheet,
|
||||
Divider
|
||||
Typography
|
||||
} from '@appwrite.io/pink-svelte';
|
||||
import { toLocaleDateTime } from '$lib/helpers/date';
|
||||
import DualTimeView from '$lib/components/dualTimeView.svelte';
|
||||
@@ -58,30 +56,23 @@
|
||||
import { type Action } from './sheetOptions.svelte';
|
||||
import EditAttribute from './attributes/edit.svelte';
|
||||
import { isSmallViewport } from '$lib/stores/viewport';
|
||||
import SideSheet from './layout/sidesheet.svelte';
|
||||
import SpreadsheetContainer from './layout/spreadsheet.svelte';
|
||||
|
||||
export let data: PageData;
|
||||
export let showRecordsCreateSheet: boolean;
|
||||
|
||||
const databaseId = page.params.database;
|
||||
const collectionId = page.params.collection;
|
||||
|
||||
let displayNames = {};
|
||||
let showRelationships = false;
|
||||
let selectedRelationship: Models.AttributeRelationship = null;
|
||||
let relationshipData: Partial<Models.Document>[];
|
||||
|
||||
let spreadsheetHeight = '75vh';
|
||||
let spreadsheetWrapper: HTMLDivElement;
|
||||
let selectedRelationship: Models.AttributeRelationship = null;
|
||||
|
||||
$: documents = data.documents;
|
||||
|
||||
function resizeSheet() {
|
||||
if (!spreadsheetWrapper) return;
|
||||
const rect = spreadsheetWrapper.getBoundingClientRect();
|
||||
spreadsheetHeight = window.innerHeight - rect.top + 'px';
|
||||
}
|
||||
|
||||
onMount(async () => {
|
||||
resizeSheet();
|
||||
displayNames = preferences.getDisplayNames();
|
||||
});
|
||||
|
||||
@@ -316,7 +307,7 @@
|
||||
if (type === 'update') {
|
||||
$databaseSheetOptions.show = true;
|
||||
$databaseSheetOptions.isEdit = true;
|
||||
$databaseSheetOptions.title = 'update column';
|
||||
$databaseSheetOptions.title = 'Update column';
|
||||
}
|
||||
|
||||
if (type === 'column-left' || type === 'column-right') {
|
||||
@@ -339,13 +330,7 @@
|
||||
: emptyCellsLimit - data.documents.documents.length;
|
||||
</script>
|
||||
|
||||
<svelte:window on:resize={resizeSheet} />
|
||||
|
||||
<div
|
||||
bind:this={spreadsheetWrapper}
|
||||
style:height={spreadsheetHeight}
|
||||
style:position="relative"
|
||||
style:width="100%">
|
||||
<SpreadsheetContainer>
|
||||
<Spreadsheet.Root
|
||||
let:root
|
||||
{loading}
|
||||
@@ -361,7 +346,10 @@
|
||||
{#each $columns as column (column.id)}
|
||||
{#if column.isAction}
|
||||
<Spreadsheet.Header.Cell column="actions" {root}>
|
||||
<Button.Button icon variant="extra-compact" on:click={() => {}}>
|
||||
<Button.Button
|
||||
icon
|
||||
variant="extra-compact"
|
||||
on:click={() => (showRecordsCreateSheet = true)}>
|
||||
<Icon icon={IconPlus} color="--fgcolor-neutral-primary" />
|
||||
</Button.Button>
|
||||
</Spreadsheet.Header.Cell>
|
||||
@@ -553,7 +541,7 @@
|
||||
</FloatingActionBar>
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
</SpreadsheetContainer>
|
||||
|
||||
<RelationshipsModal bind:show={showRelationships} {selectedRelationship} data={relationshipData} />
|
||||
|
||||
@@ -624,49 +612,19 @@
|
||||
</p>
|
||||
</Confirm>
|
||||
|
||||
<div style="position: absolute; top: 0; ">
|
||||
<Sheet bind:open={$databaseSheetOptions.show} closeOnBlur={false}>
|
||||
<div slot="header" style:width="100%">
|
||||
<Layout.Stack direction="row" justifyContent="space-between" alignItems="center">
|
||||
<Layout.Stack direction="row" gap="m" alignItems="center">
|
||||
<Typography.Text variant="m-400">{$databaseSheetOptions.title}</Typography.Text>
|
||||
</Layout.Stack>
|
||||
</Layout.Stack>
|
||||
</div>
|
||||
|
||||
<Layout.Stack direction="column" justifyContent="space-evenly">
|
||||
<Layout.Stack gap="xl">
|
||||
<EditAttribute
|
||||
isModal={false}
|
||||
showEdit={$databaseSheetOptions.isEdit}
|
||||
selectedAttribute={$databaseSheetOptions.column} />
|
||||
</Layout.Stack>
|
||||
|
||||
<div class="sheet-footer">
|
||||
<Layout.Stack gap="l">
|
||||
<Divider />
|
||||
|
||||
<div class="sheet-footer-actions">
|
||||
<Layout.Stack gap="m" direction="row" justifyContent="flex-end">
|
||||
<Button.Button
|
||||
size="s"
|
||||
variant="secondary"
|
||||
on:click={() => ($databaseSheetOptions.show = false)}
|
||||
>Cancel</Button.Button>
|
||||
|
||||
<Button.Button
|
||||
size="s"
|
||||
disabled={$databaseSheetOptions.disableSubmit}
|
||||
on:click={() => $databaseSheetOptions.submitAction()}>
|
||||
Update
|
||||
</Button.Button>
|
||||
</Layout.Stack>
|
||||
</div>
|
||||
</Layout.Stack>
|
||||
</div>
|
||||
</Layout.Stack>
|
||||
</Sheet>
|
||||
</div>
|
||||
<SideSheet
|
||||
title={$databaseSheetOptions.title}
|
||||
bind:show={$databaseSheetOptions.show}
|
||||
submit={{
|
||||
text: 'Update',
|
||||
disabled: $databaseSheetOptions.disableSubmit,
|
||||
onClick: () => $databaseSheetOptions.submitAction()
|
||||
}}>
|
||||
<EditAttribute
|
||||
isModal={false}
|
||||
showEdit={$databaseSheetOptions.isEdit}
|
||||
selectedAttribute={$databaseSheetOptions.column} />
|
||||
</SideSheet>
|
||||
|
||||
<style lang="scss">
|
||||
.floating-action-bar {
|
||||
@@ -676,15 +634,4 @@
|
||||
position: absolute;
|
||||
transform: translateX(-50%);
|
||||
}
|
||||
|
||||
.sheet-footer {
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
position: absolute;
|
||||
|
||||
& .sheet-footer-actions {
|
||||
padding: var(--space-8);
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
Reference in New Issue
Block a user