phonograph/svelte/src/field-details.svelte

212 lines
5.7 KiB
Svelte
Raw Normal View History

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 icon_trash from "../assets/heroicons/20/solid/trash.svg?raw";
2025-09-08 15:56:57 -07:00
import {
type Presentation,
all_presentation_tags,
all_text_input_modes,
} from "./presentation.svelte";
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;
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,
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);
on_presentation_input?.(presentation);
2025-09-08 15:56:57 -07:00
}
function get_empty_presentation(
tag: (typeof all_presentation_tags)[number],
): Presentation {
if (tag === "Dropdown") {
return { t: "Dropdown", c: { allow_custom: false, options: [] } };
}
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");
}
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"
name="table_label"
2025-09-08 15:56:57 -07:00
oninput={on_name_input}
type="text"
/>
</label>
<label class="form-section">
<div class="form-section__label">Present As</div>
2025-09-08 15:56:57 -07:00
<select
class="form-section__input"
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>
{#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"
>
{@html icon_trash}
</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"
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}