|
|
|
import { _get, _post, toggleLoader, notificationBox } from "./modules/common.js";
|
|
|
|
import { lang, LangFile, loadLangSelector } from "./modules/lang.js";
|
setup: flex-ify, light/dark, keep page position on reload
got rid of a bunch of m[l/r/x/y]-x tailwind classes and used more
flex-[row/col] gap-2's. UI should be more consistent in general, and
with the admin UI.
The page you were on is actually read from the URL on reload, however
does not keep settings (implemented just for ease of UI editing,
really).
`missing-colors.js` preprocessor script now applies dark prefixes for
<section>s, but like with cards, does not apply a default ~neutral to
those without, so that <section class=""> looks different to <section
class="~neutral">.
Light/dark selector added to setup too, and the actual mode given to the
browser through CSS `color-scheme` is correct, meaning things like textareas, checkboxes and
controls are now colored according to the theme.
3 months ago
|
|
|
import { ThemeManager } from "./modules/theme.js";
|
ts: move "page" stuff to module
not the happiest with it, but it works alright. PageManager is
instantiated, you pass is Page{} objects, which have a (code)name, page
title, and url, and a show, hide, and shouldSkip function, each
returning a bool. The first two are self explanatory, the last tells you
if the page is disabled for some reason (like on setup some are disabled
if messages are). You can then call load(<(code)name>), or
prev/next(<name>).
3 months ago
|
|
|
import { PageManager } from "./modules/pages.js";
|
|
|
|
|
|
|
|
interface sWindow extends Window {
|
|
|
|
messages: {};
|
|
|
|
}
|
|
|
|
|
|
|
|
declare var window: sWindow;
|
|
|
|
window.URLBase = "";
|
|
|
|
|
setup: flex-ify, light/dark, keep page position on reload
got rid of a bunch of m[l/r/x/y]-x tailwind classes and used more
flex-[row/col] gap-2's. UI should be more consistent in general, and
with the admin UI.
The page you were on is actually read from the URL on reload, however
does not keep settings (implemented just for ease of UI editing,
really).
`missing-colors.js` preprocessor script now applies dark prefixes for
<section>s, but like with cards, does not apply a default ~neutral to
those without, so that <section class=""> looks different to <section
class="~neutral">.
Light/dark selector added to setup too, and the actual mode given to the
browser through CSS `color-scheme` is correct, meaning things like textareas, checkboxes and
controls are now colored according to the theme.
3 months ago
|
|
|
const theme = new ThemeManager(document.getElementById("button-theme"));
|
|
|
|
|
|
|
|
window.notifications = new notificationBox(document.getElementById('notification-box') as HTMLDivElement, 5);
|
|
|
|
|
|
|
|
|
|
|
|
const get = (id: string): HTMLElement => document.getElementById(id);
|
|
|
|
const text = (id: string, val: string) => { document.getElementById(id).textContent = val; };
|
|
|
|
const html = (id: string, val: string) => { document.getElementById(id).innerHTML = val; };
|
|
|
|
|
ts: move "page" stuff to module
not the happiest with it, but it works alright. PageManager is
instantiated, you pass is Page{} objects, which have a (code)name, page
title, and url, and a show, hide, and shouldSkip function, each
returning a bool. The first two are self explanatory, the last tells you
if the page is disabled for some reason (like on setup some are disabled
if messages are). You can then call load(<(code)name>), or
prev/next(<name>).
3 months ago
|
|
|
|
|
|
|
// FIXME: Reuse setting types from ts/modules/settings.ts
|
|
|
|
interface boolEvent extends Event {
|
|
|
|
detail: boolean;
|
|
|
|
}
|
|
|
|
|
|
|
|
class Input {
|
|
|
|
private _el: HTMLInputElement;
|
|
|
|
get value(): string { return ""+this._el.value; }
|
|
|
|
set value(v: string) { this._el.value = v; }
|
|
|
|
// Nothing depends on input, but we add an empty broadcast function so we can just loop over all settings to fix dependents on start.
|
|
|
|
broadcast = () => {}
|
|
|
|
constructor(el: HTMLElement, placeholder?: any, value?: any, depends?: string, dependsTrue?: boolean, section?: string) {
|
|
|
|
this._el = el as HTMLInputElement;
|
|
|
|
if (placeholder) { this._el.placeholder = placeholder; }
|
|
|
|
if (value) { this.value = value; }
|
|
|
|
if (depends) {
|
|
|
|
document.addEventListener(`settings-${section}-${depends}`, (event: boolEvent) => {
|
|
|
|
let el = this._el as HTMLElement;
|
|
|
|
if (el.parentElement.tagName == "LABEL") { el = el.parentElement; }
|
|
|
|
if (event.detail !== dependsTrue) {
|
|
|
|
el.classList.add("unfocused");
|
|
|
|
} else {
|
|
|
|
el.classList.remove("unfocused");
|
|
|
|
}
|
|
|
|
});
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
class Checkbox {
|
|
|
|
private _el: HTMLInputElement;
|
|
|
|
private _hideEl: HTMLElement;
|
|
|
|
get value(): string { return this._el.checked ? "true" : "false"; }
|
|
|
|
set value(v: string) { this._el.checked = (v == "true") ? true : false; }
|
|
|
|
|
|
|
|
private _section: string;
|
|
|
|
private _setting: string;
|
|
|
|
broadcast = () => {
|
|
|
|
let state = this._el.checked;
|
|
|
|
if (this._hideEl.classList.contains("unfocused")) {
|
|
|
|
state = false;
|
|
|
|
}
|
|
|
|
if (this._section && this._setting) {
|
|
|
|
const ev = new CustomEvent(`settings-${this._section}-${this._setting}`, { "detail": state })
|
|
|
|
document.dispatchEvent(ev);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
set onchange(f: () => void) {
|
|
|
|
this._el.addEventListener("change", f);
|
|
|
|
}
|
|
|
|
constructor(el: HTMLElement, depends?: string, dependsTrue?: boolean, section?: string, setting?: string) {
|
|
|
|
this._el = el as HTMLInputElement;
|
|
|
|
this._hideEl = this._el as HTMLElement;
|
setup: flex-ify, light/dark, keep page position on reload
got rid of a bunch of m[l/r/x/y]-x tailwind classes and used more
flex-[row/col] gap-2's. UI should be more consistent in general, and
with the admin UI.
The page you were on is actually read from the URL on reload, however
does not keep settings (implemented just for ease of UI editing,
really).
`missing-colors.js` preprocessor script now applies dark prefixes for
<section>s, but like with cards, does not apply a default ~neutral to
those without, so that <section class=""> looks different to <section
class="~neutral">.
Light/dark selector added to setup too, and the actual mode given to the
browser through CSS `color-scheme` is correct, meaning things like textareas, checkboxes and
controls are now colored according to the theme.
3 months ago
|
|
|
if (this._hideEl.parentElement.tagName == "LABEL") {
|
|
|
|
this._hideEl = this._hideEl.parentElement;
|
|
|
|
} else if (this._hideEl.parentElement.classList.contains("switch")) {
|
|
|
|
if (this._hideEl.parentElement.parentElement.tagName == "LABEL") {
|
|
|
|
this._hideEl = this._hideEl.parentElement.parentElement;
|
|
|
|
} else {
|
|
|
|
this._hideEl = this._hideEl.parentElement;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (section && setting) {
|
|
|
|
this._section = section;
|
|
|
|
this._setting = setting;
|
|
|
|
this._el.onchange = this.broadcast;
|
|
|
|
}
|
|
|
|
if (depends) {
|
|
|
|
document.addEventListener(`settings-${section}-${depends}`, (event: boolEvent) => {
|
|
|
|
if (event.detail !== dependsTrue) {
|
|
|
|
this._hideEl.classList.add("unfocused");
|
|
|
|
this.broadcast();
|
|
|
|
} else {
|
|
|
|
this._hideEl.classList.remove("unfocused");
|
|
|
|
this.broadcast();
|
|
|
|
}
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
setup: flex-ify, light/dark, keep page position on reload
got rid of a bunch of m[l/r/x/y]-x tailwind classes and used more
flex-[row/col] gap-2's. UI should be more consistent in general, and
with the admin UI.
The page you were on is actually read from the URL on reload, however
does not keep settings (implemented just for ease of UI editing,
really).
`missing-colors.js` preprocessor script now applies dark prefixes for
<section>s, but like with cards, does not apply a default ~neutral to
those without, so that <section class=""> looks different to <section
class="~neutral">.
Light/dark selector added to setup too, and the actual mode given to the
browser through CSS `color-scheme` is correct, meaning things like textareas, checkboxes and
controls are now colored according to the theme.
3 months ago
|
|
|
/* if (this._el.hasAttribute("checked")) {
|
|
|
|
this._el.checked = true;
|
|
|
|
} else {
|
|
|
|
this._el.checked = false;
|
setup: flex-ify, light/dark, keep page position on reload
got rid of a bunch of m[l/r/x/y]-x tailwind classes and used more
flex-[row/col] gap-2's. UI should be more consistent in general, and
with the admin UI.
The page you were on is actually read from the URL on reload, however
does not keep settings (implemented just for ease of UI editing,
really).
`missing-colors.js` preprocessor script now applies dark prefixes for
<section>s, but like with cards, does not apply a default ~neutral to
those without, so that <section class=""> looks different to <section
class="~neutral">.
Light/dark selector added to setup too, and the actual mode given to the
browser through CSS `color-scheme` is correct, meaning things like textareas, checkboxes and
controls are now colored according to the theme.
3 months ago
|
|
|
} */
|
|
|
|
this.broadcast();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
class BoolRadios {
|
|
|
|
private _els: NodeListOf<HTMLInputElement>;
|
|
|
|
get value(): string { return this._els[0].checked ? "true" : "false" }
|
|
|
|
set value(v: string) {
|
|
|
|
const bool = (v == "true") ? true : false;
|
|
|
|
this._els[0].checked = bool;
|
|
|
|
this._els[1].checked = !bool;
|
|
|
|
}
|
|
|
|
|
|
|
|
private _section: string;
|
|
|
|
private _setting: string;
|
|
|
|
broadcast = () => {
|
|
|
|
if (this._section && this._setting) {
|
|
|
|
const ev = new CustomEvent(`settings-${this._section}-${this._setting}`, { "detail": this._els[0].checked })
|
|
|
|
document.dispatchEvent(ev);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
constructor(name: string, depends?: string, dependsTrue?: boolean, section?: string, setting?: string) {
|
|
|
|
this._els = document.getElementsByName(name) as NodeListOf<HTMLInputElement>;
|
|
|
|
if (section && setting) {
|
|
|
|
this._section = section;
|
|
|
|
this._setting = setting;
|
|
|
|
this._els[0].onchange = this.broadcast;
|
|
|
|
this._els[1].onchange = this.broadcast;
|
|
|
|
}
|
|
|
|
if (depends) {
|
|
|
|
document.addEventListener(`settings-${section}-${depends}`, (event: boolEvent) => {
|
|
|
|
if (event.detail !== dependsTrue) {
|
|
|
|
if (this._els[0].parentElement.tagName == "LABEL") {
|
|
|
|
this._els[0].parentElement.classList.add("unfocused");
|
|
|
|
}
|
|
|
|
if (this._els[1].parentElement.tagName == "LABEL") {
|
|
|
|
this._els[1].parentElement.classList.add("unfocused");
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
if (this._els[0].parentElement.tagName == "LABEL") {
|
|
|
|
this._els[0].parentElement.classList.remove("unfocused");
|
|
|
|
}
|
|
|
|
if (this._els[1].parentElement.tagName == "LABEL") {
|
|
|
|
this._els[1].parentElement.classList.remove("unfocused");
|
|
|
|
}
|
|
|
|
}
|
|
|
|
});
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// class Radios {
|
|
|
|
// private _el: HTMLInputElement;
|
|
|
|
// get value(): string { return this._el.value; }
|
|
|
|
// set value(v: string) { this._el.value = v; }
|
|
|
|
// constructor(name: string, depends?: string, dependsTrue?: boolean, section?: string) {
|
|
|
|
// this._el = document.getElementsByName(name)[0] as HTMLInputElement;
|
|
|
|
// if (depends) {
|
|
|
|
// document.addEventListener(`settings-${section}-${depends}`, (event: boolEvent) => {
|
|
|
|
// let el = this._el as HTMLElement;
|
|
|
|
// if (el.parentElement.tagName == "LABEL") { el = el.parentElement; }
|
|
|
|
// if (event.detail !== dependsTrue) {
|
|
|
|
// el.classList.add("unfocused");
|
|
|
|
// } else {
|
|
|
|
// el.classList.remove("unfocused");
|
|
|
|
// }
|
|
|
|
// });
|
|
|
|
// }
|
|
|
|
// }
|
|
|
|
// }
|
|
|
|
|
|
|
|
class Select {
|
|
|
|
private _el: HTMLSelectElement;
|
|
|
|
get value(): string { return this._el.value; }
|
|
|
|
set value(v: string) { this._el.value = v; }
|
|
|
|
add = (val: string, label: string) => {
|
|
|
|
const item = document.createElement("option") as HTMLOptionElement;
|
|
|
|
item.value = val;
|
|
|
|
item.textContent = label;
|
|
|
|
this._el.appendChild(item);
|
|
|
|
}
|
|
|
|
set onchange(f: () => void) {
|
|
|
|
this._el.addEventListener("change", f);
|
|
|
|
}
|
|
|
|
|
|
|
|
private _section: string;
|
|
|
|
private _setting: string;
|
|
|
|
broadcast = () => {
|
|
|
|
if (this._section && this._setting) {
|
|
|
|
const ev = new CustomEvent(`settings-${this._section}-${this._setting}`, { "detail": this.value ? true : false })
|
|
|
|
document.dispatchEvent(ev);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
constructor(el: HTMLElement, depends?: string, dependsTrue?: boolean, section?: string, setting?: string) {
|
|
|
|
this._el = el as HTMLSelectElement;
|
|
|
|
if (section && setting) {
|
|
|
|
this._section = section;
|
|
|
|
this._setting = setting;
|
|
|
|
this._el.addEventListener("change", this.broadcast);
|
|
|
|
}
|
|
|
|
if (depends) {
|
|
|
|
document.addEventListener(`settings-${section}-${depends}`, (event: boolEvent) => {
|
|
|
|
let el = this._el as HTMLElement;
|
|
|
|
while (el.tagName != "LABEL") {
|
|
|
|
el = el.parentElement;
|
|
|
|
}
|
|
|
|
if (event.detail !== dependsTrue) {
|
|
|
|
el.classList.add("unfocused");
|
|
|
|
} else {
|
|
|
|
el.classList.remove("unfocused");
|
|
|
|
}
|
|
|
|
});
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
class LangSelect extends Select {
|
|
|
|
constructor(page: string, el: HTMLElement, depends?: string, dependsTrue?: boolean, section?: string, setting?: string) {
|
|
|
|
super(el, depends, dependsTrue, section, setting);
|
|
|
|
_get("/lang/" + page, null, (req: XMLHttpRequest) => {
|
|
|
|
if (req.readyState == 4 && req.status == 200) {
|
|
|
|
for (let code in req.response) {
|
|
|
|
this.add(code, req.response[code]);
|
|
|
|
}
|
|
|
|
this.value = "en-us";
|
|
|
|
}
|
setup: flex-ify, light/dark, keep page position on reload
got rid of a bunch of m[l/r/x/y]-x tailwind classes and used more
flex-[row/col] gap-2's. UI should be more consistent in general, and
with the admin UI.
The page you were on is actually read from the URL on reload, however
does not keep settings (implemented just for ease of UI editing,
really).
`missing-colors.js` preprocessor script now applies dark prefixes for
<section>s, but like with cards, does not apply a default ~neutral to
those without, so that <section class=""> looks different to <section
class="~neutral">.
Light/dark selector added to setup too, and the actual mode given to the
browser through CSS `color-scheme` is correct, meaning things like textareas, checkboxes and
controls are now colored according to the theme.
3 months ago
|
|
|
}, true);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
const replaceLink = (elName: string, sect: string, name: string, url: string, text: string) => html(elName, window.lang.var(sect, name, `<a class="underline" target="_blank" href="${url}">${text}</a>`));
|
|
|
|
|
|
|
|
window.lang = new lang(window.langFile as LangFile);
|
|
|
|
replaceLink("language-description", "language", "description", "https://weblate.jfa-go.com", "Weblate");
|
|
|
|
replaceLink("email-description", "email", "description", "https://mailgun.com", "Mailgun");
|
|
|
|
replaceLink("email-dateformat-notice", "email", "dateFormatNotice", "https://strftime.timpetricola.com/", "strftime.timpetricola.com");
|
|
|
|
replaceLink("updates-description", "updates", "description", "https://builds.hrfee.dev/view/hrfee/jfa-go", "buildrone");
|
|
|
|
replaceLink("messages-description", "messages", "description", "https://wiki.jfa-go.com", "Wiki");
|
|
|
|
replaceLink("password_resets-more-info", "passwordResets", "moreInfo", "https://wiki.jfa-go.com/docs/pwr/", "wiki.jfa-go.com");
|
|
|
|
replaceLink("ombi-stability-warning", "ombi", "stabilityWarning", "https://wiki.jfa-go.com/docs/ombi/", "wiki.jfa-go.com");
|
|
|
|
|
|
|
|
const settings = {
|
|
|
|
"jellyfin": {
|
|
|
|
"type": new Select(get("jellyfin-type")),
|
|
|
|
"server": new Input(get("jellyfin-server")),
|
|
|
|
"public_server": new Input(get("jellyfin-public_server")),
|
|
|
|
"username": new Input(get("jellyfin-username")),
|
|
|
|
"password": new Input(get("jellyfin-password")),
|
|
|
|
"substitute_jellyfin_strings": new Input(get("jellyfin-substitute_jellyfin_strings"))
|
|
|
|
},
|
|
|
|
"updates": {
|
|
|
|
"enabled": new Checkbox(get("updates-enabled"), "", false, "updates", "enabled"),
|
|
|
|
"channel": new Select(get("updates-channel"), "enabled", true, "updates")
|
|
|
|
},
|
|
|
|
"ui": {
|
|
|
|
"host": new Input(get("ui-host")),
|
|
|
|
"port": new Input(get("ui-port")),
|
|
|
|
"url_base": new Input(get("ui-url_base")),
|
|
|
|
"jfa_url": new Input(get("ui-jfa_url")),
|
|
|
|
"theme": new Select(get("ui-theme")),
|
|
|
|
"language-form": new LangSelect("form", get("ui-language-form")),
|
|
|
|
"language-admin": new LangSelect("admin", get("ui-language-admin")),
|
|
|
|
"jellyfin_login": new BoolRadios("ui-jellyfin_login", "", false, "ui", "jellyfin_login"),
|
|
|
|
"admin_only": new Checkbox(get("ui-admin_only"), "jellyfin_login", true, "ui"),
|
|
|
|
"allow_all": new Checkbox(get("ui-allow_all"), "jellyfin_login", true, "ui"),
|
|
|
|
"username": new Input(get("ui-username"), "", "", "jellyfin_login", false, "ui"),
|
|
|
|
"password": new Input(get("ui-password"), "", "", "jellyfin_login", false, "ui"),
|
|
|
|
"email": new Input(get("ui-email"), "", "", "jellyfin_login", false, "ui"),
|
|
|
|
"contact_message": new Input(get("ui-contact_message"), window.messages["ui"]["contact_message"]),
|
|
|
|
"help_message": new Input(get("ui-help_message"), window.messages["ui"]["help_message"]),
|
|
|
|
"success_message": new Input(get("ui-success_message"), window.messages["ui"]["success_message"])
|
|
|
|
},
|
|
|
|
"password_validation": {
|
|
|
|
"enabled": new Checkbox(get("password_validation-enabled"), "", false, "password_validation", "enabled"),
|
|
|
|
"min_length": new Input(get("password_validation-min_length"), "", 8, "enabled", true, "password_validation"),
|
|
|
|
"upper": new Input(get("password_validation-upper"), "", 1, "enabled", true, "password_validation"),
|
|
|
|
"lower": new Input(get("password_validation-lower"), "", 0, "enabled", true, "password_validation"),
|
|
|
|
"number": new Input(get("password_validation-number"), "", 1, "enabled", true, "password_validation"),
|
|
|
|
"special": new Input(get("password_validation-special"), "", 0, "enabled", true, "password_validation")
|
|
|
|
},
|
|
|
|
"messages": {
|
|
|
|
"enabled": new Checkbox(get("messages-enabled"), "", false, "messages", "enabled"),
|
|
|
|
"use_24h": new BoolRadios("email-24h", "enabled", true, "messages"),
|
|
|
|
"date_format": new Input(get("email-date_format"), "", "%d/%m/%y", "enabled", true, "messages"),
|
|
|
|
"message": new Input(get("email-message"), window.messages["messages"]["message"], "", "enabled", true, "messages")
|
|
|
|
},
|
|
|
|
"email": {
|
|
|
|
"language": new LangSelect("email", get("email-language")),
|
|
|
|
"no_username": new Checkbox(get("email-no_username"), "method", true, "email"),
|
|
|
|
"method": new Select(get("email-method"), "", false, "email", "method"),
|
|
|
|
"address": new Input(get("email-address"), "jellyfin@jellyf.in", "", "method", true, "email"),
|
|
|
|
"from": new Input(get("email-from"), "", "Jellyfin", "method", true, "email")
|
|
|
|
},
|
|
|
|
"password_resets": {
|
|
|
|
"enabled": new Checkbox(get("password_resets-enabled"), "", false, "password_resets", "enabled"),
|
|
|
|
"watch_directory": new Input(get("password_resets-watch_directory"), "", "", "enabled", true, "password_resets"),
|
|
|
|
"subject": new Input(get("password_resets-subject"), "", "", "enabled", true, "password_resets"),
|
|
|
|
"link_reset": new Checkbox(get("password_resets-link_reset"), "enabled", true, "password_resets", "link_reset"),
|
|
|
|
"language": new LangSelect("pwr", get("password_resets-language"), "link_reset", true, "password_resets", "language"),
|
|
|
|
"set_password": new Checkbox(get("password_resets-set_password"), "link_reset", true, "password_resets", "set_password")
|
|
|
|
},
|
|
|
|
"notifications": {
|
|
|
|
"enabled": new Checkbox(get("notifications-enabled"))
|
|
|
|
},
|
|
|
|
"user_page": {
|
|
|
|
"enabled": new Checkbox(get("userpage-enabled"))
|
|
|
|
},
|
|
|
|
"welcome_email": {
|
|
|
|
"enabled": new Checkbox(get("welcome_email-enabled"), "", false, "welcome_email", "enabled"),
|
|
|
|
"subject": new Input(get("welcome_email-subject"), "", "", "enabled", true, "welcome_email")
|
|
|
|
},
|
|
|
|
"invite_emails": {
|
|
|
|
"enabled": new Checkbox(get("invite_emails-enabled"), "", false, "invite_emails", "enabled"),
|
|
|
|
"subject": new Input(get("invite_emails-subject"), "", "", "enabled", true, "invite_emails"),
|
|
|
|
},
|
|
|
|
"mailgun": {
|
|
|
|
"api_url": new Input(get("mailgun-api_url")),
|
|
|
|
"api_key": new Input(get("mailgun-api_key"))
|
|
|
|
},
|
|
|
|
"smtp": {
|
|
|
|
"username": new Input(get("smtp-username")),
|
|
|
|
"encryption": new Select(get("smtp-encryption")),
|
|
|
|
"server": new Input(get("smtp-server")),
|
|
|
|
"port": new Input(get("smtp-port")),
|
|
|
|
"password": new Input(get("smtp-password"))
|
|
|
|
},
|
|
|
|
"ombi": {
|
|
|
|
"enabled": new Checkbox(get("ombi-enabled"), "", false, "ombi", "enabled"),
|
|
|
|
"server": new Input(get("ombi-server"), "", "", "enabled", true, "ombi"),
|
|
|
|
"api_key": new Input(get("ombi-api_key"), "", "", "enabled", true, "ombi")
|
|
|
|
},
|
|
|
|
"jellyseerr": {
|
|
|
|
"enabled": new Checkbox(get("jellyseerr-enabled"), "", false, "jellyseerr", "enabled"),
|
|
|
|
"server": new Input(get("jellyseerr-server"), "", "", "enabled", true, "jellyseerr"),
|
|
|
|
"api_key": new Input(get("jellyseerr-api_key"), "", "", "enabled", true, "jellyseerr"),
|
|
|
|
"import_existing": new Checkbox(get("jellyseerr-import_existing"), "enabled", true, "jellyseerr", "import_existing")
|
|
|
|
},
|
|
|
|
"advanced": {
|
|
|
|
"tls": new Checkbox(get("advanced-tls"), "", false, "advanced", "tls"),
|
|
|
|
"tls_port": new Input(get("advanced-tls_port"), "", "", "tls", true, "advanced"),
|
|
|
|
"tls_cert": new Input(get("advanced-tls_cert"), "", "", "tls", true, "advanced"),
|
|
|
|
"tls_key": new Input(get("advanced-tls_key"), "", "", "tls", true, "advanced"),
|
|
|
|
"proxy": new Checkbox(get("advanced-proxy"), "", false, "advanced", "proxy"),
|
|
|
|
"proxy_protocol": new Select(get("advanced-proxy_protocol"), "proxy", true, "advanced"),
|
|
|
|
"proxy_address": new Input(get("advanced-proxy_address"), "", "", "proxy", true, "advanced"),
|
|
|
|
"proxy_user": new Input(get("advanced-proxy_user"), "", "", "proxy", true, "advanced"),
|
|
|
|
"proxy_password": new Input(get("advanced-proxy_password"), "", "", "proxy", true, "advanced")
|
|
|
|
}
|
|
|
|
};
|
|
|
|
const checkTheme = () => {
|
|
|
|
if (settings["ui"]["theme"].value.includes("Dark")) {
|
|
|
|
document.documentElement.classList.add("dark-theme");
|
|
|
|
document.documentElement.classList.remove("light-theme");
|
|
|
|
} else {
|
|
|
|
document.documentElement.classList.add("light-theme");
|
|
|
|
document.documentElement.classList.remove("dark-theme");
|
|
|
|
}
|
|
|
|
};
|
|
|
|
settings["ui"]["theme"].onchange = checkTheme;
|
|
|
|
checkTheme();
|
|
|
|
|
|
|
|
const restartButton = document.getElementById("restart") as HTMLSpanElement;
|
|
|
|
const serialize = () => {
|
|
|
|
toggleLoader(restartButton);
|
|
|
|
let config = {};
|
|
|
|
for (let section in settings) {
|
|
|
|
config[section] = {};
|
|
|
|
for (let setting in settings[section]) {
|
|
|
|
if (settings[section][setting].value) {
|
|
|
|
config[section][setting] = settings[section][setting].value;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
config["restart-program"] = true;
|
|
|
|
_post("/config", config, (req: XMLHttpRequest) => {
|
|
|
|
if (req.readyState == 4) {
|
|
|
|
toggleLoader(restartButton);
|
|
|
|
if (req.status == 500) {
|
|
|
|
if (req.response == null) {
|
|
|
|
const old = restartButton.textContent;
|
|
|
|
restartButton.classList.add("~critical");
|
|
|
|
restartButton.classList.remove("~urge");
|
|
|
|
restartButton.textContent = window.lang.strings("errorUnknown");
|
|
|
|
setTimeout(() => {
|
|
|
|
restartButton.classList.add("~urge");
|
|
|
|
restartButton.classList.remove("~critical");
|
|
|
|
restartButton.textContent = old;
|
|
|
|
}, 5000);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
if (req.response["error"] as string) {
|
|
|
|
const old = restartButton.textContent;
|
|
|
|
restartButton.classList.add("~critical");
|
|
|
|
restartButton.classList.remove("~urge");
|
|
|
|
restartButton.textContent = req.response["error"];
|
|
|
|
setTimeout(() => {
|
|
|
|
restartButton.classList.add("~urge");
|
|
|
|
restartButton.classList.remove("~critical");
|
|
|
|
restartButton.textContent = old;
|
|
|
|
}, 5000);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
restartButton.parentElement.querySelector("span.back").classList.add("unfocused");
|
|
|
|
restartButton.classList.add("unfocused");
|
|
|
|
const refresh = document.getElementById("refresh") as HTMLSpanElement;
|
|
|
|
refresh.classList.remove("unfocused");
|
|
|
|
refresh.onclick = () => {
|
|
|
|
let host = window.location.href.split("#")[0].split("?")[0] + settings["ui"]["url_base"].value;
|
|
|
|
window.location.href = host;
|
|
|
|
};
|
|
|
|
}
|
|
|
|
}, true, (req: XMLHttpRequest) => {
|
|
|
|
if (req.status == 0) {
|
|
|
|
window.notifications.customError("connectionError", window.lang.strings("errorConnectionRefused"));
|
|
|
|
}
|
|
|
|
});
|
|
|
|
}
|
|
|
|
restartButton.onclick = serialize;
|
|
|
|
|
|
|
|
const relatedToEmail = Array.from(document.getElementsByClassName("related-to-email"));
|
|
|
|
const emailMethodChange = () => {
|
|
|
|
const val = settings["email"]["method"].value;
|
|
|
|
const smtp = document.getElementById("email-smtp");
|
|
|
|
const mailgun = document.getElementById("email-mailgun");
|
|
|
|
const emailSect = document.getElementById("email-sect");
|
|
|
|
const enabled = settings["messages"]["enabled"].value;
|
|
|
|
if (enabled == "false") {
|
|
|
|
for (let el of relatedToEmail) {
|
|
|
|
el.classList.add("hidden");
|
|
|
|
}
|
|
|
|
emailSect.classList.add("unfocused");
|
|
|
|
return;
|
|
|
|
} else {
|
|
|
|
for (let el of relatedToEmail) {
|
|
|
|
el.classList.remove("hidden");
|
|
|
|
}
|
|
|
|
emailSect.classList.remove("unfocused");
|
|
|
|
}
|
|
|
|
if (val == "smtp") {
|
|
|
|
smtp.classList.remove("unfocused");
|
|
|
|
mailgun.classList.add("unfocused");
|
|
|
|
} else if (val == "mailgun") {
|
|
|
|
mailgun.classList.remove("unfocused");
|
|
|
|
smtp.classList.add("unfocused");
|
|
|
|
for (let el of relatedToEmail) {
|
|
|
|
el.classList.remove("hidden");
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
mailgun.classList.add("unfocused");
|
|
|
|
smtp.classList.add("unfocused");
|
|
|
|
}
|
|
|
|
};
|
|
|
|
settings["email"]["method"].onchange = emailMethodChange;
|
|
|
|
settings["messages"]["enabled"].onchange = emailMethodChange;
|
|
|
|
emailMethodChange();
|
|
|
|
|
setup: flex-ify, light/dark, keep page position on reload
got rid of a bunch of m[l/r/x/y]-x tailwind classes and used more
flex-[row/col] gap-2's. UI should be more consistent in general, and
with the admin UI.
The page you were on is actually read from the URL on reload, however
does not keep settings (implemented just for ease of UI editing,
really).
`missing-colors.js` preprocessor script now applies dark prefixes for
<section>s, but like with cards, does not apply a default ~neutral to
those without, so that <section class=""> looks different to <section
class="~neutral">.
Light/dark selector added to setup too, and the actual mode given to the
browser through CSS `color-scheme` is correct, meaning things like textareas, checkboxes and
controls are now colored according to the theme.
3 months ago
|
|
|
const getParentCard = (el: HTMLElement): HTMLDivElement => {
|
|
|
|
let pEl = el.parentElement;
|
|
|
|
while (pEl.tagName != "html") {
|
|
|
|
if (pEl.classList.contains("card")) return pEl as HTMLDivElement;
|
|
|
|
pEl = pEl.parentElement;
|
|
|
|
}
|
|
|
|
return pEl as HTMLDivElement;
|
|
|
|
};
|
|
|
|
|
|
|
|
const jellyfinLoginAccessChange = () => {
|
|
|
|
const adminOnly = settings["ui"]["admin_only"].value == "true";
|
|
|
|
const allowAll = settings["ui"]["allow_all"].value == "true";
|
|
|
|
const adminOnlyEl = document.getElementById("ui-admin_only") as HTMLInputElement;
|
setup: flex-ify, light/dark, keep page position on reload
got rid of a bunch of m[l/r/x/y]-x tailwind classes and used more
flex-[row/col] gap-2's. UI should be more consistent in general, and
with the admin UI.
The page you were on is actually read from the URL on reload, however
does not keep settings (implemented just for ease of UI editing,
really).
`missing-colors.js` preprocessor script now applies dark prefixes for
<section>s, but like with cards, does not apply a default ~neutral to
those without, so that <section class=""> looks different to <section
class="~neutral">.
Light/dark selector added to setup too, and the actual mode given to the
browser through CSS `color-scheme` is correct, meaning things like textareas, checkboxes and
controls are now colored according to the theme.
3 months ago
|
|
|
const allowAllEl = document.getElementById("ui-allow_all") as HTMLInputElement;
|
|
|
|
const nextButton = getParentCard(adminOnlyEl).querySelector("span.next") as HTMLSpanElement;
|
|
|
|
if (adminOnly && !allowAll) {
|
setup: flex-ify, light/dark, keep page position on reload
got rid of a bunch of m[l/r/x/y]-x tailwind classes and used more
flex-[row/col] gap-2's. UI should be more consistent in general, and
with the admin UI.
The page you were on is actually read from the URL on reload, however
does not keep settings (implemented just for ease of UI editing,
really).
`missing-colors.js` preprocessor script now applies dark prefixes for
<section>s, but like with cards, does not apply a default ~neutral to
those without, so that <section class=""> looks different to <section
class="~neutral">.
Light/dark selector added to setup too, and the actual mode given to the
browser through CSS `color-scheme` is correct, meaning things like textareas, checkboxes and
controls are now colored according to the theme.
3 months ago
|
|
|
allowAllEl.disabled = true;
|
|
|
|
adminOnlyEl.disabled = false;
|
|
|
|
nextButton.removeAttribute("disabled");
|
|
|
|
} else if (!adminOnly && allowAll) {
|
|
|
|
adminOnlyEl.disabled = true;
|
setup: flex-ify, light/dark, keep page position on reload
got rid of a bunch of m[l/r/x/y]-x tailwind classes and used more
flex-[row/col] gap-2's. UI should be more consistent in general, and
with the admin UI.
The page you were on is actually read from the URL on reload, however
does not keep settings (implemented just for ease of UI editing,
really).
`missing-colors.js` preprocessor script now applies dark prefixes for
<section>s, but like with cards, does not apply a default ~neutral to
those without, so that <section class=""> looks different to <section
class="~neutral">.
Light/dark selector added to setup too, and the actual mode given to the
browser through CSS `color-scheme` is correct, meaning things like textareas, checkboxes and
controls are now colored according to the theme.
3 months ago
|
|
|
allowAllEl.disabled = false;
|
|
|
|
nextButton.removeAttribute("disabled");
|
|
|
|
} else {
|
|
|
|
adminOnlyEl.disabled = false;
|
setup: flex-ify, light/dark, keep page position on reload
got rid of a bunch of m[l/r/x/y]-x tailwind classes and used more
flex-[row/col] gap-2's. UI should be more consistent in general, and
with the admin UI.
The page you were on is actually read from the URL on reload, however
does not keep settings (implemented just for ease of UI editing,
really).
`missing-colors.js` preprocessor script now applies dark prefixes for
<section>s, but like with cards, does not apply a default ~neutral to
those without, so that <section class=""> looks different to <section
class="~neutral">.
Light/dark selector added to setup too, and the actual mode given to the
browser through CSS `color-scheme` is correct, meaning things like textareas, checkboxes and
controls are now colored according to the theme.
3 months ago
|
|
|
allowAllEl.disabled = false;
|
|
|
|
nextButton.setAttribute("disabled", "true")
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
settings["ui"]["admin_only"].onchange = jellyfinLoginAccessChange;
|
|
|
|
settings["ui"]["allow_all"].onchange = jellyfinLoginAccessChange;
|
|
|
|
jellyfinLoginAccessChange();
|
|
|
|
|
|
|
|
const embyHidePWR = () => {
|
|
|
|
const pwr = document.getElementById("password-resets");
|
|
|
|
const val = settings["jellyfin"]["type"].value;
|
|
|
|
if (val == "jellyfin") {
|
|
|
|
pwr.classList.remove("hidden");
|
|
|
|
} else if (val == "emby") {
|
|
|
|
pwr.classList.add("hidden");
|
|
|
|
}
|
|
|
|
}
|
|
|
|
settings["jellyfin"]["type"].onchange = embyHidePWR;
|
|
|
|
embyHidePWR();
|
|
|
|
|
|
|
|
(window as any).settings = settings;
|
|
|
|
|
|
|
|
for (let section in settings) {
|
|
|
|
for (let setting in settings[section]) {
|
|
|
|
settings[section][setting].broadcast();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
ts: move "page" stuff to module
not the happiest with it, but it works alright. PageManager is
instantiated, you pass is Page{} objects, which have a (code)name, page
title, and url, and a show, hide, and shouldSkip function, each
returning a bool. The first two are self explanatory, the last tells you
if the page is disabled for some reason (like on setup some are disabled
if messages are). You can then call load(<(code)name>), or
prev/next(<name>).
3 months ago
|
|
|
let pages = new PageManager({
|
|
|
|
hideOthersOnPageShow: true,
|
|
|
|
defaultName: "welcome",
|
|
|
|
defaultTitle: "Setup - jfa-go",
|
|
|
|
});
|
|
|
|
|
setup: flex-ify, light/dark, keep page position on reload
got rid of a bunch of m[l/r/x/y]-x tailwind classes and used more
flex-[row/col] gap-2's. UI should be more consistent in general, and
with the admin UI.
The page you were on is actually read from the URL on reload, however
does not keep settings (implemented just for ease of UI editing,
really).
`missing-colors.js` preprocessor script now applies dark prefixes for
<section>s, but like with cards, does not apply a default ~neutral to
those without, so that <section class=""> looks different to <section
class="~neutral">.
Light/dark selector added to setup too, and the actual mode given to the
browser through CSS `color-scheme` is correct, meaning things like textareas, checkboxes and
controls are now colored according to the theme.
3 months ago
|
|
|
const cards = Array.from(document.getElementsByClassName("page-container")[0].querySelectorAll(".card.sectioned")) as Array<HTMLDivElement>;
|
|
|
|
(window as any).cards = cards;
|
|
|
|
|
|
|
|
(() => {
|
|
|
|
for (let i = 0; i < cards.length; i++) {
|
|
|
|
const card = cards[i];
|
|
|
|
const back = card.getElementsByClassName("back")[0] as HTMLSpanElement;
|
|
|
|
const next = card.getElementsByClassName("next")[0] as HTMLSpanElement;
|
|
|
|
const titleEl = cards[i].querySelector("span.heading") as HTMLElement;
|
|
|
|
let title = titleEl.textContent.replace("/", "_").replace(" ", "-");
|
|
|
|
if (titleEl.classList.contains("welcome")) {
|
|
|
|
title = "";
|
|
|
|
}
|
ts: move "page" stuff to module
not the happiest with it, but it works alright. PageManager is
instantiated, you pass is Page{} objects, which have a (code)name, page
title, and url, and a show, hide, and shouldSkip function, each
returning a bool. The first two are self explanatory, the last tells you
if the page is disabled for some reason (like on setup some are disabled
if messages are). You can then call load(<(code)name>), or
prev/next(<name>).
3 months ago
|
|
|
pages.setPage({
|
|
|
|
name: title,
|
|
|
|
title: titleEl.textContent + " - jfa-go",
|
|
|
|
url: "/#" + title,
|
|
|
|
show: () => {
|
|
|
|
cards[i].classList.remove("unfocused");
|
|
|
|
return true;
|
|
|
|
},
|
|
|
|
hide: () => {
|
|
|
|
cards[i].classList.add("unfocused");
|
|
|
|
return true;
|
|
|
|
},
|
|
|
|
shouldSkip: () => {
|
|
|
|
return cards[i].classList.contains("hidden");
|
|
|
|
},
|
|
|
|
});
|
|
|
|
if (back) back.addEventListener("click", () => pages.prev(title));
|
|
|
|
if (next) next.addEventListener("click", () => {
|
|
|
|
if (next.hasAttribute("disabled")) return;
|
|
|
|
pages.next(title);
|
|
|
|
});
|
|
|
|
}
|
|
|
|
})();
|
|
|
|
|
|
|
|
(() => {
|
|
|
|
const button = document.getElementById("jellyfin-test-connection") as HTMLSpanElement;
|
|
|
|
const ogText = button.textContent;
|
|
|
|
const nextButton = button.parentElement.querySelector("span.next") as HTMLSpanElement;
|
|
|
|
button.onclick = () => {
|
|
|
|
toggleLoader(button);
|
|
|
|
let send = {
|
|
|
|
"type": settings["jellyfin"]["type"].value,
|
|
|
|
"server": settings["jellyfin"]["server"].value,
|
|
|
|
"username": settings["jellyfin"]["username"].value,
|
|
|
|
"password": settings["jellyfin"]["password"].value,
|
|
|
|
"proxy": settings["advanced"]["proxy"].value == "true",
|
|
|
|
"proxy_protocol": settings["advanced"]["proxy_protocol"].value,
|
|
|
|
"proxy_address": settings["advanced"]["proxy_address"].value,
|
|
|
|
"proxy_user": settings["advanced"]["proxy_user"].value,
|
|
|
|
"proxy_password": settings["advanced"]["proxy_password"].value
|
|
|
|
};
|
|
|
|
_post("/jellyfin/test", send, (req: XMLHttpRequest) => {
|
|
|
|
if (req.readyState == 4) {
|
|
|
|
toggleLoader(button);
|
|
|
|
if (req.status != 200) {
|
|
|
|
nextButton.setAttribute("disabled", "");
|
|
|
|
button.classList.add("~critical");
|
|
|
|
button.classList.remove("~urge");
|
|
|
|
setTimeout(() => {
|
|
|
|
button.textContent = ogText;
|
|
|
|
button.classList.add("~urge");
|
|
|
|
button.classList.remove("~critical");
|
|
|
|
}, 5000);
|
|
|
|
const errorMsg = req.response["error"] as string;
|
|
|
|
if (!errorMsg) {
|
|
|
|
button.textContent = window.lang.strings("error");
|
|
|
|
} else {
|
|
|
|
button.textContent = window.lang.strings(errorMsg);
|
|
|
|
}
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
nextButton.removeAttribute("disabled");
|
|
|
|
button.textContent = window.lang.strings("success");
|
|
|
|
button.classList.add("~positive");
|
|
|
|
button.classList.remove("~urge");
|
|
|
|
setTimeout(() => {
|
|
|
|
button.textContent = ogText;
|
|
|
|
button.classList.add("~urge");
|
|
|
|
button.classList.remove("~positive");
|
|
|
|
}, 5000);
|
|
|
|
}
|
|
|
|
}, true, (req: XMLHttpRequest) => {
|
|
|
|
if (req.status == 0) {
|
|
|
|
window.notifications.customError("connectionError", window.lang.strings("errorConnectionRefused"));
|
|
|
|
}
|
|
|
|
});
|
|
|
|
};
|
|
|
|
})();
|
|
|
|
|
|
|
|
loadLangSelector("setup");
|
ts: move "page" stuff to module
not the happiest with it, but it works alright. PageManager is
instantiated, you pass is Page{} objects, which have a (code)name, page
title, and url, and a show, hide, and shouldSkip function, each
returning a bool. The first two are self explanatory, the last tells you
if the page is disabled for some reason (like on setup some are disabled
if messages are). You can then call load(<(code)name>), or
prev/next(<name>).
3 months ago
|
|
|
|
|
|
|
pages.load(window.location.hash.replace("#", ""));
|