import { h } from '@ledge/jsx';
import { NgService } from './service';
type LogType = '$log' | '$warn' | '$error' | '$info' | '$success';
enum LogTypeToastBackgrounds {
$log = 'white',
$success = 'success',
$info = 'info',
$warn = 'warning',
$error = 'danger',
}
export class NgToast {
protected type!: LogType;
protected readonly toastHeaderTimestamp = ;
protected readonly toastHeader =
;
protected readonly toastBody = ;
protected readonly toast =
{this.toastHeader}
{this.toastBody}
;
constructor(options: {
text: string,
type: LogType,
container: HTMLElement,
}) {
this.toast.style.setProperty('cursor', 'pointer');
this.toast.style.setProperty('opacity', '0');
this.toast.style.setProperty('transition', '500ms');
this.toastHeader.style.setProperty('border-bottom', 'none');
this.setBodyText(options.text);
this.setType(options.type);
options.container.appendChild(this.toast);
}
/**
* Append a child element to the toast element
*
* @param el The element to append
*/
public appendChild(el: HTMLElement) {
this.toast.appendChild(el);
return this;
}
/**
* Set the inner HTML of the toast's body element
*
* @param text The HTML to set
*/
public setBodyText(text: string) {
this.toastBody.innerHTML = text;
}
/**
* Change the look & feel of the toast based on its associated log type
*
* @param type The log type of toast
*/
public setType(type: LogType) {
if (this.type != null) {
this.toast.classList.remove(`bg-${LogTypeToastBackgrounds[this.type]}`);
}
this.type = type;
this.toast.classList.add(`bg-${LogTypeToastBackgrounds[this.type]}`);
if (type === '$log') {
this.toastHeader.classList.add('bg-dark');
this.toastHeader.style.setProperty('opacity', '0.75');
this.toastHeaderTimestamp.classList.remove('text-dark');
this.toastHeaderTimestamp.classList.add('text-white');
} else if (type !== '$warn') {
this.toast.classList.add('text-white');
}
}
/**
* Show the toast
*
* @param timeout Length in ms before toast disappears (`false` to set permanently)
* @param container Optional container override
*/
public show(timeout: false | number, container?: HTMLElement) {
if (container != null) {
this.remove();
container.appendChild(this.toast);
}
this.toastHeaderTimestamp.innerText = new Date()
.toLocaleTimeString(navigator.language)
.replace(/(:\d{2})(?=\s[AP]M$)/, '');
return new Promise(resolve => {
const onComplete = () => {
this.toast.removeEventListener('click', onComplete);
this.remove();
resolve();
};
setTimeout(() => {
this.toast.addEventListener('click', onComplete);
this.toast.style.setProperty('opacity', '1');
if (typeof timeout === 'number' && !isNaN(timeout)) {
(setTimeout as typeof window['setTimeout'])(() => {
this.toast.style.setProperty('opacity', '0');
(setTimeout as typeof window['setTimeout'])(onComplete, 300);
}, timeout as number);
}
});
});
}
/**
* Hide the toast
*/
public hide() {
this.toast.click();
}
/**
* Remove the toast from its container
*/
public remove() {
this.toast.parentElement?.removeChild(this.toast);
}
}
// tslint:disable:no-console
export class NgConsole extends NgService {
/**
* Invokes `Console.prototype.debug`
*
* @param items list of items to log
*/
public $debug(...items: any[]) {
console.debug(...items);
}
/**
* Invokes `Console.prototype.error`
*
* @param items list of items to log
*/
public $error(...items: any[]) {
console.error(...items);
}
/**
* Invokes `Console.prototype.info`
*
* @param items list of items to log
*/
public $info(...items: any[]) {
console.info(...items);
}
/**
* Invokes `Console.prototype.warn`
*
* @param items list of items to log
*/
public $warn(...items: any[]) {
console.warn(...items);
}
/**
* Invokes `Console.prototype.log`
*
* @param items list of items to log
*/
public $log(...items: any[]) {
console.log(...items);
}
/**
* Invokes `Console.prototype.log`
*
* @param items list of items to log
*/
public $success(...items: any[]) {
this.$log(...items);
}
}
// tslint:enable:no-console
export class NgLogger extends NgConsole {
protected readonly container = ;
protected readonly toasts: NgToast[] = [];
constructor(private isProd = false) {
super();
this.container.style.setProperty('top', '0.5rem');
this.container.style.setProperty('right', '-1.5rem');
this.container.style.setProperty('width', '100%');
this.container.style.setProperty('max-width', '23rem');
this.container.style.setProperty('z-index', '2323');
document.body.appendChild(this.container);
}
/**
* Clear all active notifications
*/
public clear() {
this.toasts.forEach(x => x.hide());
}
/**
* Prompt the user to confirm intent for a previous action
*
* @param msg Confirmation message
*/
public confirm(msg = 'Please confirm your action') {
const okBtn = ;
const cancelBtn = ;
const footer = {cancelBtn}{okBtn}
;
const toast = this.notify(msg, '$log', false);
toast.appendChild(footer);
return new Promise((resolve, reject) => {
const removeListeners = () => {
okBtn.removeEventListener('click', ok);
cancelBtn.removeEventListener('click', cancel);
};
const ok = () => {
toast.hide();
removeListeners();
resolve();
};
const cancel = () => {
toast.hide();
removeListeners();
reject();
};
okBtn.addEventListener('click', ok);
cancelBtn.addEventListener('click', cancel);
});
}
/**
* Display an error notification
*
* @param text Display text
* @param isTemporary Whether the notification should disappear automatically (false by default)
*/
public error(text: string, isTemporary = false) {
this.notify(text, '$error', isTemporary && undefined);
}
/**
* Display an informational notification
*
* @param text Display text
* @param isTemporary Whether the notification should disappear automatically (true by default)
*/
public info(text: string, isTemporary = true) {
this.notify(text, '$info', isTemporary && undefined);
}
/**
* Display a success notification
*
* @param text Display text
* @param isTemporary Whether the notification should disappear automatically (true by default)
*/
public success(text: string, isTemporary = true) {
this.notify(text, '$success', isTemporary && undefined);
}
/**
* Display a warning notification
*
* @param text Display text
* @param isTemporary Whether the notification should disappear automatically (true by default)
*/
public warning(text: string, isTemporary = true) {
this.notify(text, '$warn', isTemporary && undefined);
}
/**
* Display a warning notification (disabled when `process.env.NODE_ENV === 'production'`)
*
* @param text Display text
*/
public devWarning(text: string) {
if (this.isProd === false) {
this.warning(`[DEV] ${text}`, false);
}
}
/**
* Show a customizable notification to the user
*
* @param text Display text
* @param type Display type
* @param timeout Length in ms before notification disappears (`false` to set permanently)
* @param buttons Interaction points for the user
*/
public notify(text: string, type: LogType, timeout: false | number = 2323) {
this[type](`${type}: ${text}`);
const toast = new NgToast({ text, type, container: this.container });
toast.show(timeout).then(() => {
let i = this.toasts.length;
for (; i > -1; --i) {
if (this.toasts[i] === toast) {
break;
}
}
this.toasts.splice(i, 1);
});
this.toasts.push(toast);
return toast;
}
}