add: editor support on mobile.

This commit is contained in:
Darshan
2025-10-19 13:18:46 +05:30
parent 2e7ff2279e
commit e2600d73ba
5 changed files with 122 additions and 66 deletions
@@ -11,6 +11,7 @@
let {
show = $bindable(false),
title,
headerEnd,
closeOnBlur = false,
submit,
cancel,
@@ -51,6 +52,7 @@
| undefined;
children?: Snippet;
footer?: Snippet | null;
headerEnd?: Snippet | null;
} & HTMLAttributes<HTMLDivElement> = $props();
let form: Form;
@@ -90,6 +92,8 @@
{/if}
{/if}
</Layout.Stack>
{@render headerEnd?.()}
</Layout.Stack>
</div>
@@ -8,11 +8,24 @@
let {
children,
noSqlEditor,
sideSheetHeaderAction,
sideSheetOptions = null,
showEditorSideSheet = $bindable(false)
}: {
children: Snippet;
noSqlEditor?: Snippet;
sideSheetHeaderAction?: Snippet;
showEditorSideSheet?: boolean;
sideSheetOptions?: {
sideSheetTitle?: string;
submit?:
| {
text: string;
disabled?: boolean;
onClick?: () => boolean | void | Promise<boolean | void>;
}
| undefined;
};
} = $props();
let spreadsheetWrapper: HTMLDivElement;
@@ -133,12 +146,14 @@
{:else}
<SideSheet
noContentPadding
title="Edit document"
bind:show={showEditorSideSheet}
submit={{
text: 'Update'
}}>
submit={sideSheetOptions?.submit}
title={sideSheetOptions?.sideSheetTitle ?? 'Edit document'}>
{@render noSqlEditor?.()}
{#snippet headerEnd()}
{@render sideSheetHeaderAction?.()}
{/snippet}
</SideSheet>
{/if}
</div>
@@ -73,12 +73,13 @@
data?: JsonValue;
isSaving?: boolean;
loading?: boolean;
onChange?: (newData: JsonValue) => Promise<void> | void;
onChange?: (newData: JsonValue, hasChanged: boolean) => Promise<void> | void;
onSave?: (newData: JsonValue) => Promise<void> | void;
readonly?: boolean;
wrapLines?: boolean;
errorInPlace?: boolean;
ctrlSave?: boolean;
showHeaderActions?: boolean;
}
let {
@@ -91,7 +92,8 @@
readonly = false,
wrapLines = true,
errorInPlace = true,
ctrlSave = false
ctrlSave = false,
showHeaderActions = true
}: Props = $props();
let editorContainer: HTMLDivElement = $state(null);
@@ -794,7 +796,7 @@
}
data = parsed;
onChange?.(parsed);
onChange?.(parsed, hasDataChanged);
lastExpectedContent = dataToString(parsed);
}, DEBOUNCE_DELAY);
}),
@@ -897,65 +899,67 @@
</script>
<div class="editor-container" class:loading>
<div class="editor-header">
{#if loading}
<Skeleton variant="line" height="12px" width="143px" />
{/if}
{#if showHeaderActions}
<div class="editor-header">
{#if loading}
<Skeleton variant="line" height="12px" width="143px" />
{/if}
<Layout.Stack direction="row">
{#if documentId && !loading}
<div class="id-tag-button-wrapper">
<Id value={documentId} tooltipPlacement="top">{truncateId(documentId)}</Id>
<Layout.Stack direction="row">
{#if documentId && !loading}
<div class="id-tag-button-wrapper">
<Id value={documentId} tooltipPlacement="top">{truncateId(documentId)}</Id>
</div>
{/if}
</Layout.Stack>
{#if errorMessage && !$isSmallViewport && !loading}
<div class="editor-header">
<span class="error-message">{errorMessage}</span>
</div>
{/if}
</Layout.Stack>
{#if errorMessage && !$isSmallViewport && !loading}
<div class="editor-header">
<span class="error-message">{errorMessage}</span>
</div>
{/if}
{#if documentId}
<Layout.Stack direction="row" inline gap="s">
<Tooltip placement="top" disabled={!hasDataChanged}>
<Button
icon
secondary
size="xs"
disabled={!hasDataChanged}
class="icon-button"
on:click={handleSave}>
{#if isSaving}
<Spinner size="s" />
{:else}
<Icon icon={IconCheck} size="s" />
{/if}
</Button>
{#if documentId}
<Layout.Stack direction="row" inline gap="s">
<Tooltip placement="top" disabled={!hasDataChanged}>
<Button
icon
secondary
size="xs"
disabled={!hasDataChanged}
class="icon-button"
on:click={handleSave}>
{#if isSaving}
<Spinner size="s" />
{:else}
<Icon icon={IconCheck} size="s" />
{/if}
</Button>
<span slot="tooltip">Save</span>
</Tooltip>
<span slot="tooltip">Save</span>
</Tooltip>
<Tooltip placement="top">
<Button
icon
secondary
size="xs"
class="icon-button"
disabled={loading}
on:click={async () => {
await copy(JSON.stringify(data, null, 2));
tooltipMessage = 'Copied';
setTimeout(() => (tooltipMessage = 'Copy document'), 1000);
}}>
<Icon icon={IconDuplicate} size="s" />
</Button>
<Tooltip placement="top">
<Button
icon
secondary
size="xs"
class="icon-button"
disabled={loading}
on:click={async () => {
await copy(JSON.stringify(data, null, 2));
tooltipMessage = 'Copied';
setTimeout(() => (tooltipMessage = 'Copy document'), 1000);
}}>
<Icon icon={IconDuplicate} size="s" />
</Button>
<span slot="tooltip">{tooltipMessage}</span>
</Tooltip>
</Layout.Stack>
{/if}
</div>
<span slot="tooltip">{tooltipMessage}</span>
</Tooltip>
</Layout.Stack>
{/if}
</div>
{/if}
{#if errorMessage && $isSmallViewport && !loading}
<div class="editor-header mobile" transition:slide={{ duration: 150 }}>
@@ -1053,7 +1057,7 @@
border-bottom: 1px solid var(--border-neutral);
&.mobile {
background: var(--bgcolor-error);
background: var(--bgcolor-error-weak);
}
.error-message {
@@ -25,6 +25,7 @@
import {
IconCalendar,
IconDotsHorizontal,
IconDuplicate,
IconFingerPrint
} from '@appwrite.io/pink-icons-svelte';
import { isSmallViewport, isTabletViewport } from '$lib/stores/viewport';
@@ -411,9 +412,10 @@
await invalidate(Dependencies.DOCUMENTS);
noSqlDocument.update(() => {
return {
isNew: false,
show: false,
document: {}
isNew: false,
document: {},
hasDataChanged: false
};
});
@@ -514,7 +516,15 @@
<SpreadsheetContainer
bind:this={spreadsheetContainer}
bind:showEditorSideSheet={$noSqlDocument.show}>
bind:showEditorSideSheet={$noSqlDocument.show}
sideSheetOptions={{
sideSheetTitle: $noSqlDocument.document?.$id,
submit: {
text: 'Update',
disabled: !$noSqlDocument.hasDataChanged,
onClick: async () => await createOrUpdateDocument($noSqlDocument.document)
}
}}>
{#key $spreadsheetRenderKey}
<Spreadsheet.Root
height="100%"
@@ -596,7 +606,7 @@
id={document?.$id}
virtualItem={item}
select={rowSelection}
isSelected={$noSqlDocument?.document?.['$id'] === document.$id}>
isSelected={$noSqlDocument?.document?.$id === document.$id}>
{#each $collectionColumns as { id: columnId } (columnId)}
<Spreadsheet.Cell {root} isEditable={false} column={columnId}>
{#if columnId === '$id'}
@@ -705,9 +715,30 @@
isNew={$noSqlDocument.isNew}
loading={$noSqlDocument.loading}
bind:data={$noSqlDocument.document}
showHeaderActions={!$isSmallViewport}
onChange={(_, hasDataChanged) => {
$noSqlDocument.hasDataChanged = hasDataChanged;
}}
onSave={async (document) => await createOrUpdateDocument(document)} />
{/snippet}
{#snippet sideSheetHeaderAction()}
<Button.Button
icon
variant="secondary"
size="xs"
on:click={async () => {
await copy(JSON.stringify($noSqlDocument.document, null, 2));
addNotification({
type: 'success',
message: 'Document copied',
timeout: 1250
});
}}>
<Icon icon={IconDuplicate}></Icon>
</Button.Button>
{/snippet}
{#if selectedDocuments.length > 0}
<div class="floating-action-bar">
<FloatingActionBar>
@@ -22,16 +22,18 @@ export const sortState = writable<SortState>({
export const noSqlDocument = writable<{
show: boolean;
document?: Models.Document | object;
document?: Models.Document | (object & { $id?: string });
isNew?: boolean;
loading?: boolean;
documentId?: string /* for loading from a given id */;
hasDataChanged?: boolean;
}>({
show: false,
document: null,
isNew: false,
loading: false,
documentId: null
documentId: null,
hasDataChanged: false
});
export const documentPermissionSheet = writable({