Implement thumbnail manager

This implements a frontend for submitting new thumbnails and viewing existing thumbnail data.
pull/449/head
ElementalAlchemist 2 weeks ago committed by Christopher Usher
parent 6290850ff0
commit c8899f5133

@ -0,0 +1,170 @@
let viewingTemplate = null;
let googleUser = null;
let templateData = [];
function googleOnSignIn(googleUserData) {
googleUser = googleUserData;
const signInElem = document.getElementById("google-auth-sign-in");
const signOutElem = document.getElementById("google-auth-sign-out");
signInElem.classList.add("hidden");
signOutElem.classList.remove("hidden");
}
async function googleSignOut() {
if (googleUser) {
googleUser = null;
await gapi.auth2.getAuthInstance().signOut();
const signInElem = document.getElementById("google-auth-sign-in");
const signOutElem = document.getElementById("google-auth-sign-out");
signInElem.classList.remove("hidden");
signOutElem.classList.add("hidden");
}
}
window.addEventListener("DOMContentLoaded", async (event) => {
document.getElementById("template-new-form").addEventListener("submit", async (event) => {
event.preventDefault();
const errorListContainer = document.getElementById("template-new-errors");
errorListContainer.innerHTML = "";
const form = document.getElementById("template-new-form");
const formData = new FormData(form);
const name = formData.get("name");
const imageFile = formData.get("image");
const fileReader = new FileReader();
const fileReaderCompletePromise = new Promise((resolve, reject) => {
fileReader.addEventListener("loadend", (event) => resolve());
});
fileReader.readAsDataURL(imageFile);
const description = formData.get("description");
const attribution = formData.get("attribution");
const cropXStart = parseInt(formData.get("cropxstart"), 10);
const cropYStart = parseInt(formData.get("cropystart"), 10);
const cropXEnd = parseInt(formData.get("cropxend"), 10);
const cropYEnd = parseInt(formData.get("cropyend"), 10);
const locXStart = parseInt(formData.get("locxstart"), 10);
const locYStart = parseInt(formData.get("locystart"), 10);
const locXEnd = parseInt(formData.get("locxend"), 10);
const locYEnd = parseInt(formData.get("locyend"), 10);
if (
isNaN(cropXStart) ||
isNaN(cropYStart) ||
isNaN(cropXEnd) ||
isNaN(cropYEnd) ||
isNaN(locXStart) ||
isNaN(locYStart) ||
isNaN(locXEnd) ||
isNaN(locYEnd)
) {
const parseNumbersError = document.createElement("li");
parseNumbersError.innerText = "All crop and location information must be entered";
errorListContainer.appendChild(parseNumbersError);
}
await fileReaderCompletePromise;
const imageDataURL = fileReader.result;
if (!imageDataURL.startsWith("data:image/png;base64,")) {
const imageReadError = document.createElement("li");
imageReadError.innerText = "Couldn't read the image data, or the image wasn't a valid PNG";
errorListContainer.appendChild(imageReadError);
return;
}
const image = imageDataURL.substring(22);
const submitData = {
name: name,
image: image,
description: description,
attribution: attribution,
crop: [cropXStart, cropYStart, cropXEnd, cropYEnd],
location: [locXStart, locYStart, locXEnd, locYEnd],
};
if (!errorListContainer.hasChildNodes()) {
const submitResponse = await fetch("/thrimshim/add-template", {
method: "POST",
body: JSON.stringify(submitData),
headers: { "Content-Type": "application/json" },
});
if (!submitResponse.ok) {
const submitError = document.createElement("li");
submitError.innerText = await submitResponse.text();
errorListContainer.appendChild(submitError);
return;
}
addTemplate(templateData.length, submitData);
templateData.push(submitData);
form.reset();
}
});
document.getElementById("google-auth-sign-out").addEventListener("click", (_event) => {
googleSignOut();
});
const templateDataResponse = await fetch("/thrimshim/templates");
if (!templateDataResponse.ok) {
return;
}
templateData = await templateDataResponse.json();
for (const [index, template] of templateData.entries()) {
addTemplate(index, template);
}
});
function addTemplate(index, template) {
const { name, description, attribution, crop, location } = template;
const nameCell = document.createElement("td");
nameCell.innerText = name;
const descriptionCell = document.createElement("td");
descriptionCell.innerText = description;
const attributionCell = document.createElement("td");
attributionCell.innerText = attribution;
const cropCell = document.createElement("td");
cropCell.innerText = `(${crop[0]}, ${crop[1]}) to (${crop[2]}, ${crop[3]})`;
const locationCell = document.createElement("td");
locationCell.innerText = `(${location[0]}, ${location[1]}) to (${location[2]}, ${location[3]})`;
const previewCell = document.createElement("td");
const previewLink = document.createElement("a");
previewLink.href = `javascript:showPreview(${index})`;
previewLink.innerText = "Preview";
previewCell.appendChild(previewLink);
previewCell.id = `template-list-preview-${index}`;
const templateRow = document.createElement("tr");
templateRow.appendChild(nameCell);
templateRow.appendChild(descriptionCell);
templateRow.appendChild(attributionCell);
templateRow.appendChild(cropCell);
templateRow.appendChild(locationCell);
templateRow.appendChild(previewCell);
document.getElementById("template-list-data").appendChild(templateRow);
}
function showPreview(index) {
const template = templateData[index];
const previewCell = document.getElementById(`template-list-preview-${index}`);
if (!previewCell) {
return;
}
const previewContents = document.createElement("img");
previewContents.classList.add("template-list-preview");
previewContents.src = `/thrimshim/template/${template.name}.png`;
previewCell.innerHTML = "";
previewCell.appendChild(previewContents);
}

