@necrolab/dashboard 0.5.22 → 0.5.24
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/package.json +4 -4
- package/src/components/Editors/Account/Account.vue +1 -1
- package/src/components/Editors/Profile/CreateProfile.vue +11 -11
- package/src/components/Filter/Filter.vue +13 -0
- package/src/components/Tasks/Task.vue +63 -26
- package/src/components/ui/StatusBadge.vue +1 -1
- package/src/composables/useFilterCSS.js +6 -12
- package/src/libs/Filter.js +241 -21
- package/src/libs/tm-renderer/axs/renderer.js +97 -0
- package/src/libs/tm-renderer/axs/requests.js +37 -0
- package/src/libs/tm-renderer/base-renderer.js +93 -0
- package/src/libs/tm-renderer/dependencies/logger.js +186 -0
- package/src/libs/tm-renderer/dependencies/web.persist.js +64 -0
- package/src/libs/tm-renderer/factory.js +40 -0
- package/src/libs/tm-renderer/index.js +3 -0
- package/src/libs/tm-renderer/request-utils.js +85 -0
- package/src/libs/tm-renderer/tm/renderer.js +568 -0
- package/src/libs/tm-renderer/tm/requests.js +47 -0
- package/src/libs/tm-renderer/tm/tm-utils.js +40 -0
- package/src/libs/tm-renderer/utils.js +90 -0
- package/src/views/FilterBuilder.vue +163 -57
- package/vite.config.js +1 -2
package/src/libs/Filter.js
CHANGED
|
@@ -10,7 +10,48 @@ const colors = {
|
|
|
10
10
|
UNSELECTABLE: "#311432"
|
|
11
11
|
};
|
|
12
12
|
|
|
13
|
-
|
|
13
|
+
// Color palette for unique filter colors - distinct but professional
|
|
14
|
+
const filterColorPalette = [
|
|
15
|
+
"#7bc999", // Mint green
|
|
16
|
+
"#6ba8d6", // Steel blue
|
|
17
|
+
"#d4a876", // Tan/gold
|
|
18
|
+
"#a88bc9", // Soft purple
|
|
19
|
+
"#e89ba3", // Coral pink
|
|
20
|
+
"#7dc9c9", // Turquoise
|
|
21
|
+
"#c9a87b", // Warm tan
|
|
22
|
+
"#8bc99a", // Light green
|
|
23
|
+
"#9ba8c9", // Periwinkle
|
|
24
|
+
"#c9b88b" // Khaki
|
|
25
|
+
];
|
|
26
|
+
|
|
27
|
+
const getFilterColor = (filterIndex, isExpanded, isExcluded) => {
|
|
28
|
+
if (isExcluded) {
|
|
29
|
+
return isExpanded ? colors.EXCLUDED_EXPANDED : colors.EXCLUDED;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
// Use palette colors for normal filters
|
|
33
|
+
const baseColor = filterColorPalette[filterIndex % filterColorPalette.length];
|
|
34
|
+
|
|
35
|
+
if (!isExpanded) {
|
|
36
|
+
return baseColor;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
// Make expanded filters brighter
|
|
40
|
+
const hex = baseColor.replace('#', '');
|
|
41
|
+
const r = parseInt(hex.substr(0, 2), 16);
|
|
42
|
+
const g = parseInt(hex.substr(2, 2), 16);
|
|
43
|
+
const b = parseInt(hex.substr(4, 2), 16);
|
|
44
|
+
|
|
45
|
+
// Lighten by 20%
|
|
46
|
+
const lighter = (c) => Math.min(255, Math.floor(c + (255 - c) * 0.3));
|
|
47
|
+
return `#${lighter(r).toString(16).padStart(2, '0')}${lighter(g).toString(16).padStart(2, '0')}${lighter(b).toString(16).padStart(2, '0')}`;
|
|
48
|
+
};
|
|
49
|
+
|
|
50
|
+
const BASE_CSS = `
|
|
51
|
+
.svg-wrapper path[generaladmission]:hover {fill: ${colors.HIGHLIGHT};pointer-events:all;cursor:pointer;}
|
|
52
|
+
.svg-wrapper path {pointer-events: all; cursor: pointer; touch-action: manipulation;}
|
|
53
|
+
.svg-wrapper {touch-action: manipulation; -webkit-user-select: none; user-select: none;}
|
|
54
|
+
`;
|
|
14
55
|
|
|
15
56
|
const getAttributeValue = (element, attributeName) => {
|
|
16
57
|
try {
|
|
@@ -158,6 +199,8 @@ export default class FilterBuilder {
|
|
|
158
199
|
};
|
|
159
200
|
this.cssClasses = "";
|
|
160
201
|
this.temporaryCSS = "";
|
|
202
|
+
this._highlightedElements = null;
|
|
203
|
+
this._currentlyHighlightedSection = null;
|
|
161
204
|
|
|
162
205
|
this.filterTypes = {
|
|
163
206
|
INVALID: -1,
|
|
@@ -211,9 +254,16 @@ export default class FilterBuilder {
|
|
|
211
254
|
}
|
|
212
255
|
|
|
213
256
|
addRowHandlers() {
|
|
257
|
+
// Track the currently highlighted section across all handlers
|
|
258
|
+
// This prevents flicker when moving mouse between rows
|
|
259
|
+
if (!this.lastHighlightedSection) {
|
|
260
|
+
this.lastHighlightedSection = null;
|
|
261
|
+
}
|
|
262
|
+
|
|
214
263
|
for (let i = 0; i < this.rows.length; i++) {
|
|
215
264
|
const row = this.rows[i];
|
|
216
|
-
|
|
265
|
+
|
|
266
|
+
const clickHandler = () => {
|
|
217
267
|
const parsed = {
|
|
218
268
|
section: getAttributeValue(row, "section"),
|
|
219
269
|
row: getAttributeValue(row, "row"),
|
|
@@ -245,6 +295,13 @@ export default class FilterBuilder {
|
|
|
245
295
|
});
|
|
246
296
|
};
|
|
247
297
|
|
|
298
|
+
// Support both click and touch events
|
|
299
|
+
row.onclick = clickHandler;
|
|
300
|
+
row.ontouchend = (e) => {
|
|
301
|
+
e.preventDefault();
|
|
302
|
+
clickHandler();
|
|
303
|
+
};
|
|
304
|
+
|
|
248
305
|
row.onmouseover = () => {
|
|
249
306
|
const parsed = {
|
|
250
307
|
section: getAttributeValue(row, "section"),
|
|
@@ -252,6 +309,18 @@ export default class FilterBuilder {
|
|
|
252
309
|
id: getAttributeValue(row, "id")
|
|
253
310
|
};
|
|
254
311
|
|
|
312
|
+
const sectionKey = `${parsed.section}`;
|
|
313
|
+
|
|
314
|
+
// Skip if already highlighting this section - prevents flickering
|
|
315
|
+
if (this.lastHighlightedSection === sectionKey) return;
|
|
316
|
+
|
|
317
|
+
// Only clear if switching sections
|
|
318
|
+
if (this.lastHighlightedSection && this.lastHighlightedSection !== sectionKey) {
|
|
319
|
+
this.clearHighlight();
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
this.lastHighlightedSection = sectionKey;
|
|
323
|
+
|
|
255
324
|
const matchingFilter = this.filters.find(
|
|
256
325
|
(f) =>
|
|
257
326
|
this.isForCurrentEvent(f) &&
|
|
@@ -265,21 +334,101 @@ export default class FilterBuilder {
|
|
|
265
334
|
else this.highlight({ section: matchingFilter.section });
|
|
266
335
|
};
|
|
267
336
|
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
};
|
|
337
|
+
// Don't use onmouseout - it causes flicker when moving between rows in the same section
|
|
338
|
+
// clearHighlight is already handled when switching to a different section (line 312-314)
|
|
271
339
|
}
|
|
272
340
|
}
|
|
273
341
|
|
|
274
342
|
reload(eventId) {
|
|
275
343
|
const paths = document.querySelectorAll("path");
|
|
276
|
-
|
|
344
|
+
// Include ALL paths that have a section attribute, not just rows
|
|
345
|
+
// This includes section containers, backgrounds, and any other paths
|
|
346
|
+
this.rows = [...paths].filter((r) => {
|
|
347
|
+
const hasSection = r.attributes['section'];
|
|
348
|
+
const isNotGA = !r.attributes['generaladmission'];
|
|
349
|
+
return hasSection && isNotGA;
|
|
350
|
+
});
|
|
277
351
|
|
|
278
352
|
this.addLabelHandlers();
|
|
279
353
|
this.addGAHandlers();
|
|
280
354
|
this.addRowHandlers();
|
|
281
355
|
this.currentEventId = eventId;
|
|
282
356
|
|
|
357
|
+
// Handle mouse events on the SVG wrapper
|
|
358
|
+
const svgWrapper = document.querySelector('.svg-wrapper');
|
|
359
|
+
if (svgWrapper) {
|
|
360
|
+
// Clear highlight when mouse leaves the entire SVG area
|
|
361
|
+
svgWrapper.onmouseleave = () => {
|
|
362
|
+
this.lastHighlightedSection = null;
|
|
363
|
+
if (!this.isDragging) this.clearHighlight();
|
|
364
|
+
};
|
|
365
|
+
|
|
366
|
+
// When hovering on SVG background (between rows), maintain current highlight
|
|
367
|
+
svgWrapper.onmouseover = (e) => {
|
|
368
|
+
const target = e.target;
|
|
369
|
+
if (target === svgWrapper || (target.tagName && target.tagName.toLowerCase() === 'svg')) {
|
|
370
|
+
e.stopPropagation();
|
|
371
|
+
return;
|
|
372
|
+
}
|
|
373
|
+
};
|
|
374
|
+
|
|
375
|
+
// CRITICAL: Handle clicks on SVG background (empty space)
|
|
376
|
+
// When user clicks empty space, create filter for currently highlighted section
|
|
377
|
+
svgWrapper.onclick = (e) => {
|
|
378
|
+
const target = e.target;
|
|
379
|
+
// Only handle clicks on background (not on path elements)
|
|
380
|
+
if (target === svgWrapper || (target.tagName && target.tagName.toLowerCase() === 'svg')) {
|
|
381
|
+
// If we have a highlighted section, create filter for it
|
|
382
|
+
if (this._currentlyHighlightedSection) {
|
|
383
|
+
const matchingFilter = this.filters.find(
|
|
384
|
+
(f) => this.isForCurrentEvent(f) && f.section === this._currentlyHighlightedSection
|
|
385
|
+
);
|
|
386
|
+
|
|
387
|
+
if (matchingFilter) {
|
|
388
|
+
// Toggle expanded state
|
|
389
|
+
if (this.expandedFilter === matchingFilter.id) {
|
|
390
|
+
this.setExpandedFilter(null);
|
|
391
|
+
} else {
|
|
392
|
+
this.setExpandedFilter(matchingFilter.id);
|
|
393
|
+
}
|
|
394
|
+
} else {
|
|
395
|
+
// Create new filter
|
|
396
|
+
this.addFilter({
|
|
397
|
+
section: this._currentlyHighlightedSection,
|
|
398
|
+
event: this.currentEventId
|
|
399
|
+
});
|
|
400
|
+
}
|
|
401
|
+
}
|
|
402
|
+
}
|
|
403
|
+
};
|
|
404
|
+
|
|
405
|
+
// Support touch events on background too
|
|
406
|
+
svgWrapper.ontouchend = (e) => {
|
|
407
|
+
const target = e.target;
|
|
408
|
+
if (target === svgWrapper || (target.tagName && target.tagName.toLowerCase() === 'svg')) {
|
|
409
|
+
e.preventDefault();
|
|
410
|
+
if (this._currentlyHighlightedSection) {
|
|
411
|
+
const matchingFilter = this.filters.find(
|
|
412
|
+
(f) => this.isForCurrentEvent(f) && f.section === this._currentlyHighlightedSection
|
|
413
|
+
);
|
|
414
|
+
|
|
415
|
+
if (matchingFilter) {
|
|
416
|
+
if (this.expandedFilter === matchingFilter.id) {
|
|
417
|
+
this.setExpandedFilter(null);
|
|
418
|
+
} else {
|
|
419
|
+
this.setExpandedFilter(matchingFilter.id);
|
|
420
|
+
}
|
|
421
|
+
} else {
|
|
422
|
+
this.addFilter({
|
|
423
|
+
section: this._currentlyHighlightedSection,
|
|
424
|
+
event: this.currentEventId
|
|
425
|
+
});
|
|
426
|
+
}
|
|
427
|
+
}
|
|
428
|
+
}
|
|
429
|
+
};
|
|
430
|
+
}
|
|
431
|
+
|
|
283
432
|
this.updateCss();
|
|
284
433
|
}
|
|
285
434
|
|
|
@@ -360,7 +509,7 @@ export default class FilterBuilder {
|
|
|
360
509
|
this.cssClasses = BASE_CSS;
|
|
361
510
|
const gaSectionNameMapping = this.getSectionNameMapping();
|
|
362
511
|
|
|
363
|
-
this.filters.forEach((filter) => {
|
|
512
|
+
this.filters.forEach((filter, filterIndex) => {
|
|
364
513
|
if (!this.isForCurrentEvent(filter)) return;
|
|
365
514
|
|
|
366
515
|
// Try to find the short name (attribute name) from the real name (stored in filter)
|
|
@@ -377,12 +526,12 @@ export default class FilterBuilder {
|
|
|
377
526
|
|
|
378
527
|
const type = this.getFilterType(filter);
|
|
379
528
|
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
529
|
+
// Use unique color for each filter
|
|
530
|
+
const color = getFilterColor(
|
|
531
|
+
filterIndex,
|
|
532
|
+
this.expandedFilter === filter.id,
|
|
533
|
+
filter.exclude
|
|
534
|
+
);
|
|
386
535
|
|
|
387
536
|
switch (type) {
|
|
388
537
|
case this.filterTypes.NORMAL:
|
|
@@ -430,6 +579,11 @@ export default class FilterBuilder {
|
|
|
430
579
|
this.cssClasses += `.svg-wrapper path[section="${section}"][row="${row}"] {stroke: ${color} !important;}\n`;
|
|
431
580
|
});
|
|
432
581
|
|
|
582
|
+
// CRITICAL: Add hover styles AFTER filter styles so they win specificity battle
|
|
583
|
+
// Use BOTH stroke and fill to cover entire section area, not just row lines
|
|
584
|
+
this.cssClasses += `.svg-wrapper path.hover-highlight-stroke {stroke: ${colors.HIGHLIGHT} !important; fill: ${colors.HIGHLIGHT} !important; fill-opacity: 0.3 !important;}\n`;
|
|
585
|
+
this.cssClasses += `.svg-wrapper path.hover-highlight-fill {fill: ${colors.HIGHLIGHT} !important;}\n`;
|
|
586
|
+
|
|
433
587
|
log("Generated CSS:", this.cssClasses);
|
|
434
588
|
}
|
|
435
589
|
|
|
@@ -442,7 +596,7 @@ export default class FilterBuilder {
|
|
|
442
596
|
const label = labels[i];
|
|
443
597
|
const section = getAttributeValue(label, "section");
|
|
444
598
|
|
|
445
|
-
|
|
599
|
+
const labelClickHandler = () => {
|
|
446
600
|
// filter exists already for section - return
|
|
447
601
|
const matchingFilter = this.filters.find((f) => {
|
|
448
602
|
if (!this.isForCurrentEvent(f)) return false;
|
|
@@ -490,6 +644,43 @@ export default class FilterBuilder {
|
|
|
490
644
|
log("Could not add labelHandler", section, e);
|
|
491
645
|
}
|
|
492
646
|
};
|
|
647
|
+
|
|
648
|
+
// Support both click and touch
|
|
649
|
+
label.onclick = labelClickHandler;
|
|
650
|
+
label.ontouchend = (e) => {
|
|
651
|
+
e.preventDefault();
|
|
652
|
+
labelClickHandler();
|
|
653
|
+
};
|
|
654
|
+
|
|
655
|
+
// Add hover handler to highlight section when hovering over label
|
|
656
|
+
label.onmouseover = () => {
|
|
657
|
+
const matchingFilter = this.filters.find((f) => {
|
|
658
|
+
if (!this.isForCurrentEvent(f)) return false;
|
|
659
|
+
if (f.section !== section && f.section !== gaSectionNameMapping[section]) return false;
|
|
660
|
+
return true;
|
|
661
|
+
});
|
|
662
|
+
|
|
663
|
+
if (!matchingFilter) return;
|
|
664
|
+
|
|
665
|
+
const sectionKey = matchingFilter.section;
|
|
666
|
+
|
|
667
|
+
// Skip if already highlighting this section
|
|
668
|
+
if (this.lastHighlightedSection === sectionKey) return;
|
|
669
|
+
|
|
670
|
+
// Clear if switching sections
|
|
671
|
+
if (this.lastHighlightedSection && this.lastHighlightedSection !== sectionKey) {
|
|
672
|
+
this.clearHighlight();
|
|
673
|
+
}
|
|
674
|
+
|
|
675
|
+
this.lastHighlightedSection = sectionKey;
|
|
676
|
+
|
|
677
|
+
// Highlight the section
|
|
678
|
+
if (Array.isArray(matchingFilter.rows)) {
|
|
679
|
+
matchingFilter.rows.forEach((row) => this.highlight({ section: matchingFilter.section, row: row }));
|
|
680
|
+
} else {
|
|
681
|
+
this.highlight({ section: matchingFilter.section });
|
|
682
|
+
}
|
|
683
|
+
};
|
|
493
684
|
}
|
|
494
685
|
}
|
|
495
686
|
|
|
@@ -506,7 +697,7 @@ export default class FilterBuilder {
|
|
|
506
697
|
const section = getAttributeValue(path, "name");
|
|
507
698
|
const sectionName = gaSectionNameMapping[section];
|
|
508
699
|
|
|
509
|
-
|
|
700
|
+
const gaClickHandler = () => {
|
|
510
701
|
// filter exists already for section
|
|
511
702
|
const matchingFilter = this.filters.find((f) => f.section === sectionName && this.isForCurrentEvent(f));
|
|
512
703
|
if (matchingFilter) {
|
|
@@ -525,6 +716,13 @@ export default class FilterBuilder {
|
|
|
525
716
|
event: this.currentEventId
|
|
526
717
|
});
|
|
527
718
|
};
|
|
719
|
+
|
|
720
|
+
// Support both click and touch
|
|
721
|
+
path.onclick = gaClickHandler;
|
|
722
|
+
path.ontouchend = (e) => {
|
|
723
|
+
e.preventDefault();
|
|
724
|
+
gaClickHandler();
|
|
725
|
+
};
|
|
528
726
|
}
|
|
529
727
|
}
|
|
530
728
|
|
|
@@ -566,21 +764,43 @@ export default class FilterBuilder {
|
|
|
566
764
|
highlight(filter, isGA = false) {
|
|
567
765
|
if (filter.row && this.isUnselectable(filter.sec, filter.row)) return;
|
|
568
766
|
|
|
569
|
-
|
|
767
|
+
// Check if we're already highlighting this exact section
|
|
768
|
+
const sectionKey = filter.section || filter.name;
|
|
769
|
+
if (this._currentlyHighlightedSection === sectionKey) {
|
|
770
|
+
return; // Already highlighting this section
|
|
771
|
+
}
|
|
570
772
|
|
|
773
|
+
// Clear previous highlight first
|
|
774
|
+
this.clearHighlight();
|
|
775
|
+
this._currentlyHighlightedSection = sectionKey;
|
|
776
|
+
|
|
777
|
+
// Use direct DOM manipulation instead of CSS injection
|
|
778
|
+
// This is MUCH faster and prevents flicker
|
|
571
779
|
if (isGA) {
|
|
572
780
|
if (this.filters.find((f) => f.section === filter.name)) return;
|
|
573
|
-
|
|
781
|
+
const paths = document.querySelectorAll(`path[name="${filter.section || filter.name}"]`);
|
|
782
|
+
paths.forEach(p => p.classList.add('hover-highlight-fill'));
|
|
783
|
+
this._highlightedElements = Array.from(paths);
|
|
574
784
|
} else if (filter.row) {
|
|
575
|
-
|
|
785
|
+
const paths = document.querySelectorAll(`path[section="${filter.section}"][row="${filter.row}"]`);
|
|
786
|
+
paths.forEach(p => p.classList.add('hover-highlight-stroke'));
|
|
787
|
+
this._highlightedElements = Array.from(paths);
|
|
576
788
|
} else {
|
|
577
|
-
|
|
789
|
+
const paths = document.querySelectorAll(`path[section="${filter.section}"]`);
|
|
790
|
+
paths.forEach(p => p.classList.add('hover-highlight-stroke'));
|
|
791
|
+
this._highlightedElements = Array.from(paths);
|
|
578
792
|
}
|
|
579
|
-
this.temporaryCSS += newCSS;
|
|
580
793
|
}
|
|
581
794
|
|
|
582
795
|
clearHighlight() {
|
|
583
|
-
|
|
796
|
+
// Remove classes from previously highlighted elements
|
|
797
|
+
if (this._highlightedElements) {
|
|
798
|
+
this._highlightedElements.forEach(el => {
|
|
799
|
+
el.classList.remove('hover-highlight-stroke', 'hover-highlight-fill');
|
|
800
|
+
});
|
|
801
|
+
this._highlightedElements = null;
|
|
802
|
+
}
|
|
803
|
+
this._currentlyHighlightedSection = null;
|
|
584
804
|
}
|
|
585
805
|
|
|
586
806
|
isUnselectable(section, row) {
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
import BaseRenderer from "../base-renderer.js";
|
|
2
|
+
import { getEventData, getEventDataUrl } from "./requests.js";
|
|
3
|
+
|
|
4
|
+
class AXSRenderer extends BaseRenderer {
|
|
5
|
+
constructor(eventId, options = {}) {
|
|
6
|
+
super(eventId, options);
|
|
7
|
+
this.config = this.getDefaultConfig();
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
async render() {
|
|
11
|
+
if (!this.storage) {
|
|
12
|
+
this.logger.Error("Cannot render without storage");
|
|
13
|
+
return false;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
try {
|
|
17
|
+
const data = await this.getEventData();
|
|
18
|
+
this.logger.Debug("Event data loaded");
|
|
19
|
+
|
|
20
|
+
if (this.fs) {
|
|
21
|
+
this.fs.writeFileSync("out.json", JSON.stringify(data));
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
let svgBody = "";
|
|
25
|
+
|
|
26
|
+
data.n.forEach((subsection) => {
|
|
27
|
+
let sectionData = "";
|
|
28
|
+
switch (subsection.h.t) {
|
|
29
|
+
case "seat":
|
|
30
|
+
subsection.n.forEach((seat) => {
|
|
31
|
+
sectionData += this.createElement("circle", {
|
|
32
|
+
cx: seat.c[0],
|
|
33
|
+
cy: seat.c[1],
|
|
34
|
+
id: seat.i,
|
|
35
|
+
r: "0.5",
|
|
36
|
+
fill: "#fff",
|
|
37
|
+
});
|
|
38
|
+
});
|
|
39
|
+
break;
|
|
40
|
+
case "section":
|
|
41
|
+
subsection.n.forEach((polygon) => {
|
|
42
|
+
sectionData += this.createElement(
|
|
43
|
+
"g",
|
|
44
|
+
{
|
|
45
|
+
id: polygon.i,
|
|
46
|
+
transform: `translate(${polygon.c[0]} ${polygon.c[1]})`,
|
|
47
|
+
},
|
|
48
|
+
this.createElement("path", {
|
|
49
|
+
d: polygon.pt,
|
|
50
|
+
fill: "rgba(100,100,100,0.5)",
|
|
51
|
+
}),
|
|
52
|
+
);
|
|
53
|
+
});
|
|
54
|
+
break;
|
|
55
|
+
default:
|
|
56
|
+
break;
|
|
57
|
+
}
|
|
58
|
+
svgBody += this.createElement("g", {}, sectionData);
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
this.svg = this.createElement(
|
|
62
|
+
"svg",
|
|
63
|
+
{
|
|
64
|
+
width: "1440",
|
|
65
|
+
height: "816",
|
|
66
|
+
viewBox: "0 0 1200 680",
|
|
67
|
+
xmlns: "http://www.w3.org/2000/svg",
|
|
68
|
+
version: "1.1",
|
|
69
|
+
},
|
|
70
|
+
svgBody,
|
|
71
|
+
);
|
|
72
|
+
|
|
73
|
+
if (this.fs) {
|
|
74
|
+
this.fs.writeFileSync("test.svg", this.svg);
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
return true;
|
|
78
|
+
} catch (e) {
|
|
79
|
+
this.logger.Error(`Couldn't render ${this.eventId} ${e.message}`);
|
|
80
|
+
return false;
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
async getEventData() {
|
|
85
|
+
return await getEventData(this.eventId, this.logger);
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
async getEventDataUrl() {
|
|
89
|
+
return await getEventDataUrl(this.eventId, this.logger);
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
async fetchBackgroundImage() {
|
|
93
|
+
throw new Error("Background image fetching not implemented for AXS renderer");
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
export default AXSRenderer;
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { getJsonDataWithHeaders } from "../request-utils.js";
|
|
2
|
+
|
|
3
|
+
function buildAXSEventDataUrl(eventId, data) {
|
|
4
|
+
const base = data.url.replace("*", "");
|
|
5
|
+
const policy = data.keys["CloudFront-Policy"];
|
|
6
|
+
const sig = data.keys["CloudFront-Signature"];
|
|
7
|
+
const keypair = data.keys["CloudFront-Key-Pair-Id"];
|
|
8
|
+
const version = "2.6.16";
|
|
9
|
+
return `${base}maps/main/master_full.json?Policy=${policy}_&Signature=${sig}&Key-Pair-Id=${keypair}&v=${version}`;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
async function getEventDataUrl(eventId, logger) {
|
|
13
|
+
const url = `https://contentdistribution.3ddvapis.com/api/v1/dvm/token/venue/${eventId}`;
|
|
14
|
+
const customHeaders = {
|
|
15
|
+
"referer": "https://tickets-de.axs.com/",
|
|
16
|
+
"user-agent":
|
|
17
|
+
"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/135.0.0.0 Safari/537.36",
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
return await getJsonDataWithHeaders(url, customHeaders, 0, logger);
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
async function getEventData(eventId, logger) {
|
|
24
|
+
const urlData = await getEventDataUrl(eventId, logger);
|
|
25
|
+
if (!urlData) {
|
|
26
|
+
throw new Error("Failed to get event data URL");
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
const url = buildAXSEventDataUrl(eventId, urlData);
|
|
30
|
+
return await getJsonDataWithHeaders(url, {}, 0, logger);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export {
|
|
34
|
+
getEventData,
|
|
35
|
+
getEventDataUrl,
|
|
36
|
+
buildAXSEventDataUrl,
|
|
37
|
+
};
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
import { createLogger } from "./dependencies/logger.js";
|
|
2
|
+
import * as utils from "./utils.js";
|
|
3
|
+
|
|
4
|
+
class BaseRenderer {
|
|
5
|
+
constructor(eventId, options = {}) {
|
|
6
|
+
this.eventId = eventId;
|
|
7
|
+
this.options = {
|
|
8
|
+
proxy: null,
|
|
9
|
+
country: null,
|
|
10
|
+
...options,
|
|
11
|
+
};
|
|
12
|
+
|
|
13
|
+
this.logger = createLogger([this.constructor.name, this.eventId]);
|
|
14
|
+
this.storage = null;
|
|
15
|
+
this.svg = null;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
init(dependencies) {
|
|
19
|
+
this.storage = dependencies.storage;
|
|
20
|
+
return this;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
setHighlightedSeats(placeIds, color = null) {
|
|
24
|
+
if (!this.highlighted) {
|
|
25
|
+
this.highlighted = [];
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
const seatsToAdd = (placeIds || []).map((id) => ({
|
|
29
|
+
id,
|
|
30
|
+
color: color || this.getDefaultConfig().highlightedSeatColor,
|
|
31
|
+
}));
|
|
32
|
+
|
|
33
|
+
this.highlighted.push(...seatsToAdd);
|
|
34
|
+
return this;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
setCustomConfig(config) {
|
|
38
|
+
this.config = {
|
|
39
|
+
...this.getDefaultConfig(),
|
|
40
|
+
...config,
|
|
41
|
+
};
|
|
42
|
+
return this;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
getDefaultConfig() {
|
|
46
|
+
return {
|
|
47
|
+
// Basic rendering options
|
|
48
|
+
outputWidth: 2048,
|
|
49
|
+
includeDetailedAttributes: false,
|
|
50
|
+
logFullError: true,
|
|
51
|
+
|
|
52
|
+
// Colors
|
|
53
|
+
seatColor: "#0557ae",
|
|
54
|
+
nonAvSeatColor: "#dadcde",
|
|
55
|
+
highlightedSeatColor: "#d0006f",
|
|
56
|
+
GAColor: "#61c6c2",
|
|
57
|
+
nonAvGAColor: "#b4babe",
|
|
58
|
+
grayedOutSection: "#a3a3a3",
|
|
59
|
+
|
|
60
|
+
// Text styling
|
|
61
|
+
labelColor: "#000",
|
|
62
|
+
labelColorOnGA: "#fff",
|
|
63
|
+
textOpacity: 0.8,
|
|
64
|
+
textOpacityOnGA: 1,
|
|
65
|
+
fontSize: "1em",
|
|
66
|
+
font: "sans-serif",
|
|
67
|
+
useBig: true,
|
|
68
|
+
|
|
69
|
+
// Rendering behavior
|
|
70
|
+
dontRenderUnusedSections: false,
|
|
71
|
+
dontRenderSeats: false,
|
|
72
|
+
seatsAsPins: false,
|
|
73
|
+
renderRowBlocks: false,
|
|
74
|
+
increaseHighlightedSeatSize: 6,
|
|
75
|
+
};
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
async render() {
|
|
79
|
+
throw new Error("render() method must be implemented by subclass");
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
async renderSeatmap(priceData) {
|
|
83
|
+
// Store price data for subclasses to use
|
|
84
|
+
this.priceData = priceData;
|
|
85
|
+
throw new Error("renderSeatmap() method must be implemented by subclass");
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
createElement(tag, attrs, body = "") {
|
|
89
|
+
return utils.createElement(tag, attrs, body);
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
export default BaseRenderer;
|