@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,300 @@
1
+ /**
2
+ * @namespace qui.utils.date
3
+ */
4
+
5
+ /* eslint-disable no-multi-spaces */
6
+
7
+ import {gettext} from '$qui/base/i18n.js'
8
+
9
+
10
+ const MONTH_NAMES = [
11
+ {longName: 'January', shortName: 'Jan', longNameTrans: gettext('January'), shortNameTrans: gettext('Jan')},
12
+ {longName: 'February', shortName: 'Feb', longNameTrans: gettext('February'), shortNameTrans: gettext('Feb')},
13
+ {longName: 'March', shortName: 'Mar', longNameTrans: gettext('March'), shortNameTrans: gettext('Mar')},
14
+ {longName: 'April', shortName: 'Apr', longNameTrans: gettext('April'), shortNameTrans: gettext('Apr')},
15
+ {longName: 'May', shortName: 'May', longNameTrans: gettext('May'), shortNameTrans: gettext('May')},
16
+ {longName: 'June', shortName: 'Jun', longNameTrans: gettext('June'), shortNameTrans: gettext('Jun')},
17
+ {longName: 'July', shortName: 'Jul', longNameTrans: gettext('July'), shortNameTrans: gettext('Jul')},
18
+ {longName: 'August', shortName: 'Aug', longNameTrans: gettext('August'), shortNameTrans: gettext('Aug')},
19
+ {longName: 'September', shortName: 'Sep', longNameTrans: gettext('September'), shortNameTrans: gettext('Sep')},
20
+ {longName: 'October', shortName: 'Oct', longNameTrans: gettext('October'), shortNameTrans: gettext('Oct')},
21
+ {longName: 'November', shortName: 'Nov', longNameTrans: gettext('November'), shortNameTrans: gettext('Nov')},
22
+ {longName: 'December', shortName: 'Dec', longNameTrans: gettext('December'), shortNameTrans: gettext('Dec')}
23
+ ]
24
+
25
+ const WEEK_DAY_NAMES = [
26
+ {longName: 'Sunday', shortName: 'Sun', longNameTrans: gettext('Sunday'), shortNameTrans: gettext('Sun')},
27
+ {longName: 'Monday', shortName: 'Mon', longNameTrans: gettext('Monday'), shortNameTrans: gettext('Mon')},
28
+ {longName: 'Tuesday', shortName: 'Tue', longNameTrans: gettext('Tuesday'), shortNameTrans: gettext('Tue')},
29
+ {longName: 'Wednesday', shortName: 'Wed', longNameTrans: gettext('Wednesday'), shortNameTrans: gettext('Wed')},
30
+ {longName: 'Thursday', shortName: 'Thu', longNameTrans: gettext('Thursday'), shortNameTrans: gettext('Thu')},
31
+ {longName: 'Friday', shortName: 'Fri', longNameTrans: gettext('Friday'), shortNameTrans: gettext('Fri')},
32
+ {longName: 'Saturday', shortName: 'Sat', longNameTrans: gettext('Saturday'), shortNameTrans: gettext('Sat')}
33
+ ]
34
+
35
+ const MILLISECONDS_IN_MINUTE = 60 * 1000
36
+ const MILLISECONDS_IN_HOUR = 60 * MILLISECONDS_IN_MINUTE
37
+ const MILLISECONDS_IN_DAY = 24 * MILLISECONDS_IN_HOUR
38
+
39
+
40
+ /**
41
+ * Transform a date into a string, according to the given format. Recognized placeholders:
42
+ * * `"%a"` - short week day name (e.g. `"Mon"`)
43
+ * * `"%A"` - long week day name (e.g. `"Monday"`)
44
+ * * `"%w"` - day of week, from `"0"` (Sunday) to `"6"` (Saturday),
45
+ * * `"%u"` - day of week, from `"1"` (Monday) to `"7"` (Sunday),
46
+ * * `"%d"` - zero padded day of month, from `"00"` to `"31'`
47
+ * * `"%b"` - short month name (e.g. `"Apr"`)
48
+ * * `"%B"` - long month name (e.g. `"April"`)
49
+ * * `"%m"` - zero padded month number, from `"01"` to `"12"`
50
+ * * `"%y"` - zero padded short year (e.g. `"97"` or `"03"`)
51
+ * * `"%Y"` - zero padded long year (e.g. `"1997"` or `"2003"`)
52
+ * * `"%H"` - zero padded hour, from `"00"` to `"23"`
53
+ * * `"%I"` - zero padded hour, from `"00"` to `"11"`
54
+ * * `"%p"` - `"am"` or `"pm"`
55
+ * * `"%M"` - zero padded minutes, from `"00"` to `"59"`
56
+ * * `"%S"` - zero padded seconds, from `"00"` to `"59"`
57
+ * * `"%f"` - zero padded milliseconds, from `"000"` to `"999"`
58
+ *
59
+ * Use a `-` character in front of a format specifier to remove zero padding (e.g. "%-d"`).
60
+ *
61
+ * @alias qui.utils.date.formatPercent
62
+ * @param {Date} date the date to format
63
+ * @param {String} format the format to be used
64
+ * @param {Boolean} [trans] whether to translate week day and month names; defaults to `true`
65
+ * @returns {String} the formatted date
66
+ */
67
+ export function formatPercent(date, format, trans = true) {
68
+ if (!date || isNaN(date.getTime())) {
69
+ return ''
70
+ }
71
+
72
+ let weekDay = date.getDay()
73
+ let monthDay = date.getDate()
74
+ let month = date.getMonth()
75
+ let year = date.getFullYear()
76
+ let hours = date.getHours()
77
+ let minutes = date.getMinutes()
78
+ let seconds = date.getSeconds()
79
+ let milliseconds = date.getMilliseconds()
80
+
81
+ let hours12
82
+ if (hours === 0) {
83
+ hours12 = 12
84
+ }
85
+ else if (hours < 13) {
86
+ hours12 = hours
87
+ }
88
+ else { /* Hours between 13 and 23 */
89
+ hours12 = hours % 12
90
+ }
91
+
92
+ let ampm = hours < 12 ? 'AM' : 'PM'
93
+ let weekDayName = WEEK_DAY_NAMES[weekDay]
94
+ let weekDayNameShort = trans ? weekDayName.shortNameTrans : weekDayName.shortName
95
+ let weekDayNameLong = trans ? weekDayName.longNameTrans : weekDayName.longName
96
+ let monthName = MONTH_NAMES[month]
97
+ let monthNameShort = trans ? monthName.shortNameTrans : monthName.shortName
98
+ let monthNameLong = trans ? monthName.longNameTrans : monthName.longName
99
+
100
+ let result = ''
101
+ let percent = false
102
+ let noLeading = false
103
+ for (let i = 0; i < format.length; i++) {
104
+ let c = format.charAt(i)
105
+ if (percent) {
106
+ if (c === '-') {
107
+ noLeading = true
108
+ }
109
+ else {
110
+ switch (c) {
111
+ case 'a':
112
+ result += weekDayNameShort
113
+ break
114
+
115
+ case 'A':
116
+ result += weekDayNameLong
117
+ break
118
+
119
+ case 'w':
120
+ result += weekDay.toString()
121
+ break
122
+
123
+ case 'u':
124
+ result += (weekDay || 7).toString()
125
+ break
126
+
127
+ case 'd':
128
+ result += monthDay.toString().padStart(noLeading ? 0 : 2, '0')
129
+ break
130
+
131
+ case 'b':
132
+ result += monthNameShort
133
+ break
134
+
135
+ case 'B':
136
+ result += monthNameLong
137
+ break
138
+
139
+ case 'm':
140
+ result += (month + 1).toString().padStart(noLeading ? 0 : 2, '0')
141
+ break
142
+
143
+ case 'y':
144
+ result += (year % 100).toString().padStart(noLeading ? 0 : 2, '0')
145
+ break
146
+
147
+ case 'Y':
148
+ result += year.toString().padStart(noLeading ? 0 : 4, '0')
149
+ break
150
+
151
+ case 'H':
152
+ result += hours.toString().padStart(noLeading ? 0 : 2, '0')
153
+ break
154
+
155
+ case 'I':
156
+ result += hours12.toString().padStart(noLeading ? 0 : 2, '0')
157
+ break
158
+
159
+ case 'p':
160
+ result += ampm
161
+ break
162
+
163
+ case 'M':
164
+ result += minutes.toString().padStart(noLeading ? 0 : 2, '0')
165
+ break
166
+
167
+ case 'S':
168
+ result += seconds.toString().padStart(noLeading ? 0 : 2, '0')
169
+ break
170
+
171
+ case 'f':
172
+ result += milliseconds.toString().padStart(noLeading ? 0 : 3, '0')
173
+ break
174
+
175
+ case '%':
176
+ result += '%'
177
+ break
178
+ }
179
+
180
+ noLeading = false
181
+ percent = false
182
+ }
183
+ }
184
+ else if (c === '%') {
185
+ percent = true
186
+ }
187
+ else {
188
+ result += c
189
+ }
190
+ }
191
+
192
+ return result
193
+ }
194
+
195
+ /**
196
+ * Transform a time duration into a string, according to the given format. Recognized placeholders:
197
+ * * `"%d"` - number of days
198
+ * * `"%H"` - number of hours, from `"00"` to `"23"`
199
+ * * `"%M"` - number of minutes, from `"00"` to `"59"`
200
+ * * `"%S"` - number of seconds, from `"00"` to `"59"`
201
+ * * `"%f"` - number of milliseconds, from `"000"` to `"999"`
202
+ *
203
+ * Use a `-` character in front of a format specifier to remove zero padding (e.g. "%-H"`).
204
+ *
205
+ * @alias qui.utils.date.formatDurationPercent
206
+ * @param {Number} duration duration, in milliseconds
207
+ * @param {String} format the format to be used
208
+ * @returns {String} the formatted duration
209
+ */
210
+ export function formatDurationPercent(duration, format) {
211
+ let negative = false
212
+ if (duration < 0) {
213
+ duration = -duration
214
+ negative = true
215
+ }
216
+
217
+ let days = Math.floor(duration / MILLISECONDS_IN_DAY)
218
+ duration = duration % MILLISECONDS_IN_DAY
219
+
220
+ let hours = Math.floor(duration / MILLISECONDS_IN_HOUR)
221
+ duration = duration % MILLISECONDS_IN_HOUR
222
+
223
+ let minutes = Math.floor(duration / MILLISECONDS_IN_MINUTE)
224
+ duration = duration % MILLISECONDS_IN_MINUTE
225
+
226
+ let seconds = Math.floor(duration / 1000)
227
+ let milliseconds = duration % 1000
228
+
229
+ let result = ''
230
+ let percent = false
231
+ let noLeading = false
232
+ for (let i = 0; i < format.length; i++) {
233
+ let c = format.charAt(i)
234
+ if (percent) {
235
+ if (c === '-') {
236
+ noLeading = true
237
+ }
238
+ else {
239
+ switch (c) {
240
+ case 'd':
241
+ result += days.toString()
242
+ break
243
+
244
+ case 'H':
245
+ result += hours.toString().padStart(noLeading ? 0 : 2, '0')
246
+ break
247
+
248
+ case 'M':
249
+ result += minutes.toString().padStart(noLeading ? 0 : 2, '0')
250
+ break
251
+
252
+ case 'S':
253
+ result += seconds.toString().padStart(noLeading ? 0 : 2, '0')
254
+ break
255
+
256
+ case 'f':
257
+ result += milliseconds.toString().padStart(noLeading ? 0 : 3, '0')
258
+ break
259
+
260
+ case '%':
261
+ result += '%'
262
+ break
263
+ }
264
+
265
+ noLeading = false
266
+ percent = false
267
+ }
268
+ }
269
+ else if (c === '%') {
270
+ percent = true
271
+ }
272
+ else {
273
+ result += c
274
+ }
275
+ }
276
+
277
+ if (negative) {
278
+ result = `-${result}`
279
+ }
280
+
281
+ return result
282
+ }
283
+
284
+ /**
285
+ * Transform a given date object into its UTC equivalent.
286
+ * @alias qui.utils.date.toUTC
287
+ * @param {Date} date
288
+ * @returns {Date}
289
+ */
290
+ export function toUTC(date) {
291
+ return new Date(
292
+ date.getUTCFullYear(),
293
+ date.getUTCMonth(),
294
+ date.getUTCDate(),
295
+ date.getUTCHours(),
296
+ date.getUTCMinutes(),
297
+ date.getUTCSeconds(),
298
+ date.getUTCMilliseconds()
299
+ )
300
+ }
@@ -0,0 +1,27 @@
1
+ /**
2
+ * @namespace qui.utils.files
3
+ */
4
+
5
+
6
+ /**
7
+ * Trigger a browser download of a client-side generated file.
8
+ * @alias qui.utils.files.clientSideDownload
9
+ * @param {String} filename
10
+ * @param {String} contentType
11
+ * @param {String|Uint8Array|ArrayBuffer} content
12
+ */
13
+ export function clientSideDownload(filename, contentType, content) {
14
+ if (content instanceof ArrayBuffer) {
15
+ content = new Uint8Array(content)
16
+ }
17
+ if (content instanceof Uint8Array) {
18
+ content = new TextDecoder('utf-8').decode(content)
19
+ }
20
+
21
+ let blob = new window.Blob([content], {type: contentType})
22
+ let dataURL = URL.createObjectURL(blob)
23
+ let a = document.createElement('a')
24
+ a.href = dataURL
25
+ a.setAttribute('download', filename)
26
+ a.click()
27
+ }
@@ -0,0 +1,165 @@
1
+ /**
2
+ * @namespace qui.utils.gestures
3
+ */
4
+
5
+ import * as Window from '$qui/window.js'
6
+
7
+
8
+ /**
9
+ * Drag Move Callback Function.
10
+ * @callback qui.utils.gestures.DragMoveCallback
11
+ * @param {Number} elemX the new element x coordinate, relative to page
12
+ * @param {Number} elemY the new element y coordinate, relative to page
13
+ * @param {Number} deltaX the x coordinate variation, relative to initial drag point
14
+ * @param {Number} deltaY the y coordinate variation, relative to initial drag point
15
+ * @param {Number} pageX the x coordinate, relative to page
16
+ * @param {Number} pageY the y coordinate, relative to page
17
+ */
18
+
19
+ /**
20
+ * Drag Begin Callback Function.
21
+ * @callback qui.utils.gestures.DragBeginCallback
22
+ * @param {Number} elemX the initial element x coordinate, relative to page
23
+ * @param {Number} elemY the initial element y coordinate, relative to page
24
+ * @param {Number} pageX the x coordinate, relative to page
25
+ * @param {Number} pageY the y coordinate, relative to page
26
+ * @returns {Boolean} `false` to prevent dragging
27
+ */
28
+
29
+ /**
30
+ * Drag End Callback Function.
31
+ * @callback qui.utils.gestures.DragEndCallback
32
+ * @param {Number} elemX the final element x coordinate, relative to page
33
+ * @param {Number} elemY the final element y coordinate, relative to page
34
+ * @param {Number} deltaX the final x coordinate variation, relative to initial drag point
35
+ * @param {Number} deltaY the final y coordinate variation, relative to initial drag point
36
+ * @param {Number} pageX the x coordinate inside, relative to page
37
+ * @param {Number} pageY the y coordinate inside, relative to page
38
+ */
39
+
40
+ /**
41
+ * Setup an HTML element for dragging.
42
+ * @alias qui.utils.gestures.enableDragging
43
+ * @param {jQuery} element the dragged element
44
+ * @param {qui.utils.gestures.DragMoveCallback} onMove
45
+ * @param {qui.utils.gestures.DragBeginCallback} onBegin
46
+ * @param {qui.utils.gestures.DragEndCallback} onEnd
47
+ * @param {?String} [direction] indicates dragging direction: `"x"`, `"y"` or `null` for both; defaults to `null`
48
+ */
49
+ export function enableDragging(element, onMove, onBegin, onEnd, direction) {
50
+ let beginPageX = 0, beginPageY = 0
51
+ let beginElemX = 0, beginElemY = 0
52
+
53
+ function pointerDown(e) {
54
+ let elemOffset = element.offset()
55
+ beginElemX = elemOffset.left
56
+ beginElemY = elemOffset.top
57
+
58
+ let scalingFactor = Window.getScalingFactor()
59
+ e.pageX /= scalingFactor
60
+ e.pageY /= scalingFactor
61
+
62
+ beginPageX = e.pageX
63
+ beginPageY = e.pageY
64
+
65
+ if (onBegin) {
66
+ if (onBegin(beginElemX, beginElemY, e.pageX, e.pageY) === false) {
67
+ return
68
+ }
69
+ }
70
+
71
+ Window.$body.on('pointermove', pointerMove)
72
+ .on('pointerup pointercancel pointerleave', pointerUp)
73
+ }
74
+
75
+ function pointerUp(e) {
76
+ Window.$body.off('pointermove', pointerMove)
77
+ .off('pointerup pointercancel pointerleave', pointerUp)
78
+
79
+ let scalingFactor = Window.getScalingFactor()
80
+ e.pageX /= scalingFactor
81
+ e.pageY /= scalingFactor
82
+
83
+ if (direction === 'x') { /* Constrain moving to horizontal axis */
84
+ e.pageY = beginPageY
85
+ }
86
+ if (direction === 'y') { /* Constrain moving to vertical axis */
87
+ e.pageX = beginPageX
88
+ }
89
+
90
+ let deltaX = e.pageX - beginPageX
91
+ let deltaY = e.pageY - beginPageY
92
+
93
+ let elemX = beginElemX + deltaX
94
+ let elemY = beginElemY + deltaY
95
+
96
+ beginPageX = beginPageY = 0
97
+ beginElemX = beginElemY = 0
98
+
99
+ if (onEnd) {
100
+ onEnd(elemX, elemY, deltaX, deltaY, e.pageX, e.pageY)
101
+ }
102
+ }
103
+
104
+ function pointerMove(e) {
105
+ let scalingFactor = Window.getScalingFactor()
106
+ e.pageX /= scalingFactor
107
+ e.pageY /= scalingFactor
108
+
109
+ if (direction === 'x') { /* Constrain moving to horizontal axis */
110
+ e.pageY = beginPageY
111
+ }
112
+ if (direction === 'y') { /* Constrain moving to vertical axis */
113
+ e.pageX = beginPageX
114
+ }
115
+
116
+ let deltaX = e.pageX - beginPageX
117
+ let deltaY = e.pageY - beginPageY
118
+
119
+ let elemX = beginElemX + deltaX
120
+ let elemY = beginElemY + deltaY
121
+
122
+ if (onMove) {
123
+ onMove(elemX, elemY, deltaX, deltaY, e.pageX, e.pageY)
124
+ }
125
+
126
+ e.preventDefault()
127
+ }
128
+
129
+ element.data('qui.utils.gestures.dragging', {
130
+ pointerDown: pointerDown,
131
+ pointerUp: pointerUp,
132
+ pointerMove: pointerMove
133
+ })
134
+
135
+ let touchAction = 'none'
136
+ if (direction === 'x') {
137
+ touchAction = 'pan-y'
138
+ }
139
+ else if (direction === 'y') {
140
+ touchAction = 'pan-x'
141
+ }
142
+
143
+ element.css('touch-action', touchAction)
144
+ element.attr('touch-action', touchAction) /* Required for pep.js (on iOS) */
145
+ element.on('pointerdown', pointerDown)
146
+ }
147
+
148
+ /**
149
+ * Disable previously configured dragging support on an HTML element.
150
+ * @alias qui.utils.gestures.disableDragging
151
+ * @param {jQuery} element the dragged element
152
+ */
153
+ export function disableDragging(element) {
154
+ let draggingData = element.data('qui.utils.gestures.dragging')
155
+ if (!draggingData) {
156
+ return
157
+ }
158
+
159
+ Window.$body.off('pointermove', draggingData.pointerMove)
160
+ .off('pointerup pointercancel pointerleave', draggingData.pointerUp)
161
+
162
+ element.css('touch-action', '')
163
+ element.attr('touch-action', '') /* Required for pep.js (on iOS) */
164
+ element.off('pointerdown', draggingData.pointerDown)
165
+ }
@@ -0,0 +1,76 @@
1
+ /**
2
+ * @namespace qui.utils.html
3
+ */
4
+
5
+ import $ from '$qui/lib/jquery.module.js'
6
+
7
+
8
+ const HTML_ENTITIES = {
9
+ '&': '&amp;',
10
+ '<': '&lt;',
11
+ '>': '&gt;',
12
+ '"': '&quot;',
13
+ "'": '&#39;',
14
+ '/': '&#x2F;',
15
+ '`': '&#x60;',
16
+ '=': '&#x3D;'
17
+ }
18
+
19
+ const HTML_CHARS_TO_ESCAPE_RE = new RegExp('[&<>"\'`=/]', 'g')
20
+
21
+
22
+ /**
23
+ * Remove any HTML tags from a string, keeping only plain text.
24
+ * @alias qui.utils.html.plainText
25
+ * @param {String} s
26
+ * @returns {String}
27
+ */
28
+ export function plainText(s) {
29
+ let span = document.createElement('span')
30
+ span.innerHTML = s
31
+
32
+ return span.innerText
33
+ }
34
+
35
+ /**
36
+ * Escape HTML content, using HTML entities.
37
+ * @alias qui.utils.html.escape
38
+ * @param {String} s
39
+ * @returns {String}
40
+ */
41
+ export function escape(s) {
42
+ return s.replace(HTML_CHARS_TO_ESCAPE_RE, function (s) {
43
+ return HTML_ENTITIES[s]
44
+ })
45
+ }
46
+
47
+ /**
48
+ * Replace percent-formatted placeholders in a string with given values. Compared to
49
+ * {@link qui.utils.string.formatPercent}, this function works with `jQuery` elements instead of primitive values.
50
+ * @alias qui.utils.html.formatPercent
51
+ * @param {String} text the text template with placeholders given as `"%(name)s"`
52
+ * @param {String} wrapElement the HTML element type to use to wrap the result (e.g. `"span"`)
53
+ * @param {Object<String,jQuery>} values the values used to replace the placeholders
54
+ * @returns {jQuery}
55
+ */
56
+ export function formatPercent(text, wrapElement, values) {
57
+ let result = $(`<${wrapElement}></${wrapElement}>`)
58
+
59
+ let re = /%\(([a-zA-Z0-9_-]+)\)s/
60
+ let match
61
+ while ((match = re.exec(text))) {
62
+ let firstPart = text.substring(0, match.index)
63
+ text = text.substring(match.index + match[0].length)
64
+ let name = match[1]
65
+ let value = values[name]
66
+
67
+ result.append(firstPart)
68
+ result.append(value || match[0])
69
+ }
70
+
71
+ if (text.length) {
72
+ result.append(text)
73
+ }
74
+
75
+ return result
76
+ }
@@ -0,0 +1,81 @@
1
+ /**
2
+ * @namespace qui.utils.misc
3
+ */
4
+
5
+ import Config from '$qui/config.js'
6
+ import * as ObjectUtils from '$qui/utils/object.js'
7
+
8
+ import URL from './url.js'
9
+
10
+
11
+ /**
12
+ * Run a function as soon as possible, in the next main loop iteration.
13
+ * @alias qui.utils.misc.asap
14
+ * @param {Function} func function to run
15
+ * @returns {Number} a timeout handle
16
+ */
17
+ export function asap(func) {
18
+ return setTimeout(function () {
19
+ func()
20
+ }, 20)
21
+ }
22
+
23
+ /**
24
+ * Ensure that a URL has a query argument named `h`, representing the build hash.
25
+ * @alias qui.utils.misc.appendBuildHash
26
+ * @param {String} strURL
27
+ * @returns {String}
28
+ */
29
+ export function appendBuildHash(strURL) {
30
+ let url = URL.parse(strURL)
31
+ if (!Config.buildHash) {
32
+ return url.toString()
33
+ }
34
+
35
+ url = url.alter({query: ObjectUtils.combine(url.query, {h: Config.buildHash})})
36
+ return url.toString()
37
+ }
38
+
39
+ /**
40
+ * Tell if argument is a function and not a class.
41
+ * @param {*} f
42
+ * @returns {Boolean}
43
+ */
44
+ export function isFunction(f) {
45
+ if (typeof f !== 'function') {
46
+ return false
47
+ }
48
+
49
+ let s
50
+ try {
51
+ s = Function.prototype.toString.call(f)
52
+ }
53
+ catch {
54
+ /* Definitely not a function */
55
+ return false
56
+ }
57
+
58
+ return !/^class\s/.test(s)
59
+ }
60
+
61
+ /**
62
+ * Tell if argument is a class.
63
+ * @param {*} c
64
+ * @returns {Boolean}
65
+ */
66
+ export function isClass(c) {
67
+ if (typeof c !== 'function') {
68
+ return false
69
+ }
70
+
71
+ let s
72
+ try {
73
+ s = Function.prototype.toString.call(c)
74
+ }
75
+ catch {
76
+ /* Definitely not a class */
77
+ return false
78
+ }
79
+
80
+ return /^class\s/.test(s)
81
+ }