pixl-xyapp 2.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE.md +11 -0
- package/README.md +485 -0
- package/css/base.css +2736 -0
- package/css/boilerplate.css +295 -0
- package/css/normalize.css +349 -0
- package/js/base.js +648 -0
- package/js/calendar.js +166 -0
- package/js/datetime.js +233 -0
- package/js/dialog.js +385 -0
- package/js/misc.js +311 -0
- package/js/page.js +1940 -0
- package/js/popover.js +158 -0
- package/js/select.js +845 -0
- package/js/tools.js +1212 -0
- package/js/unscroll.min.js +3 -0
- package/package.json +20 -0
package/js/base.js
ADDED
|
@@ -0,0 +1,648 @@
|
|
|
1
|
+
// Base App Framework
|
|
2
|
+
|
|
3
|
+
var app = {
|
|
4
|
+
|
|
5
|
+
username: '',
|
|
6
|
+
cacheBust: 0,
|
|
7
|
+
proto: location.protocol.match(/^https/i) ? 'https://' : 'http://',
|
|
8
|
+
secure: !!location.protocol.match(/^https/i),
|
|
9
|
+
retina: (window.devicePixelRatio > 1),
|
|
10
|
+
mobile: !!navigator.userAgent.match(/(iOS|iPhone|iPad|Android)/),
|
|
11
|
+
base_api_url: '/api',
|
|
12
|
+
plain_text_post: false,
|
|
13
|
+
prefs: {},
|
|
14
|
+
lastClick: {},
|
|
15
|
+
|
|
16
|
+
init: function() {
|
|
17
|
+
// override this in your app.js
|
|
18
|
+
},
|
|
19
|
+
|
|
20
|
+
extend: function(obj) {
|
|
21
|
+
// extend app object with another
|
|
22
|
+
for (var key in obj) this[key] = obj[key];
|
|
23
|
+
},
|
|
24
|
+
|
|
25
|
+
setAPIBaseURL: function(url) {
|
|
26
|
+
// set the API base URL (commands are appended to this)
|
|
27
|
+
this.base_api_url = url;
|
|
28
|
+
},
|
|
29
|
+
|
|
30
|
+
setWindowTitle: function(title) {
|
|
31
|
+
// set the current window title, includes app name
|
|
32
|
+
document.title = title + ' | ' + this.name;
|
|
33
|
+
},
|
|
34
|
+
|
|
35
|
+
setHeaderTitle: function(title) {
|
|
36
|
+
// set header title
|
|
37
|
+
$('.header_title').html( title );
|
|
38
|
+
},
|
|
39
|
+
|
|
40
|
+
setHeaderNav: function(items) {
|
|
41
|
+
// populate header with multiple nav elements
|
|
42
|
+
var html = '<div class="header_nav_cont">';
|
|
43
|
+
|
|
44
|
+
items.forEach( function(item, idx) {
|
|
45
|
+
if (typeof(item) == 'string') {
|
|
46
|
+
if (config.ui.nav[item]) item = config.ui.nav[item];
|
|
47
|
+
else { html += item; return; }
|
|
48
|
+
} // custom
|
|
49
|
+
if (!item.type && (idx > 0)) html += '<div class="header_nav_sep"><i class="mdi mdi-chevron-right"></i></div>';
|
|
50
|
+
if (item.loc) item.type = 'link';
|
|
51
|
+
|
|
52
|
+
switch (item.type) {
|
|
53
|
+
case 'badge':
|
|
54
|
+
html += '<div class="color_label ' + item.color + '">';
|
|
55
|
+
if (item.icon) html += '<i class="mdi mdi-' + item.icon + '"> </i>';
|
|
56
|
+
html += item.title + '</div>';
|
|
57
|
+
break;
|
|
58
|
+
|
|
59
|
+
case 'link':
|
|
60
|
+
html += '<a class="header_nav_item" href="' + item.loc + '">';
|
|
61
|
+
if (item.icon) html += '<i class="mdi mdi-' + item.icon + '"></i>';
|
|
62
|
+
html += '<span>' + item.title + '</span></a>';
|
|
63
|
+
break;
|
|
64
|
+
|
|
65
|
+
default:
|
|
66
|
+
html += '<div class="header_nav_item">';
|
|
67
|
+
if (item.icon) html += '<i class="mdi mdi-' + item.icon + '"></i>';
|
|
68
|
+
html += item.title + '</div>';
|
|
69
|
+
break;
|
|
70
|
+
} // switch item.type
|
|
71
|
+
} ); // foreach nav item
|
|
72
|
+
|
|
73
|
+
html += '</div>';
|
|
74
|
+
this.setHeaderTitle(html);
|
|
75
|
+
},
|
|
76
|
+
|
|
77
|
+
showSidebar: function(visible) {
|
|
78
|
+
// show or hide sidebar
|
|
79
|
+
if (visible) $('body').addClass('sidebar');
|
|
80
|
+
else $('body').removeClass('sidebar');
|
|
81
|
+
},
|
|
82
|
+
|
|
83
|
+
highlightTab: function(id) {
|
|
84
|
+
// highlight custom tab in sidebar
|
|
85
|
+
$('.sidebar .section_item').removeClass('active').addClass('inactive');
|
|
86
|
+
$('#tab_' + id).removeClass('inactive').addClass('active');
|
|
87
|
+
},
|
|
88
|
+
|
|
89
|
+
updateHeaderInfo: function() {
|
|
90
|
+
// update top-right display
|
|
91
|
+
// override this function in app
|
|
92
|
+
},
|
|
93
|
+
|
|
94
|
+
getUserAvatarURL: function(size, bust) {
|
|
95
|
+
// get url to current user avatar
|
|
96
|
+
var url = '';
|
|
97
|
+
|
|
98
|
+
// user may have custom avatar
|
|
99
|
+
if (this.user && this.user.avatar) {
|
|
100
|
+
// convert to protocol-less URL
|
|
101
|
+
url = this.user.avatar.replace(/^\w+\:/, '');
|
|
102
|
+
}
|
|
103
|
+
else {
|
|
104
|
+
url = '/api/app/avatar/' + this.username + '.png?size=' + size;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
if (bust) {
|
|
108
|
+
url += (url.match(/\?/) ? '&' : '?') + 'random=' + Math.random();
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
return url;
|
|
112
|
+
},
|
|
113
|
+
|
|
114
|
+
doMyAccount: function() {
|
|
115
|
+
// nav to the my account page
|
|
116
|
+
Nav.go('MyAccount');
|
|
117
|
+
},
|
|
118
|
+
|
|
119
|
+
isAdmin: function() {
|
|
120
|
+
// return true if user is logged in and admin, false otherwise
|
|
121
|
+
return !!( app.user && app.user.privileges && app.user.privileges.admin );
|
|
122
|
+
},
|
|
123
|
+
|
|
124
|
+
handleResize: function() {
|
|
125
|
+
// called when window resizes
|
|
126
|
+
if (this.page_manager && this.page_manager.current_page_id) {
|
|
127
|
+
var id = this.page_manager.current_page_id;
|
|
128
|
+
var page = this.page_manager.find(id);
|
|
129
|
+
if (page && page.onResize) page.onResize( get_inner_window_size() );
|
|
130
|
+
if (page && page.updateBoxButtonFloaterPosition) page.updateBoxButtonFloaterPosition();
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
// also handle sending resize events at a 250ms delay
|
|
134
|
+
// so some pages can perform a more expensive refresh at a slower interval
|
|
135
|
+
if (!this.resize_timer) {
|
|
136
|
+
this.resize_timer = setTimeout( this.handleResizeDelay.bind(this), 250 );
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
if (Dialog.active) Dialog.autoResize();
|
|
140
|
+
if (CodeEditor.active) CodeEditor.autoResize();
|
|
141
|
+
if (Popover.enabled && !this.mobile) Popover.detach();
|
|
142
|
+
},
|
|
143
|
+
|
|
144
|
+
handleResizeDelay: function() {
|
|
145
|
+
// called 250ms after latest resize event
|
|
146
|
+
this.resize_timer = null;
|
|
147
|
+
|
|
148
|
+
if (this.page_manager && this.page_manager.current_page_id) {
|
|
149
|
+
var id = this.page_manager.current_page_id;
|
|
150
|
+
var page = this.page_manager.find(id);
|
|
151
|
+
if (page && page.onResizeDelay) page.onResizeDelay( get_inner_window_size() );
|
|
152
|
+
}
|
|
153
|
+
},
|
|
154
|
+
|
|
155
|
+
handleKeyDown: function(event) {
|
|
156
|
+
// send keydown event to page if text element isn't current focused
|
|
157
|
+
if (document.activeElement && document.activeElement.tagName.match(/^(INPUT|TEXTAREA)$/)) return;
|
|
158
|
+
|
|
159
|
+
if (this.page_manager && this.page_manager.current_page_id) {
|
|
160
|
+
var id = this.page_manager.current_page_id;
|
|
161
|
+
var page = this.page_manager.find(id);
|
|
162
|
+
if (page && page.onKeyDown) page.onKeyDown(event);
|
|
163
|
+
}
|
|
164
|
+
},
|
|
165
|
+
|
|
166
|
+
handleUnload: function(event) {
|
|
167
|
+
// called just before user navs off
|
|
168
|
+
if (this.page_manager && this.page_manager.current_page_id && $P && $P() && $P().onBeforeUnload) {
|
|
169
|
+
var result = $P().onBeforeUnload();
|
|
170
|
+
if (result) {
|
|
171
|
+
(event || window.event).returnValue = result; //Gecko + IE
|
|
172
|
+
return result; // Webkit, Safari, Chrome etc.
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
},
|
|
176
|
+
|
|
177
|
+
doError: function(msg, args) {
|
|
178
|
+
// show an error message at the top of the screen
|
|
179
|
+
// and hide the progress dialog if applicable
|
|
180
|
+
if (config.ui.errors[msg]) msg = config.ui.errors[msg];
|
|
181
|
+
if (args) msg = substitute(msg, args);
|
|
182
|
+
|
|
183
|
+
Debug.trace('error', "ERROR: " + msg);
|
|
184
|
+
this.showMessage( 'error', msg, 0 );
|
|
185
|
+
if (Dialog.progress) Dialog.hideProgress();
|
|
186
|
+
return null;
|
|
187
|
+
},
|
|
188
|
+
|
|
189
|
+
badField: function(id, msg, args) {
|
|
190
|
+
// mark field as bad
|
|
191
|
+
if (!msg) {
|
|
192
|
+
var raw_id = id.replace(/^\#/, '');
|
|
193
|
+
if (config.ui.errors[raw_id]) msg = config.ui.errors[raw_id];
|
|
194
|
+
}
|
|
195
|
+
if (msg && args) msg = substitute(msg, args);
|
|
196
|
+
|
|
197
|
+
if (id.match(/^\w+$/)) id = '#' + id;
|
|
198
|
+
$(id).removeClass('invalid').width(); // trigger reflow to reset css animation
|
|
199
|
+
$(id).addClass('invalid');
|
|
200
|
+
try { $(id).focus(); } catch (e) {;}
|
|
201
|
+
|
|
202
|
+
if (msg) return this.doError(msg);
|
|
203
|
+
else return false;
|
|
204
|
+
},
|
|
205
|
+
|
|
206
|
+
clearError: function() {
|
|
207
|
+
// clear last error
|
|
208
|
+
$('div.toast.error').remove();
|
|
209
|
+
$('.invalid').removeClass('invalid');
|
|
210
|
+
},
|
|
211
|
+
|
|
212
|
+
showMessage: function(type, msg, lifetime, loc) {
|
|
213
|
+
// show success, warning or error message
|
|
214
|
+
// Dialog.hide();
|
|
215
|
+
var icon = '';
|
|
216
|
+
switch (type) {
|
|
217
|
+
case 'success': icon = 'check-circle'; break;
|
|
218
|
+
case 'warning': icon = 'alert-circle'; break;
|
|
219
|
+
case 'error': icon = 'alert-decagram'; break;
|
|
220
|
+
case 'info': icon = 'information-outline'; break;
|
|
221
|
+
|
|
222
|
+
default:
|
|
223
|
+
if (type.match(/^(\w+)\/(.+)$/)) { type = RegExp.$1; icon = RegExp.$2; }
|
|
224
|
+
break;
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
// strip html to prevent script injection
|
|
228
|
+
msg = strip_html(msg);
|
|
229
|
+
|
|
230
|
+
this.toast({ type, icon, msg, lifetime, loc });
|
|
231
|
+
},
|
|
232
|
+
|
|
233
|
+
toast: function(args) {
|
|
234
|
+
// show toast notification given raw html
|
|
235
|
+
var { type, icon, msg, lifetime, loc } = args;
|
|
236
|
+
|
|
237
|
+
var html = '';
|
|
238
|
+
html += '<div class="toast ' + type + '" style="display:none">';
|
|
239
|
+
html += '<i class="mdi mdi-' + icon + '"></i>';
|
|
240
|
+
html += '<span>' + msg + '</span>';
|
|
241
|
+
html += '</div>';
|
|
242
|
+
|
|
243
|
+
var $toast = $(html);
|
|
244
|
+
var timer = null;
|
|
245
|
+
$('#toaster').append( $toast );
|
|
246
|
+
|
|
247
|
+
$toast.fadeIn(250);
|
|
248
|
+
$toast.on('click', function() {
|
|
249
|
+
if (timer) clearTimeout(timer);
|
|
250
|
+
$toast.fadeOut( 250, function() { $(this).remove(); } );
|
|
251
|
+
if (loc) Nav.go(loc);
|
|
252
|
+
} );
|
|
253
|
+
|
|
254
|
+
if ((type == 'success') || (type == 'info') || lifetime) {
|
|
255
|
+
if (!lifetime) lifetime = 8;
|
|
256
|
+
timer = setTimeout( function() {
|
|
257
|
+
$toast.fadeOut( 500, function() { $(this).remove(); } );
|
|
258
|
+
}, lifetime * 1000 );
|
|
259
|
+
}
|
|
260
|
+
},
|
|
261
|
+
|
|
262
|
+
hideMessage: function(animate) {
|
|
263
|
+
// if (animate) $('#d_message').hide(animate);
|
|
264
|
+
// else $('#d_message').hide();
|
|
265
|
+
|
|
266
|
+
if (animate) $('div.toast').fadeOut( animate, function() { $(this).remove(); } );
|
|
267
|
+
else $('div.toast').remove();
|
|
268
|
+
},
|
|
269
|
+
|
|
270
|
+
api: {
|
|
271
|
+
|
|
272
|
+
request: function(url, opts, callback, errorCallback) {
|
|
273
|
+
// send HTTP GET to API endpoint
|
|
274
|
+
Debug.trace('api', "Sending API request: " + url );
|
|
275
|
+
|
|
276
|
+
// default 10 sec timeout
|
|
277
|
+
var timeout = opts.timeout || 10000;
|
|
278
|
+
delete opts.timeout;
|
|
279
|
+
|
|
280
|
+
// retry delay (w/exp backoff)
|
|
281
|
+
var retryDelay = opts.retryDelay || 100;
|
|
282
|
+
delete opts.retryDelay;
|
|
283
|
+
|
|
284
|
+
var timed_out = false;
|
|
285
|
+
var timer = setTimeout( function() {
|
|
286
|
+
timed_out = true;
|
|
287
|
+
timer = null;
|
|
288
|
+
var err = new Error("Timeout");
|
|
289
|
+
Debug.trace('api', "HTTP Error: " + err);
|
|
290
|
+
|
|
291
|
+
if (errorCallback) errorCallback({ code: 'http', description: '' + (err.message || err) });
|
|
292
|
+
else app.doError( "HTTP Error: " + err.message || err );
|
|
293
|
+
}, timeout );
|
|
294
|
+
|
|
295
|
+
window.fetch( url, opts )
|
|
296
|
+
.then( function(res) {
|
|
297
|
+
if (timer) { clearTimeout(timer); timer = null; }
|
|
298
|
+
if (!res.ok) throw new Error("HTTP " + res.status + " " + res.statusText);
|
|
299
|
+
return res.json();
|
|
300
|
+
} )
|
|
301
|
+
.then(function(json) {
|
|
302
|
+
// got response
|
|
303
|
+
if (timed_out) return;
|
|
304
|
+
if (timer) { clearTimeout(timer); timer = null; }
|
|
305
|
+
var text = JSON.stringify(json);
|
|
306
|
+
if (text.length > 8192) text = "(" + text.length + " bytes)";
|
|
307
|
+
Debug.trace('api', "API Response: " + text );
|
|
308
|
+
|
|
309
|
+
// use setTimeout to avoid insanity with the stupid fetch promise
|
|
310
|
+
setTimeout( function() {
|
|
311
|
+
if (('code' in json) && (json.code != 0)) {
|
|
312
|
+
// an error occurred within the JSON response
|
|
313
|
+
// session errors are handled specially
|
|
314
|
+
if (json.code == 'session') app.doUserLogout(true);
|
|
315
|
+
else if (errorCallback) errorCallback(json);
|
|
316
|
+
else app.doError("API Error: " + json.description);
|
|
317
|
+
}
|
|
318
|
+
else if (callback) callback( json );
|
|
319
|
+
}, 1 );
|
|
320
|
+
} )
|
|
321
|
+
.catch( function(err) {
|
|
322
|
+
// HTTP error
|
|
323
|
+
if (timed_out) return;
|
|
324
|
+
if (timer) { clearTimeout(timer); timer = null; }
|
|
325
|
+
Debug.trace('api', "HTTP Error: " + err);
|
|
326
|
+
|
|
327
|
+
// retry network errors
|
|
328
|
+
if (err instanceof TypeError) {
|
|
329
|
+
retryDelay = Math.min( retryDelay * 2, 8000 );
|
|
330
|
+
Debug.trace('api', `Retrying network error in ${retryDelay}ms...`);
|
|
331
|
+
setTimeout( function() { app.api.request(url, { ...opts, timeout, retryDelay }, callback, errorCallback); }, retryDelay );
|
|
332
|
+
return;
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
if (errorCallback) errorCallback({ code: 'http', description: '' + (err.message || err) });
|
|
336
|
+
else app.doError( err.message || err );
|
|
337
|
+
} );
|
|
338
|
+
}, // api.request
|
|
339
|
+
|
|
340
|
+
post: function(cmd, data, callback, errorCallback) {
|
|
341
|
+
// send HTTP POST to API endpoint
|
|
342
|
+
var url = cmd;
|
|
343
|
+
if (!url.match(/^(\w+\:\/\/|\/)/)) url = app.base_api_url + "/" + cmd;
|
|
344
|
+
|
|
345
|
+
var json_raw = JSON.stringify(data);
|
|
346
|
+
Debug.trace( 'api', "Sending HTTP POST to: " + url + ": " + json_raw );
|
|
347
|
+
|
|
348
|
+
app.api.request( url, {
|
|
349
|
+
method: "POST",
|
|
350
|
+
headers: {
|
|
351
|
+
"Content-Type": app.plain_text_post ? 'text/plain' : 'application/json',
|
|
352
|
+
},
|
|
353
|
+
body: json_raw
|
|
354
|
+
}, callback, errorCallback );
|
|
355
|
+
}, // api.post
|
|
356
|
+
|
|
357
|
+
upload: function(cmd, data, callback, errorCallback) {
|
|
358
|
+
// send FormData to API endpoint
|
|
359
|
+
var url = cmd;
|
|
360
|
+
if (!url.match(/^(\w+\:\/\/|\/)/)) url = app.base_api_url + "/" + cmd;
|
|
361
|
+
|
|
362
|
+
Debug.trace( 'api', "Uploading files to: " + url );
|
|
363
|
+
|
|
364
|
+
app.api.request( url, {
|
|
365
|
+
method: "POST",
|
|
366
|
+
body: data,
|
|
367
|
+
timeout: 300 * 1000 // 5 minutes
|
|
368
|
+
}, callback, errorCallback );
|
|
369
|
+
}, // api.post
|
|
370
|
+
|
|
371
|
+
get: function(cmd, query, callback, errorCallback) {
|
|
372
|
+
// send HTTP GET to API endpoint
|
|
373
|
+
var url = cmd;
|
|
374
|
+
if (!url.match(/^(\w+\:\/\/|\/)/)) url = app.base_api_url + "/" + cmd;
|
|
375
|
+
|
|
376
|
+
if (!query) query = {};
|
|
377
|
+
if (app.cacheBust) query.cachebust = app.cacheBust;
|
|
378
|
+
url += compose_query_string(query);
|
|
379
|
+
|
|
380
|
+
Debug.trace( 'api', "Sending HTTP GET to: " + url );
|
|
381
|
+
app.api.request( url, {}, callback, errorCallback );
|
|
382
|
+
} // api.get
|
|
383
|
+
|
|
384
|
+
}, // api
|
|
385
|
+
|
|
386
|
+
initPrefs: function(key) {
|
|
387
|
+
// init prefs, load from localStorage if applicable
|
|
388
|
+
this.prefs = {};
|
|
389
|
+
|
|
390
|
+
if (localStorage.prefs) {
|
|
391
|
+
Debug.trace('prefs', "localStorage.prefs: " + localStorage.prefs );
|
|
392
|
+
try { this.prefs = JSON.parse( localStorage.prefs ); }
|
|
393
|
+
catch (err) {
|
|
394
|
+
Debug.trace('prefs', "ERROR: Failed to load prefs: " + err, localStorage.prefs);
|
|
395
|
+
}
|
|
396
|
+
}
|
|
397
|
+
|
|
398
|
+
// apply defaults
|
|
399
|
+
if (this.default_prefs) {
|
|
400
|
+
for (var key in this.default_prefs) {
|
|
401
|
+
if (!(key in this.prefs)) {
|
|
402
|
+
this.prefs[key] = this.default_prefs[key];
|
|
403
|
+
}
|
|
404
|
+
}
|
|
405
|
+
}
|
|
406
|
+
|
|
407
|
+
Debug.trace('prefs', "Loaded: " + JSON.stringify(this.prefs));
|
|
408
|
+
},
|
|
409
|
+
|
|
410
|
+
getPref: function(key) {
|
|
411
|
+
// get user preference, accepts single key, dot.path or slash/path syntax.
|
|
412
|
+
return get_path( this.prefs, key );
|
|
413
|
+
},
|
|
414
|
+
|
|
415
|
+
setPref: function(key, value) {
|
|
416
|
+
// set user preference, accepts single key, dot.path or slash/path syntax.
|
|
417
|
+
set_path( this.prefs, key, value );
|
|
418
|
+
this.savePrefs();
|
|
419
|
+
},
|
|
420
|
+
|
|
421
|
+
deletePref: function(key) {
|
|
422
|
+
// delete user preference, accepts single key, dot.path or slash/path syntax.
|
|
423
|
+
delete_path( this.prefs, key );
|
|
424
|
+
this.savePrefs();
|
|
425
|
+
},
|
|
426
|
+
|
|
427
|
+
savePrefs: function() {
|
|
428
|
+
// save local pref cache back to localStorage
|
|
429
|
+
localStorage.prefs = JSON.stringify( this.prefs );
|
|
430
|
+
},
|
|
431
|
+
|
|
432
|
+
get_base_url: function() {
|
|
433
|
+
return app.proto + location.hostname + '/';
|
|
434
|
+
},
|
|
435
|
+
|
|
436
|
+
setTheme: function(theme) {
|
|
437
|
+
// set light/dark theme
|
|
438
|
+
var icon = '';
|
|
439
|
+
|
|
440
|
+
this.setPref('theme', theme);
|
|
441
|
+
|
|
442
|
+
switch (theme) {
|
|
443
|
+
case 'light': icon = 'white-balance-sunny'; break; // weather-sunny
|
|
444
|
+
case 'dark': icon = 'moon-waning-crescent'; break; // weather-night
|
|
445
|
+
case 'auto': icon = 'circle-half-full'; break;
|
|
446
|
+
}
|
|
447
|
+
|
|
448
|
+
if (theme == 'auto') {
|
|
449
|
+
if (window.matchMedia('(prefers-color-scheme: dark)').matches) theme = 'dark';
|
|
450
|
+
else theme = 'light';
|
|
451
|
+
}
|
|
452
|
+
|
|
453
|
+
if (this.onThemeChange) this.onThemeChange(theme);
|
|
454
|
+
|
|
455
|
+
if (theme == 'dark') {
|
|
456
|
+
$('body').addClass('dark');
|
|
457
|
+
$('head > meta[name = theme-color]').attr('content', '#222222');
|
|
458
|
+
}
|
|
459
|
+
else {
|
|
460
|
+
$('body').removeClass('dark');
|
|
461
|
+
$('head > meta[name = theme-color]').attr('content', '#3791F5');
|
|
462
|
+
}
|
|
463
|
+
|
|
464
|
+
$('#d_theme_ctrl').html( '<i class="mdi mdi-' + icon + '"></i>' );
|
|
465
|
+
},
|
|
466
|
+
|
|
467
|
+
initTheme: function() {
|
|
468
|
+
// set theme to user's preference
|
|
469
|
+
this.setTheme( this.getPref('theme') || 'auto' );
|
|
470
|
+
|
|
471
|
+
// listen for changes
|
|
472
|
+
var match = window.matchMedia('(prefers-color-scheme: dark)');
|
|
473
|
+
match.addEventListener('change', function(event) {
|
|
474
|
+
if (app.getPref('theme') == 'auto') app.setTheme('auto');
|
|
475
|
+
});
|
|
476
|
+
},
|
|
477
|
+
|
|
478
|
+
toggleTheme: function() {
|
|
479
|
+
// toggle light/dark theme
|
|
480
|
+
if (this.getPref('theme') == 'dark') this.setTheme('light');
|
|
481
|
+
else this.setTheme('dark');
|
|
482
|
+
},
|
|
483
|
+
|
|
484
|
+
getTheme() {
|
|
485
|
+
// get current theme, computed if auto
|
|
486
|
+
var theme = this.getPref('theme') || 'auto';
|
|
487
|
+
|
|
488
|
+
if (theme == 'auto') {
|
|
489
|
+
if (window.matchMedia('(prefers-color-scheme: dark)').matches) theme = 'dark';
|
|
490
|
+
else theme = 'light';
|
|
491
|
+
}
|
|
492
|
+
|
|
493
|
+
return theme;
|
|
494
|
+
},
|
|
495
|
+
|
|
496
|
+
pullSidebar: function() {
|
|
497
|
+
// mobile: pull sidebar over content
|
|
498
|
+
if (!$('div.sidebar').hasClass('force')) {
|
|
499
|
+
$('div.sidebar').addClass('force');
|
|
500
|
+
|
|
501
|
+
if ($('#sidebar_overlay').length) {
|
|
502
|
+
$('#sidebar_overlay').stop().remove();
|
|
503
|
+
}
|
|
504
|
+
|
|
505
|
+
var $overlay = $('<div id="sidebar_overlay"></div>').css('opacity', 0);
|
|
506
|
+
$('body').append($overlay);
|
|
507
|
+
$overlay.fadeTo( 500, 0.5 ).click(function() {
|
|
508
|
+
app.pushSidebar();
|
|
509
|
+
});
|
|
510
|
+
}
|
|
511
|
+
},
|
|
512
|
+
|
|
513
|
+
pushSidebar: function() {
|
|
514
|
+
// mobile: return sidebar to its hidden drawer
|
|
515
|
+
if ($('div.sidebar').hasClass('force')) {
|
|
516
|
+
$('div.sidebar').removeClass('force');
|
|
517
|
+
$('#sidebar_overlay').stop().fadeOut( 500, function() { $(this).remove(); } );
|
|
518
|
+
}
|
|
519
|
+
},
|
|
520
|
+
|
|
521
|
+
notifyUserNav: function(loc) {
|
|
522
|
+
// override in app
|
|
523
|
+
// called by each page nav operation
|
|
524
|
+
}
|
|
525
|
+
|
|
526
|
+
}; // app object
|
|
527
|
+
|
|
528
|
+
function $P(id) {
|
|
529
|
+
// shortcut for page_manager.find(), also defaults to current page
|
|
530
|
+
if (!id) id = app.page_manager.current_page_id;
|
|
531
|
+
var page = app.page_manager.find(id);
|
|
532
|
+
assert( !!page, "Failed to locate page: " + id );
|
|
533
|
+
return page;
|
|
534
|
+
};
|
|
535
|
+
|
|
536
|
+
window.Debug = {
|
|
537
|
+
|
|
538
|
+
enabled: false,
|
|
539
|
+
categories: { all: 1 },
|
|
540
|
+
backlog: [],
|
|
541
|
+
|
|
542
|
+
colors: ["#001F3F", "#0074D9", "#7FDBFF", "#39CCCC", "#3D9970", "#2ECC40", "#01FF70", "#FFDC00", "#FF851B", "#FF4136", "#F012BE", "#B10DC9", "#85144B"],
|
|
543
|
+
nextColorIdx: 0,
|
|
544
|
+
catColors: {},
|
|
545
|
+
|
|
546
|
+
enable: function(cats) {
|
|
547
|
+
// enable debug logging and flush backlog if applicable
|
|
548
|
+
if (cats) this.categories = cats;
|
|
549
|
+
this.enabled = true;
|
|
550
|
+
this._dump();
|
|
551
|
+
},
|
|
552
|
+
|
|
553
|
+
disable: function() {
|
|
554
|
+
// disable debug logging, but keep backlog
|
|
555
|
+
this.enabled = false;
|
|
556
|
+
},
|
|
557
|
+
|
|
558
|
+
trace: function(cat, msg, data) {
|
|
559
|
+
// trace one line to console, or store in backlog
|
|
560
|
+
// allow msg, cat + msg, msg + data, or cat + msg + data
|
|
561
|
+
if (arguments.length == 1) {
|
|
562
|
+
msg = cat;
|
|
563
|
+
cat = 'debug';
|
|
564
|
+
}
|
|
565
|
+
else if ((arguments.length == 2) && (typeof(arguments[arguments.length - 1]) == 'object')) {
|
|
566
|
+
data = msg;
|
|
567
|
+
msg = cat;
|
|
568
|
+
cat = 'debug';
|
|
569
|
+
}
|
|
570
|
+
|
|
571
|
+
var now = new Date();
|
|
572
|
+
var timestamp = '' +
|
|
573
|
+
this._zeroPad( now.getHours(), 2 ) + ':' +
|
|
574
|
+
this._zeroPad( now.getMinutes(), 2 ) + ':' +
|
|
575
|
+
this._zeroPad( now.getSeconds(), 2 ) + '.' +
|
|
576
|
+
this._zeroPad( now.getMilliseconds(), 3 );
|
|
577
|
+
|
|
578
|
+
if (data && (typeof(data) == 'object')) data = JSON.stringify(data);
|
|
579
|
+
if (!data) data = false;
|
|
580
|
+
|
|
581
|
+
if (this.enabled) {
|
|
582
|
+
if ((this.categories.all || this.categories[cat]) && (this.categories[cat] !== false)) {
|
|
583
|
+
this._print(timestamp, cat, msg, data);
|
|
584
|
+
}
|
|
585
|
+
}
|
|
586
|
+
else {
|
|
587
|
+
this.backlog.push([ timestamp, cat, msg, data ]);
|
|
588
|
+
if (this.backlog.length > 1000) this.backlog.shift();
|
|
589
|
+
}
|
|
590
|
+
},
|
|
591
|
+
|
|
592
|
+
_dump: function() {
|
|
593
|
+
// dump backlog to console
|
|
594
|
+
for (var idx = 0, len = this.backlog.length; idx < len; idx++) {
|
|
595
|
+
this._print.apply( this, this.backlog[idx] );
|
|
596
|
+
}
|
|
597
|
+
this.backlog = [];
|
|
598
|
+
},
|
|
599
|
+
|
|
600
|
+
_print: function(timestamp, cat, msg, data) {
|
|
601
|
+
// format and print one message to the console
|
|
602
|
+
var color = this.catColors[cat] || '';
|
|
603
|
+
if (!color) {
|
|
604
|
+
color = this.catColors[cat] = this.colors[this.nextColorIdx];
|
|
605
|
+
this.nextColorIdx = (this.nextColorIdx + 1) % this.colors.length;
|
|
606
|
+
}
|
|
607
|
+
|
|
608
|
+
console.log( timestamp + ' %c[' + cat + ']%c ' + msg, 'color:' + color + '; font-weight:bold', 'color:inherit; font-weight:normal' );
|
|
609
|
+
if (data) console.log(data);
|
|
610
|
+
},
|
|
611
|
+
|
|
612
|
+
_zeroPad: function(value, len) {
|
|
613
|
+
// Pad a number with zeroes to achieve a desired total length (max 10)
|
|
614
|
+
return ('0000000000' + value).slice(0 - len);
|
|
615
|
+
}
|
|
616
|
+
};
|
|
617
|
+
|
|
618
|
+
if (!window.assert) window.assert = function(fact, msg) {
|
|
619
|
+
// very simple assert
|
|
620
|
+
if (!fact) {
|
|
621
|
+
console.error("ASSERT FAILURE: " + msg);
|
|
622
|
+
throw("ASSERT FAILED! " + msg);
|
|
623
|
+
}
|
|
624
|
+
return fact;
|
|
625
|
+
};
|
|
626
|
+
|
|
627
|
+
$(document).ready(function() {
|
|
628
|
+
app.init();
|
|
629
|
+
});
|
|
630
|
+
|
|
631
|
+
window.addEventListener( "click", function(e) {
|
|
632
|
+
app.lastClick = { altKey: e.altKey, ctrlKey: e.ctrlKey, shiftKey: e.shiftKey, metaKey: e.metaKey };
|
|
633
|
+
}, true );
|
|
634
|
+
|
|
635
|
+
window.addEventListener( "keydown", function(event) {
|
|
636
|
+
if (Popover.enabled) Popover.handleKeyDown(event);
|
|
637
|
+
else if (CodeEditor.handleKeyDown) CodeEditor.handleKeyDown(event);
|
|
638
|
+
else if (Dialog.active) Dialog.confirm_key(event);
|
|
639
|
+
else app.handleKeyDown(event);
|
|
640
|
+
}, false );
|
|
641
|
+
|
|
642
|
+
window.addEventListener( "resize", function() {
|
|
643
|
+
app.handleResize();
|
|
644
|
+
}, false );
|
|
645
|
+
|
|
646
|
+
window.addEventListener("beforeunload", function (e) {
|
|
647
|
+
return app.handleUnload(e);
|
|
648
|
+
}, false );
|