2025-09-08 15:56:57 -07:00
|
|
|
<!--
|
|
|
|
|
@component
|
|
|
|
|
UI for inspecting and altering field configuration when creating or editing a
|
|
|
|
|
field. This is typically rendered within a popover component, and within an HTML
|
|
|
|
|
<form> element defining the appropriate action for form submission.
|
|
|
|
|
-->
|
|
|
|
|
|
|
|
|
|
<script lang="ts">
|
|
|
|
|
import {
|
|
|
|
|
type Presentation,
|
|
|
|
|
all_presentation_tags,
|
|
|
|
|
all_text_input_modes,
|
|
|
|
|
} from "./presentation.svelte";
|
|
|
|
|
|
2025-10-21 18:58:09 +00:00
|
|
|
const COLORS: string[] = [
|
|
|
|
|
"Red",
|
|
|
|
|
"Orange",
|
|
|
|
|
"Yellow",
|
|
|
|
|
"Green",
|
|
|
|
|
"Blue",
|
|
|
|
|
"Indigo",
|
|
|
|
|
"Violet",
|
|
|
|
|
] as const;
|
|
|
|
|
|
2025-09-08 15:56:57 -07:00
|
|
|
type Assert<_T extends true> = void;
|
|
|
|
|
|
|
|
|
|
type Props = {
|
|
|
|
|
label_value: string;
|
|
|
|
|
name_input_disabled?: boolean;
|
|
|
|
|
name_value: string;
|
|
|
|
|
on_name_input?(
|
|
|
|
|
ev: Event & { currentTarget: EventTarget & HTMLInputElement },
|
|
|
|
|
): void;
|
2025-10-07 06:23:50 +00:00
|
|
|
on_presentation_input?(presentation: Presentation): void;
|
2025-09-08 15:56:57 -07:00
|
|
|
presentation?: Presentation;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
let {
|
|
|
|
|
presentation = $bindable(get_empty_presentation("Text")),
|
|
|
|
|
name_input_disabled = false,
|
|
|
|
|
name_value = $bindable(),
|
|
|
|
|
label_value = $bindable(),
|
|
|
|
|
on_name_input,
|
2025-10-07 06:23:50 +00:00
|
|
|
on_presentation_input,
|
2025-09-08 15:56:57 -07:00
|
|
|
}: Props = $props();
|
|
|
|
|
|
|
|
|
|
function handle_presentation_tag_change(
|
|
|
|
|
ev: Event & { currentTarget: EventTarget & HTMLSelectElement },
|
|
|
|
|
) {
|
|
|
|
|
const tag = ev.currentTarget
|
|
|
|
|
.value as (typeof all_presentation_tags)[number];
|
|
|
|
|
presentation = get_empty_presentation(tag);
|
2025-10-07 06:23:50 +00:00
|
|
|
on_presentation_input?.(presentation);
|
2025-09-08 15:56:57 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function get_empty_presentation(
|
|
|
|
|
tag: (typeof all_presentation_tags)[number],
|
|
|
|
|
): Presentation {
|
2025-10-07 06:23:50 +00:00
|
|
|
if (tag === "Dropdown") {
|
2025-10-21 18:58:09 +00:00
|
|
|
return { t: "Dropdown", c: { allow_custom: false, options: [] } };
|
2025-10-07 06:23:50 +00:00
|
|
|
}
|
2025-09-08 15:56:57 -07:00
|
|
|
if (tag === "Text") {
|
|
|
|
|
return { t: "Text", c: { input_mode: { t: "SingleLine", c: {} } } };
|
|
|
|
|
}
|
|
|
|
|
if (tag === "Timestamp") {
|
|
|
|
|
return { t: "Timestamp", c: {} };
|
|
|
|
|
}
|
|
|
|
|
if (tag === "Uuid") {
|
|
|
|
|
return { t: "Uuid", c: {} };
|
|
|
|
|
}
|
|
|
|
|
type _ = Assert<typeof tag extends never ? true : false>;
|
|
|
|
|
throw new Error("this should be unreachable");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function handle_text_input_mode_change(
|
|
|
|
|
ev: Event & { currentTarget: EventTarget & HTMLSelectElement },
|
|
|
|
|
) {
|
|
|
|
|
if (presentation.t === "Text") {
|
|
|
|
|
const tag = ev.currentTarget
|
|
|
|
|
.value as (typeof all_text_input_modes)[number];
|
|
|
|
|
if (tag === "SingleLine") {
|
|
|
|
|
presentation.c.input_mode = {
|
|
|
|
|
t: "SingleLine",
|
|
|
|
|
c: {},
|
|
|
|
|
};
|
|
|
|
|
} else if (tag === "MultiLine") {
|
|
|
|
|
presentation.c.input_mode = {
|
|
|
|
|
t: "MultiLine",
|
|
|
|
|
c: {},
|
|
|
|
|
};
|
|
|
|
|
} else {
|
|
|
|
|
type _ = Assert<typeof tag extends never ? true : false>;
|
|
|
|
|
throw new Error("this should be unreachable");
|
|
|
|
|
}
|
2025-10-07 06:23:50 +00:00
|
|
|
on_presentation_input?.(presentation);
|
2025-09-08 15:56:57 -07:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
</script>
|
|
|
|
|
|
|
|
|
|
<h2 class="form-section__heading">Field Details</h2>
|
|
|
|
|
<label class="form-section">
|
|
|
|
|
<div class="form-section__label">SQL-friendly Name</div>
|
|
|
|
|
<input
|
|
|
|
|
bind:value={name_value}
|
|
|
|
|
class="form-section__input form-section__input--text"
|
|
|
|
|
disabled={name_input_disabled}
|
|
|
|
|
name="name"
|
|
|
|
|
type="text"
|
|
|
|
|
/>
|
|
|
|
|
</label>
|
|
|
|
|
<label class="form-section">
|
|
|
|
|
<div class="form-section__label">Human-friendly Label</div>
|
|
|
|
|
<input
|
|
|
|
|
bind:value={label_value}
|
|
|
|
|
class="form-section__input form-section__input--text"
|
2025-10-21 18:58:09 +00:00
|
|
|
name="table_label"
|
2025-09-08 15:56:57 -07:00
|
|
|
oninput={on_name_input}
|
|
|
|
|
type="text"
|
|
|
|
|
/>
|
|
|
|
|
</label>
|
|
|
|
|
<label class="form-section">
|
2025-09-14 16:19:44 -04:00
|
|
|
<div class="form-section__label">Present As</div>
|
2025-09-08 15:56:57 -07:00
|
|
|
<select
|
|
|
|
|
class="form-section__input"
|
2025-09-14 16:19:44 -04:00
|
|
|
name="presentation_tag"
|
2025-09-08 15:56:57 -07:00
|
|
|
onchange={handle_presentation_tag_change}
|
|
|
|
|
value={presentation?.t}
|
|
|
|
|
>
|
|
|
|
|
{#each all_presentation_tags as presentation_tag}
|
|
|
|
|
<option value={presentation_tag}>
|
|
|
|
|
{presentation_tag}
|
|
|
|
|
</option>
|
|
|
|
|
{/each}
|
|
|
|
|
</select>
|
|
|
|
|
</label>
|
2025-10-21 18:58:09 +00:00
|
|
|
{#if presentation?.t === "Dropdown"}
|
|
|
|
|
<div class="form-section">
|
|
|
|
|
<ul class="field-details__dropdown-options-list">
|
|
|
|
|
{#each presentation.c.options as option, i}
|
|
|
|
|
<li class="field-details__dropdown-option">
|
|
|
|
|
<select
|
|
|
|
|
class="field-details__dropdown-option-color"
|
|
|
|
|
name="dropdown_option_colors"
|
|
|
|
|
>
|
|
|
|
|
{#each COLORS as color}
|
|
|
|
|
<option value={color}>{color}</option>
|
|
|
|
|
{/each}
|
|
|
|
|
</select>
|
|
|
|
|
<input
|
|
|
|
|
type="text"
|
|
|
|
|
name="dropdown_option_values"
|
|
|
|
|
class="form-section__input--text"
|
|
|
|
|
value={option.value}
|
|
|
|
|
/>
|
|
|
|
|
<button
|
|
|
|
|
class="button--clear"
|
|
|
|
|
onclick={() => {
|
|
|
|
|
if (presentation?.t !== "Dropdown") {
|
|
|
|
|
console.warn(
|
|
|
|
|
"remove dropdown option onclick() preconditions not met",
|
|
|
|
|
);
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
presentation.c.options = presentation.c.options.filter(
|
|
|
|
|
(_, j) => j !== i,
|
|
|
|
|
);
|
|
|
|
|
}}
|
|
|
|
|
type="button"
|
|
|
|
|
>
|
2025-10-24 18:21:40 +00:00
|
|
|
<i class="ti ti-trash"></i>
|
2025-10-21 18:58:09 +00:00
|
|
|
</button>
|
|
|
|
|
</li>
|
|
|
|
|
{/each}
|
|
|
|
|
</ul>
|
|
|
|
|
<button
|
|
|
|
|
class="button--secondary"
|
|
|
|
|
onclick={() => {
|
|
|
|
|
if (presentation?.t !== "Dropdown") {
|
|
|
|
|
console.warn(
|
|
|
|
|
"remove dropdown option onclick() preconditions not met",
|
|
|
|
|
);
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
presentation.c.options = [
|
|
|
|
|
...presentation.c.options,
|
|
|
|
|
{ color: COLORS[0], value: "" },
|
|
|
|
|
];
|
|
|
|
|
}}
|
|
|
|
|
type="button"
|
|
|
|
|
>
|
|
|
|
|
Add option
|
|
|
|
|
</button>
|
|
|
|
|
</div>
|
|
|
|
|
{:else if presentation?.t === "Text"}
|
2025-09-08 15:56:57 -07:00
|
|
|
<label class="form-section">
|
|
|
|
|
<div class="form-section__label">Input Mode</div>
|
|
|
|
|
<select
|
|
|
|
|
class="form-section__input"
|
2025-09-14 16:19:44 -04:00
|
|
|
name="text_input_mode"
|
2025-09-08 15:56:57 -07:00
|
|
|
onchange={handle_text_input_mode_change}
|
|
|
|
|
value={presentation.c.input_mode.t}
|
|
|
|
|
>
|
|
|
|
|
{#each all_text_input_modes as input_mode}
|
|
|
|
|
<option value={input_mode}>
|
|
|
|
|
{input_mode}
|
|
|
|
|
</option>
|
|
|
|
|
{/each}
|
|
|
|
|
</select>
|
|
|
|
|
</label>
|
|
|
|
|
{/if}
|