From 3f28ef5a4b89043d2e59bc01297e955616f2465b Mon Sep 17 00:00:00 2001 From: ElementalAlchemist Date: Wed, 6 Nov 2024 23:39:10 -0600 Subject: [PATCH] Allow editing thumbnail data --- thrimbletrimmer/scripts/thumbnails.js | 285 ++++++++++++++++++++++++- thrimbletrimmer/styles/thumbnails.css | 8 +- thrimbletrimmer/thumbnail_manager.html | 17 +- 3 files changed, 291 insertions(+), 19 deletions(-) diff --git a/thrimbletrimmer/scripts/thumbnails.js b/thrimbletrimmer/scripts/thumbnails.js index 99ff05b..3a4e00d 100644 --- a/thrimbletrimmer/scripts/thumbnails.js +++ b/thrimbletrimmer/scripts/thumbnails.js @@ -126,35 +126,302 @@ window.addEventListener("DOMContentLoaded", async (event) => { } }); -function addTemplate(index, template) { +function generateTemplateDOM(index, template) { const { name, description, attribution, crop, location } = template; + const editForm = document.createElement("form"); + editForm.id = `template-data-edit-form-${index}`; + const nameCell = document.createElement("td"); - nameCell.innerText = name; + const nameReadCell = document.createElement("div"); + nameReadCell.classList.add("template-data-view"); + nameReadCell.innerText = name; + const nameEditCell = document.createElement("div"); + nameEditCell.classList.add("template-data-edit", "hidden"); + const nameEditField = document.createElement("input"); + nameEditField.type = "text"; + nameEditField.name = "name"; + nameEditField.value = name; + nameEditField.form = editForm.id; + nameEditCell.appendChild(nameEditField); + nameCell.appendChild(nameReadCell); + nameCell.appendChild(nameEditCell); + const descriptionCell = document.createElement("td"); - descriptionCell.innerText = description; + const descriptionReadCell = document.createElement("div"); + descriptionReadCell.classList.add("template-data-view"); + descriptionReadCell.innerText = description; + const descriptionEditCell = document.createElement("div"); + descriptionEditCell.classList.add("template-data-edit", "hidden"); + const descriptionEditField = document.createElement("textarea"); + descriptionEditField.name = "description"; + descriptionEditField.value = description; + descriptionEditField.form = editForm.id; + descriptionEditCell.appendChild(descriptionEditField); + descriptionCell.appendChild(descriptionReadCell); + descriptionCell.appendChild(descriptionEditCell); + const attributionCell = document.createElement("td"); - attributionCell.innerText = attribution; + const attributionReadCell = document.createElement("div"); + attributionReadCell.classList.add("template-data-view"); + attributionReadCell.innerText = attribution; + const attributionEditCell = document.createElement("div"); + attributionEditCell.classList.add("template-data-edit", "hidden"); + const attributionEditField = document.createElement("input"); + attributionEditField.type = "text"; + attributionEditField.name = "attribution"; + attributionEditField.value = attribution; + attributionEditField.form = editForm.id; + attributionEditCell.appendChild(attributionEditField); + attributionCell.appendChild(attributionReadCell); + attributionCell.appendChild(attributionEditCell); + const cropCell = document.createElement("td"); - cropCell.innerText = `(${crop[0]}, ${crop[1]}) to (${crop[2]}, ${crop[3]})`; + const cropReadCell = document.createElement("div"); + cropReadCell.classList.add("template-data-view"); + cropReadCell.innerText = `(${crop[0]}, ${crop[1]}) to (${crop[2]}, ${crop[3]})`; + const cropEditCell = document.createElement("div"); + cropEditCell.classList.add("template-data-edit", "hidden"); + + const cropXStartField = document.createElement("input"); + cropXStartField.name = "cropxstart"; + setCoordNumberFieldProps(cropXStartField, "X"); + cropXStartField.value = crop[0]; + cropXStartField.form = editForm.id; + const cropYStartField = document.createElement("input"); + cropYStartField.name = "cropystart"; + setCoordNumberFieldProps(cropYStartField, "Y"); + cropYStartField.value = crop[1]; + cropYStartField.form = editForm.id; + const cropXEndField = document.createElement("input"); + cropXEndField.name = "cropxend"; + setCoordNumberFieldProps(cropXEndField, "X"); + cropXEndField.value = crop[2]; + cropXEndField.form = editForm.id; + const cropYEndField = document.createElement("input"); + cropYEndField.name = "cropyend"; + setCoordNumberFieldProps(cropYEndField, "Y"); + cropYEndField.value = crop[3]; + cropYEndField.form = editForm.id; + + cropEditCell.appendChild(document.createTextNode("(")); + cropEditCell.appendChild(cropXStartField); + cropEditCell.appendChild(document.createTextNode(", ")); + cropEditCell.appendChild(cropYStartField); + cropEditCell.appendChild(document.createTextNode(") to (")); + cropEditCell.appendChild(cropXEndField); + cropEditCell.appendChild(document.createTextNode(", ")); + cropEditCell.appendChild(cropYEndField); + cropEditCell.appendChild(document.createTextNode(")")); + + cropCell.appendChild(cropReadCell); + cropCell.appendChild(cropEditCell); + const locationCell = document.createElement("td"); - locationCell.innerText = `(${location[0]}, ${location[1]}) to (${location[2]}, ${location[3]})`; + const locationReadCell = document.createElement("div"); + locationReadCell.classList.add("template-data-view"); + locationReadCell.innerText = `(${location[0]}, ${location[1]}) to (${location[2]}, ${location[3]})`; + const locationEditCell = document.createElement("div"); + locationEditCell.classList.add("template-data-edit", "hidden"); + + const locationXStartField = document.createElement("input"); + locationXStartField.name = "locxstart"; + setCoordNumberFieldProps(locationXStartField, "X"); + locationXStartField.value = location[0]; + locationXStartField.form = editForm.id; + const locationYStartField = document.createElement("input"); + locationYStartField.name = "locystart"; + setCoordNumberFieldProps(locationYStartField, "Y"); + locationYStartField.value = location[1]; + locationYStartField.form = editForm.id; + const locationXEndField = document.createElement("input"); + locationXEndField.name = "locxend"; + setCoordNumberFieldProps(locationXEndField, "X"); + locationXEndField.value = location[2]; + locationXEndField.form = editForm.id; + const locationYEndField = document.createElement("input"); + locationYEndField.name = "locyend"; + setCoordNumberFieldProps(locationYEndField, "Y"); + locationYEndField.value = location[3]; + locationYEndField.form = editForm.id; + + locationEditCell.appendChild(document.createTextNode("(")); + locationEditCell.appendChild(locationXStartField); + locationEditCell.appendChild(document.createTextNode(", ")); + locationEditCell.appendChild(locationYStartField); + locationEditCell.appendChild(document.createTextNode(") to (")); + locationEditCell.appendChild(locationXEndField); + locationEditCell.appendChild(document.createTextNode(", ")); + locationEditCell.appendChild(locationYEndField); + locationEditCell.appendChild(document.createTextNode(")")); + + locationCell.appendChild(locationReadCell); + locationCell.appendChild(locationEditCell); + const previewCell = document.createElement("td"); + const previewReadCell = document.createElement("div"); + previewReadCell.id = `template-list-preview-${index}`; + previewReadCell.classList.add("template-data-view"); const previewLink = document.createElement("a"); previewLink.href = `javascript:showPreview(${index})`; previewLink.innerText = "Preview"; - previewCell.appendChild(previewLink); - previewCell.id = `template-list-preview-${index}`; + previewReadCell.appendChild(previewLink); + const previewEditCell = document.createElement("div"); + previewEditCell.classList.add("template-data-edit", "hidden"); + const imageEditField = document.createElement("input"); + imageEditField.name = "image"; + imageEditField.type = "file"; + imageEditField.accept = "image/png"; + imageEditField.form = editForm.id; + previewEditCell.appendChild(imageEditField); + previewCell.appendChild(previewReadCell); + previewCell.appendChild(previewEditCell); + + const editCell = document.createElement("td"); + const editReadCell = document.createElement("div"); + editReadCell.classList.add("template-data-view"); + const switchToEditButton = document.createElement("button"); + switchToEditButton.type = "button"; + switchToEditButton.innerText = "Edit" + editReadCell.appendChild(switchToEditButton); + const editEditCell = document.createElement("div"); + editEditCell.classList.add("template-data-edit", "hidden"); + const editSubmitButton = document.createElement("button"); + editSubmitButton.type = "submit"; + editSubmitButton.innerText = "Submit"; + const editErrors = document.createElement("ul"); + editErrors.id = `template-data-edit-errors-${index}`; + editErrors.classList.add("template-data-edit-errors"); + editForm.appendChild(editSubmitButton); + editForm.appendChild(editErrors); + editEditCell.appendChild(editForm); + editCell.appendChild(editReadCell); + editCell.appendChild(editEditCell); const templateRow = document.createElement("tr"); + templateRow.id = `template-list-data-${index}`; templateRow.appendChild(nameCell); templateRow.appendChild(descriptionCell); templateRow.appendChild(attributionCell); templateRow.appendChild(cropCell); templateRow.appendChild(locationCell); templateRow.appendChild(previewCell); + templateRow.appendChild(editCell); + + switchToEditButton.addEventListener("click", (event) => { + for (const element of templateRow.getElementsByClassName("template-data-view")) { + element.classList.add("hidden"); + } + for (const element of templateRow.getElementsByClassName("template-data-edit")) { + element.classList.remove("hidden"); + } + }); + + templateRow.addEventListener("submit", async (event) => { + event.preventDefault(); + + editErrors.innerHTML = ""; + + const name = nameEditField.value; + + const description = descriptionEditField.value; + const attribution = attributionEditField.value; + + const cropXStart = parseInt(cropXStartField.value, 10); + const cropYStart = parseInt(cropYStartField.value, 10); + const cropXEnd = parseInt(cropXEndField.value, 10); + const cropYEnd = parseInt(cropYEndField.value, 10); + const locXStart = parseInt(locationXStartField.value, 10); + const locYStart = parseInt(locationYStartField.value, 10); + const locXEnd = parseInt(locationXEndField.value, 10); + const locYEnd = parseInt(locationYEndField.value, 10); + + if ( + isNaN(cropXStart) || + isNaN(cropYStart) || + isNaN(cropXEnd) || + isNaN(cropYEnd) || + isNaN(locXStart) || + isNaN(locXEnd) || + isNaN(locYStart) || + isNaN(locYEnd) + ) { + const parseNumbersError = document.createElement("li"); + parseNumbersError.innerText = "All crop and location information must be entered"; + editErrors.appendChild(parseNumbersError); + } + + const submitData = { + name: name, + description: description, + attribution: attribution, + crop: [cropXStart, cropYStart, cropXEnd, cropYEnd], + location: [locXStart, locYStart, locXEnd, locYEnd] + }; + + const imageFiles = imageEditField.files; + if (imageFiles.length > 0) { + const fileReader = new FileReader(); + const fileReaderCompletePromise = new Promise((resolve, reject) => { + fileReader.addEventListener("loadend", (event) => resolve()); + }); + fileReader.readAsDataURL(imageFile); + await fileReaderCompletePromise; + + const imageDataURL = fileReader.result; + if (imageDataURL.startsWith("data:image/png;base64,")) { + submitData.image = imageDataURL.substring(22); + } else { + const imageError = document.createElement("li"); + imageError.innerText = "Failed to process image as PNG"; + editErrors.appendChild(imageError); + } + } + if (googleUser) { + submitData.token = googleUser.getAuthResponse().id_token; + } + + if (editErrors.hasChildNodes()) { + return; + } + + const origName = templateData[index].name; + const encodedName = encodeURIComponent(origName); + const submitResponse = await fetch(`/thrimshim/update-template/${encodedName}`, { method: "POST", body: JSON.stringify(submitData), headers: { "Content-Type": "application/json" }}); + if (!submitResponse.ok) { + const submitError = document.createElement("li"); + submitError.innerText = await submitResponse.text(); + editErrors.appendChild(submitError); + return; + } + + templateData[index].name = name; + if (submitData.hasOwnProperty("image")) { + templateData[index].image = submitData.image; + } + templateData[index].description = description; + templateData[index].attribution = attribution; + templateData[index].crop = submitData.crop; + templateData[index].location = submitData.location; + + const templateDOM = generateTemplateDOM(index, templateData[index]); + templateRow.replaceWith(templateDOM); + }); + + return templateRow; +} + +function addTemplate(index, template) { + const templateDOM = generateTemplateDOM(index, template); + document.getElementById("template-list-data").appendChild(templateDOM); +} - document.getElementById("template-list-data").appendChild(templateRow); +function setCoordNumberFieldProps(field, direction) { + field.type = "number"; + field.placeholder = direction; + field.min = 0; + field.step = 1; + field.classList.add("template-coord"); } function showPreview(index) { diff --git a/thrimbletrimmer/styles/thumbnails.css b/thrimbletrimmer/styles/thumbnails.css index c045f7f..9922ff7 100644 --- a/thrimbletrimmer/styles/thumbnails.css +++ b/thrimbletrimmer/styles/thumbnails.css @@ -2,6 +2,10 @@ display: none; } +table > form { + display: table-row; +} + #template-list-data { border-collapse: collapse; } @@ -15,7 +19,7 @@ width: 480px; } -#template-new-errors { +#template-new-errors, .template-data-edit-errors { color: #c00; } @@ -29,7 +33,7 @@ display: contents; } -.template-new-coord { +.template-coord { width: 50px; } diff --git a/thrimbletrimmer/thumbnail_manager.html b/thrimbletrimmer/thumbnail_manager.html index 03ff1df..69442fd 100644 --- a/thrimbletrimmer/thumbnail_manager.html +++ b/thrimbletrimmer/thumbnail_manager.html @@ -23,6 +23,7 @@ Crop Coordinates Location Coordinates Preview + @@ -53,7 +54,7 @@