@sensiblestats/widget-react 0.56.0 → 0.57.0
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 +14 -0
- package/dist/index.cjs +73 -15
- 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 +73 -15
- package/dist/index.js.map +1 -1
- package/dist/styles.css +7 -1
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,19 @@
|
|
|
1
1
|
# @sensiblestats/widget-react
|
|
2
2
|
|
|
3
|
+
## 0.57.0
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- Guess the XI now reads as a real formation: the shape is named in text (e.g. "4-3-3") and a
|
|
8
|
+
position label (GK, CB, CM, ST…) sits under every flag, so the goalkeeper's end is unmistakable.
|
|
9
|
+
Wrong-guess reveals show the player's surname on the pitch instead of a raw slot code.
|
|
10
|
+
|
|
11
|
+
Games/Leaderboard tabs are now gated on the operator actually having games for the day. The games
|
|
12
|
+
list endpoint returns 200 with an empty list for non-games operators (only a hard 404 means the
|
|
13
|
+
feature is off), so the hook now enables the tabs from a non-empty list rather than defaulting them
|
|
14
|
+
on — which had shown the tabs on every operator. Also fixes the tabs never appearing when the
|
|
15
|
+
conversation initialises after first render (the one-shot fetch was dropped under StrictMode).
|
|
16
|
+
|
|
3
17
|
## 0.56.0
|
|
4
18
|
|
|
5
19
|
### Minor Changes
|
package/dist/index.cjs
CHANGED
|
@@ -1724,18 +1724,19 @@ function computeGameDay(timezone) {
|
|
|
1724
1724
|
return new Intl.DateTimeFormat("en-CA", { timeZone: timezone }).format(/* @__PURE__ */ new Date());
|
|
1725
1725
|
}
|
|
1726
1726
|
function useGames(conversation, { activeTab, operatorTimezone }) {
|
|
1727
|
-
const [enabled, setEnabled] = (0, import_react6.useState)(
|
|
1727
|
+
const [enabled, setEnabled] = (0, import_react6.useState)(false);
|
|
1728
1728
|
const [games, setGames] = (0, import_react6.useState)([]);
|
|
1729
1729
|
const [loading, setLoading] = (0, import_react6.useState)(false);
|
|
1730
1730
|
const [refreshCount, setRefreshCount] = (0, import_react6.useState)(0);
|
|
1731
1731
|
const inFlightRef = (0, import_react6.useRef)(false);
|
|
1732
|
-
const
|
|
1732
|
+
const hardOffRef = (0, import_react6.useRef)(false);
|
|
1733
1733
|
const conversationRef = (0, import_react6.useRef)(conversation);
|
|
1734
1734
|
conversationRef.current = conversation;
|
|
1735
1735
|
const tzRef = (0, import_react6.useRef)(operatorTimezone ?? DEFAULT_TZ);
|
|
1736
1736
|
tzRef.current = operatorTimezone ?? DEFAULT_TZ;
|
|
1737
|
+
const ready = conversation != null;
|
|
1737
1738
|
(0, import_react6.useEffect)(() => {
|
|
1738
|
-
if (
|
|
1739
|
+
if (hardOffRef.current) return;
|
|
1739
1740
|
let active = true;
|
|
1740
1741
|
const doFetch = async () => {
|
|
1741
1742
|
const conv = conversationRef.current;
|
|
@@ -1747,10 +1748,12 @@ function useGames(conversation, { activeTab, operatorTimezone }) {
|
|
|
1747
1748
|
const result = await conv.getGames(gameDay);
|
|
1748
1749
|
if (!active) return;
|
|
1749
1750
|
if (result === null) {
|
|
1750
|
-
|
|
1751
|
+
hardOffRef.current = true;
|
|
1751
1752
|
setEnabled(false);
|
|
1753
|
+
setGames([]);
|
|
1752
1754
|
} else {
|
|
1753
1755
|
setGames(result.games);
|
|
1756
|
+
setEnabled(result.games.length > 0);
|
|
1754
1757
|
}
|
|
1755
1758
|
} catch {
|
|
1756
1759
|
} finally {
|
|
@@ -1762,7 +1765,7 @@ function useGames(conversation, { activeTab, operatorTimezone }) {
|
|
|
1762
1765
|
let intervalId;
|
|
1763
1766
|
if (activeTab === "games") {
|
|
1764
1767
|
intervalId = window.setInterval(() => {
|
|
1765
|
-
if (
|
|
1768
|
+
if (hardOffRef.current) {
|
|
1766
1769
|
window.clearInterval(intervalId);
|
|
1767
1770
|
return;
|
|
1768
1771
|
}
|
|
@@ -1771,9 +1774,10 @@ function useGames(conversation, { activeTab, operatorTimezone }) {
|
|
|
1771
1774
|
}
|
|
1772
1775
|
return () => {
|
|
1773
1776
|
active = false;
|
|
1777
|
+
inFlightRef.current = false;
|
|
1774
1778
|
if (intervalId !== void 0) window.clearInterval(intervalId);
|
|
1775
1779
|
};
|
|
1776
|
-
}, [activeTab, refreshCount]);
|
|
1780
|
+
}, [activeTab, refreshCount, ready]);
|
|
1777
1781
|
const refresh = (0, import_react6.useCallback)(() => {
|
|
1778
1782
|
setRefreshCount((c) => c + 1);
|
|
1779
1783
|
}, []);
|
|
@@ -4970,6 +4974,47 @@ function toRows(positions) {
|
|
|
4970
4974
|
});
|
|
4971
4975
|
return [...byRow.keys()].sort((a, b) => b - a).map((k) => byRow.get(k));
|
|
4972
4976
|
}
|
|
4977
|
+
function formationLabel(rows) {
|
|
4978
|
+
if (rows.length < 2) return "";
|
|
4979
|
+
const gkFirst = [...rows].reverse();
|
|
4980
|
+
return gkFirst.slice(1).map((r) => r.length).join("-");
|
|
4981
|
+
}
|
|
4982
|
+
var FORMATION_POSITIONS = {
|
|
4983
|
+
"4-4-2": [["GK"], ["LB", "CB", "CB", "RB"], ["LM", "CM", "CM", "RM"], ["ST", "ST"]],
|
|
4984
|
+
"4-3-3": [["GK"], ["LB", "CB", "CB", "RB"], ["CM", "CM", "CM"], ["LW", "ST", "RW"]],
|
|
4985
|
+
"4-2-3-1": [["GK"], ["LB", "CB", "CB", "RB"], ["CDM", "CDM"], ["LW", "CAM", "RW"], ["ST"]],
|
|
4986
|
+
"4-1-4-1": [["GK"], ["LB", "CB", "CB", "RB"], ["CDM"], ["LM", "CM", "CM", "RM"], ["ST"]],
|
|
4987
|
+
"4-5-1": [["GK"], ["LB", "CB", "CB", "RB"], ["LM", "CM", "CM", "CM", "RM"], ["ST"]],
|
|
4988
|
+
"4-4-1-1": [["GK"], ["LB", "CB", "CB", "RB"], ["LM", "CM", "CM", "RM"], ["CAM"], ["ST"]],
|
|
4989
|
+
"4-1-2-1-2": [["GK"], ["LB", "CB", "CB", "RB"], ["CDM"], ["LM", "RM"], ["CAM"], ["ST", "ST"]],
|
|
4990
|
+
"3-5-2": [["GK"], ["CB", "CB", "CB"], ["LWB", "CM", "CM", "CM", "RWB"], ["ST", "ST"]],
|
|
4991
|
+
"3-4-3": [["GK"], ["CB", "CB", "CB"], ["LM", "CM", "CM", "RM"], ["LW", "ST", "RW"]],
|
|
4992
|
+
"5-3-2": [["GK"], ["LWB", "CB", "CB", "CB", "RWB"], ["CM", "CM", "CM"], ["ST", "ST"]],
|
|
4993
|
+
"5-4-1": [["GK"], ["LWB", "CB", "CB", "CB", "RWB"], ["LM", "CM", "CM", "RM"], ["ST"]]
|
|
4994
|
+
};
|
|
4995
|
+
function lineFallback(lineIdx, totalLines) {
|
|
4996
|
+
if (lineIdx === 0) return "GK";
|
|
4997
|
+
if (lineIdx === totalLines - 1) return "FWD";
|
|
4998
|
+
if (lineIdx === 1) return "DEF";
|
|
4999
|
+
return "MID";
|
|
5000
|
+
}
|
|
5001
|
+
function positionsBySlot(formation, rowsAttackFirst) {
|
|
5002
|
+
const gkFirst = [...rowsAttackFirst].reverse();
|
|
5003
|
+
const template = FORMATION_POSITIONS[formation];
|
|
5004
|
+
const map = /* @__PURE__ */ new Map();
|
|
5005
|
+
gkFirst.forEach((line, li) => {
|
|
5006
|
+
line.forEach((entry, ci) => {
|
|
5007
|
+
if (!entry.slot) return;
|
|
5008
|
+
const label = template?.[li]?.[ci] ?? lineFallback(li, gkFirst.length);
|
|
5009
|
+
map.set(entry.slot, label);
|
|
5010
|
+
});
|
|
5011
|
+
});
|
|
5012
|
+
return map;
|
|
5013
|
+
}
|
|
5014
|
+
function surname(name) {
|
|
5015
|
+
const parts = name.trim().split(/\s+/).filter(Boolean);
|
|
5016
|
+
return parts.length ? parts[parts.length - 1] : name;
|
|
5017
|
+
}
|
|
4973
5018
|
function GuessTheXiGame({
|
|
4974
5019
|
config,
|
|
4975
5020
|
result,
|
|
@@ -4983,9 +5028,11 @@ function GuessTheXiGame({
|
|
|
4983
5028
|
const solved = clientState?.status === "solved";
|
|
4984
5029
|
const guessedTeamIds = new Set(clientState?.guessHistory.map((g) => g.teamId) ?? []);
|
|
4985
5030
|
const rows = config ? toRows(config.positions) : [];
|
|
4986
|
-
const
|
|
5031
|
+
const formation = config?.hasRealFormation ? formationLabel(rows) : "";
|
|
5032
|
+
const posBySlot = config?.hasRealFormation ? positionsBySlot(formation, rows) : /* @__PURE__ */ new Map();
|
|
5033
|
+
const nameBySlot = /* @__PURE__ */ new Map();
|
|
4987
5034
|
(clientState?.earnedReveals ?? []).forEach((r) => {
|
|
4988
|
-
if (r.slot)
|
|
5035
|
+
if (r.slot && r.type !== "position") nameBySlot.set(r.slot, r.value);
|
|
4989
5036
|
});
|
|
4990
5037
|
const filtered = config && !isOver ? config.teamPool.filter(
|
|
4991
5038
|
(t) => !guessedTeamIds.has(t.teamId) && t.teamName.toLowerCase().includes(query.toLowerCase())
|
|
@@ -4998,13 +5045,16 @@ function GuessTheXiGame({
|
|
|
4998
5045
|
onSubmit(JSON.stringify({ guesses: newGuesses }));
|
|
4999
5046
|
}
|
|
5000
5047
|
const wrongGuesses = (clientState?.guessHistory ?? []).filter((g) => !g.wasCorrect);
|
|
5001
|
-
const revealCount =
|
|
5048
|
+
const revealCount = nameBySlot.size;
|
|
5002
5049
|
return /* @__PURE__ */ (0, import_jsx_runtime59.jsxs)("div", { className: "ss-w-gxi", "data-testid": "game-screen-guess-the-xi", children: [
|
|
5003
5050
|
!config && /* @__PURE__ */ (0, import_jsx_runtime59.jsx)("div", { className: "ss-w-gscreen-body", children: "Loading\u2026" }),
|
|
5004
5051
|
config && /* @__PURE__ */ (0, import_jsx_runtime59.jsxs)(import_jsx_runtime59.Fragment, { children: [
|
|
5005
5052
|
/* @__PURE__ */ (0, import_jsx_runtime59.jsxs)("div", { className: "ss-w-gxi-head", children: [
|
|
5006
5053
|
/* @__PURE__ */ (0, import_jsx_runtime59.jsxs)("div", { children: [
|
|
5007
|
-
/* @__PURE__ */ (0, import_jsx_runtime59.
|
|
5054
|
+
/* @__PURE__ */ (0, import_jsx_runtime59.jsxs)("div", { className: "ss-w-gxi-title", children: [
|
|
5055
|
+
"Guess the XI",
|
|
5056
|
+
formation && /* @__PURE__ */ (0, import_jsx_runtime59.jsx)("span", { className: "ss-w-gxi-form", "aria-label": `Formation ${formation}`, children: formation })
|
|
5057
|
+
] }),
|
|
5008
5058
|
/* @__PURE__ */ (0, import_jsx_runtime59.jsxs)("div", { className: "ss-w-gxi-sub", children: [
|
|
5009
5059
|
"Name the team \xB7 flags are each player's nationality \xB7 ",
|
|
5010
5060
|
config.poolLabel
|
|
@@ -5017,18 +5067,20 @@ function GuessTheXiGame({
|
|
|
5017
5067
|
] }),
|
|
5018
5068
|
/* @__PURE__ */ (0, import_jsx_runtime59.jsxs)("div", { className: "ss-w-gscroll", children: [
|
|
5019
5069
|
rows.length > 0 && /* @__PURE__ */ (0, import_jsx_runtime59.jsx)("div", { className: "ss-w-gxi-pitch", children: rows.map((row, r) => /* @__PURE__ */ (0, import_jsx_runtime59.jsx)("div", { className: "ss-w-gxi-prow", children: row.map((pos, c) => {
|
|
5020
|
-
const
|
|
5070
|
+
const posLabel = pos.slot ? posBySlot.get(pos.slot) ?? "" : "";
|
|
5071
|
+
const name = pos.slot ? nameBySlot.get(pos.slot) : void 0;
|
|
5021
5072
|
return /* @__PURE__ */ (0, import_jsx_runtime59.jsxs)("div", { className: "ss-w-gxi-slot", children: [
|
|
5022
5073
|
/* @__PURE__ */ (0, import_jsx_runtime59.jsx)(
|
|
5023
5074
|
"div",
|
|
5024
5075
|
{
|
|
5025
|
-
className: `ss-w-gxi-flag${
|
|
5076
|
+
className: `ss-w-gxi-flag${name ? " ss-w-gxi-flag--revealed" : ""}`,
|
|
5026
5077
|
title: pos.nationality,
|
|
5027
|
-
"aria-label":
|
|
5078
|
+
"aria-label": `${posLabel ? posLabel + " \u2014 " : ""}${pos.nationality}${name ? " \u2014 " + name : ""}`,
|
|
5028
5079
|
children: nationalityFlag(pos.nationality)
|
|
5029
5080
|
}
|
|
5030
5081
|
),
|
|
5031
|
-
|
|
5082
|
+
posLabel && /* @__PURE__ */ (0, import_jsx_runtime59.jsx)("span", { className: "ss-w-gxi-pos", children: posLabel }),
|
|
5083
|
+
name && /* @__PURE__ */ (0, import_jsx_runtime59.jsx)("span", { className: "ss-w-gxi-flag-lab", children: surname(name) })
|
|
5032
5084
|
] }, `${pos.slot}-${c}`);
|
|
5033
5085
|
}) }, r)) }),
|
|
5034
5086
|
revealCount > 0 && !isOver && /* @__PURE__ */ (0, import_jsx_runtime59.jsxs)("div", { className: "ss-w-gxi-revnote", children: [
|
|
@@ -7189,7 +7241,9 @@ var WIDGET_CSS = `.ss-w-root { all: revert; }
|
|
|
7189
7241
|
.ss-w-gxi { display: flex; flex-direction: column; gap: 12px; }
|
|
7190
7242
|
/* Header */
|
|
7191
7243
|
.ss-w-gxi-head { display: flex; align-items: flex-start; gap: 8px; }
|
|
7192
|
-
.ss-w-gxi-title { font-size: 18px; font-weight: 800; text-transform: uppercase; letter-spacing: .02em; color: var(--ss-w-ink); line-height: 1; }
|
|
7244
|
+
.ss-w-gxi-title { font-size: 18px; font-weight: 800; text-transform: uppercase; letter-spacing: .02em; color: var(--ss-w-ink); line-height: 1; display: flex; align-items: center; gap: 8px; flex-wrap: wrap; }
|
|
7245
|
+
/* Formation pill next to the title, e.g. "4-2-3-1". */
|
|
7246
|
+
.ss-w-gxi-form { font-size: 12px; font-weight: 800; letter-spacing: .06em; color: var(--ss-w-accent); background: color-mix(in srgb, var(--ss-w-accent) 12%, var(--ss-w-panel)); border: 1px solid color-mix(in srgb, var(--ss-w-accent) 26%, var(--ss-w-line)); border-radius: 999px; padding: 2px 9px; font-variant-numeric: tabular-nums; }
|
|
7193
7247
|
.ss-w-gxi-sub { font-size: 11px; color: var(--ss-w-faint); margin-top: 4px; }
|
|
7194
7248
|
.ss-w-gxi-left { margin-left: auto; flex: none; font-size: 11px; font-weight: 700; color: var(--ss-w-accent); background: color-mix(in srgb, var(--ss-w-accent) 11%, var(--ss-w-panel)); border: 1px solid color-mix(in srgb, var(--ss-w-accent) 22%, var(--ss-w-line)); border-radius: 999px; padding: 4px 10px; white-space: nowrap; }
|
|
7195
7249
|
/* Pitch: a flag per slot laid out in formation rows on a green field. A real min-height plus
|
|
@@ -7208,6 +7262,10 @@ var WIDGET_CSS = `.ss-w-root { all: revert; }
|
|
|
7208
7262
|
.ss-w-gxi-flag { width: 32px; height: 32px; flex: none; border-radius: 50%; display: grid; place-items: center; font-size: 17px; line-height: 1; background: rgba(9, 12, 10, .55); border: 1px solid rgba(255,255,255,.16); box-shadow: 0 1px 3px rgba(0,0,0,.3); overflow: hidden; }
|
|
7209
7263
|
/* A slot whose player has been revealed by a wrong guess. */
|
|
7210
7264
|
.ss-w-gxi-flag--revealed { border-color: var(--ss-w-accent); box-shadow: 0 0 0 2px color-mix(in srgb, var(--ss-w-accent) 45%, transparent); }
|
|
7265
|
+
/* Position label shown under every flag (GK, CB, CM, ST\u2026) \u2014 reads the formation and marks the
|
|
7266
|
+
keeper's end. Muted so it sits behind the flag, not competing with it. */
|
|
7267
|
+
.ss-w-gxi-pos { font-size: 8.5px; line-height: 1.05; font-weight: 800; letter-spacing: .04em; text-align: center; color: rgba(255,255,255,.82); text-shadow: 0 1px 2px rgba(0,0,0,.5); }
|
|
7268
|
+
/* Player surname, shown under the position once a wrong guess reveals it \u2014 in the accent colour. */
|
|
7211
7269
|
.ss-w-gxi-flag-lab { font-size: 8px; line-height: 1.05; font-weight: 700; text-align: center; color: var(--ss-w-accent); max-width: 46px; word-break: break-word; }
|
|
7212
7270
|
.ss-w-gxi-revnote { font-size: 11px; color: var(--ss-w-faint); }
|
|
7213
7271
|
/* Wrong-guess trail */
|