feature (admin): admin settings

This commit is contained in:
Mickael Kerjean 2023-07-30 20:08:15 +10:00
parent e93088945b
commit b83155aeda
6 changed files with 111 additions and 86 deletions

View file

@ -158,6 +158,9 @@ select:-moz-focusring {
}
.scroll-y {
flex: 1;
overflow-y: scroll;
overflow-x: hidden;
scrollbar-3dlight-color: #7d7e94;
scrollbar-arrow-color: #c1c1d1;
scrollbar-darkshadow-color: #2d2c4d;

View file

@ -1,5 +1,4 @@
import { createElement } from "./skeleton/index.js";
import { qs } from "./dom.js";
export function mutateForm(formSpec, formState) {
Object.keys(formState).forEach((inputName) => {
@ -13,27 +12,34 @@ export function mutateForm(formSpec, formState) {
return formSpec;
}
export function createForm(node, { renderNode, renderLeaf, path = [], level = 0 }) {
export function createForm(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>`);
}
// CASE 1: leaf node = the input elements who have many possible types
else if (typeof node["type"] === "string") {
return renderLeaf({ ...node, path: path });
}
// CASE 2: non leaf node
else {
const $container = document.createElement("div");
Object.keys(node).forEach((key) => {
const $container = window.document.createElement("div");
Object.keys(node).forEach((key) => {
// CASE 0: invalid form spec
if (typeof node[key] !== "object") {
$container.appendChild(createElement(`<div>ERR: node[${typeof node[key]}] path[${path.join(".")}] level[${level}]</div>`));
}
// CASE 1: leaf node = the input elements who have many possible types
else if (typeof node[key]["type"] === "string") {
const $leaf = renderLeaf({ ...node[key], path, label: key });
const $target = $leaf.querySelector(`[data-bind="children"]`) || $leaf;
$target.appendChild(renderInput({ ...node, path }));
$container.appendChild($target);
}
// CASE 2: non leaf node
else {
const $chunk = renderNode({ level, label: key });
const $children = qs($chunk, `[data-bind="children"]`);
const $children = $chunk.querySelector(`[data-bind="children"]`) || $chunk;
$children.appendChild(createForm(node[key], {
path: path.concat(key), level: level+1,
renderNode, renderLeaf,
path: path.concat(key), level: level + 1, label: key,
renderNode, renderLeaf, renderInput,
}));
$container.appendChild($chunk);
});
return $container;
}
}
});
return $container;
}

View file

@ -16,7 +16,7 @@
"jest": {
"verbose": true,
"setupFiles": [
"<rootDir>/common/skeleton/test/jest.setup.js"
"<rootDir>/lib/skeleton/test/jest.setup.js"
],
"coveragePathIgnorePatterns": [
"test",

View file

@ -24,6 +24,7 @@ function Page(render) {
export default AdminOnly(WithShell(Page));
function componentLog(render) {
render(createElement(`<div>log stuff</div>`));
}

View file

@ -10,17 +10,21 @@ import transition from "./animate.js";
export default AdminOnly(WithShell(function(render) {
const $container = createElement(`
<div className="component_settingspage sticky">
<form>
SETTINGS
</form>
<div class="component_settingspage sticky">
<form data-bind="form"></form>
</div>
`);
render(transition($container));
const config$ = Config.get();
const config$ = Config.get().pipe(
rxjs.map((res) => {
delete res.constant;
delete res.middleware;
return res;
}),
)
const form$ = config$.pipe(
rxjs.mergeMap(() => qsa($container, "form [name]")),
rxjs.mergeMap(() => qsa($container, `form[data-bind="form"] [name]`)),
rxjs.mergeMap(($el) => rxjs.fromEvent($el, "input")),
rxjs.map((e) => ({
name: e.target.getAttribute("name"),
@ -35,7 +39,7 @@ export default AdminOnly(WithShell(function(render) {
effect(config$.pipe(
rxjs.map((formSpec) => createForm(formSpec, formTmpl)),
rxjs.map(($form) => [$form]),
applyMutation(qs($container, "form"), "appendChild"),
applyMutation(qs($container, `form[data-bind="form"]`), "appendChild"),
));
effect(form$.pipe(
@ -48,36 +52,40 @@ export default AdminOnly(WithShell(function(render) {
const formTmpl = {
renderNode: ({ label, level }) => {
let $chunk;
if (level === 0) $chunk = createElement(`
<label className="no-select input_type_TODO">
if (level === 0) return createElement(`
<div class="formbuilder">
<h2 class="no-select">${label}</h2>
<div data-bind="children"></div>
</div>
`);
return createElement(`
<fieldset>
<legend class="no-select">${label}</legend>
<div data-bind="children"></div>
</fieldset>
`);
},
renderLeaf: ({ label, type, description, path = [] }) => {
return createElement(`
<label class="no-select input_type_TODO">
<div>
<span>
${label}:
</span>
<div style={{ width: "100%" }}>
<div style="width:100%">
<div data-bind="children"></div>
</div>
</div>
<div>
<span class="nothing"></span>
<div style="width:100%">
<div className="description">${label}</div>
<div class="description">${description}</div>
</div>
</div>
</label>
`);
else $chunk = createElement(`
<div>
<fieldset>
<legend className="no-select">${label}</legend>
<div data-bind="children"></div>
</fieldset>
</div>
`);
return $chunk;
},
renderLeaf: ({ label, type, description, path = [] }) => {
renderInput: ({ label, type, description, path = [] }) => {
return createElement(`<input type="text" name=${path.join(".")} />`);
},
};

View file

@ -10,53 +10,60 @@ class ConfigManager {
}
get() {
return rxjs.of({
"general": {
"name": {
"label": "name",
"type": "text",
"description": "Name has shown in the UI",
"placeholder": "Default: \"Filestash\"",
"readonly": false,
"default": "Filestash",
"value": null,
"required": false
},
"port": {
"label": "port",
"type": "number",
"description": "Port on which the application is available.",
"placeholder": "Default: 8334",
"readonly": false,
"default": 8334,
"value": null,
"required": false
},
},
"features": {
"api": {
"enable": {
"label": "enable",
"type": "boolean",
"description": "Enable/Disable the API",
"readonly": false,
"default": true,
"value": null,
"required": false
},
"api_key": {
"label": "api_key",
"type": "long_text",
"description": "Format: '[mandatory:key] [optional:hostname]'. The hostname is used to enabled CORS for your application.",
"placeholder": "foobar *.filestash.app",
"readonly": false,
"default": null,
"value": null,
"required": false
}
},
},
}).pipe(rxjs.share());
return ajax({
url: "/admin/api/config",
withCredentials: true,
method: "GET", responseType: "json",
}).pipe(
rxjs.map((res) => res.responseJSON.result),
);
// return rxjs.of({
// "general": {
// "name": {
// "label": "name",
// "type": "text",
// "description": "Name has shown in the UI",
// "placeholder": "Default: \"Filestash\"",
// "readonly": false,
// "default": "Filestash",
// "value": null,
// "required": false
// },
// "port": {
// "label": "port",
// "type": "number",
// "description": "Port on which the application is available.",
// "placeholder": "Default: 8334",
// "readonly": false,
// "default": 8334,
// "value": null,
// "required": false
// },
// },
// "features": {
// "api": {
// "enable": {
// "label": "enable",
// "type": "boolean",
// "description": "Enable/Disable the API",
// "readonly": false,
// "default": true,
// "value": null,
// "required": false
// },
// "api_key": {
// "label": "api_key",
// "type": "long_text",
// "description": "Format: '[mandatory:key] [optional:hostname]'. The hostname is used to enabled CORS for your application.",
// "placeholder": "foobar *.filestash.app",
// "readonly": false,
// "default": null,
// "value": null,
// "required": false
// }
// },
// },
// }).pipe(rxjs.share());
}
save() {