OSDN Git Service

fixed multiple splash screen movment stacking with a state machine
[alterlinux/lightdm-webkit2-theme-alter.git] / js / theme.js
1
2 const  DEF_OPT =
3 {
4         "fit": true,
5         "filter": false,
6         "vignette": true,
7         "active-timeout": 15,
8         "content": {
9                 "clock": [{
10                         "format": "dddd, MMMM Do",
11                         "css": {
12                                 "color": "white"
13                         },
14                         "parent-css": {
15                                 "margin-top": "calc(20vh - 70pt)",
16                                 "text-align": "center",
17                                 "font-size": "70pt",
18                                 "font-family": "Noto Sans",
19                                 "font-weight": "lighter",
20                                 "text-shadow": "rgba(0, 0, 0, 0.8) 0px 7px 10px",
21                         }
22                 },{
23                         "format": ["h:mm", "A"],
24                         "css": [
25                                 {"font-size": "65pt", "font-weight": 200 },
26                                 {"font-size": "30pt", "font-weight": "lighter", "margin-left": "10pt"}
27                         ],
28                         "parent-css": {
29                                 "margin-top": "20vh",
30                                 "color": "white",
31                                 "font-family": "Noto Sans",
32                                 "text-align": "center",
33                                 "text-shadow": "rgba(0, 0, 0, 0.8) 0px 7px 10px",
34                         }
35                 }],
36
37                 "html": [{
38                         "html":"<text style='display: none' class='active-appear'>Press any key to login</text>",
39                         "css": {
40
41                                 "margin-top": "5vh",
42                                 "font-weight": "200",
43                                 "font-size": "23pt",
44                                 "text-align": "center",
45                                 "color": "rgba(255, 255, 255, 0.8)"
46                         }
47                 }]
48         }
49 };
50 window.onerror = function() {
51     log.error("Error caught");
52 };
53 var $log;
54 var $autoscroll;
55 $(document).ready(()=> {
56         $log = $("#console .terminal");
57         $autoscroll = $("#console input");
58 });
59 var log = {
60         _parse(str, color) {
61                         if (typeof str == "object") str = JSON.stringify(str,null, 2);
62                         str =  "[" + moment().format("hh:mm:ss") + "]: " + str;
63                         str = "<text style='color: " + color + "'>" + str + "</text>";
64                         return $log.html() + str;
65         },
66         normal (str) {
67                 $log.html(this._parse(str,"white"));
68                 if ($autoscroll.prop('checked'))
69                         $log[0].scrollTop = $log[0].scrollHeight;
70         },
71         error (str) {
72                 $log.html(this._parse(str,"red"));
73                 if ($autoscroll.prop('checked'))
74                         $log[0].scrollTop = $log[0].scrollHeight;
75         },
76         warn (str) {
77                 $log.html(this._parse(str,"yellow"));
78                 if ($autoscroll.prop('checked'))
79                         $log[0].scrollTop = $log[0].scrollHeight;
80         },
81         debug (str) {
82                 $log.html(this._parse(str,"lightblue"));
83                 if ($autoscroll.prop('checked'))
84                         $log[0].scrollTop = $log[0].scrollHeight;
85         }
86 }
87
88 /**
89  * Scale an image up or down until it's larger than or equal to the viewport
90  * and then center it.
91  */
92 var adjustBackground = function ($img) {
93         var viewportWidth = screen.width;
94         var viewportHeight = screen.height;
95         var viewportAspect = viewportWidth/viewportHeight;
96         var imgWidth = $img.width();
97         var imgHeight = $img.height();
98         var imgAspect = imgWidth/imgHeight;
99
100         /* First determine what is
101            the limiting factor (ie. if the image is wider, then the height is
102            is the limiting factor and needs to be adjustested */
103         if (imgAspect < viewportAspect) {
104                 /* The view port is wider compared to its height than
105                    the image is compared to the image height  meaning
106                    the width is the limiting dimension. Therefore we
107                    set image width = view ports width use the aspect
108                    ratio to set the correct height */
109                 $img.width(viewportWidth);
110                 $img.height(viewportWidth/imgAspect);
111         } else {
112                 /* The image is wider than it is tall compared to the
113                    viewport so we adjust the to fit */
114                 $img.height(viewportHeight);
115                 $img.width(viewportHeight*imgAspect);
116         }
117         this.centerImage($img);
118 }
119
120 var centerImage =  function($img) {
121         var overlapWidth = $img.width() - screen.width;
122         var overlapHeight = $img.height() - screen.height;
123
124
125         // image overlaps viewport, move the image back
126         // half the length of the overlap
127         $img.css({
128                 position: "relative",
129                 right: overlapWidth/2,
130                 bottom: overlapHeight/2
131         });
132 }
133
134 class LoginManager {
135         constructor() {
136                 this.use_splash = true;
137                 $(document).ready(() => {
138                         this.init();
139                 });
140         }
141
142         init() {
143                                 this.lightdm = typeof (lightdm) == "undefined" ? {} : lightdm;
144
145                 if (this.use_splash) {
146                         this.splash = new SplashScreen();
147                 }
148                 $(this).trigger("ready");
149         }
150
151
152         auth(username, password, callback) {
153                 // lightdm must have each of
154                 let req = ["select_user", "is_authenticated", "authenticate"];
155                 if (!req.every((x) => this.lightdm.hasOwnProperty(x) )) {
156                         log.warn("Cannot attempt login because lightdm is missing the " +
157                         "required fields. Please note that lightdm is not explicitly " +
158                         "instantiated in a browser session.");
159
160                         // call async so that events can be binded in cascade
161                         setTimeout(() => $(this).trigger("deny"));
162                         return;
163                 }
164
165                 username = username || this.lightdm.select_user;
166                 password = password || "";
167                 //  session_key = session_key || lightdm.sessions[0].key;
168
169                 let auth_cb = () =>  {
170                     this.lightdm.respond(password);
171     }
172                 let auth_complete_cb = () => {
173                         if (typeof callback == "function")
174                                 callback(this.lightdm.is_authenticated);
175
176                         $(this).trigger(this.lightdm.is_authenticated ? "grant" : "deny");
177                 }
178
179           window.show_prompt = auth_cb;
180                 window.authentication_complete = auth_complete_cb;
181                 this.lightdm.authenticate(username);
182   }
183
184         login(session_key) {
185                 if (!this.lightdm.sessions.find(x => x.key == session_key)) {
186                         log.error("Attempting to login without a valid session.");
187                         return;
188                 }
189
190                 if (!this.lightdm.is_authenticated) {
191                         log.error("Attempting to login without authentication.");
192                         return;
193                 }
194                 this.lightdm.start_session_sync(session_key);
195         }
196         authenticateWithSelected($user_input, $password_input) {
197
198         }
199
200         fillUserSelect($el) {
201                         if (!Array.isArray(this.lightdm.users)) {
202                                         log.warn("Cannot fill empty user list in lightdm.");
203                                         return;
204                         }
205
206                         $el.empty();
207                         for (let s of this.lightdm.users)
208                                         $el.append("<option value=" + s.username + ">" + s.display_name + "</option>");
209                         $el.formSelect();
210         }
211
212         fillSessionSelect($el) {
213                 if (!Array.isArray(this.lightdm.sessions)) {
214                                 log.warn("Cannot fill empty session list in lightdm.");
215                                 return;
216                 }
217
218                 $el.empty();
219
220                 for (let s of this.lightdm.sessions)
221                                 $el.append("<option value=" + s.key + ">" + s.name + "</option>");
222
223
224                 $el.formSelect();
225         }
226
227         get users() {
228                 return this.lightdm.users;
229         }
230
231         get sessions() {
232                 return this.lightdm.sessions;
233         }
234 }
235
236 class SplashScreen {
237         constructor() {
238                 this.$el = $("#splash-screen");
239                 this.$content = $("#splash-screen-content");
240                 this.options = this.getUserOptions();
241                 this.is_open = false;
242                 this.last_active = 0;
243                 this.active_timeout = 15;
244
245                 if (!this.$el.length)
246                         log.error("Missing-screen element.");
247
248                 // fit background image to sreen size and center
249                 this.$img = $(".splash-screen-img");
250                 if (!this.$img.length)
251                         log.warn("No background images supplied for splash screen.");
252                 this.$img.each((i, v) => adjustBackground($(v)));
253
254                 let options = this.options; // shorthand
255                 if (typeof options == "object") {
256                         // initilize global values if specfied in the config
257                         this.is_open = false;
258
259
260                         if (typeof options["active-timeout"] == "number")
261                                 this.active_timeout = options["active-timeout"];
262                         if (options.filter == true)
263                                 this.$img.addClass("filter");
264                         if (options.vignette == true)
265                                 this.$vignette = $("#vignette");
266                                 this.$vignette.show();
267                         if (typeof options.content == "object")
268                                 this.initContent(options.content);
269                 }
270
271                 /******************** Event Listeners ********************/
272                 this.clock = setInterval(() => {
273                         $(this).trigger("tick");
274
275                         if (!this.isActive())
276                                 $(this).trigger("inactive");
277                 }, 500);
278
279                 // update last active time
280                 $(this).on("active", () => this.last_active = moment());
281
282                 $(document).keyup((e) => {
283                         // handle events in seperate method
284                         this.keyHandler.call(this, e);
285                 }).keypress((e) => this.keyHandler.call(this, e));
286
287                 this.$el.click(() => {
288                         this.open();
289                 }).mousemove((e) => {
290                         if (!this.isActive())
291                                 $(this).trigger("active", e)
292                 });
293                 setTimeout(() => $(this).trigger("active"), 1);
294         }
295
296         /**
297          * Loops through the user specified content and adds them to the DOM in order
298          */
299         initContent(content) {
300                 for (let content_type in content) {
301                         if (content_type == "clock")
302                                 this.initClock(content[content_type]);
303                         else if (content_type == "html")
304                                 this.initHTML(content[content_type]);
305                         else
306                                 log.warn("Specified content " + content_type + " is not valid.");
307                 }
308         }
309
310         getUserOptions() {
311                 let options = {};
312                 $.extend(true, options, DEF_OPT);
313                 $.extend(true, options, {});
314                 return options;
315         }
316
317         /**
318          * open and close will toggle the screen and animate it opening and closing
319          * adds a resetTimeout function to automatically close after a period of user
320          * inactivity */
321         close(time=450)  {
322                 if (this.state == "closed" || this.state == "moving") {
323                         log.warn("Cannot close splash screen when state is: " + this.state);
324                         return;
325                 }
326
327                 this.state = "moving";
328                 this.$el.animate({
329                         top: "0"
330                 }, time, "easeInCubic", () => {
331                         this.state = "closed";
332                         clearTimeout(this.resetTimeout);
333                 });
334         }
335         open(time=400) {
336                 if (this.state == "open" || this.state == "moving") {
337                         log.warn("Cannot open splash screen when state is: " + this.state);
338                         return;
339                 }
340                 clearTimeout(this.resetTimeout);
341                 let reset_duration = 60*1000;
342
343                 if (this.state == "open" || this.state == "moving") {
344                         this.resetTimeout = setTimeout(this.reset, reset_duration);
345                         return;
346                 }
347                 this.state = "moving";
348                 this.$el.animate({
349                         top: "-100%"
350                 }, time, "easeInCubic", () => {
351                         this.state = "open";
352                         // close the screen after 1 minute of inactivty
353                         this.resetTimeout = setTimeout(() => this.reset, reset_duration);
354                 });
355         }
356         reset() {
357                 if (this.state == "open") {
358                         this.close();
359                         $(this).trigger("timeout");
360                 }
361         }
362
363         /**
364          * handles the key events for the splash
365          */
366         keyHandler(e) {
367                 switch (e.keyCode) {
368                         case 32:
369                         case 13:
370                                 this.open();
371                                 break;
372                         case 27:
373                                 if (this.state == "open") this.close();
374                                 else if (this.state == "closed") this.open();
375                                 break;
376                         default:
377                                 if (e.keyCode != 82 && e.keyCode != 17) // for testing
378                                 this.open();
379                                 break;
380                 }
381
382                 // stop reset timeout since there has been user activity
383                 if (this.state == "open")
384                         clearTimeout(this.resetTimeout);
385
386                 if (!this.isActive())
387                         $(this).trigger("active", e);
388         }
389
390         isActive() {
391                 if (moment().diff(this.last_active, "seconds", true) > 30) {
392                         return 0;
393                 }
394                 return 1;
395         }
396
397         /**
398          *  Creates clock elements based on the usr config
399          */
400         initClock(opts) {
401                 if (typeof opts != "object") {
402                         log.error("Unable to initialize clock thats not an object");
403                         return -1;
404                 }
405                 // handle arrays and a single clock object
406                 if (!Array.isArray(opts))
407                         opts = [opts];
408
409                 for (let i in opts) {
410                         this.$clock = $("<div id='clock-" + i + "' class='clock'></div>");
411                         this.$content.append(this.$clock);
412                         this.startClock(this.$clock, opts[i]);
413                 }
414         }
415
416         /**
417          * Applys the css specfied in the argument opts to the jQuery oboject $clock.
418          * Subscribes the clock to a tick event
419          */
420         startClock($clock, opts) {
421                 if (typeof opts != "object") {
422                         log.error("Clock opts is not a valid object");
423                         return -1;
424                 }
425                 // handle multiple formats for multiple clocks on the same line
426                 if(typeof opts.format == "string")
427                         opts.format = [opts.format];
428
429                 // ensure the format is now an array
430                 if(!Array.isArray(opts.format)) {
431                         log.error(`Specfied clock format is not a valid type.
432                                 Type can be a single string or Array.`);
433                         return -1;
434                 }
435
436                 if(!Array.isArray(opts.css))
437                         opts.css = [opts.css];
438
439                 for (let i in opts.format) {
440
441                         let $format = $("<sub></sub>");
442                         // create text field in clock
443                         $clock.append($format);
444                         // apply css styles
445                         if (i < opts.css.length && typeof opts.css[i] == "object")
446                                 $format.css(opts.css[i]);
447
448                         // start clock
449                         $format.text(moment().format(opts.format[i]));
450                         $(this).on("tick", () => {
451                                 $format.text(moment().format(opts.format[i]));
452                         });
453                 }
454
455                 if (typeof opts["parent-css"] == "object")
456                         $clock.css(opts["parent-css"]);
457                 log.debug($clock);
458                 $clock.show();
459         }
460
461         /**
462          * Inserts HTML specified in the user config into the splash screen
463          * accepts plain strings and objects. String literals are interpreted as
464          * normal text element. Objects are set using the jQuery API
465          */
466         initHTML(opts) {
467                 // handle single objects and strings
468                 if (!Array.isArray(opts)) {
469                         opts = [opts];
470                 }
471
472                 for (let el of opts) {
473                         if (typeof el == "string") {
474                                 let $el = $("<text>");
475                                 $el.text(el);
476                                 // create simple text element
477                                 this.$content.append($el);
478                         } else if (typeof el == "object") {
479                                 // let user specify element properites in object el.
480                                 let $el = $("<div>");
481                                 for (let prop in el) {
482                                         $el[prop](el[prop]);
483                                 }
484                                 this.$content.append($el);
485
486                         } else {
487                                 log.warn("Splash screen html element is invalid type");
488                         }
489                 }
490
491         }
492
493
494 }