diff --git a/css/base.css b/css/base.css
index 53f1bb0..cd11b53 100644
--- a/css/base.css
+++ b/css/base.css
@@ -274,6 +274,10 @@ sup.\~critical, .text-critical {
resize: vertical;
}
+.overflow {
+ overflow: visible;
+}
+
select {
color: inherit;
border: 0 solid var(--color-neutral-300);
@@ -291,4 +295,8 @@ p.top {
margin-top: 0px;
}
-
+#notification-box {
+ position: fixed;
+ right: 1rem;
+ bottom: 1rem;
+}
diff --git a/html/admin.html b/html/admin.html
index f795f92..3b5f6a3 100644
--- a/html/admin.html
+++ b/html/admin.html
@@ -113,6 +113,7 @@
+
diff --git a/ts/admin.ts b/ts/admin.ts
index b4947e9..ed601d9 100644
--- a/ts/admin.ts
+++ b/ts/admin.ts
@@ -2,6 +2,7 @@ import { toggleTheme, loadTheme } from "./modules/theme.js";
import { Modal } from "./modules/modal.js";
import { Tabs } from "./modules/tabs.js";
import { inviteList } from "./modules/invites.js";
+import { notificationBox } from "./modules/common.js";
loadTheme();
(document.getElementById('button-theme') as HTMLSpanElement).onclick = toggleTheme;
@@ -14,21 +15,8 @@ const whichAnimationEvent = () => {
return "webkitAnimationEnd";
}
window.animationEvent = whichAnimationEvent();
-/*const toggles: HTMLInputElement[] = Array.from(document.getElementsByClassName('toggle-details'));
-for (let toggle of toggles) {
- toggle.onclick = () => {
- const el = toggle.parentElement.parentElement.parentElement.nextElementSibling as HTMLDivElement;
- if (el.classList.contains("focused")) {
- el.classList.toggle("focused");
- el.classList.toggle("unfocused");
- } else {
- el.classList.toggle("unfocused");
- el.classList.toggle("focused");
- }
- toggle.previousElementSibling.classList.toggle("rotated");
- toggle.previousElementSibling.classList.toggle("not-rotated");
- };
-}*/
+
+window.notifications = new notificationBox(document.getElementById('notification-box') as HTMLDivElement, 5);
const checkInfUses = function (check: HTMLInputElement, mode = 2) {
const uses = document.getElementById('inv-uses') as HTMLInputElement;
diff --git a/ts/modules/common.ts b/ts/modules/common.ts
index a7a0041..3a1937f 100644
--- a/ts/modules/common.ts
+++ b/ts/modules/common.ts
@@ -56,11 +56,11 @@ export const _get = (url: string, data: Object, onreadystatechange: (req: XMLHtt
req.responseType = 'json';
req.setRequestHeader("Authorization", "Bearer " + window.token);
req.setRequestHeader('Content-Type', 'application/json; charset=UTF-8');
- req.onreadystatechange = () => { onreadystatechange(req); };
+ req.onreadystatechange = () => { if (req.status == 0) { window.notifications.connectionError(); } else { onreadystatechange(req); } };
req.send(JSON.stringify(data));
};
-export const _post = (url: string, data: Object, onreadystatechange: () => void, response?: boolean): void => {
+export const _post = (url: string, data: Object, onreadystatechange: (req: XMLHttpRequest) => void, response?: boolean): void => {
let req = new XMLHttpRequest();
req.open("POST", window.URLBase + url, true);
if (response) {
@@ -68,16 +68,16 @@ export const _post = (url: string, data: Object, onreadystatechange: () => void,
}
req.setRequestHeader("Authorization", "Bearer " + window.token);
req.setRequestHeader('Content-Type', 'application/json; charset=UTF-8');
- req.onreadystatechange = onreadystatechange;
+ req.onreadystatechange = () => { if (req.status == 0) { window.notifications.connectionError(); } else { onreadystatechange(req); } };
req.send(JSON.stringify(data));
};
-export function _delete(url: string, data: Object, onreadystatechange: () => void): void {
+export function _delete(url: string, data: Object, onreadystatechange: (req: XMLHttpRequest) => void): void {
let req = new XMLHttpRequest();
req.open("DELETE", window.URLBase + url, true);
req.setRequestHeader("Authorization", "Bearer " + window.token);
req.setRequestHeader('Content-Type', 'application/json; charset=UTF-8');
- req.onreadystatechange = onreadystatechange;
+ req.onreadystatechange = () => { if (req.status == 0) { window.notifications.connectionError(); } else { onreadystatechange(req); } };
req.send(JSON.stringify(data));
}
@@ -97,3 +97,32 @@ export function toClipboard (str: string) {
document.getSelection().addRange(selected);
}
}
+
+export class notificationBox implements NotificationBox {
+ private _box: HTMLDivElement;
+ timeout: number
+ constructor(box: HTMLDivElement, timeout?: number) { this._box = box; this.timeout = timeout || 5; }
+
+ private _error = (message: string): HTMLElement => {
+ const noti = document.createElement('aside');
+ noti.classList.add("aside", "~critical", "!normal", "mt-half", "notification-error");
+ noti.innerHTML = `Error: ${message}`;
+ const closeButton = document.createElement('span') as HTMLSpanElement;
+ closeButton.classList.add("button", "~critical", "!low", "ml-1");
+ closeButton.innerHTML = ``;
+ closeButton.onclick = () => { this._box.removeChild(noti); };
+ noti.appendChild(closeButton);
+ return noti;
+ }
+
+ private _connectionError: boolean = false;
+ connectionError = () => {
+ const noti = this._error("Couldn't connect to jfa-go.");
+ if (this._connectionError) {
+ this._box.querySelector("aside.notification-error").remove();
+ }
+ this._box.appendChild(noti);
+ this._connectionError = true;
+ setTimeout(() => { this._box.removeChild(noti); this._connectionError = false; }, this.timeout*1000);
+ }
+}
diff --git a/ts/modules/invites.ts b/ts/modules/invites.ts
index 39a57ca..112def4 100644
--- a/ts/modules/invites.ts
+++ b/ts/modules/invites.ts
@@ -1,10 +1,28 @@
import { _get, _post, _delete, toClipboard } from "../modules/common.js";
export class DOMInvite implements Invite {
-
- // TODO
- updateNotify = () => {}; // SetNotify
- delete = () => {}; // deleteInvite
+ updateNotify = (checkbox: HTMLInputElement) => {
+ let state: { [code: string]: { [type: string]: boolean } } = {};
+ let revertChanges: () => void;
+ if (checkbox.classList.contains("inv-notify-expiry")) {
+ revertChanges = () => { this.notifyExpiry = !this.notifyExpiry };
+ state[this.code] = { "notify-expiry": this.notifyExpiry };
+ } else {
+ revertChanges = () => { this.notifyCreation = !this.notifyCreation };
+ state[this.code] = { "notify-creation": this.notifyCreation };
+ }
+ _post("/invites/notify", state, (req: XMLHttpRequest) => {
+ if (req.readyState == 4 && !(req.status == 200 || req.status == 204)) {
+ revertChanges();
+ }
+ });
+ }
+
+ delete = () => { _delete("/invites", { "code": this.code }, (req: XMLHttpRequest) => {
+ if (req.readyState == 4 && (req.status == 200 || req.status == 204)) {
+ this.remove();
+ }
+ }); }
private _code: string = "None";
get code(): string { return this._code; }
@@ -175,7 +193,7 @@ export class DOMInvite implements Invite {
this._header = document.createElement('div') as HTMLDivElement;
this._container.appendChild(this._header);
- this._header.classList.add("card", "~neutral", "!normal", "inv-header", "flex-expand", "mt-half");
+ this._header.classList.add("card", "~neutral", "!normal", "inv-header", "flex-expand", "mt-half", "overflow");
this._codeArea = document.createElement('div') as HTMLDivElement;
this._header.appendChild(this._codeArea);
@@ -234,6 +252,11 @@ export class DOMInvite implements Invite {
On user creation
`;
+ const notifyExpiry = this._left.querySelector("input.inv-notify-expiry") as HTMLInputElement;
+ notifyExpiry.onchange = () => { this._notifyExpiry = notifyExpiry.checked; this.updateNotify(notifyExpiry); };
+
+ const notifyCreation = this._left.querySelector("input.inv-notify-creation") as HTMLInputElement;
+ notifyCreation.onchange = () => { this._notifyCreation = notifyCreation.checked; this.updateNotify(notifyCreation); };
this._middle = document.createElement('div') as HTMLDivElement;
detailsInner.appendChild(this._middle);
diff --git a/ts/typings/d.ts b/ts/typings/d.ts
index b3b53a4..c92eadd 100644
--- a/ts/typings/d.ts
+++ b/ts/typings/d.ts
@@ -23,6 +23,11 @@ declare interface Window {
animationEvent: string;
tabs: Tabs;
invites: inviteList;
+ notifications: NotificationBox;
+}
+
+declare interface NotificationBox {
+ connectionError: (timeout: number) => void;
}
declare interface Tabs {