@seatlayer/core 0.1.2 → 0.1.4
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/dist/de-A4KJQ374.js +72 -0
- package/dist/de-A4KJQ374.js.map +1 -0
- package/dist/es-YNYMMP37.js +72 -0
- package/dist/es-YNYMMP37.js.map +1 -0
- package/dist/fr-DQ4KWKKF.js +72 -0
- package/dist/fr-DQ4KWKKF.js.map +1 -0
- package/dist/index.cjs +1248 -94
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +468 -8
- package/dist/index.d.ts +468 -8
- package/dist/index.js +985 -93
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.cjs
CHANGED
|
@@ -3,6 +3,9 @@ var __defProp = Object.defineProperty;
|
|
|
3
3
|
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
4
|
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
5
|
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
6
|
+
var __esm = (fn, res) => function __init() {
|
|
7
|
+
return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
|
|
8
|
+
};
|
|
6
9
|
var __export = (target, all) => {
|
|
7
10
|
for (var name in all)
|
|
8
11
|
__defProp(target, name, { get: all[name], enumerable: true });
|
|
@@ -17,24 +20,283 @@ var __copyProps = (to, from, except, desc) => {
|
|
|
17
20
|
};
|
|
18
21
|
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
19
22
|
|
|
23
|
+
// src/i18n/locales/es.ts
|
|
24
|
+
var es_exports = {};
|
|
25
|
+
__export(es_exports, {
|
|
26
|
+
es: () => es
|
|
27
|
+
});
|
|
28
|
+
var es;
|
|
29
|
+
var init_es = __esm({
|
|
30
|
+
"src/i18n/locales/es.ts"() {
|
|
31
|
+
"use strict";
|
|
32
|
+
es = {
|
|
33
|
+
// shared
|
|
34
|
+
"common.cancel": "Cancelar",
|
|
35
|
+
"common.close": "Cerrar",
|
|
36
|
+
"common.done": "Listo",
|
|
37
|
+
"common.copied": "\u2713 Copiado",
|
|
38
|
+
// buyer picker (extraction in progress — P4 of the port plan)
|
|
39
|
+
"picker.holdSeats": "Retener asientos y comprar",
|
|
40
|
+
"picker.completeBooking": "Completar reserva",
|
|
41
|
+
"picker.seatsHeld": "Asientos retenidos \u2014 {time}",
|
|
42
|
+
"picker.holdExpired": "Tu retenci\xF3n expir\xF3 \u2014 los asientos fueron liberados. Elige de nuevo.",
|
|
43
|
+
"picker.seatTaken": "El asiento {label} fue tomado por otro comprador.",
|
|
44
|
+
"picker.poweredBy": "Con la tecnolog\xEDa de SeatLayer",
|
|
45
|
+
"picker.colorblind": "Colores aptos para daltonismo",
|
|
46
|
+
"picker.orphanHint": "Esto deja un asiento aislado \u2014 considera desplazarte un asiento.",
|
|
47
|
+
"picker.seats.one": "{count} asiento",
|
|
48
|
+
"picker.seats.other": "{count} asientos",
|
|
49
|
+
// renderer (drawn on the Konva map — shared by the embed SDK)
|
|
50
|
+
"map.aria": "Mapa de asientos. Usa las flechas para moverte entre asientos, Intro para seleccionar.",
|
|
51
|
+
"map.seatsLeft": "{count} LIBRES",
|
|
52
|
+
"map.fromPrice": "DESDE {price}",
|
|
53
|
+
// buyer picker page (src/pages/PickerPage.tsx)
|
|
54
|
+
"picker.language": "Idioma",
|
|
55
|
+
"picker.zoomToFit": "Ajustar zoom",
|
|
56
|
+
"picker.viewMode": "Modo de vista",
|
|
57
|
+
"picker.floor": "Piso",
|
|
58
|
+
"picker.zoomLevel": "Nivel de zoom",
|
|
59
|
+
"picker.rungTip.zones": "Nivel m\xE1s lejano \u2014 bloques de zona (Piso / Patio Bajo / Patio Alto)",
|
|
60
|
+
"picker.rungTip.sections": "Bloques de secci\xF3n \u2014 mezcla de categor\xEDas + matiz de disponibilidad",
|
|
61
|
+
"picker.rungTip.seats": "Asientos individuales \u2014 los bloques se convierten en puntos",
|
|
62
|
+
"picker.rungLabel.zones": "ZONAS",
|
|
63
|
+
"picker.rungLabel.sections": "SECCIONES",
|
|
64
|
+
"picker.rungLabel.seats": "ASIENTOS",
|
|
65
|
+
"picker.sectionSummaryAria": "Resumen de secci\xF3n {label}",
|
|
66
|
+
"picker.closeSectionSummary": "Cerrar resumen de secci\xF3n",
|
|
67
|
+
"picker.seatsLeftInSection.one": "{count} asiento disponible",
|
|
68
|
+
"picker.seatsLeftInSection.other": "{count} asientos disponibles",
|
|
69
|
+
"picker.overview": "Descripci\xF3n general",
|
|
70
|
+
"picker.tapSeatHint": "Toca cualquier asiento para ver su vista",
|
|
71
|
+
"picker.chartSize": "Tama\xF1o del gr\xE1fico",
|
|
72
|
+
"picker.custom": "Personalizado",
|
|
73
|
+
"picker.categories": "Categor\xEDas",
|
|
74
|
+
"picker.accessibility": "Accesibilidad",
|
|
75
|
+
"picker.showAnyAccessible": "Mostrar cualquier asiento accesible",
|
|
76
|
+
"picker.any": "Cualquiera",
|
|
77
|
+
"picker.yourSeats": "Tus asientos",
|
|
78
|
+
"picker.emptySeats": "Toca asientos en el mapa",
|
|
79
|
+
"picker.emptySeatsWithGa": "Toca asientos en el mapa \xB7 toca un \xE1rea de pie para entradas de entrada general",
|
|
80
|
+
"picker.oneFewer": "Uno menos",
|
|
81
|
+
"picker.oneMore": "Uno m\xE1s",
|
|
82
|
+
"picker.remove": "Eliminar {label}",
|
|
83
|
+
"picker.ticketTierFor": "Categor\xEDa de entrada para {label}",
|
|
84
|
+
"picker.total": "Total: {amount}",
|
|
85
|
+
"picker.viewFromSeat": "Vista desde el asiento {label}",
|
|
86
|
+
"picker.real360": "REAL 360\xB0",
|
|
87
|
+
"picker.preview": "VISTA PREVIA",
|
|
88
|
+
"picker.open360": "Abrir vista de 360\xB0",
|
|
89
|
+
"picker.sightline": "\u2248 {rows} filas del escenario \xB7 {angle}\xB0 fuera del centro",
|
|
90
|
+
"picker.bookedDemo": "\xA1Reservado! (demostraci\xF3n)",
|
|
91
|
+
"picker.bookButton.one": "Reservar {count} entrada \u2014 {amount}",
|
|
92
|
+
"picker.bookButton.other": "Reservar {count} entradas \u2014 {amount}",
|
|
93
|
+
"picker.simulateCrowd": "Simular multitud: {state}",
|
|
94
|
+
"picker.on": "ACTIVADO",
|
|
95
|
+
"picker.off": "DESACTIVADO",
|
|
96
|
+
"picker.panorama360": "Foto de 360\xB0 del lugar",
|
|
97
|
+
"picker.illustrationCaption": "ilustraci\xF3n \xB7 \u2248 {m} m del escenario"
|
|
98
|
+
};
|
|
99
|
+
}
|
|
100
|
+
});
|
|
101
|
+
|
|
102
|
+
// src/i18n/locales/de.ts
|
|
103
|
+
var de_exports = {};
|
|
104
|
+
__export(de_exports, {
|
|
105
|
+
de: () => de
|
|
106
|
+
});
|
|
107
|
+
var de;
|
|
108
|
+
var init_de = __esm({
|
|
109
|
+
"src/i18n/locales/de.ts"() {
|
|
110
|
+
"use strict";
|
|
111
|
+
de = {
|
|
112
|
+
// shared
|
|
113
|
+
"common.cancel": "Abbrechen",
|
|
114
|
+
"common.close": "Schlie\xDFen",
|
|
115
|
+
"common.done": "Fertig",
|
|
116
|
+
"common.copied": "\u2713 Kopiert",
|
|
117
|
+
// buyer picker (extraction in progress — P4 of the port plan)
|
|
118
|
+
"picker.holdSeats": "Pl\xE4tze reservieren & Kasse",
|
|
119
|
+
"picker.completeBooking": "Buchung abschlie\xDFen",
|
|
120
|
+
"picker.seatsHeld": "Pl\xE4tze reserviert \u2014 {time}",
|
|
121
|
+
"picker.holdExpired": "Ihre Reservierung ist abgelaufen \u2014 die Pl\xE4tze wurden freigegeben. Bitte erneut ausw\xE4hlen.",
|
|
122
|
+
"picker.seatTaken": "Platz {label} wurde gerade von einem anderen K\xE4ufer gebucht.",
|
|
123
|
+
"picker.poweredBy": "Bereitgestellt von SeatLayer",
|
|
124
|
+
"picker.colorblind": "Farbenblind-freundliche Farben",
|
|
125
|
+
"picker.orphanHint": "Dadurch bleibt ein einzelner Platz \xFCbrig \u2014 r\xFCcken Sie ggf. einen Platz weiter.",
|
|
126
|
+
"picker.seats.one": "{count} Platz",
|
|
127
|
+
"picker.seats.other": "{count} Pl\xE4tze",
|
|
128
|
+
// renderer (drawn on the Konva map — shared by the embed SDK)
|
|
129
|
+
"map.aria": "Sitzplan. Mit Pfeiltasten zwischen Pl\xE4tzen navigieren, Eingabe zum Ausw\xE4hlen.",
|
|
130
|
+
"map.seatsLeft": "{count} FREI",
|
|
131
|
+
"map.fromPrice": "AB {price}",
|
|
132
|
+
// buyer picker page (src/pages/PickerPage.tsx)
|
|
133
|
+
"picker.language": "Sprache",
|
|
134
|
+
"picker.zoomToFit": "Auf Gr\xF6\xDFe anpassen",
|
|
135
|
+
"picker.viewMode": "Ansichtsmodus",
|
|
136
|
+
"picker.floor": "Etage",
|
|
137
|
+
"picker.zoomLevel": "Zoomstufe",
|
|
138
|
+
"picker.rungTip.zones": "Fernste Stufe \u2014 Zonen-Bl\xF6cke (Etage / Unteres Parterre / Oberes Parterre)",
|
|
139
|
+
"picker.rungTip.sections": "Bereichs-Bl\xF6cke \u2014 Kategoriesortierung + Verf\xFCgbarkeitst\xF6nung",
|
|
140
|
+
"picker.rungTip.seats": "Einzelne Pl\xE4tze \u2014 Bl\xF6cke l\xF6sen sich zu Punkten auf",
|
|
141
|
+
"picker.rungLabel.zones": "ZONEN",
|
|
142
|
+
"picker.rungLabel.sections": "BEREICHE",
|
|
143
|
+
"picker.rungLabel.seats": "PL\xC4TZE",
|
|
144
|
+
"picker.sectionSummaryAria": "Bereichszusammenfassung {label}",
|
|
145
|
+
"picker.closeSectionSummary": "Bereichszusammenfassung schlie\xDFen",
|
|
146
|
+
"picker.seatsLeftInSection.one": "{count} Platz verf\xFCgbar",
|
|
147
|
+
"picker.seatsLeftInSection.other": "{count} Pl\xE4tze verf\xFCgbar",
|
|
148
|
+
"picker.overview": "\xDCbersicht",
|
|
149
|
+
"picker.tapSeatHint": "Tippen Sie auf einen Platz, um die Ansicht zu pr\xFCfen",
|
|
150
|
+
"picker.chartSize": "Kartengr\xF6\xDFe",
|
|
151
|
+
"picker.custom": "Benutzerdefiniert",
|
|
152
|
+
"picker.categories": "Kategorien",
|
|
153
|
+
"picker.accessibility": "Barrierefreiheit",
|
|
154
|
+
"picker.showAnyAccessible": "Jeden barrierefreien Platz anzeigen",
|
|
155
|
+
"picker.any": "Beliebig",
|
|
156
|
+
"picker.yourSeats": "Ihre Pl\xE4tze",
|
|
157
|
+
"picker.emptySeats": "Tippen Sie auf die Pl\xE4tze auf der Karte",
|
|
158
|
+
"picker.emptySeatsWithGa": "Tippen Sie auf die Pl\xE4tze auf der Karte \xB7 tippen Sie auf einen Stehbereich f\xFCr Stehplatztickets",
|
|
159
|
+
"picker.oneFewer": "Einen weniger",
|
|
160
|
+
"picker.oneMore": "Einen mehr",
|
|
161
|
+
"picker.remove": "{label} entfernen",
|
|
162
|
+
"picker.ticketTierFor": "Ticketklasse f\xFCr {label}",
|
|
163
|
+
"picker.total": "Gesamt: {amount}",
|
|
164
|
+
"picker.viewFromSeat": "Ansicht von Platz {label}",
|
|
165
|
+
"picker.real360": "REAL 360\xB0",
|
|
166
|
+
"picker.preview": "VORSCHAU",
|
|
167
|
+
"picker.open360": "\xD6ffnen Sie die 360\xB0-Ansicht",
|
|
168
|
+
"picker.sightline": "\u2248 {rows} Reihen von der B\xFChne \xB7 {angle}\xB0 seitlich versetzt",
|
|
169
|
+
"picker.bookedDemo": "Gebucht! (Demo)",
|
|
170
|
+
"picker.bookButton.one": "{count} Ticket buchen \u2014 {amount}",
|
|
171
|
+
"picker.bookButton.other": "{count} Tickets buchen \u2014 {amount}",
|
|
172
|
+
"picker.simulateCrowd": "Menschenmenge simulieren: {state}",
|
|
173
|
+
"picker.on": "AN",
|
|
174
|
+
"picker.off": "AUS",
|
|
175
|
+
"picker.panorama360": "360\xB0-Veranstaltungsfotos",
|
|
176
|
+
"picker.illustrationCaption": "Illustration \xB7 \u2248 {m} m von der B\xFChne"
|
|
177
|
+
};
|
|
178
|
+
}
|
|
179
|
+
});
|
|
180
|
+
|
|
181
|
+
// src/i18n/locales/fr.ts
|
|
182
|
+
var fr_exports = {};
|
|
183
|
+
__export(fr_exports, {
|
|
184
|
+
fr: () => fr
|
|
185
|
+
});
|
|
186
|
+
var fr;
|
|
187
|
+
var init_fr = __esm({
|
|
188
|
+
"src/i18n/locales/fr.ts"() {
|
|
189
|
+
"use strict";
|
|
190
|
+
fr = {
|
|
191
|
+
// shared
|
|
192
|
+
"common.cancel": "Annuler",
|
|
193
|
+
"common.close": "Fermer",
|
|
194
|
+
"common.done": "Termin\xE9",
|
|
195
|
+
"common.copied": "\u2713 Copi\xE9",
|
|
196
|
+
// buyer picker (extraction in progress — P4 of the port plan)
|
|
197
|
+
"picker.holdSeats": "R\xE9server les si\xE8ges et valider",
|
|
198
|
+
"picker.completeBooking": "Terminer la r\xE9servation",
|
|
199
|
+
"picker.seatsHeld": "Si\xE8ges r\xE9serv\xE9s \u2014 {time}",
|
|
200
|
+
"picker.holdExpired": "Votre r\xE9servation a expir\xE9 \u2014 les si\xE8ges ont \xE9t\xE9 lib\xE9r\xE9s. S\xE9lectionnez \xE0 nouveau.",
|
|
201
|
+
"picker.seatTaken": "Le si\xE8ge {label} vient d'\xEAtre r\xE9serv\xE9 par un autre acheteur.",
|
|
202
|
+
"picker.poweredBy": "Propuls\xE9 par SeatLayer",
|
|
203
|
+
"picker.colorblind": "Couleurs adapt\xE9es au daltonisme",
|
|
204
|
+
"picker.orphanHint": "Cela laisse un si\xE8ge isol\xE9 \u2014 pensez \xE0 vous d\xE9caler d'un si\xE8ge.",
|
|
205
|
+
"picker.seats.one": "{count} si\xE8ge",
|
|
206
|
+
"picker.seats.other": "{count} si\xE8ges",
|
|
207
|
+
// renderer (drawn on the Konva map — shared by the embed SDK)
|
|
208
|
+
"map.aria": "Plan de salle. Utilisez les fl\xE8ches pour naviguer entre les si\xE8ges, Entr\xE9e pour s\xE9lectionner.",
|
|
209
|
+
"map.seatsLeft": "{count} LIBRES",
|
|
210
|
+
"map.fromPrice": "D\xC8S {price}",
|
|
211
|
+
// buyer picker page (src/pages/PickerPage.tsx)
|
|
212
|
+
"picker.language": "Langue",
|
|
213
|
+
"picker.zoomToFit": "Ajuster le zoom",
|
|
214
|
+
"picker.viewMode": "Mode d'affichage",
|
|
215
|
+
"picker.floor": "\xC9tage",
|
|
216
|
+
"picker.zoomLevel": "Niveau de zoom",
|
|
217
|
+
"picker.rungTip.zones": "Vue la plus large \u2014 blocs de zones (\xC9tage / Parquet inf\xE9rieur / Parquet sup\xE9rieur)",
|
|
218
|
+
"picker.rungTip.sections": "Blocs de sections \u2014 m\xE9lange de cat\xE9gories + teinte de disponibilit\xE9",
|
|
219
|
+
"picker.rungTip.seats": "Si\xE8ges individuels \u2014 les blocs se transforment en points",
|
|
220
|
+
"picker.rungLabel.zones": "ZONES",
|
|
221
|
+
"picker.rungLabel.sections": "SECTIONS",
|
|
222
|
+
"picker.rungLabel.seats": "SI\xC8GES",
|
|
223
|
+
"picker.sectionSummaryAria": "R\xE9sum\xE9 de la section {label}",
|
|
224
|
+
"picker.closeSectionSummary": "Fermer le r\xE9sum\xE9 de la section",
|
|
225
|
+
"picker.seatsLeftInSection.one": "{count} si\xE8ge restant",
|
|
226
|
+
"picker.seatsLeftInSection.other": "{count} si\xE8ges restants",
|
|
227
|
+
"picker.overview": "Vue d'ensemble",
|
|
228
|
+
"picker.tapSeatHint": "Appuyez sur un si\xE8ge pour v\xE9rifier sa vue",
|
|
229
|
+
"picker.chartSize": "Taille du plan",
|
|
230
|
+
"picker.custom": "Personnalis\xE9",
|
|
231
|
+
"picker.categories": "Cat\xE9gories",
|
|
232
|
+
"picker.accessibility": "Accessibilit\xE9",
|
|
233
|
+
"picker.showAnyAccessible": "Afficher un si\xE8ge accessible",
|
|
234
|
+
"picker.any": "N'importe quel",
|
|
235
|
+
"picker.yourSeats": "Vos si\xE8ges",
|
|
236
|
+
"picker.emptySeats": "Appuyez sur les si\xE8ges sur le plan",
|
|
237
|
+
"picker.emptySeatsWithGa": "Appuyez sur les si\xE8ges sur le plan \xB7 appuyez sur une zone debout pour les billets GA",
|
|
238
|
+
"picker.oneFewer": "Un de moins",
|
|
239
|
+
"picker.oneMore": "Un de plus",
|
|
240
|
+
"picker.remove": "Supprimer {label}",
|
|
241
|
+
"picker.ticketTierFor": "Cat\xE9gorie de billet pour {label}",
|
|
242
|
+
"picker.total": "Total : {amount}",
|
|
243
|
+
"picker.viewFromSeat": "Vue depuis le si\xE8ge {label}",
|
|
244
|
+
"picker.real360": "VRAI 360\xB0",
|
|
245
|
+
"picker.preview": "APER\xC7U",
|
|
246
|
+
"picker.open360": "Ouvrir la vue \xE0 360\xB0",
|
|
247
|
+
"picker.sightline": "\u2248 {rows} rang\xE9es de la sc\xE8ne \xB7 {angle}\xB0 d\xE9cal\xE9 du centre",
|
|
248
|
+
"picker.bookedDemo": "R\xE9serv\xE9 ! (d\xE9mo)",
|
|
249
|
+
"picker.bookButton.one": "R\xE9server {count} billet \u2014 {amount}",
|
|
250
|
+
"picker.bookButton.other": "R\xE9server {count} billets \u2014 {amount}",
|
|
251
|
+
"picker.simulateCrowd": "Simuler la foule : {state}",
|
|
252
|
+
"picker.on": "ACTIV\xC9",
|
|
253
|
+
"picker.off": "D\xC9SACTIV\xC9",
|
|
254
|
+
"picker.panorama360": "Photo panoramique 360\xB0 du lieu",
|
|
255
|
+
"picker.illustrationCaption": "illustration \xB7 \u2248 {m} m de la sc\xE8ne"
|
|
256
|
+
};
|
|
257
|
+
}
|
|
258
|
+
});
|
|
259
|
+
|
|
20
260
|
// src/index.ts
|
|
21
261
|
var index_exports = {};
|
|
22
262
|
__export(index_exports, {
|
|
23
263
|
ACCESSIBILITY_TYPES: () => ACCESSIBILITY_TYPES,
|
|
24
264
|
CHART_STORAGE_KEY: () => CHART_STORAGE_KEY,
|
|
265
|
+
DEFAULT_CURRENCY: () => DEFAULT_CURRENCY,
|
|
25
266
|
PickerController: () => PickerController,
|
|
267
|
+
SUPPORTED_LOCALES: () => SUPPORTED_LOCALES,
|
|
26
268
|
SeatmapRenderer: () => SeatmapRenderer,
|
|
269
|
+
UNGROUPED_ID: () => UNGROUPED_ID,
|
|
27
270
|
accessibilityMeta: () => accessibilityMeta,
|
|
271
|
+
allObjects: () => allObjects,
|
|
272
|
+
applyHidden: () => applyHidden,
|
|
28
273
|
chartBounds: () => chartBounds,
|
|
274
|
+
computeSections: () => computeSections,
|
|
29
275
|
createRenderer: () => createRenderer,
|
|
276
|
+
currencySymbol: () => currencySymbol,
|
|
30
277
|
expandBooth: () => expandBooth,
|
|
31
278
|
expandChart: () => expandChart,
|
|
32
279
|
expandRow: () => expandRow,
|
|
33
280
|
expandRowSlots: () => expandRowSlots,
|
|
34
281
|
expandTable: () => expandTable,
|
|
282
|
+
floorObjects: () => floorObjects,
|
|
283
|
+
floorsOf: () => floorsOf,
|
|
284
|
+
formatDate: () => formatDate,
|
|
285
|
+
formatMoney: () => formatMoney,
|
|
286
|
+
getLocale: () => getLocale,
|
|
287
|
+
hiddenObjectIds: () => hiddenObjectIds,
|
|
288
|
+
isSectionHidden: () => isSectionHidden,
|
|
35
289
|
layerOf: () => layerOf,
|
|
290
|
+
loadLocale: () => loadLocale,
|
|
36
291
|
objectCenter: () => objectCenter,
|
|
37
|
-
pointInPolygon: () => pointInPolygon
|
|
292
|
+
pointInPolygon: () => pointInPolygon,
|
|
293
|
+
resolveLocale: () => resolveLocale,
|
|
294
|
+
setLocale: () => setLocale,
|
|
295
|
+
setMoneyLocale: () => setMoneyLocale,
|
|
296
|
+
setStringOverrides: () => setStringOverrides,
|
|
297
|
+
stackFloors: () => stackFloors,
|
|
298
|
+
t: () => t,
|
|
299
|
+
tCount: () => tCount
|
|
38
300
|
});
|
|
39
301
|
module.exports = __toCommonJS(index_exports);
|
|
40
302
|
|
|
@@ -118,7 +380,15 @@ function expandRowSlots(row) {
|
|
|
118
380
|
const start = row.seatLabelStart ?? 1;
|
|
119
381
|
const dir = row.seatNumbering?.direction ?? "ltr";
|
|
120
382
|
const step = row.seatNumbering?.step ?? 1;
|
|
121
|
-
const
|
|
383
|
+
const n = row.seatCount;
|
|
384
|
+
let seatNumber;
|
|
385
|
+
if (dir === "center") {
|
|
386
|
+
const rank = new Array(n);
|
|
387
|
+
Array.from({ length: n }, (_, i) => i).sort((a, b) => Math.abs(2 * a - (n - 1)) - Math.abs(2 * b - (n - 1)) || a - b).forEach((idx, k) => rank[idx] = k);
|
|
388
|
+
seatNumber = (i) => start + rank[i] * step;
|
|
389
|
+
} else {
|
|
390
|
+
seatNumber = (i) => start + (dir === "rtl" ? n - 1 - i : i) * step;
|
|
391
|
+
}
|
|
122
392
|
const ov = overrideMap(row);
|
|
123
393
|
return rowSeatPositions(row).map((p, i) => {
|
|
124
394
|
const o = ov.get(i);
|
|
@@ -153,30 +423,41 @@ function expandRow(row) {
|
|
|
153
423
|
}
|
|
154
424
|
return seats;
|
|
155
425
|
}
|
|
156
|
-
function expandTable(
|
|
426
|
+
function expandTable(t2) {
|
|
157
427
|
const seats = [];
|
|
158
|
-
const n = Math.max(0, Math.round(
|
|
428
|
+
const n = Math.max(0, Math.round(t2.seatCount));
|
|
159
429
|
if (n === 0) return seats;
|
|
160
430
|
const mk = (i, x, y) => ({
|
|
161
|
-
id: `${
|
|
162
|
-
label: `${
|
|
431
|
+
id: `${t2.id}:${i}`,
|
|
432
|
+
label: `${t2.label}-${i + 1}`,
|
|
163
433
|
x,
|
|
164
434
|
y,
|
|
165
|
-
rowId:
|
|
166
|
-
categoryKey:
|
|
435
|
+
rowId: t2.id,
|
|
436
|
+
categoryKey: t2.categoryKey
|
|
167
437
|
});
|
|
168
|
-
if (
|
|
169
|
-
const R = (
|
|
170
|
-
const base =
|
|
438
|
+
if (t2.shape === "round") {
|
|
439
|
+
const R = (t2.radius ?? 40) + TABLE_SEAT_OFFSET;
|
|
440
|
+
const base = t2.rotation * DEG;
|
|
441
|
+
const arc = Math.max(0, Math.min(360, t2.seatArc ?? 360));
|
|
442
|
+
if (arc >= 360 || n === 1) {
|
|
443
|
+
for (let i = 0; i < n; i++) {
|
|
444
|
+
const a = base + i / n * 2 * Math.PI;
|
|
445
|
+
seats.push(mk(i, t2.center.x + R * Math.cos(a), t2.center.y + R * Math.sin(a)));
|
|
446
|
+
}
|
|
447
|
+
return seats;
|
|
448
|
+
}
|
|
449
|
+
const arcRad = arc * DEG;
|
|
450
|
+
const halfGap = (2 * Math.PI - arcRad) / 2;
|
|
451
|
+
const start = base + halfGap;
|
|
171
452
|
for (let i = 0; i < n; i++) {
|
|
172
|
-
const a =
|
|
173
|
-
seats.push(mk(i,
|
|
453
|
+
const a = start + i / (n - 1) * arcRad;
|
|
454
|
+
seats.push(mk(i, t2.center.x + R * Math.cos(a), t2.center.y + R * Math.sin(a)));
|
|
174
455
|
}
|
|
175
456
|
return seats;
|
|
176
457
|
}
|
|
177
|
-
const w =
|
|
178
|
-
const h =
|
|
179
|
-
const enabled =
|
|
458
|
+
const w = t2.width ?? 80;
|
|
459
|
+
const h = t2.height ?? 50;
|
|
460
|
+
const enabled = t2.sides && t2.sides.length ? t2.sides : ["top", "bottom"];
|
|
180
461
|
const order = ["top", "bottom", "left", "right"].filter((s) => enabled.includes(s));
|
|
181
462
|
if (!order.length) return seats;
|
|
182
463
|
const counts = new Map(order.map((s) => [s, 0]));
|
|
@@ -203,7 +484,7 @@ function expandTable(t) {
|
|
|
203
484
|
localX = w / 2 + TABLE_SEAT_OFFSET;
|
|
204
485
|
localY = -h / 2 + (j + 0.5) * h / count;
|
|
205
486
|
}
|
|
206
|
-
const p = place(localX, localY,
|
|
487
|
+
const p = place(localX, localY, t2.rotation, t2.center);
|
|
207
488
|
seats.push(mk(idx++, p.x, p.y));
|
|
208
489
|
}
|
|
209
490
|
}
|
|
@@ -274,9 +555,53 @@ function objectCenter(o) {
|
|
|
274
555
|
return { x: o.x ?? 0, y: o.y ?? 0 };
|
|
275
556
|
}
|
|
276
557
|
}
|
|
558
|
+
function floorsOf(doc) {
|
|
559
|
+
if (doc.floors && doc.floors.length) return doc.floors;
|
|
560
|
+
return [{ id: "floor-0", name: "Main", objects: doc.objects, focalPoint: doc.focalPoint, backgroundImage: doc.backgroundImage }];
|
|
561
|
+
}
|
|
562
|
+
function floorObjects(doc, floorId) {
|
|
563
|
+
const floors = floorsOf(doc);
|
|
564
|
+
return (floorId ? floors.find((f) => f.id === floorId) : floors[0])?.objects ?? [];
|
|
565
|
+
}
|
|
566
|
+
function allObjects(doc) {
|
|
567
|
+
return doc.floors && doc.floors.length ? doc.floors.flatMap((f) => f.objects) : doc.objects;
|
|
568
|
+
}
|
|
569
|
+
function translateObject(o, dx, dy) {
|
|
570
|
+
const p = (pt) => ({ x: pt.x + dx, y: pt.y + dy });
|
|
571
|
+
const pts = (a) => a.map(p);
|
|
572
|
+
switch (o.type) {
|
|
573
|
+
case "row":
|
|
574
|
+
return { ...o, origin: p(o.origin) };
|
|
575
|
+
case "table":
|
|
576
|
+
case "booth":
|
|
577
|
+
return { ...o, center: p(o.center) };
|
|
578
|
+
case "gaArea":
|
|
579
|
+
return { ...o, points: pts(o.points) };
|
|
580
|
+
case "section":
|
|
581
|
+
return { ...o, outline: pts(o.outline) };
|
|
582
|
+
case "text":
|
|
583
|
+
return { ...o, position: p(o.position) };
|
|
584
|
+
case "shape":
|
|
585
|
+
return {
|
|
586
|
+
...o,
|
|
587
|
+
...o.points ? { points: pts(o.points) } : {},
|
|
588
|
+
...o.x != null ? { x: o.x + dx } : {},
|
|
589
|
+
...o.y != null ? { y: o.y + dy } : {}
|
|
590
|
+
};
|
|
591
|
+
}
|
|
592
|
+
}
|
|
593
|
+
function stackFloors(doc, spread = 900) {
|
|
594
|
+
if (!doc.floors || doc.floors.length < 2) return doc;
|
|
595
|
+
const objects = [];
|
|
596
|
+
doc.floors.forEach((f, i) => {
|
|
597
|
+
const dy = -i * spread;
|
|
598
|
+
for (const o of f.objects) objects.push(dy === 0 ? o : translateObject(o, 0, dy));
|
|
599
|
+
});
|
|
600
|
+
return { ...doc, objects, floors: void 0 };
|
|
601
|
+
}
|
|
277
602
|
function expandChart(doc) {
|
|
278
603
|
const out = [];
|
|
279
|
-
for (const obj of doc
|
|
604
|
+
for (const obj of allObjects(doc)) {
|
|
280
605
|
if (obj.type === "row") out.push(...expandRow(obj));
|
|
281
606
|
else if (obj.type === "table") out.push(...expandTable(obj));
|
|
282
607
|
else if (obj.type === "booth") out.push(...expandBooth(obj));
|
|
@@ -296,7 +621,7 @@ function chartBounds(doc) {
|
|
|
296
621
|
if (y > maxY) maxY = y;
|
|
297
622
|
};
|
|
298
623
|
for (const s of expandChart(doc)) acc(s.x, s.y);
|
|
299
|
-
for (const obj of doc
|
|
624
|
+
for (const obj of allObjects(doc)) {
|
|
300
625
|
if (obj.type === "gaArea") {
|
|
301
626
|
for (const p of obj.points) acc(p.x, p.y);
|
|
302
627
|
} else if (obj.type === "section") {
|
|
@@ -341,6 +666,87 @@ function chartBounds(doc) {
|
|
|
341
666
|
};
|
|
342
667
|
}
|
|
343
668
|
|
|
669
|
+
// src/core/sections.ts
|
|
670
|
+
var UNGROUPED_ID = "__ungrouped__";
|
|
671
|
+
function objectSeatLabels(o) {
|
|
672
|
+
if (o.type === "row") return expandRow(o).map((s) => s.label);
|
|
673
|
+
if (o.type === "table") return expandTable(o).map((s) => s.label);
|
|
674
|
+
if (o.type === "booth") return expandBooth(o).map((s) => s.label);
|
|
675
|
+
return [];
|
|
676
|
+
}
|
|
677
|
+
function isSeatObject(o) {
|
|
678
|
+
return o.type === "row" || o.type === "table" || o.type === "booth";
|
|
679
|
+
}
|
|
680
|
+
function computeSections(doc) {
|
|
681
|
+
const objs = allObjects(doc);
|
|
682
|
+
const sectionObjs = objs.filter((o) => o.type === "section");
|
|
683
|
+
const nodes = /* @__PURE__ */ new Map();
|
|
684
|
+
for (const s of sectionObjs) {
|
|
685
|
+
nodes.set(s.id, { id: s.id, label: s.label || "Section", zone: s.zone, seatCount: 0, objectIds: [], seatLabels: [] });
|
|
686
|
+
}
|
|
687
|
+
const ungrouped = { id: UNGROUPED_ID, label: "Other seats", seatCount: 0, objectIds: [], seatLabels: [] };
|
|
688
|
+
const objectToSection = /* @__PURE__ */ new Map();
|
|
689
|
+
for (const obj of objs) {
|
|
690
|
+
if (!isSeatObject(obj)) continue;
|
|
691
|
+
const labels = objectSeatLabels(obj);
|
|
692
|
+
if (labels.length === 0) continue;
|
|
693
|
+
const c = objectCenter(obj);
|
|
694
|
+
const owner = sectionObjs.find((s) => pointInPolygon(c, s.outline));
|
|
695
|
+
const node = owner ? nodes.get(owner.id) : ungrouped;
|
|
696
|
+
node.seatCount += labels.length;
|
|
697
|
+
node.objectIds.push(obj.id);
|
|
698
|
+
node.seatLabels.push(...labels);
|
|
699
|
+
objectToSection.set(obj.id, node.id);
|
|
700
|
+
}
|
|
701
|
+
return {
|
|
702
|
+
sections: sectionObjs.map((s) => nodes.get(s.id)),
|
|
703
|
+
ungrouped: ungrouped.objectIds.length ? ungrouped : null,
|
|
704
|
+
objectToSection
|
|
705
|
+
};
|
|
706
|
+
}
|
|
707
|
+
function isSectionHidden(s, hidden) {
|
|
708
|
+
return hidden.has(s.id) || !!s.zone && hidden.has(s.zone);
|
|
709
|
+
}
|
|
710
|
+
function hiddenObjectIds(doc, hidden) {
|
|
711
|
+
const out = /* @__PURE__ */ new Set();
|
|
712
|
+
if (!hidden.size) return out;
|
|
713
|
+
const { sections, ungrouped, objectToSection } = computeSections(doc);
|
|
714
|
+
const hiddenSectionIds = /* @__PURE__ */ new Set();
|
|
715
|
+
const zoneById = new Map(sections.map((s) => [s.id, s.zone]));
|
|
716
|
+
for (const s of sections) {
|
|
717
|
+
const zone = zoneById.get(s.id);
|
|
718
|
+
if (hidden.has(s.id) || zone && hidden.has(zone)) hiddenSectionIds.add(s.id);
|
|
719
|
+
}
|
|
720
|
+
const ungroupedHidden = !!ungrouped && hidden.has(UNGROUPED_ID);
|
|
721
|
+
for (const [objId, secId] of objectToSection) {
|
|
722
|
+
if (hiddenSectionIds.has(secId) || ungroupedHidden && secId === UNGROUPED_ID) out.add(objId);
|
|
723
|
+
}
|
|
724
|
+
return out;
|
|
725
|
+
}
|
|
726
|
+
function applyHidden(doc, hidden) {
|
|
727
|
+
if (!hidden.size) return doc;
|
|
728
|
+
const hide = hiddenObjectIds(doc, hidden);
|
|
729
|
+
const hiddenSet = hidden instanceof Set ? hidden : new Set(hidden);
|
|
730
|
+
const keep = (o) => {
|
|
731
|
+
if (hide.has(o.id)) return false;
|
|
732
|
+
if (o.type === "section" && isSectionHidden(o, hiddenSet)) return false;
|
|
733
|
+
return true;
|
|
734
|
+
};
|
|
735
|
+
if (doc.floors && doc.floors.length) {
|
|
736
|
+
let changed = false;
|
|
737
|
+
const floors = doc.floors.map((f) => {
|
|
738
|
+
const objects2 = f.objects.filter(keep);
|
|
739
|
+
if (objects2.length !== f.objects.length) changed = true;
|
|
740
|
+
return objects2.length === f.objects.length ? f : { ...f, objects: objects2 };
|
|
741
|
+
});
|
|
742
|
+
if (!changed) return doc;
|
|
743
|
+
return { ...doc, floors, objects: floors[0].objects };
|
|
744
|
+
}
|
|
745
|
+
const objects = doc.objects.filter(keep);
|
|
746
|
+
if (objects.length === doc.objects.length) return doc;
|
|
747
|
+
return { ...doc, objects };
|
|
748
|
+
}
|
|
749
|
+
|
|
344
750
|
// src/engine/SeatmapRenderer.ts
|
|
345
751
|
var import_Core = require("konva/lib/Core");
|
|
346
752
|
var import_Stage = require("konva/lib/Stage");
|
|
@@ -352,26 +758,170 @@ var import_Ellipse = require("konva/lib/shapes/Ellipse");
|
|
|
352
758
|
var import_Line = require("konva/lib/shapes/Line");
|
|
353
759
|
var import_Text = require("konva/lib/shapes/Text");
|
|
354
760
|
var import_Image = require("konva/lib/shapes/Image");
|
|
761
|
+
|
|
762
|
+
// src/lib/money.ts
|
|
763
|
+
var DEFAULT_CURRENCY = "USD";
|
|
764
|
+
var displayLocale;
|
|
765
|
+
function setMoneyLocale(locale) {
|
|
766
|
+
displayLocale = locale;
|
|
767
|
+
}
|
|
768
|
+
var formatterCache = /* @__PURE__ */ new Map();
|
|
769
|
+
function formatter(currency, digits) {
|
|
770
|
+
const key = `${displayLocale ?? ""}|${currency}|${digits ?? "auto"}`;
|
|
771
|
+
let fmt = formatterCache.get(key);
|
|
772
|
+
if (!fmt) {
|
|
773
|
+
fmt = new Intl.NumberFormat(displayLocale, {
|
|
774
|
+
style: "currency",
|
|
775
|
+
currency,
|
|
776
|
+
minimumFractionDigits: digits,
|
|
777
|
+
maximumFractionDigits: digits
|
|
778
|
+
});
|
|
779
|
+
formatterCache.set(key, fmt);
|
|
780
|
+
}
|
|
781
|
+
return fmt;
|
|
782
|
+
}
|
|
783
|
+
function formatMoney(amount, currency = DEFAULT_CURRENCY, fractionDigits) {
|
|
784
|
+
const digits = fractionDigits ?? (Number.isInteger(amount) ? 0 : void 0);
|
|
785
|
+
return formatter(currency, digits).format(amount);
|
|
786
|
+
}
|
|
787
|
+
function currencySymbol(currency = DEFAULT_CURRENCY) {
|
|
788
|
+
const part = formatter(currency, 0).formatToParts(0).find((p) => p.type === "currency");
|
|
789
|
+
return part?.value ?? currency;
|
|
790
|
+
}
|
|
791
|
+
|
|
792
|
+
// src/i18n/locales/en.ts
|
|
793
|
+
var en = {
|
|
794
|
+
// shared
|
|
795
|
+
"common.cancel": "Cancel",
|
|
796
|
+
"common.close": "Close",
|
|
797
|
+
"common.done": "Done",
|
|
798
|
+
"common.copied": "\u2713 Copied",
|
|
799
|
+
// buyer picker (extraction in progress — P4 of the port plan)
|
|
800
|
+
"picker.holdSeats": "Hold seats & checkout",
|
|
801
|
+
"picker.completeBooking": "Complete booking",
|
|
802
|
+
"picker.seatsHeld": "Seats held \u2014 {time}",
|
|
803
|
+
"picker.holdExpired": "Your hold expired \u2014 the seats were released. Pick again.",
|
|
804
|
+
"picker.seatTaken": "Seat {label} was just taken by another buyer.",
|
|
805
|
+
"picker.poweredBy": "Powered by SeatLayer",
|
|
806
|
+
"picker.colorblind": "Colorblind-friendly colors",
|
|
807
|
+
"picker.orphanHint": "This leaves a single seat stranded \u2014 consider shifting one seat over.",
|
|
808
|
+
"picker.seats.one": "{count} seat",
|
|
809
|
+
"picker.seats.other": "{count} seats",
|
|
810
|
+
// renderer (drawn on the Konva map — shared by the embed SDK)
|
|
811
|
+
"map.aria": "Seating map. Use arrow keys to move between seats, Enter to select.",
|
|
812
|
+
"map.seatsLeft": "{count} LEFT",
|
|
813
|
+
"map.fromPrice": "FROM {price}",
|
|
814
|
+
// buyer picker page (src/pages/PickerPage.tsx)
|
|
815
|
+
"picker.language": "Language",
|
|
816
|
+
"picker.zoomToFit": "Zoom to fit",
|
|
817
|
+
"picker.viewMode": "View mode",
|
|
818
|
+
"picker.floor": "Floor",
|
|
819
|
+
"picker.zoomLevel": "Zoom level",
|
|
820
|
+
"picker.rungTip.zones": "Farthest rung \u2014 zone blocks (Floor / Lower Bowl / Upper Bowl)",
|
|
821
|
+
"picker.rungTip.sections": "Section blocks \u2014 category-mix fill + availability tint",
|
|
822
|
+
"picker.rungTip.seats": "Individual seats \u2014 blocks melt into dots",
|
|
823
|
+
"picker.rungLabel.zones": "ZONES",
|
|
824
|
+
"picker.rungLabel.sections": "SECTIONS",
|
|
825
|
+
"picker.rungLabel.seats": "SEATS",
|
|
826
|
+
"picker.sectionSummaryAria": "{label} section summary",
|
|
827
|
+
"picker.closeSectionSummary": "Close section summary",
|
|
828
|
+
"picker.seatsLeftInSection.one": "{count} seat left",
|
|
829
|
+
"picker.seatsLeftInSection.other": "{count} seats left",
|
|
830
|
+
"picker.overview": "Overview",
|
|
831
|
+
"picker.tapSeatHint": "Tap any seat to check its view",
|
|
832
|
+
"picker.chartSize": "Chart size",
|
|
833
|
+
"picker.custom": "Custom",
|
|
834
|
+
"picker.categories": "Categories",
|
|
835
|
+
"picker.accessibility": "Accessibility",
|
|
836
|
+
"picker.showAnyAccessible": "Show any accessible seat",
|
|
837
|
+
"picker.any": "Any",
|
|
838
|
+
"picker.yourSeats": "Your seats",
|
|
839
|
+
"picker.emptySeats": "Tap seats on the map",
|
|
840
|
+
"picker.emptySeatsWithGa": "Tap seats on the map \xB7 tap a standing area for GA tickets",
|
|
841
|
+
"picker.oneFewer": "One fewer",
|
|
842
|
+
"picker.oneMore": "One more",
|
|
843
|
+
"picker.remove": "Remove {label}",
|
|
844
|
+
"picker.ticketTierFor": "Ticket tier for {label}",
|
|
845
|
+
"picker.total": "Total: {amount}",
|
|
846
|
+
"picker.viewFromSeat": "View from seat {label}",
|
|
847
|
+
"picker.real360": "REAL 360\xB0",
|
|
848
|
+
"picker.preview": "PREVIEW",
|
|
849
|
+
"picker.open360": "Open 360\xB0 view",
|
|
850
|
+
"picker.sightline": "\u2248 {rows} rows from stage \xB7 {angle}\xB0 off-center",
|
|
851
|
+
"picker.bookedDemo": "Booked! (demo)",
|
|
852
|
+
"picker.bookButton.one": "Book {count} ticket \u2014 {amount}",
|
|
853
|
+
"picker.bookButton.other": "Book {count} tickets \u2014 {amount}",
|
|
854
|
+
"picker.simulateCrowd": "Simulate crowd: {state}",
|
|
855
|
+
"picker.on": "ON",
|
|
856
|
+
"picker.off": "OFF",
|
|
857
|
+
"picker.panorama360": "360\xB0 venue photo",
|
|
858
|
+
"picker.illustrationCaption": "illustration \xB7 \u2248 {m} m from stage"
|
|
859
|
+
};
|
|
860
|
+
|
|
861
|
+
// src/i18n/index.ts
|
|
862
|
+
var SUPPORTED_LOCALES = ["en", "es", "de", "fr"];
|
|
863
|
+
var bundles = { en };
|
|
864
|
+
var overrides = {};
|
|
865
|
+
var active = "en";
|
|
866
|
+
function resolveLocale(explicit, stored) {
|
|
867
|
+
for (const candidate of [explicit, stored, typeof navigator !== "undefined" ? navigator.language : null]) {
|
|
868
|
+
if (!candidate) continue;
|
|
869
|
+
const base = candidate.toLowerCase().split("-")[0];
|
|
870
|
+
if (SUPPORTED_LOCALES.includes(base)) return base;
|
|
871
|
+
}
|
|
872
|
+
return "en";
|
|
873
|
+
}
|
|
874
|
+
function getLocale() {
|
|
875
|
+
return active;
|
|
876
|
+
}
|
|
877
|
+
function setLocale(locale, bundle) {
|
|
878
|
+
if (bundle) bundles[locale] = { ...bundles[locale], ...bundle };
|
|
879
|
+
active = bundles[locale] ? locale : "en";
|
|
880
|
+
setMoneyLocale(active);
|
|
881
|
+
if (typeof document !== "undefined") document.documentElement.lang = active;
|
|
882
|
+
}
|
|
883
|
+
function setStringOverrides(next) {
|
|
884
|
+
overrides = next;
|
|
885
|
+
}
|
|
886
|
+
var PLACEHOLDER = /\{(\w+)\}/g;
|
|
887
|
+
function t(key, vars) {
|
|
888
|
+
const raw = overrides[key] ?? bundles[active]?.[key] ?? en[key] ?? key;
|
|
889
|
+
if (!vars) return raw;
|
|
890
|
+
return raw.replace(PLACEHOLDER, (_, name) => String(vars[name] ?? `{${name}}`));
|
|
891
|
+
}
|
|
892
|
+
function tCount(key, count, vars) {
|
|
893
|
+
const rule = new Intl.PluralRules(active).select(count);
|
|
894
|
+
const exact = overrides[`${key}.${rule}`] ?? bundles[active]?.[`${key}.${rule}`] ?? en[`${key}.${rule}`];
|
|
895
|
+
const raw = exact ?? overrides[`${key}.other`] ?? bundles[active]?.[`${key}.other`] ?? en[`${key}.other`] ?? key;
|
|
896
|
+
return raw.replace(PLACEHOLDER, (_, name) => String({ count, ...vars }[name] ?? `{${name}}`));
|
|
897
|
+
}
|
|
898
|
+
function formatDate(value, opts) {
|
|
899
|
+
return new Intl.DateTimeFormat(active, opts ?? { dateStyle: "medium", timeStyle: "short" }).format(value);
|
|
900
|
+
}
|
|
901
|
+
|
|
902
|
+
// src/engine/SeatmapRenderer.ts
|
|
355
903
|
var SEAT_RADIUS = 9;
|
|
356
904
|
var SEAT_LEGIBLE_SCALE = 0.9;
|
|
357
905
|
var CACHE_THRESHOLD = 0.55 * SEAT_LEGIBLE_SCALE;
|
|
358
906
|
var LABEL_SCALE = 1;
|
|
359
|
-
var SECTION_PROMINENT_SCALE = 0.
|
|
907
|
+
var SECTION_PROMINENT_SCALE = 0.45 * SEAT_LEGIBLE_SCALE;
|
|
908
|
+
var BLOCK_MELT_TOP = 0.9 * SEAT_LEGIBLE_SCALE;
|
|
360
909
|
var ZONE_PROMINENT_SCALE = 0.55 * SECTION_PROMINENT_SCALE;
|
|
361
910
|
var MAX_LABELS = 700;
|
|
362
911
|
var ISO_ANGLE_DEG = -11.5;
|
|
363
912
|
var ISO_SQUASH = 0.58;
|
|
364
|
-
var LIFT_PER_STEP =
|
|
913
|
+
var LIFT_PER_STEP = 58;
|
|
365
914
|
var ISO_TWEEN_MS = 320;
|
|
366
915
|
var BLOCK_FILL_ALPHA = 0.85;
|
|
367
916
|
var SOLD_DARKEN = 0.5;
|
|
368
|
-
var SECTION_LABEL_PX =
|
|
369
|
-
var SECTION_SUB_PX =
|
|
917
|
+
var SECTION_LABEL_PX = 20;
|
|
918
|
+
var SECTION_SUB_PX = 12.5;
|
|
370
919
|
var ZONE_LABEL_PX = 30;
|
|
371
920
|
var ZONE_SUB_PX = 12;
|
|
372
921
|
var HELD_FILL = "#6b7280";
|
|
373
922
|
var TAKEN_FILL = "#374151";
|
|
374
923
|
var NFS_STROKE = "#4b5563";
|
|
924
|
+
var CB_PALETTE = ["#E69F00", "#56B4E9", "#009E73", "#F0E442", "#0072B2", "#D55E00", "#CC79A7"];
|
|
375
925
|
var ACCESS_RING = {
|
|
376
926
|
wheelchair: "#3b82f6",
|
|
377
927
|
companion: "#8b5cf6",
|
|
@@ -383,7 +933,7 @@ var ACCESS_RING = {
|
|
|
383
933
|
};
|
|
384
934
|
function seatMatchesAccess(seat, filter) {
|
|
385
935
|
if (filter.length === 0) return !!seat.accessible;
|
|
386
|
-
return !!seat.accessibility?.some((
|
|
936
|
+
return !!seat.accessibility?.some((t2) => filter.includes(t2));
|
|
387
937
|
}
|
|
388
938
|
var DEF_SEAT_LABEL = "#0b1220";
|
|
389
939
|
var DEF_SELECTION = "#ffffff";
|
|
@@ -452,16 +1002,21 @@ function mixColors(parts, fallback) {
|
|
|
452
1002
|
}
|
|
453
1003
|
return tw > 0 ? toHex(r / tw, g / tw, b / tw) : fallback;
|
|
454
1004
|
}
|
|
455
|
-
function lerpColor(a, b,
|
|
1005
|
+
function lerpColor(a, b, t2) {
|
|
456
1006
|
const ca = hexToRgb(a);
|
|
457
1007
|
const cb = hexToRgb(b);
|
|
458
1008
|
if (!ca || !cb) return a;
|
|
459
|
-
return toHex(ca[0] + (cb[0] - ca[0]) *
|
|
1009
|
+
return toHex(ca[0] + (cb[0] - ca[0]) * t2, ca[1] + (cb[1] - ca[1]) * t2, ca[2] + (cb[2] - ca[2]) * t2);
|
|
460
1010
|
}
|
|
461
1011
|
var _SeatmapRenderer = class _SeatmapRenderer {
|
|
462
1012
|
constructor(container, options = {}) {
|
|
463
1013
|
this.seats = [];
|
|
464
1014
|
this.seatById = /* @__PURE__ */ new Map();
|
|
1015
|
+
/** Multi-floor (Batch 5): the last-set chart + which floor we're rendering. */
|
|
1016
|
+
this.chartDoc = null;
|
|
1017
|
+
this.activeFloorId = "";
|
|
1018
|
+
/** When true on a multi-floor chart, render ALL floors stacked (3D overview). */
|
|
1019
|
+
this.stacked = false;
|
|
465
1020
|
/** Interactive node per seat/booth — a Circle for seats, a Rect for booths. */
|
|
466
1021
|
this.circleById = /* @__PURE__ */ new Map();
|
|
467
1022
|
/** Booth block geometry, keyed by booth id (= the unit's rowId). */
|
|
@@ -469,6 +1024,10 @@ var _SeatmapRenderer = class _SeatmapRenderer {
|
|
|
469
1024
|
this.statusById = /* @__PURE__ */ new Map();
|
|
470
1025
|
this.catColor = /* @__PURE__ */ new Map();
|
|
471
1026
|
this.theme = {};
|
|
1027
|
+
/** Colorblind-safe mode (Okabe-Ito hues + hollow booked seats). */
|
|
1028
|
+
this.colorblind = false;
|
|
1029
|
+
/** Category order from the doc — the stable index into the CB palette. */
|
|
1030
|
+
this.catOrder = [];
|
|
472
1031
|
this.selection = /* @__PURE__ */ new Set();
|
|
473
1032
|
this.selectionRings = /* @__PURE__ */ new Map();
|
|
474
1033
|
this.focusedId = null;
|
|
@@ -485,6 +1044,10 @@ var _SeatmapRenderer = class _SeatmapRenderer {
|
|
|
485
1044
|
this.zones = [];
|
|
486
1045
|
this.seatSection = /* @__PURE__ */ new Map();
|
|
487
1046
|
this.catPrice = /* @__PURE__ */ new Map();
|
|
1047
|
+
/** Section/zone ids to render dimmed (organizer manager: held-back inventory). */
|
|
1048
|
+
this.dimmedSections = /* @__PURE__ */ new Set();
|
|
1049
|
+
/** Object id → floor id (multi-floor only) — resolves a deck tap in the 3D stack. */
|
|
1050
|
+
this.objectFloor = /* @__PURE__ */ new Map();
|
|
488
1051
|
/** Zone id → colour (drives extruded side faces in iso view). */
|
|
489
1052
|
this.zoneColor = /* @__PURE__ */ new Map();
|
|
490
1053
|
this.hasSections = false;
|
|
@@ -497,6 +1060,8 @@ var _SeatmapRenderer = class _SeatmapRenderer {
|
|
|
497
1060
|
this.isoRaf = 0;
|
|
498
1061
|
/** Chart centre the iso projection pivots about (bounds centre). */
|
|
499
1062
|
this.isoCentre = { x: 0, y: 0 };
|
|
1063
|
+
/** rAF for an in-flight camera glide (focusRegion / setRung); 0 = none. */
|
|
1064
|
+
this.glideRaf = 0;
|
|
500
1065
|
/** Set in destroy() so an in-flight iso tween bails. */
|
|
501
1066
|
this.destroyed = false;
|
|
502
1067
|
/** Cached scale the section/zone labels were last sized for (scale-compensation). */
|
|
@@ -544,6 +1109,7 @@ var _SeatmapRenderer = class _SeatmapRenderer {
|
|
|
544
1109
|
// pointer events away from Konva's canvas, killing its click/tap/hover
|
|
545
1110
|
// pipeline. We rely on bubbling instead.
|
|
546
1111
|
this.onPointerDown = (e) => {
|
|
1112
|
+
this.cancelGlide();
|
|
547
1113
|
this.pointers.set(e.pointerId, this.toLocal(e));
|
|
548
1114
|
if (this.pointers.size === 1) {
|
|
549
1115
|
this.moved = 0;
|
|
@@ -605,6 +1171,7 @@ var _SeatmapRenderer = class _SeatmapRenderer {
|
|
|
605
1171
|
};
|
|
606
1172
|
this.container = container;
|
|
607
1173
|
this.opts = { maxSelection: 10, selectableStatuses: ["free"], ...options };
|
|
1174
|
+
this.currency = options.currency;
|
|
608
1175
|
import_Core.Konva.pixelRatio = this.dpr;
|
|
609
1176
|
this.stage = new import_Stage.Stage({
|
|
610
1177
|
container,
|
|
@@ -621,7 +1188,7 @@ var _SeatmapRenderer = class _SeatmapRenderer {
|
|
|
621
1188
|
if (container.tabIndex < 0) container.tabIndex = 0;
|
|
622
1189
|
container.setAttribute("role", "application");
|
|
623
1190
|
if (!container.getAttribute("aria-label")) {
|
|
624
|
-
container.setAttribute("aria-label", "
|
|
1191
|
+
container.setAttribute("aria-label", t("map.aria"));
|
|
625
1192
|
}
|
|
626
1193
|
container.addEventListener("keydown", this.onKeyDown);
|
|
627
1194
|
this.bgLayer = new import_Layer.Layer({ listening: true });
|
|
@@ -664,7 +1231,15 @@ var _SeatmapRenderer = class _SeatmapRenderer {
|
|
|
664
1231
|
}
|
|
665
1232
|
}
|
|
666
1233
|
// ---- ISeatmapRenderer -----------------------------------------------------
|
|
667
|
-
setChart(doc) {
|
|
1234
|
+
setChart(doc, opts) {
|
|
1235
|
+
if (doc !== this.chartDoc) this.stacked = false;
|
|
1236
|
+
this.chartDoc = doc;
|
|
1237
|
+
this.activeFloorId = opts?.floorId ?? floorsOf(doc)[0].id;
|
|
1238
|
+
this.objectFloor.clear();
|
|
1239
|
+
if (doc.floors && doc.floors.length > 1) {
|
|
1240
|
+
for (const f of doc.floors) for (const o of f.objects) this.objectFloor.set(o.id, f.id);
|
|
1241
|
+
}
|
|
1242
|
+
const view = this.floorView(doc);
|
|
668
1243
|
this.focusedId = null;
|
|
669
1244
|
this.focusRing.visible(false);
|
|
670
1245
|
this.bgLayer.destroyChildren();
|
|
@@ -692,7 +1267,7 @@ var _SeatmapRenderer = class _SeatmapRenderer {
|
|
|
692
1267
|
this.isoT = 0;
|
|
693
1268
|
this.isoTarget = 0;
|
|
694
1269
|
this.resetLayerTransforms();
|
|
695
|
-
this.hasSections =
|
|
1270
|
+
this.hasSections = view.objects.some((o) => o.type === "section");
|
|
696
1271
|
for (const z of doc.zones ?? []) if (z.color) this.zoneColor.set(z.id, z.color);
|
|
697
1272
|
this.seatLayer.opacity(1);
|
|
698
1273
|
this.hoverRing.visible(false);
|
|
@@ -703,28 +1278,61 @@ var _SeatmapRenderer = class _SeatmapRenderer {
|
|
|
703
1278
|
this.hoverRing.radius(this.seatR + 2);
|
|
704
1279
|
this.catColor.clear();
|
|
705
1280
|
this.catPrice.clear();
|
|
1281
|
+
this.catOrder = doc.categories.map((c) => c.key);
|
|
706
1282
|
for (const c of doc.categories) {
|
|
707
1283
|
this.catColor.set(c.key, c.color);
|
|
708
1284
|
if (typeof c.price === "number") this.catPrice.set(c.key, c.price);
|
|
709
1285
|
}
|
|
710
|
-
for (const obj of
|
|
1286
|
+
for (const obj of view.objects) {
|
|
711
1287
|
if (obj.type === "booth") {
|
|
712
1288
|
this.boothDims.set(obj.id, { width: obj.width, height: obj.height, rotation: obj.rotation });
|
|
713
1289
|
}
|
|
714
1290
|
}
|
|
715
|
-
this.seats = expandChart(
|
|
1291
|
+
this.seats = expandChart(view);
|
|
716
1292
|
for (const s of this.seats) {
|
|
717
1293
|
this.seatById.set(s.id, s);
|
|
718
1294
|
this.statusById.set(s.id, "free");
|
|
719
1295
|
}
|
|
720
|
-
this.renderBackground(
|
|
1296
|
+
this.renderBackground(view);
|
|
721
1297
|
this.renderSeats();
|
|
722
1298
|
this.overlayLayer.add(this.labelGroup);
|
|
723
1299
|
this.overlayLayer.add(this.hoverRing);
|
|
724
|
-
this.bounds = chartBounds(
|
|
1300
|
+
this.bounds = chartBounds(view);
|
|
725
1301
|
this.isoCentre = { x: this.bounds.x + this.bounds.width / 2, y: this.bounds.y + this.bounds.height / 2 };
|
|
726
1302
|
this.zoomToFit();
|
|
727
1303
|
}
|
|
1304
|
+
/** The chart to render: all floors stacked (3D overview), the active floor, or
|
|
1305
|
+
* the whole chart for single-floor charts. */
|
|
1306
|
+
floorView(doc) {
|
|
1307
|
+
if (!doc.floors || !doc.floors.length) return doc;
|
|
1308
|
+
if (this.stacked && doc.floors.length >= 2) return stackFloors(doc);
|
|
1309
|
+
const floor = doc.floors.find((f) => f.id === this.activeFloorId) ?? doc.floors[0];
|
|
1310
|
+
return { ...doc, objects: floor.objects, focalPoint: floor.focalPoint, backgroundImage: floor.backgroundImage, floors: void 0 };
|
|
1311
|
+
}
|
|
1312
|
+
/** Toggle the 3D all-floors stacked overview (Batch 5). Re-renders; no-op on
|
|
1313
|
+
* single-floor charts. The caller re-applies statuses + animates the iso view. */
|
|
1314
|
+
setStacked(on) {
|
|
1315
|
+
if (!this.chartDoc || on === this.stacked) return;
|
|
1316
|
+
const multi = !!this.chartDoc.floors && this.chartDoc.floors.length >= 2;
|
|
1317
|
+
if (!multi) return;
|
|
1318
|
+
this.stacked = on;
|
|
1319
|
+
this.setChart(this.chartDoc, { floorId: this.activeFloorId });
|
|
1320
|
+
}
|
|
1321
|
+
isStacked() {
|
|
1322
|
+
return this.stacked;
|
|
1323
|
+
}
|
|
1324
|
+
/** Switch which floor is shown (2D). Re-renders + re-fits; no-op if unchanged. */
|
|
1325
|
+
setActiveFloor(floorId) {
|
|
1326
|
+
if (!this.chartDoc || floorId === this.activeFloorId) return;
|
|
1327
|
+
this.setChart(this.chartDoc, { floorId });
|
|
1328
|
+
}
|
|
1329
|
+
/** Floors for the host's switcher (single-floor charts return one synthetic floor). */
|
|
1330
|
+
getFloors() {
|
|
1331
|
+
return this.chartDoc ? floorsOf(this.chartDoc).map((f) => ({ id: f.id, name: f.name })) : [];
|
|
1332
|
+
}
|
|
1333
|
+
getActiveFloorId() {
|
|
1334
|
+
return this.activeFloorId;
|
|
1335
|
+
}
|
|
728
1336
|
setStatus(seatIds, status) {
|
|
729
1337
|
let touched = false;
|
|
730
1338
|
const affected = this.hasSections ? /* @__PURE__ */ new Set() : null;
|
|
@@ -804,11 +1412,11 @@ var _SeatmapRenderer = class _SeatmapRenderer {
|
|
|
804
1412
|
const dur = 620;
|
|
805
1413
|
const step = (now) => {
|
|
806
1414
|
if (!ring.getLayer()) return;
|
|
807
|
-
const
|
|
808
|
-
ring.radius(this.seatR * (1 +
|
|
809
|
-
ring.opacity(0.9 * (1 -
|
|
1415
|
+
const t2 = Math.min(1, (now - start) / dur);
|
|
1416
|
+
ring.radius(this.seatR * (1 + t2 * 1.8));
|
|
1417
|
+
ring.opacity(0.9 * (1 - t2));
|
|
810
1418
|
this.overlayLayer.batchDraw();
|
|
811
|
-
if (
|
|
1419
|
+
if (t2 < 1) requestAnimationFrame(step);
|
|
812
1420
|
else {
|
|
813
1421
|
ring.destroy();
|
|
814
1422
|
this.overlayLayer.batchDraw();
|
|
@@ -915,7 +1523,7 @@ var _SeatmapRenderer = class _SeatmapRenderer {
|
|
|
915
1523
|
sameAccessFilter(next) {
|
|
916
1524
|
const cur = this.accessFilter;
|
|
917
1525
|
if (cur === null || next === null) return cur === next;
|
|
918
|
-
return cur.length === next.length && cur.every((
|
|
1526
|
+
return cur.length === next.length && cur.every((t2, i) => t2 === next[i]);
|
|
919
1527
|
}
|
|
920
1528
|
// ---- isometric ("3D") view mode -------------------------------------------
|
|
921
1529
|
/**
|
|
@@ -963,6 +1571,10 @@ var _SeatmapRenderer = class _SeatmapRenderer {
|
|
|
963
1571
|
};
|
|
964
1572
|
this.isoRaf = requestAnimationFrame(step);
|
|
965
1573
|
}
|
|
1574
|
+
/** Current projection — reflects the tween target, not the mid-tween isoT. */
|
|
1575
|
+
getViewMode() {
|
|
1576
|
+
return this.isoTarget === 1 ? "isometric" : "flat";
|
|
1577
|
+
}
|
|
966
1578
|
/** Iso angle (rad) + y-squash for the current isoT. */
|
|
967
1579
|
isoParams() {
|
|
968
1580
|
return { th: ISO_ANGLE_DEG * Math.PI / 180 * this.isoT, sg: 1 - (1 - ISO_SQUASH) * this.isoT };
|
|
@@ -1020,8 +1632,8 @@ var _SeatmapRenderer = class _SeatmapRenderer {
|
|
|
1020
1632
|
* lift + extruded side faces on elevated sections.
|
|
1021
1633
|
*/
|
|
1022
1634
|
applyIso() {
|
|
1023
|
-
const
|
|
1024
|
-
if (
|
|
1635
|
+
const t2 = this.isoT;
|
|
1636
|
+
if (t2 === 0) {
|
|
1025
1637
|
this.resetLayerTransforms();
|
|
1026
1638
|
} else {
|
|
1027
1639
|
const { th, sg } = this.isoParams();
|
|
@@ -1074,13 +1686,13 @@ var _SeatmapRenderer = class _SeatmapRenderer {
|
|
|
1074
1686
|
}
|
|
1075
1687
|
/** Lift elevated sections (+ their members) and update their extruded side faces. */
|
|
1076
1688
|
applyElevation() {
|
|
1077
|
-
const
|
|
1689
|
+
const t2 = this.isoT;
|
|
1078
1690
|
for (const sec of this.sections) {
|
|
1079
1691
|
if (sec.elevation <= 0) continue;
|
|
1080
1692
|
const off = this.isoLiftLocal(sec.elevation);
|
|
1081
1693
|
sec.liftGroupBg?.position(off);
|
|
1082
1694
|
sec.liftGroupSeat?.position(off);
|
|
1083
|
-
const alpha = 0.9 *
|
|
1695
|
+
const alpha = 0.9 * t2;
|
|
1084
1696
|
for (let i = 0; i < sec.sideFaces.length; i++) {
|
|
1085
1697
|
const face = sec.sideFaces[i];
|
|
1086
1698
|
const p0 = sec.outline[i];
|
|
@@ -1094,6 +1706,7 @@ var _SeatmapRenderer = class _SeatmapRenderer {
|
|
|
1094
1706
|
this.destroyed = true;
|
|
1095
1707
|
if (this.rafId) cancelAnimationFrame(this.rafId);
|
|
1096
1708
|
if (this.isoRaf) cancelAnimationFrame(this.isoRaf);
|
|
1709
|
+
if (this.glideRaf) cancelAnimationFrame(this.glideRaf);
|
|
1097
1710
|
if (this.viewChangeRaf) cancelAnimationFrame(this.viewChangeRaf);
|
|
1098
1711
|
if (this.recacheTimer) clearTimeout(this.recacheTimer);
|
|
1099
1712
|
this.resizeObs?.disconnect();
|
|
@@ -1173,7 +1786,7 @@ var _SeatmapRenderer = class _SeatmapRenderer {
|
|
|
1173
1786
|
this.circleById.set(seat.id, rect);
|
|
1174
1787
|
this.paintSeat(rect, seat.id);
|
|
1175
1788
|
target.add(rect);
|
|
1176
|
-
const
|
|
1789
|
+
const t2 = new import_Text.Text({
|
|
1177
1790
|
x: seat.x,
|
|
1178
1791
|
y: seat.y,
|
|
1179
1792
|
text: seat.label,
|
|
@@ -1184,17 +1797,23 @@ var _SeatmapRenderer = class _SeatmapRenderer {
|
|
|
1184
1797
|
listening: false,
|
|
1185
1798
|
perfectDrawEnabled: false
|
|
1186
1799
|
});
|
|
1187
|
-
|
|
1188
|
-
|
|
1800
|
+
t2.offsetX(t2.width() / 2);
|
|
1801
|
+
t2.offsetY(t2.height() / 2);
|
|
1189
1802
|
this.hasBoothText = true;
|
|
1190
|
-
target.add(
|
|
1803
|
+
target.add(t2);
|
|
1804
|
+
}
|
|
1805
|
+
/** The category's display color — Okabe-Ito hue when colorblind-safe is on. */
|
|
1806
|
+
seatBaseColor(categoryKey) {
|
|
1807
|
+
if (!this.colorblind) return this.catColor.get(categoryKey) ?? "#6e7bff";
|
|
1808
|
+
const idx = this.catOrder.indexOf(categoryKey);
|
|
1809
|
+
return CB_PALETTE[(idx >= 0 ? idx : 0) % CB_PALETTE.length];
|
|
1191
1810
|
}
|
|
1192
1811
|
/** Apply fill/stroke/opacity for a seat's current status + selection. */
|
|
1193
1812
|
paintSeat(c, id) {
|
|
1194
1813
|
const seat = this.seatById.get(id);
|
|
1195
1814
|
const status = this.statusById.get(id) ?? "free";
|
|
1196
1815
|
const selected = this.selection.has(id);
|
|
1197
|
-
const base = this.
|
|
1816
|
+
const base = this.seatBaseColor(seat.categoryKey);
|
|
1198
1817
|
c.dash([]);
|
|
1199
1818
|
c.strokeWidth(0);
|
|
1200
1819
|
c.stroke("");
|
|
@@ -1207,8 +1826,15 @@ var _SeatmapRenderer = class _SeatmapRenderer {
|
|
|
1207
1826
|
c.fill(HELD_FILL);
|
|
1208
1827
|
break;
|
|
1209
1828
|
case "booked":
|
|
1210
|
-
|
|
1211
|
-
|
|
1829
|
+
if (this.colorblind) {
|
|
1830
|
+
c.fill("rgba(0,0,0,0)");
|
|
1831
|
+
c.stroke(TAKEN_FILL);
|
|
1832
|
+
c.strokeWidth(1.5);
|
|
1833
|
+
c.opacity(0.9);
|
|
1834
|
+
} else {
|
|
1835
|
+
c.fill(TAKEN_FILL);
|
|
1836
|
+
c.opacity(0.45);
|
|
1837
|
+
}
|
|
1212
1838
|
break;
|
|
1213
1839
|
case "not_for_sale":
|
|
1214
1840
|
c.fill(TAKEN_FILL);
|
|
@@ -1223,6 +1849,50 @@ var _SeatmapRenderer = class _SeatmapRenderer {
|
|
|
1223
1849
|
if (this.categoryHighlight && status === "free" && !selected && seat.categoryKey !== this.categoryHighlight) {
|
|
1224
1850
|
c.opacity(0.25);
|
|
1225
1851
|
}
|
|
1852
|
+
if (this.dimmedSections.size) {
|
|
1853
|
+
const sec = this.seatSection.get(id);
|
|
1854
|
+
if (sec && (this.dimmedSections.has(sec.id) || sec.zone != null && this.dimmedSections.has(sec.zone))) {
|
|
1855
|
+
c.opacity(0.18);
|
|
1856
|
+
}
|
|
1857
|
+
}
|
|
1858
|
+
}
|
|
1859
|
+
/**
|
|
1860
|
+
* Colorblind-safe mode: swap category hues for the Okabe-Ito palette and
|
|
1861
|
+
* render booked seats hollow. Off restores the exact default rendering.
|
|
1862
|
+
*/
|
|
1863
|
+
setColorblindSafe(on) {
|
|
1864
|
+
if (on === this.colorblind) return;
|
|
1865
|
+
this.colorblind = on;
|
|
1866
|
+
for (const seat of this.seats) {
|
|
1867
|
+
const c = this.circleById.get(seat.id);
|
|
1868
|
+
if (c) this.paintSeat(c, seat.id);
|
|
1869
|
+
}
|
|
1870
|
+
if (this.cached) {
|
|
1871
|
+
this.seatLayer.clearCache();
|
|
1872
|
+
this.cacheSeatLayer();
|
|
1873
|
+
} else {
|
|
1874
|
+
this.seatLayer.batchDraw();
|
|
1875
|
+
}
|
|
1876
|
+
}
|
|
1877
|
+
/**
|
|
1878
|
+
* Dim the seats of these section/zone ids (organizer manager use) so held-back
|
|
1879
|
+
* inventory is visually distinct on the canvas without hiding it. `null`/empty
|
|
1880
|
+
* clears. Unlike the buyer's applyHidden, the sections stay rendered.
|
|
1881
|
+
*/
|
|
1882
|
+
setDimmedSections(ids) {
|
|
1883
|
+
const next = new Set(ids ?? []);
|
|
1884
|
+
if (next.size === this.dimmedSections.size && [...next].every((i) => this.dimmedSections.has(i))) return;
|
|
1885
|
+
this.dimmedSections = next;
|
|
1886
|
+
for (const seat of this.seats) {
|
|
1887
|
+
const c = this.circleById.get(seat.id);
|
|
1888
|
+
if (c) this.paintSeat(c, seat.id);
|
|
1889
|
+
}
|
|
1890
|
+
if (this.cached) {
|
|
1891
|
+
this.seatLayer.clearCache();
|
|
1892
|
+
this.cacheSeatLayer();
|
|
1893
|
+
} else {
|
|
1894
|
+
this.seatLayer.batchDraw();
|
|
1895
|
+
}
|
|
1226
1896
|
}
|
|
1227
1897
|
/** Legend hover: highlight one category (dim the rest), or null to clear. */
|
|
1228
1898
|
setCategoryHighlight(key) {
|
|
@@ -1405,7 +2075,7 @@ var _SeatmapRenderer = class _SeatmapRenderer {
|
|
|
1405
2075
|
}
|
|
1406
2076
|
/** Prominent stage caption: uppercase, letter-spaced, larger, softly dimmed. */
|
|
1407
2077
|
addStageLabel(x, y, text) {
|
|
1408
|
-
const
|
|
2078
|
+
const t2 = new import_Text.Text({
|
|
1409
2079
|
x,
|
|
1410
2080
|
y,
|
|
1411
2081
|
text: text.toUpperCase(),
|
|
@@ -1417,9 +2087,9 @@ var _SeatmapRenderer = class _SeatmapRenderer {
|
|
|
1417
2087
|
listening: false,
|
|
1418
2088
|
perfectDrawEnabled: false
|
|
1419
2089
|
});
|
|
1420
|
-
|
|
1421
|
-
|
|
1422
|
-
this.bgLayer.add(
|
|
2090
|
+
t2.offsetX(t2.width() / 2);
|
|
2091
|
+
t2.offsetY(t2.height() / 2);
|
|
2092
|
+
this.bgLayer.add(t2);
|
|
1423
2093
|
}
|
|
1424
2094
|
renderGA(obj) {
|
|
1425
2095
|
const color = this.catColor.get(obj.categoryKey) ?? "#6e7bff";
|
|
@@ -1500,12 +2170,14 @@ var _SeatmapRenderer = class _SeatmapRenderer {
|
|
|
1500
2170
|
this.seatLayer.add(liftGroupSeat);
|
|
1501
2171
|
}
|
|
1502
2172
|
const bgTarget = liftGroupBg ?? this.bgLayer;
|
|
2173
|
+
const outlineTint = obj.color ?? this.zoneColor.get(obj.zone ?? "") ?? "#3a4358";
|
|
1503
2174
|
const outlinePoly = new import_Line.Line({
|
|
1504
2175
|
points: pts,
|
|
1505
2176
|
closed: true,
|
|
1506
|
-
stroke:
|
|
1507
|
-
strokeWidth: 1.
|
|
1508
|
-
fill: rgba(
|
|
2177
|
+
stroke: rgba(outlineTint, 0.5),
|
|
2178
|
+
strokeWidth: 1.75,
|
|
2179
|
+
fill: rgba(outlineTint, 0.08),
|
|
2180
|
+
lineJoin: "round",
|
|
1509
2181
|
listening: false,
|
|
1510
2182
|
perfectDrawEnabled: false
|
|
1511
2183
|
});
|
|
@@ -1521,6 +2193,30 @@ var _SeatmapRenderer = class _SeatmapRenderer {
|
|
|
1521
2193
|
perfectDrawEnabled: false
|
|
1522
2194
|
});
|
|
1523
2195
|
bgTarget.add(blockPoly);
|
|
2196
|
+
const rowSeats = /* @__PURE__ */ new Map();
|
|
2197
|
+
for (const id of memberIds) {
|
|
2198
|
+
const s = this.seatById.get(id);
|
|
2199
|
+
if (!s) continue;
|
|
2200
|
+
const i = Number(id.slice(id.lastIndexOf(":") + 1)) || 0;
|
|
2201
|
+
(rowSeats.get(s.rowId) ?? rowSeats.set(s.rowId, []).get(s.rowId)).push({ i, x: s.x, y: s.y });
|
|
2202
|
+
}
|
|
2203
|
+
const rowLines = [];
|
|
2204
|
+
for (const arr of rowSeats.values()) {
|
|
2205
|
+
if (arr.length < 2) continue;
|
|
2206
|
+
arr.sort((a, b) => a.i - b.i);
|
|
2207
|
+
const line = new import_Line.Line({
|
|
2208
|
+
points: arr.flatMap((p) => [p.x, p.y]),
|
|
2209
|
+
stroke: rgba("#ffffff", 0.34),
|
|
2210
|
+
strokeWidth: SEAT_RADIUS * 0.55,
|
|
2211
|
+
lineCap: "round",
|
|
2212
|
+
lineJoin: "round",
|
|
2213
|
+
opacity: 0,
|
|
2214
|
+
listening: false,
|
|
2215
|
+
perfectDrawEnabled: false
|
|
2216
|
+
});
|
|
2217
|
+
rowLines.push(line);
|
|
2218
|
+
bgTarget.add(line);
|
|
2219
|
+
}
|
|
1524
2220
|
const nameLabel = new import_Text.Text({
|
|
1525
2221
|
x: centroid.x,
|
|
1526
2222
|
y: centroid.y,
|
|
@@ -1529,6 +2225,11 @@ var _SeatmapRenderer = class _SeatmapRenderer {
|
|
|
1529
2225
|
fontStyle: "700",
|
|
1530
2226
|
fontFamily: this.labelFont(),
|
|
1531
2227
|
fill: "#8b93a7",
|
|
2228
|
+
// Dark halo so the label reads over the seat dots at any zoom.
|
|
2229
|
+
shadowColor: "#05070c",
|
|
2230
|
+
shadowBlur: 6,
|
|
2231
|
+
shadowOpacity: 0.9,
|
|
2232
|
+
shadowForStrokeEnabled: false,
|
|
1532
2233
|
listening: false,
|
|
1533
2234
|
perfectDrawEnabled: false
|
|
1534
2235
|
});
|
|
@@ -1538,11 +2239,15 @@ var _SeatmapRenderer = class _SeatmapRenderer {
|
|
|
1538
2239
|
const subLabel = new import_Text.Text({
|
|
1539
2240
|
x: centroid.x,
|
|
1540
2241
|
y: centroid.y,
|
|
1541
|
-
text:
|
|
2242
|
+
text: t("map.seatsLeft", { count: free }),
|
|
1542
2243
|
fontSize: 12,
|
|
1543
|
-
fontStyle: "
|
|
2244
|
+
fontStyle: "700",
|
|
1544
2245
|
fontFamily: "JetBrains Mono, ui-monospace, monospace",
|
|
1545
|
-
fill:
|
|
2246
|
+
fill: "#f4f6fb",
|
|
2247
|
+
shadowColor: "#05070c",
|
|
2248
|
+
shadowBlur: 5,
|
|
2249
|
+
shadowOpacity: 0.9,
|
|
2250
|
+
shadowForStrokeEnabled: false,
|
|
1546
2251
|
opacity: 0,
|
|
1547
2252
|
listening: false,
|
|
1548
2253
|
perfectDrawEnabled: false
|
|
@@ -1561,6 +2266,7 @@ var _SeatmapRenderer = class _SeatmapRenderer {
|
|
|
1561
2266
|
baseFill,
|
|
1562
2267
|
outlinePoly,
|
|
1563
2268
|
blockPoly,
|
|
2269
|
+
rowLines,
|
|
1564
2270
|
nameLabel,
|
|
1565
2271
|
subLabel,
|
|
1566
2272
|
elevation,
|
|
@@ -1576,7 +2282,7 @@ var _SeatmapRenderer = class _SeatmapRenderer {
|
|
|
1576
2282
|
refreshSectionFill(sec) {
|
|
1577
2283
|
const sold = sec.total > 0 ? (sec.total - sec.free) / sec.total : 0;
|
|
1578
2284
|
sec.blockPoly.fill(darken(sec.baseFill, sold * SOLD_DARKEN));
|
|
1579
|
-
sec.subLabel.text(
|
|
2285
|
+
sec.subLabel.text(t("map.seatsLeft", { count: sec.free }));
|
|
1580
2286
|
sec.subLabel.offsetX(sec.subLabel.width() / 2);
|
|
1581
2287
|
}
|
|
1582
2288
|
/**
|
|
@@ -1594,8 +2300,10 @@ var _SeatmapRenderer = class _SeatmapRenderer {
|
|
|
1594
2300
|
for (const z of doc.zones) {
|
|
1595
2301
|
const members = byZone.get(z.id);
|
|
1596
2302
|
if (!members || !members.length) continue;
|
|
1597
|
-
|
|
1598
|
-
const
|
|
2303
|
+
let anchor = members[0];
|
|
2304
|
+
for (const s of members) if (s.centroid.y < anchor.centroid.y) anchor = s;
|
|
2305
|
+
const cx = anchor.centroid.x;
|
|
2306
|
+
const cy = anchor.centroid.y;
|
|
1599
2307
|
let minPrice = Infinity;
|
|
1600
2308
|
for (const sec of members) {
|
|
1601
2309
|
for (const id of sec.memberIds) {
|
|
@@ -1613,6 +2321,10 @@ var _SeatmapRenderer = class _SeatmapRenderer {
|
|
|
1613
2321
|
letterSpacing: 2,
|
|
1614
2322
|
fontFamily: this.labelFont(),
|
|
1615
2323
|
fill: z.color ?? "#f2f4f8",
|
|
2324
|
+
shadowColor: "#05070c",
|
|
2325
|
+
shadowBlur: 10,
|
|
2326
|
+
shadowOpacity: 0.95,
|
|
2327
|
+
shadowForStrokeEnabled: false,
|
|
1616
2328
|
opacity: 0,
|
|
1617
2329
|
listening: false,
|
|
1618
2330
|
perfectDrawEnabled: false
|
|
@@ -1625,7 +2337,7 @@ var _SeatmapRenderer = class _SeatmapRenderer {
|
|
|
1625
2337
|
sub = new import_Text.Text({
|
|
1626
2338
|
x: cx,
|
|
1627
2339
|
y: cy,
|
|
1628
|
-
text:
|
|
2340
|
+
text: t("map.fromPrice", { price: formatMoney(minPrice, this.currency) }),
|
|
1629
2341
|
fontSize: 14,
|
|
1630
2342
|
fontStyle: "600",
|
|
1631
2343
|
fontFamily: "JetBrains Mono, ui-monospace, monospace",
|
|
@@ -1656,7 +2368,7 @@ var _SeatmapRenderer = class _SeatmapRenderer {
|
|
|
1656
2368
|
blockT = scale <= SECTION_PROMINENT_SCALE ? 1 : 0;
|
|
1657
2369
|
zoneT = scale <= ZONE_PROMINENT_SCALE ? 1 : 0;
|
|
1658
2370
|
} else {
|
|
1659
|
-
blockT = clamp((
|
|
2371
|
+
blockT = clamp((BLOCK_MELT_TOP - scale) / (BLOCK_MELT_TOP - SECTION_PROMINENT_SCALE), 0, 1);
|
|
1660
2372
|
zoneT = clamp((SECTION_PROMINENT_SCALE - scale) / (SECTION_PROMINENT_SCALE - ZONE_PROMINENT_SCALE), 0, 1);
|
|
1661
2373
|
}
|
|
1662
2374
|
if (!this.zones.length) zoneT = 0;
|
|
@@ -1666,7 +2378,8 @@ var _SeatmapRenderer = class _SeatmapRenderer {
|
|
|
1666
2378
|
if (rescale) this.lodScale = scale;
|
|
1667
2379
|
for (const sec of this.sections) {
|
|
1668
2380
|
sec.blockPoly.opacity(BLOCK_FILL_ALPHA * blockT);
|
|
1669
|
-
sec.
|
|
2381
|
+
for (const line of sec.rowLines) line.opacity(blockT * (1 - zoneT));
|
|
2382
|
+
sec.nameLabel.fill(lerpColor("#aab3c5", "#ffffff", blockT));
|
|
1670
2383
|
sec.nameLabel.opacity(1 - zoneT);
|
|
1671
2384
|
sec.subLabel.opacity(blockT * (1 - zoneT));
|
|
1672
2385
|
if (rescale) {
|
|
@@ -1674,9 +2387,10 @@ var _SeatmapRenderer = class _SeatmapRenderer {
|
|
|
1674
2387
|
this.sizeLabel(sec.subLabel, SECTION_SUB_PX / sx, sec.centroid.y + SECTION_LABEL_PX / sx);
|
|
1675
2388
|
}
|
|
1676
2389
|
}
|
|
2390
|
+
const zoneOpacity = zoneT * (1 - this.isoT);
|
|
1677
2391
|
for (const zone of this.zones) {
|
|
1678
|
-
zone.label.opacity(
|
|
1679
|
-
if (zone.sub) zone.sub.opacity(
|
|
2392
|
+
zone.label.opacity(zoneOpacity);
|
|
2393
|
+
if (zone.sub) zone.sub.opacity(zoneOpacity);
|
|
1680
2394
|
if (rescale) {
|
|
1681
2395
|
const cy = zone.label.y();
|
|
1682
2396
|
this.sizeLabel(zone.label, ZONE_LABEL_PX / sx, cy);
|
|
@@ -1686,11 +2400,11 @@ var _SeatmapRenderer = class _SeatmapRenderer {
|
|
|
1686
2400
|
this.bgLayer.batchDraw();
|
|
1687
2401
|
}
|
|
1688
2402
|
/** Set a centred label's world fontSize (for a target screen px) and re-anchor it. */
|
|
1689
|
-
sizeLabel(
|
|
1690
|
-
|
|
1691
|
-
|
|
1692
|
-
|
|
1693
|
-
|
|
2403
|
+
sizeLabel(t2, fontSize, y) {
|
|
2404
|
+
t2.fontSize(Math.max(1, fontSize));
|
|
2405
|
+
t2.offsetX(t2.width() / 2);
|
|
2406
|
+
t2.offsetY(t2.height() / 2);
|
|
2407
|
+
t2.y(y);
|
|
1694
2408
|
}
|
|
1695
2409
|
/**
|
|
1696
2410
|
* Map a container-relative screen point back to world coords. Inverts the
|
|
@@ -1714,7 +2428,7 @@ var _SeatmapRenderer = class _SeatmapRenderer {
|
|
|
1714
2428
|
return this.sections.find((s) => s.id === id)?.memberIds.slice() ?? [];
|
|
1715
2429
|
}
|
|
1716
2430
|
addCentredLabel(layer, text, x, y, fill, fontSize, bold) {
|
|
1717
|
-
const
|
|
2431
|
+
const t2 = new import_Text.Text({
|
|
1718
2432
|
x,
|
|
1719
2433
|
y,
|
|
1720
2434
|
text,
|
|
@@ -1725,9 +2439,9 @@ var _SeatmapRenderer = class _SeatmapRenderer {
|
|
|
1725
2439
|
listening: false,
|
|
1726
2440
|
perfectDrawEnabled: false
|
|
1727
2441
|
});
|
|
1728
|
-
|
|
1729
|
-
|
|
1730
|
-
layer.add(
|
|
2442
|
+
t2.offsetX(t2.width() / 2);
|
|
2443
|
+
t2.offsetY(t2.height() / 2);
|
|
2444
|
+
layer.add(t2);
|
|
1731
2445
|
}
|
|
1732
2446
|
// ---- selection ------------------------------------------------------------
|
|
1733
2447
|
isSelectable(id) {
|
|
@@ -1781,7 +2495,15 @@ var _SeatmapRenderer = class _SeatmapRenderer {
|
|
|
1781
2495
|
this.seatLayer.on("click tap", (e) => {
|
|
1782
2496
|
if (this.moved > 8) return;
|
|
1783
2497
|
const id = seatIdOf(e.target);
|
|
1784
|
-
if (id)
|
|
2498
|
+
if (!id) return;
|
|
2499
|
+
if (this.stacked && this.opts.onDeckTap) {
|
|
2500
|
+
const floorId = this.objectFloor.get(this.seatById.get(id)?.rowId ?? "");
|
|
2501
|
+
if (floorId) {
|
|
2502
|
+
this.opts.onDeckTap(floorId);
|
|
2503
|
+
return;
|
|
2504
|
+
}
|
|
2505
|
+
}
|
|
2506
|
+
this.toggleSeat(id);
|
|
1785
2507
|
});
|
|
1786
2508
|
this.seatLayer.on("mouseover", (e) => {
|
|
1787
2509
|
const id = seatIdOf(e.target);
|
|
@@ -1811,11 +2533,19 @@ var _SeatmapRenderer = class _SeatmapRenderer {
|
|
|
1811
2533
|
if (!this.cached || this.moved > 8) return;
|
|
1812
2534
|
const pointer = this.stage.getPointerPosition();
|
|
1813
2535
|
if (!pointer) return;
|
|
2536
|
+
if (this.stacked && this.opts.onDeckTap) {
|
|
2537
|
+
const floorId = this.deckFloorAt();
|
|
2538
|
+
if (floorId) {
|
|
2539
|
+
this.opts.onDeckTap(floorId);
|
|
2540
|
+
return;
|
|
2541
|
+
}
|
|
2542
|
+
}
|
|
1814
2543
|
if (this.sections.length) {
|
|
1815
2544
|
const world = this.screenToWorld(pointer);
|
|
1816
2545
|
const hit = this.sections.find((sn) => pointInPolygon(world, sn.outline));
|
|
1817
2546
|
if (hit) {
|
|
1818
|
-
this.
|
|
2547
|
+
if (this.opts.onSectionTap) this.opts.onSectionTap(hit.id);
|
|
2548
|
+
else this.focusRegion(hit.id);
|
|
1819
2549
|
return;
|
|
1820
2550
|
}
|
|
1821
2551
|
}
|
|
@@ -1823,6 +2553,30 @@ var _SeatmapRenderer = class _SeatmapRenderer {
|
|
|
1823
2553
|
this.zoomAbout(target, pointer);
|
|
1824
2554
|
});
|
|
1825
2555
|
}
|
|
2556
|
+
/**
|
|
2557
|
+
* Which deck (floor) a screen tap landed on in the 3D stacked overview. The
|
|
2558
|
+
* seat layer is melt-cached at that zoom so individual seat nodes aren't
|
|
2559
|
+
* hit-testable; instead we invert the iso+stage transform via the layer's
|
|
2560
|
+
* relative pointer and take the nearest seat's floor. Only the floor matters,
|
|
2561
|
+
* so within-floor elevation offsets don't affect the result. Null if none.
|
|
2562
|
+
*/
|
|
2563
|
+
deckFloorAt() {
|
|
2564
|
+
if (!this.objectFloor.size) return null;
|
|
2565
|
+
const p = this.seatLayer.getRelativePointerPosition();
|
|
2566
|
+
if (!p) return null;
|
|
2567
|
+
let best = null;
|
|
2568
|
+
let bestD = Infinity;
|
|
2569
|
+
for (const s of this.seats) {
|
|
2570
|
+
const dx = s.x - p.x;
|
|
2571
|
+
const dy = s.y - p.y;
|
|
2572
|
+
const d = dx * dx + dy * dy;
|
|
2573
|
+
if (d < bestD) {
|
|
2574
|
+
bestD = d;
|
|
2575
|
+
best = s.rowId;
|
|
2576
|
+
}
|
|
2577
|
+
}
|
|
2578
|
+
return best ? this.objectFloor.get(best) ?? null : null;
|
|
2579
|
+
}
|
|
1826
2580
|
// ---- Pan & pinch (raw pointer events — mouse, touch and pen alike) --------
|
|
1827
2581
|
toLocal(e) {
|
|
1828
2582
|
const r = this.container.getBoundingClientRect();
|
|
@@ -1833,7 +2587,9 @@ var _SeatmapRenderer = class _SeatmapRenderer {
|
|
|
1833
2587
|
* reach a readable size on any chart, however large the venue.
|
|
1834
2588
|
*/
|
|
1835
2589
|
zoomBounds() {
|
|
1836
|
-
|
|
2590
|
+
let min = 0.5 * this.fitScale;
|
|
2591
|
+
if (this.zones.length) min = Math.min(min, ZONE_PROMINENT_SCALE * 0.9);
|
|
2592
|
+
return { min, max: Math.max(10 * this.fitScale, 4) };
|
|
1837
2593
|
}
|
|
1838
2594
|
/** Zoom so `clientPoint` (stage-relative px) stays fixed under the cursor. */
|
|
1839
2595
|
zoomAbout(nextScale, clientPoint) {
|
|
@@ -1866,6 +2622,92 @@ var _SeatmapRenderer = class _SeatmapRenderer {
|
|
|
1866
2622
|
this.afterViewChange();
|
|
1867
2623
|
this.stage.batchDraw();
|
|
1868
2624
|
}
|
|
2625
|
+
/**
|
|
2626
|
+
* Smoothly glide the camera to frame a section (by id) or a world-space bounds
|
|
2627
|
+
* rect — the Slice 5 "glide in". Pan+zoom tween over ~450ms easeInOutCubic; the
|
|
2628
|
+
* melt/LOD rides the camera every frame. `prefers-reduced-motion` (or
|
|
2629
|
+
* `opts.animate === false`) snaps via zoomToBounds. A grab (pointer-down) or a
|
|
2630
|
+
* newer glide cancels an in-flight one.
|
|
2631
|
+
*/
|
|
2632
|
+
focusRegion(target, opts) {
|
|
2633
|
+
const b = typeof target === "string" ? (() => {
|
|
2634
|
+
const sec = this.sections.find((s) => s.id === target);
|
|
2635
|
+
return sec ? polyBounds(sec.outline) : null;
|
|
2636
|
+
})() : target;
|
|
2637
|
+
if (!b) return;
|
|
2638
|
+
this.cancelGlide();
|
|
2639
|
+
if (opts?.animate === false || this.reducedMotion) {
|
|
2640
|
+
this.zoomToBounds(b);
|
|
2641
|
+
return;
|
|
2642
|
+
}
|
|
2643
|
+
const w = this.stage.width();
|
|
2644
|
+
const h = this.stage.height();
|
|
2645
|
+
const { min, max } = this.zoomBounds();
|
|
2646
|
+
const margin = 1.12;
|
|
2647
|
+
const toScale = clamp(Math.min(w / (b.width * margin), h / (b.height * margin)), min, max);
|
|
2648
|
+
const toX = w / 2 - (b.x + b.width / 2) * toScale;
|
|
2649
|
+
const toY = h / 2 - (b.y + b.height / 2) * toScale;
|
|
2650
|
+
const fromScale = this.stage.scaleX();
|
|
2651
|
+
const fromX = this.stage.x();
|
|
2652
|
+
const fromY = this.stage.y();
|
|
2653
|
+
if (Math.abs(toScale - fromScale) < 1e-4 && Math.abs(toX - fromX) < 0.5 && Math.abs(toY - fromY) < 0.5) {
|
|
2654
|
+
this.afterViewChange();
|
|
2655
|
+
return;
|
|
2656
|
+
}
|
|
2657
|
+
const start = performance.now();
|
|
2658
|
+
const DUR = 450;
|
|
2659
|
+
const step = (now) => {
|
|
2660
|
+
if (this.destroyed) {
|
|
2661
|
+
this.glideRaf = 0;
|
|
2662
|
+
return;
|
|
2663
|
+
}
|
|
2664
|
+
const raw = Math.min(1, (now - start) / DUR);
|
|
2665
|
+
const e = raw < 0.5 ? 4 * raw * raw * raw : 1 - Math.pow(-2 * raw + 2, 3) / 2;
|
|
2666
|
+
const sc = fromScale + (toScale - fromScale) * e;
|
|
2667
|
+
this.stage.scale({ x: sc, y: sc });
|
|
2668
|
+
this.stage.position({ x: fromX + (toX - fromX) * e, y: fromY + (toY - fromY) * e });
|
|
2669
|
+
this.updateLOD();
|
|
2670
|
+
this.scheduleViewChange();
|
|
2671
|
+
this.stage.batchDraw();
|
|
2672
|
+
if (raw < 1) {
|
|
2673
|
+
this.glideRaf = requestAnimationFrame(step);
|
|
2674
|
+
} else {
|
|
2675
|
+
this.glideRaf = 0;
|
|
2676
|
+
this.afterViewChange();
|
|
2677
|
+
}
|
|
2678
|
+
};
|
|
2679
|
+
this.glideRaf = requestAnimationFrame(step);
|
|
2680
|
+
}
|
|
2681
|
+
/** Cancel an in-flight camera glide (a grab or a newer glide interrupts it). */
|
|
2682
|
+
cancelGlide() {
|
|
2683
|
+
if (this.glideRaf) {
|
|
2684
|
+
cancelAnimationFrame(this.glideRaf);
|
|
2685
|
+
this.glideRaf = 0;
|
|
2686
|
+
}
|
|
2687
|
+
}
|
|
2688
|
+
/** Current LOD rung derived from effective zoom — drives the ZONES/SECTIONS/SEATS pill. */
|
|
2689
|
+
getRung() {
|
|
2690
|
+
const scale = this.effScale();
|
|
2691
|
+
if (scale >= CACHE_THRESHOLD) return "seats";
|
|
2692
|
+
if (this.zones.length && scale < ZONE_PROMINENT_SCALE) return "zones";
|
|
2693
|
+
return "sections";
|
|
2694
|
+
}
|
|
2695
|
+
/** Jump the camera to a rung's zoom band, centred on the chart (glided). */
|
|
2696
|
+
setRung(rung) {
|
|
2697
|
+
if (rung === "zones") {
|
|
2698
|
+
this.cancelGlide();
|
|
2699
|
+
this.zoomToFit();
|
|
2700
|
+
return;
|
|
2701
|
+
}
|
|
2702
|
+
const target = rung === "sections" ? (SECTION_PROMINENT_SCALE + CACHE_THRESHOLD) / 2 : Math.max(SEAT_LEGIBLE_SCALE * 1.1, CACHE_THRESHOLD * 1.3);
|
|
2703
|
+
const w = this.stage.width();
|
|
2704
|
+
const h = this.stage.height();
|
|
2705
|
+
const cx = this.bounds.x + this.bounds.width / 2;
|
|
2706
|
+
const cy = this.bounds.y + this.bounds.height / 2;
|
|
2707
|
+
const bw = w / (target * 1.12);
|
|
2708
|
+
const bh = h / (target * 1.12);
|
|
2709
|
+
this.focusRegion({ x: cx - bw / 2, y: cy - bh / 2, width: bw, height: bh });
|
|
2710
|
+
}
|
|
1869
2711
|
/** Recompute LOD (cache/labels) after any pan/zoom settles. */
|
|
1870
2712
|
afterViewChange() {
|
|
1871
2713
|
this.updateLOD();
|
|
@@ -1916,7 +2758,7 @@ var _SeatmapRenderer = class _SeatmapRenderer {
|
|
|
1916
2758
|
let count = 0;
|
|
1917
2759
|
for (const seat of this.seats) {
|
|
1918
2760
|
if (seat.x < x0 || seat.x > x1 || seat.y < y0 || seat.y > y1) continue;
|
|
1919
|
-
const
|
|
2761
|
+
const t2 = new import_Text.Text({
|
|
1920
2762
|
x: seat.x,
|
|
1921
2763
|
y: seat.y,
|
|
1922
2764
|
text: seat.label,
|
|
@@ -1928,14 +2770,14 @@ var _SeatmapRenderer = class _SeatmapRenderer {
|
|
|
1928
2770
|
perfectDrawEnabled: false
|
|
1929
2771
|
});
|
|
1930
2772
|
const maxW = this.seatR * 2 - 3;
|
|
1931
|
-
if (
|
|
1932
|
-
if (
|
|
1933
|
-
|
|
2773
|
+
if (t2.width() > maxW) t2.fontSize(Math.max(4, 7 * maxW / t2.width()));
|
|
2774
|
+
if (t2.fontSize() < 4.2) {
|
|
2775
|
+
t2.destroy();
|
|
1934
2776
|
continue;
|
|
1935
2777
|
}
|
|
1936
|
-
|
|
1937
|
-
|
|
1938
|
-
this.labelGroup.add(
|
|
2778
|
+
t2.offsetX(t2.width() / 2);
|
|
2779
|
+
t2.offsetY(t2.height() / 2);
|
|
2780
|
+
this.labelGroup.add(t2);
|
|
1939
2781
|
if (++count >= MAX_LABELS) break;
|
|
1940
2782
|
}
|
|
1941
2783
|
if (this.isoT > 0) this.applyUprightLabels();
|
|
@@ -1970,6 +2812,38 @@ function createRenderer(container, opts) {
|
|
|
1970
2812
|
return new SeatmapRenderer(container, opts);
|
|
1971
2813
|
}
|
|
1972
2814
|
|
|
2815
|
+
// src/core/orphans.ts
|
|
2816
|
+
function seatIndex(id) {
|
|
2817
|
+
const n = Number(id.slice(id.lastIndexOf(":") + 1));
|
|
2818
|
+
return Number.isFinite(n) ? n : -1;
|
|
2819
|
+
}
|
|
2820
|
+
function strandedSingles(seats, statusOf, selectedIds) {
|
|
2821
|
+
const rows = /* @__PURE__ */ new Map();
|
|
2822
|
+
for (const s of seats) {
|
|
2823
|
+
if (s.kind === "booth") continue;
|
|
2824
|
+
const list = rows.get(s.rowId);
|
|
2825
|
+
if (list) list.push(s);
|
|
2826
|
+
else rows.set(s.rowId, [s]);
|
|
2827
|
+
}
|
|
2828
|
+
const unavailable = (s) => selectedIds.has(s.id) || statusOf(s.id) !== "free";
|
|
2829
|
+
const out = [];
|
|
2830
|
+
for (const list of rows.values()) {
|
|
2831
|
+
if (list.length < 3) continue;
|
|
2832
|
+
list.sort((a, b) => seatIndex(a.id) - seatIndex(b.id));
|
|
2833
|
+
for (let i = 1; i < list.length - 1; i++) {
|
|
2834
|
+
const seat = list[i];
|
|
2835
|
+
if (unavailable(seat)) continue;
|
|
2836
|
+
const left = list[i - 1];
|
|
2837
|
+
const right = list[i + 1];
|
|
2838
|
+
if (seatIndex(seat.id) - seatIndex(left.id) !== 1 || seatIndex(right.id) - seatIndex(seat.id) !== 1) continue;
|
|
2839
|
+
if (!unavailable(left) || !unavailable(right)) continue;
|
|
2840
|
+
if (!selectedIds.has(left.id) && !selectedIds.has(right.id)) continue;
|
|
2841
|
+
out.push(seat);
|
|
2842
|
+
}
|
|
2843
|
+
}
|
|
2844
|
+
return out;
|
|
2845
|
+
}
|
|
2846
|
+
|
|
1973
2847
|
// src/picker/PickerController.ts
|
|
1974
2848
|
var DEFAULT_MAX_SELECTION = 10;
|
|
1975
2849
|
var MAX_BACKOFF_MS = 15e3;
|
|
@@ -1986,9 +2860,15 @@ var PickerController = class {
|
|
|
1986
2860
|
constructor(options) {
|
|
1987
2861
|
this.renderer = null;
|
|
1988
2862
|
this._doc = null;
|
|
2863
|
+
/** Section/zone ids hidden from buyers this event (3.3) — seats vanish, not grey. */
|
|
2864
|
+
this.hidden = /* @__PURE__ */ new Set();
|
|
1989
2865
|
/** label ⇄ id maps — backend speaks labels, the engine speaks ids. */
|
|
1990
2866
|
this.labelToId = /* @__PURE__ */ new Map();
|
|
1991
2867
|
this.labelToSeat = /* @__PURE__ */ new Map();
|
|
2868
|
+
/** seatId → chosen ticket-tier id (absent ⇒ the category's first/default tier). */
|
|
2869
|
+
this.seatTiers = /* @__PURE__ */ new Map();
|
|
2870
|
+
/** id → seat, for the section-summary breakdown (renderer members are ids). */
|
|
2871
|
+
this.seatById = /* @__PURE__ */ new Map();
|
|
1992
2872
|
this.allIds = [];
|
|
1993
2873
|
// realtime socket
|
|
1994
2874
|
this.ws = null;
|
|
@@ -1998,6 +2878,8 @@ var PickerController = class {
|
|
|
1998
2878
|
// hold state
|
|
1999
2879
|
this.hold_ = null;
|
|
2000
2880
|
this.expiryTimer = null;
|
|
2881
|
+
/** Whether the last emitted hint was non-null (avoids clearing repeatedly). */
|
|
2882
|
+
this.hintShown = false;
|
|
2001
2883
|
this.opts = options;
|
|
2002
2884
|
this.api = options.transport;
|
|
2003
2885
|
this.key = options.eventKey;
|
|
@@ -2006,6 +2888,18 @@ var PickerController = class {
|
|
|
2006
2888
|
get doc() {
|
|
2007
2889
|
return this._doc;
|
|
2008
2890
|
}
|
|
2891
|
+
/** The chart with hidden sections' seats removed — what buyers actually see. */
|
|
2892
|
+
visibleDoc() {
|
|
2893
|
+
return this._doc ? applyHidden(this._doc, this.hidden) : { objects: [] };
|
|
2894
|
+
}
|
|
2895
|
+
/** Adopt a new hidden set; rebuild the visible chart only if it differs. */
|
|
2896
|
+
syncHidden(ids) {
|
|
2897
|
+
const next = ids.filter((x) => typeof x === "string");
|
|
2898
|
+
if (next.length === this.hidden.size && next.every((id) => this.hidden.has(id))) return false;
|
|
2899
|
+
this.hidden = new Set(next);
|
|
2900
|
+
this.renderer?.setChart(this.visibleDoc());
|
|
2901
|
+
return true;
|
|
2902
|
+
}
|
|
2009
2903
|
currentHold() {
|
|
2010
2904
|
return this.hold_;
|
|
2011
2905
|
}
|
|
@@ -2033,15 +2927,19 @@ var PickerController = class {
|
|
|
2033
2927
|
this._doc = res.doc;
|
|
2034
2928
|
this.labelToId = /* @__PURE__ */ new Map();
|
|
2035
2929
|
this.labelToSeat = /* @__PURE__ */ new Map();
|
|
2930
|
+
this.seatById = /* @__PURE__ */ new Map();
|
|
2036
2931
|
this.allIds = [];
|
|
2037
2932
|
for (const s of expandChart(res.doc)) {
|
|
2038
2933
|
this.labelToId.set(s.label, s.id);
|
|
2039
2934
|
this.labelToSeat.set(s.label, s);
|
|
2935
|
+
this.seatById.set(s.id, s);
|
|
2040
2936
|
this.allIds.push(s.id);
|
|
2041
2937
|
}
|
|
2938
|
+
const currency = res.event.currency ?? this.opts.currency;
|
|
2042
2939
|
const renderer = createRenderer(host, {
|
|
2043
2940
|
maxSelection: this.maxSelection,
|
|
2044
2941
|
confirmSelection: this.opts.confirmSelection,
|
|
2942
|
+
currency,
|
|
2045
2943
|
onSelect: (seat) => {
|
|
2046
2944
|
this.opts.onSelect?.(seat);
|
|
2047
2945
|
this.emitSelectionChange();
|
|
@@ -2054,6 +2952,9 @@ var PickerController = class {
|
|
|
2054
2952
|
onFocusSeat: this.opts.onFocusSeat,
|
|
2055
2953
|
onViewChange: this.opts.onViewChange,
|
|
2056
2954
|
onGAClick: this.opts.onGAClick,
|
|
2955
|
+
// Section tap at the far/zone rung → glide in + surface the summary card.
|
|
2956
|
+
onSectionTap: (id) => this.handleSectionTap(id),
|
|
2957
|
+
onDeckTap: (floorId) => this.handleDeckTap(floorId),
|
|
2057
2958
|
onFps: this.opts.onFps
|
|
2058
2959
|
});
|
|
2059
2960
|
if (this.closed) {
|
|
@@ -2061,15 +2962,29 @@ var PickerController = class {
|
|
|
2061
2962
|
return null;
|
|
2062
2963
|
}
|
|
2063
2964
|
this.renderer = renderer;
|
|
2064
|
-
|
|
2065
|
-
|
|
2965
|
+
let seats = null;
|
|
2966
|
+
try {
|
|
2967
|
+
const objs = await this.api.objects(this.key);
|
|
2968
|
+
this.hidden = new Set(objs.hidden ?? []);
|
|
2969
|
+
seats = objs.seats;
|
|
2970
|
+
} catch {
|
|
2971
|
+
}
|
|
2972
|
+
if (this.closed) {
|
|
2973
|
+
renderer.destroy();
|
|
2974
|
+
this.renderer = null;
|
|
2975
|
+
return null;
|
|
2976
|
+
}
|
|
2977
|
+
renderer.setChart(this.visibleDoc());
|
|
2978
|
+
if (this.opts.colorblindSafe) renderer.setColorblindSafe?.(true);
|
|
2979
|
+
if (seats) this.applySeatsMap(seats);
|
|
2066
2980
|
this.connect();
|
|
2067
2981
|
return {
|
|
2068
2982
|
doc: res.doc,
|
|
2069
2983
|
salesClosed: !!res.event.salesClosed,
|
|
2070
2984
|
eventName: res.event.name,
|
|
2071
2985
|
venue: res.event.venue,
|
|
2072
|
-
startsAt: res.event.startsAt
|
|
2986
|
+
startsAt: res.event.startsAt,
|
|
2987
|
+
currency
|
|
2073
2988
|
};
|
|
2074
2989
|
}
|
|
2075
2990
|
// ---- selection ------------------------------------------------------------
|
|
@@ -2241,6 +3156,126 @@ var PickerController = class {
|
|
|
2241
3156
|
setAccessibilityFilter(types) {
|
|
2242
3157
|
this.renderer?.setAccessibilityFilter?.(types);
|
|
2243
3158
|
}
|
|
3159
|
+
// ---- multi-floor (Batch 5) ------------------------------------------------
|
|
3160
|
+
/** Floors for the buyer's switcher (>1 ⇒ show it). Single-floor ⇒ one entry. */
|
|
3161
|
+
getFloors() {
|
|
3162
|
+
return this.renderer?.getFloors?.() ?? [];
|
|
3163
|
+
}
|
|
3164
|
+
getActiveFloorId() {
|
|
3165
|
+
return this.renderer?.getActiveFloorId?.() ?? "";
|
|
3166
|
+
}
|
|
3167
|
+
/** Switch the shown floor (2D), then re-apply live seat statuses onto it. */
|
|
3168
|
+
setFloor(id) {
|
|
3169
|
+
const r = this.renderer;
|
|
3170
|
+
if (!r?.setActiveFloor || id === r.getActiveFloorId?.()) return;
|
|
3171
|
+
r.setStacked?.(false);
|
|
3172
|
+
r.setActiveFloor(id);
|
|
3173
|
+
void this.resnapshot();
|
|
3174
|
+
this.emitSelectionChange();
|
|
3175
|
+
}
|
|
3176
|
+
/** 3D all-floors stacked overview ⇄ active floor. Re-applies statuses after. */
|
|
3177
|
+
setStacked(on) {
|
|
3178
|
+
const r = this.renderer;
|
|
3179
|
+
if (!r?.setStacked || on === r.isStacked?.()) return;
|
|
3180
|
+
r.setStacked(on);
|
|
3181
|
+
void this.resnapshot();
|
|
3182
|
+
}
|
|
3183
|
+
isMultiFloor() {
|
|
3184
|
+
return (this._doc?.floors?.length ?? 0) > 1;
|
|
3185
|
+
}
|
|
3186
|
+
/** Tap-a-deck-to-enter: leave the 3D stack, drop to flat 2D on `floorId`, and
|
|
3187
|
+
* tell the host so it can sync its 2D/3D toggle + floor-switcher state. */
|
|
3188
|
+
handleDeckTap(floorId) {
|
|
3189
|
+
const r = this.renderer;
|
|
3190
|
+
if (!r) return;
|
|
3191
|
+
r.setViewMode?.("flat");
|
|
3192
|
+
r.setStacked?.(false);
|
|
3193
|
+
r.setActiveFloor?.(floorId);
|
|
3194
|
+
void this.resnapshot();
|
|
3195
|
+
this.emitSelectionChange();
|
|
3196
|
+
this.opts.onDeckTap?.(floorId);
|
|
3197
|
+
}
|
|
3198
|
+
// ---- big-venue: sections / rungs / projection (Slice 5) -------------------
|
|
3199
|
+
/** Switch the map projection (2D flat ⇄ 3D isometric). No-op on a flat renderer. */
|
|
3200
|
+
setViewMode(mode) {
|
|
3201
|
+
this.renderer?.setViewMode?.(mode);
|
|
3202
|
+
}
|
|
3203
|
+
getViewMode() {
|
|
3204
|
+
return this.renderer?.getViewMode?.() ?? "flat";
|
|
3205
|
+
}
|
|
3206
|
+
/** Current LOD rung (for the ZONES/SECTIONS/SEATS pill). */
|
|
3207
|
+
getRung() {
|
|
3208
|
+
return this.renderer?.getRung?.() ?? "seats";
|
|
3209
|
+
}
|
|
3210
|
+
/** Jump to a rung; ZONES clears any focused summary (back to overview). */
|
|
3211
|
+
setRung(rung) {
|
|
3212
|
+
if (rung === "zones") {
|
|
3213
|
+
this.overview();
|
|
3214
|
+
return;
|
|
3215
|
+
}
|
|
3216
|
+
this.renderer?.setRung?.(rung);
|
|
3217
|
+
}
|
|
3218
|
+
/** Glide in on a section and surface its summary (same path as a section tap). */
|
|
3219
|
+
focusSection(id) {
|
|
3220
|
+
this.handleSectionTap(id);
|
|
3221
|
+
}
|
|
3222
|
+
/** Zoom back out to the whole chart and clear the section-summary card. */
|
|
3223
|
+
overview() {
|
|
3224
|
+
this.renderer?.zoomToFit();
|
|
3225
|
+
this.opts.onSectionFocus?.(null);
|
|
3226
|
+
}
|
|
3227
|
+
/** Glide the camera into a tapped section and emit its computed summary. */
|
|
3228
|
+
handleSectionTap(id) {
|
|
3229
|
+
const r = this.renderer;
|
|
3230
|
+
if (!r) return;
|
|
3231
|
+
r.focusRegion?.(id);
|
|
3232
|
+
this.opts.onSectionFocus?.(this.sectionSummary(id));
|
|
3233
|
+
}
|
|
3234
|
+
/**
|
|
3235
|
+
* Build a section summary from the renderer's spatial membership: section +
|
|
3236
|
+
* zone labels, live seats-left, price range, and the per-category breakdown.
|
|
3237
|
+
*/
|
|
3238
|
+
sectionSummary(id) {
|
|
3239
|
+
const r = this.renderer;
|
|
3240
|
+
const doc = this._doc;
|
|
3241
|
+
if (!r || !doc) return null;
|
|
3242
|
+
const sec = doc.objects.find(
|
|
3243
|
+
(o) => o.type === "section" && o.id === id
|
|
3244
|
+
);
|
|
3245
|
+
if (!sec) return null;
|
|
3246
|
+
const memberIds = r.sectionMembers?.(id) ?? [];
|
|
3247
|
+
const byCat = /* @__PURE__ */ new Map();
|
|
3248
|
+
let seatsLeft = 0;
|
|
3249
|
+
for (const sid of memberIds) {
|
|
3250
|
+
const seat = this.seatById.get(sid);
|
|
3251
|
+
if (!seat) continue;
|
|
3252
|
+
byCat.set(seat.categoryKey, (byCat.get(seat.categoryKey) ?? 0) + 1);
|
|
3253
|
+
if (r.getStatus(sid) === "free") seatsLeft++;
|
|
3254
|
+
}
|
|
3255
|
+
const categories = [...byCat.entries()].map(([key, count]) => {
|
|
3256
|
+
const cat = doc.categories.find((c) => c.key === key);
|
|
3257
|
+
return {
|
|
3258
|
+
key,
|
|
3259
|
+
count,
|
|
3260
|
+
label: cat?.label ?? key,
|
|
3261
|
+
color: cat?.color ?? "#6e7bff",
|
|
3262
|
+
price: cat?.price ?? 0
|
|
3263
|
+
};
|
|
3264
|
+
}).sort((a, b) => a.price - b.price);
|
|
3265
|
+
const prices = categories.map((c) => c.price);
|
|
3266
|
+
const zone = sec.zone ? doc.zones?.find((z) => z.id === sec.zone) : void 0;
|
|
3267
|
+
const color = sec.color ?? zone?.color ?? categories[0]?.color ?? "#6e7bff";
|
|
3268
|
+
return {
|
|
3269
|
+
id,
|
|
3270
|
+
label: sec.label,
|
|
3271
|
+
zoneLabel: zone?.label ?? "",
|
|
3272
|
+
color,
|
|
3273
|
+
seatsLeft,
|
|
3274
|
+
priceMin: prices.length ? prices[0] : 0,
|
|
3275
|
+
priceMax: prices.length ? prices[prices.length - 1] : 0,
|
|
3276
|
+
categories
|
|
3277
|
+
};
|
|
3278
|
+
}
|
|
2244
3279
|
destroy() {
|
|
2245
3280
|
this.closed = true;
|
|
2246
3281
|
if (this.reconnectTimer) {
|
|
@@ -2263,13 +3298,80 @@ var PickerController = class {
|
|
|
2263
3298
|
}
|
|
2264
3299
|
// ---- internals ------------------------------------------------------------
|
|
2265
3300
|
toSeat(s) {
|
|
2266
|
-
|
|
3301
|
+
const tiers = this.tiersFor(s.categoryKey);
|
|
3302
|
+
if (!tiers) {
|
|
3303
|
+
return { id: s.id, label: s.label, categoryKey: s.categoryKey, price: this.priceFor(s.categoryKey) };
|
|
3304
|
+
}
|
|
3305
|
+
const chosen = tiers.find((t2) => t2.id === this.seatTiers.get(s.id)) ?? tiers[0];
|
|
3306
|
+
return {
|
|
3307
|
+
id: s.id,
|
|
3308
|
+
label: s.label,
|
|
3309
|
+
categoryKey: s.categoryKey,
|
|
3310
|
+
price: chosen.price,
|
|
3311
|
+
tiers,
|
|
3312
|
+
tierId: chosen.id
|
|
3313
|
+
};
|
|
2267
3314
|
}
|
|
2268
3315
|
priceFor(categoryKey) {
|
|
2269
3316
|
return this._doc?.categories.find((c) => c.key === categoryKey)?.price ?? 0;
|
|
2270
3317
|
}
|
|
3318
|
+
tiersFor(categoryKey) {
|
|
3319
|
+
const t2 = this._doc?.categories.find((c) => c.key === categoryKey)?.tiers;
|
|
3320
|
+
return t2 && t2.length ? t2 : void 0;
|
|
3321
|
+
}
|
|
3322
|
+
/**
|
|
3323
|
+
* Choose a ticket tier for a selected seat (e.g. Adult → Child). Re-emits the
|
|
3324
|
+
* selection so the host's cart + the eventual hold carry the new tier + price.
|
|
3325
|
+
* `tierId = null` reverts to the category's first (default) tier. No-op if the
|
|
3326
|
+
* seat's category has no tiers or the id isn't one of them.
|
|
3327
|
+
*/
|
|
3328
|
+
setSeatTier(seatId, tierId) {
|
|
3329
|
+
const seat = this.seatById.get(seatId);
|
|
3330
|
+
if (!seat) return;
|
|
3331
|
+
const tiers = this.tiersFor(seat.categoryKey);
|
|
3332
|
+
if (!tiers) return;
|
|
3333
|
+
if (tierId == null) this.seatTiers.delete(seatId);
|
|
3334
|
+
else if (tiers.some((t2) => t2.id === tierId)) this.seatTiers.set(seatId, tierId);
|
|
3335
|
+
else return;
|
|
3336
|
+
this.emitSelectionChange();
|
|
3337
|
+
if (this.hold_) this.hold_ = { ...this.hold_, seats: this.seatsForLabels(this.hold_.labels) };
|
|
3338
|
+
}
|
|
3339
|
+
/** PickerSeats (with chosen tier) for a set of held labels. */
|
|
3340
|
+
seatsForLabels(labels) {
|
|
3341
|
+
const out = [];
|
|
3342
|
+
for (const l of labels) {
|
|
3343
|
+
const s = this.labelToSeat.get(l);
|
|
3344
|
+
if (s) out.push(this.toSeat(s));
|
|
3345
|
+
}
|
|
3346
|
+
return out;
|
|
3347
|
+
}
|
|
2271
3348
|
emitSelectionChange() {
|
|
2272
3349
|
this.opts.onSelectionChange?.(this.getSelection());
|
|
3350
|
+
this.emitOrphanHint();
|
|
3351
|
+
}
|
|
3352
|
+
/**
|
|
3353
|
+
* Orphan-seat advice on manual selection: when the buyer's current picks
|
|
3354
|
+
* strand one (or more) single free seats between unavailable same-row
|
|
3355
|
+
* neighbors, surface a localized, non-blocking hint. Cleared (null) as soon
|
|
3356
|
+
* as the selection stops stranding anyone.
|
|
3357
|
+
*/
|
|
3358
|
+
emitOrphanHint() {
|
|
3359
|
+
if (!this.opts.onHint) return;
|
|
3360
|
+
const r = this.renderer;
|
|
3361
|
+
if (!r) return;
|
|
3362
|
+
const selected = new Set(r.getSelection().map((s) => s.id));
|
|
3363
|
+
const stranded = selected.size ? strandedSingles(this.seatById.values(), (id) => r.getStatus(id), selected) : [];
|
|
3364
|
+
if (stranded.length > 0) {
|
|
3365
|
+
this.hintShown = true;
|
|
3366
|
+
this.opts.onHint(t("picker.orphanHint"));
|
|
3367
|
+
} else if (this.hintShown) {
|
|
3368
|
+
this.hintShown = false;
|
|
3369
|
+
this.opts.onHint(null);
|
|
3370
|
+
}
|
|
3371
|
+
}
|
|
3372
|
+
/** Toggle colorblind-safe rendering at runtime (see PickerOptions.colorblindSafe). */
|
|
3373
|
+
setColorblindSafe(on) {
|
|
3374
|
+
this.renderer?.setColorblindSafe?.(on);
|
|
2273
3375
|
}
|
|
2274
3376
|
emitError(err) {
|
|
2275
3377
|
if (this.opts.onError) this.opts.onError(err);
|
|
@@ -2293,11 +3395,12 @@ var PickerController = class {
|
|
|
2293
3395
|
}
|
|
2294
3396
|
/** Set the open hold + (re)arm the server-authoritative expiry timer. */
|
|
2295
3397
|
setHold(hold) {
|
|
2296
|
-
|
|
3398
|
+
const full = { ...hold, seats: this.seatsForLabels(hold.labels) };
|
|
3399
|
+
this.hold_ = full;
|
|
2297
3400
|
if (this.expiryTimer) clearTimeout(this.expiryTimer);
|
|
2298
|
-
const ms = Math.max(0,
|
|
3401
|
+
const ms = Math.max(0, full.expiresAt - Date.now());
|
|
2299
3402
|
this.expiryTimer = setTimeout(() => this.onHoldExpired(), ms);
|
|
2300
|
-
this.opts.onHold?.(
|
|
3403
|
+
this.opts.onHold?.(full);
|
|
2301
3404
|
}
|
|
2302
3405
|
clearHold() {
|
|
2303
3406
|
this.hold_ = null;
|
|
@@ -2361,6 +3464,11 @@ var PickerController = class {
|
|
|
2361
3464
|
const r = this.renderer;
|
|
2362
3465
|
if (!r || !msg || typeof msg !== "object") return;
|
|
2363
3466
|
const m = msg;
|
|
3467
|
+
if (Array.isArray(m.hidden) && this.syncHidden(m.hidden)) {
|
|
3468
|
+
void this.resnapshot();
|
|
3469
|
+
this.opts.onStatusChange?.();
|
|
3470
|
+
}
|
|
3471
|
+
if (m.type === "hidden") return;
|
|
2364
3472
|
if (m.seats && typeof m.seats === "object") {
|
|
2365
3473
|
this.applySeatsMap(m.seats);
|
|
2366
3474
|
} else if (Array.isArray(m.changes)) {
|
|
@@ -2397,22 +3505,68 @@ var PickerController = class {
|
|
|
2397
3505
|
}, delay);
|
|
2398
3506
|
}
|
|
2399
3507
|
};
|
|
3508
|
+
|
|
3509
|
+
// src/i18n/bundles.ts
|
|
3510
|
+
var LOADERS = {
|
|
3511
|
+
es: () => Promise.resolve().then(() => (init_es(), es_exports)).then((m) => ({ default: m.es })),
|
|
3512
|
+
de: () => Promise.resolve().then(() => (init_de(), de_exports)).then((m) => ({ default: m.de })),
|
|
3513
|
+
fr: () => Promise.resolve().then(() => (init_fr(), fr_exports)).then((m) => ({ default: m.fr }))
|
|
3514
|
+
};
|
|
3515
|
+
var loaded = /* @__PURE__ */ new Set(["en"]);
|
|
3516
|
+
async function loadLocale(code) {
|
|
3517
|
+
const locale = resolveLocale(code);
|
|
3518
|
+
if (locale === "en" || loaded.has(locale)) {
|
|
3519
|
+
setLocale(locale);
|
|
3520
|
+
return locale;
|
|
3521
|
+
}
|
|
3522
|
+
try {
|
|
3523
|
+
const mod = await LOADERS[locale]();
|
|
3524
|
+
setLocale(locale, mod.default);
|
|
3525
|
+
loaded.add(locale);
|
|
3526
|
+
return locale;
|
|
3527
|
+
} catch {
|
|
3528
|
+
setLocale("en");
|
|
3529
|
+
return "en";
|
|
3530
|
+
}
|
|
3531
|
+
}
|
|
2400
3532
|
// Annotate the CommonJS export names for ESM import in node:
|
|
2401
3533
|
0 && (module.exports = {
|
|
2402
3534
|
ACCESSIBILITY_TYPES,
|
|
2403
3535
|
CHART_STORAGE_KEY,
|
|
3536
|
+
DEFAULT_CURRENCY,
|
|
2404
3537
|
PickerController,
|
|
3538
|
+
SUPPORTED_LOCALES,
|
|
2405
3539
|
SeatmapRenderer,
|
|
3540
|
+
UNGROUPED_ID,
|
|
2406
3541
|
accessibilityMeta,
|
|
3542
|
+
allObjects,
|
|
3543
|
+
applyHidden,
|
|
2407
3544
|
chartBounds,
|
|
3545
|
+
computeSections,
|
|
2408
3546
|
createRenderer,
|
|
3547
|
+
currencySymbol,
|
|
2409
3548
|
expandBooth,
|
|
2410
3549
|
expandChart,
|
|
2411
3550
|
expandRow,
|
|
2412
3551
|
expandRowSlots,
|
|
2413
3552
|
expandTable,
|
|
3553
|
+
floorObjects,
|
|
3554
|
+
floorsOf,
|
|
3555
|
+
formatDate,
|
|
3556
|
+
formatMoney,
|
|
3557
|
+
getLocale,
|
|
3558
|
+
hiddenObjectIds,
|
|
3559
|
+
isSectionHidden,
|
|
2414
3560
|
layerOf,
|
|
3561
|
+
loadLocale,
|
|
2415
3562
|
objectCenter,
|
|
2416
|
-
pointInPolygon
|
|
3563
|
+
pointInPolygon,
|
|
3564
|
+
resolveLocale,
|
|
3565
|
+
setLocale,
|
|
3566
|
+
setMoneyLocale,
|
|
3567
|
+
setStringOverrides,
|
|
3568
|
+
stackFloors,
|
|
3569
|
+
t,
|
|
3570
|
+
tCount
|
|
2417
3571
|
});
|
|
2418
3572
|
//# sourceMappingURL=index.cjs.map
|