@sensiblestats/widget-react 0.46.0 → 0.47.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 +21 -0
- package/dist/index.cjs +65 -17
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +65 -17
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,26 @@
|
|
|
1
1
|
# @sensiblestats/widget-react
|
|
2
2
|
|
|
3
|
+
## 0.47.1
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- A match whose live reading has gone stale now leaves the live band as well as losing its clock,
|
|
8
|
+
with the band and the competition group deciding from one shared predicate so it can never fall
|
|
9
|
+
between them.
|
|
10
|
+
|
|
11
|
+
## 0.47.0
|
|
12
|
+
|
|
13
|
+
### Minor Changes
|
|
14
|
+
|
|
15
|
+
- Shows a half-time label instead of a frozen 45', ages the live minute against when the reading was
|
|
16
|
+
actually taken so it no longer sits up to two minutes behind, and stops asserting a clock for a
|
|
17
|
+
single match whose sync window has stalled.
|
|
18
|
+
|
|
19
|
+
### Patch Changes
|
|
20
|
+
|
|
21
|
+
- Updated dependencies
|
|
22
|
+
- @sensiblestats/widget-sdk@0.35.0
|
|
23
|
+
|
|
3
24
|
## 0.46.0
|
|
4
25
|
|
|
5
26
|
### Minor Changes
|
package/dist/index.cjs
CHANGED
|
@@ -128,6 +128,7 @@ var STRINGS = {
|
|
|
128
128
|
accordionSuspended: "Suspended",
|
|
129
129
|
tabToday: "Today",
|
|
130
130
|
sectionLive: "Live",
|
|
131
|
+
halfTime: "HT",
|
|
131
132
|
staleStates: "Live scores can't be updated \u2014 showing the last known state."
|
|
132
133
|
},
|
|
133
134
|
tr: {
|
|
@@ -205,6 +206,7 @@ var STRINGS = {
|
|
|
205
206
|
accordionSuspended: "Ask\u0131da",
|
|
206
207
|
tabToday: "Bug\xFCn",
|
|
207
208
|
sectionLive: "Canl\u0131",
|
|
209
|
+
halfTime: "\u0130Y",
|
|
208
210
|
staleStates: "Canl\u0131 skorlar g\xFCncellenemiyor \u2014 son bilinen durum g\xF6steriliyor."
|
|
209
211
|
}
|
|
210
212
|
};
|
|
@@ -283,6 +285,7 @@ function normalizeUi(cfg) {
|
|
|
283
285
|
tabOdds: strings.tabOdds,
|
|
284
286
|
tabToday: strings.tabToday,
|
|
285
287
|
sectionLive: strings.sectionLive,
|
|
288
|
+
halfTime: strings.halfTime,
|
|
286
289
|
staleStates: strings.staleStates,
|
|
287
290
|
pinLabel: strings.pinLabel,
|
|
288
291
|
unpinLabel: strings.unpinLabel,
|
|
@@ -1542,8 +1545,43 @@ function dueMatchIds(groups, states, nowMs) {
|
|
|
1542
1545
|
return [...due];
|
|
1543
1546
|
}
|
|
1544
1547
|
|
|
1548
|
+
// src/liveClock.ts
|
|
1549
|
+
var STALE_READING_MS = 5 * 6e4;
|
|
1550
|
+
function parseMinute(label) {
|
|
1551
|
+
if (!label) return null;
|
|
1552
|
+
const plus = /^(\d+)\+(\d+)'$/.exec(label);
|
|
1553
|
+
if (plus) return Number(plus[1]) + Number(plus[2]);
|
|
1554
|
+
const plain = /^(\d+)'$/.exec(label);
|
|
1555
|
+
return plain ? Number(plain[1]) : null;
|
|
1556
|
+
}
|
|
1557
|
+
function readingAgeMs(state, nowMs) {
|
|
1558
|
+
if (!state?.stateAsOfUtc) return null;
|
|
1559
|
+
const t = Date.parse(state.stateAsOfUtc);
|
|
1560
|
+
return Number.isNaN(t) ? null : Math.max(0, nowMs - t);
|
|
1561
|
+
}
|
|
1562
|
+
function isReadingStale(state, nowMs) {
|
|
1563
|
+
if (state?.isHalfTime) return false;
|
|
1564
|
+
const age = readingAgeMs(state, nowMs);
|
|
1565
|
+
return age !== null && age > STALE_READING_MS;
|
|
1566
|
+
}
|
|
1567
|
+
function displayMinute(state, nowMs) {
|
|
1568
|
+
if (!state || state.phase !== "live") return null;
|
|
1569
|
+
if (state.isHalfTime) return null;
|
|
1570
|
+
if (isReadingStale(state, nowMs)) return null;
|
|
1571
|
+
const base = parseMinute(state.minuteLabel);
|
|
1572
|
+
if (base === null) return null;
|
|
1573
|
+
const age = readingAgeMs(state, nowMs);
|
|
1574
|
+
if (age === null) return state.minuteLabel ?? null;
|
|
1575
|
+
const advanced = base + Math.floor(age / 6e4);
|
|
1576
|
+
return `${advanced}'`;
|
|
1577
|
+
}
|
|
1578
|
+
function isTreatedLive(state, pollStale, nowMs) {
|
|
1579
|
+
return state?.phase === "live" && !pollStale && !isReadingStale(state, nowMs);
|
|
1580
|
+
}
|
|
1581
|
+
|
|
1545
1582
|
// src/useMatchDiscovery.ts
|
|
1546
1583
|
var POLL_MS = 6e4;
|
|
1584
|
+
var CLOCK_MS = 1e4;
|
|
1547
1585
|
var ROLLOVER_CHECK_MS = 6e4;
|
|
1548
1586
|
var STALE_AFTER_FAILURES = 2;
|
|
1549
1587
|
function localDateOf(now) {
|
|
@@ -1556,6 +1594,7 @@ function useMatchDiscovery(opts) {
|
|
|
1556
1594
|
const [groups, setGroups] = (0, import_react4.useState)([]);
|
|
1557
1595
|
const [states, setStates] = (0, import_react4.useState)({});
|
|
1558
1596
|
const [failures, setFailures] = (0, import_react4.useState)(0);
|
|
1597
|
+
const [nowMs, setNowMs] = (0, import_react4.useState)(() => Date.now());
|
|
1559
1598
|
const [expanded, setExpanded] = (0, import_react4.useState)({});
|
|
1560
1599
|
const [touched, setTouched] = (0, import_react4.useState)({});
|
|
1561
1600
|
const groupsRef = (0, import_react4.useRef)(groups);
|
|
@@ -1633,16 +1672,21 @@ function useMatchDiscovery(opts) {
|
|
|
1633
1672
|
}, POLL_MS);
|
|
1634
1673
|
return () => clearInterval(id);
|
|
1635
1674
|
}, [opts.isOpen, tick, groups]);
|
|
1675
|
+
(0, import_react4.useEffect)(() => {
|
|
1676
|
+
if (!opts.isOpen) return;
|
|
1677
|
+
const id = setInterval(() => setNowMs(Date.now()), CLOCK_MS);
|
|
1678
|
+
return () => clearInterval(id);
|
|
1679
|
+
}, [opts.isOpen]);
|
|
1636
1680
|
const stale = failures >= STALE_AFTER_FAILURES;
|
|
1637
1681
|
const liveCount = (0, import_react4.useMemo)(
|
|
1638
|
-
() => stale ? 0 : Object.values(states).filter((s) => s.phase === "live").length,
|
|
1639
|
-
[states, stale]
|
|
1682
|
+
() => stale ? 0 : Object.values(states).filter((s) => s.phase === "live" && !isReadingStale(s, nowMs)).length,
|
|
1683
|
+
[states, stale, nowMs]
|
|
1640
1684
|
);
|
|
1641
1685
|
const toggleGroup = (0, import_react4.useCallback)((competitionId) => {
|
|
1642
1686
|
setTouched((prev) => ({ ...prev, [competitionId]: true }));
|
|
1643
1687
|
setExpanded((prev) => ({ ...prev, [competitionId]: !prev[competitionId] }));
|
|
1644
1688
|
}, []);
|
|
1645
|
-
return { groups, states, liveCount, stale, expanded, toggleGroup, _tick: tick };
|
|
1689
|
+
return { groups, states, nowMs, liveCount, stale, expanded, toggleGroup, _tick: tick };
|
|
1646
1690
|
}
|
|
1647
1691
|
|
|
1648
1692
|
// src/eventLogContext.ts
|
|
@@ -3000,7 +3044,7 @@ function computeDisplayMinute(minute, clockRunning, receivedAtMs) {
|
|
|
3000
3044
|
return minute + Math.min(Math.max(0, elapsedMinutes), MAX_LIVE_TICK_MINUTES);
|
|
3001
3045
|
}
|
|
3002
3046
|
function useLiveClock(minute, clockRunning, receivedAtMs) {
|
|
3003
|
-
const [
|
|
3047
|
+
const [displayMinute2, setDisplayMinute] = (0, import_react19.useState)(
|
|
3004
3048
|
() => computeDisplayMinute(minute, clockRunning, receivedAtMs)
|
|
3005
3049
|
);
|
|
3006
3050
|
(0, import_react19.useEffect)(() => {
|
|
@@ -3011,7 +3055,7 @@ function useLiveClock(minute, clockRunning, receivedAtMs) {
|
|
|
3011
3055
|
}, TICK_MS);
|
|
3012
3056
|
return () => window.clearInterval(id);
|
|
3013
3057
|
}, [minute, clockRunning, receivedAtMs]);
|
|
3014
|
-
return
|
|
3058
|
+
return displayMinute2;
|
|
3015
3059
|
}
|
|
3016
3060
|
|
|
3017
3061
|
// src/components/dashboard/LiveScoreCard.tsx
|
|
@@ -3043,7 +3087,7 @@ function LiveScoreCard({
|
|
|
3043
3087
|
if (!seedRef.current || seedRef.current.key !== seedKey) {
|
|
3044
3088
|
seedRef.current = { key: seedKey, receivedAtMs: Date.now() };
|
|
3045
3089
|
}
|
|
3046
|
-
const
|
|
3090
|
+
const displayMinute2 = useLiveClock(data.minute, data.clockRunning, seedRef.current.receivedAtMs);
|
|
3047
3091
|
const minuteSuffix = data.minuteLabel.replace(/^\d+/, "");
|
|
3048
3092
|
const scorers = scorersLine(goalEvents);
|
|
3049
3093
|
return /* @__PURE__ */ (0, import_jsx_runtime36.jsxs)("div", { className: "ss-w-fandash-card ss-w-fandash-live-score", children: [
|
|
@@ -3053,7 +3097,7 @@ function LiveScoreCard({
|
|
|
3053
3097
|
/* @__PURE__ */ (0, import_jsx_runtime36.jsx)("span", { className: "ss-w-fandash-live-dot", "aria-hidden": "true" }),
|
|
3054
3098
|
data.statusLabel
|
|
3055
3099
|
] }),
|
|
3056
|
-
/* @__PURE__ */ (0, import_jsx_runtime36.jsx)("span", { className: "ss-w-fandash-live-minute", children: `${
|
|
3100
|
+
/* @__PURE__ */ (0, import_jsx_runtime36.jsx)("span", { className: "ss-w-fandash-live-minute", children: `${displayMinute2}${minuteSuffix}` }),
|
|
3057
3101
|
data.competition ? /* @__PURE__ */ (0, import_jsx_runtime36.jsx)("span", { className: "ss-w-fandash-live-competition", children: data.competition }) : null
|
|
3058
3102
|
] }),
|
|
3059
3103
|
/* @__PURE__ */ (0, import_jsx_runtime36.jsxs)("div", { className: "ss-w-fandash-live-main", children: [
|
|
@@ -3735,12 +3779,14 @@ function Side({ name, crest }) {
|
|
|
3735
3779
|
/* @__PURE__ */ (0, import_jsx_runtime49.jsx)("span", { className: "ss-w-drow-nm", children: name })
|
|
3736
3780
|
] });
|
|
3737
3781
|
}
|
|
3738
|
-
function FixtureRow({ match, state, stale, ui, monitor }) {
|
|
3739
|
-
const
|
|
3782
|
+
function FixtureRow({ match, state, stale, nowMs, ui, monitor }) {
|
|
3783
|
+
const readingStale = isReadingStale(state, nowMs);
|
|
3784
|
+
const isLive = state?.phase === "live" && !stale && !readingStale;
|
|
3740
3785
|
const hasScore = (state?.phase === "live" || state?.phase === "fullTime") && typeof state.homeScore === "number" && typeof state.awayScore === "number";
|
|
3741
|
-
const
|
|
3742
|
-
|
|
3743
|
-
|
|
3786
|
+
const ticked = displayMinute(state, nowMs);
|
|
3787
|
+
const when = state?.isHalfTime && !stale ? ui.halfTime : isLive ? ticked ?? "\u2014" : state?.phase === "live" ? "\u2014" : kickoffLabel(match.kickoffUnix);
|
|
3788
|
+
return /* @__PURE__ */ (0, import_jsx_runtime49.jsxs)("div", { className: isLive || state?.isHalfTime ? "ss-w-drow ss-w-drow--live" : "ss-w-drow", children: [
|
|
3789
|
+
/* @__PURE__ */ (0, import_jsx_runtime49.jsx)("span", { className: isLive || state?.isHalfTime ? "ss-w-drow-when ss-w-drow-when--live" : "ss-w-drow-when", children: when }),
|
|
3744
3790
|
/* @__PURE__ */ (0, import_jsx_runtime49.jsxs)("span", { className: "ss-w-drow-fx", children: [
|
|
3745
3791
|
/* @__PURE__ */ (0, import_jsx_runtime49.jsx)(Side, { name: match.homeTeamName, crest: match.homeTeamImagePath }),
|
|
3746
3792
|
/* @__PURE__ */ (0, import_jsx_runtime49.jsx)("span", { className: "ss-w-drow-dash", children: "\u2013" }),
|
|
@@ -3753,8 +3799,8 @@ function FixtureRow({ match, state, stale, ui, monitor }) {
|
|
|
3753
3799
|
|
|
3754
3800
|
// src/components/CompetitionGroup.tsx
|
|
3755
3801
|
var import_jsx_runtime50 = require("react/jsx-runtime");
|
|
3756
|
-
function CompetitionGroup({ group, open, onToggle, states, stale, ui, monitor, disambiguate = false }) {
|
|
3757
|
-
const own = group.matches.filter((m) => states[m.matchId]
|
|
3802
|
+
function CompetitionGroup({ group, open, onToggle, states, stale, nowMs, ui, monitor, disambiguate = false }) {
|
|
3803
|
+
const own = group.matches.filter((m) => !isTreatedLive(states[m.matchId], stale, nowMs));
|
|
3758
3804
|
const hasLive = own.length !== group.matches.length;
|
|
3759
3805
|
return /* @__PURE__ */ (0, import_jsx_runtime50.jsxs)("div", { className: "ss-w-grp", children: [
|
|
3760
3806
|
/* @__PURE__ */ (0, import_jsx_runtime50.jsxs)("button", { type: "button", className: "ss-w-grp-head", "aria-expanded": open, onClick: onToggle, children: [
|
|
@@ -3770,15 +3816,15 @@ function CompetitionGroup({ group, open, onToggle, states, stale, ui, monitor, d
|
|
|
3770
3816
|
] }),
|
|
3771
3817
|
/* @__PURE__ */ (0, import_jsx_runtime50.jsx)("span", { className: "ss-w-grp-count", children: own.length })
|
|
3772
3818
|
] }),
|
|
3773
|
-
open && /* @__PURE__ */ (0, import_jsx_runtime50.jsx)("div", { className: "ss-w-grp-body", children: own.map((m) => /* @__PURE__ */ (0, import_jsx_runtime50.jsx)(FixtureRow, { match: m, state: states[m.matchId], stale, ui, monitor }, m.matchId)) })
|
|
3819
|
+
open && /* @__PURE__ */ (0, import_jsx_runtime50.jsx)("div", { className: "ss-w-grp-body", children: own.map((m) => /* @__PURE__ */ (0, import_jsx_runtime50.jsx)(FixtureRow, { match: m, state: states[m.matchId], stale, nowMs, ui, monitor }, m.matchId)) })
|
|
3774
3820
|
] });
|
|
3775
3821
|
}
|
|
3776
3822
|
|
|
3777
3823
|
// src/components/TodayPanel.tsx
|
|
3778
3824
|
var import_jsx_runtime51 = require("react/jsx-runtime");
|
|
3779
3825
|
function TodayPanel({ discovery, ui, monitor }) {
|
|
3780
|
-
const { groups, states, stale, expanded, toggleGroup } = discovery;
|
|
3781
|
-
const liveRows = stale ? [] : groups.flatMap((group) => group.matches.filter((m) => states[m.matchId]
|
|
3826
|
+
const { groups, states, nowMs, stale, expanded, toggleGroup } = discovery;
|
|
3827
|
+
const liveRows = stale ? [] : groups.flatMap((group) => group.matches.filter((m) => isTreatedLive(states[m.matchId], stale, nowMs)).map((match) => ({ match, rank: group.rank }))).sort((a, b) => a.rank - b.rank);
|
|
3782
3828
|
const nameCounts = groups.reduce((acc, g) => {
|
|
3783
3829
|
acc[g.competitionName] = (acc[g.competitionName] ?? 0) + 1;
|
|
3784
3830
|
return acc;
|
|
@@ -3796,6 +3842,7 @@ function TodayPanel({ discovery, ui, monitor }) {
|
|
|
3796
3842
|
match,
|
|
3797
3843
|
state: states[match.matchId],
|
|
3798
3844
|
stale,
|
|
3845
|
+
nowMs,
|
|
3799
3846
|
ui,
|
|
3800
3847
|
monitor
|
|
3801
3848
|
},
|
|
@@ -3811,6 +3858,7 @@ function TodayPanel({ discovery, ui, monitor }) {
|
|
|
3811
3858
|
disambiguate: (nameCounts[group.competitionName] ?? 0) > 1,
|
|
3812
3859
|
states,
|
|
3813
3860
|
stale,
|
|
3861
|
+
nowMs,
|
|
3814
3862
|
ui,
|
|
3815
3863
|
monitor
|
|
3816
3864
|
},
|