@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/base/base.js
ADDED
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @namespace qui.base
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Make an imported module globally accessible via `window`.
|
|
8
|
+
*
|
|
9
|
+
* This function is intended to be used with dynamic imports, such as:
|
|
10
|
+
*
|
|
11
|
+
* import('$qui/path/to/module.js').then(globalize('qui.path.to.module'))
|
|
12
|
+
*
|
|
13
|
+
* When imported module has default export, the last element in `path` is used to represent it:
|
|
14
|
+
*
|
|
15
|
+
* import('$qui/path/to/class.js').then(globalize('qui.path.to.Class'))
|
|
16
|
+
*
|
|
17
|
+
* @alias qui.base.globalize
|
|
18
|
+
* @param {String} path dotted path
|
|
19
|
+
* @returns {Function} a function that handles dynamic import call promise result
|
|
20
|
+
*/
|
|
21
|
+
export function globalize(path) {
|
|
22
|
+
return function (module) {
|
|
23
|
+
let parts = path.split('.')
|
|
24
|
+
let obj = window
|
|
25
|
+
let defName = null
|
|
26
|
+
|
|
27
|
+
/* When module has default export, last element of path is used as default name */
|
|
28
|
+
if ('default' in module) {
|
|
29
|
+
defName = parts[parts.length - 1]
|
|
30
|
+
parts = parts.slice(0, -1)
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
parts.forEach(function (part) {
|
|
34
|
+
if (!(part in obj)) {
|
|
35
|
+
obj[part] = {}
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
obj = obj[part]
|
|
39
|
+
})
|
|
40
|
+
|
|
41
|
+
Object.entries(module).forEach(([key, value]) => (obj[key] = value))
|
|
42
|
+
if (defName) {
|
|
43
|
+
obj[defName] = obj['default']
|
|
44
|
+
delete obj['default']
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
}
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
|
|
2
|
+
import {AssertionError} from '$qui/base/errors.js'
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* An asynchronous implementation of condition variables based on promises. Due to the fact that promises cannot be
|
|
7
|
+
* easily extended, this class actually wraps a promise.
|
|
8
|
+
* @alias qui.base.ConditionVariable
|
|
9
|
+
* @extends Promise
|
|
10
|
+
*/
|
|
11
|
+
class ConditionVariable {
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* @constructs
|
|
15
|
+
*/
|
|
16
|
+
constructor() {
|
|
17
|
+
this._resolve = null
|
|
18
|
+
this._reject = null
|
|
19
|
+
|
|
20
|
+
this._promise = new Promise(function (resolve, reject) {
|
|
21
|
+
|
|
22
|
+
this._resolve = resolve
|
|
23
|
+
this._reject = reject
|
|
24
|
+
|
|
25
|
+
}.bind(this))
|
|
26
|
+
|
|
27
|
+
this._fulfilled = false
|
|
28
|
+
this._cancelled = false
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Fulfill the condition, notifying everybody that waits on this condition.
|
|
33
|
+
* @param {*} [arg] an optional fulfill argument
|
|
34
|
+
*/
|
|
35
|
+
fulfill(arg) {
|
|
36
|
+
if (this._fulfilled || this._cancelled) {
|
|
37
|
+
throw new AssertionError('Condition variable already settled')
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
this._fulfilled = true
|
|
41
|
+
this._resolve(arg)
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Cancel the condition, notifying everybody that waits on this condition.
|
|
46
|
+
* @param {*} [error] an optional error argument
|
|
47
|
+
*/
|
|
48
|
+
cancel(error) {
|
|
49
|
+
if (this._fulfilled || this._cancelled) {
|
|
50
|
+
throw new AssertionError('Condition variable already settled')
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
this._cancelled = true
|
|
54
|
+
this._reject(error)
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* Tell if the condition is fulfilled or not.
|
|
59
|
+
* @returns {Boolean}
|
|
60
|
+
*/
|
|
61
|
+
isFulfilled() {
|
|
62
|
+
return this._fulfilled
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* Tell if the condition is cancelled or not.
|
|
67
|
+
* @returns {Boolean}
|
|
68
|
+
*/
|
|
69
|
+
isCancelled() {
|
|
70
|
+
return this._cancelled
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* Tell if the condition is settled (fulfilled or cancelled) or not.
|
|
75
|
+
* @returns {Boolean}
|
|
76
|
+
*/
|
|
77
|
+
isSettled() {
|
|
78
|
+
return this._fulfilled || this._cancelled
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
then(f, r) {
|
|
82
|
+
return this._promise.then(f, r)
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
catch(r) {
|
|
86
|
+
return this._promise.catch(r)
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
export default ConditionVariable
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @namespace qui.base.errors
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* An error class used when assertions fail.
|
|
7
|
+
* @alias qui.base.errors.AssertionError
|
|
8
|
+
* @extends Error
|
|
9
|
+
*/
|
|
10
|
+
export class AssertionError extends Error {
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* An error class used when an operation times out.
|
|
15
|
+
* @alias qui.base.errors.TimeoutError
|
|
16
|
+
* @extends Error
|
|
17
|
+
*/
|
|
18
|
+
export class TimeoutError extends Error {
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* An error class thrown from methods or functions that are not implemented.
|
|
23
|
+
* @alias qui.base.errors.NotImplementedError
|
|
24
|
+
* @extends Error
|
|
25
|
+
*/
|
|
26
|
+
export class NotImplementedError extends Error {
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* An error class used to indicate an operation that has been cancelled. Exceptions of this type are usually ignored and
|
|
31
|
+
* not treated as errors.
|
|
32
|
+
* @alias qui.base.errors.CancelledError
|
|
33
|
+
* @extends Error
|
|
34
|
+
*/
|
|
35
|
+
export class CancelledError extends Error {
|
|
36
|
+
}
|
package/js/base/i18n.js
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @namespace qui.base.i18n
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Translate a message.
|
|
7
|
+
* @alias qui.base.i18n.gettext
|
|
8
|
+
* @param {String} msg the message to translate
|
|
9
|
+
* @returns {String} the translated message or the original message, if no translation found
|
|
10
|
+
*/
|
|
11
|
+
export function gettext(msg) {
|
|
12
|
+
if (window._jsTranslations) {
|
|
13
|
+
let transMsg = window._jsTranslations[msg]
|
|
14
|
+
if (transMsg != null) {
|
|
15
|
+
return transMsg
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
return msg
|
|
20
|
+
}
|
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Utilities for writing mixins. Copied and adapted from Justin Fagnani's work:
|
|
3
|
+
* {@link https://github.com/justinfagnani/mixwith.js}.
|
|
4
|
+
*
|
|
5
|
+
* @namespace qui.base.mixwith
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
const _appliedMixin = '__mixwith_appliedMixin'
|
|
9
|
+
const _wrappedMixin = '__mixwith_wrappedMixin'
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* A function that, when called with (applied to) a superclass, generates a new class inherited indirectly from the
|
|
14
|
+
* superclass and directly from an intermediate mixin dynamic class, offering extra functionality, specific to the
|
|
15
|
+
* mixin.
|
|
16
|
+
*
|
|
17
|
+
* @callback qui.base.mixwith.MixinFunction
|
|
18
|
+
* @param {typeof Object} superclass class to which the mixin will be applied
|
|
19
|
+
* @param rootclass
|
|
20
|
+
* @param mixin
|
|
21
|
+
*/
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
function apply(superclass, rootclass, mixin) {
|
|
25
|
+
let application = mixin(superclass, rootclass)
|
|
26
|
+
application.prototype[_appliedMixin] = unwrap(mixin)
|
|
27
|
+
|
|
28
|
+
return application
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
function isApplicationOf(proto, mixin) {
|
|
32
|
+
return Object.prototype.hasOwnProperty.call(proto, _appliedMixin) && proto[_appliedMixin] === unwrap(mixin)
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
function hasMixin(o, mixin) {
|
|
36
|
+
while (o != null) {
|
|
37
|
+
if (isApplicationOf(o, mixin)) {
|
|
38
|
+
return true
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
o = Object.getPrototypeOf(o)
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
return false
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
function wrap(mixin, wrapper) {
|
|
48
|
+
Object.setPrototypeOf(wrapper, mixin)
|
|
49
|
+
if (!mixin[_wrappedMixin]) {
|
|
50
|
+
mixin[_wrappedMixin] = mixin
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
return wrapper
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
function unwrap(wrapper) {
|
|
57
|
+
return wrapper[_wrappedMixin] || wrapper
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
function HasInstance(mixin) {
|
|
61
|
+
if (Symbol && Symbol.hasInstance) {
|
|
62
|
+
Object.defineProperty(mixin, Symbol.hasInstance, {
|
|
63
|
+
value(o) {
|
|
64
|
+
return hasMixin(o, mixin)
|
|
65
|
+
}
|
|
66
|
+
})
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
return mixin
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
function BareMixin(mixin) {
|
|
73
|
+
return wrap(mixin, (s, r) => apply(s, r, mixin))
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* A helper class capable of applying one or more mixins to a superclass.
|
|
79
|
+
* @alias qui.base.mixwith.MixinBuilder
|
|
80
|
+
*/
|
|
81
|
+
export class MixinBuilder {
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* @constructs
|
|
85
|
+
* @param {typeof Object} superclass
|
|
86
|
+
*/
|
|
87
|
+
constructor(superclass) {
|
|
88
|
+
this.superclass = superclass || Object
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
* @param {...qui.base.mixwith.MixinFunction} mixins mixins to apply to the superclass
|
|
93
|
+
* @returns {*} a new class derived from the superclass, with all `mixins` applied
|
|
94
|
+
*/
|
|
95
|
+
with(...mixins) {
|
|
96
|
+
return mixins.reduce((c, m) => m(c, /* rootclass = */ this.superclass), this.superclass)
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
|
|
102
|
+
/**
|
|
103
|
+
* Prepare a mixin function.
|
|
104
|
+
* @alias qui.base.mixwith.Mixin
|
|
105
|
+
* @param {qui.base.mixwith.MixinFunction} mixin the mixin function
|
|
106
|
+
* @returns {qui.base.mixwith.MixinFunction} the prepared mixin function
|
|
107
|
+
*/
|
|
108
|
+
export function Mixin(mixin) {
|
|
109
|
+
return HasInstance(BareMixin(mixin))
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
|
|
113
|
+
/**
|
|
114
|
+
* Apply a list of mixins to a superclass.
|
|
115
|
+
*
|
|
116
|
+
* ```
|
|
117
|
+
* class X extends mix(Object).with(A, B, C) {}
|
|
118
|
+
* ```
|
|
119
|
+
*
|
|
120
|
+
* The mixins are applied in order to the superclass, so the prototype chain
|
|
121
|
+
* will be: X->C'->B'->A'->Object.
|
|
122
|
+
*
|
|
123
|
+
* This is purely a convenience function. The above example is equivalent to:
|
|
124
|
+
*
|
|
125
|
+
* ```
|
|
126
|
+
* class X extends C(B(A(Object))) {}
|
|
127
|
+
* ```
|
|
128
|
+
*
|
|
129
|
+
* @alias qui.base.mixwith.mix
|
|
130
|
+
* @param {typeof Object} [superclass]
|
|
131
|
+
* @returns {MixinBuilder}
|
|
132
|
+
*/
|
|
133
|
+
export function mix(superclass = Object) {
|
|
134
|
+
return new MixinBuilder(superclass)
|
|
135
|
+
}
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* A layer of compatibility between modules based on `define()`/`require()` and ES6 modules.
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
let definedModules = {}
|
|
6
|
+
|
|
7
|
+
const ALIASES = {
|
|
8
|
+
jquery: ['jQuery']
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
function findCallerModuleName() {
|
|
13
|
+
/* Use stack trace to find the name of the first JS file, which is expected to be the module */
|
|
14
|
+
let error = new Error()
|
|
15
|
+
let names = error.stack.match(new RegExp('[A-Za-z0-9_.-]+\\.js', 'g'))
|
|
16
|
+
if (!names) {
|
|
17
|
+
return
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/* The name of the module is the name of the script file without the extension */
|
|
21
|
+
let name = names[names.length - 1]
|
|
22
|
+
name = name.split('.').slice(0, -1).join('.')
|
|
23
|
+
|
|
24
|
+
return name
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
window.define = function (...args) {
|
|
28
|
+
let name = null
|
|
29
|
+
let depNames = []
|
|
30
|
+
let factory
|
|
31
|
+
|
|
32
|
+
if (args.length < 1) {
|
|
33
|
+
return
|
|
34
|
+
}
|
|
35
|
+
else if (args.length < 2) {
|
|
36
|
+
[factory] = args
|
|
37
|
+
}
|
|
38
|
+
else if (args.length < 3) {
|
|
39
|
+
[depNames, factory] = args
|
|
40
|
+
}
|
|
41
|
+
else {
|
|
42
|
+
[name, depNames, factory] = args
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/* For anonymous modules, we need to look for the name of the module script */
|
|
46
|
+
if (!name) {
|
|
47
|
+
name = findCallerModuleName()
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
let deps = depNames.map(depName => window[depName])
|
|
51
|
+
let module
|
|
52
|
+
if (typeof factory === 'function') {
|
|
53
|
+
module = factory.apply(null, deps)
|
|
54
|
+
}
|
|
55
|
+
else {
|
|
56
|
+
module = factory
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
if (name) {
|
|
60
|
+
window[name] = definedModules[name] = module
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/* Also register module under its aliases */
|
|
64
|
+
(ALIASES[name] || []).forEach(function (alias) {
|
|
65
|
+
window[alias] = module
|
|
66
|
+
})
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
window.define.amd = true
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* Cleanup workarounds set up for providing `define()`/`require()` compatibility.
|
|
74
|
+
*/
|
|
75
|
+
export function cleanup() {
|
|
76
|
+
Object.keys(definedModules).forEach(name => delete window[name])
|
|
77
|
+
delete window['define']
|
|
78
|
+
}
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A signal handler.
|
|
3
|
+
* @callback qui.base.Signal.SignalHandler
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* A signal.
|
|
8
|
+
* @alias qui.base.Signal
|
|
9
|
+
*/
|
|
10
|
+
class Signal {
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* @constructs
|
|
14
|
+
* @param {Object} [object] an optional object that owns and emits the signal
|
|
15
|
+
*/
|
|
16
|
+
constructor(object = null) {
|
|
17
|
+
this._object = object
|
|
18
|
+
this._handlers = []
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Add a handler to the signal. This function doesn't check for duplicates, so if a handler is bound more than once
|
|
23
|
+
* to the same signal, it will be called multiple times when the signal is emitted.
|
|
24
|
+
* @param {qui.base.Signal.SignalHandler} handler the handler function to add
|
|
25
|
+
* @param {Boolean} [once] optionally set this to `true` to call the handler only once, the first time the signal is
|
|
26
|
+
* emitted, and then disconnect the handler from the signal
|
|
27
|
+
* @returns {qui.base.Signal} this signal
|
|
28
|
+
*/
|
|
29
|
+
connect(handler, once = false) {
|
|
30
|
+
this._handlers.push({handler: handler, once: once})
|
|
31
|
+
|
|
32
|
+
return this
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Remove a handler from the signal.
|
|
37
|
+
* @param {qui.base.Signal.SignalHandler} [handler] the handler function to disconnect
|
|
38
|
+
* @returns {qui.base.Signal} this signal
|
|
39
|
+
*/
|
|
40
|
+
disconnect(handler) {
|
|
41
|
+
this._handlers = this._handlers.filter(e => e.handler !== handler)
|
|
42
|
+
|
|
43
|
+
return this
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Remove all handlers from the signal.
|
|
48
|
+
* @returns {qui.base.Signal} this signal
|
|
49
|
+
*/
|
|
50
|
+
disconnectAll() {
|
|
51
|
+
this._handlers = []
|
|
52
|
+
|
|
53
|
+
return this
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* Emit the signal. All the handler functions will be called in the order of binding, until one of them returns
|
|
58
|
+
* `false`.
|
|
59
|
+
*
|
|
60
|
+
* Any additional arguments to this function will be passed to the handlers.
|
|
61
|
+
*
|
|
62
|
+
* The handler functions are called with `this` set to the owner object of the signal.
|
|
63
|
+
*
|
|
64
|
+
* @params {...*} args arguments to be passed to the handlers
|
|
65
|
+
* @returns {*} the value returned by the last called handler
|
|
66
|
+
*/
|
|
67
|
+
emit(...args) {
|
|
68
|
+
// eslint-disable-next-line no-undef-init
|
|
69
|
+
let result = undefined
|
|
70
|
+
|
|
71
|
+
this._handlers.some(function ({handler, once}) {
|
|
72
|
+
|
|
73
|
+
if (once) {
|
|
74
|
+
this.disconnect(handler)
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
result = handler.apply(this._object, args)
|
|
78
|
+
if (result === false) {
|
|
79
|
+
/* Returning false from a handler will stop the signal from being handled any further */
|
|
80
|
+
return true
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
}, this)
|
|
84
|
+
|
|
85
|
+
return result
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
export default Signal
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
|
|
2
|
+
import {Mixin} from '$qui/base/mixwith.js'
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
/** @lends qui.base.SingletonMixin */
|
|
6
|
+
const SingletonMixin = Mixin((superclass = Object) => {
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* A mixin that helps implementing the singleton pattern.
|
|
10
|
+
* @alias qui.base.SingletonMixin
|
|
11
|
+
* @mixin
|
|
12
|
+
*/
|
|
13
|
+
class SingletonMixin extends superclass {
|
|
14
|
+
|
|
15
|
+
static _instance = null
|
|
16
|
+
static _args = null
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* @constructs
|
|
21
|
+
* @param {...*} args parent class parameters
|
|
22
|
+
*/
|
|
23
|
+
constructor(...args) {
|
|
24
|
+
super(...args)
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Prepare the class for instantiation, establishing the constructor arguments.
|
|
29
|
+
*
|
|
30
|
+
* Any arguments passed to this method will be passed to the constructor, when creating the singleton.
|
|
31
|
+
*
|
|
32
|
+
* @param {...*} args arguments to be passed to the constructor
|
|
33
|
+
*/
|
|
34
|
+
static setup(...args) {
|
|
35
|
+
this._args = args
|
|
36
|
+
this._instance = null
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Return the singleton instance. Instantiates the class on first call.
|
|
41
|
+
* @returns {Object} the instance
|
|
42
|
+
*/
|
|
43
|
+
static getInstance() {
|
|
44
|
+
if (this._instance) {
|
|
45
|
+
return this._instance
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/* Make sure setup() has been called */
|
|
49
|
+
if (this._args == null) {
|
|
50
|
+
this.setup()
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
// eslint-disable-next-line new-parens
|
|
54
|
+
this._instance = new (this.bind.apply(this, this._args))
|
|
55
|
+
|
|
56
|
+
return this._instance
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
return SingletonMixin
|
|
62
|
+
|
|
63
|
+
})
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
export default SingletonMixin
|
package/js/base/timer.js
ADDED
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
|
|
2
|
+
import {AssertionError} from '$qui/base/errors.js'
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* A convenience class that makes it easy to work with timeouts.
|
|
7
|
+
* @alias qui.base.Timer
|
|
8
|
+
*/
|
|
9
|
+
class Timer {
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* @constructs
|
|
13
|
+
* @param {Number} [defaultTimeout] a default timeout, in milliseconds
|
|
14
|
+
* @param {Function} [onTimeout] an optional timeout callback
|
|
15
|
+
* @param {Boolean} [repeat] set to `true` to automatically restart timer on timeout
|
|
16
|
+
*/
|
|
17
|
+
constructor(defaultTimeout = null, onTimeout = null, repeat = false) {
|
|
18
|
+
this._defaultTimeout = defaultTimeout
|
|
19
|
+
this._onTimeout = onTimeout
|
|
20
|
+
this._repeat = repeat
|
|
21
|
+
|
|
22
|
+
this._cancelled = false
|
|
23
|
+
this._startedTime = null
|
|
24
|
+
this._timeoutHandle = null
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Start the timer.
|
|
29
|
+
* @param {Number} [timeout] a timeout, in milliseconds, that overrides the timer's `defaultTimeout`
|
|
30
|
+
*/
|
|
31
|
+
start(timeout = null) {
|
|
32
|
+
if (this._timeoutHandle) {
|
|
33
|
+
throw new AssertionError('Timer already started')
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
if (timeout == null) {
|
|
37
|
+
timeout = this._defaultTimeout
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
if (timeout == null) {
|
|
41
|
+
throw new AssertionError('Cannot start timer without timeout')
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
this._cancelled = false
|
|
45
|
+
this._startedTime = new Date().getTime()
|
|
46
|
+
|
|
47
|
+
this._timeoutHandle = setTimeout(function () {
|
|
48
|
+
this._timeoutHandle = null
|
|
49
|
+
if (this._repeat) {
|
|
50
|
+
this.start(timeout)
|
|
51
|
+
}
|
|
52
|
+
if (this._onTimeout) {
|
|
53
|
+
this._onTimeout()
|
|
54
|
+
}
|
|
55
|
+
}.bind(this), timeout)
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* Cancel the timer.
|
|
60
|
+
*/
|
|
61
|
+
cancel() {
|
|
62
|
+
if (!this._timeoutHandle) {
|
|
63
|
+
throw new AssertionError('Timer not started')
|
|
64
|
+
}
|
|
65
|
+
if (this._cancelled) {
|
|
66
|
+
throw new AssertionError('Timer already cancelled')
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
clearTimeout(this._timeoutHandle)
|
|
70
|
+
this._timeoutHandle = null
|
|
71
|
+
this._cancelled = true
|
|
72
|
+
this._startedTime = null
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* Start the timer if it's not currently running. Otherwise cancel and start it over.
|
|
77
|
+
* @param {Number} [timeout] a timeout, in milliseconds, that overrides the timer's `defaultTimeout`
|
|
78
|
+
*/
|
|
79
|
+
restart(timeout = null) {
|
|
80
|
+
if (this.isRunning()) {
|
|
81
|
+
this.cancel()
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
this.start(timeout)
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* Tell if the timer is currently running.
|
|
89
|
+
* @returns {Boolean}
|
|
90
|
+
*/
|
|
91
|
+
isRunning() {
|
|
92
|
+
return !!this._timeoutHandle
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
/**
|
|
96
|
+
* Tell if the timer has been cancelled.
|
|
97
|
+
* @returns {Boolean}
|
|
98
|
+
*/
|
|
99
|
+
isCancelled() {
|
|
100
|
+
return this._cancelled
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
/**
|
|
104
|
+
* Tell if the timer has fired.
|
|
105
|
+
* @returns {Boolean}
|
|
106
|
+
*/
|
|
107
|
+
isFired() {
|
|
108
|
+
return !this._timeoutHandle && !!this._startedTime
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
/**
|
|
112
|
+
* Tell the remaining time, in milliseconds.
|
|
113
|
+
* @returns {Number}
|
|
114
|
+
*/
|
|
115
|
+
getRemainingTime() {
|
|
116
|
+
if (this.isRunning()) {
|
|
117
|
+
throw new AssertionError('Timer not started')
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
return new Date().getTime() - this._startedTime
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
|
|
126
|
+
export default Timer
|