diff --git a/css/base.css b/css/base.css
index 684be76..7c3b8cc 100644
--- a/css/base.css
+++ b/css/base.css
@@ -3,6 +3,7 @@
@import "modal.css";
@import "dark.css";
@import "tooltip.css";
+@import "loader.css";
:root {
--border-width-default: 2px;
@@ -233,7 +234,7 @@ sup.\~critical, .text-critical {
padding-bottom: 0.1rem;
margin-left: 0.5rem;
margin-right: 1rem;
- max-width: 50%;
+ max-width: 75%;
}
.stealth-input-hidden {
diff --git a/css/loader.css b/css/loader.css
new file mode 100644
index 0000000..2b7951a
--- /dev/null
+++ b/css/loader.css
@@ -0,0 +1,40 @@
+.loader {
+ height: auto;
+ color: rgba(0, 0, 0, 0);
+}
+
+.loader .dot {
+ --diameter: 0.5rem;
+ --radius: calc(var(--diameter) / 2);
+ --deviation: 20%;
+ height: var(--diameter);
+ width: var(--diameter);
+ background-color: var(--color-content);
+ border-radius: 50%;
+ position: absolute;
+ left: calc(50% - var(--radius));
+ animation: osc 1s cubic-bezier(.72,.16,.31,.97) infinite;
+}
+.loader.loader-sm .dot {
+ --deviation: 10%;
+}
+
+@keyframes osc {
+ 25% {
+ left: calc(50% + var(--deviation) - var(--radius));
+ }
+ 75% {
+ left: calc(50% - var(--deviation));
+ }
+ 0%, 50%, 100% {
+ left: calc(50% - var(--radius));
+ }
+/*
+ 0%, 100% {
+ left: calc(50% - var(--deviation))
+ }
+ 50% {
+ left: calc(50% + var(--deviation) - var(--radius));
+ }
+ */
+}
diff --git a/html/admin.html b/html/admin.html
index a1a3440..5bff46c 100644
--- a/html/admin.html
+++ b/html/admin.html
@@ -5,6 +5,10 @@
{{ template "header.html" . }}
Admin - jfa-go
@@ -15,15 +19,22 @@
Login
-
+
@@ -38,7 +49,7 @@
-
-
@@ -202,20 +224,7 @@
Last Active |
-
-
- |
- Person Admin |
- |
- 13/12/20 00:39 |
-
-
- |
- Other person |
- |
- 12/12/20 17:46 |
-
-
+
diff --git a/ts/admin.ts b/ts/admin.ts
index 05f96a3..58e8887 100644
--- a/ts/admin.ts
+++ b/ts/admin.ts
@@ -2,7 +2,8 @@ import { toggleTheme, loadTheme } from "./modules/theme.js";
import { Modal } from "./modules/modal.js";
import { Tabs } from "./modules/tabs.js";
import { inviteList, createInvite } from "./modules/invites.js";
-import { _post, notificationBox, whichAnimationEvent } from "./modules/common.js";
+import { accountsList } from "./modules/accounts.js";
+import { _post, notificationBox, whichAnimationEvent, toggleLoader } from "./modules/common.js";
loadTheme();
(document.getElementById('button-theme') as HTMLSpanElement).onclick = toggleTheme;
@@ -13,75 +14,37 @@ window.token = "";
window.availableProfiles = window.availableProfiles || [];
+// load modals
+(() => {
+ window.modals = {} as Modals;
+
+ window.modals.login = new Modal(document.getElementById('modal-login'), true);
+
+ window.modals.addUser = new Modal(document.getElementById('modal-add-user'));
+
+ window.modals.about = new Modal(document.getElementById('modal-about'));
+ (document.getElementById('setting-about') as HTMLSpanElement).onclick = window.modals.about.toggle;
+
+ window.modals.modifyUser = new Modal(document.getElementById('modal-modify-user'));
+
+ window.modals.deleteUser = new Modal(document.getElementById('modal-delete-user'));
+
+ window.modals.settingsRestart = new Modal(document.getElementById('modal-restart'));
+
+ window.modals.settingsRefresh = new Modal(document.getElementById('modal-refresh'));
+
+ window.modals.ombiDefaults = new Modal(document.getElementById('modal-ombi-defaults'));
+ document.getElementById('form-ombi-defaults').addEventListener('submit', window.modals.ombiDefaults.close);
+})();
+
var inviteCreator = new createInvite();
+var accounts = new accountsList();
window.invites = new inviteList();
window.notifications = new notificationBox(document.getElementById('notification-box') as HTMLDivElement, 5);
-
-const loadAccounts = function () {
- const rows: HTMLTableRowElement[] = Array.from(document.getElementById("accounts-list").children);
- const selectAll = document.getElementById("accounts-select-all") as HTMLInputElement;
- selectAll.onchange = () => {
- for (let i = 0; i < rows.length; i++) {
- (rows[i].querySelector("input[type=checkbox]") as HTMLInputElement).checked = selectAll.checked;
- }
- }
-
- const checkAllChecks = (state: boolean): boolean => {
- let s = true;
- for (let i = 0; i < rows.length; i++) {
- const selectCheck = rows[i].querySelector("input[type=checkbox]") as HTMLInputElement;
- if (selectCheck.checked != state) {
- s = false;
- break;
- }
- }
- return s
- }
-
- for (let i = 0; i < rows.length; i++) {
- const row = rows[i];
- const selectCheck = row.querySelector("input[type=checkbox]") as HTMLInputElement;
- selectCheck.addEventListener("change", () => {
- if (checkAllChecks(false)) {
- selectAll.indeterminate = false;
- selectAll.checked = false;
- } else if (checkAllChecks(true)) {
- selectAll.checked = true;
- selectAll.indeterminate = false;
- } else {
- selectAll.indeterminate = true;
- selectAll.checked = false;
- }
-
-
- });
- const editButton = row.querySelector(".icon") as HTMLElement;
- const emailInput = row.querySelector(".input") as HTMLInputElement;
- const outerClickListener = (event: Event) => {
- if (!(event.target instanceof HTMLElement && (emailInput.contains(event.target) || editButton.contains(event.target)))) {
- emailInput.classList.toggle('stealth-input-hidden');
- emailInput.readOnly = !emailInput.readOnly;
- editButton.classList.toggle('ri-edit-line');
- editButton.classList.toggle('ri-check-line');
- document.removeEventListener('click', outerClickListener);
- }
- };
- editButton.onclick = function () {
- emailInput.classList.toggle('stealth-input-hidden');
- emailInput.readOnly = !emailInput.readOnly;
- editButton.classList.toggle('ri-edit-line');
- editButton.classList.toggle('ri-check-line');
- if (editButton.classList.contains('ri-check-line')) {
- document.addEventListener('click', outerClickListener);
- }
- };
- }
-};
-
-const modifySettingsSource = function () {
+/*const modifySettingsSource = function () {
const profile = document.getElementById('radio-use-profile') as HTMLInputElement;
const user = document.getElementById('radio-use-user') as HTMLInputElement;
const profileSelect = document.getElementById('modify-user-profiles') as HTMLDivElement;
@@ -92,64 +55,16 @@ const modifySettingsSource = function () {
(profile.nextElementSibling as HTMLSpanElement).classList.toggle('!high');
profileSelect.classList.toggle('unfocused');
userSelect.classList.toggle('unfocused');
-}
-
-const radioUseProfile = document.getElementById('radio-use-profile') as HTMLInputElement;
-radioUseProfile.addEventListener("change", modifySettingsSource);
-radioUseProfile.checked = true;
-const radioUseUser = document.getElementById('radio-use-user') as HTMLInputElement;
-radioUseUser.addEventListener("change", modifySettingsSource);
-radioUseUser.checked = false;
-
-const checkDeleteUserNotify = function () {
- if ((document.getElementById('delete-user-notify') as HTMLInputElement).checked) {
- document.getElementById('textarea-delete-user').classList.remove('unfocused');
- } else {
- document.getElementById('textarea-delete-user').classList.add('unfocused');
- }
-};
-
-(document.getElementById('delete-user-notify') as HTMLInputElement).onchange = checkDeleteUserNotify;
-checkDeleteUserNotify();
-
+}*/
// load tabs
window.tabs = new Tabs();
-for (let tabID of ["invitesTab", "accountsTab", "settingsTab"]) {
+for (let tabID of ["invitesTab", "settingsTab"]) {
window.tabs.addTab(tabID);
}
-window.tabs.addTab("accountsTab", loadAccounts);
+window.tabs.addTab("accountsTab", null, accounts.reload);
-// load modals
-(() => {
- window.modals = {} as Modals;
-
- window.modals.login = new Modal(document.getElementById('modal-login'), true);
-
- window.modals.addUser = new Modal(document.getElementById('modal-add-user'));
- (document.getElementById('accounts-add-user') as HTMLSpanElement).onclick = window.modals.addUser.toggle;
- document.getElementById('form-add-user').addEventListener('submit', window.modals.addUser.close);
-
- window.modals.about = new Modal(document.getElementById('modal-about'));
- (document.getElementById('setting-about') as HTMLSpanElement).onclick = window.modals.about.toggle;
-
- window.modals.modifyUser = new Modal(document.getElementById('modal-modify-user'));
- document.getElementById('form-modify-user').addEventListener('submit', window.modals.modifyUser.close);
- (document.getElementById('accounts-modify-user') as HTMLSpanElement).onclick = window.modals.modifyUser.toggle;
-
- window.modals.deleteUser = new Modal(document.getElementById('modal-delete-user'));
- document.getElementById('form-delete-user').addEventListener('submit', window.modals.deleteUser.close);
- (document.getElementById('accounts-delete-user') as HTMLSpanElement).onclick = window.modals.deleteUser.toggle;
-
- window.modals.settingsRestart = new Modal(document.getElementById('modal-restart'));
-
- window.modals.settingsRefresh = new Modal(document.getElementById('modal-refresh'));
-
- window.modals.ombiDefaults = new Modal(document.getElementById('modal-ombi-defaults'));
- document.getElementById('form-ombi-defaults').addEventListener('submit', window.modals.ombiDefaults.close);
-})();
-
-function login(username: string, password: string) {
+function login(username: string, password: string, run?: (state?: number) => void) {
const req = new XMLHttpRequest();
req.responseType = 'json';
let url = window.URLBase;
@@ -183,41 +98,28 @@ function login(username: string, password: string) {
window.token = data["token"];
window.modals.login.close();
window.invites.reload();
- setInterval(window.invites.reload, 30*1000);
+ accounts.reload();
+ setInterval(() => { window.invites.reload(); accounts.reload(); }, 30*1000);
document.getElementById("logout-button").classList.remove("unfocused");
-
- /*generateInvites();
- setInterval((): void => generateInvites(), 60 * 1000);
- addOptions(30, document.getElementById('days') as HTMLSelectElement);
- addOptions(24, document.getElementById('hours') as HTMLSelectElement);
- const minutes = document.getElementById('minutes') as HTMLSelectElement;
- addOptions(59, minutes);
- minutes.value = "30";
- checkDuration();
- if (modal) {
- window.Modals.login.hide();
- }
- Focus(document.getElementById('logoutButton'));
- }
- if (run) {
- run(+this.status);
- }*/
}
+ if (run) { run(+this.status); }
}
};
req.send();
}
-document.getElementById('form-login').addEventListener('submit', (event: Event) => {
+(document.getElementById('form-login') as HTMLFormElement).onsubmit = (event: SubmitEvent) => {
event.preventDefault();
+ const button = (event.target as HTMLElement).querySelector(".submit") as HTMLSpanElement;
const username = (document.getElementById("login-user") as HTMLInputElement).value;
const password = (document.getElementById("login-password") as HTMLInputElement).value;
if (!username || !password) {
window.notifications.customError("loginError", "The username and/or password were left blank.");
return;
}
- login(username, password);
-});
+ toggleLoader(button);
+ login(username, password, () => toggleLoader(button));
+};
login("", "");
@@ -228,3 +130,4 @@ login("", "");
return false;
}
});
+
diff --git a/ts/modules/accounts.ts b/ts/modules/accounts.ts
new file mode 100644
index 0000000..80c1593
--- /dev/null
+++ b/ts/modules/accounts.ts
@@ -0,0 +1,409 @@
+import { _get, _post, _delete, toggleLoader } from "../modules/common.js";
+
+interface User {
+ id: string;
+ name: string;
+ email: string | undefined;
+ last_active: string;
+ admin: boolean;
+}
+
+class user implements User {
+ private _row: HTMLTableRowElement;
+ private _check: HTMLInputElement;
+ private _username: HTMLSpanElement;
+ private _admin: HTMLSpanElement;
+ private _email: HTMLInputElement;
+ private _emailAddress: string;
+ private _emailEditButton: HTMLElement;
+ private _lastActive: HTMLTableDataCellElement;
+ id: string;
+ private _selected: boolean;
+
+ get selected(): boolean { return this._selected; }
+ set selected(state: boolean) {
+ this._selected = state;
+ this._check.checked = state;
+ state ? document.dispatchEvent(this._checkEvent) : document.dispatchEvent(this._uncheckEvent);
+ }
+
+ get name(): string { return this._username.textContent; }
+ set name(value: string) { this._username.textContent = value; }
+
+ get admin(): boolean { return this._admin.classList.contains("chip"); }
+ set admin(state: boolean) {
+ if (state) {
+ this._admin.classList.add("chip", "~info", "ml-1");
+ this._admin.textContent = "Admin";
+ } else {
+ this._admin.classList.remove("chip", "~info", "ml-1");
+ this._admin.textContent = ""
+ }
+ }
+
+ get email(): string { return this._emailAddress; }
+ set email(value: string) { this._email.value = value; this._emailAddress = value; }
+
+ get last_active(): string { return this._lastActive.textContent; }
+ set last_active(value: string) { this._lastActive.textContent = value; }
+
+ private _checkEvent = new CustomEvent("accountCheckEvent");
+ private _uncheckEvent = new CustomEvent("accountUncheckEvent");
+
+ constructor(user: User) {
+ this._row = document.createElement("tr") as HTMLTableRowElement;
+ this._row.innerHTML = `
+ |
+ |
+ |
+ |
+ `;
+ this._check = this._row.querySelector("input[type=checkbox]") as HTMLInputElement;
+ this._username = this._row.querySelector(".accounts-username") as HTMLSpanElement;
+ this._admin = this._row.querySelector(".accounts-admin") as HTMLSpanElement;
+ this._email = this._row.querySelector(".accounts-email") as HTMLInputElement;
+ this._emailEditButton = this._row.querySelector(".accounts-email-edit") as HTMLElement;
+ this._lastActive = this._row.querySelector(".accounts-last-active") as HTMLTableDataCellElement;
+ this._check.onchange = () => { this.selected = this._check.checked; }
+
+ const toggleStealthInput = () => {
+ this._email.classList.toggle("stealth-input-hidden");
+ this._email.readOnly = !this._email.readOnly;
+ this._emailEditButton.classList.toggle("ri-check-line");
+ this._emailEditButton.classList.toggle("ri-edit-line");
+ };
+ const outerClickListener = (event: Event) => {
+ if (!(event.target instanceof HTMLElement && (this._email.contains(event.target) || this._emailEditButton.contains(event.target)))) {
+ toggleStealthInput();
+ this.email = this.email;
+ document.removeEventListener("click", outerClickListener);
+ }
+ };
+ this._emailEditButton.onclick = () => {
+ if (this._email.classList.contains("stealth-input-hidden")) {
+ document.addEventListener('click', outerClickListener);
+ } else {
+ this._updateEmail();
+ document.removeEventListener('click', outerClickListener);
+ }
+ toggleStealthInput();
+ };
+
+ this.update(user);
+ }
+
+ private _updateEmail = () => {
+ let oldEmail = this.email;
+ this.email = this._email.value;
+ let send = {};
+ send[this.id] = this.email;
+ _post("/users/emails", send, (req: XMLHttpRequest) => {
+ if (req.readyState == 4) {
+ if (req.status == 200) {
+ window.notifications.customPositive("emailChanged", "Success:", `Changed email address of "${this.name}".`);
+ } else {
+ this.email = oldEmail;
+ window.notifications.customError("emailChanged", `Couldn't change email address of "${this.name}".`);
+ }
+ }
+ });
+ }
+
+ update = (user: User) => {
+ this.id = user.id;
+ this.name = user.name;
+ this.email = user.email || "";
+ this.last_active = user.last_active;
+ this.admin = user.admin;
+ }
+
+ asElement = (): HTMLTableRowElement => { return this._row; }
+ remove = () => {
+ if (this.selected) {
+ document.dispatchEvent(this._uncheckEvent);
+ }
+ this._row.remove();
+ }
+}
+
+
+
+
+export class accountsList {
+ private _table = document.getElementById("accounts-list") as HTMLTableSectionElement;
+
+ private _addUserButton = document.getElementById("accounts-add-user") as HTMLSpanElement;
+ private _deleteUser = document.getElementById("accounts-delete-user") as HTMLSpanElement;
+ private _deleteNotify = document.getElementById("delete-user-notify") as HTMLInputElement;
+ private _deleteReason = document.getElementById("textarea-delete-user") as HTMLTextAreaElement;
+ private _modifySettings = document.getElementById("accounts-modify-user") as HTMLSpanElement;
+ private _modifySettingsProfile = document.getElementById("radio-use-profile") as HTMLInputElement;
+ private _modifySettingsUser = document.getElementById("radio-use-user") as HTMLInputElement;
+ private _profileSelect = document.getElementById("modify-user-profiles") as HTMLSelectElement;
+ private _userSelect = document.getElementById("modify-user-users") as HTMLSelectElement;
+
+ private _selectAll = document.getElementById("accounts-select-all") as HTMLInputElement;
+ private _users: { [id: string]: user };
+ private _checkCount: number = 0;
+
+ private _addUserForm = document.getElementById("form-add-user") as HTMLFormElement;
+ private _addUserName = this._addUserForm.querySelector("input[type=text]") as HTMLInputElement;
+ private _addUserEmail = this._addUserForm.querySelector("input[type=email]") as HTMLInputElement;
+ private _addUserPassword = this._addUserForm.querySelector("input[type=password]") as HTMLInputElement;
+
+ get selectAll(): boolean { return this._selectAll.checked; }
+ set selectAll(state: boolean) {
+ for (let id in this._users) {
+ this._users[id].selected = state;
+ }
+ this._selectAll.checked = state;
+ this._selectAll.indeterminate = false;
+ state ? this._checkCount = Object.keys(this._users).length : 0;
+
+ }
+
+ add = (u: User) => {
+ let domAccount = new user(u);
+ this._users[u.id] = domAccount;
+ this._table.appendChild(domAccount.asElement());
+ }
+
+ private _checkCheckCount = () => {
+ if (this._checkCount == 0) {
+ this._selectAll.indeterminate = false;
+ this._selectAll.checked = false;
+ this._modifySettings.classList.add("unfocused");
+ this._deleteUser.classList.add("unfocused");
+ } else {
+ if (this._checkCount == Object.keys(this._users).length) {
+ this._selectAll.checked = true;
+ this._selectAll.indeterminate = false;
+ } else {
+ this._selectAll.checked = false;
+ this._selectAll.indeterminate = true;
+ }
+ this._modifySettings.classList.remove("unfocused");
+ this._deleteUser.classList.remove("unfocused");
+ (this._checkCount == 1) ? this._deleteUser.textContent = "Delete User" : this._deleteUser.textContent = "Delete Users";
+ }
+ }
+
+ private _genCountString = (): string => { return `${this._checkCount} user${(this._checkCount > 1) ? "s" : ""}`; }
+
+ private _collectUsers = (): string[] => {
+ let list: string[] = [];
+ for (let id in this._users) {
+ if (this._users[id].selected) { list.push(id); }
+ }
+ return list;
+ }
+
+ private _addUser = (event: Event) => {
+ event.preventDefault();
+ const button = this._addUserForm.querySelector("span.submit") as HTMLSpanElement;
+ const send = {
+ "username": this._addUserName.value,
+ "email": this._addUserEmail.value,
+ "password": this._addUserPassword.value
+ };
+ for (let field in send) {
+ if (!send[field]) {
+ window.notifications.customError("addUserBlankField", "Fields were left blank.");
+ return;
+ }
+ }
+ toggleLoader(button);
+ _post("/users", send, (req: XMLHttpRequest) => {
+ if (req.readyState == 4) {
+ toggleLoader(button);
+ if (req.status == 200) {
+ window.notifications.customPositive("addUser", "Success:", `user "${send['username']}" created.`);
+ }
+ this.reload();
+ window.modals.addUser.close();
+ }
+ });
+ }
+
+ deleteUsers = () => {
+ const modalHeader = document.getElementById("header-delete-user");
+ modalHeader.textContent = this._genCountString();
+ let list = this._collectUsers();
+ const form = document.getElementById("form-delete-user") as HTMLFormElement;
+ const button = form.querySelector("span.submit") as HTMLSpanElement;
+ this._deleteNotify.checked = false;
+ this._deleteReason.value = "";
+ this._deleteReason.classList.add("unfocused");
+ form.onsubmit = (event: Event) => {
+ event.preventDefault();
+ toggleLoader(button);
+ let send = {
+ "users": list,
+ "notify": this._deleteNotify.checked,
+ "reason": this._deleteNotify ? this._deleteReason.value : ""
+ };
+ _delete("/users", send, (req: XMLHttpRequest) => {
+ if (req.readyState == 4) {
+ toggleLoader(button);
+ window.modals.deleteUser.close();
+ if (req.status != 200 && req.status != 204) {
+ let errorMsg = "Failed (check console/logs).";
+ if (!("error" in req.response)) {
+ errorMsg = "Partial failure (check console/logs).";
+ }
+ window.notifications.customError("deleteUserError", errorMsg);
+ } else {
+ window.notifications.customPositive("deleteUserSuccess", "Success:", `deleted ${this._genCountString()}.`);
+ }
+ this.reload();
+ }
+ });
+ };
+ window.modals.deleteUser.show();
+ }
+
+ modifyUsers = () => {
+ const modalHeader = document.getElementById("header-modify-user");
+ modalHeader.textContent = this._genCountString();
+ let list = this._collectUsers();
+ (() => {
+ let innerHTML = "";
+ for (const profile of window.availableProfiles) {
+ innerHTML += ``;
+ }
+ this._profileSelect.innerHTML = innerHTML;
+ })();
+
+ (() => {
+ let innerHTML = "";
+ for (let id in this._users) {
+ innerHTML += ``;
+ }
+ this._userSelect.innerHTML = innerHTML;
+ })();
+
+ const form = document.getElementById("form-modify-user") as HTMLFormElement;
+ const button = form.querySelector("span.submit") as HTMLSpanElement;
+ this._modifySettingsProfile.checked = true;
+ this._modifySettingsUser.checked = false;
+ form.onsubmit = (event: Event) => {
+ event.preventDefault();
+ toggleLoader(button);
+ let send = {
+ "apply_to": list,
+ "homescreen": (document.getElementById("modify-user-homescreen") as HTMLInputElement).checked
+ };
+ if (this._modifySettingsProfile.checked && !this._modifySettingsUser.checked) {
+ send["from"] = "profile";
+ send["profile"] = this._profileSelect.value;
+ } else if (this._modifySettingsUser.checked && !this._modifySettingsProfile.checked) {
+ send["from"] = "user";
+ send["id"] = this._userSelect.value;
+ }
+ _post("/users/settings", send, (req: XMLHttpRequest) => {
+ if (req.readyState == 4) {
+ toggleLoader(button);
+ if (req.status == 500) {
+ let response = JSON.parse(req.response);
+ let errorMsg = "";
+ if ("homescreen" in response && "policy" in response) {
+ const homescreen = Object.keys(response["homescreen"]).length;
+ const policy = Object.keys(response["policy"]).length;
+ if (homescreen != 0 && policy == 0) {
+ errorMsg = "Settings were applied, but applying homescreen layout may have failed.";
+ } else if (policy != 0 && homescreen == 0) {
+ errorMsg = "Homescreen layout was applied, but applying settings may have failed.";
+ } else if (policy != 0 && homescreen != 0) {
+ errorMsg = "Application failed.";
+ }
+ } else if ("error" in response) {
+ errorMsg = response["error"];
+ }
+ window.notifications.customError("modifySettingsError", errorMsg);
+ } else if (req.status == 200 || req.status == 204) {
+ window.notifications.customPositive("modifySettingsSuccess", "Success:", `applied settings to ${this._genCountString()}.`);
+ }
+ this.reload();
+ window.modals.modifyUser.close();
+ }
+ });
+ };
+ window.modals.modifyUser.show();
+ }
+
+
+
+ constructor() {
+ this._users = {};
+ this._selectAll.checked = false;
+ this._selectAll.onchange = () => { this.selectAll = this._selectAll.checked };
+ document.addEventListener("accountCheckEvent", () => { this._checkCount++; this._checkCheckCount(); });
+ document.addEventListener("accountUncheckEvent", () => { this._checkCount--; this._checkCheckCount(); });
+ this._addUserButton.onclick = window.modals.addUser.toggle;
+ this._addUserForm.addEventListener("submit", this._addUser);
+
+ this._deleteNotify.onchange = () => {
+ if (this._deleteNotify.checked) {
+ this._deleteReason.classList.remove("unfocused");
+ } else {
+ this._deleteReason.classList.add("unfocused");
+ }
+ };
+ this._modifySettings.onclick = this.modifyUsers;
+ this._modifySettings.classList.add("unfocused");
+ const checkSource = () => {
+ const profileSpan = this._modifySettingsProfile.nextElementSibling as HTMLSpanElement;
+ const userSpan = this._modifySettingsUser.nextElementSibling as HTMLSpanElement;
+ if (this._modifySettingsProfile.checked) {
+ this._userSelect.parentElement.classList.add("unfocused");
+ this._profileSelect.parentElement.classList.remove("unfocused")
+ profileSpan.classList.add("!high");
+ profileSpan.classList.remove("!normal");
+ userSpan.classList.remove("!high");
+ userSpan.classList.add("!normal");
+ } else {
+ this._userSelect.parentElement.classList.remove("unfocused");
+ this._profileSelect.parentElement.classList.add("unfocused");
+ userSpan.classList.add("!high");
+ userSpan.classList.remove("!normal");
+ profileSpan.classList.remove("!high");
+ profileSpan.classList.add("!normal");
+ }
+ };
+ this._modifySettingsProfile.onchange = checkSource;
+ this._modifySettingsUser.onchange = checkSource;
+
+ this._deleteUser.onclick = this.deleteUsers;
+ this._deleteUser.classList.add("unfocused");
+
+ if (!window.usernameEnabled) {
+ this._addUserName.classList.add("unfocused");
+ this._addUserName = this._addUserEmail;
+ }
+ /*if (!window.emailEnabled) {
+ this._deleteNotify.parentElement.classList.add("unfocused");
+ this._deleteNotify.checked = false;
+ }*/
+ }
+
+ reload = () => _get("/users", null, (req: XMLHttpRequest) => {
+ if (req.readyState == 4 && req.status == 200) {
+ // same method as inviteList.reload()
+ let accountsOnDOM: { [id: string]: boolean } = {};
+ for (let id in this._users) { accountsOnDOM[id] = true; }
+ for (let u of (req.response["users"] as User[])) {
+ if (u.id in this._users) {
+ this._users[u.id].update(u);
+ delete accountsOnDOM[u.id];
+ } else {
+ this.add(u);
+ }
+ }
+ for (let id in accountsOnDOM) {
+ this._users[id].remove();
+ delete this._users[id];
+ }
+ this._checkCheckCount;
+ }
+ })
+}
diff --git a/ts/modules/common.ts b/ts/modules/common.ts
index 0ba7821..7dfe918 100644
--- a/ts/modules/common.ts
+++ b/ts/modules/common.ts
@@ -124,6 +124,7 @@ export function toClipboard (str: string) {
export class notificationBox implements NotificationBox {
private _box: HTMLDivElement;
private _errorTypes: { [type: string]: boolean } = {};
+ private _positiveTypes: { [type: string]: boolean } = {};
timeout: number;
constructor(box: HTMLDivElement, timeout?: number) { this._box = box; this.timeout = timeout || 5; }
@@ -139,7 +140,19 @@ export class notificationBox implements NotificationBox {
return noti;
}
- connectionError = () => { this.customError("connectionError", "Couldn't connect to jfa-go"); }
+ private _positive = (bold: string, message: string): HTMLElement => {
+ const noti = document.createElement('aside');
+ noti.classList.add("aside", "~positive", "!normal", "mt-half", "notification-positive");
+ noti.innerHTML = `${bold} ${message}`;
+ const closeButton = document.createElement('span') as HTMLSpanElement;
+ closeButton.classList.add("button", "~positive", "!low", "ml-1");
+ closeButton.innerHTML = ``;
+ closeButton.onclick = () => { this._box.removeChild(noti); };
+ noti.appendChild(closeButton);
+ return noti;
+ }
+
+ connectionError = () => { this.customError("connectionError", "Couldn't connect to jfa-go."); }
customError = (type: string, message: string) => {
this._errorTypes[type] = this._errorTypes[type] || false;
@@ -153,6 +166,19 @@ export class notificationBox implements NotificationBox {
this._errorTypes[type] = true;
setTimeout(() => { if (this._box.contains(noti)) { this._box.removeChild(noti); this._errorTypes[type] = false; } }, this.timeout*1000);
}
+
+ customPositive = (type: string, bold: string, message: string) => {
+ this._positiveTypes[type] = this._positiveTypes[type] || false;
+ const noti = this._positive(bold, message);
+ noti.classList.add("positive-" + type);
+ const previousNoti: HTMLElement | undefined = this._box.querySelector("aside.positive-" + type);
+ if (this._positiveTypes[type] && previousNoti !== undefined && previousNoti != null) {
+ previousNoti.remove();
+ }
+ this._box.appendChild(noti);
+ this._positiveTypes[type] = true;
+ setTimeout(() => { if (this._box.contains(noti)) { this._box.removeChild(noti); this._positiveTypes[type] = false; } }, this.timeout*1000);
+ }
}
export const whichAnimationEvent = () => {
@@ -162,3 +188,18 @@ export const whichAnimationEvent = () => {
}
return "webkitAnimationEnd";
}
+
+export function toggleLoader(el: HTMLElement, small: boolean = true) {
+ if (el.classList.contains("loader")) {
+ el.classList.remove("loader");
+ el.classList.remove("loader-sm");
+ const dot = el.querySelector("span.dot");
+ if (dot) { dot.remove(); }
+ } else {
+ el.classList.add("loader");
+ if (small) { el.classList.add("loader-sm"); }
+ const dot = document.createElement("span") as HTMLSpanElement;
+ dot.classList.add("dot")
+ el.appendChild(dot);
+ }
+}
diff --git a/ts/modules/invites.ts b/ts/modules/invites.ts
index dcaf8a9..abe31aa 100644
--- a/ts/modules/invites.ts
+++ b/ts/modules/invites.ts
@@ -1,4 +1,4 @@
-import { _get, _post, _delete, toClipboard } from "../modules/common.js";
+import { _get, _post, _delete, toClipboard, toggleLoader } from "../modules/common.js";
export class DOMInvite implements Invite {
// TODO
@@ -390,8 +390,10 @@ export class inviteList implements inviteList {
reload = () => _get("/invites", null, (req: XMLHttpRequest) => {
if (req.readyState == 4) {
let data = req.response;
- window.availableProfiles = data["profiles"];
- document.dispatchEvent(this._profileLoadEvent);
+ if (req.status == 200) {
+ window.availableProfiles = data["profiles"];
+ document.dispatchEvent(this._profileLoadEvent);
+ }
if (data["invites"] === undefined || data["invites"] == null || data["invites"].length == 0) {
this.empty = true;
return;
@@ -446,8 +448,13 @@ export class createInvite {
private _uses = document.getElementById('create-uses') as HTMLInputElement;
private _infUses = document.getElementById("create-inf-uses") as HTMLInputElement;
private _infUsesWarning = document.getElementById('create-inf-uses-warning') as HTMLParagraphElement;
- private _createButton = document.getElementById("create-submit") as HTMLInputElement; // Actually a but this allows "disabled"
+ private _createButton = document.getElementById("create-submit") as HTMLSpanElement;
private _profile = document.getElementById("create-profile") as HTMLSelectElement;
+
+ private _days = document.getElementById("create-days") as HTMLSelectElement;
+ private _hours = document.getElementById("create-hours") as HTMLSelectElement;
+ private _minutes = document.getElementById("create-minutes") as HTMLSelectElement;
+
// Broadcast when new invite created
private _newInviteEvent = new CustomEvent("newInviteEvent");
private _firstLoad = true;
@@ -503,28 +510,34 @@ export class createInvite {
set uses(n: number) { this._uses.valueAsNumber = n; }
private _checkDurationValidity = () => {
- this._createButton.disabled = (this.days + this.hours + this.minutes == 0);
+ if (this.days + this.hours + this.minutes == 0) {
+ this._createButton.setAttribute("disabled", "");
+ this._createButton.onclick = null;
+ } else {
+ this._createButton.removeAttribute("disabled");
+ this._createButton.onclick = this.create;
+ }
}
get days(): number {
- return +(document.getElementById("create-days") as HTMLSelectElement).value;
+ return +this._days.value;
}
set days(n: number) {
- (document.getElementById("create-days") as HTMLSelectElement).value = ""+n;
+ this._days.value = ""+n;
this._checkDurationValidity();
}
get hours(): number {
- return +(document.getElementById("create-hours") as HTMLSelectElement).value;
+ return +this._hours.value;
}
set hours(n: number) {
- (document.getElementById("create-hours") as HTMLSelectElement).value = ""+n;
+ this._hours.value = ""+n;
this._checkDurationValidity();
}
get minutes(): number {
- return +(document.getElementById("create-minutes") as HTMLSelectElement).value;
+ return +this._minutes.value;
}
set minutes(n: number) {
- (document.getElementById("create-minutes") as HTMLSelectElement).value = ""+n;
+ this._minutes.value = ""+n;
this._checkDurationValidity();
}
@@ -559,6 +572,7 @@ export class createInvite {
}
create = () => {
+ toggleLoader(this._createButton);
let send = {
"days": this.days,
"hours": this.hours,
@@ -570,8 +584,11 @@ export class createInvite {
"profile": this.profile
};
_post("/invites", send, (req: XMLHttpRequest) => {
- if (req.readyState == 4 && (req.status == 200 || req.status == 204)) {
- document.dispatchEvent(this._newInviteEvent);
+ if (req.readyState == 4) {
+ if (req.status == 200 || req.status == 204) {
+ document.dispatchEvent(this._newInviteEvent);
+ }
+ toggleLoader(this._createButton);
}
});
}
@@ -588,7 +605,15 @@ export class createInvite {
this._createButton.onclick = this.create;
this.sendTo = "";
this.uses = 1;
+
+ this._days.onchange = this._checkDurationValidity;
+ this._hours.onchange = this._checkDurationValidity;
+ this._minutes.onchange = this._checkDurationValidity;
document.addEventListener("profileLoadEvent", () => { this.loadProfiles(); }, false);
+
+ if (!window.emailEnabled) {
+ document.getElementById("create-send-to-container").classList.add("unfocused");
+ }
}
}
diff --git a/ts/modules/tabs.ts b/ts/modules/tabs.ts
index c2f60db..09ae14b 100644
--- a/ts/modules/tabs.ts
+++ b/ts/modules/tabs.ts
@@ -20,14 +20,14 @@ export class Tabs implements Tabs {
for (let t of this.tabs) {
if (t.tabID == tabID) {
t.buttonEl.classList.add("active", "~urge");
- t.preFunc();
+ if (t.preFunc) { t.preFunc(); }
t.tabEl.classList.remove("unfocused");
+ if (t.postFunc) { t.postFunc(); }
} else {
t.buttonEl.classList.remove("active");
t.buttonEl.classList.remove("~urge");
t.tabEl.classList.add("unfocused");
}
- t.postFunc();
}
}
}
diff --git a/ts/typings/d.ts b/ts/typings/d.ts
index 9442ad7..1d975da 100644
--- a/ts/typings/d.ts
+++ b/ts/typings/d.ts
@@ -16,7 +16,10 @@ declare interface Window {
cssFile: string;
availableProfiles: string[];
jfUsers: Array