underpost 2.8.85 → 2.8.87

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 (70) hide show
  1. package/.env.development +7 -2
  2. package/.env.production +7 -2
  3. package/.env.test +7 -2
  4. package/.github/workflows/pwa-microservices-template-page.cd.yml +1 -1
  5. package/.github/workflows/release.cd.yml +37 -0
  6. package/README.md +7 -24
  7. package/bin/build.js +1 -0
  8. package/bin/db.js +1 -3
  9. package/bin/deploy.js +43 -368
  10. package/bin/file.js +16 -3
  11. package/bin/util.js +1 -56
  12. package/cli.md +46 -21
  13. package/conf.js +3 -3
  14. package/manifests/deployment/{dd-template-development → dd-default-development}/deployment.yaml +16 -16
  15. package/manifests/deployment/{dd-template-development → dd-default-development}/proxy.yaml +3 -3
  16. package/manifests/deployment/mongo-express/deployment.yaml +12 -12
  17. package/manifests/grafana/deployment.yaml +57 -0
  18. package/manifests/grafana/kustomization.yaml +7 -0
  19. package/manifests/grafana/pvc.yaml +12 -0
  20. package/manifests/grafana/service.yaml +14 -0
  21. package/manifests/maas/nvim.sh +91 -0
  22. package/manifests/maas/ssh-cluster-info.sh +14 -0
  23. package/manifests/prometheus/deployment.yaml +82 -0
  24. package/package.json +3 -12
  25. package/src/api/file/file.service.js +28 -8
  26. package/src/api/user/user.router.js +31 -5
  27. package/src/api/user/user.service.js +11 -38
  28. package/src/cli/cluster.js +45 -25
  29. package/src/cli/cron.js +12 -45
  30. package/src/cli/db.js +149 -19
  31. package/src/cli/deploy.js +41 -110
  32. package/src/cli/fs.js +1 -0
  33. package/src/cli/index.js +24 -7
  34. package/src/cli/monitor.js +1 -4
  35. package/src/cli/repository.js +15 -6
  36. package/src/cli/run.js +94 -16
  37. package/src/client/Default.index.js +0 -2
  38. package/src/client/components/core/Account.js +6 -2
  39. package/src/client/components/core/Content.js +11 -7
  40. package/src/client/components/core/Css.js +5 -1
  41. package/src/client/components/core/CssCore.js +12 -0
  42. package/src/client/components/core/FullScreen.js +19 -28
  43. package/src/client/components/core/Input.js +7 -1
  44. package/src/client/components/core/LogIn.js +3 -0
  45. package/src/client/components/core/LogOut.js +1 -1
  46. package/src/client/components/core/Modal.js +32 -43
  47. package/src/client/components/core/ObjectLayerEngine.js +229 -4
  48. package/src/client/components/core/ObjectLayerEngineModal.js +441 -0
  49. package/src/client/components/core/Recover.js +5 -2
  50. package/src/client/components/core/Scroll.js +65 -120
  51. package/src/client/components/core/SignUp.js +1 -0
  52. package/src/client/components/core/ToggleSwitch.js +15 -1
  53. package/src/client/components/core/VanillaJs.js +48 -2
  54. package/src/client/components/default/MenuDefault.js +2 -2
  55. package/src/client/components/default/RoutesDefault.js +3 -3
  56. package/src/client/public/default/assets/mailer/api-user-default-avatar.png +0 -0
  57. package/src/index.js +1 -1
  58. package/src/mailer/MailerProvider.js +37 -0
  59. package/src/server/client-build-docs.js +1 -1
  60. package/src/server/client-build-live.js +1 -1
  61. package/src/server/client-build.js +4 -12
  62. package/src/server/client-dev-server.js +1 -1
  63. package/src/server/client-icons.js +6 -78
  64. package/src/server/conf.js +83 -408
  65. package/src/server/proxy.js +2 -3
  66. package/src/server/runtime.js +1 -2
  67. package/src/server/start.js +5 -5
  68. package/test/api.test.js +3 -2
  69. package/docker-compose.yml +0 -67
  70. package/prometheus.yml +0 -36
