@seatlayer/core 0.35.0 → 0.36.1
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.js
CHANGED
|
@@ -33,13 +33,12 @@ import {
|
|
|
33
33
|
rowInventoryCount,
|
|
34
34
|
rowSeatPositions,
|
|
35
35
|
seatCommercialMeta,
|
|
36
|
-
seatLabelPart,
|
|
37
36
|
sectionElevationTier,
|
|
38
37
|
sectionGeometry,
|
|
39
38
|
stackFloors,
|
|
40
39
|
tableInventoryCount,
|
|
41
40
|
tableSeatCountsBySide
|
|
42
|
-
} from "./chunk-
|
|
41
|
+
} from "./chunk-5MRJCIZP.js";
|
|
43
42
|
|
|
44
43
|
// src/core/ga.ts
|
|
45
44
|
var PREFIX = "__sl_ga__";
|
|
@@ -1385,6 +1384,8 @@ var BLOCK_MELT_TOP = 0.9 * SEAT_LEGIBLE_SCALE;
|
|
|
1385
1384
|
var SEAT_FOCUS_SCALE = Math.max(SEAT_LEGIBLE_SCALE * 1.1, BLOCK_MELT_TOP);
|
|
1386
1385
|
var PAN_START_SLOP_PX = 8;
|
|
1387
1386
|
var GHOST_CLICK_MS = 700;
|
|
1387
|
+
var ROW_LABEL_CLEARANCE = 2;
|
|
1388
|
+
var ROW_LABEL_COLLISION_CELL = 64;
|
|
1388
1389
|
var ZONE_PROMINENT_SCALE = 0.55 * SECTION_PROMINENT_SCALE;
|
|
1389
1390
|
var MAX_LABELS = 700;
|
|
1390
1391
|
var MARQUEE_RING_CAP = 2500;
|
|
@@ -1471,6 +1472,44 @@ function opaqueColorHex(color) {
|
|
|
1471
1472
|
if (channels.some((channel) => !Number.isInteger(channel) || channel < 0 || channel > 255)) return null;
|
|
1472
1473
|
return `#${channels.map((channel) => channel.toString(16).padStart(2, "0")).join("")}`;
|
|
1473
1474
|
}
|
|
1475
|
+
function worldBoxesOverlap(a, b) {
|
|
1476
|
+
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;
|
|
1477
|
+
}
|
|
1478
|
+
var WorldBoxIndex = class {
|
|
1479
|
+
constructor() {
|
|
1480
|
+
this.cells = /* @__PURE__ */ new Map();
|
|
1481
|
+
}
|
|
1482
|
+
insert(box) {
|
|
1483
|
+
for (const key of this.keys(box)) {
|
|
1484
|
+
const bucket = this.cells.get(key);
|
|
1485
|
+
if (bucket) bucket.push(box);
|
|
1486
|
+
else this.cells.set(key, [box]);
|
|
1487
|
+
}
|
|
1488
|
+
}
|
|
1489
|
+
collisionCount(box) {
|
|
1490
|
+
const seen = /* @__PURE__ */ new Set();
|
|
1491
|
+
let count = 0;
|
|
1492
|
+
for (const key of this.keys(box)) {
|
|
1493
|
+
for (const existing of this.cells.get(key) ?? []) {
|
|
1494
|
+
if (seen.has(existing)) continue;
|
|
1495
|
+
seen.add(existing);
|
|
1496
|
+
if (worldBoxesOverlap(box, existing)) count++;
|
|
1497
|
+
}
|
|
1498
|
+
}
|
|
1499
|
+
return count;
|
|
1500
|
+
}
|
|
1501
|
+
keys(box) {
|
|
1502
|
+
const x0 = Math.floor(box.x / ROW_LABEL_COLLISION_CELL);
|
|
1503
|
+
const x1 = Math.floor((box.x + Math.max(0, box.width)) / ROW_LABEL_COLLISION_CELL);
|
|
1504
|
+
const y0 = Math.floor(box.y / ROW_LABEL_COLLISION_CELL);
|
|
1505
|
+
const y1 = Math.floor((box.y + Math.max(0, box.height)) / ROW_LABEL_COLLISION_CELL);
|
|
1506
|
+
const keys = [];
|
|
1507
|
+
for (let y = y0; y <= y1; y++) {
|
|
1508
|
+
for (let x = x0; x <= x1; x++) keys.push(`${x}:${y}`);
|
|
1509
|
+
}
|
|
1510
|
+
return keys;
|
|
1511
|
+
}
|
|
1512
|
+
};
|
|
1474
1513
|
function overviewPalette(canvasBackground) {
|
|
1475
1514
|
return isLightColor(canvasBackground) ? {
|
|
1476
1515
|
sectionFill: LIGHT_OVERVIEW_SECTION_FILL,
|
|
@@ -1876,6 +1915,11 @@ var _SeatmapRenderer = class _SeatmapRenderer {
|
|
|
1876
1915
|
this.bounds = { x: 0, y: 0, width: 1, height: 1 };
|
|
1877
1916
|
this.cached = false;
|
|
1878
1917
|
this.dpr = Math.min(typeof window !== "undefined" ? window.devicePixelRatio || 1 : 1, 2);
|
|
1918
|
+
/** Current chart's buyer-visible image work. Failures are retained until ready(). */
|
|
1919
|
+
this.assetGeneration = 0;
|
|
1920
|
+
this.assetPromises = [];
|
|
1921
|
+
this.assetErrors = [];
|
|
1922
|
+
this.assetCancels = /* @__PURE__ */ new Set();
|
|
1879
1923
|
this.rafId = 0;
|
|
1880
1924
|
this.frames = 0;
|
|
1881
1925
|
this.lastFpsAt = 0;
|
|
@@ -2043,7 +2087,10 @@ var _SeatmapRenderer = class _SeatmapRenderer {
|
|
|
2043
2087
|
};
|
|
2044
2088
|
this.container = container;
|
|
2045
2089
|
this.opts = { maxSelection: 10, selectableStatuses: ["free"], ...options };
|
|
2090
|
+
this.exportMode = options.exportMode === true;
|
|
2046
2091
|
this.currency = options.currency;
|
|
2092
|
+
const previousPixelRatio = Konva.pixelRatio;
|
|
2093
|
+
if (this.exportMode) this.dpr = 1;
|
|
2047
2094
|
Konva.pixelRatio = this.dpr;
|
|
2048
2095
|
this.stage = new Stage({
|
|
2049
2096
|
container,
|
|
@@ -2052,19 +2099,21 @@ var _SeatmapRenderer = class _SeatmapRenderer {
|
|
|
2052
2099
|
draggable: false
|
|
2053
2100
|
// pan/pinch are ours, via pointer events
|
|
2054
2101
|
});
|
|
2055
|
-
|
|
2056
|
-
|
|
2057
|
-
|
|
2058
|
-
|
|
2059
|
-
|
|
2060
|
-
|
|
2061
|
-
|
|
2062
|
-
|
|
2063
|
-
container.
|
|
2064
|
-
|
|
2065
|
-
|
|
2066
|
-
|
|
2067
|
-
|
|
2102
|
+
if (!this.exportMode) {
|
|
2103
|
+
container.style.touchAction = "none";
|
|
2104
|
+
container.addEventListener("pointerdown", this.onPointerDown, { passive: false });
|
|
2105
|
+
container.addEventListener("pointermove", this.onPointerMove, { passive: false });
|
|
2106
|
+
container.addEventListener("pointerup", this.onPointerEnd, { passive: false });
|
|
2107
|
+
container.addEventListener("pointercancel", this.onPointerEnd, { passive: false });
|
|
2108
|
+
if (container.tabIndex < 0) container.tabIndex = 0;
|
|
2109
|
+
container.setAttribute("role", "application");
|
|
2110
|
+
if (!container.getAttribute("aria-label")) {
|
|
2111
|
+
container.setAttribute("aria-label", t("map.aria"));
|
|
2112
|
+
}
|
|
2113
|
+
container.addEventListener("keydown", this.onKeyDown);
|
|
2114
|
+
}
|
|
2115
|
+
this.bgLayer = new Layer({ listening: !this.exportMode });
|
|
2116
|
+
this.seatLayer = new Layer({ listening: !this.exportMode });
|
|
2068
2117
|
this.overlayLayer = new Layer({ listening: false });
|
|
2069
2118
|
this.labelGroup = new Group({ listening: false });
|
|
2070
2119
|
this.overlayLayer.add(this.labelGroup);
|
|
@@ -2094,12 +2143,15 @@ var _SeatmapRenderer = class _SeatmapRenderer {
|
|
|
2094
2143
|
});
|
|
2095
2144
|
this.overlayLayer.add(this.focusRing);
|
|
2096
2145
|
this.stage.add(this.bgLayer, this.seatLayer, this.overlayLayer);
|
|
2097
|
-
this.
|
|
2098
|
-
this.
|
|
2146
|
+
if (this.exportMode) Konva.pixelRatio = previousPixelRatio;
|
|
2147
|
+
if (!this.exportMode) {
|
|
2148
|
+
this.wireInteraction();
|
|
2149
|
+
this.startFpsLoop();
|
|
2150
|
+
}
|
|
2099
2151
|
if (false) {
|
|
2100
2152
|
window.__seatmap = this;
|
|
2101
2153
|
}
|
|
2102
|
-
if (typeof ResizeObserver !== "undefined") {
|
|
2154
|
+
if (!this.exportMode && typeof ResizeObserver !== "undefined") {
|
|
2103
2155
|
this.resizeObs = new ResizeObserver(() => this.handleResize());
|
|
2104
2156
|
this.resizeObs.observe(container);
|
|
2105
2157
|
}
|
|
@@ -2122,6 +2174,7 @@ var _SeatmapRenderer = class _SeatmapRenderer {
|
|
|
2122
2174
|
// ---- ISeatmapRenderer -----------------------------------------------------
|
|
2123
2175
|
setChart(doc, opts) {
|
|
2124
2176
|
if (doc !== this.chartDoc) this.stacked = false;
|
|
2177
|
+
this.cancelAssetLoads();
|
|
2125
2178
|
this.chartDoc = doc;
|
|
2126
2179
|
this.activeFloorId = opts?.floorId ?? floorsOf(doc)[0].id;
|
|
2127
2180
|
this.objectFloor.clear();
|
|
@@ -2135,7 +2188,7 @@ var _SeatmapRenderer = class _SeatmapRenderer {
|
|
|
2135
2188
|
this.focusDimOverlay?.destroy();
|
|
2136
2189
|
this.focusDimOverlay = null;
|
|
2137
2190
|
this.seatLayer.clearCache();
|
|
2138
|
-
this.seatLayer.listening(
|
|
2191
|
+
this.seatLayer.listening(!this.exportMode);
|
|
2139
2192
|
this.seatLayer.destroyChildren();
|
|
2140
2193
|
this.unsectionedSeatGroup = null;
|
|
2141
2194
|
this.labelGroup.destroyChildren();
|
|
@@ -2235,7 +2288,7 @@ var _SeatmapRenderer = class _SeatmapRenderer {
|
|
|
2235
2288
|
this.isoCentre = { x: this.bounds.x + this.bounds.width / 2, y: this.bounds.y + this.bounds.height / 2 };
|
|
2236
2289
|
this.buildPerspectiveProjection(view);
|
|
2237
2290
|
this.renderBackground(view);
|
|
2238
|
-
this.unsectionedSeatGroup = new Group({ listening:
|
|
2291
|
+
this.unsectionedSeatGroup = new Group({ listening: !this.exportMode });
|
|
2239
2292
|
this.seatLayer.add(this.unsectionedSeatGroup);
|
|
2240
2293
|
this.renderSeats();
|
|
2241
2294
|
this.buildRowLabelPlan(view);
|
|
@@ -2870,9 +2923,9 @@ var _SeatmapRenderer = class _SeatmapRenderer {
|
|
|
2870
2923
|
paintGAStateForView() {
|
|
2871
2924
|
for (const ga of this.gaById.values()) {
|
|
2872
2925
|
const filteredOut = Boolean(this.categoryFilter && !this.categoryFilter.has(ga.categoryKey));
|
|
2873
|
-
const overviewHidden = ga.sectionId != null && this.effScale() < CACHE_THRESHOLD;
|
|
2926
|
+
const overviewHidden = !this.exportMode && ga.sectionId != null && this.effScale() < CACHE_THRESHOLD;
|
|
2874
2927
|
ga.polygon.opacity(overviewHidden ? 0 : this.gaCategoryDimmed(ga.categoryKey) ? GA_FILL_OPACITY * 0.08 : GA_FILL_OPACITY);
|
|
2875
|
-
ga.polygon.listening(!overviewHidden && !filteredOut);
|
|
2928
|
+
ga.polygon.listening(!this.exportMode && !overviewHidden && !filteredOut);
|
|
2876
2929
|
}
|
|
2877
2930
|
}
|
|
2878
2931
|
/** Frame the currently available inventory that survived a buyer price
|
|
@@ -3329,6 +3382,7 @@ var _SeatmapRenderer = class _SeatmapRenderer {
|
|
|
3329
3382
|
}
|
|
3330
3383
|
destroy() {
|
|
3331
3384
|
this.destroyed = true;
|
|
3385
|
+
this.cancelAssetLoads();
|
|
3332
3386
|
if (this.rafId) cancelAnimationFrame(this.rafId);
|
|
3333
3387
|
if (this.isoRaf) cancelAnimationFrame(this.isoRaf);
|
|
3334
3388
|
if (this.glideRaf) cancelAnimationFrame(this.glideRaf);
|
|
@@ -3345,6 +3399,9 @@ var _SeatmapRenderer = class _SeatmapRenderer {
|
|
|
3345
3399
|
// ---- rendering ------------------------------------------------------------
|
|
3346
3400
|
/** Theme font stack for all rendered text (falls back to Inter). */
|
|
3347
3401
|
labelFont() {
|
|
3402
|
+
if (this.exportMode) {
|
|
3403
|
+
return this.theme.fontFamily?.toLowerCase().includes("jetbrains") ? "JetBrains Mono, monospace" : "Inter, sans-serif";
|
|
3404
|
+
}
|
|
3348
3405
|
return this.theme.fontFamily || "Inter, sans-serif";
|
|
3349
3406
|
}
|
|
3350
3407
|
/**
|
|
@@ -3392,7 +3449,7 @@ var _SeatmapRenderer = class _SeatmapRenderer {
|
|
|
3392
3449
|
* rung. Overview caching always restores all groups first, so panning a
|
|
3393
3450
|
* cached whole-venue bitmap can never reveal missing inventory. */
|
|
3394
3451
|
updateSeatGroupVisibility() {
|
|
3395
|
-
const liveSeats = this.effScale() >= CACHE_THRESHOLD;
|
|
3452
|
+
const liveSeats = this.exportMode || this.effScale() >= CACHE_THRESHOLD;
|
|
3396
3453
|
const deferPerspectiveSeatReveal = this.viewMode === "perspective" && this.glideInProgress && this.seats.length > 2500;
|
|
3397
3454
|
const padding = 96;
|
|
3398
3455
|
const width = this.stage.width();
|
|
@@ -4029,11 +4086,67 @@ var _SeatmapRenderer = class _SeatmapRenderer {
|
|
|
4029
4086
|
}
|
|
4030
4087
|
}
|
|
4031
4088
|
}
|
|
4089
|
+
/**
|
|
4090
|
+
* Track an image through decode without ever leaving a rejected promise
|
|
4091
|
+
* unobserved. Ordinary picker rendering remains best-effort; export ready()
|
|
4092
|
+
* turns the retained failures into one actionable error.
|
|
4093
|
+
*/
|
|
4094
|
+
trackAssetImage(image, source, label, onReady) {
|
|
4095
|
+
const generation = this.assetGeneration;
|
|
4096
|
+
if (/^https?:\/\//i.test(source)) image.crossOrigin = "anonymous";
|
|
4097
|
+
const work = new Promise((resolve) => {
|
|
4098
|
+
let settled = false;
|
|
4099
|
+
const finish = () => {
|
|
4100
|
+
if (settled) return;
|
|
4101
|
+
settled = true;
|
|
4102
|
+
this.assetCancels.delete(cancel);
|
|
4103
|
+
resolve();
|
|
4104
|
+
};
|
|
4105
|
+
const cancel = () => {
|
|
4106
|
+
image.onload = null;
|
|
4107
|
+
image.onerror = null;
|
|
4108
|
+
try {
|
|
4109
|
+
image.removeAttribute("src");
|
|
4110
|
+
image.src = "";
|
|
4111
|
+
} catch {
|
|
4112
|
+
}
|
|
4113
|
+
finish();
|
|
4114
|
+
};
|
|
4115
|
+
this.assetCancels.add(cancel);
|
|
4116
|
+
image.onload = () => {
|
|
4117
|
+
if (!this.exportMode) {
|
|
4118
|
+
if (generation === this.assetGeneration) onReady();
|
|
4119
|
+
finish();
|
|
4120
|
+
return;
|
|
4121
|
+
}
|
|
4122
|
+
const decoded = typeof image.decode === "function" ? image.decode() : Promise.resolve();
|
|
4123
|
+
void decoded.then(() => {
|
|
4124
|
+
if (generation === this.assetGeneration) onReady();
|
|
4125
|
+
}).catch(() => {
|
|
4126
|
+
if (generation === this.assetGeneration) this.assetErrors.push(`${label} could not be decoded.`);
|
|
4127
|
+
}).finally(finish);
|
|
4128
|
+
};
|
|
4129
|
+
image.onerror = () => {
|
|
4130
|
+
if (generation === this.assetGeneration) this.assetErrors.push(`${label} could not be loaded.`);
|
|
4131
|
+
finish();
|
|
4132
|
+
};
|
|
4133
|
+
image.src = source;
|
|
4134
|
+
});
|
|
4135
|
+
this.assetPromises.push(work);
|
|
4136
|
+
}
|
|
4137
|
+
/** Cancel and release every image retained by the previous chart/export. */
|
|
4138
|
+
cancelAssetLoads() {
|
|
4139
|
+
this.assetGeneration++;
|
|
4140
|
+
for (const cancel of [...this.assetCancels]) cancel();
|
|
4141
|
+
this.assetCancels.clear();
|
|
4142
|
+
this.assetPromises = [];
|
|
4143
|
+
this.assetErrors = [];
|
|
4144
|
+
}
|
|
4032
4145
|
/** Organizer floor-plan photo, dimmed, at the very bottom of the bg layer. */
|
|
4033
4146
|
renderBackgroundImage(bg) {
|
|
4034
4147
|
if (!bg.url || bg.visible === false) return;
|
|
4035
4148
|
const img = new window.Image();
|
|
4036
|
-
img.
|
|
4149
|
+
this.trackAssetImage(img, bg.url, "Buyer background image", () => {
|
|
4037
4150
|
const natW = img.naturalWidth || 4;
|
|
4038
4151
|
const natH = img.naturalHeight || 3;
|
|
4039
4152
|
const rawCrop = bg.crop ?? { x: 0, y: 0, width: 1, height: 1 };
|
|
@@ -4068,8 +4181,7 @@ var _SeatmapRenderer = class _SeatmapRenderer {
|
|
|
4068
4181
|
this.bgLayer.add(node);
|
|
4069
4182
|
node.moveToBottom();
|
|
4070
4183
|
this.bgLayer.batchDraw();
|
|
4071
|
-
};
|
|
4072
|
-
img.src = bg.url;
|
|
4184
|
+
});
|
|
4073
4185
|
}
|
|
4074
4186
|
/**
|
|
4075
4187
|
* A decor graphic (rink / court / stage art). The KImage node is added to the
|
|
@@ -4095,12 +4207,11 @@ var _SeatmapRenderer = class _SeatmapRenderer {
|
|
|
4095
4207
|
});
|
|
4096
4208
|
if (obj.layer === "foreground") this.fgDecorGroup.add(node);
|
|
4097
4209
|
else this.bgLayer.add(node);
|
|
4098
|
-
img.
|
|
4210
|
+
this.trackAssetImage(img, obj.href, `Decor image \u201C${obj.label || obj.id}\u201D`, () => {
|
|
4099
4211
|
const layer = node.getLayer();
|
|
4100
4212
|
if (!layer) return;
|
|
4101
4213
|
layer.batchDraw();
|
|
4102
|
-
};
|
|
4103
|
-
img.src = obj.href;
|
|
4214
|
+
});
|
|
4104
4215
|
}
|
|
4105
4216
|
renderTable(obj) {
|
|
4106
4217
|
if (obj.shape === "round") {
|
|
@@ -5763,6 +5874,7 @@ var _SeatmapRenderer = class _SeatmapRenderer {
|
|
|
5763
5874
|
}
|
|
5764
5875
|
/** rAF-coalesced `onViewChange` — at most one host callback per animation frame. */
|
|
5765
5876
|
scheduleViewChange() {
|
|
5877
|
+
if (this.exportMode) return;
|
|
5766
5878
|
if (this.viewChangeRaf) return;
|
|
5767
5879
|
this.viewChangeRaf = requestAnimationFrame(() => {
|
|
5768
5880
|
this.viewChangeRaf = 0;
|
|
@@ -5770,7 +5882,7 @@ var _SeatmapRenderer = class _SeatmapRenderer {
|
|
|
5770
5882
|
});
|
|
5771
5883
|
}
|
|
5772
5884
|
updateLOD() {
|
|
5773
|
-
const scale = this.effScale();
|
|
5885
|
+
const scale = this.exportMode ? Math.max(this.effScale(), BLOCK_MELT_TOP + 0.01, LABEL_SCALE) : this.effScale();
|
|
5774
5886
|
const focalScale = Math.max(scale, 1e-4);
|
|
5775
5887
|
for (const [label, targetPx] of this.primaryFocalLabels) {
|
|
5776
5888
|
this.sizeLabel(label, targetPx / focalScale, label.y());
|
|
@@ -5779,7 +5891,7 @@ var _SeatmapRenderer = class _SeatmapRenderer {
|
|
|
5779
5891
|
else if (this.primaryFocalLabels.size) this.bgLayer.batchDraw();
|
|
5780
5892
|
this.paintGAStateForView();
|
|
5781
5893
|
this.updateAccessGlyphs(scale);
|
|
5782
|
-
const shouldCache = scale < CACHE_THRESHOLD;
|
|
5894
|
+
const shouldCache = !this.exportMode && scale < CACHE_THRESHOLD;
|
|
5783
5895
|
const suppressPerspectiveSeatCache = this.viewMode === "perspective" && this.hasSections && this.seats.length > 2500 && shouldCache;
|
|
5784
5896
|
if (suppressPerspectiveSeatCache) {
|
|
5785
5897
|
this.seatLayer.listening(false);
|
|
@@ -5789,7 +5901,7 @@ var _SeatmapRenderer = class _SeatmapRenderer {
|
|
|
5789
5901
|
this.cacheSeatLayer();
|
|
5790
5902
|
} else if (!shouldCache && (this.cached || !this.seatLayer.listening())) {
|
|
5791
5903
|
if (this.cached) this.releaseSeatLayerBitmap();
|
|
5792
|
-
this.seatLayer.listening(
|
|
5904
|
+
this.seatLayer.listening(!this.exportMode);
|
|
5793
5905
|
this.cached = false;
|
|
5794
5906
|
this.seatLayer.batchDraw();
|
|
5795
5907
|
}
|
|
@@ -5857,8 +5969,103 @@ var _SeatmapRenderer = class _SeatmapRenderer {
|
|
|
5857
5969
|
this.seatLayer.draw();
|
|
5858
5970
|
this.overlayLayer.draw();
|
|
5859
5971
|
}
|
|
5972
|
+
/**
|
|
5973
|
+
* Resolve every buyer-visible image requested by the current chart. Export
|
|
5974
|
+
* callers get a hard error with the affected layer name; the interactive
|
|
5975
|
+
* renderer never calls this and retains its existing best-effort behaviour.
|
|
5976
|
+
*/
|
|
5977
|
+
async ready() {
|
|
5978
|
+
const generation = this.assetGeneration;
|
|
5979
|
+
const fontSet = typeof document !== "undefined" && "fonts" in document ? document.fonts : null;
|
|
5980
|
+
await Promise.all([
|
|
5981
|
+
...this.assetPromises.slice(),
|
|
5982
|
+
...fontSet ? [
|
|
5983
|
+
fontSet.ready.then(() => void 0),
|
|
5984
|
+
fontSet.load("700 24px Inter").then(() => void 0),
|
|
5985
|
+
fontSet.load('600 12px "JetBrains Mono"').then(() => void 0)
|
|
5986
|
+
] : []
|
|
5987
|
+
]);
|
|
5988
|
+
if (generation !== this.assetGeneration) {
|
|
5989
|
+
throw new Error("The chart changed while its export assets were loading.");
|
|
5990
|
+
}
|
|
5991
|
+
if (this.assetErrors.length) {
|
|
5992
|
+
throw new Error(`Export stopped because ${this.assetErrors.join(" ")}`);
|
|
5993
|
+
}
|
|
5994
|
+
if (this.exportMode) {
|
|
5995
|
+
this.fitExportContent();
|
|
5996
|
+
this.forceDraw();
|
|
5997
|
+
}
|
|
5998
|
+
}
|
|
5999
|
+
/** Exact visible scene bounds in chart coordinates after fonts/assets exist. */
|
|
6000
|
+
exportSceneBounds() {
|
|
6001
|
+
const rects = [this.bgLayer, this.seatLayer, this.overlayLayer].map((layer) => layer.getClientRect({ relativeTo: this.stage })).filter((rect) => rect.width > 0 && rect.height > 0);
|
|
6002
|
+
if (!rects.length) return this.bounds;
|
|
6003
|
+
const minX = Math.min(...rects.map((rect) => rect.x));
|
|
6004
|
+
const minY = Math.min(...rects.map((rect) => rect.y));
|
|
6005
|
+
const maxX = Math.max(...rects.map((rect) => rect.x + rect.width));
|
|
6006
|
+
const maxY = Math.max(...rects.map((rect) => rect.y + rect.height));
|
|
6007
|
+
return {
|
|
6008
|
+
x: minX,
|
|
6009
|
+
y: minY,
|
|
6010
|
+
width: Math.max(1, maxX - minX),
|
|
6011
|
+
height: Math.max(1, maxY - minY)
|
|
6012
|
+
};
|
|
6013
|
+
}
|
|
6014
|
+
/** Fit exact rendered nodes, including rotated text/decor, with pixel padding. */
|
|
6015
|
+
fitExportContent() {
|
|
6016
|
+
if (!this.exportMode) return;
|
|
6017
|
+
const width = this.stage.width();
|
|
6018
|
+
const height = this.stage.height();
|
|
6019
|
+
const padding = Math.max(12, Math.min(36, Math.min(width, height) * 0.025));
|
|
6020
|
+
const fit = () => {
|
|
6021
|
+
const bounds2 = this.exportSceneBounds();
|
|
6022
|
+
const availableWidth = Math.max(1, width - padding * 2);
|
|
6023
|
+
const availableHeight = Math.max(1, height - padding * 2);
|
|
6024
|
+
const scale = Math.min(availableWidth / bounds2.width, availableHeight / bounds2.height) || 1;
|
|
6025
|
+
this.fitScale = scale;
|
|
6026
|
+
this.stage.scale({ x: scale, y: scale });
|
|
6027
|
+
this.stage.position({
|
|
6028
|
+
x: (width - bounds2.width * scale) / 2 - bounds2.x * scale,
|
|
6029
|
+
y: (height - bounds2.height * scale) / 2 - bounds2.y * scale
|
|
6030
|
+
});
|
|
6031
|
+
this.afterViewChange();
|
|
6032
|
+
};
|
|
6033
|
+
fit();
|
|
6034
|
+
fit();
|
|
6035
|
+
}
|
|
6036
|
+
/**
|
|
6037
|
+
* Capture a clean, opaque fixed-size canvas. The CSS host background is
|
|
6038
|
+
* painted explicitly because Konva layers themselves are transparent.
|
|
6039
|
+
*/
|
|
6040
|
+
captureCanvas() {
|
|
6041
|
+
if (!this.exportMode) {
|
|
6042
|
+
throw new Error("captureCanvas() is available only on a renderer created with exportMode.");
|
|
6043
|
+
}
|
|
6044
|
+
this.forceDraw();
|
|
6045
|
+
let scene;
|
|
6046
|
+
try {
|
|
6047
|
+
scene = this.stage.toCanvas({ pixelRatio: 1, imageSmoothingEnabled: true });
|
|
6048
|
+
} catch (error) {
|
|
6049
|
+
const detail = error instanceof Error ? error.message : String(error);
|
|
6050
|
+
throw new Error(`The chart canvas could not be captured. A buyer-visible image may block export. ${detail}`);
|
|
6051
|
+
}
|
|
6052
|
+
const canvas = document.createElement("canvas");
|
|
6053
|
+
canvas.width = this.stage.width();
|
|
6054
|
+
canvas.height = this.stage.height();
|
|
6055
|
+
const context = canvas.getContext("2d");
|
|
6056
|
+
if (!context) throw new Error("Canvas 2D is unavailable; this browser cannot create an export.");
|
|
6057
|
+
context.fillStyle = this.canvasBackground;
|
|
6058
|
+
context.fillRect(0, 0, canvas.width, canvas.height);
|
|
6059
|
+
try {
|
|
6060
|
+
context.drawImage(scene, 0, 0);
|
|
6061
|
+
} finally {
|
|
6062
|
+
scene.width = 1;
|
|
6063
|
+
scene.height = 1;
|
|
6064
|
+
}
|
|
6065
|
+
return canvas;
|
|
6066
|
+
}
|
|
5860
6067
|
updateFreeTextVisibility() {
|
|
5861
|
-
const effectiveScale = this.effScale();
|
|
6068
|
+
const effectiveScale = this.exportMode ? Math.max(this.effScale(), LABEL_SCALE) : this.effScale();
|
|
5862
6069
|
for (const { objectId, node, categoryKey, kind } of this.freeTextById.values()) {
|
|
5863
6070
|
const gaDimmed = categoryKey != null && (kind === "ga-label" || kind === "ga-capacity") && this.gaCategoryDimmed(categoryKey);
|
|
5864
6071
|
const gaOverviewHidden = objectId != null && (kind === "ga-label" || kind === "ga-capacity") && this.gaById.get(objectId)?.sectionId != null && effectiveScale < CACHE_THRESHOLD;
|
|
@@ -5903,18 +6110,89 @@ var _SeatmapRenderer = class _SeatmapRenderer {
|
|
|
5903
6110
|
const rotation = presentation?.rotation ?? 0;
|
|
5904
6111
|
const text = obj.segmentedRow?.displayLabel ?? obj.displayLabel ?? obj.label;
|
|
5905
6112
|
const opacity = this.objectFilteredOut(obj) ? 0.15 : 1;
|
|
5906
|
-
const push = (x, y) => this.rowLabelPlan.push({
|
|
6113
|
+
const push = (x, y, automaticAway) => this.rowLabelPlan.push({
|
|
6114
|
+
text,
|
|
6115
|
+
x,
|
|
6116
|
+
y,
|
|
6117
|
+
rotation,
|
|
6118
|
+
fontSize,
|
|
6119
|
+
ink,
|
|
6120
|
+
opacity,
|
|
6121
|
+
...automaticAway ? { automaticAway } : {}
|
|
6122
|
+
});
|
|
5907
6123
|
if (presentation?.position) {
|
|
5908
6124
|
push(presentation.position.x, presentation.position.y);
|
|
5909
6125
|
continue;
|
|
5910
6126
|
}
|
|
5911
|
-
const radians = obj.rotation * Math.PI / 180;
|
|
5912
|
-
const along = { x: Math.cos(radians), y: Math.sin(radians) };
|
|
5913
6127
|
const gap = this.seatR + 12;
|
|
5914
6128
|
const preset = presentation?.positionPreset ?? "start";
|
|
5915
|
-
if (preset === "start" || preset === "both")
|
|
5916
|
-
|
|
6129
|
+
if (preset === "start" || preset === "both") {
|
|
6130
|
+
const radians = obj.rotation * Math.PI / 180;
|
|
6131
|
+
const away = { x: -Math.cos(radians), y: -Math.sin(radians) };
|
|
6132
|
+
push(first.x + away.x * gap, first.y + away.y * gap, away);
|
|
6133
|
+
}
|
|
6134
|
+
if (preset === "end" || preset === "both") {
|
|
6135
|
+
const radians = (obj.rotation + obj.curve) * Math.PI / 180;
|
|
6136
|
+
const away = { x: Math.cos(radians), y: Math.sin(radians) };
|
|
6137
|
+
push(last.x + away.x * gap, last.y + away.y * gap, away);
|
|
6138
|
+
}
|
|
6139
|
+
}
|
|
6140
|
+
}
|
|
6141
|
+
/** Axis-aligned world bounds of a centred, potentially rotated row label. */
|
|
6142
|
+
rowLabelBox(label, position, padding = ROW_LABEL_CLEARANCE) {
|
|
6143
|
+
const radians = label.rotation() * Math.PI / 180;
|
|
6144
|
+
const cos = Math.abs(Math.cos(radians));
|
|
6145
|
+
const sin = Math.abs(Math.sin(radians));
|
|
6146
|
+
const halfWidth = (label.width() * cos + label.height() * sin) / 2 + padding;
|
|
6147
|
+
const halfHeight = (label.width() * sin + label.height() * cos) / 2 + padding;
|
|
6148
|
+
return {
|
|
6149
|
+
x: position.x - halfWidth,
|
|
6150
|
+
y: position.y - halfHeight,
|
|
6151
|
+
width: halfWidth * 2,
|
|
6152
|
+
height: halfHeight * 2
|
|
6153
|
+
};
|
|
6154
|
+
}
|
|
6155
|
+
/**
|
|
6156
|
+
* Keep an automatic start/end label out of seat markers and already-painted
|
|
6157
|
+
* text. The authored point is never altered. Candidates are deliberately
|
|
6158
|
+
* bounded and deterministic: the closest clear offset wins; if a chart is so
|
|
6159
|
+
* dense that none is clear, the least-colliding candidate wins so the required
|
|
6160
|
+
* row label remains present rather than being silently dropped.
|
|
6161
|
+
*/
|
|
6162
|
+
resolveAutomaticRowLabelPosition(label, plan, occupiedText) {
|
|
6163
|
+
const base = { x: plan.x, y: plan.y };
|
|
6164
|
+
const away = plan.automaticAway;
|
|
6165
|
+
if (!away) return base;
|
|
6166
|
+
const normal = { x: -away.y, y: away.x };
|
|
6167
|
+
const step = Math.max(7, Math.min(16, plan.fontSize * 0.75));
|
|
6168
|
+
const offsets = [{ x: 0, y: 0 }];
|
|
6169
|
+
for (let ring = 1; ring <= 4; ring++) {
|
|
6170
|
+
const distance = step * ring;
|
|
6171
|
+
offsets.push(
|
|
6172
|
+
{ x: normal.x * distance, y: normal.y * distance },
|
|
6173
|
+
{ x: -normal.x * distance, y: -normal.y * distance },
|
|
6174
|
+
{ x: away.x * distance, y: away.y * distance },
|
|
6175
|
+
{ x: (away.x + normal.x) * distance, y: (away.y + normal.y) * distance },
|
|
6176
|
+
{ x: (away.x - normal.x) * distance, y: (away.y - normal.y) * distance }
|
|
6177
|
+
);
|
|
6178
|
+
}
|
|
6179
|
+
let best = base;
|
|
6180
|
+
let bestCollisions = Infinity;
|
|
6181
|
+
let bestDistance = Infinity;
|
|
6182
|
+
for (const offset of offsets) {
|
|
6183
|
+
const candidate = { x: base.x + offset.x, y: base.y + offset.y };
|
|
6184
|
+
const box = this.rowLabelBox(label, candidate);
|
|
6185
|
+
const seatCollisions = this.seatIndex ? queryRect(this.seatIndex, box, { mode: "overlap" }).length : 0;
|
|
6186
|
+
const collisions = seatCollisions + occupiedText.collisionCount(box);
|
|
6187
|
+
const distance = Math.hypot(offset.x, offset.y);
|
|
6188
|
+
if (collisions < bestCollisions || collisions === bestCollisions && distance < bestDistance) {
|
|
6189
|
+
best = candidate;
|
|
6190
|
+
bestCollisions = collisions;
|
|
6191
|
+
bestDistance = distance;
|
|
6192
|
+
}
|
|
6193
|
+
if (collisions === 0) break;
|
|
5917
6194
|
}
|
|
6195
|
+
return best;
|
|
5918
6196
|
}
|
|
5919
6197
|
/** Row labels never carry status; the buyer just dims them under the same
|
|
5920
6198
|
* category/price filters that dim their seats. */
|
|
@@ -5924,7 +6202,7 @@ var _SeatmapRenderer = class _SeatmapRenderer {
|
|
|
5924
6202
|
return Boolean(this.categoryHighlight && key !== this.categoryHighlight || this.categoryFilter && !this.categoryFilter.has(key));
|
|
5925
6203
|
}
|
|
5926
6204
|
updateLabels() {
|
|
5927
|
-
const effectiveScale = this.effScale();
|
|
6205
|
+
const effectiveScale = this.exportMode ? Math.max(this.effScale(), LABEL_SCALE) : this.effScale();
|
|
5928
6206
|
const show = effectiveScale >= LABEL_SCALE;
|
|
5929
6207
|
for (const [id, label] of this.boothLabelById) {
|
|
5930
6208
|
const shape = this.circleById.get(id);
|
|
@@ -6015,6 +6293,21 @@ var _SeatmapRenderer = class _SeatmapRenderer {
|
|
|
6015
6293
|
this.seatLabelById.set(seat.id, t2);
|
|
6016
6294
|
if (++count >= MAX_LABELS) break;
|
|
6017
6295
|
}
|
|
6296
|
+
const occupiedText = new WorldBoxIndex();
|
|
6297
|
+
for (const { node } of this.freeTextById.values()) {
|
|
6298
|
+
if (!node.isVisible()) continue;
|
|
6299
|
+
const box = node.getClientRect({
|
|
6300
|
+
relativeTo: this.bgLayer,
|
|
6301
|
+
skipShadow: true,
|
|
6302
|
+
skipStroke: true
|
|
6303
|
+
});
|
|
6304
|
+
occupiedText.insert({
|
|
6305
|
+
x: box.x - ROW_LABEL_CLEARANCE,
|
|
6306
|
+
y: box.y - ROW_LABEL_CLEARANCE,
|
|
6307
|
+
width: box.width + ROW_LABEL_CLEARANCE * 2,
|
|
6308
|
+
height: box.height + ROW_LABEL_CLEARANCE * 2
|
|
6309
|
+
});
|
|
6310
|
+
}
|
|
6018
6311
|
for (const rl of this.rowLabelPlan) {
|
|
6019
6312
|
const screen = this.worldToScreen({ x: rl.x, y: rl.y });
|
|
6020
6313
|
if (screen.x < -40 || screen.x > this.stage.width() + 40 || screen.y < -40 || screen.y > this.stage.height() + 40) continue;
|
|
@@ -6037,6 +6330,10 @@ var _SeatmapRenderer = class _SeatmapRenderer {
|
|
|
6037
6330
|
});
|
|
6038
6331
|
t2.offsetX(t2.width() / 2);
|
|
6039
6332
|
t2.offsetY(t2.height() / 2);
|
|
6333
|
+
const resolved = this.resolveAutomaticRowLabelPosition(t2, rl, occupiedText);
|
|
6334
|
+
t2.position(resolved);
|
|
6335
|
+
t2.setAttr("rowLabel", true);
|
|
6336
|
+
occupiedText.insert(this.rowLabelBox(t2, resolved));
|
|
6040
6337
|
this.labelGroup.add(t2);
|
|
6041
6338
|
}
|
|
6042
6339
|
if (this.isoT > 0 && this.viewMode !== "perspective") this.applyUprightLabels();
|
|
@@ -7640,7 +7937,8 @@ var PickerController = class {
|
|
|
7640
7937
|
if (!url) return;
|
|
7641
7938
|
let ws;
|
|
7642
7939
|
try {
|
|
7643
|
-
|
|
7940
|
+
const protocols = this.api.socketProtocols?.(this.key);
|
|
7941
|
+
ws = protocols && protocols.length ? new WebSocket(url, protocols) : new WebSocket(url);
|
|
7644
7942
|
} catch {
|
|
7645
7943
|
this.scheduleReconnect();
|
|
7646
7944
|
return;
|
|
@@ -8951,7 +9249,6 @@ export {
|
|
|
8951
9249
|
rowInventoryCount,
|
|
8952
9250
|
rowSeatPositions,
|
|
8953
9251
|
seatCommercialMeta,
|
|
8954
|
-
seatLabelPart,
|
|
8955
9252
|
sectionGeometry,
|
|
8956
9253
|
setLocale,
|
|
8957
9254
|
setMoneyLocale,
|