@sensiblestats/widget-react 0.69.0 → 0.70.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/CHANGELOG.md +25 -0
- package/dist/index.cjs +1064 -319
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js +978 -233
- package/dist/index.js.map +1 -1
- package/dist/styles.css +66 -0
- package/package.json +2 -2
package/dist/index.cjs
CHANGED
|
@@ -37,7 +37,7 @@ __export(index_exports, {
|
|
|
37
37
|
module.exports = __toCommonJS(index_exports);
|
|
38
38
|
|
|
39
39
|
// src/StatsWidget.tsx
|
|
40
|
-
var
|
|
40
|
+
var import_react55 = require("react");
|
|
41
41
|
var import_widget_sdk5 = require("@sensiblestats/widget-sdk");
|
|
42
42
|
|
|
43
43
|
// src/kickoff.ts
|
|
@@ -161,6 +161,30 @@ var STRINGS = {
|
|
|
161
161
|
gamePlay: "Play",
|
|
162
162
|
gameLocked: "Locked",
|
|
163
163
|
gameVoided: "Voided",
|
|
164
|
+
presets: {
|
|
165
|
+
lang: "en",
|
|
166
|
+
setNumbers: "Set the numbers",
|
|
167
|
+
buildCombo: "Build a combo",
|
|
168
|
+
dialName: "Set the numbers and Build a combo",
|
|
169
|
+
maxPriceRow: "{count} picks, max {price}",
|
|
170
|
+
minPriceRow: "Picks from {price} up",
|
|
171
|
+
comboRow: "A combo of {legs} matches priced at about {total}",
|
|
172
|
+
maxPriceDisplay: (count, price) => `${count} picks, max ${price.toFixed(2)}`,
|
|
173
|
+
maxPriceMessage: (count, price) => `Show me ${count} picks priced at ${price.toFixed(2)} or less.`,
|
|
174
|
+
minPriceDisplay: (count, price) => `${count} picks from ${price.toFixed(2)} up`,
|
|
175
|
+
minPriceMessage: (count, price) => `Show me ${count} picks priced at ${price.toFixed(2)} or more.`,
|
|
176
|
+
comboDisplay: (legs, total) => `A combo of ${legs} priced at about ${total.toFixed(1)}`,
|
|
177
|
+
comboMessage: (legs, total) => `Build me a combo of ${legs} priced at about ${total.toFixed(1)}.`,
|
|
178
|
+
unavailable: "Not available today.",
|
|
179
|
+
send: "Send",
|
|
180
|
+
close: "Close",
|
|
181
|
+
totalLabel: "Total",
|
|
182
|
+
matchesLabel: "Matches",
|
|
183
|
+
fewerMatches: "Fewer matches",
|
|
184
|
+
moreMatches: "More matches",
|
|
185
|
+
changeNumber: (value) => `${value}, tap to change`,
|
|
186
|
+
changeCombo: "Change total or matches"
|
|
187
|
+
},
|
|
164
188
|
games: {
|
|
165
189
|
lang: "en",
|
|
166
190
|
dailyChallenge: "Daily challenge",
|
|
@@ -467,6 +491,30 @@ var STRINGS = {
|
|
|
467
491
|
gamePlay: "Oyna",
|
|
468
492
|
gameLocked: "Kilitli",
|
|
469
493
|
gameVoided: "\u0130ptal edildi",
|
|
494
|
+
presets: {
|
|
495
|
+
lang: "tr",
|
|
496
|
+
setNumbers: "Say\u0131lar\u0131 belirle",
|
|
497
|
+
buildCombo: "Kombin olu\u015Ftur",
|
|
498
|
+
dialName: "Say\u0131lar\u0131 belirle ve Kombin olu\u015Ftur",
|
|
499
|
+
maxPriceRow: "{count} se\xE7im, en fazla {price}",
|
|
500
|
+
minPriceRow: "{price} ve \xFCzeri se\xE7imler",
|
|
501
|
+
comboRow: "Yakla\u015F\u0131k {total} oranl\u0131 {legs} ma\xE7l\u0131k kombin",
|
|
502
|
+
maxPriceDisplay: (count, price) => `${count} se\xE7im, en fazla ${price.toFixed(2)}`,
|
|
503
|
+
maxPriceMessage: (count, price) => `${price.toFixed(2)} veya alt\u0131 oranl\u0131 ${count} se\xE7im g\xF6ster.`,
|
|
504
|
+
minPriceDisplay: (count, price) => `${count} se\xE7im, ${price.toFixed(2)} ve \xFCzeri`,
|
|
505
|
+
minPriceMessage: (count, price) => `${price.toFixed(2)} veya \xFCzeri oranl\u0131 ${count} se\xE7im g\xF6ster.`,
|
|
506
|
+
comboDisplay: (legs, total) => `${legs} ma\xE7l\u0131k kombin, yakla\u015F\u0131k ${total.toFixed(1)}`,
|
|
507
|
+
comboMessage: (legs, total) => `Yakla\u015F\u0131k ${total.toFixed(1)} oranl\u0131 ${legs} ma\xE7l\u0131k bir kombin haz\u0131rla.`,
|
|
508
|
+
unavailable: "Bug\xFCn mevcut de\u011Fil.",
|
|
509
|
+
send: "G\xF6nder",
|
|
510
|
+
close: "Kapat",
|
|
511
|
+
totalLabel: "Toplam",
|
|
512
|
+
matchesLabel: "Ma\xE7 say\u0131s\u0131",
|
|
513
|
+
fewerMatches: "Daha az ma\xE7",
|
|
514
|
+
moreMatches: "Daha fazla ma\xE7",
|
|
515
|
+
changeNumber: (value) => `${value}, de\u011Fi\u015Ftirmek i\xE7in dokun`,
|
|
516
|
+
changeCombo: "Toplam\u0131 veya ma\xE7lar\u0131 de\u011Fi\u015Ftir"
|
|
517
|
+
},
|
|
470
518
|
games: {
|
|
471
519
|
lang: "tr",
|
|
472
520
|
dailyChallenge: "G\xFCn\xFCn meydan okumas\u0131",
|
|
@@ -793,6 +841,7 @@ function normalizeUi(cfg) {
|
|
|
793
841
|
gameLocked: strings.gameLocked,
|
|
794
842
|
gameVoided: strings.gameVoided,
|
|
795
843
|
games: strings.games,
|
|
844
|
+
presets: strings.presets,
|
|
796
845
|
liveCard: strings.liveCard
|
|
797
846
|
};
|
|
798
847
|
}
|
|
@@ -1131,7 +1180,9 @@ function toComboCardVM(card) {
|
|
|
1131
1180
|
legs,
|
|
1132
1181
|
multiplication,
|
|
1133
1182
|
replacedNote: str(card.replacedNote),
|
|
1134
|
-
addToSlipEnabled: card.addToSlipEnabled === true
|
|
1183
|
+
addToSlipEnabled: card.addToSlipEnabled === true,
|
|
1184
|
+
target: finite(card.target),
|
|
1185
|
+
legCount: finite(card.legCount)
|
|
1135
1186
|
};
|
|
1136
1187
|
}
|
|
1137
1188
|
function comboLegToSlipSelection(leg, conversationId) {
|
|
@@ -1205,10 +1256,10 @@ function resultToToast(result, shownOdds, ui) {
|
|
|
1205
1256
|
}
|
|
1206
1257
|
|
|
1207
1258
|
// src/useHostEvents.ts
|
|
1208
|
-
var
|
|
1259
|
+
var import_react44 = require("react");
|
|
1209
1260
|
|
|
1210
1261
|
// src/components/ChatPanel.tsx
|
|
1211
|
-
var
|
|
1262
|
+
var import_react43 = require("react");
|
|
1212
1263
|
|
|
1213
1264
|
// src/components/ChatHeader.tsx
|
|
1214
1265
|
var import_react2 = require("react");
|
|
@@ -1237,6 +1288,20 @@ function KebabIcon() {
|
|
|
1237
1288
|
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("circle", { cx: "12", cy: "19", r: "1.6" })
|
|
1238
1289
|
] }) });
|
|
1239
1290
|
}
|
|
1291
|
+
function SlidersIcon() {
|
|
1292
|
+
return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("svg", { viewBox: "0 0 24 24", width: "1em", height: "1em", "aria-hidden": "true", children: /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("g", { fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", children: [
|
|
1293
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("path", { d: "M4 6h9M17 6h3M4 12h3M11 12h9M4 18h11M19 18h1" }),
|
|
1294
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("circle", { cx: "15", cy: "6", r: "2" }),
|
|
1295
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("circle", { cx: "9", cy: "12", r: "2" }),
|
|
1296
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("circle", { cx: "17", cy: "18", r: "2" })
|
|
1297
|
+
] }) });
|
|
1298
|
+
}
|
|
1299
|
+
function ComboIcon() {
|
|
1300
|
+
return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("svg", { viewBox: "0 0 24 24", width: "1em", height: "1em", "aria-hidden": "true", children: /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("g", { fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinejoin: "round", children: [
|
|
1301
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("rect", { x: "3", y: "3", width: "11", height: "11", rx: "2" }),
|
|
1302
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("rect", { x: "10", y: "10", width: "11", height: "11", rx: "2" })
|
|
1303
|
+
] }) });
|
|
1304
|
+
}
|
|
1240
1305
|
var DASHBOARD_EMOJI = {
|
|
1241
1306
|
football: "\u26BD",
|
|
1242
1307
|
"goal-net": "\u{1F945}",
|
|
@@ -1342,7 +1407,7 @@ function ChatHeader({
|
|
|
1342
1407
|
}
|
|
1343
1408
|
|
|
1344
1409
|
// src/components/MessageList.tsx
|
|
1345
|
-
var
|
|
1410
|
+
var import_react22 = require("react");
|
|
1346
1411
|
|
|
1347
1412
|
// src/presets.ts
|
|
1348
1413
|
var CLIENT_CAPABILITIES = ["price_presets"];
|
|
@@ -2009,6 +2074,13 @@ function BettingInsightList({ insights, ui, slip, monitor }) {
|
|
|
2009
2074
|
return /* @__PURE__ */ (0, import_jsx_runtime13.jsx)("div", { className: "ss-w-binsights", children: insights.map((it) => /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(InsightCard, { it, ui, slip, monitor }, it.id)) });
|
|
2010
2075
|
}
|
|
2011
2076
|
|
|
2077
|
+
// src/presetToolsContext.ts
|
|
2078
|
+
var import_react12 = require("react");
|
|
2079
|
+
var PresetToolsContext = (0, import_react12.createContext)(null);
|
|
2080
|
+
function usePresetTools() {
|
|
2081
|
+
return (0, import_react12.useContext)(PresetToolsContext);
|
|
2082
|
+
}
|
|
2083
|
+
|
|
2012
2084
|
// src/components/ComboCardList.tsx
|
|
2013
2085
|
var import_jsx_runtime14 = require("react/jsx-runtime");
|
|
2014
2086
|
function ComboLegRow({ leg, addToSlipEnabled, ui, slip }) {
|
|
@@ -2051,10 +2123,13 @@ function ComboLegRow({ leg, addToSlipEnabled, ui, slip }) {
|
|
|
2051
2123
|
] });
|
|
2052
2124
|
}
|
|
2053
2125
|
function ComboCardView({ vm, ui, slip }) {
|
|
2126
|
+
const tools = usePresetTools();
|
|
2127
|
+
const { target, legCount } = vm;
|
|
2054
2128
|
return /* @__PURE__ */ (0, import_jsx_runtime14.jsxs)("div", { className: "ss-w-combo", children: [
|
|
2055
2129
|
/* @__PURE__ */ (0, import_jsx_runtime14.jsx)("ol", { className: "ss-w-combo-legs", role: "list", children: vm.legs.map((leg) => /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(ComboLegRow, { leg, addToSlipEnabled: vm.addToSlipEnabled, ui, slip }, leg.key)) }),
|
|
2056
2130
|
/* @__PURE__ */ (0, import_jsx_runtime14.jsx)("div", { className: "ss-w-combo-math", children: vm.multiplication }),
|
|
2057
|
-
vm.replacedNote ? /* @__PURE__ */ (0, import_jsx_runtime14.jsx)("p", { className: "ss-w-combo-note", children: vm.replacedNote }) : null
|
|
2131
|
+
vm.replacedNote ? /* @__PURE__ */ (0, import_jsx_runtime14.jsx)("p", { className: "ss-w-combo-note", children: vm.replacedNote }) : null,
|
|
2132
|
+
tools && target !== void 0 && legCount !== void 0 ? /* @__PURE__ */ (0, import_jsx_runtime14.jsx)("button", { type: "button", className: "ss-w-combo-change", onClick: () => tools.openBuilder(target, legCount), children: tools.changeLabel }) : null
|
|
2058
2133
|
] });
|
|
2059
2134
|
}
|
|
2060
2135
|
function ComboCardList({ cards, ui, slip }) {
|
|
@@ -2063,7 +2138,7 @@ function ComboCardList({ cards, ui, slip }) {
|
|
|
2063
2138
|
}
|
|
2064
2139
|
|
|
2065
2140
|
// src/components/MarketOddsCardList.tsx
|
|
2066
|
-
var
|
|
2141
|
+
var import_react13 = require("react");
|
|
2067
2142
|
var import_jsx_runtime15 = require("react/jsx-runtime");
|
|
2068
2143
|
function OddsRow({ vm, row, ui, slip, inSlipIds, history, suppressSlip }) {
|
|
2069
2144
|
const selection = vm.addToSlipEnabled && !suppressSlip ? marketOddsRowToSlipSelection(vm, row, slip.conversationId) : null;
|
|
@@ -2092,7 +2167,7 @@ function fmtHhMm(iso, locale) {
|
|
|
2092
2167
|
return new Date(iso).toLocaleTimeString(locale, { hour: "2-digit", minute: "2-digit", hour12: false });
|
|
2093
2168
|
}
|
|
2094
2169
|
function MarketOddsCard({ vm, ui, slip, monitor, inSlipIds, hideHeader, pinRef }) {
|
|
2095
|
-
const [open, setOpen] = (0,
|
|
2170
|
+
const [open, setOpen] = (0, import_react13.useState)(false);
|
|
2096
2171
|
const kickoffFallback = vm.kickoffUtc && ui?.bettingKickoff ? ui.bettingKickoff(vm.kickoffUtc) : void 0;
|
|
2097
2172
|
const kickoff = vm.kickoffLabel ?? (kickoffFallback ? `${kickoffFallback.dayLabel} ${kickoffFallback.time}` : void 0);
|
|
2098
2173
|
const hasNamedTeams = !!vm.homeTeamName && !!vm.awayTeamName;
|
|
@@ -2102,9 +2177,9 @@ function MarketOddsCard({ vm, ui, slip, monitor, inSlipIds, hideHeader, pinRef }
|
|
|
2102
2177
|
const asOfMs = live ? new Date(live.asOfUtc).getTime() : NaN;
|
|
2103
2178
|
const isStale = live != null && !isClosed && (vm.awaitingLiveUpdate === true || Date.now() - asOfMs > STALE_AFTER_MS);
|
|
2104
2179
|
const staleTime = isStale && live ? fmtHhMm(live.asOfUtc, ui?.locale ?? "en") : null;
|
|
2105
|
-
const [, setStaleTick] = (0,
|
|
2180
|
+
const [, setStaleTick] = (0, import_react13.useState)(0);
|
|
2106
2181
|
const watchStale = live != null && !isClosed && !isStale;
|
|
2107
|
-
(0,
|
|
2182
|
+
(0, import_react13.useEffect)(() => {
|
|
2108
2183
|
if (!watchStale) return;
|
|
2109
2184
|
const id = window.setTimeout(() => setStaleTick((n) => n + 1), Math.max(0, asOfMs + STALE_AFTER_MS - Date.now()) + 1);
|
|
2110
2185
|
return () => window.clearTimeout(id);
|
|
@@ -2178,7 +2253,7 @@ function MarketOddsCardList({ cards, ui, slip, monitor, inSlipIds, hideHeader, p
|
|
|
2178
2253
|
}
|
|
2179
2254
|
|
|
2180
2255
|
// src/components/CompactOddsList.tsx
|
|
2181
|
-
var
|
|
2256
|
+
var import_react14 = require("react");
|
|
2182
2257
|
|
|
2183
2258
|
// src/compactList.ts
|
|
2184
2259
|
var COMPACT_MIN_CARDS = 3;
|
|
@@ -2291,11 +2366,11 @@ function Row({ row, columns, ui, slip, monitor, rowRef }) {
|
|
|
2291
2366
|
] });
|
|
2292
2367
|
}
|
|
2293
2368
|
function CompactOddsList({ list, ui, slip, monitor }) {
|
|
2294
|
-
const [shown, setShown] = (0,
|
|
2369
|
+
const [shown, setShown] = (0, import_react14.useState)(COMPACT_PAGE);
|
|
2295
2370
|
const hidden = list.rowCount - shown;
|
|
2296
|
-
const [focusIndex, setFocusIndex] = (0,
|
|
2297
|
-
const focusRef = (0,
|
|
2298
|
-
(0,
|
|
2371
|
+
const [focusIndex, setFocusIndex] = (0, import_react14.useState)(null);
|
|
2372
|
+
const focusRef = (0, import_react14.useRef)(null);
|
|
2373
|
+
(0, import_react14.useEffect)(() => {
|
|
2299
2374
|
if (focusIndex !== null) focusRef.current?.focus();
|
|
2300
2375
|
}, [focusIndex]);
|
|
2301
2376
|
let left = shown;
|
|
@@ -2596,7 +2671,7 @@ function FixtureCard({ card }) {
|
|
|
2596
2671
|
}
|
|
2597
2672
|
|
|
2598
2673
|
// src/components/dashboard/CountdownCard.tsx
|
|
2599
|
-
var
|
|
2674
|
+
var import_react15 = require("react");
|
|
2600
2675
|
var import_jsx_runtime22 = require("react/jsx-runtime");
|
|
2601
2676
|
var DAY_MS = 864e5;
|
|
2602
2677
|
function remainingSegments(kickoffUnix, nowMs, withSeconds) {
|
|
@@ -2615,12 +2690,12 @@ function remainingSegments(kickoffUnix, nowMs, withSeconds) {
|
|
|
2615
2690
|
function CountdownCard({ card }) {
|
|
2616
2691
|
const { data } = card;
|
|
2617
2692
|
const unitLabel = { d: data.daysUnit, h: data.hoursUnit, m: data.minutesUnit, s: data.secondsUnit };
|
|
2618
|
-
const [nowMs, setNowMs] = (0,
|
|
2619
|
-
const [reduced] = (0,
|
|
2693
|
+
const [nowMs, setNowMs] = (0, import_react15.useState)(() => Date.now());
|
|
2694
|
+
const [reduced] = (0, import_react15.useState)(
|
|
2620
2695
|
() => typeof window !== "undefined" && window.matchMedia("(prefers-reduced-motion: reduce)").matches
|
|
2621
2696
|
);
|
|
2622
2697
|
const withinDay = data.kickoffUnix * 1e3 - nowMs < DAY_MS;
|
|
2623
|
-
(0,
|
|
2698
|
+
(0, import_react15.useEffect)(() => {
|
|
2624
2699
|
if (reduced) return;
|
|
2625
2700
|
const id = window.setInterval(() => setNowMs(Date.now()), withinDay ? 1e3 : 6e4);
|
|
2626
2701
|
return () => window.clearInterval(id);
|
|
@@ -2628,7 +2703,7 @@ function CountdownCard({ card }) {
|
|
|
2628
2703
|
const segments = remainingSegments(data.kickoffUnix, nowMs, withinDay && !reduced);
|
|
2629
2704
|
return /* @__PURE__ */ (0, import_jsx_runtime22.jsx)("div", { className: "ss-w-fandash-card ss-w-fandash-countdown", children: /* @__PURE__ */ (0, import_jsx_runtime22.jsxs)("div", { className: "ss-w-fandash-countdown-top", children: [
|
|
2630
2705
|
/* @__PURE__ */ (0, import_jsx_runtime22.jsx)("span", { className: "ss-w-fandash-eyebrow", children: card.title ?? "Kick-off in" }),
|
|
2631
|
-
segments ? /* @__PURE__ */ (0, import_jsx_runtime22.jsx)("span", { className: "ss-w-fandash-countdown-clock", children: segments.map((s, i) => /* @__PURE__ */ (0, import_jsx_runtime22.jsxs)(
|
|
2706
|
+
segments ? /* @__PURE__ */ (0, import_jsx_runtime22.jsx)("span", { className: "ss-w-fandash-countdown-clock", children: segments.map((s, i) => /* @__PURE__ */ (0, import_jsx_runtime22.jsxs)(import_react15.Fragment, { children: [
|
|
2632
2707
|
i > 0 ? /* @__PURE__ */ (0, import_jsx_runtime22.jsx)("span", { className: "ss-w-fandash-countdown-colon", children: ":" }) : null,
|
|
2633
2708
|
/* @__PURE__ */ (0, import_jsx_runtime22.jsxs)("span", { className: "ss-w-fandash-countdown-seg", children: [
|
|
2634
2709
|
/* @__PURE__ */ (0, import_jsx_runtime22.jsx)("span", { className: "ss-w-fandash-countdown-value", children: s.value }),
|
|
@@ -2639,7 +2714,7 @@ function CountdownCard({ card }) {
|
|
|
2639
2714
|
}
|
|
2640
2715
|
|
|
2641
2716
|
// src/components/dashboard/InjuriesCard.tsx
|
|
2642
|
-
var
|
|
2717
|
+
var import_react16 = require("react");
|
|
2643
2718
|
var import_jsx_runtime23 = require("react/jsx-runtime");
|
|
2644
2719
|
var SHOWN = 3;
|
|
2645
2720
|
function ColHead({ name, crestUrl }) {
|
|
@@ -2662,7 +2737,7 @@ function InjuryList({ rows, emptyLabel, limit }) {
|
|
|
2662
2737
|
}
|
|
2663
2738
|
function InjuriesCard({ card, ui }) {
|
|
2664
2739
|
const { data } = card;
|
|
2665
|
-
const [expanded, setExpanded] = (0,
|
|
2740
|
+
const [expanded, setExpanded] = (0, import_react16.useState)(false);
|
|
2666
2741
|
const hidden = Math.max(0, data.ours.length - SHOWN) + Math.max(0, data.theirs.length - SHOWN);
|
|
2667
2742
|
const limit = expanded ? void 0 : SHOWN;
|
|
2668
2743
|
const showMore = ui?.showMore ?? ((n) => `Show ${n} more`);
|
|
@@ -2768,7 +2843,7 @@ function LinksCard({ card }) {
|
|
|
2768
2843
|
}
|
|
2769
2844
|
|
|
2770
2845
|
// src/components/dashboard/PostMatchResultCard.tsx
|
|
2771
|
-
var
|
|
2846
|
+
var import_react17 = require("react");
|
|
2772
2847
|
var import_jsx_runtime29 = require("react/jsx-runtime");
|
|
2773
2848
|
function initials3(name) {
|
|
2774
2849
|
return name.trim().slice(0, 3).toUpperCase();
|
|
@@ -2778,7 +2853,7 @@ function PostMatchCrest({
|
|
|
2778
2853
|
name,
|
|
2779
2854
|
side
|
|
2780
2855
|
}) {
|
|
2781
|
-
const [failed, setFailed] = (0,
|
|
2856
|
+
const [failed, setFailed] = (0, import_react17.useState)(false);
|
|
2782
2857
|
const cls = `ss-w-fandash-postmatch-crest ss-w-fandash-postmatch-crest--${side}`;
|
|
2783
2858
|
if (url && !failed) {
|
|
2784
2859
|
return /* @__PURE__ */ (0, import_jsx_runtime29.jsx)("img", { className: cls, src: url, alt: "", "aria-hidden": "true", onError: () => setFailed(true) });
|
|
@@ -2977,10 +3052,10 @@ function RestingFormCard({ card }) {
|
|
|
2977
3052
|
}
|
|
2978
3053
|
|
|
2979
3054
|
// src/components/dashboard/LiveScoreCard.tsx
|
|
2980
|
-
var
|
|
3055
|
+
var import_react19 = require("react");
|
|
2981
3056
|
|
|
2982
3057
|
// src/useLiveClock.ts
|
|
2983
|
-
var
|
|
3058
|
+
var import_react18 = require("react");
|
|
2984
3059
|
var TICK_MS = 6e4;
|
|
2985
3060
|
var MAX_LIVE_TICK_MINUTES = 15;
|
|
2986
3061
|
function computeDisplayMinute(minute, clockRunning, receivedAtMs) {
|
|
@@ -2989,10 +3064,10 @@ function computeDisplayMinute(minute, clockRunning, receivedAtMs) {
|
|
|
2989
3064
|
return minute + Math.min(Math.max(0, elapsedMinutes), MAX_LIVE_TICK_MINUTES);
|
|
2990
3065
|
}
|
|
2991
3066
|
function useLiveClock(minute, clockRunning, receivedAtMs) {
|
|
2992
|
-
const [displayMinute, setDisplayMinute] = (0,
|
|
3067
|
+
const [displayMinute, setDisplayMinute] = (0, import_react18.useState)(
|
|
2993
3068
|
() => computeDisplayMinute(minute, clockRunning, receivedAtMs)
|
|
2994
3069
|
);
|
|
2995
|
-
(0,
|
|
3070
|
+
(0, import_react18.useEffect)(() => {
|
|
2996
3071
|
setDisplayMinute(computeDisplayMinute(minute, clockRunning, receivedAtMs));
|
|
2997
3072
|
if (!clockRunning) return;
|
|
2998
3073
|
const id = window.setInterval(() => {
|
|
@@ -3009,7 +3084,7 @@ function initials5(name) {
|
|
|
3009
3084
|
return name.trim().slice(0, 3).toUpperCase();
|
|
3010
3085
|
}
|
|
3011
3086
|
function LiveCrest({ url, name, side }) {
|
|
3012
|
-
const [failed, setFailed] = (0,
|
|
3087
|
+
const [failed, setFailed] = (0, import_react19.useState)(false);
|
|
3013
3088
|
const cls = `ss-w-fandash-live-crest ss-w-fandash-live-crest--${side}`;
|
|
3014
3089
|
if (url && !failed) {
|
|
3015
3090
|
return /* @__PURE__ */ (0, import_jsx_runtime38.jsx)("img", { className: cls, src: url, alt: "", "aria-hidden": "true", onError: () => setFailed(true) });
|
|
@@ -3028,7 +3103,7 @@ function LiveScoreCard({
|
|
|
3028
3103
|
}) {
|
|
3029
3104
|
const { data } = card;
|
|
3030
3105
|
const seedKey = `${data.minute}:${data.homeScore}:${data.awayScore}`;
|
|
3031
|
-
const seedRef = (0,
|
|
3106
|
+
const seedRef = (0, import_react19.useRef)(null);
|
|
3032
3107
|
if (!seedRef.current || seedRef.current.key !== seedKey) {
|
|
3033
3108
|
seedRef.current = { key: seedKey, receivedAtMs: Date.now() };
|
|
3034
3109
|
}
|
|
@@ -3068,7 +3143,7 @@ function LiveScoreCard({
|
|
|
3068
3143
|
}
|
|
3069
3144
|
|
|
3070
3145
|
// src/components/dashboard/LiveTimelineCard.tsx
|
|
3071
|
-
var
|
|
3146
|
+
var import_react20 = require("react");
|
|
3072
3147
|
var import_jsx_runtime39 = require("react/jsx-runtime");
|
|
3073
3148
|
var SHOWN2 = 3;
|
|
3074
3149
|
function EventIcon({ type }) {
|
|
@@ -3110,7 +3185,7 @@ function EventRow({ e }) {
|
|
|
3110
3185
|
] });
|
|
3111
3186
|
}
|
|
3112
3187
|
function LiveTimelineCard({ card, ui }) {
|
|
3113
|
-
const [expanded, setExpanded] = (0,
|
|
3188
|
+
const [expanded, setExpanded] = (0, import_react20.useState)(false);
|
|
3114
3189
|
const events = card.data.events;
|
|
3115
3190
|
if (!events || events.length === 0) return null;
|
|
3116
3191
|
const sorted = [...events].sort((a, b) => b.minute - a.minute);
|
|
@@ -3135,7 +3210,7 @@ function LiveTimelineCard({ card, ui }) {
|
|
|
3135
3210
|
}
|
|
3136
3211
|
|
|
3137
3212
|
// src/components/dashboard/MomentumWave.tsx
|
|
3138
|
-
var
|
|
3213
|
+
var import_react21 = require("react");
|
|
3139
3214
|
var import_jsx_runtime40 = require("react/jsx-runtime");
|
|
3140
3215
|
var WIDTH = 270;
|
|
3141
3216
|
var HEIGHT = 56;
|
|
@@ -3159,7 +3234,7 @@ function MomentumWave({
|
|
|
3159
3234
|
}) {
|
|
3160
3235
|
if (points.length === 0) return null;
|
|
3161
3236
|
const { area, line, lastLeftPct, lastTopPx } = buildPaths(points);
|
|
3162
|
-
const clipId = (0,
|
|
3237
|
+
const clipId = (0, import_react21.useId)();
|
|
3163
3238
|
const topClipId = `${clipId}-top`;
|
|
3164
3239
|
const bottomClipId = `${clipId}-bottom`;
|
|
3165
3240
|
const topClass = clubIsHome ? "ss-w-fandash-live-mom-home" : "ss-w-fandash-live-mom-away";
|
|
@@ -3352,11 +3427,11 @@ function turnAnchor(messages) {
|
|
|
3352
3427
|
}
|
|
3353
3428
|
function MessageList({ controller, ui, slip, onDashboardTap, monitor }) {
|
|
3354
3429
|
const { state, send } = controller;
|
|
3355
|
-
const listRef = (0,
|
|
3356
|
-
const endRef = (0,
|
|
3357
|
-
const pinnedRef = (0,
|
|
3358
|
-
const anchoredTurnRef = (0,
|
|
3359
|
-
(0,
|
|
3430
|
+
const listRef = (0, import_react22.useRef)(null);
|
|
3431
|
+
const endRef = (0, import_react22.useRef)(null);
|
|
3432
|
+
const pinnedRef = (0, import_react22.useRef)(true);
|
|
3433
|
+
const anchoredTurnRef = (0, import_react22.useRef)(null);
|
|
3434
|
+
(0, import_react22.useEffect)(() => {
|
|
3360
3435
|
const anchor = turnAnchor(state.messages);
|
|
3361
3436
|
const list = listRef.current;
|
|
3362
3437
|
if (anchor && anchor.turnId !== anchoredTurnRef.current) {
|
|
@@ -3413,11 +3488,15 @@ function MessageList({ controller, ui, slip, onDashboardTap, monitor }) {
|
|
|
3413
3488
|
}
|
|
3414
3489
|
|
|
3415
3490
|
// src/components/ChatInput.tsx
|
|
3416
|
-
var
|
|
3491
|
+
var import_react23 = require("react");
|
|
3417
3492
|
var import_jsx_runtime46 = require("react/jsx-runtime");
|
|
3418
|
-
|
|
3419
|
-
const [value, setValue] = (0,
|
|
3493
|
+
var ChatInput = (0, import_react23.forwardRef)(function ChatInput2({ placeholder, streaming, onSend, onStop, stopLabel = "Stop response", sendLabel = "Send message", disabled = false, onType }, ref) {
|
|
3494
|
+
const [value, setValue] = (0, import_react23.useState)("");
|
|
3495
|
+
const textareaRef = (0, import_react23.useRef)(null);
|
|
3496
|
+
const stopRef = (0, import_react23.useRef)(null);
|
|
3497
|
+
const textBoxWantsFocusRef = (0, import_react23.useRef)(false);
|
|
3420
3498
|
const submit = () => {
|
|
3499
|
+
if (disabled) return;
|
|
3421
3500
|
const t = value.trim();
|
|
3422
3501
|
if (!t) return;
|
|
3423
3502
|
onSend(t);
|
|
@@ -3429,11 +3508,48 @@ function ChatInput({ placeholder, streaming, onSend, onStop, stopLabel = "Stop r
|
|
|
3429
3508
|
submit();
|
|
3430
3509
|
}
|
|
3431
3510
|
};
|
|
3511
|
+
(0, import_react23.useImperativeHandle)(ref, () => ({
|
|
3512
|
+
focus() {
|
|
3513
|
+
const box = textareaRef.current;
|
|
3514
|
+
if (box && !box.disabled) {
|
|
3515
|
+
textBoxWantsFocusRef.current = false;
|
|
3516
|
+
box.focus();
|
|
3517
|
+
return;
|
|
3518
|
+
}
|
|
3519
|
+
textBoxWantsFocusRef.current = true;
|
|
3520
|
+
stopRef.current?.focus();
|
|
3521
|
+
}
|
|
3522
|
+
}), []);
|
|
3523
|
+
(0, import_react23.useEffect)(() => {
|
|
3524
|
+
if (streaming || disabled || !textBoxWantsFocusRef.current) return;
|
|
3525
|
+
textBoxWantsFocusRef.current = false;
|
|
3526
|
+
const box = textareaRef.current;
|
|
3527
|
+
if (!box) return;
|
|
3528
|
+
const active = box.getRootNode().activeElement;
|
|
3529
|
+
const input = box.closest(".ss-w-input");
|
|
3530
|
+
const isWithinComponent = input?.contains(active);
|
|
3531
|
+
if (!active || active === box.ownerDocument.body || isWithinComponent) box.focus();
|
|
3532
|
+
}, [streaming, disabled]);
|
|
3432
3533
|
return /* @__PURE__ */ (0, import_jsx_runtime46.jsxs)("div", { className: "ss-w-input", children: [
|
|
3433
|
-
/* @__PURE__ */ (0, import_jsx_runtime46.jsx)(
|
|
3434
|
-
|
|
3534
|
+
/* @__PURE__ */ (0, import_jsx_runtime46.jsx)(
|
|
3535
|
+
"textarea",
|
|
3536
|
+
{
|
|
3537
|
+
ref: textareaRef,
|
|
3538
|
+
className: "ss-w-textarea",
|
|
3539
|
+
rows: 1,
|
|
3540
|
+
placeholder,
|
|
3541
|
+
value,
|
|
3542
|
+
disabled: streaming || disabled,
|
|
3543
|
+
onChange: (e) => {
|
|
3544
|
+
setValue(e.target.value);
|
|
3545
|
+
onType?.();
|
|
3546
|
+
},
|
|
3547
|
+
onKeyDown
|
|
3548
|
+
}
|
|
3549
|
+
),
|
|
3550
|
+
streaming ? /* @__PURE__ */ (0, import_jsx_runtime46.jsx)("button", { ref: stopRef, type: "button", className: "ss-w-send ss-w-stop", "aria-label": stopLabel, disabled, onClick: onStop, children: /* @__PURE__ */ (0, import_jsx_runtime46.jsx)("span", { className: "ss-w-stop-glyph" }) }) : /* @__PURE__ */ (0, import_jsx_runtime46.jsx)("button", { type: "button", className: "ss-w-send", "aria-label": sendLabel, disabled, onClick: submit, children: /* @__PURE__ */ (0, import_jsx_runtime46.jsx)(SendIcon, {}) })
|
|
3435
3551
|
] });
|
|
3436
|
-
}
|
|
3552
|
+
});
|
|
3437
3553
|
|
|
3438
3554
|
// src/components/TabBar.tsx
|
|
3439
3555
|
var import_jsx_runtime47 = require("react/jsx-runtime");
|
|
@@ -3526,10 +3642,10 @@ function MarketAccordion({ match, monitor, ui, slip }) {
|
|
|
3526
3642
|
}
|
|
3527
3643
|
|
|
3528
3644
|
// src/components/LiveFactBlock.tsx
|
|
3529
|
-
var
|
|
3645
|
+
var import_react24 = require("react");
|
|
3530
3646
|
var import_jsx_runtime49 = require("react/jsx-runtime");
|
|
3531
3647
|
function LiveFactBlock({ facts, matchId, ui, slip, onAsk }) {
|
|
3532
|
-
const [expanded, setExpanded] = (0,
|
|
3648
|
+
const [expanded, setExpanded] = (0, import_react24.useState)(false);
|
|
3533
3649
|
const [current, ...earlier] = facts;
|
|
3534
3650
|
if (!current) return null;
|
|
3535
3651
|
const m = current.market;
|
|
@@ -3916,7 +4032,7 @@ function TodayPanel({ discovery, ui, monitor }) {
|
|
|
3916
4032
|
}
|
|
3917
4033
|
|
|
3918
4034
|
// src/components/GamesPanel.tsx
|
|
3919
|
-
var
|
|
4035
|
+
var import_react38 = require("react");
|
|
3920
4036
|
|
|
3921
4037
|
// src/components/GamesLoginWall.tsx
|
|
3922
4038
|
var import_jsx_runtime55 = require("react/jsx-runtime");
|
|
@@ -3925,7 +4041,7 @@ function GamesLoginWall({ ui }) {
|
|
|
3925
4041
|
}
|
|
3926
4042
|
|
|
3927
4043
|
// src/components/GamesHub.tsx
|
|
3928
|
-
var
|
|
4044
|
+
var import_react25 = require("react");
|
|
3929
4045
|
|
|
3930
4046
|
// src/components/games/ui/gameMeta.ts
|
|
3931
4047
|
function prettifyGameId(gameId) {
|
|
@@ -4110,8 +4226,8 @@ function pickHeroGame(games) {
|
|
|
4110
4226
|
}
|
|
4111
4227
|
var tileKey = (g) => `${g.gameId}-${g.gameDay}-${g.fixtureId}`;
|
|
4112
4228
|
function GamesHub({ games, myWeeklyRow, weeklyPrize, onOpen, ui }) {
|
|
4113
|
-
const [, setTick] = (0,
|
|
4114
|
-
(0,
|
|
4229
|
+
const [, setTick] = (0, import_react25.useState)(0);
|
|
4230
|
+
(0, import_react25.useEffect)(() => {
|
|
4115
4231
|
const id = setInterval(() => setTick((t) => t + 1), 6e4);
|
|
4116
4232
|
return () => clearInterval(id);
|
|
4117
4233
|
}, []);
|
|
@@ -4171,10 +4287,10 @@ function GamesHub({ games, myWeeklyRow, weeklyPrize, onOpen, ui }) {
|
|
|
4171
4287
|
}
|
|
4172
4288
|
|
|
4173
4289
|
// src/components/GameScreenHost.tsx
|
|
4174
|
-
var
|
|
4290
|
+
var import_react37 = require("react");
|
|
4175
4291
|
|
|
4176
4292
|
// src/useGamePlay.ts
|
|
4177
|
-
var
|
|
4293
|
+
var import_react26 = require("react");
|
|
4178
4294
|
function toSubmitResult(payload) {
|
|
4179
4295
|
return {
|
|
4180
4296
|
// 'pending' means the play is recorded but awaiting resolution (fixture games)
|
|
@@ -4186,17 +4302,17 @@ function toSubmitResult(payload) {
|
|
|
4186
4302
|
};
|
|
4187
4303
|
}
|
|
4188
4304
|
function useGamePlay(conversation, { gameId, gameDay, fixtureId, locale, refresh }) {
|
|
4189
|
-
const [result, setResult] = (0,
|
|
4190
|
-
const [submitting, setSubmitting] = (0,
|
|
4191
|
-
const [rawStatus, setRawStatus] = (0,
|
|
4192
|
-
const inFlightRef = (0,
|
|
4193
|
-
const convRef = (0,
|
|
4305
|
+
const [result, setResult] = (0, import_react26.useState)(null);
|
|
4306
|
+
const [submitting, setSubmitting] = (0, import_react26.useState)(false);
|
|
4307
|
+
const [rawStatus, setRawStatus] = (0, import_react26.useState)(null);
|
|
4308
|
+
const inFlightRef = (0, import_react26.useRef)(false);
|
|
4309
|
+
const convRef = (0, import_react26.useRef)(conversation);
|
|
4194
4310
|
convRef.current = conversation;
|
|
4195
|
-
const optsRef = (0,
|
|
4311
|
+
const optsRef = (0, import_react26.useRef)({ gameId, gameDay, fixtureId, locale, refresh });
|
|
4196
4312
|
optsRef.current = { gameId, gameDay, fixtureId, locale, refresh };
|
|
4197
|
-
const resultRef = (0,
|
|
4313
|
+
const resultRef = (0, import_react26.useRef)(null);
|
|
4198
4314
|
resultRef.current = result;
|
|
4199
|
-
const refetch = (0,
|
|
4315
|
+
const refetch = (0, import_react26.useCallback)(async () => {
|
|
4200
4316
|
const conv = convRef.current;
|
|
4201
4317
|
if (!conv) return;
|
|
4202
4318
|
const opts = optsRef.current;
|
|
@@ -4212,7 +4328,7 @@ function useGamePlay(conversation, { gameId, gameDay, fixtureId, locale, refresh
|
|
|
4212
4328
|
} catch {
|
|
4213
4329
|
}
|
|
4214
4330
|
}, []);
|
|
4215
|
-
const submit = (0,
|
|
4331
|
+
const submit = (0, import_react26.useCallback)(async (answerJson) => {
|
|
4216
4332
|
const conv = convRef.current;
|
|
4217
4333
|
if (!conv || inFlightRef.current) return;
|
|
4218
4334
|
inFlightRef.current = true;
|
|
@@ -4239,14 +4355,14 @@ function useGamePlay(conversation, { gameId, gameDay, fixtureId, locale, refresh
|
|
|
4239
4355
|
setSubmitting(false);
|
|
4240
4356
|
}
|
|
4241
4357
|
}, [refetch]);
|
|
4242
|
-
const hydrate = (0,
|
|
4358
|
+
const hydrate = (0, import_react26.useCallback)(() => {
|
|
4243
4359
|
void refetch();
|
|
4244
4360
|
}, [refetch]);
|
|
4245
4361
|
return { result, submitting, submit, hydrate, rawStatus };
|
|
4246
4362
|
}
|
|
4247
4363
|
|
|
4248
4364
|
// src/components/games/MysteryTigerGame.tsx
|
|
4249
|
-
var
|
|
4365
|
+
var import_react28 = require("react");
|
|
4250
4366
|
|
|
4251
4367
|
// src/components/games/nationFlags.ts
|
|
4252
4368
|
var FLAGS = {
|
|
@@ -4457,7 +4573,7 @@ function isNationCode(flag) {
|
|
|
4457
4573
|
}
|
|
4458
4574
|
|
|
4459
4575
|
// src/components/games/PlayerAvatar.tsx
|
|
4460
|
-
var
|
|
4576
|
+
var import_react27 = require("react");
|
|
4461
4577
|
var import_jsx_runtime61 = require("react/jsx-runtime");
|
|
4462
4578
|
function initialsOf(name) {
|
|
4463
4579
|
const parts = name.trim().split(/\s+/).filter(Boolean);
|
|
@@ -4475,7 +4591,7 @@ function PlayerAvatar({
|
|
|
4475
4591
|
imagePath,
|
|
4476
4592
|
className
|
|
4477
4593
|
}) {
|
|
4478
|
-
const [failed, setFailed] = (0,
|
|
4594
|
+
const [failed, setFailed] = (0, import_react27.useState)(false);
|
|
4479
4595
|
const showImage = Boolean(imagePath) && !isPlaceholderImage(imagePath) && !failed;
|
|
4480
4596
|
return /* @__PURE__ */ (0, import_jsx_runtime61.jsx)("div", { className, "aria-hidden": "true", children: showImage ? /* @__PURE__ */ (0, import_jsx_runtime61.jsx)("img", { src: imagePath, alt: "", loading: "lazy", onError: () => setFailed(true) }) : /* @__PURE__ */ (0, import_jsx_runtime61.jsx)("span", { className: "ss-w-av-initials", children: initialsOf(name) }) });
|
|
4481
4597
|
}
|
|
@@ -4690,8 +4806,8 @@ function MysteryTigerGame({
|
|
|
4690
4806
|
onBack
|
|
4691
4807
|
}) {
|
|
4692
4808
|
const t = ui.games.mysteryTiger;
|
|
4693
|
-
const [query, setQuery] = (0,
|
|
4694
|
-
const inputId = (0,
|
|
4809
|
+
const [query, setQuery] = (0, import_react28.useState)("");
|
|
4810
|
+
const inputId = (0, import_react28.useId)();
|
|
4695
4811
|
const clientState = parseClientState(result?.clientStateJson);
|
|
4696
4812
|
const guessResults = clientState?.guessResults ?? [];
|
|
4697
4813
|
const alreadyGuessedIds = new Set(guessResults.map((g) => g.playerId));
|
|
@@ -4966,13 +5082,13 @@ function HigherOrLowerGame({
|
|
|
4966
5082
|
}
|
|
4967
5083
|
|
|
4968
5084
|
// src/components/games/CareerPathGame.tsx
|
|
4969
|
-
var
|
|
5085
|
+
var import_react30 = require("react");
|
|
4970
5086
|
|
|
4971
5087
|
// src/components/games/ui/CrestShield.tsx
|
|
4972
|
-
var
|
|
5088
|
+
var import_react29 = require("react");
|
|
4973
5089
|
var import_jsx_runtime72 = require("react/jsx-runtime");
|
|
4974
5090
|
function CrestShield({ src, name, size = 28 }) {
|
|
4975
|
-
const [failed, setFailed] = (0,
|
|
5091
|
+
const [failed, setFailed] = (0, import_react29.useState)(false);
|
|
4976
5092
|
if (src && !isPlaceholderImage(src) && !failed) {
|
|
4977
5093
|
return /* @__PURE__ */ (0, import_jsx_runtime72.jsx)("img", { className: "ss-g-crest", src, alt: name, width: size, height: size, loading: "lazy", onError: () => setFailed(true) });
|
|
4978
5094
|
}
|
|
@@ -5004,8 +5120,8 @@ function CareerPathGame({
|
|
|
5004
5120
|
}) {
|
|
5005
5121
|
const s = ui.games.careerPath;
|
|
5006
5122
|
const title = gameTitle({ gameId: "career-path" }, ui.games.titles);
|
|
5007
|
-
const [query, setQuery] = (0,
|
|
5008
|
-
const inputId = (0,
|
|
5123
|
+
const [query, setQuery] = (0, import_react30.useState)("");
|
|
5124
|
+
const inputId = (0, import_react30.useId)();
|
|
5009
5125
|
const clientState = parseClientState3(result?.clientStateJson);
|
|
5010
5126
|
const alreadyGuessedIds = new Set(clientState?.guessResults.map((g) => g.playerId) ?? []);
|
|
5011
5127
|
const isOver = result?.alreadyPlayed === true || clientState?.solved === true || clientState?.failed === true;
|
|
@@ -5124,7 +5240,7 @@ function CareerPathGame({
|
|
|
5124
5240
|
}
|
|
5125
5241
|
|
|
5126
5242
|
// src/components/games/BeatTheGridGame.tsx
|
|
5127
|
-
var
|
|
5243
|
+
var import_react31 = __toESM(require("react"), 1);
|
|
5128
5244
|
var import_jsx_runtime74 = require("react/jsx-runtime");
|
|
5129
5245
|
function criterionIconKind(label, key) {
|
|
5130
5246
|
const l = (key ?? label).toLowerCase();
|
|
@@ -5205,10 +5321,10 @@ function SearchSheet({
|
|
|
5205
5321
|
ui
|
|
5206
5322
|
}) {
|
|
5207
5323
|
const s = ui.games.beatTheGrid;
|
|
5208
|
-
const [query, setQuery] = (0,
|
|
5209
|
-
const inputId = (0,
|
|
5210
|
-
const inputRef = (0,
|
|
5211
|
-
(0,
|
|
5324
|
+
const [query, setQuery] = (0, import_react31.useState)("");
|
|
5325
|
+
const inputId = (0, import_react31.useId)();
|
|
5326
|
+
const inputRef = (0, import_react31.useRef)(null);
|
|
5327
|
+
(0, import_react31.useEffect)(() => {
|
|
5212
5328
|
inputRef.current?.focus();
|
|
5213
5329
|
}, [row, col]);
|
|
5214
5330
|
const filtered = playerPool.filter(
|
|
@@ -5273,7 +5389,7 @@ function BeatTheGridGame({
|
|
|
5273
5389
|
}) {
|
|
5274
5390
|
const s = ui.games.beatTheGrid;
|
|
5275
5391
|
const title = gameTitle({ gameId: "beat-the-grid" }, ui.games.titles);
|
|
5276
|
-
const [activeCell, setActiveCell] = (0,
|
|
5392
|
+
const [activeCell, setActiveCell] = (0, import_react31.useState)(null);
|
|
5277
5393
|
const clientState = parseClientState4(result?.clientStateJson);
|
|
5278
5394
|
const filledCells = clientState?.cells ?? [];
|
|
5279
5395
|
const isLocked2 = result?.alreadyPlayed === true || clientState?.isComplete === true;
|
|
@@ -5347,7 +5463,7 @@ function BeatTheGridGame({
|
|
|
5347
5463
|
/* @__PURE__ */ (0, import_jsx_runtime74.jsx)("span", { "aria-hidden": "true", children: abbr }),
|
|
5348
5464
|
/* @__PURE__ */ (0, import_jsx_runtime74.jsx)("span", { className: "ss-g-sr", children: rowLabel })
|
|
5349
5465
|
] });
|
|
5350
|
-
return /* @__PURE__ */ (0, import_jsx_runtime74.jsxs)(
|
|
5466
|
+
return /* @__PURE__ */ (0, import_jsx_runtime74.jsxs)(import_react31.default.Fragment, { children: [
|
|
5351
5467
|
/* @__PURE__ */ (0, import_jsx_runtime74.jsx)("div", { className: "ss-g-btg-rowhdr", "data-meta": rowMeta ? "true" : void 0, children: rowHeaderContent }),
|
|
5352
5468
|
cols.map((colLabel, colIdx) => {
|
|
5353
5469
|
const filled = findCellResult(filledCells, rowIdx, colIdx);
|
|
@@ -5428,7 +5544,7 @@ function BeatTheGridGame({
|
|
|
5428
5544
|
}
|
|
5429
5545
|
|
|
5430
5546
|
// src/components/games/GuessTheXiGame.tsx
|
|
5431
|
-
var
|
|
5547
|
+
var import_react32 = require("react");
|
|
5432
5548
|
|
|
5433
5549
|
// src/components/games/ui/PitchSvg.tsx
|
|
5434
5550
|
var import_jsx_runtime75 = require("react/jsx-runtime");
|
|
@@ -5517,8 +5633,8 @@ function GuessTheXiGame({
|
|
|
5517
5633
|
onBack
|
|
5518
5634
|
}) {
|
|
5519
5635
|
const t = ui.games.guessTheXi;
|
|
5520
|
-
const [query, setQuery] = (0,
|
|
5521
|
-
const inputId = (0,
|
|
5636
|
+
const [query, setQuery] = (0, import_react32.useState)("");
|
|
5637
|
+
const inputId = (0, import_react32.useId)();
|
|
5522
5638
|
const clientState = parseClientState5(result?.clientStateJson);
|
|
5523
5639
|
const isOver = result?.alreadyPlayed === true || clientState?.status === "solved" || clientState?.status === "failed";
|
|
5524
5640
|
const solved = clientState?.status === "solved";
|
|
@@ -5659,7 +5775,7 @@ function GuessTheXiGame({
|
|
|
5659
5775
|
}
|
|
5660
5776
|
|
|
5661
5777
|
// src/components/games/DailyQuizGame.tsx
|
|
5662
|
-
var
|
|
5778
|
+
var import_react33 = require("react");
|
|
5663
5779
|
var import_jsx_runtime77 = require("react/jsx-runtime");
|
|
5664
5780
|
function parseClientState6(json) {
|
|
5665
5781
|
if (!json) return null;
|
|
@@ -5700,8 +5816,8 @@ function DailyQuizGame({
|
|
|
5700
5816
|
}) {
|
|
5701
5817
|
const s = ui.games.dailyQuiz;
|
|
5702
5818
|
const title = gameTitle({ gameId: "daily-quiz" }, ui.games.titles);
|
|
5703
|
-
const [selections, setSelections] = (0,
|
|
5704
|
-
const [page, setPage] = (0,
|
|
5819
|
+
const [selections, setSelections] = (0, import_react33.useState)(/* @__PURE__ */ new Map());
|
|
5820
|
+
const [page, setPage] = (0, import_react33.useState)(0);
|
|
5705
5821
|
const clientState = parseClientState6(result?.clientStateJson);
|
|
5706
5822
|
const results = clientState?.results ?? [];
|
|
5707
5823
|
const isRevealed = results.length > 0 || result?.alreadyPlayed === true;
|
|
@@ -5811,7 +5927,7 @@ function DailyQuizGame({
|
|
|
5811
5927
|
}
|
|
5812
5928
|
|
|
5813
5929
|
// src/components/games/MatchdayPredictorGame.tsx
|
|
5814
|
-
var
|
|
5930
|
+
var import_react34 = require("react");
|
|
5815
5931
|
var import_jsx_runtime78 = require("react/jsx-runtime");
|
|
5816
5932
|
var SCORER_SEARCH_THRESHOLD = 9;
|
|
5817
5933
|
function parseClientState7(json) {
|
|
@@ -5907,12 +6023,12 @@ function MatchdayPredictorGame({
|
|
|
5907
6023
|
onBack
|
|
5908
6024
|
}) {
|
|
5909
6025
|
const t = ui.games.predictor;
|
|
5910
|
-
const [homeScore, setHomeScore] = (0,
|
|
5911
|
-
const [awayScore, setAwayScore] = (0,
|
|
5912
|
-
const [firstScorerPlayerId, setFirstScorerPlayerId] = (0,
|
|
5913
|
-
const [lastEdited, setLastEdited] = (0,
|
|
5914
|
-
const [query, setQuery] = (0,
|
|
5915
|
-
const searchId = (0,
|
|
6026
|
+
const [homeScore, setHomeScore] = (0, import_react34.useState)(0);
|
|
6027
|
+
const [awayScore, setAwayScore] = (0, import_react34.useState)(0);
|
|
6028
|
+
const [firstScorerPlayerId, setFirstScorerPlayerId] = (0, import_react34.useState)(null);
|
|
6029
|
+
const [lastEdited, setLastEdited] = (0, import_react34.useState)("home");
|
|
6030
|
+
const [query, setQuery] = (0, import_react34.useState)("");
|
|
6031
|
+
const searchId = (0, import_react34.useId)();
|
|
5916
6032
|
const clientState = parseClientState7(result?.clientStateJson);
|
|
5917
6033
|
const alreadyPlayed = result?.alreadyPlayed === true;
|
|
5918
6034
|
const header = /* @__PURE__ */ (0, import_jsx_runtime78.jsx)(
|
|
@@ -6079,7 +6195,7 @@ function MatchdayPredictorGame({
|
|
|
6079
6195
|
}
|
|
6080
6196
|
|
|
6081
6197
|
// src/components/games/StartingXiGame.tsx
|
|
6082
|
-
var
|
|
6198
|
+
var import_react35 = require("react");
|
|
6083
6199
|
var import_jsx_runtime79 = require("react/jsx-runtime");
|
|
6084
6200
|
var POINTS_PER_STARTER = 8;
|
|
6085
6201
|
var XI_SIZE = 11;
|
|
@@ -6123,10 +6239,10 @@ function StartingXiGame({
|
|
|
6123
6239
|
lockAtUtc
|
|
6124
6240
|
}) {
|
|
6125
6241
|
const t = ui.games.startingXi;
|
|
6126
|
-
const [slotPlayers, setSlotPlayers] = (0,
|
|
6127
|
-
const [active, setActive] = (0,
|
|
6128
|
-
const [query, setQuery] = (0,
|
|
6129
|
-
const searchId = (0,
|
|
6242
|
+
const [slotPlayers, setSlotPlayers] = (0, import_react35.useState)(() => Array(XI_SIZE).fill(null));
|
|
6243
|
+
const [active, setActive] = (0, import_react35.useState)(0);
|
|
6244
|
+
const [query, setQuery] = (0, import_react35.useState)("");
|
|
6245
|
+
const searchId = (0, import_react35.useId)();
|
|
6130
6246
|
const clientState = parseClientState8(result?.clientStateJson);
|
|
6131
6247
|
const alreadyPlayed = result?.alreadyPlayed === true;
|
|
6132
6248
|
const header = /* @__PURE__ */ (0, import_jsx_runtime79.jsx)(
|
|
@@ -6265,7 +6381,7 @@ function StartingXiGame({
|
|
|
6265
6381
|
}
|
|
6266
6382
|
|
|
6267
6383
|
// src/components/games/StatLadderGame.tsx
|
|
6268
|
-
var
|
|
6384
|
+
var import_react36 = require("react");
|
|
6269
6385
|
var import_jsx_runtime80 = require("react/jsx-runtime");
|
|
6270
6386
|
function ladderClue(ladder) {
|
|
6271
6387
|
const mystery = ladder.find((r) => r.rank === 2);
|
|
@@ -6302,9 +6418,9 @@ function StatLadderGame({
|
|
|
6302
6418
|
onBack
|
|
6303
6419
|
}) {
|
|
6304
6420
|
const t = ui.games.statLadder;
|
|
6305
|
-
const [query, setQuery] = (0,
|
|
6306
|
-
const [selectedId, setSelectedId] = (0,
|
|
6307
|
-
const searchId = (0,
|
|
6421
|
+
const [query, setQuery] = (0, import_react36.useState)("");
|
|
6422
|
+
const [selectedId, setSelectedId] = (0, import_react36.useState)(null);
|
|
6423
|
+
const searchId = (0, import_react36.useId)();
|
|
6308
6424
|
const clientState = parseClientState9(result?.clientStateJson);
|
|
6309
6425
|
const revealed = clientState != null;
|
|
6310
6426
|
const header = /* @__PURE__ */ (0, import_jsx_runtime80.jsx)(GameHeader, { title: gameTitle({ gameId: "stat-ladder" }, ui.games.titles), eyebrow: t.eyebrow, onBack, backLabel: ui.back });
|
|
@@ -6509,8 +6625,8 @@ function GameScreenHost({
|
|
|
6509
6625
|
ui
|
|
6510
6626
|
}) {
|
|
6511
6627
|
const { gameId, fixtureId, gameDay = "", lockAtUtc = null } = selected;
|
|
6512
|
-
const [configJson, setConfigJson] = (0,
|
|
6513
|
-
(0,
|
|
6628
|
+
const [configJson, setConfigJson] = (0, import_react37.useState)(null);
|
|
6629
|
+
(0, import_react37.useEffect)(() => {
|
|
6514
6630
|
setConfigJson(null);
|
|
6515
6631
|
if (!conversation) return;
|
|
6516
6632
|
let cancelled = false;
|
|
@@ -6533,12 +6649,12 @@ function GameScreenHost({
|
|
|
6533
6649
|
fixtureId: fixtureId || void 0,
|
|
6534
6650
|
locale: ui.games.lang
|
|
6535
6651
|
});
|
|
6536
|
-
(0,
|
|
6652
|
+
(0, import_react37.useEffect)(() => {
|
|
6537
6653
|
if (conversation) hydrate();
|
|
6538
6654
|
}, [conversation, gameId]);
|
|
6539
|
-
const hydrateRef = (0,
|
|
6655
|
+
const hydrateRef = (0, import_react37.useRef)(hydrate);
|
|
6540
6656
|
hydrateRef.current = hydrate;
|
|
6541
|
-
(0,
|
|
6657
|
+
(0, import_react37.useEffect)(() => {
|
|
6542
6658
|
if (!FIXTURE_GAME_IDS.has(gameId)) return;
|
|
6543
6659
|
if (rawStatus !== "pending") return;
|
|
6544
6660
|
const timerId = setInterval(() => {
|
|
@@ -6673,8 +6789,8 @@ function GamesPanel({
|
|
|
6673
6789
|
leaderboard,
|
|
6674
6790
|
refreshHub
|
|
6675
6791
|
}) {
|
|
6676
|
-
const [selected, setSelected] = (0,
|
|
6677
|
-
(0,
|
|
6792
|
+
const [selected, setSelected] = (0, import_react38.useState)(null);
|
|
6793
|
+
(0, import_react38.useEffect)(() => {
|
|
6678
6794
|
ensureGamesFonts();
|
|
6679
6795
|
}, []);
|
|
6680
6796
|
if (!loggedIn) return /* @__PURE__ */ (0, import_jsx_runtime83.jsx)(GamesLoginWall, { ui });
|
|
@@ -6705,7 +6821,7 @@ function GamesPanel({
|
|
|
6705
6821
|
}
|
|
6706
6822
|
|
|
6707
6823
|
// src/components/Leaderboard.tsx
|
|
6708
|
-
var
|
|
6824
|
+
var import_react39 = require("react");
|
|
6709
6825
|
|
|
6710
6826
|
// src/components/LeaderboardRow.tsx
|
|
6711
6827
|
var import_jsx_runtime84 = require("react/jsx-runtime");
|
|
@@ -6790,7 +6906,7 @@ function Leaderboard({
|
|
|
6790
6906
|
ui,
|
|
6791
6907
|
onPlay
|
|
6792
6908
|
}) {
|
|
6793
|
-
const [segment, setSegment] = (0,
|
|
6909
|
+
const [segment, setSegment] = (0, import_react39.useState)("weekly");
|
|
6794
6910
|
const myRow = segment === "weekly" ? data.myWeeklyRow : data.myAllTimeRow;
|
|
6795
6911
|
const offBoard = myRow !== null && myRow !== void 0 && myRow.rank === -1;
|
|
6796
6912
|
const isEmpty = segment === "weekly" ? data.weeklyTop.length === 0 : data.allTimeTop.length === 0;
|
|
@@ -6869,12 +6985,440 @@ function LeaderboardPanel({ loggedIn, data, loading, onPlay, ui }) {
|
|
|
6869
6985
|
return /* @__PURE__ */ (0, import_jsx_runtime87.jsx)("div", { className: "ss-w-mon-list", children: /* @__PURE__ */ (0, import_jsx_runtime87.jsx)(Leaderboard, { data: data ?? EMPTY_PAYLOAD, ui, onPlay }) });
|
|
6870
6986
|
}
|
|
6871
6987
|
|
|
6872
|
-
// src/components/
|
|
6988
|
+
// src/components/PresetSpeedDial.tsx
|
|
6989
|
+
var import_react40 = require("react");
|
|
6873
6990
|
var import_jsx_runtime88 = require("react/jsx-runtime");
|
|
6991
|
+
function PresetSpeedDial({ expanded, showDot, strings, onToggle, onOpen, rootRef, buttonRef }) {
|
|
6992
|
+
const pillsId = (0, import_react40.useId)();
|
|
6993
|
+
const tabIndex = expanded ? 0 : -1;
|
|
6994
|
+
return /* @__PURE__ */ (0, import_jsx_runtime88.jsxs)("div", { ref: rootRef, className: "ss-w-dial", children: [
|
|
6995
|
+
/* @__PURE__ */ (0, import_jsx_runtime88.jsxs)(
|
|
6996
|
+
"button",
|
|
6997
|
+
{
|
|
6998
|
+
ref: buttonRef,
|
|
6999
|
+
type: "button",
|
|
7000
|
+
className: "ss-w-dial-btn",
|
|
7001
|
+
"aria-expanded": expanded,
|
|
7002
|
+
"aria-controls": pillsId,
|
|
7003
|
+
"aria-label": strings.dialName,
|
|
7004
|
+
onClick: onToggle,
|
|
7005
|
+
children: [
|
|
7006
|
+
/* @__PURE__ */ (0, import_jsx_runtime88.jsx)("span", { className: "ss-w-dial-icon ss-w-dial-icon--open", children: /* @__PURE__ */ (0, import_jsx_runtime88.jsx)(SlidersIcon, {}) }),
|
|
7007
|
+
/* @__PURE__ */ (0, import_jsx_runtime88.jsx)("span", { className: "ss-w-dial-icon ss-w-dial-icon--close", children: /* @__PURE__ */ (0, import_jsx_runtime88.jsx)(CloseIcon, {}) }),
|
|
7008
|
+
showDot && !expanded ? /* @__PURE__ */ (0, import_jsx_runtime88.jsx)("span", { className: "ss-w-dial-dot", "aria-hidden": "true" }) : null
|
|
7009
|
+
]
|
|
7010
|
+
}
|
|
7011
|
+
),
|
|
7012
|
+
/* @__PURE__ */ (0, import_jsx_runtime88.jsxs)("div", { id: pillsId, className: "ss-w-dial-pills", "data-open": expanded ? "" : void 0, children: [
|
|
7013
|
+
/* @__PURE__ */ (0, import_jsx_runtime88.jsxs)("button", { type: "button", className: "ss-w-dial-pill", tabIndex, onClick: () => onOpen("numbers"), children: [
|
|
7014
|
+
/* @__PURE__ */ (0, import_jsx_runtime88.jsx)(SlidersIcon, {}),
|
|
7015
|
+
/* @__PURE__ */ (0, import_jsx_runtime88.jsx)("span", { className: "ss-w-dial-pill-label", children: strings.setNumbers })
|
|
7016
|
+
] }),
|
|
7017
|
+
/* @__PURE__ */ (0, import_jsx_runtime88.jsxs)("button", { type: "button", className: "ss-w-dial-pill", tabIndex, onClick: () => onOpen("combo"), children: [
|
|
7018
|
+
/* @__PURE__ */ (0, import_jsx_runtime88.jsx)(ComboIcon, {}),
|
|
7019
|
+
/* @__PURE__ */ (0, import_jsx_runtime88.jsx)("span", { className: "ss-w-dial-pill-label", children: strings.buildCombo })
|
|
7020
|
+
] })
|
|
7021
|
+
] })
|
|
7022
|
+
] });
|
|
7023
|
+
}
|
|
7024
|
+
|
|
7025
|
+
// src/components/PresetTools.tsx
|
|
7026
|
+
var import_react41 = require("react");
|
|
7027
|
+
|
|
7028
|
+
// src/presetGrid.ts
|
|
7029
|
+
var DEFAULT_SINGLES_LIMITS = [1.3, 1.5, 1.7, 2];
|
|
7030
|
+
var DEFAULT_SINGLES_FLOORS = [1.5, 2, 2.5, 3];
|
|
7031
|
+
var DEFAULT_COMBO_TOTALS = [3, 5, 10, 20, 50];
|
|
7032
|
+
var FIXED_SINGLES_COUNT = 3;
|
|
7033
|
+
var MIN_COMBO_LEGS = 2;
|
|
7034
|
+
var MAX_COMBO_LEGS = 6;
|
|
7035
|
+
var START = { maxPrice: 1.5, minPrice: 2, comboTotal: 5, comboLegs: 3 };
|
|
7036
|
+
var finite2 = (v) => typeof v === "number" && Number.isFinite(v);
|
|
7037
|
+
var cents = (n) => Math.round(n * 100);
|
|
7038
|
+
function samePrice(a, b) {
|
|
7039
|
+
return cents(a) === cents(b);
|
|
7040
|
+
}
|
|
7041
|
+
function isPresetGrid(v) {
|
|
7042
|
+
if (!v || typeof v !== "object") return false;
|
|
7043
|
+
const g = v;
|
|
7044
|
+
return Array.isArray(g.singles) && Array.isArray(g.singlesFrom) && Array.isArray(g.combos) && finite2(g.maxLegs);
|
|
7045
|
+
}
|
|
7046
|
+
function drawable(p) {
|
|
7047
|
+
return !!p && finite2(p.price) && finite2(p.matchOddsId) && finite2(p.matchId) && typeof p.homeTeamName === "string" && typeof p.awayTeamName === "string";
|
|
7048
|
+
}
|
|
7049
|
+
function choices(defaults, present) {
|
|
7050
|
+
const out = [];
|
|
7051
|
+
for (const n of [...defaults, ...present]) {
|
|
7052
|
+
if (finite2(n) && n > 0 && !out.some((m) => samePrice(m, n))) out.push(n);
|
|
7053
|
+
}
|
|
7054
|
+
return out.sort((a, b) => a - b);
|
|
7055
|
+
}
|
|
7056
|
+
function singlesLimits(grid) {
|
|
7057
|
+
return choices(DEFAULT_SINGLES_LIMITS, grid.singles.map((c) => c?.price));
|
|
7058
|
+
}
|
|
7059
|
+
function singlesFloors(grid) {
|
|
7060
|
+
return choices(DEFAULT_SINGLES_FLOORS, grid.singlesFrom.map((c) => c?.price));
|
|
7061
|
+
}
|
|
7062
|
+
function comboTotals(grid) {
|
|
7063
|
+
return choices(DEFAULT_COMBO_TOTALS, grid.combos.map((c) => c?.total));
|
|
7064
|
+
}
|
|
7065
|
+
function comboLegChoices(grid) {
|
|
7066
|
+
const max = Math.min(MAX_COMBO_LEGS, Math.floor(grid.maxLegs));
|
|
7067
|
+
const out = [];
|
|
7068
|
+
for (let n = MIN_COMBO_LEGS; n <= max; n++) out.push(n);
|
|
7069
|
+
return out;
|
|
7070
|
+
}
|
|
7071
|
+
function cycle(values, current) {
|
|
7072
|
+
if (values.length === 0) return current;
|
|
7073
|
+
const i = values.findIndex((v) => samePrice(v, current));
|
|
7074
|
+
return values[(i + 1) % values.length] ?? current;
|
|
7075
|
+
}
|
|
7076
|
+
function startValue(values, preferred) {
|
|
7077
|
+
return values.find((v) => samePrice(v, preferred)) ?? values[0] ?? preferred;
|
|
7078
|
+
}
|
|
7079
|
+
function nearest(values, target) {
|
|
7080
|
+
const first = values[0];
|
|
7081
|
+
if (first === void 0) return target;
|
|
7082
|
+
if (!(target > 0)) return first;
|
|
7083
|
+
const distance = (v) => Math.abs(Math.log(v / target));
|
|
7084
|
+
return values.reduce((best, v) => distance(v) < distance(best) ? v : best, first);
|
|
7085
|
+
}
|
|
7086
|
+
function clampLegs(values, legs) {
|
|
7087
|
+
const lo = values[0];
|
|
7088
|
+
const hi = values[values.length - 1];
|
|
7089
|
+
if (lo === void 0 || hi === void 0) return legs;
|
|
7090
|
+
return Math.min(Math.max(Math.round(legs), lo), hi);
|
|
7091
|
+
}
|
|
7092
|
+
function findSingles(cells, price, count) {
|
|
7093
|
+
const cell = cells.find((c) => !!c && finite2(c.price) && samePrice(c.price, price) && c.count === count);
|
|
7094
|
+
return cell && Array.isArray(cell.picks) && cell.picks.length === count && cell.picks.every(drawable) ? cell : null;
|
|
7095
|
+
}
|
|
7096
|
+
function findCombo(grid, total, legs) {
|
|
7097
|
+
const cell = grid.combos.find((c) => !!c && finite2(c.total) && samePrice(c.total, total) && c.legs === legs);
|
|
7098
|
+
const combo = cell?.combo;
|
|
7099
|
+
const whole = !!combo && typeof combo.multiplication === "string" && combo.multiplication !== "" && Array.isArray(combo.legs) && combo.legs.length === legs && combo.legs.every(drawable);
|
|
7100
|
+
return cell && whole ? cell : null;
|
|
7101
|
+
}
|
|
7102
|
+
function idsOf(picks) {
|
|
7103
|
+
return { matchOddsIds: picks.map((p) => p.matchOddsId), matchIds: picks.map((p) => p.matchId) };
|
|
7104
|
+
}
|
|
7105
|
+
function maxPriceAsk(cell, s) {
|
|
7106
|
+
return {
|
|
7107
|
+
message: s.maxPriceMessage(cell.count, cell.price),
|
|
7108
|
+
displayText: s.maxPriceDisplay(cell.count, cell.price),
|
|
7109
|
+
preset: { kind: "max_price", price: cell.price, count: cell.count, ...idsOf(cell.picks) }
|
|
7110
|
+
};
|
|
7111
|
+
}
|
|
7112
|
+
function minPriceAsk(cell, s) {
|
|
7113
|
+
return {
|
|
7114
|
+
message: s.minPriceMessage(cell.count, cell.price),
|
|
7115
|
+
displayText: s.minPriceDisplay(cell.count, cell.price),
|
|
7116
|
+
preset: { kind: "min_price", price: cell.price, count: cell.count, ...idsOf(cell.picks) }
|
|
7117
|
+
};
|
|
7118
|
+
}
|
|
7119
|
+
function comboAsk(cell, s) {
|
|
7120
|
+
return {
|
|
7121
|
+
message: s.comboMessage(cell.legs, cell.total),
|
|
7122
|
+
displayText: s.comboDisplay(cell.legs, cell.total),
|
|
7123
|
+
preset: { kind: "combo", total: cell.total, legs: cell.legs, ...idsOf(cell.combo.legs) }
|
|
7124
|
+
};
|
|
7125
|
+
}
|
|
7126
|
+
|
|
7127
|
+
// src/components/PresetTools.tsx
|
|
7128
|
+
var import_jsx_runtime89 = require("react/jsx-runtime");
|
|
7129
|
+
var FOCUSABLE = 'button:not([disabled]), [href], input:not([disabled]), textarea:not([disabled]), [tabindex]:not([tabindex="-1"])';
|
|
7130
|
+
function PresetToolModal({ title, closeLabel, onClose, children, footer }) {
|
|
7131
|
+
const dialogRef = (0, import_react41.useRef)(null);
|
|
7132
|
+
const titleId = (0, import_react41.useId)();
|
|
7133
|
+
(0, import_react41.useEffect)(() => {
|
|
7134
|
+
dialogRef.current?.focus();
|
|
7135
|
+
}, []);
|
|
7136
|
+
(0, import_react41.useEffect)(() => {
|
|
7137
|
+
const onKey = (e) => {
|
|
7138
|
+
if (e.key !== "Escape") return;
|
|
7139
|
+
e.stopPropagation();
|
|
7140
|
+
e.preventDefault();
|
|
7141
|
+
onClose();
|
|
7142
|
+
};
|
|
7143
|
+
window.addEventListener("keydown", onKey, true);
|
|
7144
|
+
return () => window.removeEventListener("keydown", onKey, true);
|
|
7145
|
+
}, [onClose]);
|
|
7146
|
+
const trapTab = (e) => {
|
|
7147
|
+
if (e.key !== "Tab") return;
|
|
7148
|
+
const dialog = dialogRef.current;
|
|
7149
|
+
if (!dialog) return;
|
|
7150
|
+
const items = Array.from(dialog.querySelectorAll(FOCUSABLE));
|
|
7151
|
+
const first = items[0];
|
|
7152
|
+
const last = items[items.length - 1];
|
|
7153
|
+
if (!first || !last) {
|
|
7154
|
+
e.preventDefault();
|
|
7155
|
+
return;
|
|
7156
|
+
}
|
|
7157
|
+
const active = dialog.getRootNode().activeElement;
|
|
7158
|
+
if (e.shiftKey && (active === first || active === dialog)) {
|
|
7159
|
+
e.preventDefault();
|
|
7160
|
+
last.focus();
|
|
7161
|
+
} else if (!e.shiftKey && active === last) {
|
|
7162
|
+
e.preventDefault();
|
|
7163
|
+
first.focus();
|
|
7164
|
+
}
|
|
7165
|
+
};
|
|
7166
|
+
return /* @__PURE__ */ (0, import_jsx_runtime89.jsx)("div", { className: "ss-w-tool-backdrop", children: /* @__PURE__ */ (0, import_jsx_runtime89.jsxs)("div", { ref: dialogRef, className: "ss-w-tool", role: "dialog", "aria-modal": "true", "aria-labelledby": titleId, tabIndex: -1, onKeyDown: trapTab, children: [
|
|
7167
|
+
/* @__PURE__ */ (0, import_jsx_runtime89.jsxs)("div", { className: "ss-w-tool-head", children: [
|
|
7168
|
+
/* @__PURE__ */ (0, import_jsx_runtime89.jsx)("h2", { id: titleId, className: "ss-w-tool-title", children: title }),
|
|
7169
|
+
/* @__PURE__ */ (0, import_jsx_runtime89.jsx)("button", { type: "button", className: "ss-w-tool-close", "aria-label": closeLabel, onClick: onClose, children: /* @__PURE__ */ (0, import_jsx_runtime89.jsx)(CloseIcon, {}) })
|
|
7170
|
+
] }),
|
|
7171
|
+
/* @__PURE__ */ (0, import_jsx_runtime89.jsx)("div", { className: "ss-w-tool-body", children }),
|
|
7172
|
+
footer ? /* @__PURE__ */ (0, import_jsx_runtime89.jsx)("div", { className: "ss-w-tool-foot", children: footer }) : null
|
|
7173
|
+
] }) });
|
|
7174
|
+
}
|
|
7175
|
+
function fillTemplate(template, slots) {
|
|
7176
|
+
return template.split(/(\{[a-z]+\})/).filter((part) => part !== "").map((part, i) => {
|
|
7177
|
+
const key = /^\{([a-z]+)\}$/.exec(part)?.[1];
|
|
7178
|
+
return /* @__PURE__ */ (0, import_jsx_runtime89.jsx)(import_react41.Fragment, { children: key !== void 0 && key in slots ? slots[key] : part }, i);
|
|
7179
|
+
});
|
|
7180
|
+
}
|
|
7181
|
+
function NumberButton({ value, label, onCycle }) {
|
|
7182
|
+
return /* @__PURE__ */ (0, import_jsx_runtime89.jsx)("button", { type: "button", className: "ss-w-tool-num", "aria-label": label, onClick: onCycle, children: value });
|
|
7183
|
+
}
|
|
7184
|
+
function SendButton({ label, display, disabled, onClick }) {
|
|
7185
|
+
return /* @__PURE__ */ (0, import_jsx_runtime89.jsx)("button", { type: "button", className: "ss-w-tool-send", "aria-label": `${label}: ${display}`, disabled, onClick, children: label });
|
|
7186
|
+
}
|
|
7187
|
+
function PickList({ picks, formatKickoff }) {
|
|
7188
|
+
return /* @__PURE__ */ (0, import_jsx_runtime89.jsx)("ul", { className: "ss-w-tool-picks", role: "list", children: picks.map((p) => /* @__PURE__ */ (0, import_jsx_runtime89.jsxs)("li", { className: "ss-w-tool-pick", children: [
|
|
7189
|
+
/* @__PURE__ */ (0, import_jsx_runtime89.jsxs)("span", { className: "ss-w-tool-pick-match", children: [
|
|
7190
|
+
p.homeTeamName,
|
|
7191
|
+
" \u2013 ",
|
|
7192
|
+
p.awayTeamName
|
|
7193
|
+
] }),
|
|
7194
|
+
/* @__PURE__ */ (0, import_jsx_runtime89.jsxs)("span", { className: "ss-w-tool-pick-sel", children: [
|
|
7195
|
+
p.marketLabel,
|
|
7196
|
+
": ",
|
|
7197
|
+
p.selectionLabel,
|
|
7198
|
+
p.line ? ` ${p.line}` : ""
|
|
7199
|
+
] }),
|
|
7200
|
+
/* @__PURE__ */ (0, import_jsx_runtime89.jsx)("span", { className: "ss-w-tool-pick-kick", children: formatKickoff(p.kickoffUtc) }),
|
|
7201
|
+
/* @__PURE__ */ (0, import_jsx_runtime89.jsx)("span", { className: "ss-w-tool-pick-price", children: p.price.toFixed(2) })
|
|
7202
|
+
] }, p.matchOddsId)) });
|
|
7203
|
+
}
|
|
7204
|
+
function ComboPreview({ cell, formatKickoff }) {
|
|
7205
|
+
return /* @__PURE__ */ (0, import_jsx_runtime89.jsxs)(import_jsx_runtime89.Fragment, { children: [
|
|
7206
|
+
/* @__PURE__ */ (0, import_jsx_runtime89.jsx)(PickList, { picks: cell.combo.legs, formatKickoff }),
|
|
7207
|
+
/* @__PURE__ */ (0, import_jsx_runtime89.jsx)("div", { className: "ss-w-tool-math", children: cell.combo.multiplication })
|
|
7208
|
+
] });
|
|
7209
|
+
}
|
|
7210
|
+
function Unavailable({ text }) {
|
|
7211
|
+
return /* @__PURE__ */ (0, import_jsx_runtime89.jsx)("p", { className: "ss-w-tool-unavailable", children: text });
|
|
7212
|
+
}
|
|
7213
|
+
function SetNumbersTool({ grid, strings, streaming, formatKickoff, onSend, onClose }) {
|
|
7214
|
+
const limits = singlesLimits(grid);
|
|
7215
|
+
const floors = singlesFloors(grid);
|
|
7216
|
+
const totals = comboTotals(grid);
|
|
7217
|
+
const legChoices = comboLegChoices(grid);
|
|
7218
|
+
const count = FIXED_SINGLES_COUNT;
|
|
7219
|
+
const [maxPrice, setMaxPrice] = (0, import_react41.useState)(() => startValue(limits, START.maxPrice));
|
|
7220
|
+
const [minPrice, setMinPrice] = (0, import_react41.useState)(() => startValue(floors, START.minPrice));
|
|
7221
|
+
const [total, setTotal] = (0, import_react41.useState)(() => startValue(totals, START.comboTotal));
|
|
7222
|
+
const [legs, setLegs] = (0, import_react41.useState)(() => startValue(legChoices, START.comboLegs));
|
|
7223
|
+
const [announcement, setAnnouncement] = (0, import_react41.useState)("");
|
|
7224
|
+
const maxCell = findSingles(grid.singles, maxPrice, count);
|
|
7225
|
+
const minCell = findSingles(grid.singlesFrom, minPrice, count);
|
|
7226
|
+
const comboCell = findCombo(grid, total, legs);
|
|
7227
|
+
const announce = (display, available) => setAnnouncement(available ? display : `${display}. ${strings.unavailable}`);
|
|
7228
|
+
const nextMax = () => {
|
|
7229
|
+
const v = cycle(limits, maxPrice);
|
|
7230
|
+
setMaxPrice(v);
|
|
7231
|
+
announce(strings.maxPriceDisplay(count, v), !!findSingles(grid.singles, v, count));
|
|
7232
|
+
};
|
|
7233
|
+
const nextMin = () => {
|
|
7234
|
+
const v = cycle(floors, minPrice);
|
|
7235
|
+
setMinPrice(v);
|
|
7236
|
+
announce(strings.minPriceDisplay(count, v), !!findSingles(grid.singlesFrom, v, count));
|
|
7237
|
+
};
|
|
7238
|
+
const nextLegs = () => {
|
|
7239
|
+
const v = cycle(legChoices, legs);
|
|
7240
|
+
setLegs(v);
|
|
7241
|
+
announce(strings.comboDisplay(v, total), !!findCombo(grid, total, v));
|
|
7242
|
+
};
|
|
7243
|
+
const nextTotal = () => {
|
|
7244
|
+
const v = cycle(totals, total);
|
|
7245
|
+
setTotal(v);
|
|
7246
|
+
announce(strings.comboDisplay(legs, v), !!findCombo(grid, v, legs));
|
|
7247
|
+
};
|
|
7248
|
+
return /* @__PURE__ */ (0, import_jsx_runtime89.jsxs)(PresetToolModal, { title: strings.setNumbers, closeLabel: strings.close, onClose, children: [
|
|
7249
|
+
/* @__PURE__ */ (0, import_jsx_runtime89.jsxs)("section", { className: "ss-w-tool-row", children: [
|
|
7250
|
+
/* @__PURE__ */ (0, import_jsx_runtime89.jsx)("p", { className: "ss-w-tool-ask", children: fillTemplate(strings.maxPriceRow, {
|
|
7251
|
+
count: String(count),
|
|
7252
|
+
price: /* @__PURE__ */ (0, import_jsx_runtime89.jsx)(NumberButton, { value: maxPrice.toFixed(2), label: strings.changeNumber(maxPrice.toFixed(2)), onCycle: nextMax })
|
|
7253
|
+
}) }),
|
|
7254
|
+
maxCell ? /* @__PURE__ */ (0, import_jsx_runtime89.jsx)(PickList, { picks: maxCell.picks, formatKickoff }) : /* @__PURE__ */ (0, import_jsx_runtime89.jsx)(Unavailable, { text: strings.unavailable }),
|
|
7255
|
+
/* @__PURE__ */ (0, import_jsx_runtime89.jsx)(SendButton, { label: strings.send, display: strings.maxPriceDisplay(count, maxPrice), disabled: !maxCell || streaming, onClick: () => {
|
|
7256
|
+
if (maxCell) onSend(maxPriceAsk(maxCell, strings));
|
|
7257
|
+
} })
|
|
7258
|
+
] }),
|
|
7259
|
+
/* @__PURE__ */ (0, import_jsx_runtime89.jsxs)("section", { className: "ss-w-tool-row", children: [
|
|
7260
|
+
/* @__PURE__ */ (0, import_jsx_runtime89.jsx)("p", { className: "ss-w-tool-ask", children: fillTemplate(strings.minPriceRow, {
|
|
7261
|
+
count: String(count),
|
|
7262
|
+
price: /* @__PURE__ */ (0, import_jsx_runtime89.jsx)(NumberButton, { value: minPrice.toFixed(2), label: strings.changeNumber(minPrice.toFixed(2)), onCycle: nextMin })
|
|
7263
|
+
}) }),
|
|
7264
|
+
minCell ? /* @__PURE__ */ (0, import_jsx_runtime89.jsx)(PickList, { picks: minCell.picks, formatKickoff }) : /* @__PURE__ */ (0, import_jsx_runtime89.jsx)(Unavailable, { text: strings.unavailable }),
|
|
7265
|
+
/* @__PURE__ */ (0, import_jsx_runtime89.jsx)(SendButton, { label: strings.send, display: strings.minPriceDisplay(count, minPrice), disabled: !minCell || streaming, onClick: () => {
|
|
7266
|
+
if (minCell) onSend(minPriceAsk(minCell, strings));
|
|
7267
|
+
} })
|
|
7268
|
+
] }),
|
|
7269
|
+
/* @__PURE__ */ (0, import_jsx_runtime89.jsxs)("section", { className: "ss-w-tool-row", children: [
|
|
7270
|
+
/* @__PURE__ */ (0, import_jsx_runtime89.jsx)("p", { className: "ss-w-tool-ask", children: fillTemplate(strings.comboRow, {
|
|
7271
|
+
legs: /* @__PURE__ */ (0, import_jsx_runtime89.jsx)(NumberButton, { value: String(legs), label: strings.changeNumber(String(legs)), onCycle: nextLegs }),
|
|
7272
|
+
total: /* @__PURE__ */ (0, import_jsx_runtime89.jsx)(NumberButton, { value: total.toFixed(1), label: strings.changeNumber(total.toFixed(1)), onCycle: nextTotal })
|
|
7273
|
+
}) }),
|
|
7274
|
+
comboCell ? /* @__PURE__ */ (0, import_jsx_runtime89.jsx)(ComboPreview, { cell: comboCell, formatKickoff }) : /* @__PURE__ */ (0, import_jsx_runtime89.jsx)(Unavailable, { text: strings.unavailable }),
|
|
7275
|
+
/* @__PURE__ */ (0, import_jsx_runtime89.jsx)(SendButton, { label: strings.send, display: strings.comboDisplay(legs, total), disabled: !comboCell || streaming, onClick: () => {
|
|
7276
|
+
if (comboCell) onSend(comboAsk(comboCell, strings));
|
|
7277
|
+
} })
|
|
7278
|
+
] }),
|
|
7279
|
+
/* @__PURE__ */ (0, import_jsx_runtime89.jsx)("p", { className: "ss-w-vh", "aria-live": "polite", children: announcement })
|
|
7280
|
+
] });
|
|
7281
|
+
}
|
|
7282
|
+
function BuildComboTool({ grid, strings, streaming, formatKickoff, initialTotal, initialLegs, onSend, onClose }) {
|
|
7283
|
+
const totals = comboTotals(grid);
|
|
7284
|
+
const legChoices = comboLegChoices(grid);
|
|
7285
|
+
const [total, setTotal] = (0, import_react41.useState)(() => initialTotal !== void 0 ? nearest(totals, initialTotal) : startValue(totals, START.comboTotal));
|
|
7286
|
+
const [legs, setLegs] = (0, import_react41.useState)(() => initialLegs !== void 0 ? clampLegs(legChoices, initialLegs) : startValue(legChoices, START.comboLegs));
|
|
7287
|
+
const [announcement, setAnnouncement] = (0, import_react41.useState)("");
|
|
7288
|
+
const cell = findCombo(grid, total, legs);
|
|
7289
|
+
const minLegs = legChoices[0] ?? legs;
|
|
7290
|
+
const maxLegs = legChoices[legChoices.length - 1] ?? legs;
|
|
7291
|
+
const display = strings.comboDisplay(legs, total);
|
|
7292
|
+
const choose = (nextTotal, nextLegs) => {
|
|
7293
|
+
setTotal(nextTotal);
|
|
7294
|
+
setLegs(nextLegs);
|
|
7295
|
+
const d = strings.comboDisplay(nextLegs, nextTotal);
|
|
7296
|
+
setAnnouncement(findCombo(grid, nextTotal, nextLegs) ? d : `${d}. ${strings.unavailable}`);
|
|
7297
|
+
};
|
|
7298
|
+
const footer = /* @__PURE__ */ (0, import_jsx_runtime89.jsx)(SendButton, { label: strings.send, display, disabled: !cell || streaming, onClick: () => {
|
|
7299
|
+
if (cell) onSend(comboAsk(cell, strings));
|
|
7300
|
+
} });
|
|
7301
|
+
return /* @__PURE__ */ (0, import_jsx_runtime89.jsxs)(PresetToolModal, { title: strings.buildCombo, closeLabel: strings.close, onClose, footer, children: [
|
|
7302
|
+
/* @__PURE__ */ (0, import_jsx_runtime89.jsxs)("div", { className: "ss-w-tool-section", role: "group", "aria-label": strings.totalLabel, children: [
|
|
7303
|
+
/* @__PURE__ */ (0, import_jsx_runtime89.jsx)("span", { className: "ss-w-tool-label", "aria-hidden": "true", children: strings.totalLabel }),
|
|
7304
|
+
/* @__PURE__ */ (0, import_jsx_runtime89.jsx)("div", { className: "ss-w-tool-chips", children: totals.map((t) => /* @__PURE__ */ (0, import_jsx_runtime89.jsx)("button", { type: "button", className: "ss-w-tool-chip", "aria-pressed": samePrice(t, total), onClick: () => choose(t, legs), children: t.toFixed(1) }, t)) })
|
|
7305
|
+
] }),
|
|
7306
|
+
/* @__PURE__ */ (0, import_jsx_runtime89.jsxs)("div", { className: "ss-w-tool-section", role: "group", "aria-label": strings.matchesLabel, children: [
|
|
7307
|
+
/* @__PURE__ */ (0, import_jsx_runtime89.jsx)("span", { className: "ss-w-tool-label", "aria-hidden": "true", children: strings.matchesLabel }),
|
|
7308
|
+
/* @__PURE__ */ (0, import_jsx_runtime89.jsxs)("div", { className: "ss-w-tool-stepper", children: [
|
|
7309
|
+
/* @__PURE__ */ (0, import_jsx_runtime89.jsx)("button", { type: "button", className: "ss-w-tool-step", "aria-label": strings.fewerMatches, disabled: legs <= minLegs, onClick: () => choose(total, legs - 1), children: "\u2212" }),
|
|
7310
|
+
/* @__PURE__ */ (0, import_jsx_runtime89.jsx)("span", { className: "ss-w-tool-count", children: legs }),
|
|
7311
|
+
/* @__PURE__ */ (0, import_jsx_runtime89.jsx)("button", { type: "button", className: "ss-w-tool-step", "aria-label": strings.moreMatches, disabled: legs >= maxLegs, onClick: () => choose(total, legs + 1), children: "+" })
|
|
7312
|
+
] })
|
|
7313
|
+
] }),
|
|
7314
|
+
cell ? /* @__PURE__ */ (0, import_jsx_runtime89.jsx)(ComboPreview, { cell, formatKickoff }) : /* @__PURE__ */ (0, import_jsx_runtime89.jsx)(Unavailable, { text: strings.unavailable }),
|
|
7315
|
+
/* @__PURE__ */ (0, import_jsx_runtime89.jsx)("p", { className: "ss-w-vh", "aria-live": "polite", children: announcement })
|
|
7316
|
+
] });
|
|
7317
|
+
}
|
|
7318
|
+
|
|
7319
|
+
// src/usePresetDial.ts
|
|
7320
|
+
var import_react42 = require("react");
|
|
7321
|
+
function sendInFlight(messages, isStreaming) {
|
|
7322
|
+
const last = messages[messages.length - 1];
|
|
7323
|
+
return isStreaming && !!last && last.role === "assistant" && !last.done && !last.isGreeting;
|
|
7324
|
+
}
|
|
7325
|
+
function lastUserId(messages) {
|
|
7326
|
+
for (let i = messages.length - 1; i >= 0; i--) {
|
|
7327
|
+
const m = messages[i];
|
|
7328
|
+
if (m && m.role === "user") return m.id;
|
|
7329
|
+
}
|
|
7330
|
+
return null;
|
|
7331
|
+
}
|
|
7332
|
+
var SCROLL_KEYS = /* @__PURE__ */ new Set(["ArrowUp", "ArrowDown", "PageUp", "PageDown", "Home", "End", " "]);
|
|
7333
|
+
function usePresetDial({ layerRef, dialRef, dialButtonRef, messages, isStreaming, activeTab }) {
|
|
7334
|
+
const [expanded, setExpanded] = (0, import_react42.useState)(() => !sendInFlight(messages, isStreaming));
|
|
7335
|
+
const fold = (0, import_react42.useCallback)(() => setExpanded(false), []);
|
|
7336
|
+
const toggle = (0, import_react42.useCallback)(() => setExpanded((e) => !e), []);
|
|
7337
|
+
const currentUserId = lastUserId(messages);
|
|
7338
|
+
const userIdRef = (0, import_react42.useRef)(currentUserId);
|
|
7339
|
+
(0, import_react42.useEffect)(() => {
|
|
7340
|
+
if (currentUserId === userIdRef.current) return;
|
|
7341
|
+
userIdRef.current = currentUserId;
|
|
7342
|
+
setExpanded(false);
|
|
7343
|
+
}, [currentUserId]);
|
|
7344
|
+
const tabRef = (0, import_react42.useRef)(activeTab);
|
|
7345
|
+
(0, import_react42.useEffect)(() => {
|
|
7346
|
+
if (activeTab === tabRef.current) return;
|
|
7347
|
+
tabRef.current = activeTab;
|
|
7348
|
+
setExpanded(false);
|
|
7349
|
+
}, [activeTab]);
|
|
7350
|
+
const expandedRef = (0, import_react42.useRef)(expanded);
|
|
7351
|
+
expandedRef.current = expanded;
|
|
7352
|
+
(0, import_react42.useEffect)(() => {
|
|
7353
|
+
const onEscape = (e) => {
|
|
7354
|
+
const dial = dialRef.current;
|
|
7355
|
+
if (e.key !== "Escape" || !dial || !e.composedPath().includes(dial)) return;
|
|
7356
|
+
e.stopPropagation();
|
|
7357
|
+
e.preventDefault();
|
|
7358
|
+
if (!expandedRef.current) return;
|
|
7359
|
+
setExpanded(false);
|
|
7360
|
+
dialButtonRef.current?.focus();
|
|
7361
|
+
};
|
|
7362
|
+
window.addEventListener("keydown", onEscape, true);
|
|
7363
|
+
return () => window.removeEventListener("keydown", onEscape, true);
|
|
7364
|
+
}, [dialRef, dialButtonRef]);
|
|
7365
|
+
(0, import_react42.useEffect)(() => {
|
|
7366
|
+
if (!expanded) return;
|
|
7367
|
+
const layer = layerRef.current;
|
|
7368
|
+
const inDial = (e) => {
|
|
7369
|
+
const dial = dialRef.current;
|
|
7370
|
+
return !!dial && e.composedPath().includes(dial);
|
|
7371
|
+
};
|
|
7372
|
+
const inList = (e) => e.composedPath().some((n) => n instanceof Element && n.classList.contains("ss-w-list"));
|
|
7373
|
+
const onPress = (e) => {
|
|
7374
|
+
if (!inDial(e)) setExpanded(false);
|
|
7375
|
+
};
|
|
7376
|
+
const onScrollGesture = (e) => {
|
|
7377
|
+
if (inList(e) && !inDial(e)) setExpanded(false);
|
|
7378
|
+
};
|
|
7379
|
+
const onKey = (e) => {
|
|
7380
|
+
if (SCROLL_KEYS.has(e.key) && inList(e)) setExpanded(false);
|
|
7381
|
+
};
|
|
7382
|
+
const doc = layer?.ownerDocument ?? document;
|
|
7383
|
+
doc.addEventListener("pointerdown", onPress, true);
|
|
7384
|
+
layer?.addEventListener("wheel", onScrollGesture, { passive: true });
|
|
7385
|
+
layer?.addEventListener("touchmove", onScrollGesture, { passive: true });
|
|
7386
|
+
window.addEventListener("keydown", onKey, true);
|
|
7387
|
+
return () => {
|
|
7388
|
+
doc.removeEventListener("pointerdown", onPress, true);
|
|
7389
|
+
layer?.removeEventListener("wheel", onScrollGesture);
|
|
7390
|
+
layer?.removeEventListener("touchmove", onScrollGesture);
|
|
7391
|
+
window.removeEventListener("keydown", onKey, true);
|
|
7392
|
+
};
|
|
7393
|
+
}, [expanded, layerRef, dialRef]);
|
|
7394
|
+
return { expanded, toggle, fold };
|
|
7395
|
+
}
|
|
7396
|
+
|
|
7397
|
+
// src/presetSeen.ts
|
|
7398
|
+
var KEY = "ss-w-presets-tool-opened";
|
|
7399
|
+
var openedThisPage = false;
|
|
7400
|
+
function readToolSeen() {
|
|
7401
|
+
if (openedThisPage) return true;
|
|
7402
|
+
try {
|
|
7403
|
+
return window.localStorage.getItem(KEY) === "1";
|
|
7404
|
+
} catch {
|
|
7405
|
+
return false;
|
|
7406
|
+
}
|
|
7407
|
+
}
|
|
7408
|
+
function markToolSeen() {
|
|
7409
|
+
openedThisPage = true;
|
|
7410
|
+
try {
|
|
7411
|
+
window.localStorage.setItem(KEY, "1");
|
|
7412
|
+
} catch {
|
|
7413
|
+
}
|
|
7414
|
+
}
|
|
7415
|
+
|
|
7416
|
+
// src/components/ChatPanel.tsx
|
|
7417
|
+
var import_jsx_runtime90 = require("react/jsx-runtime");
|
|
6874
7418
|
var FULLSCREEN_QUERY = "(max-width: 639px)";
|
|
6875
7419
|
function useIsModal() {
|
|
6876
|
-
const [modal, setModal] = (0,
|
|
6877
|
-
(0,
|
|
7420
|
+
const [modal, setModal] = (0, import_react43.useState)(false);
|
|
7421
|
+
(0, import_react43.useEffect)(() => {
|
|
6878
7422
|
if (typeof window === "undefined" || typeof window.matchMedia !== "function") return;
|
|
6879
7423
|
const mql = window.matchMedia(FULLSCREEN_QUERY);
|
|
6880
7424
|
const sync = () => setModal(mql.matches);
|
|
@@ -6884,10 +7428,11 @@ function useIsModal() {
|
|
|
6884
7428
|
}, []);
|
|
6885
7429
|
return modal;
|
|
6886
7430
|
}
|
|
6887
|
-
|
|
7431
|
+
var HIDDEN_LAYER_STYLE = { position: "absolute", inset: 0, visibility: "hidden", display: "flex", flexDirection: "column" };
|
|
7432
|
+
function ChatPanel({ controller, ui, slip, onDashboardTap, monitor, discovery, gamesResult, leaderboardResult, loggedIn, conversation, presetGrid }) {
|
|
6888
7433
|
const { state, close, stop, send, reset } = controller;
|
|
6889
7434
|
const isModal = useIsModal();
|
|
6890
|
-
(0,
|
|
7435
|
+
(0, import_react43.useEffect)(() => {
|
|
6891
7436
|
const onKey = (e) => {
|
|
6892
7437
|
if (e.key === "Escape") close();
|
|
6893
7438
|
};
|
|
@@ -6902,11 +7447,66 @@ function ChatPanel({ controller, ui, slip, onDashboardTap, monitor, discovery, g
|
|
|
6902
7447
|
monitor?.setActiveTab("chat");
|
|
6903
7448
|
send(text, { grounding });
|
|
6904
7449
|
};
|
|
6905
|
-
(0,
|
|
7450
|
+
(0, import_react43.useEffect)(() => {
|
|
6906
7451
|
if (monitor && monitor.activeTab !== "chat" && state.isStreaming) monitor.markChatActivity();
|
|
6907
7452
|
}, [monitor, state.isStreaming, state.messages.length]);
|
|
6908
|
-
|
|
6909
|
-
|
|
7453
|
+
const onChatTab = !monitor || monitor.activeTab === "chat";
|
|
7454
|
+
const layerRef = (0, import_react43.useRef)(null);
|
|
7455
|
+
const dialRef = (0, import_react43.useRef)(null);
|
|
7456
|
+
const dialButtonRef = (0, import_react43.useRef)(null);
|
|
7457
|
+
const inputRef = (0, import_react43.useRef)(null);
|
|
7458
|
+
const focusAfterCloseRef = (0, import_react43.useRef)(null);
|
|
7459
|
+
const [tool, setTool] = (0, import_react43.useState)(null);
|
|
7460
|
+
const [toolSeen, setToolSeen] = (0, import_react43.useState)(readToolSeen);
|
|
7461
|
+
const dial = usePresetDial({
|
|
7462
|
+
layerRef,
|
|
7463
|
+
dialRef,
|
|
7464
|
+
dialButtonRef,
|
|
7465
|
+
messages: state.messages,
|
|
7466
|
+
isStreaming: state.isStreaming,
|
|
7467
|
+
activeTab: monitor?.activeTab ?? "chat"
|
|
7468
|
+
});
|
|
7469
|
+
const { fold } = dial;
|
|
7470
|
+
const showDial = !!presetGrid && onChatTab && !tool;
|
|
7471
|
+
const openTool = (0, import_react43.useCallback)((next) => {
|
|
7472
|
+
fold();
|
|
7473
|
+
markToolSeen();
|
|
7474
|
+
setToolSeen(true);
|
|
7475
|
+
setTool(next);
|
|
7476
|
+
}, [fold]);
|
|
7477
|
+
const onPill = (0, import_react43.useCallback)((kind) => openTool(kind === "numbers" ? { kind: "numbers" } : { kind: "combo" }), [openTool]);
|
|
7478
|
+
const toolsValue = (0, import_react43.useMemo)(
|
|
7479
|
+
() => presetGrid ? { openBuilder: (total, legs) => openTool({ kind: "combo", total, legs }), changeLabel: ui.presets.changeCombo } : null,
|
|
7480
|
+
[presetGrid, openTool, ui.presets.changeCombo]
|
|
7481
|
+
);
|
|
7482
|
+
const closeTool = (0, import_react43.useCallback)(() => {
|
|
7483
|
+
focusAfterCloseRef.current = "dial";
|
|
7484
|
+
setTool(null);
|
|
7485
|
+
}, []);
|
|
7486
|
+
const sendAsk = (0, import_react43.useCallback)((ask) => {
|
|
7487
|
+
focusAfterCloseRef.current = "composer";
|
|
7488
|
+
setTool(null);
|
|
7489
|
+
monitor?.setActiveTab("chat");
|
|
7490
|
+
send(ask.message, { displayText: ask.displayText, preset: ask.preset });
|
|
7491
|
+
}, [monitor, send]);
|
|
7492
|
+
const formatKickoff = (0, import_react43.useCallback)((kickoffUtc) => {
|
|
7493
|
+
const k = ui.bettingKickoff(kickoffUtc);
|
|
7494
|
+
return `${k.dayLabel} ${k.time}`;
|
|
7495
|
+
}, [ui]);
|
|
7496
|
+
(0, import_react43.useEffect)(() => {
|
|
7497
|
+
if (tool) return;
|
|
7498
|
+
const target = focusAfterCloseRef.current;
|
|
7499
|
+
focusAfterCloseRef.current = null;
|
|
7500
|
+
if (target === "dial") dialButtonRef.current?.focus();
|
|
7501
|
+
else if (target === "composer") inputRef.current?.focus();
|
|
7502
|
+
}, [tool]);
|
|
7503
|
+
(0, import_react43.useEffect)(() => {
|
|
7504
|
+
if (!tool || onChatTab && presetGrid) return;
|
|
7505
|
+
focusAfterCloseRef.current = "composer";
|
|
7506
|
+
setTool(null);
|
|
7507
|
+
}, [tool, onChatTab, presetGrid]);
|
|
7508
|
+
return /* @__PURE__ */ (0, import_jsx_runtime90.jsxs)("div", { className: "ss-w-panel", role: "dialog", "aria-modal": isModal, "aria-label": ui.chatDialogLabel, children: [
|
|
7509
|
+
/* @__PURE__ */ (0, import_jsx_runtime90.jsx)(
|
|
6910
7510
|
ChatHeader,
|
|
6911
7511
|
{
|
|
6912
7512
|
onClose: close,
|
|
@@ -6919,7 +7519,7 @@ function ChatPanel({ controller, ui, slip, onDashboardTap, monitor, discovery, g
|
|
|
6919
7519
|
moreOptionsLabel: ui.moreOptions
|
|
6920
7520
|
}
|
|
6921
7521
|
),
|
|
6922
|
-
monitor && /* @__PURE__ */ (0,
|
|
7522
|
+
monitor && /* @__PURE__ */ (0, import_jsx_runtime90.jsx)(
|
|
6923
7523
|
TabBar,
|
|
6924
7524
|
{
|
|
6925
7525
|
monitor,
|
|
@@ -6931,17 +7531,26 @@ function ChatPanel({ controller, ui, slip, onDashboardTap, monitor, discovery, g
|
|
|
6931
7531
|
gamesEnabled: gamesResult?.enabled ?? false
|
|
6932
7532
|
}
|
|
6933
7533
|
),
|
|
6934
|
-
/* @__PURE__ */ (0,
|
|
6935
|
-
|
|
6936
|
-
|
|
6937
|
-
|
|
6938
|
-
|
|
6939
|
-
|
|
6940
|
-
|
|
6941
|
-
|
|
6942
|
-
|
|
6943
|
-
|
|
6944
|
-
|
|
7534
|
+
/* @__PURE__ */ (0, import_jsx_runtime90.jsxs)("div", { ref: layerRef, className: "ss-w-chat-layer", "data-dial": presetGrid ? "" : void 0, style: onChatTab ? void 0 : HIDDEN_LAYER_STYLE, children: [
|
|
7535
|
+
/* @__PURE__ */ (0, import_jsx_runtime90.jsx)(PresetToolsContext.Provider, { value: toolsValue, children: /* @__PURE__ */ (0, import_jsx_runtime90.jsx)(MessageList, { controller, ui, slip, onDashboardTap, monitor }) }),
|
|
7536
|
+
showDial ? /* @__PURE__ */ (0, import_jsx_runtime90.jsx)(
|
|
7537
|
+
PresetSpeedDial,
|
|
7538
|
+
{
|
|
7539
|
+
expanded: dial.expanded,
|
|
7540
|
+
showDot: !toolSeen,
|
|
7541
|
+
strings: ui.presets,
|
|
7542
|
+
onToggle: dial.toggle,
|
|
7543
|
+
onOpen: onPill,
|
|
7544
|
+
rootRef: dialRef,
|
|
7545
|
+
buttonRef: dialButtonRef
|
|
7546
|
+
}
|
|
7547
|
+
) : null,
|
|
7548
|
+
tool && presetGrid && onChatTab ? tool.kind === "numbers" ? /* @__PURE__ */ (0, import_jsx_runtime90.jsx)(SetNumbersTool, { grid: presetGrid, strings: ui.presets, streaming: state.isStreaming, formatKickoff, onSend: sendAsk, onClose: closeTool }) : /* @__PURE__ */ (0, import_jsx_runtime90.jsx)(BuildComboTool, { grid: presetGrid, strings: ui.presets, streaming: state.isStreaming, formatKickoff, initialTotal: tool.total, initialLegs: tool.legs, onSend: sendAsk, onClose: closeTool }) : null
|
|
7549
|
+
] }),
|
|
7550
|
+
monitor && discovery && monitor.activeTab === "today" && /* @__PURE__ */ (0, import_jsx_runtime90.jsx)(TodayPanel, { discovery, ui, monitor }),
|
|
7551
|
+
monitor && monitor.activeTab === "following" && /* @__PURE__ */ (0, import_jsx_runtime90.jsx)(MatchesPanel, { monitor, ui, slip, onAsk: handleAsk }),
|
|
7552
|
+
monitor && monitor.activeTab === "odds" && /* @__PURE__ */ (0, import_jsx_runtime90.jsx)(OddsPanel, { monitor, ui, slip }),
|
|
7553
|
+
monitor && monitor.activeTab === "games" && gamesResult && /* @__PURE__ */ (0, import_jsx_runtime90.jsx)(
|
|
6945
7554
|
GamesPanel,
|
|
6946
7555
|
{
|
|
6947
7556
|
loggedIn: !!loggedIn,
|
|
@@ -6955,7 +7564,7 @@ function ChatPanel({ controller, ui, slip, onDashboardTap, monitor, discovery, g
|
|
|
6955
7564
|
}
|
|
6956
7565
|
}
|
|
6957
7566
|
),
|
|
6958
|
-
monitor && monitor.activeTab === "leaderboard" && leaderboardResult && /* @__PURE__ */ (0,
|
|
7567
|
+
monitor && monitor.activeTab === "leaderboard" && leaderboardResult && /* @__PURE__ */ (0, import_jsx_runtime90.jsx)(
|
|
6959
7568
|
LeaderboardPanel,
|
|
6960
7569
|
{
|
|
6961
7570
|
loggedIn: !!loggedIn,
|
|
@@ -6965,7 +7574,20 @@ function ChatPanel({ controller, ui, slip, onDashboardTap, monitor, discovery, g
|
|
|
6965
7574
|
ui
|
|
6966
7575
|
}
|
|
6967
7576
|
),
|
|
6968
|
-
(!monitor || monitor.activeTab !== "games" && monitor.activeTab !== "leaderboard") && /* @__PURE__ */ (0,
|
|
7577
|
+
(!monitor || monitor.activeTab !== "games" && monitor.activeTab !== "leaderboard") && /* @__PURE__ */ (0, import_jsx_runtime90.jsx)(
|
|
7578
|
+
ChatInput,
|
|
7579
|
+
{
|
|
7580
|
+
ref: inputRef,
|
|
7581
|
+
placeholder: ui.placeholder,
|
|
7582
|
+
streaming: state.isStreaming,
|
|
7583
|
+
onSend: handleSend,
|
|
7584
|
+
onStop: stop,
|
|
7585
|
+
stopLabel: ui.stopResponse,
|
|
7586
|
+
sendLabel: ui.sendMessage,
|
|
7587
|
+
disabled: !!tool,
|
|
7588
|
+
onType: fold
|
|
7589
|
+
}
|
|
7590
|
+
)
|
|
6969
7591
|
] });
|
|
6970
7592
|
}
|
|
6971
7593
|
|
|
@@ -7174,15 +7796,15 @@ function toSlipEvent(name, entry) {
|
|
|
7174
7796
|
// src/useHostEvents.ts
|
|
7175
7797
|
function useHostEvents(opts) {
|
|
7176
7798
|
const { host, operatorId, currentUser, logger, isOpen, closePanel, onOpenMatch, openMatchLabel } = opts;
|
|
7177
|
-
const [added, setAdded] = (0,
|
|
7178
|
-
const [settled, setSettled] = (0,
|
|
7179
|
-
const markAdded = (0,
|
|
7799
|
+
const [added, setAdded] = (0, import_react44.useState)({});
|
|
7800
|
+
const [settled, setSettled] = (0, import_react44.useState)({});
|
|
7801
|
+
const markAdded = (0, import_react44.useCallback)((matchOddsId) => {
|
|
7180
7802
|
setAdded((prev) => prev[matchOddsId] ? prev : { ...prev, [matchOddsId]: true });
|
|
7181
7803
|
}, []);
|
|
7182
|
-
const settle = (0,
|
|
7804
|
+
const settle = (0, import_react44.useCallback)((matchOddsId) => {
|
|
7183
7805
|
setSettled((prev) => prev[matchOddsId] ? prev : { ...prev, [matchOddsId]: true });
|
|
7184
7806
|
}, []);
|
|
7185
|
-
const clearMarks = (0,
|
|
7807
|
+
const clearMarks = (0, import_react44.useCallback)((ids) => {
|
|
7186
7808
|
const drop = (prev) => {
|
|
7187
7809
|
if (!ids.some((id) => prev[id])) return prev;
|
|
7188
7810
|
const next = { ...prev };
|
|
@@ -7193,26 +7815,26 @@ function useHostEvents(opts) {
|
|
|
7193
7815
|
setSettled(drop);
|
|
7194
7816
|
}, []);
|
|
7195
7817
|
const hasHost = host !== void 0;
|
|
7196
|
-
const ledger = (0,
|
|
7818
|
+
const ledger = (0, import_react44.useMemo)(
|
|
7197
7819
|
() => hasHost ? createSlipLedger(`ss-w-slip:${operatorId}`) : null,
|
|
7198
7820
|
[hasHost, operatorId]
|
|
7199
7821
|
);
|
|
7200
|
-
const currentUserRef = (0,
|
|
7822
|
+
const currentUserRef = (0, import_react44.useRef)(currentUser);
|
|
7201
7823
|
currentUserRef.current = currentUser;
|
|
7202
|
-
const recordAdd = (0,
|
|
7824
|
+
const recordAdd = (0, import_react44.useCallback)((add) => {
|
|
7203
7825
|
ledger?.record({ id: add.selection.matchOddsId, market: add.market, ...add.pick, user: currentUserRef.current });
|
|
7204
7826
|
}, [ledger]);
|
|
7205
|
-
const slipState = (0,
|
|
7827
|
+
const slipState = (0, import_react44.useMemo)(
|
|
7206
7828
|
() => ({ added, settled, settle, recordAdd }),
|
|
7207
7829
|
[added, settled, settle, recordAdd]
|
|
7208
7830
|
);
|
|
7209
7831
|
const hasOpenMatch = typeof onOpenMatch === "function";
|
|
7210
7832
|
const tracksViews = hasHost || hasOpenMatch;
|
|
7211
|
-
const views = (0,
|
|
7833
|
+
const views = (0, import_react44.useMemo)(
|
|
7212
7834
|
() => tracksViews ? createMatchViews(`ss-w-views:${operatorId}`, { user: currentUser }) : null,
|
|
7213
7835
|
[tracksViews, operatorId, currentUser]
|
|
7214
7836
|
);
|
|
7215
|
-
const sinkRef = (0,
|
|
7837
|
+
const sinkRef = (0, import_react44.useRef)(() => {
|
|
7216
7838
|
});
|
|
7217
7839
|
sinkRef.current = (command) => {
|
|
7218
7840
|
if (command.kind === "matchOpened") {
|
|
@@ -7233,11 +7855,11 @@ function useHostEvents(opts) {
|
|
|
7233
7855
|
logger?.log(toSlipEvent(name, entry));
|
|
7234
7856
|
}
|
|
7235
7857
|
};
|
|
7236
|
-
(0,
|
|
7858
|
+
(0, import_react44.useEffect)(() => {
|
|
7237
7859
|
if (!host) return;
|
|
7238
7860
|
return attachHost(host, (command) => sinkRef.current(command));
|
|
7239
7861
|
}, [host]);
|
|
7240
|
-
(0,
|
|
7862
|
+
(0, import_react44.useEffect)(() => {
|
|
7241
7863
|
if (!isOpen || !views) return;
|
|
7242
7864
|
const held = views.markOpened();
|
|
7243
7865
|
if (held.length === 0) return;
|
|
@@ -7246,7 +7868,7 @@ function useHostEvents(opts) {
|
|
|
7246
7868
|
}
|
|
7247
7869
|
void logger?.flushNow();
|
|
7248
7870
|
}, [isOpen, views, logger]);
|
|
7249
|
-
const openMatchRef = (0,
|
|
7871
|
+
const openMatchRef = (0, import_react44.useRef)(() => {
|
|
7250
7872
|
});
|
|
7251
7873
|
openMatchRef.current = (matchId) => {
|
|
7252
7874
|
if (views?.view(matchId) === "log") {
|
|
@@ -7260,7 +7882,7 @@ function useHostEvents(opts) {
|
|
|
7260
7882
|
}
|
|
7261
7883
|
if (typeof window.matchMedia === "function" && window.matchMedia(FULLSCREEN_QUERY).matches) closePanel();
|
|
7262
7884
|
};
|
|
7263
|
-
const matchLink = (0,
|
|
7885
|
+
const matchLink = (0, import_react44.useMemo)(
|
|
7264
7886
|
() => hasOpenMatch ? { open: (matchId) => openMatchRef.current(matchId), label: openMatchLabel } : null,
|
|
7265
7887
|
[hasOpenMatch, openMatchLabel]
|
|
7266
7888
|
);
|
|
@@ -7328,7 +7950,7 @@ function themeAttrs(ui) {
|
|
|
7328
7950
|
}
|
|
7329
7951
|
|
|
7330
7952
|
// src/useChatStream.ts
|
|
7331
|
-
var
|
|
7953
|
+
var import_react46 = require("react");
|
|
7332
7954
|
var import_widget_sdk4 = require("@sensiblestats/widget-sdk");
|
|
7333
7955
|
|
|
7334
7956
|
// src/reducer.ts
|
|
@@ -7488,7 +8110,7 @@ function reducer(state, action) {
|
|
|
7488
8110
|
}
|
|
7489
8111
|
|
|
7490
8112
|
// src/useLiveDashboardPoll.ts
|
|
7491
|
-
var
|
|
8113
|
+
var import_react45 = require("react");
|
|
7492
8114
|
var POLL_INTERVAL_MS = 6e4;
|
|
7493
8115
|
var LEAD_MS = 12e4;
|
|
7494
8116
|
var GIVE_UP_MS = 30 * 6e4;
|
|
@@ -7530,13 +8152,13 @@ function findEarliestWakeupMs(messages) {
|
|
|
7530
8152
|
return earliest;
|
|
7531
8153
|
}
|
|
7532
8154
|
function useLiveDashboardPoll({ messages, dispatch, getLiveDashboard }) {
|
|
7533
|
-
const [, forceUpdate] = (0,
|
|
8155
|
+
const [, forceUpdate] = (0, import_react45.useReducer)((x) => x + 1, 0);
|
|
7534
8156
|
const anchor = findAnchor(messages);
|
|
7535
|
-
const anchorRef = (0,
|
|
8157
|
+
const anchorRef = (0, import_react45.useRef)(anchor);
|
|
7536
8158
|
anchorRef.current = anchor;
|
|
7537
|
-
const inFlightRef = (0,
|
|
8159
|
+
const inFlightRef = (0, import_react45.useRef)(false);
|
|
7538
8160
|
const anchorKey = anchor ? `${anchor.messageId}:${anchor.kind}` : null;
|
|
7539
|
-
(0,
|
|
8161
|
+
(0, import_react45.useEffect)(() => {
|
|
7540
8162
|
if (!anchorKey || !anchorRef.current) return;
|
|
7541
8163
|
const tick = async () => {
|
|
7542
8164
|
if (inFlightRef.current) return;
|
|
@@ -7572,7 +8194,7 @@ function useLiveDashboardPoll({ messages, dispatch, getLiveDashboard }) {
|
|
|
7572
8194
|
}, POLL_INTERVAL_MS);
|
|
7573
8195
|
return () => window.clearInterval(id);
|
|
7574
8196
|
}, [anchorKey, dispatch, getLiveDashboard]);
|
|
7575
|
-
(0,
|
|
8197
|
+
(0, import_react45.useEffect)(() => {
|
|
7576
8198
|
if (anchorKey !== null) return;
|
|
7577
8199
|
const wakeAt = findEarliestWakeupMs(messages);
|
|
7578
8200
|
if (wakeAt === null) return;
|
|
@@ -7637,7 +8259,7 @@ function seedIdCounter(messages) {
|
|
|
7637
8259
|
return max + 1;
|
|
7638
8260
|
}
|
|
7639
8261
|
function useChatStream(conversation, starterTexts, greetingMessage, storageKey) {
|
|
7640
|
-
const boot = (0,
|
|
8262
|
+
const boot = (0, import_react46.useMemo)(() => {
|
|
7641
8263
|
const payload = storageKey ? readConversation(storageKey, Date.now()) : null;
|
|
7642
8264
|
const restored = payload != null && payload !== "expired" && payload !== "corrupt";
|
|
7643
8265
|
const base = initialState(starterTexts.map(chipFromText));
|
|
@@ -7646,27 +8268,27 @@ function useChatStream(conversation, starterTexts, greetingMessage, storageKey)
|
|
|
7646
8268
|
}
|
|
7647
8269
|
return { state: base, restored: false, stale: payload === "expired" || payload === "corrupt" };
|
|
7648
8270
|
}, []);
|
|
7649
|
-
const [state, dispatch] = (0,
|
|
7650
|
-
const idRef = (0,
|
|
7651
|
-
const handleRef = (0,
|
|
7652
|
-
const fallbackTimerRef = (0,
|
|
7653
|
-
const stateRef = (0,
|
|
8271
|
+
const [state, dispatch] = (0, import_react46.useReducer)(reducer, boot.state);
|
|
8272
|
+
const idRef = (0, import_react46.useRef)(seedIdCounter(boot.state.messages));
|
|
8273
|
+
const handleRef = (0, import_react46.useRef)(null);
|
|
8274
|
+
const fallbackTimerRef = (0, import_react46.useRef)(null);
|
|
8275
|
+
const stateRef = (0, import_react46.useRef)(state);
|
|
7654
8276
|
stateRef.current = state;
|
|
7655
|
-
const greetedRef = (0,
|
|
7656
|
-
const hydratedRef = (0,
|
|
7657
|
-
const restoreSeededRef = (0,
|
|
8277
|
+
const greetedRef = (0, import_react46.useRef)(boot.restored);
|
|
8278
|
+
const hydratedRef = (0, import_react46.useRef)(false);
|
|
8279
|
+
const restoreSeededRef = (0, import_react46.useRef)(false);
|
|
7658
8280
|
if (!restoreSeededRef.current) {
|
|
7659
8281
|
restoreSeededRef.current = true;
|
|
7660
8282
|
if (boot.restored && boot.state.conversationId) conversation.conversationId = boot.state.conversationId;
|
|
7661
8283
|
}
|
|
7662
8284
|
const nextId = () => `m${idRef.current++}`;
|
|
7663
|
-
const clearFallbackTimer = (0,
|
|
8285
|
+
const clearFallbackTimer = (0, import_react46.useCallback)(() => {
|
|
7664
8286
|
if (fallbackTimerRef.current != null) {
|
|
7665
8287
|
clearTimeout(fallbackTimerRef.current);
|
|
7666
8288
|
fallbackTimerRef.current = null;
|
|
7667
8289
|
}
|
|
7668
8290
|
}, []);
|
|
7669
|
-
const armFallbackTimer = (0,
|
|
8291
|
+
const armFallbackTimer = (0, import_react46.useCallback)((onFire) => {
|
|
7670
8292
|
clearFallbackTimer();
|
|
7671
8293
|
fallbackTimerRef.current = setTimeout(() => {
|
|
7672
8294
|
fallbackTimerRef.current = null;
|
|
@@ -7675,7 +8297,7 @@ function useChatStream(conversation, starterTexts, greetingMessage, storageKey)
|
|
|
7675
8297
|
onFire();
|
|
7676
8298
|
}, STREAM_FALLBACK_TIMEOUT_MS);
|
|
7677
8299
|
}, [clearFallbackTimer]);
|
|
7678
|
-
const consume = (0,
|
|
8300
|
+
const consume = (0, import_react46.useCallback)((handle, onError) => {
|
|
7679
8301
|
;
|
|
7680
8302
|
(async () => {
|
|
7681
8303
|
try {
|
|
@@ -7693,7 +8315,7 @@ function useChatStream(conversation, starterTexts, greetingMessage, storageKey)
|
|
|
7693
8315
|
}
|
|
7694
8316
|
})();
|
|
7695
8317
|
}, [clearFallbackTimer]);
|
|
7696
|
-
const run = (0,
|
|
8318
|
+
const run = (0, import_react46.useCallback)((input, displayText) => {
|
|
7697
8319
|
handleRef.current?.cancel();
|
|
7698
8320
|
clearFallbackTimer();
|
|
7699
8321
|
dispatch({ kind: "USER_SENT", input, userId: nextId(), assistantId: nextId(), displayText });
|
|
@@ -7705,7 +8327,7 @@ function useChatStream(conversation, starterTexts, greetingMessage, storageKey)
|
|
|
7705
8327
|
});
|
|
7706
8328
|
consume(handle, (err) => dispatch({ kind: "STREAM_ERROR", error: err }));
|
|
7707
8329
|
}, [conversation, consume, clearFallbackTimer, armFallbackTimer]);
|
|
7708
|
-
const send = (0,
|
|
8330
|
+
const send = (0, import_react46.useCallback)((text, opts) => {
|
|
7709
8331
|
const trimmed = text.trim();
|
|
7710
8332
|
if (!trimmed) return;
|
|
7711
8333
|
run({
|
|
@@ -7713,14 +8335,15 @@ function useChatStream(conversation, starterTexts, greetingMessage, storageKey)
|
|
|
7713
8335
|
actionId: opts?.actionId,
|
|
7714
8336
|
actionRef: opts?.actionRef,
|
|
7715
8337
|
grounding: opts?.grounding,
|
|
7716
|
-
clientCapabilities: [...CLIENT_CAPABILITIES]
|
|
8338
|
+
clientCapabilities: [...CLIENT_CAPABILITIES],
|
|
8339
|
+
...opts?.preset ? { preset: opts.preset } : {}
|
|
7717
8340
|
}, opts?.displayText);
|
|
7718
8341
|
}, [run]);
|
|
7719
|
-
const retry = (0,
|
|
8342
|
+
const retry = (0, import_react46.useCallback)(() => {
|
|
7720
8343
|
const last = stateRef.current.lastUserInput;
|
|
7721
8344
|
if (last) run(last, stateRef.current.lastDisplayText ?? void 0);
|
|
7722
8345
|
}, [run]);
|
|
7723
|
-
const fireGreeting = (0,
|
|
8346
|
+
const fireGreeting = (0, import_react46.useCallback)(() => {
|
|
7724
8347
|
const message = greetingMessage?.trim();
|
|
7725
8348
|
if (!message) return;
|
|
7726
8349
|
try {
|
|
@@ -7735,15 +8358,15 @@ function useChatStream(conversation, starterTexts, greetingMessage, storageKey)
|
|
|
7735
8358
|
dispatch({ kind: "STOP" });
|
|
7736
8359
|
}
|
|
7737
8360
|
}, [greetingMessage, conversation, consume, clearFallbackTimer, armFallbackTimer]);
|
|
7738
|
-
(0,
|
|
8361
|
+
(0, import_react46.useEffect)(() => {
|
|
7739
8362
|
if (greetedRef.current) return;
|
|
7740
8363
|
greetedRef.current = true;
|
|
7741
8364
|
fireGreeting();
|
|
7742
8365
|
}, [fireGreeting]);
|
|
7743
|
-
(0,
|
|
8366
|
+
(0, import_react46.useEffect)(() => {
|
|
7744
8367
|
if (boot.stale && storageKey) clearConversation(storageKey);
|
|
7745
8368
|
}, []);
|
|
7746
|
-
(0,
|
|
8369
|
+
(0, import_react46.useEffect)(() => {
|
|
7747
8370
|
if (!storageKey || state.isStreaming) return;
|
|
7748
8371
|
if (hydratedRef.current) {
|
|
7749
8372
|
hydratedRef.current = false;
|
|
@@ -7760,7 +8383,7 @@ function useChatStream(conversation, starterTexts, greetingMessage, storageKey)
|
|
|
7760
8383
|
if (persistable.length === 0) return;
|
|
7761
8384
|
writeConversation(storageKey, state.conversationId, persistable, Date.now());
|
|
7762
8385
|
}, [storageKey, state.isStreaming, state.messages, state.conversationId]);
|
|
7763
|
-
(0,
|
|
8386
|
+
(0, import_react46.useEffect)(() => {
|
|
7764
8387
|
if (!storageKey) return;
|
|
7765
8388
|
const onStorage = (e) => {
|
|
7766
8389
|
if (e.key !== storageKey || e.storageArea !== window.localStorage) return;
|
|
@@ -7784,25 +8407,25 @@ function useChatStream(conversation, starterTexts, greetingMessage, storageKey)
|
|
|
7784
8407
|
window.addEventListener("storage", onStorage);
|
|
7785
8408
|
return () => window.removeEventListener("storage", onStorage);
|
|
7786
8409
|
}, [storageKey, conversation, fireGreeting]);
|
|
7787
|
-
const open = (0,
|
|
7788
|
-
const close = (0,
|
|
8410
|
+
const open = (0, import_react46.useCallback)(() => dispatch({ kind: "OPEN" }), []);
|
|
8411
|
+
const close = (0, import_react46.useCallback)(() => {
|
|
7789
8412
|
handleRef.current?.cancel();
|
|
7790
8413
|
handleRef.current = null;
|
|
7791
8414
|
clearFallbackTimer();
|
|
7792
8415
|
dispatch({ kind: "CLOSE" });
|
|
7793
8416
|
}, [clearFallbackTimer]);
|
|
7794
|
-
const stop = (0,
|
|
8417
|
+
const stop = (0, import_react46.useCallback)(() => {
|
|
7795
8418
|
handleRef.current?.cancel();
|
|
7796
8419
|
handleRef.current = null;
|
|
7797
8420
|
clearFallbackTimer();
|
|
7798
8421
|
dispatch({ kind: "STOP" });
|
|
7799
8422
|
}, [clearFallbackTimer]);
|
|
7800
|
-
const toggle = (0,
|
|
8423
|
+
const toggle = (0, import_react46.useCallback)(() => {
|
|
7801
8424
|
if (stateRef.current.isOpen) close();
|
|
7802
8425
|
else open();
|
|
7803
8426
|
}, [close, open]);
|
|
7804
|
-
const dismissPopup = (0,
|
|
7805
|
-
const reset = (0,
|
|
8427
|
+
const dismissPopup = (0, import_react46.useCallback)(() => dispatch({ kind: "DISMISS_POPUP" }), []);
|
|
8428
|
+
const reset = (0, import_react46.useCallback)(() => {
|
|
7806
8429
|
handleRef.current?.cancel();
|
|
7807
8430
|
handleRef.current = null;
|
|
7808
8431
|
clearFallbackTimer();
|
|
@@ -7813,18 +8436,18 @@ function useChatStream(conversation, starterTexts, greetingMessage, storageKey)
|
|
|
7813
8436
|
greetedRef.current = true;
|
|
7814
8437
|
fireGreeting();
|
|
7815
8438
|
}, [storageKey, conversation, clearFallbackTimer, fireGreeting]);
|
|
7816
|
-
const getLiveDashboard = (0,
|
|
8439
|
+
const getLiveDashboard = (0, import_react46.useCallback)(() => conversation.getLiveDashboard(), [conversation]);
|
|
7817
8440
|
useLiveDashboardPoll({ messages: state.messages, dispatch, getLiveDashboard });
|
|
7818
8441
|
return { state, open, close, stop, toggle, send, retry, dismissPopup, reset, _dispatch: dispatch };
|
|
7819
8442
|
}
|
|
7820
8443
|
|
|
7821
8444
|
// src/useLiveMonitor.ts
|
|
7822
|
-
var
|
|
8445
|
+
var import_react48 = require("react");
|
|
7823
8446
|
|
|
7824
8447
|
// src/useTabs.ts
|
|
7825
|
-
var
|
|
8448
|
+
var import_react47 = require("react");
|
|
7826
8449
|
function useTabs(initial = "chat") {
|
|
7827
|
-
const [activeTab, setActiveTab] = (0,
|
|
8450
|
+
const [activeTab, setActiveTab] = (0, import_react47.useState)(initial);
|
|
7828
8451
|
return { activeTab, setActiveTab };
|
|
7829
8452
|
}
|
|
7830
8453
|
|
|
@@ -7849,38 +8472,38 @@ function phaseForPin(snapshot, pin) {
|
|
|
7849
8472
|
return snapshot.markets.find((mk) => mk.key === pin.key)?.phase;
|
|
7850
8473
|
}
|
|
7851
8474
|
function useLiveMonitor(opts) {
|
|
7852
|
-
const sessionIdRef = (0,
|
|
7853
|
-
const [pins, setPins] = (0,
|
|
7854
|
-
const [store] = (0,
|
|
7855
|
-
const [enabled, setEnabled] = (0,
|
|
7856
|
-
const [snapshot, setSnapshotState] = (0,
|
|
8475
|
+
const sessionIdRef = (0, import_react48.useRef)(crypto.randomUUID());
|
|
8476
|
+
const [pins, setPins] = (0, import_react48.useState)([]);
|
|
8477
|
+
const [store] = (0, import_react48.useState)(() => createPinStore(opts.storageKey, sessionIdRef.current, setPins));
|
|
8478
|
+
const [enabled, setEnabled] = (0, import_react48.useState)(true);
|
|
8479
|
+
const [snapshot, setSnapshotState] = (0, import_react48.useState)(null);
|
|
7857
8480
|
const { activeTab, setActiveTab: setActiveTabState } = useTabs("chat");
|
|
7858
|
-
const [chatDot, setChatDot] = (0,
|
|
7859
|
-
const [matchesDot, setMatchesDot] = (0,
|
|
7860
|
-
const [oddsDot, setOddsDot] = (0,
|
|
7861
|
-
const [todayDot, setTodayDot] = (0,
|
|
7862
|
-
const [lastViewedPrices, setLastViewedPrices] = (0,
|
|
7863
|
-
const [openMarket, setOpenMarket] = (0,
|
|
7864
|
-
const [marketOdds, setMarketOdds] = (0,
|
|
7865
|
-
const pinsRef = (0,
|
|
8481
|
+
const [chatDot, setChatDot] = (0, import_react48.useState)(false);
|
|
8482
|
+
const [matchesDot, setMatchesDot] = (0, import_react48.useState)(false);
|
|
8483
|
+
const [oddsDot, setOddsDot] = (0, import_react48.useState)(false);
|
|
8484
|
+
const [todayDot, setTodayDot] = (0, import_react48.useState)(false);
|
|
8485
|
+
const [lastViewedPrices, setLastViewedPrices] = (0, import_react48.useState)({});
|
|
8486
|
+
const [openMarket, setOpenMarket] = (0, import_react48.useState)({});
|
|
8487
|
+
const [marketOdds, setMarketOdds] = (0, import_react48.useState)({});
|
|
8488
|
+
const pinsRef = (0, import_react48.useRef)(pins);
|
|
7866
8489
|
pinsRef.current = pins;
|
|
7867
|
-
const watchesRef = (0,
|
|
8490
|
+
const watchesRef = (0, import_react48.useRef)(opts.watches ?? []);
|
|
7868
8491
|
watchesRef.current = opts.watches ?? [];
|
|
7869
|
-
const activeTabRef = (0,
|
|
8492
|
+
const activeTabRef = (0, import_react48.useRef)(activeTab);
|
|
7870
8493
|
activeTabRef.current = activeTab;
|
|
7871
|
-
const snapshotRef = (0,
|
|
8494
|
+
const snapshotRef = (0, import_react48.useRef)(snapshot);
|
|
7872
8495
|
snapshotRef.current = snapshot;
|
|
7873
|
-
const lastViewedPricesRef = (0,
|
|
8496
|
+
const lastViewedPricesRef = (0, import_react48.useRef)(lastViewedPrices);
|
|
7874
8497
|
lastViewedPricesRef.current = lastViewedPrices;
|
|
7875
|
-
const openMarketRef = (0,
|
|
8498
|
+
const openMarketRef = (0, import_react48.useRef)(openMarket);
|
|
7876
8499
|
openMarketRef.current = openMarket;
|
|
7877
|
-
const marketOddsRef = (0,
|
|
8500
|
+
const marketOddsRef = (0, import_react48.useRef)(marketOdds);
|
|
7878
8501
|
marketOddsRef.current = marketOdds;
|
|
7879
8502
|
const hasToday = opts.hasToday ?? false;
|
|
7880
8503
|
const marketKey = (matchId, dev) => `${matchId}:${dev}`;
|
|
7881
|
-
const getMarketOddsRef = (0,
|
|
8504
|
+
const getMarketOddsRef = (0, import_react48.useRef)(opts.getMarketOdds);
|
|
7882
8505
|
getMarketOddsRef.current = opts.getMarketOdds;
|
|
7883
|
-
const fetchMarket = (0,
|
|
8506
|
+
const fetchMarket = (0, import_react48.useCallback)((matchId, dev) => {
|
|
7884
8507
|
const key = marketKey(matchId, dev);
|
|
7885
8508
|
const existing = marketOddsRef.current[key];
|
|
7886
8509
|
if (existing?.loading) return;
|
|
@@ -7899,7 +8522,7 @@ function useLiveMonitor(opts) {
|
|
|
7899
8522
|
setMarketOdds((prev) => ({ ...prev, [key]: entry }));
|
|
7900
8523
|
});
|
|
7901
8524
|
}, []);
|
|
7902
|
-
const toggleMarket = (0,
|
|
8525
|
+
const toggleMarket = (0, import_react48.useCallback)((matchId, dev) => {
|
|
7903
8526
|
const key = marketKey(matchId, dev);
|
|
7904
8527
|
const wasOpen = openMarketRef.current[matchId] === dev;
|
|
7905
8528
|
const nextValue = wasOpen ? null : dev;
|
|
@@ -7910,10 +8533,10 @@ function useLiveMonitor(opts) {
|
|
|
7910
8533
|
fetchMarket(matchId, dev);
|
|
7911
8534
|
}
|
|
7912
8535
|
}, [fetchMarket]);
|
|
7913
|
-
const retryMarket = (0,
|
|
8536
|
+
const retryMarket = (0, import_react48.useCallback)((matchId, dev) => {
|
|
7914
8537
|
fetchMarket(matchId, dev);
|
|
7915
8538
|
}, [fetchMarket]);
|
|
7916
|
-
const applySnapshot = (0,
|
|
8539
|
+
const applySnapshot = (0, import_react48.useCallback)((next) => {
|
|
7917
8540
|
if (next === null) {
|
|
7918
8541
|
setEnabled(false);
|
|
7919
8542
|
return;
|
|
@@ -7976,13 +8599,13 @@ function useLiveMonitor(opts) {
|
|
|
7976
8599
|
snapshotRef.current = next;
|
|
7977
8600
|
setSnapshotState(next);
|
|
7978
8601
|
}, [store]);
|
|
7979
|
-
const inFlightRef = (0,
|
|
7980
|
-
const timerRef = (0,
|
|
7981
|
-
const disabledRef = (0,
|
|
7982
|
-
const mountedRef = (0,
|
|
7983
|
-
const getLiveMonitorRef = (0,
|
|
8602
|
+
const inFlightRef = (0, import_react48.useRef)(false);
|
|
8603
|
+
const timerRef = (0, import_react48.useRef)(void 0);
|
|
8604
|
+
const disabledRef = (0, import_react48.useRef)(false);
|
|
8605
|
+
const mountedRef = (0, import_react48.useRef)(false);
|
|
8606
|
+
const getLiveMonitorRef = (0, import_react48.useRef)(opts.getLiveMonitor);
|
|
7984
8607
|
getLiveMonitorRef.current = opts.getLiveMonitor;
|
|
7985
|
-
const tick = (0,
|
|
8608
|
+
const tick = (0, import_react48.useCallback)(async () => {
|
|
7986
8609
|
const allPins = [...pinsRef.current, ...watchesRef.current];
|
|
7987
8610
|
if (inFlightRef.current || disabledRef.current || allPins.length === 0) return;
|
|
7988
8611
|
inFlightRef.current = true;
|
|
@@ -8006,7 +8629,7 @@ function useLiveMonitor(opts) {
|
|
|
8006
8629
|
scheduleRef.current();
|
|
8007
8630
|
}
|
|
8008
8631
|
}, [applySnapshot, fetchMarket]);
|
|
8009
|
-
const scheduleRef = (0,
|
|
8632
|
+
const scheduleRef = (0, import_react48.useRef)(() => {
|
|
8010
8633
|
});
|
|
8011
8634
|
scheduleRef.current = () => {
|
|
8012
8635
|
window.clearTimeout(timerRef.current);
|
|
@@ -8016,14 +8639,14 @@ function useLiveMonitor(opts) {
|
|
|
8016
8639
|
const hinted = snapshotRef.current ? snapshotRef.current.nextPollSeconds * 1e3 : fallbackMs;
|
|
8017
8640
|
timerRef.current = window.setTimeout(() => void tick(), Math.max(MIN_POLL_MS, hinted));
|
|
8018
8641
|
};
|
|
8019
|
-
(0,
|
|
8642
|
+
(0, import_react48.useEffect)(() => {
|
|
8020
8643
|
mountedRef.current = true;
|
|
8021
8644
|
return () => {
|
|
8022
8645
|
mountedRef.current = false;
|
|
8023
8646
|
};
|
|
8024
8647
|
}, []);
|
|
8025
|
-
const prevPinsLengthRef = (0,
|
|
8026
|
-
(0,
|
|
8648
|
+
const prevPinsLengthRef = (0, import_react48.useRef)(0);
|
|
8649
|
+
(0, import_react48.useEffect)(() => {
|
|
8027
8650
|
const wasEmpty = prevPinsLengthRef.current === 0 && watchesRef.current.length === 0;
|
|
8028
8651
|
prevPinsLengthRef.current = pins.length;
|
|
8029
8652
|
if (pins.length === 0 && watchesRef.current.length === 0) {
|
|
@@ -8032,27 +8655,27 @@ function useLiveMonitor(opts) {
|
|
|
8032
8655
|
}
|
|
8033
8656
|
if (wasEmpty) void tick();
|
|
8034
8657
|
}, [pins.length, tick]);
|
|
8035
|
-
const prevWatchesLengthRef = (0,
|
|
8658
|
+
const prevWatchesLengthRef = (0, import_react48.useRef)(0);
|
|
8036
8659
|
const watchesLength = opts.watches?.length ?? 0;
|
|
8037
|
-
(0,
|
|
8660
|
+
(0, import_react48.useEffect)(() => {
|
|
8038
8661
|
const wasEmpty = prevWatchesLengthRef.current === 0 && pinsRef.current.length === 0;
|
|
8039
8662
|
prevWatchesLengthRef.current = watchesLength;
|
|
8040
8663
|
if (wasEmpty && watchesLength > 0) void tick();
|
|
8041
8664
|
}, [watchesLength, tick]);
|
|
8042
|
-
(0,
|
|
8665
|
+
(0, import_react48.useEffect)(() => {
|
|
8043
8666
|
const matchPins = pins.filter((p) => p.kind === "match").length;
|
|
8044
8667
|
const marketPins = pins.length - matchPins;
|
|
8045
8668
|
if (activeTab === "following" && matchPins === 0) setActiveTabState(hasToday ? "today" : "chat");
|
|
8046
8669
|
if (activeTab === "odds" && marketPins === 0) setActiveTabState(hasToday ? "today" : "chat");
|
|
8047
8670
|
if (activeTab === "today" && !hasToday) setActiveTabState("chat");
|
|
8048
8671
|
}, [pins, activeTab, hasToday]);
|
|
8049
|
-
const prevIsOpenRef = (0,
|
|
8050
|
-
(0,
|
|
8672
|
+
const prevIsOpenRef = (0, import_react48.useRef)(false);
|
|
8673
|
+
(0, import_react48.useEffect)(() => {
|
|
8051
8674
|
const wasClosed = !prevIsOpenRef.current;
|
|
8052
8675
|
prevIsOpenRef.current = opts.isOpen;
|
|
8053
8676
|
if (wasClosed && opts.isOpen) void tick();
|
|
8054
8677
|
}, [opts.isOpen, tick]);
|
|
8055
|
-
(0,
|
|
8678
|
+
(0, import_react48.useEffect)(() => {
|
|
8056
8679
|
const onVisibility = () => {
|
|
8057
8680
|
if (document.visibilityState === "hidden") window.clearTimeout(timerRef.current);
|
|
8058
8681
|
else void tick();
|
|
@@ -8063,7 +8686,7 @@ function useLiveMonitor(opts) {
|
|
|
8063
8686
|
window.clearTimeout(timerRef.current);
|
|
8064
8687
|
};
|
|
8065
8688
|
}, [tick]);
|
|
8066
|
-
const setActiveTab = (0,
|
|
8689
|
+
const setActiveTab = (0, import_react48.useCallback)((tab) => {
|
|
8067
8690
|
setActiveTabState(tab);
|
|
8068
8691
|
if (tab === "chat") setChatDot(false);
|
|
8069
8692
|
if (tab === "today") setTodayDot(false);
|
|
@@ -8084,14 +8707,14 @@ function useLiveMonitor(opts) {
|
|
|
8084
8707
|
void tick();
|
|
8085
8708
|
}
|
|
8086
8709
|
}, [tick]);
|
|
8087
|
-
const togglePin = (0,
|
|
8710
|
+
const togglePin = (0, import_react48.useCallback)((pin) => {
|
|
8088
8711
|
const result = store.toggle(pin);
|
|
8089
8712
|
pinsRef.current = store.pins;
|
|
8090
8713
|
return result;
|
|
8091
8714
|
}, [store]);
|
|
8092
|
-
const isPinned = (0,
|
|
8093
|
-
const markChatActivity = (0,
|
|
8094
|
-
const markTodayActivity = (0,
|
|
8715
|
+
const isPinned = (0, import_react48.useCallback)((key) => pinsRef.current.some((p) => p.key === key), []);
|
|
8716
|
+
const markChatActivity = (0, import_react48.useCallback)(() => setChatDot(true), []);
|
|
8717
|
+
const markTodayActivity = (0, import_react48.useCallback)(() => {
|
|
8095
8718
|
if (activeTabRef.current !== "today") setTodayDot(true);
|
|
8096
8719
|
}, []);
|
|
8097
8720
|
return {
|
|
@@ -8118,7 +8741,7 @@ function useLiveMonitor(opts) {
|
|
|
8118
8741
|
}
|
|
8119
8742
|
|
|
8120
8743
|
// src/useMatchDiscovery.ts
|
|
8121
|
-
var
|
|
8744
|
+
var import_react49 = require("react");
|
|
8122
8745
|
|
|
8123
8746
|
// src/scheduleCache.ts
|
|
8124
8747
|
var PREFIX = "ss-w-schedule:";
|
|
@@ -8206,27 +8829,27 @@ function localDateOf(now) {
|
|
|
8206
8829
|
return `${y}-${m}-${d}`;
|
|
8207
8830
|
}
|
|
8208
8831
|
function useMatchDiscovery(opts) {
|
|
8209
|
-
const [groups, setGroups] = (0,
|
|
8210
|
-
const [states, setStates] = (0,
|
|
8211
|
-
const [failures, setFailures] = (0,
|
|
8212
|
-
const [expanded, setExpanded] = (0,
|
|
8213
|
-
const [touched, setTouched] = (0,
|
|
8214
|
-
const groupsRef = (0,
|
|
8832
|
+
const [groups, setGroups] = (0, import_react49.useState)([]);
|
|
8833
|
+
const [states, setStates] = (0, import_react49.useState)({});
|
|
8834
|
+
const [failures, setFailures] = (0, import_react49.useState)(0);
|
|
8835
|
+
const [expanded, setExpanded] = (0, import_react49.useState)({});
|
|
8836
|
+
const [touched, setTouched] = (0, import_react49.useState)({});
|
|
8837
|
+
const groupsRef = (0, import_react49.useRef)(groups);
|
|
8215
8838
|
groupsRef.current = groups;
|
|
8216
|
-
const statesRef = (0,
|
|
8839
|
+
const statesRef = (0, import_react49.useRef)(states);
|
|
8217
8840
|
statesRef.current = states;
|
|
8218
|
-
const getStatesRef = (0,
|
|
8841
|
+
const getStatesRef = (0, import_react49.useRef)(opts.getStates);
|
|
8219
8842
|
getStatesRef.current = opts.getStates;
|
|
8220
8843
|
const pinnedKey = opts.pinnedMatchIds.join(",");
|
|
8221
|
-
const [localDate, setLocalDate] = (0,
|
|
8222
|
-
(0,
|
|
8844
|
+
const [localDate, setLocalDate] = (0, import_react49.useState)(() => localDateOf(/* @__PURE__ */ new Date()));
|
|
8845
|
+
(0, import_react49.useEffect)(() => {
|
|
8223
8846
|
const id = setInterval(() => {
|
|
8224
8847
|
const next = localDateOf(/* @__PURE__ */ new Date());
|
|
8225
8848
|
setLocalDate((prev) => prev === next ? prev : next);
|
|
8226
8849
|
}, ROLLOVER_CHECK_MS);
|
|
8227
8850
|
return () => clearInterval(id);
|
|
8228
8851
|
}, []);
|
|
8229
|
-
(0,
|
|
8852
|
+
(0, import_react49.useEffect)(() => {
|
|
8230
8853
|
let cancelled = false;
|
|
8231
8854
|
const utcOffsetMinutes = -(/* @__PURE__ */ new Date()).getTimezoneOffset();
|
|
8232
8855
|
const cached = readSchedule(opts.operatorId, localDate, Date.now());
|
|
@@ -8251,7 +8874,7 @@ function useMatchDiscovery(opts) {
|
|
|
8251
8874
|
cancelled = true;
|
|
8252
8875
|
};
|
|
8253
8876
|
}, [opts.operatorId, localDate]);
|
|
8254
|
-
(0,
|
|
8877
|
+
(0, import_react49.useEffect)(() => {
|
|
8255
8878
|
setExpanded((prev) => {
|
|
8256
8879
|
const next = {};
|
|
8257
8880
|
for (const group of groups) {
|
|
@@ -8262,7 +8885,7 @@ function useMatchDiscovery(opts) {
|
|
|
8262
8885
|
return next;
|
|
8263
8886
|
});
|
|
8264
8887
|
}, [groups, pinnedKey, touched]);
|
|
8265
|
-
const tick = (0,
|
|
8888
|
+
const tick = (0, import_react49.useCallback)(async () => {
|
|
8266
8889
|
const ids = dueMatchIds(groupsRef.current, statesRef.current, Date.now());
|
|
8267
8890
|
if (ids.length === 0) return;
|
|
8268
8891
|
try {
|
|
@@ -8278,7 +8901,7 @@ function useMatchDiscovery(opts) {
|
|
|
8278
8901
|
setFailures((n) => n + 1);
|
|
8279
8902
|
}
|
|
8280
8903
|
}, []);
|
|
8281
|
-
(0,
|
|
8904
|
+
(0, import_react49.useEffect)(() => {
|
|
8282
8905
|
if (!opts.isOpen) return;
|
|
8283
8906
|
void tick();
|
|
8284
8907
|
const id = setInterval(() => {
|
|
@@ -8287,11 +8910,11 @@ function useMatchDiscovery(opts) {
|
|
|
8287
8910
|
return () => clearInterval(id);
|
|
8288
8911
|
}, [opts.isOpen, tick, groups]);
|
|
8289
8912
|
const stale = failures >= STALE_AFTER_FAILURES;
|
|
8290
|
-
const liveCount = (0,
|
|
8913
|
+
const liveCount = (0, import_react49.useMemo)(
|
|
8291
8914
|
() => stale ? 0 : Object.values(states).filter((s) => s.phase === "live").length,
|
|
8292
8915
|
[states, stale]
|
|
8293
8916
|
);
|
|
8294
|
-
const toggleGroup = (0,
|
|
8917
|
+
const toggleGroup = (0, import_react49.useCallback)((competitionId) => {
|
|
8295
8918
|
setTouched((prev) => ({ ...prev, [competitionId]: true }));
|
|
8296
8919
|
setExpanded((prev) => ({ ...prev, [competitionId]: !prev[competitionId] }));
|
|
8297
8920
|
}, []);
|
|
@@ -8299,25 +8922,25 @@ function useMatchDiscovery(opts) {
|
|
|
8299
8922
|
}
|
|
8300
8923
|
|
|
8301
8924
|
// src/useGames.ts
|
|
8302
|
-
var
|
|
8925
|
+
var import_react50 = require("react");
|
|
8303
8926
|
var GAMES_POLL_MS = 6e4;
|
|
8304
8927
|
var DEFAULT_TZ = "Europe/London";
|
|
8305
8928
|
function computeGameDay(timezone) {
|
|
8306
8929
|
return new Intl.DateTimeFormat("en-CA", { timeZone: timezone }).format(/* @__PURE__ */ new Date());
|
|
8307
8930
|
}
|
|
8308
8931
|
function useGames(conversation, { activeTab, operatorTimezone }) {
|
|
8309
|
-
const [enabled, setEnabled] = (0,
|
|
8310
|
-
const [games, setGames] = (0,
|
|
8311
|
-
const [loading, setLoading] = (0,
|
|
8312
|
-
const [refreshCount, setRefreshCount] = (0,
|
|
8313
|
-
const inFlightRef = (0,
|
|
8314
|
-
const hardOffRef = (0,
|
|
8315
|
-
const conversationRef = (0,
|
|
8932
|
+
const [enabled, setEnabled] = (0, import_react50.useState)(false);
|
|
8933
|
+
const [games, setGames] = (0, import_react50.useState)([]);
|
|
8934
|
+
const [loading, setLoading] = (0, import_react50.useState)(false);
|
|
8935
|
+
const [refreshCount, setRefreshCount] = (0, import_react50.useState)(0);
|
|
8936
|
+
const inFlightRef = (0, import_react50.useRef)(false);
|
|
8937
|
+
const hardOffRef = (0, import_react50.useRef)(false);
|
|
8938
|
+
const conversationRef = (0, import_react50.useRef)(conversation);
|
|
8316
8939
|
conversationRef.current = conversation;
|
|
8317
|
-
const tzRef = (0,
|
|
8940
|
+
const tzRef = (0, import_react50.useRef)(operatorTimezone ?? DEFAULT_TZ);
|
|
8318
8941
|
tzRef.current = operatorTimezone ?? DEFAULT_TZ;
|
|
8319
8942
|
const ready = conversation != null;
|
|
8320
|
-
(0,
|
|
8943
|
+
(0, import_react50.useEffect)(() => {
|
|
8321
8944
|
if (hardOffRef.current) return;
|
|
8322
8945
|
let active = true;
|
|
8323
8946
|
const doFetch = async () => {
|
|
@@ -8360,22 +8983,22 @@ function useGames(conversation, { activeTab, operatorTimezone }) {
|
|
|
8360
8983
|
if (intervalId !== void 0) window.clearInterval(intervalId);
|
|
8361
8984
|
};
|
|
8362
8985
|
}, [activeTab, refreshCount, ready]);
|
|
8363
|
-
const refresh = (0,
|
|
8986
|
+
const refresh = (0, import_react50.useCallback)(() => {
|
|
8364
8987
|
setRefreshCount((c) => c + 1);
|
|
8365
8988
|
}, []);
|
|
8366
8989
|
return { enabled, games, loading, refresh };
|
|
8367
8990
|
}
|
|
8368
8991
|
|
|
8369
8992
|
// src/useLeaderboard.ts
|
|
8370
|
-
var
|
|
8993
|
+
var import_react51 = require("react");
|
|
8371
8994
|
function useLeaderboard(conversation, { activeTab }) {
|
|
8372
|
-
const [data, setData] = (0,
|
|
8373
|
-
const [loading, setLoading] = (0,
|
|
8374
|
-
const [refreshCount, setRefreshCount] = (0,
|
|
8375
|
-
const inFlightRef = (0,
|
|
8376
|
-
const conversationRef = (0,
|
|
8995
|
+
const [data, setData] = (0, import_react51.useState)(null);
|
|
8996
|
+
const [loading, setLoading] = (0, import_react51.useState)(false);
|
|
8997
|
+
const [refreshCount, setRefreshCount] = (0, import_react51.useState)(0);
|
|
8998
|
+
const inFlightRef = (0, import_react51.useRef)(false);
|
|
8999
|
+
const conversationRef = (0, import_react51.useRef)(conversation);
|
|
8377
9000
|
conversationRef.current = conversation;
|
|
8378
|
-
(0,
|
|
9001
|
+
(0, import_react51.useEffect)(() => {
|
|
8379
9002
|
if (activeTab !== "leaderboard" && activeTab !== "games") return;
|
|
8380
9003
|
let active = true;
|
|
8381
9004
|
const doFetch = async () => {
|
|
@@ -8398,18 +9021,68 @@ function useLeaderboard(conversation, { activeTab }) {
|
|
|
8398
9021
|
inFlightRef.current = false;
|
|
8399
9022
|
};
|
|
8400
9023
|
}, [activeTab, refreshCount]);
|
|
8401
|
-
const refresh = (0,
|
|
9024
|
+
const refresh = (0, import_react51.useCallback)(() => {
|
|
8402
9025
|
setRefreshCount((c) => c + 1);
|
|
8403
9026
|
}, []);
|
|
8404
9027
|
return { data, loading, refresh };
|
|
8405
9028
|
}
|
|
8406
9029
|
|
|
9030
|
+
// src/usePresetGrid.ts
|
|
9031
|
+
var import_react52 = require("react");
|
|
9032
|
+
var GRID_MAX_AGE_MS = 6e5;
|
|
9033
|
+
function usePresetGrid({ getPricePresets, isOpen, locale, now = Date.now }) {
|
|
9034
|
+
const [grid, setGrid] = (0, import_react52.useState)(null);
|
|
9035
|
+
const keptRef = (0, import_react52.useRef)(null);
|
|
9036
|
+
const localeRef = (0, import_react52.useRef)(locale);
|
|
9037
|
+
const inFlightRef = (0, import_react52.useRef)(false);
|
|
9038
|
+
const mountedRef = (0, import_react52.useRef)(true);
|
|
9039
|
+
const getRef = (0, import_react52.useRef)(getPricePresets);
|
|
9040
|
+
getRef.current = getPricePresets;
|
|
9041
|
+
const nowRef = (0, import_react52.useRef)(now);
|
|
9042
|
+
nowRef.current = now;
|
|
9043
|
+
(0, import_react52.useEffect)(() => {
|
|
9044
|
+
mountedRef.current = true;
|
|
9045
|
+
return () => {
|
|
9046
|
+
mountedRef.current = false;
|
|
9047
|
+
};
|
|
9048
|
+
}, []);
|
|
9049
|
+
(0, import_react52.useEffect)(() => {
|
|
9050
|
+
if (localeRef.current !== locale) {
|
|
9051
|
+
localeRef.current = locale;
|
|
9052
|
+
keptRef.current = null;
|
|
9053
|
+
setGrid(null);
|
|
9054
|
+
}
|
|
9055
|
+
if (!isOpen || inFlightRef.current) return;
|
|
9056
|
+
const kept = keptRef.current;
|
|
9057
|
+
const age = kept ? nowRef.current() - Date.parse(kept.builtAtUtc) : Number.NaN;
|
|
9058
|
+
if (age <= GRID_MAX_AGE_MS) return;
|
|
9059
|
+
inFlightRef.current = true;
|
|
9060
|
+
Promise.resolve().then(() => getRef.current(locale)).then((body) => {
|
|
9061
|
+
if (body === null || isPresetGrid(body) && !hasAnyCell(body)) {
|
|
9062
|
+
keptRef.current = null;
|
|
9063
|
+
if (mountedRef.current) setGrid(null);
|
|
9064
|
+
return;
|
|
9065
|
+
}
|
|
9066
|
+
if (!isPresetGrid(body)) return;
|
|
9067
|
+
keptRef.current = body;
|
|
9068
|
+
if (mountedRef.current) setGrid(body);
|
|
9069
|
+
}).catch(() => {
|
|
9070
|
+
}).finally(() => {
|
|
9071
|
+
inFlightRef.current = false;
|
|
9072
|
+
});
|
|
9073
|
+
}, [isOpen, locale]);
|
|
9074
|
+
return grid;
|
|
9075
|
+
}
|
|
9076
|
+
function hasAnyCell(grid) {
|
|
9077
|
+
return grid.singles.length > 0 || grid.singlesFrom.length > 0 || grid.combos.length > 0;
|
|
9078
|
+
}
|
|
9079
|
+
|
|
8407
9080
|
// src/popupTelemetry.ts
|
|
8408
|
-
var
|
|
9081
|
+
var import_react53 = require("react");
|
|
8409
9082
|
function usePopupTelemetry(log, isOpen, popupText, dismissPopup) {
|
|
8410
|
-
const shownRef = (0,
|
|
8411
|
-
const closedTextRef = (0,
|
|
8412
|
-
(0,
|
|
9083
|
+
const shownRef = (0, import_react53.useRef)(/* @__PURE__ */ new Set());
|
|
9084
|
+
const closedTextRef = (0, import_react53.useRef)(null);
|
|
9085
|
+
(0, import_react53.useEffect)(() => {
|
|
8413
9086
|
if (isOpen) {
|
|
8414
9087
|
if (closedTextRef.current) log?.log({ name: "popup_opened" });
|
|
8415
9088
|
closedTextRef.current = null;
|
|
@@ -8421,65 +9094,65 @@ function usePopupTelemetry(log, isOpen, popupText, dismissPopup) {
|
|
|
8421
9094
|
log?.log({ name: "popup_shown" });
|
|
8422
9095
|
}
|
|
8423
9096
|
}, [log, isOpen, popupText]);
|
|
8424
|
-
return (0,
|
|
9097
|
+
return (0, import_react53.useCallback)(() => {
|
|
8425
9098
|
log?.log({ name: "popup_dismissed" });
|
|
8426
9099
|
dismissPopup();
|
|
8427
9100
|
}, [log, dismissPopup]);
|
|
8428
9101
|
}
|
|
8429
9102
|
|
|
8430
9103
|
// src/components/ChatFab.tsx
|
|
8431
|
-
var
|
|
9104
|
+
var import_react54 = require("react");
|
|
8432
9105
|
|
|
8433
9106
|
// src/components/GreetingBubble.tsx
|
|
8434
|
-
var
|
|
9107
|
+
var import_jsx_runtime91 = require("react/jsx-runtime");
|
|
8435
9108
|
function GreetingBubble({ text, onOpen, onDismiss, dismissLabel = "Dismiss", asks }) {
|
|
8436
|
-
const body = /* @__PURE__ */ (0,
|
|
8437
|
-
/* @__PURE__ */ (0,
|
|
8438
|
-
/* @__PURE__ */ (0,
|
|
8439
|
-
/* @__PURE__ */ (0,
|
|
8440
|
-
/* @__PURE__ */ (0,
|
|
9109
|
+
const body = /* @__PURE__ */ (0, import_jsx_runtime91.jsxs)("button", { type: "button", className: "ss-w-greeting-body", onClick: onOpen, children: [
|
|
9110
|
+
/* @__PURE__ */ (0, import_jsx_runtime91.jsxs)("span", { className: "ss-w-greeting-dots", "aria-hidden": "true", children: [
|
|
9111
|
+
/* @__PURE__ */ (0, import_jsx_runtime91.jsx)("i", {}),
|
|
9112
|
+
/* @__PURE__ */ (0, import_jsx_runtime91.jsx)("i", {}),
|
|
9113
|
+
/* @__PURE__ */ (0, import_jsx_runtime91.jsx)("i", {})
|
|
8441
9114
|
] }),
|
|
8442
|
-
/* @__PURE__ */ (0,
|
|
9115
|
+
/* @__PURE__ */ (0, import_jsx_runtime91.jsx)("span", { className: "ss-w-greeting-text", children: text })
|
|
8443
9116
|
] });
|
|
8444
|
-
const dismiss = onDismiss ? /* @__PURE__ */ (0,
|
|
9117
|
+
const dismiss = onDismiss ? /* @__PURE__ */ (0, import_jsx_runtime91.jsx)("button", { type: "button", className: "ss-w-greeting-x", "aria-label": dismissLabel, onClick: onDismiss, children: "\xD7" }) : null;
|
|
8445
9118
|
if (!asks || asks.length === 0) {
|
|
8446
|
-
return /* @__PURE__ */ (0,
|
|
9119
|
+
return /* @__PURE__ */ (0, import_jsx_runtime91.jsxs)("div", { className: "ss-w-greeting", role: "note", children: [
|
|
8447
9120
|
body,
|
|
8448
9121
|
dismiss
|
|
8449
9122
|
] });
|
|
8450
9123
|
}
|
|
8451
|
-
return /* @__PURE__ */ (0,
|
|
8452
|
-
/* @__PURE__ */ (0,
|
|
9124
|
+
return /* @__PURE__ */ (0, import_jsx_runtime91.jsxs)("div", { className: "ss-w-greeting ss-w-greeting--asks", role: "note", children: [
|
|
9125
|
+
/* @__PURE__ */ (0, import_jsx_runtime91.jsxs)("div", { className: "ss-w-greeting-top", children: [
|
|
8453
9126
|
body,
|
|
8454
9127
|
dismiss
|
|
8455
9128
|
] }),
|
|
8456
|
-
/* @__PURE__ */ (0,
|
|
9129
|
+
/* @__PURE__ */ (0, import_jsx_runtime91.jsx)("div", { className: "ss-w-greeting-asks", children: asks.map((ask, i) => /* @__PURE__ */ (0, import_jsx_runtime91.jsx)("button", { type: "button", className: "ss-w-greeting-ask", onClick: ask.onTap, children: ask.label }, `${i}:${ask.label}`)) })
|
|
8457
9130
|
] });
|
|
8458
9131
|
}
|
|
8459
9132
|
|
|
8460
9133
|
// src/components/ChatFab.tsx
|
|
8461
|
-
var
|
|
9134
|
+
var import_jsx_runtime92 = require("react/jsx-runtime");
|
|
8462
9135
|
function ChatFab({ label, greeting, popupText, logoUrl, onOpen, onDismissPopup, dismissLabel = "Dismiss", introAllowed = false, onIntroStart, asks }) {
|
|
8463
9136
|
const bubble = popupText ?? greeting;
|
|
8464
|
-
const [intro, setIntro] = (0,
|
|
8465
|
-
const armedRef = (0,
|
|
8466
|
-
(0,
|
|
9137
|
+
const [intro, setIntro] = (0, import_react54.useState)(false);
|
|
9138
|
+
const armedRef = (0, import_react54.useRef)(introAllowed);
|
|
9139
|
+
(0, import_react54.useEffect)(() => {
|
|
8467
9140
|
if (armedRef.current && bubble) {
|
|
8468
9141
|
armedRef.current = false;
|
|
8469
9142
|
setIntro(true);
|
|
8470
9143
|
onIntroStart?.();
|
|
8471
9144
|
}
|
|
8472
9145
|
}, [bubble, onIntroStart]);
|
|
8473
|
-
return /* @__PURE__ */ (0,
|
|
8474
|
-
bubble ? /* @__PURE__ */ (0,
|
|
8475
|
-
/* @__PURE__ */ (0,
|
|
9146
|
+
return /* @__PURE__ */ (0, import_jsx_runtime92.jsxs)("div", { className: intro ? "ss-w-fab-wrap ss-w-intro" : "ss-w-fab-wrap", children: [
|
|
9147
|
+
bubble ? /* @__PURE__ */ (0, import_jsx_runtime92.jsx)(GreetingBubble, { text: bubble, onOpen, onDismiss: popupText ? onDismissPopup : void 0, dismissLabel, asks: popupText ? asks : void 0 }) : null,
|
|
9148
|
+
/* @__PURE__ */ (0, import_jsx_runtime92.jsx)("button", { type: "button", className: "ss-w-fab", "aria-label": label, onClick: onOpen, children: /* @__PURE__ */ (0, import_jsx_runtime92.jsx)(BrandMark, { logoUrl, className: "ss-w-fab-mark" }) })
|
|
8476
9149
|
] });
|
|
8477
9150
|
}
|
|
8478
9151
|
|
|
8479
9152
|
// src/components/HostEventsProvider.tsx
|
|
8480
|
-
var
|
|
9153
|
+
var import_jsx_runtime93 = require("react/jsx-runtime");
|
|
8481
9154
|
function HostEventsProvider({ value, children }) {
|
|
8482
|
-
return /* @__PURE__ */ (0,
|
|
9155
|
+
return /* @__PURE__ */ (0, import_jsx_runtime93.jsx)(SlipStateContext.Provider, { value: value.slipState, children: /* @__PURE__ */ (0, import_jsx_runtime93.jsx)(MatchLinkContext.Provider, { value: value.matchLink, children }) });
|
|
8483
9156
|
}
|
|
8484
9157
|
|
|
8485
9158
|
// src/styles/css.generated.ts
|
|
@@ -8916,6 +9589,71 @@ var WIDGET_CSS = `.ss-w-root { all: revert; }
|
|
|
8916
9589
|
.ss-w-combo-leg-stat { font-size: 12px; line-height: 1.45; color: var(--ss-w-ink); background: var(--ss-w-bubble); border-radius: 8px; padding: 6px 9px; }
|
|
8917
9590
|
.ss-w-combo-math { font-family: ui-monospace, monospace; font-variant-numeric: tabular-nums; font-size: 14px; font-weight: 700; line-height: 1.4; color: var(--ss-w-ink); text-align: right; background: color-mix(in srgb, var(--ss-w-accent) 9%, var(--ss-w-panel)); border-radius: 8px; padding: 8px 10px; overflow-wrap: anywhere; }
|
|
8918
9591
|
.ss-w-combo-note { font-size: 11px; line-height: 1.4; color: var(--ss-w-faint); }
|
|
9592
|
+
.ss-w-combo-change { align-self: flex-start; min-height: 36px; padding: 6px 12px; font: inherit; font-size: 12px; font-weight: 650; color: var(--ss-w-accent); background: var(--ss-w-panel); border: 1px solid var(--ss-w-accent); border-radius: 16px; cursor: pointer; }
|
|
9593
|
+
|
|
9594
|
+
/* onboarding presets stage 2: the two tools (spec Stage 2 Behaviour, boards 1c and 2c). A sheet
|
|
9595
|
+
over the message list, inside .ss-w-chat-layer; the body scrolls and the builder's Send sits in
|
|
9596
|
+
the footer, so it stays visible on a phone. Colours come from the theme tokens. */
|
|
9597
|
+
.ss-w-tool-backdrop { position: absolute; inset: 0; z-index: 3; display: flex; flex-direction: column; justify-content: flex-end; background: rgba(0,0,0,.35); }
|
|
9598
|
+
.ss-w-tool { display: flex; flex-direction: column; min-height: 0; max-height: 100%; background: var(--ss-w-panel); color: var(--ss-w-ink); border-top: 1px solid var(--ss-w-line); border-radius: 16px 16px 0 0; box-shadow: 0 -8px 24px rgba(0,0,0,.18); outline: none; }
|
|
9599
|
+
.ss-w-tool-head { flex: none; display: flex; align-items: center; gap: 8px; padding: 12px 14px 8px; }
|
|
9600
|
+
.ss-w-tool-title { flex: 1; font-size: 15px; font-weight: 700; line-height: 1.3; }
|
|
9601
|
+
.ss-w-tool-close { flex: none; width: 36px; height: 36px; display: grid; place-items: center; font-size: 16px; color: var(--ss-w-faint); background: var(--ss-w-bubble); border: none; border-radius: 50%; cursor: pointer; }
|
|
9602
|
+
.ss-w-tool-body { flex: 1 1 auto; min-height: 0; overflow-y: auto; overscroll-behavior: contain; display: flex; flex-direction: column; gap: 12px; padding: 4px 14px 14px; }
|
|
9603
|
+
.ss-w-tool-body > * { flex: none; }
|
|
9604
|
+
.ss-w-tool-foot { flex: none; padding: 10px 14px 12px; border-top: 1px solid var(--ss-w-line); }
|
|
9605
|
+
.ss-w-tool-row { display: flex; flex-direction: column; gap: 8px; padding: 10px 11px; border: 1px solid var(--ss-w-line); border-left: 3px solid var(--ss-w-accent); border-radius: 12px; }
|
|
9606
|
+
.ss-w-tool-ask { font-size: 14px; font-weight: 650; line-height: 1.7; color: var(--ss-w-ink); overflow-wrap: anywhere; }
|
|
9607
|
+
.ss-w-tool-num { display: inline-block; min-width: 44px; min-height: 32px; margin: 0 2px; padding: 3px 8px; font: inherit; font-variant-numeric: tabular-nums; font-weight: 800; color: var(--ss-w-accent); background: color-mix(in srgb, var(--ss-w-accent) 10%, var(--ss-w-panel)); border: 1px dashed color-mix(in srgb, var(--ss-w-accent) 55%, var(--ss-w-line)); border-radius: 8px; cursor: pointer; }
|
|
9608
|
+
.ss-w-tool-picks { list-style: none; display: flex; flex-direction: column; gap: 4px; }
|
|
9609
|
+
.ss-w-tool-pick { display: grid; grid-template-columns: 1fr auto; column-gap: 8px; padding: 5px 8px; font-size: 12px; line-height: 1.35; background: var(--ss-w-bubble); border-radius: 8px; }
|
|
9610
|
+
.ss-w-tool-pick-match { grid-column: 1; font-weight: 650; color: var(--ss-w-ink); overflow-wrap: anywhere; }
|
|
9611
|
+
.ss-w-tool-pick-sel { grid-column: 1; color: var(--ss-w-faint); overflow-wrap: anywhere; }
|
|
9612
|
+
.ss-w-tool-pick-kick { grid-column: 1; font-size: 11px; color: var(--ss-w-faint); }
|
|
9613
|
+
.ss-w-tool-pick-price { grid-column: 2; grid-row: 1 / span 3; align-self: center; font-family: ui-monospace, monospace; font-variant-numeric: tabular-nums; font-size: 14px; font-weight: 800; color: var(--ss-w-accent); }
|
|
9614
|
+
.ss-w-tool-math { padding: 6px 8px; font-family: ui-monospace, monospace; font-variant-numeric: tabular-nums; font-size: 13px; font-weight: 700; text-align: right; color: var(--ss-w-ink); background: color-mix(in srgb, var(--ss-w-accent) 9%, var(--ss-w-panel)); border-radius: 8px; overflow-wrap: anywhere; }
|
|
9615
|
+
.ss-w-tool-unavailable { padding: 6px 8px; font-size: 12px; color: var(--ss-w-faint); border: 1px dashed var(--ss-w-line); border-radius: 8px; }
|
|
9616
|
+
.ss-w-tool-send { align-self: flex-end; min-height: 40px; padding: 8px 18px; font: inherit; font-size: 13px; font-weight: 700; color: #fff; background: var(--ss-w-accent); border: none; border-radius: 20px; cursor: pointer; }
|
|
9617
|
+
.ss-w-tool-send:disabled { color: var(--ss-w-faint); background: var(--ss-w-bubble); cursor: not-allowed; }
|
|
9618
|
+
.ss-w-tool-foot .ss-w-tool-send { width: 100%; }
|
|
9619
|
+
.ss-w-tool-section { display: flex; flex-direction: column; gap: 6px; }
|
|
9620
|
+
.ss-w-tool-label { font-size: 11.5px; font-weight: 650; color: var(--ss-w-faint); }
|
|
9621
|
+
.ss-w-tool-chips { display: flex; flex-wrap: wrap; gap: 6px; }
|
|
9622
|
+
.ss-w-tool-chip { min-width: 52px; min-height: 36px; padding: 6px 10px; font: inherit; font-variant-numeric: tabular-nums; font-weight: 700; color: var(--ss-w-ink); background: var(--ss-w-panel); border: 1px solid var(--ss-w-line); border-radius: 18px; cursor: pointer; }
|
|
9623
|
+
.ss-w-tool-chip[aria-pressed="true"] { color: #fff; background: var(--ss-w-accent); border-color: var(--ss-w-accent); }
|
|
9624
|
+
.ss-w-tool-stepper { display: inline-flex; align-items: center; gap: 10px; }
|
|
9625
|
+
.ss-w-tool-step { width: 40px; height: 40px; display: grid; place-items: center; font: inherit; font-size: 18px; font-weight: 700; color: var(--ss-w-accent); background: var(--ss-w-panel); border: 1px solid var(--ss-w-line); border-radius: 50%; cursor: pointer; }
|
|
9626
|
+
.ss-w-tool-step:disabled { opacity: .5; cursor: not-allowed; }
|
|
9627
|
+
.ss-w-tool-count { min-width: 24px; text-align: center; font-size: 16px; font-weight: 800; font-variant-numeric: tabular-nums; }
|
|
9628
|
+
|
|
9629
|
+
/* onboarding presets stage 2: the chat layer and the speed dial (spec Stage 2 Behaviour). The
|
|
9630
|
+
layer is the message list's positioned box (P6), so the dial sits against the list's bottom
|
|
9631
|
+
right and rises with the composer: right 8px puts the 44px button's centre on Send's (12px
|
|
9632
|
+
padding + 18px), bottom 10px is above the composer's top border. While the button shows the list
|
|
9633
|
+
gets 54px more bottom padding so its last message can scroll clear. */
|
|
9634
|
+
.ss-w-chat-layer { position: relative; flex: 1; min-height: 0; display: flex; flex-direction: column; }
|
|
9635
|
+
.ss-w-chat-layer[data-dial] .ss-w-list { padding-bottom: 68px; }
|
|
9636
|
+
.ss-w-dial { position: absolute; left: 12px; right: 8px; bottom: 10px; z-index: 2; display: flex; flex-direction: column-reverse; align-items: flex-end; gap: 8px; pointer-events: none; }
|
|
9637
|
+
.ss-w-dial-btn, .ss-w-dial-pill { pointer-events: auto; }
|
|
9638
|
+
.ss-w-dial-btn { position: relative; flex: none; width: 44px; height: 44px; display: grid; place-items: center; padding: 0; font-size: 20px; color: var(--ss-w-accent); background: var(--ss-w-panel); border: 1px solid color-mix(in srgb, var(--ss-w-accent) 40%, var(--ss-w-line)); border-radius: 50%; box-shadow: 0 4px 14px rgba(0,0,0,.18); cursor: pointer; }
|
|
9639
|
+
.ss-w-dial-btn[aria-expanded="true"] { color: var(--ss-w-panel); background: var(--ss-w-accent); border-color: var(--ss-w-accent); }
|
|
9640
|
+
.ss-w-dial-btn:focus-visible, .ss-w-dial-pill:focus-visible { outline: 2px solid var(--ss-w-accent); outline-offset: 2px; }
|
|
9641
|
+
.ss-w-dial-icon { grid-area: 1 / 1; display: grid; place-items: center; transition: transform 200ms ease, opacity 200ms ease; }
|
|
9642
|
+
.ss-w-dial-icon--close { opacity: 0; transform: rotate(-90deg); }
|
|
9643
|
+
.ss-w-dial-btn[aria-expanded="true"] .ss-w-dial-icon--open { opacity: 0; transform: rotate(90deg); }
|
|
9644
|
+
.ss-w-dial-btn[aria-expanded="true"] .ss-w-dial-icon--close { opacity: 1; transform: none; }
|
|
9645
|
+
.ss-w-dial-dot { position: absolute; top: 3px; right: 3px; width: 10px; height: 10px; background: var(--ss-w-accent); border: 2px solid var(--ss-w-panel); border-radius: 50%; }
|
|
9646
|
+
.ss-w-dial-pills { max-width: 100%; display: flex; flex-direction: column; align-items: flex-end; gap: 8px; }
|
|
9647
|
+
.ss-w-dial-pill { max-width: 100%; min-height: 44px; display: inline-flex; align-items: center; gap: 8px; padding: 6px 16px 6px 12px; font: inherit; font-size: 12.5px; font-weight: 650; line-height: 1.25; text-align: left; white-space: normal; overflow-wrap: anywhere; color: var(--ss-w-ink); background: var(--ss-w-panel); border: 1px solid color-mix(in srgb, var(--ss-w-accent) 35%, var(--ss-w-line)); border-radius: 22px; box-shadow: 0 4px 14px rgba(0,0,0,.16); cursor: pointer; opacity: 0; visibility: hidden; transform: translateY(14px) scale(.5); transform-origin: bottom right; transition: opacity 200ms ease, transform 200ms ease, visibility 0s linear 200ms; }
|
|
9648
|
+
.ss-w-dial-pill svg { flex: none; font-size: 18px; color: var(--ss-w-accent); }
|
|
9649
|
+
.ss-w-dial-pill-label { min-width: 0; }
|
|
9650
|
+
.ss-w-dial-pills[data-open] .ss-w-dial-pill { opacity: 1; visibility: visible; transform: none; transition: opacity 200ms ease, transform 200ms ease, visibility 0s; }
|
|
9651
|
+
@media (prefers-reduced-motion: reduce) {
|
|
9652
|
+
.ss-w-dial-pill { transform: none; transition: opacity 200ms ease, visibility 0s linear 200ms; }
|
|
9653
|
+
.ss-w-dial-pills[data-open] .ss-w-dial-pill { transition: opacity 200ms ease, visibility 0s; }
|
|
9654
|
+
.ss-w-dial-icon { transition: opacity 200ms ease; }
|
|
9655
|
+
.ss-w-dial-icon--close, .ss-w-dial-btn[aria-expanded="true"] .ss-w-dial-icon--open { transform: none; }
|
|
9656
|
+
}
|
|
8919
9657
|
|
|
8920
9658
|
/* Compact price list (D16): three or more cards of one turn, one row per fixture. Team names wrap
|
|
8921
9659
|
to a second line and are never cut; the time sits above the match pin, with no column of its own. */
|
|
@@ -9002,6 +9740,7 @@ var WIDGET_CSS = `.ss-w-root { all: revert; }
|
|
|
9002
9740
|
.ss-w-textarea { font-size: 16px; }
|
|
9003
9741
|
}
|
|
9004
9742
|
.ss-w-send { width: 36px; height: 36px; flex: none; border: none; border-radius: 50%; background: var(--ss-w-accent); color: #fff; cursor: pointer; display: grid; place-items: center; font-size: 16px; }
|
|
9743
|
+
.ss-w-send:disabled { opacity: .45; cursor: not-allowed; }
|
|
9005
9744
|
.ss-w-stop-glyph { width: 11px; height: 11px; background: currentColor; border-radius: 2px; }
|
|
9006
9745
|
|
|
9007
9746
|
/* add to slip */
|
|
@@ -10041,7 +10780,7 @@ var WIDGET_CSS = `.ss-w-root { all: revert; }
|
|
|
10041
10780
|
`;
|
|
10042
10781
|
|
|
10043
10782
|
// src/StatsWidget.tsx
|
|
10044
|
-
var
|
|
10783
|
+
var import_jsx_runtime94 = require("react/jsx-runtime");
|
|
10045
10784
|
function buildClient(props) {
|
|
10046
10785
|
try {
|
|
10047
10786
|
if ("client" in props && props.client) {
|
|
@@ -10066,24 +10805,24 @@ function injectStyles(root) {
|
|
|
10066
10805
|
function StatsWidget(props) {
|
|
10067
10806
|
const injectedClient = "client" in props ? props.client : void 0;
|
|
10068
10807
|
const cfg = props.config;
|
|
10069
|
-
const built = (0,
|
|
10070
|
-
const logger = (0,
|
|
10808
|
+
const built = (0, import_react55.useMemo)(() => buildClient(props), [injectedClient, cfg?.operatorId, cfg?.publicKey, cfg?.baseUrl]);
|
|
10809
|
+
const logger = (0, import_react55.useMemo)(
|
|
10071
10810
|
() => built ? built.client.createEventLogger({
|
|
10072
10811
|
operatorUserId: cfg?.userContext?.operatorUserId,
|
|
10073
10812
|
anonymousUserId: cfg?.userContext?.anonymousUserId
|
|
10074
10813
|
}) : null,
|
|
10075
10814
|
[built?.client]
|
|
10076
10815
|
);
|
|
10077
|
-
const ui = (0,
|
|
10078
|
-
const rootRef = (0,
|
|
10079
|
-
const introDoneRef = (0,
|
|
10080
|
-
const markIntroDone = (0,
|
|
10816
|
+
const ui = (0, import_react55.useMemo)(() => normalizeUi(props.config ?? { operatorId: "", publicKey: "" }), [props.config]);
|
|
10817
|
+
const rootRef = (0, import_react55.useRef)(null);
|
|
10818
|
+
const introDoneRef = (0, import_react55.useRef)(false);
|
|
10819
|
+
const markIntroDone = (0, import_react55.useCallback)(() => {
|
|
10081
10820
|
introDoneRef.current = true;
|
|
10082
10821
|
}, []);
|
|
10083
10822
|
const convStorageKey = `ss-w-conv:${cfg?.operatorId ?? ""}:${cfg?.userContext?.operatorUserId ?? "anon"}`;
|
|
10084
10823
|
const controller = useChatStream(built?.conversation ?? emptyConversation(), ui.starters, built ? ui.greetingMessage : void 0, convStorageKey);
|
|
10085
|
-
const [hasToday, setHasToday] = (0,
|
|
10086
|
-
const liveCardWatches = (0,
|
|
10824
|
+
const [hasToday, setHasToday] = (0, import_react55.useState)(false);
|
|
10825
|
+
const liveCardWatches = (0, import_react55.useMemo)(() => {
|
|
10087
10826
|
const seen = /* @__PURE__ */ new Set();
|
|
10088
10827
|
const out = [];
|
|
10089
10828
|
for (const msg of controller.state.messages) {
|
|
@@ -10105,15 +10844,15 @@ function StatsWidget(props) {
|
|
|
10105
10844
|
hasToday,
|
|
10106
10845
|
watches: liveCardWatches
|
|
10107
10846
|
});
|
|
10108
|
-
const getSchedule = (0,
|
|
10847
|
+
const getSchedule = (0, import_react55.useCallback)(
|
|
10109
10848
|
(localDate, utcOffsetMinutes) => built ? built.conversation.getSchedule(localDate, utcOffsetMinutes) : Promise.resolve(null),
|
|
10110
10849
|
[built]
|
|
10111
10850
|
);
|
|
10112
|
-
const getStates = (0,
|
|
10851
|
+
const getStates = (0, import_react55.useCallback)(
|
|
10113
10852
|
(matchIds) => built ? built.conversation.getStates(matchIds) : Promise.resolve(null),
|
|
10114
10853
|
[built]
|
|
10115
10854
|
);
|
|
10116
|
-
const pinnedMatchIds = (0,
|
|
10855
|
+
const pinnedMatchIds = (0, import_react55.useMemo)(
|
|
10117
10856
|
() => monitor.pins.filter((p) => p.kind === "match").map((p) => p.matchId),
|
|
10118
10857
|
[monitor.pins]
|
|
10119
10858
|
);
|
|
@@ -10125,11 +10864,11 @@ function StatsWidget(props) {
|
|
|
10125
10864
|
pinnedMatchIds
|
|
10126
10865
|
});
|
|
10127
10866
|
const todayHasContent = visibleGroupsOf(discovery.groups, discovery.states).length > 0;
|
|
10128
|
-
(0,
|
|
10867
|
+
(0, import_react55.useEffect)(() => {
|
|
10129
10868
|
setHasToday(todayHasContent);
|
|
10130
10869
|
}, [todayHasContent]);
|
|
10131
|
-
const prevLiveSnapshotRef = (0,
|
|
10132
|
-
(0,
|
|
10870
|
+
const prevLiveSnapshotRef = (0, import_react55.useRef)(monitor.snapshot);
|
|
10871
|
+
(0, import_react55.useEffect)(() => {
|
|
10133
10872
|
const snap = monitor.snapshot;
|
|
10134
10873
|
if (!snap || snap === prevLiveSnapshotRef.current) return;
|
|
10135
10874
|
prevLiveSnapshotRef.current = snap;
|
|
@@ -10165,9 +10904,14 @@ function StatsWidget(props) {
|
|
|
10165
10904
|
}, [monitor.snapshot]);
|
|
10166
10905
|
const gamesResult = useGames(built?.conversation ?? null, { activeTab: monitor.activeTab });
|
|
10167
10906
|
const leaderboardResult = useLeaderboard(built?.conversation ?? null, { activeTab: monitor.activeTab });
|
|
10907
|
+
const getPricePresets = (0, import_react55.useCallback)(
|
|
10908
|
+
(locale2) => built ? built.conversation.getPricePresets(locale2) : Promise.resolve(null),
|
|
10909
|
+
[built]
|
|
10910
|
+
);
|
|
10911
|
+
const presetGrid = usePresetGrid({ getPricePresets, isOpen: controller.state.isOpen, locale: ui.presets.lang });
|
|
10168
10912
|
const markTodayActivity = monitor.markTodayActivity;
|
|
10169
|
-
const prevLiveCountRef = (0,
|
|
10170
|
-
(0,
|
|
10913
|
+
const prevLiveCountRef = (0, import_react55.useRef)(0);
|
|
10914
|
+
(0, import_react55.useEffect)(() => {
|
|
10171
10915
|
const rose = prevLiveCountRef.current === 0 && discovery.liveCount > 0;
|
|
10172
10916
|
prevLiveCountRef.current = discovery.liveCount;
|
|
10173
10917
|
if (rose) markTodayActivity();
|
|
@@ -10183,7 +10927,7 @@ function StatsWidget(props) {
|
|
|
10183
10927
|
openMatchLabel: ui.openMatch
|
|
10184
10928
|
});
|
|
10185
10929
|
const markAdded = hostEvents.markAdded;
|
|
10186
|
-
const slip = (0,
|
|
10930
|
+
const slip = (0, import_react55.useMemo)(
|
|
10187
10931
|
() => {
|
|
10188
10932
|
const handler = props.config?.onAddToSlip;
|
|
10189
10933
|
return {
|
|
@@ -10202,7 +10946,7 @@ function StatsWidget(props) {
|
|
|
10202
10946
|
},
|
|
10203
10947
|
[props.config?.onAddToSlip, controller.state.conversationId, markAdded]
|
|
10204
10948
|
);
|
|
10205
|
-
const onDashboardTap = (0,
|
|
10949
|
+
const onDashboardTap = (0, import_react55.useMemo)(
|
|
10206
10950
|
() => built ? createDashboardTapSignal((input) => built.conversation.sendSignal(input), logger) : void 0,
|
|
10207
10951
|
[built, logger]
|
|
10208
10952
|
);
|
|
@@ -10220,11 +10964,11 @@ function StatsWidget(props) {
|
|
|
10220
10964
|
const operatorUserId = cfg?.userContext?.operatorUserId;
|
|
10221
10965
|
const anonymousUserId = cfg?.userContext?.anonymousUserId;
|
|
10222
10966
|
const conversationId = controller.state.conversationId ?? void 0;
|
|
10223
|
-
(0,
|
|
10967
|
+
(0, import_react55.useEffect)(() => {
|
|
10224
10968
|
if (!logger) return;
|
|
10225
10969
|
logger.setIdentity({ operatorUserId, anonymousUserId, conversationId });
|
|
10226
10970
|
}, [logger, operatorUserId, anonymousUserId, conversationId]);
|
|
10227
|
-
(0,
|
|
10971
|
+
(0, import_react55.useEffect)(() => {
|
|
10228
10972
|
if (!logger) return;
|
|
10229
10973
|
logger.resume();
|
|
10230
10974
|
const onHide = () => {
|
|
@@ -10242,14 +10986,14 @@ function StatsWidget(props) {
|
|
|
10242
10986
|
logger.dispose();
|
|
10243
10987
|
};
|
|
10244
10988
|
}, [logger]);
|
|
10245
|
-
(0,
|
|
10989
|
+
(0, import_react55.useEffect)(() => {
|
|
10246
10990
|
if (!built || !ui.injectStyles) return;
|
|
10247
10991
|
const node = rootRef.current?.getRootNode();
|
|
10248
10992
|
if (node) injectStyles(node);
|
|
10249
10993
|
}, [built, ui.injectStyles]);
|
|
10250
10994
|
if (!built) return null;
|
|
10251
10995
|
const attrs = themeAttrs(ui);
|
|
10252
|
-
return /* @__PURE__ */ (0,
|
|
10996
|
+
return /* @__PURE__ */ (0, import_jsx_runtime94.jsx)(EventLogContext.Provider, { value: logger, children: /* @__PURE__ */ (0, import_jsx_runtime94.jsx)(HostEventsProvider, { value: hostEvents, children: /* @__PURE__ */ (0, import_jsx_runtime94.jsx)("div", { ref: rootRef, className: "ss-w-root", "data-ss-w-theme": attrs["data-ss-w-theme"], "data-ss-w-pos": attrs["data-ss-w-pos"], style: attrs.style, children: controller.state.isOpen ? /* @__PURE__ */ (0, import_jsx_runtime94.jsx)(
|
|
10253
10997
|
ChatPanel,
|
|
10254
10998
|
{
|
|
10255
10999
|
controller,
|
|
@@ -10261,9 +11005,10 @@ function StatsWidget(props) {
|
|
|
10261
11005
|
gamesResult,
|
|
10262
11006
|
leaderboardResult,
|
|
10263
11007
|
loggedIn: !!operatorUserId,
|
|
10264
|
-
conversation: built?.conversation ?? null
|
|
11008
|
+
conversation: built?.conversation ?? null,
|
|
11009
|
+
presetGrid
|
|
10265
11010
|
}
|
|
10266
|
-
) : /* @__PURE__ */ (0,
|
|
11011
|
+
) : /* @__PURE__ */ (0, import_jsx_runtime94.jsx)(
|
|
10267
11012
|
ChatFab,
|
|
10268
11013
|
{
|
|
10269
11014
|
label: ui.launcherLabel,
|