chore (rewrite): persistence of settings and modal handling

This commit is contained in:
MickaelK 2024-07-01 18:20:24 +10:00
parent b7e7e78920
commit d0e856fe90
7 changed files with 92 additions and 53 deletions

View file

@ -3,7 +3,8 @@ export function settingsGet(initialValues, prefix = "") {
let currentSettings = {};
Object.keys(initialValues).forEach((key) => {
const settingsKey = prefix ? `${prefix}_${key}` : key;
currentSettings[key] = raw[settingsKey];
if (settingsKey in raw) currentSettings[key] = raw[settingsKey];
else currentSettings[key] = initialValues[key];
});
return currentSettings;
}

View file

@ -134,32 +134,31 @@ function componentLeft(render, { $scroll }) {
onClick(qs($page, `[data-action="tag"]`)).pipe(rxjs.tap(() => {
componentTag(createModal(modalOpt));
})),
onClick(qs($page, `[data-action="rename"]`)).pipe(
rxjs.mergeMap(() => componentRename(
onClick(qs($page, `[data-action="rename"]`)).pipe(rxjs.mergeMap(() => {
const path = expandSelection()[0].path;
return rxjs.from(componentRename(
createModal(modalOpt),
basename(expandSelection()[0].path.replace(new RegExp("/$"), "")),
)),
rxjs.mergeMap((val) => { // TODO: migrate to transcient impl
const path = expandSelection()[0].path;
basename(path.replace(new RegExp("/$"), "")),
)).pipe(rxjs.mergeMap((val) => {
const [basepath, filename] = extractPath(path);
clearSelection();
clearCache(path);
clearCache(basepath + val);
return mv(path, basepath + val);
}),
),
onClick(qs($page, `[data-action="delete"]`)).pipe(
rxjs.mergeMap(() => componentDelete(
}));
})),
onClick(qs($page, `[data-action="delete"]`)).pipe(rxjs.mergeMap(() => {
const path = expandSelection()[0].path;
return rxjs.from(componentDelete(
createModal(modalOpt),
basename(expandSelection()[0].path.replace(new RegExp("/$"), "")).substr(0, 15),
)),
rxjs.mergeMap((val) => { // TODO: migrate to transcient impl
basename(path.replace(new RegExp("/$"), "")).substr(0, 15),
)).pipe(rxjs.mergeMap(() =>{
const selection = expandSelection()[0].path;
clearSelection();
clearCache(selection);
clearCache(path);
return rm(selection);
}),
),
}));
})),
)),
));
@ -174,14 +173,16 @@ function componentLeft(render, { $scroll }) {
</button>
`))),
rxjs.mergeMap(($page) => rxjs.merge(
onClick(qs($page, `[data-action="delete"]`)).pipe(
rxjs.mergeMap(() => componentDelete(createModal(modalOpt), "remove")),
rxjs.mergeMap((val) => {
const selections = expandSelection().map(({ path }) => path);
onClick(qs($page, `[data-action="delete"]`)).pipe(rxjs.mergeMap(() => {
const paths = expandSelection().map(({ path }) => path);
return rxjs.from(componentDelete(
createModal(modalOpt),
"remove",
)).pipe(rxjs.mergeMap((val) => {
clearSelection();
return rm(...selections);
}),
),
return rm(...paths);
}));
})),
)),
));
}
@ -203,9 +204,20 @@ function componentRight(render) {
rxjs.share(),
);
const defaultLayout = (view) => {
switch (view) {
case "grid": return `<img class="component_icon" draggable="false" src="data:image/svg+xml;base64,${ICONS.LIST_VIEW}" alt="grid" />`;
case "list": return `<img class="component_icon" draggable="false" src="data:image/svg+xml;base64,${ICONS.GRID_VIEW}" alt="list" />`;
default: throw new Error("NOT_IMPLEMENTED");
}
};
const defaultSort = () => {
return `<img class="component_icon" draggable="false" src="data:image/svg+xml;base64,${ICONS.SORT}" alt="sort" />`;
};
effect(getSelection$().pipe(
rxjs.filter((selections) => selections.length === 0),
rxjs.map(() => render(createFragment(`
rxjs.mergeMap(() => getState$().pipe(rxjs.first())),
rxjs.map(({ view, sort }) => render(createFragment(`
<form style="display: inline-block;" onsubmit="event.preventDefault()">
<input class="hidden" placeholder="${t("search")}" name="q" style="
background: transparent;
@ -218,10 +230,10 @@ function componentRight(render) {
<img class="component_icon" draggable="false" src="data:image/svg+xml;base64,${ICONS.MAGNIFYING_GLASS}" alt="search" />
</button>
<button data-action="view" title="${t("Layout")}">
<img class="component_icon" draggable="false" src="data:image/svg+xml;base64,${ICONS.LIST_VIEW}" alt="list" />
${defaultLayout(view)}
</button>
<button data-action="sort" title="${t("Sort")}">
<img class="component_icon" draggable="false" src="data:image/svg+xml;base64,${ICONS.SORT}" alt="sort" />
${defaultSort(sort)}
</button>
<div class="component_dropdown view sort" data-target="sort">
<div class="dropdown_container">
@ -245,13 +257,13 @@ function componentRight(render) {
onClick(qs($page, `[data-action="view"]`)).pipe(rxjs.tap(($button) => {
const $img = $button.querySelector("img");
if ($img.getAttribute("alt") === "list") {
setState("view", "list");
$img.setAttribute("alt", "grid");
$img.setAttribute("src", "data:image/svg+xml;base64," + ICONS.GRID_VIEW);
} else {
setState("view", "grid");
$img.setAttribute("alt", "list");
$img.setAttribute("alt", "grid");
$img.setAttribute("src", "data:image/svg+xml;base64," + ICONS.LIST_VIEW);
} else {
setState("view", "list");
$img.setAttribute("alt", "list");
$img.setAttribute("src", "data:image/svg+xml;base64," + ICONS.GRID_VIEW);
}
})),
// feature: sort button

View file

@ -19,14 +19,13 @@ export default function(render, removeLabel) {
const $input = qs($modal, "input");
const pressOK = render($modal, (id) => {
if (id !== MODAL_RIGHT_BUTTON) {
ret.complete();
return ret.toPromise();
return;
}
else if (!isValid()) {
qs($modal, ".modal-error-message").textContent = t("Doesn't match");
return ret.toPromise();
}
ret.next(true);
ret.next();
ret.complete();
return ret.toPromise();
}).bind(this, MODAL_RIGHT_BUTTON);

View file

@ -17,13 +17,12 @@ export default function(render, filename) {
`);
const ret = new rxjs.Subject();
const $input = qs($modal, "input");
const pressOK = render($modal, (id) => {
const pressOK = render($modal, function (id) {
const value = $input.value.trim();
if (id !== MODAL_RIGHT_BUTTON) {
ret.complete();
return ret.toPromise();
return;
} else if (!value || value === filename) {
qs($modal, ".modal-error-message").textContent = "Not Valid";
qs($modal, ".modal-error-message").textContent = t("Not Valid");
return ret.toPromise();
}
ret.next(value);

View file

@ -6,7 +6,7 @@ export default function(render) {
MODAL SHARE
</div>
`);
render($modal, ({ id }) => {
render($modal, (id) => {
if (id !== 1) return;
});
}

View file

@ -1,6 +1,8 @@
import rxjs from "../../lib/rx.js";
import ajax from "../../lib/ajax.js";
import { basename } from "../../lib/path.js";
import notification from "../../components/notification.js";
import t from "../../locales/index.js";
import { currentPath } from "./helper.js";
import { setPermissions } from "./model_acl.js";
@ -20,34 +22,48 @@ import { ls as middlewareLs } from "./model_virtual_layer.js";
* 3. the new file is being persisted in the screen if the API call is a success
*/
const withNotification = rxjs.catchError((err) => {
const handleSuccess = (text) => rxjs.tap(() => notification.info(text));
const handleError = rxjs.catchError((err) => {
notification.error(err);
throw err;
});
const trimDirectorySuffix = (name) => name.replace(new RegExp("/$"), "");
export const touch = (path) => ajax({
url: `api/files/touch?path=${encodeURIComponent(path)}`,
method: "POST",
responseType: "json",
}).pipe(withNotification);
}).pipe(
handleSuccess(t("A file named '{{VALUE}}' was created", basename(path))),
handleError,
);
export const mkdir = (path) => ajax({
url: `api/files/mkdir?path=${encodeURIComponent(path)}`,
method: "POST",
responseType: "json",
}).pipe(withNotification);
}).pipe(
handleSuccess(t("A folder named '{{VALUE}}' was created", basename(trimDirectorySuffix(path)))),
handleError,
);
export const rm = (...paths) => rxjs.forkJoin(paths.map((path) => ajax({
url: `api/files/rm?path=${encodeURIComponent(path)}`,
method: "POST",
responseType: "json",
}).pipe(withNotification)));
}))).pipe(
handleSuccess(paths.length > 1 ? t("All Done!") : t("The file '{{VALUE}}' was deleted", basename(trimDirectorySuffix(paths[0])))),
handleError,
);
export const mv = (from, to) => ajax({
url: `api/files/mv?from=${encodeURIComponent(from)}&to=${encodeURIComponent(to)}`,
method: "POST",
responseType: "json",
}).pipe(withNotification);
}).pipe(
handleSuccess(t("The file '{{VALUE}}' was renamed", basename(trimDirectorySuffix(from)))),
handleError,
);
export const save = (path) => rxjs.of(null).pipe(rxjs.delay(1000));

View file

@ -1,16 +1,23 @@
import { onDestroy } from "../../lib/skeleton/index.js";
import rxjs, { effect, preventDefault } from "../../lib/rx.js";
import { settingsGet, settingsSave } from "../../lib/store.js";
import { get as getConfig } from "./model_config.js";
const state$ = new rxjs.BehaviorSubject({
view: "grid",
sort: "type",
show_hidden: false,
order: null,
search: "",
const state$ = new rxjs.BehaviorSubject(null);
getConfig().subscribe((config) => {
state$.next(settingsGet({
view: config.default_view || "grid",
show_hidden: config.display_hidden || false,
sort: config.default_sort || "type",
order: null,
search: "",
}, "filespage"));
});
export const getState$ = () => state$.asObservable();
export const getState$ = () => state$.asObservable().pipe(
rxjs.filter((state) => state !== null),
);
export const setState = (...args) => {
const obj = { ...state$.value };
@ -22,7 +29,12 @@ export const setState = (...args) => {
}
if (!hasChange) return
state$.next(obj);
settingsSave(state$.value, "filespage");
settingsSave({
view: state$.value.view,
show_hidden: state$.value.show_hidden,
sort: state$.value.sort,
order: state$.value.order,
}, "filespage");
}
effect(rxjs.fromEvent(window, "keydown").pipe(