Add template form

thrimbletrimmer-solid
ElementalAlchemist 2 weeks ago
parent 375694875a
commit 1e5718f3a6

@ -31,3 +31,17 @@
.templateUpdateErrors { .templateUpdateErrors {
color: #c00; color: #c00;
} }
.newTemplateFormFields {
display: grid;
grid-template-columns: max-content max-content;
gap: 1px;
}
.newTemplateFieldLabelContainer {
display: contents;
}
.newTemplateMidText {
margin: 2px;
}

@ -1,4 +1,4 @@
import { Accessor, Component, createSignal, For, Index, onMount, Show } from "solid-js"; import { Accessor, Component, createSignal, For, Index, onMount, Setter, Show } from "solid-js";
import { GoogleSignIn, googleUser } from "../common/googleAuth"; import { GoogleSignIn, googleUser } from "../common/googleAuth";
import styles from "./ThumbnailManager.module.scss"; import styles from "./ThumbnailManager.module.scss";
@ -19,6 +19,7 @@ class Template {
const ThumbnailManager: Component = () => { const ThumbnailManager: Component = () => {
const [templates, setTemplates] = createSignal<Template[]>([]); const [templates, setTemplates] = createSignal<Template[]>([]);
const [newTemplateErrors, setNewTemplateErrors] = createSignal<string[]>([]);
onMount(async () => { onMount(async () => {
const templateDataResponse = await fetch("/thrimshim/templates"); const templateDataResponse = await fetch("/thrimshim/templates");
@ -46,27 +47,8 @@ const ThumbnailManager: Component = () => {
setTemplates(templateList); setTemplates(templateList);
}); });
return ( const submitHandler = async (origName: string, noImageIsError: boolean, errorList: Accessor<string[]>, setErrorList: Setter<string[]>, event: SubmitEvent): Promise<Template | null> => {
<> setErrorList([]);
<div class={styles.templatesList}>
<div class={`${styles.templatesListRow} ${styles.templatesListHeader}`}>
<div>Name</div>
<div>Description</div>
<div>Attribution</div>
<div>Crop Coordiates</div>
<div>Location Coordinates</div>
<div>Preview</div>
<div></div>
</div>
<Index each={templates()}>
{(template: Accessor<Template>, index: number) => {
const [formErrors, setFormErrors] = createSignal<string[]>([]);
const [displayImagePreview, setDisplayImagePreview] = createSignal(false);
const [editing, setEditing] = createSignal(false);
let imageEditField;
const formSubmit = async (event: SubmitEvent) => {
setFormErrors([]);
const form = event.currentTarget as HTMLFormElement; const form = event.currentTarget as HTMLFormElement;
const formData = new FormData(form); const formData = new FormData(form);
@ -86,7 +68,7 @@ const ThumbnailManager: Component = () => {
const locEndY = parseInt(formData.get("locendy") as string, 10); const locEndY = parseInt(formData.get("locendy") as string, 10);
if (isNaN(cropStartX) || isNaN(cropStartY) || isNaN(cropEndX) || isNaN(cropEndY) || isNaN(locStartX) || isNaN(locStartY) || isNaN(locEndX) || isNaN(locEndY)) { if (isNaN(cropStartX) || isNaN(cropStartY) || isNaN(cropEndX) || isNaN(cropEndY) || isNaN(locStartX) || isNaN(locStartY) || isNaN(locEndX) || isNaN(locEndY)) {
setFormErrors((errors) => { setErrorList((errors) => {
errors.push("All crop and location information must be entered."); errors.push("All crop and location information must be entered.");
return errors; return errors;
}); });
@ -111,33 +93,39 @@ const ThumbnailManager: Component = () => {
const imageDataURL = fileReader.result as string; const imageDataURL = fileReader.result as string;
if (imageDataURL.startsWith("data:image/png;base64,")) { if (imageDataURL.startsWith("data:image/png;base64,")) {
submitData.set("image", imageDataURL.substring(22)); submitData.set("image", imageDataURL.substring(22));
} else if (noImageIsError) {
setErrorList((errors) => {
errors.push("A PNG image must be selected.");
return errors;
});
} }
if (googleUser) { if (googleUser) {
submitData.set("token", googleUser.getAuthResponse().id_token); submitData.set("token", googleUser.getAuthResponse().id_token);
} }
if (formErrors().length > 0) { if (errorList().length > 0) {
return; return null;
} }
const origName = template().name; const submitURL = (origName === "") ? "/thrimshim/add-template" : `/thrimshim/update-template/${encodeURIComponent(origName)}`;
const encodedName = encodeURIComponent(origName);
const submitDataJSON = JSON.stringify(Object.fromEntries(submitData)); const submitDataJSON = JSON.stringify(Object.fromEntries(submitData));
const submitResponse = await fetch(`/thrimshim/update-template/${encodedName}`, { const submitResponse = await fetch(submitURL, {
method: "POST", method: "POST",
body: submitDataJSON, body: submitDataJSON,
headers: { "Content-Type": "application/json" } headers: { "Content-Type": "application/json" }
}); });
if (!submitResponse.ok) { if (!submitResponse.ok) {
const errorText = await submitResponse.text(); const errorText = await submitResponse.text();
setFormErrors((errors) => { setErrorList((errors) => {
errors.push(errorText); errors.push(errorText);
return errors; return errors;
}); });
} }
const newTemplate: Template = { form.reset();
return {
name: name, name: name,
description: description, description: description,
attribution: attribution, attribution: attribution,
@ -146,13 +134,39 @@ const ThumbnailManager: Component = () => {
locationStart: { x: locStartX, y: locStartY }, locationStart: { x: locStartX, y: locStartY },
locationEnd: { x: locEndX, y: locEndY } locationEnd: { x: locEndX, y: locEndY }
}; };
};
return (
<>
<div class={styles.templatesList}>
<div class={`${styles.templatesListRow} ${styles.templatesListHeader}`}>
<div>Name</div>
<div>Description</div>
<div>Attribution</div>
<div>Crop Coordiates</div>
<div>Location Coordinates</div>
<div>Preview</div>
<div></div>
</div>
<Index each={templates()}>
{(template: Accessor<Template>, index: number) => {
const [formErrors, setFormErrors] = createSignal<string[]>([]);
const [displayImagePreview, setDisplayImagePreview] = createSignal(false);
const [editing, setEditing] = createSignal(false);
return (
<form
class={styles.templatesListRow}
onSubmit={async (event) => {
const submitData = await submitHandler(template().name, false, formErrors, setFormErrors, event);
if (submitData) {
setTemplates((templateList) => { setTemplates((templateList) => {
templateList[index] = newTemplate; templateList[index] = submitData;
return templateList; return templateList;
}); });
}; }
return ( }}
<form class={styles.templatesListRow} onSubmit={formSubmit}> >
<Show <Show
when={editing()} when={editing()}
fallback={ fallback={
@ -237,7 +251,7 @@ const ThumbnailManager: Component = () => {
) )
</div> </div>
<div> <div>
<input type="file" name="image" accept="image/png" ref={imageEditField} /> <input type="file" name="image" accept="image/png" />
</div> </div>
<div> <div>
<button type="submit">Submit</button> <button type="submit">Submit</button>
@ -254,6 +268,120 @@ const ThumbnailManager: Component = () => {
}} }}
</Index> </Index>
</div> </div>
<form
onSubmit={async (event) => {
const submitData = await submitHandler("", true, newTemplateErrors, setNewTemplateErrors, event);
if (submitData) {
setTemplates((templateList) => [...templateList, submitData]);
}
}}
>
<h1>Add New Template</h1>
<ul class={styles.templateUpdateErrors}>
<For each={newTemplateErrors()}>
{(error: string, index: Accessor<number>) => <li>{error}</li>}
</For>
</ul>
<div class={styles.newTemplateFormFields}>
<label class={styles.newTemplateFieldLabelContainer}>
<span>Name:</span>
<input type="text" name="name" />
</label>
<label class={styles.newTemplateFieldLabelContainer}>
<span>Image:</span>
<input type="file" name="image" accept="image/png" />
</label>
<label class={styles.newTemplateFieldLabelContainer}>
<span>Description:</span>
<textarea name="description"></textarea>
</label>
<label class={styles.newTemplateFieldLabelContainer}>
<span>Attribution:</span>
<input type="text" name="attribution" />
</label>
<span>Crop:</span>
<span>
<input
type="number"
class={styles.templateCoord}
name="cropstartx"
placeholder="X"
min={0}
step={1}
value={182}
/>
<input
type="number"
class={styles.templateCoord}
name="cropstarty"
placeholder="Y"
min={0}
step={1}
value={0}
/>
<span class={styles.newTemplateMidText}>to</span>
<input
type="number"
class={styles.templateCoord}
name="cropendx"
placeholder="X"
min={0}
step={1}
value={1738}
/>
<input
type="number"
class={styles.templateCoord}
name="cropendy"
placeholder="Y"
min={0}
step={1}
value={824}
/>
</span>
<span class={styles.newTemplateInlineLabel}>Location:</span>
<span>
<input
type="number"
class={styles.templateCoord}
name="locstartx"
placeholder="X"
min={0}
step={1}
value={45}
/>
<input
type="number"
class={styles.templateCoord}
name="locstarty"
placeholder="Y"
min={0}
step={1}
value={45}
/>
<span class={styles.newTemplateMidText}>to</span>
<input
type="number"
class={styles.templateCoord}
name="locendx"
placeholder="X"
min={0}
step={1}
value={1235}
/>
<input
type="number"
class={styles.templateCoord}
name="locendy"
placeholder="Y"
min={0}
step={1}
value={675}
/>
</span>
</div>
<button type="submit">Add Template</button>
</form>
<GoogleSignIn /> <GoogleSignIn />
</> </>
); );

Loading…
Cancel
Save