This commit is contained in:
Darshan
2025-07-22 19:11:49 +05:30
parent 40deb42e02
commit d56828ddd9
20 changed files with 362 additions and 194 deletions
+1 -1
View File
@@ -26,7 +26,7 @@
"@appwrite.io/pink-icons": "0.25.0",
"@appwrite.io/pink-icons-svelte": "^2.0.0-RC.1",
"@appwrite.io/pink-legacy": "^1.0.3",
"@appwrite.io/pink-svelte": "https://pkg.pr.new/appwrite/pink/@appwrite.io/pink-svelte@7f8f433",
"@appwrite.io/pink-svelte": "https://pkg.vc/-/@appwrite/@appwrite.io/pink-svelte@8057bf6",
"@popperjs/core": "^2.11.8",
"@sentry/sveltekit": "^8.38.0",
"@stripe/stripe-js": "^3.5.0",
+9 -10
View File
@@ -24,8 +24,8 @@ importers:
specifier: ^1.0.3
version: 1.0.3
'@appwrite.io/pink-svelte':
specifier: https://pkg.pr.new/appwrite/pink/@appwrite.io/pink-svelte@7f8f433
version: https://pkg.pr.new/appwrite/pink/@appwrite.io/pink-svelte@7f8f433(svelte@5.25.3)
specifier: https://pkg.vc/-/@appwrite/@appwrite.io/pink-svelte@8057bf6
version: https://pkg.vc/-/@appwrite/@appwrite.io/pink-svelte@8057bf6(svelte@5.25.3)
'@popperjs/core':
specifier: ^2.11.8
version: 2.11.8
@@ -261,9 +261,8 @@ packages:
resolution: {tarball: https://pkg.pr.new/appwrite-labs/cloud/@appwrite.io/console@8836b0c}
version: 1.9.0
'@appwrite.io/pink-icons-svelte@https://pkg.pr.new/appwrite/pink/@appwrite.io/pink-icons-svelte@7f8f43343777058cd7f5801b32e05a6917918d44':
resolution: {tarball: https://pkg.pr.new/appwrite/pink/@appwrite.io/pink-icons-svelte@7f8f43343777058cd7f5801b32e05a6917918d44}
version: 2.0.0-RC.1
'@appwrite.io/pink-icons-svelte@2.0.0-RC.1':
resolution: {integrity: sha512-iLFlV55hj8mGuAbmxJGenxN5RaZMmVT4GJb9dv/MP1xBAtYibFq7JvBcxm18qV2KU8c31Rntf+Ub4GL7HwqTYg==}
peerDependencies:
svelte: ^4.0.0
@@ -282,8 +281,8 @@ packages:
'@appwrite.io/pink-legacy@1.0.3':
resolution: {integrity: sha512-GGde5fmPhs+s6/3aFeMPc/kKADG/gTFkYQSy6oBN8pK0y0XNCLrZZgBv+EBbdhwdtqVEWXa0X85Mv9w7jcIlwQ==}
'@appwrite.io/pink-svelte@https://pkg.pr.new/appwrite/pink/@appwrite.io/pink-svelte@7f8f433':
resolution: {tarball: https://pkg.pr.new/appwrite/pink/@appwrite.io/pink-svelte@7f8f433}
'@appwrite.io/pink-svelte@https://pkg.vc/-/@appwrite/@appwrite.io/pink-svelte@8057bf6':
resolution: {tarball: https://pkg.vc/-/@appwrite/@appwrite.io/pink-svelte@8057bf6}
version: 2.0.0-RC.2
peerDependencies:
svelte: ^4.0.0
@@ -3663,7 +3662,7 @@ snapshots:
'@appwrite.io/console@https://pkg.pr.new/appwrite-labs/cloud/@appwrite.io/console@8836b0c': {}
'@appwrite.io/pink-icons-svelte@https://pkg.pr.new/appwrite/pink/@appwrite.io/pink-icons-svelte@7f8f43343777058cd7f5801b32e05a6917918d44(svelte@5.25.3)':
'@appwrite.io/pink-icons-svelte@2.0.0-RC.1(svelte@5.25.3)':
dependencies:
svelte: 5.25.3
@@ -3680,9 +3679,9 @@ snapshots:
'@appwrite.io/pink-icons': 1.0.0
the-new-css-reset: 1.11.3
'@appwrite.io/pink-svelte@https://pkg.pr.new/appwrite/pink/@appwrite.io/pink-svelte@7f8f433(svelte@5.25.3)':
'@appwrite.io/pink-svelte@https://pkg.vc/-/@appwrite/@appwrite.io/pink-svelte@8057bf6(svelte@5.25.3)':
dependencies:
'@appwrite.io/pink-icons-svelte': https://pkg.pr.new/appwrite/pink/@appwrite.io/pink-icons-svelte@7f8f43343777058cd7f5801b32e05a6917918d44(svelte@5.25.3)
'@appwrite.io/pink-icons-svelte': 2.0.0-RC.1(svelte@5.25.3)
'@floating-ui/dom': 1.6.13
'@melt-ui/pp': 0.3.2(@melt-ui/svelte@0.86.6(svelte@5.25.3))(svelte@5.25.3)
'@melt-ui/svelte': 0.86.6(svelte@5.25.3)
+115 -15
View File
@@ -4,23 +4,39 @@
import { preferences } from '$lib/stores/preferences';
import { onMount, type Snippet } from 'svelte';
import type { Column } from '$lib/helpers/types';
import { ActionMenu, Layout, Popover, Selector } from '@appwrite.io/pink-svelte';
import { ActionMenu, Divider, Layout, Popover, Selector } from '@appwrite.io/pink-svelte';
import { Button } from '$lib/elements/forms';
let {
columns,
isCustomCollection = false,
ui = 'legacy',
allowNoColumns = false,
children
}: {
columns: Writable<Column[]>;
isCustomCollection?: boolean;
allowNoColumns?: boolean;
ui?: 'legacy' | 'new';
children: Snippet<[toggle: () => void, selectedColumnsNumber: number]>;
} = $props();
let search = $state('');
let filteredColumns = $derived(
$columns
.filter((column) => !column.isAction)
.filter(
(col) =>
!col.exclude &&
(col.title?.toLowerCase().includes(search.toLowerCase()) ||
col.id?.toLowerCase().includes(search.toLowerCase()))
)
);
let maxHeight = $state('none');
let containerRef = $state<HTMLElement>(null);
const collectionId = $derived(page.params.collection);
const isNewStyle = ui === 'new';
const calcMaxHeight = () => {
if (containerRef) {
@@ -78,38 +94,122 @@
function toggleColumn(column: Column) {
columns.update((cols) =>
cols.map((col) => {
if (col.id === column.id) {
column.hide = !column.hide;
}
return col;
})
cols.map((col) => (col.id === column.id ? { ...col, hide: !col.hide } : col))
);
saveColumnPreferences();
}
function selectAll() {
columns.update((cols) =>
cols.map((col) =>
col.exclude
? col
: filteredColumns.some((fc) => fc.id === col.id)
? { ...col, hide: false }
: col
)
);
saveColumnPreferences();
}
function deselectAll() {
columns.update((cols) => {
const realColumns = cols.filter((col) => !col.exclude && !col.isAction);
const filtered = filteredColumns.filter((col) => !col.exclude && !col.isAction);
if (filtered.length === 0) return cols;
const visibleRealColumns = realColumns.filter((col) => !col.hide);
if (!allowNoColumns && visibleRealColumns.length <= 1) {
return cols;
}
const willHideCount = filtered.filter((col) => !col.hide).length;
if (!allowNoColumns && visibleRealColumns.length - willHideCount < 1) {
const [keep] = filtered;
return cols.map((col) => {
if (col.exclude || col.isAction) return col;
if (!filtered.some((fc) => fc.id === col.id)) return col;
if (col.id === keep.id) {
const stillVisible = cols
.filter((c) => !c.exclude && !c.isAction)
.filter((c) =>
c.id === keep.id
? true
: filtered.some((fc) => fc.id === c.id)
? false
: !c.hide
);
if (stillVisible.length === 1 && typeof col.width === 'number') {
return { ...col, hide: false, width: { min: col.width } };
}
return { ...col, hide: false };
}
return { ...col, hide: true };
});
}
return cols.map((col) =>
col.exclude || col.isAction
? col
: filtered.some((fc) => fc.id === col.id)
? { ...col, hide: true }
: col
);
});
saveColumnPreferences();
}
let visibleRealColumns = $derived(
$columns.filter((col) => !col.exclude && !col.isAction && !col.hide)
);
</script>
<svelte:window on:resize={calcMaxHeight} />
{#if $columns?.length}
<Popover let:toggle placement="bottom-end" padding="none">
{@const placement = isNewStyle ? undefined : 'bottom-end'}
<Popover let:toggle {placement} padding="none">
{@render children(toggle, selectedColumnsNumber)}
<svelte:fragment slot="tooltip">
<div style:max-height={maxHeight} style:overflow="scroll" bind:this={containerRef}>
<ActionMenu.Root>
{#each $columns as column}
{#if isNewStyle}
<Layout.Stack gap="m">
<ActionMenu.Item.Input
id="columns"
placeholder="Search"
bind:value={search} />
<Layout.Stack
direction="row"
alignItems="center"
style="padding-block-end: 0.5rem">
<Button icon extraCompact on:click={selectAll}>Select all</Button>
<div style:height="1rem">
<Divider vertical />
</div>
<Button icon extraCompact on:click={deselectAll}
>Deselect all</Button>
</Layout.Stack>
</Layout.Stack>
{/if}
{#each filteredColumns as column}
{#if !column?.exclude}
<ActionMenu.Item.Button
on:click={() => toggleColumn(column)}
disabled={allowNoColumns
? false
: selectedColumnsNumber <= 1 && column.hide !== true}>
: visibleRealColumns.length <= 1 && !column.hide}>
<Layout.Stack direction="row" gap="s">
<Selector.Checkbox
checked={!column.hide}
size="s"
on:click={() => toggleColumn(column)} />
<Selector.Checkbox size="s" checked={!column.hide} />
{column.title}
</Layout.Stack>
</ActionMenu.Item.Button>
+1 -2
View File
@@ -221,8 +221,7 @@
}
.upload-box-content {
min-width: 400px;
max-width: 100vw;
width: 304px;
}
.upload-box-button {
+57 -45
View File
@@ -19,6 +19,7 @@
import { Click, Submit, trackEvent } from '$lib/actions/analytics';
export let query = '[]';
export let onlyIcon = false;
export let columns: Writable<Column[]>;
export let disabled = false;
export let fullWidthMobile = false;
@@ -124,62 +125,73 @@
<Popover let:toggle placement="bottom-start">
<Button
secondary
icon={onlyIcon}
on:click={(event) => {
toggle(event);
trackEvent(Click.FilterApplyClick, { source: analyticsSource });
}}
{disabled}>
<Icon icon={IconFilterLine} slot="start" size="s" />
Filters
{#if applied > 0}
<span class="inline-tag">
{applied}
</span>
<Icon icon={IconFilterLine} slot="start" size={onlyIcon ? 'm' : 's'} />
{#if !onlyIcon}
Filters
{#if applied > 0}
<span class="inline-tag">
{applied}
</span>
{/if}
{/if}
</Button>
<svelte:fragment slot="tooltip">
<div style:width="420px">
<Layout.Stack gap="s">
{#if displayQuickFilters}
<slot name="quick" />
{:else}
<p>Apply filter rules to refine the table view</p>
<Content
bind:columnId={selectedColumn}
bind:operatorKey
bind:value
bind:arrayValues
{columns}
{singleCondition}
on:apply={afterApply}
on:clear={() => (applied = 0)} />
{/if}
<div
class="u-flex u-cross-center u-margin-block-start-16"
class:u-main-end={!quickFilters}
class:u-main-space-between={quickFilters}>
{#if quickFilters}
<Button
text
on:click={() => (displayQuickFilters = !displayQuickFilters)}
class="u-margin-block-end-auto">
{displayQuickFilters ? 'Advanced filters' : 'Quick filters'}
</Button>
<svelte:fragment slot="tooltip" let:showing>
{#if showing}
<div style:width="420px">
<Layout.Stack gap="s">
{#if displayQuickFilters}
<slot name="quick" />
{:else}
<p>Apply filter rules to refine the table view</p>
<!-- this needs to be portalled -->
<Content
bind:columnId={selectedColumn}
bind:operatorKey
bind:value
bind:arrayValues
{columns}
{singleCondition}
on:apply={afterApply}
on:clear={() => (applied = 0)} />
{/if}
<div class="u-flex u-gap-8">
{#if singleCondition}
<Button size="s" text on:click={toggleDropdown}>Cancel</Button>
{:else}
<Button size="s" disabled={applied === 0} text on:click={clearAll}>
Clear all
<div
class="u-flex u-cross-center u-margin-block-start-16"
class:u-main-end={!quickFilters}
class:u-main-space-between={quickFilters}>
{#if quickFilters}
<Button
text
on:click={() => (displayQuickFilters = !displayQuickFilters)}
class="u-margin-block-end-auto">
{displayQuickFilters ? 'Advanced filters' : 'Quick filters'}
</Button>
{/if}
<Button size="s" on:click={apply} disabled={isButtonDisabled}
>Apply</Button>
<div class="u-flex u-gap-8">
{#if singleCondition}
<Button size="s" text on:click={toggleDropdown}>Cancel</Button>
{:else}
<Button
size="s"
disabled={applied === 0}
text
on:click={clearAll}>
Clear all
</Button>
{/if}
<Button size="s" on:click={apply} disabled={isButtonDisabled}
>Apply</Button>
</div>
</div>
</div>
</Layout.Stack>
</div>
</Layout.Stack>
</div>
{/if}
</svelte:fragment>
</Popover>
</div>
+18 -10
View File
@@ -12,11 +12,13 @@
let {
column,
state,
onSort = () => {}
onSort = () => {},
disabled = false
}: {
column: string;
onSort: (query: string[]) => void | Promise<void>;
state: Writable<{ column: string | null; direction: SortDirection }>;
disabled?: boolean;
onSort?: (query: string[]) => void | Promise<void>;
state?: Writable<{ column: string | null; direction: SortDirection }>;
} = $props();
function sort() {
@@ -29,27 +31,27 @@
? 'default'
: 'asc';
state.set({
state?.set({
direction: nextDir,
column: nextDir === 'default' ? null : column
});
if (nextDir === 'default') {
onSort([]);
onSort?.([]);
} else if (nextDir === 'asc') {
onSort([Query.orderAsc(column)]);
onSort?.([Query.orderAsc(column)]);
} else {
onSort([Query.orderDesc(column)]);
onSort?.([Query.orderDesc(column)]);
}
}
</script>
<Button extraCompact on:click={sort} class="hoverable-compact">
<Button extraCompact on:click={sort} class="hoverable-compact" {disabled}>
<Icon
size="s"
icon={$state.column !== column || $state.direction === 'default'
icon={$state?.column !== column || $state?.direction === 'default'
? IconSelector
: $state.direction === 'asc'
: $state?.direction === 'asc'
? IconChevronUp
: IconChevronDown}
color="--fgcolor-neutral-weak" />
@@ -74,5 +76,11 @@
background-color: var(--bgcolor-neutral-tertiary);
border: var(--border-width-s) solid var(--bgcolor-neutral-tertiary);
}
&:disabled {
& :global(i) {
--icon-fill: var(--fgcolor-neutral-secondary) !important;
}
}
}
</style>
+9 -22
View File
@@ -4,26 +4,13 @@
</script>
{#if $uploader?.isOpen}
<div class="upload-box-wrapper">
<Upload.Box
files={$uploader.files.map((file) => {
return {
name: file.name,
size: file.size,
status: file.status
};
})}
on:close={() => uploader.close()} />
</div>
<Upload.Box
files={$uploader.files.map((file) => {
return {
name: file.name,
size: file.size,
status: file.status
};
})}
on:close={() => uploader.close()} />
{/if}
<style lang="scss">
.upload-box-wrapper {
min-width: 400px;
max-width: 100vw;
& :global(.upload-box) {
width: 100%;
}
}
</style>
+5 -2
View File
@@ -7,6 +7,8 @@
import ViewToggle from './viewToggle.svelte';
import ColumnSelector from './columnSelector.svelte';
export let onlyIcon = false;
export let ui: 'legacy' | 'new' = 'legacy';
export let columns: Writable<Column[]>;
export let view: View;
export let isCustomCollection = false;
@@ -16,12 +18,13 @@
</script>
{#if !hideColumns && view === View.Table}
<ColumnSelector {columns} {isCustomCollection} {allowNoColumns}>
<ColumnSelector {columns} {isCustomCollection} {allowNoColumns} {ui}>
{#snippet children(toggle, selectedColumnsNumber)}
<Button.Button
size="s"
icon={onlyIcon}
variant="secondary"
badge={selectedColumnsNumber.toString()}
badge={onlyIcon ? undefined : selectedColumnsNumber.toString()}
on:click={toggle}>
<Icon slot="start" icon={IconViewBoards} />
</Button.Button>
+5
View File
@@ -1,6 +1,7 @@
import { get } from 'svelte/store';
import { user } from '$lib/stores/user';
import { sdk } from '$lib/stores/sdk';
import { page } from '$app/state';
const userPreferences = () => get(user)?.prefs;
@@ -18,6 +19,10 @@ export function updateSidebarState(state: 'closed' | 'open' | 'icons') {
}
export function getSidebarState(): 'open' | 'icons' {
// for spreadsheet
const isInDatabases = page.route.id?.includes('database-[database]');
if (isInDatabases) return 'icons';
const currentPrefs = userPreferences();
if (currentPrefs && currentPrefs.sidebarState) {
if (currentPrefs.sidebarState === 'open' || currentPrefs.sidebarState === 'icons') {
+2
View File
@@ -26,6 +26,8 @@
margin-block: var(--base-32);
&.expanded {
max-width: unset;
margin-inline: 2.75rem;
padding-inline-end: 0 !important;
margin-block: var(--base-8) !important;
}
+14 -3
View File
@@ -3,6 +3,7 @@
export let size: 'small' | 'medium' | 'large' | 'xl' = null;
export let blocksize = '152px';
export let expanded: boolean = true;
$: style = size
? `--p-container-max-size: var(--container-max-size, var(--container-size-${size}))`
@@ -10,15 +11,17 @@
</script>
<div class="top-cover-console" style:block-size={blocksize}>
<div class="cover-container" {style}>
<div class="cover-container" {style} class:expanded>
<Layout.Stack direction="row" alignItems="baseline">
<slot name="header" />
</Layout.Stack>
<slot />
<div class:expanded-slot={expanded}>
<slot />
</div>
</div>
</div>
<style>
<style lang="scss">
.top-cover-console {
container-type: inline-size;
padding-block-start: var(--base-32);
@@ -55,5 +58,13 @@
margin-inline: auto;
max-width: 1200px;
}
&.expanded {
margin-inline: 1rem !important;
}
}
.expanded-slot {
margin-inline: 3rem !important;
}
</style>
+2 -3
View File
@@ -1,5 +1,5 @@
<script lang="ts">
import { isSmallViewport, isTabletViewport } from '$lib/stores/viewport';
import { isSmallViewport } from '$lib/stores/viewport';
import { IconChevronLeft } from '@appwrite.io/pink-icons-svelte';
import { Typography, Button, Icon, Layout } from '@appwrite.io/pink-svelte';
@@ -8,8 +8,7 @@
<Layout.Stack justifyContent="center" alignItems="center" direction="row" gap="xs" inline>
{#if href}
<span
style={$isTabletViewport ? 'position: relative' : 'position: absolute; left: -2.75rem'}>
<span style:position="relative">
<Button.Anchor size="s" icon variant="text" {href} aria-label="page back">
<Icon icon={IconChevronLeft} />
</Button.Anchor>
@@ -3,6 +3,7 @@ import { Dependencies } from '$lib/constants';
import type { LayoutLoad } from './$types';
import Breadcrumbs from './breadcrumbs.svelte';
import Header from './header.svelte';
import SubNavigation from './subNavigation.svelte';
export const load: LayoutLoad = async ({ params, depends }) => {
depends(Dependencies.DATABASE);
@@ -10,6 +11,7 @@ export const load: LayoutLoad = async ({ params, depends }) => {
return {
header: Header,
breadcrumbs: Breadcrumbs,
subNavigation: SubNavigation,
database: await sdk.forProject(params.region, params.project).databases.get(params.database)
};
};
@@ -3,13 +3,12 @@ import { Dependencies } from '$lib/constants';
import type { LayoutLoad } from './$types';
import Breadcrumbs from './breadcrumbs.svelte';
import Header from './header.svelte';
import SubNavigation from './subNavigation.svelte';
import { Query } from '@appwrite.io/console';
export const load: LayoutLoad = async ({ params, depends }) => {
depends(Dependencies.COLLECTION);
const [collection, allCollections] = await Promise.all([
const [collection, collections] = await Promise.all([
sdk
.forProject(params.region, params.project)
.databases.getCollection(params.database, params.collection),
@@ -21,8 +20,7 @@ export const load: LayoutLoad = async ({ params, depends }) => {
return {
header: Header,
breadcrumbs: Breadcrumbs,
subNavigation: SubNavigation,
collection,
allCollections
collections
};
};
@@ -18,7 +18,7 @@
import { addNotification } from '$lib/stores/notifications';
import { Click, Submit, trackError, trackEvent } from '$lib/actions/analytics';
import { isSmallViewport } from '$lib/stores/viewport';
import { IconPlus } from '@appwrite.io/pink-icons-svelte';
import { IconPlus, IconSearch } from '@appwrite.io/pink-icons-svelte';
import type { Models } from '@appwrite.io/console';
import EmptySheet from './layout/emptySheet.svelte';
import CreateRecord from './createRecord.svelte';
@@ -83,17 +83,28 @@
<Container expanded style="background: var(--bgcolor-neutral-primary)">
<Layout.Stack direction="column" gap="xl">
<Layout.Stack direction="row" justifyContent="space-between">
<Filters
query={data.query}
columns={filterColumns}
disabled={!(hasAttributes && hasValidAttributes)}
analyticsSource="database_documents" />
<Layout.Stack direction="row" alignItems="center" justifyContent="flex-end">
<Layout.Stack direction="row" gap="s">
<ViewSelector
onlyIcon
ui="new"
view={data.view}
columns={filterColumns}
{columns}
hideView
isCustomCollection />
<Filters
onlyIcon
query={data.query}
columns={filterColumns}
disabled={!(hasAttributes && hasValidAttributes)}
analyticsSource="database_documents" />
<!-- TODO: not yet implemented -->
<Button icon secondary>
<Icon icon={IconSearch} slot="start" />
</Button>
</Layout.Stack>
<Layout.Stack direction="row" alignItems="center" justifyContent="flex-end">
<Button
secondary
event={Click.DatabaseImportCsv}
@@ -103,45 +103,47 @@
{#if $createDocument}
<div class="sheet-container">
<!-- TODO: add a ID badge-->
<SideSheet
spaced
title={`${existingData ? 'Duplicate' : '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-->
<SideSheet
spaced
title={`${existingData ? 'Duplicate' : '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} />
<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 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>
</Layout.Stack>
</SideSheet>
</div>
{/if}
</SideSheet>
</div>
{/if}
@@ -60,7 +60,7 @@
</script>
<div style:margin-top={$isTabletViewport ? '48px' : 0}>
<Cover>
<Cover expanded>
<svelte:fragment slot="header">
<CoverTitle
href={`${base}/project-${page.params.region}-${page.params.project}/databases/database-${databaseId}`}>
@@ -10,6 +10,7 @@
import type { Column } from '$lib/helpers/types';
import { IconCalendar, IconFingerPrint, IconPlus } from '@appwrite.io/pink-icons-svelte';
import { isSmallViewport } from '$lib/stores/viewport';
import { SortButton } from '$lib/components';
type Mode = 'records' | 'columns' | 'indexes';
@@ -37,14 +38,14 @@
id: '$id',
title: 'ID',
type: 'string',
width: 200,
width: 180,
icon: IconFingerPrint
},
...middle,
{
id: 'actions',
title: '',
width: 555,
width: 555 * 1.5,
draggable: false,
type: 'string',
icon: IconPlus
@@ -52,9 +53,6 @@
{
id: 'empty',
title: '',
width: 40,
isAction: true,
draggable: false,
type: 'string'
}
];
@@ -66,7 +64,7 @@
id: '$createdAt',
title: 'Created',
type: 'datetime',
width: { min: 180 },
width: 180,
draggable: false,
icon: IconCalendar
},
@@ -74,7 +72,7 @@
id: '$updatedAt',
title: 'Updated',
type: 'datetime',
width: { min: 180 },
width: 180,
draggable: false,
icon: IconCalendar
}
@@ -84,7 +82,7 @@
id: 'indexed',
title: 'Indexed',
type: 'boolean',
width: { min: 180 },
width: 180,
draggable: false,
isAction: false
},
@@ -92,7 +90,7 @@
id: 'default',
title: 'Default',
type: 'string',
width: { min: 180 },
width: 180,
draggable: false,
isAction: false
}
@@ -102,7 +100,7 @@
id: 'type',
title: 'Type',
type: 'string',
width: { min: 150 },
width: 180,
draggable: false,
isAction: false
},
@@ -110,7 +108,7 @@
id: 'attributes',
title: 'Columns',
type: 'string',
width: { min: 200 },
width: 180,
draggable: false,
isAction: false
}
@@ -133,8 +131,17 @@
<Button.Button icon variant="extra-compact">
<Icon icon={IconPlus} color="--fgcolor-neutral-primary" />
</Button.Button>
{:else}
{:else if column.id === 'actions' || column.id === 'empty'}
{column.title}
{:else}
<Layout.Stack
gap="xs"
direction="row"
alignItems="center"
alignContent="center">
{column.title}
<SortButton disabled column={column.id} />
</Layout.Stack>
{/if}
</Spreadsheet.Header.Cell>
{/each}
@@ -194,6 +201,11 @@
& :global(.spreadsheet-container) {
overflow-x: hidden;
overflow-y: hidden;
// hides the last columns left border, as per design
& :global([role='rowheader'] [role='cell']:nth-last-child(2) [role='presentation']) {
display: none;
}
}
& :global([data-select='true']) {
@@ -38,7 +38,7 @@
].filter((tab) => !tab.disabled);
</script>
<Cover>
<Cover expanded>
<svelte:fragment slot="header">
<CoverTitle href={`${base}/project-${page.params.region}-${projectId}/databases`}>
{$database.name}
@@ -1,10 +1,10 @@
<script lang="ts">
import { base } from '$app/paths';
import { page } from '$app/state';
import { showCreate } from '../store';
import { showCreate } from './store';
import type { PageData } from './$types';
import { showSubNavigation } from '$lib/stores/layout';
import { Icon, Sidebar, Navbar, Layout, Link, ActionMenu } from '@appwrite.io/pink-svelte';
import { Icon, Sidebar, Navbar, Layout, Link } from '@appwrite.io/pink-svelte';
import {
IconChevronDown,
IconDatabase,
@@ -13,6 +13,8 @@
} from '@appwrite.io/pink-icons-svelte';
import { isTabletViewport } from '$lib/stores/viewport';
import { BottomSheet } from '$lib/components';
import Button from '$lib/elements/forms/button.svelte';
import type { Models } from '@appwrite.io/console';
let data = $derived(page.data) as PageData;
let region = $derived(page.params.region);
@@ -20,12 +22,14 @@
let databaseId = $derived(page.params.database);
let collectionId = $derived(page.params.collection);
const collections = $derived(data.collections);
const sortedCollections = $derived.by(() =>
data?.allCollections?.collections?.slice().sort((a, b) => a.name.localeCompare(b.name))
collections?.collections?.slice().sort((a, b) => a.name.localeCompare(b.name))
);
const selectedCollection = $derived.by(() =>
sortedCollections?.find((collection) => collection.$id === collectionId)
sortedCollections?.find((collection: Models.Collection) => collection.$id === collectionId)
);
let openBottomSheet = $state(false);
@@ -48,7 +52,7 @@
{data.database?.name}
</a>
<div class="collection-content">
{#if data?.allCollections?.total}
{#if collections?.total}
<ul class="drop-list u-margin-inline-start-8 u-margin-block-start-8">
{#each sortedCollections as collection}
{@const href = `${base}/project-${region}-${project}/databases/database-${databaseId}/collection-${collection.$id}`}
@@ -69,18 +73,32 @@
</li>
{/each}
</ul>
{:else}
<Layout.Stack gap="m">
<Layout.Stack
gap="m"
direction="row"
class="u-margin-inline-start-8 u-margin-block-start-8">
<div style="border-left: 1px solid var(--border-neutral, #ededf0)">
</div>
No tables yet
</Layout.Stack>
</Layout.Stack>
{/if}
<Layout.Stack gap="m" alignItems="center" direction="row">
<Icon icon={IconPlus} size="s" />
<Button
compact
on:click={() => {
$showCreate = true;
$showSubNavigation = false;
}}>
Create collection
</Button>
</Layout.Stack>
</div>
<ActionMenu.Root noPadding width="167">
<ActionMenu.Item.Button
on:click={() => {
$showCreate = true;
$showSubNavigation = false;
}}
leadingIcon={IconPlus}>
Create collection
</ActionMenu.Item.Button>
</ActionMenu.Root>
</section>
</Sidebar.Base>
{:else}