mirror of
https://github.com/hrfee/jfa-go.git
synced 2026-01-18 16:47:42 +01:00
profiles: add ability to directly edit profile JSON
allows for customizing small things, like changing admin status.
This commit is contained in:
@@ -58,6 +58,8 @@ window.availableProfiles = window.availableProfiles || [];
|
||||
window.modals.profiles = new Modal(document.getElementById("modal-user-profiles"));
|
||||
|
||||
window.modals.addProfile = new Modal(document.getElementById("modal-add-profile"));
|
||||
|
||||
window.modals.editProfile = new Modal(document.getElementById("modal-edit-profile"));
|
||||
|
||||
window.modals.announce = new Modal(document.getElementById("modal-announce"));
|
||||
|
||||
|
||||
@@ -85,10 +85,10 @@ export const _upload = (url: string, formData: FormData): void => {
|
||||
req.send(formData);
|
||||
};
|
||||
|
||||
export const _post = (url: string, data: Object, onreadystatechange: (req: XMLHttpRequest) => void, response?: boolean, statusHandler?: (req: XMLHttpRequest) => void, noConnectionError: boolean = false): void => {
|
||||
export const _req = (method: string, url: string, data: Object, onreadystatechange: (req: XMLHttpRequest) => void, response?: boolean, statusHandler?: (req: XMLHttpRequest) => void, noConnectionError: boolean = false): void => {
|
||||
let req = new XMLHttpRequest();
|
||||
if (window.pages) { url = window.pages.Base + url; }
|
||||
req.open("POST", url, true);
|
||||
req.open(method, url, true);
|
||||
if (response) {
|
||||
req.responseType = 'json';
|
||||
}
|
||||
@@ -107,6 +107,10 @@ export const _post = (url: string, data: Object, onreadystatechange: (req: XMLHt
|
||||
req.send(JSON.stringify(data));
|
||||
};
|
||||
|
||||
export const _post = (url: string, data: Object, onreadystatechange: (req: XMLHttpRequest) => void, response?: boolean, statusHandler?: (req: XMLHttpRequest) => void, noConnectionError: boolean = false): void => _req("POST", url, data, onreadystatechange, response, statusHandler, noConnectionError);
|
||||
|
||||
export const _put = (url: string, data: Object, onreadystatechange: (req: XMLHttpRequest) => void, response?: boolean, statusHandler?: (req: XMLHttpRequest) => void, noConnectionError: boolean = false): void => _req("PUT", url, data, onreadystatechange, response, statusHandler, noConnectionError);
|
||||
|
||||
export function _delete(url: string, data: Object, onreadystatechange: (req: XMLHttpRequest) => void, noConnectionError: boolean = false): void {
|
||||
let req = new XMLHttpRequest();
|
||||
if (window.pages) { url = window.pages.Base + url; }
|
||||
|
||||
@@ -1,4 +1,14 @@
|
||||
import { _get, _post, _delete, toggleLoader } from "../modules/common.js";
|
||||
import { _get, _post, _delete, toggleLoader, _put } from "../modules/common.js";
|
||||
import hljs from "highlight.js/lib/core";
|
||||
import json from 'highlight.js/lib/languages/json';
|
||||
import codeInput, { CodeInput } from "@webcoder49/code-input/code-input.mjs";
|
||||
import Template from "@webcoder49/code-input/templates/hljs.mjs";
|
||||
import Indent from "@webcoder49/code-input/plugins/indent.mjs";
|
||||
|
||||
hljs.registerLanguage("json", json);
|
||||
codeInput.registerTemplate("json-highlighted",
|
||||
new Template(hljs, [new Indent()])
|
||||
);
|
||||
|
||||
declare var window: GlobalWindow;
|
||||
|
||||
@@ -32,6 +42,7 @@ class profile implements Profile {
|
||||
private _defaultRadio: HTMLInputElement;
|
||||
private _referralsButton: HTMLSpanElement;
|
||||
private _referralsEnabled: boolean;
|
||||
private _editButton: HTMLButtonElement;
|
||||
|
||||
get name(): string { return this._name.textContent; }
|
||||
set name(v: string) { this._name.textContent = v; }
|
||||
@@ -119,6 +130,7 @@ class profile implements Profile {
|
||||
innerHTML += `
|
||||
<td class="profile-from truncate"></td>
|
||||
<td class="profile-libraries"></td>
|
||||
<td><button class="button ~neutral @low flex flex-row gap-2 profile-edit"><i class="ri-edit-line"></i>${window.lang.strings("edit")}</button></td>
|
||||
<td><span class="button ~critical @low">${window.lang.strings("delete")}</span></td>
|
||||
`;
|
||||
this._row.innerHTML = innerHTML;
|
||||
@@ -132,6 +144,7 @@ class profile implements Profile {
|
||||
if (window.referralsEnabled)
|
||||
this._referralsButton = this._row.querySelector("span.profile-referrals") as HTMLSpanElement;
|
||||
this._fromUser = this._row.querySelector("td.profile-from") as HTMLTableDataCellElement;
|
||||
this._editButton = this._row.querySelector(".profile-edit") as HTMLButtonElement;
|
||||
this._defaultRadio = this._row.querySelector("input[type=radio]") as HTMLInputElement;
|
||||
this._defaultRadio.onclick = () => document.dispatchEvent(new CustomEvent("profiles-default", { detail: this.name }));
|
||||
(this._row.querySelector("span.\\~critical") as HTMLSpanElement).onclick = this.delete;
|
||||
@@ -152,6 +165,7 @@ class profile implements Profile {
|
||||
setOmbiFunc = (ombiFunc: (ombi: boolean) => void) => { this._ombiButton.onclick = () => ombiFunc(this._ombi); }
|
||||
setJellyseerrFunc = (jellyseerrFunc: (jellyseerr: boolean) => void) => { this._jellyseerrButton.onclick = () => jellyseerrFunc(this._jellyseerr); }
|
||||
setReferralFunc = (referralFunc: (enabled: boolean) => void) => { this._referralsButton.onclick = () => referralFunc(this._referralsEnabled); }
|
||||
setEditFunc = (editFunc: (name: string) => void) => { this._editButton.onclick = () => editFunc(this.name); }
|
||||
|
||||
remove = () => { document.dispatchEvent(new CustomEvent("profiles-delete", { detail: this._name })); this._row.remove(); }
|
||||
|
||||
@@ -262,6 +276,7 @@ export class ProfileEditor {
|
||||
}
|
||||
});
|
||||
}
|
||||
this._profiles[name].setEditFunc(this._loadProfileEditor);
|
||||
this._table.appendChild(this._profiles[name].asElement());
|
||||
}
|
||||
}
|
||||
@@ -331,6 +346,64 @@ export class ProfileEditor {
|
||||
window.modals.enableReferralsProfile.show();
|
||||
};
|
||||
|
||||
private _loadProfileEditor = (name: string) => {
|
||||
const urlSafeName = encodeURIComponent(encodeURIComponent(name));
|
||||
_get("/profiles/raw/" + urlSafeName, null, (req: XMLHttpRequest) => {
|
||||
if (req.readyState != 4) return;
|
||||
if (req.status != 200) {
|
||||
window.notifications.customError("errorLoadProfile", window.lang.notif("errorLoadProfile"));
|
||||
return;
|
||||
}
|
||||
const editorContainer = document.getElementById("modal-edit-profile-editor");
|
||||
const editor = document.createElement("code-input") as CodeInput;
|
||||
editor.setAttribute("template", "json-highlighted");
|
||||
editor.setAttribute("language", "json");
|
||||
editor.classList.add("rounded-sm");
|
||||
editor.value = JSON.stringify(req.response, null, 2);
|
||||
editorContainer.replaceChildren(editor);
|
||||
|
||||
const form = document.getElementById("form-edit-profile") as HTMLFormElement;
|
||||
const submit = form.querySelector("input[type=submit]").nextElementSibling;
|
||||
form.onsubmit = (event: SubmitEvent) => {
|
||||
event.preventDefault();
|
||||
let send: any;
|
||||
try {
|
||||
send = JSON.parse(editor.value);
|
||||
} catch(e: any) {
|
||||
submit.classList.add("~critical");
|
||||
submit.classList.remove("~urge");
|
||||
window.notifications.customError("errorInvalidJSON", window.lang.notif("errorInvalidJSON"));
|
||||
setTimeout(() => {
|
||||
submit.classList.add("~urge");
|
||||
submit.classList.remove("~critical");
|
||||
}, 2000);
|
||||
}
|
||||
if (!send) return;
|
||||
_put("/profiles/raw/" + urlSafeName, send, (req: XMLHttpRequest) => {
|
||||
if (req.readyState != 4) return;
|
||||
if (req.status == 200 || req.status == 201 || req.status == 204) {
|
||||
window.notifications.customSuccess("savedProfile", window.lang.notif("savedProfile"));
|
||||
// a 201 implies the profile was renamed. Since reloading profiles doesn't delete missing ones,
|
||||
// we should delete the old one ourselves.
|
||||
if (req.status == 201) {
|
||||
this._profiles[name].remove()
|
||||
delete this._profiles[name];
|
||||
}
|
||||
} else {
|
||||
window.notifications.customError("errorSavedProfile", window.lang.notif("errorSavedProfile"));
|
||||
}
|
||||
window.modals.editProfile.close();
|
||||
// Reload with new info from edits
|
||||
this.load();
|
||||
});
|
||||
};
|
||||
|
||||
window.modals.profiles.close();
|
||||
window.modals.editProfile.show();
|
||||
})
|
||||
|
||||
}
|
||||
|
||||
constructor() {
|
||||
(document.getElementById('setting-profiles') as HTMLSpanElement).onclick = this.load;
|
||||
document.addEventListener("profiles-default", (event: CustomEvent) => {
|
||||
|
||||
@@ -2,6 +2,10 @@ export class ThemeManager {
|
||||
|
||||
private _themeButton: HTMLElement = null;
|
||||
private _metaTag: HTMLMetaElement;
|
||||
|
||||
private _cssLightFiles: HTMLLinkElement[];
|
||||
private _cssDarkFiles: HTMLLinkElement[];
|
||||
|
||||
|
||||
private _beforeTransition = () => {
|
||||
const doc = document.documentElement;
|
||||
@@ -47,6 +51,11 @@ export class ThemeManager {
|
||||
|
||||
constructor(button?: HTMLElement) {
|
||||
this._metaTag = document.querySelector("meta[name=color-scheme]") as HTMLMetaElement;
|
||||
|
||||
this._cssLightFiles = Array.from(document.head.querySelectorAll("link[data-theme=light]")) as Array<HTMLLinkElement>;
|
||||
this._cssDarkFiles = Array.from(document.head.querySelectorAll("link[data-theme=dark]")) as Array<HTMLLinkElement>;
|
||||
this._cssLightFiles.forEach((el) => el.remove());
|
||||
this._cssDarkFiles.forEach((el) => el.remove());
|
||||
const theme = localStorage.getItem("theme");
|
||||
if (theme == "dark") {
|
||||
this._enable(true);
|
||||
@@ -63,11 +72,16 @@ export class ThemeManager {
|
||||
private _toggle = () => {
|
||||
let metaValue = "light dark";
|
||||
this._beforeTransition();
|
||||
if (!document.documentElement.classList.contains('dark')) {
|
||||
const dark = !document.documentElement.classList.contains("dark");
|
||||
if (dark) {
|
||||
document.documentElement.classList.add('dark');
|
||||
metaValue = "dark light";
|
||||
this._cssLightFiles.forEach((el) => el.remove());
|
||||
this._cssDarkFiles.forEach((el) => document.head.appendChild(el));
|
||||
} else {
|
||||
document.documentElement.classList.remove('dark');
|
||||
this._cssDarkFiles.forEach((el) => el.remove());
|
||||
this._cssLightFiles.forEach((el) => document.head.appendChild(el));
|
||||
}
|
||||
localStorage.setItem('theme', document.documentElement.classList.contains('dark') ? "dark" : "light");
|
||||
|
||||
@@ -86,7 +100,14 @@ export class ThemeManager {
|
||||
document.documentElement.classList.remove(opposite);
|
||||
}
|
||||
document.documentElement.classList.add(mode);
|
||||
|
||||
|
||||
if (dark) {
|
||||
this._cssLightFiles.forEach((el) => el.remove());
|
||||
this._cssDarkFiles.forEach((el) => document.head.appendChild(el));
|
||||
} else {
|
||||
this._cssDarkFiles.forEach((el) => el.remove());
|
||||
this._cssLightFiles.forEach((el) => document.head.appendChild(el));
|
||||
}
|
||||
// this._metaTag.setAttribute("content", `${mode} ${opposite}`);
|
||||
};
|
||||
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
"typeRoots": ["./typings", "../node_modules/@types"],
|
||||
"module": "esnext",
|
||||
"moduleResolution": "bundler",
|
||||
"esModuleInterop": true
|
||||
"esModuleInterop": true,
|
||||
"skipLibCheck": true
|
||||
}
|
||||
}
|
||||
|
||||
@@ -111,6 +111,7 @@ declare interface Modals {
|
||||
jellyseerrProfile?: Modal;
|
||||
profiles: Modal;
|
||||
addProfile: Modal;
|
||||
editProfile: Modal;
|
||||
announce: Modal;
|
||||
editor: Modal;
|
||||
customizeEmails: Modal;
|
||||
|
||||
Reference in New Issue
Block a user