@qtoggle/qui 0.0.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/.eslintignore +2 -0
- package/.eslintrc.json +492 -0
- package/.github/ISSUE_TEMPLATE/bug_report.md +33 -0
- package/.github/ISSUE_TEMPLATE/feature_request.md +23 -0
- package/.github/ISSUE_TEMPLATE/improvement_proposal.md +20 -0
- package/.github/workflows/main.yml +74 -0
- package/.pre-commit-config.yaml +8 -0
- package/LICENSE.txt +177 -0
- package/README.md +4 -0
- package/font/dejavusans-bold.woff +0 -0
- package/font/dejavusans-bolditalic.woff +0 -0
- package/font/dejavusans-italic.woff +0 -0
- package/font/dejavusans-regular.woff +0 -0
- package/img/qui-icons.svg +1937 -0
- package/js/base/base.js +47 -0
- package/js/base/condition-variable.js +92 -0
- package/js/base/errors.js +36 -0
- package/js/base/i18n.js +20 -0
- package/js/base/mixwith.js +135 -0
- package/js/base/require-js-compat.js +78 -0
- package/js/base/signal.js +91 -0
- package/js/base/singleton.js +66 -0
- package/js/base/timer.js +126 -0
- package/js/config.js +184 -0
- package/js/forms/common-fields/check-field.js +42 -0
- package/js/forms/common-fields/choice-buttons-field.js +30 -0
- package/js/forms/common-fields/color-combo-field.js +37 -0
- package/js/forms/common-fields/combo-field.js +108 -0
- package/js/forms/common-fields/common-fields.js +23 -0
- package/js/forms/common-fields/composite-field.js +132 -0
- package/js/forms/common-fields/custom-html-field.js +51 -0
- package/js/forms/common-fields/email-field.js +30 -0
- package/js/forms/common-fields/file-picker-field.js +46 -0
- package/js/forms/common-fields/jquery-ui-field.js +111 -0
- package/js/forms/common-fields/labels-field.js +69 -0
- package/js/forms/common-fields/numeric-field.js +39 -0
- package/js/forms/common-fields/password-field.js +28 -0
- package/js/forms/common-fields/phone-field.js +26 -0
- package/js/forms/common-fields/progress-disk-field.js +69 -0
- package/js/forms/common-fields/push-button-field.js +138 -0
- package/js/forms/common-fields/slider-field.js +51 -0
- package/js/forms/common-fields/text-area-field.js +34 -0
- package/js/forms/common-fields/text-field.js +89 -0
- package/js/forms/common-fields/up-down-field.js +85 -0
- package/js/forms/common-forms/common-forms.js +16 -0
- package/js/forms/common-forms/options-form.js +77 -0
- package/js/forms/common-forms/page-form.js +115 -0
- package/js/forms/form-button.js +202 -0
- package/js/forms/form-field.js +1183 -0
- package/js/forms/form.js +1181 -0
- package/js/forms/forms.js +68 -0
- package/js/global-glass.js +100 -0
- package/js/icons/default-stock.js +173 -0
- package/js/icons/icon.js +64 -0
- package/js/icons/icons.js +16 -0
- package/js/icons/multi-state-sprites-icon.js +362 -0
- package/js/icons/stock-icon.js +219 -0
- package/js/icons/stock.js +98 -0
- package/js/icons/stocks.js +57 -0
- package/js/index.js +232 -0
- package/js/lib/jquery.longpress.js +79 -0
- package/js/lib/jquery.module.js +4 -0
- package/js/lib/logger.module.js +4 -0
- package/js/lib/pep.module.js +4 -0
- package/js/lists/common-items/common-items.js +5 -0
- package/js/lists/common-items/icon-label-list-item.js +86 -0
- package/js/lists/common-lists/common-lists.js +5 -0
- package/js/lists/common-lists/page-list.js +53 -0
- package/js/lists/list-item.js +147 -0
- package/js/lists/list.js +636 -0
- package/js/lists/lists.js +26 -0
- package/js/main-ui/main-ui.js +64 -0
- package/js/main-ui/menu-bar.js +144 -0
- package/js/main-ui/options-bar.js +181 -0
- package/js/main-ui/status.js +185 -0
- package/js/main-ui/top-bar.js +59 -0
- package/js/messages/common-message-forms/common-message-forms.js +7 -0
- package/js/messages/common-message-forms/confirm-message-form.js +81 -0
- package/js/messages/common-message-forms/simple-message-form.js +67 -0
- package/js/messages/common-message-forms/sticky-simple-message-form.js +27 -0
- package/js/messages/message-form.js +107 -0
- package/js/messages/messages.js +21 -0
- package/js/messages/sticky-modal-page.js +98 -0
- package/js/messages/sticky-modal-progress-message.js +27 -0
- package/js/messages/toast.js +164 -0
- package/js/navigation.js +654 -0
- package/js/pages/breadcrumbs.js +124 -0
- package/js/pages/common-pages/common-pages.js +6 -0
- package/js/pages/common-pages/modal-progress-page.js +83 -0
- package/js/pages/common-pages/structured-page.js +46 -0
- package/js/pages/page.js +1018 -0
- package/js/pages/pages-context.js +154 -0
- package/js/pages/pages.js +252 -0
- package/js/pwa.js +337 -0
- package/js/sections/section.js +612 -0
- package/js/sections/sections.js +300 -0
- package/js/tables/common-cells/common-cells.js +7 -0
- package/js/tables/common-cells/icon-label-table-cell.js +68 -0
- package/js/tables/common-cells/push-button-table-cell.js +133 -0
- package/js/tables/common-cells/simple-table-cell.js +37 -0
- package/js/tables/common-tables/common-tables.js +5 -0
- package/js/tables/common-tables/page-table.js +55 -0
- package/js/tables/table-cell.js +198 -0
- package/js/tables/table-row.js +126 -0
- package/js/tables/table.js +492 -0
- package/js/tables/tables.js +36 -0
- package/js/theme.js +304 -0
- package/js/utils/ajax.js +126 -0
- package/js/utils/array.js +194 -0
- package/js/utils/colors.js +445 -0
- package/js/utils/cookies.js +65 -0
- package/js/utils/crypto.js +439 -0
- package/js/utils/css.js +234 -0
- package/js/utils/date.js +300 -0
- package/js/utils/files.js +27 -0
- package/js/utils/gestures.js +165 -0
- package/js/utils/html.js +76 -0
- package/js/utils/misc.js +81 -0
- package/js/utils/object.js +324 -0
- package/js/utils/promise.js +49 -0
- package/js/utils/string.js +192 -0
- package/js/utils/url.js +187 -0
- package/js/utils/utils.js +3 -0
- package/js/utils/visibility-manager.js +211 -0
- package/js/views/common-views/common-views.js +7 -0
- package/js/views/common-views/icon-label-view.js +210 -0
- package/js/views/common-views/progress-view.js +89 -0
- package/js/views/common-views/structured-view.js +368 -0
- package/js/views/view.js +467 -0
- package/js/views/views.js +3 -0
- package/js/widgets/base-widget.js +23 -0
- package/js/widgets/common-widgets/check-button.js +109 -0
- package/js/widgets/common-widgets/choice-buttons.js +322 -0
- package/js/widgets/common-widgets/color-combo.js +104 -0
- package/js/widgets/common-widgets/combo.js +645 -0
- package/js/widgets/common-widgets/common-widgets.js +17 -0
- package/js/widgets/common-widgets/email-input.js +7 -0
- package/js/widgets/common-widgets/file-picker.js +133 -0
- package/js/widgets/common-widgets/labels.js +132 -0
- package/js/widgets/common-widgets/numeric-input.js +49 -0
- package/js/widgets/common-widgets/password-input.js +91 -0
- package/js/widgets/common-widgets/phone-input.js +7 -0
- package/js/widgets/common-widgets/progress-disk.js +174 -0
- package/js/widgets/common-widgets/push-button.js +155 -0
- package/js/widgets/common-widgets/slider.js +455 -0
- package/js/widgets/common-widgets/text-area.js +52 -0
- package/js/widgets/common-widgets/text-input.js +174 -0
- package/js/widgets/common-widgets/up-down.js +351 -0
- package/js/widgets/widgets.js +57 -0
- package/js/window.js +557 -0
- package/jsdoc.conf.json +20 -0
- package/less/base.less +123 -0
- package/less/forms/common-fields.less +101 -0
- package/less/forms/common-forms.less +5 -0
- package/less/forms/form-button.less +21 -0
- package/less/forms/form-field.less +266 -0
- package/less/forms/form.less +131 -0
- package/less/global-glass.less +64 -0
- package/less/icon-label-view.less +82 -0
- package/less/icons.less +144 -0
- package/less/lists.less +105 -0
- package/less/main-ui.less +328 -0
- package/less/messages.less +189 -0
- package/less/no-effects.less +24 -0
- package/less/pages/breadcrumbs.less +98 -0
- package/less/pages/common-pages.less +36 -0
- package/less/pages/page.less +70 -0
- package/less/progress-view.less +51 -0
- package/less/stock-icons.less +43 -0
- package/less/structured-view.less +245 -0
- package/less/tables.less +84 -0
- package/less/theme-dark.less +133 -0
- package/less/theme-light.less +132 -0
- package/less/theme.less +419 -0
- package/less/visibility-manager.less +11 -0
- package/less/widgets/check-button.less +96 -0
- package/less/widgets/choice-buttons.less +160 -0
- package/less/widgets/color-combo.less +33 -0
- package/less/widgets/combo.less +230 -0
- package/less/widgets/common-buttons.less +120 -0
- package/less/widgets/common.less +24 -0
- package/less/widgets/input.less +258 -0
- package/less/widgets/labels.less +81 -0
- package/less/widgets/progress-disk.less +70 -0
- package/less/widgets/slider.less +199 -0
- package/less/widgets/updown.less +115 -0
- package/less/widgets/various.less +36 -0
- package/package.json +47 -0
- package/pyproject.toml +45 -0
- package/qui/__init__.py +110 -0
- package/qui/constants.py +1 -0
- package/qui/exceptions.py +2 -0
- package/qui/j2template.py +71 -0
- package/qui/settings.py +60 -0
- package/qui/templates/manifest.json +25 -0
- package/qui/templates/qui.html +126 -0
- package/qui/templates/service-worker.js +188 -0
- package/qui/web/__init__.py +0 -0
- package/qui/web/tornado.py +220 -0
- package/scripts/postinstall.sh +10 -0
- package/webpack/webpack-adjust-css-urls-loader.js +36 -0
- package/webpack/webpack-common.js +384 -0
package/js/theme.js
ADDED
|
@@ -0,0 +1,304 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @namespace qui.theme
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import $ from '$qui/lib/jquery.module.js'
|
|
6
|
+
import Logger from '$qui/lib/logger.module.js'
|
|
7
|
+
|
|
8
|
+
import {gettext} from '$qui/base/i18n.js'
|
|
9
|
+
import Signal from '$qui/base/signal.js'
|
|
10
|
+
import Config from '$qui/config.js'
|
|
11
|
+
import * as Colors from '$qui/utils/colors.js'
|
|
12
|
+
import * as CSS from '$qui/utils/css.js'
|
|
13
|
+
import {asap} from '$qui/utils/misc.js'
|
|
14
|
+
import * as ObjectUtils from '$qui/utils/object.js'
|
|
15
|
+
import * as PromiseUtils from '$qui/utils/promise.js'
|
|
16
|
+
import * as StringUtils from '$qui/utils/string.js'
|
|
17
|
+
import * as Window from '$qui/window.js'
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
const STORAGE_BACKGROUND_COLOR_KEY = 'theme.background-color'
|
|
21
|
+
|
|
22
|
+
const logger = Logger.get('qui.theme')
|
|
23
|
+
|
|
24
|
+
let currentTheme = null
|
|
25
|
+
let themeVars = null
|
|
26
|
+
let transitionDuration = null
|
|
27
|
+
let effectsDisabled = null
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Emitted whenever the theme is changed. Handlers are called with the following parameters:
|
|
32
|
+
* * `theme: String`, the new theme
|
|
33
|
+
* @alias qui.theme.changeSignal
|
|
34
|
+
*/
|
|
35
|
+
export const changeSignal = new Signal()
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Tell the current theme.
|
|
40
|
+
* @alias qui.theme.getCurrent
|
|
41
|
+
* @returns {String}
|
|
42
|
+
*/
|
|
43
|
+
export function getCurrent() {
|
|
44
|
+
return currentTheme
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Change the theme.
|
|
49
|
+
* @alias qui.theme.setCurrent
|
|
50
|
+
* @param {String} theme
|
|
51
|
+
* @returns {Promise} a promise that resolves as soon as the theme has been set
|
|
52
|
+
*/
|
|
53
|
+
export function setCurrent(theme) {
|
|
54
|
+
if (currentTheme === theme) {
|
|
55
|
+
return Promise.resolve()
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
currentTheme = theme
|
|
59
|
+
|
|
60
|
+
logger.debug(`setting theme to ${theme}`)
|
|
61
|
+
|
|
62
|
+
if (Window.$body == null) {
|
|
63
|
+
return Promise.resolve() /* Not initialized yet */
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
return updateCurrent()
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
function updateCurrent() {
|
|
70
|
+
/* Fade out body */
|
|
71
|
+
Window.$body.css('opacity', '')
|
|
72
|
+
|
|
73
|
+
function isLoaded() {
|
|
74
|
+
return CSS.findRules('^br.-theme-name$').some(function (rule) {
|
|
75
|
+
let parts = rule.declaration.split(':', 2)
|
|
76
|
+
if (parts.length < 2) {
|
|
77
|
+
return
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
let value = parts[1].split(';')[0].trim()
|
|
81
|
+
value = value.replace(/"/g, '') /* Remove quotation marks */
|
|
82
|
+
|
|
83
|
+
return value === currentTheme
|
|
84
|
+
})
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
function loadedOrLater() {
|
|
88
|
+
if (isLoaded()) {
|
|
89
|
+
return Promise.resolve()
|
|
90
|
+
}
|
|
91
|
+
else {
|
|
92
|
+
return PromiseUtils.later(10).then(() => loadedOrLater())
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
/* Allow 500ms for fading-out */
|
|
97
|
+
return PromiseUtils.later(500).then(function () {
|
|
98
|
+
|
|
99
|
+
/* Update disabled attribute of CSS link elements */
|
|
100
|
+
$('link[theme]').each(function () {
|
|
101
|
+
let $link = $(this)
|
|
102
|
+
let linkTheme = $link.attr('theme')
|
|
103
|
+
if (linkTheme === currentTheme) {
|
|
104
|
+
$link.removeAttr('disabled')
|
|
105
|
+
}
|
|
106
|
+
else {
|
|
107
|
+
$link.attr('disabled', '')
|
|
108
|
+
}
|
|
109
|
+
})
|
|
110
|
+
|
|
111
|
+
return loadedOrLater().then(function () {
|
|
112
|
+
|
|
113
|
+
logger.debug(`theme set to ${currentTheme}`)
|
|
114
|
+
|
|
115
|
+
/* Invalidate theme vars */
|
|
116
|
+
themeVars = null
|
|
117
|
+
|
|
118
|
+
/* Finally, emit change signal */
|
|
119
|
+
changeSignal.emit(currentTheme)
|
|
120
|
+
|
|
121
|
+
/* Consider the theme updated, but allow another 500ms for section soft reload */
|
|
122
|
+
setTimeout(function () {
|
|
123
|
+
Window.$body.css('opacity', '1')
|
|
124
|
+
}, 500)
|
|
125
|
+
|
|
126
|
+
/* Set current background color in local storage, to be used at next app reload */
|
|
127
|
+
window.localStorage.setItem(STORAGE_BACKGROUND_COLOR_KEY, getVar('background-color'))
|
|
128
|
+
})
|
|
129
|
+
|
|
130
|
+
})
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
/**
|
|
134
|
+
* Return the available themes.
|
|
135
|
+
* @alias qui.theme.getAvailable
|
|
136
|
+
* @returns {Object<String,String>} a dictionary with theme names as keys and display names as values
|
|
137
|
+
*/
|
|
138
|
+
export function getAvailable() {
|
|
139
|
+
return ObjectUtils.fromEntries(Config.themes.split(',').map(function (theme) {
|
|
140
|
+
return [theme, gettext(StringUtils.title(theme))]
|
|
141
|
+
}))
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
/**
|
|
145
|
+
* Return the value of a theme variable.
|
|
146
|
+
* @alias qui.theme.getVar
|
|
147
|
+
* @param {String} name the variable name
|
|
148
|
+
* @param {String} [def] a default value if the variable is not found or not set
|
|
149
|
+
* @returns {String}
|
|
150
|
+
*/
|
|
151
|
+
export function getVar(name, def) {
|
|
152
|
+
if (!themeVars) {
|
|
153
|
+
themeVars = {}
|
|
154
|
+
CSS.findRules('^br.-theme-').forEach(function (rule) {
|
|
155
|
+
let name = rule.selector.substring(10)
|
|
156
|
+
let parts = rule.declaration.split(':', 2)
|
|
157
|
+
if (parts.length < 2) {
|
|
158
|
+
return
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
themeVars[name] = parts[1].split(';')[0].trim()
|
|
162
|
+
})
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
return themeVars[name] || def
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
/**
|
|
169
|
+
* Resolve a color name and normalize it using {@link qui.utils.colors.normalize}.
|
|
170
|
+
*
|
|
171
|
+
* A color name can be an HTML color (e.g. `teal`) or a color theme variable name starting with an `@` (e.g.
|
|
172
|
+
* `@background-color`).
|
|
173
|
+
*
|
|
174
|
+
* If a color is given, it will be normalized and returned right away. If the given color name cannot be resolved, the
|
|
175
|
+
* `@foreground-color` is returned.
|
|
176
|
+
*
|
|
177
|
+
* @alias qui.theme.getColor
|
|
178
|
+
* @param {String} color a color or a color name
|
|
179
|
+
* @returns {String}
|
|
180
|
+
*/
|
|
181
|
+
export function getColor(color) {
|
|
182
|
+
if (color.startsWith('@')) {
|
|
183
|
+
color = getVar(color.substring(1))
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
if (!color) {
|
|
187
|
+
color = getVar('foreground-color')
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
return Colors.normalize(color)
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
/**
|
|
194
|
+
* Return the default transition duration, in milliseconds.
|
|
195
|
+
* @alias qui.theme.getTransitionDuration
|
|
196
|
+
* @returns {Number}
|
|
197
|
+
*/
|
|
198
|
+
export function getTransitionDuration() {
|
|
199
|
+
if (transitionDuration == null) {
|
|
200
|
+
transitionDuration = parseFloat(getVar('transition-duration')) * 1000
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
return transitionDuration
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
/**
|
|
207
|
+
* Call a function after a timeout equal to a transition duration,
|
|
208
|
+
* @alias qui.theme.afterTransition
|
|
209
|
+
* @param {Function} func function to run
|
|
210
|
+
* @param {?jQuery} [element] an optional HTML element whose visibility will be tested; if element is not currently
|
|
211
|
+
* visible, `func` will be called asap; if supplied, will be used as `this` argument for `func`
|
|
212
|
+
* @returns {Number} a timeout handle
|
|
213
|
+
*/
|
|
214
|
+
export function afterTransition(func, element = null) {
|
|
215
|
+
let thisArg = element || window
|
|
216
|
+
|
|
217
|
+
if (element && !element.is(':visible')) {
|
|
218
|
+
return asap(function () {
|
|
219
|
+
func.call(thisArg)
|
|
220
|
+
})
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
return setTimeout(function () {
|
|
224
|
+
func.call(thisArg)
|
|
225
|
+
}, getTransitionDuration())
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
/**
|
|
229
|
+
* Create a promise that resolves after a timeout equal to a transition duration,
|
|
230
|
+
* @alias qui.theme.afterTransitionPromise
|
|
231
|
+
* @param {?jQuery} [element] an optional HTML element whose visibility will be tested; if element is not currently
|
|
232
|
+
* visible, promise is resolved asap
|
|
233
|
+
* @returns {Promise}
|
|
234
|
+
*/
|
|
235
|
+
export function afterTransitionPromise(element = null) {
|
|
236
|
+
return new Promise(function (resolve, reject) {
|
|
237
|
+
|
|
238
|
+
if (element && !element.is(':visible')) {
|
|
239
|
+
resolve()
|
|
240
|
+
}
|
|
241
|
+
else {
|
|
242
|
+
afterTransition(function () {
|
|
243
|
+
resolve()
|
|
244
|
+
}, element)
|
|
245
|
+
}
|
|
246
|
+
})
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
/**
|
|
250
|
+
* Enable transitions, animations, blur filters and other effects. Use this function to re-enable effects disabled by
|
|
251
|
+
* {@link qui.theme.disableEffects}.
|
|
252
|
+
* @alias qui.theme.enableEffects
|
|
253
|
+
*/
|
|
254
|
+
export function enableEffects() {
|
|
255
|
+
if (!effectsDisabled) {
|
|
256
|
+
return
|
|
257
|
+
}
|
|
258
|
+
effectsDisabled = false
|
|
259
|
+
logger.debug('enabling effects')
|
|
260
|
+
|
|
261
|
+
if (Window.$body != null) {
|
|
262
|
+
Window.$body.removeClass('effects-disabled')
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
/**
|
|
267
|
+
* Disable transitions, animations, blur filters and other effects. Use {@link qui.theme.enableEffects} to re-enable
|
|
268
|
+
* effects.
|
|
269
|
+
* @alias qui.theme.disableEffects
|
|
270
|
+
*/
|
|
271
|
+
export function disableEffects() {
|
|
272
|
+
if (effectsDisabled) {
|
|
273
|
+
return
|
|
274
|
+
}
|
|
275
|
+
effectsDisabled = true
|
|
276
|
+
logger.debug('disabling effects')
|
|
277
|
+
|
|
278
|
+
if (Window.$body != null) {
|
|
279
|
+
Window.$body.addClass('effects-disabled')
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
|
|
284
|
+
/**
|
|
285
|
+
* Initialize the theme subsystem.
|
|
286
|
+
* @alias qui.theme.init
|
|
287
|
+
* @returns {Promise} a promise that is resolved when theme subsystem has been initialized
|
|
288
|
+
*/
|
|
289
|
+
export function init() {
|
|
290
|
+
if (effectsDisabled == null) {
|
|
291
|
+
effectsDisabled = Config.defaultEffectsDisabled
|
|
292
|
+
}
|
|
293
|
+
if (currentTheme == null) {
|
|
294
|
+
currentTheme = Config.defaultTheme
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
Window.$body.toggleClass('effects-disabled', effectsDisabled)
|
|
298
|
+
|
|
299
|
+
return updateCurrent().then(function () {
|
|
300
|
+
/* Normally updateCurrent() will take care of fading in body, adds extra 500ms for section soft reload.
|
|
301
|
+
* This being the first call, we want the body visible as soon as theme is loaded */
|
|
302
|
+
Window.$body.css('opacity', '1')
|
|
303
|
+
})
|
|
304
|
+
}
|
package/js/utils/ajax.js
ADDED
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @namespace qui.utils.ajax
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import $ from '$qui/lib/jquery.module.js'
|
|
6
|
+
|
|
7
|
+
import * as ObjectUtils from '$qui/utils/object.js'
|
|
8
|
+
import * as Window from '$qui/window.js'
|
|
9
|
+
|
|
10
|
+
import {asap} from './misc.js'
|
|
11
|
+
import URL from './url.js'
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
const DEFAULT_REQUEST_TIMEOUT = 60 /* Seconds */
|
|
15
|
+
|
|
16
|
+
let pendingRequests = []
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
function prepareResponseHeaders(response) {
|
|
20
|
+
let headersStr = response.getAllResponseHeaders()
|
|
21
|
+
let headers = headersStr.split('\r\n').filter(h => h.length > 0)
|
|
22
|
+
|
|
23
|
+
return ObjectUtils.fromEntries(headers.map(function (h) {
|
|
24
|
+
let parts = h.split(':')
|
|
25
|
+
let name = parts[0].trim()
|
|
26
|
+
let value = parts.slice(1).join(':').trim()
|
|
27
|
+
|
|
28
|
+
return [name, value]
|
|
29
|
+
}))
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Return a list of currently pending requests
|
|
35
|
+
* @alias qui.utils.ajax.getPendingRequests
|
|
36
|
+
* @returns {jqXHR[]}
|
|
37
|
+
*/
|
|
38
|
+
export function getPendingRequests() {
|
|
39
|
+
return pendingRequests.slice()
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Perform an AJAX JSON HTTP request and decode response as JSON.
|
|
44
|
+
* @alias qui.utils.ajax.requestJSON
|
|
45
|
+
* @param {String} method the HTTP method
|
|
46
|
+
* @param {String} path the path or a URL to request
|
|
47
|
+
* @param {Object} [query]
|
|
48
|
+
* @param {*} [data] data to transmit in request body
|
|
49
|
+
* @param {Function} [success] successful callback; will be called with decoded response and headers as parameters
|
|
50
|
+
* @param {Function} [failure] failure callback; will be called with decoded response, status code, a result message and
|
|
51
|
+
* headers as parameters
|
|
52
|
+
* @param {Object} [headers] optional request headers
|
|
53
|
+
* @param {Number} [timeout] optional request timeout, in seconds
|
|
54
|
+
* @returns {jqXHR}
|
|
55
|
+
*/
|
|
56
|
+
export function requestJSON(
|
|
57
|
+
method, path, query = null, data = null, success = null, failure = null, headers = null,
|
|
58
|
+
timeout = DEFAULT_REQUEST_TIMEOUT
|
|
59
|
+
) {
|
|
60
|
+
let contentType = null
|
|
61
|
+
|
|
62
|
+
if (data != null) {
|
|
63
|
+
data = JSON.stringify(data)
|
|
64
|
+
contentType = 'application/json'
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
let url = URL.parse(URL.qualify(path)).alter({query: query || {}}).toString()
|
|
68
|
+
|
|
69
|
+
let request = $.ajax({
|
|
70
|
+
url: url,
|
|
71
|
+
type: method,
|
|
72
|
+
data: data,
|
|
73
|
+
processData: false,
|
|
74
|
+
cache: false,
|
|
75
|
+
contentType: contentType,
|
|
76
|
+
timeout: timeout * 1000,
|
|
77
|
+
beforeSend: function (xhr) {
|
|
78
|
+
if (headers) {
|
|
79
|
+
ObjectUtils.forEach(headers, function (k, v) {
|
|
80
|
+
xhr.setRequestHeader(k, v)
|
|
81
|
+
})
|
|
82
|
+
}
|
|
83
|
+
},
|
|
84
|
+
success: function (data, status, response) {
|
|
85
|
+
let headers = prepareResponseHeaders(response)
|
|
86
|
+
|
|
87
|
+
/* Delay calling the success handler a bit so that complete handler below gets called */
|
|
88
|
+
asap(function () {
|
|
89
|
+
if (success) {
|
|
90
|
+
success(data, headers)
|
|
91
|
+
}
|
|
92
|
+
})
|
|
93
|
+
},
|
|
94
|
+
error: function (request, msg) {
|
|
95
|
+
let headers = prepareResponseHeaders(request)
|
|
96
|
+
|
|
97
|
+
/* Defer error handling a bit, it seems to fix call order when window closes */
|
|
98
|
+
asap(function () {
|
|
99
|
+
if (failure) {
|
|
100
|
+
failure(request.responseJSON || {}, request.status, msg, headers)
|
|
101
|
+
}
|
|
102
|
+
})
|
|
103
|
+
},
|
|
104
|
+
complete: function (req, status) {
|
|
105
|
+
/* Remove the request from pending list */
|
|
106
|
+
let index = pendingRequests.indexOf(request)
|
|
107
|
+
if (index >= 0) {
|
|
108
|
+
pendingRequests.splice(index, 1)
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
})
|
|
112
|
+
|
|
113
|
+
request.details = {
|
|
114
|
+
method: method,
|
|
115
|
+
path: path,
|
|
116
|
+
query: query,
|
|
117
|
+
data: data,
|
|
118
|
+
sent: new Date()
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
if (!Window.isClosing()) {
|
|
122
|
+
pendingRequests.push(request)
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
return request
|
|
126
|
+
}
|
|
@@ -0,0 +1,194 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @namespace qui.utils.array
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
function makeSortFunc(extractFunc, desc, thisArg) {
|
|
6
|
+
return function (e1, e2) {
|
|
7
|
+
let k1 = extractFunc.call(thisArg, e1)
|
|
8
|
+
let k2 = extractFunc.call(thisArg, e2)
|
|
9
|
+
|
|
10
|
+
if ((desc && k1 > k2) || (!desc && k1 < k2)) {
|
|
11
|
+
return -1
|
|
12
|
+
}
|
|
13
|
+
if ((desc && k1 < k2) || (!desc && k1 > k2)) {
|
|
14
|
+
return 1
|
|
15
|
+
}
|
|
16
|
+
else {
|
|
17
|
+
return 0
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
function merge(left, right, compareFunc) {
|
|
23
|
+
let result = []
|
|
24
|
+
|
|
25
|
+
while (left.length > 0 || right.length > 0) {
|
|
26
|
+
if (left.length > 0 && right.length > 0) {
|
|
27
|
+
if (compareFunc(left[0], right[0]) <= 0) {
|
|
28
|
+
result.push(left[0])
|
|
29
|
+
left = left.slice(1)
|
|
30
|
+
}
|
|
31
|
+
else {
|
|
32
|
+
result.push(right[0])
|
|
33
|
+
right = right.slice(1)
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
else if (left.length > 0) {
|
|
37
|
+
result.push(left[0])
|
|
38
|
+
left = left.slice(1)
|
|
39
|
+
}
|
|
40
|
+
else if (right.length > 0) {
|
|
41
|
+
result.push(right[0])
|
|
42
|
+
right = right.slice(1)
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
return result
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
function sorted(array, compareFunc) {
|
|
50
|
+
let length = array.length
|
|
51
|
+
let middle = Math.floor(length / 2)
|
|
52
|
+
|
|
53
|
+
compareFunc = compareFunc || function (left, right) {
|
|
54
|
+
if (left < right) {
|
|
55
|
+
return -1
|
|
56
|
+
}
|
|
57
|
+
else if (left === right) {
|
|
58
|
+
return 0
|
|
59
|
+
}
|
|
60
|
+
else {
|
|
61
|
+
return 1
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
if (length < 2) {
|
|
66
|
+
return array
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
return merge(
|
|
70
|
+
sorted(array.slice(0, middle), compareFunc),
|
|
71
|
+
sorted(array.slice(middle, length), compareFunc),
|
|
72
|
+
compareFunc
|
|
73
|
+
)
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* Perform a stable sort on an array, *in place*, using a comparison function.
|
|
79
|
+
* @alias qui.utils.array.stableSort
|
|
80
|
+
* @param {Array} array
|
|
81
|
+
* @param {Function} compareFunc comparison function; takes two elements as parameters and returns `-1, `0` or `1`
|
|
82
|
+
* @returns {Array} the array
|
|
83
|
+
*/
|
|
84
|
+
export function stableSort(array, compareFunc) {
|
|
85
|
+
/* Slower, but such is life */
|
|
86
|
+
let result = sorted(array, compareFunc).slice()
|
|
87
|
+
|
|
88
|
+
/* Replace all the elements in array with the sorted ones */
|
|
89
|
+
array.length = 0
|
|
90
|
+
Array.prototype.splice.apply(array, [0, 0].concat(result))
|
|
91
|
+
|
|
92
|
+
return array
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
/**
|
|
96
|
+
* Perform a sort on an array, *in place*, using a key extraction function.
|
|
97
|
+
* @alias qui.utils.array.sortKey
|
|
98
|
+
* @param {Array} array
|
|
99
|
+
* @param {Function} func key extraction function; will be called with each element as parameter and is expected to
|
|
100
|
+
* return the comparison key
|
|
101
|
+
* @param {Boolean} [desc] whether to do a descending sort (defaults to `false`)
|
|
102
|
+
* @param {*} [thisArg] optional argument to use as `this` when calling the key extraction function.
|
|
103
|
+
* @returns {Array} the array
|
|
104
|
+
*/
|
|
105
|
+
export function sortKey(array, func, desc = false, thisArg = null) {
|
|
106
|
+
return array.sort(makeSortFunc(func, desc, thisArg))
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
/**
|
|
110
|
+
* Perform a stable sort on an array, *in place*, using a key extraction function.
|
|
111
|
+
* @alias qui.utils.array.stableSortKey
|
|
112
|
+
* @param {Array} array
|
|
113
|
+
* @param {Function} func key extraction function; will be called with each element as parameter and is expected to
|
|
114
|
+
* return the comparison key
|
|
115
|
+
* @param {Boolean} [desc] whether to do a descending sort (defaults to `false`)
|
|
116
|
+
* @param {*} [thisArg] optional argument to use as `this` when calling the key extraction function.
|
|
117
|
+
* @returns {Array} the array
|
|
118
|
+
*/
|
|
119
|
+
export function stableSortKey(array, func, desc = false, thisArg = null) {
|
|
120
|
+
return stableSort(array, makeSortFunc(func, desc, thisArg))
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
/**
|
|
124
|
+
* Generate an array based on a range of numbers.
|
|
125
|
+
* @alias qui.utils.array.range
|
|
126
|
+
* @param {Number} start inclusive range start
|
|
127
|
+
* @param {Number} stop exclusive range stop
|
|
128
|
+
* @param {Number} [step] range step (defaults to `1`)
|
|
129
|
+
* @returns {Number[]}
|
|
130
|
+
*/
|
|
131
|
+
export function range(start, stop, step = 1) {
|
|
132
|
+
let array = []
|
|
133
|
+
for (let i = start; i < stop; i += step) {
|
|
134
|
+
array.push(i)
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
return array
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
/**
|
|
141
|
+
* Return an array of distinct elements found in an input array.
|
|
142
|
+
* @alias qui.utils.array.distinct
|
|
143
|
+
* @param {Array} array
|
|
144
|
+
* @param {Function} [equalsFunc] a comparison function (defaults to the `===` operator)
|
|
145
|
+
* @param {*} [thisArg] optional argument to be used as `this` when calling `equalsFunc`
|
|
146
|
+
* @returns {Array} the distinct elements array
|
|
147
|
+
*/
|
|
148
|
+
export function distinct(array, equalsFunc = null, thisArg = null) {
|
|
149
|
+
let uniqueElements = []
|
|
150
|
+
|
|
151
|
+
if (!equalsFunc) {
|
|
152
|
+
equalsFunc = (a, b) => a === b
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
array.forEach(function (element) {
|
|
156
|
+
let found = uniqueElements.find(function (elem) {
|
|
157
|
+
return equalsFunc.call(thisArg, elem, element)
|
|
158
|
+
})
|
|
159
|
+
|
|
160
|
+
if (found === undefined) {
|
|
161
|
+
uniqueElements.push(element)
|
|
162
|
+
}
|
|
163
|
+
})
|
|
164
|
+
|
|
165
|
+
return uniqueElements
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
/**
|
|
169
|
+
* Remove all occurrences of an element from an array, in place.
|
|
170
|
+
* @alias qui.utils.array.remove
|
|
171
|
+
* @param {Array} array
|
|
172
|
+
* @param {*} element the element to remove
|
|
173
|
+
*/
|
|
174
|
+
export function remove(array, element) {
|
|
175
|
+
let index
|
|
176
|
+
while ((index = array.indexOf(element)) >= 0) {
|
|
177
|
+
array.splice(index, 1)
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
/**
|
|
182
|
+
* Remove all occurrences of a set of elements from an array, in place.
|
|
183
|
+
* @alias qui.utils.array.removeMany
|
|
184
|
+
* @param {Array} array
|
|
185
|
+
* @param {Array} elements the set of elements to remove
|
|
186
|
+
*/
|
|
187
|
+
export function removeMany(array, elements) {
|
|
188
|
+
let index
|
|
189
|
+
elements.forEach(function (element) {
|
|
190
|
+
while ((index = array.indexOf(element)) >= 0) {
|
|
191
|
+
array.splice(index, 1)
|
|
192
|
+
}
|
|
193
|
+
})
|
|
194
|
+
}
|