@@ -0,0 +1,441 @@
1
+ import { CoreService } from '../../services/core/core.service.js';
2
+ import { BtnIcon } from './BtnIcon.js';
3
+ import { borderChar, dynamicCol } from './Css.js';
4
+ import { DropDown } from './DropDown.js';
5
+ import { EventsUI } from './EventsUI.js';
6
+ import { Translate } from './Translate.js';
7
+ import { getProxyPath, s, append, hexToRgbA } from './VanillaJs.js';
8
+ import { s4 } from './CommonJs.js';
9
+ import { Input } from './Input.js';
10
+ import { ToggleSwitch } from './ToggleSwitch.js';
11
+ import { ObjectLayerService } from '../../services/object-layer/object-layer.service.js';
12
+
13
+ const ObjectLayerEngineModal = {
14
+ templates: [
15
+ {
16
+ label: 'empty',
17
+ id: 'empty',
18
+ data: [],
19
+ },
20
+ ],
21
+ RenderTemplate: (colorTemplate) => {
22
+ const ole = s('object-layer-engine');
23
+ if (!ole) {
24
+ return;
25
+ }
26
+
27
+ if (colorTemplate.length === 0) {
28
+ ole.clear();
29
+ return;
30
+ }
31
+
32
+ const matrix = colorTemplate.map((row) => row.map((hex) => [...hexToRgbA(hex), 255]));
33
+ ole.loadMatrix(matrix);
34
+ },
35
+ ObjectLayerData: {},
36
+ Render: async (options = { idModal: '' }) => {
37
+ await import(`${getProxyPath()}components/core/ObjectLayerEngine.js`);
38
+ // await import(`${getProxyPath()}components/core/WebComponent.js`);
39
+ const directionCodes = ['08', '18', '02', '12', '04', '14', '06', '16'];
40
+ const itemTypes = ['skin', 'weapon', 'armor', 'artifact', 'floor'];
41
+ const statTypes = ['effect', 'resistance', 'agility', 'range', 'intelligence', 'utility'];
42
+ let selectItemType = itemTypes[0];
43
+ let itemActivable = false;
44
+ let renderIsStateless = false;
45
+ let renderFrameDuration = 100;
46
+
47
+ for (const url of [
48
+ `${getProxyPath()}assets/templates/item-skin-08.json`,
49
+ `${getProxyPath()}assets/templates/item-skin-06.json`,
50
+ ]) {
51
+ const id = url.split('/').pop().replace('.json', '');
52
+ ObjectLayerEngineModal.templates.push({
53
+ label: id,
54
+ id,
55
+ data: JSON.parse(await CoreService.getRaw({ url })).color,
56
+ });
57
+ }
58
+
59
+ const cells = 26;
60
+ const pixelSize = parseInt(320 / cells);
61
+ const idSectionA = 'template-section-a';
62
+ const idSectionB = 'template-section-b';
63
+
64
+ let directionsCodeBarRender = '';
65
+
66
+ for (const directionCode of directionCodes) {
67
+ setTimeout(() => {
68
+ EventsUI.onClick(`.direction-code-bar-frames-btn-${directionCode}`, async () => {
69
+ const image = await s('object-layer-engine').toBlob();
70
+ const json = s('object-layer-engine').exportMatrixJSON();
71
+ const id = `frame-capture-${s4()}-${s4()}`;
72
+
73
+ if (!ObjectLayerEngineModal.ObjectLayerData[directionCode])
74
+ ObjectLayerEngineModal.ObjectLayerData[directionCode] = [];
75
+ ObjectLayerEngineModal.ObjectLayerData[directionCode].push({ id, image, json });
76
+
77
+ append(
78
+ `.frames-${directionCode}`,
79
+ html`
80
+ <div class="in fll ${id}">
81
+ <img class="in fll direction-code-bar-frames-img" src="${URL.createObjectURL(image)}" />
82
+ ${await BtnIcon.Render({
83
+ label: html`<i class="fa-solid fa-trash"></i>`,
84
+ class: `abs direction-code-bar-trash-btn direction-code-bar-trash-btn-${id}`,
85
+ })}
86
+ </div>
87
+ `,
88
+ );
89
+
90
+ EventsUI.onClick(`.direction-code-bar-trash-btn-${id}`, async () => {
91
+ s(`.${id}`).remove();
92
+ ObjectLayerEngineModal.ObjectLayerData[directionCode] = ObjectLayerEngineModal.ObjectLayerData[
93
+ directionCode
94
+ ].filter((frame) => frame.id !== id);
95
+ });
96
+ });
97
+
98
+ EventsUI.onClick(`.ol-btn-save`, async () => {
99
+ const objectLayer = {
100
+ data: {
101
+ render: {
102
+ frames: {},
103
+ color: [],
104
+ frame_duration: 0,
105
+ is_stateless: false,
106
+ },
107
+ stats: {},
108
+ item: {},
109
+ },
110
+ };
111
+ for (const directionCode of directionCodes) {
112
+ const directions = ObjectLayerEngineModal.getDirectionsFromDirectionCode(directionCode);
113
+ for (const direction of directions) {
114
+ if (!objectLayer.data.render.frames[direction]) objectLayer.data.render.frames[direction] = [];
115
+
116
+ if (!(directionCode in ObjectLayerEngineModal.ObjectLayerData)) {
117
+ console.warn('No set directionCodeBarFrameData for directionCode', directionCode);
118
+ continue;
119
+ }
120
+
121
+ for (const frameData of ObjectLayerEngineModal.ObjectLayerData[directionCode]) {
122
+ const { matrix } = JSON.parse(frameData.json);
123
+ const frameIndexColorMatrix = [];
124
+ let indexRow = -1;
125
+ for (const row of matrix) {
126
+ indexRow++;
127
+ frameIndexColorMatrix[indexRow] = [];
128
+ let indexCol = -1;
129
+ for (const value of row) {
130
+ indexCol++;
131
+ let colorIndex = objectLayer.data.render.color.findIndex(
132
+ (color) =>
133
+ color[0] === value[0] &&
134
+ color[1] === value[1] &&
135
+ color[2] === value[2] &&
136
+ color[3] === value[3],
137
+ );
138
+ if (colorIndex === -1) {
139
+ objectLayer.data.render.color.push(value);
140
+ colorIndex = objectLayer.data.render.color.length - 1;
141
+ }
142
+ frameIndexColorMatrix[indexRow][indexCol] = colorIndex;
143
+ }
144
+ }
145
+ objectLayer.data.render.frames[direction].push(frameIndexColorMatrix);
146
+ }
147
+ }
148
+ }
149
+ objectLayer.data.render.frame_duration = parseInt(s(`.ol-input-render-frame-duration`).value);
150
+ objectLayer.data.render.is_stateless = renderIsStateless;
151
+ objectLayer.data.stats = {
152
+ effect: parseInt(s(`.ol-input-item-stats-effect`).value),
153
+ resistance: parseInt(s(`.ol-input-item-stats-resistance`).value),
154
+ agility: parseInt(s(`.ol-input-item-stats-agility`).value),
155
+ range: parseInt(s(`.ol-input-item-stats-range`).value),
156
+ intelligence: parseInt(s(`.ol-input-item-stats-intelligence`).value),
157
+ utility: parseInt(s(`.ol-input-item-stats-utility`).value),
158
+ };
159
+ objectLayer.data.item = {
160
+ type: selectItemType,
161
+ activable: itemActivable,
162
+ id: s(`.ol-input-item-id`).value,
163
+ description: s(`.ol-input-item-description`).value,
164
+ };
165
+ console.warn('objectLayer', objectLayer);
166
+
167
+ // Upload images
168
+ {
169
+ for (const directionCode of Object.keys(ObjectLayerEngineModal.ObjectLayerData)) {
170
+ let frameIndex = -1;
171
+ for (const frame of ObjectLayerEngineModal.ObjectLayerData[directionCode]) {
172
+ frameIndex++;
173
+ const pngBlob = frame.image;
174
+
175
+ const form = new FormData();
176
+ form.append(directionCode, pngBlob, `${frameIndex}.png`);
177
+
178
+ const { status, data } = await ObjectLayerService.post({
179
+ id: `frame-image/${objectLayer.data.item.type}/${objectLayer.data.item.id}/${directionCode}`,
180
+ body: form,
181
+ headerId: 'file',
182
+ });
183
+ }
184
+ }
185
+ }
186
+
187
+ // Upload metadata
188
+ {
189
+ delete objectLayer.data.render.frames;
190
+ delete objectLayer.data.render.color;
191
+ const { status, data } = await ObjectLayerService.post({
192
+ id: `metadata/${objectLayer.data.item.type}/${objectLayer.data.item.id}`,
193
+ body: objectLayer,
194
+ });
195
+ }
196
+ });
197
+ });
198
+ directionsCodeBarRender += html`
199
+ <div class="in section-mp-border">
200
+ <div class="fl">
201
+ <div class="in fll">
202
+ <div class="in direction-code-bar-frames-title">${directionCode}</div>
203
+ <div class="in direction-code-bar-frames-btn">
204
+ ${await BtnIcon.Render({
205
+ label: html`<i class="fa-solid fa-plus"></i>`,
206
+ class: `direction-code-bar-frames-btn-${directionCode}`,
207
+ })}
208
+ </div>
209
+ </div>
210
+ <div class="frames-${directionCode}"></div>
211
+ </div>
212
+ </div>
213
+ `;
214
+ }
215
+
216
+ let statsInputsRender = '';
217
+ for (const statType of statTypes) {
218
+ statsInputsRender += html`
219
+ ${await Input.Render({
220
+ id: `ol-input-item-stats-${statType}`,
221
+ label: html`<div class="inl" style="width: 120px; font-size: 16px; overflow: hidden">
222
+ <i class="fa-solid fa-chart-simple"></i> ${statType}
223
+ </div>`,
224
+ containerClass: 'inl',
225
+ type: 'number',
226
+ min: 0,
227
+ max: 10,
228
+ placeholder: true,
229
+ value: 0,
230
+ })}
231
+ `;
232
+ }
233
+
234
+ return html`
235
+ <style>
236
+ .direction-code-bar-frames-title {
237
+ font-weight: bold;
238
+ font-size: 1.2rem;
239
+ padding: 0.5rem;
240
+ }
241
+ .direction-code-bar-frames-img {
242
+ width: 100px;
243
+ height: auto;
244
+ margin: 3px;
245
+ }
246
+ .direction-code-bar-trash-btn {
247
+ top: 3px;
248
+ left: 3px;
249
+ background: red;
250
+ color: white;
251
+ }
252
+ .ol-btn-save {
253
+ padding: 0.5rem;
254
+ font-size: 30px;
255
+ font-weight: bold;
256
+ }
257
+ .ol-number-label {
258
+ width: 120px;
259
+ font-size: 16px;
260
+ overflow: hidden;
261
+ font-family: 'retro-font';
262
+ }
263
+ .sub-title-modal {
264
+ color: #ffcc00;
265
+ }
266
+ </style>
267
+ ${borderChar(2, 'black', ['.sub-title-modal'])}
268
+
269
+ <div class="in section-mp section-mp-border">
270
+ <div class="in sub-title-modal"><i class="fa-solid fa-table-cells-large"></i> Frame editor</div>
271
+
272
+ <object-layer-engine id="ole" width="${cells}" height="${cells}" pixel-size="${pixelSize}">
273
+ </object-layer-engine>
274
+ </div>
275
+
276
+ <div class="in section-mp section-mp-border">
277
+ <div class="in sub-title-modal"><i class="fa-solid fa-database"></i> Render data</div>
278
+ ${dynamicCol({ containerSelector: options.idModal, id: idSectionA })}
279
+
280
+ <div class="fl">
281
+ <div class="in fll ${idSectionA}-col-a">
282
+ <div class="in section-mp">
283
+ ${await DropDown.Render({
284
+ value: ObjectLayerEngineModal.templates[0].id,
285
+ label: html`${Translate.Render('select-template')}`,
286
+ data: ObjectLayerEngineModal.templates.map((template) => {
287
+ return {
288
+ value: template.id,
289
+ display: html`<i class="fa-solid fa-paint-roller"></i> ${template.label}`,
290
+ onClick: async () => {
291
+ ObjectLayerEngineModal.RenderTemplate(template.data);
292
+ },
293
+ };
294
+ }),
295
+ })}
296
+ </div>
297
+ </div>
298
+ <div class="in fll ${idSectionA}-col-b">
299
+ <div class="in section-mp-border" style="width: 135px;">
300
+ ${await Input.Render({
301
+ id: `ol-input-render-frame-duration`,
302
+ label: html`<div class="inl ol-number-label">
303
+ <i class="fa-solid fa-chart-simple"></i> Frame duration
304
+ </div>`,
305
+ containerClass: 'inl',
306
+ type: 'number',
307
+ min: 100,
308
+ max: 1000,
309
+ placeholder: true,
310
+ value: renderFrameDuration,
311
+ })}
312
+ </div>
313
+ <div class="in section-mp">
314
+ ${await ToggleSwitch.Render({
315
+ id: 'ol-toggle-render-is-stateless',
316
+ wrapper: true,
317
+ wrapperLabel: html`${Translate.Render('is-stateless')}`,
318
+ disabledOnClick: true,
319
+ checked: renderIsStateless,
320
+ on: {
321
+ unchecked: () => {
322
+ renderIsStateless = false;
323
+ console.warn('renderIsStateless', renderIsStateless);
324
+ },
325
+ checked: () => {
326
+ renderIsStateless = true;
327
+ console.warn('renderIsStateless', renderIsStateless);
328
+ },
329
+ },
330
+ })}
331
+ </div>
332
+ </div>
333
+ </div>
334
+ ${directionsCodeBarRender}
335
+ </div>
336
+ ${dynamicCol({ containerSelector: options.idModal, id: idSectionB, type: 'a-50-b-50' })}
337
+
338
+ <div class="fl">
339
+ <div class="in fll ${idSectionB}-col-a">
340
+ <div class="in section-mp section-mp-border">
341
+ <div class="in sub-title-modal"><i class="fa-solid fa-database"></i> Item data</div>
342
+ ${await Input.Render({
343
+ id: `ol-input-item-id`,
344
+ label: html`<i class="fa-solid fa-pen-to-square"></i> ${Translate.Render('item-id')}`,
345
+ containerClass: '',
346
+ placeholder: true,
347
+ })}
348
+ ${await Input.Render({
349
+ id: `ol-input-item-description`,
350
+ label: html`<i class="fa-solid fa-pen-to-square"></i> ${Translate.Render('item-description')}`,
351
+ containerClass: '',
352
+ placeholder: true,
353
+ })}
354
+ <div class="in section-mp">
355
+ ${await DropDown.Render({
356
+ value: itemTypes[0],
357
+ label: html`${Translate.Render('select-item-type')}`,
358
+ data: itemTypes.map((itemType) => {
359
+ return {
360
+ value: itemType,
361
+ display: html`${itemType}`,
362
+ onClick: async () => {
363
+ console.warn('itemType click', itemType);
364
+ selectItemType = itemType;
365
+ },
366
+ };
367
+ }),
368
+ })}
369
+ </div>
370
+ <div class="in section-mp">
371
+ ${await ToggleSwitch.Render({
372
+ id: 'ol-toggle-item-activable',
373
+ wrapper: true,
374
+ wrapperLabel: html`${Translate.Render('item-activable')}`,
375
+ disabledOnClick: true,
376
+ checked: itemActivable,
377
+ on: {
378
+ unchecked: () => {
379
+ itemActivable = false;
380
+ console.warn('itemActivable', itemActivable);
381
+ },
382
+ checked: () => {
383
+ itemActivable = true;
384
+ console.warn('itemActivable', itemActivable);
385
+ },
386
+ },
387
+ })}
388
+ </div>
389
+ </div>
390
+ </div>
391
+ <div class="in fll ${idSectionB}-col-b">
392
+ <div class="in section-mp section-mp-border">
393
+ <div class="in sub-title-modal"><i class="fa-solid fa-database"></i> Stats data</div>
394
+ ${statsInputsRender}
395
+ </div>
396
+ </div>
397
+ </div>
398
+
399
+ <div class="in section-mp">
400
+ ${await BtnIcon.Render({
401
+ label: html`<i class="fa-solid fa-save"></i> ${Translate.Render('save')}`,
402
+ class: `in flr ol-btn-save`,
403
+ })}
404
+ </div>
405
+ `;
406
+ },
407
+ getDirectionsFromDirectionCode(directionCode = '08') {
408
+ let objectLayerFrameDirections = [];
409
+
410
+ switch (directionCode) {
411
+ case '08':
412
+ objectLayerFrameDirections = ['down_idle', 'none_idle', 'default_idle'];
413
+ break;
414
+ case '18':
415
+ objectLayerFrameDirections = ['down_walking'];
416
+ break;
417
+ case '02':
418
+ objectLayerFrameDirections = ['up_idle'];
419
+ break;
420
+ case '12':
421
+ objectLayerFrameDirections = ['up_walking'];
422
+ break;
423
+ case '04':
424
+ objectLayerFrameDirections = ['left_idle', 'up_left_idle', 'down_left_idle'];
425
+ break;
426
+ case '14':
427
+ objectLayerFrameDirections = ['left_walking', 'up_left_walking', 'down_left_walking'];
428
+ break;
429
+ case '06':
430
+ objectLayerFrameDirections = ['right_idle', 'up_right_idle', 'down_right_idle'];
431
+ break;
432
+ case '16':
433
+ objectLayerFrameDirections = ['right_walking', 'up_right_walking', 'down_right_walking'];
434
+ break;
435
+ }
436
+
437
+ return objectLayerFrameDirections;
438
+ },
439
+ };
440
+
441
+ export { ObjectLayerEngineModal };
@@ -7,7 +7,7 @@ import { LogIn } from './LogIn.js';
7
7
  import { NotificationManager } from './NotificationManager.js';