@ -0,0 +1,38 @@
.hidden {
display: none;
}
#template-list-data {
border-collapse: collapse;
}
#template-list-data td {
border: 1px solid #000;
padding: 2px;
}
.template-list-preview {
width: 480px;
}
#template-new-errors {
color: #c00;
}
#template-new-form-fields {
display: grid;
grid-template-columns: max-content max-content;
gap: 1px;
}
#template-new-form-fields > div {
display: contents;
}
.template-new-coord {
width: 50px;
}
#google-authentication {
margin-top: 5px;
}

@ -9,50 +9,138 @@
content="345276493482-r84m2giavk10glnmqna0lbq8e1hdaus0.apps.googleusercontent.com"
/>
<script src="https://apis.google.com/js/platform.js?onload=onGLoad" async defer></script>
<link rel="stylesheet" href="styles/thumbnails.css" />
<script src="scripts/thumbnails.js"></script>
</head>
<body>
<h1>Create New Template</h1>
<div id="new-template-box">
<div>
<label for="new-template-name">Template name:</label>
<input type="text" id="new-template-name">
<label for="new-template-image">Template image:</label>
<input type="file" id="new-template-image" accept="image/png" />
<br/>
<label for="new-template-description">Description:</label>
<textarea id="new-template-description"></textarea>
<br/>
<label for="new-template-attibution">Attribution:</label>
<textarea id="new-template-attibution"></textarea>
<br/>
Crop:
<input type="text" id="new-thumbnail-crop-0" />
<input type="text" id="new-thumbnail-crop-1" />
to
<input type="text" id="new-thumbnail-crop-2" />
<input type="text" id="new-thumbnail-crop-3" />
<br/>
<div id="template-list">
<h1>Template List</h1>
<table id="template-list-data">
<tr>
<th>Name</th>
<th>Description</th>
<th>Attribution</th>
<th>Crop Coordinates</th>
<th>Location Coordinates</th>
<th>Preview</th>
</tr>
</table>
</div>
Location:
<input type="text" id="new-thumbnail-location-0" />
<input type="text" id="new-thumbnail-location-1" />
to
<input type="text" id="new-thumbnail-location-2" />
<input type="text" id="new-thumbnail-location-3" />
<br/>
<div id="template-new">
<h1>Add New Template</h1>
<ul id="template-new-errors"></ul>
<form id="template-new-form">
<div id="template-new-form-fields">
<div>
<label for="template-new-name">Name:</label>
<input type="text" name="name" id="template-new-name" />
</div>
<div>
<label for="template-new-image">Image:</label>
<input type="file" id="template-new-image" name="image" accept="image/png" />
</div>
<div>
<label for="template-new-description">Description:</label>
<textarea id="template-new-description" name="description"></textarea>
</div>
<div>
<label for="template-new-attribution">Attribution:</label>
<input type="text" id="template-new-attribution" name="attribution" />
</div>
<div>
<span>Crop:</span>
<span>
<input
type="number"
class="template-new-coord"
id="template-new-crop-x-start"
name="cropxstart"
placeholder="X"
min="0"
step="1"
/>
<input
type="number"
class="template-new-coord"
id="template-new-crop-y-start"
name="cropystart"
placeholder="Y"
min="0"
step="1"
/>
to
<input
type="number"
class="template-new-coord"
id="template-new-crop-x-end"
name="cropxend"
placeholder="X"
min="0"
step="1"
/>
<input
type="number"
class="template-new-coord"
id="template-new-crop-y-end"
name="cropyend"
placeholder="Y"
min="0"
step="1"
/>
</span>
</div>
<div>
<span>Location:</span>
<span>
<input
type="number"
class="template-new-coord"
id="template-new-location-x-start"
name="locxstart"
placeholder="X"
min="0"
step="1"
/>
<input
type="number"
class="template-new-coord"
id="template-new-location-y-start"
name="locystart"
placeholder="Y"
min="0"
step="1"
/>
to
<input
type="number"
class="template-new-coord"
id="template-new-location-x-end"
name="locxend"
placeholder="X"
min="0"
step="1"
/>
<input
type="number"
class="template-new-coord"
id="template-new-location-y-end"
name="locyend"
placeholder="Y"
min="0"
step="1"
/>
</span>
</div>
</div>
<button id="create-template">Create</button>
</div>
<button type="submit">Add Template</button>
</form>
</div>
<div id="google-authentication">
<div id="google-auth-sign-in" class="g-signin2" data-onsuccess="googleOnSignIn"></div>
<a href="#" id="google-auth-sign-out" class="hidden">Sign Out of Google Account</a>
</div>
</body>
</html>

Loading…
Cancel
Save