@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/utils/date.js
ADDED
|
@@ -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
|
+
}
|
package/js/utils/html.js
ADDED
|
@@ -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
|
+
'&': '&',
|
|
10
|
+
'<': '<',
|
|
11
|
+
'>': '>',
|
|
12
|
+
'"': '"',
|
|
13
|
+
"'": ''',
|
|
14
|
+
'/': '/',
|
|
15
|
+
'`': '`',
|
|
16
|
+
'=': '='
|
|
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
|
+
}
|
package/js/utils/misc.js
ADDED
|
@@ -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
|
+
}
|