8
8
  import { Translate } from './Translate.js';
9
9
  import { Validator } from './Validator.js';
10
- import { getQueryParams, s } from './VanillaJs.js';
10
+ import { getProxyPath, getQueryParams, s } from './VanillaJs.js';
11
11
 
12
12
  const Recover = {
13
13
  Event: {},
@@ -80,7 +80,10 @@ const Recover = {
80
80
  }
81
81
  switch (mode) {
82
82
  case 'recover-verify-email': {
83
- const result = await UserService.post({ id: 'recover-verify-email', body });
83
+ const result = await UserService.post({
84
+ id: 'recover-verify-email',
85
+ body: { ...body, proxyPath: getProxyPath(), hostname: `${location.hostname}` },
86
+ });
84
87
  NotificationManager.Push({
85
88
  html:
86
89
  result.status === 'error' ? result.message : Translate.Render(`${result.status}-recover-verify-email`),
@@ -1,131 +1,76 @@
1
- import { borderChar } from './Css.js';
2
- import { Modal } from './Modal.js';
3
- import { append, s } from './VanillaJs.js';
1
+ import { s } from './VanillaJs.js';
4
2
 
5
- const Scroll = {
6
- data: {},
7
- init: function (selector) {
8
- s(selector).addEventListener('scroll', Scroll.scrollHandler);
9
- Scroll.data[selector] = {
10
- element: s(selector),
3
+ class Scroll {
4
+ /**
5
+ * Attach scroll listener to an element (resolved with s(selector)).
6
+ * @param {string} selector - selector passed to s(selector)
7
+ * @param {function} [callback] - callback function to be called on scroll
8
+ * @param {object} options
9
+ * @param {number} [options.threshold=1] - px margin to treat as bottom
10
+ * @param {number} [options.precision=3] - decimal places for percentages
11
+ */
12
+ static setEvent(selector, callback = async () => {}, options = { threshold: 1, precision: 3 }) {
13
+ const el = s(selector);
14
+ if (!el) return;
15
+
16
+ const threshold = options.threshold ?? 1; // px tolerance for bottom detection
17
+ const precision = options.precision ?? 3;
18
+ let ticking = false;
19
+
20
+ const round = (v) => {
21
+ const m = Math.pow(10, precision);
22
+ return Math.round(v * m) / m;
11
23
  };
12
- return Scroll.data[selector];
13
- },
14
- getScrollPosition: function (selector) {
15
- // Scroll.data[selector].element.clientHeight -
16
- return Scroll.data[selector].element.scrollTop;
17
- },
18
- scrollHandler: async function () {
19
- for (const selector in Scroll.data) await Scroll.data[selector].callback(Scroll.getScrollPosition(selector));
20
- },
21
- addEvent: function (selector = '', callback = (position = 0) => {}) {
22
- Scroll.data[selector].callback = callback;
23
- },
24
- removeEvent: function (selector) {
25
- delete Scroll.data[selector];
26
- },
27
- to: function (elector = '', options = { top: 100, left: 100, behavior: 'smooth' }) {
28
- Scroll.data[selector].element.scrollTo({
29
- top: options.top || Scroll.getScrollPosition(selector),
30
- left: options.left || 0,
31
- behavior: options.behavior || 'smooth',
32
- });
33
- },
34
- topRefreshEvents: {},
35
- addTopRefreshEvent: function (options = { id: '', callback: () => {}, condition: () => {} }) {
36
- this.topRefreshEvents[options.id] = options;
37
- },
38
- removeTopRefreshEvent: function (id = '') {
39
- delete this.topRefreshEvents[id];
40
- },
41
- pullTopRefresh: function () {
42
- return;
43
- append(
44
- 'body',
45
- html` <style>
46
- .pull-refresh-icon-container {
47
- height: 60px;
48
- width: 100%;
49
- z-index: 10;
50
- transition: 0.3s;
51
- left: 0px;
52
- }
53
- .pull-refresh-icon {
54
- width: 60px;
55
- height: 60px;
56
- margin: auto;
57
- color: white;
58
- font-size: 30px;
59
- }
60
- </style>
61
- ${borderChar(2, 'black', [' .pull-refresh-icon-container'])}
62
- <div style="top: -60px" class="abs pull-refresh-icon-container">
63
- <div class="in pull-refresh-icon">
64
- <div class="abs center"><i class="fa-solid fa-arrows-rotate"></i></div>
65
- </div>
66
- </div>`,
67
- );
68
24
 
69
- let touchstartY = 0;
70
- let reload = false;
71
- const minHeightDragReload = 3;
72
- const maxHeightDragReload = 20;
25
+ const listener = (event) => {
26
+ if (ticking) return;
27
+ ticking = true;
28
+
29
+ requestAnimationFrame(() => {
30
+ const scrollHeight = el.scrollHeight;
31
+ const clientHeight = el.clientHeight;
32
+ const scrollTop = el.scrollTop;
33
+
34
+ // pixels left to scroll (clamped to >= 0)
35
+ const remaining = Math.max(0, scrollHeight - clientHeight - scrollTop);
73
36
 
74
- document.addEventListener('touchstart', (e) => {
75
- touchstartY = e.touches[0].clientY;
76
- // console.warn('touchstart', touchstartY);
77
- });
37
+ // maximum possible remaining (0 if content fits without scrolling)
38
+ const maxRemaining = Math.max(0, scrollHeight - clientHeight);
78
39
 
79
- document.addEventListener('touchmove', (e) => {
80
- if (
81
- !Object.keys(Scroll.topRefreshEvents).find((event) => Scroll.topRefreshEvents[event].condition()) ||
82
- (!s(`.btn-bar-center-icon-close`).classList.contains('hide') &&
83
- !s(
84
- `.btn-icon-menu-mode-${Modal.Data['modal-menu'].options.mode !== 'slide-menu-right' ? 'left' : 'right'}`,
85
- ).classList.contains('hide'))
86
- )
87
- return;
40
+ // percentRemaining: 1 = top (all remaining), 0 = bottom (none remaining)
41
+ let percentRemaining = maxRemaining === 0 ? 0 : remaining / maxRemaining;
42
+ percentRemaining = Math.max(0, Math.min(1, percentRemaining));
88
43
 
89
- const touchY = e.touches[0].clientY;
90
- const touchDiff = touchY - touchstartY;
44
+ // percentScrolled: complementary value (0 = top, 1 = bottom)
45
+ let percentScrolled = 1 - percentRemaining;
46
+ percentScrolled = Math.max(0, Math.min(1, percentScrolled));
91
47
 
92
- // console.warn('touchDiff', touchDiff, maxHeightDragReload);
48
+ const payload = {
49
+ scrollHeight,
50
+ clientHeight,
51
+ scrollTop,
52
+ remaining, // px left (>= 0)
53
+ scrollBottom: remaining <= threshold ? 0 : remaining,
54
+ atBottom: remaining <= threshold,
55
+ percentRemaining: round(percentRemaining), // 0..1
56
+ percentScrolled: round(percentScrolled), // 0..1
57
+ };
93
58
 
94
- if (touchDiff > maxHeightDragReload)
95
- s(`.pull-refresh-icon-container`).style.top = 60 + maxHeightDragReload + 'px';
96
- else s(`.pull-refresh-icon-container`).style.top = 60 + touchDiff + 'px';
59
+ // replace this with an event dispatch or callback if you prefer
60
+ // console.warn('scroll', event, JSON.stringify(payload, null, 2));
61
+ callback(payload);
97
62
 
98
- if (touchDiff > minHeightDragReload && window.scrollY === 0) {
99
- reload = true;
100
- } else {
101
- reload = false;
102
- }
103
- });
104
- document.addEventListener('touchend', (e) => {
105
- // console.warn('touchend');
106
- s(`.pull-refresh-icon-container`).style.top = '-60px';
107
- if (reload) {
108
- for (const event of Object.keys(Scroll.topRefreshEvents))
109
- if (Scroll.topRefreshEvents[event].condition()) Scroll.topRefreshEvents[event].callback();
110
- }
111
- reload = false;
112
- });
113
- Scroll.addTopRefreshEvent({
114
- id: 'main-body',
115
- callback: () => {
116
- location.reload();
117
- },
118
- condition: () => {
119
- return (
120
- s('.main-body') &&
121
- s('.main-body').scrollTop === 0 &&
122
- !Object.keys(Modal.Data).find(
123
- (idModal) => !['modal-menu', 'main-body', 'bottom-bar', 'main-body-top'].includes(idModal),
124
- )
125
- );
126
- },
127
- });
128
- },
129
- };
63
+ ticking = false;
64
+ });
65
+ };
66
+
67
+ el.addEventListener('scroll', listener, { passive: true });
68
+
69
+ return {
70
+ removeEvent: () => el.removeEventListener('scroll', listener),
71
+ };
72
+ }
73
+ }
130
74
 
131
75
  export { Scroll };
76
+ export default Scroll;
@@ -53,6 +53,7 @@ const SignUp = {
53
53
  status: result.status,
54
54
  });
55
55
  if (result.status === 'success') {
56
+ await Auth.sessionOut();
56
57
  await Auth.signUpToken(result);
57
58
  s(`.btn-close-${options.idModal}`).click();
58
59
  }