implemented toasts

This commit is contained in:
Fran Jurmanović
2021-06-12 21:21:03 +02:00
parent ecedbb8b57
commit 64b54b3568
17 changed files with 7186 additions and 11 deletions

View File

@@ -8,7 +8,7 @@ class BaseElement extends HTMLElement {
@closest appMain: AppMainElement;
private _appMain: AppMainElement;
public loader: Loader;
private elementDisconnectCallbacks: Array<Function> = [];
public elementDisconnectCallbacks: Array<Function> = [];
constructor() {
super();
this.connectedCallback = this.connectedCallback.bind(this);

View File

@@ -46,6 +46,11 @@ class AppDropdownElement extends BaseComponentElement {
this.validator = new Validator(this, this.appForm, this.rules);
this.randId = `${name}${randomId()}`;
this.update();
this.appMain.addEventListener('click', this.outsideClick);
this.elementDisconnectCallbacks.push((appMain) => {
console.log('oslo');
appMain.removeEventListener('click', this.outsideClick);
});
const options = {
rpp: this.rpp,
@@ -54,6 +59,13 @@ class AppDropdownElement extends BaseComponentElement {
this.getItems(options);
};
elementDisconnected = (): void => {};
outsideClick = (e) => {
console.log(e.target);
this.closeDropdown(e);
};
attributeChangedCallback(): void {
this.update();
}
@@ -135,10 +147,16 @@ class AppDropdownElement extends BaseComponentElement {
this.isOpen = isOpen;
};
openDropdown = () => {
openDropdown = (e?) => {
if (e?.target?.closest('app-dropdown > .dropdown-custom')?.randId == this.randId) return;
this.setOpen(true);
};
closeDropdown = (e?) => {
if (e?.target?.closest('app-dropdown')?.randId == this.randId) return;
this.setOpen(false);
};
stopPropagation = (e) => {
e.stopPropagation();
};
@@ -179,9 +197,9 @@ class AppDropdownElement extends BaseComponentElement {
return html`
<div>
<label app-action="click:app-dropdown#openDropdown">
<label>
${label ? html`<div>${label}</div>` : html``}
<div class="dropdown-custom" app-action="click:app-dropdown#stopPropagation">
<div class="dropdown-custom">
<div class="dropdown-custom-top${isOpen ? ' --open' : ''}" app-action="click:app-dropdown#toggleDropdown">
<span class="dropdown-custom-fieldname">${selectedItem ? selectedItem[displaykey] : 'Select'}</span>
</div>

View File

@@ -4,6 +4,7 @@ import { AuthStore } from 'core/store';
import { AppModalElement, AppRootElement } from 'components/';
import { closest } from 'core/utils';
import { AppLoaderElement } from 'components/app-loader/AppLoaderElement';
import { ToastPortalElement } from 'components/toast-portal/ToastPortalElement';
@controller
class AppMainElement extends HTMLElement {
@@ -11,9 +12,11 @@ class AppMainElement extends HTMLElement {
public authStore: AuthStore;
private httpClient: HttpClient;
public appService: AppService;
public shadow: any;
@target appModal: AppModalElement;
@target mainRoot: AppRootElement;
@target appLoader: AppLoaderElement;
@target toastPortal;
@closest appMain: AppMainElement;
public domEvents: any = {
routechanged: new Event('routechanged'),
@@ -148,7 +151,7 @@ class AppMainElement extends HTMLElement {
const _divEl = document.createElement('div');
_modalElement.setAttribute('data-target', 'app-modal.modalElement');
_divEl.setAttribute('data-target', 'app-modal.modalContent');
_divEl.setAttribute('data-action', 'click:app-main#preventClosing');
//_divEl.setAttribute('data-action', 'click:app-main#preventClosing');
_divEl.appendChild(_modalElement);
return _divEl;
};
@@ -168,12 +171,36 @@ class AppMainElement extends HTMLElement {
return _mainRoot;
};
preventClosing = (e: Event) => {
e.stopPropagation();
private createToastPortal = () => {
const _toastPortal = document.createElement('toast-portal');
_toastPortal.setAttribute('data-target', 'app-main.toastPortal');
this.appendChild(_toastPortal);
return _toastPortal;
};
closeModal = () => {
if (this.appModal) this.removeChild(this.appModal);
removeToastPortal = () => {
if (this.toastPortal) this.removeChild(this.toastPortal);
};
pushToast = (type, message) => {
const toastPortal: ToastPortalElement = this.toastPortal
? this.toastPortal
: this.appendChild(this.createToastPortal());
toastPortal?.pushToast(type, message);
};
closeModal = (e?) => {
const selector = "[data-target='app-modal.modalContent']";
if (this.appModal) {
if (e?.target) {
if (e?.target?.closest(selector)) return;
if (e?.target?.closest('app-main')) {
this.removeChild(this.appModal);
}
} else {
this.removeChild(this.appModal);
}
}
this.appMain.removeEventListener('routechanged', this.closeModal);
};

View File

@@ -75,6 +75,7 @@ class AppMenuElement extends BaseComponentElement {
this.appMain.closeModal();
} else {
this.appMain.createModal('wallet-create');
this.appMain.pushToast(null, 'Da');
}
};

View File

@@ -4,6 +4,7 @@ import { BaseComponentElement } from 'common/';
@controller
class AppModalElement extends BaseComponentElement {
@target modalElement: HTMLElement;
@target modalContent: HTMLElement;
constructor() {
super();
}

View File

@@ -1,4 +1,5 @@
import { controller } from '@github/catalyst';
import { AppMainElement } from 'components/app-main/AppMainElement';
import style from 'styles/main.scss';
(function () {
@@ -14,6 +15,7 @@ import style from 'styles/main.scss';
connectedCallback() {
const _root = _shadow.get(this);
const _appMain = document.createElement('app-main');
(_appMain as AppMainElement).shadow = _root;
const _style = document.createElement('style');
_style.innerHTML = style;

View File

@@ -11,6 +11,7 @@ export * from './app-loader/AppLoaderElement';
export * from './circle-loader/CircleLoaderElement';
export * from './app-form/AppFormElement';
export * from './wallet-header/WalletHeaderElement';
export * from './toast-portal/ToastPortalElement';
// LAST
export * from './app-main/AppMainElement';

View File

@@ -0,0 +1,57 @@
import { controller, targets } from '@github/catalyst';
import { delay, html, until } from 'core/utils';
import { BaseComponentElement } from 'common/';
@controller
class ToastPortalElement extends BaseComponentElement {
@targets toastElement: HTMLElement;
toasts: Array<Toast> = [];
constructor() {
super();
}
elementConnected = (): void => {
this.update();
};
pushToast = (type: string, message: string): void => {
this.toasts = [{ type, message }, ...this.toasts];
const interval = setInterval(() => {
this.popToast();
clearInterval(interval);
}, 5000);
this.update();
};
popToast = () => {
this.toasts?.pop();
if (this.toasts?.length < 1) {
this.appMain?.removeToastPortal();
}
this.update();
};
render = () => {
const renderToast = (note: string, type: string) => {
const message = () => html` <div class="toast ${type ? `--${type}` : '--default'}">${note}</div> `;
return html`${message()}`;
};
const renderToasts = (toasts: Array<Toast>) => {
if (toasts) {
return html`<div class="toast-list">
${toasts.map(({ type, message }, i) => (i < 3 ? renderToast(message, type) : html``))}
</div>`;
}
return html``;
};
return html`<div class="toast-portal">${renderToasts(this.toasts)}</div>`;
};
}
type Toast = {
type: string;
message: string;
};
export type { ToastPortalElement };

View File

@@ -0,0 +1,8 @@
import { Timer } from '..';
export const delay = (ms, callback?, value?) =>
new Promise((resolve, reject) => {
const args = typeof callback == 'function' ? [resolve, reject, value] : [value];
const timer = new Timer(typeof callback == 'function' ? callback : resolve, ms, ...args);
return timer;
});

View File

@@ -0,0 +1,64 @@
import { isPrimitive, directive } from 'core/utils';
interface AsyncState {
lastRenderedIndex: number;
values: unknown[];
}
const _state = new WeakMap<any, AsyncState>();
const _infinity = 0x7fffffff;
export const until = directive((...args: unknown[]) => (part: any) => {
let state = _state.get(part)!;
if (state === undefined) {
state = {
lastRenderedIndex: _infinity,
values: [],
};
_state.set(part, state);
}
const previousValues = state.values;
let previousLength = previousValues.length;
state.values = args;
for (let i = 0; i < args.length; i++) {
// If we've rendered a higher-priority value already, stop.
if (i > state.lastRenderedIndex) {
break;
}
const value = args[i];
// Render non-Promise values immediately
if (isPrimitive(value) || typeof (value as { then?: unknown }).then !== 'function') {
part.setValue(value);
state.lastRenderedIndex = i;
// Since a lower-priority value will never overwrite a higher-priority
// synchronous value, we can stop processing now.
break;
}
// If this is a Promise we've already handled, skip it.
if (i < previousLength && value === previousValues[i]) {
continue;
}
// We have a Promise that we haven't seen before, so priorities may have
// changed. Forget what we rendered before.
state.lastRenderedIndex = _infinity;
previousLength = 0;
Promise.resolve(value).then((resolvedValue: unknown) => {
const index = state.values.indexOf(value);
// If state.values doesn't contain the value, we've re-rendered without
// the value, so don't render it. Then, only render if the value is
// higher-priority than what's already been rendered.
if (index > -1 && index < state.lastRenderedIndex) {
state.lastRenderedIndex = index;
part.setValue(resolvedValue);
part.commit();
}
});
}
});

View File

@@ -1,6 +1,9 @@
export * from './library';
export * from './templating';
export * from './directives/until';
export * from './directives/delay';
export { default as toKebabCase } from './toKebabCase';
export { default as update } from './update-deco';
export { default as index } from './index-deco';
@@ -11,3 +14,4 @@ export { default as query } from './query-deco';
export { default as querys } from './querys-deco';
export { default as findMethod } from './find-method';
export { default as validator } from './validator';
export { default as Timer } from './timer';

View File

@@ -1,3 +1,3 @@
import { render, html, TemplateResult } from 'lit-html';
import { render, html, TemplateResult, directive, isPrimitive } from 'lit-html';
export { render, html, TemplateResult };
export { render, html, TemplateResult, directive, isPrimitive };

20
src/core/utils/timer.ts Normal file
View File

@@ -0,0 +1,20 @@
const Timer = function (callback, delay, ...args) {
var timerId,
start,
remaining = delay;
this.pause = function () {
window.clearTimeout(timerId);
remaining -= Date.now() - start;
};
this.resume = function () {
start = Date.now();
window.clearTimeout(timerId);
timerId = window.setTimeout(callback, remaining, ...args);
};
this.resume();
};
export default Timer;

View File

@@ -10,3 +10,4 @@
@import './layout/index.scss';
@import './app-dropdown/index.scss';
@import './input-field/index.scss';
@import './toast-portal/index.scss';

View File

@@ -0,0 +1 @@
@import './toast-portal.scss';

View File

@@ -0,0 +1,29 @@
toast-portal {
.toast-portal {
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
z-index: 2500;
overflow: visible;
pointer-events: none;
background: none !important;
.toast-list {
position: absolute;
top: 5px;
right: 7px;
.toast {
&.--default {
position: relative;
width: 200px;
height: 40px;
background-color: $gray-07;
border: 1px solid $gray-08;
border-radius: 4px;
}
}
}
}
}

6941
yarn-error.log Normal file

File diff suppressed because it is too large Load Diff