mirror of
https://github.com/mickael-kerjean/filestash.git
synced 2025-10-30 09:37:55 +08:00
121 lines
5.5 KiB
JavaScript
121 lines
5.5 KiB
JavaScript
import { createElement } from "./skeleton/index.js";
|
|
import { ApplicationError } from "./error.js";
|
|
import { animate } from "./animate.js";
|
|
|
|
export function mutateForm(formSpec, formState) {
|
|
Object.keys(formState).forEach((inputName) => {
|
|
const value = formState[inputName];
|
|
const keys = inputName.split(".");
|
|
|
|
let ptr = formSpec;
|
|
while (keys.length > 1) {
|
|
const k = keys.shift();
|
|
if (!k) throw new ApplicationError("INTERNAL_ERROR", "assumption failed: missing key");
|
|
ptr = ptr[k];
|
|
}
|
|
const key = keys.shift() || "";
|
|
if (ptr && ptr[key]) ptr[key].value = (value === "" ? null : value);
|
|
});
|
|
return formSpec;
|
|
}
|
|
|
|
async function createFormNodes(node, { renderNode, renderLeaf, renderInput, path = [], level = 0 }) {
|
|
// CASE 0: invalid form spec
|
|
if (typeof node !== "object") {
|
|
return [createElement(`<div>ERR: node[${typeof node}] path[${path.join(".")}] level[${level}]</div>`)];
|
|
}
|
|
const $list = [];
|
|
for (const key of Object.keys(node)) {
|
|
if (typeof node[key] !== "object") {
|
|
$list.push(createElement(`<div>ERR: node[${typeof node[key]}] path[${path.join(".")}] level[${level}]</div>`));
|
|
}
|
|
// CASE 1: non leaf node
|
|
else if (typeof node[key].type !== "string") {
|
|
const $chunk = renderNode({ level, label: key });
|
|
const $children = $chunk.querySelector("[data-bind=\"children\"]") || $chunk;
|
|
$children.removeAttribute("data-bind");
|
|
const $nested = await createForm(node[key], {
|
|
path: path.concat(key),
|
|
level: level + 1,
|
|
label: key,
|
|
renderNode,
|
|
renderLeaf,
|
|
renderInput
|
|
});
|
|
$children.appendChild($nested);
|
|
$list.push($chunk);
|
|
}
|
|
// CASE 2: leaf node
|
|
else {
|
|
const currentPath = path.concat(key);
|
|
const $leaf = renderLeaf({ ...node[key], path: currentPath, label: key });
|
|
const $input = await renderInput({ ...node[key], path: currentPath.filter((chunk) => !!chunk) });
|
|
const $target = $leaf.querySelector("[data-bind=\"children\"]") || $leaf;
|
|
|
|
// leaf node is either "classic" or can be the target of something that can be toggled
|
|
// That's how we can hide input elements conditionally for use cases like the log level
|
|
// settings that will not be visible unless log is first enabled or the advanced section
|
|
// of the login screen
|
|
const isAToggleElementItself = typeof node[key].id === "string";
|
|
const canToggleOtherElements = node[key].type === "enable" && node[key].target && node[key].target.length > 0;
|
|
if (!isAToggleElementItself) {
|
|
$target.removeAttribute("data-bind");
|
|
$target.appendChild($input);
|
|
$list.push($leaf);
|
|
}
|
|
if (canToggleOtherElements) {
|
|
// initialise the dom structure
|
|
const $container = window.document.createElement("div");
|
|
$container.classList.add("advanced_form");
|
|
$container.style.setProperty("overflow", "hidden");
|
|
for (const k of Object.keys(node)) {
|
|
if (typeof node[k] !== "object") continue;
|
|
else if (!node[k].id) continue;
|
|
else if (node[key].target.indexOf(node[k].id) === -1) continue;
|
|
|
|
const $kleaf = renderLeaf({ ...node[k], path: path.concat(k), label: k });
|
|
const $kinput = await renderInput({ ...node[k], path: path.concat(k) });
|
|
const $ktarget = $kleaf.querySelector("[data-bind=\"children\"]") || $kleaf;
|
|
$ktarget.removeAttribute("data-bind");
|
|
$ktarget.appendChild($kinput);
|
|
$container.appendChild($kleaf);
|
|
}
|
|
$list.push($container);
|
|
|
|
// initial state of the toggle
|
|
const isToggled = typeof node[key].value === "boolean" ? node[key].value : node[key].default;
|
|
if (!isToggled) $container.style.setProperty("display", "none");
|
|
let clientHeight = null; // this will only be known when the dom is mounted
|
|
|
|
// setup events
|
|
$input.onchange = async(e) => {
|
|
$container.style.setProperty("display", "inherit");
|
|
if (clientHeight === null) clientHeight = $container.offsetHeight;
|
|
if (e.target.checked) {
|
|
animate($container, {
|
|
time: Math.max(50, Math.min(clientHeight, 150)),
|
|
keyframes: [{ height: "0" }, { height: `${clientHeight}px` }]
|
|
});
|
|
} else {
|
|
animate($container, {
|
|
time: Math.max(25, Math.min(clientHeight, 75)),
|
|
keyframes: [{ height: `${clientHeight}px` }, { height: "0" }]
|
|
});
|
|
}
|
|
};
|
|
}
|
|
}
|
|
}
|
|
return $list;
|
|
}
|
|
|
|
export async function createForm(node, opts) {
|
|
const $container = window.document.createElement("div");
|
|
if (!(opts.level >= 1)) $container.classList.add("formbuilder");
|
|
const $nodes = await createFormNodes(node, opts);
|
|
$nodes.forEach(($node) => {
|
|
$container.appendChild($node);
|
|
});
|
|
return $container;
|
|
}
|