fix(data_store): ensure changes are discarded on cancel and close
This commit is contained in:
@@ -72,8 +72,28 @@ let {
|
||||
// Local reactive state
|
||||
let trigger: null | string = $state(null);
|
||||
let draft_value = $state('');
|
||||
let draft_name = $state('');
|
||||
let draft_code = $state('');
|
||||
let draft_type = $state('');
|
||||
let draft_use_account_id = $state(false);
|
||||
let html_edit_mode = $state<'source' | 'visual'>('source');
|
||||
|
||||
// Change detection derived from draft vs current LiveQuery object
|
||||
let has_changes = $derived.by(() => {
|
||||
const entry = $lq__ds_obj as ae_DataStore | null;
|
||||
if (!entry) return true; // Treat new record as having changes
|
||||
|
||||
const current_val = entry.type === 'json'
|
||||
? (typeof entry.json === 'string' ? entry.json : JSON.stringify(entry.json, null, 2))
|
||||
: (entry.text || entry.html || '');
|
||||
|
||||
return draft_value !== current_val ||
|
||||
draft_name !== (entry.name || '') ||
|
||||
draft_code !== (entry.code || '') ||
|
||||
draft_type !== (entry.type || 'text') ||
|
||||
draft_use_account_id !== (!!entry.account_id);
|
||||
});
|
||||
|
||||
// Dexie LiveQuery for data store
|
||||
let lq__ds_obj = $derived(
|
||||
liveQuery(async () => {
|
||||
@@ -110,6 +130,23 @@ let lq__ds_obj = $derived(
|
||||
})
|
||||
);
|
||||
|
||||
/**
|
||||
* reset_drafts: Resets all draft fields to the current object's values.
|
||||
* Called when the live data changes OR when the editor is closed/cancelled.
|
||||
*/
|
||||
function reset_drafts() {
|
||||
const entry = $lq__ds_obj as ae_DataStore | null;
|
||||
if (!entry) return;
|
||||
|
||||
draft_name = entry.name || '';
|
||||
draft_code = entry.code || '';
|
||||
draft_type = entry.type || 'text';
|
||||
draft_use_account_id = !!entry.account_id;
|
||||
draft_value = entry.type === 'json'
|
||||
? (typeof entry.json === 'string' ? entry.json : JSON.stringify(entry.json, null, 2))
|
||||
: (entry.text || entry.html || '');
|
||||
}
|
||||
|
||||
// Sync status and bound props when the live data changes
|
||||
$effect(() => {
|
||||
const entry = $lq__ds_obj as ae_DataStore | null;
|
||||
@@ -120,16 +157,30 @@ $effect(() => {
|
||||
if (ds_type === 'sql') {
|
||||
val_sql = entry.text || entry.html || null;
|
||||
}
|
||||
// Initialize draft_value when not editing
|
||||
// Initialize draft values when not editing
|
||||
if (!show_edit) {
|
||||
draft_value = entry.type === 'json'
|
||||
? (typeof entry.json === 'string' ? entry.json : JSON.stringify(entry.json, null, 2))
|
||||
: (entry.text || entry.html || '');
|
||||
reset_drafts();
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
// Reset draft values when the editor is closed (discard changes)
|
||||
$effect(() => {
|
||||
if (!show_edit) {
|
||||
untrack(() => reset_drafts());
|
||||
}
|
||||
});
|
||||
|
||||
// Reset submit status when opening the editor
|
||||
$effect(() => {
|
||||
if (show_edit) {
|
||||
untrack(() => {
|
||||
$ae_sess.ds.submit_status = 'idle';
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// Context Change Guard
|
||||
$effect(() => {
|
||||
void for_id; void for_type; void ds_code;
|
||||
@@ -204,25 +255,16 @@ async function load_data_store() {
|
||||
}
|
||||
|
||||
async function handle_submit_form(event: Event) {
|
||||
const target = event.target as HTMLFormElement;
|
||||
$ae_sess.ds.submit_status = 'processing';
|
||||
|
||||
const form_data = new FormData(target);
|
||||
const data_store_di = ae_util.extract_prefixed_form_data({
|
||||
prefix: null,
|
||||
form_data,
|
||||
trim_values: true,
|
||||
bool_tf_str: true
|
||||
});
|
||||
|
||||
const data_store_do: key_val = {
|
||||
code: data_store_di.ds_code ?? ds_code,
|
||||
name: data_store_di.ds_name ?? ds_name,
|
||||
type: data_store_di.ds_type ?? ds_type,
|
||||
for_type: data_store_di.ds_for_type ?? null,
|
||||
for_id: data_store_di.ds_for_id ?? null,
|
||||
enable: data_store_di.ds_enable ?? true,
|
||||
account_id: data_store_di.ds_use_account_id ? (data_store_di.ds_account_id ?? $slct.account_id) : null
|
||||
code: draft_code,
|
||||
name: draft_name,
|
||||
type: draft_type,
|
||||
for_type: for_type,
|
||||
for_id: for_id,
|
||||
enable: true,
|
||||
account_id: draft_use_account_id ? ($ae_loc.account_id || $slct.account_id) : null
|
||||
};
|
||||
|
||||
if (data_store_do.type === 'json') {
|
||||
@@ -260,6 +302,12 @@ async function handle_delete() {
|
||||
show_edit = false;
|
||||
}
|
||||
}
|
||||
|
||||
function handle_cancel() {
|
||||
if (has_changes && !confirm('Discard unsaved changes?')) return;
|
||||
show_edit = false;
|
||||
// reset_drafts() will be called by the $effect watching show_edit
|
||||
}
|
||||
</script>
|
||||
|
||||
<div
|
||||
@@ -277,9 +325,10 @@ async function handle_delete() {
|
||||
{/if}
|
||||
|
||||
<Modal
|
||||
title="{$lq__ds_obj.name || 'Unnamed'} - {$lq__ds_obj.code}"
|
||||
title="{draft_name || 'Unnamed'} - {draft_code}"
|
||||
bind:open={show_edit}
|
||||
autoclose={false}
|
||||
outsideclose={!has_changes}
|
||||
size="xl"
|
||||
class="w-full max-w-6xl">
|
||||
<form class="flex flex-col gap-4" onsubmit={(e) => { e.preventDefault(); handle_submit_form(e); }}>
|
||||
@@ -287,17 +336,17 @@ async function handle_delete() {
|
||||
<div class="space-y-2">
|
||||
<label class="label">
|
||||
<span class="text-xs font-bold opacity-70">Code</span>
|
||||
<input type="text" name="ds_code" class="input font-mono" value={$lq__ds_obj.code} readonly={!$ae_loc.manager_access} required />
|
||||
<input type="text" name="ds_code" class="input font-mono" bind:value={draft_code} readonly={!$ae_loc.manager_access} required />
|
||||
</label>
|
||||
<label class="label">
|
||||
<span class="text-xs font-bold opacity-70">Name</span>
|
||||
<input type="text" name="ds_name" class="input" value={$lq__ds_obj.name} required />
|
||||
<input type="text" name="ds_name" class="input" bind:value={draft_name} required />
|
||||
</label>
|
||||
</div>
|
||||
<div class="space-y-2">
|
||||
<label class="label">
|
||||
<span class="text-xs font-bold opacity-70">Type</span>
|
||||
<select name="ds_type" class="select" bind:value={$lq__ds_obj.type}>
|
||||
<select name="ds_type" class="select" bind:value={draft_type}>
|
||||
<option value="text">Text</option>
|
||||
<option value="html">HTML</option>
|
||||
<option value="json">JSON</option>
|
||||
@@ -306,7 +355,7 @@ async function handle_delete() {
|
||||
</select>
|
||||
</label>
|
||||
<div class="flex items-center gap-2 pt-6">
|
||||
<input type="checkbox" name="ds_use_account_id" id="ds_use_account_id" class="checkbox" checked={!!$lq__ds_obj.account_id} />
|
||||
<input type="checkbox" name="ds_use_account_id" id="ds_use_account_id" class="checkbox" bind:checked={draft_use_account_id} />
|
||||
<label for="ds_use_account_id" class="text-xs cursor-pointer">Account Specific</label>
|
||||
</div>
|
||||
</div>
|
||||
@@ -315,7 +364,7 @@ async function handle_delete() {
|
||||
<div class="space-y-2">
|
||||
<div class="flex items-center justify-between">
|
||||
<span class="text-xs font-bold opacity-70">Content</span>
|
||||
{#if $lq__ds_obj.type === 'html'}
|
||||
{#if draft_type === 'html'}
|
||||
<div class="flex items-center gap-1 rounded bg-black/5 p-0.5">
|
||||
<button
|
||||
type="button"
|
||||
@@ -341,14 +390,14 @@ async function handle_delete() {
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
{#if $lq__ds_obj.type === 'html'}
|
||||
{#if draft_type === 'html'}
|
||||
{#if html_edit_mode === 'source'}
|
||||
<AE_Comp_Editor_CodeMirror bind:content={draft_value} placeholder="Enter HTML Source..." />
|
||||
{:else}
|
||||
<AE_Comp_Editor_TipTap bind:content={draft_value} placeholder="Enter HTML content..." />
|
||||
{/if}
|
||||
{:else if $lq__ds_obj.type === 'json' || $lq__ds_obj.type === 'sql' || $lq__ds_obj.type === 'md'}
|
||||
<AE_Comp_Editor_CodeMirror bind:content={draft_value} placeholder="Enter {$lq__ds_obj.type.toUpperCase()} content..." />
|
||||
{:else if draft_type === 'json' || draft_type === 'sql' || draft_type === 'md'}
|
||||
<AE_Comp_Editor_CodeMirror bind:content={draft_value} placeholder="Enter {draft_type.toUpperCase()} content..." />
|
||||
{:else}
|
||||
<textarea bind:value={draft_value} class="textarea font-mono text-sm" rows="15" placeholder="Enter text content..."></textarea>
|
||||
{/if}
|
||||
@@ -366,13 +415,14 @@ async function handle_delete() {
|
||||
<button
|
||||
type="button"
|
||||
class="btn preset-tonal-surface"
|
||||
onclick={() => (show_edit = false)}
|
||||
onclick={handle_cancel}
|
||||
title="Discard changes and close">
|
||||
<X size="14" class="mr-2" /> Cancel
|
||||
</button>
|
||||
<button
|
||||
type="submit"
|
||||
class="btn preset-filled-primary-500"
|
||||
disabled={!has_changes || $ae_sess.ds.submit_status === 'processing'}
|
||||
title="Save changes to this data store">
|
||||
{#if $ae_sess.ds.submit_status === 'processing'}
|
||||
<LoaderCircle size="14" class="mr-2 animate-spin" />
|
||||
|
||||
Reference in New Issue
Block a user