@sensiblestats/widget-react 0.48.0 → 0.48.2
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 +12 -0
- package/dist/index.cjs +25 -63
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +25 -63
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,17 @@
|
|
|
1
1
|
# @sensiblestats/widget-react
|
|
2
2
|
|
|
3
|
+
## 0.48.2
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- A match that finished more than three and a half hours ago now shows FT and its score instead of its kickoff time and a dash. The Today tab stopped asking about a match once a fixed window after kickoff elapsed, so on a cold load the earliest of the day's matches never had a state at all.
|
|
8
|
+
|
|
9
|
+
## 0.48.1
|
|
10
|
+
|
|
11
|
+
### Patch Changes
|
|
12
|
+
|
|
13
|
+
- A live match no longer loses its minute and drop out of the live section just because its score has not changed recently. The per-match staleness check read a last-changed timestamp as if it were a last-polled one.
|
|
14
|
+
|
|
3
15
|
## 0.48.0
|
|
4
16
|
|
|
5
17
|
### Minor Changes
|
package/dist/index.cjs
CHANGED
|
@@ -1535,56 +1535,22 @@ function writeSchedule(operatorId, localDate, payload, nowMs) {
|
|
|
1535
1535
|
}
|
|
1536
1536
|
|
|
1537
1537
|
// src/dueSet.ts
|
|
1538
|
-
var
|
|
1538
|
+
var TERMINAL_PHASES = /* @__PURE__ */ new Set(["fullTime", "postponed"]);
|
|
1539
1539
|
function dueMatchIds(groups, states, nowMs) {
|
|
1540
1540
|
const due = /* @__PURE__ */ new Set();
|
|
1541
1541
|
for (const group of groups) {
|
|
1542
1542
|
for (const match of group.matches) {
|
|
1543
|
-
|
|
1544
|
-
|
|
1545
|
-
if (
|
|
1543
|
+
if (nowMs - match.kickoffUnix * 1e3 < 0) continue;
|
|
1544
|
+
const phase = states[match.matchId]?.phase;
|
|
1545
|
+
if (phase !== void 0 && TERMINAL_PHASES.has(phase)) continue;
|
|
1546
|
+
due.add(match.matchId);
|
|
1546
1547
|
}
|
|
1547
1548
|
}
|
|
1548
1549
|
return [...due];
|
|
1549
1550
|
}
|
|
1550
1551
|
|
|
1551
|
-
// src/liveClock.ts
|
|
1552
|
-
var STALE_READING_MS = 5 * 6e4;
|
|
1553
|
-
function parseMinute(label) {
|
|
1554
|
-
if (!label) return null;
|
|
1555
|
-
const plus = /^(\d+)\+(\d+)'$/.exec(label);
|
|
1556
|
-
if (plus) return Number(plus[1]) + Number(plus[2]);
|
|
1557
|
-
const plain = /^(\d+)'$/.exec(label);
|
|
1558
|
-
return plain ? Number(plain[1]) : null;
|
|
1559
|
-
}
|
|
1560
|
-
function readingAgeMs(state, nowMs) {
|
|
1561
|
-
if (!state?.stateAsOfUtc) return null;
|
|
1562
|
-
const t = Date.parse(state.stateAsOfUtc);
|
|
1563
|
-
return Number.isNaN(t) ? null : Math.max(0, nowMs - t);
|
|
1564
|
-
}
|
|
1565
|
-
function isReadingStale(state, nowMs) {
|
|
1566
|
-
if (state?.isHalfTime) return false;
|
|
1567
|
-
const age = readingAgeMs(state, nowMs);
|
|
1568
|
-
return age !== null && age > STALE_READING_MS;
|
|
1569
|
-
}
|
|
1570
|
-
function displayMinute(state, nowMs) {
|
|
1571
|
-
if (!state || state.phase !== "live") return null;
|
|
1572
|
-
if (state.isHalfTime) return null;
|
|
1573
|
-
if (isReadingStale(state, nowMs)) return null;
|
|
1574
|
-
const base = parseMinute(state.minuteLabel);
|
|
1575
|
-
if (base === null) return null;
|
|
1576
|
-
const age = readingAgeMs(state, nowMs);
|
|
1577
|
-
if (age === null) return state.minuteLabel ?? null;
|
|
1578
|
-
const advanced = base + Math.floor(age / 6e4);
|
|
1579
|
-
return `${advanced}'`;
|
|
1580
|
-
}
|
|
1581
|
-
function isTreatedLive(state, pollStale, nowMs) {
|
|
1582
|
-
return state?.phase === "live" && !pollStale && !isReadingStale(state, nowMs);
|
|
1583
|
-
}
|
|
1584
|
-
|
|
1585
1552
|
// src/useMatchDiscovery.ts
|
|
1586
1553
|
var POLL_MS = 6e4;
|
|
1587
|
-
var CLOCK_MS = 1e4;
|
|
1588
1554
|
var ROLLOVER_CHECK_MS = 6e4;
|
|
1589
1555
|
var STALE_AFTER_FAILURES = 2;
|
|
1590
1556
|
function localDateOf(now) {
|
|
@@ -1597,7 +1563,6 @@ function useMatchDiscovery(opts) {
|
|
|
1597
1563
|
const [groups, setGroups] = (0, import_react4.useState)([]);
|
|
1598
1564
|
const [states, setStates] = (0, import_react4.useState)({});
|
|
1599
1565
|
const [failures, setFailures] = (0, import_react4.useState)(0);
|
|
1600
|
-
const [nowMs, setNowMs] = (0, import_react4.useState)(() => Date.now());
|
|
1601
1566
|
const [expanded, setExpanded] = (0, import_react4.useState)({});
|
|
1602
1567
|
const [touched, setTouched] = (0, import_react4.useState)({});
|
|
1603
1568
|
const groupsRef = (0, import_react4.useRef)(groups);
|
|
@@ -1675,21 +1640,16 @@ function useMatchDiscovery(opts) {
|
|
|
1675
1640
|
}, POLL_MS);
|
|
1676
1641
|
return () => clearInterval(id);
|
|
1677
1642
|
}, [opts.isOpen, tick, groups]);
|
|
1678
|
-
(0, import_react4.useEffect)(() => {
|
|
1679
|
-
if (!opts.isOpen) return;
|
|
1680
|
-
const id = setInterval(() => setNowMs(Date.now()), CLOCK_MS);
|
|
1681
|
-
return () => clearInterval(id);
|
|
1682
|
-
}, [opts.isOpen]);
|
|
1683
1643
|
const stale = failures >= STALE_AFTER_FAILURES;
|
|
1684
1644
|
const liveCount = (0, import_react4.useMemo)(
|
|
1685
|
-
() => stale ? 0 : Object.values(states).filter((s) => s.phase === "live"
|
|
1686
|
-
[states, stale
|
|
1645
|
+
() => stale ? 0 : Object.values(states).filter((s) => s.phase === "live").length,
|
|
1646
|
+
[states, stale]
|
|
1687
1647
|
);
|
|
1688
1648
|
const toggleGroup = (0, import_react4.useCallback)((competitionId) => {
|
|
1689
1649
|
setTouched((prev) => ({ ...prev, [competitionId]: true }));
|
|
1690
1650
|
setExpanded((prev) => ({ ...prev, [competitionId]: !prev[competitionId] }));
|
|
1691
1651
|
}, []);
|
|
1692
|
-
return { groups, states,
|
|
1652
|
+
return { groups, states, liveCount, stale, expanded, toggleGroup, _tick: tick };
|
|
1693
1653
|
}
|
|
1694
1654
|
|
|
1695
1655
|
// src/eventLogContext.ts
|
|
@@ -3047,7 +3007,7 @@ function computeDisplayMinute(minute, clockRunning, receivedAtMs) {
|
|
|
3047
3007
|
return minute + Math.min(Math.max(0, elapsedMinutes), MAX_LIVE_TICK_MINUTES);
|
|
3048
3008
|
}
|
|
3049
3009
|
function useLiveClock(minute, clockRunning, receivedAtMs) {
|
|
3050
|
-
const [
|
|
3010
|
+
const [displayMinute, setDisplayMinute] = (0, import_react19.useState)(
|
|
3051
3011
|
() => computeDisplayMinute(minute, clockRunning, receivedAtMs)
|
|
3052
3012
|
);
|
|
3053
3013
|
(0, import_react19.useEffect)(() => {
|
|
@@ -3058,7 +3018,7 @@ function useLiveClock(minute, clockRunning, receivedAtMs) {
|
|
|
3058
3018
|
}, TICK_MS);
|
|
3059
3019
|
return () => window.clearInterval(id);
|
|
3060
3020
|
}, [minute, clockRunning, receivedAtMs]);
|
|
3061
|
-
return
|
|
3021
|
+
return displayMinute;
|
|
3062
3022
|
}
|
|
3063
3023
|
|
|
3064
3024
|
// src/components/dashboard/LiveScoreCard.tsx
|
|
@@ -3090,7 +3050,7 @@ function LiveScoreCard({
|
|
|
3090
3050
|
if (!seedRef.current || seedRef.current.key !== seedKey) {
|
|
3091
3051
|
seedRef.current = { key: seedKey, receivedAtMs: Date.now() };
|
|
3092
3052
|
}
|
|
3093
|
-
const
|
|
3053
|
+
const displayMinute = useLiveClock(data.minute, data.clockRunning, seedRef.current.receivedAtMs);
|
|
3094
3054
|
const minuteSuffix = data.minuteLabel.replace(/^\d+/, "");
|
|
3095
3055
|
const scorers = scorersLine(goalEvents);
|
|
3096
3056
|
return /* @__PURE__ */ (0, import_jsx_runtime36.jsxs)("div", { className: "ss-w-fandash-card ss-w-fandash-live-score", children: [
|
|
@@ -3100,7 +3060,7 @@ function LiveScoreCard({
|
|
|
3100
3060
|
/* @__PURE__ */ (0, import_jsx_runtime36.jsx)("span", { className: "ss-w-fandash-live-dot", "aria-hidden": "true" }),
|
|
3101
3061
|
data.statusLabel
|
|
3102
3062
|
] }),
|
|
3103
|
-
/* @__PURE__ */ (0, import_jsx_runtime36.jsx)("span", { className: "ss-w-fandash-live-minute", children: `${
|
|
3063
|
+
/* @__PURE__ */ (0, import_jsx_runtime36.jsx)("span", { className: "ss-w-fandash-live-minute", children: `${displayMinute}${minuteSuffix}` }),
|
|
3104
3064
|
data.competition ? /* @__PURE__ */ (0, import_jsx_runtime36.jsx)("span", { className: "ss-w-fandash-live-competition", children: data.competition }) : null
|
|
3105
3065
|
] }),
|
|
3106
3066
|
/* @__PURE__ */ (0, import_jsx_runtime36.jsxs)("div", { className: "ss-w-fandash-live-main", children: [
|
|
@@ -3782,11 +3742,10 @@ function Side({ name, crest }) {
|
|
|
3782
3742
|
/* @__PURE__ */ (0, import_jsx_runtime49.jsx)("span", { className: "ss-w-drow-nm", children: name })
|
|
3783
3743
|
] });
|
|
3784
3744
|
}
|
|
3785
|
-
function FixtureRow({ match, state, stale,
|
|
3786
|
-
const
|
|
3787
|
-
const isLive = state?.phase === "live" && !stale && !readingStale;
|
|
3745
|
+
function FixtureRow({ match, state, stale, ui, monitor }) {
|
|
3746
|
+
const isLive = state?.phase === "live" && !stale;
|
|
3788
3747
|
const hasScore = (state?.phase === "live" || state?.phase === "fullTime") && typeof state.homeScore === "number" && typeof state.awayScore === "number";
|
|
3789
|
-
const ticked =
|
|
3748
|
+
const ticked = state?.phase === "live" ? state.minuteLabel ?? null : null;
|
|
3790
3749
|
const isFinished = state?.phase === "fullTime";
|
|
3791
3750
|
const when = state?.isHalfTime && !stale ? ui.halfTime : isLive ? ticked ?? "\u2014" : state?.phase === "live" ? "\u2014" : isFinished ? ui.fullTime : kickoffLabel(match.kickoffUnix);
|
|
3792
3751
|
const pin = { kind: "match", matchId: match.matchId };
|
|
@@ -3802,10 +3761,15 @@ function FixtureRow({ match, state, stale, nowMs, ui, monitor }) {
|
|
|
3802
3761
|
] });
|
|
3803
3762
|
}
|
|
3804
3763
|
|
|
3764
|
+
// src/liveClock.ts
|
|
3765
|
+
function isTreatedLive(state, pollStale) {
|
|
3766
|
+
return state?.phase === "live" && !pollStale;
|
|
3767
|
+
}
|
|
3768
|
+
|
|
3805
3769
|
// src/components/CompetitionGroup.tsx
|
|
3806
3770
|
var import_jsx_runtime50 = require("react/jsx-runtime");
|
|
3807
|
-
function CompetitionGroup({ group, open, onToggle, states, stale,
|
|
3808
|
-
const own = group.matches.filter((m) => !isTreatedLive(states[m.matchId], stale
|
|
3771
|
+
function CompetitionGroup({ group, open, onToggle, states, stale, ui, monitor, disambiguate = false }) {
|
|
3772
|
+
const own = group.matches.filter((m) => !isTreatedLive(states[m.matchId], stale));
|
|
3809
3773
|
const hasLive = own.length !== group.matches.length;
|
|
3810
3774
|
if (own.length === 0) {
|
|
3811
3775
|
return null;
|
|
@@ -3824,15 +3788,15 @@ function CompetitionGroup({ group, open, onToggle, states, stale, nowMs, ui, mon
|
|
|
3824
3788
|
] }),
|
|
3825
3789
|
/* @__PURE__ */ (0, import_jsx_runtime50.jsx)("span", { className: "ss-w-grp-count", children: own.length })
|
|
3826
3790
|
] }),
|
|
3827
|
-
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,
|
|
3791
|
+
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)) })
|
|
3828
3792
|
] });
|
|
3829
3793
|
}
|
|
3830
3794
|
|
|
3831
3795
|
// src/components/TodayPanel.tsx
|
|
3832
3796
|
var import_jsx_runtime51 = require("react/jsx-runtime");
|
|
3833
3797
|
function TodayPanel({ discovery, ui, monitor }) {
|
|
3834
|
-
const { groups, states,
|
|
3835
|
-
const liveRows = stale ? [] : groups.flatMap((group) => group.matches.filter((m) => isTreatedLive(states[m.matchId], stale
|
|
3798
|
+
const { groups, states, stale, expanded, toggleGroup } = discovery;
|
|
3799
|
+
const liveRows = stale ? [] : groups.flatMap((group) => group.matches.filter((m) => isTreatedLive(states[m.matchId], stale)).map((match) => ({ match, rank: group.rank }))).sort((a, b) => a.rank - b.rank);
|
|
3836
3800
|
const nameCounts = groups.reduce((acc, g) => {
|
|
3837
3801
|
acc[g.competitionName] = (acc[g.competitionName] ?? 0) + 1;
|
|
3838
3802
|
return acc;
|
|
@@ -3850,7 +3814,6 @@ function TodayPanel({ discovery, ui, monitor }) {
|
|
|
3850
3814
|
match,
|
|
3851
3815
|
state: states[match.matchId],
|
|
3852
3816
|
stale,
|
|
3853
|
-
nowMs,
|
|
3854
3817
|
ui,
|
|
3855
3818
|
monitor
|
|
3856
3819
|
},
|
|
@@ -3866,7 +3829,6 @@ function TodayPanel({ discovery, ui, monitor }) {
|
|
|
3866
3829
|
disambiguate: (nameCounts[group.competitionName] ?? 0) > 1,
|
|
3867
3830
|
states,
|
|
3868
3831
|
stale,
|
|
3869
|
-
nowMs,
|
|
3870
3832
|
ui,
|
|
3871
3833
|
monitor
|
|
3872
3834
|
},
|