Merge branch '8.x' into 'feat-documentsdb'.

This commit is contained in:
Darshan
2026-01-15 16:54:48 +05:30
parent bca84c0e7c
commit d28a5e6795
7 changed files with 246 additions and 102 deletions
+33 -6
View File
@@ -10,8 +10,10 @@
Layout,
Popover,
Selector,
Typography
Typography,
Icon
} from '@appwrite.io/pink-svelte';
import { IconPlus } from '@appwrite.io/pink-icons-svelte';
import { Button } from '$lib/elements/forms';
let {
@@ -21,7 +23,8 @@
allowNoColumns = false,
showAnyway = false,
children,
onPreferencesUpdated = null
onPreferencesUpdated = null,
onCustomOptionClick = null
}: {
columns: Writable<Column[]>;
isCustomTable?: boolean;
@@ -30,6 +33,7 @@
showAnyway?: boolean;
children: Snippet<[toggle: () => void, selectedColumnsNumber: number]>;
onPreferencesUpdated?: () => void;
onCustomOptionClick?: () => void;
} = $props();
let search = $state('');
@@ -115,7 +119,7 @@
cols.map((col) =>
col.exclude
? col
: filteredColumns.some((fc) => fc.id === col.id)
: filteredColumns.some((fc) => fc.id === col.id && !col.disable)
? { ...col, hide: false }
: col
)
@@ -126,7 +130,9 @@
function deselectAll() {
columns.update((cols) => {
const realColumns = cols.filter((col) => !col.exclude && !col.isAction);
const filtered = filteredColumns.filter((col) => !col.exclude && !col.isAction);
const filtered = filteredColumns.filter(
(col) => !col.exclude && !col.isAction && !col.disable
);
if (filtered.length === 0) return cols;
@@ -187,7 +193,7 @@
{@const placement = isNewStyle ? 'bottom-start' : 'bottom-end'}
<Popover let:toggle {placement} padding="none">
{@render children(toggle, selectedColumnsNumber)}
<svelte:fragment slot="tooltip">
<svelte:fragment slot="tooltip" let:toggle>
<div bind:this={containerRef} class="actions-menu-wrapper" style:max-height={maxHeight}>
<ActionMenu.Root>
{#if isNewStyle && showActions}
@@ -231,7 +237,8 @@
on:click={() => toggleColumn(column)}
disabled={allowNoColumns
? false
: visibleRealColumns.length <= 1 && !column.hide}>
: (visibleRealColumns.length <= 1 && !column.hide) ||
column.disable}>
<Layout.Stack direction="row" gap="s">
<Selector.Checkbox
size="s"
@@ -243,6 +250,26 @@
{/if}
{/each}
</Layout.Stack>
{#if onCustomOptionClick && isCustomTable}
<Divider />
<Layout.Stack gap="s" direction="row" style="padding-block-start: 0.175rem">
<Button
text
size="s"
fullWidth
on:click={() => {
toggle();
onCustomOptionClick();
}}>
<Layout.Stack direction="row" gap="s" alignItems="center">
<Icon icon={IconPlus} size="s" />
Custom
</Layout.Stack>
</Button>
</Layout.Stack>
{/if}
</ActionMenu.Root>
</div>
</svelte:fragment>
+4 -1
View File
@@ -22,6 +22,7 @@
allowNoColumns?: boolean;
showAnyway?: boolean;
disableButton?: boolean;
onCustomOptionClick?: () => void;
}
let {
@@ -34,7 +35,8 @@
hideColumns = false,
allowNoColumns = false,
showAnyway = false,
disableButton = false
disableButton = false,
onCustomOptionClick = null
}: Props = $props();
let showCountBadge = $state(false);
@@ -65,6 +67,7 @@
{showAnyway}
{isCustomTable}
{allowNoColumns}
{onCustomOptionClick}
onPreferencesUpdated={updateBadgeState}>
{#snippet children(toggle, selectedColumnsNumber)}
<Button.Button
+1
View File
@@ -47,6 +47,7 @@ export type Column = PinkColumn & {
array?: boolean;
format?: string;
exclude?: boolean;
disable?: boolean;
elements?: string[] | { value: string | number; label: string }[];
encrypt?: boolean;
icon?: ComponentType;
@@ -0,0 +1,76 @@
<script lang="ts">
import { InputTags } from '$lib/elements/forms';
import { symmetricDifference } from '$lib/helpers/array';
import { preferences } from '$lib/stores/preferences';
import { Input, Layout } from '@appwrite.io/pink-svelte';
import { organization } from '$lib/stores/organization';
let {
collectionId,
databaseType,
inModal = false,
onSuccess = null,
onFailure = null
}: {
collectionId: string;
databaseType: string;
inModal?: boolean;
onSuccess?: () => Promise<void> | void;
onFailure?: (error: Error) => Promise<void> | void;
} = $props();
let names = $state<string[]>(getDisplayNames());
const isDisabled = $derived(
!symmetricDifference(names, getDisplayNames()).length || names.length > 5
);
function getDisplayNames() {
const displayNames = preferences.getDisplayNames(collectionId, databaseType) ?? [];
return displayNames.filter((name) => !name.startsWith('$'));
}
export function hasChanged() {
return isDisabled;
}
export async function updateDisplayNames() {
try {
const regularArray = [...names];
await preferences.setDisplayNames(
$organization.$id,
collectionId,
regularArray,
databaseType
);
await onSuccess?.();
// reset with new values!
names = getDisplayNames();
} catch (error) {
await onFailure?.(error);
}
}
$effect(() => {
names = getDisplayNames();
});
</script>
<Layout.Stack>
<Layout.Stack gap="s">
{#key names.length}
<InputTags
bind:tags={names}
id="custom-columns-{collectionId}"
placeholder="Enter document keys"
label={inModal ? null : 'Fields to display'} />
{/key}
<Input.Helper state="default">
ID, createdAt, and updatedAt are always included and cannot be modified
</Input.Helper>
</Layout.Stack>
</Layout.Stack>
@@ -1,9 +1,10 @@
<script lang="ts">
import { hasPageQueries, queries } from '$lib/components/filters';
import ViewSelector from '$lib/components/viewSelector.svelte';
import { Button } from '$lib/elements/forms';
import { Container } from '$lib/layout';
import { preferences } from '$lib/stores/preferences';
import { Icon, Layout, Divider } from '@appwrite.io/pink-svelte';
import { Icon, Layout, Divider, Tooltip } from '@appwrite.io/pink-svelte';
import type { PageProps } from './$types';
import FilePicker from '$lib/components/filePicker.svelte';
import { page } from '$app/state';
@@ -22,15 +23,22 @@
import { EmptySheet, EmptySheetCards } from '$database/(entity)';
import {
isCollectionsCsvImportInProgress,
noSqlDocument
noSqlDocument,
collectionColumns
} from '$database/collection-[collection]/store';
import { canWriteRows } from '$lib/stores/roles';
import SpreadSheet from '$database/collection-[collection]/spreadsheet.svelte';
import { toLocaleDateTime } from '$lib/helpers/date';
import CustomColumnsEditor from '$database/collection-[collection]/(components)/customColumnsEditor.svelte';
import { Modal } from '$lib/components';
const { data }: PageProps = $props();
let showImportCSV = $state(false);
let showCustomColumnsModal = $state(false);
let columnsError: string = $state(null);
let customColumnEditor: CustomColumnsEditor | null = $state(null);
function buildInitDoc() {
const now = new Date().toISOString();
@@ -76,38 +84,67 @@
<Container expanded expandHeightButton style="background: var(--bgcolor-neutral-primary)">
<Layout.Stack direction="column" gap="xl">
<Layout.Stack direction="row" justifyContent="space-between">
<Layout.Stack direction="row" alignItems="center" justifyContent="flex-end">
<Button
secondary
event={Click.DatabaseImportCsv}
on:click={() => (showImportCSV = true)}>
Import CSV
</Button>
{#if !$isSmallViewport}
<Button
secondary
event="create_document"
on:click={() => {
if (!$noSqlDocument.isNew) {
noSqlDocument.create(buildInitDoc());
}
}}>
<Icon icon={IconPlus} slot="start" size="s" />
Create document
</Button>
<Layout.Stack direction="row" gap="s">
<Tooltip>
<div>
<ViewSelector
onlyIcon
ui="new"
hideView
showAnyway
isCustomTable
view={data.view}
columns={collectionColumns}
onCustomOptionClick={() => (showCustomColumnsModal = true)} />
</div>
<svelte:fragment slot="tooltip">Columns</svelte:fragment>
</Tooltip>
</Layout.Stack>
<Layout.Stack
direction="row"
alignItems="center"
justifyContent="flex-end"
style="padding-right: 40px;">
<Layout.Stack
gap="s"
direction="row"
alignItems="center"
justifyContent="flex-end">
<Button
icon
size="s"
secondary
class="small-button-dimensions"
on:click={() => {
$expandTabs = !$expandTabs;
preferences.setKey('entityHeaderExpanded', $expandTabs);
}}>
<Icon icon={!$expandTabs ? IconChevronDown : IconChevronUp} size="s" />
event={Click.DatabaseImportCsv}
on:click={() => (showImportCSV = true)}>
Import CSV
</Button>
{/if}
{#if !$isSmallViewport}
<Button
secondary
event="create_document"
on:click={() => {
if (!$noSqlDocument.isNew) {
noSqlDocument.create(buildInitDoc());
}
}}>
<Icon icon={IconPlus} slot="start" size="s" />
Create document
</Button>
<Button
icon
size="s"
secondary
class="small-button-dimensions"
on:click={() => {
$expandTabs = !$expandTabs;
preferences.setKey('entityHeaderExpanded', $expandTabs);
}}>
<Icon
icon={!$expandTabs ? IconChevronDown : IconChevronUp}
size="s" />
</Button>
{/if}
</Layout.Stack>
</Layout.Stack>
</Layout.Stack>
{#if $isSmallViewport}
@@ -189,6 +226,37 @@
}} />
{/if}
<Modal
title="Custom columns"
bind:error={columnsError}
bind:show={showCustomColumnsModal}
onSubmit={async () => {
await customColumnEditor?.updateDisplayNames();
}}>
<svelte:fragment slot="description">
Add up to 5 document fields to display as columns in the table view for easy identification.
</svelte:fragment>
<CustomColumnsEditor
inModal
bind:this={customColumnEditor}
databaseType={data.database.type}
collectionId={page.params.collection}
onSuccess={() => {
columnsError = null;
showCustomColumnsModal = false;
}}
onFailure={(error) => {
columnsError = error.message;
}} />
<svelte:fragment slot="footer">
<Button size="s" secondary on:click={() => (showCustomColumnsModal = false)}>Cancel</Button>
<Button size="s" submit disabled={customColumnEditor?.hasChanged()}>Update</Button>
</svelte:fragment>
</Modal>
<style>
:global(.small-button-dimensions) {
width: 32px !important;
@@ -3,84 +3,50 @@
import { Submit, trackEvent, trackError } from '$lib/actions/analytics';
import { CardGrid } from '$lib/components';
import { Dependencies } from '$lib/constants';
import { Button, Form, InputTags } from '$lib/elements/forms';
import { symmetricDifference } from '$lib/helpers/array';
import { Button, Form } from '$lib/elements/forms';
import { addNotification } from '$lib/stores/notifications';
import { preferences } from '$lib/stores/preferences';
import { page } from '$app/state';
import { Input, Layout } from '@appwrite.io/pink-svelte';
import { organization } from '$lib/stores/organization';
import { getTerminologies } from '$database/(entity)';
import CustomColumnsEditor from '../(components)/customColumnsEditor.svelte';
const collectionId = page.params.collection;
const { terminology } = getTerminologies();
let names = $state<string[]>(
(preferences.getDisplayNames(collectionId, terminology.type) ?? []).filter(
(name) => !name.startsWith('$')
)
);
async function updateDisplayName() {
try {
// $state makes proxy,
// structuredClone doesn't work
const regularArray = [...names];
await preferences.setDisplayNames(
$organization.$id,
collectionId,
regularArray,
terminology.type
);
await invalidate(Dependencies.TEAM);
addNotification({
message: 'Display names have been updated',
type: 'success'
});
trackEvent(Submit.TableUpdateDisplayNames);
} catch (error) {
addNotification({
message: error.message,
type: 'error'
});
trackError(error, Submit.TableUpdateDisplayNames);
}
}
const isDisabled = $derived(
!symmetricDifference(
names,
(preferences.getDisplayNames(collectionId, terminology.type) ?? []).filter(
(name) => !name.startsWith('$')
)
).length || names.length > 5
);
let customColumnsEditor: CustomColumnsEditor | null = $state(null);
</script>
<Form onSubmit={updateDisplayName}>
<Form
onSubmit={async () => {
await customColumnsEditor?.updateDisplayNames();
}}>
<CardGrid>
<svelte:fragment slot="title">Display name</svelte:fragment>
Add up to 5 document fields to display as columns in the collection view.
<svelte:fragment slot="aside">
<Layout.Stack gap="s">
{#key names.length}
<InputTags
id="display-names"
label="Document keys"
placeholder="Enter document keys"
bind:tags={names} />
{/key}
<Input.Helper state="default">
ID, createdAt, and updatedAt are always included and cannot be modified
</Input.Helper>
</Layout.Stack>
<CustomColumnsEditor
{collectionId}
databaseType={terminology.type}
bind:this={customColumnsEditor}
onSuccess={async () => {
await invalidate(Dependencies.TEAM);
addNotification({
message: 'Display names have been updated',
type: 'success'
});
trackEvent(Submit.CollectionUpdateDisplayNames);
}}
onFailure={(error) => {
addNotification({
message: error.message,
type: 'error'
});
trackError(error, Submit.CollectionUpdateDisplayNames);
}} />
</svelte:fragment>
<svelte:fragment slot="actions">
<Button disabled={isDisabled} submit>Update</Button>
<Button disabled={customColumnsEditor?.hasChanged()} submit>Update</Button>
</svelte:fragment>
</CardGrid>
</Form>
@@ -171,7 +171,7 @@
type: 'dynamic',
icon: IconCode /* fuzzy search based Icon later */,
isEditable: false,
hide: false
hide: !!selectedColumnsToHide?.includes(key)
}));
const staticColumns: Column[] = [
@@ -185,7 +185,8 @@
icon: IconFingerPrint,
isEditable: false,
isPrimary: false,
hide: !!selectedColumnsToHide?.includes('$id')
hide: false,
disable: true
},
...customColumns,
{
@@ -197,7 +198,8 @@
type: 'datetime',
icon: IconCalendar,
isEditable: false,
hide: !!selectedColumnsToHide?.includes('$createdAt')
hide: false,
disable: true
},
{
id: '$updatedAt',
@@ -208,7 +210,8 @@
type: 'datetime',
icon: IconCalendar,
isEditable: false,
hide: !!selectedColumnsToHide?.includes('$updatedAt')
hide: false,
disable: true
},
{
id: 'actions',