@panoramax/web-viewer
Version:
Panoramax web viewer for geolocated pictures
204 lines (182 loc) • 5.62 kB
JavaScript
import { LitElement, html, css, unsafeCSS, nothing } from "lit";
import LogoDead from "../../img/logo_dead.svg";
import LoaderBg from "../../img/loader_base.jpg";
/**
* Loader component display a full page covering for user waiting.
* @class Panoramax.components.ui.Loader
* @element pnx-loader
* @extends [lit.LitElement](https://lit.dev/docs/api/LitElement/)
* @example
* ```html
* <pnx-loader ._parent=${this.viewer} />
* ```
*/
export default class Loader extends LitElement {
/** @private */
static styles = css`
:host {
position: absolute;
inset: 0;
display: flex;
flex-direction: column;
justify-content: center;
gap: 10px;
align-items: center;
background-image: url('${unsafeCSS(LoaderBg)}');
background-repeat: no-repeat;
background-size: cover;
background-position: center;
z-index: 200;
font-family: var(--font-family);
font-weight: 550;
color: var(--black);
font-size: 1.4em;
text-align: center;
visibility: hidden;
opacity: 0;
}
:host(*[visible]) {
visibility: visible;
opacity: 1;
}
div.label {
background-color: rgba(255,255,255,0.5);
box-shadow: white 0 0 10px;
padding: 3px 10px;
border-radius: 50px;
}
/* Logo */
img.logo-dead { width: 100px; }
`;
/**
* Component properties.
* @memberof Panoramax.components.ui.Loader#
* @type {Object}
* @property {boolean} [visible=true] Is the loader visible to user ?
* @property {boolean} [no-label=false] Set to true to avoid loading label display
* @property {number} [value] Progress bar percentage (0-100)
*/
static properties = {
_mode: {state: true},
_label: {state: true},
_isLabelFun: {state: true},
visible: {type: Boolean, reflect: true},
"no-label": {type: Boolean},
value: {type: Number},
};
constructor() {
super();
this.visible = true;
this["no-label"] = false;
this._mode = "loading";
this._isLabelFun = false;
this.value = -1;
}
/** @private */
connectedCallback() {
super.connectedCallback();
this._nextLabel();
}
/**
* Is the loader currently visible ?
* @returns {boolean} True if visible
* @memberof Panoramax.components.ui.Loader#
*/
isVisible() {
return this.visible;
}
/**
* Dismiss loader, or show error
* @param {object} [err] Optional error object to show in browser console
* @param {str} [errMeaningful] Optional error message to show to user
* @param {fct} [next] Optional function to run after loader dismiss
* @memberof Panoramax.components.ui.Loader#
*/
dismiss(err = null, errMeaningful = null, next = null) {
clearTimeout(this._loaderLabelChanger);
if(!err) {
this.value = 100;
this._mode = "done";
this.visible = false;
this.style.transition = "all 0.5s";
setTimeout(() => this.parentNode.removeChild(this), 2000);
/**
* Event when component is ready to use.
* This happens when loader screen disappear, with picture and map loaded.
*
* To follow more precisely loading steps, you can also watch for sub-components `ready` events.
* ```js
* // Watch API-readiness
* viewer.addEventListener("api:ready", ...); // From parent
* viewer.api.addEventListener("ready", ...); // Or on sub-component
* ```
* @event Panoramax.components.core.Basic#ready
* @type {CustomEvent}
*/
const readyEvt = new CustomEvent("ready");
this._parent.dispatchEvent(readyEvt);
if(next) { next(); }
}
else {
if(err !== true) { console.error(err); }
let errHtml = [ this._parent?._t.pnx.error, html`<br />` ];
if(errMeaningful) { errHtml.push(errMeaningful, html`<br />`); }
if(next) {
errHtml.push(html`<pnx-button kind="full">${this._parent?._t.pnx.error_click}</pnx-button>`);
this.addEventListener("click", next);
}
else { errHtml.push(html`<small>${this._parent?._t.pnx.error_retry}</small>`); }
this._mode = "error";
this._label = errHtml;
const errLabel = errMeaningful || "Panoramax JS had a blocking exception";
/**
* Event for viewer failing to initially load.
*
* To follow more precisely loading failures, you can also watch for sub-components `broken` events.
* ```js
* // Watch API breaks
* viewer.addEventListener("api:broken", ...); // From parent
* viewer.api.addEventListener("broken", ...); // Or on sub-component
* ```
* @event Panoramax.components.core.Basic#broken
* @type {CustomEvent}
* @property {string} detail.error The user-friendly error message to display
*/
const brokenEvt = new CustomEvent("broken", {
detail: { error: errLabel }
});
this._parent.dispatchEvent(brokenEvt);
// Throw error
throw new Error(errLabel);
}
}
/** @private */
_nextLabel() {
if(!this._isLabelFun) {
this._label = this._parent?._t.pnx.loading_labels_serious[
Math.floor(Math.random() * this._parent?._t.pnx.loading_labels_serious.length)
];
this._isLabelFun = true;
}
else {
this._label = this._parent?._t.pnx.loading_labels_fun[
Math.floor(Math.random() * this._parent?._t.pnx.loading_labels_fun.length)
];
}
this._loaderLabelChanger = setTimeout(this._nextLabel.bind(this), 500 + Math.random() * 1000);
}
/** @private */
render() {
if(this._mode == "error") {
return html`
<img class="logo-dead" src=${LogoDead} alt="" title=${this._parent?._t.map.loading} />
${this._label}
`;
}
return html`
<pnx-progress-bar .value=${this.value}></pnx-progress-bar>
${this["no-label"] ? nothing : html`<div class="label">${this._label}</div>`}
`;
}
}
customElements.define("pnx-loader", Loader);