@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,70 @@
1
+
2
+ @import (reference) "../base";
3
+ @import (reference) "../theme";
4
+
5
+
6
+ div.qui-progress-disk-container {
7
+ position: relative;
8
+
9
+ & > svg {
10
+ transform: rotate(-90deg);
11
+ vertical-align: top;
12
+
13
+ & > ellipse.qui-progress-disk-ring {
14
+ opacity: 0.2;
15
+ fill: transparent;
16
+ }
17
+
18
+ & > path.qui-progress-disk-cursor {
19
+ fill: transparent;
20
+ }
21
+
22
+ }
23
+
24
+ & > div.qui-progress-disk-label {
25
+ position: absolute;
26
+ width: 100%;
27
+ top: 0;
28
+ left: 0;
29
+ text-align: center;
30
+ font-size: 90%;
31
+
32
+ &.small {
33
+ font-size: 50%;
34
+ }
35
+
36
+ }
37
+
38
+ &.no-value > svg {
39
+ transform: rotate(0deg);
40
+ animation: spin 1s linear infinite;
41
+ }
42
+
43
+ &.has-warning {
44
+
45
+ & > svg > ellipse.qui-progress-disk-ring,
46
+ & > svg > path.qui-progress-disk-cursor {
47
+ stroke: @warning-color !important;
48
+ }
49
+
50
+ }
51
+
52
+ &.has-error {
53
+
54
+ & > svg > ellipse.qui-progress-disk-ring,
55
+ & > svg > path.qui-progress-disk-cursor {
56
+ stroke: @error-color !important;
57
+ }
58
+
59
+ }
60
+
61
+ &.disabled {
62
+
63
+ & > svg > ellipse.qui-progress-disk-ring,
64
+ & > svg > path.qui-progress-disk-cursor {
65
+ stroke: @disabled-color !important;
66
+ }
67
+
68
+ }
69
+
70
+ }
@@ -0,0 +1,199 @@
1
+
2
+ @import (reference) "../base";
3
+ @import (reference) "../theme";
4
+ @import (reference) "common";
5
+
6
+
7
+ div.qui-slider {
8
+ padding: 0.5em 0.8em 0.8em 0.8em;
9
+ overflow: hidden;
10
+ .noselect;
11
+
12
+ & > div.qui-slider-labels {
13
+ position: relative;
14
+ white-space: nowrap;
15
+ height: 1.8em;
16
+ margin: 0 0.5em;
17
+
18
+ & > span.qui-slider-label {
19
+ width: 30%;
20
+ text-align: center;
21
+ position: absolute;
22
+ font-size: 0.85em;
23
+ opacity: 1;
24
+ transition: opacity @transition-duration ease,
25
+ color @transition-duration ease;
26
+
27
+ &.qui-slider-cursor-label {
28
+ color: @interactive-color;
29
+ }
30
+
31
+ &.overlapped {
32
+ opacity: 0;
33
+ }
34
+
35
+ }
36
+
37
+ &:NOT(.all-ticks-visible) {
38
+
39
+ & > span.qui-slider-cursor-label {
40
+ background: linear-gradient(to right,
41
+ transparent 0%, @background-color 25%,
42
+ @background-color 75%, transparent 100%);
43
+ }
44
+
45
+ }
46
+
47
+ &.all-ticks-visible {
48
+
49
+ & > span.qui-slider-label {
50
+ transition: none;
51
+ }
52
+
53
+ }
54
+
55
+ }
56
+
57
+ & > div.qui-slider-bar-container {
58
+ padding: 0 0.5em;
59
+
60
+ & > div.qui-slider-bar {
61
+ position: relative;
62
+ height: 0.5em;
63
+ border: 0.0625em solid @interactive-color;
64
+ border-radius: 0.2em;
65
+ transition: border-color @transition-duration linear;
66
+
67
+ & > div.qui-slider-cursor {
68
+ position: absolute;
69
+ left: 0;
70
+ top: -0.5em;
71
+ width: 1.5em;
72
+ height: 1.5em;
73
+ margin-left: -0.75em;
74
+ cursor: pointer;
75
+ border-width: 0;
76
+ border-radius: 0.2em;
77
+ background: @interactive-color;
78
+ transition+: background @transition-duration ease;
79
+ .qui-focusable-widget();
80
+ }
81
+
82
+ & > div.qui-slider-cursor.active {
83
+ background: @interactive-active-color;
84
+ }
85
+
86
+ }
87
+
88
+ }
89
+
90
+ &:FOCUS {
91
+
92
+ & > div.qui-slider-bar-container > div.qui-slider-bar > div.qui-slider-cursor {
93
+ .qui-focused-widget();
94
+ }
95
+
96
+ }
97
+
98
+ &.active {
99
+
100
+ & > div.qui-slider-labels {
101
+
102
+ & > span.qui-slider-label.overlapped:NOT(.completely-overlapped) {
103
+ opacity: 1;
104
+ }
105
+
106
+ }
107
+
108
+ }
109
+
110
+ &.has-warning {
111
+
112
+ & > div.qui-slider-bar-container > div.qui-slider-bar {
113
+ border-color: @warning-color;
114
+
115
+ & > div.qui-slider-cursor {
116
+ background: @warning-color;
117
+
118
+ &.active {
119
+ background: @warning-active-color;
120
+ }
121
+
122
+ }
123
+
124
+ }
125
+
126
+ &:FOCUS > div.qui-slider-bar-container > div.qui-slider-bar > div.qui-slider-cursor {
127
+ .qui-focused-widget-warning();
128
+ }
129
+
130
+ }
131
+
132
+ &.has-error {
133
+
134
+ & > div.qui-slider-bar-container > div.qui-slider-bar {
135
+ border-color: @error-color;
136
+
137
+ & > div.qui-slider-cursor {
138
+ background: @error-color;
139
+
140
+ &.active {
141
+ background: @error-active-color;
142
+ }
143
+
144
+ }
145
+
146
+ }
147
+
148
+ &:FOCUS > div.qui-slider-bar-container > div.qui-slider-bar > div.qui-slider-cursor {
149
+ .qui-focused-widget-error();
150
+ }
151
+
152
+ }
153
+
154
+ &.readonly {
155
+
156
+ & > div.qui-slider-bar-container > div.qui-slider-bar {
157
+ border-color: @readonly-color;
158
+
159
+ & > div.qui-slider-cursor {
160
+ background: @foreground-color;
161
+
162
+ &.active {
163
+ background: @foreground-color;
164
+ }
165
+
166
+ }
167
+
168
+ }
169
+
170
+ &:FOCUS > div.qui-slider-bar-container > div.qui-slider-bar > div.qui-slider-cursor {
171
+ .qui-focused-widget-readonly();
172
+ }
173
+
174
+ }
175
+
176
+ &.disabled {
177
+
178
+ & > div.qui-slider-labels > span.qui-slider-label {
179
+ color: @disabled-color;
180
+ }
181
+
182
+ & > div.qui-slider-bar-container > div.qui-slider-bar {
183
+ border-color: @disabled-color;
184
+
185
+ & > div.qui-slider-cursor {
186
+ background: @disabled-color;
187
+ cursor: default;
188
+
189
+ &.active {
190
+ background: @disabled-color;
191
+ }
192
+
193
+ }
194
+
195
+ }
196
+
197
+ }
198
+
199
+ }
@@ -0,0 +1,115 @@
1
+
2
+ @import (reference) "../base";
3
+ @import (reference) "../theme";
4
+ @import (reference) "common";
5
+
6
+
7
+ div.qui-updown-container {
8
+ white-space: nowrap;
9
+ overflow: hidden;
10
+ border-radius: 0.2em;
11
+ .qui-focusable-widget();
12
+
13
+ & > input.qui-updown-input {
14
+ width: 100%;
15
+ height: 1.75em;
16
+ box-sizing: border-box;
17
+ border-color: @interactive-color;
18
+ }
19
+
20
+ & > div.qui-updown-button {
21
+ display: inline-block;
22
+ width: 1.75em;
23
+ height: 1.75em;
24
+ line-height: 1.75em;
25
+ vertical-align: top;
26
+ text-align: center;
27
+ border-width: 0;
28
+
29
+ &.down {
30
+ margin-left: -3.5625em;
31
+ border-radius: 0;
32
+
33
+ &:AFTER {
34
+ content: '\2013';
35
+ }
36
+
37
+ }
38
+
39
+ &.up {
40
+ margin-left: 0.0625em;
41
+ border-radius: 0 0.2em 0.2em 0;
42
+
43
+ &:AFTER {
44
+ content: '+';
45
+ }
46
+
47
+ }
48
+
49
+ }
50
+
51
+ &:FOCUS {
52
+ .qui-focused-widget();
53
+ }
54
+
55
+ &.has-error {
56
+
57
+ & > input.qui-updown-input {
58
+ border-color: @error-color;
59
+ }
60
+
61
+ & > div.qui-updown-button {
62
+ background: @error-color;
63
+
64
+ &.active {
65
+ background: @error-active-color;
66
+ }
67
+ }
68
+
69
+ &:FOCUS {
70
+ .qui-focused-widget-error();
71
+ }
72
+
73
+ }
74
+
75
+ &.readonly {
76
+
77
+ & > input.qui-updown-input {
78
+ border-color: @readonly-color;
79
+ }
80
+
81
+ & > div.qui-updown-button {
82
+ background: @readonly-color;
83
+ cursor: default;
84
+
85
+ &.active {
86
+ background: @readonly-color;
87
+ }
88
+
89
+ }
90
+
91
+ &:FOCUS {
92
+ .qui-focused-widget-readonly();
93
+ }
94
+
95
+ }
96
+
97
+ &.disabled {
98
+
99
+ & > input.qui-updown-input {
100
+ border-color: @disabled-color;
101
+ }
102
+
103
+ & > div.qui-updown-button {
104
+ background: @disabled-color;
105
+ cursor: default;
106
+
107
+ &.active {
108
+ background: @disabled-color;
109
+ }
110
+
111
+ }
112
+
113
+ }
114
+
115
+ }
@@ -0,0 +1,36 @@
1
+
2
+ @import (reference) "../base";
3
+ @import (reference) "../theme";
4
+
5
+
6
+ .qui-active-indicator {
7
+ content: '';
8
+ display: block;
9
+ position: absolute;
10
+ width: 100%;
11
+ left: 0;
12
+ height: 0;
13
+ bottom: 0;
14
+ background: @interactive-color;
15
+ transition: height @transition-duration ease;
16
+ }
17
+
18
+ .qui-active-indicator-visible {
19
+ height: 0.25em;
20
+ }
21
+
22
+ div.qui-icon-text-caption-container {
23
+ display: flex;
24
+ align-items: center;
25
+ justify-content: center;
26
+ }
27
+
28
+ div.qui-icon-text-caption-icon {
29
+ width: 1.5em;
30
+ height: 1.5em;
31
+ top: -0.05em;
32
+ }
33
+
34
+ span.qui-icon-text-caption-text {
35
+ margin-left: 0.2em;
36
+ }
package/package.json ADDED
@@ -0,0 +1,47 @@
1
+ {
2
+ "name": "@qtoggle/qui",
3
+ "description": "A JavaScript UI library with batteries included.",
4
+ "version": "0.0.0",
5
+ "author": {
6
+ "name": "Calin Crisan",
7
+ "email": "ccrisan@gmail.com"
8
+ },
9
+ "keywords": [],
10
+ "license": "Apache-2.0",
11
+ "dependencies": {
12
+ "jquery": "*",
13
+ "jquery-mousewheel": "*",
14
+ "jquery-ui-dist": "*",
15
+ "js-logger": "*",
16
+ "pepjs": "*"
17
+ },
18
+ "devDependencies": {
19
+ "@babel/core": "^7",
20
+ "@babel/plugin-proposal-class-properties": "^7",
21
+ "@babel/preset-env": "^7",
22
+ "babel-eslint": "^10",
23
+ "babel-loader": "^8",
24
+ "css-loader": "^3",
25
+ "eslint": "^6",
26
+ "expose-loader": "^0",
27
+ "file-loader": "^6",
28
+ "glob": "^7",
29
+ "jsdoc": "^3",
30
+ "jsdoc-export-default-interop": "^0",
31
+ "jsdoc-typeof-plugin": "^1",
32
+ "less": "^3",
33
+ "less-loader": "^6",
34
+ "mini-css-extract-plugin": "^0",
35
+ "optimize-css-assets-webpack-plugin": "^5",
36
+ "string-replace-loader": "^2",
37
+ "terser-webpack-plugin": "^3",
38
+ "webpack": "^4",
39
+ "webpack-cli": "^3",
40
+ "webpack-fix-style-only-entries": "^0",
41
+ "webpack-inject-plugin": "^1",
42
+ "webpack-shell-plugin": "^0"
43
+ },
44
+ "scripts": {
45
+ "postinstall": "scripts/postinstall.sh"
46
+ }
47
+ }
package/pyproject.toml ADDED
@@ -0,0 +1,45 @@
1
+ [project]
2
+ name = "qui-server"
3
+ version = "0.0.0"
4
+ description = "A fully fledged qToggle implementation written in Python"
5
+ authors = [
6
+ {name = "Calin Crisan", email = "ccrisan@gmail.com"},
7
+ ]
8
+ requires-python = "==3.10.*"
9
+ readme = "README.md"
10
+ license = {text = "Apache 2.0"}
11
+ dependencies = [
12
+ "jinja2",
13
+ "tornado",
14
+ ]
15
+
16
+ [tool.setuptools.packages.find]
17
+ include = [
18
+ "qui*",
19
+ ]
20
+
21
+ [tool.setuptools.package-data]
22
+ qui = [
23
+ "templates/**",
24
+ ]
25
+
26
+ [tool.uv]
27
+ package = true
28
+
29
+ [tool.ruff]
30
+ line-length = 120
31
+ target-version = "py310"
32
+ lint.extend-select = ["I", "RUF022"]
33
+ lint.isort.lines-after-imports = 2
34
+ lint.isort.lines-between-types = 1
35
+ lint.isort.force-wrap-aliases = true
36
+
37
+ [dependency-groups]
38
+ dev = [
39
+ "ruff",
40
+ "pre-commit",
41
+ ]
42
+
43
+ [tool.mypy]
44
+ explicit_package_bases = true
45
+ ignore_missing_imports = true
@@ -0,0 +1,110 @@
1
+ import hashlib
2
+ import logging
3
+ import re
4
+ import secrets
5
+
6
+ from typing import Any
7
+
8
+ from . import settings
9
+
10
+
11
+ logger = logging.getLogger(__name__)
12
+
13
+
14
+ def configure(
15
+ *,
16
+ name: str,
17
+ display_name: str,
18
+ display_short_name: str | None = None,
19
+ description: str,
20
+ version: str,
21
+ debug: bool,
22
+ theme_color: str | None = None,
23
+ background_color: str | None = None,
24
+ frontend_dir: str | None = None,
25
+ frontend_url_prefix: str | None = None,
26
+ static_url: str | None = None,
27
+ package_name: str | None = None,
28
+ enable_pwa: bool | None = None,
29
+ extra_context: dict[str, Any] | None = None,
30
+ ) -> None:
31
+ """Configure QUI on the server side.
32
+
33
+ :param name: project name, normally lowercase, no spaces, e.g. ``my-project``
34
+ :param display_name: project display name, e.g. ``My Nice And Shiny Project``
35
+ :param display_short_name: optional project display short name, e.g. ``My Project``
36
+ :param description: project description, e.g. ``A project that does stuff``
37
+ :param version: project version
38
+ :param debug: indicates whether frontend runs in debug mode or not
39
+ :param theme_color: optionally overrides default theme (accent) color
40
+ :param background_color: overrides default background color
41
+ :param frontend_dir: overrides default frontend directory, relative to project root package (defaults to
42
+ ``frontend``)
43
+ :param frontend_url_prefix: overrides default frontend URL prefix (defaults to ``frontend``)
44
+ :param static_url: overrides default URL where static files are served (defaults to
45
+ ``{frontend_url_prefix}/static``)
46
+ :param package_name: sets the root project package name; by default, the ``name`` parameter is used after removing
47
+ non-alpha-numeric-or-underscore characters
48
+ :param enable_pwa: enables or disables PWA support (enabled by default)
49
+ :param extra_context: specifies extra context to be supplied when rendering templates
50
+ """
51
+
52
+ settings.name = name
53
+ settings.display_name = display_name
54
+ settings.description = description
55
+ settings.version = version
56
+ settings.debug = debug
57
+
58
+ if display_short_name is not None:
59
+ settings.display_short_name = display_short_name
60
+
61
+ if theme_color is not None:
62
+ settings.theme_color = theme_color
63
+
64
+ if background_color is not None:
65
+ settings.background_color = background_color
66
+
67
+ if frontend_dir is not None:
68
+ settings.frontend_dir = frontend_dir
69
+
70
+ if frontend_url_prefix is not None:
71
+ settings.frontend_url_prefix = frontend_url_prefix
72
+
73
+ if static_url is not None:
74
+ settings.static_url = static_url
75
+
76
+ if package_name is not None:
77
+ settings.package_name = package_name
78
+
79
+ if enable_pwa is not None:
80
+ settings.enable_pwa = enable_pwa
81
+
82
+ if extra_context is not None:
83
+ settings.extra_context = extra_context
84
+
85
+ # Static URL may (and normally does) depend on frontend URL prefix
86
+ settings.static_url = settings.static_url.format(frontend_url_prefix=settings.frontend_url_prefix)
87
+
88
+ # Project package defaults to app (project) name
89
+ if not settings.package_name:
90
+ settings.package_name = re.sub(r"[^a-zA-Z_0-9]", "", settings.name)
91
+
92
+ if settings.debug:
93
+ settings.build_hash = secrets.token_hex()[:16]
94
+
95
+ else:
96
+ settings.build_hash = hashlib.sha256(version.encode()).hexdigest()[:16]
97
+
98
+ logger.debug('using name = "%s"', settings.name)
99
+ logger.debug('using display_name = "%s"', settings.display_name)
100
+ logger.debug('using description = "%s"', settings.description)
101
+ logger.debug('using version = "%s"', settings.version)
102
+ logger.debug("using debug = %s", str(settings.debug).lower())
103
+ logger.debug('using theme_color = "%s"', settings.theme_color)
104
+ logger.debug('using background_color = "%s"', settings.background_color)
105
+ logger.debug('using frontend_dir = "%s"', settings.frontend_dir)
106
+ logger.debug('using frontend_url_prefix = "%s"', settings.frontend_url_prefix)
107
+ logger.debug('using static_url = "%s"', settings.static_url)
108
+ logger.debug('using package_name = "%s"', settings.package_name)
109
+ logger.debug("using enable_pwa = %s", str(settings.enable_pwa).lower())
110
+ logger.debug('using build_hash = "%s"', settings.build_hash)
@@ -0,0 +1 @@
1
+ BASE_PREFIX_HEADER = "X-Forwarded-Path"
@@ -0,0 +1,2 @@
1
+ class QUIException(Exception):
2
+ pass
@@ -0,0 +1,71 @@
1
+ import importlib
2
+ import logging
3
+ import os
4
+
5
+ from urllib.parse import quote_plus
6
+
7
+ from jinja2 import (
8
+ ChoiceLoader,
9
+ Environment,
10
+ FileSystemLoader,
11
+ PackageLoader,
12
+ select_autoescape,
13
+ )
14
+
15
+ from . import settings
16
+
17
+
18
+ _env: Environment | None = None
19
+
20
+ logger = logging.getLogger(__name__)
21
+
22
+
23
+ class NamespaceLoader(FileSystemLoader):
24
+ def __init__(
25
+ self,
26
+ namespace_name: str,
27
+ path: str | list[str] = "templates",
28
+ encoding: str = "utf-8",
29
+ followlinks: bool = False,
30
+ ) -> None:
31
+ if isinstance(path, str):
32
+ path = [path]
33
+
34
+ namespace = importlib.import_module(namespace_name)
35
+ namespace_path_list = list(namespace.__path__)
36
+ searchpath = []
37
+ for namespace_path in namespace_path_list:
38
+ searchpath += [os.path.join(namespace_path, p) for p in path]
39
+
40
+ super().__init__(searchpath=searchpath, encoding=encoding, followlinks=followlinks)
41
+
42
+
43
+ def urlquote(s: str | bytes) -> str | bytes:
44
+ if s:
45
+ return quote_plus(s)
46
+
47
+ return s
48
+
49
+
50
+ def get_env() -> Environment:
51
+ global _env
52
+
53
+ if _env is None:
54
+ logger.debug("creating Jinja2 template environment")
55
+
56
+ app_loader = NamespaceLoader(
57
+ settings.package_name,
58
+ [
59
+ f"{settings.frontend_dir}/templates",
60
+ f"{settings.frontend_dir}/dist/templates",
61
+ ],
62
+ )
63
+
64
+ qui_loader = PackageLoader("qui")
65
+
66
+ loader = ChoiceLoader([qui_loader, app_loader])
67
+
68
+ _env = Environment(loader=loader, autoescape=select_autoescape(), enable_async=True)
69
+ _env.filters["urlquote"] = urlquote
70
+
71
+ return _env