mirror of
https://github.com/appwrite/console.git
synced 2026-06-06 19:27:48 +00:00
feat: some work on relationship modal
This commit is contained in:
@@ -1,4 +1,4 @@
|
||||
import { writable, readable } from 'svelte/store';
|
||||
import { writable } from 'svelte/store';
|
||||
import type { SvelteComponent } from 'svelte';
|
||||
import { browser } from '$app/environment';
|
||||
import { PAGE_LIMIT } from '$lib/constants';
|
||||
|
||||
@@ -0,0 +1,15 @@
|
||||
import { writable } from 'svelte/store';
|
||||
|
||||
export type Column = {
|
||||
id: string;
|
||||
name: string;
|
||||
show: boolean;
|
||||
width?: number;
|
||||
};
|
||||
|
||||
export const columns = writable<Column[]>([
|
||||
{ id: '$id', name: 'Database ID', show: true, width: 50 },
|
||||
{ id: 'name', name: 'Name', show: true, width: 120 },
|
||||
{ id: '$createdAt', name: 'Created', show: true, width: 120 },
|
||||
{ id: '$updatedAt', name: 'Updated', show: true, width: 120 }
|
||||
]);
|
||||
+58
-46
@@ -18,9 +18,11 @@
|
||||
import { collection } from '../store';
|
||||
import { page } from '$app/stores';
|
||||
import { PAGE_LIMIT } from '$lib/constants';
|
||||
import CreateAttribute from '../createAttribute.svelte';
|
||||
import { tooltip } from '$lib/actions/tooltip';
|
||||
|
||||
export let data: PageData;
|
||||
let showCreateAttribute = false;
|
||||
|
||||
function openWizard() {
|
||||
wizard.start(Create);
|
||||
@@ -85,56 +87,66 @@
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
{#if data.documents.total}
|
||||
<TableScroll isSticky>
|
||||
<TableHeader>
|
||||
<TableCellHead eyebrow={false}>Document ID</TableCellHead>
|
||||
{#each columns as column}
|
||||
<TableCellHead eyebrow={false}>{column.title}</TableCellHead>
|
||||
{/each}
|
||||
</TableHeader>
|
||||
<TableBody>
|
||||
{#each data.documents.documents as document}
|
||||
<TableRowLink
|
||||
href={`${base}/console/project-${$page.params.project}/databases/database-${$page.params.database}/collection-${$collection.$id}/document-${document.$id}`}>
|
||||
<TableCell width={230}>
|
||||
<Copy value={document.$id}>
|
||||
<Pill button>
|
||||
<span class="icon-duplicate" aria-hidden="true" />
|
||||
<span class="text u-trim-start">{document.$id}</span>
|
||||
</Pill>
|
||||
</Copy>
|
||||
</TableCell>
|
||||
{#each columns as column}
|
||||
{@const formatted = formatColumn(document[column.key])}
|
||||
<TableCell>
|
||||
<div
|
||||
use:tooltip={{
|
||||
content: formatted.whole,
|
||||
disabled: !formatted.truncated
|
||||
}}>
|
||||
{formatted.value}
|
||||
</div>
|
||||
{#if $collection?.attributes?.length}
|
||||
{#if data.documents.total}
|
||||
<TableScroll isSticky>
|
||||
<TableHeader>
|
||||
<TableCellHead eyebrow={false}>Document ID</TableCellHead>
|
||||
{#each columns as column}
|
||||
<TableCellHead eyebrow={false}>{column.title}</TableCellHead>
|
||||
{/each}
|
||||
</TableHeader>
|
||||
<TableBody>
|
||||
{#each data.documents.documents as document}
|
||||
<TableRowLink
|
||||
href={`${base}/console/project-${$page.params.project}/databases/database-${$page.params.database}/collection-${$collection.$id}/document-${document.$id}`}>
|
||||
<TableCell width={230}>
|
||||
<Copy value={document.$id}>
|
||||
<Pill button>
|
||||
<span class="icon-duplicate" aria-hidden="true" />
|
||||
<span class="text u-trim-start">{document.$id}</span>
|
||||
</Pill>
|
||||
</Copy>
|
||||
</TableCell>
|
||||
{/each}
|
||||
</TableRowLink>
|
||||
{/each}
|
||||
</TableBody>
|
||||
</TableScroll>
|
||||
{#each columns as column}
|
||||
{@const formatted = formatColumn(document[column.key])}
|
||||
<TableCell>
|
||||
<div
|
||||
use:tooltip={{
|
||||
content: formatted.whole,
|
||||
disabled: !formatted.truncated
|
||||
}}>
|
||||
{formatted.value}
|
||||
</div>
|
||||
</TableCell>
|
||||
{/each}
|
||||
</TableRowLink>
|
||||
{/each}
|
||||
</TableBody>
|
||||
</TableScroll>
|
||||
|
||||
<div class="u-flex common-section u-main-space-between">
|
||||
<p class="text">Total results: {data.documents.total}</p>
|
||||
<Pagination
|
||||
limit={PAGE_LIMIT}
|
||||
path={`/console/project-${$page.params.project}/databases/database-${$page.params.database}/collection-${$page.params.collection}`}
|
||||
offset={data.offset}
|
||||
sum={data.documents.total} />
|
||||
</div>
|
||||
<div class="u-flex common-section u-main-space-between">
|
||||
<p class="text">Total results: {data.documents.total}</p>
|
||||
<Pagination
|
||||
limit={PAGE_LIMIT}
|
||||
path={`/console/project-${$page.params.project}/databases/database-${$page.params.database}/collection-${$page.params.collection}`}
|
||||
offset={data.offset}
|
||||
sum={data.documents.total} />
|
||||
</div>
|
||||
{:else}
|
||||
<Empty
|
||||
single
|
||||
href="https://appwrite.io/docs/databases#create-documents"
|
||||
target="document"
|
||||
on:click={openWizard} />
|
||||
{/if}
|
||||
{:else}
|
||||
<Empty
|
||||
single
|
||||
href="https://appwrite.io/docs/databases#create-documents"
|
||||
target="document"
|
||||
on:click={openWizard} />
|
||||
href="https://appwrite.io/docs/databases#attributes"
|
||||
target="attribute"
|
||||
on:click={() => (showCreateAttribute = true)} />
|
||||
{/if}
|
||||
</Container>
|
||||
|
||||
<CreateAttribute bind:showCreate={showCreateAttribute} />
|
||||
|
||||
+2
-14
@@ -17,6 +17,7 @@
|
||||
import CreateIndex from '../indexes/createIndex.svelte';
|
||||
import Delete from './deleteAttribute.svelte';
|
||||
import Overview from './overview.svelte';
|
||||
import { options } from './store';
|
||||
|
||||
let showCreateDropdown = false;
|
||||
let showDropdown = [];
|
||||
@@ -26,19 +27,6 @@
|
||||
let showDelete = false;
|
||||
let showOverview = false;
|
||||
let showCreateIndex = false;
|
||||
|
||||
const attributesList = [
|
||||
{ name: 'String', icon: 'text' },
|
||||
{ name: 'Integer', icon: 'hashtag' },
|
||||
{ name: 'Float', icon: 'hashtag' },
|
||||
{ name: 'Boolean', icon: 'toggle' },
|
||||
{ name: 'Datetime', icon: 'calendar' },
|
||||
{ name: 'Email', icon: 'mail' },
|
||||
{ name: 'IP', icon: 'location-marker' },
|
||||
{ name: 'URL', icon: 'link' },
|
||||
{ name: 'Enum', icon: 'list' },
|
||||
{ name: 'Relationship', icon: 'relationship' }
|
||||
];
|
||||
</script>
|
||||
|
||||
<Container>
|
||||
@@ -53,7 +41,7 @@
|
||||
<span class="text">Create attribute</span>
|
||||
</Button>
|
||||
<svelte:fragment slot="list">
|
||||
{#each attributesList as attribute}
|
||||
{#each options as attribute}
|
||||
<DropListItem
|
||||
icon={attribute.icon}
|
||||
on:click={() => {
|
||||
|
||||
+39
-28
@@ -21,7 +21,9 @@
|
||||
</script>
|
||||
|
||||
<script lang="ts">
|
||||
import { InputNumber, InputText, InputChoice } from '$lib/elements/forms';
|
||||
import { InputText, InputChoice, InputSelect } from '$lib/elements/forms';
|
||||
import { onMount } from 'svelte';
|
||||
import { database } from '../../store';
|
||||
|
||||
export let selectedAttribute: Models.AttributeString;
|
||||
export let data: Partial<Models.AttributeString> = {
|
||||
@@ -31,6 +33,12 @@
|
||||
array: false
|
||||
};
|
||||
|
||||
let isOneWay = true;
|
||||
|
||||
onMount(async () => {
|
||||
console.log($database);
|
||||
});
|
||||
|
||||
$: if (selectedAttribute) {
|
||||
({
|
||||
required: data.required,
|
||||
@@ -44,30 +52,33 @@
|
||||
}
|
||||
</script>
|
||||
|
||||
<InputNumber
|
||||
id="size"
|
||||
label="Size"
|
||||
bind:value={data.size}
|
||||
required
|
||||
readonly={!!selectedAttribute} />
|
||||
<InputText
|
||||
id="default"
|
||||
label="Default value"
|
||||
bind:value={data.default}
|
||||
maxlength={data.size}
|
||||
disabled={data.required || data.array}
|
||||
readonly={!!selectedAttribute} />
|
||||
<InputChoice
|
||||
id="required"
|
||||
label="Required"
|
||||
bind:value={data.required}
|
||||
disabled={!!selectedAttribute || data.array}>
|
||||
Indicate whether this is a required attribute
|
||||
</InputChoice>
|
||||
<InputChoice
|
||||
id="array"
|
||||
label="Array"
|
||||
bind:value={data.array}
|
||||
disabled={!!selectedAttribute || data.required}>
|
||||
Indicate whether this attribute should act as an array
|
||||
</InputChoice>
|
||||
{#if isOneWay}
|
||||
<!-- <InputSelect id="related" label="Size" bind:value={data.size} required /> -->
|
||||
<InputText
|
||||
id="default"
|
||||
label="Default value"
|
||||
bind:value={data.default}
|
||||
maxlength={data.size}
|
||||
disabled={data.required || data.array}
|
||||
readonly={!!selectedAttribute} />
|
||||
<div>
|
||||
<InputText
|
||||
id="key"
|
||||
label="Attribute Key"
|
||||
placeholder="Enter Key"
|
||||
bind:value={data.key}
|
||||
autofocus
|
||||
required />
|
||||
|
||||
<div class="u-flex u-gap-4 u-margin-block-start-8 u-small">
|
||||
<span
|
||||
class="icon-info u-cross-center u-margin-block-start-2 u-line-height-1 u-icon-small"
|
||||
aria-hidden="true" />
|
||||
<span class="text u-line-height-1-5">
|
||||
Allowed characters: alphanumeric, hyphen, non-leading underscore, period
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
{:else}
|
||||
2
|
||||
{/if}
|
||||
|
||||
+21
-10
@@ -23,6 +23,7 @@ export type Option = {
|
||||
data: Partial<Attributes>
|
||||
) => Promise<void>;
|
||||
format?: 'email' | 'ip' | 'url' | 'enum' | 'relationship';
|
||||
icon: string;
|
||||
};
|
||||
|
||||
export const options: Option[] = [
|
||||
@@ -30,66 +31,76 @@ export const options: Option[] = [
|
||||
name: 'String',
|
||||
component: String,
|
||||
type: 'string',
|
||||
func: submitString
|
||||
func: submitString,
|
||||
icon: 'text'
|
||||
},
|
||||
{
|
||||
name: 'Integer',
|
||||
component: Integer,
|
||||
type: 'integer',
|
||||
func: submitInteger
|
||||
func: submitInteger,
|
||||
icon: 'hashtag'
|
||||
},
|
||||
{
|
||||
name: 'Float',
|
||||
component: Float,
|
||||
type: 'double',
|
||||
func: submitFloat
|
||||
func: submitFloat,
|
||||
icon: 'hashtag'
|
||||
},
|
||||
{
|
||||
name: 'Boolean',
|
||||
component: Boolean,
|
||||
type: 'boolean',
|
||||
func: submitBoolean
|
||||
func: submitBoolean,
|
||||
icon: 'toggle'
|
||||
},
|
||||
{
|
||||
name: 'Datetime',
|
||||
component: Datetime,
|
||||
type: 'datetime',
|
||||
func: submitDatetime
|
||||
func: submitDatetime,
|
||||
icon: 'calendar'
|
||||
},
|
||||
{
|
||||
name: 'Email',
|
||||
component: Email,
|
||||
type: 'string',
|
||||
format: 'email',
|
||||
func: submitEmail
|
||||
func: submitEmail,
|
||||
icon: 'mail'
|
||||
},
|
||||
{
|
||||
name: 'IP',
|
||||
component: Ip,
|
||||
type: 'string',
|
||||
format: 'ip',
|
||||
func: submitIp
|
||||
func: submitIp,
|
||||
icon: 'location-marker'
|
||||
},
|
||||
{
|
||||
name: 'URL',
|
||||
component: Url,
|
||||
type: 'string',
|
||||
format: 'url',
|
||||
func: submitUrl
|
||||
func: submitUrl,
|
||||
icon: 'link'
|
||||
},
|
||||
{
|
||||
name: 'Enum',
|
||||
component: Enum,
|
||||
type: 'string',
|
||||
format: 'enum',
|
||||
func: submitEnum
|
||||
func: submitEnum,
|
||||
icon: 'list'
|
||||
},
|
||||
{
|
||||
name: 'Relationship',
|
||||
component: Relationship,
|
||||
type: 'relationship',
|
||||
format: 'relationship',
|
||||
func: submitRelationship
|
||||
func: submitRelationship,
|
||||
icon: 'link'
|
||||
}
|
||||
];
|
||||
|
||||
|
||||
+27
-20
@@ -9,9 +9,10 @@
|
||||
import { base } from '$app/paths';
|
||||
import type { Attributes } from './store';
|
||||
import { Submit, trackEvent, trackError } from '$lib/actions/analytics';
|
||||
import { Pill } from '$lib/elements';
|
||||
|
||||
export let showCreate = false;
|
||||
export let selectedOption = null;
|
||||
export let selectedOption: string = null;
|
||||
|
||||
let key: string = null;
|
||||
let data: Partial<Attributes> = {
|
||||
@@ -62,28 +63,34 @@
|
||||
}
|
||||
</script>
|
||||
|
||||
<Modal size="big" bind:show={showCreate} on:submit={submit}>
|
||||
<svelte:fragment slot="header">Create Attribute</svelte:fragment>
|
||||
<Modal size="big" bind:show={showCreate} on:submit={submit} icon={$option?.icon}>
|
||||
<svelte:fragment slot="header"
|
||||
>{selectedOption}
|
||||
{#if selectedOption === 'Relationship'}
|
||||
<Pill>BETA</Pill>
|
||||
{/if}
|
||||
</svelte:fragment>
|
||||
<FormList>
|
||||
<div>
|
||||
<InputText
|
||||
id="key"
|
||||
label="Attribute Key"
|
||||
placeholder="Enter Key"
|
||||
bind:value={key}
|
||||
autofocus
|
||||
required />
|
||||
{#if selectedOption !== 'Relationship'}
|
||||
<div>
|
||||
<InputText
|
||||
id="key"
|
||||
label="Attribute Key"
|
||||
placeholder="Enter Key"
|
||||
bind:value={key}
|
||||
autofocus
|
||||
required />
|
||||
|
||||
<div class="u-flex u-gap-4 u-margin-block-start-8 u-small">
|
||||
<span
|
||||
class="icon-info u-cross-center u-margin-block-start-2 u-line-height-1 u-icon-small"
|
||||
aria-hidden="true" />
|
||||
<span class="text u-line-height-1-5">
|
||||
Allowed characters: alphanumeric, hyphen, non-leading underscore, period
|
||||
</span>
|
||||
<div class="u-flex u-gap-4 u-margin-block-start-8 u-small">
|
||||
<span
|
||||
class="icon-info u-cross-center u-margin-block-start-2 u-line-height-1 u-icon-small"
|
||||
aria-hidden="true" />
|
||||
<span class="text u-line-height-1-5">
|
||||
Allowed characters: alphanumeric, hyphen, non-leading underscore, period
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/if}
|
||||
{#if selectedOption}
|
||||
<svelte:component
|
||||
this={$option.component}
|
||||
|
||||
+84
-69
@@ -15,6 +15,7 @@
|
||||
import Delete from './deleteIndex.svelte';
|
||||
import Create from './createIndex.svelte';
|
||||
import Overview from './overviewIndex.svelte';
|
||||
import CreateAttribute from '../createAttribute.svelte';
|
||||
import type { Models } from '@aw-labs/appwrite-console';
|
||||
import { invalidate } from '$app/navigation';
|
||||
import { Dependencies } from '$lib/constants';
|
||||
@@ -25,6 +26,7 @@
|
||||
let showCreateIndex = false;
|
||||
let showOverview = false;
|
||||
let showDelete = false;
|
||||
let showCreateAttribute = false;
|
||||
|
||||
const handleDelete = async () => {
|
||||
invalidate(Dependencies.COLLECTION);
|
||||
@@ -43,79 +45,90 @@
|
||||
<span class="text">Create index</span>
|
||||
</Button>
|
||||
</div>
|
||||
{#if $indexes.length}
|
||||
<Table>
|
||||
<TableHeader>
|
||||
<TableCellHead>Key</TableCellHead>
|
||||
<TableCellHead onlyDesktop>Type</TableCellHead>
|
||||
<TableCellHead onlyDesktop>Attributes</TableCellHead>
|
||||
<TableCellHead onlyDesktop>Asc/Desc</TableCellHead>
|
||||
<TableCellHead width={30} />
|
||||
</TableHeader>
|
||||
<TableBody>
|
||||
{#each $indexes as index, i}
|
||||
<TableRow>
|
||||
<TableCell title="Key">
|
||||
<div class="u-flex u-main-space-between">
|
||||
<span class="text u-trim"> {index.key}</span>
|
||||
{#if index.status !== 'available'}
|
||||
<Pill
|
||||
warning={index.status === 'processing'}
|
||||
danger={['deleting', 'stuck', 'failed'].includes(
|
||||
index.status
|
||||
)}>
|
||||
{index.status}
|
||||
</Pill>
|
||||
{/if}
|
||||
</div>
|
||||
</TableCell>
|
||||
<TableCellText title="Type" onlyDesktop>{index.type}</TableCellText>
|
||||
<TableCellText title="Attributes" onlyDesktop>
|
||||
{index.attributes}
|
||||
</TableCellText>
|
||||
<TableCellText title="ASC/DESC" onlyDesktop>
|
||||
{index.orders}
|
||||
</TableCellText>
|
||||
<TableCell showOverflow>
|
||||
<DropList bind:show={showDropdown[i]} placement="bottom-start" noArrow>
|
||||
<button
|
||||
class="button is-only-icon is-text"
|
||||
aria-label="More options"
|
||||
on:click|preventDefault={() => {
|
||||
showDropdown[i] = !showDropdown[i];
|
||||
}}>
|
||||
<span class="icon-dots-horizontal" aria-hidden="true" />
|
||||
</button>
|
||||
<svelte:fragment slot="list">
|
||||
<DropListItem
|
||||
icon="eye"
|
||||
on:click={() => {
|
||||
selectedIndex = index;
|
||||
showOverview = true;
|
||||
}}>Overview</DropListItem>
|
||||
{#if $collection?.attributes?.length}
|
||||
{#if $indexes.length}
|
||||
<Table>
|
||||
<TableHeader>
|
||||
<TableCellHead>Key</TableCellHead>
|
||||
<TableCellHead onlyDesktop>Type</TableCellHead>
|
||||
<TableCellHead onlyDesktop>Attributes</TableCellHead>
|
||||
<TableCellHead onlyDesktop>Asc/Desc</TableCellHead>
|
||||
<TableCellHead width={30} />
|
||||
</TableHeader>
|
||||
<TableBody>
|
||||
{#each $indexes as index, i}
|
||||
<TableRow>
|
||||
<TableCell title="Key">
|
||||
<div class="u-flex u-main-space-between">
|
||||
<span class="text u-trim"> {index.key}</span>
|
||||
{#if index.status !== 'available'}
|
||||
<Pill
|
||||
warning={index.status === 'processing'}
|
||||
danger={['deleting', 'stuck', 'failed'].includes(
|
||||
index.status
|
||||
)}>
|
||||
{index.status}
|
||||
</Pill>
|
||||
{/if}
|
||||
</div>
|
||||
</TableCell>
|
||||
<TableCellText title="Type" onlyDesktop>{index.type}</TableCellText>
|
||||
<TableCellText title="Attributes" onlyDesktop>
|
||||
{index.attributes}
|
||||
</TableCellText>
|
||||
<TableCellText title="ASC/DESC" onlyDesktop>
|
||||
{index.orders}
|
||||
</TableCellText>
|
||||
<TableCell showOverflow>
|
||||
<DropList
|
||||
bind:show={showDropdown[i]}
|
||||
placement="bottom-start"
|
||||
noArrow>
|
||||
<button
|
||||
class="button is-only-icon is-text"
|
||||
aria-label="More options"
|
||||
on:click|preventDefault={() => {
|
||||
showDropdown[i] = !showDropdown[i];
|
||||
}}>
|
||||
<span class="icon-dots-horizontal" aria-hidden="true" />
|
||||
</button>
|
||||
<svelte:fragment slot="list">
|
||||
<DropListItem
|
||||
icon="eye"
|
||||
on:click={() => {
|
||||
selectedIndex = index;
|
||||
showOverview = true;
|
||||
}}>Overview</DropListItem>
|
||||
|
||||
<DropListItem
|
||||
icon="trash"
|
||||
on:click={() => {
|
||||
selectedIndex = index;
|
||||
showDelete = true;
|
||||
}}>Delete</DropListItem>
|
||||
</svelte:fragment>
|
||||
</DropList>
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
{/each}
|
||||
</TableBody>
|
||||
</Table>
|
||||
<div class="u-flex u-margin-block-start-32 u-main-space-between">
|
||||
<p class="text">Total results: {$indexes.length}</p>
|
||||
</div>
|
||||
<DropListItem
|
||||
icon="trash"
|
||||
on:click={() => {
|
||||
selectedIndex = index;
|
||||
showDelete = true;
|
||||
}}>Delete</DropListItem>
|
||||
</svelte:fragment>
|
||||
</DropList>
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
{/each}
|
||||
</TableBody>
|
||||
</Table>
|
||||
<div class="u-flex u-margin-block-start-32 u-main-space-between">
|
||||
<p class="text">Total results: {$indexes.length}</p>
|
||||
</div>
|
||||
{:else}
|
||||
<Empty
|
||||
single
|
||||
href="https://appwrite.io/docs/databases#indexes"
|
||||
target="index"
|
||||
on:click={() => (showCreateIndex = true)} />
|
||||
{/if}
|
||||
{:else}
|
||||
<Empty
|
||||
single
|
||||
href="https://appwrite.io/docs/databases#indexes"
|
||||
target="index"
|
||||
on:click={() => (showCreateIndex = true)} />
|
||||
target="attribute"
|
||||
href="https://appwrite.io/docs/databases#attributes"
|
||||
on:click={() => (showCreateAttribute = true)} />
|
||||
{/if}
|
||||
</Container>
|
||||
|
||||
@@ -125,3 +138,5 @@
|
||||
<Delete bind:showDelete {selectedIndex} on:deleted={handleDelete} />
|
||||
<Overview bind:showOverview {selectedIndex} />
|
||||
{/if}
|
||||
|
||||
<CreateAttribute bind:showCreate={showCreateAttribute} />
|
||||
|
||||
Reference in New Issue
Block a user