mirror of
https://github.com/FJurmanovic/wallet-web.git
synced 2026-02-06 06:08:10 +00:00
implemented toasts
This commit is contained in:
@@ -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);
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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);
|
||||
};
|
||||
|
||||
|
||||
@@ -75,6 +75,7 @@ class AppMenuElement extends BaseComponentElement {
|
||||
this.appMain.closeModal();
|
||||
} else {
|
||||
this.appMain.createModal('wallet-create');
|
||||
this.appMain.pushToast(null, 'Da');
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -4,6 +4,7 @@ import { BaseComponentElement } from 'common/';
|
||||
@controller
|
||||
class AppModalElement extends BaseComponentElement {
|
||||
@target modalElement: HTMLElement;
|
||||
@target modalContent: HTMLElement;
|
||||
constructor() {
|
||||
super();
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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';
|
||||
|
||||
57
src/components/toast-portal/ToastPortalElement.ts
Normal file
57
src/components/toast-portal/ToastPortalElement.ts
Normal 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 };
|
||||
8
src/core/utils/directives/delay.ts
Normal file
8
src/core/utils/directives/delay.ts
Normal 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;
|
||||
});
|
||||
64
src/core/utils/directives/until.ts
Normal file
64
src/core/utils/directives/until.ts
Normal 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();
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
@@ -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';
|
||||
|
||||
@@ -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
20
src/core/utils/timer.ts
Normal 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;
|
||||
@@ -10,3 +10,4 @@
|
||||
@import './layout/index.scss';
|
||||
@import './app-dropdown/index.scss';
|
||||
@import './input-field/index.scss';
|
||||
@import './toast-portal/index.scss';
|
||||
|
||||
1
src/styles/toast-portal/index.scss
Normal file
1
src/styles/toast-portal/index.scss
Normal file
@@ -0,0 +1 @@
|
||||
@import './toast-portal.scss';
|
||||
29
src/styles/toast-portal/toast-portal.scss
Normal file
29
src/styles/toast-portal/toast-portal.scss
Normal 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;
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user