@necrolab/dashboard 0.5.23 → 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 -2
- package/src/components/Editors/Account/Account.vue +1 -1
- package/src/components/Editors/Profile/CreateProfile.vue +11 -11
- 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 +157 -15
- package/src/libs/tm-renderer/axs/renderer.js +2 -2
- package/src/libs/tm-renderer/axs/requests.js +5 -5
- package/src/libs/tm-renderer/base-renderer.js +0 -78
- package/src/libs/tm-renderer/factory.js +5 -12
- package/src/libs/tm-renderer/index.js +0 -18
- package/src/libs/tm-renderer/request-utils.js +7 -23
- package/src/libs/tm-renderer/tm/renderer.js +3 -3
- package/src/libs/tm-renderer/tm/requests.js +6 -6
- package/src/views/FilterBuilder.vue +17 -1
- package/vite.config.js +1 -3
- package/src/libs/tm-renderer/dependencies/node.persist.js +0 -100
- package/src/libs/tm-renderer/dependencies/persist.js +0 -23
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@necrolab/dashboard",
|
|
3
|
-
"version": "0.5.
|
|
3
|
+
"version": "0.5.24",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"scripts": {
|
|
6
6
|
"build": "rm -rf dist && vite build && npx workbox-cli generateSW workbox-config.cjs",
|
|
@@ -9,7 +9,9 @@
|
|
|
9
9
|
"expose": "node dev-server.js",
|
|
10
10
|
"postinstall": "node postinstall.js",
|
|
11
11
|
"preview": "vite preview",
|
|
12
|
-
"lint": "npx eslint src/ --fix"
|
|
12
|
+
"lint": "npx eslint src/ --fix",
|
|
13
|
+
"release": "npm version patch && npm i && npm publish",
|
|
14
|
+
"release:minor": "npm version minor && npm i && npm publish"
|
|
13
15
|
},
|
|
14
16
|
"dependencies": {
|
|
15
17
|
"@msgpack/msgpack": "^3.0.0-beta2",
|
|
@@ -8,8 +8,17 @@
|
|
|
8
8
|
|
|
9
9
|
<div>
|
|
10
10
|
<div class="my-3 grid grid-cols-12 gap-3">
|
|
11
|
+
<!-- Country chooser -->
|
|
12
|
+
<FormField label="Country" :icon="StadiumIcon" z-index="0" class="col-span-4 md:col-span-2" noWrapper>
|
|
13
|
+
<ProfileCountryChooser
|
|
14
|
+
class="h-10"
|
|
15
|
+
:value="formProfile.country"
|
|
16
|
+
:onClick="chooseCountry"
|
|
17
|
+
:disabled="true" />
|
|
18
|
+
</FormField>
|
|
19
|
+
|
|
11
20
|
<!-- Profile tag -->
|
|
12
|
-
<FormField label="Profile Tag" :icon="TagIcon" z-index="0" class="col-span-
|
|
21
|
+
<FormField label="Profile Tag" :icon="TagIcon" z-index="0" class="col-span-8 md:col-span-4" noWrapper>
|
|
13
22
|
<Dropdown
|
|
14
23
|
:class="`input-default dropdown w-full`"
|
|
15
24
|
:default="ui.profile.tags[0]"
|
|
@@ -19,7 +28,7 @@
|
|
|
19
28
|
</FormField>
|
|
20
29
|
|
|
21
30
|
<!-- Card Number -->
|
|
22
|
-
<FormField label="Card Number" :icon="CartIcon" :error="errors.includes('cardNumber')" z-index="0" class="col-span-12 md:col-span-
|
|
31
|
+
<FormField label="Card Number" :icon="CartIcon" :error="errors.includes('cardNumber')" z-index="0" class="col-span-12 md:col-span-6">
|
|
23
32
|
<input
|
|
24
33
|
ref="cardNumberInput"
|
|
25
34
|
placeholder="Enter card number"
|
|
@@ -30,15 +39,6 @@
|
|
|
30
39
|
@focus="formatCardNumberDisplay" />
|
|
31
40
|
</FormField>
|
|
32
41
|
|
|
33
|
-
<!-- Country chooser -->
|
|
34
|
-
<FormField label="Country" :icon="StadiumIcon" z-index="0" class="col-span-12 md:col-span-2" noWrapper>
|
|
35
|
-
<ProfileCountryChooser
|
|
36
|
-
class="h-10"
|
|
37
|
-
:value="formProfile.country"
|
|
38
|
-
:onClick="chooseCountry"
|
|
39
|
-
:disabled="true" />
|
|
40
|
-
</FormField>
|
|
41
|
-
|
|
42
42
|
<!-- Exp Year -->
|
|
43
43
|
<FormField label="Expiry Year" :icon="TimerIcon" :error="errors.includes('expYear')" z-index="0" class="col-span-6 md:col-span-5" noWrapper>
|
|
44
44
|
<Dropdown
|
|
@@ -1,24 +1,26 @@
|
|
|
1
1
|
<template>
|
|
2
2
|
<Row
|
|
3
|
-
class="relative grid-cols-10 gap-2 text-white lg:grid-cols-12
|
|
3
|
+
class="relative min-h-full grid-cols-10 gap-2 text-white lg:grid-cols-12"
|
|
4
4
|
@click="ui.setOpenContextMenu('')"
|
|
5
5
|
@dblclick="handleDoubleClick"
|
|
6
6
|
@touchstart="handleTouchStart"
|
|
7
7
|
@touchend="handleTouchEnd">
|
|
8
|
-
<div class="col-span-1 flex items-center justify-start lg:col-span-2
|
|
8
|
+
<div class="col-span-1 flex items-center justify-start py-2 lg:col-span-2">
|
|
9
9
|
<Checkbox
|
|
10
10
|
class="ml-2 mr-4 flex-shrink-0"
|
|
11
11
|
:toggled="props.task.selected"
|
|
12
12
|
@valueUpdate="ui.toggleTaskSelected(props.task.taskId)" />
|
|
13
13
|
<div
|
|
14
14
|
v-if="props.preferEventName && props.task.eventName"
|
|
15
|
-
class="hidden
|
|
15
|
+
class="hidden min-w-0 max-w-full cursor-pointer flex-col justify-center gap-0.5 lg:flex"
|
|
16
16
|
@click="copy(props.task.eventId, 'Copied event ID')"
|
|
17
17
|
:title="`Event ID: ${props.task.eventId}`">
|
|
18
|
-
<div class="max-w-45
|
|
18
|
+
<div class="max-w-45 text-xs+ truncate font-semibold leading-tight text-white">
|
|
19
19
|
{{ props.task.eventName }}
|
|
20
20
|
</div>
|
|
21
|
-
<EventDetailRow
|
|
21
|
+
<EventDetailRow
|
|
22
|
+
:content="[props.task.venueName, props.task.eventCity].filter(Boolean).join(', ')"
|
|
23
|
+
truncate>
|
|
22
24
|
<template #icon>
|
|
23
25
|
<path d="M21 10c0 7-9 13-9 13s-9-6-9-13a9 9 0 0 1 18 0z"></path>
|
|
24
26
|
<circle cx="12" cy="10" r="3"></circle>
|
|
@@ -32,26 +34,46 @@
|
|
|
32
34
|
<line x1="3" y1="10" x2="21" y2="10"></line>
|
|
33
35
|
</template>
|
|
34
36
|
</EventDetailRow>
|
|
35
|
-
<div v-if="props.task.email" class="
|
|
36
|
-
<svg
|
|
37
|
+
<div v-if="props.task.email" class="text-3xs leading-tight-sm min-h-2.75 flex items-start gap-1">
|
|
38
|
+
<svg
|
|
39
|
+
class="icon-sm"
|
|
40
|
+
width="10"
|
|
41
|
+
height="10"
|
|
42
|
+
fill="none"
|
|
43
|
+
stroke="currentColor"
|
|
44
|
+
stroke-width="2"
|
|
45
|
+
viewBox="0 0 24 24">
|
|
37
46
|
<path d="M4 4h16c1.1 0 2 .9 2 2v12c0 1.1-.9 2-2 2H4c-1.1 0-2-.9-2-2V6c0-1.1.9-2 2-2z"></path>
|
|
38
47
|
<polyline points="22,6 12,13 2,6"></polyline>
|
|
39
48
|
</svg>
|
|
40
|
-
<span class="
|
|
49
|
+
<span class="text-3xs leading-tight-sm truncate text-light-500">{{ props.task.email }}</span>
|
|
41
50
|
</div>
|
|
42
51
|
</div>
|
|
43
52
|
<div
|
|
44
53
|
v-else
|
|
45
|
-
class="hidden
|
|
46
|
-
@click="
|
|
47
|
-
|
|
48
|
-
|
|
54
|
+
class="hidden min-w-0 max-w-full cursor-pointer flex-col justify-center gap-0.5 lg:flex"
|
|
55
|
+
@click="
|
|
56
|
+
copy(
|
|
57
|
+
props.task.eventId || props.task.email,
|
|
58
|
+
props.task.eventId ? 'Copied event ID' : 'Copied email'
|
|
59
|
+
)
|
|
60
|
+
">
|
|
61
|
+
<div
|
|
62
|
+
class="flex-gap-1 text-xs+ max-w-45 leading-tight-md items-center truncate font-semibold text-white">
|
|
63
|
+
<svg
|
|
64
|
+
class="icon-sm"
|
|
65
|
+
width="11"
|
|
66
|
+
height="11"
|
|
67
|
+
fill="none"
|
|
68
|
+
stroke="currentColor"
|
|
69
|
+
stroke-width="2.5"
|
|
70
|
+
viewBox="0 0 24 24">
|
|
49
71
|
<path d="M9 11l3 3L22 4"></path>
|
|
50
72
|
<path d="M21 12v7a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h11"></path>
|
|
51
73
|
</svg>
|
|
52
74
|
<span>{{ props.task.eventId || props.task.email }}</span>
|
|
53
75
|
</div>
|
|
54
|
-
<EventDetailRow v-if="props.task.eventId
|
|
76
|
+
<EventDetailRow v-if="!props.task.eventId" :content="props.task.email" truncate>
|
|
55
77
|
<template #icon>
|
|
56
78
|
<path d="M4 4h16c1.1 0 2 .9 2 2v12c0 1.1-.9 2-2 2H4c-1.1 0-2-.9-2-2V6c0-1.1.9-2 2-2z"></path>
|
|
57
79
|
<polyline points="22,6 12,13 2,6"></polyline>
|
|
@@ -60,19 +82,29 @@
|
|
|
60
82
|
</div>
|
|
61
83
|
</div>
|
|
62
84
|
<div class="col-span-2 overflow-hidden lg:col-span-3 xl:col-span-2">
|
|
63
|
-
<h4 class="text-
|
|
85
|
+
<h4 class="lg:text-2xs text-center text-xs leading-tight text-light-300">
|
|
64
86
|
<span v-if="!props.task.reservedTicketsList">-</span>
|
|
65
87
|
<div v-else class="overflow-hidden">
|
|
66
|
-
<div
|
|
88
|
+
<div
|
|
89
|
+
v-for="(l, index) in props.task.reservedTicketsList.split('\n')"
|
|
90
|
+
:key="`ticket-${index}`"
|
|
91
|
+
class="truncate">
|
|
67
92
|
<span
|
|
68
93
|
v-if="!!l.trim()"
|
|
69
94
|
class="text-xs"
|
|
70
|
-
:class="{
|
|
71
|
-
|
|
95
|
+
:class="{
|
|
96
|
+
'font-bold text-green-400': isTotalPrice(
|
|
97
|
+
l,
|
|
98
|
+
index,
|
|
99
|
+
props.task.reservedTicketsList.split(' ')
|
|
100
|
+
)
|
|
101
|
+
}">
|
|
102
|
+
{{ l.trim() }}
|
|
103
|
+
</span>
|
|
72
104
|
</div>
|
|
73
105
|
</div>
|
|
74
106
|
<span
|
|
75
|
-
class="
|
|
107
|
+
class="mt-1 block text-xs font-bold"
|
|
76
108
|
:class="{
|
|
77
109
|
'text-red-400':
|
|
78
110
|
props.task._timeLeftString === '00:00' || props.task._timeLeftString === 'No Cartholds'
|
|
@@ -84,7 +116,7 @@
|
|
|
84
116
|
<div class="col-span-6 justify-center text-center md:col-span-5 lg:col-span-4 xl:col-span-5">
|
|
85
117
|
<div class="task-status-badge">
|
|
86
118
|
<div
|
|
87
|
-
class="
|
|
119
|
+
class="h-2 w-2 flex-shrink-0 rounded-full"
|
|
88
120
|
:class="
|
|
89
121
|
colorToClass(
|
|
90
122
|
props.task.active || props.task.status.toLowerCase() === 'idle'
|
|
@@ -92,10 +124,14 @@
|
|
|
92
124
|
: 'red'
|
|
93
125
|
)
|
|
94
126
|
"></div>
|
|
95
|
-
<span
|
|
127
|
+
<span
|
|
128
|
+
class="max-md:text-xs max-md:max-w-full max-sm:tracking-normal truncate text-sm font-medium uppercase tracking-wide text-light-300">
|
|
129
|
+
{{ props.task.status }}
|
|
130
|
+
</span>
|
|
96
131
|
</div>
|
|
97
132
|
</div>
|
|
98
|
-
<div
|
|
133
|
+
<div
|
|
134
|
+
class="max-sm:min-w-0 max-sm:flex-shrink-0 max-sm:items-center max-sm:justify-end max-sm:px-0.5 col-span-1 flex overflow-visible md:col-span-2 lg:col-span-3">
|
|
99
135
|
<ActionButtonGroup class="overflow-visible" :allowWrap="true">
|
|
100
136
|
<li>
|
|
101
137
|
<button v-if="task.active" @click="ui.stopTask(task.taskId)" aria-label="Stop task">
|
|
@@ -127,8 +163,10 @@
|
|
|
127
163
|
</li>
|
|
128
164
|
</ActionButtonGroup>
|
|
129
165
|
</div>
|
|
130
|
-
<div
|
|
131
|
-
|
|
166
|
+
<div
|
|
167
|
+
class="max-sm:left-16 max-sm:top-1 max-sm:z-10 absolute right-5 top-4 col-span-1 hidden items-center justify-center xl:flex">
|
|
168
|
+
<h4
|
|
169
|
+
class="bg-dark-475/40 text-2xs lg:text-2xs m-0 rounded border border-dark-500 px-1.5 py-0.5 text-center text-xs font-semibold tracking-wide text-light-500">
|
|
132
170
|
{{ props.task.taskId }}
|
|
133
171
|
</h4>
|
|
134
172
|
</div>
|
|
@@ -137,7 +175,7 @@
|
|
|
137
175
|
<div
|
|
138
176
|
v-if="ui.openContextMenu === task.taskId"
|
|
139
177
|
ref="contextMenuRef"
|
|
140
|
-
class="w-42 context-menu z-max
|
|
178
|
+
class="w-42 context-menu fixed z-max grid grid-cols-1 gap-1 rounded-lg border border-dark-650 bg-dark-500 p-1 text-white shadow-xl"
|
|
141
179
|
:style="contextMenuPosition">
|
|
142
180
|
<button class="btn-primary" @click="openInNewTab(`${ui.currentCountry.url}/event/${task.eventId}`)">
|
|
143
181
|
Open Event
|
|
@@ -237,10 +275,9 @@ onUnmounted(() => {
|
|
|
237
275
|
document.removeEventListener("click", handleClickOutside);
|
|
238
276
|
});
|
|
239
277
|
|
|
240
|
-
|
|
241
278
|
const openViewTaskModal = () => {
|
|
242
279
|
ui.selectedTaskForView = props.task;
|
|
243
|
-
ui.toggleModal(
|
|
280
|
+
ui.toggleModal("view-task");
|
|
244
281
|
};
|
|
245
282
|
|
|
246
283
|
const openInBrowser = (debug) => {
|
|
@@ -25,7 +25,7 @@ const disabledClasses = "bg-red-500/20 border-red-500/30";
|
|
|
25
25
|
<template>
|
|
26
26
|
<div
|
|
27
27
|
:class="[
|
|
28
|
-
'flex-center rounded-full border-2 transition-all duration-200',
|
|
28
|
+
'flex items-center justify-center rounded-full border-2 transition-all duration-200',
|
|
29
29
|
enabled ? enabledClasses : disabledClasses,
|
|
30
30
|
sizeClasses[size]
|
|
31
31
|
]">
|
|
@@ -19,7 +19,9 @@ export function useFilterCSS(filterBuilder, svg) {
|
|
|
19
19
|
document.head.appendChild(styleElement);
|
|
20
20
|
}
|
|
21
21
|
|
|
22
|
-
|
|
22
|
+
// Only use cssClasses now (permanent filter colors)
|
|
23
|
+
// temporaryCSS is no longer used - hover uses DOM classes instead
|
|
24
|
+
const combinedCSS = filterBuilder.value.cssClasses;
|
|
23
25
|
if (styleElement.textContent !== combinedCSS) {
|
|
24
26
|
styleElement.textContent = combinedCSS;
|
|
25
27
|
}
|
|
@@ -35,17 +37,9 @@ export function useFilterCSS(filterBuilder, svg) {
|
|
|
35
37
|
});
|
|
36
38
|
};
|
|
37
39
|
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
nextTick(() => injectStyles());
|
|
42
|
-
};
|
|
43
|
-
|
|
44
|
-
const originalClearHighlight = filterBuilder.value.clearHighlight.bind(filterBuilder.value);
|
|
45
|
-
filterBuilder.value.clearHighlight = () => {
|
|
46
|
-
originalClearHighlight();
|
|
47
|
-
nextTick(() => injectStyles());
|
|
48
|
-
};
|
|
40
|
+
// NOTE: highlight() and clearHighlight() now use DOM class manipulation
|
|
41
|
+
// They don't modify CSS, so we don't wrap them to inject styles
|
|
42
|
+
// This eliminates the flicker caused by style element updates
|
|
49
43
|
};
|
|
50
44
|
|
|
51
45
|
const cleanupStyles = () => {
|
package/src/libs/Filter.js
CHANGED
|
@@ -199,6 +199,8 @@ export default class FilterBuilder {
|
|
|
199
199
|
};
|
|
200
200
|
this.cssClasses = "";
|
|
201
201
|
this.temporaryCSS = "";
|
|
202
|
+
this._highlightedElements = null;
|
|
203
|
+
this._currentlyHighlightedSection = null;
|
|
202
204
|
|
|
203
205
|
this.filterTypes = {
|
|
204
206
|
INVALID: -1,
|
|
@@ -252,7 +254,11 @@ export default class FilterBuilder {
|
|
|
252
254
|
}
|
|
253
255
|
|
|
254
256
|
addRowHandlers() {
|
|
255
|
-
|
|
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
|
+
}
|
|
256
262
|
|
|
257
263
|
for (let i = 0; i < this.rows.length; i++) {
|
|
258
264
|
const row = this.rows[i];
|
|
@@ -306,14 +312,14 @@ export default class FilterBuilder {
|
|
|
306
312
|
const sectionKey = `${parsed.section}`;
|
|
307
313
|
|
|
308
314
|
// Skip if already highlighting this section - prevents flickering
|
|
309
|
-
if (lastHighlightedSection === sectionKey) return;
|
|
315
|
+
if (this.lastHighlightedSection === sectionKey) return;
|
|
310
316
|
|
|
311
317
|
// Only clear if switching sections
|
|
312
|
-
if (lastHighlightedSection && lastHighlightedSection !== sectionKey) {
|
|
318
|
+
if (this.lastHighlightedSection && this.lastHighlightedSection !== sectionKey) {
|
|
313
319
|
this.clearHighlight();
|
|
314
320
|
}
|
|
315
321
|
|
|
316
|
-
lastHighlightedSection = sectionKey;
|
|
322
|
+
this.lastHighlightedSection = sectionKey;
|
|
317
323
|
|
|
318
324
|
const matchingFilter = this.filters.find(
|
|
319
325
|
(f) =>
|
|
@@ -328,22 +334,101 @@ export default class FilterBuilder {
|
|
|
328
334
|
else this.highlight({ section: matchingFilter.section });
|
|
329
335
|
};
|
|
330
336
|
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
if (!this.isDragging) this.clearHighlight();
|
|
334
|
-
};
|
|
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)
|
|
335
339
|
}
|
|
336
340
|
}
|
|
337
341
|
|
|
338
342
|
reload(eventId) {
|
|
339
343
|
const paths = document.querySelectorAll("path");
|
|
340
|
-
|
|
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
|
+
});
|
|
341
351
|
|
|
342
352
|
this.addLabelHandlers();
|
|
343
353
|
this.addGAHandlers();
|
|
344
354
|
this.addRowHandlers();
|
|
345
355
|
this.currentEventId = eventId;
|
|
346
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
|
+
|
|
347
432
|
this.updateCss();
|
|
348
433
|
}
|
|
349
434
|
|
|
@@ -494,6 +579,11 @@ export default class FilterBuilder {
|
|
|
494
579
|
this.cssClasses += `.svg-wrapper path[section="${section}"][row="${row}"] {stroke: ${color} !important;}\n`;
|
|
495
580
|
});
|
|
496
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
|
+
|
|
497
587
|
log("Generated CSS:", this.cssClasses);
|
|
498
588
|
}
|
|
499
589
|
|
|
@@ -561,6 +651,36 @@ export default class FilterBuilder {
|
|
|
561
651
|
e.preventDefault();
|
|
562
652
|
labelClickHandler();
|
|
563
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
|
+
};
|
|
564
684
|
}
|
|
565
685
|
}
|
|
566
686
|
|
|
@@ -644,21 +764,43 @@ export default class FilterBuilder {
|
|
|
644
764
|
highlight(filter, isGA = false) {
|
|
645
765
|
if (filter.row && this.isUnselectable(filter.sec, filter.row)) return;
|
|
646
766
|
|
|
647
|
-
|
|
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
|
+
}
|
|
772
|
+
|
|
773
|
+
// Clear previous highlight first
|
|
774
|
+
this.clearHighlight();
|
|
775
|
+
this._currentlyHighlightedSection = sectionKey;
|
|
648
776
|
|
|
777
|
+
// Use direct DOM manipulation instead of CSS injection
|
|
778
|
+
// This is MUCH faster and prevents flicker
|
|
649
779
|
if (isGA) {
|
|
650
780
|
if (this.filters.find((f) => f.section === filter.name)) return;
|
|
651
|
-
|
|
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);
|
|
652
784
|
} else if (filter.row) {
|
|
653
|
-
|
|
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);
|
|
654
788
|
} else {
|
|
655
|
-
|
|
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);
|
|
656
792
|
}
|
|
657
|
-
this.temporaryCSS += newCSS;
|
|
658
793
|
}
|
|
659
794
|
|
|
660
795
|
clearHighlight() {
|
|
661
|
-
|
|
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;
|
|
662
804
|
}
|
|
663
805
|
|
|
664
806
|
isUnselectable(section, row) {
|
|
@@ -82,11 +82,11 @@ class AXSRenderer extends BaseRenderer {
|
|
|
82
82
|
}
|
|
83
83
|
|
|
84
84
|
async getEventData() {
|
|
85
|
-
return await getEventData(this.eventId, this.
|
|
85
|
+
return await getEventData(this.eventId, this.logger);
|
|
86
86
|
}
|
|
87
87
|
|
|
88
88
|
async getEventDataUrl() {
|
|
89
|
-
return await getEventDataUrl(this.eventId, this.
|
|
89
|
+
return await getEventDataUrl(this.eventId, this.logger);
|
|
90
90
|
}
|
|
91
91
|
|
|
92
92
|
async fetchBackgroundImage() {
|
|
@@ -9,7 +9,7 @@ function buildAXSEventDataUrl(eventId, data) {
|
|
|
9
9
|
return `${base}maps/main/master_full.json?Policy=${policy}_&Signature=${sig}&Key-Pair-Id=${keypair}&v=${version}`;
|
|
10
10
|
}
|
|
11
11
|
|
|
12
|
-
async function getEventDataUrl(eventId,
|
|
12
|
+
async function getEventDataUrl(eventId, logger) {
|
|
13
13
|
const url = `https://contentdistribution.3ddvapis.com/api/v1/dvm/token/venue/${eventId}`;
|
|
14
14
|
const customHeaders = {
|
|
15
15
|
"referer": "https://tickets-de.axs.com/",
|
|
@@ -17,17 +17,17 @@ async function getEventDataUrl(eventId, proxy, logger) {
|
|
|
17
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
18
|
};
|
|
19
19
|
|
|
20
|
-
return await getJsonDataWithHeaders(url, customHeaders,
|
|
20
|
+
return await getJsonDataWithHeaders(url, customHeaders, 0, logger);
|
|
21
21
|
}
|
|
22
22
|
|
|
23
|
-
async function getEventData(eventId,
|
|
24
|
-
const urlData = await getEventDataUrl(eventId,
|
|
23
|
+
async function getEventData(eventId, logger) {
|
|
24
|
+
const urlData = await getEventDataUrl(eventId, logger);
|
|
25
25
|
if (!urlData) {
|
|
26
26
|
throw new Error("Failed to get event data URL");
|
|
27
27
|
}
|
|
28
28
|
|
|
29
29
|
const url = buildAXSEventDataUrl(eventId, urlData);
|
|
30
|
-
return await getJsonDataWithHeaders(url, {},
|
|
30
|
+
return await getJsonDataWithHeaders(url, {}, 0, logger);
|
|
31
31
|
}
|
|
32
32
|
|
|
33
33
|
export {
|
|
@@ -12,16 +12,11 @@ class BaseRenderer {
|
|
|
12
12
|
|
|
13
13
|
this.logger = createLogger([this.constructor.name, this.eventId]);
|
|
14
14
|
this.storage = null;
|
|
15
|
-
this.sharp = null;
|
|
16
|
-
this.fs = null;
|
|
17
15
|
this.svg = null;
|
|
18
|
-
this.bgImg = null;
|
|
19
16
|
}
|
|
20
17
|
|
|
21
18
|
init(dependencies) {
|
|
22
19
|
this.storage = dependencies.storage;
|
|
23
|
-
this.sharp = dependencies.sharp;
|
|
24
|
-
this.fs = dependencies.fs;
|
|
25
20
|
return this;
|
|
26
21
|
}
|
|
27
22
|
|
|
@@ -90,82 +85,9 @@ class BaseRenderer {
|
|
|
90
85
|
throw new Error("renderSeatmap() method must be implemented by subclass");
|
|
91
86
|
}
|
|
92
87
|
|
|
93
|
-
async outputToSVG(path) {
|
|
94
|
-
if (!this.svg || !this.fs) {
|
|
95
|
-
this.logger.Error("No SVG data available or fs not initialized");
|
|
96
|
-
return this;
|
|
97
|
-
}
|
|
98
|
-
this.fs.writeFileSync(path, this.svg);
|
|
99
|
-
return this;
|
|
100
|
-
}
|
|
101
|
-
|
|
102
|
-
async outputToPNG(filePath) {
|
|
103
|
-
if (!this.svg || !this.sharp) {
|
|
104
|
-
this.logger.Error("No SVG data available or sharp not initialized");
|
|
105
|
-
return this;
|
|
106
|
-
}
|
|
107
|
-
const s = await this._png();
|
|
108
|
-
await s.toFile(filePath);
|
|
109
|
-
return this;
|
|
110
|
-
}
|
|
111
|
-
|
|
112
|
-
async outputToPNGBuffer() {
|
|
113
|
-
if (!this.svg || !this.sharp) {
|
|
114
|
-
return Buffer.from([0x00]);
|
|
115
|
-
}
|
|
116
|
-
const s = await this._png();
|
|
117
|
-
return s.toBuffer();
|
|
118
|
-
}
|
|
119
|
-
|
|
120
|
-
async _png() {
|
|
121
|
-
let background = await this.storage.getItem(`renderer.background.${this.eventId}`);
|
|
122
|
-
if (!background) {
|
|
123
|
-
try {
|
|
124
|
-
background = await this._fetchSaveBgImage();
|
|
125
|
-
} catch (e) {
|
|
126
|
-
this.logger.Error(e.message);
|
|
127
|
-
}
|
|
128
|
-
} else if (background) {
|
|
129
|
-
if (typeof background === "string" && background.startsWith("{")) {
|
|
130
|
-
background = JSON.parse(background);
|
|
131
|
-
}
|
|
132
|
-
background = Buffer.from(background);
|
|
133
|
-
const bgData = await this.sharp(background).metadata();
|
|
134
|
-
if (bgData.width !== this.config.outputWidth) {
|
|
135
|
-
this.config.outputWidth = bgData.width;
|
|
136
|
-
}
|
|
137
|
-
}
|
|
138
|
-
|
|
139
|
-
const svgBuffer = Buffer.from(this.svg);
|
|
140
|
-
const png = await this.sharp(svgBuffer).png().resize(this.config.outputWidth).toBuffer();
|
|
141
|
-
|
|
142
|
-
if (background) {
|
|
143
|
-
const foreground = await this.sharp(png).toBuffer();
|
|
144
|
-
return this.sharp(background).composite([
|
|
145
|
-
{
|
|
146
|
-
input: foreground,
|
|
147
|
-
gravity: "southeast",
|
|
148
|
-
},
|
|
149
|
-
]);
|
|
150
|
-
} else {
|
|
151
|
-
return this.sharp(png);
|
|
152
|
-
}
|
|
153
|
-
}
|
|
154
|
-
|
|
155
|
-
async _fetchSaveBgImage() {
|
|
156
|
-
const bgImgData = await this.fetchBackgroundImage();
|
|
157
|
-
const background = await this.sharp(bgImgData).png().resize(this.config.outputWidth).toBuffer();
|
|
158
|
-
await this.storage.setItem(`renderer.background.${this.eventId}`, background);
|
|
159
|
-
return background;
|
|
160
|
-
}
|
|
161
|
-
|
|
162
88
|
createElement(tag, attrs, body = "") {
|
|
163
89
|
return utils.createElement(tag, attrs, body);
|
|
164
90
|
}
|
|
165
|
-
|
|
166
|
-
async fetchBackgroundImage() {
|
|
167
|
-
throw new Error("fetchBackgroundImage() method must be implemented by subclass");
|
|
168
|
-
}
|
|
169
91
|
}
|
|
170
92
|
|
|
171
93
|
export default BaseRenderer;
|
|
@@ -1,14 +1,15 @@
|
|
|
1
1
|
import TMRenderer from "./tm/renderer.js";
|
|
2
2
|
import AXSRenderer from "./axs/renderer.js";
|
|
3
|
+
import WebPersistance from "./dependencies/web.persist.js";
|
|
3
4
|
import { createLogger } from "./dependencies/logger.js";
|
|
4
5
|
|
|
5
6
|
class RendererFactory {
|
|
6
7
|
constructor() {
|
|
7
8
|
this.logger = createLogger("RENDERER-FACTORY");
|
|
9
|
+
this.storage = null;
|
|
8
10
|
}
|
|
11
|
+
|
|
9
12
|
async init() {
|
|
10
|
-
// Browser-only version - always use WebPersistance
|
|
11
|
-
const { default: WebPersistance } = await import("./dependencies/web.persist.js");
|
|
12
13
|
this.storage = new WebPersistance();
|
|
13
14
|
this.logger.Info("Renderer initialized for browser");
|
|
14
15
|
}
|
|
@@ -20,11 +21,7 @@ class RendererFactory {
|
|
|
20
21
|
}
|
|
21
22
|
|
|
22
23
|
const renderer = new TMRenderer(eventId, options);
|
|
23
|
-
renderer.init({
|
|
24
|
-
storage: this.storage,
|
|
25
|
-
sharp: this.sharp,
|
|
26
|
-
fs: this.fs,
|
|
27
|
-
});
|
|
24
|
+
renderer.init({ storage: this.storage });
|
|
28
25
|
return renderer;
|
|
29
26
|
}
|
|
30
27
|
|
|
@@ -35,11 +32,7 @@ class RendererFactory {
|
|
|
35
32
|
}
|
|
36
33
|
|
|
37
34
|
const renderer = new AXSRenderer(eventId, options);
|
|
38
|
-
renderer.init({
|
|
39
|
-
storage: this.storage,
|
|
40
|
-
sharp: this.sharp,
|
|
41
|
-
fs: this.fs,
|
|
42
|
-
});
|
|
35
|
+
renderer.init({ storage: this.storage });
|
|
43
36
|
return renderer;
|
|
44
37
|
}
|
|
45
38
|
}
|
|
@@ -1,21 +1,3 @@
|
|
|
1
1
|
import RendererFactory from "./factory.js";
|
|
2
2
|
|
|
3
|
-
if (typeof process !== "undefined" && process.env) {
|
|
4
|
-
process.env.NODE_TLS_REJECT_UNAUTHORIZED = "0";
|
|
5
|
-
|
|
6
|
-
if (process.removeAllListeners) {
|
|
7
|
-
process.removeAllListeners("warning");
|
|
8
|
-
}
|
|
9
|
-
|
|
10
|
-
if (process.emitWarning) {
|
|
11
|
-
const originalEmitWarning = process.emitWarning;
|
|
12
|
-
process.emitWarning = function (warning, type, code, ctor) {
|
|
13
|
-
const suppressedTypes = ["ExperimentalWarning", "DeprecationWarning", "UnhandledPromiseRejectionWarning"];
|
|
14
|
-
if (suppressedTypes.includes(type)) return;
|
|
15
|
-
|
|
16
|
-
return originalEmitWarning.call(process, warning, type, code, ctor);
|
|
17
|
-
};
|
|
18
|
-
}
|
|
19
|
-
}
|
|
20
|
-
|
|
21
3
|
export default RendererFactory;
|
|
@@ -21,22 +21,12 @@ const headers = {
|
|
|
21
21
|
"accept-language": "en-US,en;q=0.9",
|
|
22
22
|
};
|
|
23
23
|
|
|
24
|
-
async function
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
async function getFetchOptions(targetUrl, proxy = null, customHeaders = {}) {
|
|
30
|
-
// Browser-only version with minimal headers for maximum compatibility
|
|
31
|
-
const options = {
|
|
32
|
-
headers: {
|
|
33
|
-
...customHeaders,
|
|
34
|
-
},
|
|
24
|
+
async function getFetchOptions(targetUrl, customHeaders = {}) {
|
|
25
|
+
return {
|
|
26
|
+
headers: { ...customHeaders },
|
|
35
27
|
mode: "cors",
|
|
36
28
|
cache: "default"
|
|
37
29
|
};
|
|
38
|
-
|
|
39
|
-
return options;
|
|
40
30
|
}
|
|
41
31
|
|
|
42
32
|
async function makeRequest(url, options = {}, retries = 0, logger = null) {
|
|
@@ -70,26 +60,20 @@ async function getJsonData(url, options = {}, retries = 0, logger = null) {
|
|
|
70
60
|
async function getBinaryData(url, options = {}, retries = 0, logger = null) {
|
|
71
61
|
const response = await makeRequest(url, options, retries, logger);
|
|
72
62
|
const arrayBuffer = await response.arrayBuffer();
|
|
73
|
-
|
|
74
|
-
// Use Buffer in Node.js, Uint8Array in browser
|
|
75
|
-
if (typeof Buffer !== 'undefined') {
|
|
76
|
-
return Buffer.from(arrayBuffer);
|
|
77
|
-
}
|
|
78
63
|
return new Uint8Array(arrayBuffer);
|
|
79
64
|
}
|
|
80
65
|
|
|
81
|
-
async function getJsonDataWithHeaders(url, customHeaders = {},
|
|
82
|
-
const options = await getFetchOptions(url,
|
|
66
|
+
async function getJsonDataWithHeaders(url, customHeaders = {}, retries = 0, logger = null) {
|
|
67
|
+
const options = await getFetchOptions(url, customHeaders);
|
|
83
68
|
return await getJsonData(url, options, retries, logger);
|
|
84
69
|
}
|
|
85
70
|
|
|
86
|
-
async function getBinaryDataWithHeaders(url, customHeaders = {},
|
|
87
|
-
const options = await getFetchOptions(url,
|
|
71
|
+
async function getBinaryDataWithHeaders(url, customHeaders = {}, retries = 0, logger = null) {
|
|
72
|
+
const options = await getFetchOptions(url, customHeaders);
|
|
88
73
|
return await getBinaryData(url, options, retries, logger);
|
|
89
74
|
}
|
|
90
75
|
|
|
91
76
|
export {
|
|
92
|
-
createProxyAgent,
|
|
93
77
|
getFetchOptions,
|
|
94
78
|
makeRequest,
|
|
95
79
|
getJsonData,
|
|
@@ -550,18 +550,18 @@ class TMRenderer extends BaseRenderer {
|
|
|
550
550
|
|
|
551
551
|
// Request methods
|
|
552
552
|
async getEventData() {
|
|
553
|
-
return await getEventData(this.eventId, this.options.country, this.
|
|
553
|
+
return await getEventData(this.eventId, this.options.country, this.logger);
|
|
554
554
|
}
|
|
555
555
|
|
|
556
556
|
async getSeatData() {
|
|
557
|
-
return await getSeatData(this.eventId, this.options.country, this.
|
|
557
|
+
return await getSeatData(this.eventId, this.options.country, this.logger);
|
|
558
558
|
}
|
|
559
559
|
|
|
560
560
|
async fetchBackgroundImage() {
|
|
561
561
|
if (!this.bgImg) {
|
|
562
562
|
throw new Error("Background image URL not available");
|
|
563
563
|
}
|
|
564
|
-
return await fetchBackgroundImage(this.bgImg, this.
|
|
564
|
+
return await fetchBackgroundImage(this.bgImg, this.logger);
|
|
565
565
|
}
|
|
566
566
|
}
|
|
567
567
|
|
|
@@ -24,18 +24,18 @@ function buildTMSeatDataUrl(eventId, country = null) {
|
|
|
24
24
|
return url;
|
|
25
25
|
}
|
|
26
26
|
|
|
27
|
-
async function getEventData(eventId, country,
|
|
27
|
+
async function getEventData(eventId, country, logger) {
|
|
28
28
|
const url = buildTMEventDataUrl(eventId, country);
|
|
29
|
-
return await getJsonDataWithHeaders(url, {},
|
|
29
|
+
return await getJsonDataWithHeaders(url, {}, 0, logger);
|
|
30
30
|
}
|
|
31
31
|
|
|
32
|
-
async function getSeatData(eventId, country,
|
|
32
|
+
async function getSeatData(eventId, country, logger) {
|
|
33
33
|
const url = buildTMSeatDataUrl(eventId, country);
|
|
34
|
-
return await getJsonDataWithHeaders(url, {},
|
|
34
|
+
return await getJsonDataWithHeaders(url, {}, 0, logger);
|
|
35
35
|
}
|
|
36
36
|
|
|
37
|
-
async function fetchBackgroundImage(bgImgUrl,
|
|
38
|
-
return await getBinaryDataWithHeaders(bgImgUrl, {},
|
|
37
|
+
async function fetchBackgroundImage(bgImgUrl, logger) {
|
|
38
|
+
return await getBinaryDataWithHeaders(bgImgUrl, {}, 0, logger);
|
|
39
39
|
}
|
|
40
40
|
|
|
41
41
|
export {
|
|
@@ -177,7 +177,23 @@ const panzoomOptions = {
|
|
|
177
177
|
bounds: true,
|
|
178
178
|
boundsPadding: 0.1, // Add padding to ensure it stays within bounds
|
|
179
179
|
transformOrigin: { x: 0.5, y: 0.5 }, // Center the zoom
|
|
180
|
-
contain: true // Force the image to stay within the parent container
|
|
180
|
+
contain: true, // Force the image to stay within the parent container
|
|
181
|
+
beforeMouseDown: function(e) {
|
|
182
|
+
// Don't intercept clicks on interactive SVG paths - let them handle clicks directly
|
|
183
|
+
const target = e.target;
|
|
184
|
+
if (target && target.tagName && (target.tagName.toLowerCase() === 'path' || target.tagName.toLowerCase() === 'tspan')) {
|
|
185
|
+
return true; // Return true to prevent panzoom from handling this event
|
|
186
|
+
}
|
|
187
|
+
return false; // Allow panzoom to handle other clicks
|
|
188
|
+
},
|
|
189
|
+
onTouch: function(e) {
|
|
190
|
+
// Don't intercept touches on interactive SVG paths - let them handle touches directly
|
|
191
|
+
const target = e.target;
|
|
192
|
+
if (target && target.tagName && (target.tagName.toLowerCase() === 'path' || target.tagName.toLowerCase() === 'tspan')) {
|
|
193
|
+
return false; // Return false to prevent panzoom from handling this touch
|
|
194
|
+
}
|
|
195
|
+
return true; // Allow panzoom to handle other touches
|
|
196
|
+
}
|
|
181
197
|
};
|
|
182
198
|
let panzoomInstance;
|
|
183
199
|
|
package/vite.config.js
CHANGED
|
@@ -10,7 +10,6 @@ export default defineConfig({
|
|
|
10
10
|
cacheDir: "./node_modules/.vite",
|
|
11
11
|
build: {
|
|
12
12
|
rollupOptions: {
|
|
13
|
-
external: ['sharp', 'node-persist', 'undici'],
|
|
14
13
|
output: {
|
|
15
14
|
manualChunks: {
|
|
16
15
|
vue: ["vue", "vue-router", "pinia"],
|
|
@@ -26,8 +25,7 @@ export default defineConfig({
|
|
|
26
25
|
emptyOutDir: false
|
|
27
26
|
},
|
|
28
27
|
optimizeDeps: {
|
|
29
|
-
include: ["vue", "vue-router", "pinia", "@vueuse/core"]
|
|
30
|
-
exclude: ["sharp"]
|
|
28
|
+
include: ["vue", "vue-router", "pinia", "@vueuse/core"]
|
|
31
29
|
},
|
|
32
30
|
define: {
|
|
33
31
|
__APP_VERSION__: JSON.stringify(pkg.version),
|
|
@@ -1,100 +0,0 @@
|
|
|
1
|
-
import { createLogger } from "./logger.js";
|
|
2
|
-
import { sleep } from "../utils.js";
|
|
3
|
-
|
|
4
|
-
// eslint-disable-next-line no-unused-vars
|
|
5
|
-
const logger = createLogger("TM-RENDERER");
|
|
6
|
-
|
|
7
|
-
class NodePersistProxy {
|
|
8
|
-
constructor(db) {
|
|
9
|
-
this.db = db;
|
|
10
|
-
this.retryLimit = 3;
|
|
11
|
-
this.sleepTime = 1000;
|
|
12
|
-
}
|
|
13
|
-
|
|
14
|
-
async init(options) {
|
|
15
|
-
logger.Success("Initialized local DB");
|
|
16
|
-
return this.db.init(options);
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
async keys(retries = 0) {
|
|
20
|
-
if (retries == this.retryLimt) return;
|
|
21
|
-
|
|
22
|
-
try {
|
|
23
|
-
return this.db.keys().catch((ex) => {
|
|
24
|
-
throw ex;
|
|
25
|
-
});
|
|
26
|
-
} catch (ex) {
|
|
27
|
-
logger.Error(`PERSIST: Retrying .keys() (${ex.message})`);
|
|
28
|
-
await sleep(this.sleepTime);
|
|
29
|
-
return this.keys(++retries);
|
|
30
|
-
}
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
async getItem(key, retries = 0) {
|
|
34
|
-
if (!key) return;
|
|
35
|
-
if (retries == this.retryLimt) return;
|
|
36
|
-
|
|
37
|
-
try {
|
|
38
|
-
return this.db.getItem(key).catch(() => {});
|
|
39
|
-
} catch (ex) {
|
|
40
|
-
logger.Error(`PERSIST: Retrying .getItem() (${ex.message})`);
|
|
41
|
-
await sleep(this.sleepTime);
|
|
42
|
-
return this.getItem(key, ++retries);
|
|
43
|
-
}
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
async getMultiple(keys, retries = 0) {
|
|
47
|
-
if (!keys) return;
|
|
48
|
-
if (retries == this.retryLimt) return;
|
|
49
|
-
|
|
50
|
-
try {
|
|
51
|
-
const results = [];
|
|
52
|
-
for (let i = 0; i < keys.length; i++) {
|
|
53
|
-
results.push(await this.getItem(keys[i]));
|
|
54
|
-
}
|
|
55
|
-
return results;
|
|
56
|
-
} catch (ex) {
|
|
57
|
-
logger.Error(`PERSIST: Retrying .getMultiple() (${ex.message})`);
|
|
58
|
-
await sleep(this.sleepTime);
|
|
59
|
-
return this.getMultiple(keys, ++retries);
|
|
60
|
-
}
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
async setItem(key, value, retries = 0) {
|
|
64
|
-
if (retries == this.retryLimt) return;
|
|
65
|
-
|
|
66
|
-
try {
|
|
67
|
-
return this.db.setItem(key, value).catch(() => {});
|
|
68
|
-
} catch (ex) {
|
|
69
|
-
logger.Error(`PERSIST: Retrying .setItem() (${ex.message})`);
|
|
70
|
-
await sleep(this.sleepTime);
|
|
71
|
-
return this.setItem(key, value, ++retries);
|
|
72
|
-
}
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
async removeItem(key, retries = 0) {
|
|
76
|
-
if (retries == this.retryLimt) return;
|
|
77
|
-
|
|
78
|
-
try {
|
|
79
|
-
return this.db.removeItem(key).catch(() => {});
|
|
80
|
-
} catch (ex) {
|
|
81
|
-
logger.Error(`PERSIST: Retrying .removeItem() (${ex.message})`);
|
|
82
|
-
await sleep(this.sleepTime);
|
|
83
|
-
return this.removeItem(key, ++retries);
|
|
84
|
-
}
|
|
85
|
-
}
|
|
86
|
-
|
|
87
|
-
async updateItem(key, value, opts = {}, retries = 0) {
|
|
88
|
-
if (retries == this.retryLimt) return;
|
|
89
|
-
|
|
90
|
-
try {
|
|
91
|
-
return this.db.updateItem(key, value, opts).catch(() => {});
|
|
92
|
-
} catch (ex) {
|
|
93
|
-
logger.Error(`PERSIST: Retrying .updateItem() (${ex.message})`, key, value);
|
|
94
|
-
await sleep(this.sleepTime);
|
|
95
|
-
return this.updateItem(key, opts, value, ++retries);
|
|
96
|
-
}
|
|
97
|
-
}
|
|
98
|
-
}
|
|
99
|
-
|
|
100
|
-
export default NodePersistProxy;
|
|
@@ -1,23 +0,0 @@
|
|
|
1
|
-
import { createLogger } from "./logger.js";
|
|
2
|
-
import NodePersistance from "./node.persist.js";
|
|
3
|
-
|
|
4
|
-
const logger = createLogger("TM-RENDERER");
|
|
5
|
-
|
|
6
|
-
export default async () => {
|
|
7
|
-
logger.Info("Using node-persist");
|
|
8
|
-
const path = await import("node:path");
|
|
9
|
-
const os = await import("node:os");
|
|
10
|
-
const storage = await import("node-persist");
|
|
11
|
-
const fullPath = `${os.homedir()}/.renderer-store/`;
|
|
12
|
-
const p = path.relative(process.cwd(), fullPath);
|
|
13
|
-
|
|
14
|
-
let storageProxy = new NodePersistance(storage);
|
|
15
|
-
await storageProxy.init({
|
|
16
|
-
forgiveParseErrors: true,
|
|
17
|
-
writeQueue: true,
|
|
18
|
-
writeQueueIntervalMs: 1000,
|
|
19
|
-
writeQueueWriteOnlyLast: true,
|
|
20
|
-
dir: p,
|
|
21
|
-
});
|
|
22
|
-
return storageProxy;
|
|
23
|
-
};
|