@necrolab/dashboard 0.5.21 → 0.5.23
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 +1 -3
- package/src/components/Filter/Filter.vue +26 -9
- package/src/libs/Filter.js +103 -23
- 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 +171 -0
- package/src/libs/tm-renderer/dependencies/logger.js +186 -0
- package/src/libs/tm-renderer/dependencies/node.persist.js +100 -0
- package/src/libs/tm-renderer/dependencies/persist.js +23 -0
- package/src/libs/tm-renderer/dependencies/web.persist.js +64 -0
- package/src/libs/tm-renderer/factory.js +47 -0
- package/src/libs/tm-renderer/index.js +21 -0
- package/src/libs/tm-renderer/request-utils.js +101 -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 +269 -64
- package/vite.config.js +1 -0
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
const manipulateHexColor = (p, c0, c1, l) => {
|
|
2
|
+
let r,
|
|
3
|
+
g,
|
|
4
|
+
b,
|
|
5
|
+
P,
|
|
6
|
+
f,
|
|
7
|
+
t,
|
|
8
|
+
h,
|
|
9
|
+
i = parseInt,
|
|
10
|
+
m = Math.round,
|
|
11
|
+
a = typeof c1 == "string";
|
|
12
|
+
if (
|
|
13
|
+
typeof p != "number" ||
|
|
14
|
+
p < -1 ||
|
|
15
|
+
p > 1 ||
|
|
16
|
+
typeof c0 != "string" ||
|
|
17
|
+
(c0[0] != "r" && c0[0] != "#") ||
|
|
18
|
+
(c1 && !a)
|
|
19
|
+
)
|
|
20
|
+
return null;
|
|
21
|
+
if (!this.pSBCr)
|
|
22
|
+
this.pSBCr = (d) => {
|
|
23
|
+
let n = d.length,
|
|
24
|
+
x = {};
|
|
25
|
+
if (n > 9) {
|
|
26
|
+
([r, g, b, a] = d = d.split(",")), (n = d.length);
|
|
27
|
+
if (n < 3 || n > 4) return null;
|
|
28
|
+
(x.r = i(r[3] == "a" ? r.slice(5) : r.slice(4))),
|
|
29
|
+
(x.g = i(g)),
|
|
30
|
+
(x.b = i(b)),
|
|
31
|
+
(x.a = a ? parseFloat(a) : -1);
|
|
32
|
+
} else {
|
|
33
|
+
if (n == 8 || n == 6 || n < 4) return null;
|
|
34
|
+
if (n < 6) d = "#" + d[1] + d[1] + d[2] + d[2] + d[3] + d[3] + (n > 4 ? d[4] + d[4] : "");
|
|
35
|
+
d = i(d.slice(1), 16);
|
|
36
|
+
if (n == 9 || n == 5)
|
|
37
|
+
(x.r = (d >> 24) & 255),
|
|
38
|
+
(x.g = (d >> 16) & 255),
|
|
39
|
+
(x.b = (d >> 8) & 255),
|
|
40
|
+
(x.a = m((d & 255) / 0.255) / 1000);
|
|
41
|
+
else (x.r = d >> 16), (x.g = (d >> 8) & 255), (x.b = d & 255), (x.a = -1);
|
|
42
|
+
}
|
|
43
|
+
return x;
|
|
44
|
+
};
|
|
45
|
+
(h = c0.length > 9),
|
|
46
|
+
(h = a ? (c1.length > 9 ? true : c1 == "c" ? !h : false) : h),
|
|
47
|
+
(f = this.pSBCr(c0)),
|
|
48
|
+
(P = p < 0),
|
|
49
|
+
(t = c1 && c1 != "c" ? this.pSBCr(c1) : P ? { r: 0, g: 0, b: 0, a: -1 } : { r: 255, g: 255, b: 255, a: -1 }),
|
|
50
|
+
(p = P ? p * -1 : p),
|
|
51
|
+
(P = 1 - p);
|
|
52
|
+
if (!f || !t) return null;
|
|
53
|
+
if (l) (r = m(P * f.r + p * t.r)), (g = m(P * f.g + p * t.g)), (b = m(P * f.b + p * t.b));
|
|
54
|
+
else
|
|
55
|
+
(r = m((P * f.r ** 2 + p * t.r ** 2) ** 0.5)),
|
|
56
|
+
(g = m((P * f.g ** 2 + p * t.g ** 2) ** 0.5)),
|
|
57
|
+
(b = m((P * f.b ** 2 + p * t.b ** 2) ** 0.5));
|
|
58
|
+
(a = f.a), (t = t.a), (f = a >= 0 || t >= 0), (a = f ? (a < 0 ? t : t < 0 ? a : a * P + t * p) : 0);
|
|
59
|
+
if (h) return "rgb" + (f ? "a(" : "(") + r + "," + g + "," + b + (f ? "," + m(a * 1000) / 1000 : "") + ")";
|
|
60
|
+
else
|
|
61
|
+
return (
|
|
62
|
+
"#" +
|
|
63
|
+
(4294967296 + r * 16777216 + g * 65536 + b * 256 + (f ? m(a * 255) : 0))
|
|
64
|
+
.toString(16)
|
|
65
|
+
.slice(1, f ? undefined : -2)
|
|
66
|
+
);
|
|
67
|
+
};
|
|
68
|
+
|
|
69
|
+
const pinIcon = `
|
|
70
|
+
<g>
|
|
71
|
+
<path d="m 0,0 c -1.25,1.34 -2,3.3 -2,5.14 0,3.85 1.8,5.3 4.56,10.6 1,2.32 2,4.8 3,8.89 0.137,0.6 0.27,1.16 0.33,1.2 0,0 0.2,-0.51 0.33,-1.11 1,-4 2,-6.54 3,-8.87 2.77,-5.3 4.5,-6.75 4.56,-10.6 0,-1.83 -0.75,-3.8 -2,-5.14 -1.43,-1.53 -3.6,-2.66 -5.9,-2.71 -2.31,0 -4.5,1 -5.92,2.62 z" style="display:inline;opacity:1;fill:#ff4646;fill-opacity:1;stroke:#d73534;stroke-width:1;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"></path>
|
|
72
|
+
<circle r="3.5" cy="5.3" cx="5.7" style="display:inline;opacity:1;fill:#590000;fill-opacity:1;stroke-width:0"></circle>
|
|
73
|
+
</g>
|
|
74
|
+
`;
|
|
75
|
+
|
|
76
|
+
const marshalAttributes = (attrs) => {
|
|
77
|
+
let out = [];
|
|
78
|
+
Object.entries(attrs).forEach(([k, v]) => k && v && out.push(`${k}="${v}"`));
|
|
79
|
+
return out.join(" ");
|
|
80
|
+
};
|
|
81
|
+
|
|
82
|
+
export const createElement = (tag, attrs, body = "") => `<${tag} ${marshalAttributes(attrs)}>${body}</${tag}>\n`;
|
|
83
|
+
export { manipulateHexColor };
|
|
84
|
+
export { pinIcon };
|
|
85
|
+
export const sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
|
|
86
|
+
export const binder = (functionsObject, thisClass) => {
|
|
87
|
+
for (let [functionKey, functionValue] of Object.entries(functionsObject)) {
|
|
88
|
+
thisClass[functionKey] = functionValue.bind(thisClass);
|
|
89
|
+
}
|
|
90
|
+
};
|
|
@@ -1,18 +1,19 @@
|
|
|
1
1
|
<template>
|
|
2
2
|
<div class="flex w-full flex-col">
|
|
3
|
-
<div class="page-header">
|
|
3
|
+
<div class="page-header flex-wrap gap-3">
|
|
4
4
|
<div class="page-header-card flex-shrink-0">
|
|
5
5
|
<FilterIcon />
|
|
6
6
|
<h4>Filter creator</h4>
|
|
7
7
|
</div>
|
|
8
|
-
<div class="unified-search-group flex w-
|
|
8
|
+
<div class="unified-search-group flex w-full flex-1 items-center md:w-auto md:flex-initial md:min-w-64">
|
|
9
9
|
<input
|
|
10
|
-
class="h-10
|
|
10
|
+
class="flex-1 h-10 px-3 text-sm text-white placeholder-light-500"
|
|
11
11
|
placeholder="Event ID"
|
|
12
12
|
v-model="eventId"
|
|
13
13
|
aria-label="Event ID" />
|
|
14
14
|
<button
|
|
15
|
-
class="
|
|
15
|
+
class="hover:bg-dark-450 flex h-10 w-10 flex-shrink-0 items-center justify-center bg-dark-400 text-white"
|
|
16
|
+
style="transition: all 0.2s ease"
|
|
16
17
|
@click="updateShownVenue"
|
|
17
18
|
aria-label="Load venue">
|
|
18
19
|
<ReloadIcon class="icon-md" />
|
|
@@ -21,26 +22,29 @@
|
|
|
21
22
|
</div>
|
|
22
23
|
|
|
23
24
|
<div
|
|
24
|
-
class="mb-3 flex flex-1 flex-col overflow-hidden rounded border border-dark-650 bg-dark-400 p-
|
|
25
|
-
|
|
25
|
+
class="mb-3 flex flex-1 flex-col overflow-hidden rounded border border-dark-650 bg-dark-400 p-2 shadow-sm md:p-3 md:mb-4"
|
|
26
|
+
style="max-height: calc(100vh - 200px);">
|
|
27
|
+
<div class="flex h-full w-full flex-col gap-2 md:gap-3 lg:flex-row lg:gap-4">
|
|
26
28
|
<div
|
|
27
|
-
class="
|
|
28
|
-
|
|
29
|
+
class="relative flex w-full min-w-0 flex-col overflow-hidden rounded-lg lg:flex-1 lg:w-3/5 svg-container"
|
|
30
|
+
:class="{ 'has-svg': svg }">
|
|
31
|
+
<div v-if="svg" class="flex-gap-2 mb-2 flex-shrink-0 items-center">
|
|
29
32
|
<button @click="handleZoom(true)" class="btn-icon-small" aria-label="Zoom in">+</button>
|
|
30
33
|
<button @click="handleZoom(false)" class="btn-icon-small" aria-label="Zoom out">-</button>
|
|
31
34
|
<button @click="handleZoom('r')" class="btn-icon-small" aria-label="Reset zoom">
|
|
32
35
|
<ReloadIcon class="icon-md text-white" />
|
|
33
36
|
</button>
|
|
34
37
|
</div>
|
|
35
|
-
<div class="selecto-wrapper flex-1 overflow-hidden">
|
|
38
|
+
<div class="selecto-wrapper flex-1 overflow-hidden" style="max-height: 100%;">
|
|
36
39
|
<div
|
|
37
40
|
v-if="svg"
|
|
38
|
-
class="hidden-scrollbars
|
|
41
|
+
class="hidden-scrollbars relative h-full w-full overflow-auto rounded border border-dark-550 bg-dark-500 p-1 md:p-2 shadow">
|
|
39
42
|
<div class="svg-wrapper" id="svg-wrapper" v-html="svg"></div>
|
|
40
43
|
</div>
|
|
41
44
|
<div
|
|
42
45
|
v-else
|
|
43
|
-
class="
|
|
46
|
+
class="relative flex h-full w-full items-center justify-center rounded border border-dark-550 bg-dark-500 p-2 shadow"
|
|
47
|
+
style="min-height: 200px;">
|
|
44
48
|
<div class="text-center">
|
|
45
49
|
<FilterIcon class="empty-state-icon mx-auto" />
|
|
46
50
|
<p class="text-sm text-light-400">No Map</p>
|
|
@@ -51,8 +55,8 @@
|
|
|
51
55
|
</div>
|
|
52
56
|
</div>
|
|
53
57
|
</div>
|
|
54
|
-
<div class="
|
|
55
|
-
<div class="mb-2 flex flex-shrink-0 flex-wrap items-center gap-2 text-white" v-if="hasLoaded">
|
|
58
|
+
<div class="flex w-full min-w-0 flex-col lg:flex-1 lg:w-2/5 filters-container">
|
|
59
|
+
<div class="mb-2 flex flex-shrink-0 flex-wrap items-center gap-1.5 md:gap-2 text-white text-xs md:text-sm" v-if="hasLoaded">
|
|
56
60
|
<PriceSortToggle
|
|
57
61
|
:current="filterBuilder.globalFilter.priceSort"
|
|
58
62
|
class="smooth-hover h-8"
|
|
@@ -71,8 +75,8 @@
|
|
|
71
75
|
placeholder="999" />
|
|
72
76
|
</div>
|
|
73
77
|
<div
|
|
74
|
-
class="flex min-h-0 flex-1 flex-col overflow-hidden rounded-lg border border-dark-550 bg-dark-500 shadow-sm">
|
|
75
|
-
<div class="flex-shrink-0 border-b border-dark-550 bg-dark-300 px-4 py-3 text-xs text-white">
|
|
78
|
+
class="flex min-h-0 flex-1 flex-col overflow-hidden rounded-lg border border-dark-550 bg-dark-500 shadow-sm filters-card">
|
|
79
|
+
<div class="flex-shrink-0 border-b border-dark-550 bg-dark-300 px-2 md:px-4 py-2 md:py-3 text-xs text-white">
|
|
76
80
|
<div class="flex w-full items-center justify-between gap-2">
|
|
77
81
|
<div class="flex-gap-2 items-center">
|
|
78
82
|
<span class="text-sm font-medium text-white">Filters</span>
|
|
@@ -98,26 +102,21 @@
|
|
|
98
102
|
</div>
|
|
99
103
|
</div>
|
|
100
104
|
</div>
|
|
101
|
-
<div class="hidden-scrollbars flex-1 overflow-auto bg-dark-400">
|
|
102
|
-
<
|
|
105
|
+
<div class="hidden-scrollbars flex-1 overflow-y-auto overflow-x-hidden bg-dark-400" style="max-height: 100%; min-height: 150px;">
|
|
106
|
+
<div
|
|
103
107
|
v-if="filterBuilder.filters.length"
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
:index="i"
|
|
117
|
-
:filterBuilder="filterBuilder"
|
|
118
|
-
class="!p-1 !text-xs" />
|
|
119
|
-
</template>
|
|
120
|
-
</draggable>
|
|
108
|
+
ref="filtersListRef"
|
|
109
|
+
class="space-y-0 p-1">
|
|
110
|
+
<Filter
|
|
111
|
+
v-for="(f, i) in filterBuilder.filters"
|
|
112
|
+
v-show="doesFilterShow(f)"
|
|
113
|
+
:key="f.id"
|
|
114
|
+
:filter="f"
|
|
115
|
+
:index="i"
|
|
116
|
+
:filterBuilder="filterBuilder"
|
|
117
|
+
:color="getFilterColor(i)"
|
|
118
|
+
class="!p-1 !text-xs" />
|
|
119
|
+
</div>
|
|
121
120
|
<div v-else class="empty-state flex flex-col items-center justify-center py-8 text-center">
|
|
122
121
|
<FilterIcon class="empty-state-icon" />
|
|
123
122
|
<p class="text-sm text-light-400">No filters yet</p>
|
|
@@ -129,9 +128,10 @@
|
|
|
129
128
|
<button
|
|
130
129
|
@click="addWildcardFilter"
|
|
131
130
|
:disabled="hasWildcardFilter"
|
|
132
|
-
class="filter-
|
|
131
|
+
class="filter-action-btn"
|
|
133
132
|
:title="hasWildcardFilter ? 'Wildcard filter already exists' : 'Add wildcard filter'">
|
|
134
|
-
|
|
133
|
+
<WildcardIcon class="h-3 w-3 flex-shrink-0" />
|
|
134
|
+
<span>* Wildcard</span>
|
|
135
135
|
</button>
|
|
136
136
|
<button @click="ui.toggleModal('preview-filter')" class="filter-action-btn">
|
|
137
137
|
<CameraIcon class="h-3 w-3" />
|
|
@@ -149,17 +149,19 @@
|
|
|
149
149
|
</template>
|
|
150
150
|
|
|
151
151
|
<script setup>
|
|
152
|
-
import
|
|
153
|
-
import
|
|
152
|
+
import Draggable from "vuedraggable";
|
|
153
|
+
import Sortable from "sortablejs";
|
|
154
|
+
import { ref, computed, defineAsyncComponent, watch, nextTick, onMounted } from "vue";
|
|
154
155
|
import { useFilterCSS } from "@/composables/useFilterCSS";
|
|
155
156
|
import Filter from "@/components/Filter/Filter.vue";
|
|
156
|
-
import { FilterIcon } from "@/components/icons";
|
|
157
|
+
import { FilterIcon, WildcardIcon } from "@/components/icons";
|
|
157
158
|
import { ReloadIcon, TrashIcon, EditIcon, CameraIcon } from "@/components/icons";
|
|
158
159
|
import { sendSaveFilter } from "@/stores/requests";
|
|
159
160
|
import PriceSortToggle from "@/components/Filter/PriceSortToggle.vue";
|
|
160
161
|
import { useUIStore } from "@/stores/ui";
|
|
161
162
|
import FilterBuilder from "@/libs/Filter";
|
|
162
163
|
import panzoom from "@/libs/panzoom.js";
|
|
164
|
+
import RendererFactory from "@/libs/tm-renderer/index.js";
|
|
163
165
|
|
|
164
166
|
// Lazy-loaded modal component
|
|
165
167
|
const FilterPreview = defineAsyncComponent(() => import("@/components/Filter/FilterPreview.vue"));
|
|
@@ -190,6 +192,35 @@ const draggableFilters = computed({
|
|
|
190
192
|
get: () => filterBuilder.value.filters,
|
|
191
193
|
set: (value) => {
|
|
192
194
|
filterBuilder.value.filters = value;
|
|
195
|
+
filterBuilder.value.updateCss();
|
|
196
|
+
filterBuilder.value.updateHooks.forEach((fn) => fn());
|
|
197
|
+
}
|
|
198
|
+
});
|
|
199
|
+
|
|
200
|
+
const filtersListRef = ref(null);
|
|
201
|
+
let sortableInstance = null;
|
|
202
|
+
|
|
203
|
+
// Initialize Sortable on the filters list
|
|
204
|
+
watch(filtersListRef, (el) => {
|
|
205
|
+
if (el && !sortableInstance) {
|
|
206
|
+
sortableInstance = new Sortable(el, {
|
|
207
|
+
handle: '.handle',
|
|
208
|
+
animation: 200,
|
|
209
|
+
ghostClass: 'sortable-ghost',
|
|
210
|
+
dragClass: 'sortable-drag',
|
|
211
|
+
onEnd: (evt) => {
|
|
212
|
+
const { oldIndex, newIndex } = evt;
|
|
213
|
+
if (oldIndex !== newIndex) {
|
|
214
|
+
// Reorder the filters array
|
|
215
|
+
const filters = [...filterBuilder.value.filters];
|
|
216
|
+
const [movedItem] = filters.splice(oldIndex, 1);
|
|
217
|
+
filters.splice(newIndex, 0, movedItem);
|
|
218
|
+
filterBuilder.value.filters = filters;
|
|
219
|
+
filterBuilder.value.updateCss();
|
|
220
|
+
filterBuilder.value.updateHooks.forEach((fn) => fn());
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
});
|
|
193
224
|
}
|
|
194
225
|
});
|
|
195
226
|
|
|
@@ -205,8 +236,6 @@ const addWildcardFilter = () => {
|
|
|
205
236
|
}
|
|
206
237
|
};
|
|
207
238
|
|
|
208
|
-
let RendererFactory = import("@necrolab/tm-renderer");
|
|
209
|
-
|
|
210
239
|
useFilterCSS(filterBuilder, svg);
|
|
211
240
|
|
|
212
241
|
const doesFilterShow = (filter) => {
|
|
@@ -216,13 +245,30 @@ const doesFilterShow = (filter) => {
|
|
|
216
245
|
return shownFilters.value === filterState;
|
|
217
246
|
};
|
|
218
247
|
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
248
|
+
// Color palette for unique filter colors (same as Filter.js)
|
|
249
|
+
const filterColorPalette = [
|
|
250
|
+
"#7bc999", "#6ba8d6", "#d4a876", "#a88bc9", "#e89ba3",
|
|
251
|
+
"#7dc9c9", "#c9a87b", "#8bc99a", "#9ba8c9", "#c9b88b"
|
|
252
|
+
];
|
|
253
|
+
|
|
254
|
+
const getFilterColor = (index) => {
|
|
255
|
+
return filterColorPalette[index % filterColorPalette.length];
|
|
256
|
+
};
|
|
257
|
+
|
|
258
|
+
// Initialize renderer factory
|
|
259
|
+
const rendererFactory = new RendererFactory();
|
|
260
|
+
let rendererReady = false;
|
|
261
|
+
|
|
262
|
+
// Initialize renderer and mark as ready
|
|
263
|
+
(async () => {
|
|
264
|
+
try {
|
|
265
|
+
await rendererFactory.init();
|
|
266
|
+
rendererReady = true;
|
|
267
|
+
ui.logger.Info("Renderer initialized successfully");
|
|
268
|
+
} catch (error) {
|
|
269
|
+
ui.logger.Error("Failed to initialize renderer:", error);
|
|
270
|
+
}
|
|
271
|
+
})();
|
|
226
272
|
|
|
227
273
|
const updateShownVenue = async () => {
|
|
228
274
|
eventId.value = eventId.value.trim();
|
|
@@ -254,19 +300,26 @@ const updateShownVenue = async () => {
|
|
|
254
300
|
}
|
|
255
301
|
|
|
256
302
|
try {
|
|
257
|
-
if (!rendererFactory) {
|
|
258
|
-
ui.showError("Renderer not initialized yet. Please wait and try again.");
|
|
303
|
+
if (!rendererFactory || !rendererReady) {
|
|
304
|
+
ui.showError("Renderer not initialized yet. Please wait a moment and try again.");
|
|
259
305
|
return;
|
|
260
306
|
}
|
|
261
307
|
|
|
308
|
+
ui.logger.Info(`Creating renderer for event ${eventId.value} with country: ${country}`);
|
|
309
|
+
|
|
262
310
|
renderer = rendererFactory.createRenderer(eventId.value, {
|
|
263
311
|
proxy: "",
|
|
264
312
|
country: country,
|
|
265
|
-
seatColor: "#
|
|
266
|
-
nonAvSeatColor: "#
|
|
267
|
-
highlightedSeatColor: "#
|
|
313
|
+
seatColor: "#b8e6d5",
|
|
314
|
+
nonAvSeatColor: "#707070",
|
|
315
|
+
highlightedSeatColor: "#7bc999"
|
|
268
316
|
});
|
|
269
317
|
|
|
318
|
+
if (!renderer) {
|
|
319
|
+
throw new Error("Failed to create renderer instance");
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
ui.logger.Info("Renderer created, setting config...");
|
|
270
323
|
filterBuilder.value.highlightedSeatColor = renderer.config.highlightedSeatColor;
|
|
271
324
|
renderer.setCustomConfig({
|
|
272
325
|
logFullError: true,
|
|
@@ -274,20 +327,46 @@ const updateShownVenue = async () => {
|
|
|
274
327
|
renderRowBlocks: true
|
|
275
328
|
});
|
|
276
329
|
|
|
330
|
+
ui.logger.Info("Starting render...");
|
|
277
331
|
await renderer.render();
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
332
|
+
ui.logger.Info("Render completed");
|
|
333
|
+
|
|
334
|
+
// Add extra validation
|
|
335
|
+
if (!renderer.svg || renderer.svg.trim() === '') {
|
|
336
|
+
ui.logger.Error("Renderer SVG is empty or null:", {
|
|
337
|
+
hasSvg: !!renderer.svg,
|
|
338
|
+
svgLength: renderer.svg?.length,
|
|
339
|
+
country,
|
|
340
|
+
eventId: eventId.value
|
|
341
|
+
});
|
|
342
|
+
throw new Error(`Renderer returned empty SVG. Country: ${country}, EventID: ${eventId.value}`);
|
|
282
343
|
}
|
|
344
|
+
|
|
345
|
+
ui.logger.Info(`SVG generated successfully, length: ${renderer.svg.length}`);
|
|
346
|
+
svg.value = renderer.svg;
|
|
283
347
|
} catch (e) {
|
|
284
348
|
ui.logger.Error("COULD NOT RENDER SVG", e);
|
|
349
|
+
ui.logger.Error("Error details:", {
|
|
350
|
+
message: e?.message,
|
|
351
|
+
stack: e?.stack,
|
|
352
|
+
name: e?.name,
|
|
353
|
+
cause: e?.cause,
|
|
354
|
+
renderer: !!renderer,
|
|
355
|
+
rendererFactory: !!rendererFactory,
|
|
356
|
+
rendererReady,
|
|
357
|
+
userAgent: navigator.userAgent
|
|
358
|
+
});
|
|
359
|
+
|
|
285
360
|
const errorMsg = e?.message || "Unknown rendering error";
|
|
286
361
|
ui.showError(`Failed to render venue: ${errorMsg}`);
|
|
287
362
|
|
|
288
363
|
// Try to load existing filter even if rendering fails
|
|
289
|
-
|
|
290
|
-
|
|
364
|
+
try {
|
|
365
|
+
await loadFilter();
|
|
366
|
+
filterBuilder.value.reload(eventId.value);
|
|
367
|
+
} catch (loadErr) {
|
|
368
|
+
ui.logger.Error("Could not load filter either", loadErr);
|
|
369
|
+
}
|
|
291
370
|
return;
|
|
292
371
|
}
|
|
293
372
|
|
|
@@ -414,13 +493,139 @@ watch(renderSeats, async () => {
|
|
|
414
493
|
}
|
|
415
494
|
|
|
416
495
|
.svg-wrapper path[generaladmission] {
|
|
417
|
-
pointer-events: auto;
|
|
418
|
-
cursor: pointer;
|
|
496
|
+
pointer-events: auto !important;
|
|
497
|
+
cursor: pointer !important;
|
|
498
|
+
}
|
|
499
|
+
|
|
500
|
+
/* Hover effects */
|
|
501
|
+
.svg-wrapper path:hover {
|
|
502
|
+
opacity: 0.8;
|
|
419
503
|
}
|
|
420
504
|
|
|
421
|
-
/* Hover effects for general admission seats */
|
|
422
505
|
.svg-wrapper path[generaladmission]:hover {
|
|
423
|
-
|
|
424
|
-
|
|
506
|
+
opacity: 1;
|
|
507
|
+
fill: #7ee8fa !important;
|
|
508
|
+
}
|
|
509
|
+
|
|
510
|
+
/* Mobile responsiveness - bulletproof on all devices */
|
|
511
|
+
@media (max-width: 768px) {
|
|
512
|
+
.page-header {
|
|
513
|
+
flex-direction: column;
|
|
514
|
+
align-items: stretch !important;
|
|
515
|
+
gap: 0.75rem;
|
|
516
|
+
}
|
|
517
|
+
|
|
518
|
+
.unified-search-group {
|
|
519
|
+
width: 100% !important;
|
|
520
|
+
min-width: 100% !important;
|
|
521
|
+
}
|
|
522
|
+
}
|
|
523
|
+
|
|
524
|
+
@media (max-width: 768px) {
|
|
525
|
+
.page-header {
|
|
526
|
+
flex-direction: column !important;
|
|
527
|
+
}
|
|
528
|
+
}
|
|
529
|
+
|
|
530
|
+
@media (max-width: 480px) {
|
|
531
|
+
.page-header {
|
|
532
|
+
padding-bottom: 0.75rem;
|
|
533
|
+
}
|
|
534
|
+
|
|
535
|
+
.unified-search-group input {
|
|
536
|
+
min-width: 0;
|
|
537
|
+
font-size: 13px;
|
|
538
|
+
}
|
|
539
|
+
|
|
540
|
+
.filter-action-btn {
|
|
541
|
+
font-size: 11px;
|
|
542
|
+
padding: 0.25rem 0.5rem;
|
|
543
|
+
}
|
|
544
|
+
|
|
545
|
+
.btn-icon-small {
|
|
546
|
+
width: 1.75rem;
|
|
547
|
+
height: 1.75rem;
|
|
548
|
+
font-size: 12px;
|
|
549
|
+
}
|
|
550
|
+
|
|
551
|
+
}
|
|
552
|
+
|
|
553
|
+
/* Force filters list to be constrained and scrollable */
|
|
554
|
+
.hidden-scrollbars.flex-1.overflow-y-auto {
|
|
555
|
+
max-height: 100%;
|
|
556
|
+
min-height: 150px;
|
|
557
|
+
overflow-y: auto !important;
|
|
558
|
+
}
|
|
559
|
+
|
|
560
|
+
/* SVG and Filters container responsive sizing */
|
|
561
|
+
.svg-container {
|
|
562
|
+
min-height: 200px;
|
|
563
|
+
max-height: 250px;
|
|
564
|
+
flex-shrink: 0;
|
|
565
|
+
}
|
|
566
|
+
|
|
567
|
+
.svg-container.has-svg {
|
|
568
|
+
min-height: 240px;
|
|
569
|
+
max-height: 280px;
|
|
570
|
+
flex-shrink: 0;
|
|
571
|
+
}
|
|
572
|
+
|
|
573
|
+
.filters-container {
|
|
574
|
+
min-height: 260px;
|
|
575
|
+
max-height: 320px;
|
|
576
|
+
flex-shrink: 1;
|
|
577
|
+
flex-grow: 1;
|
|
578
|
+
display: flex;
|
|
579
|
+
flex-direction: column;
|
|
580
|
+
overflow: hidden;
|
|
581
|
+
}
|
|
582
|
+
|
|
583
|
+
/* Ensure filters card wrapper doesn't grow too large */
|
|
584
|
+
.filters-card {
|
|
585
|
+
max-height: calc(100% - 3.5rem);
|
|
586
|
+
min-height: 200px;
|
|
587
|
+
}
|
|
588
|
+
|
|
589
|
+
/* Add spacing for bottom buttons */
|
|
590
|
+
.filters-container > div:last-child {
|
|
591
|
+
margin-bottom: 0.5rem;
|
|
592
|
+
}
|
|
593
|
+
|
|
594
|
+
@media (min-width: 1024px) {
|
|
595
|
+
.svg-container {
|
|
596
|
+
min-height: 400px;
|
|
597
|
+
max-height: 100%;
|
|
598
|
+
flex-shrink: 1;
|
|
599
|
+
}
|
|
600
|
+
|
|
601
|
+
.svg-container.has-svg {
|
|
602
|
+
min-height: 400px;
|
|
603
|
+
max-height: 100%;
|
|
604
|
+
flex-shrink: 1;
|
|
605
|
+
}
|
|
606
|
+
|
|
607
|
+
.filters-container {
|
|
608
|
+
min-height: 400px;
|
|
609
|
+
max-height: 100%;
|
|
610
|
+
flex-shrink: 1;
|
|
611
|
+
}
|
|
612
|
+
}
|
|
613
|
+
|
|
614
|
+
/* Ensure filters list always scrolls */
|
|
615
|
+
.hidden-scrollbars::-webkit-scrollbar {
|
|
616
|
+
width: 4px;
|
|
617
|
+
}
|
|
618
|
+
|
|
619
|
+
.hidden-scrollbars::-webkit-scrollbar-track {
|
|
620
|
+
background: transparent;
|
|
621
|
+
}
|
|
622
|
+
|
|
623
|
+
.hidden-scrollbars::-webkit-scrollbar-thumb {
|
|
624
|
+
background: oklch(0.35 0 0);
|
|
625
|
+
border-radius: 4px;
|
|
626
|
+
}
|
|
627
|
+
|
|
628
|
+
.hidden-scrollbars::-webkit-scrollbar-thumb:hover {
|
|
629
|
+
background: oklch(0.45 0 0);
|
|
425
630
|
}
|
|
426
631
|
</style>
|