/** Return the non-executable form configuration emitted by the server. */
function getCardPageData()
{
return JSON.parse(document.getElementById("CardPageData").textContent);
}
/** Return whether the current form is editing an existing card. */
function isEditForm()
{
return getCardPageData().mode == "edit";
}
/** Return whether the form requests replacement front artwork. */
function replacesFront()
{
if(!isEditForm())
{
return true;
}
return document.getElementById("CardFrontAction").value == "replace";
}
/** Return whether the form requests replacement foil control. */
function replacesFoil()
{
if(!isEditForm())
{
return true;
}
return document.getElementById("CardFoilAction").value == "replace";
}
/** Show and enable image-source fields required by the current actions. */
function updateSourceMode()
{
const selected_mode = document.querySelector(
'input[name="source_mode"]:checked').value;
const uses_files = selected_mode == "files";
const file_fields = document.getElementById("FileSourceFields");
const url_fields = document.getElementById("UrlSourceFields");
file_fields.hidden = !uses_files;
url_fields.hidden = uses_files;
const front_file = document.getElementById("CardFrontFile");
const foil_file = document.getElementById("CardFoilFile");
const front_url = document.getElementById("CardFrontUrl");
const foil_url = document.getElementById("CardFoilUrl");
const state = window.cardFormLogic.sourceInputState({
mode: isEditForm() ? "edit" : "create",
source_mode: selected_mode,
front_action: replacesFront() ? "replace" : "keep",
foil_action: replacesFoil() ? "replace" : "keep",
});
front_file.disabled = !state.front_file_enabled;
foil_file.disabled = !state.foil_file_enabled;
front_url.disabled = !state.front_url_enabled;
foil_url.disabled = !state.foil_url_enabled;
front_file.required = state.front_file_enabled && state.front_required;
front_url.required = state.front_url_enabled && state.front_required;
foil_file.required = state.foil_file_enabled && state.foil_required;
foil_url.required = state.foil_url_enabled && state.foil_required;
}
/** Show controls and series belonging to the selected database game. */
function updateSeriesChoices()
{
const game = document.getElementById("CardGame").value;
for(const group of document.querySelectorAll(".game-fields"))
{
const visible = window.cardFormLogic.gameControlEnabled(
game, group.dataset.game);
group.hidden = !visible;
for(const input of group.querySelectorAll("input, textarea, select"))
{
input.disabled = !visible;
}
}
for(const choice of document.querySelectorAll(".series-choice"))
{
const visible = window.cardFormLogic.seriesChoiceEnabled(
game, choice.dataset.game);
choice.hidden = !visible;
choice.querySelector("input").disabled = !visible;
}
}
/** Display the selected image filename beside one file input. */
function updateFileMetadata(input, output)
{
const file = input.files[0];
output.textContent = file == null ? "No file selected" : file.name;
}
/** Put one browser Blob into a file input for multipart submission. */
function setInputFile(input, blob, filename)
{
const transfer = new DataTransfer();
transfer.items.add(new File([blob], filename, {type: blob.type}));
input.files = transfer.files;
}
/** Fetch an existing same-origin image for a mixed edit preview. */
async function fetchExistingImage(url, filename)
{
const response = await fetch(url, {credentials: "same-origin"});
if(!response.ok)
{
throw(new Error(
`Failed to load the current card image: HTTP ${response.status}`));
}
const blob = await response.blob();
return new File([blob], filename, {type: blob.type});
}
/** Resolve retained and replacement files for the current preview. */
async function getPreviewFiles()
{
const page_data = getCardPageData();
const selected_front = document.getElementById(
"CardFrontFile").files[0];
const selected_foil = document.getElementById(
"CardFoilFile").files[0];
if(page_data.mode == "create")
{
return {front: selected_front, foil: selected_foil};
}
let front = selected_front;
if(!replacesFront() || front == null)
{
front = await fetchExistingImage(
page_data.front_url, "current-front");
}
const foil_action = document.getElementById("CardFoilAction").value;
let foil = selected_foil;
if(foil_action == "remove")
{
foil = null;
}
else if((foil_action == "keep" || foil == null) &&
page_data.foil_url != null)
{
foil = await fetchExistingImage(
page_data.foil_url, "current-foil");
}
return {front, foil};
}
/** Refresh the WebGL preview from retained and selected image files. */
async function updateLocalPreview()
{
const preview = window.cardPreview;
if(preview == null)
{
return;
}
const files = await getPreviewFiles();
if(files.front != null)
{
await preview.setFiles(files.front, files.foil);
}
}
/** Fetch one CORS-enabled URL into its corresponding file input. */
async function fetchImageInput(url_input, file_input, filename)
{
if(!window.cardFormLogic.validRemoteImageUrl(url_input.value))
{
throw(new Error("Image URLs must use HTTP or HTTPS."));
}
const url = new URL(url_input.value);
const response = await fetch(url, {
mode: "cors",
credentials: "omit",
});
if(!response.ok)
{
throw(new Error(
`Failed to fetch ${url}: HTTP ${response.status}`));
}
setInputFile(file_input, await response.blob(), filename);
}
/** Prepare URL images and the foil thumbnail before native submission. */
async function prepareCardSubmission(event)
{
const form = event.currentTarget;
if(form.dataset.prepared == "true")
{
return;
}
event.preventDefault();
if(form.dataset.state == "preparing")
{
return;
}
form.dataset.state = "preparing";
const submitter = event.submitter;
const submit_button = form.querySelector('button[type="submit"]');
submit_button.disabled = true;
const status = document.getElementById("CardFormStatus");
status.textContent = "Preparing card images…";
try
{
const page_data = getCardPageData();
const foil_action = page_data.mode == "edit"
? document.getElementById("CardFoilAction").value
: "replace";
const rendering_changed = page_data.mode == "create" ||
replacesFront() || foil_action != "keep";
const mode = document.querySelector(
'input[name="source_mode"]:checked').value;
const front_input = document.getElementById("CardFrontFile");
const foil_input = document.getElementById("CardFoilFile");
if(mode == "urls")
{
if(replacesFront())
{
await fetchImageInput(
document.getElementById("CardFrontUrl"),
front_input,
"front-upload");
front_input.disabled = false;
}
if(replacesFoil())
{
foil_input.value = "";
const foil_url = document.getElementById("CardFoilUrl");
if(foil_url.value != "")
{
await fetchImageInput(
foil_url, foil_input, "foil-upload");
}
foil_input.disabled = false;
}
}
if(rendering_changed)
{
await updateLocalPreview();
}
const needs_thumbnail = window.cardFormLogic.foilThumbnailRequired({
mode: page_data.mode,
front_action: page_data.mode == "edit"
? document.getElementById("CardFrontAction").value
: "replace",
foil_action,
has_current_foil: page_data.foil_url != null,
has_replacement_foil: foil_input.files.length != 0,
});
if(needs_thumbnail)
{
if(window.cardPreview == null)
{
throw(new Error("The foil preview is not ready."));
}
const thumbnail = await window.cardPreview.captureThumbnail();
const thumbnail_input = document.getElementById(
"CardThumbnailFile");
setInputFile(thumbnail_input, thumbnail, "thumbnail.png");
thumbnail_input.disabled = false;
}
form.dataset.prepared = "true";
form.dataset.state = "ready";
submit_button.disabled = false;
if(submitter == null)
{
form.requestSubmit();
}
else
{
form.requestSubmit(submitter);
}
}
catch(error)
{
console.error(error);
status.textContent = error.message;
form.dataset.state = "idle";
submit_button.disabled = false;
}
}
/** Connect the image-source controls after the form is available. */
function initializeCardForm()
{
for(const input of document.querySelectorAll(
'input[name="source_mode"]'))
{
input.addEventListener("change", updateSourceMode);
}
const front_input = document.getElementById("CardFrontFile");
const foil_input = document.getElementById("CardFoilFile");
front_input.addEventListener("change", function updateFrontMetadata()
{
updateFileMetadata(
front_input,
document.getElementById("CardFrontFileMeta"));
updateLocalPreview().catch(console.error);
});
foil_input.addEventListener("change", function updateFoilMetadata()
{
updateFileMetadata(
foil_input,
document.getElementById("CardFoilFileMeta"));
updateLocalPreview().catch(console.error);
});
if(isEditForm())
{
document.getElementById("CardFrontAction").addEventListener(
"change", function updateFrontAction()
{
updateSourceMode();
updateLocalPreview().catch(console.error);
});
document.getElementById("CardFoilAction").addEventListener(
"change", function updateFoilAction()
{
updateSourceMode();
updateLocalPreview().catch(console.error);
});
}
else
{
document.getElementById("CardGame").addEventListener(
"change", updateSeriesChoices);
updateSeriesChoices();
}
document.getElementById("CardForm").addEventListener(
"submit", prepareCardSubmission);
updateSourceMode();
}
window.addEventListener("DOMContentLoaded", initializeCardForm);