@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.
Files changed (202) hide show
  1. package/.eslintignore +2 -0
  2. package/.eslintrc.json +492 -0
  3. package/.github/ISSUE_TEMPLATE/bug_report.md +33 -0
  4. package/.github/ISSUE_TEMPLATE/feature_request.md +23 -0
  5. package/.github/ISSUE_TEMPLATE/improvement_proposal.md +20 -0
  6. package/.github/workflows/main.yml +74 -0
  7. package/.pre-commit-config.yaml +8 -0
  8. package/LICENSE.txt +177 -0
  9. package/README.md +4 -0
  10. package/font/dejavusans-bold.woff +0 -0
  11. package/font/dejavusans-bolditalic.woff +0 -0
  12. package/font/dejavusans-italic.woff +0 -0
  13. package/font/dejavusans-regular.woff +0 -0
  14. package/img/qui-icons.svg +1937 -0
  15. package/js/base/base.js +47 -0
  16. package/js/base/condition-variable.js +92 -0
  17. package/js/base/errors.js +36 -0
  18. package/js/base/i18n.js +20 -0
  19. package/js/base/mixwith.js +135 -0
  20. package/js/base/require-js-compat.js +78 -0
  21. package/js/base/signal.js +91 -0
  22. package/js/base/singleton.js +66 -0
  23. package/js/base/timer.js +126 -0
  24. package/js/config.js +184 -0
  25. package/js/forms/common-fields/check-field.js +42 -0
  26. package/js/forms/common-fields/choice-buttons-field.js +30 -0
  27. package/js/forms/common-fields/color-combo-field.js +37 -0
  28. package/js/forms/common-fields/combo-field.js +108 -0
  29. package/js/forms/common-fields/common-fields.js +23 -0
  30. package/js/forms/common-fields/composite-field.js +132 -0
  31. package/js/forms/common-fields/custom-html-field.js +51 -0
  32. package/js/forms/common-fields/email-field.js +30 -0
  33. package/js/forms/common-fields/file-picker-field.js +46 -0
  34. package/js/forms/common-fields/jquery-ui-field.js +111 -0
  35. package/js/forms/common-fields/labels-field.js +69 -0
  36. package/js/forms/common-fields/numeric-field.js +39 -0
  37. package/js/forms/common-fields/password-field.js +28 -0
  38. package/js/forms/common-fields/phone-field.js +26 -0
  39. package/js/forms/common-fields/progress-disk-field.js +69 -0
  40. package/js/forms/common-fields/push-button-field.js +138 -0
  41. package/js/forms/common-fields/slider-field.js +51 -0
  42. package/js/forms/common-fields/text-area-field.js +34 -0
  43. package/js/forms/common-fields/text-field.js +89 -0
  44. package/js/forms/common-fields/up-down-field.js +85 -0
  45. package/js/forms/common-forms/common-forms.js +16 -0
  46. package/js/forms/common-forms/options-form.js +77 -0
  47. package/js/forms/common-forms/page-form.js +115 -0
  48. package/js/forms/form-button.js +202 -0
  49. package/js/forms/form-field.js +1183 -0
  50. package/js/forms/form.js +1181 -0
  51. package/js/forms/forms.js +68 -0
  52. package/js/global-glass.js +100 -0
  53. package/js/icons/default-stock.js +173 -0
  54. package/js/icons/icon.js +64 -0
  55. package/js/icons/icons.js +16 -0
  56. package/js/icons/multi-state-sprites-icon.js +362 -0
  57. package/js/icons/stock-icon.js +219 -0
  58. package/js/icons/stock.js +98 -0
  59. package/js/icons/stocks.js +57 -0
  60. package/js/index.js +232 -0
  61. package/js/lib/jquery.longpress.js +79 -0
  62. package/js/lib/jquery.module.js +4 -0
  63. package/js/lib/logger.module.js +4 -0
  64. package/js/lib/pep.module.js +4 -0
  65. package/js/lists/common-items/common-items.js +5 -0
  66. package/js/lists/common-items/icon-label-list-item.js +86 -0
  67. package/js/lists/common-lists/common-lists.js +5 -0
  68. package/js/lists/common-lists/page-list.js +53 -0
  69. package/js/lists/list-item.js +147 -0
  70. package/js/lists/list.js +636 -0
  71. package/js/lists/lists.js +26 -0
  72. package/js/main-ui/main-ui.js +64 -0
  73. package/js/main-ui/menu-bar.js +144 -0
  74. package/js/main-ui/options-bar.js +181 -0
  75. package/js/main-ui/status.js +185 -0
  76. package/js/main-ui/top-bar.js +59 -0
  77. package/js/messages/common-message-forms/common-message-forms.js +7 -0
  78. package/js/messages/common-message-forms/confirm-message-form.js +81 -0
  79. package/js/messages/common-message-forms/simple-message-form.js +67 -0
  80. package/js/messages/common-message-forms/sticky-simple-message-form.js +27 -0
  81. package/js/messages/message-form.js +107 -0
  82. package/js/messages/messages.js +21 -0
  83. package/js/messages/sticky-modal-page.js +98 -0
  84. package/js/messages/sticky-modal-progress-message.js +27 -0
  85. package/js/messages/toast.js +164 -0
  86. package/js/navigation.js +654 -0
  87. package/js/pages/breadcrumbs.js +124 -0
  88. package/js/pages/common-pages/common-pages.js +6 -0
  89. package/js/pages/common-pages/modal-progress-page.js +83 -0
  90. package/js/pages/common-pages/structured-page.js +46 -0
  91. package/js/pages/page.js +1018 -0
  92. package/js/pages/pages-context.js +154 -0
  93. package/js/pages/pages.js +252 -0
  94. package/js/pwa.js +337 -0
  95. package/js/sections/section.js +612 -0
  96. package/js/sections/sections.js +300 -0
  97. package/js/tables/common-cells/common-cells.js +7 -0
  98. package/js/tables/common-cells/icon-label-table-cell.js +68 -0
  99. package/js/tables/common-cells/push-button-table-cell.js +133 -0
  100. package/js/tables/common-cells/simple-table-cell.js +37 -0
  101. package/js/tables/common-tables/common-tables.js +5 -0
  102. package/js/tables/common-tables/page-table.js +55 -0
  103. package/js/tables/table-cell.js +198 -0
  104. package/js/tables/table-row.js +126 -0
  105. package/js/tables/table.js +492 -0
  106. package/js/tables/tables.js +36 -0
  107. package/js/theme.js +304 -0
  108. package/js/utils/ajax.js +126 -0
  109. package/js/utils/array.js +194 -0
  110. package/js/utils/colors.js +445 -0
  111. package/js/utils/cookies.js +65 -0
  112. package/js/utils/crypto.js +439 -0
  113. package/js/utils/css.js +234 -0
  114. package/js/utils/date.js +300 -0
  115. package/js/utils/files.js +27 -0
  116. package/js/utils/gestures.js +165 -0
  117. package/js/utils/html.js +76 -0
  118. package/js/utils/misc.js +81 -0
  119. package/js/utils/object.js +324 -0
  120. package/js/utils/promise.js +49 -0
  121. package/js/utils/string.js +192 -0
  122. package/js/utils/url.js +187 -0
  123. package/js/utils/utils.js +3 -0
  124. package/js/utils/visibility-manager.js +211 -0
  125. package/js/views/common-views/common-views.js +7 -0
  126. package/js/views/common-views/icon-label-view.js +210 -0
  127. package/js/views/common-views/progress-view.js +89 -0
  128. package/js/views/common-views/structured-view.js +368 -0
  129. package/js/views/view.js +467 -0
  130. package/js/views/views.js +3 -0
  131. package/js/widgets/base-widget.js +23 -0
  132. package/js/widgets/common-widgets/check-button.js +109 -0
  133. package/js/widgets/common-widgets/choice-buttons.js +322 -0
  134. package/js/widgets/common-widgets/color-combo.js +104 -0
  135. package/js/widgets/common-widgets/combo.js +645 -0
  136. package/js/widgets/common-widgets/common-widgets.js +17 -0
  137. package/js/widgets/common-widgets/email-input.js +7 -0
  138. package/js/widgets/common-widgets/file-picker.js +133 -0
  139. package/js/widgets/common-widgets/labels.js +132 -0
  140. package/js/widgets/common-widgets/numeric-input.js +49 -0
  141. package/js/widgets/common-widgets/password-input.js +91 -0
  142. package/js/widgets/common-widgets/phone-input.js +7 -0
  143. package/js/widgets/common-widgets/progress-disk.js +174 -0
  144. package/js/widgets/common-widgets/push-button.js +155 -0
  145. package/js/widgets/common-widgets/slider.js +455 -0
  146. package/js/widgets/common-widgets/text-area.js +52 -0
  147. package/js/widgets/common-widgets/text-input.js +174 -0
  148. package/js/widgets/common-widgets/up-down.js +351 -0
  149. package/js/widgets/widgets.js +57 -0
  150. package/js/window.js +557 -0
  151. package/jsdoc.conf.json +20 -0
  152. package/less/base.less +123 -0
  153. package/less/forms/common-fields.less +101 -0
  154. package/less/forms/common-forms.less +5 -0
  155. package/less/forms/form-button.less +21 -0
  156. package/less/forms/form-field.less +266 -0
  157. package/less/forms/form.less +131 -0
  158. package/less/global-glass.less +64 -0
  159. package/less/icon-label-view.less +82 -0
  160. package/less/icons.less +144 -0
  161. package/less/lists.less +105 -0
  162. package/less/main-ui.less +328 -0
  163. package/less/messages.less +189 -0
  164. package/less/no-effects.less +24 -0
  165. package/less/pages/breadcrumbs.less +98 -0
  166. package/less/pages/common-pages.less +36 -0
  167. package/less/pages/page.less +70 -0
  168. package/less/progress-view.less +51 -0
  169. package/less/stock-icons.less +43 -0
  170. package/less/structured-view.less +245 -0
  171. package/less/tables.less +84 -0
  172. package/less/theme-dark.less +133 -0
  173. package/less/theme-light.less +132 -0
  174. package/less/theme.less +419 -0
  175. package/less/visibility-manager.less +11 -0
  176. package/less/widgets/check-button.less +96 -0
  177. package/less/widgets/choice-buttons.less +160 -0
  178. package/less/widgets/color-combo.less +33 -0
  179. package/less/widgets/combo.less +230 -0
  180. package/less/widgets/common-buttons.less +120 -0
  181. package/less/widgets/common.less +24 -0
  182. package/less/widgets/input.less +258 -0
  183. package/less/widgets/labels.less +81 -0
  184. package/less/widgets/progress-disk.less +70 -0
  185. package/less/widgets/slider.less +199 -0
  186. package/less/widgets/updown.less +115 -0
  187. package/less/widgets/various.less +36 -0
  188. package/package.json +47 -0
  189. package/pyproject.toml +45 -0
  190. package/qui/__init__.py +110 -0
  191. package/qui/constants.py +1 -0
  192. package/qui/exceptions.py +2 -0
  193. package/qui/j2template.py +71 -0
  194. package/qui/settings.py +60 -0
  195. package/qui/templates/manifest.json +25 -0
  196. package/qui/templates/qui.html +126 -0
  197. package/qui/templates/service-worker.js +188 -0
  198. package/qui/web/__init__.py +0 -0
  199. package/qui/web/tornado.py +220 -0
  200. package/scripts/postinstall.sh +10 -0
  201. package/webpack/webpack-adjust-css-urls-loader.js +36 -0
  202. package/webpack/webpack-common.js +384 -0
@@ -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
+ }
@@ -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
@@ -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