@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
package/js/pwa.js ADDED
@@ -0,0 +1,337 @@
1
+ /**
2
+ * @namespace qui.pwa
3
+ */
4
+
5
+ import Logger from '$qui/lib/logger.module.js'
6
+
7
+ import ConditionVariable from '$qui/base/condition-variable.js'
8
+ import {AssertionError} from '$qui/base/errors.js'
9
+ import Signal from '$qui/base/signal.js'
10
+ import Config from '$qui/config.js'
11
+ import * as QUI from '$qui/index.js'
12
+ import * as Navigation from '$qui/navigation.js'
13
+ import * as Theme from '$qui/theme.js'
14
+ import {appendBuildHash} from '$qui/utils/misc.js'
15
+ import * as PromiseUtils from '$qui/utils/promise.js'
16
+ import URL from '$qui/utils/url.js'
17
+ import * as Window from '$qui/window.js'
18
+
19
+
20
+ const SERVICE_WORKER_SCRIPT = 'service-worker.js'
21
+ const SERVICE_WORKER_MESSAGE_ACTIVATE = 'qui-activate'
22
+
23
+ const MANIFEST_FILE = 'manifest.json'
24
+
25
+ const logger = Logger.get('qui.pwa')
26
+
27
+ let manifestParams = {}
28
+ let serviceWorker = null
29
+ let serviceWorkerUpdateCalled = false /* duplicate call protection */
30
+ let installElementHandler = null
31
+ let installResponseHandler = null
32
+ let installPrompted = false
33
+
34
+ /**
35
+ * A condition that is fulfilled as soon as this client becomes controlled by a service worker.
36
+ * The condition is passed the service worker as parameter.
37
+ * @type {qui.base.ConditionVariable}
38
+ */
39
+ export let whenServiceWorkerReady = new ConditionVariable()
40
+
41
+ /**
42
+ * Emitted whenever a message is received from the controlling service worker. Handlers are called with the following
43
+ * parameters:
44
+ * * `serviceWorker`, the new controlling service worker
45
+ * * `message`, the incoming message
46
+ * @type {qui.base.Signal}
47
+ */
48
+ export const serviceWorkerMessageSignal = new Signal()
49
+
50
+
51
+ function handleServiceWorkerUpdate(sw, updateHandler) {
52
+ if (serviceWorkerUpdateCalled) {
53
+ return
54
+ }
55
+
56
+ serviceWorkerUpdateCalled = true
57
+
58
+ logger.info('service worker updated')
59
+
60
+ if (updateHandler) {
61
+ let result = updateHandler(sw)
62
+ if (result) {
63
+ if (result.then) { /* A promise */
64
+ result.then(function () {
65
+ provisionServiceWorker(sw)
66
+ }).catch(() => {})
67
+ }
68
+ else { /* Assuming a true value */
69
+ provisionServiceWorker(sw)
70
+ }
71
+ }
72
+ }
73
+ else {
74
+ provisionServiceWorker(sw)
75
+ }
76
+ }
77
+
78
+ function handleServiceWorkerReady(sw) {
79
+ logger.info('service worker is ready')
80
+ serviceWorker = sw
81
+ whenServiceWorkerReady.fulfill(serviceWorker)
82
+ }
83
+
84
+ function provisionServiceWorker(sw) {
85
+ logger.info('provisioning service worker')
86
+ let message = {
87
+ type: SERVICE_WORKER_MESSAGE_ACTIVATE,
88
+ config: Config.dump()
89
+ }
90
+
91
+ sw.postMessage(message)
92
+ }
93
+
94
+ function handleServiceWorkerMessage(message) {
95
+ logger.debug(`received service worker message: ${message.data}`)
96
+ serviceWorkerMessageSignal.emit(serviceWorker, message.data)
97
+ }
98
+
99
+
100
+ /**
101
+ * Tell if service workers are supported.
102
+ * @returns {Boolean}
103
+ */
104
+ export function isServiceWorkerSupported() {
105
+ return 'serviceWorker' in navigator
106
+ }
107
+
108
+ /**
109
+ * Enable the service worker functionality.
110
+ *
111
+ * @param {String} [url] URL at which the service worker lives; {@link qui.config.navigationBasePrefix} +
112
+ * `"/service-worker.js"` will be used by default
113
+ * @param {Function} [updateHandler] a function to be called when the service worker is updated; should return a promise
114
+ * that will be used to control the activation of the new service worker
115
+ * @alias qui.pwa.enableServiceWorker
116
+ */
117
+ export function enableServiceWorker(url = null, updateHandler = null) {
118
+ if (!('serviceWorker' in navigator)) {
119
+ throw new Error('service workers not supported')
120
+ }
121
+
122
+ if (!url) {
123
+ url = `${Config.navigationBasePrefix}/${SERVICE_WORKER_SCRIPT}`
124
+ }
125
+
126
+ url = appendBuildHash(url)
127
+ if (Config.debug) {
128
+ url += '&debug=true'
129
+ }
130
+
131
+ navigator.serviceWorker.addEventListener('message', handleServiceWorkerMessage)
132
+
133
+ let refreshing = false
134
+ navigator.serviceWorker.addEventListener('controllerchange', function () {
135
+ if (refreshing) {
136
+ return
137
+ }
138
+
139
+ refreshing = true
140
+ Window.reload()
141
+ })
142
+
143
+ navigator.serviceWorker.ready.then(function (registration) {
144
+ handleServiceWorkerReady(registration.active)
145
+ })
146
+
147
+ navigator.serviceWorker.register(url).then(function (registration) {
148
+ logger.info(`service worker registered with scope "${registration.scope}"`)
149
+ registration.update() /* Manually trigger an update on each refresh */
150
+
151
+ function awaitStateChange() {
152
+ registration.installing.addEventListener('statechange', function () {
153
+ if (this.state === 'installed') {
154
+ handleServiceWorkerUpdate(this, updateHandler)
155
+ }
156
+ })
157
+ }
158
+
159
+ if (registration.waiting) {
160
+ return handleServiceWorkerUpdate(registration.waiting, updateHandler)
161
+ }
162
+
163
+ if (registration.installing) {
164
+ awaitStateChange()
165
+ }
166
+
167
+ registration.addEventListener('updatefound', awaitStateChange)
168
+
169
+ }).catch(function (e) {
170
+ logger.error(`service worker registration failed: ${e}`)
171
+ })
172
+
173
+ logger.debug(`service worker setup with URL = "${url}"`)
174
+ }
175
+
176
+ /**
177
+ * Return the service worker that currently controls the client.
178
+ * @returns {?ServiceWorker}
179
+ */
180
+ export function getServiceWorker() {
181
+ return serviceWorker
182
+ }
183
+
184
+ /**
185
+ * Send a message to the controlling service worker.
186
+ * @param {*} message the message to send
187
+ */
188
+ export function sendServiceWorkerMessage(message) {
189
+ if (!serviceWorker) {
190
+ throw new AssertionError('Attempt to send service worker message from uncontrolled client')
191
+ }
192
+
193
+ serviceWorker.postMessage(message)
194
+ }
195
+
196
+ /**
197
+ * A handler function responsible for providing a clickable element that prompts user for app installation.
198
+ * @callback qui.pwa.InstallElementHandler
199
+ * @returns {Promise<jQuery>} a promise that resolves to a clickable element that prompts for app installation upon
200
+ * click
201
+ */
202
+
203
+ /**
204
+ * A handler function called after the user responds to installation prompt.
205
+ * @callback qui.pwa.InstallResponseHandler
206
+ * @param {Boolean} accepted indicates whether user accepted installation or not
207
+ */
208
+
209
+ /**
210
+ * Configures the handler functions for the app installation.
211
+ *
212
+ * This must be called before {@link qui.init}.
213
+ *
214
+ * @alias qui.pwa.setInstallHandlers
215
+ * @param {qui.pwa.InstallElementHandler} elementHandler
216
+ * @param {?qui.pwa.InstallResponseHandler} [responseHandler]
217
+ */
218
+ export function setInstallHandlers(elementHandler, responseHandler = null) {
219
+ installElementHandler = elementHandler
220
+ installResponseHandler = responseHandler
221
+ }
222
+
223
+ /**
224
+ * Setup the web app manifest.
225
+ * @alias qui.pwa.setupManifest
226
+ * @param {String} [url] the URL where the manifest file lives; {@link qui.config.navigationBasePrefix} +
227
+ * `"/manifest.json"` will be used if not specified
228
+ * @param {String} [displayName] an optional display name to append to the URL as a query argument (must be handled by
229
+ * the template rendering engine on the server side); defaults to {@link qui.config.appDisplayName}
230
+ * @param {String} [displayShortName] an optional short display name to append to the URL as a query argument (must be
231
+ * handled by the template rendering engine on the server side)
232
+ * @param {String} [description] an optional description to append to the URL as a query argument (must be handled by
233
+ * the template rendering engine on the server side)
234
+ * @param {String} [version] an optional version to append to the URL as a query argument (must be handled by the
235
+ * template rendering engine on the server side)
236
+ * @param {String} [themeColor] an optional theme color to append to the URL as a query argument (must be handled by the
237
+ * template rendering engine on the server side); defaults to `@interactive-color`
238
+ * @param {String} [backgroundColor] an optional background color to append to the URL as a query argument (must be
239
+ * handled by the template rendering engine on the server side); defaults to `@background-color`
240
+ */
241
+ export function setupManifest({
242
+ url = `${Config.navigationBasePrefix}/${MANIFEST_FILE}`,
243
+ displayName = Config.appDisplayName,
244
+ displayShortName = null,
245
+ description = null,
246
+ version = null,
247
+ themeColor = Theme.getColor('@interactive-color'),
248
+ backgroundColor = Theme.getColor('@background-color')
249
+ } = {}) {
250
+ manifestParams = {
251
+ url,
252
+ displayName,
253
+ displayShortName,
254
+ description,
255
+ version,
256
+ themeColor,
257
+ backgroundColor
258
+ }
259
+
260
+ let manifest = Window.$document.find('link[rel=manifest]')
261
+ if (!manifest.length) {
262
+ throw new Error('Manifest link element not found')
263
+ }
264
+
265
+ url = appendBuildHash(url)
266
+
267
+ let parsedURL = URL.parse(url)
268
+ if (displayName != null) {
269
+ parsedURL.query['display_name'] = displayName
270
+ }
271
+ if (displayShortName != null) {
272
+ parsedURL.query['display_short_name'] = displayShortName
273
+ }
274
+ if (description != null) {
275
+ parsedURL.query['description'] = description
276
+ }
277
+ if (version != null) {
278
+ parsedURL.query['version'] = version
279
+ }
280
+ if (themeColor != null) {
281
+ parsedURL.query['theme_color'] = themeColor
282
+ }
283
+ if (backgroundColor != null) {
284
+ parsedURL.query['background_color'] = backgroundColor
285
+ }
286
+ url = parsedURL.toString()
287
+
288
+ manifest.attr('href', url)
289
+
290
+ logger.debug(`manifest setup with URL = "${url}"`)
291
+ }
292
+
293
+ export function init() {
294
+ Window.$window.on('beforeinstallprompt', function (e) {
295
+ logger.debug('received install prompt event')
296
+ if (installElementHandler) {
297
+ let promptEvent = e.originalEvent
298
+ promptEvent.preventDefault()
299
+
300
+ if (installPrompted) {
301
+ logger.debug('ignoring subsequent install prompt event')
302
+ return
303
+ }
304
+
305
+ installPrompted = true
306
+
307
+ /* Don't call the install handler before QUI & initial navigation are ready, as it will probably use UI
308
+ * elements that need to be initialized first; add an extra delay to allow the UI animations to settle */
309
+ Promise.all([QUI.whenReady, Navigation.whenInitialNavigationReady]).then(function () {
310
+ return PromiseUtils.later(1000)
311
+ }).then(function () {
312
+ logger.debug('calling install handler')
313
+ installElementHandler().then(function (element) {
314
+ element.on('click', function clickHandler() {
315
+ element.off('click', clickHandler) /* Don't prompt again */
316
+
317
+ promptEvent.prompt()
318
+ promptEvent.userChoice.then(function (choiceResult) {
319
+ if (choiceResult.outcome === 'accepted') {
320
+ logger.info('user accepted installation')
321
+ if (installResponseHandler) {
322
+ installResponseHandler(true)
323
+ }
324
+ }
325
+ else {
326
+ logger.info('user rejected installation')
327
+ if (installResponseHandler) {
328
+ installResponseHandler(false)
329
+ }
330
+ }
331
+ })
332
+ })
333
+ }).catch(() => {})
334
+ })
335
+ }
336
+ })
337
+ }