chesspal 0.1.0 → 0.2.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/README.md +45 -82
- package/dist/cli/chat-hook.js +1 -1
- package/dist/cli/command-line.d.ts +1 -2
- package/dist/cli/command-line.js +2 -3
- package/dist/commands/agents.d.ts +26 -0
- package/dist/commands/agents.js +72 -0
- package/dist/commands/ai-move.d.ts +5 -14
- package/dist/commands/ai-move.js +28 -35
- package/dist/commands/ai-say.d.ts +3 -1
- package/dist/commands/ai-say.js +24 -6
- package/dist/commands/analyse-game.d.ts +4 -0
- package/dist/commands/analyse-game.js +43 -16
- package/dist/commands/annotate-game.d.ts +4 -0
- package/dist/commands/annotate-game.js +65 -12
- package/dist/commands/answer-lines.d.ts +28 -0
- package/dist/commands/answer-lines.js +144 -0
- package/dist/commands/coach.d.ts +45 -0
- package/dist/commands/coach.js +344 -0
- package/dist/commands/context.d.ts +16 -5
- package/dist/commands/context.js +1 -0
- package/dist/commands/delete-game.d.ts +7 -0
- package/dist/commands/delete-game.js +30 -0
- package/dist/commands/delete-review.d.ts +8 -0
- package/dist/commands/delete-review.js +34 -0
- package/dist/commands/drawings.d.ts +0 -2
- package/dist/commands/drawings.js +5 -3
- package/dist/commands/end-game.js +5 -2
- package/dist/commands/export-game.d.ts +1 -1
- package/dist/commands/export-game.js +4 -3
- package/dist/commands/game-summary.d.ts +21 -38
- package/dist/commands/game-summary.js +30 -167
- package/dist/commands/index.d.ts +15 -0
- package/dist/commands/index.js +47 -2
- package/dist/commands/init.d.ts +3 -3
- package/dist/commands/init.js +4 -4
- package/dist/commands/lead-input.d.ts +8 -0
- package/dist/commands/lead-input.js +21 -0
- package/dist/commands/look.js +4 -1
- package/dist/commands/new-game.d.ts +2 -3
- package/dist/commands/new-game.js +7 -4
- package/dist/commands/play-move.d.ts +8 -15
- package/dist/commands/play-move.js +5 -13
- package/dist/commands/player-move.d.ts +2 -6
- package/dist/commands/player-move.js +13 -6
- package/dist/commands/player-say.d.ts +1 -1
- package/dist/commands/player-say.js +1 -1
- package/dist/commands/ponder.d.ts +10 -0
- package/dist/commands/ponder.js +33 -21
- package/dist/commands/position.d.ts +1 -1
- package/dist/commands/position.js +3 -1
- package/dist/commands/profiles.d.ts +13 -4
- package/dist/commands/profiles.js +49 -15
- package/dist/commands/registry.d.ts +8 -3
- package/dist/commands/registry.js +12 -11
- package/dist/commands/say.d.ts +1 -1
- package/dist/commands/say.js +16 -16
- package/dist/commands/speech.d.ts +8 -0
- package/dist/commands/speech.js +20 -0
- package/dist/commands/status.d.ts +9 -3
- package/dist/commands/status.js +40 -15
- package/dist/commands/study-games.js +1 -1
- package/dist/commands/study.js +3 -19
- package/dist/core/facts.js +15 -15
- package/dist/core/game.d.ts +3 -1
- package/dist/core/game.js +8 -4
- package/dist/core/illegal.js +1 -1
- package/dist/core/index.d.ts +5 -4
- package/dist/core/index.js +5 -4
- package/dist/core/lead.d.ts +15 -0
- package/dist/core/lead.js +40 -0
- package/dist/core/levels.d.ts +1 -1
- package/dist/core/levels.js +1 -1
- package/dist/core/moods.d.ts +0 -2
- package/dist/core/moods.js +2 -2
- package/dist/core/openings.d.ts +0 -2
- package/dist/core/openings.js +1 -1
- package/dist/core/profile.js +1 -1
- package/dist/core/record.d.ts +1 -1
- package/dist/core/record.js +1 -1
- package/dist/core/review.d.ts +9 -3
- package/dist/core/review.js +29 -9
- package/dist/games/analysis.d.ts +1 -1
- package/dist/games/archive.d.ts +24 -3
- package/dist/games/archive.js +96 -22
- package/dist/games/coach.d.ts +57 -0
- package/dist/games/coach.js +144 -0
- package/dist/games/current-game.d.ts +2 -0
- package/dist/games/current-game.js +7 -1
- package/dist/games/files.js +1 -0
- package/dist/games/index.d.ts +4 -3
- package/dist/games/index.js +4 -3
- package/dist/games/talk.d.ts +1 -5
- package/dist/games/talk.js +14 -14
- package/dist/home/open-folder.js +1 -1
- package/dist/host-files/{ponder-agents.d.ts → agents.d.ts} +10 -2
- package/dist/host-files/agents.js +53 -0
- package/dist/host-files/host-files.d.ts +7 -2
- package/dist/host-files/host-files.js +27 -12
- package/dist/host-files/index.d.ts +2 -2
- package/dist/host-files/index.js +3 -3
- package/dist/index.d.ts +1 -3
- package/dist/index.js +1 -0
- package/dist/page/app/anchor.js +88 -0
- package/dist/page/{app.js → app/app.js} +112 -230
- package/dist/page/app/keys.js +94 -0
- package/dist/page/app/phase.js +30 -0
- package/dist/page/app/served.js +73 -0
- package/dist/page/avatars/avatars.js +19 -0
- package/dist/page/avatars/thought.js +10 -0
- package/dist/page/base.css +160 -0
- package/dist/page/board/board.css +959 -0
- package/dist/page/board/board.js +125 -0
- package/dist/page/{explain.js → board/explain.js} +1 -1
- package/dist/page/board/marks.js +160 -0
- package/dist/page/board/motion.js +71 -0
- package/dist/page/board/pointer.js +239 -0
- package/dist/page/board/squares.js +26 -0
- package/dist/page/dialogs/analyses.js +195 -0
- package/dist/page/{dialog.js → dialogs/dialog.js} +1 -1
- package/dist/page/dialogs/dialogs.css +1228 -0
- package/dist/page/{new-game-dialog.js → dialogs/new-game.js} +3 -3
- package/dist/page/{profile-dialogs.js → dialogs/profiles.js} +40 -11
- package/dist/page/dialogs/search.js +31 -0
- package/dist/page/dialogs/settings.js +42 -0
- package/dist/page/dialogs/studied.js +39 -0
- package/dist/page/dialogs/study.js +51 -0
- package/dist/page/{badges.js → game/badges.js} +1 -1
- package/dist/page/{browse.js → game/browse.js} +1 -1
- package/dist/page/game/card-fold.js +40 -0
- package/dist/page/game/column.js +51 -0
- package/dist/page/{ending.js → game/ending.js} +2 -2
- package/dist/page/game/game.css +577 -0
- package/dist/page/{move-list.js → game/move-list.js} +10 -6
- package/dist/page/{plies.js → game/plies.js} +30 -10
- package/dist/page/game/result-tile.js +14 -0
- package/dist/page/helps/helps.css +689 -0
- package/dist/page/helps/helps.js +220 -0
- package/dist/page/helps/view.js +127 -0
- package/dist/page/layout/fold-tab.js +11 -0
- package/dist/page/{folds.js → layout/folds.js} +1 -1
- package/dist/page/{header.js → layout/header.js} +5 -4
- package/dist/page/layout/layout.css +834 -0
- package/dist/page/lead/lead.js +82 -0
- package/dist/page/lead/view.js +38 -0
- package/dist/page/main.js +2 -2
- package/dist/page/pieces/CREDITS.md +12 -29
- package/dist/page/pieces/capablanca-small.svg +20 -0
- package/dist/page/pieces/capablanca.svg +22 -0
- package/dist/page/pieces/nitida-small.svg +3 -2
- package/dist/page/pieces/nitida.svg +3 -2
- package/dist/page/{pieces.js → pieces/pieces.js} +2 -2
- package/dist/page/pieces/torretta-small.svg +15 -14
- package/dist/page/pieces/torretta.svg +17 -15
- package/dist/page/{player-card.js → players/card.js} +2 -6
- package/dist/page/{clock-time.js → players/clock-time.js} +2 -2
- package/dist/page/{profile.js → profile/profile.js} +9 -5
- package/dist/page/{settings.js → profile/settings.js} +5 -4
- package/dist/page/style.css +9 -4398
- package/dist/page/talk/talk.css +958 -0
- package/dist/page/{talk.js → talk/talk.js} +39 -46
- package/dist/page/text.js +137 -19
- package/dist/page/theme.css +292 -0
- package/dist/profiles/index.d.ts +1 -1
- package/dist/profiles/profiles.d.ts +15 -4
- package/dist/profiles/profiles.js +24 -7
- package/dist/server/bell.d.ts +10 -2
- package/dist/server/bell.js +28 -12
- package/dist/server/caller.d.ts +14 -3
- package/dist/server/caller.js +44 -11
- package/dist/server/mcp.d.ts +1 -2
- package/dist/server/mcp.js +42 -14
- package/dist/server/page/analyses.d.ts +23 -0
- package/dist/server/page/analyses.js +65 -0
- package/dist/server/page/files.d.ts +5 -0
- package/dist/server/page/files.js +92 -0
- package/dist/server/page/game.d.ts +6 -0
- package/dist/server/page/game.js +381 -0
- package/dist/server/{http.d.ts → page/http.d.ts} +13 -0
- package/dist/server/{http.js → page/http.js} +11 -0
- package/dist/server/{page.d.ts → page/page.d.ts} +8 -13
- package/dist/server/page/page.js +229 -0
- package/dist/server/{profile-routes.d.ts → page/profiles.d.ts} +2 -2
- package/dist/server/{profile-routes.js → page/profiles.js} +47 -18
- package/instructions/analysis.md +4 -0
- package/instructions/analyst-agent.md +8 -0
- package/instructions/analyst.md +3 -7
- package/instructions/coach.md +11 -0
- package/instructions/opponent.md +4 -4
- package/package.json +1 -1
- package/dist/host-files/ponder-agents.js +0 -40
- package/dist/page/avatars.js +0 -16
- package/dist/page/board.js +0 -566
- package/dist/page/fold-tab.js +0 -9
- package/dist/page/game-column.js +0 -44
- package/dist/page/helps-view.js +0 -51
- package/dist/page/helps.js +0 -90
- package/dist/page/pieces/cburnett-small.svg +0 -20
- package/dist/page/pieces/cburnett.svg +0 -20
- package/dist/page/settings-panel.js +0 -32
- package/dist/page/studied.js +0 -14
- package/dist/page/study-dialog.js +0 -58
- package/dist/server/analyses.d.ts +0 -17
- package/dist/server/analyses.js +0 -46
- package/dist/server/page.js +0 -657
- /package/dist/page/{torretta.js → avatars/torretta.js} +0 -0
- /package/dist/page/{opening-book.js → game/opening-book.js} +0 -0
- /package/dist/page/{model-name.js → players/model-name.js} +0 -0
- /package/dist/page/{sounds.js → sounds/sounds.js} +0 -0
- /package/dist/page/{voice.js → talk/voice.js} +0 -0
|
@@ -0,0 +1,195 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "preact/jsx-runtime";
|
|
2
|
+
// The list of the analyses (D-92): the games analysed or brought to study, found by a search, to open again, analyse again or delete, each row with its
|
|
3
|
+
// menu over the dialog and the question it asks in its place.
|
|
4
|
+
import { useEffect, useLayoutEffect, useRef, useState } from "preact/hooks";
|
|
5
|
+
import { ResultTile } from "../game/result-tile.js";
|
|
6
|
+
import { signature } from "../players/model-name.js";
|
|
7
|
+
import { searched } from "./search.js";
|
|
8
|
+
import { accountSide, playerIn, studiedSides } from "./studied.js";
|
|
9
|
+
/** The list, read anew at each opening: the search, the row that loads a game, then a row for each game found, with its menu or its question. */
|
|
10
|
+
export function Analyses({ onStudy, onLoad, you, accounts, ai, text }) {
|
|
11
|
+
const words = text.study;
|
|
12
|
+
const [analyses, reload] = useAnalyses();
|
|
13
|
+
const [query, setQuery] = useState("");
|
|
14
|
+
const [menu, setMenu] = useState(undefined);
|
|
15
|
+
const [asking, setAsking] = useState(undefined);
|
|
16
|
+
const [busy, setBusy] = useState(false);
|
|
17
|
+
const [problem, setProblem] = useState(undefined);
|
|
18
|
+
useClosing(menu !== undefined, () => setMenu(undefined));
|
|
19
|
+
useClosing(asking !== undefined, () => setAsking(undefined));
|
|
20
|
+
const act = async (name, asked) => {
|
|
21
|
+
setBusy(true);
|
|
22
|
+
const refused = asked === "remove" ? await deletion("/api/delete-game", name) : await deletion("/api/delete-review", name);
|
|
23
|
+
// Analysing again opens the game without its review: the chat's analyst goes over it (D-89).
|
|
24
|
+
const after = refused === undefined && asked === "redo" ? await onStudy({ game: name }) : refused;
|
|
25
|
+
setBusy(false);
|
|
26
|
+
setAsking(undefined);
|
|
27
|
+
setProblem(after);
|
|
28
|
+
reload();
|
|
29
|
+
};
|
|
30
|
+
const open = (name) => {
|
|
31
|
+
setBusy(true);
|
|
32
|
+
void onStudy({ game: name }).then((refused) => {
|
|
33
|
+
setBusy(false);
|
|
34
|
+
setProblem(refused);
|
|
35
|
+
});
|
|
36
|
+
};
|
|
37
|
+
const rows = analyses.map((item) => {
|
|
38
|
+
const named = namedSides(item, you, accounts, ai, text);
|
|
39
|
+
// How it went only for the player's own games: played here, or loaded and named by their name on the site (D-93).
|
|
40
|
+
const played = item.name.startsWith("games/") ? named.you : accountSide(tagsOf(item), accounts);
|
|
41
|
+
const outcome = played === undefined ? undefined : outcomeOf(item.result, played);
|
|
42
|
+
return { item, named, outcome };
|
|
43
|
+
});
|
|
44
|
+
const found = searched(rows, query, ({ item, named, outcome }) => searchText(item, named.sides, outcome, text));
|
|
45
|
+
return (_jsxs("div", { class: "analyses", children: [_jsxs("label", { class: "search", children: [_jsx("svg", { class: "icon", viewBox: "0 0 24 24", "aria-hidden": "true", children: _jsx("path", { d: "M10.5 17A6.5 6.5 0 1 0 10.5 4A6.5 6.5 0 0 0 10.5 17ZM15.5 15.5L20 20" }) }), _jsx("input", { type: "search", value: query, placeholder: words.search, "aria-label": words.search, spellcheck: false, onInput: (event) => setQuery(event.currentTarget.value) })] }), _jsxs("button", { class: "load-row", type: "button", onClick: onLoad, children: [_jsx("span", { class: "plus", "aria-hidden": "true", children: _jsx("svg", { class: "icon", viewBox: "0 0 24 24", children: _jsx("path", { d: "M12 5V19M5 12H19" }) }) }), _jsxs("span", { children: [_jsx("b", { children: words.bring }), _jsx("small", { children: words.bringNote })] })] }), problem === undefined ? null : _jsx("p", { class: "problem", role: "alert", children: problem }), analyses.length > 0 && found.length === 0 ? _jsx("p", { class: "none", children: words.noMatch }) : null, found.length === 0 ? null : (_jsx("ul", { class: "games", "aria-label": words.yours, children: found.map(({ item, named, outcome }) => asking?.name === item.name
|
|
46
|
+
? (_jsx(Question, { asked: asking.asked, game: named.sides, when: dayOf(item.date, text.language), by: by(item), busy: busy, onYes: () => void act(item.name, asking.asked), onNo: () => setAsking(undefined), words: words }, item.name))
|
|
47
|
+
: (_jsxs("li", { class: "game", children: [_jsxs("button", { class: "reopen", type: "button", disabled: busy, onClick: () => open(item.name), children: [_jsx(ResultTile, { result: item.result }), _jsxs("span", { class: "what", children: [_jsx("b", { class: "who", children: named.sides }), _jsx("span", { class: "meta", children: [dayOf(item.date, text.language), words.moves(item.moves), item.opening].filter((part) => part !== undefined).join(" · ") }), item.reviewed ? _jsx(Reviewed, { by: by(item), words: words }) : null] }), _jsxs("span", { class: "score", children: [_jsx("b", { children: scoreOf(item.result) }), outcome === undefined ? _jsx("span", {}) : _jsx(OutcomeBadge, { outcome: outcome, words: words })] })] }), _jsx("span", { class: "row-actions", children: _jsx("button", { class: `icon-button${menu?.name === item.name ? " on" : ""}`, type: "button", title: words.more, "aria-label": words.more, "aria-expanded": menu?.name === item.name, disabled: busy, onClick: (event) => setMenu(menu?.name === item.name ? undefined : { name: item.name, anchor: event.currentTarget }), children: _jsx("svg", { class: "icon", viewBox: "0 0 24 24", "aria-hidden": "true", children: _jsx("path", { d: "M5 12H5.01M12 12H12.01M19 12H19.01" }) }) }) }), menu?.name === item.name
|
|
48
|
+
? (_jsx(RowMenu, { anchor: menu.anchor, reviewed: item.reviewed, onPick: (asked) => {
|
|
49
|
+
setMenu(undefined);
|
|
50
|
+
setAsking({ name: item.name, asked });
|
|
51
|
+
}, onClose: () => setMenu(undefined), words: words }))
|
|
52
|
+
: null] }, item.name))) }))] }));
|
|
53
|
+
}
|
|
54
|
+
/**
|
|
55
|
+
* A row's menu, over the dialog and the page in the browser's top layer, under its button or above it when there is no room: analyse again and delete the
|
|
56
|
+
* analysis, for a game reviewed; delete the game, apart and in red. Scrolling closes it, which would leave its button.
|
|
57
|
+
*/
|
|
58
|
+
function RowMenu({ anchor, reviewed, onPick, onClose, words }) {
|
|
59
|
+
const menu = useRef(null);
|
|
60
|
+
useLayoutEffect(() => {
|
|
61
|
+
const element = menu.current;
|
|
62
|
+
element.showPopover();
|
|
63
|
+
placeUnder(element, anchor);
|
|
64
|
+
element.querySelector("button")?.focus();
|
|
65
|
+
window.addEventListener("scroll", onClose, true);
|
|
66
|
+
window.addEventListener("resize", onClose);
|
|
67
|
+
return () => {
|
|
68
|
+
window.removeEventListener("scroll", onClose, true);
|
|
69
|
+
window.removeEventListener("resize", onClose);
|
|
70
|
+
};
|
|
71
|
+
}, []);
|
|
72
|
+
return (_jsxs("div", { ref: menu, class: "row-menu", popover: "manual", children: [reviewed
|
|
73
|
+
? (_jsxs(_Fragment, { children: [_jsxs("button", { type: "button", onClick: () => onPick("redo"), children: [_jsx("svg", { class: "icon", viewBox: "0 0 24 24", "aria-hidden": "true", children: _jsx("path", { d: "M19 12A7 7 0 1 1 16.5 6.6M17 3V7H13" }) }), _jsxs("span", { children: [words.redo, _jsx("small", { children: words.redoNote })] })] }), _jsxs("button", { type: "button", onClick: () => onPick("unreview"), children: [_jsx("svg", { class: "icon", viewBox: "0 0 24 24", "aria-hidden": "true", children: _jsx("path", { d: "M5 19H19M8 15L4.5 11.5L12 4L18 10L11 17H8ZM8.5 7.5L14.5 13.5" }) }), _jsxs("span", { children: [words.unreview, _jsx("small", { children: words.unreviewNote })] })] }), _jsx("hr", {})] }))
|
|
74
|
+
: null, _jsxs("button", { class: "danger", type: "button", onClick: () => onPick("remove"), children: [_jsx(TrashIcon, {}), _jsx("span", { children: words.remove })] })] }));
|
|
75
|
+
}
|
|
76
|
+
/** The question a row asks in place of the row: deleting in red, analysing again in the accent. */
|
|
77
|
+
function Question({ asked, game, when, by, busy, onYes, onNo, words }) {
|
|
78
|
+
const { question, note, yes } = asked === "redo" ? words.askRedo(game, by) : asked === "unreview" ? words.askUnreview(game) : words.askRemove(game, when);
|
|
79
|
+
// The safe answer takes the focus, as the menu's item had it.
|
|
80
|
+
const cancel = useRef(null);
|
|
81
|
+
useEffect(() => cancel.current?.focus(), []);
|
|
82
|
+
return (_jsxs("li", { class: `ask ${asked === "redo" ? "redo" : "danger"}`, role: "alertdialog", "aria-label": question, children: [_jsxs("p", { children: [_jsx("b", { children: question }), _jsx("small", { children: note })] }), _jsxs("span", { class: "buttons", children: [_jsx("button", { ref: cancel, type: "button", disabled: busy, onClick: onNo, children: words.cancel }), _jsxs("button", { class: "yes", type: "button", disabled: busy, onClick: onYes, children: [asked === "remove" ? _jsx(TrashIcon, {}) : null, yes] })] })] }));
|
|
83
|
+
}
|
|
84
|
+
function TrashIcon() {
|
|
85
|
+
return (_jsx("svg", { class: "icon", viewBox: "0 0 24 24", "aria-hidden": "true", children: _jsx("path", { d: "M4 7H20M9 7V4.5H15V7M6.5 7L7.5 20H16.5L17.5 7M10.5 11V16M13.5 11V16" }) }));
|
|
86
|
+
}
|
|
87
|
+
// The gap between the menu and its button, and the least margin it keeps from the window's edges.
|
|
88
|
+
const menuGap = 4;
|
|
89
|
+
const edge = 8;
|
|
90
|
+
/** Puts a menu under its button, its right edges aligned, or above the button when it would go past the window's bottom. */
|
|
91
|
+
function placeUnder(menu, anchor) {
|
|
92
|
+
const button = anchor.getBoundingClientRect();
|
|
93
|
+
const box = menu.getBoundingClientRect();
|
|
94
|
+
const below = button.bottom + menuGap + box.height <= window.innerHeight - edge;
|
|
95
|
+
menu.style.top = `${below ? button.bottom + menuGap : Math.max(edge, button.top - menuGap - box.height)}px`;
|
|
96
|
+
menu.style.left = `${Math.max(edge, button.right - box.width)}px`;
|
|
97
|
+
}
|
|
98
|
+
/** Who played a game listed, as its analysis names them, «Stefano – Opus 5.5 · xhigh», and the player's side when they played one. */
|
|
99
|
+
function namedSides(item, you, accounts, ai, text) {
|
|
100
|
+
const model = item.aiModel === undefined ? ai : signature(item.aiModel.model, item.aiModel.effort);
|
|
101
|
+
const sides = studiedSides(tagsOf(item), you, model, text.sides, accounts);
|
|
102
|
+
const side = playerIn(sides);
|
|
103
|
+
return { sides: `${sides.white.name} – ${sides.black.name}`, ...(side === undefined ? {} : { you: side }) };
|
|
104
|
+
}
|
|
105
|
+
/** What the search looks through in a row: who played, the models, the date, the opening and its variation, how it went and whether it is reviewed. */
|
|
106
|
+
function searchText(item, sides, outcome, text) {
|
|
107
|
+
const words = text.study;
|
|
108
|
+
const state = item.reviewed ? words.reviewed : words.unreviewed;
|
|
109
|
+
const went = outcome === undefined ? undefined : words.outcome[outcome];
|
|
110
|
+
return [sides, item.aiModel?.model, by(item), item.annotator?.model, dayOf(item.date, text.language), item.opening, scoreOf(item.result), went, state].join(" ");
|
|
111
|
+
}
|
|
112
|
+
/** How a game went for the player, as chess.com marks it: a green square with a plus, a red one with a minus, a grey one with an equals sign. */
|
|
113
|
+
function OutcomeBadge({ outcome, words }) {
|
|
114
|
+
return (_jsxs("svg", { class: `outcome ${outcome}`, viewBox: "0 0 16 16", role: "img", "aria-label": words.outcome[outcome], children: [_jsx("title", { children: words.outcome[outcome] }), _jsx("rect", { width: "16", height: "16", rx: "4" }), _jsx("path", { d: outcomeSigns[outcome] })] }));
|
|
115
|
+
}
|
|
116
|
+
/** A game the AI reviewed: its speech bubble, and the model that wrote the review when the tags say it. */
|
|
117
|
+
function Reviewed({ by, words }) {
|
|
118
|
+
return (_jsxs("span", { class: "state reviewed", title: by === undefined ? words.reviewed : `${words.reviewed}: ${by}`, children: [_jsx("svg", { class: "icon", viewBox: "0 0 24 24", role: "img", "aria-label": words.reviewed, children: _jsx("path", { d: "M5 5H19V15.5H11L7 19.5V15.5H5Z" }) }), by === undefined ? null : _jsx("span", { children: by })] }));
|
|
119
|
+
}
|
|
120
|
+
const outcomeSigns = { won: "M5 8h6M8 5v6", lost: "M5 8h6", drawn: "M5 6.5h6M5 9.5h6" };
|
|
121
|
+
/** The tags of a game listed that name its sides. */
|
|
122
|
+
function tagsOf(item) {
|
|
123
|
+
return { White: item.white, Black: item.black, ...(item.site === undefined ? {} : { Site: item.site }) };
|
|
124
|
+
}
|
|
125
|
+
/** Who reviewed a game, «Opus 5.5 · high», when its tags say it (D-81). */
|
|
126
|
+
function by(item) {
|
|
127
|
+
return item.annotator === undefined ? undefined : signature(item.annotator.model, item.annotator.effort);
|
|
128
|
+
}
|
|
129
|
+
/** A result as the row shows it: 1–0, 0–1, ½–½ or *. */
|
|
130
|
+
function scoreOf(result) {
|
|
131
|
+
return result === "1/2-1/2" ? "½–½" : result.replace("-", "–");
|
|
132
|
+
}
|
|
133
|
+
/** How a game went for the player: won, lost or drawn; nothing for a game without a result. */
|
|
134
|
+
function outcomeOf(result, you) {
|
|
135
|
+
if (result === "1/2-1/2") {
|
|
136
|
+
return "drawn";
|
|
137
|
+
}
|
|
138
|
+
return result === "1-0" || result === "0-1" ? (result === "1-0") === (you === "white") ? "won" : "lost" : undefined;
|
|
139
|
+
}
|
|
140
|
+
/** A PGN date, 2026.11.04, as the player reads it: «4 nov», with the year when it is not this one. */
|
|
141
|
+
function dayOf(date, language) {
|
|
142
|
+
const [, year, month, day] = /^(\d{4})\.(\d{2})\.(\d{2})$/.exec(date ?? "") ?? [];
|
|
143
|
+
if (year === undefined || month === undefined || day === undefined) {
|
|
144
|
+
return date;
|
|
145
|
+
}
|
|
146
|
+
const when = new Date(Number(year), Number(month) - 1, Number(day));
|
|
147
|
+
return when.toLocaleDateString(language, { day: "numeric", month: "short", ...(when.getFullYear() === new Date().getFullYear() ? {} : { year: "numeric" }) });
|
|
148
|
+
}
|
|
149
|
+
/** A deletion asked of the server: undefined once done, else why it refused. */
|
|
150
|
+
async function deletion(route, game) {
|
|
151
|
+
try {
|
|
152
|
+
const response = await fetch(route, { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ game }) });
|
|
153
|
+
return response.ok ? undefined : (await response.json()).error ?? response.statusText;
|
|
154
|
+
}
|
|
155
|
+
catch (error) {
|
|
156
|
+
return error.message;
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
/** Closes a menu, or a row's question, at a click outside it or at Escape, which would otherwise close the whole dialog. */
|
|
160
|
+
function useClosing(open, close) {
|
|
161
|
+
useEffect(() => {
|
|
162
|
+
if (!open) {
|
|
163
|
+
return undefined;
|
|
164
|
+
}
|
|
165
|
+
const outside = (event) => {
|
|
166
|
+
if (!event.target.closest(".row-menu, .row-actions, .ask")) {
|
|
167
|
+
close();
|
|
168
|
+
}
|
|
169
|
+
};
|
|
170
|
+
const escape = (event) => {
|
|
171
|
+
if (event.key === "Escape") {
|
|
172
|
+
event.preventDefault();
|
|
173
|
+
close();
|
|
174
|
+
}
|
|
175
|
+
};
|
|
176
|
+
document.addEventListener("pointerdown", outside);
|
|
177
|
+
document.addEventListener("keydown", escape, true);
|
|
178
|
+
return () => {
|
|
179
|
+
document.removeEventListener("pointerdown", outside);
|
|
180
|
+
document.removeEventListener("keydown", escape, true);
|
|
181
|
+
};
|
|
182
|
+
}, [open]);
|
|
183
|
+
}
|
|
184
|
+
/** The analyses of the player's folder, read once the dialog opens and again on demand; none while they come, or when the server cannot give them. */
|
|
185
|
+
function useAnalyses() {
|
|
186
|
+
const [analyses, setAnalyses] = useState([]);
|
|
187
|
+
const [reads, setReads] = useState(0);
|
|
188
|
+
useEffect(() => {
|
|
189
|
+
void fetch("/api/analyses")
|
|
190
|
+
.then(async (response) => response.ok ? await response.json() : [])
|
|
191
|
+
.catch(() => [])
|
|
192
|
+
.then(setAnalyses);
|
|
193
|
+
}, [reads]);
|
|
194
|
+
return [analyses, () => setReads((count) => count + 1)];
|
|
195
|
+
}
|
|
@@ -22,5 +22,5 @@ export function Dialog({ open, title, onBack, onClose, text, children }) {
|
|
|
22
22
|
onClose();
|
|
23
23
|
}
|
|
24
24
|
};
|
|
25
|
-
return (_jsxs("dialog", { ref: dialog, class: "
|
|
25
|
+
return (_jsxs("dialog", { ref: dialog, class: "dialog", "aria-label": title, onClose: onClose, onClick: closeOutside, children: [_jsxs("header", { children: [onBack === undefined ? null : (_jsx("button", { class: "icon-button", type: "button", title: text.profile.back, "aria-label": text.profile.back, onClick: onBack, children: _jsx("svg", { class: "icon", viewBox: "0 0 24 24", "aria-hidden": "true", children: _jsx("path", { d: "M15 6L9 12L15 18" }) }) })), _jsx("h2", { children: title }), _jsx("button", { class: "icon-button", type: "button", title: text.close, "aria-label": text.close, onClick: onClose, children: _jsx("svg", { class: "icon", viewBox: "0 0 24 24", "aria-hidden": "true", children: _jsx("path", { d: "M6 6L18 18M18 6L6 18" }) }) })] }), open ? children : null] }));
|
|
26
26
|
}
|