File: /home/lindsay/xeolabs/xeogl-next/xeogl/src/canvas/spinner.js
/**
A Progress displays a progress animation at the center of its {{#crossLink "Canvas"}}{{/crossLink}} while things are loading or otherwise busy.
## Overview
* Spinners are normally shown by {{#crossLink "Model"}}Models{{/crossLink}} while they are loading, however they may also
be shown by any application code that wants to indicate busyness.
* By default, they are also shown by components that load assets, such as {{#crossLink "Texture"}}{{/crossLink}}. You
can disable that by flipping the Spinner's {{#crossLink "Spinner/textures:property"}}{{/crossLink}} property.
* A Spinner component has a {{#crossLink "Spinner/processes:property"}}{{/crossLink}} count that indicates how many
active processes it currently represents. As a process starts, a process would increment {{#crossLink "Spinner/processes:property"}}{{/crossLink}}, then as it
completes (or fails), would decrement it again.
* A Spinner is only visible while {{#crossLink "Spinner/processes:property"}}{{/crossLink}} is greater than zero.
## Examples
* [Loading glTF model with spinner](../../examples/#importing_gltf_GearboxAssy)
## Usage
````javascript
var spinner = myScene.canvas.spinner;
// Increment count of busy processes represented by the spinner;
// assuming the count was zero, this now shows the spinner
spinner.processes++;
// Increment the count again, by some other process;
// spinner already visible, now requires two decrements
// before it becomes invisible again
spinner.processes++;
// Decrement the count; count still greater
// than zero, so spinner remains visible
spinner.process--;
// Decrement the count; count now zero,
// so spinner becomes invisible
spinner.process--;
````
By default, a Spinner shows while resources are loading for components like
{{#crossLink "Texture"}}{{/crossLink}}. We can disable that like this:
````javascript
// Don't show while resources are loading for Textures etc.
spinner.textures = false;
````
@class Spinner
@module xeogl
@submodule canvas
@extends Component
*/
import {Component} from '../component.js';
const type = "xeogl.Spinner";
let spinnerCSSInjected = false; // Ensures lazy-injected CSS only injected once
const spinnerCSS = ".sk-fading-circle {\
background: transparent;\
margin: 20px auto;\
width: 50px;\
height:50px;\
position: relative;\
}\
.sk-fading-circle .sk-circle {\
width: 120%;\
height: 120%;\
position: absolute;\
left: 0;\
top: 0;\
}\
.sk-fading-circle .sk-circle:before {\
content: '';\
display: block;\
margin: 0 auto;\
width: 15%;\
height: 15%;\
background-color: #ff8800;\
border-radius: 100%;\
-webkit-animation: sk-circleFadeDelay 1.2s infinite ease-in-out both;\
animation: sk-circleFadeDelay 1.2s infinite ease-in-out both;\
}\
.sk-fading-circle .sk-circle2 {\
-webkit-transform: rotate(30deg);\
-ms-transform: rotate(30deg);\
transform: rotate(30deg);\
}\
.sk-fading-circle .sk-circle3 {\
-webkit-transform: rotate(60deg);\
-ms-transform: rotate(60deg);\
transform: rotate(60deg);\
}\
.sk-fading-circle .sk-circle4 {\
-webkit-transform: rotate(90deg);\
-ms-transform: rotate(90deg);\
transform: rotate(90deg);\
}\
.sk-fading-circle .sk-circle5 {\
-webkit-transform: rotate(120deg);\
-ms-transform: rotate(120deg);\
transform: rotate(120deg);\
}\
.sk-fading-circle .sk-circle6 {\
-webkit-transform: rotate(150deg);\
-ms-transform: rotate(150deg);\
transform: rotate(150deg);\
}\
.sk-fading-circle .sk-circle7 {\
-webkit-transform: rotate(180deg);\
-ms-transform: rotate(180deg);\
transform: rotate(180deg);\
}\
.sk-fading-circle .sk-circle8 {\
-webkit-transform: rotate(210deg);\
-ms-transform: rotate(210deg);\
transform: rotate(210deg);\
}\
.sk-fading-circle .sk-circle9 {\
-webkit-transform: rotate(240deg);\
-ms-transform: rotate(240deg);\
transform: rotate(240deg);\
}\
.sk-fading-circle .sk-circle10 {\
-webkit-transform: rotate(270deg);\
-ms-transform: rotate(270deg);\
transform: rotate(270deg);\
}\
.sk-fading-circle .sk-circle11 {\
-webkit-transform: rotate(300deg);\
-ms-transform: rotate(300deg);\
transform: rotate(300deg);\
}\
.sk-fading-circle .sk-circle12 {\
-webkit-transform: rotate(330deg);\
-ms-transform: rotate(330deg);\
transform: rotate(330deg);\
}\
.sk-fading-circle .sk-circle2:before {\
-webkit-animation-delay: -1.1s;\
animation-delay: -1.1s;\
}\
.sk-fading-circle .sk-circle3:before {\
-webkit-animation-delay: -1s;\
animation-delay: -1s;\
}\
.sk-fading-circle .sk-circle4:before {\
-webkit-animation-delay: -0.9s;\
animation-delay: -0.9s;\
}\
.sk-fading-circle .sk-circle5:before {\
-webkit-animation-delay: -0.8s;\
animation-delay: -0.8s;\
}\
.sk-fading-circle .sk-circle6:before {\
-webkit-animation-delay: -0.7s;\
animation-delay: -0.7s;\
}\
.sk-fading-circle .sk-circle7:before {\
-webkit-animation-delay: -0.6s;\
animation-delay: -0.6s;\
}\
.sk-fading-circle .sk-circle8:before {\
-webkit-animation-delay: -0.5s;\
animation-delay: -0.5s;\
}\
.sk-fading-circle .sk-circle9:before {\
-webkit-animation-delay: -0.4s;\
animation-delay: -0.4s;\
}\
.sk-fading-circle .sk-circle10:before {\
-webkit-animation-delay: -0.3s;\
animation-delay: -0.3s;\
}\
.sk-fading-circle .sk-circle11:before {\
-webkit-animation-delay: -0.2s;\
animation-delay: -0.2s;\
}\
.sk-fading-circle .sk-circle12:before {\
-webkit-animation-delay: -0.1s;\
animation-delay: -0.1s;\
}\
@-webkit-keyframes sk-circleFadeDelay {\
0%, 39%, 100% { opacity: 0; }\
40% { opacity: 1; }\
}\
@keyframes sk-circleFadeDelay {\
0%, 39%, 100% { opacity: 0; }\
40% { opacity: 1; }\
}";
class Spinner extends Component {
/**
JavaScript class name for this Component.
For example: "xeogl.AmbientLight", "xeogl.MetallicMaterial" etc.
@property type
@type String
@final
*/
get type() {
return type;
}
init(cfg) {
super.init(cfg);
this._canvas = cfg.canvas;
this._injectSpinnerCSS();
const div = document.createElement('div');
const style = div.style;
style["z-index"] = "9000";
style.position = "absolute";
div.innerHTML = '<div class="sk-fading-circle">\
<div class="sk-circle1 sk-circle"></div>\
<div class="sk-circle2 sk-circle"></div>\
<div class="sk-circle3 sk-circle"></div>\
<div class="sk-circle4 sk-circle"></div>\
<div class="sk-circle5 sk-circle"></div>\
<div class="sk-circle6 sk-circle"></div>\
<div class="sk-circle7 sk-circle"></div>\
<div class="sk-circle8 sk-circle"></div>\
<div class="sk-circle9 sk-circle"></div>\
<div class="sk-circle10 sk-circle"></div>\
<div class="sk-circle11 sk-circle"></div>\
<div class="sk-circle12 sk-circle"></div>\
</div>';
this._canvas.parentElement.appendChild(div);
this._element = div;
this._adjustPosition();
this.processes = 0;
}
/**
The number of processes this Spinner represents.
The Spinner is visible while this property is greater than zero.
Increment this property whenever you commence some process during which you want
the Spinner to be visible, then decrement it again when the process is complete.
Clamps to zero if you attempt to set to to a negative value.
Fires a {{#crossLink "Spinner/processes:event"}}{{/crossLink}} event on change.
@property processes
@default 0
@type Number
*/
set processes(value) {
value = value || 0;
if (this._processes === value) {
return;
}
if (value < 0) {
return;
}
const prevValue = this._processes;
this._processes = value;
this._element.style["visibility"] = (this._processes > 0) ? "visible" : "hidden";
/**
Fired whenever this Spinner's {{#crossLink "Spinner/visible:property"}}{{/crossLink}} property changes.
@event processes
@param value The property's new value
*/
this.fire("processes", this._processes);
if (this._processes === 0 && this._processes !== prevValue) {
/**
Fired whenever this Spinner's {{#crossLink "Spinner/visible:property"}}{{/crossLink}} property becomes zero.
@event zeroProcesses
*/
this.fire("zeroProcesses", this._processes);
}
}
get processes() {
return this._processes;
}
_adjustPosition() { // (Re)positions spinner DIV over the center of the canvas
if (!this._canvas || !this._element) {
return;
}
const canvas = this._canvas;
const spinner = this._element;
const spinnerStyle = spinner.style;
spinnerStyle["left"] = (canvas.offsetLeft + (canvas.clientWidth * 0.5) - (spinner.clientWidth * 0.5)) + "px";
spinnerStyle["top"] = (canvas.offsetTop + (canvas.clientHeight * 0.5) - (spinner.clientHeight * 0.5)) + "px";
}
_injectSpinnerCSS() {
if (spinnerCSSInjected) {
return;
}
const node = document.createElement('style');
node.innerHTML = spinnerCSS;
document.body.appendChild(node);
spinnerCSSInjected = true;
}
}
export{Spinner};