/**
* @file
* @brief Settings page: two-way binds data-path controls to /api/setting (queued), number steppers, multiple-of-64 rounding, and list pickers.
*/
// Settings
let settings = {};
// Lists
let lists = [];
let settingsQueue = [];
let settingsQueueOngoing = false;
// Converts to start case but fixes numbering
/**
* Start case.
* @param {string} text
* @returns {*}
*/
function startCase(text) {
let ret = _.startCase(text);
// Replace D with Danbooru (More readable)
ret = ret.replace(/^D /m, "Danbooru ");
// Replace Danbooru with anime to be more readable
ret = ret.replace("Danbooru", "Anime");
ret = ret.replace("Dhigh", "Digipa High");
ret = ret.replace("Dmed", "Digipa Medium");
ret = ret.replace("Dlow", "Digipa Low");
// Makes it too long, skip, was to add to readability
// ret = ret.replace("Danbooru Character C", "Danbooru Character Trademark");
// ret = ret.replace("Danbooru Character Nc", "Danbooru Character OC");
// 3 D Print V 1 => 3D Print V1
ret = ret.replaceAll(/(\d) (\w)/gm, "$1$2");
ret = ret.replaceAll(/(\w) (\d)/gm, "$1$2");
return ret;
}
async function saveSettings() {
// Don't do anything if ongoing
if (settingsQueueOngoing) return;
// Mark on-going
settingsQueueOngoing = true;
// Clone settings queue
const _settingsQueue = _.cloneDeep(settingsQueue);
settingsQueue = [];
try {
// Save settings one-by-one
for (let i = 0; i < _settingsQueue.length; i++) {
await fetch(`/api/setting`, {
method: "POST",
body: JSON.stringify({
path: _settingsQueue[i].path,
value: _settingsQueue[i].value,
}),
headers: {
"Content-Type": "application/json",
},
});
}
} catch (err) {
console.log("Error:");
console.log(err);
}
// Mark false
settingsQueueOngoing = false;
// If the queue has items in it again, restart queue
if (settingsQueue.length > 0) setTimeout(saveSettings, 1);
}
/**
* Save setting.
* @param {string} path
* @param {*} value
*/
function saveSetting(path, value) {
settingsQueue.push({ path, value });
saveSettings();
}
/**
* Return val to setting.
*/
function returnValToSetting() {
// Ignore if invalid
if ($(this).is(":invalid")) return;
// Get path(s)
const paths = $(this).data("path").split(",");
// Get the value
let val;
if ($(this).is('[type="checkbox"]')) val = $(this).prop("checked").toString();
// else if($(this).is('textarea'))
// val = $(this).text();
else val = $(this).val();
// Convert value to an array of 1 or more elements only if this is a text input or textarea
// and it's either implicitly or explicitly requested
if (
(paths.length > 1 || $(this).data("join") != undefined) &&
($(this).is("input[type='text']") || $(this).is("textarea"))
) {
if ($(this).data("join") != undefined) val = val.split($(this).data("join"));
else val = val.split("-");
}
// Otherwise just make into an array
else val = [val];
// Type conversion from string to proper primitive values
for (let i = 0; i < val.length; i++) {
// Try to convert to null
if (val[i] == "null") {
val[i] = null;
}
// Try t convert to undefined
else if (val[i] == "undefined") {
val[i] = undefined;
}
// Try to convert to a boolean
else if (val[i] == "true" || val[i] == "false") {
val[i] = val[i] == "true";
}
// Try to convert to number
else if (!isNaN(Number(val[i]))) {
val[i] = Number(val[i]);
}
// Try to convert it to a float
else if (val[i].endsWith("%") && !isNaN(Number.parseFloat(val[i]))) {
val[i] = Number.parseFloat(val[i]);
val[i] = +(val[i] * 0.01).toFixed(2);
}
// Leave as a string
}
// Save
// 1 path to 1 value
if (paths.length == 1 && val.length == 1) {
saveSetting(paths[0], val[0]);
return;
}
// 1 path to an array value
else if (paths.length == 1 && val.length > 1) {
saveSetting(paths[0], val);
return;
}
// 1 value per path
else if (val.length == paths.length) {
for (let i = 0; i < paths.length; i++) {
// Grab path
const path = paths[i];
// Grab value
const value = val[i];
// Save
saveSetting(path, value);
}
return;
}
console.error("Can't classify how to save data back, paths", paths, "values", val);
}
/**
* Set val to setting.
*/
function setValToSetting() {
const path = $(this).data("path").split(",");
let value = _.at(settings, path);
if (value == null || value == undefined || value.length == 0) return;
if ($(this).data("percent") != undefined) {
for (let i = 0; i < value.length; i++) {
value[i] = +(value[i] * 100).toFixed(2) + "%";
}
}
let joinStr = "-";
if ($(this).data("join") != undefined) {
joinStr = $(this).data("join");
for (let i = 0; i < value.length; i++) {
if (Array.isArray(value[i])) value[i] = value[i].join(joinStr);
}
}
// console.log(path, value);
if ($(this).is('[type="checkbox"]')) $(this).prop("checked", value[0]);
// else if($(this).is('textarea'))
// $(this).text(value.join(joinStr));
else $(this).val(value.join(joinStr));
}
/**
* On settings download.
*/
function onSettingsDownload() {
$("select[data-lists]").each(function () {
for (let i = 0; i < lists.length; i++) {
let display = startCase(lists[i]);
if (display == "False") display = "Random";
$(this).append(`<option value="${lists[i]}">${display}</option>`);
}
});
$("[data-path]").each(setValToSetting);
}
/**
* Download lists.
*/
function downloadLists() {
$.ajax({
type: "GET",
url: `/api/files/lists`,
success: function (data) {
lists = ["false", ...data];
onSettingsDownload();
},
error: function (error) {
console.log("Error:");
console.log(error);
},
});
}
/**
* Download settings.
*/
function downloadSettings() {
$.ajax({
type: "GET",
url: `/api/settings`,
success: function (data) {
settings = data;
downloadLists();
},
error: function (error) {
console.log("Error:");
console.log(error);
},
});
}
/**
* On page button click.
*/
function onPageButtonClick() {
const target = $(this).attr("data-page");
$(".content").hide();
$("#sections button").removeClass("active");
$(this).addClass("active");
$("#" + target).show();
}
/**
* On minus stepper click.
* @param {object} input
*/
function onMinusStepperClick(input) {
const currentValue = +input.val();
const step = +input.attr("step");
input.val(currentValue - step);
if (input.val < step) input.val(step);
$(input).trigger("change");
}
/**
* On plus stepper click.
* @param {object} input
*/
function onPlusStepperClick(input) {
const currentValue = +input.val();
const step = +input.attr("step");
input.val(currentValue + step);
$(input).trigger("change");
}
/**
* Setup number steppers.
*/
function setupNumberSteppers() {
$(".number-stepper").each(function () {
const minusBtn = $(this).children("button:first-child");
const input = $(this).children("input");
const plusBtn = $(this).children("button:nth-child(3)");
minusBtn.click(onMinusStepperClick.bind(undefined, input));
plusBtn.click(onPlusStepperClick.bind(undefined, input));
});
}
/**
* On number change wstep.
*/
function onNumberChangeWStep() {
// Get value
let value = +$(this).val();
const step = +$(this).attr("step");
// If value is multiple of 64, then stop here
const mult64Check = value % step;
if (mult64Check === 0) return;
// Convert partial multiple of 64 to percent
const mult64Percent = mult64Check / step;
// Round up or down to nearest multiple of 64
if (mult64Percent >= 0.5) value += step - mult64Check;
else value -= mult64Check;
// Set value
$(this).val(value);
}
$(document).ready(function () {
setupNumberSteppers();
downloadSettings();
$("#sections button").click(onPageButtonClick);
$("input[step]").change(onNumberChangeWStep);
$("[data-path]").change(returnValToSetting);
});