refactor(data-stores): extract shared element_data_store_form component

New element_data_store_form.svelte handles all form fields for creating/
editing a Data Store. Replaces the inline form in the management page modal.

Features vs old inline form:
- Help text on every label (type descriptions, constraint notes, etc.)
- Advanced section (collapsible, hidden by default): Enable, Hide, Priority,
  Sort, Group, Notes — each with hint text
- For ID: editable on new, read-only on edit (with explanation why)
- show_account_field / show_for_fields props for embedded widget use later
- html_edit_mode + show_advanced are internal state, reset via {#key} on parent

Management page: drops html_edit_mode state + Code/Eye/editor imports; uses
{#key editing_obj?.id ?? 'new'} to recreate the form on each record change.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Scott Idem
2026-06-17 15:08:37 -04:00
parent ea0a85fd92
commit ae3bc7e085
2 changed files with 339 additions and 175 deletions

View File

@@ -11,9 +11,7 @@ import {
ChevronLeft,
ChevronRight,
ChevronUp,
Code,
Database,
Eye,
Filter,
LoaderCircle,
Pencil,
@@ -30,8 +28,7 @@ import { ae_loc, ae_api } from '$lib/stores/ae_stores';
import { db_core } from '$lib/ae_core/db_core';
import { ae_util } from '$lib/ae_utils/ae_utils';
import type { ae_DataStore } from '$lib/types/ae_types';
import AE_Comp_Editor_CodeMirror from '$lib/elements/element_editor_codemirror.svelte';
import AE_Comp_Editor_TipTap from '$lib/elements/element_editor_tiptap.svelte';
import AE_DataStore_Form from '$lib/elements/element_data_store_form.svelte';
onMount(() => {
if (!$ae_loc.manager_access) {
@@ -73,7 +70,6 @@ let draft_priority = $state(false);
let draft_sort = $state('');
let draft_group = $state('');
let draft_notes = $state('');
let html_edit_mode = $state<'source' | 'visual'>('source');
let submit_status = $state<'idle' | 'processing' | 'saved' | 'error'>('idle');
// ── Bulk rename state ─────────────────────────────────────────────────────────
@@ -170,7 +166,6 @@ function open_edit(obj: ae_DataStore) {
? obj.json
: JSON.stringify(obj.json ?? '', null, 2)
: (obj.text ?? obj.html ?? '');
html_edit_mode = 'source';
submit_status = 'idle';
show_edit = true;
}
@@ -191,7 +186,6 @@ function open_new() {
draft_sort = '';
draft_group = '';
draft_notes = '';
html_edit_mode = 'source';
submit_status = 'idle';
show_edit = true;
}
@@ -748,174 +742,24 @@ function content_preview(ds: ae_DataStore): string {
class="space-y-4"
onsubmit={(e) => { e.preventDefault(); handle_save(); }}>
<!-- Code + Name -->
<div class="grid grid-cols-1 gap-4 md:grid-cols-2">
<label class="label space-y-1">
<span class="text-xs font-bold opacity-70">Code <span class="text-error-500">*</span></span>
<input
type="text"
class="input font-mono text-sm"
bind:value={draft_code}
required
placeholder="my_data_store_code" />
</label>
<label class="label space-y-1">
<span class="text-xs font-bold opacity-70">Name <span class="text-error-500">*</span></span>
<input
type="text"
class="input"
bind:value={draft_name}
required
placeholder="Human-readable name" />
</label>
</div>
<!-- Type + Account ID -->
<div class="grid grid-cols-1 gap-4 md:grid-cols-2">
<label class="label space-y-1">
<span class="text-xs font-bold opacity-70">Type</span>
<select class="select" bind:value={draft_type}>
<option value="text">Text</option>
<option value="html">HTML</option>
<option value="json">JSON</option>
<option value="md">Markdown</option>
<option value="sql">SQL</option>
</select>
</label>
<label class="label space-y-1">
<span class="text-xs font-bold opacity-70">
Account ID
<span class="font-normal opacity-60">— blank = global</span>
</span>
<input
type="text"
class="input font-mono text-sm"
bind:value={draft_account_id}
placeholder="Leave blank for global (no account)" />
</label>
</div>
<!-- For Type + For ID -->
<div class="grid grid-cols-1 gap-4 md:grid-cols-2">
<label class="label space-y-1">
<span class="text-xs font-bold opacity-70">
For Type
<span class="font-normal opacity-60">— polymorphic parent object type</span>
</span>
<input
type="text"
class="input font-mono text-sm"
bind:value={draft_for_type}
placeholder="event, event_session, person, journal_entry…" />
</label>
<div class="space-y-1">
<span class="text-xs font-bold opacity-70">
For ID
<span class="font-normal opacity-60">— random string ID of parent</span>
</span>
{#if is_new}
<input
type="text"
class="input font-mono text-sm"
bind:value={draft_for_id}
placeholder="random string ID" />
{:else}
<!-- for_id is stored as an integer FK in DB; backend resolves string→int on create
but does not accept the string on PATCH. Shown read-only here. -->
<div class="input bg-surface-200-700-token flex items-center font-mono text-sm opacity-70">
{#if editing_obj?.for_id}
{editing_obj.for_id}
{:else}
<span class="italic opacity-50">not set</span>
{/if}
</div>
<p class="text-[10px] opacity-50">
For ID is set at creation time. To change it, use phpMyAdmin (DB stores the integer FK).
</p>
{/if}
</div>
</div>
<!-- Flags row -->
<div class="border-surface-500/20 flex flex-wrap items-center gap-x-6 gap-y-2 rounded-lg border bg-surface-500/10 px-4 py-3">
<label class="flex cursor-pointer items-center gap-2 text-sm">
<input type="checkbox" class="checkbox" bind:checked={draft_enable} />
Enable
</label>
<label class="flex cursor-pointer items-center gap-2 text-sm">
<input type="checkbox" class="checkbox" bind:checked={draft_hide} />
Hide
</label>
<label class="flex cursor-pointer items-center gap-2 text-sm">
<input type="checkbox" class="checkbox" bind:checked={draft_priority} />
Priority
</label>
<label class="flex items-center gap-2 text-sm">
<span class="opacity-70 shrink-0">Sort:</span>
<input type="text" class="input input-sm w-20 font-mono text-xs" bind:value={draft_sort} placeholder="0" />
</label>
<label class="flex items-center gap-2 text-sm">
<span class="opacity-70 shrink-0">Group:</span>
<input type="text" class="input input-sm w-28 font-mono text-xs" bind:value={draft_group} placeholder="group" />
</label>
</div>
<!-- Notes -->
<label class="label space-y-1">
<span class="text-xs font-bold opacity-70">Notes</span>
<input
type="text"
class="input text-sm"
bind:value={draft_notes}
placeholder="Optional notes" />
</label>
<!-- Content editor -->
<div class="space-y-2">
<div class="flex items-center justify-between">
<span class="text-xs font-bold opacity-70">Content</span>
{#if draft_type === 'html'}
<div class="flex items-center gap-1 rounded bg-surface-500/10 p-0.5">
<button
type="button"
class="flex items-center gap-1 rounded px-2 py-0.5 text-[10px] font-bold uppercase transition-all"
class:bg-primary-500={html_edit_mode === 'source'}
class:text-white={html_edit_mode === 'source'}
class:opacity-50={html_edit_mode !== 'source'}
onclick={() => (html_edit_mode = 'source')}
title="Edit raw HTML source">
<Code size="10" /> Source
</button>
<button
type="button"
class="flex items-center gap-1 rounded px-2 py-0.5 text-[10px] font-bold uppercase transition-all"
class:bg-primary-500={html_edit_mode === 'visual'}
class:text-white={html_edit_mode === 'visual'}
class:opacity-50={html_edit_mode !== 'visual'}
onclick={() => (html_edit_mode = 'visual')}
title="Visual / WYSIWYG editor">
<Eye size="10" /> Visual
</button>
</div>
{/if}
</div>
{#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 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="12"
placeholder="Enter text content…"></textarea>
{/if}
</div>
<!-- {#key} recreates the form on each new record, resetting internal state (html_edit_mode, show_advanced) -->
{#key editing_obj?.id ?? 'new'}
<AE_DataStore_Form
bind:draft_code
bind:draft_name
bind:draft_type
bind:draft_value
bind:draft_account_id
bind:draft_for_type
bind:draft_for_id
bind:draft_enable
bind:draft_hide
bind:draft_priority
bind:draft_sort
bind:draft_group
bind:draft_notes
{is_new} />
{/key}
<!-- Footer actions -->
<div class="flex items-center justify-between border-t border-surface-500/20 pt-4">