@seatlayer/js 0.47.2 → 0.48.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/README.md +16 -1
- package/dist/{channelsMode-33ZLORVF.js → channelsMode-NCH5GYHN.js} +3 -3
- package/dist/{chunk-X2R4AQSZ.js → chunk-H4OJF6LE.js} +176 -7
- package/dist/chunk-H4OJF6LE.js.map +1 -0
- package/dist/{chunk-DMEFXZIL.js → chunk-KNMZQZXR.js} +2 -2
- package/dist/{chunk-CF5MS7UR.js → chunk-Q5QT3YLA.js} +186 -59
- package/dist/chunk-Q5QT3YLA.js.map +1 -0
- package/dist/{hostedCheckout-SHQCWN5Y.js → hostedCheckout-F6C6VCCG.js} +1 -1
- package/dist/hostedCheckout-F6C6VCCG.js.map +1 -0
- package/dist/index.cjs +612 -120
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +37 -5
- package/dist/index.d.ts +37 -5
- package/dist/index.js +258 -62
- package/dist/index.js.map +1 -1
- package/dist/{manager-CAIPkUYl.d.cts → manager-DEjbKiqJ.d.cts} +200 -17
- package/dist/{manager-CAIPkUYl.d.ts → manager-DEjbKiqJ.d.ts} +200 -17
- package/dist/manager.cjs +358 -62
- package/dist/manager.cjs.map +1 -1
- package/dist/manager.d.cts +1 -1
- package/dist/manager.d.ts +1 -1
- package/dist/manager.js +2 -2
- package/package.json +2 -2
- package/dist/chunk-CF5MS7UR.js.map +0 -1
- package/dist/chunk-X2R4AQSZ.js.map +0 -1
- package/dist/hostedCheckout-SHQCWN5Y.js.map +0 -1
- /package/dist/{channelsMode-33ZLORVF.js.map → channelsMode-NCH5GYHN.js.map} +0 -0
- /package/dist/{chunk-DMEFXZIL.js.map → chunk-KNMZQZXR.js.map} +0 -0
package/dist/index.cjs
CHANGED
|
@@ -431,6 +431,99 @@ async function parse(res) {
|
|
|
431
431
|
}
|
|
432
432
|
return data;
|
|
433
433
|
}
|
|
434
|
+
function record(value) {
|
|
435
|
+
return value && typeof value === "object" ? value : {};
|
|
436
|
+
}
|
|
437
|
+
function finite(primary, legacy, fallback = 0) {
|
|
438
|
+
if (typeof primary === "number" && Number.isFinite(primary)) return primary;
|
|
439
|
+
if (typeof legacy === "number" && Number.isFinite(legacy)) return legacy;
|
|
440
|
+
return fallback;
|
|
441
|
+
}
|
|
442
|
+
function nullableFinite(primary, legacy) {
|
|
443
|
+
if (primary === null) return null;
|
|
444
|
+
if (typeof primary === "number" && Number.isFinite(primary)) return primary;
|
|
445
|
+
if (primary !== void 0) return null;
|
|
446
|
+
if (typeof legacy === "number" && Number.isFinite(legacy)) return legacy;
|
|
447
|
+
return null;
|
|
448
|
+
}
|
|
449
|
+
function normalizeSection(value) {
|
|
450
|
+
const row = record(value);
|
|
451
|
+
const bookedValue = finite(row.bookedValue, row.bookedRevenue);
|
|
452
|
+
return { ...row, bookedValue, bookedRevenue: bookedValue };
|
|
453
|
+
}
|
|
454
|
+
function normalizeReportResult(value) {
|
|
455
|
+
const source = record(value);
|
|
456
|
+
const report = record(source.report);
|
|
457
|
+
const byCategory = Array.isArray(report.byCategory) ? report.byCategory.map((value2) => {
|
|
458
|
+
const row = record(value2);
|
|
459
|
+
const bookedValue = finite(row.bookedValue, row.bookedRevenue);
|
|
460
|
+
return { ...row, bookedValue, bookedRevenue: bookedValue };
|
|
461
|
+
}) : [];
|
|
462
|
+
const bySection = Array.isArray(report.bySection) ? report.bySection.map(normalizeSection) : void 0;
|
|
463
|
+
return {
|
|
464
|
+
...source,
|
|
465
|
+
report: { ...report, byCategory, ...bySection ? { bySection } : {} }
|
|
466
|
+
};
|
|
467
|
+
}
|
|
468
|
+
function normalizeControlRoomSnapshot(value) {
|
|
469
|
+
const source = record(value);
|
|
470
|
+
const canonical = record(source.bookedValue);
|
|
471
|
+
const legacy = record(source.revenue);
|
|
472
|
+
const selected = Object.keys(canonical).length ? canonical : legacy;
|
|
473
|
+
const bySectionSource = Array.isArray(canonical.bySection) ? canonical.bySection : Array.isArray(legacy.bySection) ? legacy.bySection : [];
|
|
474
|
+
const bookedValue = {
|
|
475
|
+
...selected,
|
|
476
|
+
gross: finite(canonical.gross, legacy.gross),
|
|
477
|
+
bySection: bySectionSource.map(normalizeSection)
|
|
478
|
+
};
|
|
479
|
+
const velocity = record(source.velocity);
|
|
480
|
+
const velocityRows = Array.isArray(velocity.bySection) ? velocity.bySection.map((value2) => {
|
|
481
|
+
const row = record(value2);
|
|
482
|
+
const rowValue = finite(row.bookedValue, row.grossRevenue);
|
|
483
|
+
return { ...row, bookedValue: rowValue, grossRevenue: rowValue };
|
|
484
|
+
}) : [];
|
|
485
|
+
return {
|
|
486
|
+
...source,
|
|
487
|
+
bookedValue,
|
|
488
|
+
revenue: bookedValue,
|
|
489
|
+
velocity: { ...velocity, bySection: velocityRows }
|
|
490
|
+
};
|
|
491
|
+
}
|
|
492
|
+
function normalizeChannelReportResult(value) {
|
|
493
|
+
const source = record(value);
|
|
494
|
+
const report = record(source.report);
|
|
495
|
+
const includesBookedValue = typeof report.includesBookedValue === "boolean" ? report.includesBookedValue : report.includesRevenue === true;
|
|
496
|
+
const rows = Array.isArray(report.rows) ? report.rows.map((value2) => {
|
|
497
|
+
const row = record(value2);
|
|
498
|
+
const attribution = record(row.attribution);
|
|
499
|
+
const bookedValue = nullableFinite(attribution.bookedValue, attribution.revenue);
|
|
500
|
+
return {
|
|
501
|
+
...row,
|
|
502
|
+
attribution: { ...attribution, bookedValue, revenue: bookedValue }
|
|
503
|
+
};
|
|
504
|
+
}) : [];
|
|
505
|
+
const totals = record(report.totals);
|
|
506
|
+
const totalBookedValue = nullableFinite(totals.bookedValue, totals.revenue);
|
|
507
|
+
return {
|
|
508
|
+
...source,
|
|
509
|
+
report: {
|
|
510
|
+
...report,
|
|
511
|
+
includesBookedValue,
|
|
512
|
+
includesRevenue: includesBookedValue,
|
|
513
|
+
rows,
|
|
514
|
+
totals: { ...totals, bookedValue: totalBookedValue, revenue: totalBookedValue }
|
|
515
|
+
}
|
|
516
|
+
};
|
|
517
|
+
}
|
|
518
|
+
function normalizeChannelReportLink(value) {
|
|
519
|
+
const link = record(value);
|
|
520
|
+
const includesBookedValue = typeof link.includesBookedValue === "boolean" ? link.includesBookedValue : link.includesRevenue === true;
|
|
521
|
+
return {
|
|
522
|
+
...link,
|
|
523
|
+
includesBookedValue,
|
|
524
|
+
includesRevenue: includesBookedValue
|
|
525
|
+
};
|
|
526
|
+
}
|
|
434
527
|
var ManageApiError, ManageApi;
|
|
435
528
|
var init_manageApi = __esm({
|
|
436
529
|
"src/manageApi.ts"() {
|
|
@@ -465,13 +558,28 @@ var init_manageApi = __esm({
|
|
|
465
558
|
}
|
|
466
559
|
return fetch(`${this.base}${path}`, { method, headers, body, credentials: "omit" }).then((r) => parse(r));
|
|
467
560
|
}
|
|
468
|
-
|
|
469
|
-
|
|
561
|
+
async authBlob(path) {
|
|
562
|
+
const res = await fetch(`${this.base}${path}`, {
|
|
563
|
+
method: "GET",
|
|
564
|
+
headers: { Authorization: `Bearer ${this.token}` },
|
|
565
|
+
credentials: "omit"
|
|
566
|
+
});
|
|
567
|
+
if (!res.ok) await parse(res);
|
|
568
|
+
return res.blob();
|
|
470
569
|
}
|
|
471
570
|
// ---- realtime read ----
|
|
472
|
-
/**
|
|
571
|
+
/** Event-pinned organizer geometry. A manage token is never sent to `/pub`. */
|
|
473
572
|
chart(key) {
|
|
474
|
-
return this.
|
|
573
|
+
return this.auth(`/v1/events/${encodeURIComponent(key)}/chart`);
|
|
574
|
+
}
|
|
575
|
+
/** Authenticated bytes for an Event-scoped organizer chart asset. */
|
|
576
|
+
asset(key, asset) {
|
|
577
|
+
if (!/^[a-zA-Z0-9._-]+$/.test(asset)) {
|
|
578
|
+
return Promise.reject(new ManageApiError(404, "not_found", "not_found"));
|
|
579
|
+
}
|
|
580
|
+
return this.authBlob(
|
|
581
|
+
`/v1/events/${encodeURIComponent(key)}/assets/${encodeURIComponent(asset)}`
|
|
582
|
+
);
|
|
475
583
|
}
|
|
476
584
|
/**
|
|
477
585
|
* The ORGANIZER's seat map: physical state, token-authed.
|
|
@@ -531,6 +639,31 @@ var init_manageApi = __esm({
|
|
|
531
639
|
setHoldTtl(key, holdTtlMs) {
|
|
532
640
|
return this.auth(`/v1/events/${encodeURIComponent(key)}/hold-ttl`, { method: "POST", body: { holdTtlMs } });
|
|
533
641
|
}
|
|
642
|
+
// ---- Platform inventory booking history (token) ----
|
|
643
|
+
/** Inventory lifecycle by stable integrator bookingRef. Absent on Managed. */
|
|
644
|
+
bookings(key, query = {}) {
|
|
645
|
+
const params = new URLSearchParams();
|
|
646
|
+
if (query.q) params.set("q", query.q);
|
|
647
|
+
if (query.state) params.set("state", query.state);
|
|
648
|
+
if (query.cursor) params.set("cursor", query.cursor);
|
|
649
|
+
if (query.limit != null) params.set("limit", String(query.limit));
|
|
650
|
+
const qs = params.toString();
|
|
651
|
+
return this.auth(`/v1/events/${encodeURIComponent(key)}/bookings${qs ? `?${qs}` : ""}`);
|
|
652
|
+
}
|
|
653
|
+
/** Exact configured-value snapshot plus book/replay/cancellation audit. */
|
|
654
|
+
booking(key, bookingRef) {
|
|
655
|
+
return this.auth(
|
|
656
|
+
`/v1/events/${encodeURIComponent(key)}/bookings/${encodeURIComponent(bookingRef)}`
|
|
657
|
+
);
|
|
658
|
+
}
|
|
659
|
+
/** Alias matching the server SDK vocabulary. */
|
|
660
|
+
listBookings(key, query = {}) {
|
|
661
|
+
return this.bookings(key, query);
|
|
662
|
+
}
|
|
663
|
+
/** Alias matching the server SDK vocabulary. */
|
|
664
|
+
retrieveBooking(key, bookingRef) {
|
|
665
|
+
return this.booking(key, bookingRef);
|
|
666
|
+
}
|
|
534
667
|
// ---- availability windows (token) ----
|
|
535
668
|
/** The organizer's current per section/zone availability windows (needs
|
|
536
669
|
* `event:view`). Ids absent from `rules` are open / on sale. */
|
|
@@ -712,10 +845,46 @@ var init_manageApi = __esm({
|
|
|
712
845
|
}
|
|
713
846
|
// ---- reports (token) ----
|
|
714
847
|
report(key) {
|
|
715
|
-
return this.auth(`/v1/events/${encodeURIComponent(key)}/report`);
|
|
848
|
+
return this.auth(`/v1/events/${encodeURIComponent(key)}/report`).then(normalizeReportResult);
|
|
716
849
|
}
|
|
717
850
|
controlRoom(key, windowMinutes = 15) {
|
|
718
|
-
return this.auth(
|
|
851
|
+
return this.auth(
|
|
852
|
+
`/v1/events/${encodeURIComponent(key)}/control-room?window=${windowMinutes}`
|
|
853
|
+
).then(normalizeControlRoomSnapshot);
|
|
854
|
+
}
|
|
855
|
+
/** Allocation beside immutable booking-time channel attribution. */
|
|
856
|
+
channelReport(key) {
|
|
857
|
+
return this.auth(
|
|
858
|
+
`/v1/events/${encodeURIComponent(key)}/channels/report`
|
|
859
|
+
).then(normalizeChannelReportResult);
|
|
860
|
+
}
|
|
861
|
+
createChannelReportLink(key, channelId, input = {}) {
|
|
862
|
+
return this.auth(
|
|
863
|
+
`/v1/events/${encodeURIComponent(key)}/channels/${encodeURIComponent(channelId)}/report-links`,
|
|
864
|
+
{ method: "POST", body: input }
|
|
865
|
+
).then((value) => {
|
|
866
|
+
const reveal = record(value);
|
|
867
|
+
return { ...reveal, link: normalizeChannelReportLink(reveal.link) };
|
|
868
|
+
});
|
|
869
|
+
}
|
|
870
|
+
channelReportLinks(key, channelId) {
|
|
871
|
+
return this.auth(
|
|
872
|
+
`/v1/events/${encodeURIComponent(key)}/channels/${encodeURIComponent(channelId)}/report-links`
|
|
873
|
+
).then((value) => {
|
|
874
|
+
const result = record(value);
|
|
875
|
+
return {
|
|
876
|
+
links: Array.isArray(result.links) ? result.links.map(normalizeChannelReportLink) : []
|
|
877
|
+
};
|
|
878
|
+
});
|
|
879
|
+
}
|
|
880
|
+
revokeChannelReportLink(key, channelId, linkId) {
|
|
881
|
+
return this.auth(
|
|
882
|
+
`/v1/events/${encodeURIComponent(key)}/channels/${encodeURIComponent(channelId)}/report-links/${encodeURIComponent(linkId)}`,
|
|
883
|
+
{ method: "DELETE" }
|
|
884
|
+
).then((value) => {
|
|
885
|
+
const result = record(value);
|
|
886
|
+
return { ...result, link: normalizeChannelReportLink(result.link) };
|
|
887
|
+
});
|
|
719
888
|
}
|
|
720
889
|
log(key, opts = {}) {
|
|
721
890
|
const params = new URLSearchParams();
|
|
@@ -4446,9 +4615,54 @@ var PubApi = class {
|
|
|
4446
4615
|
}
|
|
4447
4616
|
return data;
|
|
4448
4617
|
}
|
|
4618
|
+
/**
|
|
4619
|
+
* Binary counterpart to `request`. Buyer media needs the same in-memory
|
|
4620
|
+
* bearer/refresh rules as JSON, but returns bytes that the picker turns into
|
|
4621
|
+
* a blob URL. The bearer stays in the Authorization header and is never
|
|
4622
|
+
* appended to `path`.
|
|
4623
|
+
*/
|
|
4624
|
+
async requestBlob(path, retried = {}) {
|
|
4625
|
+
const headers = {};
|
|
4626
|
+
const authorization = await this.access?.authorization(retried.auth ? "unauthorized" : "initial");
|
|
4627
|
+
if (authorization) headers.Authorization = authorization;
|
|
4628
|
+
const res = await fetch(`${this.base}${path}`, { method: "GET", headers, credentials: "omit" });
|
|
4629
|
+
if (res.ok) return res.blob();
|
|
4630
|
+
const isJson = (res.headers.get("content-type") ?? "").includes("application/json");
|
|
4631
|
+
const data = isJson ? await res.json().catch(() => null) : null;
|
|
4632
|
+
const code = data?.code ?? data?.error;
|
|
4633
|
+
if (this.access?.configured && (res.status === 401 || res.status === 403 || res.status === 422)) {
|
|
4634
|
+
const refreshed = await this.access.handleFailure(res.status, code);
|
|
4635
|
+
if (refreshed && !retried.auth) return this.requestBlob(path, { ...retried, auth: true });
|
|
4636
|
+
}
|
|
4637
|
+
let retryAfterS;
|
|
4638
|
+
if (res.status === 429) {
|
|
4639
|
+
retryAfterS = parseRetryAfter(res.headers.get("Retry-After"), data?.retryAfterSeconds) ?? DEFAULT_RATE_LIMIT_WAIT_S;
|
|
4640
|
+
if (!retried.rateLimit && retryAfterS <= MAX_RATE_LIMIT_WAIT_S) {
|
|
4641
|
+
await new Promise((resolve) => setTimeout(resolve, retryAfterS * 1e3));
|
|
4642
|
+
return this.requestBlob(path, { ...retried, rateLimit: true });
|
|
4643
|
+
}
|
|
4644
|
+
}
|
|
4645
|
+
throw new ApiError(
|
|
4646
|
+
res.status,
|
|
4647
|
+
data?.error ?? `request_failed_${res.status}`,
|
|
4648
|
+
code,
|
|
4649
|
+
void 0,
|
|
4650
|
+
void 0,
|
|
4651
|
+
retryAfterS
|
|
4652
|
+
);
|
|
4653
|
+
}
|
|
4449
4654
|
chart(key) {
|
|
4450
4655
|
return this.request(`/pub/events/${encodeURIComponent(key)}/chart`);
|
|
4451
4656
|
}
|
|
4657
|
+
/** Authenticated bytes for an Event-scoped authored view image. */
|
|
4658
|
+
asset(key, asset) {
|
|
4659
|
+
if (!/^[a-zA-Z0-9._-]+$/.test(asset)) {
|
|
4660
|
+
return Promise.reject(new ApiError(404, "not_found", "not_found"));
|
|
4661
|
+
}
|
|
4662
|
+
return this.requestBlob(
|
|
4663
|
+
`/pub/events/${encodeURIComponent(key)}/assets/${encodeURIComponent(asset)}`
|
|
4664
|
+
);
|
|
4665
|
+
}
|
|
4452
4666
|
objects(key) {
|
|
4453
4667
|
return this.request(`/pub/events/${encodeURIComponent(key)}/objects?compact=1`);
|
|
4454
4668
|
}
|
|
@@ -4551,12 +4765,11 @@ var PubApi = class {
|
|
|
4551
4765
|
/**
|
|
4552
4766
|
* What PickerController opens its own socket with.
|
|
4553
4767
|
*
|
|
4554
|
-
* Empty for an access-scoped client: a
|
|
4768
|
+
* Empty for an access-scoped client: a scoped audience authenticates with a
|
|
4555
4769
|
* subprotocol ticket, which a URL-only constructor cannot carry, so the SDK's
|
|
4556
4770
|
* BuyerRealtimeClient owns that socket instead and the controller skips its
|
|
4557
4771
|
* own (an empty URL is its documented "no live feed" contract). A tokenless
|
|
4558
|
-
* public client returns exactly the URL it always has
|
|
4559
|
-
* public picker's realtime path changes.
|
|
4772
|
+
* Managed public client returns exactly the URL it always has.
|
|
4560
4773
|
*/
|
|
4561
4774
|
socketUrl(key) {
|
|
4562
4775
|
return this.accessScoped ? "" : this.subscribeUrl(key);
|
|
@@ -5902,6 +6115,81 @@ var import_core2 = require("@seatlayer/core");
|
|
|
5902
6115
|
var import_panorama = require("@seatlayer/core/view3d/crossfade/panorama");
|
|
5903
6116
|
var import_panoramaDelivery = require("@seatlayer/core/view/panoramaDelivery");
|
|
5904
6117
|
|
|
6118
|
+
// src/buyerAssets.ts
|
|
6119
|
+
var SAFE_ASSET = /^[a-zA-Z0-9._-]+$/;
|
|
6120
|
+
function buyerEventAssetReference(value) {
|
|
6121
|
+
let url;
|
|
6122
|
+
try {
|
|
6123
|
+
url = new URL(value, "https://seatlayer.invalid");
|
|
6124
|
+
} catch {
|
|
6125
|
+
return null;
|
|
6126
|
+
}
|
|
6127
|
+
if (url.search || url.hash) return null;
|
|
6128
|
+
const match = /^\/pub\/events\/([^/]+)\/assets\/([^/]+)$/.exec(url.pathname);
|
|
6129
|
+
if (!match) return null;
|
|
6130
|
+
try {
|
|
6131
|
+
const eventKey = decodeURIComponent(match[1]);
|
|
6132
|
+
const asset = decodeURIComponent(match[2]);
|
|
6133
|
+
if (!eventKey || !SAFE_ASSET.test(asset)) return null;
|
|
6134
|
+
return { eventKey, asset };
|
|
6135
|
+
} catch {
|
|
6136
|
+
return null;
|
|
6137
|
+
}
|
|
6138
|
+
}
|
|
6139
|
+
function looksLikeBuyerAsset(value) {
|
|
6140
|
+
try {
|
|
6141
|
+
return /^\/pub\/events\/[^/]+\/assets(?:\/|$)/.test(
|
|
6142
|
+
new URL(value, "https://seatlayer.invalid").pathname
|
|
6143
|
+
);
|
|
6144
|
+
} catch {
|
|
6145
|
+
return false;
|
|
6146
|
+
}
|
|
6147
|
+
}
|
|
6148
|
+
var BuyerAssetObjectUrls = class {
|
|
6149
|
+
constructor(eventKey, load) {
|
|
6150
|
+
this.eventKey = eventKey;
|
|
6151
|
+
this.load = load;
|
|
6152
|
+
this.pending = /* @__PURE__ */ new Map();
|
|
6153
|
+
this.created = /* @__PURE__ */ new Set();
|
|
6154
|
+
this.disposed = false;
|
|
6155
|
+
}
|
|
6156
|
+
/**
|
|
6157
|
+
* External organizer/CDN URLs pass through unchanged. SeatLayer event assets
|
|
6158
|
+
* never do: they require the transport, and a reference for another Event is
|
|
6159
|
+
* refused instead of being loaded anonymously.
|
|
6160
|
+
*/
|
|
6161
|
+
resolve(reference) {
|
|
6162
|
+
const parsed = buyerEventAssetReference(reference);
|
|
6163
|
+
if (!parsed) {
|
|
6164
|
+
return Promise.resolve(looksLikeBuyerAsset(reference) ? null : reference);
|
|
6165
|
+
}
|
|
6166
|
+
if (parsed.eventKey !== this.eventKey || !this.load || this.disposed) return Promise.resolve(null);
|
|
6167
|
+
const existing = this.pending.get(reference);
|
|
6168
|
+
if (existing) return existing;
|
|
6169
|
+
const task = this.load(this.eventKey, parsed.asset).then((blob) => {
|
|
6170
|
+
const objectUrl = URL.createObjectURL(blob);
|
|
6171
|
+
if (this.disposed) {
|
|
6172
|
+
URL.revokeObjectURL(objectUrl);
|
|
6173
|
+
return null;
|
|
6174
|
+
}
|
|
6175
|
+
this.created.add(objectUrl);
|
|
6176
|
+
return objectUrl;
|
|
6177
|
+
}).catch((error) => {
|
|
6178
|
+
this.pending.delete(reference);
|
|
6179
|
+
throw error;
|
|
6180
|
+
});
|
|
6181
|
+
this.pending.set(reference, task);
|
|
6182
|
+
return task;
|
|
6183
|
+
}
|
|
6184
|
+
dispose() {
|
|
6185
|
+
if (this.disposed) return;
|
|
6186
|
+
this.disposed = true;
|
|
6187
|
+
for (const url of this.created) URL.revokeObjectURL(url);
|
|
6188
|
+
this.created.clear();
|
|
6189
|
+
this.pending.clear();
|
|
6190
|
+
}
|
|
6191
|
+
};
|
|
6192
|
+
|
|
5905
6193
|
// src/offerAvailability.ts
|
|
5906
6194
|
var SALE_STATES = [
|
|
5907
6195
|
"on-sale",
|
|
@@ -6160,6 +6448,16 @@ var CSS2 = (
|
|
|
6160
6448
|
.sl-close.on{display:inline-flex}
|
|
6161
6449
|
.sl-close svg{width:14px;height:14px;stroke:currentColor;stroke-width:2;fill:none;stroke-linecap:round}
|
|
6162
6450
|
|
|
6451
|
+
/* A page or popup may already own the event heading. In that case only the
|
|
6452
|
+
duplicate identity disappears; operational header controls stay available.
|
|
6453
|
+
Zero block padding lets an otherwise-empty header collapse completely. */
|
|
6454
|
+
.sl-picker[data-event-details-hidden="true"] .sl-head{padding-block:0;border-bottom-width:0}
|
|
6455
|
+
.sl-picker[data-event-details-hidden="true"] .sl-logo,
|
|
6456
|
+
.sl-picker[data-event-details-hidden="true"] .sl-head-info{display:none!important}
|
|
6457
|
+
.sl-picker[data-event-details-hidden="true"] .sl-hold-pill.on,
|
|
6458
|
+
.sl-picker[data-event-details-hidden="true"] .sl-closed-pill.on,
|
|
6459
|
+
.sl-picker[data-event-details-hidden="true"] .sl-close.on{margin-block:10px}
|
|
6460
|
+
|
|
6163
6461
|
/* body */
|
|
6164
6462
|
.sl-body{display:flex;flex:1;min-height:0}
|
|
6165
6463
|
.sl-map{position:relative;flex:1;min-width:0}
|
|
@@ -6177,7 +6475,7 @@ var CSS2 = (
|
|
|
6177
6475
|
.sl-picker[data-layout="narrow"] .sl-side{width:100%;border-left:0;border-top:1px solid var(--sl-line);
|
|
6178
6476
|
flex:none;height:min(72%,480px);overflow:hidden;transition:height .3s cubic-bezier(.2,.8,.2,1);overscroll-behavior:contain}
|
|
6179
6477
|
.sl-picker[data-layout="narrow"][data-sheet="open"][data-has-selection="false"] .sl-side{height:min(252px,52%)}
|
|
6180
|
-
.sl-picker[data-layout="narrow"][data-sheet="peek"] .sl-side{height:
|
|
6478
|
+
.sl-picker[data-layout="narrow"][data-sheet="peek"] .sl-side{height:76px;overflow:hidden}
|
|
6181
6479
|
.sl-picker[data-layout="narrow"][data-sheet="peek"] .sl-side > :not(.sl-sheet-head){display:none}
|
|
6182
6480
|
.sl-picker[data-layout="narrow"] .sl-tray{flex:1;min-height:0;overflow-y:auto;overscroll-behavior:contain}
|
|
6183
6481
|
.sl-picker[data-layout="narrow"] .sl-foot{position:static;background:var(--sl-bg)}
|
|
@@ -6212,9 +6510,10 @@ var CSS2 = (
|
|
|
6212
6510
|
head is the tap/swipe toggle target (min 44px), so it reads as one control. */
|
|
6213
6511
|
.sl-sheet-head{display:none;flex-direction:column;justify-content:center;padding:6px 12px 8px;min-height:56px;
|
|
6214
6512
|
cursor:pointer;touch-action:none;user-select:none;-webkit-user-select:none;flex:none}
|
|
6215
|
-
.sl-picker[data-layout="narrow"] .sl-sheet-head{display:flex}
|
|
6216
|
-
.sl-sheet-
|
|
6217
|
-
.sl-sheet-
|
|
6513
|
+
.sl-picker[data-layout="narrow"] .sl-sheet-head{display:flex;min-height:64px;padding:4px 10px 6px}
|
|
6514
|
+
.sl-picker[data-layout="narrow"][data-sheet="peek"] .sl-sheet-head{height:100%}
|
|
6515
|
+
.sl-sheet-grab{width:36px;height:4px;border-radius:999px;background:var(--sl-muted);opacity:.55;margin:1px auto 5px}
|
|
6516
|
+
.sl-sheet-bar{display:flex;align-items:center;gap:8px;min-height:44px}
|
|
6218
6517
|
.sl-sheet-peek{display:flex;align-items:center;gap:7px;flex:1;min-width:0;font-size:13px;font-weight:700;color:var(--sl-text);
|
|
6219
6518
|
white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
|
|
6220
6519
|
.sl-sheet-peek .sub{color:var(--sl-muted);font-weight:600}
|
|
@@ -6242,7 +6541,7 @@ var CSS2 = (
|
|
|
6242
6541
|
.sl-picker[data-layout="narrow"][data-has-selection="true"] .sl-filtersec.has,
|
|
6243
6542
|
.sl-picker[data-layout="narrow"][data-has-selection="true"] .sl-filters.has{display:none}
|
|
6244
6543
|
/* Accessibility and colour-safety controls must remain reachable on phones.
|
|
6245
|
-
Keep them out of the
|
|
6544
|
+
Keep them out of the collapsed peek, then reveal their consolidated row whenever
|
|
6246
6545
|
the buyer explicitly opens the ticket panel. */
|
|
6247
6546
|
.sl-picker[data-layout="narrow"][data-sheet="open"] .sl-filtersec.has{display:block!important}
|
|
6248
6547
|
.sl-picker[data-layout="narrow"][data-sheet="open"] .sl-filters.has{display:flex!important}
|
|
@@ -6385,15 +6684,10 @@ var CSS2 = (
|
|
|
6385
6684
|
.sl-picker .sl-cta:disabled{background:var(--sl-surface);color:var(--sl-muted);opacity:1;
|
|
6386
6685
|
cursor:not-allowed;filter:none;transform:none}
|
|
6387
6686
|
|
|
6388
|
-
/* Chrome anchor regions (Feature 6) \u2014 every
|
|
6687
|
+
/* Chrome anchor regions (Feature 6) \u2014 every interactive map overlay is APPENDED
|
|
6389
6688
|
INTO one of these positioned flex containers and flows/stacks within it, so no
|
|
6390
|
-
two
|
|
6391
|
-
|
|
6392
|
-
/* align-items:center, not the flex default of stretch. Without it a short pill
|
|
6393
|
-
beside a taller control (TEST MODE next to the Map/3D toggle) is stretched to
|
|
6394
|
-
the row's height, so two pills that should read as a matched pair end up
|
|
6395
|
-
different shapes on different baselines. Column regions set their own
|
|
6396
|
-
cross-axis alignment below and are unaffected. */
|
|
6689
|
+
two controls free-float on top of each other. Regions never overlap: the top
|
|
6690
|
+
strip splits into left/center/right; rails + corners own their edge. */
|
|
6397
6691
|
.sl-anchor{position:absolute;z-index:5;display:flex;align-items:center;gap:8px;pointer-events:none}
|
|
6398
6692
|
.sl-anchor > *{pointer-events:auto}
|
|
6399
6693
|
.sl-anchor[data-region="top-left"]{top:12px;left:12px;flex-wrap:wrap;max-width:38%}
|
|
@@ -6409,18 +6703,17 @@ var CSS2 = (
|
|
|
6409
6703
|
.sl-picker[data-layout="narrow"] .sl-anchor[data-region="top-left"]{max-width:30%}
|
|
6410
6704
|
.sl-picker[data-layout="narrow"] .sl-anchor[data-region="top-center"]{max-width:44%}
|
|
6411
6705
|
|
|
6412
|
-
/* TEST MODE
|
|
6413
|
-
|
|
6414
|
-
|
|
6415
|
-
|
|
6416
|
-
|
|
6417
|
-
|
|
6418
|
-
|
|
6419
|
-
|
|
6420
|
-
|
|
6421
|
-
|
|
6422
|
-
|
|
6423
|
-
.sl-picker[data-layout="narrow"] .sl-testbadge{padding:0 8px;font-size:8.5px;letter-spacing:.06em}
|
|
6706
|
+
/* TEST MODE is environment context, not an action. A clipped corner ribbon
|
|
6707
|
+
keeps it persistent without impersonating a button or competing with Map/3D.
|
|
6708
|
+
The top-left interactive region moves below it only on test events. */
|
|
6709
|
+
.sl-testbadge{position:absolute;top:17px;left:-38px;z-index:6;width:142px;padding:5px 0;
|
|
6710
|
+
transform:rotate(-45deg);text-align:center;pointer-events:none;
|
|
6711
|
+
font-size:9.5px;font-weight:850;letter-spacing:.13em;line-height:1.2;text-transform:uppercase;
|
|
6712
|
+
white-space:nowrap;background:var(--sl-accent);color:var(--sl-accent-ink);
|
|
6713
|
+
box-shadow:0 2px 8px rgba(0,0,0,.28)}
|
|
6714
|
+
.sl-picker[data-event-mode="test"] .sl-anchor[data-region="top-left"]{top:96px}
|
|
6715
|
+
.sl-picker[data-layout="narrow"] .sl-testbadge{top:14px;left:-35px;width:128px;font-size:8.5px}
|
|
6716
|
+
.sl-picker[data-layout="narrow"][data-event-mode="test"] .sl-anchor[data-region="top-left"]{top:88px}
|
|
6424
6717
|
|
|
6425
6718
|
/* zoom column (flows within the bottom-right region) */
|
|
6426
6719
|
.sl-zoom{display:flex;flex-direction:column;gap:6px}
|
|
@@ -6655,10 +6948,15 @@ var CSS2 = (
|
|
|
6655
6948
|
.sl-ba-actions{grid-column:1/-1;display:grid;grid-template-columns:1fr 1fr;gap:7px}
|
|
6656
6949
|
.sl-ba-actions button{min-height:36px;border-radius:9px;border:1px solid var(--sl-line);font-size:11.5px;font-weight:800}
|
|
6657
6950
|
.sl-ba-actions .replace{border-color:var(--sl-accent);background:var(--sl-accent);color:var(--sl-accent-ink)}
|
|
6658
|
-
.sl-picker[data-layout="narrow"] .sl-ba{padding:
|
|
6951
|
+
.sl-picker[data-layout="narrow"] .sl-ba{padding:9px;gap:6px}
|
|
6952
|
+
.sl-picker[data-layout="narrow"] .sl-ba::after{display:none}
|
|
6953
|
+
.sl-picker[data-layout="narrow"] .sl-ba-title{font-size:12.5px}
|
|
6954
|
+
.sl-picker[data-layout="narrow"] .sl-ba-copy{display:none}
|
|
6659
6955
|
.sl-picker[data-layout="narrow"] .sl-ba-copy .wide{display:none}
|
|
6660
6956
|
.sl-picker[data-layout="narrow"] .sl-ba-copy .narrow{display:inline}
|
|
6661
|
-
.sl-picker[data-layout="narrow"] .sl-ba select{min-height:
|
|
6957
|
+
.sl-picker[data-layout="narrow"] .sl-ba select{min-height:40px}
|
|
6958
|
+
.sl-picker[data-layout="narrow"] .sl-ba-qty button{width:30px;height:30px}
|
|
6959
|
+
.sl-picker[data-layout="narrow"] .sl-ba-go{min-height:40px}
|
|
6662
6960
|
|
|
6663
6961
|
/* screen-reader live region */
|
|
6664
6962
|
.sl-sr{position:absolute;width:1px;height:1px;margin:-1px;overflow:hidden;clip:rect(0 0 0 0);white-space:nowrap}
|
|
@@ -7066,6 +7364,8 @@ var SeatPicker = class _SeatPicker {
|
|
|
7066
7364
|
this.secCardEl = null;
|
|
7067
7365
|
this.viewEl = null;
|
|
7068
7366
|
this.viewCleanup = null;
|
|
7367
|
+
/** Supersedes an older authored-view byte request when another seat is opened. */
|
|
7368
|
+
this.seatViewGen = 0;
|
|
7069
7369
|
this.allSeatsCache = null;
|
|
7070
7370
|
// F3 minimap
|
|
7071
7371
|
this.miniCanvas = null;
|
|
@@ -7099,6 +7399,8 @@ var SeatPicker = class _SeatPicker {
|
|
|
7099
7399
|
this.fsFallback = false;
|
|
7100
7400
|
this.fsChangeHandler = null;
|
|
7101
7401
|
this.fsEscHandler = null;
|
|
7402
|
+
/** Host-level event chrome owns the duplicate identity outside full screen. */
|
|
7403
|
+
this.eventDetailsHidden = false;
|
|
7102
7404
|
/** True once we've asked the host page to pin us fullscreen (framed, no native). */
|
|
7103
7405
|
this.framedFs = false;
|
|
7104
7406
|
/** Last height (px) posted to a host frame; dedupes redundant reports. */
|
|
@@ -7121,6 +7423,7 @@ var SeatPicker = class _SeatPicker {
|
|
|
7121
7423
|
if (!options.event || typeof options.event !== "string") throw new Error("seatmap: `event` key is required");
|
|
7122
7424
|
if (!options.container) throw new Error("seatmap: `container` is required (or use SeatPicker.open())");
|
|
7123
7425
|
this.opts = { ...options, confirmSelection: options.confirmSelection ?? true };
|
|
7426
|
+
this.eventDetailsHidden = !!options.hideEventDetails;
|
|
7124
7427
|
this.hostPricing = options.pricing;
|
|
7125
7428
|
this.apiBase = (options.apiBase ?? DEFAULT_API_BASE2).replace(/\/+$/, "");
|
|
7126
7429
|
this.access = options.transport ? null : createBuyerAccessContext(options, {
|
|
@@ -7138,6 +7441,10 @@ var SeatPicker = class _SeatPicker {
|
|
|
7138
7441
|
onObjectUnavailable: (event) => this.opts.onSelectedObjectUnavailable?.(event)
|
|
7139
7442
|
});
|
|
7140
7443
|
this.api = options.transport ?? this.pubApi;
|
|
7444
|
+
this.buyerAssetUrls = new BuyerAssetObjectUrls(
|
|
7445
|
+
options.event,
|
|
7446
|
+
this.api.asset ? (key, asset) => this.api.asset(key, asset) : void 0
|
|
7447
|
+
);
|
|
7141
7448
|
if (options.checkout === "hosted" && !this.pubApi) {
|
|
7142
7449
|
console.warn(
|
|
7143
7450
|
'seatlayer: checkout: "hosted" needs the widget\'s own transport \u2014 a custom `transport` owns its backend, so the picker is staying on onCheckout for this mount.'
|
|
@@ -7266,7 +7573,7 @@ var SeatPicker = class _SeatPicker {
|
|
|
7266
7573
|
}
|
|
7267
7574
|
}
|
|
7268
7575
|
const sightHtml = hasStage && distance != null ? `<div class="sl-confirm-sight">${(0, import_core2.t)("picker.sightline", { m: distance })}</div>` : "";
|
|
7269
|
-
const viewBtn = realPhoto ? `<button type="button" class="sl-confirm-view sl-confirm-thumbwrap" aria-label="${(0, import_core2.t)("picker.viewFromSeat", { label: seat.label })}"><img class="sl-confirm-thumb"
|
|
7576
|
+
const viewBtn = realPhoto ? `<button type="button" class="sl-confirm-view sl-confirm-thumbwrap" aria-label="${(0, import_core2.t)("picker.viewFromSeat", { label: seat.label })}"><img class="sl-confirm-thumb" alt="" /><span class="sl-confirm-thumb-badge">\u{1F52D} ${this.tf("picker.viewFromHere", "View from here")}</span></button>` : `<button type="button" class="sl-confirm-view sl-confirm-viewbtn" aria-label="${(0, import_core2.t)("picker.viewFromSeat", { label: seat.label })}"><span aria-hidden="true">\u{1F52D}</span><span>${this.tf("picker.viewFromHere", "View from here")}</span></button>`;
|
|
7270
7577
|
return viewBtn + sightHtml;
|
|
7271
7578
|
}
|
|
7272
7579
|
/** "See it in 3D" (2D) / "View from this seat" (already in 3D) action for the
|
|
@@ -7401,6 +7708,10 @@ var SeatPicker = class _SeatPicker {
|
|
|
7401
7708
|
}
|
|
7402
7709
|
syncFullscreenButtons() {
|
|
7403
7710
|
const active = !!document.fullscreenElement || this.fsFallback || this.framedFs;
|
|
7711
|
+
const hideEventDetails = this.eventDetailsHidden && !active;
|
|
7712
|
+
this.root?.setAttribute("data-event-details-hidden", String(hideEventDetails));
|
|
7713
|
+
this.els.logo?.toggleAttribute("hidden", hideEventDetails);
|
|
7714
|
+
this.els.headInfo?.toggleAttribute("hidden", hideEventDetails);
|
|
7404
7715
|
this.els.zfs?.setAttribute("aria-pressed", String(active));
|
|
7405
7716
|
this.view3dEl?.querySelector(".sl-view3d-fs")?.setAttribute("aria-pressed", String(active));
|
|
7406
7717
|
}
|
|
@@ -7544,7 +7855,7 @@ var SeatPicker = class _SeatPicker {
|
|
|
7544
7855
|
root.innerHTML = `
|
|
7545
7856
|
<div class="sl-head">
|
|
7546
7857
|
<div class="sl-logo" data-ref="logo"></div>
|
|
7547
|
-
<div class="sl-head-info">
|
|
7858
|
+
<div class="sl-head-info" data-ref="headInfo">
|
|
7548
7859
|
<div class="sl-head-name" data-ref="name"></div>
|
|
7549
7860
|
<div class="sl-head-meta" data-ref="meta"></div>
|
|
7550
7861
|
</div>
|
|
@@ -7606,6 +7917,7 @@ var SeatPicker = class _SeatPicker {
|
|
|
7606
7917
|
this.els[el2.dataset.ref] = el2;
|
|
7607
7918
|
});
|
|
7608
7919
|
this.mapHost = this.els.map;
|
|
7920
|
+
this.syncFullscreenButtons();
|
|
7609
7921
|
void this.refreshOfferAvailability(false);
|
|
7610
7922
|
if (this.api.availability) {
|
|
7611
7923
|
this.offerVisibilityHandler = () => {
|
|
@@ -7717,7 +8029,8 @@ var SeatPicker = class _SeatPicker {
|
|
|
7717
8029
|
}
|
|
7718
8030
|
this.els.boot.remove();
|
|
7719
8031
|
this.startRealtime();
|
|
7720
|
-
this.salesClosed = !!info.salesClosed;
|
|
8032
|
+
this.salesClosed = !!info.salesClosed || !!this.opts.readOnly;
|
|
8033
|
+
root.dataset.eventMode = info.mode === "test" ? "test" : "live";
|
|
7721
8034
|
this.controller.setViewMode(this.normalizeInitialView(this.opts.initialView));
|
|
7722
8035
|
this.buildRegions();
|
|
7723
8036
|
this.regions["bottom-right"].appendChild(this.els.zoom);
|
|
@@ -7727,7 +8040,7 @@ var SeatPicker = class _SeatPicker {
|
|
|
7727
8040
|
badge.className = "sl-testbadge";
|
|
7728
8041
|
badge.textContent = (0, import_core2.t)("picker.testMode");
|
|
7729
8042
|
badge.setAttribute("aria-label", (0, import_core2.t)("picker.testMode"));
|
|
7730
|
-
this.
|
|
8043
|
+
this.els.map.appendChild(badge);
|
|
7731
8044
|
}
|
|
7732
8045
|
const chartTheme = this.controller.doc?.theme;
|
|
7733
8046
|
Object.entries(resolveTokens(chartTheme, this.opts.theme)).forEach(([k, v]) => root.style.setProperty(k, v));
|
|
@@ -7964,8 +8277,9 @@ var SeatPicker = class _SeatPicker {
|
|
|
7964
8277
|
* idempotent DOM apply used at load and on transition.
|
|
7965
8278
|
*/
|
|
7966
8279
|
setSalesClosed(closed) {
|
|
7967
|
-
|
|
7968
|
-
this.salesClosed
|
|
8280
|
+
const next = closed || !!this.opts.readOnly;
|
|
8281
|
+
if (this.salesClosed === next) return;
|
|
8282
|
+
this.salesClosed = next;
|
|
7969
8283
|
this.applySalesClosed();
|
|
7970
8284
|
}
|
|
7971
8285
|
applySalesClosed() {
|
|
@@ -8849,6 +9163,13 @@ var SeatPicker = class _SeatPicker {
|
|
|
8849
9163
|
el2.innerHTML = `<div class="sl-confirm-grid">` + identityFields + `</div><div class="sl-confirm-cat"><span class="sl-dot" style="background:${cat?.color ?? "#6e7bff"}"></span><span class="sl-confirm-cat-name">${safe(details?.categoryLabel ?? cat?.label ?? seat.categoryKey)}</span>` + (price != null ? `<span class="sl-confirm-price">${this.money(price)}</span>` : "") + `</div><div class="sl-confirm-body">` + this.wheelchairConfirmHtml(details?.wheelchairSpaceType) + this.commercialConfirmHtml(seat.commercial) + (this.seatViewEnabled() ? this.confirmThumbHtml(seat) : "") + this.see3dConfirmHtml() + `<div class="sl-confirm-row"><button type="button" class="sl-confirm-cancel">Cancel</button><button type="button" class="sl-confirm-add"><svg viewBox="0 0 24 24" aria-hidden="true"><path d="M5 12.5l4 4L19 7"/></svg>Select</button></div></div>`;
|
|
8850
9164
|
this.els.map.appendChild(el2);
|
|
8851
9165
|
this.confirmEl = el2;
|
|
9166
|
+
const thumb = el2.querySelector(".sl-confirm-thumb");
|
|
9167
|
+
if (thumb && seat.viewUrl) {
|
|
9168
|
+
const thumbReference = seat.viewMeta?.previewUrl ?? seat.viewUrl;
|
|
9169
|
+
void this.buyerAssetUrls.resolve(thumbReference).then((url) => {
|
|
9170
|
+
if (url && el2.isConnected && this.confirmEl === el2) thumb.src = url;
|
|
9171
|
+
}).catch((error) => this.opts.onError?.(error));
|
|
9172
|
+
}
|
|
8852
9173
|
this.reanchorConfirm();
|
|
8853
9174
|
el2.querySelector(".sl-confirm-view")?.addEventListener("click", () => void this.openSeatView(seat));
|
|
8854
9175
|
el2.querySelector(".sl-confirm-3d")?.addEventListener("click", () => {
|
|
@@ -8929,6 +9250,7 @@ var SeatPicker = class _SeatPicker {
|
|
|
8929
9250
|
*/
|
|
8930
9251
|
async openSeatView(seat) {
|
|
8931
9252
|
if (!this.root || !this.seatViewEnabled()) return;
|
|
9253
|
+
const generation = ++this.seatViewGen;
|
|
8932
9254
|
const doc = this.controller.doc;
|
|
8933
9255
|
const activeId = this.controller.getActiveFloorId();
|
|
8934
9256
|
const focal = seat.focalPoint ?? doc?.floors?.find((f) => f.id === activeId)?.focalPoint ?? doc?.focalPoint ?? { x: 0, y: 0 };
|
|
@@ -8936,9 +9258,24 @@ var SeatPicker = class _SeatPicker {
|
|
|
8936
9258
|
let caption;
|
|
8937
9259
|
let real = false;
|
|
8938
9260
|
if (seat.viewUrl) {
|
|
9261
|
+
let resolvedUrl;
|
|
9262
|
+
let resolvedPreviewUrl = null;
|
|
9263
|
+
try {
|
|
9264
|
+
const previewReference = seat.viewMeta?.previewUrl;
|
|
9265
|
+
if (previewReference && previewReference !== seat.viewUrl) {
|
|
9266
|
+
resolvedPreviewUrl = await this.buyerAssetUrls.resolve(previewReference);
|
|
9267
|
+
resolvedUrl = seat.viewUrl;
|
|
9268
|
+
} else {
|
|
9269
|
+
resolvedUrl = await this.buyerAssetUrls.resolve(seat.viewUrl);
|
|
9270
|
+
}
|
|
9271
|
+
} catch (error) {
|
|
9272
|
+
if (generation === this.seatViewGen) this.opts.onError?.(error);
|
|
9273
|
+
return;
|
|
9274
|
+
}
|
|
9275
|
+
if (generation !== this.seatViewGen || !resolvedUrl || seat.viewMeta?.previewUrl && seat.viewMeta.previewUrl !== seat.viewUrl && !resolvedPreviewUrl || !this.root || !this.seatViewEnabled()) return;
|
|
8939
9276
|
const view = {
|
|
8940
|
-
url:
|
|
8941
|
-
...
|
|
9277
|
+
url: resolvedUrl,
|
|
9278
|
+
...resolvedPreviewUrl ? { previewUrl: resolvedPreviewUrl } : {},
|
|
8942
9279
|
...seat.viewMeta?.sourceWidth !== void 0 ? { sourceWidth: seat.viewMeta.sourceWidth } : {},
|
|
8943
9280
|
...seat.viewMeta?.sourceHeight !== void 0 ? { sourceHeight: seat.viewMeta.sourceHeight } : {},
|
|
8944
9281
|
...seat.viewMeta?.previewWidth !== void 0 ? { previewWidth: seat.viewMeta.previewWidth } : {},
|
|
@@ -8956,14 +9293,14 @@ var SeatPicker = class _SeatPicker {
|
|
|
8956
9293
|
const { generateSeatPanorama } = await loadPanorama();
|
|
8957
9294
|
pano2 = generateSeatPanorama(seat, focal, this.allSeats());
|
|
8958
9295
|
} catch (err) {
|
|
8959
|
-
this.opts.onError?.(err);
|
|
9296
|
+
if (generation === this.seatViewGen) this.opts.onError?.(err);
|
|
8960
9297
|
return;
|
|
8961
9298
|
}
|
|
8962
|
-
if (!this.root || !this.seatViewEnabled()) return;
|
|
9299
|
+
if (generation !== this.seatViewGen || !this.root || !this.seatViewEnabled()) return;
|
|
8963
9300
|
panoSource = { url: pano2.url, generated: true };
|
|
8964
9301
|
caption = (0, import_core2.t)("picker.illustrationCaption", { m: pano2.distanceM });
|
|
8965
9302
|
}
|
|
8966
|
-
this.closeSeatView();
|
|
9303
|
+
this.closeSeatView(false);
|
|
8967
9304
|
const el2 = document.createElement("div");
|
|
8968
9305
|
el2.className = "sl-view";
|
|
8969
9306
|
el2.setAttribute("role", "dialog");
|
|
@@ -8979,9 +9316,12 @@ var SeatPicker = class _SeatPicker {
|
|
|
8979
9316
|
};
|
|
8980
9317
|
if (delivery.upgradeUrl) {
|
|
8981
9318
|
cancelUpgrade = (0, import_panoramaDelivery.schedulePanoramaUpgrade)(() => {
|
|
8982
|
-
void
|
|
8983
|
-
if (!
|
|
8984
|
-
|
|
9319
|
+
void this.buyerAssetUrls.resolve(delivery.upgradeUrl).then((url) => {
|
|
9320
|
+
if (!url || loadAbort.signal.aborted) return null;
|
|
9321
|
+
return (0, import_panoramaDelivery.loadPanoramaImage)(url, loadAbort.signal).then(() => url);
|
|
9322
|
+
}).then((url) => {
|
|
9323
|
+
if (!url || !el2.isConnected || loadAbort.signal.aborted) return;
|
|
9324
|
+
pano.style.backgroundImage = `url("${url}")`;
|
|
8985
9325
|
}).catch(() => {
|
|
8986
9326
|
});
|
|
8987
9327
|
});
|
|
@@ -9057,7 +9397,8 @@ var SeatPicker = class _SeatPicker {
|
|
|
9057
9397
|
el2.removeEventListener("keydown", onKey);
|
|
9058
9398
|
};
|
|
9059
9399
|
}
|
|
9060
|
-
closeSeatView() {
|
|
9400
|
+
closeSeatView(cancelPending = true) {
|
|
9401
|
+
if (cancelPending) this.seatViewGen += 1;
|
|
9061
9402
|
this.viewCleanup?.();
|
|
9062
9403
|
this.viewCleanup = null;
|
|
9063
9404
|
this.viewEl?.remove();
|
|
@@ -9989,6 +10330,15 @@ var SeatPicker = class _SeatPicker {
|
|
|
9989
10330
|
this.opts.theme = { ...this.opts.theme ?? {}, map: map ?? void 0 };
|
|
9990
10331
|
this.controller.setMapTheme(map);
|
|
9991
10332
|
}
|
|
10333
|
+
/**
|
|
10334
|
+
* Let a host suppress duplicate event identity after mount without remounting
|
|
10335
|
+
* the live picker (and therefore without disturbing a selection or hold).
|
|
10336
|
+
* Full-screen mode still restores the identity until the buyer exits it.
|
|
10337
|
+
*/
|
|
10338
|
+
setEventDetailsHidden(hidden) {
|
|
10339
|
+
this.eventDetailsHidden = hidden;
|
|
10340
|
+
this.syncFullscreenButtons();
|
|
10341
|
+
}
|
|
9992
10342
|
/**
|
|
9993
10343
|
* Replace the host pricing override AFTER mount, and repaint everything that
|
|
9994
10344
|
* shows a price.
|
|
@@ -10140,19 +10490,33 @@ var SeatPicker = class _SeatPicker {
|
|
|
10140
10490
|
async seatViewFor3d(seatId) {
|
|
10141
10491
|
const seat = this.allSeats().find((s) => s.id === seatId);
|
|
10142
10492
|
if (!seat) return null;
|
|
10143
|
-
if (seat.viewUrl)
|
|
10144
|
-
|
|
10145
|
-
|
|
10146
|
-
|
|
10147
|
-
|
|
10148
|
-
|
|
10149
|
-
|
|
10150
|
-
|
|
10151
|
-
|
|
10152
|
-
|
|
10153
|
-
|
|
10154
|
-
|
|
10155
|
-
|
|
10493
|
+
if (seat.viewUrl) {
|
|
10494
|
+
try {
|
|
10495
|
+
const previewReference = seat.viewMeta?.previewUrl;
|
|
10496
|
+
const progressive = !!previewReference && previewReference !== seat.viewUrl;
|
|
10497
|
+
const previewUrl = progressive ? await this.buyerAssetUrls.resolve(previewReference) : null;
|
|
10498
|
+
const url = progressive ? seat.viewUrl : await this.buyerAssetUrls.resolve(seat.viewUrl);
|
|
10499
|
+
if (!url) return null;
|
|
10500
|
+
if (progressive && !previewUrl) return null;
|
|
10501
|
+
return {
|
|
10502
|
+
url,
|
|
10503
|
+
...previewUrl ? { previewUrl } : {},
|
|
10504
|
+
...progressive ? { resolveUrl: (reference) => this.buyerAssetUrls.resolve(reference) } : {},
|
|
10505
|
+
...seat.viewMeta?.sourceWidth !== void 0 ? { sourceWidth: seat.viewMeta.sourceWidth } : {},
|
|
10506
|
+
...seat.viewMeta?.sourceHeight !== void 0 ? { sourceHeight: seat.viewMeta.sourceHeight } : {},
|
|
10507
|
+
...seat.viewMeta?.previewWidth !== void 0 ? { previewWidth: seat.viewMeta.previewWidth } : {},
|
|
10508
|
+
...seat.viewMeta?.previewHeight !== void 0 ? { previewHeight: seat.viewMeta.previewHeight } : {},
|
|
10509
|
+
...seat.viewMeta?.initialBearingDeg !== void 0 ? { initialBearingDeg: seat.viewMeta.initialBearingDeg } : {},
|
|
10510
|
+
...seat.viewMeta?.initialPitchDeg !== void 0 ? { initialPitchDeg: seat.viewMeta.initialPitchDeg } : {},
|
|
10511
|
+
...seat.viewMeta?.coverage ? { coverage: seat.viewMeta.coverage } : {},
|
|
10512
|
+
...seat.viewMeta?.capturedAt ? { capturedAt: seat.viewMeta.capturedAt } : {},
|
|
10513
|
+
...seat.viewMeta?.sourceLabel ? { sourceLabel: seat.viewMeta.sourceLabel } : {}
|
|
10514
|
+
};
|
|
10515
|
+
} catch (error) {
|
|
10516
|
+
this.opts.onError?.(error);
|
|
10517
|
+
return null;
|
|
10518
|
+
}
|
|
10519
|
+
}
|
|
10156
10520
|
const doc = this.controller.doc;
|
|
10157
10521
|
if (!doc) return null;
|
|
10158
10522
|
const activeId = this.controller.getActiveFloorId();
|
|
@@ -10735,6 +11099,7 @@ var SeatPicker = class _SeatPicker {
|
|
|
10735
11099
|
this.closeSeatView();
|
|
10736
11100
|
this.closeCheckoutPanel();
|
|
10737
11101
|
this.exit3d();
|
|
11102
|
+
this.buyerAssetUrls.dispose();
|
|
10738
11103
|
this.stopHoldTimer();
|
|
10739
11104
|
if (this.toastTimer) clearTimeout(this.toastTimer);
|
|
10740
11105
|
if (this.liveTimer) clearTimeout(this.liveTimer);
|
|
@@ -10855,6 +11220,108 @@ function attachPickerFrame(iframe, opts = {}) {
|
|
|
10855
11220
|
// src/SeatManager.ts
|
|
10856
11221
|
var import_core3 = require("@seatlayer/core");
|
|
10857
11222
|
init_manageApi();
|
|
11223
|
+
|
|
11224
|
+
// src/manageAssets.ts
|
|
11225
|
+
var SAFE_ASSET2 = /^[a-zA-Z0-9._-]+$/;
|
|
11226
|
+
function organizerEventAssetReference(value) {
|
|
11227
|
+
let url;
|
|
11228
|
+
try {
|
|
11229
|
+
url = new URL(value, "https://seatlayer.invalid");
|
|
11230
|
+
} catch {
|
|
11231
|
+
return null;
|
|
11232
|
+
}
|
|
11233
|
+
if (url.search || url.hash) return null;
|
|
11234
|
+
const match = /^\/v1\/events\/([^/]+)\/assets\/([^/]+)$/.exec(url.pathname);
|
|
11235
|
+
if (!match) return null;
|
|
11236
|
+
try {
|
|
11237
|
+
const eventKey = decodeURIComponent(match[1]);
|
|
11238
|
+
const asset = decodeURIComponent(match[2]);
|
|
11239
|
+
if (!eventKey || !SAFE_ASSET2.test(asset)) return null;
|
|
11240
|
+
return { eventKey, asset };
|
|
11241
|
+
} catch {
|
|
11242
|
+
return null;
|
|
11243
|
+
}
|
|
11244
|
+
}
|
|
11245
|
+
function looksLikeOrganizerAsset(value) {
|
|
11246
|
+
try {
|
|
11247
|
+
return /^\/v1\/events\/[^/]+\/assets(?:\/|$)/.test(
|
|
11248
|
+
new URL(value, "https://seatlayer.invalid").pathname
|
|
11249
|
+
);
|
|
11250
|
+
} catch {
|
|
11251
|
+
return false;
|
|
11252
|
+
}
|
|
11253
|
+
}
|
|
11254
|
+
var OrganizerAssetObjectUrls = class {
|
|
11255
|
+
constructor(eventKey, load) {
|
|
11256
|
+
this.eventKey = eventKey;
|
|
11257
|
+
this.load = load;
|
|
11258
|
+
this.pending = /* @__PURE__ */ new Map();
|
|
11259
|
+
this.created = /* @__PURE__ */ new Set();
|
|
11260
|
+
this.disposed = false;
|
|
11261
|
+
}
|
|
11262
|
+
resolve(reference) {
|
|
11263
|
+
const parsed = organizerEventAssetReference(reference);
|
|
11264
|
+
if (!parsed) {
|
|
11265
|
+
return Promise.resolve(looksLikeOrganizerAsset(reference) ? null : reference);
|
|
11266
|
+
}
|
|
11267
|
+
if (parsed.eventKey !== this.eventKey || this.disposed) return Promise.resolve(null);
|
|
11268
|
+
const cacheKey = `${parsed.eventKey}/${parsed.asset}`;
|
|
11269
|
+
const existing = this.pending.get(cacheKey);
|
|
11270
|
+
if (existing) return existing;
|
|
11271
|
+
const task = this.load(parsed.eventKey, parsed.asset).then((blob) => {
|
|
11272
|
+
const objectUrl = URL.createObjectURL(blob);
|
|
11273
|
+
if (this.disposed) {
|
|
11274
|
+
URL.revokeObjectURL(objectUrl);
|
|
11275
|
+
return null;
|
|
11276
|
+
}
|
|
11277
|
+
this.created.add(objectUrl);
|
|
11278
|
+
return objectUrl;
|
|
11279
|
+
}).catch((error) => {
|
|
11280
|
+
this.pending.delete(cacheKey);
|
|
11281
|
+
throw error;
|
|
11282
|
+
});
|
|
11283
|
+
this.pending.set(cacheKey, task);
|
|
11284
|
+
return task;
|
|
11285
|
+
}
|
|
11286
|
+
/**
|
|
11287
|
+
* Resolve the image fields the synchronous map renderer loads immediately.
|
|
11288
|
+
* View-from-seat media stays lazy: SeatManager does not open that buyer
|
|
11289
|
+
* surface, and eagerly downloading every row panorama would be unbounded.
|
|
11290
|
+
*/
|
|
11291
|
+
async prepareRendererChart(doc) {
|
|
11292
|
+
const prepareBackground = async (background) => {
|
|
11293
|
+
if (!background?.url) return;
|
|
11294
|
+
const resolved = await this.resolve(background.url);
|
|
11295
|
+
if (!resolved) throw new Error("organizer_event_asset_scope_mismatch");
|
|
11296
|
+
background.url = resolved;
|
|
11297
|
+
};
|
|
11298
|
+
const prepareObjects = async (objects) => {
|
|
11299
|
+
for (const object of objects) {
|
|
11300
|
+
if (object.type !== "decorImage") continue;
|
|
11301
|
+
const image = object;
|
|
11302
|
+
const resolved = await this.resolve(image.href);
|
|
11303
|
+
if (!resolved) throw new Error("organizer_event_asset_scope_mismatch");
|
|
11304
|
+
image.href = resolved;
|
|
11305
|
+
}
|
|
11306
|
+
};
|
|
11307
|
+
const prepareOwner = async (owner) => {
|
|
11308
|
+
await prepareBackground(owner.backgroundImage);
|
|
11309
|
+
await prepareObjects(owner.objects);
|
|
11310
|
+
};
|
|
11311
|
+
await prepareOwner(doc);
|
|
11312
|
+
for (const floor of doc.floors ?? []) await prepareOwner(floor);
|
|
11313
|
+
return doc;
|
|
11314
|
+
}
|
|
11315
|
+
dispose() {
|
|
11316
|
+
if (this.disposed) return;
|
|
11317
|
+
this.disposed = true;
|
|
11318
|
+
for (const url of this.created) URL.revokeObjectURL(url);
|
|
11319
|
+
this.created.clear();
|
|
11320
|
+
this.pending.clear();
|
|
11321
|
+
}
|
|
11322
|
+
};
|
|
11323
|
+
|
|
11324
|
+
// src/SeatManager.ts
|
|
10858
11325
|
function availabilityModeOf(rule) {
|
|
10859
11326
|
return rule ? rule.mode : "open";
|
|
10860
11327
|
}
|
|
@@ -11133,8 +11600,8 @@ var MANAGER_CSS = (
|
|
|
11133
11600
|
.slm.compact .slm-modes{min-width:0}.slm.compact .slm-mode{padding-inline:11px}
|
|
11134
11601
|
.slm.compact .slm-live{justify-self:end}.slm.compact .slm-bar-actions{grid-column:1/-1;justify-self:stretch}
|
|
11135
11602
|
.slm.compact .slm-barbtn{flex:1;padding:6px 9px}.slm.compact .slm-kpis{grid-template-columns:repeat(4,minmax(0,1fr));gap:8px}
|
|
11136
|
-
.slm.compact .slm-kpi[data-kpi="
|
|
11137
|
-
.slm.compact .slm-kpi[data-kpi="
|
|
11603
|
+
.slm.compact .slm-kpi[data-kpi="viewing-map"],.slm.compact .slm-kpi[data-kpi="active-holds"],
|
|
11604
|
+
.slm.compact .slm-kpi[data-kpi="booked-pct"],.slm.compact .slm-kpi[data-kpi="booked-value"]{display:none}
|
|
11138
11605
|
/* Reduced motion, as a BLANKET over the cockpit subtree rather than a list of
|
|
11139
11606
|
selectors. The list this replaces named four animations and two transitions,
|
|
11140
11607
|
and had silently fallen behind the stylesheet: the zoom hint, the toast and
|
|
@@ -11344,6 +11811,10 @@ var SeatManager = class {
|
|
|
11344
11811
|
this.currency = options.currency ?? "USD";
|
|
11345
11812
|
this.tokenExpiresAt = options.tokenExpiresAt ?? null;
|
|
11346
11813
|
this.api = new ManageApi(options.apiBase ?? DEFAULT_API_BASE3, options.token);
|
|
11814
|
+
this.organizerAssetUrls = new OrganizerAssetObjectUrls(
|
|
11815
|
+
this.key,
|
|
11816
|
+
(key, asset) => this.withAuthRetry(() => this.api.asset(key, asset))
|
|
11817
|
+
);
|
|
11347
11818
|
this.host = resolveContainer4(options.container);
|
|
11348
11819
|
}
|
|
11349
11820
|
/** Build the DOM, load the chart, subscribe to realtime, mount the board. */
|
|
@@ -11351,10 +11822,10 @@ var SeatManager = class {
|
|
|
11351
11822
|
injectStyle();
|
|
11352
11823
|
this.buildChrome();
|
|
11353
11824
|
try {
|
|
11354
|
-
const res = await this.api.chart(this.key);
|
|
11355
|
-
this.doc = res.doc;
|
|
11825
|
+
const res = await this.withAuthRetry(() => this.api.chart(this.key));
|
|
11826
|
+
this.doc = await this.organizerAssetUrls.prepareRendererChart(res.doc);
|
|
11356
11827
|
this.currency = res.event.currency ?? this.opts.currency ?? this.currency;
|
|
11357
|
-
this.buildUnitUniverse(
|
|
11828
|
+
this.buildUnitUniverse(this.doc);
|
|
11358
11829
|
this.buildRenderer();
|
|
11359
11830
|
this.buildSectionOptions();
|
|
11360
11831
|
const [, controlRoom] = await Promise.all([
|
|
@@ -11701,10 +12172,10 @@ var SeatManager = class {
|
|
|
11701
12172
|
try {
|
|
11702
12173
|
await this.api.unbook(this.key, targets, bookingRef);
|
|
11703
12174
|
this.clearSelection();
|
|
11704
|
-
this.done("cancelBooking", targets, `
|
|
12175
|
+
this.done("cancelBooking", targets, `Released ${targets.length} booked unit${targets.length === 1 ? "" : "s"}.`);
|
|
11705
12176
|
} catch (err) {
|
|
11706
12177
|
this.setSeatsLocal(targets, "booked");
|
|
11707
|
-
this.toastErr("Couldn't
|
|
12178
|
+
this.toastErr("Couldn't release that booked inventory. Check the booking reference.");
|
|
11708
12179
|
this.opts.onError?.(err);
|
|
11709
12180
|
}
|
|
11710
12181
|
}
|
|
@@ -11757,9 +12228,9 @@ var SeatManager = class {
|
|
|
11757
12228
|
async setHoldTtl(ms) {
|
|
11758
12229
|
try {
|
|
11759
12230
|
await this.api.setHoldTtl(this.key, ms);
|
|
11760
|
-
this.done("setHoldTtl", [], ms ? `
|
|
12231
|
+
this.done("setHoldTtl", [], ms ? `Hold window set to ${Math.round(ms / 6e4)} min.` : "Hold window reset.");
|
|
11761
12232
|
} catch (err) {
|
|
11762
|
-
this.toastErr("Couldn't update the
|
|
12233
|
+
this.toastErr("Couldn't update the hold window.");
|
|
11763
12234
|
this.opts.onError?.(err);
|
|
11764
12235
|
}
|
|
11765
12236
|
}
|
|
@@ -11804,6 +12275,7 @@ var SeatManager = class {
|
|
|
11804
12275
|
}
|
|
11805
12276
|
this.renderer?.destroy();
|
|
11806
12277
|
this.renderer = null;
|
|
12278
|
+
this.organizerAssetUrls.dispose();
|
|
11807
12279
|
if (this.root && this.root.parentNode === this.host) this.host.removeChild(this.root);
|
|
11808
12280
|
}
|
|
11809
12281
|
// ---- renderer lifecycle ---------------------------------------------------
|
|
@@ -11924,24 +12396,28 @@ var SeatManager = class {
|
|
|
11924
12396
|
* projects its deltas, so any change inside a private channel allocation is
|
|
11925
12397
|
* structurally suppressed and the map silently drifts.
|
|
11926
12398
|
*
|
|
11927
|
-
* If the mint fails
|
|
11928
|
-
*
|
|
11929
|
-
*
|
|
11930
|
-
* the authenticated HTTP read.
|
|
12399
|
+
* If the mint fails, remain reconnecting. An unticketed socket is a buyer
|
|
12400
|
+
* projection, so applying it to organizer state would be worse than staying
|
|
12401
|
+
* visibly offline while the host refreshes authority or upgrades the API.
|
|
11931
12402
|
*/
|
|
11932
12403
|
async connect() {
|
|
11933
12404
|
if (this.closed) return;
|
|
11934
12405
|
let protocols;
|
|
11935
12406
|
try {
|
|
11936
|
-
protocols = (await this.api.subscribeTicket(this.key)).protocols;
|
|
11937
|
-
|
|
11938
|
-
|
|
12407
|
+
protocols = (await this.withAuthRetry(() => this.api.subscribeTicket(this.key))).protocols;
|
|
12408
|
+
if (!protocols.length) throw new Error("manage_subscribe_ticket_missing");
|
|
12409
|
+
} catch (err) {
|
|
12410
|
+
this.setLive(false);
|
|
12411
|
+
this.opts.onError?.(err);
|
|
12412
|
+
this.scheduleReconnect();
|
|
12413
|
+
return;
|
|
11939
12414
|
}
|
|
11940
12415
|
if (this.closed) return;
|
|
11941
12416
|
let ws;
|
|
11942
12417
|
try {
|
|
11943
|
-
ws =
|
|
11944
|
-
} catch {
|
|
12418
|
+
ws = new WebSocket(this.api.socketUrl(this.key), protocols);
|
|
12419
|
+
} catch (err) {
|
|
12420
|
+
this.opts.onError?.(err);
|
|
11945
12421
|
this.scheduleReconnect();
|
|
11946
12422
|
return;
|
|
11947
12423
|
}
|
|
@@ -12032,8 +12508,9 @@ var SeatManager = class {
|
|
|
12032
12508
|
this.lastSyncedAt = Date.now();
|
|
12033
12509
|
this.afterPaint();
|
|
12034
12510
|
}
|
|
12035
|
-
|
|
12036
|
-
|
|
12511
|
+
const liveBookedValue = typeof m.bookedValue?.gross === "number" ? m.bookedValue.gross : m.revenue?.gross;
|
|
12512
|
+
if (typeof liveBookedValue === "number" && Number.isFinite(liveBookedValue)) {
|
|
12513
|
+
this.applyLiveGross(liveBookedValue);
|
|
12037
12514
|
}
|
|
12038
12515
|
this.recomputeTallies();
|
|
12039
12516
|
}
|
|
@@ -12050,9 +12527,12 @@ var SeatManager = class {
|
|
|
12050
12527
|
this.authoritativeGrossRevenue = gross;
|
|
12051
12528
|
this.revenueStatus = "current";
|
|
12052
12529
|
if (this.controlRoomSnapshot) {
|
|
12530
|
+
const current = this.controlRoomSnapshot.bookedValue ?? this.controlRoomSnapshot.revenue;
|
|
12531
|
+
const bookedValue = { ...current, gross };
|
|
12053
12532
|
this.controlRoomSnapshot = {
|
|
12054
12533
|
...this.controlRoomSnapshot,
|
|
12055
|
-
|
|
12534
|
+
bookedValue,
|
|
12535
|
+
revenue: bookedValue
|
|
12056
12536
|
};
|
|
12057
12537
|
this.opts.onControlRoom?.(this.controlRoomSnapshot);
|
|
12058
12538
|
}
|
|
@@ -12230,7 +12710,10 @@ var SeatManager = class {
|
|
|
12230
12710
|
// ---- tallies + feed -------------------------------------------------------
|
|
12231
12711
|
applyReportRevenue(report) {
|
|
12232
12712
|
this.authoritativeGrossRevenue = report.report.byCategory.reduce(
|
|
12233
|
-
(sum, row) =>
|
|
12713
|
+
(sum, row) => {
|
|
12714
|
+
const value = Number.isFinite(row.bookedValue) ? row.bookedValue : row.bookedRevenue;
|
|
12715
|
+
return sum + (Number.isFinite(value) ? value : 0);
|
|
12716
|
+
},
|
|
12234
12717
|
0
|
|
12235
12718
|
);
|
|
12236
12719
|
this.revenueStatus = "current";
|
|
@@ -12249,7 +12732,13 @@ var SeatManager = class {
|
|
|
12249
12732
|
const requestedAt = Date.now();
|
|
12250
12733
|
try {
|
|
12251
12734
|
const fetched = await this.api.controlRoom(this.key, this.trendWindowMinutes);
|
|
12252
|
-
|
|
12735
|
+
const incoming = fetched.bookedValue ?? fetched.revenue ?? { gross: 0, bySection: [] };
|
|
12736
|
+
const normalizedSections = (incoming.bySection ?? []).map((row) => {
|
|
12737
|
+
const value = Number.isFinite(row.bookedValue) ? row.bookedValue : row.bookedRevenue;
|
|
12738
|
+
return { ...row, bookedValue: value ?? 0, bookedRevenue: value ?? 0 };
|
|
12739
|
+
});
|
|
12740
|
+
const canonical = { ...incoming, bySection: normalizedSections };
|
|
12741
|
+
let snapshot = { ...fetched, bookedValue: canonical, revenue: canonical };
|
|
12253
12742
|
if (request === this.revenueRequest) {
|
|
12254
12743
|
if (this.livePresence && this.livePresence.at >= requestedAt) {
|
|
12255
12744
|
snapshot = { ...snapshot, presence: this.livePresence.value };
|
|
@@ -12257,14 +12746,15 @@ var SeatManager = class {
|
|
|
12257
12746
|
this.livePresence = null;
|
|
12258
12747
|
}
|
|
12259
12748
|
if (this.liveGross && this.liveGross.at >= requestedAt) {
|
|
12260
|
-
|
|
12749
|
+
const bookedValue = { ...snapshot.bookedValue, gross: this.liveGross.value };
|
|
12750
|
+
snapshot = { ...snapshot, bookedValue, revenue: bookedValue };
|
|
12261
12751
|
} else {
|
|
12262
12752
|
this.liveGross = null;
|
|
12263
12753
|
}
|
|
12264
12754
|
this.controlRoomSnapshot = snapshot;
|
|
12265
12755
|
this.rebaseServerTotals(snapshot);
|
|
12266
12756
|
this.lastSyncedAt = Date.now();
|
|
12267
|
-
this.authoritativeGrossRevenue = snapshot.
|
|
12757
|
+
this.authoritativeGrossRevenue = snapshot.bookedValue.gross;
|
|
12268
12758
|
this.currency = snapshot.currency;
|
|
12269
12759
|
this.revenueStatus = "current";
|
|
12270
12760
|
this.recomputeTallies();
|
|
@@ -12322,7 +12812,9 @@ var SeatManager = class {
|
|
|
12322
12812
|
total: Number.isFinite(seatTotal) ? seatTotal : this.unitTotal(),
|
|
12323
12813
|
capacityPct: 0,
|
|
12324
12814
|
sellThroughPct: 0,
|
|
12815
|
+
bookedValue: this.authoritativeGrossRevenue,
|
|
12325
12816
|
grossRevenue: this.authoritativeGrossRevenue,
|
|
12817
|
+
bookedValueStatus: this.revenueStatus,
|
|
12326
12818
|
revenueStatus: this.revenueStatus,
|
|
12327
12819
|
currency: this.currency
|
|
12328
12820
|
};
|
|
@@ -12477,8 +12969,8 @@ var SeatManager = class {
|
|
|
12477
12969
|
<button class="slm-barbtn follow" data-ref="follow" aria-pressed="false"
|
|
12478
12970
|
title="Stay on the current map view unless enabled">Follow live</button>
|
|
12479
12971
|
<button class="slm-barbtn" data-ref="heat" aria-pressed="false"
|
|
12480
|
-
aria-label="
|
|
12481
|
-
title="Highlight sections
|
|
12972
|
+
aria-label="Booking momentum overlay off"
|
|
12973
|
+
title="Highlight sections booking fastest in the selected time window">Booking momentum</button>
|
|
12482
12974
|
<button class="slm-barbtn" data-ref="fullscreen" title="Full screen (F)" aria-keyshortcuts="F">Full screen</button>
|
|
12483
12975
|
</div>
|
|
12484
12976
|
<div class="slm-kpis" data-ref="kpis"></div>
|
|
@@ -12581,16 +13073,16 @@ var SeatManager = class {
|
|
|
12581
13073
|
if (!button) return;
|
|
12582
13074
|
button.classList.toggle("on", this.followLive);
|
|
12583
13075
|
button.setAttribute("aria-pressed", String(this.followLive));
|
|
12584
|
-
button.setAttribute("title", this.followLive ? "Following new
|
|
13076
|
+
button.setAttribute("title", this.followLive ? "Following new holds and bookings. Turn off to keep the current view." : "Stay on the current map view. Enable to follow new holds and bookings.");
|
|
12585
13077
|
}
|
|
12586
13078
|
paintHeatButton() {
|
|
12587
13079
|
const button = this.els.heat;
|
|
12588
13080
|
if (!button) return;
|
|
12589
13081
|
button.classList.toggle("on", this.heatEnabled);
|
|
12590
13082
|
button.setAttribute("aria-pressed", String(this.heatEnabled));
|
|
12591
|
-
button.setAttribute("aria-label", `
|
|
12592
|
-
button.setAttribute("title", `${this.heatEnabled ? "Hide" : "Highlight"} sections
|
|
12593
|
-
button.textContent = "
|
|
13083
|
+
button.setAttribute("aria-label", `Booking momentum overlay ${this.heatEnabled ? "on" : "off"}`);
|
|
13084
|
+
button.setAttribute("title", `${this.heatEnabled ? "Hide" : "Highlight"} sections booking fastest in the selected time window`);
|
|
13085
|
+
button.textContent = "Booking momentum";
|
|
12594
13086
|
this.paintMomentumHelp();
|
|
12595
13087
|
}
|
|
12596
13088
|
paintMomentumHelp() {
|
|
@@ -12634,23 +13126,23 @@ var SeatManager = class {
|
|
|
12634
13126
|
formatKpiDelta(key, delta, currency) {
|
|
12635
13127
|
const sign = delta > 0 ? "+" : "\u2212";
|
|
12636
13128
|
const absolute = Math.abs(delta);
|
|
12637
|
-
if (key === "
|
|
12638
|
-
if (key === "
|
|
13129
|
+
if (key === "booked-value") return `${sign}${fmtMoney(absolute, currency)}`;
|
|
13130
|
+
if (key === "booked-pct") return `${sign}${absolute.toLocaleString()}pt`;
|
|
12639
13131
|
return `${sign}${absolute.toLocaleString()}`;
|
|
12640
13132
|
}
|
|
12641
13133
|
paintKpis(t3) {
|
|
12642
13134
|
if (!this.els.kpis) return;
|
|
12643
|
-
const
|
|
13135
|
+
const bookedValue = t3.bookedValueStatus === "current" ? fmtMoney(t3.bookedValue, t3.currency) : "\u2014";
|
|
12644
13136
|
const presence = this.presenceCounts();
|
|
12645
13137
|
const items = [
|
|
12646
|
-
{ key: "
|
|
12647
|
-
{ key: "held-seats", raw: t3.held, n: t3.held.toLocaleString(), l: "Held
|
|
12648
|
-
{ key: "free-seats", raw: t3.free, n: t3.free.toLocaleString(), l: "
|
|
12649
|
-
{ key: "blocked", raw: t3.blocked, n: t3.blocked.toLocaleString(), l: "Blocked", dot: "#8b94ac", title: "
|
|
12650
|
-
{ key: "
|
|
12651
|
-
{ key: "
|
|
12652
|
-
{ key: "
|
|
12653
|
-
{ key: "
|
|
13138
|
+
{ key: "booked-inventory", raw: t3.booked, n: t3.booked.toLocaleString(), l: "Booked inventory", dot: "#22a06b", title: "Inventory units booked" },
|
|
13139
|
+
{ key: "held-seats", raw: t3.held, n: t3.held.toLocaleString(), l: "Held inventory", dot: "#f4b740", title: "Inventory currently held" },
|
|
13140
|
+
{ key: "free-seats", raw: t3.free, n: t3.free.toLocaleString(), l: "Available", dot: "#6e7bff", title: "Inventory available to book" },
|
|
13141
|
+
{ key: "blocked", raw: t3.blocked, n: t3.blocked.toLocaleString(), l: "Blocked", dot: "#8b94ac", title: "Inventory withheld from booking" },
|
|
13142
|
+
{ key: "viewing-map", raw: presence?.shoppingSessions ?? null, n: presence ? presence.shoppingSessions.toLocaleString() : "\u2014", l: "Viewing map", title: "Active map sessions right now" },
|
|
13143
|
+
{ key: "active-holds", raw: presence?.activeHolds ?? null, n: presence ? presence.activeHolds.toLocaleString() : "\u2014", l: "Active holds", title: "Sessions currently holding inventory" },
|
|
13144
|
+
{ key: "booked-pct", raw: t3.capacityPct, n: `${t3.capacityPct}%`, l: "Booked", title: "Booked inventory as a share of the whole event" },
|
|
13145
|
+
{ key: "booked-value", raw: t3.bookedValueStatus === "current" ? t3.bookedValue : null, n: bookedValue, l: "Booked value", title: "Configured value attached to booked inventory" }
|
|
12654
13146
|
];
|
|
12655
13147
|
let hasChanges = false;
|
|
12656
13148
|
this.els.kpis.innerHTML = items.map((item) => {
|
|
@@ -12699,12 +13191,12 @@ var SeatManager = class {
|
|
|
12699
13191
|
renderViewRail() {
|
|
12700
13192
|
this.els.rail.innerHTML = `
|
|
12701
13193
|
<p class="slm-eyebrow">Monitor</p>
|
|
12702
|
-
<p class="slm-hint">Read-only. Inventory,
|
|
13194
|
+
<p class="slm-hint">Read-only. Inventory, map activity and booking movement update on the same live board.</p>
|
|
12703
13195
|
<div class="slm-health" data-ref="presence"></div>
|
|
12704
13196
|
<div class="slm-legend" data-ref="legend"></div>
|
|
12705
13197
|
<div class="slm-sectionhead">
|
|
12706
|
-
<div><p class="slm-eyebrow">Section
|
|
12707
|
-
<div class="slm-windows" aria-label="
|
|
13198
|
+
<div><p class="slm-eyebrow">Section inventory</p><p class="slm-note">Configured booked value \xB7 booking momentum</p></div>
|
|
13199
|
+
<div class="slm-windows" aria-label="Booking momentum window">
|
|
12708
13200
|
${[5, 15, 30, 60].map((window2) => `<button class="slm-window" data-window="${window2}">${window2}m</button>`).join("")}
|
|
12709
13201
|
</div>
|
|
12710
13202
|
</div>
|
|
@@ -12743,8 +13235,8 @@ var SeatManager = class {
|
|
|
12743
13235
|
const sync = this.lastSyncedAt ? relTime(this.lastSyncedAt, Date.now()) : "waiting";
|
|
12744
13236
|
const presence = this.presenceCounts();
|
|
12745
13237
|
this.els.presence.innerHTML = `
|
|
12746
|
-
<div class="slm-healthitem" title="
|
|
12747
|
-
<div class="slm-healthitem" title="
|
|
13238
|
+
<div class="slm-healthitem" title="Active map sessions right now"><b>${presence ? presence.shoppingSessions.toLocaleString() : "\u2014"}</b><span>Viewing map</span></div>
|
|
13239
|
+
<div class="slm-healthitem" title="Sessions currently holding inventory"><b>${presence ? presence.activeHolds.toLocaleString() : "\u2014"}</b><span>Active holds</span></div>
|
|
12748
13240
|
<div class="slm-healthitem"><b>${connected ? "Healthy" : "Reconnecting"}</b><span>Live connection</span></div>
|
|
12749
13241
|
<div class="slm-healthitem"><b>${sync}</b><span>Last sync</span></div>`;
|
|
12750
13242
|
}
|
|
@@ -12754,10 +13246,10 @@ var SeatManager = class {
|
|
|
12754
13246
|
return;
|
|
12755
13247
|
}
|
|
12756
13248
|
const velocity = new Map(snapshot.velocity.bySection.map((row) => [row.sectionId, row]));
|
|
12757
|
-
const rows = [...snapshot.
|
|
13249
|
+
const rows = [...snapshot.bookedValue.bySection].sort((a, b) => {
|
|
12758
13250
|
const av = velocity.get(a.sectionId)?.netBooked ?? 0;
|
|
12759
13251
|
const bv = velocity.get(b.sectionId)?.netBooked ?? 0;
|
|
12760
|
-
return bv - av || b.
|
|
13252
|
+
return bv - av || b.bookedValue - a.bookedValue;
|
|
12761
13253
|
});
|
|
12762
13254
|
this.els.sections.innerHTML = rows.length ? rows.map((row) => {
|
|
12763
13255
|
const speed = velocity.get(row.sectionId);
|
|
@@ -12765,8 +13257,8 @@ var SeatManager = class {
|
|
|
12765
13257
|
const netLabel = `${net > 0 ? "+" : ""}${net}`;
|
|
12766
13258
|
const trend = speed?.trend === "rising" || speed?.trend === "cooling" ? speed.trend : "steady";
|
|
12767
13259
|
return `<button type="button" class="slm-sectionrow" data-section-focus="${esc2(row.sectionId)}" title="Focus ${esc2(row.sectionLabel)} on the map">
|
|
12768
|
-
<span class="slm-sectiontop"><span>${esc2(row.sectionLabel)}</span><span>${fmtMoney(row.
|
|
12769
|
-
<span class="slm-sectionmeta"><span>${row.booked.toLocaleString()}/${row.total.toLocaleString()}
|
|
13260
|
+
<span class="slm-sectiontop"><span>${esc2(row.sectionLabel)}</span><span>${fmtMoney(row.bookedValue, snapshot.currency)}</span></span>
|
|
13261
|
+
<span class="slm-sectionmeta"><span>${row.booked.toLocaleString()}/${row.total.toLocaleString()} booked \xB7 ${netLabel} in ${snapshot.velocity.windowMinutes}m</span><span class="slm-trend ${trend}">${trend}</span><span class="slm-sectionlocate">Locate</span></span>
|
|
12770
13262
|
</button>`;
|
|
12771
13263
|
}).join("") : '<div class="slm-empty">No section metrics are available for this chart.</div>';
|
|
12772
13264
|
this.paintTrendWindow();
|
|
@@ -12778,7 +13270,7 @@ var SeatManager = class {
|
|
|
12778
13270
|
this.renderer?.setSectionHeat(null);
|
|
12779
13271
|
return;
|
|
12780
13272
|
}
|
|
12781
|
-
const capacity = new Map(snapshot.
|
|
13273
|
+
const capacity = new Map(snapshot.bookedValue.bySection.map((row) => [row.sectionId, Math.max(1, row.total)]));
|
|
12782
13274
|
const rates = snapshot.velocity.bySection.map((row) => ({
|
|
12783
13275
|
sectionId: row.sectionId,
|
|
12784
13276
|
rate: Math.max(0, row.netBooked) / (capacity.get(row.sectionId) ?? 1) / snapshot.velocity.windowMinutes
|
|
@@ -12793,7 +13285,7 @@ var SeatManager = class {
|
|
|
12793
13285
|
if (!seat) {
|
|
12794
13286
|
this.els.rail.innerHTML = `
|
|
12795
13287
|
<p class="slm-eyebrow">Inspect seats</p>
|
|
12796
|
-
<p class="slm-hint">Select a seat to see its availability and
|
|
13288
|
+
<p class="slm-hint">Select a seat to see its availability and booking context. Nothing changes in this view.</p>
|
|
12797
13289
|
<div class="slm-empty">Select a seat on the map.</div>`;
|
|
12798
13290
|
return;
|
|
12799
13291
|
}
|
|
@@ -12802,7 +13294,7 @@ var SeatManager = class {
|
|
|
12802
13294
|
const sectionId = this.sectionByObject.get(seat.rowId) ?? import_core3.UNGROUPED_ID;
|
|
12803
13295
|
const sectionLabel = this.sectionLabelById.get(sectionId) ?? "Other seats";
|
|
12804
13296
|
const category = this.doc?.categories.find((item) => item.key === seat.categoryKey);
|
|
12805
|
-
const sectionMetric = this.controlRoomSnapshot?.
|
|
13297
|
+
const sectionMetric = this.controlRoomSnapshot?.bookedValue.bySection.find((row) => row.sectionId === sectionId);
|
|
12806
13298
|
const object = this.doc?.objects.find((item) => item.id === seat.rowId);
|
|
12807
13299
|
const location2 = object?.type === "row" ? { label: "Row", value: object.label } : object?.type === "table" ? { label: "Table", value: object.label } : seat.kind === "booth" ? { label: "Type", value: "Booth" } : null;
|
|
12808
13300
|
const itemKind = seat.kind === "booth" ? "Booth" : "Seat";
|
|
@@ -12816,8 +13308,8 @@ var SeatManager = class {
|
|
|
12816
13308
|
<div><span>Section</span><b>${esc2(sectionLabel)}</b></div>
|
|
12817
13309
|
${location2 ? `<div><span>${location2.label}</span><b>${esc2(location2.value)}</b></div>` : ""}
|
|
12818
13310
|
<div><span>Category</span><b>${esc2(category?.label ?? seat.categoryKey)}</b></div>
|
|
12819
|
-
<div><span>
|
|
12820
|
-
<div><span>Section
|
|
13311
|
+
<div><span>Booked in section</span><b>${sectionMetric ? `${sectionMetric.booked} of ${sectionMetric.total}` : "\u2014"}</b></div>
|
|
13312
|
+
<div><span>Section booked value</span><b>${sectionMetric && this.controlRoomSnapshot ? fmtMoney(sectionMetric.bookedValue, this.controlRoomSnapshot.currency) : "\u2014"}</b></div>
|
|
12821
13313
|
</div>
|
|
12822
13314
|
</div>`;
|
|
12823
13315
|
}
|