@seatlayer/core 0.35.0 → 0.36.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/{chunk-MGC5BVAD.js → chunk-5MRJCIZP.js} +116 -97
- package/dist/chunk-5MRJCIZP.js.map +1 -0
- package/dist/index.cjs +452 -135
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +87 -8
- package/dist/index.d.ts +87 -8
- package/dist/index.js +338 -41
- package/dist/index.js.map +1 -1
- package/dist/{types-DO_J5DtX.d.cts → types-DBnRO2hX.d.cts} +13 -1
- package/dist/{types-DO_J5DtX.d.ts → types-DBnRO2hX.d.ts} +13 -1
- package/dist/view3d/index.cjs.map +1 -1
- package/dist/view3d/index.d.cts +1 -1
- package/dist/view3d/index.d.ts +1 -1
- package/dist/view3d/index.js +1 -1
- package/package.json +1 -1
- package/dist/chunk-MGC5BVAD.js.map +0 -1
package/dist/index.cjs
CHANGED
|
@@ -265,7 +265,6 @@ __export(index_exports, {
|
|
|
265
265
|
rowInventoryCount: () => rowInventoryCount,
|
|
266
266
|
rowSeatPositions: () => rowSeatPositions,
|
|
267
267
|
seatCommercialMeta: () => seatCommercialMeta,
|
|
268
|
-
seatLabelPart: () => seatLabelPart,
|
|
269
268
|
sectionGeometry: () => sectionGeometry,
|
|
270
269
|
setLocale: () => setLocale,
|
|
271
270
|
setMoneyLocale: () => setMoneyLocale,
|
|
@@ -565,6 +564,44 @@ function transformSectionOutlinePath(path, transform, radiusScale = 1, reflected
|
|
|
565
564
|
}
|
|
566
565
|
var SAGITTA_TO_CONTROL = 4 / 3;
|
|
567
566
|
|
|
567
|
+
// src/core/segmentedRowModel.ts
|
|
568
|
+
function resolveSegmentedRowGroups(objects) {
|
|
569
|
+
const segmented = /* @__PURE__ */ new Map();
|
|
570
|
+
const grouped = /* @__PURE__ */ new Map();
|
|
571
|
+
for (const object of objects) {
|
|
572
|
+
if (object.type !== "row" || !object.segmentedRow) continue;
|
|
573
|
+
const list = grouped.get(object.segmentedRow.groupId) ?? [];
|
|
574
|
+
list.push(object);
|
|
575
|
+
grouped.set(object.segmentedRow.groupId, list);
|
|
576
|
+
}
|
|
577
|
+
for (const [groupId, members] of grouped) {
|
|
578
|
+
const ordered = members.slice().sort((left, right) => left.segmentedRow.componentIndex - right.segmentedRow.componentIndex);
|
|
579
|
+
const expectedCount = ordered[0]?.segmentedRow?.componentCount ?? 0;
|
|
580
|
+
const first = ordered[0]?.segmentedRow;
|
|
581
|
+
if (!first) continue;
|
|
582
|
+
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);
|
|
583
|
+
if (!valid) continue;
|
|
584
|
+
const totalSeats = ordered.reduce((sum, row) => sum + row.seatCount, 0);
|
|
585
|
+
let adjacencyOffset = 0;
|
|
586
|
+
let displayOffset = 0;
|
|
587
|
+
for (const row of ordered) {
|
|
588
|
+
if (row.segmentedRow.boundaryBefore === "break") adjacencyOffset += 1;
|
|
589
|
+
segmented.set(row.id, {
|
|
590
|
+
groupId,
|
|
591
|
+
adjacencyOffset,
|
|
592
|
+
displayOffset,
|
|
593
|
+
displayLabel: first.displayLabel,
|
|
594
|
+
totalSeats,
|
|
595
|
+
canonical: ordered[0],
|
|
596
|
+
viewFromSeatUrl: first.viewFromSeatUrl
|
|
597
|
+
});
|
|
598
|
+
adjacencyOffset += row.seatCount;
|
|
599
|
+
displayOffset += row.seatCount;
|
|
600
|
+
}
|
|
601
|
+
}
|
|
602
|
+
return segmented;
|
|
603
|
+
}
|
|
604
|
+
|
|
568
605
|
// src/core/labeling.ts
|
|
569
606
|
var FULL_ALPHABET = "ABCDEFGHIJKLMNOPQRSTUVWXYZ";
|
|
570
607
|
var LOWER_ALPHABET = "abcdefghijklmnopqrstuvwxyz";
|
|
@@ -612,6 +649,78 @@ function toRoman(value) {
|
|
|
612
649
|
return out;
|
|
613
650
|
}
|
|
614
651
|
|
|
652
|
+
// src/core/seatNumbering.ts
|
|
653
|
+
function centerRank(n) {
|
|
654
|
+
const rank = new Array(n);
|
|
655
|
+
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);
|
|
656
|
+
return rank;
|
|
657
|
+
}
|
|
658
|
+
function seatLabelPart(row, i) {
|
|
659
|
+
const rawStart = row.seatLabelStart ?? 1;
|
|
660
|
+
const dir = row.seatNumbering?.direction ?? "ltr";
|
|
661
|
+
const step = row.seatNumbering?.step ?? 1;
|
|
662
|
+
const scheme = row.seatNumbering?.scheme ?? "decimal";
|
|
663
|
+
const prefix = row.seatNumbering?.prefix ?? "";
|
|
664
|
+
const endAt = row.seatNumbering?.endAt;
|
|
665
|
+
const n = row.seatCount;
|
|
666
|
+
if (scheme === "updown" || scheme === "updown-descending") {
|
|
667
|
+
const half = Math.ceil(n / 2);
|
|
668
|
+
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);
|
|
669
|
+
return `${prefix}${core2}`;
|
|
670
|
+
}
|
|
671
|
+
const effStep = scheme === "odd" || scheme === "even" ? 2 : step;
|
|
672
|
+
const start = endAt != null && Number.isFinite(endAt) ? endAt - (n - 1) * effStep : rawStart;
|
|
673
|
+
const p = dir === "center" ? centerRank(n)[i] : dir === "rtl" ? n - 1 - i : i;
|
|
674
|
+
let core;
|
|
675
|
+
switch (scheme) {
|
|
676
|
+
case "odd": {
|
|
677
|
+
const firstOdd = start % 2 === 1 ? start : start + 1;
|
|
678
|
+
core = String(firstOdd + p * 2);
|
|
679
|
+
break;
|
|
680
|
+
}
|
|
681
|
+
case "even": {
|
|
682
|
+
const firstEven = start % 2 === 0 ? start : start + 1;
|
|
683
|
+
core = String(firstEven + p * 2);
|
|
684
|
+
break;
|
|
685
|
+
}
|
|
686
|
+
case "roman":
|
|
687
|
+
core = toRoman(start + p * step);
|
|
688
|
+
break;
|
|
689
|
+
case "letters-upper":
|
|
690
|
+
core = toLetters(start + p * step, false);
|
|
691
|
+
break;
|
|
692
|
+
case "letters-lower":
|
|
693
|
+
core = toLetters(start + p * step, true);
|
|
694
|
+
break;
|
|
695
|
+
case "decimal":
|
|
696
|
+
default:
|
|
697
|
+
core = String(start + p * step);
|
|
698
|
+
break;
|
|
699
|
+
}
|
|
700
|
+
return `${prefix}${core}`;
|
|
701
|
+
}
|
|
702
|
+
function logicalNumberingRow(placement) {
|
|
703
|
+
return {
|
|
704
|
+
...placement.canonical,
|
|
705
|
+
seatCount: placement.totalSeats,
|
|
706
|
+
label: placement.displayLabel,
|
|
707
|
+
displayLabel: placement.displayLabel
|
|
708
|
+
};
|
|
709
|
+
}
|
|
710
|
+
function seatInventoryLabel(row, physicalIndex, override) {
|
|
711
|
+
return override?.label ?? `${row.label}-${seatLabelPart(row, physicalIndex)}`;
|
|
712
|
+
}
|
|
713
|
+
function seatDisplayLabel(row, physicalIndex, placement, override) {
|
|
714
|
+
if (override?.displayLabel) return override.displayLabel;
|
|
715
|
+
if (!placement) {
|
|
716
|
+
const prefix = row.displayLabel ?? row.label;
|
|
717
|
+
return `${prefix}-${seatLabelPart(row, physicalIndex)}`;
|
|
718
|
+
}
|
|
719
|
+
const numberingRow = logicalNumberingRow(placement);
|
|
720
|
+
const ordinal = placement.displayOffset + physicalIndex;
|
|
721
|
+
return `${placement.displayLabel}-${seatLabelPart(numberingRow, ordinal)}`;
|
|
722
|
+
}
|
|
723
|
+
|
|
615
724
|
// src/core/units.ts
|
|
616
725
|
var METRES_PER_CHART_UNIT = 0.55 / 24;
|
|
617
726
|
var CHART_UNITS_PER_METRE = 1 / METRES_PER_CHART_UNIT;
|
|
@@ -1047,70 +1156,19 @@ function rowInventoryCount(row) {
|
|
|
1047
1156
|
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));
|
|
1048
1157
|
return Math.max(0, row.seatCount - skipped.size);
|
|
1049
1158
|
}
|
|
1050
|
-
function
|
|
1051
|
-
const rank = new Array(n);
|
|
1052
|
-
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);
|
|
1053
|
-
return rank;
|
|
1054
|
-
}
|
|
1055
|
-
function seatLabelPart(row, i) {
|
|
1056
|
-
const rawStart = row.seatLabelStart ?? 1;
|
|
1057
|
-
const dir = row.seatNumbering?.direction ?? "ltr";
|
|
1058
|
-
const step = row.seatNumbering?.step ?? 1;
|
|
1059
|
-
const scheme = row.seatNumbering?.scheme ?? "decimal";
|
|
1060
|
-
const prefix = row.seatNumbering?.prefix ?? "";
|
|
1061
|
-
const endAt = row.seatNumbering?.endAt;
|
|
1062
|
-
const n = row.seatCount;
|
|
1063
|
-
if (scheme === "updown" || scheme === "updown-descending") {
|
|
1064
|
-
const half = Math.ceil(n / 2);
|
|
1065
|
-
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);
|
|
1066
|
-
return `${prefix}${core2}`;
|
|
1067
|
-
}
|
|
1068
|
-
const effStep = scheme === "odd" || scheme === "even" ? 2 : step;
|
|
1069
|
-
const start = endAt != null && Number.isFinite(endAt) ? endAt - (n - 1) * effStep : rawStart;
|
|
1070
|
-
const p = dir === "center" ? centerRank(n)[i] : dir === "rtl" ? n - 1 - i : i;
|
|
1071
|
-
let core;
|
|
1072
|
-
switch (scheme) {
|
|
1073
|
-
case "odd": {
|
|
1074
|
-
const firstOdd = start % 2 === 1 ? start : start + 1;
|
|
1075
|
-
core = String(firstOdd + p * 2);
|
|
1076
|
-
break;
|
|
1077
|
-
}
|
|
1078
|
-
case "even": {
|
|
1079
|
-
const firstEven = start % 2 === 0 ? start : start + 1;
|
|
1080
|
-
core = String(firstEven + p * 2);
|
|
1081
|
-
break;
|
|
1082
|
-
}
|
|
1083
|
-
case "roman":
|
|
1084
|
-
core = toRoman(start + p * step);
|
|
1085
|
-
break;
|
|
1086
|
-
case "letters-upper":
|
|
1087
|
-
core = toLetters(start + p * step, false);
|
|
1088
|
-
break;
|
|
1089
|
-
case "letters-lower":
|
|
1090
|
-
core = toLetters(start + p * step, true);
|
|
1091
|
-
break;
|
|
1092
|
-
case "decimal":
|
|
1093
|
-
default:
|
|
1094
|
-
core = String(start + p * step);
|
|
1095
|
-
break;
|
|
1096
|
-
}
|
|
1097
|
-
return `${prefix}${core}`;
|
|
1098
|
-
}
|
|
1099
|
-
function expandRowSlots(row) {
|
|
1159
|
+
function expandRowSlots(row, placement) {
|
|
1100
1160
|
const ov = overrideMap(row);
|
|
1101
1161
|
return rowSeatPositions(row).map((p, i) => {
|
|
1102
1162
|
const o = ov.get(i);
|
|
1103
1163
|
const accessibility = overrideAccessibility(o);
|
|
1104
|
-
const
|
|
1105
|
-
const inventoryLabel = o?.label ?? `${row.label}-${part}`;
|
|
1106
|
-
const displayPrefix = row.displayLabel ?? row.label;
|
|
1164
|
+
const inventoryLabel = seatInventoryLabel(row, i, o);
|
|
1107
1165
|
const commercial = { ...row.commercial, ...o?.commercial };
|
|
1108
1166
|
return {
|
|
1109
1167
|
index: i,
|
|
1110
1168
|
x: p.x + (o?.dx ?? 0),
|
|
1111
1169
|
y: p.y + (o?.dy ?? 0),
|
|
1112
1170
|
label: inventoryLabel,
|
|
1113
|
-
displayLabel:
|
|
1171
|
+
displayLabel: seatDisplayLabel(row, i, placement, o),
|
|
1114
1172
|
categoryKey: o?.categoryKey ?? row.categoryKey,
|
|
1115
1173
|
skipped: !!o?.skip,
|
|
1116
1174
|
accessible: accessibility.length > 0,
|
|
@@ -1411,39 +1469,7 @@ function stackFloors(doc, spread = 900) {
|
|
|
1411
1469
|
}
|
|
1412
1470
|
function expandFloorObjects(objects, zones, fallbackFocal, viewFallback) {
|
|
1413
1471
|
const out = [];
|
|
1414
|
-
const segmented =
|
|
1415
|
-
const grouped = /* @__PURE__ */ new Map();
|
|
1416
|
-
for (const object of objects) {
|
|
1417
|
-
if (object.type !== "row" || !object.segmentedRow) continue;
|
|
1418
|
-
const list = grouped.get(object.segmentedRow.groupId) ?? [];
|
|
1419
|
-
list.push(object);
|
|
1420
|
-
grouped.set(object.segmentedRow.groupId, list);
|
|
1421
|
-
}
|
|
1422
|
-
for (const [groupId, members] of grouped) {
|
|
1423
|
-
const ordered = members.slice().sort((left, right) => left.segmentedRow.componentIndex - right.segmentedRow.componentIndex);
|
|
1424
|
-
const expectedCount = ordered[0]?.segmentedRow?.componentCount ?? 0;
|
|
1425
|
-
const first = ordered[0]?.segmentedRow;
|
|
1426
|
-
if (!first) continue;
|
|
1427
|
-
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);
|
|
1428
|
-
if (!valid) continue;
|
|
1429
|
-
const totalSeats = ordered.reduce((sum, row) => sum + row.seatCount, 0);
|
|
1430
|
-
let adjacencyOffset = 0;
|
|
1431
|
-
let displayOffset = 0;
|
|
1432
|
-
for (const row of ordered) {
|
|
1433
|
-
if (row.segmentedRow.boundaryBefore === "break") adjacencyOffset += 1;
|
|
1434
|
-
segmented.set(row.id, {
|
|
1435
|
-
groupId,
|
|
1436
|
-
adjacencyOffset,
|
|
1437
|
-
displayOffset,
|
|
1438
|
-
displayLabel: first.displayLabel,
|
|
1439
|
-
totalSeats,
|
|
1440
|
-
canonical: ordered[0],
|
|
1441
|
-
viewFromSeatUrl: first.viewFromSeatUrl
|
|
1442
|
-
});
|
|
1443
|
-
adjacencyOffset += row.seatCount;
|
|
1444
|
-
displayOffset += row.seatCount;
|
|
1445
|
-
}
|
|
1446
|
-
}
|
|
1472
|
+
const segmented = resolveSegmentedRowGroups(objects);
|
|
1447
1473
|
for (const obj of objects) {
|
|
1448
1474
|
let seats = [];
|
|
1449
1475
|
if (obj.type === "row") seats = expandRow(obj);
|
|
@@ -1457,17 +1483,10 @@ function expandFloorObjects(objects, zones, fallbackFocal, viewFallback) {
|
|
|
1457
1483
|
for (const seat of seats) {
|
|
1458
1484
|
const physicalIndex = Number(seat.id.slice(seat.id.lastIndexOf(":") + 1));
|
|
1459
1485
|
if (!Number.isInteger(physicalIndex)) continue;
|
|
1460
|
-
const displayOrdinal = logical.displayOffset + physicalIndex;
|
|
1461
1486
|
seat.logicalRowId = logical.groupId;
|
|
1462
1487
|
seat.logicalSeatIndex = logical.adjacencyOffset + physicalIndex;
|
|
1463
1488
|
if (!overrides2.get(physicalIndex)?.displayLabel) {
|
|
1464
|
-
|
|
1465
|
-
...logical.canonical,
|
|
1466
|
-
seatCount: logical.totalSeats,
|
|
1467
|
-
label: logical.displayLabel,
|
|
1468
|
-
displayLabel: logical.displayLabel
|
|
1469
|
-
};
|
|
1470
|
-
seat.displayLabel = `${logical.displayLabel}-${seatLabelPart(numberingRow, displayOrdinal)}`;
|
|
1489
|
+
seat.displayLabel = seatDisplayLabel(obj, physicalIndex, logical);
|
|
1471
1490
|
}
|
|
1472
1491
|
seat.viewUrl ??= logical.viewFromSeatUrl;
|
|
1473
1492
|
}
|
|
@@ -2953,6 +2972,8 @@ var BLOCK_MELT_TOP = 0.9 * SEAT_LEGIBLE_SCALE;
|
|
|
2953
2972
|
var SEAT_FOCUS_SCALE = Math.max(SEAT_LEGIBLE_SCALE * 1.1, BLOCK_MELT_TOP);
|
|
2954
2973
|
var PAN_START_SLOP_PX = 8;
|
|
2955
2974
|
var GHOST_CLICK_MS = 700;
|
|
2975
|
+
var ROW_LABEL_CLEARANCE = 2;
|
|
2976
|
+
var ROW_LABEL_COLLISION_CELL = 64;
|
|
2956
2977
|
var ZONE_PROMINENT_SCALE = 0.55 * SECTION_PROMINENT_SCALE;
|
|
2957
2978
|
var MAX_LABELS = 700;
|
|
2958
2979
|
var MARQUEE_RING_CAP = 2500;
|
|
@@ -3039,6 +3060,44 @@ function opaqueColorHex(color) {
|
|
|
3039
3060
|
if (channels.some((channel) => !Number.isInteger(channel) || channel < 0 || channel > 255)) return null;
|
|
3040
3061
|
return `#${channels.map((channel) => channel.toString(16).padStart(2, "0")).join("")}`;
|
|
3041
3062
|
}
|
|
3063
|
+
function worldBoxesOverlap(a, b) {
|
|
3064
|
+
return a.x < b.x + b.width && b.x < a.x + a.width && a.y < b.y + b.height && b.y < a.y + a.height;
|
|
3065
|
+
}
|
|
3066
|
+
var WorldBoxIndex = class {
|
|
3067
|
+
constructor() {
|
|
3068
|
+
this.cells = /* @__PURE__ */ new Map();
|
|
3069
|
+
}
|
|
3070
|
+
insert(box) {
|
|
3071
|
+
for (const key of this.keys(box)) {
|
|
3072
|
+
const bucket = this.cells.get(key);
|
|
3073
|
+
if (bucket) bucket.push(box);
|
|
3074
|
+
else this.cells.set(key, [box]);
|
|
3075
|
+
}
|
|
3076
|
+
}
|
|
3077
|
+
collisionCount(box) {
|
|
3078
|
+
const seen = /* @__PURE__ */ new Set();
|
|
3079
|
+
let count = 0;
|
|
3080
|
+
for (const key of this.keys(box)) {
|
|
3081
|
+
for (const existing of this.cells.get(key) ?? []) {
|
|
3082
|
+
if (seen.has(existing)) continue;
|
|
3083
|
+
seen.add(existing);
|
|
3084
|
+
if (worldBoxesOverlap(box, existing)) count++;
|
|
3085
|
+
}
|
|
3086
|
+
}
|
|
3087
|
+
return count;
|
|
3088
|
+
}
|
|
3089
|
+
keys(box) {
|
|
3090
|
+
const x0 = Math.floor(box.x / ROW_LABEL_COLLISION_CELL);
|
|
3091
|
+
const x1 = Math.floor((box.x + Math.max(0, box.width)) / ROW_LABEL_COLLISION_CELL);
|
|
3092
|
+
const y0 = Math.floor(box.y / ROW_LABEL_COLLISION_CELL);
|
|
3093
|
+
const y1 = Math.floor((box.y + Math.max(0, box.height)) / ROW_LABEL_COLLISION_CELL);
|
|
3094
|
+
const keys = [];
|
|
3095
|
+
for (let y = y0; y <= y1; y++) {
|
|
3096
|
+
for (let x = x0; x <= x1; x++) keys.push(`${x}:${y}`);
|
|
3097
|
+
}
|
|
3098
|
+
return keys;
|
|
3099
|
+
}
|
|
3100
|
+
};
|
|
3042
3101
|
function overviewPalette(canvasBackground) {
|
|
3043
3102
|
return isLightColor(canvasBackground) ? {
|
|
3044
3103
|
sectionFill: LIGHT_OVERVIEW_SECTION_FILL,
|
|
@@ -3444,6 +3503,11 @@ var _SeatmapRenderer = class _SeatmapRenderer {
|
|
|
3444
3503
|
this.bounds = { x: 0, y: 0, width: 1, height: 1 };
|
|
3445
3504
|
this.cached = false;
|
|
3446
3505
|
this.dpr = Math.min(typeof window !== "undefined" ? window.devicePixelRatio || 1 : 1, 2);
|
|
3506
|
+
/** Current chart's buyer-visible image work. Failures are retained until ready(). */
|
|
3507
|
+
this.assetGeneration = 0;
|
|
3508
|
+
this.assetPromises = [];
|
|
3509
|
+
this.assetErrors = [];
|
|
3510
|
+
this.assetCancels = /* @__PURE__ */ new Set();
|
|
3447
3511
|
this.rafId = 0;
|
|
3448
3512
|
this.frames = 0;
|
|
3449
3513
|
this.lastFpsAt = 0;
|
|
@@ -3611,7 +3675,10 @@ var _SeatmapRenderer = class _SeatmapRenderer {
|
|
|
3611
3675
|
};
|
|
3612
3676
|
this.container = container;
|
|
3613
3677
|
this.opts = { maxSelection: 10, selectableStatuses: ["free"], ...options };
|
|
3678
|
+
this.exportMode = options.exportMode === true;
|
|
3614
3679
|
this.currency = options.currency;
|
|
3680
|
+
const previousPixelRatio = import_Core.Konva.pixelRatio;
|
|
3681
|
+
if (this.exportMode) this.dpr = 1;
|
|
3615
3682
|
import_Core.Konva.pixelRatio = this.dpr;
|
|
3616
3683
|
this.stage = new import_Stage.Stage({
|
|
3617
3684
|
container,
|
|
@@ -3620,19 +3687,21 @@ var _SeatmapRenderer = class _SeatmapRenderer {
|
|
|
3620
3687
|
draggable: false
|
|
3621
3688
|
// pan/pinch are ours, via pointer events
|
|
3622
3689
|
});
|
|
3623
|
-
|
|
3624
|
-
|
|
3625
|
-
|
|
3626
|
-
|
|
3627
|
-
|
|
3628
|
-
|
|
3629
|
-
|
|
3630
|
-
|
|
3631
|
-
container.
|
|
3632
|
-
|
|
3633
|
-
|
|
3634
|
-
|
|
3635
|
-
|
|
3690
|
+
if (!this.exportMode) {
|
|
3691
|
+
container.style.touchAction = "none";
|
|
3692
|
+
container.addEventListener("pointerdown", this.onPointerDown, { passive: false });
|
|
3693
|
+
container.addEventListener("pointermove", this.onPointerMove, { passive: false });
|
|
3694
|
+
container.addEventListener("pointerup", this.onPointerEnd, { passive: false });
|
|
3695
|
+
container.addEventListener("pointercancel", this.onPointerEnd, { passive: false });
|
|
3696
|
+
if (container.tabIndex < 0) container.tabIndex = 0;
|
|
3697
|
+
container.setAttribute("role", "application");
|
|
3698
|
+
if (!container.getAttribute("aria-label")) {
|
|
3699
|
+
container.setAttribute("aria-label", t("map.aria"));
|
|
3700
|
+
}
|
|
3701
|
+
container.addEventListener("keydown", this.onKeyDown);
|
|
3702
|
+
}
|
|
3703
|
+
this.bgLayer = new import_Layer.Layer({ listening: !this.exportMode });
|
|
3704
|
+
this.seatLayer = new import_Layer.Layer({ listening: !this.exportMode });
|
|
3636
3705
|
this.overlayLayer = new import_Layer.Layer({ listening: false });
|
|
3637
3706
|
this.labelGroup = new import_Group.Group({ listening: false });
|
|
3638
3707
|
this.overlayLayer.add(this.labelGroup);
|
|
@@ -3662,12 +3731,15 @@ var _SeatmapRenderer = class _SeatmapRenderer {
|
|
|
3662
3731
|
});
|
|
3663
3732
|
this.overlayLayer.add(this.focusRing);
|
|
3664
3733
|
this.stage.add(this.bgLayer, this.seatLayer, this.overlayLayer);
|
|
3665
|
-
this.
|
|
3666
|
-
this.
|
|
3734
|
+
if (this.exportMode) import_Core.Konva.pixelRatio = previousPixelRatio;
|
|
3735
|
+
if (!this.exportMode) {
|
|
3736
|
+
this.wireInteraction();
|
|
3737
|
+
this.startFpsLoop();
|
|
3738
|
+
}
|
|
3667
3739
|
if (false) {
|
|
3668
3740
|
window.__seatmap = this;
|
|
3669
3741
|
}
|
|
3670
|
-
if (typeof ResizeObserver !== "undefined") {
|
|
3742
|
+
if (!this.exportMode && typeof ResizeObserver !== "undefined") {
|
|
3671
3743
|
this.resizeObs = new ResizeObserver(() => this.handleResize());
|
|
3672
3744
|
this.resizeObs.observe(container);
|
|
3673
3745
|
}
|
|
@@ -3690,6 +3762,7 @@ var _SeatmapRenderer = class _SeatmapRenderer {
|
|
|
3690
3762
|
// ---- ISeatmapRenderer -----------------------------------------------------
|
|
3691
3763
|
setChart(doc, opts) {
|
|
3692
3764
|
if (doc !== this.chartDoc) this.stacked = false;
|
|
3765
|
+
this.cancelAssetLoads();
|
|
3693
3766
|
this.chartDoc = doc;
|
|
3694
3767
|
this.activeFloorId = opts?.floorId ?? floorsOf(doc)[0].id;
|
|
3695
3768
|
this.objectFloor.clear();
|
|
@@ -3703,7 +3776,7 @@ var _SeatmapRenderer = class _SeatmapRenderer {
|
|
|
3703
3776
|
this.focusDimOverlay?.destroy();
|
|
3704
3777
|
this.focusDimOverlay = null;
|
|
3705
3778
|
this.seatLayer.clearCache();
|
|
3706
|
-
this.seatLayer.listening(
|
|
3779
|
+
this.seatLayer.listening(!this.exportMode);
|
|
3707
3780
|
this.seatLayer.destroyChildren();
|
|
3708
3781
|
this.unsectionedSeatGroup = null;
|
|
3709
3782
|
this.labelGroup.destroyChildren();
|
|
@@ -3803,7 +3876,7 @@ var _SeatmapRenderer = class _SeatmapRenderer {
|
|
|
3803
3876
|
this.isoCentre = { x: this.bounds.x + this.bounds.width / 2, y: this.bounds.y + this.bounds.height / 2 };
|
|
3804
3877
|
this.buildPerspectiveProjection(view);
|
|
3805
3878
|
this.renderBackground(view);
|
|
3806
|
-
this.unsectionedSeatGroup = new import_Group.Group({ listening:
|
|
3879
|
+
this.unsectionedSeatGroup = new import_Group.Group({ listening: !this.exportMode });
|
|
3807
3880
|
this.seatLayer.add(this.unsectionedSeatGroup);
|
|
3808
3881
|
this.renderSeats();
|
|
3809
3882
|
this.buildRowLabelPlan(view);
|
|
@@ -4438,9 +4511,9 @@ var _SeatmapRenderer = class _SeatmapRenderer {
|
|
|
4438
4511
|
paintGAStateForView() {
|
|
4439
4512
|
for (const ga of this.gaById.values()) {
|
|
4440
4513
|
const filteredOut = Boolean(this.categoryFilter && !this.categoryFilter.has(ga.categoryKey));
|
|
4441
|
-
const overviewHidden = ga.sectionId != null && this.effScale() < CACHE_THRESHOLD;
|
|
4514
|
+
const overviewHidden = !this.exportMode && ga.sectionId != null && this.effScale() < CACHE_THRESHOLD;
|
|
4442
4515
|
ga.polygon.opacity(overviewHidden ? 0 : this.gaCategoryDimmed(ga.categoryKey) ? GA_FILL_OPACITY * 0.08 : GA_FILL_OPACITY);
|
|
4443
|
-
ga.polygon.listening(!overviewHidden && !filteredOut);
|
|
4516
|
+
ga.polygon.listening(!this.exportMode && !overviewHidden && !filteredOut);
|
|
4444
4517
|
}
|
|
4445
4518
|
}
|
|
4446
4519
|
/** Frame the currently available inventory that survived a buyer price
|
|
@@ -4897,6 +4970,7 @@ var _SeatmapRenderer = class _SeatmapRenderer {
|
|
|
4897
4970
|
}
|
|
4898
4971
|
destroy() {
|
|
4899
4972
|
this.destroyed = true;
|
|
4973
|
+
this.cancelAssetLoads();
|
|
4900
4974
|
if (this.rafId) cancelAnimationFrame(this.rafId);
|
|
4901
4975
|
if (this.isoRaf) cancelAnimationFrame(this.isoRaf);
|
|
4902
4976
|
if (this.glideRaf) cancelAnimationFrame(this.glideRaf);
|
|
@@ -4913,6 +4987,9 @@ var _SeatmapRenderer = class _SeatmapRenderer {
|
|
|
4913
4987
|
// ---- rendering ------------------------------------------------------------
|
|
4914
4988
|
/** Theme font stack for all rendered text (falls back to Inter). */
|
|
4915
4989
|
labelFont() {
|
|
4990
|
+
if (this.exportMode) {
|
|
4991
|
+
return this.theme.fontFamily?.toLowerCase().includes("jetbrains") ? "JetBrains Mono, monospace" : "Inter, sans-serif";
|
|
4992
|
+
}
|
|
4916
4993
|
return this.theme.fontFamily || "Inter, sans-serif";
|
|
4917
4994
|
}
|
|
4918
4995
|
/**
|
|
@@ -4960,7 +5037,7 @@ var _SeatmapRenderer = class _SeatmapRenderer {
|
|
|
4960
5037
|
* rung. Overview caching always restores all groups first, so panning a
|
|
4961
5038
|
* cached whole-venue bitmap can never reveal missing inventory. */
|
|
4962
5039
|
updateSeatGroupVisibility() {
|
|
4963
|
-
const liveSeats = this.effScale() >= CACHE_THRESHOLD;
|
|
5040
|
+
const liveSeats = this.exportMode || this.effScale() >= CACHE_THRESHOLD;
|
|
4964
5041
|
const deferPerspectiveSeatReveal = this.viewMode === "perspective" && this.glideInProgress && this.seats.length > 2500;
|
|
4965
5042
|
const padding = 96;
|
|
4966
5043
|
const width = this.stage.width();
|
|
@@ -5597,11 +5674,67 @@ var _SeatmapRenderer = class _SeatmapRenderer {
|
|
|
5597
5674
|
}
|
|
5598
5675
|
}
|
|
5599
5676
|
}
|
|
5677
|
+
/**
|
|
5678
|
+
* Track an image through decode without ever leaving a rejected promise
|
|
5679
|
+
* unobserved. Ordinary picker rendering remains best-effort; export ready()
|
|
5680
|
+
* turns the retained failures into one actionable error.
|
|
5681
|
+
*/
|
|
5682
|
+
trackAssetImage(image, source, label, onReady) {
|
|
5683
|
+
const generation = this.assetGeneration;
|
|
5684
|
+
if (/^https?:\/\//i.test(source)) image.crossOrigin = "anonymous";
|
|
5685
|
+
const work = new Promise((resolve) => {
|
|
5686
|
+
let settled = false;
|
|
5687
|
+
const finish = () => {
|
|
5688
|
+
if (settled) return;
|
|
5689
|
+
settled = true;
|
|
5690
|
+
this.assetCancels.delete(cancel);
|
|
5691
|
+
resolve();
|
|
5692
|
+
};
|
|
5693
|
+
const cancel = () => {
|
|
5694
|
+
image.onload = null;
|
|
5695
|
+
image.onerror = null;
|
|
5696
|
+
try {
|
|
5697
|
+
image.removeAttribute("src");
|
|
5698
|
+
image.src = "";
|
|
5699
|
+
} catch {
|
|
5700
|
+
}
|
|
5701
|
+
finish();
|
|
5702
|
+
};
|
|
5703
|
+
this.assetCancels.add(cancel);
|
|
5704
|
+
image.onload = () => {
|
|
5705
|
+
if (!this.exportMode) {
|
|
5706
|
+
if (generation === this.assetGeneration) onReady();
|
|
5707
|
+
finish();
|
|
5708
|
+
return;
|
|
5709
|
+
}
|
|
5710
|
+
const decoded = typeof image.decode === "function" ? image.decode() : Promise.resolve();
|
|
5711
|
+
void decoded.then(() => {
|
|
5712
|
+
if (generation === this.assetGeneration) onReady();
|
|
5713
|
+
}).catch(() => {
|
|
5714
|
+
if (generation === this.assetGeneration) this.assetErrors.push(`${label} could not be decoded.`);
|
|
5715
|
+
}).finally(finish);
|
|
5716
|
+
};
|
|
5717
|
+
image.onerror = () => {
|
|
5718
|
+
if (generation === this.assetGeneration) this.assetErrors.push(`${label} could not be loaded.`);
|
|
5719
|
+
finish();
|
|
5720
|
+
};
|
|
5721
|
+
image.src = source;
|
|
5722
|
+
});
|
|
5723
|
+
this.assetPromises.push(work);
|
|
5724
|
+
}
|
|
5725
|
+
/** Cancel and release every image retained by the previous chart/export. */
|
|
5726
|
+
cancelAssetLoads() {
|
|
5727
|
+
this.assetGeneration++;
|
|
5728
|
+
for (const cancel of [...this.assetCancels]) cancel();
|
|
5729
|
+
this.assetCancels.clear();
|
|
5730
|
+
this.assetPromises = [];
|
|
5731
|
+
this.assetErrors = [];
|
|
5732
|
+
}
|
|
5600
5733
|
/** Organizer floor-plan photo, dimmed, at the very bottom of the bg layer. */
|
|
5601
5734
|
renderBackgroundImage(bg) {
|
|
5602
5735
|
if (!bg.url || bg.visible === false) return;
|
|
5603
5736
|
const img = new window.Image();
|
|
5604
|
-
img.
|
|
5737
|
+
this.trackAssetImage(img, bg.url, "Buyer background image", () => {
|
|
5605
5738
|
const natW = img.naturalWidth || 4;
|
|
5606
5739
|
const natH = img.naturalHeight || 3;
|
|
5607
5740
|
const rawCrop = bg.crop ?? { x: 0, y: 0, width: 1, height: 1 };
|
|
@@ -5636,8 +5769,7 @@ var _SeatmapRenderer = class _SeatmapRenderer {
|
|
|
5636
5769
|
this.bgLayer.add(node);
|
|
5637
5770
|
node.moveToBottom();
|
|
5638
5771
|
this.bgLayer.batchDraw();
|
|
5639
|
-
};
|
|
5640
|
-
img.src = bg.url;
|
|
5772
|
+
});
|
|
5641
5773
|
}
|
|
5642
5774
|
/**
|
|
5643
5775
|
* A decor graphic (rink / court / stage art). The KImage node is added to the
|
|
@@ -5663,12 +5795,11 @@ var _SeatmapRenderer = class _SeatmapRenderer {
|
|
|
5663
5795
|
});
|
|
5664
5796
|
if (obj.layer === "foreground") this.fgDecorGroup.add(node);
|
|
5665
5797
|
else this.bgLayer.add(node);
|
|
5666
|
-
img.
|
|
5798
|
+
this.trackAssetImage(img, obj.href, `Decor image \u201C${obj.label || obj.id}\u201D`, () => {
|
|
5667
5799
|
const layer = node.getLayer();
|
|
5668
5800
|
if (!layer) return;
|
|
5669
5801
|
layer.batchDraw();
|
|
5670
|
-
};
|
|
5671
|
-
img.src = obj.href;
|
|
5802
|
+
});
|
|
5672
5803
|
}
|
|
5673
5804
|
renderTable(obj) {
|
|
5674
5805
|
if (obj.shape === "round") {
|
|
@@ -7331,6 +7462,7 @@ var _SeatmapRenderer = class _SeatmapRenderer {
|
|
|
7331
7462
|
}
|
|
7332
7463
|
/** rAF-coalesced `onViewChange` — at most one host callback per animation frame. */
|
|
7333
7464
|
scheduleViewChange() {
|
|
7465
|
+
if (this.exportMode) return;
|
|
7334
7466
|
if (this.viewChangeRaf) return;
|
|
7335
7467
|
this.viewChangeRaf = requestAnimationFrame(() => {
|
|
7336
7468
|
this.viewChangeRaf = 0;
|
|
@@ -7338,7 +7470,7 @@ var _SeatmapRenderer = class _SeatmapRenderer {
|
|
|
7338
7470
|
});
|
|
7339
7471
|
}
|
|
7340
7472
|
updateLOD() {
|
|
7341
|
-
const scale = this.effScale();
|
|
7473
|
+
const scale = this.exportMode ? Math.max(this.effScale(), BLOCK_MELT_TOP + 0.01, LABEL_SCALE) : this.effScale();
|
|
7342
7474
|
const focalScale = Math.max(scale, 1e-4);
|
|
7343
7475
|
for (const [label, targetPx] of this.primaryFocalLabels) {
|
|
7344
7476
|
this.sizeLabel(label, targetPx / focalScale, label.y());
|
|
@@ -7347,7 +7479,7 @@ var _SeatmapRenderer = class _SeatmapRenderer {
|
|
|
7347
7479
|
else if (this.primaryFocalLabels.size) this.bgLayer.batchDraw();
|
|
7348
7480
|
this.paintGAStateForView();
|
|
7349
7481
|
this.updateAccessGlyphs(scale);
|
|
7350
|
-
const shouldCache = scale < CACHE_THRESHOLD;
|
|
7482
|
+
const shouldCache = !this.exportMode && scale < CACHE_THRESHOLD;
|
|
7351
7483
|
const suppressPerspectiveSeatCache = this.viewMode === "perspective" && this.hasSections && this.seats.length > 2500 && shouldCache;
|
|
7352
7484
|
if (suppressPerspectiveSeatCache) {
|
|
7353
7485
|
this.seatLayer.listening(false);
|
|
@@ -7357,7 +7489,7 @@ var _SeatmapRenderer = class _SeatmapRenderer {
|
|
|
7357
7489
|
this.cacheSeatLayer();
|
|
7358
7490
|
} else if (!shouldCache && (this.cached || !this.seatLayer.listening())) {
|
|
7359
7491
|
if (this.cached) this.releaseSeatLayerBitmap();
|
|
7360
|
-
this.seatLayer.listening(
|
|
7492
|
+
this.seatLayer.listening(!this.exportMode);
|
|
7361
7493
|
this.cached = false;
|
|
7362
7494
|
this.seatLayer.batchDraw();
|
|
7363
7495
|
}
|
|
@@ -7425,8 +7557,103 @@ var _SeatmapRenderer = class _SeatmapRenderer {
|
|
|
7425
7557
|
this.seatLayer.draw();
|
|
7426
7558
|
this.overlayLayer.draw();
|
|
7427
7559
|
}
|
|
7560
|
+
/**
|
|
7561
|
+
* Resolve every buyer-visible image requested by the current chart. Export
|
|
7562
|
+
* callers get a hard error with the affected layer name; the interactive
|
|
7563
|
+
* renderer never calls this and retains its existing best-effort behaviour.
|
|
7564
|
+
*/
|
|
7565
|
+
async ready() {
|
|
7566
|
+
const generation = this.assetGeneration;
|
|
7567
|
+
const fontSet = typeof document !== "undefined" && "fonts" in document ? document.fonts : null;
|
|
7568
|
+
await Promise.all([
|
|
7569
|
+
...this.assetPromises.slice(),
|
|
7570
|
+
...fontSet ? [
|
|
7571
|
+
fontSet.ready.then(() => void 0),
|
|
7572
|
+
fontSet.load("700 24px Inter").then(() => void 0),
|
|
7573
|
+
fontSet.load('600 12px "JetBrains Mono"').then(() => void 0)
|
|
7574
|
+
] : []
|
|
7575
|
+
]);
|
|
7576
|
+
if (generation !== this.assetGeneration) {
|
|
7577
|
+
throw new Error("The chart changed while its export assets were loading.");
|
|
7578
|
+
}
|
|
7579
|
+
if (this.assetErrors.length) {
|
|
7580
|
+
throw new Error(`Export stopped because ${this.assetErrors.join(" ")}`);
|
|
7581
|
+
}
|
|
7582
|
+
if (this.exportMode) {
|
|
7583
|
+
this.fitExportContent();
|
|
7584
|
+
this.forceDraw();
|
|
7585
|
+
}
|
|
7586
|
+
}
|
|
7587
|
+
/** Exact visible scene bounds in chart coordinates after fonts/assets exist. */
|
|
7588
|
+
exportSceneBounds() {
|
|
7589
|
+
const rects = [this.bgLayer, this.seatLayer, this.overlayLayer].map((layer) => layer.getClientRect({ relativeTo: this.stage })).filter((rect) => rect.width > 0 && rect.height > 0);
|
|
7590
|
+
if (!rects.length) return this.bounds;
|
|
7591
|
+
const minX = Math.min(...rects.map((rect) => rect.x));
|
|
7592
|
+
const minY = Math.min(...rects.map((rect) => rect.y));
|
|
7593
|
+
const maxX = Math.max(...rects.map((rect) => rect.x + rect.width));
|
|
7594
|
+
const maxY = Math.max(...rects.map((rect) => rect.y + rect.height));
|
|
7595
|
+
return {
|
|
7596
|
+
x: minX,
|
|
7597
|
+
y: minY,
|
|
7598
|
+
width: Math.max(1, maxX - minX),
|
|
7599
|
+
height: Math.max(1, maxY - minY)
|
|
7600
|
+
};
|
|
7601
|
+
}
|
|
7602
|
+
/** Fit exact rendered nodes, including rotated text/decor, with pixel padding. */
|
|
7603
|
+
fitExportContent() {
|
|
7604
|
+
if (!this.exportMode) return;
|
|
7605
|
+
const width = this.stage.width();
|
|
7606
|
+
const height = this.stage.height();
|
|
7607
|
+
const padding = Math.max(12, Math.min(36, Math.min(width, height) * 0.025));
|
|
7608
|
+
const fit = () => {
|
|
7609
|
+
const bounds2 = this.exportSceneBounds();
|
|
7610
|
+
const availableWidth = Math.max(1, width - padding * 2);
|
|
7611
|
+
const availableHeight = Math.max(1, height - padding * 2);
|
|
7612
|
+
const scale = Math.min(availableWidth / bounds2.width, availableHeight / bounds2.height) || 1;
|
|
7613
|
+
this.fitScale = scale;
|
|
7614
|
+
this.stage.scale({ x: scale, y: scale });
|
|
7615
|
+
this.stage.position({
|
|
7616
|
+
x: (width - bounds2.width * scale) / 2 - bounds2.x * scale,
|
|
7617
|
+
y: (height - bounds2.height * scale) / 2 - bounds2.y * scale
|
|
7618
|
+
});
|
|
7619
|
+
this.afterViewChange();
|
|
7620
|
+
};
|
|
7621
|
+
fit();
|
|
7622
|
+
fit();
|
|
7623
|
+
}
|
|
7624
|
+
/**
|
|
7625
|
+
* Capture a clean, opaque fixed-size canvas. The CSS host background is
|
|
7626
|
+
* painted explicitly because Konva layers themselves are transparent.
|
|
7627
|
+
*/
|
|
7628
|
+
captureCanvas() {
|
|
7629
|
+
if (!this.exportMode) {
|
|
7630
|
+
throw new Error("captureCanvas() is available only on a renderer created with exportMode.");
|
|
7631
|
+
}
|
|
7632
|
+
this.forceDraw();
|
|
7633
|
+
let scene;
|
|
7634
|
+
try {
|
|
7635
|
+
scene = this.stage.toCanvas({ pixelRatio: 1, imageSmoothingEnabled: true });
|
|
7636
|
+
} catch (error) {
|
|
7637
|
+
const detail = error instanceof Error ? error.message : String(error);
|
|
7638
|
+
throw new Error(`The chart canvas could not be captured. A buyer-visible image may block export. ${detail}`);
|
|
7639
|
+
}
|
|
7640
|
+
const canvas = document.createElement("canvas");
|
|
7641
|
+
canvas.width = this.stage.width();
|
|
7642
|
+
canvas.height = this.stage.height();
|
|
7643
|
+
const context = canvas.getContext("2d");
|
|
7644
|
+
if (!context) throw new Error("Canvas 2D is unavailable; this browser cannot create an export.");
|
|
7645
|
+
context.fillStyle = this.canvasBackground;
|
|
7646
|
+
context.fillRect(0, 0, canvas.width, canvas.height);
|
|
7647
|
+
try {
|
|
7648
|
+
context.drawImage(scene, 0, 0);
|
|
7649
|
+
} finally {
|
|
7650
|
+
scene.width = 1;
|
|
7651
|
+
scene.height = 1;
|
|
7652
|
+
}
|
|
7653
|
+
return canvas;
|
|
7654
|
+
}
|
|
7428
7655
|
updateFreeTextVisibility() {
|
|
7429
|
-
const effectiveScale = this.effScale();
|
|
7656
|
+
const effectiveScale = this.exportMode ? Math.max(this.effScale(), LABEL_SCALE) : this.effScale();
|
|
7430
7657
|
for (const { objectId, node, categoryKey, kind } of this.freeTextById.values()) {
|
|
7431
7658
|
const gaDimmed = categoryKey != null && (kind === "ga-label" || kind === "ga-capacity") && this.gaCategoryDimmed(categoryKey);
|
|
7432
7659
|
const gaOverviewHidden = objectId != null && (kind === "ga-label" || kind === "ga-capacity") && this.gaById.get(objectId)?.sectionId != null && effectiveScale < CACHE_THRESHOLD;
|
|
@@ -7471,19 +7698,90 @@ var _SeatmapRenderer = class _SeatmapRenderer {
|
|
|
7471
7698
|
const rotation = presentation?.rotation ?? 0;
|
|
7472
7699
|
const text = obj.segmentedRow?.displayLabel ?? obj.displayLabel ?? obj.label;
|
|
7473
7700
|
const opacity = this.objectFilteredOut(obj) ? 0.15 : 1;
|
|
7474
|
-
const push = (x, y) => this.rowLabelPlan.push({
|
|
7701
|
+
const push = (x, y, automaticAway) => this.rowLabelPlan.push({
|
|
7702
|
+
text,
|
|
7703
|
+
x,
|
|
7704
|
+
y,
|
|
7705
|
+
rotation,
|
|
7706
|
+
fontSize,
|
|
7707
|
+
ink,
|
|
7708
|
+
opacity,
|
|
7709
|
+
...automaticAway ? { automaticAway } : {}
|
|
7710
|
+
});
|
|
7475
7711
|
if (presentation?.position) {
|
|
7476
7712
|
push(presentation.position.x, presentation.position.y);
|
|
7477
7713
|
continue;
|
|
7478
7714
|
}
|
|
7479
|
-
const radians = obj.rotation * Math.PI / 180;
|
|
7480
|
-
const along = { x: Math.cos(radians), y: Math.sin(radians) };
|
|
7481
7715
|
const gap = this.seatR + 12;
|
|
7482
7716
|
const preset = presentation?.positionPreset ?? "start";
|
|
7483
|
-
if (preset === "start" || preset === "both")
|
|
7484
|
-
|
|
7717
|
+
if (preset === "start" || preset === "both") {
|
|
7718
|
+
const radians = obj.rotation * Math.PI / 180;
|
|
7719
|
+
const away = { x: -Math.cos(radians), y: -Math.sin(radians) };
|
|
7720
|
+
push(first.x + away.x * gap, first.y + away.y * gap, away);
|
|
7721
|
+
}
|
|
7722
|
+
if (preset === "end" || preset === "both") {
|
|
7723
|
+
const radians = (obj.rotation + obj.curve) * Math.PI / 180;
|
|
7724
|
+
const away = { x: Math.cos(radians), y: Math.sin(radians) };
|
|
7725
|
+
push(last.x + away.x * gap, last.y + away.y * gap, away);
|
|
7726
|
+
}
|
|
7485
7727
|
}
|
|
7486
7728
|
}
|
|
7729
|
+
/** Axis-aligned world bounds of a centred, potentially rotated row label. */
|
|
7730
|
+
rowLabelBox(label, position, padding = ROW_LABEL_CLEARANCE) {
|
|
7731
|
+
const radians = label.rotation() * Math.PI / 180;
|
|
7732
|
+
const cos = Math.abs(Math.cos(radians));
|
|
7733
|
+
const sin = Math.abs(Math.sin(radians));
|
|
7734
|
+
const halfWidth = (label.width() * cos + label.height() * sin) / 2 + padding;
|
|
7735
|
+
const halfHeight = (label.width() * sin + label.height() * cos) / 2 + padding;
|
|
7736
|
+
return {
|
|
7737
|
+
x: position.x - halfWidth,
|
|
7738
|
+
y: position.y - halfHeight,
|
|
7739
|
+
width: halfWidth * 2,
|
|
7740
|
+
height: halfHeight * 2
|
|
7741
|
+
};
|
|
7742
|
+
}
|
|
7743
|
+
/**
|
|
7744
|
+
* Keep an automatic start/end label out of seat markers and already-painted
|
|
7745
|
+
* text. The authored point is never altered. Candidates are deliberately
|
|
7746
|
+
* bounded and deterministic: the closest clear offset wins; if a chart is so
|
|
7747
|
+
* dense that none is clear, the least-colliding candidate wins so the required
|
|
7748
|
+
* row label remains present rather than being silently dropped.
|
|
7749
|
+
*/
|
|
7750
|
+
resolveAutomaticRowLabelPosition(label, plan, occupiedText) {
|
|
7751
|
+
const base = { x: plan.x, y: plan.y };
|
|
7752
|
+
const away = plan.automaticAway;
|
|
7753
|
+
if (!away) return base;
|
|
7754
|
+
const normal = { x: -away.y, y: away.x };
|
|
7755
|
+
const step = Math.max(7, Math.min(16, plan.fontSize * 0.75));
|
|
7756
|
+
const offsets = [{ x: 0, y: 0 }];
|
|
7757
|
+
for (let ring = 1; ring <= 4; ring++) {
|
|
7758
|
+
const distance = step * ring;
|
|
7759
|
+
offsets.push(
|
|
7760
|
+
{ x: normal.x * distance, y: normal.y * distance },
|
|
7761
|
+
{ x: -normal.x * distance, y: -normal.y * distance },
|
|
7762
|
+
{ x: away.x * distance, y: away.y * distance },
|
|
7763
|
+
{ x: (away.x + normal.x) * distance, y: (away.y + normal.y) * distance },
|
|
7764
|
+
{ x: (away.x - normal.x) * distance, y: (away.y - normal.y) * distance }
|
|
7765
|
+
);
|
|
7766
|
+
}
|
|
7767
|
+
let best = base;
|
|
7768
|
+
let bestCollisions = Infinity;
|
|
7769
|
+
let bestDistance = Infinity;
|
|
7770
|
+
for (const offset of offsets) {
|
|
7771
|
+
const candidate = { x: base.x + offset.x, y: base.y + offset.y };
|
|
7772
|
+
const box = this.rowLabelBox(label, candidate);
|
|
7773
|
+
const seatCollisions = this.seatIndex ? queryRect(this.seatIndex, box, { mode: "overlap" }).length : 0;
|
|
7774
|
+
const collisions = seatCollisions + occupiedText.collisionCount(box);
|
|
7775
|
+
const distance = Math.hypot(offset.x, offset.y);
|
|
7776
|
+
if (collisions < bestCollisions || collisions === bestCollisions && distance < bestDistance) {
|
|
7777
|
+
best = candidate;
|
|
7778
|
+
bestCollisions = collisions;
|
|
7779
|
+
bestDistance = distance;
|
|
7780
|
+
}
|
|
7781
|
+
if (collisions === 0) break;
|
|
7782
|
+
}
|
|
7783
|
+
return best;
|
|
7784
|
+
}
|
|
7487
7785
|
/** Row labels never carry status; the buyer just dims them under the same
|
|
7488
7786
|
* category/price filters that dim their seats. */
|
|
7489
7787
|
objectFilteredOut(obj) {
|
|
@@ -7492,7 +7790,7 @@ var _SeatmapRenderer = class _SeatmapRenderer {
|
|
|
7492
7790
|
return Boolean(this.categoryHighlight && key !== this.categoryHighlight || this.categoryFilter && !this.categoryFilter.has(key));
|
|
7493
7791
|
}
|
|
7494
7792
|
updateLabels() {
|
|
7495
|
-
const effectiveScale = this.effScale();
|
|
7793
|
+
const effectiveScale = this.exportMode ? Math.max(this.effScale(), LABEL_SCALE) : this.effScale();
|
|
7496
7794
|
const show = effectiveScale >= LABEL_SCALE;
|
|
7497
7795
|
for (const [id, label] of this.boothLabelById) {
|
|
7498
7796
|
const shape = this.circleById.get(id);
|
|
@@ -7583,6 +7881,21 @@ var _SeatmapRenderer = class _SeatmapRenderer {
|
|
|
7583
7881
|
this.seatLabelById.set(seat.id, t2);
|
|
7584
7882
|
if (++count >= MAX_LABELS) break;
|
|
7585
7883
|
}
|
|
7884
|
+
const occupiedText = new WorldBoxIndex();
|
|
7885
|
+
for (const { node } of this.freeTextById.values()) {
|
|
7886
|
+
if (!node.isVisible()) continue;
|
|
7887
|
+
const box = node.getClientRect({
|
|
7888
|
+
relativeTo: this.bgLayer,
|
|
7889
|
+
skipShadow: true,
|
|
7890
|
+
skipStroke: true
|
|
7891
|
+
});
|
|
7892
|
+
occupiedText.insert({
|
|
7893
|
+
x: box.x - ROW_LABEL_CLEARANCE,
|
|
7894
|
+
y: box.y - ROW_LABEL_CLEARANCE,
|
|
7895
|
+
width: box.width + ROW_LABEL_CLEARANCE * 2,
|
|
7896
|
+
height: box.height + ROW_LABEL_CLEARANCE * 2
|
|
7897
|
+
});
|
|
7898
|
+
}
|
|
7586
7899
|
for (const rl of this.rowLabelPlan) {
|
|
7587
7900
|
const screen = this.worldToScreen({ x: rl.x, y: rl.y });
|
|
7588
7901
|
if (screen.x < -40 || screen.x > this.stage.width() + 40 || screen.y < -40 || screen.y > this.stage.height() + 40) continue;
|
|
@@ -7605,6 +7918,10 @@ var _SeatmapRenderer = class _SeatmapRenderer {
|
|
|
7605
7918
|
});
|
|
7606
7919
|
t2.offsetX(t2.width() / 2);
|
|
7607
7920
|
t2.offsetY(t2.height() / 2);
|
|
7921
|
+
const resolved = this.resolveAutomaticRowLabelPosition(t2, rl, occupiedText);
|
|
7922
|
+
t2.position(resolved);
|
|
7923
|
+
t2.setAttr("rowLabel", true);
|
|
7924
|
+
occupiedText.insert(this.rowLabelBox(t2, resolved));
|
|
7608
7925
|
this.labelGroup.add(t2);
|
|
7609
7926
|
}
|
|
7610
7927
|
if (this.isoT > 0 && this.viewMode !== "perspective") this.applyUprightLabels();
|
|
@@ -9208,7 +9525,8 @@ var PickerController = class {
|
|
|
9208
9525
|
if (!url) return;
|
|
9209
9526
|
let ws;
|
|
9210
9527
|
try {
|
|
9211
|
-
|
|
9528
|
+
const protocols = this.api.socketProtocols?.(this.key);
|
|
9529
|
+
ws = protocols && protocols.length ? new WebSocket(url, protocols) : new WebSocket(url);
|
|
9212
9530
|
} catch {
|
|
9213
9531
|
this.scheduleReconnect();
|
|
9214
9532
|
return;
|
|
@@ -10520,7 +10838,6 @@ async function loadLocale(code) {
|
|
|
10520
10838
|
rowInventoryCount,
|
|
10521
10839
|
rowSeatPositions,
|
|
10522
10840
|
seatCommercialMeta,
|
|
10523
|
-
seatLabelPart,
|
|
10524
10841
|
sectionGeometry,
|
|
10525
10842
|
setLocale,
|
|
10526
10843
|
setMoneyLocale,
|