OSDN Git Service

Added more documentation,
[alterlinux/lightdm-webkit2-theme-alter.git] / js / splashscreen.js
1
2 /**
3 * Creates a splash screen with custom generated content,
4 * diplays when the user inactive.
5 *
6 * A Plugin for the LoginManager class
7 *
8 * EVENTS:
9 * -----------------------------------------------------------------------------
10 * Listens:
11 * 'load' signifies that SplashScreen has finished asyncrhonously
12 * loading its config files.
13 *
14 * 'ready' emitted once the SplashScreen has finished creating its content
15 *
16 * Triggers:
17 * 'init' causes the SplashScreen content to be generated
18 *
19 */
20 class SplashScreen {
21   constructor() {
22     /* Speed of SplashScreen transitions */
23     this._ANIMATION_DUR = 300;
24
25     /* Default options for the  splash screen */
26     this._DEF_OPT = {
27       "fit": true,
28       "filter": false,
29       "vignette": true,
30       "active-timeout": 15,
31       "transition": "fade",
32       "img": false,
33       "content": {
34         "clock": [{
35           "format": "dddd, MMMM Do",
36           "css": {
37             "color": "white"
38           },
39           "parent-css": {
40             "margin-top": "calc(20vh - 70pt)",
41             "text-align": "center",
42             "font-size": "70pt",
43             "font-family": "Noto Sans",
44             "font-weight": "lighter",
45             "text-shadow": "rgba(0, 0, 0, 0.5) 0px 7px 10px",
46           }
47         },{
48           "format": ["h:mm", "A"],
49           "css": [
50             {"font-size": "65pt", "font-weight": 200 },
51             {"font-size": "30pt", "font-weight": "lighter", "margin-left": "10pt"}
52           ],
53           "parent-css": {
54             "margin-top": "20vh",
55             "color": "white",
56             "font-family": "Noto Sans",
57             "text-align": "center",
58             "text-shadow": "rgba(0, 0, 0, 0.5) 0px 7px 10px",
59           }
60         }],
61
62         "html": [{
63           "html":"<text style='display: none' class='active-appear'>Press any key to login</text>",
64           "css": {
65             "margin-top": "5vh",
66             "font-weight": "200",
67             "font-size": "23pt",
68             "text-align": "center",
69             "color": "rgba(255, 255, 255, 0.8)"
70           }
71         }]
72       }
73     };
74     this.template = `<!-- Autogenerated SplashScreen -->
75     <div id="splash-screen">
76     <div class="vignette"></div>
77     <div id="splash-screen-content"></div>
78     </div>
79     <!-- End Autogenerated SplashScreen -->`;
80     this.$el = $(this.template);
81     $("body").prepend(this.$el);
82
83     this._loadConfig();
84     // listen to the init event
85     $(this).on("init", () => this._init());
86   }
87
88   /**
89   * Generates the content specified by the user config.
90   * Should be called after the load event has been triggered.
91   */
92   _init() {
93     this.$content = $("#splash-screen-content");
94     this._state = "closed";
95     this._last_active = 0;
96     this._active_timeout = 15;
97
98     let options = this._options; // shorthand
99     if (typeof options == "object") {
100
101       // initilize global values if specfied in the config
102       if (typeof options.img == "string") {
103         this.$img = $(`<img class="splash-screen-img" src="${options.img}">`);
104
105         this.$el.prepend(this.$img);
106         this.$img.one("load", () => {
107           // fit background image to sreen size and center
108           adjustBackground($(".splash-screen-img"))
109         })
110       }
111
112       if (typeof options["active-timeout"] == "number")
113       this._active_timeout = options["active-timeout"];
114
115       if (options.filter == true)
116       this.$img.addClass("filter");
117
118       if (options.vignette == true) {
119         this.$vignette = $("#vignette");
120         this.$vignette.show();
121       }
122
123       if (typeof options.transition == "string")
124       this.transition = options.transition;
125
126       if (typeof options.content == "object")
127       this._initContent(options.content);
128
129       $(this).trigger("ready");
130     }
131
132     /******************** Event Listeners ********************/
133     this.clock = setInterval(() => {
134       $(this).trigger("tick");
135
136       if (!this._isActive())
137       $(this).trigger("inactive");
138     }, 500);
139
140     // update last active time
141     $(this).on("active", () => this._last_active = moment());
142
143     $(document).keyup((e) => {
144       // handle events in seperate method
145       this._keyHandler.call(this, e);
146     }).keypress((e) => this._keyHandler.call(this, e));
147
148     /* Bind event listners to trigger activity event. This can be used on the
149     front end to implement spcific behaivours while the user is active */
150     this.$el.click(() => {
151       this._open();
152     }).mousemove((e) => {
153       if (!this._isActive())
154       $(this).trigger("active", e)
155     });
156     setTimeout(() => $(this).trigger("active"), 1);
157   }
158
159   /**
160   * Loops through the user specified content and appends them to the DOM
161   * in the order specified by the user config
162   */
163   _initContent(content) {
164     for (let content_type in content) {
165       if (content_type == "clock")
166       this._initClock(content[content_type]);
167       else if (content_type == "html")
168       this._initHTML(content[content_type]);
169       else
170       log.warn("Specified content " + content_type + " is not valid.");
171     }
172   }
173
174   /**
175   * Asyncrhonously reads JSON config file from json/SplashScreen.json
176   * and overwrites the default options with those specified by the config.
177   *
178   * Triggers: 'load' on completion. Caller (LoginManager) must listen for this
179   *     event to then trigger 'init'
180   */
181   _loadConfig() {
182     let options = {};
183     $.extend(true, options, this._DEF_OPT);
184
185     $.getJSON("json/SplashScreen.json", (data) => {
186       $.extend(true, options, data);
187       this._options = options;
188       $(this).trigger("load");
189     }).fail(() => {
190       $.extend(true, options, {});
191       this._options = options;
192       $(this).trigger("load");
193     });
194   }
195
196   /**
197   * Closes the splash screen if there has been no user activity
198   */
199   _reset() {
200     if (this._state == "open") {
201       this._close();
202       $(this).trigger("timeout");
203     }
204   }
205
206   /**
207   * Determines if there was user acitivty within in a given amount
208   * of time.
209   * Returns 1 if splash screen is active, else 0
210   */
211   _isActive() {
212     if (moment().diff(this._last_active, "seconds", true) > 30) {
213       return 0;
214     }
215     return 1;
216   }
217
218   /**
219   * Creates clock elements based on the usr config.
220   * Appends each clock to the DOM and binds update events using _startClock
221   */
222   _initClock(opts) {
223     if (typeof opts != "object") {
224       log.error("Unable to initialize clock thats not an object");
225       return -1;
226     }
227     // handle arrays and a single clock object
228     if (!Array.isArray(opts))
229     opts = [opts];
230
231     /* loop through each clock in the config and add it to the dom,
232     then initialize an update event using start clock */
233     for (let i in opts) {
234       this.$clock = $("<div id='clock-" + i + "' class='clock'></div>");
235       this.$content.append(this.$clock);
236       this._startClock(this.$clock, opts[i]);
237     }
238   }
239
240   /**
241   * Applys the css specfied in the argument opts to the jQuery oboject $clock.
242   * Subscribes the clock to a tick event
243   */
244   _startClock($clock, opts) {
245     if (typeof opts != "object") {
246       log.error("Clock opts is not a valid object");
247       return -1;
248     }
249     // handle multiple formats for multiple clocks on the same line
250     if(typeof opts.format == "string")
251     opts.format = [opts.format];
252
253     // ensure the format is now an array
254     if(!Array.isArray(opts.format)) {
255       log.error(`Specfied clock format is not a valid type.
256         Type can be a single string or Array.`);
257         return -1;
258       }
259
260       if(!Array.isArray(opts.css))
261       opts.css = [opts.css];
262
263       for (let i in opts.format) {
264
265         let $format = $("<sub></sub>");
266         // create text field in clock
267         $clock.append($format);
268         // apply css styles
269         if (i < opts.css.length && typeof opts.css[i] == "object")
270         $format.css(opts.css[i]);
271
272         // start clock
273         $format.text(moment().format(opts.format[i]));
274         $(this).on("tick", () => {
275           $format.text(moment().format(opts.format[i]));
276         });
277       }
278
279       if (typeof opts["parent-css"] == "object")
280       $clock.css(opts["parent-css"]);
281
282       $clock.show();
283     }
284
285     /**
286     * Inserts HTML specified in the user config into the splash screen
287     * accepts plain strings and objects. String literals are interpreted as
288     * normal text element. Objects are set using the jQuery API
289     */
290     _initHTML(opts) {
291       // handle single objects and strings
292       if (!Array.isArray(opts)) {
293         opts = [opts];
294       }
295
296       for (let el of opts) {
297         if (typeof el == "string") {
298           let $el = $("<text>");
299           $el.text(el);
300           // create simple text element
301           this.$content.append($el);
302         } else if (typeof el == "object") {
303           // let user specify element properites in object el.
304           let $el = $("<div>");
305           for (let prop in el) {
306             $el[prop](el[prop]);
307           }
308           this.$content.append($el);
309
310         } else {
311           log.warn("Splash screen html element is invalid type");
312         }
313       }
314
315     }
316
317     /**
318     * Handles the key events for the SplachScreen and active-inactive events
319     */
320     _keyHandler(e) {
321       switch (e.keyCode) {
322         case 32:
323         case 13: // Enter key
324         if (this._state == "closed")
325         this._open();
326         break;
327         case 27: // ESC key
328         if (this._state == "open")
329         this._close();
330         else if (this._state == "closed")
331         this._open();
332         break;
333         default:
334         if (this._state == "closed")
335         this._open();
336         break;
337       }
338
339       // stop reset timeout since there has been user activity
340       if (this._state == "open")
341       clearTimeout(this.resetTimeout);
342
343       // trigger active event if the user has been inactive long enough
344       if (!this._isActive())
345       $(this).trigger("active", e);
346     }
347
348     /**
349     * _open and _close will toggle the screen and animate it opening and closing
350     * adds a resetTimeout function to automatically close after a period of user
351     * inactivity
352     *
353     * Uses a _state machine consisting of {'open', 'closed', 'moving'}.
354     * Transitions are not possible while the _state == moving. This prevents
355     * fillUserSelect from trigger concurrernt transitions which would lead to
356     * undefined behaivour.
357     */
358     _close()  {
359       if (this._state != "open") {
360         log.warn("Cannot close splash screen when _state is: " + this._state);
361         return;
362       }
363
364       this._state = "moving";
365       if (this.transition == "fade") {
366         this.$el.fadeIn("slow", () => {
367           this._state = "closed";
368           this.$content.fadeIn("slow");
369           clearTimeout(this.resetTimeout);
370         });
371       } else if (this.transition == "slide") {
372         this.$el.animate({
373           top: "0"
374         },"slow", "easeOutQuint", () => {
375           this._state = "closed";
376           clearTimeout(this.resetTimeout);
377         });
378       }
379
380
381     }
382     _open() {
383       if (this._state != "closed") {
384         log.warn("Cannot open splash screen when _state is: " + this._state);
385         return;
386       }
387       clearTimeout(this.resetTimeout);
388       let reset_duration = 60*1000;
389
390       if (this._state == "open" || this._state == "moving") {
391         this.resetTimeout = setTimeout(this.reset, reset_duration);
392         return;
393       }
394       this._state = "moving";
395
396       if (this.transition == "fade") {
397         this.$content.fadeOut("fast", () => {
398           this.$el.fadeOut(this._ANIMATION_DUR, () => {
399             this._state = "open";
400           });
401         });
402
403       } else if (this.transition == "slide") {
404         this.$el.animate({
405           top: "-100%"
406         }, "slow", "easeInCubic", () => {
407           this._state = "open";
408         });
409       }
410
411
412     }
413
414   }