@seatlayer/core 0.25.0 → 0.26.0
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/index.cjs +2101 -298
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +652 -54
- package/dist/index.d.ts +652 -54
- package/dist/index.js +2089 -297
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.cjs
CHANGED
|
@@ -216,7 +216,9 @@ __export(index_exports, {
|
|
|
216
216
|
PickerController: () => PickerController,
|
|
217
217
|
RENDERED_QUALITY_REPORT_VERSION: () => RENDERED_QUALITY_REPORT_VERSION,
|
|
218
218
|
SUPPORTED_LOCALES: () => SUPPORTED_LOCALES,
|
|
219
|
+
SURROUNDINGS_SHAPE_ROLES: () => SURROUNDINGS_SHAPE_ROLES,
|
|
219
220
|
SeatmapRenderer: () => SeatmapRenderer,
|
|
221
|
+
TIER_HEIGHT_M: () => TIER_HEIGHT_M,
|
|
220
222
|
UNGROUPED_ID: () => UNGROUPED_ID,
|
|
221
223
|
accessibilityMeta: () => accessibilityMeta,
|
|
222
224
|
accessibilityRingColor: () => accessibilityRingColor,
|
|
@@ -231,16 +233,19 @@ __export(index_exports, {
|
|
|
231
233
|
expandRow: () => expandRow,
|
|
232
234
|
expandRowSlots: () => expandRowSlots,
|
|
233
235
|
expandTable: () => expandTable,
|
|
236
|
+
expandTableSlots: () => expandTableSlots,
|
|
234
237
|
floorObjects: () => floorObjects,
|
|
235
238
|
floorsOf: () => floorsOf,
|
|
236
239
|
formatDate: () => formatDate,
|
|
237
240
|
formatMoney: () => formatMoney,
|
|
238
241
|
gaAreasOf: () => gaAreasOf,
|
|
242
|
+
gaInventorySegments: () => gaInventorySegments,
|
|
239
243
|
gaUnitLabel: () => gaUnitLabel,
|
|
240
244
|
gaUnitLabels: () => gaUnitLabels,
|
|
241
245
|
generateSeatPanorama: () => generateSeatPanorama,
|
|
242
246
|
generateSeatThumb: () => generateSeatThumb,
|
|
243
247
|
getLocale: () => getLocale,
|
|
248
|
+
growJoinedGAInventory: () => growJoinedGAInventory,
|
|
244
249
|
hiddenObjectIds: () => hiddenObjectIds,
|
|
245
250
|
inspectRenderedQualityEvidence: () => inspectRenderedQualityEvidence,
|
|
246
251
|
isGaUnitLabel: () => isGaUnitLabel,
|
|
@@ -248,18 +253,24 @@ __export(index_exports, {
|
|
|
248
253
|
layerOf: () => layerOf,
|
|
249
254
|
loadLocale: () => loadLocale,
|
|
250
255
|
objectCenter: () => objectCenter,
|
|
256
|
+
owningSectionForObject: () => owningSectionForObject,
|
|
251
257
|
pointInPolygon: () => pointInPolygon,
|
|
252
258
|
pointInPolygonWithHoles: () => pointInPolygonWithHoles,
|
|
253
259
|
polygonLabelPoint: () => polygonLabelPoint,
|
|
254
260
|
resolveLocale: () => resolveLocale,
|
|
261
|
+
rowInventoryCount: () => rowInventoryCount,
|
|
255
262
|
rowSeatPositions: () => rowSeatPositions,
|
|
256
263
|
seatLabelPart: () => seatLabelPart,
|
|
264
|
+
sectionGeometry: () => sectionGeometry,
|
|
257
265
|
setLocale: () => setLocale,
|
|
258
266
|
setMoneyLocale: () => setMoneyLocale,
|
|
259
267
|
setStringOverrides: () => setStringOverrides,
|
|
260
268
|
stackFloors: () => stackFloors,
|
|
261
269
|
t: () => t,
|
|
262
|
-
tCount: () => tCount
|
|
270
|
+
tCount: () => tCount,
|
|
271
|
+
tableInventoryCount: () => tableInventoryCount,
|
|
272
|
+
tableSeatCountsBySide: () => tableSeatCountsBySide,
|
|
273
|
+
validGAInventorySegments: () => validGAInventorySegments
|
|
263
274
|
});
|
|
264
275
|
module.exports = __toCommonJS(index_exports);
|
|
265
276
|
|
|
@@ -269,6 +280,7 @@ var ACCESSIBILITY_TYPES = [
|
|
|
269
280
|
{ key: "companion", label: "Companion seat", short: "Companion", icon: "\u{1F9D1}\u200D\u{1F91D}\u200D\u{1F9D1}" },
|
|
270
281
|
{ key: "semi-ambulatory", label: "Semi-ambulatory (limited mobility)", short: "Limited mobility", icon: "\u{1F9AF}" },
|
|
271
282
|
{ key: "hearing", label: "Assistive listening", short: "Hearing", icon: "\u{1F9BB}" },
|
|
283
|
+
{ key: "cart", label: "CART live-caption view", short: "CART captions", icon: "CC" },
|
|
272
284
|
{ key: "sign-language", label: "Sign-language view", short: "Sign language", icon: "\u{1F91F}" },
|
|
273
285
|
{ key: "plus-size", label: "Plus-size seat", short: "Plus-size", icon: "\u{1F4BA}" },
|
|
274
286
|
{ key: "lift-armrest", label: "Lift-up armrest", short: "Lift armrest", icon: "\u2195\uFE0F" }
|
|
@@ -282,6 +294,7 @@ var ACCESSIBILITY_RING_COLOR = {
|
|
|
282
294
|
companion: "#8b5cf6",
|
|
283
295
|
"semi-ambulatory": "#0ea5e9",
|
|
284
296
|
hearing: "#14b8a6",
|
|
297
|
+
cart: "#7c3aed",
|
|
285
298
|
"sign-language": "#f59e0b",
|
|
286
299
|
"plus-size": "#ec4899",
|
|
287
300
|
"lift-armrest": "#22c55e"
|
|
@@ -292,6 +305,19 @@ function accessibilityRingColor(types) {
|
|
|
292
305
|
}
|
|
293
306
|
var LABEL_STYLE_MIN_SIZE = 8;
|
|
294
307
|
var LABEL_STYLE_MAX_SIZE = 24;
|
|
308
|
+
var SURROUNDINGS_SHAPE_ROLES = [
|
|
309
|
+
"reference-focal",
|
|
310
|
+
"bar",
|
|
311
|
+
"entrance",
|
|
312
|
+
"exit",
|
|
313
|
+
"restroom",
|
|
314
|
+
"screen",
|
|
315
|
+
"sound",
|
|
316
|
+
"concession",
|
|
317
|
+
"coat",
|
|
318
|
+
"wall"
|
|
319
|
+
];
|
|
320
|
+
var SURROUNDINGS_SHAPE_ROLE_SET = new Set(SURROUNDINGS_SHAPE_ROLES);
|
|
295
321
|
function layerOf(obj) {
|
|
296
322
|
switch (obj.type) {
|
|
297
323
|
case "row":
|
|
@@ -300,10 +326,17 @@ function layerOf(obj) {
|
|
|
300
326
|
case "booth":
|
|
301
327
|
case "section":
|
|
302
328
|
return "interactive";
|
|
303
|
-
//
|
|
329
|
+
// Raster décor follows its explicit authored z-layer. Absence retains the
|
|
330
|
+
// legacy Background default; bitmap content is never guessed semantically.
|
|
331
|
+
case "decorImage":
|
|
332
|
+
return obj.layer === "foreground" ? "foreground" : "background";
|
|
333
|
+
// Source-backed focal geometry and curated venue landmarks help an author
|
|
334
|
+
// orient around the sellable plan. A stage and an ordinary authored shape
|
|
335
|
+
// remain Background even when they carry an arbitrary/custom role.
|
|
304
336
|
case "shape":
|
|
337
|
+
return obj.role && SURROUNDINGS_SHAPE_ROLE_SET.has(obj.role) ? "surroundings" : "background";
|
|
338
|
+
// Free text — including semantic icon text — is background furniture.
|
|
305
339
|
case "text":
|
|
306
|
-
case "decorImage":
|
|
307
340
|
return "background";
|
|
308
341
|
default:
|
|
309
342
|
return "interactive";
|
|
@@ -423,10 +456,57 @@ function toRoman(value) {
|
|
|
423
456
|
return out;
|
|
424
457
|
}
|
|
425
458
|
|
|
459
|
+
// src/core/units.ts
|
|
460
|
+
var METRES_PER_CHART_UNIT = 0.55 / 24;
|
|
461
|
+
var CHART_UNITS_PER_METRE = 1 / METRES_PER_CHART_UNIT;
|
|
462
|
+
var LIFT_PER_STEP_WORLD = 58;
|
|
463
|
+
var TIER_HEIGHT_M = LIFT_PER_STEP_WORLD * METRES_PER_CHART_UNIT;
|
|
464
|
+
var SECTION_ELEVATION_TIER_MIN = 0;
|
|
465
|
+
var SECTION_ELEVATION_TIER_MAX = 3;
|
|
466
|
+
var SECTION_HEIGHT_MIN_M = 0;
|
|
467
|
+
var SECTION_HEIGHT_MAX_M = 120;
|
|
468
|
+
var SECTION_RAKE_MIN_DEG = 0;
|
|
469
|
+
var SECTION_RAKE_MAX_DEG = 45;
|
|
470
|
+
var LEGACY_SECTION_ELEVATION_TIER_MAX = 7;
|
|
471
|
+
var SEATED_EYE_HEIGHT_M = 1.2;
|
|
472
|
+
function finiteClamped(value, min, max, fallback) {
|
|
473
|
+
return Number.isFinite(value) ? Math.max(min, Math.min(max, value)) : fallback;
|
|
474
|
+
}
|
|
475
|
+
function sectionElevationTier(value) {
|
|
476
|
+
if (!Number.isFinite(value)) return SECTION_ELEVATION_TIER_MIN;
|
|
477
|
+
return Math.max(
|
|
478
|
+
SECTION_ELEVATION_TIER_MIN,
|
|
479
|
+
Math.min(SECTION_ELEVATION_TIER_MAX, Math.round(value))
|
|
480
|
+
);
|
|
481
|
+
}
|
|
482
|
+
function compatibleAutomaticTier(value) {
|
|
483
|
+
if (!Number.isFinite(value) || !Number.isInteger(value) || value < 0) return 0;
|
|
484
|
+
if (value <= LEGACY_SECTION_ELEVATION_TIER_MAX) return value;
|
|
485
|
+
return SECTION_ELEVATION_TIER_MAX;
|
|
486
|
+
}
|
|
487
|
+
function sectionGeometry(section, context = {}) {
|
|
488
|
+
const floorBaseHeightM = finiteClamped(
|
|
489
|
+
context.floorBaseHeightM,
|
|
490
|
+
SECTION_HEIGHT_MIN_M,
|
|
491
|
+
SECTION_HEIGHT_MAX_M,
|
|
492
|
+
0
|
|
493
|
+
);
|
|
494
|
+
const automaticHeight = Math.min(
|
|
495
|
+
SECTION_HEIGHT_MAX_M,
|
|
496
|
+
floorBaseHeightM + compatibleAutomaticTier(section.elevation) * TIER_HEIGHT_M
|
|
497
|
+
);
|
|
498
|
+
const height = section.height === void 0 ? automaticHeight : finiteClamped(section.height, SECTION_HEIGHT_MIN_M, SECTION_HEIGHT_MAX_M, automaticHeight);
|
|
499
|
+
const rake = finiteClamped(section.rake, SECTION_RAKE_MIN_DEG, SECTION_RAKE_MAX_DEG, 0);
|
|
500
|
+
return { height, rake };
|
|
501
|
+
}
|
|
502
|
+
|
|
426
503
|
// src/core/layout.ts
|
|
427
504
|
function overrideAccessibility(o) {
|
|
428
505
|
if (!o) return [];
|
|
429
|
-
if (o.accessibility && o.accessibility.length)
|
|
506
|
+
if (o.accessibility && o.accessibility.length) {
|
|
507
|
+
return o.wheelchairSpaceType && !o.accessibility.includes("wheelchair") ? ["wheelchair", ...o.accessibility] : o.accessibility;
|
|
508
|
+
}
|
|
509
|
+
if (o.wheelchairSpaceType) return ["wheelchair"];
|
|
430
510
|
return o.accessible ? ["wheelchair"] : [];
|
|
431
511
|
}
|
|
432
512
|
var DEG = Math.PI / 180;
|
|
@@ -468,6 +548,10 @@ function overrideMap(row) {
|
|
|
468
548
|
if (row.overrides) for (const o of row.overrides) m.set(o.index, o);
|
|
469
549
|
return m;
|
|
470
550
|
}
|
|
551
|
+
function rowInventoryCount(row) {
|
|
552
|
+
const skipped = new Set((row.overrides ?? []).filter((override) => override.skip && Number.isInteger(override.index) && override.index >= 0 && override.index < row.seatCount).map((override) => override.index));
|
|
553
|
+
return Math.max(0, row.seatCount - skipped.size);
|
|
554
|
+
}
|
|
471
555
|
function centerRank(n) {
|
|
472
556
|
const rank = new Array(n);
|
|
473
557
|
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);
|
|
@@ -481,9 +565,9 @@ function seatLabelPart(row, i) {
|
|
|
481
565
|
const prefix = row.seatNumbering?.prefix ?? "";
|
|
482
566
|
const endAt = row.seatNumbering?.endAt;
|
|
483
567
|
const n = row.seatCount;
|
|
484
|
-
if (scheme === "updown") {
|
|
568
|
+
if (scheme === "updown" || scheme === "updown-descending") {
|
|
485
569
|
const half = Math.ceil(n / 2);
|
|
486
|
-
const core2 = i < half ? rawStart + 2 * i : rawStart - 1 + 2 * (n - i);
|
|
570
|
+
const core2 = scheme === "updown" ? i < half ? rawStart + 2 * i : rawStart - 1 + 2 * (n - i) : i < half ? rawStart + 2 * (half - 1 - i) : rawStart + 1 + 2 * (i - half);
|
|
487
571
|
return `${prefix}${core2}`;
|
|
488
572
|
}
|
|
489
573
|
const effStep = scheme === "odd" || scheme === "even" ? 2 : step;
|
|
@@ -536,6 +620,7 @@ function expandRowSlots(row) {
|
|
|
536
620
|
skipped: !!o?.skip,
|
|
537
621
|
accessible: accessibility.length > 0,
|
|
538
622
|
accessibility,
|
|
623
|
+
wheelchairSpaceType: o?.wheelchairSpaceType,
|
|
539
624
|
commercial: Object.values(commercial).some((value) => value !== void 0 && value !== false && value !== "") ? commercial : void 0,
|
|
540
625
|
viewUrl: o?.viewFromSeatUrl ?? row.viewFromSeatUrl,
|
|
541
626
|
labelStyle: o?.labelStyle
|
|
@@ -556,6 +641,7 @@ function expandRow(row) {
|
|
|
556
641
|
categoryKey: slot.categoryKey,
|
|
557
642
|
accessible: slot.accessible || void 0,
|
|
558
643
|
accessibility: slot.accessibility.length ? slot.accessibility : void 0,
|
|
644
|
+
wheelchairSpaceType: slot.wheelchairSpaceType,
|
|
559
645
|
commercial: slot.commercial,
|
|
560
646
|
viewUrl: slot.viewUrl,
|
|
561
647
|
labelStyle: slot.labelStyle
|
|
@@ -563,18 +649,43 @@ function expandRow(row) {
|
|
|
563
649
|
}
|
|
564
650
|
return seats;
|
|
565
651
|
}
|
|
566
|
-
function
|
|
652
|
+
function tableSeatCountsBySide(t2) {
|
|
653
|
+
if (t2.seatCountsBySide) return { ...t2.seatCountsBySide };
|
|
654
|
+
const enabled = t2.sides && t2.sides.length ? t2.sides : ["top", "bottom"];
|
|
655
|
+
const order = ["top", "bottom", "left", "right"].filter((side) => enabled.includes(side));
|
|
656
|
+
const counts = { top: 0, bottom: 0, left: 0, right: 0 };
|
|
657
|
+
if (!order.length) return counts;
|
|
658
|
+
const n = Math.max(0, Math.round(t2.seatCount));
|
|
659
|
+
for (let index = 0; index < n; index++) counts[order[index % order.length]] += 1;
|
|
660
|
+
return counts;
|
|
661
|
+
}
|
|
662
|
+
function expandTableSlots(t2) {
|
|
567
663
|
const seats = [];
|
|
568
664
|
const n = Math.max(0, Math.round(t2.seatCount));
|
|
569
665
|
if (n === 0) return seats;
|
|
570
|
-
const
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
666
|
+
const overrides2 = new Map((t2.overrides ?? []).map((override) => [override.index, override]));
|
|
667
|
+
const mk = (index, x, y, side) => {
|
|
668
|
+
const override = overrides2.get(index);
|
|
669
|
+
const accessibility = overrideAccessibility(override);
|
|
670
|
+
const label = override?.label ?? `${t2.label}-${index + 1}`;
|
|
671
|
+
const displayPrefix = t2.displayLabel ?? t2.label;
|
|
672
|
+
return {
|
|
673
|
+
index,
|
|
674
|
+
label,
|
|
675
|
+
displayLabel: override?.displayLabel ?? `${displayPrefix}-${index + 1}`,
|
|
676
|
+
x: x + (override?.dx ?? 0),
|
|
677
|
+
y: y + (override?.dy ?? 0),
|
|
678
|
+
categoryKey: override?.categoryKey ?? t2.categoryKey,
|
|
679
|
+
skipped: !!override?.skip,
|
|
680
|
+
accessible: accessibility.length > 0,
|
|
681
|
+
accessibility,
|
|
682
|
+
wheelchairSpaceType: override?.wheelchairSpaceType,
|
|
683
|
+
commercial: override?.commercial,
|
|
684
|
+
viewUrl: override?.viewFromSeatUrl,
|
|
685
|
+
labelStyle: override?.labelStyle,
|
|
686
|
+
...side ? { side } : {}
|
|
687
|
+
};
|
|
688
|
+
};
|
|
578
689
|
if (t2.shape === "round") {
|
|
579
690
|
const R = (t2.radius ?? 40) + TABLE_SEAT_OFFSET;
|
|
580
691
|
const base = t2.rotation * DEG;
|
|
@@ -597,17 +708,11 @@ function expandTable(t2) {
|
|
|
597
708
|
}
|
|
598
709
|
const w = t2.width ?? 80;
|
|
599
710
|
const h = t2.height ?? 50;
|
|
600
|
-
const
|
|
601
|
-
const order = ["top", "bottom", "left", "right"]
|
|
602
|
-
if (!order.length) return seats;
|
|
603
|
-
const counts = new Map(order.map((s) => [s, 0]));
|
|
604
|
-
for (let i = 0; i < n; i++) {
|
|
605
|
-
const s = order[i % order.length];
|
|
606
|
-
counts.set(s, counts.get(s) + 1);
|
|
607
|
-
}
|
|
711
|
+
const counts = tableSeatCountsBySide(t2);
|
|
712
|
+
const order = ["top", "bottom", "left", "right"];
|
|
608
713
|
let idx = 0;
|
|
609
714
|
for (const side of order) {
|
|
610
|
-
const count = counts
|
|
715
|
+
const count = counts[side];
|
|
611
716
|
for (let j = 0; j < count; j++) {
|
|
612
717
|
let localX;
|
|
613
718
|
let localY;
|
|
@@ -624,17 +729,39 @@ function expandTable(t2) {
|
|
|
624
729
|
localX = w / 2 + TABLE_SEAT_OFFSET;
|
|
625
730
|
localY = -h / 2 + (j + 0.5) * h / count;
|
|
626
731
|
}
|
|
627
|
-
const
|
|
628
|
-
seats.push(mk(idx++,
|
|
732
|
+
const point = place(localX, localY, t2.rotation, t2.center);
|
|
733
|
+
seats.push(mk(idx++, point.x, point.y, side));
|
|
629
734
|
}
|
|
630
735
|
}
|
|
631
736
|
return seats;
|
|
632
737
|
}
|
|
738
|
+
function tableInventoryCount(t2) {
|
|
739
|
+
if (t2.bookAsWhole || t2.variableOccupancy) return Math.max(0, Math.round(t2.seatCount));
|
|
740
|
+
return expandTableSlots(t2).filter((slot) => !slot.skipped).length;
|
|
741
|
+
}
|
|
742
|
+
function expandTable(t2) {
|
|
743
|
+
return expandTableSlots(t2).filter((slot) => !slot.skipped).map((slot) => ({
|
|
744
|
+
id: `${t2.id}:${slot.index}`,
|
|
745
|
+
label: slot.label,
|
|
746
|
+
...slot.displayLabel !== slot.label ? { displayLabel: slot.displayLabel } : {},
|
|
747
|
+
x: slot.x,
|
|
748
|
+
y: slot.y,
|
|
749
|
+
rowId: t2.id,
|
|
750
|
+
categoryKey: slot.categoryKey,
|
|
751
|
+
...slot.accessible ? { accessible: true } : {},
|
|
752
|
+
...slot.accessibility.length ? { accessibility: slot.accessibility } : {},
|
|
753
|
+
...slot.wheelchairSpaceType ? { wheelchairSpaceType: slot.wheelchairSpaceType } : {},
|
|
754
|
+
...slot.commercial ? { commercial: slot.commercial } : {},
|
|
755
|
+
...slot.viewUrl ? { viewUrl: slot.viewUrl } : {},
|
|
756
|
+
...slot.labelStyle ? { labelStyle: slot.labelStyle } : {}
|
|
757
|
+
}));
|
|
758
|
+
}
|
|
633
759
|
function expandBooth(b) {
|
|
634
760
|
return [
|
|
635
761
|
{
|
|
636
762
|
id: `${b.id}:0`,
|
|
637
763
|
label: b.label,
|
|
764
|
+
...b.displayLabel && b.displayLabel !== b.label ? { displayLabel: b.displayLabel } : {},
|
|
638
765
|
x: b.center.x,
|
|
639
766
|
y: b.center.y,
|
|
640
767
|
rowId: b.id,
|
|
@@ -672,7 +799,7 @@ function polygonLabelPoint(outer, holes) {
|
|
|
672
799
|
if (!outer.length) return { x: 0, y: 0 };
|
|
673
800
|
const xs = outer.map((point) => point.x);
|
|
674
801
|
const ys = outer.map((point) => point.y);
|
|
675
|
-
const
|
|
802
|
+
const bounds2 = { minX: Math.min(...xs), maxX: Math.max(...xs), minY: Math.min(...ys), maxY: Math.max(...ys) };
|
|
676
803
|
const centroid2 = polygonCentroid(outer);
|
|
677
804
|
if (pointInPolygonWithHoles(centroid2, outer, holes)) return centroid2;
|
|
678
805
|
let best = outer[0];
|
|
@@ -681,8 +808,8 @@ function polygonLabelPoint(outer, holes) {
|
|
|
681
808
|
for (let row = 1; row < 24; row += 1) {
|
|
682
809
|
for (let column = 1; column < 24; column += 1) {
|
|
683
810
|
const point = {
|
|
684
|
-
x:
|
|
685
|
-
y:
|
|
811
|
+
x: bounds2.minX + (bounds2.maxX - bounds2.minX) * column / 24,
|
|
812
|
+
y: bounds2.minY + (bounds2.maxY - bounds2.minY) * row / 24
|
|
686
813
|
};
|
|
687
814
|
if (!pointInPolygonWithHoles(point, outer, holes)) continue;
|
|
688
815
|
const score = Math.min(...rings.flatMap((ring) => ring.map((start, index) => {
|
|
@@ -744,9 +871,32 @@ function objectCenter(o) {
|
|
|
744
871
|
return { x: o.x + o.width / 2, y: o.y + o.height / 2 };
|
|
745
872
|
}
|
|
746
873
|
}
|
|
874
|
+
function samePoints(left, right) {
|
|
875
|
+
return left.length === right.length && left.every((point, index) => point.x === right[index].x && point.y === right[index].y);
|
|
876
|
+
}
|
|
877
|
+
function sameGASurfaceAsSection(object, section) {
|
|
878
|
+
if (object.type !== "gaArea" || !samePoints(object.points, section.outline)) return false;
|
|
879
|
+
const objectHoles = object.holes ?? [];
|
|
880
|
+
const sectionHoles = section.holes ?? [];
|
|
881
|
+
return objectHoles.length === sectionHoles.length && objectHoles.every((hole, index) => samePoints(hole, sectionHoles[index]));
|
|
882
|
+
}
|
|
883
|
+
function owningSectionForObject(objects, object) {
|
|
884
|
+
const sections = objects.filter((candidate) => candidate.type === "section");
|
|
885
|
+
const referencedLogicalId = "referenceInventorySource" in object ? object.referenceInventorySource?.logicalSectionId : void 0;
|
|
886
|
+
const center = objectCenter(object);
|
|
887
|
+
const referencedOwner = referencedLogicalId ? sections.find((section) => (section.logicalSectionId ?? section.id) === referencedLogicalId && (sameGASurfaceAsSection(object, section) || pointInPolygonWithHoles(center, section.outline, section.holes))) : void 0;
|
|
888
|
+
return referencedOwner ?? sections.find((section) => pointInPolygonWithHoles(center, section.outline, section.holes));
|
|
889
|
+
}
|
|
747
890
|
function floorsOf(doc) {
|
|
748
891
|
if (doc.floors && doc.floors.length) return doc.floors;
|
|
749
|
-
return [{
|
|
892
|
+
return [{
|
|
893
|
+
id: "floor-0",
|
|
894
|
+
name: "Main",
|
|
895
|
+
objects: doc.objects,
|
|
896
|
+
focalPoint: doc.focalPoint,
|
|
897
|
+
referenceImage: doc.referenceImage,
|
|
898
|
+
backgroundImage: doc.backgroundImage
|
|
899
|
+
}];
|
|
750
900
|
}
|
|
751
901
|
function floorObjects(doc, floorId) {
|
|
752
902
|
const floors = floorsOf(doc);
|
|
@@ -795,15 +945,138 @@ function stackFloors(doc, spread = 900) {
|
|
|
795
945
|
});
|
|
796
946
|
return { ...doc, objects, floors: void 0 };
|
|
797
947
|
}
|
|
798
|
-
function
|
|
948
|
+
function expandFloorObjects(objects, zones, fallbackFocal) {
|
|
799
949
|
const out = [];
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
950
|
+
const segmented = /* @__PURE__ */ new Map();
|
|
951
|
+
const grouped = /* @__PURE__ */ new Map();
|
|
952
|
+
for (const object of objects) {
|
|
953
|
+
if (object.type !== "row" || !object.segmentedRow) continue;
|
|
954
|
+
const list = grouped.get(object.segmentedRow.groupId) ?? [];
|
|
955
|
+
list.push(object);
|
|
956
|
+
grouped.set(object.segmentedRow.groupId, list);
|
|
957
|
+
}
|
|
958
|
+
for (const [groupId, members] of grouped) {
|
|
959
|
+
const ordered = members.slice().sort((left, right) => left.segmentedRow.componentIndex - right.segmentedRow.componentIndex);
|
|
960
|
+
const expectedCount = ordered[0]?.segmentedRow?.componentCount ?? 0;
|
|
961
|
+
const first = ordered[0]?.segmentedRow;
|
|
962
|
+
if (!first) continue;
|
|
963
|
+
const valid = expectedCount >= 2 && ordered.length === expectedCount && first?.boundaryBefore === "start" && ordered.every((row, index) => row.segmentedRow?.kind === "segmented-row-v1" && row.segmentedRow.groupId === groupId && row.segmentedRow.componentCount === expectedCount && row.segmentedRow.componentIndex === index && (index === 0 ? row.segmentedRow.boundaryBefore === "start" : row.segmentedRow.boundaryBefore !== "start") && row.segmentedRow.displayLabel === first.displayLabel);
|
|
964
|
+
if (!valid) continue;
|
|
965
|
+
const totalSeats = ordered.reduce((sum, row) => sum + row.seatCount, 0);
|
|
966
|
+
let adjacencyOffset = 0;
|
|
967
|
+
let displayOffset = 0;
|
|
968
|
+
for (const row of ordered) {
|
|
969
|
+
if (row.segmentedRow.boundaryBefore === "break") adjacencyOffset += 1;
|
|
970
|
+
segmented.set(row.id, {
|
|
971
|
+
groupId,
|
|
972
|
+
adjacencyOffset,
|
|
973
|
+
displayOffset,
|
|
974
|
+
displayLabel: first.displayLabel,
|
|
975
|
+
totalSeats,
|
|
976
|
+
canonical: ordered[0],
|
|
977
|
+
viewFromSeatUrl: first.viewFromSeatUrl
|
|
978
|
+
});
|
|
979
|
+
adjacencyOffset += row.seatCount;
|
|
980
|
+
displayOffset += row.seatCount;
|
|
981
|
+
}
|
|
982
|
+
}
|
|
983
|
+
for (const obj of objects) {
|
|
984
|
+
let seats = [];
|
|
985
|
+
if (obj.type === "row") seats = expandRow(obj);
|
|
986
|
+
else if (obj.type === "table") seats = expandTable(obj);
|
|
987
|
+
else if (obj.type === "booth") seats = expandBooth(obj);
|
|
988
|
+
if (!seats.length) continue;
|
|
989
|
+
if (obj.type === "row") {
|
|
990
|
+
const logical = segmented.get(obj.id);
|
|
991
|
+
if (logical) {
|
|
992
|
+
const overrides2 = new Map((obj.overrides ?? []).map((override) => [override.index, override]));
|
|
993
|
+
for (const seat of seats) {
|
|
994
|
+
const physicalIndex = Number(seat.id.slice(seat.id.lastIndexOf(":") + 1));
|
|
995
|
+
if (!Number.isInteger(physicalIndex)) continue;
|
|
996
|
+
const displayOrdinal = logical.displayOffset + physicalIndex;
|
|
997
|
+
seat.logicalRowId = logical.groupId;
|
|
998
|
+
seat.logicalSeatIndex = logical.adjacencyOffset + physicalIndex;
|
|
999
|
+
if (!overrides2.get(physicalIndex)?.displayLabel) {
|
|
1000
|
+
const numberingRow = {
|
|
1001
|
+
...logical.canonical,
|
|
1002
|
+
seatCount: logical.totalSeats,
|
|
1003
|
+
label: logical.displayLabel,
|
|
1004
|
+
displayLabel: logical.displayLabel
|
|
1005
|
+
};
|
|
1006
|
+
seat.displayLabel = `${logical.displayLabel}-${seatLabelPart(numberingRow, displayOrdinal)}`;
|
|
1007
|
+
}
|
|
1008
|
+
seat.viewUrl ??= logical.viewFromSeatUrl;
|
|
1009
|
+
}
|
|
1010
|
+
}
|
|
1011
|
+
}
|
|
1012
|
+
const owner = owningSectionForObject(objects, obj);
|
|
1013
|
+
const inheritedView = owner?.viewFromSeatUrl;
|
|
1014
|
+
const zone = owner?.zone ? zones?.find((candidate) => candidate.id === owner.zone) : void 0;
|
|
1015
|
+
const resolvedFocal = zone?.focalPoint ?? fallbackFocal;
|
|
1016
|
+
for (const seat of seats) {
|
|
1017
|
+
if (inheritedView) seat.viewUrl ??= inheritedView;
|
|
1018
|
+
if (owner) seat.sectionId = owner.logicalSectionId ?? owner.id;
|
|
1019
|
+
if (owner?.zone) seat.zoneId = owner.zone;
|
|
1020
|
+
if (resolvedFocal) seat.focalPoint = { ...resolvedFocal };
|
|
1021
|
+
}
|
|
1022
|
+
out.push(...seats);
|
|
804
1023
|
}
|
|
805
1024
|
return out;
|
|
806
1025
|
}
|
|
1026
|
+
function expandChart(doc, options = {}) {
|
|
1027
|
+
if (doc.floors?.length) {
|
|
1028
|
+
const out2 = [];
|
|
1029
|
+
for (const floor of doc.floors) {
|
|
1030
|
+
const floorFocal = floor.focalPoint ?? doc.focalPoint;
|
|
1031
|
+
const seats = expandFloorObjects(floor.objects, doc.zones, floorFocal);
|
|
1032
|
+
assignEyeHeights(floor.objects, floor.focalPoint ?? doc.focalPoint, floor.baseHeightM ?? 0, seats);
|
|
1033
|
+
out2.push(...seats);
|
|
1034
|
+
}
|
|
1035
|
+
return out2;
|
|
1036
|
+
}
|
|
1037
|
+
const out = expandFloorObjects(doc.objects, doc.zones, doc.focalPoint);
|
|
1038
|
+
assignEyeHeights(doc.objects, doc.focalPoint, options.floorBaseHeightM ?? 0, out);
|
|
1039
|
+
return out;
|
|
1040
|
+
}
|
|
1041
|
+
function assignEyeHeights(objects, focal, floorBaseHeightM, seats) {
|
|
1042
|
+
const sections = objects.filter((o) => o.type === "section");
|
|
1043
|
+
const hasGeometry = floorBaseHeightM > 0 || sections.some((s) => s.height !== void 0 || s.rake !== void 0 || (s.elevation ?? 0) > 0);
|
|
1044
|
+
if (!sections.length || !hasGeometry || !focal) {
|
|
1045
|
+
for (const seat of seats) seat.eyeHeightM = floorBaseHeightM + SEATED_EYE_HEIGHT_M;
|
|
1046
|
+
return;
|
|
1047
|
+
}
|
|
1048
|
+
const owner = new Array(seats.length);
|
|
1049
|
+
const geo = /* @__PURE__ */ new Map();
|
|
1050
|
+
const frontDistU = /* @__PURE__ */ new Map();
|
|
1051
|
+
for (let i = 0; i < seats.length; i++) {
|
|
1052
|
+
const seat = seats[i];
|
|
1053
|
+
const sec = sections.find((s) => pointInPolygonWithHoles({ x: seat.x, y: seat.y }, s.outline, s.holes)) ?? null;
|
|
1054
|
+
owner[i] = sec;
|
|
1055
|
+
if (!sec) continue;
|
|
1056
|
+
if (!geo.has(sec.id)) geo.set(sec.id, sectionGeometry(sec, { floorBaseHeightM }));
|
|
1057
|
+
const seatFocal = seat.focalPoint ?? focal;
|
|
1058
|
+
const d = Math.hypot(seat.x - seatFocal.x, seat.y - seatFocal.y);
|
|
1059
|
+
const cur = frontDistU.get(sec.id);
|
|
1060
|
+
if (cur === void 0 || d < cur) frontDistU.set(sec.id, d);
|
|
1061
|
+
}
|
|
1062
|
+
for (let i = 0; i < seats.length; i++) {
|
|
1063
|
+
const seat = seats[i];
|
|
1064
|
+
const sec = owner[i];
|
|
1065
|
+
if (!sec) {
|
|
1066
|
+
seat.eyeHeightM = floorBaseHeightM + SEATED_EYE_HEIGHT_M;
|
|
1067
|
+
continue;
|
|
1068
|
+
}
|
|
1069
|
+
const g = geo.get(sec.id);
|
|
1070
|
+
let riseM = 0;
|
|
1071
|
+
if (g.rake > 0) {
|
|
1072
|
+
const seatFocal = seat.focalPoint ?? focal;
|
|
1073
|
+
const d = Math.hypot(seat.x - seatFocal.x, seat.y - seatFocal.y);
|
|
1074
|
+
const depthU = Math.max(0, d - (frontDistU.get(sec.id) ?? d));
|
|
1075
|
+
riseM = depthU * METRES_PER_CHART_UNIT * Math.tan(g.rake * Math.PI / 180);
|
|
1076
|
+
}
|
|
1077
|
+
seat.eyeHeightM = g.height + riseM + SEATED_EYE_HEIGHT_M;
|
|
1078
|
+
}
|
|
1079
|
+
}
|
|
807
1080
|
var PAD = 40;
|
|
808
1081
|
function chartBounds(doc) {
|
|
809
1082
|
let minX = Infinity;
|
|
@@ -847,8 +1120,9 @@ function chartBounds(doc) {
|
|
|
847
1120
|
acc(obj.position.x + w, obj.position.y + obj.fontSize);
|
|
848
1121
|
}
|
|
849
1122
|
}
|
|
850
|
-
|
|
851
|
-
|
|
1123
|
+
for (const image of [doc.referenceImage, doc.backgroundImage]) {
|
|
1124
|
+
if (!image) continue;
|
|
1125
|
+
const { center, width } = image;
|
|
852
1126
|
const bh = width * 3 / 4;
|
|
853
1127
|
acc(center.x - width / 2, center.y - bh / 2);
|
|
854
1128
|
acc(center.x + width / 2, center.y + bh / 2);
|
|
@@ -872,9 +1146,55 @@ var MAX_EVENT_INVENTORY = 1e5;
|
|
|
872
1146
|
function gaUnitLabel(areaId, index) {
|
|
873
1147
|
return `${PREFIX}${encodeURIComponent(areaId)}__${index + 1}`;
|
|
874
1148
|
}
|
|
875
|
-
function
|
|
1149
|
+
function validGAInventorySegments(area) {
|
|
1150
|
+
const segments = area.inventorySegments;
|
|
1151
|
+
if (segments === void 0) return true;
|
|
1152
|
+
if (!Array.isArray(segments) || segments.length < 1 || segments.length > 256) return false;
|
|
1153
|
+
let total = 0;
|
|
1154
|
+
const ranges = /* @__PURE__ */ new Map();
|
|
1155
|
+
for (const segment of segments) {
|
|
1156
|
+
if (!segment || typeof segment.sourceAreaId !== "string" || !segment.sourceAreaId.trim() || segment.sourceAreaId.length > 160 || !Number.isInteger(segment.startIndex) || segment.startIndex < 0 || !Number.isInteger(segment.count) || segment.count < 1 || segment.startIndex + segment.count > MAX_GA_CAPACITY) return false;
|
|
1157
|
+
total += segment.count;
|
|
1158
|
+
if (total > MAX_GA_CAPACITY) return false;
|
|
1159
|
+
const sourceRanges = ranges.get(segment.sourceAreaId) ?? [];
|
|
1160
|
+
const end = segment.startIndex + segment.count;
|
|
1161
|
+
if (sourceRanges.some((range) => segment.startIndex < range.end && end > range.start)) return false;
|
|
1162
|
+
sourceRanges.push({ start: segment.startIndex, end });
|
|
1163
|
+
ranges.set(segment.sourceAreaId, sourceRanges);
|
|
1164
|
+
}
|
|
1165
|
+
return total === area.capacity;
|
|
1166
|
+
}
|
|
1167
|
+
function gaInventorySegments(area) {
|
|
876
1168
|
const capacity = Math.min(MAX_GA_CAPACITY, Math.max(0, Math.floor(area.capacity)));
|
|
877
|
-
|
|
1169
|
+
if (area.inventorySegments === void 0) {
|
|
1170
|
+
return capacity ? [{ sourceAreaId: area.id, startIndex: 0, count: capacity }] : [];
|
|
1171
|
+
}
|
|
1172
|
+
if (!validGAInventorySegments(area)) return [];
|
|
1173
|
+
return area.inventorySegments.map((segment) => ({ ...segment }));
|
|
1174
|
+
}
|
|
1175
|
+
function gaUnitLabels(area) {
|
|
1176
|
+
return gaInventorySegments(area).flatMap((segment) => Array.from(
|
|
1177
|
+
{ length: segment.count },
|
|
1178
|
+
(_, offset) => gaUnitLabel(segment.sourceAreaId, segment.startIndex + offset)
|
|
1179
|
+
));
|
|
1180
|
+
}
|
|
1181
|
+
function growJoinedGAInventory(area, nextCapacity) {
|
|
1182
|
+
if (area.inventorySegments === void 0 || nextCapacity === area.capacity) return area.inventorySegments;
|
|
1183
|
+
if (nextCapacity < area.capacity) return void 0;
|
|
1184
|
+
const segments = gaInventorySegments(area);
|
|
1185
|
+
if (!segments.length) return void 0;
|
|
1186
|
+
const extra = nextCapacity - area.capacity;
|
|
1187
|
+
if (!extra) return segments;
|
|
1188
|
+
const ownRanges = segments.filter((segment) => segment.sourceAreaId === area.id);
|
|
1189
|
+
const startIndex = ownRanges.reduce((max, segment) => Math.max(max, segment.startIndex + segment.count), 0);
|
|
1190
|
+
const last = segments[segments.length - 1];
|
|
1191
|
+
if (last?.sourceAreaId === area.id && last.startIndex + last.count === startIndex) {
|
|
1192
|
+
last.count += extra;
|
|
1193
|
+
} else {
|
|
1194
|
+
if (segments.length >= 256) return void 0;
|
|
1195
|
+
segments.push({ sourceAreaId: area.id, startIndex, count: extra });
|
|
1196
|
+
}
|
|
1197
|
+
return segments;
|
|
878
1198
|
}
|
|
879
1199
|
function gaAreasOf(doc) {
|
|
880
1200
|
return allObjects(doc).filter((o) => o.type === "gaArea");
|
|
@@ -895,15 +1215,6 @@ function objectSeatLabels(o) {
|
|
|
895
1215
|
function isSeatObject(o) {
|
|
896
1216
|
return o.type === "row" || o.type === "table" || o.type === "booth" || o.type === "gaArea";
|
|
897
1217
|
}
|
|
898
|
-
function samePoints(left, right) {
|
|
899
|
-
return left.length === right.length && left.every((point, index) => point.x === right[index].x && point.y === right[index].y);
|
|
900
|
-
}
|
|
901
|
-
function sameGASurfaceAsSection(object, section) {
|
|
902
|
-
if (object.type !== "gaArea" || !samePoints(object.points, section.outline)) return false;
|
|
903
|
-
const objectHoles = object.holes ?? [];
|
|
904
|
-
const sectionHoles = section.holes ?? [];
|
|
905
|
-
return objectHoles.length === sectionHoles.length && objectHoles.every((hole, index) => samePoints(hole, sectionHoles[index]));
|
|
906
|
-
}
|
|
907
1218
|
function computeSections(doc) {
|
|
908
1219
|
const objs = allObjects(doc);
|
|
909
1220
|
const sectionObjs = objs.filter((o) => o.type === "section");
|
|
@@ -929,10 +1240,7 @@ function computeSections(doc) {
|
|
|
929
1240
|
if (!isSeatObject(obj)) continue;
|
|
930
1241
|
const labels = objectSeatLabels(obj);
|
|
931
1242
|
if (labels.length === 0) continue;
|
|
932
|
-
const
|
|
933
|
-
const c = objectCenter(obj);
|
|
934
|
-
const referencedOwner = referencedLogicalId ? sectionObjs.find((section) => (section.logicalSectionId ?? section.id) === referencedLogicalId && (sameGASurfaceAsSection(obj, section) || pointInPolygonWithHoles(c, section.outline, section.holes))) : void 0;
|
|
935
|
-
const owner = referencedOwner ?? sectionObjs.find((s) => pointInPolygonWithHoles(c, s.outline, s.holes));
|
|
1243
|
+
const owner = owningSectionForObject(objs, obj);
|
|
936
1244
|
const node = owner ? nodes.get(owner.logicalSectionId ?? owner.id) : ungrouped;
|
|
937
1245
|
node.seatCount += labels.length;
|
|
938
1246
|
node.objectIds.push(obj.id);
|
|
@@ -1503,11 +1811,310 @@ var import_Circle = require("konva/lib/shapes/Circle");
|
|
|
1503
1811
|
var import_Rect = require("konva/lib/shapes/Rect");
|
|
1504
1812
|
var import_Ellipse = require("konva/lib/shapes/Ellipse");
|
|
1505
1813
|
var import_Line = require("konva/lib/shapes/Line");
|
|
1814
|
+
var import_Arrow = require("konva/lib/shapes/Arrow");
|
|
1506
1815
|
var import_Text = require("konva/lib/shapes/Text");
|
|
1507
1816
|
var import_Path = require("konva/lib/shapes/Path");
|
|
1508
1817
|
var import_Image = require("konva/lib/shapes/Image");
|
|
1509
1818
|
var import_Shape = require("konva/lib/Shape");
|
|
1510
1819
|
|
|
1820
|
+
// src/core/chartBackgrounds.ts
|
|
1821
|
+
function buyerBackgroundImage(owner) {
|
|
1822
|
+
const background = owner.backgroundImage;
|
|
1823
|
+
return background?.url && !background.assetId ? background : void 0;
|
|
1824
|
+
}
|
|
1825
|
+
|
|
1826
|
+
// src/core/spatialIndex.ts
|
|
1827
|
+
var DEFAULT_SEAT_RADIUS = 9;
|
|
1828
|
+
function halfExtents(shape) {
|
|
1829
|
+
if (shape.kind === "circle") return { hx: shape.r, hy: shape.r };
|
|
1830
|
+
const hw = shape.width / 2;
|
|
1831
|
+
const hh = shape.height / 2;
|
|
1832
|
+
const rot = shape.rotation ?? 0;
|
|
1833
|
+
if (!rot) return { hx: hw, hy: hh };
|
|
1834
|
+
const rad = rot * Math.PI / 180;
|
|
1835
|
+
const c = Math.abs(Math.cos(rad));
|
|
1836
|
+
const s = Math.abs(Math.sin(rad));
|
|
1837
|
+
return { hx: hw * c + hh * s, hy: hw * s + hh * c };
|
|
1838
|
+
}
|
|
1839
|
+
function shapeIntersectsRect(e, x0, y0, x1, y1) {
|
|
1840
|
+
if (e.shape.kind === "circle") {
|
|
1841
|
+
const cx = Math.min(Math.max(e.seat.x, x0), x1);
|
|
1842
|
+
const cy = Math.min(Math.max(e.seat.y, y0), y1);
|
|
1843
|
+
const dx = e.seat.x - cx;
|
|
1844
|
+
const dy = e.seat.y - cy;
|
|
1845
|
+
return dx * dx + dy * dy <= e.shape.r * e.shape.r;
|
|
1846
|
+
}
|
|
1847
|
+
const rot = e.shape.rotation ?? 0;
|
|
1848
|
+
if (!rot) {
|
|
1849
|
+
return e.seat.x - e.hx <= x1 && e.seat.x + e.hx >= x0 && e.seat.y - e.hy <= y1 && e.seat.y + e.hy >= y0;
|
|
1850
|
+
}
|
|
1851
|
+
const rad = rot * Math.PI / 180;
|
|
1852
|
+
const c = Math.cos(rad);
|
|
1853
|
+
const s = Math.sin(rad);
|
|
1854
|
+
const hw = e.shape.width / 2;
|
|
1855
|
+
const hh = e.shape.height / 2;
|
|
1856
|
+
const corners = [
|
|
1857
|
+
[-hw, -hh],
|
|
1858
|
+
[hw, -hh],
|
|
1859
|
+
[hw, hh],
|
|
1860
|
+
[-hw, hh]
|
|
1861
|
+
].map(([lx, ly]) => [e.seat.x + lx * c - ly * s, e.seat.y + lx * s + ly * c]);
|
|
1862
|
+
const query = [
|
|
1863
|
+
[x0, y0],
|
|
1864
|
+
[x1, y0],
|
|
1865
|
+
[x1, y1],
|
|
1866
|
+
[x0, y1]
|
|
1867
|
+
];
|
|
1868
|
+
const axes = [
|
|
1869
|
+
[1, 0],
|
|
1870
|
+
[0, 1],
|
|
1871
|
+
[c, s],
|
|
1872
|
+
[-s, c]
|
|
1873
|
+
];
|
|
1874
|
+
for (const [ax, ay] of axes) {
|
|
1875
|
+
let minA = Infinity;
|
|
1876
|
+
let maxA = -Infinity;
|
|
1877
|
+
for (const [px, py] of corners) {
|
|
1878
|
+
const d = px * ax + py * ay;
|
|
1879
|
+
if (d < minA) minA = d;
|
|
1880
|
+
if (d > maxA) maxA = d;
|
|
1881
|
+
}
|
|
1882
|
+
let minB = Infinity;
|
|
1883
|
+
let maxB = -Infinity;
|
|
1884
|
+
for (const [px, py] of query) {
|
|
1885
|
+
const d = px * ax + py * ay;
|
|
1886
|
+
if (d < minB) minB = d;
|
|
1887
|
+
if (d > maxB) maxB = d;
|
|
1888
|
+
}
|
|
1889
|
+
if (maxA < minB || maxB < minA) return false;
|
|
1890
|
+
}
|
|
1891
|
+
return true;
|
|
1892
|
+
}
|
|
1893
|
+
function buildSeatIndex(seats, opts = {}) {
|
|
1894
|
+
const seatRadius = opts.seatRadius ?? DEFAULT_SEAT_RADIUS;
|
|
1895
|
+
const n = seats.length;
|
|
1896
|
+
const entries = new Array(n);
|
|
1897
|
+
let minX = Infinity;
|
|
1898
|
+
let minY = Infinity;
|
|
1899
|
+
let maxX = -Infinity;
|
|
1900
|
+
let maxY = -Infinity;
|
|
1901
|
+
let maxHalf = 0;
|
|
1902
|
+
for (let i = 0; i < n; i++) {
|
|
1903
|
+
const seat = seats[i];
|
|
1904
|
+
const shape = opts.shapeOf?.(seat) ?? { kind: "circle", r: seatRadius };
|
|
1905
|
+
const { hx, hy } = halfExtents(shape);
|
|
1906
|
+
entries[i] = { seat, shape, hx, hy };
|
|
1907
|
+
if (seat.x - hx < minX) minX = seat.x - hx;
|
|
1908
|
+
if (seat.y - hy < minY) minY = seat.y - hy;
|
|
1909
|
+
if (seat.x + hx > maxX) maxX = seat.x + hx;
|
|
1910
|
+
if (seat.y + hy > maxY) maxY = seat.y + hy;
|
|
1911
|
+
if (hx > maxHalf) maxHalf = hx;
|
|
1912
|
+
if (hy > maxHalf) maxHalf = hy;
|
|
1913
|
+
}
|
|
1914
|
+
if (n === 0) {
|
|
1915
|
+
minX = 0;
|
|
1916
|
+
minY = 0;
|
|
1917
|
+
maxX = 0;
|
|
1918
|
+
maxY = 0;
|
|
1919
|
+
}
|
|
1920
|
+
const nominal = maxHalf > 0 ? maxHalf * 4 : 1;
|
|
1921
|
+
const spanX = Math.max(maxX - minX, 0);
|
|
1922
|
+
const spanY = Math.max(maxY - minY, 0);
|
|
1923
|
+
const maxCells = Math.max(64, opts.maxCells ?? n * 4);
|
|
1924
|
+
let cell = nominal;
|
|
1925
|
+
let cols = Math.max(1, Math.ceil(spanX / cell) || 1);
|
|
1926
|
+
let rows = Math.max(1, Math.ceil(spanY / cell) || 1);
|
|
1927
|
+
while (cols * rows > maxCells && (cols > 1 || rows > 1)) {
|
|
1928
|
+
cell *= 2;
|
|
1929
|
+
cols = Math.max(1, Math.ceil(spanX / cell) || 1);
|
|
1930
|
+
rows = Math.max(1, Math.ceil(spanY / cell) || 1);
|
|
1931
|
+
}
|
|
1932
|
+
const cellCount = cols * rows;
|
|
1933
|
+
const cellStart = new Int32Array(cellCount + 1);
|
|
1934
|
+
let total = 0;
|
|
1935
|
+
for (let i = 0; i < n; i++) {
|
|
1936
|
+
const e = entries[i];
|
|
1937
|
+
const cx0 = clampInt(Math.floor((e.seat.x - e.hx - minX) / cell), 0, cols - 1);
|
|
1938
|
+
const cx1 = clampInt(Math.floor((e.seat.x + e.hx - minX) / cell), 0, cols - 1);
|
|
1939
|
+
const cy0 = clampInt(Math.floor((e.seat.y - e.hy - minY) / cell), 0, rows - 1);
|
|
1940
|
+
const cy1 = clampInt(Math.floor((e.seat.y + e.hy - minY) / cell), 0, rows - 1);
|
|
1941
|
+
for (let cy = cy0; cy <= cy1; cy++) {
|
|
1942
|
+
for (let cx = cx0; cx <= cx1; cx++) {
|
|
1943
|
+
cellStart[cy * cols + cx + 1]++;
|
|
1944
|
+
total++;
|
|
1945
|
+
}
|
|
1946
|
+
}
|
|
1947
|
+
}
|
|
1948
|
+
for (let c = 0; c < cellCount; c++) cellStart[c + 1] += cellStart[c];
|
|
1949
|
+
const items = new Int32Array(total);
|
|
1950
|
+
const cursor = cellStart.slice(0, cellCount);
|
|
1951
|
+
for (let i = 0; i < n; i++) {
|
|
1952
|
+
const e = entries[i];
|
|
1953
|
+
const cx0 = clampInt(Math.floor((e.seat.x - e.hx - minX) / cell), 0, cols - 1);
|
|
1954
|
+
const cx1 = clampInt(Math.floor((e.seat.x + e.hx - minX) / cell), 0, cols - 1);
|
|
1955
|
+
const cy0 = clampInt(Math.floor((e.seat.y - e.hy - minY) / cell), 0, rows - 1);
|
|
1956
|
+
const cy1 = clampInt(Math.floor((e.seat.y + e.hy - minY) / cell), 0, rows - 1);
|
|
1957
|
+
for (let cy = cy0; cy <= cy1; cy++) {
|
|
1958
|
+
for (let cx = cx0; cx <= cx1; cx++) {
|
|
1959
|
+
const c = cy * cols + cx;
|
|
1960
|
+
items[cursor[c]++] = i;
|
|
1961
|
+
}
|
|
1962
|
+
}
|
|
1963
|
+
}
|
|
1964
|
+
return {
|
|
1965
|
+
seats,
|
|
1966
|
+
entries,
|
|
1967
|
+
minX,
|
|
1968
|
+
minY,
|
|
1969
|
+
cell,
|
|
1970
|
+
cols,
|
|
1971
|
+
rows,
|
|
1972
|
+
cellStart,
|
|
1973
|
+
items,
|
|
1974
|
+
stamp: new Int32Array(n),
|
|
1975
|
+
epoch: 0
|
|
1976
|
+
};
|
|
1977
|
+
}
|
|
1978
|
+
function clampInt(v, lo, hi) {
|
|
1979
|
+
return v < lo ? lo : v > hi ? hi : v;
|
|
1980
|
+
}
|
|
1981
|
+
function queryRect(index, rect, opts = {}) {
|
|
1982
|
+
const x0 = Math.min(rect.x, rect.x + rect.width);
|
|
1983
|
+
const x1 = Math.max(rect.x, rect.x + rect.width);
|
|
1984
|
+
const y0 = Math.min(rect.y, rect.y + rect.height);
|
|
1985
|
+
const y1 = Math.max(rect.y, rect.y + rect.height);
|
|
1986
|
+
const mode = opts.mode ?? "center";
|
|
1987
|
+
const { cols, rows, cell, minX, minY, cellStart, items, entries } = index;
|
|
1988
|
+
const cx0 = Math.max(0, Math.floor((x0 - minX) / cell));
|
|
1989
|
+
const cx1 = Math.min(cols - 1, Math.floor((x1 - minX) / cell));
|
|
1990
|
+
const cy0 = Math.max(0, Math.floor((y0 - minY) / cell));
|
|
1991
|
+
const cy1 = Math.min(rows - 1, Math.floor((y1 - minY) / cell));
|
|
1992
|
+
if (cx0 > cx1 || cy0 > cy1) return [];
|
|
1993
|
+
const found = [];
|
|
1994
|
+
const stamp = index.stamp;
|
|
1995
|
+
const ep = ++index.epoch;
|
|
1996
|
+
for (let cy = cy0; cy <= cy1; cy++) {
|
|
1997
|
+
const rowBase = cy * cols;
|
|
1998
|
+
for (let cx = cx0; cx <= cx1; cx++) {
|
|
1999
|
+
const c = rowBase + cx;
|
|
2000
|
+
const end = cellStart[c + 1];
|
|
2001
|
+
for (let k = cellStart[c]; k < end; k++) {
|
|
2002
|
+
const i = items[k];
|
|
2003
|
+
if (stamp[i] === ep) continue;
|
|
2004
|
+
stamp[i] = ep;
|
|
2005
|
+
const e = entries[i];
|
|
2006
|
+
if (opts.filter && !opts.filter(e.seat.id, e.seat)) continue;
|
|
2007
|
+
const ok = mode === "center" ? e.seat.x >= x0 && e.seat.x <= x1 && e.seat.y >= y0 && e.seat.y <= y1 : shapeIntersectsRect(e, x0, y0, x1, y1);
|
|
2008
|
+
if (ok) found.push(i);
|
|
2009
|
+
}
|
|
2010
|
+
}
|
|
2011
|
+
}
|
|
2012
|
+
found.sort((a, b) => a - b);
|
|
2013
|
+
return found.map((i) => entries[i].seat.id);
|
|
2014
|
+
}
|
|
2015
|
+
|
|
2016
|
+
// src/core/perspectiveProjection.ts
|
|
2017
|
+
function createPerspectiveCamera(bounds2, maxSurfaceHeightWorld = 0) {
|
|
2018
|
+
const span = Math.max(1, bounds2.width, bounds2.height);
|
|
2019
|
+
const target = {
|
|
2020
|
+
x: bounds2.x + bounds2.width / 2,
|
|
2021
|
+
y: bounds2.y + bounds2.height / 2
|
|
2022
|
+
};
|
|
2023
|
+
const distance = span * 1.55;
|
|
2024
|
+
const height = Math.max(span * 0.82, Math.max(0, maxSurfaceHeightWorld) + span * 0.45);
|
|
2025
|
+
return {
|
|
2026
|
+
target,
|
|
2027
|
+
distance,
|
|
2028
|
+
height,
|
|
2029
|
+
focalLength: Math.hypot(distance, height),
|
|
2030
|
+
yawRad: -8 * Math.PI / 180
|
|
2031
|
+
};
|
|
2032
|
+
}
|
|
2033
|
+
function projectPerspectivePoint(camera, point, heightWorld = 0) {
|
|
2034
|
+
const dx = point.x - camera.target.x;
|
|
2035
|
+
const dy = point.y - camera.target.y;
|
|
2036
|
+
const cos = Math.cos(camera.yawRad);
|
|
2037
|
+
const sin = Math.sin(camera.yawRad);
|
|
2038
|
+
const x = dx * cos - dy * sin;
|
|
2039
|
+
const y = dx * sin + dy * cos;
|
|
2040
|
+
const translatedY = y - camera.distance;
|
|
2041
|
+
const translatedZ = heightWorld - camera.height;
|
|
2042
|
+
const cameraLength = Math.hypot(camera.distance, camera.height);
|
|
2043
|
+
const depth = (-camera.distance * translatedY - camera.height * translatedZ) / cameraLength;
|
|
2044
|
+
const up = (-camera.height * translatedY + camera.distance * translatedZ) / cameraLength;
|
|
2045
|
+
const safeDepth = Math.max(cameraLength * 0.08, depth);
|
|
2046
|
+
const scale = camera.focalLength / safeDepth;
|
|
2047
|
+
return {
|
|
2048
|
+
x: camera.target.x + x * scale,
|
|
2049
|
+
y: camera.target.y - up * scale,
|
|
2050
|
+
depth: safeDepth,
|
|
2051
|
+
scale
|
|
2052
|
+
};
|
|
2053
|
+
}
|
|
2054
|
+
function applyAffine(affine, point) {
|
|
2055
|
+
return {
|
|
2056
|
+
x: affine.a * point.x + affine.c * point.y + affine.e,
|
|
2057
|
+
y: affine.b * point.x + affine.d * point.y + affine.f
|
|
2058
|
+
};
|
|
2059
|
+
}
|
|
2060
|
+
function invertAffine(affine) {
|
|
2061
|
+
const determinant = affine.a * affine.d - affine.b * affine.c;
|
|
2062
|
+
if (!Number.isFinite(determinant) || Math.abs(determinant) < 1e-9) {
|
|
2063
|
+
throw new Error("perspective projection produced a singular affine");
|
|
2064
|
+
}
|
|
2065
|
+
const a = affine.d / determinant;
|
|
2066
|
+
const b = -affine.b / determinant;
|
|
2067
|
+
const c = -affine.c / determinant;
|
|
2068
|
+
const d = affine.a / determinant;
|
|
2069
|
+
return {
|
|
2070
|
+
a,
|
|
2071
|
+
b,
|
|
2072
|
+
c,
|
|
2073
|
+
d,
|
|
2074
|
+
e: -(a * affine.e + c * affine.f),
|
|
2075
|
+
f: -(b * affine.e + d * affine.f)
|
|
2076
|
+
};
|
|
2077
|
+
}
|
|
2078
|
+
function composeAffine(left, right) {
|
|
2079
|
+
return {
|
|
2080
|
+
a: left.a * right.a + left.c * right.b,
|
|
2081
|
+
b: left.b * right.a + left.d * right.b,
|
|
2082
|
+
c: left.a * right.c + left.c * right.d,
|
|
2083
|
+
d: left.b * right.c + left.d * right.d,
|
|
2084
|
+
e: left.a * right.e + left.c * right.f + left.e,
|
|
2085
|
+
f: left.b * right.e + left.d * right.f + left.f
|
|
2086
|
+
};
|
|
2087
|
+
}
|
|
2088
|
+
function perspectiveTangentAffine(camera, anchor, heightAt = () => 0) {
|
|
2089
|
+
const origin = projectPerspectivePoint(camera, anchor, heightAt(anchor));
|
|
2090
|
+
const alongXPoint = { x: anchor.x + 1, y: anchor.y };
|
|
2091
|
+
const alongYPoint = { x: anchor.x, y: anchor.y + 1 };
|
|
2092
|
+
const alongX = projectPerspectivePoint(camera, alongXPoint, heightAt(alongXPoint));
|
|
2093
|
+
const alongY = projectPerspectivePoint(camera, alongYPoint, heightAt(alongYPoint));
|
|
2094
|
+
const a = alongX.x - origin.x;
|
|
2095
|
+
const b = alongX.y - origin.y;
|
|
2096
|
+
const c = alongY.x - origin.x;
|
|
2097
|
+
const d = alongY.y - origin.y;
|
|
2098
|
+
return {
|
|
2099
|
+
a,
|
|
2100
|
+
b,
|
|
2101
|
+
c,
|
|
2102
|
+
d,
|
|
2103
|
+
e: origin.x - a * anchor.x - c * anchor.y,
|
|
2104
|
+
f: origin.y - b * anchor.x - d * anchor.y
|
|
2105
|
+
};
|
|
2106
|
+
}
|
|
2107
|
+
function decomposeAffineLinear(affine) {
|
|
2108
|
+
const scaleX = Math.hypot(affine.a, affine.b);
|
|
2109
|
+
const determinant = affine.a * affine.d - affine.b * affine.c;
|
|
2110
|
+
return {
|
|
2111
|
+
rotationDeg: Math.atan2(affine.b, affine.a) * 180 / Math.PI,
|
|
2112
|
+
scaleX,
|
|
2113
|
+
scaleY: determinant / Math.max(scaleX, 1e-9),
|
|
2114
|
+
skewX: (affine.a * affine.c + affine.b * affine.d) / Math.max(determinant, 1e-9)
|
|
2115
|
+
};
|
|
2116
|
+
}
|
|
2117
|
+
|
|
1511
2118
|
// src/lib/money.ts
|
|
1512
2119
|
var DEFAULT_CURRENCY = "USD";
|
|
1513
2120
|
var displayLocale;
|
|
@@ -1628,6 +2235,25 @@ function formatDate(value, opts) {
|
|
|
1628
2235
|
return new Intl.DateTimeFormat(active, opts ?? { dateStyle: "medium", timeStyle: "short" }).format(value);
|
|
1629
2236
|
}
|
|
1630
2237
|
|
|
2238
|
+
// src/core/shapeLineStyle.ts
|
|
2239
|
+
var DEFAULT_SHAPE_LINE_CAP = "round";
|
|
2240
|
+
var DEFAULT_SHAPE_LINE_JOIN = "round";
|
|
2241
|
+
var DEFAULT_SHAPE_LINE_ENDING = "none";
|
|
2242
|
+
function resolvedShapeLineStyle(shape) {
|
|
2243
|
+
return {
|
|
2244
|
+
lineCap: shape.lineCap ?? DEFAULT_SHAPE_LINE_CAP,
|
|
2245
|
+
lineJoin: shape.lineJoin ?? DEFAULT_SHAPE_LINE_JOIN,
|
|
2246
|
+
startEnding: shape.startEnding ?? DEFAULT_SHAPE_LINE_ENDING,
|
|
2247
|
+
endEnding: shape.endEnding ?? DEFAULT_SHAPE_LINE_ENDING
|
|
2248
|
+
};
|
|
2249
|
+
}
|
|
2250
|
+
function shapeArrowMetrics(strokeWidth) {
|
|
2251
|
+
return {
|
|
2252
|
+
pointerLength: Math.min(48, Math.max(8, strokeWidth * 3)),
|
|
2253
|
+
pointerWidth: Math.min(40, Math.max(8, strokeWidth * 2.25))
|
|
2254
|
+
};
|
|
2255
|
+
}
|
|
2256
|
+
|
|
1631
2257
|
// src/engine/SeatmapRenderer.ts
|
|
1632
2258
|
var SEAT_RADIUS = 9;
|
|
1633
2259
|
var SEAT_LEGIBLE_SCALE = 0.9;
|
|
@@ -1635,17 +2261,18 @@ var CACHE_THRESHOLD = 0.55 * SEAT_LEGIBLE_SCALE;
|
|
|
1635
2261
|
var LABEL_SCALE = MIN_VISIBLE_BOOKABLE_LABEL_PX / SEAT_LABEL_FONT_SIZE;
|
|
1636
2262
|
var MIN_FITTED_SEAT_LABEL_FONT_SIZE = 4;
|
|
1637
2263
|
var SEAT_TAP_SLOP_PX = 14;
|
|
1638
|
-
var
|
|
2264
|
+
var WHEELCHAIR_GLYPH_MIN_PX = 10;
|
|
2265
|
+
var FILTERED_WHEELCHAIR_GLYPH_MIN_PX = 14;
|
|
1639
2266
|
var SECTION_PROMINENT_SCALE = 0.45 * SEAT_LEGIBLE_SCALE;
|
|
1640
2267
|
var BLOCK_MELT_TOP = 0.9 * SEAT_LEGIBLE_SCALE;
|
|
1641
2268
|
var SEAT_FOCUS_SCALE = Math.max(SEAT_LEGIBLE_SCALE * 1.1, BLOCK_MELT_TOP);
|
|
1642
2269
|
var PAN_START_SLOP_PX = 8;
|
|
2270
|
+
var GHOST_CLICK_MS = 700;
|
|
1643
2271
|
var ZONE_PROMINENT_SCALE = 0.55 * SECTION_PROMINENT_SCALE;
|
|
1644
2272
|
var MAX_LABELS = 700;
|
|
1645
2273
|
var MARQUEE_RING_CAP = 2500;
|
|
1646
2274
|
var ISO_ANGLE_DEG = -11.5;
|
|
1647
2275
|
var ISO_SQUASH = 0.58;
|
|
1648
|
-
var LIFT_PER_STEP = 58;
|
|
1649
2276
|
var ISO_TWEEN_MS = 320;
|
|
1650
2277
|
var CAMERA_GLIDE_MS = 650;
|
|
1651
2278
|
var BLOCK_FILL_ALPHA = 1;
|
|
@@ -1682,6 +2309,10 @@ var DEF_SELECTION = "#ffffff";
|
|
|
1682
2309
|
var DEF_SELECTION_ON_LIGHT = "#0b1220";
|
|
1683
2310
|
var DEF_DECOR_FILL = "#232c40";
|
|
1684
2311
|
var DEF_TEXT = "#8b93a7";
|
|
2312
|
+
function konvaFontStyle(bold, italic, defaultWeight = "normal") {
|
|
2313
|
+
const weight = bold ? "bold" : defaultWeight;
|
|
2314
|
+
return italic ? `italic ${weight}`.trim() : weight;
|
|
2315
|
+
}
|
|
1685
2316
|
var DEF_CANVAS_BACKGROUND = "#0e1117";
|
|
1686
2317
|
function colorLuminance(color) {
|
|
1687
2318
|
const s = color.trim();
|
|
@@ -1787,8 +2418,8 @@ function rotatedRectPoints(center, width, height, rotation) {
|
|
|
1787
2418
|
}));
|
|
1788
2419
|
}
|
|
1789
2420
|
function pointsBounds(points) {
|
|
1790
|
-
const
|
|
1791
|
-
return { x:
|
|
2421
|
+
const bounds2 = polyBounds(points);
|
|
2422
|
+
return { x: bounds2.x, y: bounds2.y, width: bounds2.width, height: bounds2.height };
|
|
1792
2423
|
}
|
|
1793
2424
|
function rotatedRectFitsPolygon(center, width, height, rotation, outer, holes) {
|
|
1794
2425
|
const radians = rotation * Math.PI / 180;
|
|
@@ -1808,14 +2439,14 @@ function rotatedRectFitsPolygon(center, width, height, rotation, outer, holes) {
|
|
|
1808
2439
|
return true;
|
|
1809
2440
|
}
|
|
1810
2441
|
function polygonLabelCandidates(outer, holes, preferred) {
|
|
1811
|
-
const
|
|
1812
|
-
const centre = { x:
|
|
2442
|
+
const bounds2 = polyBounds(outer);
|
|
2443
|
+
const centre = { x: bounds2.x + bounds2.width / 2, y: bounds2.y + bounds2.height / 2 };
|
|
1813
2444
|
const points = [preferred];
|
|
1814
2445
|
for (let row = 1; row < 12; row += 1) {
|
|
1815
2446
|
for (let column = 1; column < 12; column += 1) {
|
|
1816
2447
|
const point = {
|
|
1817
|
-
x:
|
|
1818
|
-
y:
|
|
2448
|
+
x: bounds2.x + bounds2.width * column / 12,
|
|
2449
|
+
y: bounds2.y + bounds2.height * row / 12
|
|
1819
2450
|
};
|
|
1820
2451
|
if (pointInPolygonWithHoles(point, outer, holes)) points.push(point);
|
|
1821
2452
|
}
|
|
@@ -1880,6 +2511,33 @@ function rgba(hex, a) {
|
|
|
1880
2511
|
const n = parseInt(m[1], 16);
|
|
1881
2512
|
return `rgba(${n >> 16 & 255},${n >> 8 & 255},${n & 255},${a})`;
|
|
1882
2513
|
}
|
|
2514
|
+
var ViewportGroup = class extends import_Group.Group {
|
|
2515
|
+
constructor() {
|
|
2516
|
+
super(...arguments);
|
|
2517
|
+
this.viewportCulled = false;
|
|
2518
|
+
}
|
|
2519
|
+
setViewportCulled(culled) {
|
|
2520
|
+
if (culled === this.viewportCulled) return;
|
|
2521
|
+
this.viewportCulled = culled;
|
|
2522
|
+
if (!culled) super._clearSelfAndDescendantCache();
|
|
2523
|
+
}
|
|
2524
|
+
isViewportCulled() {
|
|
2525
|
+
return this.viewportCulled;
|
|
2526
|
+
}
|
|
2527
|
+
drawScene(...args) {
|
|
2528
|
+
return this.viewportCulled ? this : super.drawScene(...args);
|
|
2529
|
+
}
|
|
2530
|
+
drawHit(...args) {
|
|
2531
|
+
return this.viewportCulled ? this : super.drawHit(...args);
|
|
2532
|
+
}
|
|
2533
|
+
_clearSelfAndDescendantCache(attr) {
|
|
2534
|
+
if (!this.viewportCulled) {
|
|
2535
|
+
super._clearSelfAndDescendantCache(attr);
|
|
2536
|
+
return;
|
|
2537
|
+
}
|
|
2538
|
+
this._clearCache(attr);
|
|
2539
|
+
}
|
|
2540
|
+
};
|
|
1883
2541
|
function hexToRgb(hex) {
|
|
1884
2542
|
const m = /^#?([\da-f]{6})$/i.exec(hex.trim());
|
|
1885
2543
|
if (!m) return null;
|
|
@@ -1910,8 +2568,15 @@ function lerpColor(a, b, t2) {
|
|
|
1910
2568
|
}
|
|
1911
2569
|
var _SeatmapRenderer = class _SeatmapRenderer {
|
|
1912
2570
|
constructor(container, options = {}) {
|
|
2571
|
+
/** Per-elevated-section label containers. Keeping the labels grouped lets the
|
|
2572
|
+
* Phase-B lift move thousands of seat labels with one transform per section
|
|
2573
|
+
* instead of rewriting every label on every tween frame. */
|
|
2574
|
+
this.labelLiftGroups = /* @__PURE__ */ new Map();
|
|
1913
2575
|
this.seats = [];
|
|
1914
2576
|
this.seatById = /* @__PURE__ */ new Map();
|
|
2577
|
+
/** One build per chart/floor; section membership queries only inspect seats in
|
|
2578
|
+
* the section bounds instead of rescanning the full 13k venue per section. */
|
|
2579
|
+
this.seatIndex = null;
|
|
1915
2580
|
/** Multi-floor (Batch 5): the last-set chart + which floor we're rendering. */
|
|
1916
2581
|
this.chartDoc = null;
|
|
1917
2582
|
this.activeFloorId = "";
|
|
@@ -1927,13 +2592,11 @@ var _SeatmapRenderer = class _SeatmapRenderer {
|
|
|
1927
2592
|
this.seatLabelById = /* @__PURE__ */ new Map();
|
|
1928
2593
|
/** Coloured accommodation ring per accessible seat (few per chart). */
|
|
1929
2594
|
this.accessRingById = /* @__PURE__ */ new Map();
|
|
1930
|
-
/** Centred
|
|
1931
|
-
*
|
|
1932
|
-
*
|
|
1933
|
-
*
|
|
2595
|
+
/** Centred ♿ glyph per physical wheelchair provision. It keeps a small
|
|
2596
|
+
* screen-space floor at every LOD and grows when the Wheelchair filter is
|
|
2597
|
+
* active. Kept in a map so zoom updates touch only the handful of matching
|
|
2598
|
+
* seats, never all 13k nodes. */
|
|
1934
2599
|
this.accessGlyphById = /* @__PURE__ */ new Map();
|
|
1935
|
-
/** Whether the accessibility glyph is legible at the current camera scale. */
|
|
1936
|
-
this.accessGlyphVisible = false;
|
|
1937
2600
|
/** Authored free-text nodes obey the same rendered-size visibility floor. */
|
|
1938
2601
|
this.freeTextById = /* @__PURE__ */ new Map();
|
|
1939
2602
|
/** Stage/rink landmarks retain a readable screen-space caption at overview. */
|
|
@@ -1958,6 +2621,8 @@ var _SeatmapRenderer = class _SeatmapRenderer {
|
|
|
1958
2621
|
this.ownedHold = /* @__PURE__ */ new Set();
|
|
1959
2622
|
/** One selected seat being inspected before it is committed to the cart. */
|
|
1960
2623
|
this.selectionFocusId = null;
|
|
2624
|
+
/** Seat currently owning the shared hover ring (null while not hovering). */
|
|
2625
|
+
this.hoveredId = null;
|
|
1961
2626
|
this.focusedId = null;
|
|
1962
2627
|
/**
|
|
1963
2628
|
* Accessibility filter: `null` = off; `[]` = dim all non-accessible free seats;
|
|
@@ -1976,6 +2641,8 @@ var _SeatmapRenderer = class _SeatmapRenderer {
|
|
|
1976
2641
|
this.sections = [];
|
|
1977
2642
|
this.zones = [];
|
|
1978
2643
|
this.seatSection = /* @__PURE__ */ new Map();
|
|
2644
|
+
/** Seats outside every authored section retain the legacy path in one group. */
|
|
2645
|
+
this.unsectionedSeatGroup = null;
|
|
1979
2646
|
this.catPrice = /* @__PURE__ */ new Map();
|
|
1980
2647
|
/** Section/zone ids to render dimmed (organizer manager: held-back inventory). */
|
|
1981
2648
|
this.dimmedSections = /* @__PURE__ */ new Set();
|
|
@@ -1988,6 +2655,9 @@ var _SeatmapRenderer = class _SeatmapRenderer {
|
|
|
1988
2655
|
this.focusedSectionId = null;
|
|
1989
2656
|
/** Light backdrop panel drawn behind the focused section (removed on clear). */
|
|
1990
2657
|
this.focusBackdrop = null;
|
|
2658
|
+
/** Non-listening visual dim outside the focused section. One overlay replaces
|
|
2659
|
+
* descendant opacity mutations across every seat in the venue. */
|
|
2660
|
+
this.focusDimOverlay = null;
|
|
1991
2661
|
/** Object id → floor id (multi-floor only) — resolves a deck tap in the 3D stack. */
|
|
1992
2662
|
this.objectFloor = /* @__PURE__ */ new Map();
|
|
1993
2663
|
/** Zone id → colour (drives extruded side faces in iso view). */
|
|
@@ -2000,10 +2670,33 @@ var _SeatmapRenderer = class _SeatmapRenderer {
|
|
|
2000
2670
|
this.isoT = 0;
|
|
2001
2671
|
this.isoTarget = 0;
|
|
2002
2672
|
this.isoRaf = 0;
|
|
2673
|
+
/** Public view target; perspective is a separate non-affine Phase-C lane. */
|
|
2674
|
+
this.viewMode = "flat";
|
|
2675
|
+
this.perspectiveCamera = null;
|
|
2676
|
+
/** Ground-plane tangent applied once to every renderer layer. */
|
|
2677
|
+
this.perspectiveBaseAffine = null;
|
|
2678
|
+
this.perspectiveBaseInverse = null;
|
|
2679
|
+
/** Exact pinhole anchor expressed in the overlay tangent-layer coordinates. */
|
|
2680
|
+
this.perspectiveSeatLocal = /* @__PURE__ */ new Map();
|
|
2681
|
+
/** Exact projected point (before Stage pan/zoom), keyed by seat. */
|
|
2682
|
+
this.perspectiveSeatProjected = /* @__PURE__ */ new Map();
|
|
2683
|
+
/** Positive camera depth, used for deterministic far→near paint order. */
|
|
2684
|
+
this.perspectiveSeatDepth = /* @__PURE__ */ new Map();
|
|
2685
|
+
/** Exact pinhole size ratio at each seat anchor. */
|
|
2686
|
+
this.perspectiveSeatScale = /* @__PURE__ */ new Map();
|
|
2687
|
+
/** Seats whose native Konva nodes currently carry the exact projected pose.
|
|
2688
|
+
* Large sectioned venues apply these lazily as a section enters the live-seat
|
|
2689
|
+
* viewport; the overview rung hides the seat layer completely. */
|
|
2690
|
+
this.perspectiveAppliedSeats = /* @__PURE__ */ new Set();
|
|
2691
|
+
this.perspectiveBounds = null;
|
|
2003
2692
|
/** Chart centre the iso projection pivots about (bounds centre). */
|
|
2004
2693
|
this.isoCentre = { x: 0, y: 0 };
|
|
2005
2694
|
/** rAF for an in-flight camera glide (focusRegion / setRung); 0 = none. */
|
|
2006
2695
|
this.glideRaf = 0;
|
|
2696
|
+
/** While the camera tween is active, defer polygon label fitting/collision to
|
|
2697
|
+
* the settled frame. Geometry remains smoothly stage-scaled, while expensive
|
|
2698
|
+
* point-in-polygon text work no longer repeats at 120 Hz. */
|
|
2699
|
+
this.glideInProgress = false;
|
|
2007
2700
|
/** Set in destroy() so an in-flight iso tween bails. */
|
|
2008
2701
|
this.destroyed = false;
|
|
2009
2702
|
/** Cached scale the section/zone labels were last sized for (scale-compensation). */
|
|
@@ -2033,6 +2726,19 @@ var _SeatmapRenderer = class _SeatmapRenderer {
|
|
|
2033
2726
|
this.panStarted = false;
|
|
2034
2727
|
/** Maximum displacement from gesture start — suppress taps only after a real pan/pinch. */
|
|
2035
2728
|
this.moved = 0;
|
|
2729
|
+
/**
|
|
2730
|
+
* Ghost-click guard. Konva binds `_pointerup` to mouseup, touchend AND
|
|
2731
|
+
* pointerup, so `on('click tap')` is two handlers for one finger tap unless
|
|
2732
|
+
* the browser's synthesized compatibility click is suppressed. Konva only
|
|
2733
|
+
* suppresses it when the touch lands on a LISTENING SHAPE (Stage.js returns
|
|
2734
|
+
* early before its preventDefault() when `!shape || !shape.isListening()`),
|
|
2735
|
+
* so a near-miss rescued by nearestSeatToScreen used to fire `tap` (select)
|
|
2736
|
+
* then `click` (deselect) 1-3ms apart and net to nothing. With
|
|
2737
|
+
* SEAT_TAP_SLOP_PX=14 against a ~12px seat radius, that broken annulus is
|
|
2738
|
+
* ~3.7x the area of the seat itself — i.e. most successful mobile taps.
|
|
2739
|
+
* `touch-action: none` does NOT suppress compatibility mouse events.
|
|
2740
|
+
*/
|
|
2741
|
+
this.lastTapAt = 0;
|
|
2036
2742
|
/**
|
|
2037
2743
|
* Manage-mode rubber-band marquee (option-gated). `start`/`cur` are WORLD-space
|
|
2038
2744
|
* points (overlayLayer rides the stage transform); `rect` is the on-canvas
|
|
@@ -2129,6 +2835,7 @@ var _SeatmapRenderer = class _SeatmapRenderer {
|
|
|
2129
2835
|
x: mid.x - this.pinch.worldMid.x * scale,
|
|
2130
2836
|
y: mid.y - this.pinch.worldMid.y * scale
|
|
2131
2837
|
});
|
|
2838
|
+
this.updateSeatGroupVisibility();
|
|
2132
2839
|
this.stage.batchDraw();
|
|
2133
2840
|
this.scheduleViewChange();
|
|
2134
2841
|
} else if (this.panLast && this.pointers.size === 1) {
|
|
@@ -2141,6 +2848,7 @@ var _SeatmapRenderer = class _SeatmapRenderer {
|
|
|
2141
2848
|
y: this.stage.y() + (p.y - this.panLast.y)
|
|
2142
2849
|
});
|
|
2143
2850
|
this.panLast = p;
|
|
2851
|
+
this.updateSeatGroupVisibility();
|
|
2144
2852
|
this.stage.batchDraw();
|
|
2145
2853
|
this.scheduleViewChange();
|
|
2146
2854
|
}
|
|
@@ -2193,6 +2901,8 @@ var _SeatmapRenderer = class _SeatmapRenderer {
|
|
|
2193
2901
|
this.overlayLayer = new import_Layer.Layer({ listening: false });
|
|
2194
2902
|
this.labelGroup = new import_Group.Group({ listening: false });
|
|
2195
2903
|
this.overlayLayer.add(this.labelGroup);
|
|
2904
|
+
this.fgDecorGroup = new import_Group.Group({ listening: false });
|
|
2905
|
+
this.overlayLayer.add(this.fgDecorGroup);
|
|
2196
2906
|
this.hoverRing = new import_Circle.Circle({
|
|
2197
2907
|
radius: SEAT_RADIUS + 2,
|
|
2198
2908
|
stroke: "#ffffff",
|
|
@@ -2227,6 +2937,21 @@ var _SeatmapRenderer = class _SeatmapRenderer {
|
|
|
2227
2937
|
this.resizeObs.observe(container);
|
|
2228
2938
|
}
|
|
2229
2939
|
}
|
|
2940
|
+
/**
|
|
2941
|
+
* True when this is the browser's synthesized compatibility click following a
|
|
2942
|
+
* finger tap we already handled. Discriminates on `e.type` deliberately —
|
|
2943
|
+
* `PointerEvent extends MouseEvent`, so an `instanceof MouseEvent` test
|
|
2944
|
+
* matches genuine pointer taps too. One shared timestamp across the layer and
|
|
2945
|
+
* stage handlers: a tap consumed at the layer must also suppress the ghost at
|
|
2946
|
+
* the stage, and only `click` is ever rejected, so bubbling stays intact.
|
|
2947
|
+
*/
|
|
2948
|
+
isGhostClick(e) {
|
|
2949
|
+
if (e.type === "tap") {
|
|
2950
|
+
this.lastTapAt = performance.now();
|
|
2951
|
+
return false;
|
|
2952
|
+
}
|
|
2953
|
+
return this.lastTapAt > 0 && performance.now() - this.lastTapAt < GHOST_CLICK_MS;
|
|
2954
|
+
}
|
|
2230
2955
|
// ---- ISeatmapRenderer -----------------------------------------------------
|
|
2231
2956
|
setChart(doc, opts) {
|
|
2232
2957
|
if (doc !== this.chartDoc) this.stacked = false;
|
|
@@ -2240,8 +2965,15 @@ var _SeatmapRenderer = class _SeatmapRenderer {
|
|
|
2240
2965
|
this.focusedId = null;
|
|
2241
2966
|
this.focusRing.visible(false);
|
|
2242
2967
|
this.bgLayer.destroyChildren();
|
|
2968
|
+
this.focusDimOverlay?.destroy();
|
|
2969
|
+
this.focusDimOverlay = null;
|
|
2970
|
+
this.seatLayer.clearCache();
|
|
2971
|
+
this.seatLayer.listening(true);
|
|
2243
2972
|
this.seatLayer.destroyChildren();
|
|
2973
|
+
this.unsectionedSeatGroup = null;
|
|
2244
2974
|
this.labelGroup.destroyChildren();
|
|
2975
|
+
this.labelLiftGroups.clear();
|
|
2976
|
+
this.fgDecorGroup.destroyChildren();
|
|
2245
2977
|
this.circleById.clear();
|
|
2246
2978
|
this.boothDims.clear();
|
|
2247
2979
|
this.boothLabelById.clear();
|
|
@@ -2258,6 +2990,7 @@ var _SeatmapRenderer = class _SeatmapRenderer {
|
|
|
2258
2990
|
this.statusById.clear();
|
|
2259
2991
|
this.selection.clear();
|
|
2260
2992
|
this.seatById.clear();
|
|
2993
|
+
this.seatIndex = null;
|
|
2261
2994
|
this.cached = false;
|
|
2262
2995
|
this.accessFilter = null;
|
|
2263
2996
|
this.sections = [];
|
|
@@ -2275,11 +3008,22 @@ var _SeatmapRenderer = class _SeatmapRenderer {
|
|
|
2275
3008
|
}
|
|
2276
3009
|
this.isoT = 0;
|
|
2277
3010
|
this.isoTarget = 0;
|
|
3011
|
+
this.viewMode = "flat";
|
|
3012
|
+
this.perspectiveCamera = null;
|
|
3013
|
+
this.perspectiveBaseAffine = null;
|
|
3014
|
+
this.perspectiveBaseInverse = null;
|
|
3015
|
+
this.perspectiveSeatLocal.clear();
|
|
3016
|
+
this.perspectiveSeatProjected.clear();
|
|
3017
|
+
this.perspectiveSeatDepth.clear();
|
|
3018
|
+
this.perspectiveSeatScale.clear();
|
|
3019
|
+
this.perspectiveAppliedSeats.clear();
|
|
3020
|
+
this.perspectiveBounds = null;
|
|
2278
3021
|
this.resetLayerTransforms();
|
|
2279
3022
|
this.hasSections = view.objects.some((o) => o.type === "section");
|
|
2280
3023
|
for (const z of doc.zones ?? []) if (z.color) this.zoneColor.set(z.id, z.color);
|
|
2281
3024
|
this.seatLayer.opacity(1);
|
|
2282
3025
|
this.hoverRing.visible(false);
|
|
3026
|
+
this.hoveredId = null;
|
|
2283
3027
|
this.theme = doc.theme ?? {};
|
|
2284
3028
|
this.seatR = clamp(this.theme.seatScale ?? 1, 0.7, 1.6) * SEAT_RADIUS;
|
|
2285
3029
|
this.container.style.background = "";
|
|
@@ -2300,26 +3044,61 @@ var _SeatmapRenderer = class _SeatmapRenderer {
|
|
|
2300
3044
|
this.boothDims.set(obj.id, { width: obj.width, height: obj.height, rotation: obj.rotation });
|
|
2301
3045
|
}
|
|
2302
3046
|
}
|
|
2303
|
-
this.seats = expandChart(view
|
|
3047
|
+
this.seats = expandChart(view, {
|
|
3048
|
+
// A stacked overview is presentation-only and never opens a seat POV. The
|
|
3049
|
+
// per-section renderer below still resolves each object's real floor base.
|
|
3050
|
+
floorBaseHeightM: this.stacked ? 0 : this.floorBaseHeightMFor()
|
|
3051
|
+
});
|
|
2304
3052
|
for (const s of this.seats) {
|
|
2305
3053
|
this.seatById.set(s.id, s);
|
|
2306
3054
|
this.statusById.set(s.id, "free");
|
|
2307
3055
|
}
|
|
3056
|
+
this.seatIndex = buildSeatIndex(this.seats, { seatRadius: this.seatR });
|
|
3057
|
+
this.bounds = chartBounds(view);
|
|
3058
|
+
this.isoCentre = { x: this.bounds.x + this.bounds.width / 2, y: this.bounds.y + this.bounds.height / 2 };
|
|
3059
|
+
this.buildPerspectiveProjection(view);
|
|
2308
3060
|
this.renderBackground(view);
|
|
3061
|
+
this.unsectionedSeatGroup = new import_Group.Group({ listening: true });
|
|
3062
|
+
this.seatLayer.add(this.unsectionedSeatGroup);
|
|
2309
3063
|
this.renderSeats();
|
|
2310
3064
|
this.overlayLayer.add(this.labelGroup);
|
|
3065
|
+
this.overlayLayer.add(this.fgDecorGroup);
|
|
2311
3066
|
this.overlayLayer.add(this.hoverRing);
|
|
2312
|
-
this.
|
|
2313
|
-
this.isoCentre = { x: this.bounds.x + this.bounds.width / 2, y: this.bounds.y + this.bounds.height / 2 };
|
|
3067
|
+
this.overlayLayer.add(this.focusRing);
|
|
2314
3068
|
this.zoomToFit();
|
|
2315
3069
|
}
|
|
2316
3070
|
/** The chart to render: all floors stacked (3D overview), the active floor, or
|
|
2317
3071
|
* the whole chart for single-floor charts. */
|
|
2318
3072
|
floorView(doc) {
|
|
2319
|
-
if (!doc.floors || !doc.floors.length) return
|
|
2320
|
-
|
|
3073
|
+
if (!doc.floors || !doc.floors.length) return {
|
|
3074
|
+
...doc,
|
|
3075
|
+
referenceImage: void 0,
|
|
3076
|
+
backgroundImage: buyerBackgroundImage(doc)
|
|
3077
|
+
};
|
|
3078
|
+
if (this.stacked && doc.floors.length >= 2) {
|
|
3079
|
+
return {
|
|
3080
|
+
...stackFloors(doc),
|
|
3081
|
+
referenceImage: void 0,
|
|
3082
|
+
backgroundImage: buyerBackgroundImage(doc)
|
|
3083
|
+
};
|
|
3084
|
+
}
|
|
2321
3085
|
const floor = doc.floors.find((f) => f.id === this.activeFloorId) ?? doc.floors[0];
|
|
2322
|
-
return {
|
|
3086
|
+
return {
|
|
3087
|
+
...doc,
|
|
3088
|
+
objects: floor.objects,
|
|
3089
|
+
focalPoint: floor.focalPoint,
|
|
3090
|
+
referenceImage: void 0,
|
|
3091
|
+
backgroundImage: buyerBackgroundImage(floor),
|
|
3092
|
+
floors: void 0
|
|
3093
|
+
};
|
|
3094
|
+
}
|
|
3095
|
+
/** Physical floor height is authored data; the 900-unit stacked overview
|
|
3096
|
+
* spread is presentation-only and must never leak into real 3D geometry. */
|
|
3097
|
+
floorBaseHeightMFor(objectId) {
|
|
3098
|
+
const floors = this.chartDoc?.floors;
|
|
3099
|
+
if (!floors?.length) return 0;
|
|
3100
|
+
const floorId = this.stacked && objectId ? this.objectFloor.get(objectId) ?? this.activeFloorId : this.activeFloorId;
|
|
3101
|
+
return floors.find((floor) => floor.id === floorId)?.baseHeightM ?? 0;
|
|
2323
3102
|
}
|
|
2324
3103
|
/** Toggle the 3D all-floors stacked overview (Batch 5). Re-renders; no-op on
|
|
2325
3104
|
* single-floor charts. The caller re-applies statuses + animates the iso view. */
|
|
@@ -2632,7 +3411,8 @@ var _SeatmapRenderer = class _SeatmapRenderer {
|
|
|
2632
3411
|
}
|
|
2633
3412
|
const ids = [];
|
|
2634
3413
|
for (const seat of this.seats) {
|
|
2635
|
-
|
|
3414
|
+
const rendered = this.renderedSeatPoint(seat);
|
|
3415
|
+
if (rendered.x < x0 || rendered.x > x1 || rendered.y < y0 || rendered.y > y1) continue;
|
|
2636
3416
|
if (!this.isSelectable(seat.id)) continue;
|
|
2637
3417
|
ids.push(seat.id);
|
|
2638
3418
|
}
|
|
@@ -2642,9 +3422,10 @@ var _SeatmapRenderer = class _SeatmapRenderer {
|
|
|
2642
3422
|
flashSeat(seatId, color = "#f43f5e") {
|
|
2643
3423
|
const seat = this.seatById.get(seatId);
|
|
2644
3424
|
if (!seat) return;
|
|
3425
|
+
const rendered = this.renderedSeatPoint(seat);
|
|
2645
3426
|
const ring = new import_Circle.Circle({
|
|
2646
|
-
x:
|
|
2647
|
-
y:
|
|
3427
|
+
x: rendered.x,
|
|
3428
|
+
y: rendered.y,
|
|
2648
3429
|
radius: this.seatR,
|
|
2649
3430
|
stroke: color,
|
|
2650
3431
|
strokeWidth: 3,
|
|
@@ -2653,6 +3434,8 @@ var _SeatmapRenderer = class _SeatmapRenderer {
|
|
|
2653
3434
|
perfectDrawEnabled: false,
|
|
2654
3435
|
shadowForStrokeEnabled: false
|
|
2655
3436
|
});
|
|
3437
|
+
const projectionScale = this.viewMode === "perspective" ? this.perspectiveSeatScale.get(seatId) ?? 1 : 1;
|
|
3438
|
+
ring.scale({ x: projectionScale, y: projectionScale });
|
|
2656
3439
|
this.overlayLayer.add(ring);
|
|
2657
3440
|
const start = performance.now();
|
|
2658
3441
|
const dur = 620;
|
|
@@ -2679,17 +3462,20 @@ var _SeatmapRenderer = class _SeatmapRenderer {
|
|
|
2679
3462
|
const matches = this.sections.filter((section) => section.id === sectionId || section.zone === sectionId);
|
|
2680
3463
|
if (!matches.length) return;
|
|
2681
3464
|
for (const section of matches) {
|
|
2682
|
-
const
|
|
3465
|
+
const outline = this.viewMode === "perspective" && section.perspectiveAffine ? section.outline.map((point) => this.perspectiveLocalPoint(this.projectedSectionPoint(section, point))) : section.outline.map((point) => {
|
|
3466
|
+
const lift = section.liftWorld > 0 ? this.isoLiftLocal(section.liftWorld) : { x: 0, y: 0 };
|
|
3467
|
+
return { x: point.x + lift.x, y: point.y + lift.y };
|
|
3468
|
+
});
|
|
3469
|
+
const centre = outline.reduce(
|
|
2683
3470
|
(sum, point) => ({ x: sum.x + point.x, y: sum.y + point.y }),
|
|
2684
3471
|
{ x: 0, y: 0 }
|
|
2685
3472
|
);
|
|
2686
|
-
centre.x /=
|
|
2687
|
-
centre.y /=
|
|
2688
|
-
const lift = section.elevation > 0 ? this.isoLiftLocal(section.elevation) : { x: 0, y: 0 };
|
|
3473
|
+
centre.x /= outline.length;
|
|
3474
|
+
centre.y /= outline.length;
|
|
2689
3475
|
const halo = new import_Line.Line({
|
|
2690
|
-
x: centre.x
|
|
2691
|
-
y: centre.y
|
|
2692
|
-
points:
|
|
3476
|
+
x: centre.x,
|
|
3477
|
+
y: centre.y,
|
|
3478
|
+
points: outline.flatMap((point) => [point.x - centre.x, point.y - centre.y]),
|
|
2693
3479
|
closed: true,
|
|
2694
3480
|
stroke: color,
|
|
2695
3481
|
strokeWidth: 3,
|
|
@@ -2733,12 +3519,14 @@ var _SeatmapRenderer = class _SeatmapRenderer {
|
|
|
2733
3519
|
nearestSeat(fromId, dir) {
|
|
2734
3520
|
const from = this.seatById.get(fromId);
|
|
2735
3521
|
if (!from) return null;
|
|
3522
|
+
const fromPoint = this.viewMode === "perspective" ? this.perspectiveSeatProjected.get(from.id) ?? from : from;
|
|
2736
3523
|
let best = null;
|
|
2737
3524
|
let bestScore = Infinity;
|
|
2738
3525
|
for (const s of this.seats) {
|
|
2739
3526
|
if (s.id === fromId) continue;
|
|
2740
|
-
const
|
|
2741
|
-
const
|
|
3527
|
+
const candidatePoint = this.viewMode === "perspective" ? this.perspectiveSeatProjected.get(s.id) ?? s : s;
|
|
3528
|
+
const dx = candidatePoint.x - fromPoint.x;
|
|
3529
|
+
const dy = candidatePoint.y - fromPoint.y;
|
|
2742
3530
|
const proj = dx * dir.x + dy * dir.y;
|
|
2743
3531
|
if (proj <= 0.5) continue;
|
|
2744
3532
|
const perp = Math.abs(dx * dir.y - dy * dir.x);
|
|
@@ -2755,7 +3543,9 @@ var _SeatmapRenderer = class _SeatmapRenderer {
|
|
|
2755
3543
|
if (!seat) return;
|
|
2756
3544
|
this.focusedId = id;
|
|
2757
3545
|
this.focusRing.radius(this.seatR + 3);
|
|
2758
|
-
this.focusRing.position(
|
|
3546
|
+
this.focusRing.position(this.renderedSeatPoint(seat));
|
|
3547
|
+
const projectionScale = this.viewMode === "perspective" ? this.perspectiveSeatScale.get(id) ?? 1 : 1;
|
|
3548
|
+
this.focusRing.scale({ x: projectionScale, y: projectionScale });
|
|
2759
3549
|
this.focusRing.visible(true);
|
|
2760
3550
|
this.ensureVisible(seat);
|
|
2761
3551
|
this.overlayLayer.batchDraw();
|
|
@@ -2770,7 +3560,8 @@ var _SeatmapRenderer = class _SeatmapRenderer {
|
|
|
2770
3560
|
const margin = 70;
|
|
2771
3561
|
const offscreen = p.x < margin || p.x > w - margin || p.y < margin || p.y > h - margin;
|
|
2772
3562
|
if (this.stage.scaleX() < target || offscreen) {
|
|
2773
|
-
const
|
|
3563
|
+
const rendered = this.renderedSeatPoint(seat);
|
|
3564
|
+
const ip = this.viewMode === "perspective" && this.perspectiveBaseAffine ? applyAffine(this.perspectiveBaseAffine, rendered) : this.isoT === 0 ? rendered : this.isoForward(rendered);
|
|
2774
3565
|
this.stage.scale({ x: target, y: target });
|
|
2775
3566
|
this.stage.position({ x: w / 2 - ip.x * target, y: h / 2 - ip.y * target });
|
|
2776
3567
|
this.afterViewChange();
|
|
@@ -2780,7 +3571,7 @@ var _SeatmapRenderer = class _SeatmapRenderer {
|
|
|
2780
3571
|
zoomToFit() {
|
|
2781
3572
|
const w = this.stage.width();
|
|
2782
3573
|
const h = this.stage.height();
|
|
2783
|
-
const b = this.bounds;
|
|
3574
|
+
const b = this.viewMode === "perspective" && this.perspectiveBounds ? this.perspectiveBounds : this.bounds;
|
|
2784
3575
|
this.fitScale = Math.min(w / b.width, h / b.height) || 1;
|
|
2785
3576
|
const s = this.fitScale;
|
|
2786
3577
|
this.stage.scale({ x: s, y: s });
|
|
@@ -2809,7 +3600,14 @@ var _SeatmapRenderer = class _SeatmapRenderer {
|
|
|
2809
3600
|
}
|
|
2810
3601
|
worldToScreen(point) {
|
|
2811
3602
|
const s = this.stage.scaleX();
|
|
2812
|
-
const
|
|
3603
|
+
const candidateId = point.id;
|
|
3604
|
+
const seat = typeof candidateId === "string" ? this.seatById.get(candidateId) : void 0;
|
|
3605
|
+
if (this.viewMode === "perspective" && this.perspectiveBaseAffine) {
|
|
3606
|
+
const projected = seat ? this.perspectiveSeatProjected.get(seat.id) ?? applyAffine(this.perspectiveBaseAffine, point) : applyAffine(this.perspectiveBaseAffine, point);
|
|
3607
|
+
return { x: projected.x * s + this.stage.x(), y: projected.y * s + this.stage.y() };
|
|
3608
|
+
}
|
|
3609
|
+
const localPoint = seat ? this.renderedSeatPoint(seat) : point;
|
|
3610
|
+
const p = this.isoT === 0 ? localPoint : this.isoForward(localPoint);
|
|
2813
3611
|
return { x: p.x * s + this.stage.x(), y: p.y * s + this.stage.y() };
|
|
2814
3612
|
}
|
|
2815
3613
|
setAccessibleFilter(on) {
|
|
@@ -2819,6 +3617,7 @@ var _SeatmapRenderer = class _SeatmapRenderer {
|
|
|
2819
3617
|
const next = types === null ? null : [...types];
|
|
2820
3618
|
if (this.sameAccessFilter(next)) return;
|
|
2821
3619
|
this.accessFilter = next;
|
|
3620
|
+
this.updateAccessGlyphs(this.effScale());
|
|
2822
3621
|
for (const seat of this.seats) {
|
|
2823
3622
|
const c = this.circleById.get(seat.id);
|
|
2824
3623
|
if (c) this.paintSeat(c, seat.id);
|
|
@@ -2917,10 +3716,11 @@ var _SeatmapRenderer = class _SeatmapRenderer {
|
|
|
2917
3716
|
let minY = Infinity;
|
|
2918
3717
|
let maxY = -Infinity;
|
|
2919
3718
|
for (const seat of matching) {
|
|
2920
|
-
|
|
2921
|
-
|
|
2922
|
-
|
|
2923
|
-
|
|
3719
|
+
const point = this.renderedSeatPoint(seat);
|
|
3720
|
+
minX = Math.min(minX, point.x);
|
|
3721
|
+
maxX = Math.max(maxX, point.x);
|
|
3722
|
+
minY = Math.min(minY, point.y);
|
|
3723
|
+
maxY = Math.max(maxY, point.y);
|
|
2924
3724
|
}
|
|
2925
3725
|
const pad = Math.max(24, this.seatR * 3);
|
|
2926
3726
|
minX -= pad;
|
|
@@ -2941,12 +3741,37 @@ var _SeatmapRenderer = class _SeatmapRenderer {
|
|
|
2941
3741
|
* inverse) keeps landing on the projected seats/sections.
|
|
2942
3742
|
*/
|
|
2943
3743
|
setViewMode(mode) {
|
|
3744
|
+
if (mode === "perspective") {
|
|
3745
|
+
if (this.viewMode === mode) {
|
|
3746
|
+
this.afterViewChange();
|
|
3747
|
+
return;
|
|
3748
|
+
}
|
|
3749
|
+
const resetElevatedSeatGroups = this.viewMode === "isometric" && this.isoT > 0;
|
|
3750
|
+
this.viewMode = mode;
|
|
3751
|
+
this.isoTarget = 1;
|
|
3752
|
+
this.isoT = 1;
|
|
3753
|
+
if (this.isoRaf) {
|
|
3754
|
+
cancelAnimationFrame(this.isoRaf);
|
|
3755
|
+
this.isoRaf = 0;
|
|
3756
|
+
}
|
|
3757
|
+
this.applyPerspective(resetElevatedSeatGroups);
|
|
3758
|
+
this.zoomToFit();
|
|
3759
|
+
return;
|
|
3760
|
+
}
|
|
2944
3761
|
const target = mode === "isometric" ? 1 : 0;
|
|
3762
|
+
const leavingPerspective = this.viewMode === "perspective";
|
|
3763
|
+
this.viewMode = mode;
|
|
2945
3764
|
this.isoTarget = target;
|
|
2946
3765
|
if (this.isoRaf) {
|
|
2947
3766
|
cancelAnimationFrame(this.isoRaf);
|
|
2948
3767
|
this.isoRaf = 0;
|
|
2949
3768
|
}
|
|
3769
|
+
if (leavingPerspective) {
|
|
3770
|
+
this.isoT = target;
|
|
3771
|
+
this.applyIso();
|
|
3772
|
+
this.afterViewChange();
|
|
3773
|
+
return;
|
|
3774
|
+
}
|
|
2950
3775
|
if (this.reducedMotion) {
|
|
2951
3776
|
this.isoT = target;
|
|
2952
3777
|
this.applyIso();
|
|
@@ -2980,7 +3805,7 @@ var _SeatmapRenderer = class _SeatmapRenderer {
|
|
|
2980
3805
|
}
|
|
2981
3806
|
/** Current projection — reflects the tween target, not the mid-tween isoT. */
|
|
2982
3807
|
getViewMode() {
|
|
2983
|
-
return this.
|
|
3808
|
+
return this.viewMode;
|
|
2984
3809
|
}
|
|
2985
3810
|
/** Iso angle (rad) + y-squash for the current isoT. */
|
|
2986
3811
|
isoParams() {
|
|
@@ -2988,6 +3813,11 @@ var _SeatmapRenderer = class _SeatmapRenderer {
|
|
|
2988
3813
|
}
|
|
2989
3814
|
/** Effective vertical scale = stage scale × iso squash — legibility math uses this. */
|
|
2990
3815
|
effScale() {
|
|
3816
|
+
if (this.viewMode === "perspective" && this.perspectiveBaseAffine) {
|
|
3817
|
+
const xScale = Math.hypot(this.perspectiveBaseAffine.a, this.perspectiveBaseAffine.b);
|
|
3818
|
+
const yScale = Math.hypot(this.perspectiveBaseAffine.c, this.perspectiveBaseAffine.d);
|
|
3819
|
+
return this.stage.scaleX() * Math.min(xScale, yScale);
|
|
3820
|
+
}
|
|
2991
3821
|
return this.stage.scaleX() * (1 - (1 - ISO_SQUASH) * this.isoT);
|
|
2992
3822
|
}
|
|
2993
3823
|
/** Project a world point through the iso affine about the chart centre (→ iso-world). */
|
|
@@ -3011,19 +3841,113 @@ var _SeatmapRenderer = class _SeatmapRenderer {
|
|
|
3011
3841
|
const wy = -dx * Math.sin(th) + uy * Math.cos(th);
|
|
3012
3842
|
return { x: c.x + wx, y: c.y + wy };
|
|
3013
3843
|
}
|
|
3844
|
+
/** Precompute the Phase-C pinhole camera and every exact seat anchor. */
|
|
3845
|
+
buildPerspectiveProjection(view) {
|
|
3846
|
+
let maxSurfaceHeightWorld = 0;
|
|
3847
|
+
for (const seat of this.seats) {
|
|
3848
|
+
const surfaceM = Math.max(0, (seat.eyeHeightM ?? SEATED_EYE_HEIGHT_M) - SEATED_EYE_HEIGHT_M);
|
|
3849
|
+
maxSurfaceHeightWorld = Math.max(maxSurfaceHeightWorld, surfaceM * CHART_UNITS_PER_METRE);
|
|
3850
|
+
}
|
|
3851
|
+
for (const object of view.objects) {
|
|
3852
|
+
if (object.type !== "section") continue;
|
|
3853
|
+
maxSurfaceHeightWorld = Math.max(
|
|
3854
|
+
maxSurfaceHeightWorld,
|
|
3855
|
+
sectionGeometry(object, { floorBaseHeightM: this.floorBaseHeightMFor(object.id) }).height * CHART_UNITS_PER_METRE
|
|
3856
|
+
);
|
|
3857
|
+
}
|
|
3858
|
+
const camera = createPerspectiveCamera(this.bounds, maxSurfaceHeightWorld);
|
|
3859
|
+
const base = perspectiveTangentAffine(camera, camera.target);
|
|
3860
|
+
const inverse = invertAffine(base);
|
|
3861
|
+
this.perspectiveCamera = camera;
|
|
3862
|
+
this.perspectiveBaseAffine = base;
|
|
3863
|
+
this.perspectiveBaseInverse = inverse;
|
|
3864
|
+
this.perspectiveSeatLocal.clear();
|
|
3865
|
+
this.perspectiveSeatProjected.clear();
|
|
3866
|
+
this.perspectiveSeatDepth.clear();
|
|
3867
|
+
this.perspectiveSeatScale.clear();
|
|
3868
|
+
this.perspectiveBounds = null;
|
|
3869
|
+
let minX = Infinity;
|
|
3870
|
+
let minY = Infinity;
|
|
3871
|
+
let maxX = -Infinity;
|
|
3872
|
+
let maxY = -Infinity;
|
|
3873
|
+
for (const seat of this.seats) {
|
|
3874
|
+
const surfaceM = Math.max(0, (seat.eyeHeightM ?? SEATED_EYE_HEIGHT_M) - SEATED_EYE_HEIGHT_M);
|
|
3875
|
+
const projected = projectPerspectivePoint(camera, seat, surfaceM * CHART_UNITS_PER_METRE);
|
|
3876
|
+
const point = { x: projected.x, y: projected.y };
|
|
3877
|
+
this.perspectiveSeatProjected.set(seat.id, point);
|
|
3878
|
+
this.perspectiveSeatLocal.set(seat.id, applyAffine(inverse, point));
|
|
3879
|
+
this.perspectiveSeatDepth.set(seat.id, projected.depth);
|
|
3880
|
+
this.perspectiveSeatScale.set(seat.id, projected.scale);
|
|
3881
|
+
minX = Math.min(minX, projected.x);
|
|
3882
|
+
minY = Math.min(minY, projected.y);
|
|
3883
|
+
maxX = Math.max(maxX, projected.x);
|
|
3884
|
+
maxY = Math.max(maxY, projected.y);
|
|
3885
|
+
}
|
|
3886
|
+
for (const point of [
|
|
3887
|
+
{ x: this.bounds.x, y: this.bounds.y },
|
|
3888
|
+
{ x: this.bounds.x + this.bounds.width, y: this.bounds.y },
|
|
3889
|
+
{ x: this.bounds.x + this.bounds.width, y: this.bounds.y + this.bounds.height },
|
|
3890
|
+
{ x: this.bounds.x, y: this.bounds.y + this.bounds.height }
|
|
3891
|
+
]) {
|
|
3892
|
+
const projected = projectPerspectivePoint(camera, point, 0);
|
|
3893
|
+
minX = Math.min(minX, projected.x);
|
|
3894
|
+
minY = Math.min(minY, projected.y);
|
|
3895
|
+
maxX = Math.max(maxX, projected.x);
|
|
3896
|
+
maxY = Math.max(maxY, projected.y);
|
|
3897
|
+
}
|
|
3898
|
+
const pad = Math.max(24, this.seatR * 3);
|
|
3899
|
+
this.perspectiveBounds = Number.isFinite(minX) ? { x: minX - pad, y: minY - pad, width: Math.max(1, maxX - minX + pad * 2), height: Math.max(1, maxY - minY + pad * 2) } : null;
|
|
3900
|
+
}
|
|
3901
|
+
/** Apply an affine to a Konva container while keeping `pivot` fixed logically. */
|
|
3902
|
+
setContainerAffine(node, affine, pivot) {
|
|
3903
|
+
const decomposed = decomposeAffineLinear(affine);
|
|
3904
|
+
node.offset(pivot);
|
|
3905
|
+
node.position(applyAffine(affine, pivot));
|
|
3906
|
+
node.rotation(decomposed.rotationDeg);
|
|
3907
|
+
node.scale({ x: decomposed.scaleX, y: decomposed.scaleY });
|
|
3908
|
+
node.skewX(decomposed.skewX);
|
|
3909
|
+
node.skewY(0);
|
|
3910
|
+
}
|
|
3911
|
+
resetContainerTransform(node) {
|
|
3912
|
+
node.position({ x: 0, y: 0 });
|
|
3913
|
+
node.offset({ x: 0, y: 0 });
|
|
3914
|
+
node.scale({ x: 1, y: 1 });
|
|
3915
|
+
node.rotation(0);
|
|
3916
|
+
node.skewX(0);
|
|
3917
|
+
node.skewY(0);
|
|
3918
|
+
}
|
|
3919
|
+
/** Exact raked surface height used for the bounded section tangent plane. */
|
|
3920
|
+
sectionSurfaceHeight(section, point) {
|
|
3921
|
+
const radial = Math.hypot(point.x - section.surfaceFocal.x, point.y - section.surfaceFocal.y);
|
|
3922
|
+
const depth = Math.max(0, radial - section.frontDistanceWorld);
|
|
3923
|
+
return section.liftWorld + depth * Math.tan(section.rakeDeg * Math.PI / 180);
|
|
3924
|
+
}
|
|
3925
|
+
/** Desired perspective-space point for a section surface, before Stage pan/zoom. */
|
|
3926
|
+
projectedSectionPoint(section, point) {
|
|
3927
|
+
return section.perspectiveAffine ? applyAffine(section.perspectiveAffine, point) : applyAffine(this.perspectiveBaseAffine, point);
|
|
3928
|
+
}
|
|
3929
|
+
/** Shared-layer local point which the ground affine paints at `projected`. */
|
|
3930
|
+
perspectiveLocalPoint(projected) {
|
|
3931
|
+
return this.perspectiveBaseInverse ? applyAffine(this.perspectiveBaseInverse, projected) : projected;
|
|
3932
|
+
}
|
|
3014
3933
|
/**
|
|
3015
3934
|
* Local-space offset that, once the layer applies the iso affine, lifts an
|
|
3016
|
-
* object straight UP in iso-world by `
|
|
3017
|
-
* (= inverse-linear of the pure vertical lift). Zero at isoT=0.
|
|
3935
|
+
* object straight UP in iso-world by `liftWorld × isoT` world units
|
|
3936
|
+
* (= inverse-linear of the pure vertical lift). Zero at isoT=0. `liftWorld` is
|
|
3937
|
+
* the section's resolved real height in world units (Phase B1).
|
|
3018
3938
|
*/
|
|
3019
|
-
isoLiftLocal(
|
|
3939
|
+
isoLiftLocal(liftWorld) {
|
|
3020
3940
|
const { th, sg } = this.isoParams();
|
|
3021
|
-
const delta =
|
|
3941
|
+
const delta = liftWorld * this.isoT;
|
|
3022
3942
|
return { x: -(delta / sg) * Math.sin(th), y: -(delta / sg) * Math.cos(th) };
|
|
3023
3943
|
}
|
|
3024
|
-
/** Restore
|
|
3025
|
-
|
|
3026
|
-
|
|
3944
|
+
/** Restore projection layers to identity (flat) — byte-for-byte the original.
|
|
3945
|
+
* `seatLayer` can be skipped when entering perspective from flat: assigning a
|
|
3946
|
+
* top-level transform invalidates all 14k descendant absolute transforms even
|
|
3947
|
+
* while the semantic overview keeps that layer fully transparent. */
|
|
3948
|
+
resetLayerTransforms(includeSeatLayer = true) {
|
|
3949
|
+
const layers = includeSeatLayer ? [this.bgLayer, this.seatLayer, this.overlayLayer] : [this.bgLayer, this.overlayLayer];
|
|
3950
|
+
for (const layer of layers) {
|
|
3027
3951
|
layer.position({ x: 0, y: 0 });
|
|
3028
3952
|
layer.offset({ x: 0, y: 0 });
|
|
3029
3953
|
layer.scale({ x: 1, y: 1 });
|
|
@@ -3032,6 +3956,117 @@ var _SeatmapRenderer = class _SeatmapRenderer {
|
|
|
3032
3956
|
layer.skewY(0);
|
|
3033
3957
|
}
|
|
3034
3958
|
}
|
|
3959
|
+
/** Restore section-owned containers before applying another projection. */
|
|
3960
|
+
resetSectionProjection(resetSeatGroups = true) {
|
|
3961
|
+
for (const section of this.sections) {
|
|
3962
|
+
this.resetContainerTransform(section.rootGroupBg);
|
|
3963
|
+
this.resetContainerTransform(section.liftGroupBg);
|
|
3964
|
+
if (resetSeatGroups) this.resetContainerTransform(section.liftGroupSeat);
|
|
3965
|
+
section.rootGroupBg.zIndex(section.bgZIndex);
|
|
3966
|
+
if (resetSeatGroups) section.liftGroupSeat.zIndex(section.seatZIndex);
|
|
3967
|
+
const labelGroup = this.labelLiftGroups.get(section.id);
|
|
3968
|
+
if (labelGroup && resetSeatGroups) this.resetContainerTransform(labelGroup);
|
|
3969
|
+
section.perspectiveAffine = void 0;
|
|
3970
|
+
section.perspectiveCorrection = void 0;
|
|
3971
|
+
section.perspectiveDepth = void 0;
|
|
3972
|
+
}
|
|
3973
|
+
}
|
|
3974
|
+
/** Move native seat/booth hit shapes to or from their exact pinhole anchors. */
|
|
3975
|
+
applyExactSeatAnchors(perspective, seatIds) {
|
|
3976
|
+
const seats = seatIds ? [...seatIds].map((id) => this.seatById.get(id)).filter((seat) => !!seat) : perspective ? this.seats : [...this.perspectiveAppliedSeats].map((id) => this.seatById.get(id)).filter((seat) => !!seat);
|
|
3977
|
+
for (const seat of seats) {
|
|
3978
|
+
if (perspective && this.perspectiveAppliedSeats.has(seat.id)) continue;
|
|
3979
|
+
const point = perspective ? this.perspectiveSeatProjected.get(seat.id) ?? seat : seat;
|
|
3980
|
+
const scale = perspective ? this.perspectiveSeatScale.get(seat.id) ?? 1 : 1;
|
|
3981
|
+
const shape = this.circleById.get(seat.id);
|
|
3982
|
+
shape?.position(point);
|
|
3983
|
+
shape?.scale({ x: scale, y: scale });
|
|
3984
|
+
const ring = this.accessRingById.get(seat.id);
|
|
3985
|
+
ring?.position(point);
|
|
3986
|
+
ring?.scale({ x: scale, y: scale });
|
|
3987
|
+
const glyph = this.accessGlyphById.get(seat.id);
|
|
3988
|
+
if (glyph) {
|
|
3989
|
+
const baseGlyphScale = this.seatR * 1.5 / ACCESS_GLYPH_VIEWBOX;
|
|
3990
|
+
glyph.position(point);
|
|
3991
|
+
glyph.scale({ x: baseGlyphScale * scale, y: baseGlyphScale * scale });
|
|
3992
|
+
}
|
|
3993
|
+
const boothLabel = this.boothLabelById.get(seat.id);
|
|
3994
|
+
boothLabel?.position(point);
|
|
3995
|
+
boothLabel?.scale({ x: scale, y: scale });
|
|
3996
|
+
if (perspective) this.perspectiveAppliedSeats.add(seat.id);
|
|
3997
|
+
}
|
|
3998
|
+
if (!perspective) this.perspectiveAppliedSeats.clear();
|
|
3999
|
+
}
|
|
4000
|
+
/**
|
|
4001
|
+
* Phase C projected 2.5D. Seat/booth anchors are exact pinhole projections;
|
|
4002
|
+
* section top surfaces use one explicitly bounded tangent plane per authored
|
|
4003
|
+
* section. Native Konva shapes move with the anchors, so direct hit testing is
|
|
4004
|
+
* the same graph that paints the buyer-visible unit.
|
|
4005
|
+
*/
|
|
4006
|
+
applyPerspective(resetElevatedSeatGroups = true) {
|
|
4007
|
+
const camera = this.perspectiveCamera;
|
|
4008
|
+
const base = this.perspectiveBaseAffine;
|
|
4009
|
+
const inverse = this.perspectiveBaseInverse;
|
|
4010
|
+
if (!camera || !base || !inverse) return;
|
|
4011
|
+
const lazySectionSeats = this.seats.length > 2500 && this.sections.length > 0;
|
|
4012
|
+
if (this.cached && !lazySectionSeats) {
|
|
4013
|
+
this.seatLayer.clearCache();
|
|
4014
|
+
this.seatLayer.listening(true);
|
|
4015
|
+
this.cached = false;
|
|
4016
|
+
}
|
|
4017
|
+
this.resetLayerTransforms(resetElevatedSeatGroups);
|
|
4018
|
+
this.resetSectionProjection(resetElevatedSeatGroups);
|
|
4019
|
+
for (const layer of [this.bgLayer, this.overlayLayer]) {
|
|
4020
|
+
this.setContainerAffine(layer, base, camera.target);
|
|
4021
|
+
}
|
|
4022
|
+
this.perspectiveAppliedSeats.clear();
|
|
4023
|
+
if (!lazySectionSeats) this.applyExactSeatAnchors(true);
|
|
4024
|
+
for (const section of this.sections) {
|
|
4025
|
+
const heightAt = (point) => this.sectionSurfaceHeight(section, point);
|
|
4026
|
+
const desired = perspectiveTangentAffine(camera, section.centroid, heightAt);
|
|
4027
|
+
const correction = composeAffine(inverse, desired);
|
|
4028
|
+
section.perspectiveAffine = desired;
|
|
4029
|
+
section.perspectiveCorrection = correction;
|
|
4030
|
+
section.perspectiveDepth = projectPerspectivePoint(
|
|
4031
|
+
camera,
|
|
4032
|
+
section.centroid,
|
|
4033
|
+
heightAt(section.centroid)
|
|
4034
|
+
).depth;
|
|
4035
|
+
this.setContainerAffine(section.liftGroupBg, correction, section.centroid);
|
|
4036
|
+
for (let index = 0; index < section.sideFaces.length; index++) {
|
|
4037
|
+
const p0 = section.outline[index];
|
|
4038
|
+
const p1 = section.outline[(index + 1) % section.outline.length];
|
|
4039
|
+
const base0 = this.perspectiveLocalPoint(projectPerspectivePoint(camera, p0, 0));
|
|
4040
|
+
const base1 = this.perspectiveLocalPoint(projectPerspectivePoint(camera, p1, 0));
|
|
4041
|
+
const top1 = this.perspectiveLocalPoint(projectPerspectivePoint(camera, p1, heightAt(p1)));
|
|
4042
|
+
const top0 = this.perspectiveLocalPoint(projectPerspectivePoint(camera, p0, heightAt(p0)));
|
|
4043
|
+
section.sideFaces[index].points([
|
|
4044
|
+
base0.x,
|
|
4045
|
+
base0.y,
|
|
4046
|
+
base1.x,
|
|
4047
|
+
base1.y,
|
|
4048
|
+
top1.x,
|
|
4049
|
+
top1.y,
|
|
4050
|
+
top0.x,
|
|
4051
|
+
top0.y
|
|
4052
|
+
]);
|
|
4053
|
+
section.sideFaces[index].opacity(0.9);
|
|
4054
|
+
}
|
|
4055
|
+
}
|
|
4056
|
+
const farToNear = [...this.sections].sort(
|
|
4057
|
+
(left, right) => (right.perspectiveDepth ?? 0) - (left.perspectiveDepth ?? 0)
|
|
4058
|
+
);
|
|
4059
|
+
for (const section of farToNear) {
|
|
4060
|
+
section.rootGroupBg.moveToTop();
|
|
4061
|
+
section.liftGroupSeat.moveToTop();
|
|
4062
|
+
}
|
|
4063
|
+
this.unsectionedSeatGroup?.moveToTop();
|
|
4064
|
+
this.syncSeatOverlayPositions();
|
|
4065
|
+
if (!lazySectionSeats) this.updateSeatGroupVisibility();
|
|
4066
|
+
this.bgLayer.batchDraw();
|
|
4067
|
+
this.seatLayer.batchDraw();
|
|
4068
|
+
this.overlayLayer.batchDraw();
|
|
4069
|
+
}
|
|
3035
4070
|
/**
|
|
3036
4071
|
* Apply the current isoT to the scene: the base rotate+squash as a decomposed
|
|
3037
4072
|
* layer transform (so seats/décor/rings project together and Konva's own
|
|
@@ -3039,6 +4074,8 @@ var _SeatmapRenderer = class _SeatmapRenderer {
|
|
|
3039
4074
|
* lift + extruded side faces on elevated sections.
|
|
3040
4075
|
*/
|
|
3041
4076
|
applyIso() {
|
|
4077
|
+
this.resetSectionProjection();
|
|
4078
|
+
this.applyExactSeatAnchors(false);
|
|
3042
4079
|
const t2 = this.isoT;
|
|
3043
4080
|
if (t2 === 0) {
|
|
3044
4081
|
this.resetLayerTransforms();
|
|
@@ -3067,6 +4104,7 @@ var _SeatmapRenderer = class _SeatmapRenderer {
|
|
|
3067
4104
|
}
|
|
3068
4105
|
this.applyUprightLabels();
|
|
3069
4106
|
this.applyElevation();
|
|
4107
|
+
this.updateSeatGroupVisibility();
|
|
3070
4108
|
this.bgLayer.batchDraw();
|
|
3071
4109
|
this.seatLayer.batchDraw();
|
|
3072
4110
|
this.overlayLayer.batchDraw();
|
|
@@ -3095,10 +4133,11 @@ var _SeatmapRenderer = class _SeatmapRenderer {
|
|
|
3095
4133
|
applyElevation() {
|
|
3096
4134
|
const t2 = this.isoT;
|
|
3097
4135
|
for (const sec of this.sections) {
|
|
3098
|
-
if (sec.
|
|
3099
|
-
const off = this.isoLiftLocal(sec.
|
|
4136
|
+
if (sec.liftWorld <= 0) continue;
|
|
4137
|
+
const off = this.isoLiftLocal(sec.liftWorld);
|
|
3100
4138
|
sec.liftGroupBg?.position(off);
|
|
3101
|
-
sec.liftGroupSeat
|
|
4139
|
+
sec.liftGroupSeat.position(off);
|
|
4140
|
+
this.labelLiftGroups.get(sec.id)?.position(off);
|
|
3102
4141
|
const alpha = 0.9 * t2;
|
|
3103
4142
|
for (let i = 0; i < sec.sideFaces.length; i++) {
|
|
3104
4143
|
const face = sec.sideFaces[i];
|
|
@@ -3108,6 +4147,7 @@ var _SeatmapRenderer = class _SeatmapRenderer {
|
|
|
3108
4147
|
face.opacity(alpha);
|
|
3109
4148
|
}
|
|
3110
4149
|
}
|
|
4150
|
+
this.syncSeatOverlayPositions();
|
|
3111
4151
|
}
|
|
3112
4152
|
destroy() {
|
|
3113
4153
|
this.destroyed = true;
|
|
@@ -3134,16 +4174,166 @@ var _SeatmapRenderer = class _SeatmapRenderer {
|
|
|
3134
4174
|
* tier shifts with one offset in iso view) or the seat layer directly.
|
|
3135
4175
|
*/
|
|
3136
4176
|
seatContainer(id) {
|
|
3137
|
-
return this.seatSection.get(id)?.liftGroupSeat ?? this.seatLayer;
|
|
4177
|
+
return this.seatSection.get(id)?.liftGroupSeat ?? this.unsectionedSeatGroup ?? this.seatLayer;
|
|
3138
4178
|
}
|
|
3139
|
-
|
|
4179
|
+
/** True when a section belongs to the active logical section/zone focus. */
|
|
4180
|
+
sectionMatchesFocus(section) {
|
|
4181
|
+
const focus = this.focusedSectionId;
|
|
4182
|
+
return focus == null || section.id === focus || section.logicalId === focus || section.zone === focus;
|
|
4183
|
+
}
|
|
4184
|
+
/** Visual opacity after the section-focus overlay is composed. */
|
|
4185
|
+
focusedSeatOpacity(id, localOpacity) {
|
|
4186
|
+
if (!this.focusedSectionId) return localOpacity;
|
|
4187
|
+
const section = this.seatSection.get(id);
|
|
4188
|
+
return localOpacity * (section && this.sectionMatchesFocus(section) ? 1 : FOCUS_DIM_OPACITY);
|
|
4189
|
+
}
|
|
4190
|
+
/** Project a section outline through elevation + the active affine view and
|
|
4191
|
+
* then into viewport pixels. The result is conservative (AABB), so a visible
|
|
4192
|
+
* section is never clipped even for rotated or isometric outlines. */
|
|
4193
|
+
sectionScreenBounds(section) {
|
|
4194
|
+
if (this.viewMode === "perspective" && section.perspectiveAffine) {
|
|
4195
|
+
const scale2 = this.stage.scaleX();
|
|
4196
|
+
const stageX2 = this.stage.x();
|
|
4197
|
+
const stageY2 = this.stage.y();
|
|
4198
|
+
return polyBounds(section.outline.map((point) => {
|
|
4199
|
+
const projected = this.projectedSectionPoint(section, point);
|
|
4200
|
+
return { x: projected.x * scale2 + stageX2, y: projected.y * scale2 + stageY2 };
|
|
4201
|
+
}));
|
|
4202
|
+
}
|
|
4203
|
+
const offset = section.liftWorld > 0 ? this.isoLiftLocal(section.liftWorld) : { x: 0, y: 0 };
|
|
4204
|
+
const scale = this.stage.scaleX();
|
|
4205
|
+
const stageX = this.stage.x();
|
|
4206
|
+
const stageY = this.stage.y();
|
|
4207
|
+
return polyBounds(section.outline.map((point) => {
|
|
4208
|
+
const local = { x: point.x + offset.x, y: point.y + offset.y };
|
|
4209
|
+
const projected = this.isoT === 0 ? local : this.isoForward(local);
|
|
4210
|
+
return { x: projected.x * scale + stageX, y: projected.y * scale + stageY };
|
|
4211
|
+
}));
|
|
4212
|
+
}
|
|
4213
|
+
/** Hide only section groups wholly outside a padded viewport at the live-seat
|
|
4214
|
+
* rung. Overview caching always restores all groups first, so panning a
|
|
4215
|
+
* cached whole-venue bitmap can never reveal missing inventory. */
|
|
4216
|
+
updateSeatGroupVisibility() {
|
|
4217
|
+
const liveSeats = this.effScale() >= CACHE_THRESHOLD;
|
|
4218
|
+
const deferPerspectiveSeatReveal = this.viewMode === "perspective" && this.glideInProgress && this.seats.length > 2500;
|
|
4219
|
+
const padding = 96;
|
|
4220
|
+
const width = this.stage.width();
|
|
4221
|
+
const height = this.stage.height();
|
|
4222
|
+
for (const section of this.sections) {
|
|
4223
|
+
let visible = true;
|
|
4224
|
+
if (liveSeats) {
|
|
4225
|
+
const bounds2 = this.sectionScreenBounds(section);
|
|
4226
|
+
visible = bounds2.x + bounds2.width >= -padding && bounds2.y + bounds2.height >= -padding && bounds2.x <= width + padding && bounds2.y <= height + padding;
|
|
4227
|
+
if (visible && this.viewMode === "perspective" && !deferPerspectiveSeatReveal) {
|
|
4228
|
+
this.applyExactSeatAnchors(true, section.memberIds);
|
|
4229
|
+
}
|
|
4230
|
+
}
|
|
4231
|
+
section.liftGroupSeat.setViewportCulled(!visible);
|
|
4232
|
+
const labelGroup = this.labelLiftGroups.get(section.id);
|
|
4233
|
+
labelGroup?.setViewportCulled(!visible);
|
|
4234
|
+
}
|
|
4235
|
+
if (this.unsectionedSeatGroup && !this.unsectionedSeatGroup.visible()) {
|
|
4236
|
+
this.unsectionedSeatGroup.visible(true);
|
|
4237
|
+
}
|
|
4238
|
+
if (liveSeats && this.viewMode === "perspective" && !deferPerspectiveSeatReveal) {
|
|
4239
|
+
this.applyExactSeatAnchors(true, this.seats.filter((seat) => !this.seatSection.has(seat.id)).map((seat) => seat.id));
|
|
4240
|
+
}
|
|
4241
|
+
}
|
|
4242
|
+
/** Seats worth considering for viewport labels. Section culling is a safe
|
|
4243
|
+
* coarse index; the later screen test remains the exact filter. */
|
|
4244
|
+
visibleSeatCandidates() {
|
|
4245
|
+
if (!this.sections.length) return this.seats;
|
|
4246
|
+
const candidates = [];
|
|
4247
|
+
for (const section of this.sections) {
|
|
4248
|
+
if (section.liftGroupSeat.isViewportCulled()) continue;
|
|
4249
|
+
for (const id of section.memberIds) {
|
|
4250
|
+
const seat = this.seatById.get(id);
|
|
4251
|
+
if (seat) candidates.push(seat);
|
|
4252
|
+
}
|
|
4253
|
+
}
|
|
3140
4254
|
for (const seat of this.seats) {
|
|
4255
|
+
if (!this.seatSection.has(seat.id)) candidates.push(seat);
|
|
4256
|
+
}
|
|
4257
|
+
return candidates;
|
|
4258
|
+
}
|
|
4259
|
+
seatViewportCulled(id) {
|
|
4260
|
+
return this.seatSection.get(id)?.liftGroupSeat.isViewportCulled() ?? false;
|
|
4261
|
+
}
|
|
4262
|
+
/** Local world coordinate at which an elevated seat is actually painted for
|
|
4263
|
+
* the current iso tween. Flat seats and the 2D endpoint are unchanged. */
|
|
4264
|
+
renderedSeatPoint(seat) {
|
|
4265
|
+
if (this.viewMode === "perspective") return this.perspectiveSeatLocal.get(seat.id) ?? seat;
|
|
4266
|
+
const section = this.seatSection.get(seat.id);
|
|
4267
|
+
if (!section || section.liftWorld <= 0 || this.isoT === 0) return seat;
|
|
4268
|
+
const offset = this.isoLiftLocal(section.liftWorld);
|
|
4269
|
+
return { x: seat.x + offset.x, y: seat.y + offset.y };
|
|
4270
|
+
}
|
|
4271
|
+
/** Overlay labels mirror the seat-layer lift without an O(seats) per-frame
|
|
4272
|
+
* rewrite. The groups are rebuilt with the zoom-dependent label set. */
|
|
4273
|
+
seatLabelContainer(id) {
|
|
4274
|
+
const section = this.seatSection.get(id);
|
|
4275
|
+
if (!section) return this.labelGroup;
|
|
4276
|
+
let group = this.labelLiftGroups.get(section.id);
|
|
4277
|
+
if (!group) {
|
|
4278
|
+
group = new ViewportGroup({
|
|
4279
|
+
listening: false
|
|
4280
|
+
});
|
|
4281
|
+
group.setViewportCulled(section.liftGroupSeat.isViewportCulled());
|
|
4282
|
+
group.position(this.viewMode === "perspective" ? { x: 0, y: 0 } : this.isoLiftLocal(section.liftWorld));
|
|
4283
|
+
this.labelGroup.add(group);
|
|
4284
|
+
this.labelLiftGroups.set(section.id, group);
|
|
4285
|
+
}
|
|
4286
|
+
return group;
|
|
4287
|
+
}
|
|
4288
|
+
/** Shared overlay furniture is not parented to section lift groups, so keep
|
|
4289
|
+
* the small transient set aligned explicitly. Selection is capped in buyer
|
|
4290
|
+
* mode, making this constant-sized work during the iso tween. */
|
|
4291
|
+
syncSeatOverlayPositions() {
|
|
4292
|
+
if (this.hoveredId) {
|
|
4293
|
+
const seat = this.seatById.get(this.hoveredId);
|
|
4294
|
+
if (seat) {
|
|
4295
|
+
this.hoverRing.position(this.renderedSeatPoint(seat));
|
|
4296
|
+
const scale = this.viewMode === "perspective" ? this.perspectiveSeatScale.get(seat.id) ?? 1 : 1;
|
|
4297
|
+
this.hoverRing.scale({ x: scale, y: scale });
|
|
4298
|
+
}
|
|
4299
|
+
}
|
|
4300
|
+
if (this.focusedId) {
|
|
4301
|
+
const seat = this.seatById.get(this.focusedId);
|
|
4302
|
+
if (seat) {
|
|
4303
|
+
this.focusRing.position(this.renderedSeatPoint(seat));
|
|
4304
|
+
const scale = this.viewMode === "perspective" ? this.perspectiveSeatScale.get(seat.id) ?? 1 : 1;
|
|
4305
|
+
this.focusRing.scale({ x: scale, y: scale });
|
|
4306
|
+
}
|
|
4307
|
+
}
|
|
4308
|
+
for (const [id, marker] of this.selectionMarkers) {
|
|
4309
|
+
const seat = this.seatById.get(id);
|
|
4310
|
+
if (seat) {
|
|
4311
|
+
marker.position(this.renderedSeatPoint(seat));
|
|
4312
|
+
const scale = this.viewMode === "perspective" ? this.perspectiveSeatScale.get(seat.id) ?? 1 : 1;
|
|
4313
|
+
marker.scale({ x: scale, y: scale });
|
|
4314
|
+
}
|
|
4315
|
+
}
|
|
4316
|
+
}
|
|
4317
|
+
renderSeats() {
|
|
4318
|
+
const paintOrder = [...this.seats].sort((left, right) => (this.perspectiveSeatDepth.get(right.id) ?? 0) - (this.perspectiveSeatDepth.get(left.id) ?? 0));
|
|
4319
|
+
for (const seat of paintOrder) {
|
|
3141
4320
|
if (seat.kind === "booth") {
|
|
3142
4321
|
this.renderBoothUnit(seat);
|
|
3143
4322
|
continue;
|
|
3144
4323
|
}
|
|
3145
4324
|
const target = this.seatContainer(seat.id);
|
|
3146
|
-
const c = new
|
|
4325
|
+
const c = seat.wheelchairSpaceType === "no-seat" ? new import_Rect.Rect({
|
|
4326
|
+
x: seat.x,
|
|
4327
|
+
y: seat.y,
|
|
4328
|
+
width: this.seatR * 2,
|
|
4329
|
+
height: this.seatR * 2,
|
|
4330
|
+
offsetX: this.seatR,
|
|
4331
|
+
offsetY: this.seatR,
|
|
4332
|
+
cornerRadius: 2,
|
|
4333
|
+
perfectDrawEnabled: false,
|
|
4334
|
+
shadowForStrokeEnabled: false,
|
|
4335
|
+
hitStrokeWidth: 0
|
|
4336
|
+
}) : new import_Circle.Circle({
|
|
3147
4337
|
x: seat.x,
|
|
3148
4338
|
y: seat.y,
|
|
3149
4339
|
radius: this.seatR,
|
|
@@ -3156,21 +4346,25 @@ var _SeatmapRenderer = class _SeatmapRenderer {
|
|
|
3156
4346
|
this.paintSeat(c, seat.id);
|
|
3157
4347
|
target.add(c);
|
|
3158
4348
|
if (seat.accessible) {
|
|
3159
|
-
|
|
3160
|
-
|
|
3161
|
-
|
|
3162
|
-
|
|
3163
|
-
|
|
3164
|
-
|
|
3165
|
-
|
|
3166
|
-
|
|
3167
|
-
|
|
3168
|
-
|
|
3169
|
-
|
|
3170
|
-
|
|
3171
|
-
|
|
3172
|
-
|
|
3173
|
-
|
|
4349
|
+
if (seat.wheelchairSpaceType !== "no-seat") {
|
|
4350
|
+
const ring = new import_Circle.Circle({
|
|
4351
|
+
x: seat.x,
|
|
4352
|
+
y: seat.y,
|
|
4353
|
+
radius: this.seatR + 1.5,
|
|
4354
|
+
stroke: accessibilityRingColor(seat.accessibility),
|
|
4355
|
+
strokeWidth: 2.5,
|
|
4356
|
+
listening: false,
|
|
4357
|
+
perfectDrawEnabled: false,
|
|
4358
|
+
shadowForStrokeEnabled: false
|
|
4359
|
+
});
|
|
4360
|
+
this.accessRingById.set(seat.id, ring);
|
|
4361
|
+
target.add(ring);
|
|
4362
|
+
}
|
|
4363
|
+
if (seat.accessibility?.includes("wheelchair")) {
|
|
4364
|
+
const glyph = this.buildAccessGlyph(seat, typeof c.fill() === "string" ? c.fill() : "");
|
|
4365
|
+
this.accessGlyphById.set(seat.id, glyph);
|
|
4366
|
+
target.add(glyph);
|
|
4367
|
+
}
|
|
3174
4368
|
}
|
|
3175
4369
|
}
|
|
3176
4370
|
}
|
|
@@ -3208,7 +4402,6 @@ var _SeatmapRenderer = class _SeatmapRenderer {
|
|
|
3208
4402
|
t2.offsetX(t2.width() / 2);
|
|
3209
4403
|
t2.offsetY(t2.height() / 2);
|
|
3210
4404
|
t2.visible(false);
|
|
3211
|
-
this.boothLabelById.set(seat.id, t2);
|
|
3212
4405
|
this.hasBoothText = true;
|
|
3213
4406
|
this.boothLabelById.set(seat.id, t2);
|
|
3214
4407
|
target.add(t2);
|
|
@@ -3231,24 +4424,40 @@ var _SeatmapRenderer = class _SeatmapRenderer {
|
|
|
3231
4424
|
scaleY: k,
|
|
3232
4425
|
fill: stateAwareBookableLabelInk(seatFill, "#ffffff"),
|
|
3233
4426
|
listening: false,
|
|
3234
|
-
visible:
|
|
4427
|
+
visible: false,
|
|
3235
4428
|
perfectDrawEnabled: false,
|
|
3236
4429
|
shadowForStrokeEnabled: false
|
|
3237
4430
|
});
|
|
3238
4431
|
}
|
|
3239
4432
|
/**
|
|
3240
|
-
*
|
|
3241
|
-
*
|
|
3242
|
-
*
|
|
3243
|
-
* never the full node graph, so it is cheap to call on every view change.
|
|
4433
|
+
* Keep physical wheelchair provision readable in screen space at every map
|
|
4434
|
+
* LOD. The active Wheelchair filter promotes it further. Iterates the small
|
|
4435
|
+
* wheelchair-seat set, never the full node graph.
|
|
3244
4436
|
*/
|
|
3245
4437
|
updateAccessGlyphs(scale) {
|
|
3246
4438
|
if (!this.accessGlyphById.size) return;
|
|
3247
|
-
this.accessGlyphVisible = this.seatR * scale >= SEAT_GLYPH_MIN_PX;
|
|
3248
4439
|
for (const [id, glyph] of this.accessGlyphById) {
|
|
3249
|
-
|
|
4440
|
+
const perspectiveScale = this.viewMode === "perspective" ? this.perspectiveSeatScale.get(id) ?? 1 : 1;
|
|
4441
|
+
const baseScale = this.seatR * 1.5 / ACCESS_GLYPH_VIEWBOX * perspectiveScale;
|
|
4442
|
+
const emphasized = this.accessGlyphFilterEmphasized(id);
|
|
4443
|
+
const minimumScreenPx = emphasized ? FILTERED_WHEELCHAIR_GLYPH_MIN_PX : WHEELCHAIR_GLYPH_MIN_PX;
|
|
4444
|
+
const glyphScale = Math.max(
|
|
4445
|
+
baseScale,
|
|
4446
|
+
minimumScreenPx / (ACCESS_GLYPH_VIEWBOX * Math.max(scale, 1e-3))
|
|
4447
|
+
);
|
|
4448
|
+
glyph.scale({ x: glyphScale, y: glyphScale });
|
|
4449
|
+
glyph.visible(this.accessGlyphShouldShow(id));
|
|
3250
4450
|
}
|
|
3251
4451
|
}
|
|
4452
|
+
/** True only for a physical wheelchair provision matching an active buyer filter. */
|
|
4453
|
+
accessGlyphFilterEmphasized(id) {
|
|
4454
|
+
const seat = this.seatById.get(id);
|
|
4455
|
+
const filter = this.accessFilter;
|
|
4456
|
+
return !!seat && filter !== null && !!seat.accessibility?.includes("wheelchair") && (filter.length === 0 || filter.includes("wheelchair")) && seatMatchesAccess(seat, filter);
|
|
4457
|
+
}
|
|
4458
|
+
accessGlyphShouldShow(id) {
|
|
4459
|
+
return this.accessGlyphById.has(id) && this.accessGlyphEligible(id);
|
|
4460
|
+
}
|
|
3252
4461
|
/**
|
|
3253
4462
|
* Whether an accessible seat's glyph should show for its current status. It is
|
|
3254
4463
|
* hidden on seats a buyer cannot take (sold, or another buyer's hold) where the
|
|
@@ -3328,6 +4537,11 @@ var _SeatmapRenderer = class _SeatmapRenderer {
|
|
|
3328
4537
|
c.dash([2, 2]);
|
|
3329
4538
|
break;
|
|
3330
4539
|
}
|
|
4540
|
+
if (seat.wheelchairSpaceType === "no-seat" && status === "free") {
|
|
4541
|
+
c.stroke(accessibilityRingColor(seat.accessibility));
|
|
4542
|
+
c.strokeWidth(selected ? 3 : 2);
|
|
4543
|
+
c.dash([3, 2]);
|
|
4544
|
+
}
|
|
3331
4545
|
if (boothLabel) {
|
|
3332
4546
|
boothLabel.text(status === "booked" ? "SOLD" : status === "held" ? "HELD" : seat.label);
|
|
3333
4547
|
boothLabel.fontSize(status === "free" ? 10 : Math.min(10, Math.max(6, this.boothDims.get(seat.rowId)?.width ?? 40) / 6));
|
|
@@ -3361,11 +4575,6 @@ var _SeatmapRenderer = class _SeatmapRenderer {
|
|
|
3361
4575
|
c.dash([]);
|
|
3362
4576
|
c.opacity(CLOSED_SEAT_OPACITY);
|
|
3363
4577
|
}
|
|
3364
|
-
if (this.focusedSectionId) {
|
|
3365
|
-
const sec = this.seatSection.get(id);
|
|
3366
|
-
const inFocus = !!sec && (sec.id === this.focusedSectionId || sec.logicalId === this.focusedSectionId || sec.zone === this.focusedSectionId);
|
|
3367
|
-
if (!inFocus) c.opacity(FOCUS_DIM_OPACITY);
|
|
3368
|
-
}
|
|
3369
4578
|
if (this.selectionFocusId && id !== this.selectionFocusId) c.opacity(Math.min(c.opacity(), 0.16));
|
|
3370
4579
|
const bookableLabel = this.boothLabelById.get(id) ?? this.seatLabelById.get(id);
|
|
3371
4580
|
if (bookableLabel) {
|
|
@@ -3379,7 +4588,7 @@ var _SeatmapRenderer = class _SeatmapRenderer {
|
|
|
3379
4588
|
const fill = c.fill();
|
|
3380
4589
|
accessGlyph.fill(stateAwareBookableLabelInk(typeof fill === "string" ? fill : "", "#ffffff"));
|
|
3381
4590
|
accessGlyph.opacity(c.opacity());
|
|
3382
|
-
accessGlyph.visible(this.
|
|
4591
|
+
accessGlyph.visible(this.accessGlyphShouldShow(id));
|
|
3383
4592
|
}
|
|
3384
4593
|
this.accessRingById.get(id)?.opacity(c.opacity());
|
|
3385
4594
|
}
|
|
@@ -3448,7 +4657,8 @@ var _SeatmapRenderer = class _SeatmapRenderer {
|
|
|
3448
4657
|
if (!this.sections.some((section) => section.id === id || section.logicalId === id)) return;
|
|
3449
4658
|
this.focusedSectionId = id;
|
|
3450
4659
|
this.drawFocusBackdrop(id);
|
|
3451
|
-
this.
|
|
4660
|
+
this.bgLayer.batchDraw();
|
|
4661
|
+
this.overlayLayer.batchDraw();
|
|
3452
4662
|
this.updateLOD();
|
|
3453
4663
|
this.focusRegion(id, { minScale: SEAT_FOCUS_SCALE });
|
|
3454
4664
|
}
|
|
@@ -3460,7 +4670,10 @@ var _SeatmapRenderer = class _SeatmapRenderer {
|
|
|
3460
4670
|
this.focusBackdrop.destroy();
|
|
3461
4671
|
this.focusBackdrop = null;
|
|
3462
4672
|
}
|
|
3463
|
-
this.
|
|
4673
|
+
this.focusDimOverlay?.destroy();
|
|
4674
|
+
this.focusDimOverlay = null;
|
|
4675
|
+
this.bgLayer.batchDraw();
|
|
4676
|
+
this.overlayLayer.batchDraw();
|
|
3464
4677
|
this.updateLOD();
|
|
3465
4678
|
}
|
|
3466
4679
|
/** The currently AXS-focused section id, or null. */
|
|
@@ -3477,16 +4690,69 @@ var _SeatmapRenderer = class _SeatmapRenderer {
|
|
|
3477
4690
|
if (!sections.length) return;
|
|
3478
4691
|
const backdrop = new import_Group.Group({ listening: false });
|
|
3479
4692
|
for (const section of sections) {
|
|
3480
|
-
|
|
4693
|
+
const perspective = this.viewMode === "perspective" && section.perspectiveAffine;
|
|
4694
|
+
const outline = perspective ? section.outline.map((point) => this.perspectiveLocalPoint(this.projectedSectionPoint(section, point))) : section.outline;
|
|
4695
|
+
const holes = perspective ? section.holes.map((hole) => hole.map((point) => this.perspectiveLocalPoint(this.projectedSectionPoint(section, point)))) : section.holes;
|
|
4696
|
+
backdrop.add(polygonWithHolesShape(outline, holes, {
|
|
3481
4697
|
fill: FOCUS_BACKDROP_FILL,
|
|
3482
4698
|
stroke: rgba("#ffffff", 0.1),
|
|
3483
4699
|
strokeWidth: 1,
|
|
3484
4700
|
listening: false
|
|
3485
|
-
}, section.outlinePath));
|
|
4701
|
+
}, perspective ? void 0 : section.outlinePath));
|
|
3486
4702
|
}
|
|
3487
4703
|
this.bgLayer.add(backdrop);
|
|
3488
4704
|
backdrop.moveToTop();
|
|
3489
4705
|
this.focusBackdrop = backdrop;
|
|
4706
|
+
this.drawFocusDimOverlay(sections);
|
|
4707
|
+
}
|
|
4708
|
+
/** Dim everything outside the active section with one paint-only shape. Group
|
|
4709
|
+
* opacity would make Konva recursively invalidate absolute-opacity caches on
|
|
4710
|
+
* all 14k descendants. The cut-out follows the live elevation offset and is
|
|
4711
|
+
* non-listening, so direct seat hit-testing is unchanged. */
|
|
4712
|
+
drawFocusDimOverlay(sections) {
|
|
4713
|
+
this.focusDimOverlay?.destroy();
|
|
4714
|
+
const span = Math.max(this.bounds.width, this.bounds.height, 1);
|
|
4715
|
+
const padding = span * 4 + 1e3;
|
|
4716
|
+
const outer = [
|
|
4717
|
+
{ x: this.bounds.x - padding, y: this.bounds.y - padding },
|
|
4718
|
+
{ x: this.bounds.x + this.bounds.width + padding, y: this.bounds.y - padding },
|
|
4719
|
+
{ x: this.bounds.x + this.bounds.width + padding, y: this.bounds.y + this.bounds.height + padding },
|
|
4720
|
+
{ x: this.bounds.x - padding, y: this.bounds.y + this.bounds.height + padding }
|
|
4721
|
+
];
|
|
4722
|
+
const signedArea = (points) => points.reduce((sum, point, index) => {
|
|
4723
|
+
const next = points[(index + 1) % points.length];
|
|
4724
|
+
return sum + point.x * next.y - next.x * point.y;
|
|
4725
|
+
}, 0);
|
|
4726
|
+
const overlay = new import_Shape.Shape({
|
|
4727
|
+
fill: this.canvasBackground,
|
|
4728
|
+
opacity: 1 - FOCUS_DIM_OPACITY,
|
|
4729
|
+
listening: false,
|
|
4730
|
+
perfectDrawEnabled: false,
|
|
4731
|
+
sceneFunc: (context, shape) => {
|
|
4732
|
+
const polygonPath = (points) => {
|
|
4733
|
+
if (!points.length) return;
|
|
4734
|
+
context.moveTo(points[0].x, points[0].y);
|
|
4735
|
+
for (let index = 1; index < points.length; index++) context.lineTo(points[index].x, points[index].y);
|
|
4736
|
+
context.closePath();
|
|
4737
|
+
};
|
|
4738
|
+
context.beginPath();
|
|
4739
|
+
polygonPath(outer);
|
|
4740
|
+
for (const section of sections) {
|
|
4741
|
+
const hole = this.viewMode === "perspective" && section.perspectiveAffine ? section.outline.map((point) => this.perspectiveLocalPoint(this.projectedSectionPoint(section, point))) : section.outline.map((point) => {
|
|
4742
|
+
const offset = section.liftWorld > 0 ? this.isoLiftLocal(section.liftWorld) : { x: 0, y: 0 };
|
|
4743
|
+
return { x: point.x + offset.x, y: point.y + offset.y };
|
|
4744
|
+
});
|
|
4745
|
+
polygonPath(signedArea(hole) > 0 ? [...hole].reverse() : hole);
|
|
4746
|
+
}
|
|
4747
|
+
context.fillStrokeShape(shape);
|
|
4748
|
+
}
|
|
4749
|
+
});
|
|
4750
|
+
this.overlayLayer.add(overlay);
|
|
4751
|
+
overlay.moveToTop();
|
|
4752
|
+
for (const marker of this.selectionMarkers.values()) marker.moveToTop();
|
|
4753
|
+
this.hoverRing.moveToTop();
|
|
4754
|
+
this.focusRing.moveToTop();
|
|
4755
|
+
this.focusDimOverlay = overlay;
|
|
3490
4756
|
}
|
|
3491
4757
|
/** Repaint every seat + section block to reflect closed/focus state, then redraw. */
|
|
3492
4758
|
repaintSectionsAndSeats() {
|
|
@@ -3550,7 +4816,8 @@ var _SeatmapRenderer = class _SeatmapRenderer {
|
|
|
3550
4816
|
this.applyGAFilterState();
|
|
3551
4817
|
}
|
|
3552
4818
|
renderBackground(doc) {
|
|
3553
|
-
|
|
4819
|
+
const buyerBackground = buyerBackgroundImage(doc);
|
|
4820
|
+
if (buyerBackground) this.renderBackgroundImage(buyerBackground);
|
|
3554
4821
|
for (const obj of doc.objects) if (obj.type === "decorImage") this.renderDecorImage(obj);
|
|
3555
4822
|
for (const obj of doc.objects) if (obj.type === "section") this.renderSection(obj);
|
|
3556
4823
|
this.renderZones(doc);
|
|
@@ -3630,10 +4897,12 @@ var _SeatmapRenderer = class _SeatmapRenderer {
|
|
|
3630
4897
|
listening: false,
|
|
3631
4898
|
perfectDrawEnabled: false
|
|
3632
4899
|
});
|
|
3633
|
-
this.
|
|
4900
|
+
if (obj.layer === "foreground") this.fgDecorGroup.add(node);
|
|
4901
|
+
else this.bgLayer.add(node);
|
|
3634
4902
|
img.onload = () => {
|
|
3635
|
-
|
|
3636
|
-
|
|
4903
|
+
const layer = node.getLayer();
|
|
4904
|
+
if (!layer) return;
|
|
4905
|
+
layer.batchDraw();
|
|
3637
4906
|
};
|
|
3638
4907
|
img.src = obj.href;
|
|
3639
4908
|
}
|
|
@@ -3670,7 +4939,7 @@ var _SeatmapRenderer = class _SeatmapRenderer {
|
|
|
3670
4939
|
})
|
|
3671
4940
|
);
|
|
3672
4941
|
}
|
|
3673
|
-
const label = this.addCentredLabel(this.bgLayer, obj.label, obj.center.x, obj.center.y, "#cbd5e1", 12, true);
|
|
4942
|
+
const label = this.addCentredLabel(this.bgLayer, obj.displayLabel ?? obj.label, obj.center.x, obj.center.y, "#cbd5e1", 12, true);
|
|
3674
4943
|
this.freeTextById.set(obj.id, { node: label, background: "#232c40", kind: "table" });
|
|
3675
4944
|
}
|
|
3676
4945
|
renderText(obj) {
|
|
@@ -3682,11 +4951,12 @@ var _SeatmapRenderer = class _SeatmapRenderer {
|
|
|
3682
4951
|
text: obj.text,
|
|
3683
4952
|
fontSize: obj.fontSize,
|
|
3684
4953
|
rotation: obj.rotation,
|
|
4954
|
+
fontStyle: konvaFontStyle(obj.bold, obj.italic, "normal"),
|
|
3685
4955
|
// Authored ink remains preferred, but an embed/theme surface can change
|
|
3686
4956
|
// the actual canvas. Fail over to readable black/white instead of
|
|
3687
4957
|
// painting an otherwise valid caption invisibly on that active surface.
|
|
3688
4958
|
fill: stateAwareBookableLabelInk(background, preferredInk),
|
|
3689
|
-
fontFamily: this.labelFont(),
|
|
4959
|
+
fontFamily: obj.fontFamily ?? this.labelFont(),
|
|
3690
4960
|
listening: false,
|
|
3691
4961
|
perfectDrawEnabled: false
|
|
3692
4962
|
});
|
|
@@ -3704,11 +4974,40 @@ var _SeatmapRenderer = class _SeatmapRenderer {
|
|
|
3704
4974
|
const isDecor = !!obj.role && !isStage;
|
|
3705
4975
|
const palette = overviewPalette(this.canvasBackground);
|
|
3706
4976
|
const fill = referenceFocal ? palette.focalFill : authoredFill;
|
|
3707
|
-
const
|
|
3708
|
-
const
|
|
4977
|
+
const isOpenPath = obj.kind === "line" || obj.kind === "polyline";
|
|
4978
|
+
const stroke = obj.stroke?.color ?? (isStage ? lighten(fill, 0.28) : referenceFocal ? palette.focalStroke : isOpenPath ? "#9aa3b5" : void 0);
|
|
4979
|
+
const strokeWidth = obj.stroke?.width ?? (isStage ? 1 : referenceFocal ? 2 : isOpenPath ? 2 : 0);
|
|
4980
|
+
const opacity = obj.opacity != null ? clamp(obj.opacity, 0.1, 1) : 1;
|
|
3709
4981
|
let cx = 0;
|
|
3710
4982
|
let cy = 0;
|
|
3711
|
-
if (
|
|
4983
|
+
if (isOpenPath && obj.points && obj.points.length >= 2) {
|
|
4984
|
+
cx = obj.points.reduce((a, p) => a + p.x, 0) / obj.points.length;
|
|
4985
|
+
cy = obj.points.reduce((a, p) => a + p.y, 0) / obj.points.length;
|
|
4986
|
+
const lineStyle = resolvedShapeLineStyle(obj);
|
|
4987
|
+
const arrow = shapeArrowMetrics(strokeWidth);
|
|
4988
|
+
const path = new import_Arrow.Arrow({
|
|
4989
|
+
points: obj.points.flatMap((p) => [p.x, p.y]),
|
|
4990
|
+
closed: false,
|
|
4991
|
+
x: cx,
|
|
4992
|
+
y: cy,
|
|
4993
|
+
offsetX: cx,
|
|
4994
|
+
offsetY: cy,
|
|
4995
|
+
rotation: obj.rotation ?? 0,
|
|
4996
|
+
stroke,
|
|
4997
|
+
fill: stroke,
|
|
4998
|
+
strokeWidth,
|
|
4999
|
+
opacity,
|
|
5000
|
+
lineCap: lineStyle.lineCap,
|
|
5001
|
+
lineJoin: lineStyle.lineJoin,
|
|
5002
|
+
pointerAtBeginning: lineStyle.startEnding === "arrow",
|
|
5003
|
+
pointerAtEnding: lineStyle.endEnding === "arrow",
|
|
5004
|
+
...arrow,
|
|
5005
|
+
listening: false,
|
|
5006
|
+
name: "buyer-shape-open-path"
|
|
5007
|
+
});
|
|
5008
|
+
path.setAttr("shapeObjectId", obj.id);
|
|
5009
|
+
this.bgLayer.add(path);
|
|
5010
|
+
} else if (obj.kind === "rect" && obj.x != null && obj.y != null && obj.width != null && obj.height != null) {
|
|
3712
5011
|
const grad = isStage ? {
|
|
3713
5012
|
fillLinearGradientStartPoint: { x: 0, y: 0 },
|
|
3714
5013
|
fillLinearGradientEndPoint: { x: 0, y: obj.height },
|
|
@@ -3728,7 +5027,8 @@ var _SeatmapRenderer = class _SeatmapRenderer {
|
|
|
3728
5027
|
...grad,
|
|
3729
5028
|
stroke,
|
|
3730
5029
|
strokeWidth,
|
|
3731
|
-
cornerRadius: 4,
|
|
5030
|
+
cornerRadius: clamp(obj.cornerRadius ?? 4, 0, Math.min(obj.width, obj.height) / 2),
|
|
5031
|
+
opacity,
|
|
3732
5032
|
listening: false
|
|
3733
5033
|
})
|
|
3734
5034
|
);
|
|
@@ -3741,7 +5041,7 @@ var _SeatmapRenderer = class _SeatmapRenderer {
|
|
|
3741
5041
|
fillLinearGradientColorStops: [0, darken(fill, 0.3), 1, lighten(fill, 0.12)]
|
|
3742
5042
|
} : { fill };
|
|
3743
5043
|
this.bgLayer.add(
|
|
3744
|
-
new import_Ellipse.Ellipse({ x: cx, y: cy, rotation: obj.rotation ?? 0, radiusX: obj.width / 2, radiusY: obj.height / 2, ...grad, stroke, strokeWidth, listening: false })
|
|
5044
|
+
new import_Ellipse.Ellipse({ x: cx, y: cy, rotation: obj.rotation ?? 0, radiusX: obj.width / 2, radiusY: obj.height / 2, ...grad, stroke, strokeWidth, opacity, listening: false })
|
|
3745
5045
|
);
|
|
3746
5046
|
} else if (obj.kind === "polygon" && obj.points && obj.points.length) {
|
|
3747
5047
|
const pts = obj.points.flatMap((p) => [p.x, p.y]);
|
|
@@ -3756,7 +5056,7 @@ var _SeatmapRenderer = class _SeatmapRenderer {
|
|
|
3756
5056
|
fillLinearGradientColorStops: [0, darken(fill, 0.3), 1, lighten(fill, 0.12)]
|
|
3757
5057
|
} : { fill };
|
|
3758
5058
|
this.bgLayer.add(
|
|
3759
|
-
new import_Line.Line({ points: pts, closed: true, x: cx, y: cy, offsetX: cx, offsetY: cy, rotation: obj.rotation ?? 0, ...grad, stroke, strokeWidth, listening: false })
|
|
5059
|
+
new import_Line.Line({ points: pts, closed: true, x: cx, y: cy, offsetX: cx, offsetY: cy, rotation: obj.rotation ?? 0, ...grad, stroke, strokeWidth, opacity, listening: false })
|
|
3760
5060
|
);
|
|
3761
5061
|
}
|
|
3762
5062
|
if (obj.label) {
|
|
@@ -3815,7 +5115,10 @@ var _SeatmapRenderer = class _SeatmapRenderer {
|
|
|
3815
5115
|
strokeWidth: 1.5
|
|
3816
5116
|
});
|
|
3817
5117
|
poly.setAttr("gaId", obj.id);
|
|
3818
|
-
poly.on("click tap", () =>
|
|
5118
|
+
poly.on("click tap", (e) => {
|
|
5119
|
+
if (this.isGhostClick(e)) return;
|
|
5120
|
+
this.opts.onGAClick?.(obj.id);
|
|
5121
|
+
});
|
|
3819
5122
|
poly.on("mouseenter", () => {
|
|
3820
5123
|
this.container.style.cursor = "pointer";
|
|
3821
5124
|
});
|
|
@@ -3825,7 +5128,7 @@ var _SeatmapRenderer = class _SeatmapRenderer {
|
|
|
3825
5128
|
this.bgLayer.add(poly);
|
|
3826
5129
|
const labelPoint = polygonLabelPoint(obj.points, obj.holes);
|
|
3827
5130
|
const containingSection = this.sections.find((section) => pointInPolygonWithHoles(labelPoint, section.outline, section.holes));
|
|
3828
|
-
const label = this.addCentredLabel(this.bgLayer, obj.label, labelPoint.x, labelPoint.y - 8, ink, GA_LABEL_FONT_SIZE, false);
|
|
5131
|
+
const label = this.addCentredLabel(this.bgLayer, obj.displayLabel ?? obj.label, labelPoint.x, labelPoint.y - 8, ink, GA_LABEL_FONT_SIZE, false);
|
|
3829
5132
|
const capacity = this.addCentredLabel(this.bgLayer, `cap ${obj.capacity}`, labelPoint.x, labelPoint.y + 10, ink, GA_CAPACITY_LABEL_FONT_SIZE, false);
|
|
3830
5133
|
this.freeTextById.set(`${obj.id}:label`, {
|
|
3831
5134
|
objectId: obj.id,
|
|
@@ -3842,7 +5145,7 @@ var _SeatmapRenderer = class _SeatmapRenderer {
|
|
|
3842
5145
|
categoryKey: obj.categoryKey
|
|
3843
5146
|
});
|
|
3844
5147
|
this.gaById.set(obj.id, {
|
|
3845
|
-
label: obj.label,
|
|
5148
|
+
label: obj.displayLabel ?? obj.label,
|
|
3846
5149
|
capacity: obj.capacity,
|
|
3847
5150
|
categoryKey: obj.categoryKey,
|
|
3848
5151
|
points: obj.points,
|
|
@@ -3865,7 +5168,9 @@ var _SeatmapRenderer = class _SeatmapRenderer {
|
|
|
3865
5168
|
const memberIds = [];
|
|
3866
5169
|
const catCounts = /* @__PURE__ */ new Map();
|
|
3867
5170
|
let free = 0;
|
|
3868
|
-
|
|
5171
|
+
const bounds2 = polyBounds(obj.outline);
|
|
5172
|
+
const candidates = this.seatIndex ? queryRect(this.seatIndex, bounds2).map((id) => this.seatById.get(id)).filter((seat) => !!seat) : this.seats;
|
|
5173
|
+
for (const seat of candidates) {
|
|
3869
5174
|
if (this.seatSection.has(seat.id)) continue;
|
|
3870
5175
|
if (!pointInPolygonWithHoles(seat, obj.outline, obj.holes)) continue;
|
|
3871
5176
|
memberIds.push(seat.id);
|
|
@@ -3876,11 +5181,19 @@ var _SeatmapRenderer = class _SeatmapRenderer {
|
|
|
3876
5181
|
[...catCounts].map(([key, w]) => ({ hex: this.catColor.get(key) ?? "#6e7bff", w })),
|
|
3877
5182
|
"#3a4358"
|
|
3878
5183
|
);
|
|
3879
|
-
const elevation =
|
|
3880
|
-
|
|
3881
|
-
|
|
5184
|
+
const elevation = sectionElevationTier(obj.elevation);
|
|
5185
|
+
const geometry = sectionGeometry(obj, {
|
|
5186
|
+
floorBaseHeightM: this.floorBaseHeightMFor(obj.id)
|
|
5187
|
+
});
|
|
5188
|
+
const liftWorld = geometry.height * CHART_UNITS_PER_METRE;
|
|
5189
|
+
const rootGroupBg = new import_Group.Group({ listening: false });
|
|
5190
|
+
this.bgLayer.add(rootGroupBg);
|
|
5191
|
+
const liftGroupBg = new import_Group.Group({ listening: false });
|
|
5192
|
+
rootGroupBg.add(liftGroupBg);
|
|
5193
|
+
const liftGroupSeat = new ViewportGroup({ listening: true });
|
|
5194
|
+
this.seatLayer.add(liftGroupSeat);
|
|
3882
5195
|
const sideFaces = [];
|
|
3883
|
-
if (
|
|
5196
|
+
if (liftWorld > 0) {
|
|
3884
5197
|
const faceFill = darken(this.zoneColor.get(obj.zone ?? "") ?? baseFill, 0.42);
|
|
3885
5198
|
for (let i = 0; i < obj.outline.length; i++) {
|
|
3886
5199
|
const face = new import_Line.Line({
|
|
@@ -3894,14 +5207,11 @@ var _SeatmapRenderer = class _SeatmapRenderer {
|
|
|
3894
5207
|
perfectDrawEnabled: false
|
|
3895
5208
|
});
|
|
3896
5209
|
sideFaces.push(face);
|
|
3897
|
-
|
|
5210
|
+
rootGroupBg.add(face);
|
|
3898
5211
|
}
|
|
3899
|
-
liftGroupBg = new import_Group.Group({ listening: false });
|
|
3900
|
-
this.bgLayer.add(liftGroupBg);
|
|
3901
|
-
liftGroupSeat = new import_Group.Group({ listening: false });
|
|
3902
|
-
this.seatLayer.add(liftGroupSeat);
|
|
3903
5212
|
}
|
|
3904
|
-
|
|
5213
|
+
liftGroupBg.moveToTop();
|
|
5214
|
+
const bgTarget = liftGroupBg;
|
|
3905
5215
|
const outlineTint = obj.color ?? this.zoneColor.get(obj.zone ?? "") ?? "#3a4358";
|
|
3906
5216
|
const outlinePoly = polygonWithHolesShape(obj.outline, obj.holes, {
|
|
3907
5217
|
stroke: rgba(outlineTint, 0.5),
|
|
@@ -3975,10 +5285,23 @@ var _SeatmapRenderer = class _SeatmapRenderer {
|
|
|
3975
5285
|
nameLabelFits: true,
|
|
3976
5286
|
subLabelFits: true,
|
|
3977
5287
|
elevation,
|
|
5288
|
+
liftWorld,
|
|
5289
|
+
rakeDeg: geometry.rake,
|
|
5290
|
+
surfaceFocal: memberIds.length ? { ...this.seatById.get(memberIds[0])?.focalPoint ?? this.isoCentre } : { ...this.chartDoc?.focalPoint ?? this.isoCentre },
|
|
5291
|
+
frontDistanceWorld: 0,
|
|
5292
|
+
rootGroupBg,
|
|
5293
|
+
bgZIndex: rootGroupBg.zIndex(),
|
|
5294
|
+
seatZIndex: liftGroupSeat.zIndex(),
|
|
3978
5295
|
liftGroupBg,
|
|
3979
5296
|
liftGroupSeat,
|
|
3980
5297
|
sideFaces
|
|
3981
5298
|
};
|
|
5299
|
+
const surfaceCandidates = memberIds.map((id) => this.seatById.get(id)).filter((seat) => !!seat);
|
|
5300
|
+
const distanceCandidates = surfaceCandidates.length ? surfaceCandidates : obj.outline;
|
|
5301
|
+
sec.frontDistanceWorld = Math.min(...distanceCandidates.map((point) => Math.hypot(
|
|
5302
|
+
point.x - sec.surfaceFocal.x,
|
|
5303
|
+
point.y - sec.surfaceFocal.y
|
|
5304
|
+
)));
|
|
3982
5305
|
for (const id of memberIds) this.seatSection.set(id, sec);
|
|
3983
5306
|
this.refreshSectionFill(sec);
|
|
3984
5307
|
this.refreshSectionHeat(sec);
|
|
@@ -4124,15 +5447,16 @@ var _SeatmapRenderer = class _SeatmapRenderer {
|
|
|
4124
5447
|
const sectionOverview = scale < CACHE_THRESHOLD;
|
|
4125
5448
|
if (sectionOverview) blockT = 1;
|
|
4126
5449
|
if (!this.zones.length) zoneT = 0;
|
|
4127
|
-
this.
|
|
5450
|
+
const deferPerspectiveSeatReveal = this.viewMode === "perspective" && this.glideInProgress && this.seats.length > 2500;
|
|
5451
|
+
this.seatLayer.opacity(sectionOverview || deferPerspectiveSeatReveal ? 0 : 1 - blockT);
|
|
4128
5452
|
const sx = this.stage.scaleX();
|
|
4129
|
-
const rescale = this.lodScale === 0 || Math.abs(scale - this.lodScale) / (this.lodScale || 1) > 0.
|
|
5453
|
+
const rescale = !this.glideInProgress && (this.lodScale === 0 || Math.abs(scale - this.lodScale) / (this.lodScale || 1) > 0.08);
|
|
4130
5454
|
if (rescale) this.lodScale = scale;
|
|
4131
5455
|
const focus = this.focusedSectionId;
|
|
4132
5456
|
const palette = overviewPalette(this.canvasBackground);
|
|
4133
5457
|
const sectionLabelT = clamp((blockT - 0.2) / 0.8, 0, 1);
|
|
4134
5458
|
for (const sec of this.sections) {
|
|
4135
|
-
const dim = focus && sec.id !== focus && sec.logicalId !== focus && sec.zone !== focus ? FOCUS_DIM_OPACITY : 1;
|
|
5459
|
+
const dim = this.focusDimOverlay ? 1 : focus && sec.id !== focus && sec.logicalId !== focus && sec.zone !== focus ? FOCUS_DIM_OPACITY : 1;
|
|
4136
5460
|
sec.outlinePoly.opacity(sectionOverview ? 0 : (1 - blockT) * dim);
|
|
4137
5461
|
sec.blockPoly.opacity(BLOCK_FILL_ALPHA * blockT * dim);
|
|
4138
5462
|
sec.blockPoly.stroke(palette.sectionStroke);
|
|
@@ -4144,7 +5468,7 @@ var _SeatmapRenderer = class _SeatmapRenderer {
|
|
|
4144
5468
|
);
|
|
4145
5469
|
sec.nameLabel.fill(sectionInk);
|
|
4146
5470
|
sec.subLabel.fill(sectionInk);
|
|
4147
|
-
if (rescale) this.fitSectionRungLabels(sec, sx);
|
|
5471
|
+
if (rescale && sectionLabelT > 0.01) this.fitSectionRungLabels(sec, sx);
|
|
4148
5472
|
const labelOpacity = sectionLabelT * (1 - zoneT) * dim;
|
|
4149
5473
|
sec.nameLabel.opacity(sec.nameLabelFits ? labelOpacity : 0);
|
|
4150
5474
|
sec.subLabel.opacity(0);
|
|
@@ -4156,12 +5480,14 @@ var _SeatmapRenderer = class _SeatmapRenderer {
|
|
|
4156
5480
|
if (zone.sub) zone.sub.opacity(zoneOpacity);
|
|
4157
5481
|
if (rescale) this.sizeZonePill(zone, sx);
|
|
4158
5482
|
}
|
|
4159
|
-
this.
|
|
4160
|
-
|
|
4161
|
-
|
|
4162
|
-
const
|
|
4163
|
-
|
|
4164
|
-
|
|
5483
|
+
if (!this.glideInProgress && (sectionLabelT > 0.01 || zoneOpacity > 0.01)) {
|
|
5484
|
+
this.decollideRungLabels(sx);
|
|
5485
|
+
this.dedupeLogicalSectionLabels();
|
|
5486
|
+
for (const zone of this.zones) {
|
|
5487
|
+
const opacity = zone.label.opacity();
|
|
5488
|
+
zone.back.opacity(opacity);
|
|
5489
|
+
if (zone.sub) zone.sub.opacity(opacity);
|
|
5490
|
+
}
|
|
4165
5491
|
}
|
|
4166
5492
|
this.bgLayer.batchDraw();
|
|
4167
5493
|
}
|
|
@@ -4288,13 +5614,30 @@ var _SeatmapRenderer = class _SeatmapRenderer {
|
|
|
4288
5614
|
screenToWorld(clientPoint) {
|
|
4289
5615
|
const s = this.stage.scaleX();
|
|
4290
5616
|
const iso = { x: (clientPoint.x - this.stage.x()) / s, y: (clientPoint.y - this.stage.y()) / s };
|
|
5617
|
+
if (this.viewMode === "perspective" && this.perspectiveBaseInverse) {
|
|
5618
|
+
return applyAffine(this.perspectiveBaseInverse, iso);
|
|
5619
|
+
}
|
|
4291
5620
|
return this.isoT === 0 ? iso : this.isoInverse(iso);
|
|
4292
5621
|
}
|
|
4293
5622
|
/** Section id under a container-relative screen point, or null (Slice 5 tap-to-zoom). */
|
|
4294
5623
|
sectionAt(clientPoint) {
|
|
4295
5624
|
if (!this.sections.length) return null;
|
|
4296
5625
|
const world = this.screenToWorld(clientPoint);
|
|
4297
|
-
const hit = this.sections.find((sec) =>
|
|
5626
|
+
const hit = this.sections.find((sec) => {
|
|
5627
|
+
if (this.viewMode === "perspective" && sec.perspectiveAffine) {
|
|
5628
|
+
return pointInPolygonWithHoles(
|
|
5629
|
+
world,
|
|
5630
|
+
sec.outline.map((point) => this.perspectiveLocalPoint(this.projectedSectionPoint(sec, point))),
|
|
5631
|
+
sec.holes.map((hole) => hole.map((point) => this.perspectiveLocalPoint(this.projectedSectionPoint(sec, point))))
|
|
5632
|
+
);
|
|
5633
|
+
}
|
|
5634
|
+
const offset = sec.liftWorld > 0 ? this.isoLiftLocal(sec.liftWorld) : { x: 0, y: 0 };
|
|
5635
|
+
return pointInPolygonWithHoles(
|
|
5636
|
+
{ x: world.x - offset.x, y: world.y - offset.y },
|
|
5637
|
+
sec.outline,
|
|
5638
|
+
sec.holes
|
|
5639
|
+
);
|
|
5640
|
+
});
|
|
4298
5641
|
return hit ? hit.logicalId : null;
|
|
4299
5642
|
}
|
|
4300
5643
|
/** Seat ids belonging to a section (Slice 5 section-summary card). */
|
|
@@ -4390,15 +5733,20 @@ var _SeatmapRenderer = class _SeatmapRenderer {
|
|
|
4390
5733
|
if (!seat) return;
|
|
4391
5734
|
const candidate = this.selectionFocusId === id;
|
|
4392
5735
|
const dims = this.boothDims.get(seat.rowId);
|
|
5736
|
+
const rendered = this.renderedSeatPoint(seat);
|
|
4393
5737
|
const marker = new import_Group.Group({
|
|
4394
5738
|
name: "selection-ring",
|
|
4395
|
-
x:
|
|
4396
|
-
y:
|
|
5739
|
+
x: rendered.x,
|
|
5740
|
+
y: rendered.y,
|
|
4397
5741
|
rotation: dims?.rotation ?? 0,
|
|
4398
5742
|
listening: false,
|
|
4399
5743
|
perfectDrawEnabled: false,
|
|
4400
5744
|
opacity: this.selectionFocusId && !candidate ? 0.2 : 1
|
|
4401
5745
|
});
|
|
5746
|
+
if (this.viewMode === "perspective") {
|
|
5747
|
+
const projectionScale = this.perspectiveSeatScale.get(id) ?? 1;
|
|
5748
|
+
marker.scale({ x: projectionScale, y: projectionScale });
|
|
5749
|
+
}
|
|
4402
5750
|
marker.setAttr("seatId", id);
|
|
4403
5751
|
const common = {
|
|
4404
5752
|
stroke: this.effSelection,
|
|
@@ -4477,12 +5825,22 @@ var _SeatmapRenderer = class _SeatmapRenderer {
|
|
|
4477
5825
|
* must fall through and pick.
|
|
4478
5826
|
*/
|
|
4479
5827
|
sectionBounds(id) {
|
|
4480
|
-
const
|
|
4481
|
-
|
|
4482
|
-
|
|
4483
|
-
|
|
4484
|
-
|
|
4485
|
-
|
|
5828
|
+
const bounds2 = this.sections.filter((section) => section.id === id || section.logicalId === id).map((section) => {
|
|
5829
|
+
if (this.viewMode === "perspective" && section.perspectiveAffine) {
|
|
5830
|
+
return polyBounds(section.outline.map((point) => this.projectedSectionPoint(section, point)));
|
|
5831
|
+
}
|
|
5832
|
+
const offset = section.liftWorld > 0 ? this.isoLiftLocal(section.liftWorld) : { x: 0, y: 0 };
|
|
5833
|
+
const points = section.outline.map((point) => {
|
|
5834
|
+
const local = { x: point.x + offset.x, y: point.y + offset.y };
|
|
5835
|
+
return this.isoT === 0 ? local : this.isoForward(local);
|
|
5836
|
+
});
|
|
5837
|
+
return polyBounds(points);
|
|
5838
|
+
});
|
|
5839
|
+
if (!bounds2.length) return null;
|
|
5840
|
+
const left = Math.min(...bounds2.map((box) => box.x));
|
|
5841
|
+
const top = Math.min(...bounds2.map((box) => box.y));
|
|
5842
|
+
const right = Math.max(...bounds2.map((box) => box.x + box.width));
|
|
5843
|
+
const bottom = Math.max(...bounds2.map((box) => box.y + box.height));
|
|
4486
5844
|
return { x: left, y: top, width: right - left, height: bottom - top };
|
|
4487
5845
|
}
|
|
4488
5846
|
sectionFrameScale(id) {
|
|
@@ -4535,15 +5893,16 @@ var _SeatmapRenderer = class _SeatmapRenderer {
|
|
|
4535
5893
|
* hijacking a clean tap on empty space beyond the slop.
|
|
4536
5894
|
*/
|
|
4537
5895
|
nearestSeatToScreen(screen, slopPx) {
|
|
4538
|
-
const s = this.stage.scaleX() || 1;
|
|
4539
|
-
const reachWorld = this.seatR + slopPx / s;
|
|
4540
|
-
const world = this.screenToWorld(screen);
|
|
4541
5896
|
let best = null;
|
|
4542
|
-
let bestD =
|
|
5897
|
+
let bestD = Infinity;
|
|
5898
|
+
const stageScale = this.stage.scaleX() || 1;
|
|
4543
5899
|
for (const seat of this.seats) {
|
|
4544
5900
|
if (!this.selection.has(seat.id) && !this.isSelectable(seat.id)) continue;
|
|
4545
|
-
const
|
|
4546
|
-
|
|
5901
|
+
const centre = this.worldToScreen(seat);
|
|
5902
|
+
const perspectiveScale = this.viewMode === "perspective" ? this.perspectiveSeatScale.get(seat.id) ?? 1 : 1;
|
|
5903
|
+
const reachPx = this.seatR * stageScale * perspectiveScale + slopPx;
|
|
5904
|
+
const d = Math.hypot(centre.x - screen.x, centre.y - screen.y);
|
|
5905
|
+
if (d <= reachPx && d < bestD) {
|
|
4547
5906
|
bestD = d;
|
|
4548
5907
|
best = seat.id;
|
|
4549
5908
|
}
|
|
@@ -4553,6 +5912,7 @@ var _SeatmapRenderer = class _SeatmapRenderer {
|
|
|
4553
5912
|
wireInteraction() {
|
|
4554
5913
|
this.seatLayer.on("click tap", (e) => {
|
|
4555
5914
|
if (this.moved > PAN_START_SLOP_PX) return;
|
|
5915
|
+
if (this.isGhostClick(e)) return;
|
|
4556
5916
|
const id = seatIdOf(e.target);
|
|
4557
5917
|
if (!id) return;
|
|
4558
5918
|
this.handleSeatTap(id);
|
|
@@ -4562,13 +5922,17 @@ var _SeatmapRenderer = class _SeatmapRenderer {
|
|
|
4562
5922
|
if (!id) return;
|
|
4563
5923
|
const seat = this.seatById.get(id);
|
|
4564
5924
|
if (!seat) return;
|
|
4565
|
-
this.
|
|
5925
|
+
this.hoveredId = id;
|
|
5926
|
+
this.hoverRing.position(this.renderedSeatPoint(seat));
|
|
5927
|
+
const projectionScale = this.viewMode === "perspective" ? this.perspectiveSeatScale.get(id) ?? 1 : 1;
|
|
5928
|
+
this.hoverRing.scale({ x: projectionScale, y: projectionScale });
|
|
4566
5929
|
this.hoverRing.visible(true);
|
|
4567
5930
|
this.overlayLayer.batchDraw();
|
|
4568
5931
|
this.container.style.cursor = "pointer";
|
|
4569
5932
|
this.opts.onHover?.(seat);
|
|
4570
5933
|
});
|
|
4571
5934
|
this.seatLayer.on("mouseout", () => {
|
|
5935
|
+
this.hoveredId = null;
|
|
4572
5936
|
this.hoverRing.visible(false);
|
|
4573
5937
|
this.overlayLayer.batchDraw();
|
|
4574
5938
|
this.container.style.cursor = "default";
|
|
@@ -4583,6 +5947,7 @@ var _SeatmapRenderer = class _SeatmapRenderer {
|
|
|
4583
5947
|
});
|
|
4584
5948
|
this.stage.on("click tap", (e) => {
|
|
4585
5949
|
if (this.moved > PAN_START_SLOP_PX) return;
|
|
5950
|
+
if (this.isGhostClick(e)) return;
|
|
4586
5951
|
const pointer = this.stage.getPointerPosition();
|
|
4587
5952
|
if (!pointer) return;
|
|
4588
5953
|
if (!this.cached) {
|
|
@@ -4599,11 +5964,10 @@ var _SeatmapRenderer = class _SeatmapRenderer {
|
|
|
4599
5964
|
}
|
|
4600
5965
|
}
|
|
4601
5966
|
if (this.sections.length) {
|
|
4602
|
-
const
|
|
4603
|
-
|
|
4604
|
-
|
|
4605
|
-
|
|
4606
|
-
else this.focusRegion(hit.logicalId);
|
|
5967
|
+
const sectionId = this.sectionAt(pointer);
|
|
5968
|
+
if (sectionId) {
|
|
5969
|
+
if (this.opts.onSectionTap) this.opts.onSectionTap(sectionId);
|
|
5970
|
+
else this.focusRegion(sectionId);
|
|
4607
5971
|
return;
|
|
4608
5972
|
}
|
|
4609
5973
|
}
|
|
@@ -4620,13 +5984,15 @@ var _SeatmapRenderer = class _SeatmapRenderer {
|
|
|
4620
5984
|
*/
|
|
4621
5985
|
deckFloorAt() {
|
|
4622
5986
|
if (!this.objectFloor.size) return null;
|
|
4623
|
-
const
|
|
4624
|
-
if (!
|
|
5987
|
+
const pointer = this.stage.getPointerPosition();
|
|
5988
|
+
if (!pointer) return null;
|
|
5989
|
+
const p = this.screenToWorld(pointer);
|
|
4625
5990
|
let best = null;
|
|
4626
5991
|
let bestD = Infinity;
|
|
4627
5992
|
for (const s of this.seats) {
|
|
4628
|
-
const
|
|
4629
|
-
const
|
|
5993
|
+
const rendered = this.renderedSeatPoint(s);
|
|
5994
|
+
const dx = rendered.x - p.x;
|
|
5995
|
+
const dy = rendered.y - p.y;
|
|
4630
5996
|
const d = dx * dx + dy * dy;
|
|
4631
5997
|
if (d < bestD) {
|
|
4632
5998
|
bestD = d;
|
|
@@ -4713,9 +6079,11 @@ var _SeatmapRenderer = class _SeatmapRenderer {
|
|
|
4713
6079
|
}
|
|
4714
6080
|
const start = performance.now();
|
|
4715
6081
|
const duration = Math.max(180, Math.min(1200, opts?.durationMs ?? CAMERA_GLIDE_MS));
|
|
6082
|
+
this.glideInProgress = true;
|
|
4716
6083
|
const step = (now) => {
|
|
4717
6084
|
if (this.destroyed) {
|
|
4718
6085
|
this.glideRaf = 0;
|
|
6086
|
+
this.glideInProgress = false;
|
|
4719
6087
|
return;
|
|
4720
6088
|
}
|
|
4721
6089
|
const raw = Math.min(1, (now - start) / duration);
|
|
@@ -4723,6 +6091,7 @@ var _SeatmapRenderer = class _SeatmapRenderer {
|
|
|
4723
6091
|
const sc = fromScale + (toScale - fromScale) * e;
|
|
4724
6092
|
this.stage.scale({ x: sc, y: sc });
|
|
4725
6093
|
this.stage.position({ x: fromX + (toX - fromX) * e, y: fromY + (toY - fromY) * e });
|
|
6094
|
+
this.updateSeatGroupVisibility();
|
|
4726
6095
|
this.updateLOD();
|
|
4727
6096
|
this.scheduleViewChange();
|
|
4728
6097
|
this.stage.batchDraw();
|
|
@@ -4730,6 +6099,7 @@ var _SeatmapRenderer = class _SeatmapRenderer {
|
|
|
4730
6099
|
this.glideRaf = requestAnimationFrame(step);
|
|
4731
6100
|
} else {
|
|
4732
6101
|
this.glideRaf = 0;
|
|
6102
|
+
this.glideInProgress = false;
|
|
4733
6103
|
this.afterViewChange();
|
|
4734
6104
|
}
|
|
4735
6105
|
};
|
|
@@ -4741,6 +6111,7 @@ var _SeatmapRenderer = class _SeatmapRenderer {
|
|
|
4741
6111
|
cancelAnimationFrame(this.glideRaf);
|
|
4742
6112
|
this.glideRaf = 0;
|
|
4743
6113
|
}
|
|
6114
|
+
this.glideInProgress = false;
|
|
4744
6115
|
}
|
|
4745
6116
|
/** Current LOD rung derived from effective zoom — drives the ZONES/SECTIONS/SEATS pill. */
|
|
4746
6117
|
getRung() {
|
|
@@ -4757,11 +6128,14 @@ var _SeatmapRenderer = class _SeatmapRenderer {
|
|
|
4757
6128
|
const labels = this.seats.map((seat) => {
|
|
4758
6129
|
const shape = this.circleById.get(seat.id);
|
|
4759
6130
|
const label = this.boothLabelById.get(seat.id) ?? this.seatLabelById.get(seat.id);
|
|
6131
|
+
const localPerspectiveScale = this.viewMode === "perspective" ? this.perspectiveSeatScale.get(seat.id) ?? 1 : 1;
|
|
6132
|
+
const seatEffectiveScale = (this.viewMode === "perspective" ? stageScale : effectiveScale) * localPerspectiveScale;
|
|
4760
6133
|
const authoredFontSize = seat.kind === "booth" ? BOOTH_LABEL_FONT_SIZE : SEAT_LABEL_FONT_SIZE * this.seatLabelScale(seat);
|
|
4761
|
-
const
|
|
6134
|
+
const labelEffectiveScale = this.viewMode === "perspective" && seat.kind !== "booth" ? effectiveScale * localPerspectiveScale : seatEffectiveScale;
|
|
6135
|
+
const renderedFontPx = rounded((label?.fontSize() ?? authoredFontSize) * labelEffectiveScale);
|
|
4762
6136
|
const screen = this.worldToScreen(seat);
|
|
4763
6137
|
const outside = screen.x < 0 || screen.x > viewport.width || screen.y < 0 || screen.y > viewport.height;
|
|
4764
|
-
const opacity = shape?.
|
|
6138
|
+
const opacity = this.focusedSeatOpacity(seat.id, shape?.getAbsoluteOpacity() ?? 0);
|
|
4765
6139
|
const section = this.seatSection.get(seat.id);
|
|
4766
6140
|
const visible = Boolean(label?.isVisible()) && opacity >= 0.5 && !outside;
|
|
4767
6141
|
let hiddenReason;
|
|
@@ -4772,17 +6146,21 @@ var _SeatmapRenderer = class _SeatmapRenderer {
|
|
|
4772
6146
|
else if (!label) hiddenReason = "clutter-or-fit";
|
|
4773
6147
|
else hiddenReason = "renderer-hidden";
|
|
4774
6148
|
}
|
|
4775
|
-
const labelWidth = label ? label.width() * stageScale : 0;
|
|
4776
|
-
const labelHeight = label ? label.height() * effectiveScale : 0;
|
|
4777
|
-
const directWidthPx = shape instanceof import_Rect.Rect ? shape.width() * stageScale : this.seatR * 2 *
|
|
4778
|
-
const directHeightPx = shape instanceof import_Rect.Rect ? shape.height() * stageScale : this.seatR * 2 *
|
|
4779
|
-
const assistedDiameterPx = 2 * (this.seatR *
|
|
6149
|
+
const labelWidth = label ? label.width() * stageScale * localPerspectiveScale : 0;
|
|
6150
|
+
const labelHeight = label ? label.height() * (this.viewMode === "perspective" && seat.kind === "booth" ? stageScale : effectiveScale) * localPerspectiveScale : 0;
|
|
6151
|
+
const directWidthPx = shape instanceof import_Rect.Rect ? shape.width() * stageScale * localPerspectiveScale : this.seatR * 2 * seatEffectiveScale;
|
|
6152
|
+
const directHeightPx = shape instanceof import_Rect.Rect ? shape.height() * (this.viewMode === "perspective" ? stageScale : effectiveScale) * localPerspectiveScale : this.seatR * 2 * seatEffectiveScale;
|
|
6153
|
+
const assistedDiameterPx = 2 * (this.seatR * seatEffectiveScale + SEAT_TAP_SLOP_PX);
|
|
4780
6154
|
const fill = shape?.fill();
|
|
4781
6155
|
const ink = label?.fill();
|
|
6156
|
+
const accessGlyph = this.accessGlyphById.get(seat.id);
|
|
6157
|
+
const accessGlyphScale = accessGlyph?.getAbsoluteScale();
|
|
4782
6158
|
return {
|
|
4783
6159
|
seatId: seat.id,
|
|
4784
6160
|
label: seat.label,
|
|
4785
6161
|
kind: seat.kind === "booth" ? "booth" : "seat",
|
|
6162
|
+
markerShape: seat.kind === "booth" ? "booth" : seat.wheelchairSpaceType === "no-seat" ? "square" : "circle",
|
|
6163
|
+
...seat.wheelchairSpaceType ? { wheelchairSpaceType: seat.wheelchairSpaceType } : {},
|
|
4786
6164
|
categoryKey: seat.categoryKey,
|
|
4787
6165
|
...section ? { sectionId: section.id } : {},
|
|
4788
6166
|
...section?.zone ? { zoneId: section.zone } : {},
|
|
@@ -4793,8 +6171,15 @@ var _SeatmapRenderer = class _SeatmapRenderer {
|
|
|
4793
6171
|
fill: typeof fill === "string" ? fill : "",
|
|
4794
6172
|
ink: typeof ink === "string" ? ink : this.theme.seatLabelColor ?? DEF_SEAT_LABEL,
|
|
4795
6173
|
opacity: rounded(opacity),
|
|
6174
|
+
...accessGlyph ? {
|
|
6175
|
+
accessibilityMarker: {
|
|
6176
|
+
glyphVisible: accessGlyph.isVisible(),
|
|
6177
|
+
glyphWidthPx: rounded(ACCESS_GLYPH_VIEWBOX * Math.abs(accessGlyphScale?.x ?? 0)),
|
|
6178
|
+
emphasizedByFilter: this.accessGlyphFilterEmphasized(seat.id)
|
|
6179
|
+
}
|
|
6180
|
+
} : {},
|
|
4796
6181
|
pointerTarget: {
|
|
4797
|
-
active: !this.cached && this.isSelectable(seat.id),
|
|
6182
|
+
active: !this.cached && !this.seatViewportCulled(seat.id) && Boolean(shape?.isVisible()) && Boolean(shape?.isListening()) && this.isSelectable(seat.id),
|
|
4798
6183
|
directWidthPx: rounded(directWidthPx),
|
|
4799
6184
|
directHeightPx: rounded(directHeightPx),
|
|
4800
6185
|
effectiveMinimumPx: rounded(Math.max(
|
|
@@ -4822,7 +6207,11 @@ var _SeatmapRenderer = class _SeatmapRenderer {
|
|
|
4822
6207
|
node.height(),
|
|
4823
6208
|
node.rotation()
|
|
4824
6209
|
);
|
|
4825
|
-
const
|
|
6210
|
+
const lift = section && section.liftWorld > 0 ? this.isoLiftLocal(section.liftWorld) : { x: 0, y: 0 };
|
|
6211
|
+
const screenBounds = pointsBounds(worldCorners.map((corner) => this.worldToScreen({
|
|
6212
|
+
x: corner.x + lift.x,
|
|
6213
|
+
y: corner.y + lift.y
|
|
6214
|
+
})));
|
|
4826
6215
|
const opacity = rounded(node.opacity());
|
|
4827
6216
|
const ink = node.fill();
|
|
4828
6217
|
const outside = screenBounds.x + screenBounds.width < 0 || screenBounds.x > viewport.width || screenBounds.y + screenBounds.height < 0 || screenBounds.y > viewport.height;
|
|
@@ -4961,6 +6350,15 @@ var _SeatmapRenderer = class _SeatmapRenderer {
|
|
|
4961
6350
|
const visibleSectionShells = this.sections.filter((section) => section.blockPoly.opacity() > 0.05);
|
|
4962
6351
|
return {
|
|
4963
6352
|
viewport,
|
|
6353
|
+
projection: this.viewMode,
|
|
6354
|
+
...this.viewMode === "perspective" ? {
|
|
6355
|
+
perspective: {
|
|
6356
|
+
model: "pinhole-exact-seat-anchors",
|
|
6357
|
+
sectionSurfaceModel: "tangent-plane",
|
|
6358
|
+
exactSeatAnchorCount: this.perspectiveSeatProjected.size,
|
|
6359
|
+
depthSorted: true
|
|
6360
|
+
}
|
|
6361
|
+
} : {},
|
|
4964
6362
|
canvasBackground: this.canvasBackground,
|
|
4965
6363
|
effectiveScale: rounded(effectiveScale),
|
|
4966
6364
|
rung: this.getRung(),
|
|
@@ -5015,10 +6413,10 @@ var _SeatmapRenderer = class _SeatmapRenderer {
|
|
|
5015
6413
|
let cy = viewCentre.y;
|
|
5016
6414
|
if (rung === "sections" && this.sections.length > 0) {
|
|
5017
6415
|
const sectionCentres = this.sections.map((section) => {
|
|
5018
|
-
const
|
|
6416
|
+
const bounds2 = polyBounds(section.outline);
|
|
5019
6417
|
return {
|
|
5020
|
-
x:
|
|
5021
|
-
y:
|
|
6418
|
+
x: bounds2.x + bounds2.width / 2,
|
|
6419
|
+
y: bounds2.y + bounds2.height / 2
|
|
5022
6420
|
};
|
|
5023
6421
|
});
|
|
5024
6422
|
const halfWidth = w / (target * 2);
|
|
@@ -5085,6 +6483,7 @@ var _SeatmapRenderer = class _SeatmapRenderer {
|
|
|
5085
6483
|
}
|
|
5086
6484
|
/** Recompute LOD (cache/labels) after any pan/zoom settles. */
|
|
5087
6485
|
afterViewChange() {
|
|
6486
|
+
this.updateSeatGroupVisibility();
|
|
5088
6487
|
this.updateLOD();
|
|
5089
6488
|
this.updateFreeTextVisibility();
|
|
5090
6489
|
this.updateLabels();
|
|
@@ -5109,10 +6508,15 @@ var _SeatmapRenderer = class _SeatmapRenderer {
|
|
|
5109
6508
|
this.paintGAStateForView();
|
|
5110
6509
|
this.updateAccessGlyphs(scale);
|
|
5111
6510
|
const shouldCache = scale < CACHE_THRESHOLD;
|
|
6511
|
+
const suppressPerspectiveSeatCache = this.viewMode === "perspective" && this.hasSections && this.seats.length > 2500 && shouldCache;
|
|
6512
|
+
if (suppressPerspectiveSeatCache) {
|
|
6513
|
+
this.seatLayer.listening(false);
|
|
6514
|
+
return;
|
|
6515
|
+
}
|
|
5112
6516
|
if (shouldCache && !this.cached) {
|
|
5113
6517
|
this.cacheSeatLayer();
|
|
5114
|
-
} else if (!shouldCache && this.cached) {
|
|
5115
|
-
this.seatLayer.clearCache();
|
|
6518
|
+
} else if (!shouldCache && (this.cached || !this.seatLayer.listening())) {
|
|
6519
|
+
if (this.cached) this.seatLayer.clearCache();
|
|
5116
6520
|
this.seatLayer.listening(true);
|
|
5117
6521
|
this.cached = false;
|
|
5118
6522
|
this.seatLayer.batchDraw();
|
|
@@ -5121,6 +6525,7 @@ var _SeatmapRenderer = class _SeatmapRenderer {
|
|
|
5121
6525
|
/** Rebuild the seat-layer bitmap synchronously (no paint). Shared by the
|
|
5122
6526
|
* debounced cacheSeatLayer() and the synchronous forceDraw() catch-up. */
|
|
5123
6527
|
rebuildSeatCache() {
|
|
6528
|
+
this.updateSeatGroupVisibility();
|
|
5124
6529
|
const pr = clamp(this.stage.scaleX() * this.dpr, 0.15, 2);
|
|
5125
6530
|
this.seatLayer.clearCache();
|
|
5126
6531
|
this.seatLayer.cache({ pixelRatio: pr });
|
|
@@ -5172,31 +6577,31 @@ var _SeatmapRenderer = class _SeatmapRenderer {
|
|
|
5172
6577
|
for (const [id, label] of this.boothLabelById) {
|
|
5173
6578
|
const shape = this.circleById.get(id);
|
|
5174
6579
|
label.visible(
|
|
5175
|
-
isBookableLabelLegibleAtScale(label.fontSize(), effectiveScale) && (shape?.
|
|
6580
|
+
isBookableLabelLegibleAtScale(label.fontSize(), effectiveScale) && Boolean(shape?.isVisible()) && this.focusedSeatOpacity(id, shape?.getAbsoluteOpacity() ?? 1) >= 0.5
|
|
5176
6581
|
);
|
|
5177
6582
|
}
|
|
5178
6583
|
this.labelGroup.destroyChildren();
|
|
6584
|
+
this.labelLiftGroups.clear();
|
|
5179
6585
|
this.seatLabelById.clear();
|
|
5180
6586
|
if (!show) {
|
|
5181
6587
|
this.overlayLayer.batchDraw();
|
|
5182
6588
|
return;
|
|
5183
6589
|
}
|
|
5184
|
-
const s = this.stage.scaleX();
|
|
5185
|
-
const x0 = -this.stage.x() / s;
|
|
5186
|
-
const y0 = -this.stage.y() / s;
|
|
5187
|
-
const x1 = (this.stage.width() - this.stage.x()) / s;
|
|
5188
|
-
const y1 = (this.stage.height() - this.stage.y()) / s;
|
|
5189
6590
|
let count = 0;
|
|
5190
|
-
for (const seat of this.
|
|
6591
|
+
for (const seat of this.visibleSeatCandidates()) {
|
|
5191
6592
|
if (seat.kind === "booth") continue;
|
|
5192
|
-
|
|
5193
|
-
if (
|
|
6593
|
+
const screen = this.worldToScreen(seat);
|
|
6594
|
+
if (screen.x < -20 || screen.x > this.stage.width() + 20 || screen.y < -20 || screen.y > this.stage.height() + 20) continue;
|
|
6595
|
+
if (this.accessGlyphById.has(seat.id) && this.accessGlyphShouldShow(seat.id)) continue;
|
|
5194
6596
|
const shape = this.circleById.get(seat.id);
|
|
5195
|
-
if (
|
|
6597
|
+
if (!shape?.isVisible() || this.focusedSeatOpacity(seat.id, shape.getAbsoluteOpacity()) < 0.5) continue;
|
|
5196
6598
|
const status = this.statusById.get(seat.id) ?? "free";
|
|
6599
|
+
const labelPoint = this.renderedSeatPoint(seat);
|
|
6600
|
+
const perspectiveScale = this.viewMode === "perspective" ? this.perspectiveSeatScale.get(seat.id) ?? 1 : 1;
|
|
5197
6601
|
const unavailable = status === "booked" || status === "held" && !this.ownedHold.has(seat.id) && !this.opts.manageMode;
|
|
5198
6602
|
if (unavailable) {
|
|
5199
|
-
const cue = new import_Group.Group({ x:
|
|
6603
|
+
const cue = new import_Group.Group({ x: labelPoint.x, y: labelPoint.y, listening: false });
|
|
6604
|
+
cue.scale({ x: perspectiveScale, y: perspectiveScale });
|
|
5200
6605
|
if (status === "held") {
|
|
5201
6606
|
cue.add(new import_Rect.Rect({
|
|
5202
6607
|
x: -4.2,
|
|
@@ -5225,14 +6630,14 @@ var _SeatmapRenderer = class _SeatmapRenderer {
|
|
|
5225
6630
|
listening: false
|
|
5226
6631
|
}));
|
|
5227
6632
|
}
|
|
5228
|
-
this.
|
|
6633
|
+
this.seatLabelContainer(seat.id).add(cue);
|
|
5229
6634
|
if (++count >= MAX_LABELS) break;
|
|
5230
6635
|
continue;
|
|
5231
6636
|
}
|
|
5232
6637
|
const authoredFontSize = SEAT_LABEL_FONT_SIZE * this.seatLabelScale(seat);
|
|
5233
6638
|
const t2 = new import_Text.Text({
|
|
5234
|
-
x:
|
|
5235
|
-
y:
|
|
6639
|
+
x: labelPoint.x,
|
|
6640
|
+
y: labelPoint.y,
|
|
5236
6641
|
text: bookableMarkerLabel(seat.displayLabel ?? seat.label),
|
|
5237
6642
|
fontSize: authoredFontSize,
|
|
5238
6643
|
fontStyle: "600",
|
|
@@ -5253,11 +6658,12 @@ var _SeatmapRenderer = class _SeatmapRenderer {
|
|
|
5253
6658
|
}
|
|
5254
6659
|
t2.offsetX(t2.width() / 2);
|
|
5255
6660
|
t2.offsetY(t2.height() / 2);
|
|
5256
|
-
|
|
6661
|
+
t2.scale({ x: perspectiveScale, y: perspectiveScale });
|
|
6662
|
+
this.seatLabelContainer(seat.id).add(t2);
|
|
5257
6663
|
this.seatLabelById.set(seat.id, t2);
|
|
5258
6664
|
if (++count >= MAX_LABELS) break;
|
|
5259
6665
|
}
|
|
5260
|
-
if (this.isoT > 0) this.applyUprightLabels();
|
|
6666
|
+
if (this.isoT > 0 && this.viewMode !== "perspective") this.applyUprightLabels();
|
|
5261
6667
|
this.overlayLayer.batchDraw();
|
|
5262
6668
|
}
|
|
5263
6669
|
handleResize() {
|
|
@@ -5323,6 +6729,7 @@ function strandedSingles(seats, statusOf, selectedIds) {
|
|
|
5323
6729
|
|
|
5324
6730
|
// src/core/bestAvailable.ts
|
|
5325
6731
|
function seatIndex2(seat, fallback) {
|
|
6732
|
+
if (Number.isInteger(seat.logicalSeatIndex)) return seat.logicalSeatIndex;
|
|
5326
6733
|
const i = seat.id.lastIndexOf(":");
|
|
5327
6734
|
if (i < 0) return fallback;
|
|
5328
6735
|
const n = Number(seat.id.slice(i + 1));
|
|
@@ -5342,21 +6749,25 @@ function centroid(seats) {
|
|
|
5342
6749
|
}
|
|
5343
6750
|
return { x: x / seats.length, y: y / seats.length };
|
|
5344
6751
|
}
|
|
6752
|
+
function candidateFocal(seats, fallback) {
|
|
6753
|
+
return seats.find((seat) => seat.focalPoint)?.focalPoint ?? fallback;
|
|
6754
|
+
}
|
|
5345
6755
|
function isPremium(seat) {
|
|
5346
6756
|
return seat.commercial?.premium === true;
|
|
5347
6757
|
}
|
|
5348
6758
|
function pickBestAvailable(seats, available, opts) {
|
|
5349
6759
|
const qty = Math.floor(opts.qty);
|
|
5350
6760
|
if (!Number.isFinite(qty) || qty <= 0) return { labels: [], reason: "sold_out" };
|
|
5351
|
-
const { categoryKey, focal, preferPremium } = opts;
|
|
6761
|
+
const { categoryKey, zoneId, focal, preferPremium } = opts;
|
|
5352
6762
|
const rows = /* @__PURE__ */ new Map();
|
|
5353
6763
|
const eligibleAll = [];
|
|
5354
6764
|
seats.forEach((seat, i) => {
|
|
5355
|
-
const elig = available.has(seat.label) && (!categoryKey || seat.categoryKey === categoryKey);
|
|
5356
|
-
|
|
6765
|
+
const elig = available.has(seat.label) && (!categoryKey || seat.categoryKey === categoryKey) && (!zoneId || seat.zoneId === zoneId);
|
|
6766
|
+
const rowId = seat.logicalRowId ?? seat.rowId;
|
|
6767
|
+
let arr = rows.get(rowId);
|
|
5357
6768
|
if (!arr) {
|
|
5358
6769
|
arr = [];
|
|
5359
|
-
rows.set(
|
|
6770
|
+
rows.set(rowId, arr);
|
|
5360
6771
|
}
|
|
5361
6772
|
arr.push({ seat, index: seatIndex2(seat, i), elig });
|
|
5362
6773
|
if (elig) eligibleAll.push(seat);
|
|
@@ -5381,7 +6792,7 @@ function pickBestAvailable(seats, available, opts) {
|
|
|
5381
6792
|
continue;
|
|
5382
6793
|
}
|
|
5383
6794
|
let j = i;
|
|
5384
|
-
while (j < slots.length && slots[j].elig) j++;
|
|
6795
|
+
while (j < slots.length && slots[j].elig && (j === i || slots[j].index === slots[j - 1].index + 1)) j++;
|
|
5385
6796
|
const segLen = j - i;
|
|
5386
6797
|
for (let p = 0; p + qty <= segLen; p++) {
|
|
5387
6798
|
const leftRem = p;
|
|
@@ -5395,7 +6806,7 @@ function pickBestAvailable(seats, available, opts) {
|
|
|
5395
6806
|
startIndex: slots[i + p].index,
|
|
5396
6807
|
orphan,
|
|
5397
6808
|
nonPremium: preferPremium ? runSeats.reduce((n, s) => n + (isPremium(s) ? 0 : 1), 0) : 0,
|
|
5398
|
-
d2: dist2(centroid(runSeats), focal)
|
|
6809
|
+
d2: dist2(centroid(runSeats), candidateFocal(runSeats, focal))
|
|
5399
6810
|
};
|
|
5400
6811
|
if (better(c)) best = c;
|
|
5401
6812
|
}
|
|
@@ -5404,20 +6815,71 @@ function pickBestAvailable(seats, available, opts) {
|
|
|
5404
6815
|
}
|
|
5405
6816
|
if (best) return { labels: best.labels };
|
|
5406
6817
|
if (eligibleAll.length < qty) return { labels: [], reason: "not_enough_together" };
|
|
5407
|
-
const ranked = eligibleAll.map((seat, i) => ({ seat, i, d2: dist2(seat, focal) })).sort((a, b) => {
|
|
6818
|
+
const ranked = eligibleAll.map((seat, i) => ({ seat, i, d2: dist2(seat, seat.focalPoint ?? focal) })).sort((a, b) => {
|
|
5408
6819
|
if (preferPremium) {
|
|
5409
6820
|
const pa = isPremium(a.seat) ? 0 : 1;
|
|
5410
6821
|
const pb = isPremium(b.seat) ? 0 : 1;
|
|
5411
6822
|
if (pa !== pb) return pa - pb;
|
|
5412
6823
|
}
|
|
5413
6824
|
if (a.d2 !== b.d2) return a.d2 - b.d2;
|
|
5414
|
-
const r = a.seat.rowId.localeCompare(b.seat.rowId);
|
|
6825
|
+
const r = (a.seat.logicalRowId ?? a.seat.rowId).localeCompare(b.seat.logicalRowId ?? b.seat.rowId);
|
|
5415
6826
|
if (r !== 0) return r;
|
|
5416
6827
|
return seatIndex2(a.seat, a.i) - seatIndex2(b.seat, b.i);
|
|
5417
6828
|
});
|
|
5418
6829
|
return { labels: ranked.slice(0, qty).map((r) => r.seat.label) };
|
|
5419
6830
|
}
|
|
5420
6831
|
|
|
6832
|
+
// src/core/tableInventory.ts
|
|
6833
|
+
var GroupedTableProjectionError = class extends Error {
|
|
6834
|
+
constructor(objectId, message) {
|
|
6835
|
+
super(message);
|
|
6836
|
+
this.objectId = objectId;
|
|
6837
|
+
this.name = "GroupedTableProjectionError";
|
|
6838
|
+
}
|
|
6839
|
+
};
|
|
6840
|
+
function bounds(table) {
|
|
6841
|
+
const min = table.minOccupancy;
|
|
6842
|
+
const max = table.maxOccupancy;
|
|
6843
|
+
if (!Number.isInteger(min) || !Number.isInteger(max) || min < 1 || max < min || max > table.seatCount) {
|
|
6844
|
+
throw new GroupedTableProjectionError(
|
|
6845
|
+
table.id,
|
|
6846
|
+
`Table "${table.label}" has invalid variable-occupancy bounds`
|
|
6847
|
+
);
|
|
6848
|
+
}
|
|
6849
|
+
return { min, max };
|
|
6850
|
+
}
|
|
6851
|
+
function groupedTableInventory(doc, modelVersion) {
|
|
6852
|
+
if (modelVersion !== 2) return [];
|
|
6853
|
+
const units = [];
|
|
6854
|
+
for (const object of allObjects(doc)) {
|
|
6855
|
+
if (object.type !== "table" || !object.bookAsWhole && !object.variableOccupancy) continue;
|
|
6856
|
+
if (object.bookAsWhole && object.variableOccupancy) {
|
|
6857
|
+
throw new GroupedTableProjectionError(
|
|
6858
|
+
object.id,
|
|
6859
|
+
`Table "${object.label}" cannot be whole-table and variable-occupancy inventory`
|
|
6860
|
+
);
|
|
6861
|
+
}
|
|
6862
|
+
if (!Number.isInteger(object.seatCount) || object.seatCount < 1 || !object.label.trim()) {
|
|
6863
|
+
throw new GroupedTableProjectionError(object.id, "Grouped tables require a label and at least one chair");
|
|
6864
|
+
}
|
|
6865
|
+
const mode = object.variableOccupancy ? "variable" : "whole";
|
|
6866
|
+
const occupancy = mode === "variable" ? bounds(object) : { min: object.seatCount, max: object.seatCount };
|
|
6867
|
+
units.push({
|
|
6868
|
+
objectId: object.id,
|
|
6869
|
+
label: object.label,
|
|
6870
|
+
...object.displayLabel ? { displayLabel: object.displayLabel } : {},
|
|
6871
|
+
...object.displayType ? { displayType: object.displayType } : {},
|
|
6872
|
+
categoryKey: object.categoryKey,
|
|
6873
|
+
mode,
|
|
6874
|
+
capacity: object.seatCount,
|
|
6875
|
+
minOccupancy: occupancy.min,
|
|
6876
|
+
maxOccupancy: occupancy.max,
|
|
6877
|
+
chairs: expandTable(object)
|
|
6878
|
+
});
|
|
6879
|
+
}
|
|
6880
|
+
return units;
|
|
6881
|
+
}
|
|
6882
|
+
|
|
5421
6883
|
// src/picker/PickerController.ts
|
|
5422
6884
|
var DEFAULT_MAX_SELECTION = 10;
|
|
5423
6885
|
var MAX_BACKOFF_MS = 15e3;
|
|
@@ -5449,6 +6911,14 @@ var PickerController = class {
|
|
|
5449
6911
|
/** id → buyer-facing spatial metadata used by every tooltip/confirm surface. */
|
|
5450
6912
|
this.seatContext = /* @__PURE__ */ new Map();
|
|
5451
6913
|
this.allIds = [];
|
|
6914
|
+
/** Model-2 tables are one booking unit whose chairs remain renderer geometry. */
|
|
6915
|
+
this.groupedTablesByObject = /* @__PURE__ */ new Map();
|
|
6916
|
+
this.groupedTablesByLabel = /* @__PURE__ */ new Map();
|
|
6917
|
+
this.groupedTableBySeatId = /* @__PURE__ */ new Map();
|
|
6918
|
+
this.tableQuantities = /* @__PURE__ */ new Map();
|
|
6919
|
+
/** Variable quantity is a buyer contract, never an inferred default. */
|
|
6920
|
+
this.confirmedVariableTables = /* @__PURE__ */ new Set();
|
|
6921
|
+
this.groupedSelectionOverhead = 0;
|
|
5452
6922
|
// realtime socket
|
|
5453
6923
|
this.ws = null;
|
|
5454
6924
|
this.reconnectTimer = null;
|
|
@@ -5510,6 +6980,20 @@ var PickerController = class {
|
|
|
5510
6980
|
idForLabel(label) {
|
|
5511
6981
|
return this.labelToId.get(label);
|
|
5512
6982
|
}
|
|
6983
|
+
idsForLabel(label) {
|
|
6984
|
+
const table = this.groupedTablesByLabel.get(label);
|
|
6985
|
+
if (table) return table.chairs.map((chair) => chair.id);
|
|
6986
|
+
const id = this.labelToId.get(label);
|
|
6987
|
+
return id ? [id] : [];
|
|
6988
|
+
}
|
|
6989
|
+
idsForLabels(labels) {
|
|
6990
|
+
return [...new Set([...labels].flatMap((label) => this.idsForLabel(label)))];
|
|
6991
|
+
}
|
|
6992
|
+
/** Resolve a physical chair id (or table inventory label) to its table unit. */
|
|
6993
|
+
tableSelection(seatIdOrLabel) {
|
|
6994
|
+
const group = this.groupedTableBySeatId.get(seatIdOrLabel) ?? this.groupedTablesByLabel.get(seatIdOrLabel);
|
|
6995
|
+
return group ? this.toTableSeat(group) : null;
|
|
6996
|
+
}
|
|
5513
6997
|
/** Fetch chart, build label maps, mount the renderer, seed statuses, go live. */
|
|
5514
6998
|
async render(host) {
|
|
5515
6999
|
if (this.renderer) return null;
|
|
@@ -5523,6 +7007,29 @@ var PickerController = class {
|
|
|
5523
7007
|
}
|
|
5524
7008
|
if (this.closed) return null;
|
|
5525
7009
|
this._doc = res.doc;
|
|
7010
|
+
try {
|
|
7011
|
+
const groups = groupedTableInventory(
|
|
7012
|
+
res.doc,
|
|
7013
|
+
res.event.inventoryModelVersion === 2 ? 2 : 1
|
|
7014
|
+
);
|
|
7015
|
+
this.groupedTablesByObject = new Map(groups.map((group) => [group.objectId, group]));
|
|
7016
|
+
this.groupedTablesByLabel = new Map(groups.map((group) => [group.label, group]));
|
|
7017
|
+
this.groupedTableBySeatId = new Map(
|
|
7018
|
+
groups.flatMap((group) => group.chairs.map((chair) => [chair.id, group]))
|
|
7019
|
+
);
|
|
7020
|
+
this.tableQuantities = new Map(groups.map((group) => [
|
|
7021
|
+
group.label,
|
|
7022
|
+
group.mode === "whole" ? group.capacity : group.minOccupancy
|
|
7023
|
+
]));
|
|
7024
|
+
this.confirmedVariableTables = /* @__PURE__ */ new Set();
|
|
7025
|
+
this.groupedSelectionOverhead = groups.reduce(
|
|
7026
|
+
(sum, group) => sum + Math.max(0, group.chairs.length - 1),
|
|
7027
|
+
0
|
|
7028
|
+
);
|
|
7029
|
+
} catch (err) {
|
|
7030
|
+
this.emitError(err);
|
|
7031
|
+
return null;
|
|
7032
|
+
}
|
|
5526
7033
|
this.labelToId = /* @__PURE__ */ new Map();
|
|
5527
7034
|
this.labelToSeat = /* @__PURE__ */ new Map();
|
|
5528
7035
|
this.seatById = /* @__PURE__ */ new Map();
|
|
@@ -5540,32 +7047,43 @@ var PickerController = class {
|
|
|
5540
7047
|
this.allIds.push(s.id);
|
|
5541
7048
|
const source = chartObjects.get(s.rowId);
|
|
5542
7049
|
const sourceLabel = source && "label" in source && typeof source.label === "string" ? source.label : void 0;
|
|
5543
|
-
const
|
|
5544
|
-
const
|
|
5545
|
-
const
|
|
7050
|
+
const logicalRow = source?.type === "row" ? source.segmentedRow : void 0;
|
|
7051
|
+
const sourceDisplayLabel = logicalRow?.displayLabel ?? (source && "displayLabel" in source && typeof source.displayLabel === "string" && source.displayLabel ? source.displayLabel : sourceLabel);
|
|
7052
|
+
const groupedTable = this.groupedTablesByObject.get(s.rowId);
|
|
7053
|
+
const objectType = source?.type === "table" ? "table" : source?.type === "booth" ? "booth" : "seat";
|
|
7054
|
+
const rowLabel = sourceDisplayLabel;
|
|
7055
|
+
const rowType = logicalRow?.displayType?.trim() || (source && "displayType" in source && typeof source.displayType === "string" && source.displayType.trim() ? source.displayType.trim() : void 0);
|
|
5546
7056
|
const visibleSeatLabel = s.displayLabel ?? s.label;
|
|
5547
7057
|
const labelParts = visibleSeatLabel.split("-");
|
|
5548
|
-
const seatNumber = sourceDisplayLabel && visibleSeatLabel.startsWith(`${sourceDisplayLabel}-`) ? visibleSeatLabel.slice(sourceDisplayLabel.length + 1) :
|
|
7058
|
+
const seatNumber = groupedTable ? void 0 : s.kind === "booth" ? void 0 : sourceDisplayLabel && visibleSeatLabel.startsWith(`${sourceDisplayLabel}-`) ? visibleSeatLabel.slice(sourceDisplayLabel.length + 1) : labelParts[labelParts.length - 1] ?? visibleSeatLabel;
|
|
5549
7059
|
this.seatContext.set(s.id, {
|
|
7060
|
+
objectId: s.rowId,
|
|
7061
|
+
objectType,
|
|
5550
7062
|
sectionLabel: sectionLabels.get(membership.objectToSection.get(s.rowId) ?? ""),
|
|
5551
7063
|
rowLabel,
|
|
5552
7064
|
seatNumber,
|
|
5553
|
-
rowType
|
|
7065
|
+
...rowType ? { displayType: rowType, rowType } : {}
|
|
5554
7066
|
});
|
|
5555
7067
|
}
|
|
7068
|
+
for (const group of this.groupedTablesByLabel.values()) {
|
|
7069
|
+
const representative = group.chairs[0];
|
|
7070
|
+
if (!representative) continue;
|
|
7071
|
+
this.labelToId.set(group.label, representative.id);
|
|
7072
|
+
this.labelToSeat.set(group.label, representative);
|
|
7073
|
+
}
|
|
5556
7074
|
const currency = res.event.currency ?? this.opts.currency;
|
|
5557
7075
|
this.currency = currency ?? "USD";
|
|
5558
7076
|
const renderer = createRenderer(host, {
|
|
5559
|
-
|
|
7077
|
+
// Group chairs are selected together for paint, but count as one logical
|
|
7078
|
+
// inventory line. The controller enforces the guest-weighted cap.
|
|
7079
|
+
maxSelection: this.rendererSelectionCap(),
|
|
5560
7080
|
confirmSelection: this.opts.confirmSelection,
|
|
5561
7081
|
currency,
|
|
5562
7082
|
onSelect: (seat) => {
|
|
5563
|
-
this.
|
|
5564
|
-
this.emitSelectionChange();
|
|
7083
|
+
this.handleRendererSelect(seat);
|
|
5565
7084
|
},
|
|
5566
7085
|
onDeselect: (seat) => {
|
|
5567
|
-
this.
|
|
5568
|
-
this.emitSelectionChange();
|
|
7086
|
+
this.handleRendererDeselect(seat);
|
|
5569
7087
|
},
|
|
5570
7088
|
onSelectionLimit: this.opts.onSelectionLimit,
|
|
5571
7089
|
onHover: (seat) => {
|
|
@@ -5620,7 +7138,20 @@ var PickerController = class {
|
|
|
5620
7138
|
// ---- selection ------------------------------------------------------------
|
|
5621
7139
|
getSelection() {
|
|
5622
7140
|
if (!this.renderer) return [];
|
|
5623
|
-
|
|
7141
|
+
const out = [];
|
|
7142
|
+
const grouped = /* @__PURE__ */ new Set();
|
|
7143
|
+
for (const seat of this.renderer.getSelection()) {
|
|
7144
|
+
const table = this.groupedTableBySeatId.get(seat.id);
|
|
7145
|
+
if (table) {
|
|
7146
|
+
if (!grouped.has(table.label)) {
|
|
7147
|
+
grouped.add(table.label);
|
|
7148
|
+
out.push(this.toTableSeat(table));
|
|
7149
|
+
}
|
|
7150
|
+
} else {
|
|
7151
|
+
out.push(this.toSeat(seat));
|
|
7152
|
+
}
|
|
7153
|
+
}
|
|
7154
|
+
return out;
|
|
5624
7155
|
}
|
|
5625
7156
|
/** Enriched metadata for a seat confirmation card or tooltip. */
|
|
5626
7157
|
seatDetails(seatId) {
|
|
@@ -5629,20 +7160,168 @@ var PickerController = class {
|
|
|
5629
7160
|
}
|
|
5630
7161
|
clearSelection() {
|
|
5631
7162
|
this.renderer?.clearSelection();
|
|
7163
|
+
this.resetUnheldTableQuantities();
|
|
5632
7164
|
this.emitSelectionChange();
|
|
5633
7165
|
}
|
|
5634
7166
|
deselect(ids) {
|
|
5635
|
-
|
|
7167
|
+
const expanded = /* @__PURE__ */ new Set();
|
|
7168
|
+
for (const id of ids) {
|
|
7169
|
+
const table = this.groupedTableBySeatId.get(id) ?? this.groupedTablesByLabel.get(id);
|
|
7170
|
+
if (table) {
|
|
7171
|
+
table.chairs.forEach((chair) => expanded.add(chair.id));
|
|
7172
|
+
if (!this.hold_?.labels.includes(table.label)) {
|
|
7173
|
+
this.tableQuantities.set(
|
|
7174
|
+
table.label,
|
|
7175
|
+
table.mode === "whole" ? table.capacity : table.minOccupancy
|
|
7176
|
+
);
|
|
7177
|
+
if (table.mode === "variable") this.confirmedVariableTables.delete(table.label);
|
|
7178
|
+
}
|
|
7179
|
+
} else expanded.add(id);
|
|
7180
|
+
}
|
|
7181
|
+
this.renderer?.deselect([...expanded]);
|
|
5636
7182
|
this.emitSelectionChange();
|
|
5637
7183
|
}
|
|
5638
7184
|
setMaxSelection(maxSelection) {
|
|
5639
7185
|
this.maxSelection = Math.max(0, Math.floor(maxSelection));
|
|
5640
|
-
this.renderer?.setMaxSelection?.(this.
|
|
7186
|
+
this.renderer?.setMaxSelection?.(this.rendererSelectionCap());
|
|
5641
7187
|
}
|
|
5642
7188
|
select(ids) {
|
|
5643
|
-
const
|
|
7189
|
+
const before = new Set(this.getSelection().map((seat) => seat.label));
|
|
7190
|
+
const expanded = /* @__PURE__ */ new Set();
|
|
7191
|
+
for (const id of ids) {
|
|
7192
|
+
const table = this.groupedTableBySeatId.get(id) ?? this.groupedTablesByLabel.get(id);
|
|
7193
|
+
if (table) table.chairs.forEach((chair) => expanded.add(chair.id));
|
|
7194
|
+
else expanded.add(id);
|
|
7195
|
+
}
|
|
7196
|
+
this.renderer?.select?.([...expanded]);
|
|
7197
|
+
if (this.selectionGuestCount() > this.maxSelection) {
|
|
7198
|
+
this.renderer?.deselect([...expanded]);
|
|
7199
|
+
this.opts.onSelectionLimit?.(this.maxSelection);
|
|
7200
|
+
return [];
|
|
7201
|
+
}
|
|
7202
|
+
const added = this.getSelection().filter((seat) => !before.has(seat.label));
|
|
5644
7203
|
if (added.length) this.emitSelectionChange();
|
|
5645
|
-
return added
|
|
7204
|
+
return added;
|
|
7205
|
+
}
|
|
7206
|
+
/** Confirm/update the guest quantity for a selected variable table. */
|
|
7207
|
+
setTableQuantity(label, quantity) {
|
|
7208
|
+
const table = this.groupedTablesByLabel.get(label);
|
|
7209
|
+
if (!table) return false;
|
|
7210
|
+
const q = table.mode === "whole" ? table.capacity : Math.floor(quantity);
|
|
7211
|
+
if (!Number.isInteger(q) || q < table.minOccupancy || q > table.maxOccupancy) return false;
|
|
7212
|
+
const previous = this.tableQuantities.get(label) ?? table.minOccupancy;
|
|
7213
|
+
this.tableQuantities.set(label, q);
|
|
7214
|
+
if (this.selectionGuestCount() > this.maxSelection) {
|
|
7215
|
+
this.tableQuantities.set(label, previous);
|
|
7216
|
+
this.opts.onSelectionLimit?.(this.maxSelection);
|
|
7217
|
+
return false;
|
|
7218
|
+
}
|
|
7219
|
+
if (table.mode === "variable") this.confirmedVariableTables.add(label);
|
|
7220
|
+
this.emitSelectionChange();
|
|
7221
|
+
return true;
|
|
7222
|
+
}
|
|
7223
|
+
/** Atomically replace an active variable-table hold with a new guest count. */
|
|
7224
|
+
async replaceTableQuantity(label, quantity) {
|
|
7225
|
+
const table = this.groupedTablesByLabel.get(label);
|
|
7226
|
+
const hold = this.hold_;
|
|
7227
|
+
if (!table || table.mode !== "variable" || !hold?.labels.includes(label)) return null;
|
|
7228
|
+
const q = Math.floor(quantity);
|
|
7229
|
+
if (!Number.isInteger(q) || q < table.minOccupancy || q > table.maxOccupancy) return null;
|
|
7230
|
+
const otherGuests = (hold.items ?? []).filter((item) => item.label !== label).reduce((sum, item) => sum + (item.quantity ?? 1), 0);
|
|
7231
|
+
if (otherGuests + q > this.maxSelection) {
|
|
7232
|
+
this.opts.onSelectionLimit?.(this.maxSelection);
|
|
7233
|
+
return null;
|
|
7234
|
+
}
|
|
7235
|
+
const selections = (hold.items ?? []).map((item) => ({
|
|
7236
|
+
label: item.label,
|
|
7237
|
+
tierId: item.tierId,
|
|
7238
|
+
quantity: item.label === label ? q : item.quantity
|
|
7239
|
+
}));
|
|
7240
|
+
if (!selections.some((item) => item.label === label)) return null;
|
|
7241
|
+
const result = await this.api.hold(this.key, selections, void 0, hold.holdId);
|
|
7242
|
+
this.tableQuantities.set(label, q);
|
|
7243
|
+
this.setHold({
|
|
7244
|
+
holdId: result.holdId,
|
|
7245
|
+
labels: selections.map((item) => item.label),
|
|
7246
|
+
expiresAt: result.expiresAt,
|
|
7247
|
+
items: result.items
|
|
7248
|
+
});
|
|
7249
|
+
return this.hold_;
|
|
7250
|
+
}
|
|
7251
|
+
rendererSelectionCap() {
|
|
7252
|
+
return this.maxSelection + this.groupedSelectionOverhead;
|
|
7253
|
+
}
|
|
7254
|
+
selectionGuestCount() {
|
|
7255
|
+
return this.getSelection().reduce((sum, seat) => sum + (seat.quantity ?? 1), 0);
|
|
7256
|
+
}
|
|
7257
|
+
handleRendererSelect(seat) {
|
|
7258
|
+
const table = this.groupedTableBySeatId.get(seat.id);
|
|
7259
|
+
if (table) {
|
|
7260
|
+
if (this.selectionGuestCount() > this.maxSelection) {
|
|
7261
|
+
this.renderer?.deselect(table.chairs.map((chair) => chair.id));
|
|
7262
|
+
this.opts.onSelectionLimit?.(this.maxSelection);
|
|
7263
|
+
this.emitSelectionChange();
|
|
7264
|
+
return;
|
|
7265
|
+
}
|
|
7266
|
+
this.renderer?.select?.(table.chairs.map((chair) => chair.id));
|
|
7267
|
+
this.opts.onSelect?.(table.chairs[0] ?? seat);
|
|
7268
|
+
this.opts.onTableSelectionRequest?.(this.toTableSeat(table));
|
|
7269
|
+
this.emitSelectionChange();
|
|
7270
|
+
return;
|
|
7271
|
+
}
|
|
7272
|
+
if (this.selectionGuestCount() > this.maxSelection) {
|
|
7273
|
+
this.renderer?.deselect([seat.id]);
|
|
7274
|
+
this.opts.onSelectionLimit?.(this.maxSelection);
|
|
7275
|
+
this.emitSelectionChange();
|
|
7276
|
+
return;
|
|
7277
|
+
}
|
|
7278
|
+
this.opts.onSelect?.(seat);
|
|
7279
|
+
this.emitSelectionChange();
|
|
7280
|
+
}
|
|
7281
|
+
handleRendererDeselect(seat) {
|
|
7282
|
+
const table = this.groupedTableBySeatId.get(seat.id);
|
|
7283
|
+
if (table) {
|
|
7284
|
+
this.renderer?.deselect(table.chairs.map((chair) => chair.id));
|
|
7285
|
+
if (!this.hold_?.labels.includes(table.label)) {
|
|
7286
|
+
this.tableQuantities.set(
|
|
7287
|
+
table.label,
|
|
7288
|
+
table.mode === "whole" ? table.capacity : table.minOccupancy
|
|
7289
|
+
);
|
|
7290
|
+
if (table.mode === "variable") this.confirmedVariableTables.delete(table.label);
|
|
7291
|
+
}
|
|
7292
|
+
this.opts.onDeselect?.(table.chairs[0] ?? seat);
|
|
7293
|
+
} else {
|
|
7294
|
+
this.opts.onDeselect?.(seat);
|
|
7295
|
+
}
|
|
7296
|
+
this.emitSelectionChange();
|
|
7297
|
+
}
|
|
7298
|
+
resetUnheldTableQuantities() {
|
|
7299
|
+
for (const table of this.groupedTablesByLabel.values()) {
|
|
7300
|
+
if (this.hold_?.labels.includes(table.label)) continue;
|
|
7301
|
+
this.tableQuantities.set(
|
|
7302
|
+
table.label,
|
|
7303
|
+
table.mode === "whole" ? table.capacity : table.minOccupancy
|
|
7304
|
+
);
|
|
7305
|
+
if (table.mode === "variable") this.confirmedVariableTables.delete(table.label);
|
|
7306
|
+
}
|
|
7307
|
+
}
|
|
7308
|
+
resetTableQuantitiesForLabels(labels) {
|
|
7309
|
+
for (const label of labels) {
|
|
7310
|
+
const table = this.groupedTablesByLabel.get(label);
|
|
7311
|
+
if (!table) continue;
|
|
7312
|
+
this.tableQuantities.set(
|
|
7313
|
+
label,
|
|
7314
|
+
table.mode === "whole" ? table.capacity : table.minOccupancy
|
|
7315
|
+
);
|
|
7316
|
+
if (table.mode === "variable") this.confirmedVariableTables.delete(label);
|
|
7317
|
+
}
|
|
7318
|
+
}
|
|
7319
|
+
tableQuantityRequest(seat) {
|
|
7320
|
+
if (seat?.objectType !== "table") return {};
|
|
7321
|
+
if (seat.bookingMode === "variable" && !this.confirmedVariableTables.has(seat.label)) {
|
|
7322
|
+
throw new Error("picker: confirm a variable table guest quantity before holding");
|
|
7323
|
+
}
|
|
7324
|
+
return { quantity: seat.quantity };
|
|
5646
7325
|
}
|
|
5647
7326
|
// ---- booking machine ------------------------------------------------------
|
|
5648
7327
|
/**
|
|
@@ -5654,7 +7333,7 @@ var PickerController = class {
|
|
|
5654
7333
|
async hold(labelsArg, ttlMs) {
|
|
5655
7334
|
const r = this.renderer;
|
|
5656
7335
|
if (!r) return null;
|
|
5657
|
-
const labels = labelsArg ??
|
|
7336
|
+
const labels = labelsArg ?? this.getSelection().map((seat) => seat.label);
|
|
5658
7337
|
const existingGA = (this.hold_?.items ?? []).filter((item) => item.objectType === "ga");
|
|
5659
7338
|
const combinedLabels = [.../* @__PURE__ */ new Set([...existingGA.map((item) => item.label), ...labels])];
|
|
5660
7339
|
if (!combinedLabels.length) return null;
|
|
@@ -5662,10 +7341,14 @@ var PickerController = class {
|
|
|
5662
7341
|
try {
|
|
5663
7342
|
const selections = combinedLabels.map((label) => {
|
|
5664
7343
|
const ga = existingGA.find((item) => item.label === label);
|
|
5665
|
-
if (ga) return { label, tierId: ga.tierId };
|
|
7344
|
+
if (ga) return { label, tierId: ga.tierId, quantity: ga.quantity };
|
|
5666
7345
|
const seat = this.labelToSeat.get(label);
|
|
5667
7346
|
const resolved = seat ? this.toSeat(seat) : null;
|
|
5668
|
-
return {
|
|
7347
|
+
return {
|
|
7348
|
+
label,
|
|
7349
|
+
...resolved?.tierId ? { tierId: resolved.tierId } : {},
|
|
7350
|
+
...this.tableQuantityRequest(resolved)
|
|
7351
|
+
};
|
|
5669
7352
|
});
|
|
5670
7353
|
const result = await this.api.hold(this.key, selections, ttlMs, this.hold_?.holdId);
|
|
5671
7354
|
this.setHold({ holdId: result.holdId, labels: combinedLabels, expiresAt: result.expiresAt, items: result.items });
|
|
@@ -5717,11 +7400,19 @@ var PickerController = class {
|
|
|
5717
7400
|
const out = {};
|
|
5718
7401
|
const closedMembers = this.closedMemberIds();
|
|
5719
7402
|
for (const [id, s] of this.seatById) {
|
|
7403
|
+
if (this.groupedTableBySeatId.has(id)) continue;
|
|
5720
7404
|
if (closedMembers.has(id)) continue;
|
|
5721
7405
|
if ((this.getStatus(id) ?? "free") === "free") {
|
|
5722
7406
|
out[s.categoryKey] = (out[s.categoryKey] ?? 0) + 1;
|
|
5723
7407
|
}
|
|
5724
7408
|
}
|
|
7409
|
+
for (const table of this.groupedTablesByLabel.values()) {
|
|
7410
|
+
if (table.chairs.some((chair) => closedMembers.has(chair.id))) continue;
|
|
7411
|
+
const representative = table.chairs[0];
|
|
7412
|
+
if (representative && (this.getStatus(representative.id) ?? "free") === "free") {
|
|
7413
|
+
out[table.categoryKey] = (out[table.categoryKey] ?? 0) + table.maxOccupancy;
|
|
7414
|
+
}
|
|
7415
|
+
}
|
|
5725
7416
|
return out;
|
|
5726
7417
|
}
|
|
5727
7418
|
/** Seat ids belonging to a currently-closed section (excluded from counts). */
|
|
@@ -5740,6 +7431,8 @@ var PickerController = class {
|
|
|
5740
7431
|
return {
|
|
5741
7432
|
id: area.id,
|
|
5742
7433
|
label: area.label,
|
|
7434
|
+
...area.displayLabel ? { displayLabel: area.displayLabel } : {},
|
|
7435
|
+
...area.displayType ? { displayType: area.displayType } : {},
|
|
5743
7436
|
capacity: Math.max(0, Math.floor(area.capacity)),
|
|
5744
7437
|
available: gaUnitLabels(area).filter((label) => (this.liveStatuses.get(label) ?? "free") === "free").length,
|
|
5745
7438
|
categoryKey: area.categoryKey,
|
|
@@ -5758,14 +7451,20 @@ var PickerController = class {
|
|
|
5758
7451
|
const labels = gaUnitLabels(area).filter((label) => !this.hold_?.labels.includes(label) && (this.liveStatuses.get(label) ?? "free") === "free").slice(0, Math.floor(qty));
|
|
5759
7452
|
if (labels.length !== Math.floor(qty)) return null;
|
|
5760
7453
|
const selections = /* @__PURE__ */ new Map();
|
|
5761
|
-
for (const item of this.hold_?.items ?? []) selections.set(item.label, {
|
|
7454
|
+
for (const item of this.hold_?.items ?? []) selections.set(item.label, {
|
|
7455
|
+
label: item.label,
|
|
7456
|
+
tierId: item.tierId,
|
|
7457
|
+
quantity: item.quantity
|
|
7458
|
+
});
|
|
5762
7459
|
if (this.hold_ && !this.hold_?.items?.length) {
|
|
5763
7460
|
for (const seat of this.hold_.seats) selections.set(seat.label, { label: seat.label, ...seat.tierId ? { tierId: seat.tierId } : {} });
|
|
5764
7461
|
}
|
|
5765
|
-
for (const seat of this.
|
|
5766
|
-
|
|
5767
|
-
|
|
5768
|
-
|
|
7462
|
+
for (const seat of this.getSelection()) {
|
|
7463
|
+
selections.set(seat.label, {
|
|
7464
|
+
label: seat.label,
|
|
7465
|
+
...seat.tierId ? { tierId: seat.tierId } : {},
|
|
7466
|
+
...this.tableQuantityRequest(seat)
|
|
7467
|
+
});
|
|
5769
7468
|
}
|
|
5770
7469
|
for (const label of labels) selections.set(label, { label, ...options.tierId ? { tierId: options.tierId } : {} });
|
|
5771
7470
|
const combined = [...selections.values()];
|
|
@@ -5782,6 +7481,15 @@ var PickerController = class {
|
|
|
5782
7481
|
}
|
|
5783
7482
|
return false;
|
|
5784
7483
|
}
|
|
7484
|
+
/** Zones which still own visible buyer inventory, in authored order. */
|
|
7485
|
+
getBestAvailableZones() {
|
|
7486
|
+
const doc = this.visibleDoc();
|
|
7487
|
+
if (!doc?.zones?.length) return [];
|
|
7488
|
+
const used = new Set(
|
|
7489
|
+
computeSections(doc).sections.filter((section) => section.seatCount > 0 && !!section.zone).map((section) => section.zone)
|
|
7490
|
+
);
|
|
7491
|
+
return doc.zones.filter((zone) => used.has(zone.id)).map((zone) => ({ id: zone.id, label: zone.label }));
|
|
7492
|
+
}
|
|
5785
7493
|
/**
|
|
5786
7494
|
* Client-side premium pre-pass: find the best contiguous block of `qty`
|
|
5787
7495
|
* PREMIUM-flagged free seats (orphan-avoiding, closest to the focal point)
|
|
@@ -5790,11 +7498,12 @@ var PickerController = class {
|
|
|
5790
7498
|
* seat status. Returns a FULL premium block of `qty`, or null when none
|
|
5791
7499
|
* exists (the caller then falls back to the normal server pick).
|
|
5792
7500
|
*/
|
|
5793
|
-
pickPremiumBlock(qty, categoryKey) {
|
|
7501
|
+
pickPremiumBlock(qty, categoryKey, zoneId) {
|
|
5794
7502
|
const doc = this.visibleDoc();
|
|
5795
7503
|
if (!doc?.objects?.length) return null;
|
|
5796
7504
|
const focal = doc.focalPoint ?? { x: 0, y: 0 };
|
|
5797
|
-
const
|
|
7505
|
+
const groupedObjectIds = new Set(this.groupedTablesByObject.keys());
|
|
7506
|
+
const seats = expandChart(doc).filter((seat) => !groupedObjectIds.has(seat.rowId));
|
|
5798
7507
|
const held = new Set(this.hold_?.labels ?? []);
|
|
5799
7508
|
const available = /* @__PURE__ */ new Set();
|
|
5800
7509
|
for (const seat of seats) {
|
|
@@ -5803,7 +7512,7 @@ var PickerController = class {
|
|
|
5803
7512
|
const status = id ? this.getStatus(id) : void 0;
|
|
5804
7513
|
if ((status ?? "free") === "free") available.add(seat.label);
|
|
5805
7514
|
}
|
|
5806
|
-
const pick = pickBestAvailable(seats, available, { qty, categoryKey, focal, preferPremium: true });
|
|
7515
|
+
const pick = pickBestAvailable(seats, available, { qty, categoryKey, zoneId, focal, preferPremium: true });
|
|
5807
7516
|
if (pick.labels.length !== qty) return null;
|
|
5808
7517
|
const allPremium = pick.labels.every((l) => this.labelToSeat.get(l)?.commercial?.premium);
|
|
5809
7518
|
return allPremium ? [...pick.labels] : null;
|
|
@@ -5818,7 +7527,7 @@ var PickerController = class {
|
|
|
5818
7527
|
const r = this.renderer;
|
|
5819
7528
|
if (!r) return null;
|
|
5820
7529
|
if (opts.preferPremium) {
|
|
5821
|
-
const block = this.pickPremiumBlock(qty, categoryKey);
|
|
7530
|
+
const block = this.pickPremiumBlock(qty, categoryKey, opts.zoneId);
|
|
5822
7531
|
if (block) {
|
|
5823
7532
|
if (this.hold_ && !await this.release()) return null;
|
|
5824
7533
|
try {
|
|
@@ -5829,7 +7538,7 @@ var PickerController = class {
|
|
|
5829
7538
|
});
|
|
5830
7539
|
const result = await this.api.hold(this.key, selection);
|
|
5831
7540
|
r.clearSelection();
|
|
5832
|
-
const ids =
|
|
7541
|
+
const ids = this.idsForLabels(block);
|
|
5833
7542
|
if (ids.length) r.setStatus(ids, "held");
|
|
5834
7543
|
this.setHold({ holdId: result.holdId, labels: [...block], expiresAt: result.expiresAt, items: result.items });
|
|
5835
7544
|
const seats = block.map((l) => this.labelToSeat.get(l)).filter((s) => !!s).map((s) => this.toSeat(s));
|
|
@@ -5846,9 +7555,9 @@ var PickerController = class {
|
|
|
5846
7555
|
}
|
|
5847
7556
|
if (this.hold_ && !await this.release()) return null;
|
|
5848
7557
|
try {
|
|
5849
|
-
const result = await this.api.bestAvailable(this.key, qty, categoryKey);
|
|
7558
|
+
const result = await this.api.bestAvailable(this.key, qty, categoryKey, opts.zoneId);
|
|
5850
7559
|
r.clearSelection();
|
|
5851
|
-
const ids =
|
|
7560
|
+
const ids = this.idsForLabels(result.labels);
|
|
5852
7561
|
if (ids.length) r.setStatus(ids, "held");
|
|
5853
7562
|
this.setHold({ holdId: result.holdId, labels: [...result.labels], expiresAt: result.expiresAt, items: result.items });
|
|
5854
7563
|
const seats = result.labels.map((l) => this.labelToSeat.get(l)).filter((s) => !!s).map((s) => this.toSeat(s));
|
|
@@ -5872,7 +7581,7 @@ var PickerController = class {
|
|
|
5872
7581
|
const r = this.renderer;
|
|
5873
7582
|
if (!r) return null;
|
|
5874
7583
|
if (!this.api.book) throw new Error("picker: transport has no book() \u2014 hold-only mode");
|
|
5875
|
-
const labels = labelsArg ??
|
|
7584
|
+
const labels = labelsArg ?? this.getSelection().map((seat) => seat.label);
|
|
5876
7585
|
if (!labels.length) return null;
|
|
5877
7586
|
let holdId;
|
|
5878
7587
|
try {
|
|
@@ -5885,7 +7594,11 @@ var PickerController = class {
|
|
|
5885
7594
|
labels.map((label) => {
|
|
5886
7595
|
const seat = this.labelToSeat.get(label);
|
|
5887
7596
|
const resolved = seat ? this.toSeat(seat) : null;
|
|
5888
|
-
return {
|
|
7597
|
+
return {
|
|
7598
|
+
label,
|
|
7599
|
+
...resolved?.tierId ? { tierId: resolved.tierId } : {},
|
|
7600
|
+
...this.tableQuantityRequest(resolved)
|
|
7601
|
+
};
|
|
5889
7602
|
}),
|
|
5890
7603
|
void 0,
|
|
5891
7604
|
replaceHoldId
|
|
@@ -5901,10 +7614,11 @@ var PickerController = class {
|
|
|
5901
7614
|
this.opts.onSalesClosed?.();
|
|
5902
7615
|
} else if (status === 409 && conflicts?.length) {
|
|
5903
7616
|
const takenLabels = new Set(conflicts.map((c) => c.label));
|
|
5904
|
-
const takenIds =
|
|
7617
|
+
const takenIds = this.idsForLabels(takenLabels);
|
|
5905
7618
|
if (takenIds.length) {
|
|
5906
7619
|
r.setStatus(takenIds, "booked");
|
|
5907
7620
|
r.deselect(takenIds);
|
|
7621
|
+
this.resetTableQuantitiesForLabels(takenLabels);
|
|
5908
7622
|
}
|
|
5909
7623
|
const stillFree = labels.filter((l) => !takenLabels.has(l));
|
|
5910
7624
|
if (stillFree.length && holdId) void this.api.release(this.key, stillFree, holdId).catch(() => {
|
|
@@ -5916,12 +7630,13 @@ var PickerController = class {
|
|
|
5916
7630
|
}
|
|
5917
7631
|
throw err;
|
|
5918
7632
|
}
|
|
5919
|
-
const ids =
|
|
7633
|
+
const ids = this.idsForLabels(labels);
|
|
5920
7634
|
if (ids.length) {
|
|
5921
7635
|
r.setStatus(ids, "booked");
|
|
5922
7636
|
r.deselect(ids);
|
|
5923
7637
|
}
|
|
5924
7638
|
this.clearHold();
|
|
7639
|
+
this.resetTableQuantitiesForLabels(labels);
|
|
5925
7640
|
this.emitSelectionChange();
|
|
5926
7641
|
this.opts.onBook?.(bookingRef);
|
|
5927
7642
|
return labels;
|
|
@@ -5942,7 +7657,8 @@ var PickerController = class {
|
|
|
5942
7657
|
}
|
|
5943
7658
|
if (this.hold_?.holdId !== hold.holdId) return true;
|
|
5944
7659
|
this.clearHold();
|
|
5945
|
-
|
|
7660
|
+
this.resetTableQuantitiesForLabels(hold.labels);
|
|
7661
|
+
const ids = this.idsForLabels(hold.labels);
|
|
5946
7662
|
if (ids.length) {
|
|
5947
7663
|
this.renderer?.deselect(ids);
|
|
5948
7664
|
this.renderer?.setStatus(ids, "free");
|
|
@@ -5973,7 +7689,8 @@ var PickerController = class {
|
|
|
5973
7689
|
const remainingItems = hold.items?.filter((item) => !drop.includes(item.label));
|
|
5974
7690
|
if (remaining.length) this.setHold({ ...hold, labels: remaining, items: remainingItems });
|
|
5975
7691
|
else this.clearHold();
|
|
5976
|
-
|
|
7692
|
+
this.resetTableQuantitiesForLabels(drop);
|
|
7693
|
+
const ids = this.idsForLabels(drop);
|
|
5977
7694
|
if (ids.length) {
|
|
5978
7695
|
this.renderer?.deselect(ids);
|
|
5979
7696
|
this.renderer?.setStatus(ids, "free");
|
|
@@ -6062,7 +7779,7 @@ var PickerController = class {
|
|
|
6062
7779
|
this.opts.onDeckTap?.(floorId);
|
|
6063
7780
|
}
|
|
6064
7781
|
// ---- big-venue: sections / rungs / projection (Slice 5) -------------------
|
|
6065
|
-
/** Switch the map projection
|
|
7782
|
+
/** Switch the map projection. No-op on a flat-only renderer. */
|
|
6066
7783
|
setViewMode(mode) {
|
|
6067
7784
|
this.renderer?.setViewMode?.(mode);
|
|
6068
7785
|
}
|
|
@@ -6161,12 +7878,21 @@ var PickerController = class {
|
|
|
6161
7878
|
if (!sec) return null;
|
|
6162
7879
|
const memberIds = r.sectionMembers?.(id) ?? [];
|
|
6163
7880
|
const byCat = /* @__PURE__ */ new Map();
|
|
7881
|
+
const seenTables = /* @__PURE__ */ new Set();
|
|
6164
7882
|
let seatsLeft = 0;
|
|
6165
7883
|
for (const sid of memberIds) {
|
|
6166
7884
|
const seat = this.seatById.get(sid);
|
|
6167
7885
|
if (!seat) continue;
|
|
6168
|
-
|
|
6169
|
-
if (
|
|
7886
|
+
const table = this.groupedTableBySeatId.get(sid);
|
|
7887
|
+
if (table) {
|
|
7888
|
+
if (seenTables.has(table.label)) continue;
|
|
7889
|
+
seenTables.add(table.label);
|
|
7890
|
+
byCat.set(table.categoryKey, (byCat.get(table.categoryKey) ?? 0) + table.maxOccupancy);
|
|
7891
|
+
if (r.getStatus(table.chairs[0]?.id ?? sid) === "free") seatsLeft += table.maxOccupancy;
|
|
7892
|
+
} else {
|
|
7893
|
+
byCat.set(seat.categoryKey, (byCat.get(seat.categoryKey) ?? 0) + 1);
|
|
7894
|
+
if (r.getStatus(sid) === "free") seatsLeft++;
|
|
7895
|
+
}
|
|
6170
7896
|
}
|
|
6171
7897
|
const categories = [...byCat.entries()].map(([key, count]) => {
|
|
6172
7898
|
const cat = doc.categories.find((c) => c.key === key);
|
|
@@ -6216,22 +7942,54 @@ var PickerController = class {
|
|
|
6216
7942
|
}
|
|
6217
7943
|
// ---- internals ------------------------------------------------------------
|
|
6218
7944
|
toSeat(s) {
|
|
7945
|
+
const table = this.groupedTableBySeatId.get(s.id);
|
|
7946
|
+
if (table) return this.toTableSeat(table);
|
|
6219
7947
|
const commercial = s.commercial ? { commercial: s.commercial } : void 0;
|
|
6220
7948
|
const display = s.displayLabel ? { displayLabel: s.displayLabel } : void 0;
|
|
7949
|
+
const accessibility = s.accessibility?.length ? { accessibility: s.accessibility } : void 0;
|
|
7950
|
+
const wheelchair = s.wheelchairSpaceType ? { wheelchairSpaceType: s.wheelchairSpaceType } : void 0;
|
|
7951
|
+
const context = this.seatContext.get(s.id);
|
|
6221
7952
|
const tiers = this.tiersFor(s.categoryKey);
|
|
6222
7953
|
if (!tiers) {
|
|
6223
|
-
return { id: s.id, label: s.label, ...display, categoryKey: s.categoryKey, price: this.priceFor(s.categoryKey), ...commercial };
|
|
7954
|
+
return { id: s.id, label: s.label, ...display, ...context, categoryKey: s.categoryKey, price: this.priceFor(s.categoryKey), ...commercial, ...accessibility, ...wheelchair };
|
|
6224
7955
|
}
|
|
6225
7956
|
const chosen = tiers.find((t2) => t2.id === this.seatTiers.get(s.id)) ?? tiers[0];
|
|
6226
7957
|
return {
|
|
6227
7958
|
id: s.id,
|
|
6228
7959
|
label: s.label,
|
|
6229
7960
|
...display,
|
|
7961
|
+
...context,
|
|
6230
7962
|
categoryKey: s.categoryKey,
|
|
6231
7963
|
price: chosen.price,
|
|
6232
7964
|
tiers,
|
|
6233
7965
|
tierId: chosen.id,
|
|
6234
|
-
...commercial
|
|
7966
|
+
...commercial,
|
|
7967
|
+
...accessibility,
|
|
7968
|
+
...wheelchair
|
|
7969
|
+
};
|
|
7970
|
+
}
|
|
7971
|
+
toTableSeat(table) {
|
|
7972
|
+
const representative = table.chairs[0];
|
|
7973
|
+
const tiers = this.tiersFor(table.categoryKey);
|
|
7974
|
+
const chosen = tiers?.find((tier) => tier.id === this.seatTiers.get(representative?.id ?? "")) ?? tiers?.[0];
|
|
7975
|
+
return {
|
|
7976
|
+
id: representative?.id ?? table.objectId,
|
|
7977
|
+
label: table.label,
|
|
7978
|
+
displayLabel: table.displayLabel ?? table.label,
|
|
7979
|
+
...table.displayType ? { displayType: table.displayType, rowType: table.displayType } : {},
|
|
7980
|
+
objectId: table.objectId,
|
|
7981
|
+
sectionLabel: representative ? this.seatContext.get(representative.id)?.sectionLabel : void 0,
|
|
7982
|
+
rowLabel: table.displayLabel ?? table.label,
|
|
7983
|
+
categoryKey: table.categoryKey,
|
|
7984
|
+
price: chosen?.price ?? this.priceFor(table.categoryKey),
|
|
7985
|
+
...tiers ? { tiers, tierId: chosen?.id } : {},
|
|
7986
|
+
objectType: "table",
|
|
7987
|
+
bookingMode: table.mode,
|
|
7988
|
+
quantity: this.tableQuantities.get(table.label) ?? (table.mode === "whole" ? table.capacity : table.minOccupancy),
|
|
7989
|
+
capacity: table.capacity,
|
|
7990
|
+
minOccupancy: table.minOccupancy,
|
|
7991
|
+
maxOccupancy: table.maxOccupancy,
|
|
7992
|
+
physicalSeatIds: table.chairs.map((chair) => chair.id)
|
|
6235
7993
|
};
|
|
6236
7994
|
}
|
|
6237
7995
|
/** Tooltip payload for a hovered seat — see PickerCallbacks.onSeatHover. */
|
|
@@ -6315,10 +8073,13 @@ var PickerController = class {
|
|
|
6315
8073
|
const h = this.hold_;
|
|
6316
8074
|
if (!h || h.labels.length !== labels.length || !labels.every((l) => h.labels.includes(l))) return false;
|
|
6317
8075
|
const tiers = new Map((h.items ?? []).map((item) => [item.label, item.tierId]));
|
|
8076
|
+
const quantities = new Map((h.items ?? []).map((item) => [item.label, item.quantity ?? 1]));
|
|
6318
8077
|
return labels.every((label) => {
|
|
6319
8078
|
const seat = this.labelToSeat.get(label);
|
|
6320
|
-
const
|
|
6321
|
-
|
|
8079
|
+
const resolved = seat ? this.toSeat(seat) : null;
|
|
8080
|
+
const currentTier = resolved?.tierId ?? null;
|
|
8081
|
+
const quantityMatches = resolved?.objectType !== "table" || (resolved.bookingMode !== "variable" || this.confirmedVariableTables.has(label)) && (!quantities.has(label) || quantities.get(label) === resolved.quantity);
|
|
8082
|
+
return quantityMatches && (!tiers.has(label) || tiers.get(label) === currentTier);
|
|
6322
8083
|
});
|
|
6323
8084
|
}
|
|
6324
8085
|
handle409Conflicts(err) {
|
|
@@ -6326,19 +8087,28 @@ var PickerController = class {
|
|
|
6326
8087
|
if (status !== 409 || !conflicts?.length) return;
|
|
6327
8088
|
const r = this.renderer;
|
|
6328
8089
|
if (!r) return;
|
|
6329
|
-
const takenIds = conflicts.map((
|
|
8090
|
+
const takenIds = this.idsForLabels(conflicts.map((conflict) => conflict.label));
|
|
6330
8091
|
if (takenIds.length) {
|
|
6331
8092
|
r.deselect(takenIds);
|
|
6332
8093
|
r.setStatus(takenIds, "held");
|
|
8094
|
+
this.resetTableQuantitiesForLabels(conflicts.map((conflict) => conflict.label));
|
|
6333
8095
|
}
|
|
6334
8096
|
this.emitSelectionChange();
|
|
6335
8097
|
}
|
|
6336
8098
|
/** Set the open hold + (re)arm the server-authoritative expiry timer. */
|
|
6337
8099
|
setHold(hold, source = "created") {
|
|
8100
|
+
for (const item of hold.items ?? []) {
|
|
8101
|
+
if (this.groupedTablesByLabel.has(item.label) && item.quantity != null) {
|
|
8102
|
+
this.tableQuantities.set(item.label, item.quantity);
|
|
8103
|
+
if (this.groupedTablesByLabel.get(item.label)?.mode === "variable") {
|
|
8104
|
+
this.confirmedVariableTables.add(item.label);
|
|
8105
|
+
}
|
|
8106
|
+
}
|
|
8107
|
+
}
|
|
6338
8108
|
const full = { ...hold, seats: this.seatsForLabels(hold.labels) };
|
|
6339
8109
|
this.hold_ = full;
|
|
6340
8110
|
this.renderer?.setOwnedHold?.(
|
|
6341
|
-
|
|
8111
|
+
this.idsForLabels(full.labels)
|
|
6342
8112
|
);
|
|
6343
8113
|
if (this.expiryTimer) clearTimeout(this.expiryTimer);
|
|
6344
8114
|
const ms = Math.max(0, full.expiresAt - Date.now());
|
|
@@ -6381,12 +8151,11 @@ var PickerController = class {
|
|
|
6381
8151
|
this.liveStatuses = new Map(Object.entries(seats));
|
|
6382
8152
|
if (this.allIds.length) r.setStatus(this.allIds, "free");
|
|
6383
8153
|
r.setOwnedHold?.(
|
|
6384
|
-
(this.hold_?.labels ?? [])
|
|
8154
|
+
this.idsForLabels(this.hold_?.labels ?? [])
|
|
6385
8155
|
);
|
|
6386
8156
|
const byStatus = { free: [], held: [], booked: [], not_for_sale: [] };
|
|
6387
8157
|
for (const [label, st] of Object.entries(seats)) {
|
|
6388
|
-
|
|
6389
|
-
if (id) byStatus[mapStatus(st)].push(id);
|
|
8158
|
+
byStatus[mapStatus(st)].push(...this.idsForLabel(label));
|
|
6390
8159
|
}
|
|
6391
8160
|
["held", "booked", "not_for_sale"].forEach((st) => {
|
|
6392
8161
|
if (byStatus[st].length) r.setStatus(byStatus[st], st);
|
|
@@ -6395,7 +8164,11 @@ var PickerController = class {
|
|
|
6395
8164
|
this.clearBookedHoldIfSettled();
|
|
6396
8165
|
}
|
|
6397
8166
|
clearBookedHoldIfSettled() {
|
|
6398
|
-
|
|
8167
|
+
const hold = this.hold_;
|
|
8168
|
+
if (hold?.labels.every((label) => this.liveStatuses.get(label) === "booked")) {
|
|
8169
|
+
this.clearHold();
|
|
8170
|
+
this.resetTableQuantitiesForLabels(hold.labels);
|
|
8171
|
+
}
|
|
6399
8172
|
}
|
|
6400
8173
|
async resnapshot() {
|
|
6401
8174
|
try {
|
|
@@ -6484,13 +8257,13 @@ var PickerController = class {
|
|
|
6484
8257
|
} else if (Array.isArray(m.changes)) {
|
|
6485
8258
|
for (const ch of m.changes) {
|
|
6486
8259
|
this.liveStatuses.set(ch.label, ch.status);
|
|
6487
|
-
const
|
|
6488
|
-
if (!
|
|
8260
|
+
const ids = this.idsForLabel(ch.label);
|
|
8261
|
+
if (!ids.length) continue;
|
|
6489
8262
|
const next = mapStatus(ch.status);
|
|
6490
|
-
if (this.opts.flashOnLiveChange && next !== "free" && r.getStatus(id) === "free" && !this.hold_?.labels.includes(ch.label)) {
|
|
6491
|
-
r.flashSeat(id, next === "held" ? "#f4b740" : "#f43f5e");
|
|
8263
|
+
if (this.opts.flashOnLiveChange && next !== "free" && ids.some((id) => r.getStatus(id) === "free") && !this.hold_?.labels.includes(ch.label)) {
|
|
8264
|
+
ids.forEach((id) => r.flashSeat(id, next === "held" ? "#f4b740" : "#f43f5e"));
|
|
6492
8265
|
}
|
|
6493
|
-
r.setStatus(
|
|
8266
|
+
r.setStatus(ids, next);
|
|
6494
8267
|
}
|
|
6495
8268
|
if (this.opts.keepLiveWhileHidden && typeof document !== "undefined" && document.visibilityState === "hidden") {
|
|
6496
8269
|
r.forceDraw();
|
|
@@ -6521,10 +8294,25 @@ var PickerController = class {
|
|
|
6521
8294
|
}
|
|
6522
8295
|
};
|
|
6523
8296
|
|
|
8297
|
+
// src/view/sightline.ts
|
|
8298
|
+
var STAGE_TOP_M = 5;
|
|
8299
|
+
var STAGE_BASE_M = -1.1;
|
|
8300
|
+
var MAX_LOOKDOWN_DEG = 35;
|
|
8301
|
+
function stageSightlinePitch(eyeHeightM, distM) {
|
|
8302
|
+
const extraEyeM = eyeHeightM - SEATED_EYE_HEIGHT_M;
|
|
8303
|
+
let topPitch = Math.atan2(STAGE_TOP_M - extraEyeM, distM) * 180 / Math.PI;
|
|
8304
|
+
let basePitch = Math.atan2(STAGE_BASE_M - extraEyeM, distM) * 180 / Math.PI;
|
|
8305
|
+
if (basePitch < -MAX_LOOKDOWN_DEG) {
|
|
8306
|
+
topPitch += -MAX_LOOKDOWN_DEG - basePitch;
|
|
8307
|
+
basePitch = -MAX_LOOKDOWN_DEG;
|
|
8308
|
+
}
|
|
8309
|
+
return { topPitch, basePitch };
|
|
8310
|
+
}
|
|
8311
|
+
|
|
6524
8312
|
// src/view/generatePanorama.ts
|
|
6525
8313
|
var W = 2048;
|
|
6526
8314
|
var H = 1024;
|
|
6527
|
-
var UNIT =
|
|
8315
|
+
var UNIT = METRES_PER_CHART_UNIT;
|
|
6528
8316
|
var yawToX = (yawDeg) => (yawDeg + 180) / 360 * W;
|
|
6529
8317
|
var pitchToY = (pitchDeg) => (90 - pitchDeg) / 180 * H;
|
|
6530
8318
|
function generateSeatPanorama(seat, focalPoint, neighborSeats) {
|
|
@@ -6544,9 +8332,11 @@ function generateSeatPanorama(seat, focalPoint, neighborSeats) {
|
|
|
6544
8332
|
sky.addColorStop(1, "#07090f");
|
|
6545
8333
|
ctx.fillStyle = sky;
|
|
6546
8334
|
ctx.fillRect(0, 0, W, H);
|
|
8335
|
+
const eyeM = seat.eyeHeightM ?? SEATED_EYE_HEIGHT_M;
|
|
6547
8336
|
const stageHalfYaw = Math.min(80, Math.atan2(4, distM) * 180 / Math.PI);
|
|
6548
|
-
const
|
|
6549
|
-
const
|
|
8337
|
+
const sightline = stageSightlinePitch(eyeM, distM);
|
|
8338
|
+
const stageTopPitch = Math.min(45, sightline.topPitch);
|
|
8339
|
+
const stageBasePitch = sightline.basePitch;
|
|
6550
8340
|
const sx0 = yawToX(-stageHalfYaw);
|
|
6551
8341
|
const sx1 = yawToX(stageHalfYaw);
|
|
6552
8342
|
const sy0 = pitchToY(stageTopPitch);
|
|
@@ -6653,9 +8443,11 @@ function generateSeatThumb(seat, focalPoint, _neighborSeats) {
|
|
|
6653
8443
|
sky.addColorStop(1, "#070910");
|
|
6654
8444
|
ctx.fillStyle = sky;
|
|
6655
8445
|
ctx.fillRect(0, 0, TW, TH);
|
|
8446
|
+
const eyeM = seat.eyeHeightM ?? SEATED_EYE_HEIGHT_M;
|
|
8447
|
+
const sightline = stageSightlinePitch(eyeM, distM);
|
|
6656
8448
|
const stageHalfYaw = Math.min(HALF_HFOV - 2, Math.atan2(4, distM) * 180 / Math.PI);
|
|
6657
|
-
const stageTopPitch = Math.min(HALF_VFOV - 2,
|
|
6658
|
-
const stageBasePitch = Math.max(-HALF_VFOV + 2,
|
|
8449
|
+
const stageTopPitch = Math.min(HALF_VFOV - 2, sightline.topPitch);
|
|
8450
|
+
const stageBasePitch = Math.max(-HALF_VFOV + 2, sightline.basePitch);
|
|
6659
8451
|
const sx0 = yawToTX(-stageHalfYaw);
|
|
6660
8452
|
const sx1 = yawToTX(stageHalfYaw);
|
|
6661
8453
|
const sy0 = pitchToTY(stageTopPitch);
|
|
@@ -6753,7 +8545,9 @@ async function loadLocale(code) {
|
|
|
6753
8545
|
PickerController,
|
|
6754
8546
|
RENDERED_QUALITY_REPORT_VERSION,
|
|
6755
8547
|
SUPPORTED_LOCALES,
|
|
8548
|
+
SURROUNDINGS_SHAPE_ROLES,
|
|
6756
8549
|
SeatmapRenderer,
|
|
8550
|
+
TIER_HEIGHT_M,
|
|
6757
8551
|
UNGROUPED_ID,
|
|
6758
8552
|
accessibilityMeta,
|
|
6759
8553
|
accessibilityRingColor,
|
|
@@ -6768,16 +8562,19 @@ async function loadLocale(code) {
|
|
|
6768
8562
|
expandRow,
|
|
6769
8563
|
expandRowSlots,
|
|
6770
8564
|
expandTable,
|
|
8565
|
+
expandTableSlots,
|
|
6771
8566
|
floorObjects,
|
|
6772
8567
|
floorsOf,
|
|
6773
8568
|
formatDate,
|
|
6774
8569
|
formatMoney,
|
|
6775
8570
|
gaAreasOf,
|
|
8571
|
+
gaInventorySegments,
|
|
6776
8572
|
gaUnitLabel,
|
|
6777
8573
|
gaUnitLabels,
|
|
6778
8574
|
generateSeatPanorama,
|
|
6779
8575
|
generateSeatThumb,
|
|
6780
8576
|
getLocale,
|
|
8577
|
+
growJoinedGAInventory,
|
|
6781
8578
|
hiddenObjectIds,
|
|
6782
8579
|
inspectRenderedQualityEvidence,
|
|
6783
8580
|
isGaUnitLabel,
|
|
@@ -6785,17 +8582,23 @@ async function loadLocale(code) {
|
|
|
6785
8582
|
layerOf,
|
|
6786
8583
|
loadLocale,
|
|
6787
8584
|
objectCenter,
|
|
8585
|
+
owningSectionForObject,
|
|
6788
8586
|
pointInPolygon,
|
|
6789
8587
|
pointInPolygonWithHoles,
|
|
6790
8588
|
polygonLabelPoint,
|
|
6791
8589
|
resolveLocale,
|
|
8590
|
+
rowInventoryCount,
|
|
6792
8591
|
rowSeatPositions,
|
|
6793
8592
|
seatLabelPart,
|
|
8593
|
+
sectionGeometry,
|
|
6794
8594
|
setLocale,
|
|
6795
8595
|
setMoneyLocale,
|
|
6796
8596
|
setStringOverrides,
|
|
6797
8597
|
stackFloors,
|
|
6798
8598
|
t,
|
|
6799
|
-
tCount
|
|
8599
|
+
tCount,
|
|
8600
|
+
tableInventoryCount,
|
|
8601
|
+
tableSeatCountsBySide,
|
|
8602
|
+
validGAInventorySegments
|
|
6800
8603
|
});
|
|
6801
8604
|
//# sourceMappingURL=index.cjs.map
|