mirror of
https://github.com/mickael-kerjean/filestash
synced 2025-12-15 04:45:45 +01:00
feature (admin): admin settings
This commit is contained in:
parent
e93088945b
commit
b83155aeda
6 changed files with 111 additions and 86 deletions
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -16,7 +16,7 @@
|
|||
"jest": {
|
||||
"verbose": true,
|
||||
"setupFiles": [
|
||||
"<rootDir>/common/skeleton/test/jest.setup.js"
|
||||
"<rootDir>/lib/skeleton/test/jest.setup.js"
|
||||
],
|
||||
"coveragePathIgnorePatterns": [
|
||||
"test",
|
||||
|
|
|
|||
|
|
@ -24,6 +24,7 @@ function Page(render) {
|
|||
export default AdminOnly(WithShell(Page));
|
||||
|
||||
function componentLog(render) {
|
||||
|
||||
render(createElement(`<div>log stuff</div>`));
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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(".")} />`);
|
||||
},
|
||||
};
|
||||
|
|
|
|||
|
|
@ -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() {
|
||||
|
|
|
|||
Loading…
Reference in a new issue