From 28187d0aa0a852128be49cc08f82b1bc06cb2889 Mon Sep 17 00:00:00 2001 From: Harvey Tindall Date: Wed, 30 Dec 2020 18:31:38 +0000 Subject: [PATCH] add connection error notification, implement notify/delete function --- css/base.css | 10 +++++++++- html/admin.html | 1 + ts/admin.ts | 18 +++--------------- ts/modules/common.ts | 39 ++++++++++++++++++++++++++++++++++----- ts/modules/invites.ts | 33 ++++++++++++++++++++++++++++----- ts/typings/d.ts | 5 +++++ 6 files changed, 80 insertions(+), 26 deletions(-) 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 {