chesspal 0.2.3 → 0.3.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 +11 -12
- package/dist/cli/cli.js +83 -3
- package/dist/cli/command-line.js +26 -9
- package/dist/cli/icon.d.ts +25 -0
- package/dist/cli/icon.js +80 -0
- package/dist/commands/agents.d.ts +19 -2
- package/dist/commands/agents.js +41 -14
- package/dist/commands/ai-say.d.ts +2 -1
- package/dist/commands/ai-say.js +3 -4
- package/dist/commands/analyse-game.d.ts +6 -0
- package/dist/commands/analyse-game.js +51 -15
- package/dist/commands/annotate-game.d.ts +4 -0
- package/dist/commands/annotate-game.js +60 -20
- package/dist/commands/answer-lines.d.ts +10 -4
- package/dist/commands/answer-lines.js +30 -4
- package/dist/commands/coach.js +6 -4
- package/dist/commands/context.d.ts +11 -1
- package/dist/commands/context.js +5 -0
- package/dist/commands/delete-review.js +9 -4
- package/dist/commands/game-summary.d.ts +9 -2
- package/dist/commands/game-summary.js +11 -0
- package/dist/commands/index.d.ts +4 -3
- package/dist/commands/index.js +8 -5
- package/dist/commands/init.js +2 -4
- package/dist/commands/instructions.d.ts +9 -0
- package/dist/commands/instructions.js +30 -0
- package/dist/commands/lead-input.d.ts +1 -1
- package/dist/commands/lead-input.js +7 -4
- package/dist/commands/look.d.ts +9 -5
- package/dist/commands/look.js +19 -9
- package/dist/commands/page-link.js +6 -5
- package/dist/commands/past-games.d.ts +1 -1
- package/dist/commands/past-games.js +12 -5
- package/dist/commands/player-move.js +2 -3
- package/dist/commands/ponder.js +1 -1
- package/dist/commands/position.js +2 -2
- package/dist/commands/profiles.d.ts +23 -9
- package/dist/commands/profiles.js +76 -35
- package/dist/commands/ratings.d.ts +8 -0
- package/dist/commands/ratings.js +45 -0
- package/dist/commands/registry.d.ts +1 -1
- package/dist/commands/registry.js +3 -2
- package/dist/commands/say.d.ts +11 -6
- package/dist/commands/say.js +21 -13
- package/dist/commands/status.d.ts +6 -2
- package/dist/commands/status.js +70 -26
- package/dist/commands/study.d.ts +11 -1
- package/dist/commands/study.js +39 -7
- package/dist/commands/view.d.ts +17 -5
- package/dist/commands/view.js +67 -10
- package/dist/core/facts.d.ts +14 -2
- package/dist/core/facts.js +32 -7
- package/dist/core/game.d.ts +5 -0
- package/dist/core/game.js +9 -0
- package/dist/core/index.d.ts +2 -1
- package/dist/core/index.js +1 -1
- package/dist/core/placement.d.ts +63 -0
- package/dist/core/placement.js +259 -0
- package/dist/core/possibilities.d.ts +38 -0
- package/dist/core/possibilities.js +254 -0
- package/dist/core/review.d.ts +6 -1
- package/dist/core/review.js +21 -6
- package/dist/games/ai-model.d.ts +4 -2
- package/dist/games/ai-model.js +10 -1
- package/dist/games/analysis.d.ts +31 -4
- package/dist/games/analysis.js +44 -5
- package/dist/games/archive.d.ts +6 -3
- package/dist/games/archive.js +13 -5
- package/dist/games/current-game.d.ts +26 -2
- package/dist/games/current-game.js +57 -21
- package/dist/games/files.d.ts +7 -0
- package/dist/games/files.js +48 -1
- package/dist/games/index.d.ts +4 -4
- package/dist/games/index.js +4 -4
- package/dist/games/table-talk.d.ts +2 -0
- package/dist/games/table-talk.js +5 -1
- package/dist/games/talk.d.ts +4 -3
- package/dist/games/talk.js +3 -3
- package/dist/home/data-folder.d.ts +2 -0
- package/dist/home/data-folder.js +5 -1
- package/dist/home/index.d.ts +1 -1
- package/dist/home/index.js +1 -1
- package/dist/host-files/agents.d.ts +2 -2
- package/dist/host-files/agents.js +12 -6
- package/dist/host-files/host-files.d.ts +5 -3
- package/dist/host-files/host-files.js +15 -8
- package/dist/host-files/index.d.ts +2 -2
- package/dist/host-files/index.js +2 -2
- package/dist/page/app/app.js +59 -22
- package/dist/page/app/keys.js +16 -2
- package/dist/page/app/served.js +2 -2
- package/dist/page/app/title.js +17 -0
- package/dist/page/avatars/torretta.js +4 -4
- package/dist/page/board/board.js +2 -1
- package/dist/page/board/marks.js +4 -24
- package/dist/page/board/signs.js +23 -0
- package/dist/page/dialogs/analyses.js +4 -4
- package/dist/page/dialogs/dialog.js +4 -1
- package/dist/page/dialogs/dialogs.css +84 -10
- package/dist/page/dialogs/profiles.js +95 -42
- package/dist/page/game/column.js +6 -2
- package/dist/page/game/game.css +12 -0
- package/dist/page/game/move-list.js +2 -2
- package/dist/page/game/plies.js +11 -0
- package/dist/page/guide/blocks.js +67 -0
- package/dist/page/guide/en.md +66 -0
- package/dist/page/guide/foot.js +18 -0
- package/dist/page/guide/guide.css +200 -0
- package/dist/page/guide/guide.js +89 -0
- package/dist/page/guide/it.md +66 -0
- package/dist/page/guide/markdown.js +67 -0
- package/dist/page/helps/helps.css +10 -11
- package/dist/page/helps/helps.js +15 -15
- package/dist/page/layout/favicon.svg +13 -0
- package/dist/page/layout/header.js +3 -3
- package/dist/page/layout/icon.icns +0 -0
- package/dist/page/layout/icon.ico +0 -0
- package/dist/page/layout/icon.png +0 -0
- package/dist/page/layout/layout.css +140 -9
- package/dist/page/layout/window.js +45 -0
- package/dist/page/legend/legend.css +203 -0
- package/dist/page/legend/legend.js +169 -0
- package/dist/page/legend/view.js +54 -0
- package/dist/page/main.js +4 -1
- package/dist/page/players/card.js +1 -1
- package/dist/page/profile/profile.js +14 -9
- package/dist/page/profile/settings.js +9 -0
- package/dist/page/style.css +4 -0
- package/dist/page/table/opener.css +65 -0
- package/dist/page/table/opener.js +59 -0
- package/dist/page/talk/chat.css +97 -0
- package/dist/page/talk/talk.css +38 -26
- package/dist/page/talk/talk.js +31 -7
- package/dist/page/text.js +120 -18
- package/dist/page/theme.css +7 -0
- package/dist/profiles/index.d.ts +2 -1
- package/dist/profiles/index.js +2 -1
- package/dist/profiles/profiles.d.ts +10 -15
- package/dist/profiles/profiles.js +30 -15
- package/dist/profiles/ratings.d.ts +35 -0
- package/dist/profiles/ratings.js +61 -0
- package/dist/server/bell.d.ts +24 -3
- package/dist/server/bell.js +84 -10
- package/dist/server/caller.d.ts +2 -1
- package/dist/server/caller.js +3 -2
- package/dist/server/mcp.d.ts +7 -3
- package/dist/server/mcp.js +106 -56
- package/dist/server/page/analyses.d.ts +2 -0
- package/dist/server/page/analyses.js +20 -5
- package/dist/server/page/attach.d.ts +4 -0
- package/dist/server/page/attach.js +112 -0
- package/dist/server/page/chat.d.ts +60 -0
- package/dist/server/page/chat.js +79 -0
- package/dist/server/page/events.d.ts +7 -0
- package/dist/server/page/events.js +20 -0
- package/dist/server/page/files.js +10 -1
- package/dist/server/page/game.js +62 -29
- package/dist/server/page/page.d.ts +47 -7
- package/dist/server/page/page.js +74 -51
- package/dist/server/page/profiles.d.ts +5 -0
- package/dist/server/page/profiles.js +26 -10
- package/dist/server/page/table.d.ts +39 -0
- package/dist/server/page/table.js +102 -0
- package/dist/server/table/browser.d.ts +6 -0
- package/dist/server/table/browser.js +48 -0
- package/dist/server/table/hosts/chats.d.ts +7 -0
- package/dist/server/table/hosts/chats.js +31 -0
- package/dist/server/table/hosts/find.d.ts +2 -0
- package/dist/server/table/hosts/find.js +20 -0
- package/dist/server/table/hosts/hosts.d.ts +34 -0
- package/dist/server/table/hosts/hosts.js +49 -0
- package/dist/server/table/hosts/index.d.ts +3 -0
- package/dist/server/table/hosts/index.js +4 -0
- package/dist/server/table/hosts/play.d.ts +25 -0
- package/dist/server/table/hosts/play.js +152 -0
- package/dist/server/table/hosts/seat.d.ts +25 -0
- package/dist/server/table/hosts/seat.js +74 -0
- package/dist/server/table/hosts/terminal.d.ts +6 -0
- package/dist/server/table/hosts/terminal.js +63 -0
- package/dist/server/table/icon/index.d.ts +32 -0
- package/dist/server/table/icon/index.js +83 -0
- package/dist/server/table/icon/linux.d.ts +5 -0
- package/dist/server/table/icon/linux.js +47 -0
- package/dist/server/table/icon/macos.d.ts +3 -0
- package/dist/server/table/icon/macos.js +38 -0
- package/dist/server/table/icon/windows.d.ts +3 -0
- package/dist/server/table/icon/windows.js +48 -0
- package/dist/server/table/index.d.ts +6 -0
- package/dist/server/table/index.js +7 -0
- package/dist/server/table/launch.d.ts +19 -0
- package/dist/server/table/launch.js +78 -0
- package/dist/server/table/link.d.ts +10 -0
- package/dist/server/table/link.js +230 -0
- package/dist/server/table/table.d.ts +35 -0
- package/dist/server/table/table.js +94 -0
- package/dist/sites/chesscom.d.ts +4 -0
- package/dist/sites/chesscom.js +25 -0
- package/dist/sites/choice.d.ts +17 -0
- package/dist/sites/choice.js +13 -0
- package/dist/sites/index.d.ts +2 -0
- package/dist/sites/index.js +4 -0
- package/dist/sites/lichess.d.ts +4 -0
- package/dist/sites/lichess.js +32 -0
- package/dist/sites/request.d.ts +11 -0
- package/dist/sites/request.js +15 -0
- package/dist/sites/sites.d.ts +6 -0
- package/dist/sites/sites.js +33 -0
- package/instructions/analysis.md +1 -1
- package/instructions/analyst-agent.md +1 -1
- package/instructions/coach.md +2 -1
- package/instructions/opponent.md +6 -6
- package/instructions/ponder.md +1 -0
- package/instructions/start.md +1 -0
- package/package.json +1 -1
package/dist/page/app/app.js
CHANGED
|
@@ -6,9 +6,10 @@ import { Thinker, Thought } from "../avatars/thought.js";
|
|
|
6
6
|
import { Torretta } from "../avatars/torretta.js";
|
|
7
7
|
import { Board } from "../board/board.js";
|
|
8
8
|
import { explain } from "../board/explain.js";
|
|
9
|
+
import { kingSigns } from "../board/signs.js";
|
|
9
10
|
import { piecesOf } from "../board/squares.js";
|
|
10
11
|
import { NewGameDialog } from "../dialogs/new-game.js";
|
|
11
|
-
import { NameButton, PictureButton,
|
|
12
|
+
import { NameButton, PictureButton, ProfileDialog, ProfilesDialog, Wizard } from "../dialogs/profiles.js";
|
|
12
13
|
import { SettingsPanel } from "../dialogs/settings.js";
|
|
13
14
|
import { playerIn, studiedSides } from "../dialogs/studied.js";
|
|
14
15
|
import { StudyDialog } from "../dialogs/study.js";
|
|
@@ -18,25 +19,30 @@ import { GameColumn } from "../game/column.js";
|
|
|
18
19
|
import { howEnded, useDeclined, useJustEnded, useSawEnd } from "../game/ending.js";
|
|
19
20
|
import { Steps } from "../game/move-list.js";
|
|
20
21
|
import { useOpeningBook } from "../game/opening-book.js";
|
|
21
|
-
import { aiSpoke, aiWords, judgmentsOf, playerSpent, playerWords, pliesOf, withBook, withJudgments, withoutReviews } from "../game/plies.js";
|
|
22
|
+
import { aiSpoke, aiWords, analysisTalk, judgmentsOf, playerSpent, playerWords, pliesOf, withBook, withJudgments, withoutReviews } from "../game/plies.js";
|
|
23
|
+
import { GuideDialog } from "../guide/guide.js";
|
|
22
24
|
import { BareButton, HelpsCard, HintLine, MapButton } from "../helps/helps.js";
|
|
23
25
|
import { helpsOf } from "../helps/view.js";
|
|
24
26
|
import { FoldTab } from "../layout/fold-tab.js";
|
|
25
27
|
import { nextFold, rememberFold, startFolds } from "../layout/folds.js";
|
|
26
28
|
import { Header } from "../layout/header.js";
|
|
27
29
|
import { standingsOf } from "../lead/view.js";
|
|
30
|
+
import { MarksLegend } from "../legend/legend.js";
|
|
31
|
+
import { legendOf } from "../legend/view.js";
|
|
28
32
|
import { ChosenSet } from "../pieces/pieces.js";
|
|
29
33
|
import { ClockFace, PlayerCard } from "../players/card.js";
|
|
30
34
|
import { signature } from "../players/model-name.js";
|
|
31
35
|
import { keepSettings } from "../profile/profile.js";
|
|
32
36
|
import { languagesOf, saveSettings, settingsFrom, showSettings } from "../profile/settings.js";
|
|
33
37
|
import { endSoundOf, playSound, soundOf, wakeSounds } from "../sounds/sounds.js";
|
|
38
|
+
import { ChatOpener } from "../table/opener.js";
|
|
34
39
|
import { AiNote, AiWords, Compose, PointSquare, TalkFaces } from "../talk/talk.js";
|
|
35
40
|
import { textFor } from "../text.js";
|
|
36
41
|
import { anchorName, useAnchor } from "./anchor.js";
|
|
37
|
-
import { useBrowseKeys, useFoldKeys, useMapKey, useWheelSteps } from "./keys.js";
|
|
42
|
+
import { useBrowseKeys, useFoldKeys, useLegendKey, useMapKey, useWheelSteps } from "./keys.js";
|
|
38
43
|
import { phaseOf, verdictOf } from "./phase.js";
|
|
39
44
|
import { post, sendMessage, sendMove, useServed } from "./served.js";
|
|
45
|
+
import { tabTitle, useTabTitle } from "./title.js";
|
|
40
46
|
/**
|
|
41
47
|
* The page: the board of the game in the player's folder, where the player moves on their turn, the talk with the AI beside it, and the game's column; in the
|
|
42
48
|
* settings the player chose, `start` as this browser remembered them (D-56).
|
|
@@ -135,6 +141,9 @@ export function App({ start }) {
|
|
|
135
141
|
const [mapping, setMapping] = useState(false);
|
|
136
142
|
// The board without its drawings while their button is held (Stefano).
|
|
137
143
|
const [bare, setBare] = useState(false);
|
|
144
|
+
// The legend of the marks on the board, open until the player closes it (D-99).
|
|
145
|
+
const [legendOpen, setLegendOpen] = useState(false);
|
|
146
|
+
useLegendKey(() => setLegendOpen((open) => !open));
|
|
138
147
|
const past = useMemo(() => viewing === undefined ? undefined : new Game({ tags: live.record.tags, moves: live.record.moves.slice(0, viewing) }), [live, viewing]);
|
|
139
148
|
const next = useMemo(() => viewing === undefined ? undefined : new Game({ tags: live.record.tags, moves: live.record.moves.slice(0, viewing + 1) }).lastMove, [live, viewing]);
|
|
140
149
|
const shown = useMemo(() => past ?? (pending === undefined ? live : withMove(live, pending)), [live, pending, past]);
|
|
@@ -196,6 +205,9 @@ export function App({ start }) {
|
|
|
196
205
|
// game a link opens is there to look at: the analyst starts on the player's yes alone (D-89; Zoe and Federico).
|
|
197
206
|
const playedName = game === undefined || game === null ? undefined : anchorName(game.name);
|
|
198
207
|
useAnchor({ ready: served !== undefined, shown: studied !== undefined ? anchorName(studied.name) : idle ? undefined : playedName, played: playedName, going }, viewing, (plies) => goTo(plies ?? total), async (wanted) => await post("/api/study", { game: wanted, look: true }, text, name) === undefined);
|
|
208
|
+
// The tab names the game and the move shown, as the cards name its players; between games, Chesspal alone.
|
|
209
|
+
const titled = idle || served === undefined ? undefined : studiedSides(live.record.tags, served.profile.name, playedAs, text.sides, served.profile.accounts);
|
|
210
|
+
useTabTitle(titled === undefined ? undefined : tabTitle(titled.white.name, titled.black.name, at === 0 ? undefined : plies[at - 1]));
|
|
199
211
|
// The header's theme switch picks the other of the two the page shows, leaving the system's.
|
|
200
212
|
const switchTheme = () => change({ ...settings, theme: document.documentElement.dataset.theme === "dark" ? "light" : "dark" });
|
|
201
213
|
// An action of the player's on the game: the game the server sends next shows it; a refusal shows on the board.
|
|
@@ -206,7 +218,9 @@ export function App({ start }) {
|
|
|
206
218
|
const result = resultOf(live);
|
|
207
219
|
const ended = isOver(result);
|
|
208
220
|
const how = ended ? howEnded(live.outcome, live.record.tags.Termination) : undefined;
|
|
209
|
-
|
|
221
|
+
// A game closed before any move counts for no one, as Lichess aborts it: no winner, no kings' signs.
|
|
222
|
+
const aborted = ended && plies.length === 0;
|
|
223
|
+
const winner = aborted ? undefined : result === "1-0" ? "white" : result === "0-1" ? "black" : undefined;
|
|
210
224
|
const justEnded = useJustEnded(played === undefined || played === null ? undefined : over ? "over" : "going");
|
|
211
225
|
const offer = played?.drawOffer;
|
|
212
226
|
const declined = useDeclined(offer, total, ended);
|
|
@@ -222,16 +236,18 @@ export function App({ start }) {
|
|
|
222
236
|
}
|
|
223
237
|
},
|
|
224
238
|
};
|
|
225
|
-
const { kind, verdict } = verdictOf(phase, how, winner, offer, declined, text);
|
|
239
|
+
const { kind, verdict } = aborted && phase === "over" ? { kind: "over", verdict: text.aborted } : verdictOf(phase, how, winner, offer, declined, text);
|
|
226
240
|
const header = (_jsx(Header, { text: text, kind: kind, verdict: verdict, ...(!ended && (offer === "ai" || declined) ? { who: name } : {}), actions: actions, onNewGame: served === undefined ? undefined : () => setDialog("new-game"),
|
|
227
241
|
// Between games the new one stands out in the player's card (D-77): in the header only when the talk, and the card with it, is folded away.
|
|
228
|
-
standOut: studied !== undefined || (between && folds.talk !== "open"), onStudy: served === undefined || going ? undefined : () => setDialog("study"), flipped: flipped, onFlip: () => setFlipped(!flipped), sound: sound, onSound: () => change({ ...settings, sound: !sound }), onTheme: switchTheme, onSettings: () => setSetting(true), ...(served === undefined ? {} : { profile: served.profile, onProfile: () => setDialog("profiles") }) }));
|
|
242
|
+
standOut: studied !== undefined || (between && folds.talk !== "open"), onStudy: served === undefined || going ? undefined : () => setDialog("study"), flipped: flipped, onFlip: () => setFlipped(!flipped), sound: sound, onSound: () => change({ ...settings, sound: !sound }), onTheme: switchTheme, onSettings: () => setSetting(true), onGuide: () => setDialog("guide"), ...(served === undefined ? {} : { profile: served.profile, onProfile: () => setDialog("profiles") }) }));
|
|
229
243
|
const panel = _jsx(SettingsPanel, { open: setting, settings: settings, onChange: change, onClose: () => setSetting(false), text: text });
|
|
244
|
+
// The guide opens before the page has its data too.
|
|
245
|
+
const guide = _jsx(GuideDialog, { open: dialog === "guide", onClose: () => setDialog(undefined), text: text });
|
|
230
246
|
if (problem !== undefined) {
|
|
231
247
|
return _jsx("p", { class: "problem", children: problem });
|
|
232
248
|
}
|
|
233
249
|
if (served === undefined || game === undefined) {
|
|
234
|
-
return (_jsxs(_Fragment, { children: [header, panel] }));
|
|
250
|
+
return (_jsxs(_Fragment, { children: [header, panel, guide] }));
|
|
235
251
|
}
|
|
236
252
|
// At the first start the page asks who plays (D-23), to complete the profile Chesspal made (D-71): the game comes after.
|
|
237
253
|
if (served.profile.new === true) {
|
|
@@ -253,6 +269,22 @@ export function App({ start }) {
|
|
|
253
269
|
const review = reviewed?.review;
|
|
254
270
|
// An open hint draws on the board as it is, in place of the last move's drawings.
|
|
255
271
|
const drawn = hintOpen && hint !== undefined && viewing === undefined && (hint.arrows !== undefined || hint.squares !== undefined) ? hint : review;
|
|
272
|
+
const drawnByCoach = hint !== undefined && drawn === hint;
|
|
273
|
+
// The game over, when this is its last position: the kings' signs, on the board and in the legend.
|
|
274
|
+
const boardEnd = ended && !aborted && viewing === undefined && pending === undefined ? { how, winner, player, fresh: justEnded } : undefined;
|
|
275
|
+
// The legend has the marks the board shows: none of the helps' or the drawings while the board is bare (D-99).
|
|
276
|
+
const shownBare = bare && helps !== undefined;
|
|
277
|
+
const legendRows = legendOf({
|
|
278
|
+
...(review?.badge === undefined || shown.lastMove === undefined ? {} : { badge: { badge: review.badge, square: shown.lastMove.to } }),
|
|
279
|
+
...(helps === undefined || shownBare ? {} : { helps }),
|
|
280
|
+
...(drawn === undefined || shownBare ? {} : { drawn }),
|
|
281
|
+
control: mapping && helps !== undefined,
|
|
282
|
+
...(boardEnd === undefined ? {} : { ends: kingSigns(piecesOf(shown.position.fen), shown.position.turn, boardEnd) }),
|
|
283
|
+
});
|
|
284
|
+
// In a game the coach draws on the player's moves, the AI on its own.
|
|
285
|
+
const drawer = drawnByCoach || (going && reviewed?.side === player) ? text.legend.drawnByCoach : text.legend.drawnBy(analyst);
|
|
286
|
+
const legendAt = (under) => (_jsx(MarksLegend, { rows: legendRows, open: legendOpen, onOpen: setLegendOpen, drawn: { words: drawnByCoach ? text.helpsCard.drawn : text.drawn, by: drawer }, onPoint: setPointed, under: under, text: text }));
|
|
287
|
+
const legend = legendAt(false);
|
|
256
288
|
const summary = studied === undefined ? undefined : gameReviewOf(live.record);
|
|
257
289
|
const closing = studied === undefined ? undefined : closingOf(live.record);
|
|
258
290
|
// In analysis the AI's card says who played that game, as its PGN keeps it (D-82), not who plays now.
|
|
@@ -270,7 +302,10 @@ export function App({ start }) {
|
|
|
270
302
|
const clockWaits = clock?.waiting === true && !ended && studied === undefined && viewing === undefined && live.position.turn === ai;
|
|
271
303
|
const clockOf = (side) => clock === undefined ? undefined : (_jsx(ClockFace, { clock: clock, side: side, toMove: !ended && live.position.turn === side, held: pending !== undefined || (asleep && side === ai), onFlag: reload, text: text }));
|
|
272
304
|
// Where to wake the chat, under the AI's card (over the board when the talk is folded); the button that starts the clocks, under the player's (Zoe).
|
|
273
|
-
|
|
305
|
+
// On the table with no chat at it, the note opens one instead (D-104).
|
|
306
|
+
const table = offline ? undefined : served.table;
|
|
307
|
+
const opener = table !== undefined && !table.chat ? _jsx(ChatOpener, { table: table, settings: settings, onChange: change, name: name, text: text }) : undefined;
|
|
308
|
+
const asleepNote = opener ?? (asleep ? _jsx("p", { class: "asleep", role: "status", children: sides === undefined ? text.asleep(name) : text.asleepInAnalysis(name) }) : null);
|
|
274
309
|
const startClocks = clockWaits
|
|
275
310
|
? (_jsxs("button", { type: "button", class: "command primary start-clocks", onClick: () => act("/api/start", {}), children: [_jsx("svg", { class: "icon", viewBox: "0 0 24 24", "aria-hidden": "true", children: _jsx("path", { d: "M8 5.5v13l10.5-6.5z" }) }), text.startClocks] }))
|
|
276
311
|
: null;
|
|
@@ -322,7 +357,8 @@ export function App({ start }) {
|
|
|
322
357
|
setRefusal({ text: words, square: from, piece: pieces.get(from), explanation: explain(illegal, to, king) });
|
|
323
358
|
}
|
|
324
359
|
};
|
|
325
|
-
|
|
360
|
+
// In analysis the words go with the game and the move shown (D-101).
|
|
361
|
+
const sendWords = (position) => {
|
|
326
362
|
const words = draft;
|
|
327
363
|
// /clear and the other commands are for Claude's app: written here they would reach the AI as words (Sebastiano's playtest).
|
|
328
364
|
if (isCommand(words)) {
|
|
@@ -330,7 +366,7 @@ export function App({ start }) {
|
|
|
330
366
|
return;
|
|
331
367
|
}
|
|
332
368
|
setUnsent(undefined);
|
|
333
|
-
void sendMessage(words, text, name).then((refused) => {
|
|
369
|
+
void sendMessage(words, text, name, position).then((refused) => {
|
|
334
370
|
setUnsent(refused?.text);
|
|
335
371
|
clearSent(refused?.text, words);
|
|
336
372
|
});
|
|
@@ -344,12 +380,10 @@ export function App({ start }) {
|
|
|
344
380
|
setDialog(undefined);
|
|
345
381
|
act("/api/new", { ai_colour: colour === "random" ? "random" : colour === "white" ? "black" : "white", level: chosen, ...(control === undefined ? {} : { clock: control }) });
|
|
346
382
|
};
|
|
347
|
-
// In analysis the AI's note
|
|
348
|
-
const top = flipped ? player : ai;
|
|
349
|
-
const bottom = flipped ? ai : player;
|
|
383
|
+
// In analysis the AI's note goes with the other side's card and the talk with the player's, turned round with the board as in a game (D-101, Zoe).
|
|
350
384
|
const studiedCard = (side, place) => sides === undefined ? null : (_jsx(PlayerCard, { place: place,
|
|
351
|
-
// The player
|
|
352
|
-
picture: sides[side].who === "player" ? _jsx(PictureButton, { profile: profile, onOpen: () => setDialog("
|
|
385
|
+
// The player opens their profile in analysis too, from their own side's card.
|
|
386
|
+
picture: sides[side].who === "player" ? _jsx(PictureButton, { profile: profile, onOpen: () => setDialog("profile"), text: text }) : pictureOf(sides[side], profile, 48, mood), name: sides[side].name, signed: sides[side].who === "ai" && playedBy !== undefined ? signature(playedBy.model, playedBy.effort) : undefined, side: side, taken: material.taken[side], ahead: ahead(side), badges: _jsx(BadgeCounts, { plies: plies, side: side, names: text.badges }), text: text }));
|
|
353
387
|
// A game brought to analyse: the page shows it once the server has it in analysis; a refusal stays in the dialog.
|
|
354
388
|
const studyGame = async (studied) => {
|
|
355
389
|
const refused = await post("/api/study", studied, text, name);
|
|
@@ -358,22 +392,25 @@ export function App({ start }) {
|
|
|
358
392
|
}
|
|
359
393
|
return refused?.text;
|
|
360
394
|
};
|
|
361
|
-
return (_jsx(ChosenSet.Provider, { value: settings.set, children: _jsxs(PointSquare.Provider, { value: pointSquare, children: [panel, _jsx(
|
|
395
|
+
return (_jsx(ChosenSet.Provider, { value: settings.set, children: _jsxs(PointSquare.Provider, { value: pointSquare, children: [panel, _jsx(ProfileDialog, { open: dialog === "profile", profile: profile, onClose: () => setDialog(undefined), text: text }), _jsx(ProfilesDialog, { open: dialog === "profiles", profiles: served.profiles, active: profile, locked: going ? text.profile.whilePlaying : undefined, dataFolder: served.dataFolder, onEdit: () => setDialog("profile"), onClose: () => setDialog(undefined), text: text }), _jsx(NewGameDialog, { open: dialog === "new-game", choice: choice, going: going, onStart: startNewGame, onClose: () => setDialog(undefined), text: text }), guide, _jsx(StudyDialog, { open: dialog === "study", onStudy: studyGame, onClose: () => setDialog(undefined), you: profile.name, accounts: profile.accounts, ai: name, text: text }), offline ? _jsx("p", { class: "offline", role: "alert", children: text.offline(name) }) : null, served.outdated === true ? _jsx("p", { class: "outdated", role: "status", children: text.outdated(name) }) : null, _jsxs("main", { class: `app${flipped ? " flipped" : ""}${folds.talk === "open" ? "" : ` talk-${folds.talk}`}${folds.game === "folded" ? " folded" : ""}`, inert: offline, children: [header, sides === undefined
|
|
362
396
|
? (_jsx(TalkFaces, { name: name, aiPicture: thinker(40), you: profile.name, picture: picture, ...(idle ? {} : { ai, player }), aiMoves: going && !playerToMove, playerMoves: playerToMove, unread: unread || asleep, text: text }))
|
|
363
|
-
: (_jsx(TalkFaces, { name: sides[ai].name, aiPicture: pictureOf(sides[ai], profile, 40, mood), you: sides[player].name, picture: pictureOf(sides[player], profile, 40, mood), ai: ai, player: player, aiMoves: false, playerMoves: false, unread: asleep, text: text })), _jsx(FoldTab, { column: "talk", folded: folds.talk === "folded", label: thinking === undefined || folds.talk !== "folded" ? text.talk : `${text.talk}: ${text.thinking[thinking]}`, unread: (unread || asleep) && folds.talk === "folded", ...(thinking === undefined || folds.talk !== "folded" ? {} : { mark: _jsx(Thought, { thinking: thinking, words: text.thinking }) }), onFold: fold }), _jsx(FoldTab, { column: "game", folded: folds.game === "folded", label: text.game, onFold: fold }), _jsx("span", { class: "visually-hidden", "aria-live": "polite", children: unread ? text.wrote(name) : "" }), _jsx("div", { class: sides === undefined ? "talk" : "talk studied", id: "talk", children: sides !== undefined
|
|
364
|
-
? (_jsxs(_Fragment, { children: [_jsxs("section", { class: "ai-block", "aria-label": sides[
|
|
397
|
+
: (_jsx(TalkFaces, { name: sides[ai].name, aiPicture: pictureOf(sides[ai], profile, 40, mood), you: sides[player].name, picture: pictureOf(sides[player], profile, 40, mood), ai: ai, player: player, aiMoves: false, playerMoves: false, unread: asleep, text: text })), _jsx(FoldTab, { column: "talk", folded: folds.talk === "folded", label: thinking === undefined || folds.talk !== "folded" ? text.talk : `${text.talk}: ${text.thinking[thinking]}`, unread: (unread || asleep) && folds.talk === "folded", ...(thinking === undefined || folds.talk !== "folded" ? {} : { mark: _jsx(Thought, { thinking: thinking, words: text.thinking }) }), onFold: fold }), _jsx(FoldTab, { column: "game", folded: folds.game === "folded", label: text.game, onFold: fold }), _jsx("span", { class: "visually-hidden", "aria-live": "polite", children: unread ? text.wrote(name) : "" }), _jsx("div", { class: sides === undefined ? "talk" : "talk studied", id: "talk", children: sides !== undefined && studied !== undefined
|
|
398
|
+
? (_jsxs(_Fragment, { children: [_jsxs("section", { class: "ai-block", "aria-label": sides[ai].name, children: [studiedCard(ai, "ai"), asleepNote, _jsx(AiNote, { ...(reviewed === undefined ? {} : { ply: reviewed }), ...(shownOpening === undefined ? {} : { opening: shownOpening }), ...(at === total && closing !== undefined ? { closing } : {}), ...(at === 0 && summary !== undefined ? { summary } : {}), ...(summary === undefined ? { reviewing: true } : {}), name: analyst, by: annotator, mood: mood, ...(settings.voice ? { voice: text.language } : {}), text: text })] }), _jsxs("section", { class: "you-block", "aria-label": sides[player].name, children: [_jsx("div", { class: "chat", children: _jsx(Compose, { lines: analysisTalk(talk, studied.name), newestFirst: false, spent: 0, draft: draft, onDraft: (words) => {
|
|
399
|
+
setDraft(words);
|
|
400
|
+
setUnsent(undefined);
|
|
401
|
+
}, onSend: () => sendWords({ game: studied.name, at }), canMove: false, name: name, problem: unsent, about: { ...(at === 0 ? {} : { ply: plies[at - 1] }), onShow: goTo }, text: text }) }), studiedCard(player, "you")] })] }))
|
|
365
402
|
: (_jsxs(_Fragment, { children: [_jsxs("section", { class: "ai-block", "aria-label": name, children: [_jsx(PlayerCard, { place: "ai", picture: thinker(48), name: name, signed: signed, side: idle ? undefined : ai, ...(idle ? {} : { level: text.start.levels[level] }), taken: material.taken[ai], ahead: ahead(ai), clock: clockOf(ai), text: text }), asleepNote, _jsx(AiWords, { words: aiWords(plies, ai, talk), newestFirst: !flipped, ...(settings.voice ? { voice: text.language } : {}) })] }), _jsxs("section", { class: "you-block", "aria-label": profile.name, children: [_jsx(Compose, { lines: said, newestFirst: flipped,
|
|
366
403
|
// Between games, and before the first, the player writes freely, a line at a time, as the AI does (D-74, D-77).
|
|
367
404
|
spent: going ? playerSpent(plies, ai, talk) : 0, draft: draft, onDraft: (words) => {
|
|
368
405
|
setDraft(words);
|
|
369
406
|
setUnsent(undefined);
|
|
370
|
-
}, onSend: sendWords, canMove: playerToMove, name: name, problem: unsent, text: text }), _jsx(PlayerCard, { place: "you", picture: _jsx(PictureButton, { profile: profile, onOpen: () => setDialog("
|
|
407
|
+
}, onSend: sendWords, canMove: playerToMove, name: name, problem: unsent, text: text }), _jsx(PlayerCard, { place: "you", picture: _jsx(PictureButton, { profile: profile, onOpen: () => setDialog("profile"), text: text }), name: _jsx(NameButton, { profile: profile, onOpen: () => setDialog("profiles"), text: text }), side: idle ? undefined : player, taken: material.taken[player], ahead: ahead(player), turn: playerToMove, clock: _jsxs(_Fragment, { children: [clockOf(player), startClocks, nextGame] }), text: text })] })] })) }), _jsxs("div", {
|
|
371
408
|
// Between games the board goes off (D-77): a game the page saw end once its effects are over, its final position less off than the table set.
|
|
372
409
|
class: between && !justEnded ? "board-wrap off" : "board-wrap", onWheel: wheel,
|
|
373
410
|
// A press on the board while the player looks back brings the game as it is: the next one plays.
|
|
374
|
-
onPointerDownCapture: viewing === undefined || studied !== undefined ? undefined : (event) => event.button === 0 && goTo(total), children: [_jsx(Board, { fen: shown.position.fen, bottom: flipped ? ai : player, lastMove: shown.lastMove, pointed: pointed, ...(helps === undefined ? {} : { helps }), ...(mapping && helps !== undefined ? { control: helps.strategy.control } : {}), bare: bare && helps !== undefined, ...(review?.badge === undefined ? {} : { badge: { badge: review.badge, name: text.badges[review.badge] } }), ...(drawn === undefined ? {} : { drawn }), nextMove: next, jumped: jumped, check: shown.position.check, end:
|
|
375
|
-
? (_jsxs("div", { class: "over-board", children: [asleep ? _jsx("p", { class: "asleep", role: "status", children: text.asleep(name) }) : null, startClocks] }))
|
|
376
|
-
: null] }), studied === undefined ? null : (_jsx("div", { class: "board-steps", children: _jsx(Steps, { shown: at, total: total, onStep: step, text: text }) })), helps === undefined ? null : (_jsxs("div", { class: "board-map", children: [_jsx(BareButton, { bare: bare, onBare: setBare, text: text }), _jsx(MapButton, { mapping: mapping, onMap: setMapping, text: text }), hint === undefined || viewing !== undefined ? null : _jsx(HintLine, { hint: hint, open: hintOpen, onOpen: openTheHint, text: text })] })), _jsx(GameColumn, { plies: idle ? [] : plies, result: ended ? result : undefined, how: how, lit: justEnded, started: idle ? undefined : played?.started, move: live.position.moveNumber, toMove: live.position.turn, opening: opening, helps: helps === undefined ? undefined : (_jsx(HelpsCard, { helps: helps, judged: judgments, hint: viewing === undefined ? hint : undefined, hintOpen: hintOpen, onHint: openTheHint, onPoint: setPointed, onMap: setMapping, mapping: mapping, onBare: setBare, bare: bare, text: text })), ...(standings === undefined ? {} : { standings }), ...(studied === undefined ? {} : { analysis: summary === undefined ? { by: analyst } : { by: analyst, summary } }), shownPlies: at, onShow: goTo, onStep: step, ...(studied === undefined ? {} : { onDownload: () => download(studied.pgn, pgnName(live.record.tags, "analysis")) }), ...(total === 0 ? {} : { onSaveMoves: () => download(writePgn(movesOnly(live.record)), pgnName(live.record.tags)) }), ...(between && game !== null ? { onAnalyse: () => void studyGame({ last: true }) } : {}), text: text })] })] }) }));
|
|
411
|
+
onPointerDownCapture: viewing === undefined || studied !== undefined ? undefined : (event) => event.button === 0 && goTo(total), children: [_jsx(Board, { fen: shown.position.fen, bottom: flipped ? ai : player, lastMove: shown.lastMove, pointed: pointed, ...(helps === undefined ? {} : { helps }), ...(mapping && helps !== undefined ? { control: helps.strategy.control } : {}), bare: bare && helps !== undefined, ...(review?.badge === undefined ? {} : { badge: { badge: review.badge, name: text.badges[review.badge] } }), ...(drawn === undefined ? {} : { drawn }), nextMove: next, jumped: jumped, check: shown.position.check, end: boardEnd, player: playerToMove && pending === undefined && viewing === undefined ? player : undefined, selected: selected, targets: selected === undefined || viewing !== undefined ? [] : live.legalMoves(selected), dots: settings.dots, names: settings.squareNames, notice: viewing === undefined ? refusal : undefined, promotion: promoting === undefined || viewing !== undefined || !playerToMove ? undefined : { ...promoting, side: player }, onPromote: (piece) => piece === undefined ? setPromoting(undefined) : play(promoting.from, promoting.to, piece), onSelect: select, onMove: play, onRefuse: refuse, text: text }), folds.talk !== "open" && (asleep || opener !== undefined || clockWaits)
|
|
412
|
+
? (_jsxs("div", { class: "over-board", children: [opener ?? (asleep ? _jsx("p", { class: "asleep", role: "status", children: text.asleep(name) }) : null), startClocks] }))
|
|
413
|
+
: null] }), studied === undefined ? null : (_jsx("div", { class: "board-steps", children: _jsx(Steps, { shown: at, total: total, onStep: step, text: text }) })), helps === undefined ? null : (_jsxs("div", { class: "board-map", children: [legendAt(true), _jsx(BareButton, { bare: bare, onBare: setBare, text: text }), _jsx(MapButton, { mapping: mapping, onMap: setMapping, text: text }), hint === undefined || viewing !== undefined ? null : _jsx(HintLine, { hint: hint, open: hintOpen, onOpen: openTheHint, text: text })] })), _jsx(GameColumn, { plies: idle ? [] : plies, result: ended ? result : undefined, how: how, lit: justEnded, started: idle ? undefined : played?.started, move: live.position.moveNumber, toMove: live.position.turn, opening: opening, helps: helps === undefined ? undefined : (_jsx(HelpsCard, { helps: helps, judged: judgments, hint: viewing === undefined ? hint : undefined, hintOpen: hintOpen, onHint: openTheHint, onPoint: setPointed, onMap: setMapping, mapping: mapping, onBare: setBare, bare: bare, legend: legend, text: text })), ...(helps === undefined && legendRows.length > 0 ? { legend } : {}), ...(standings === undefined ? {} : { standings }), ...(studied === undefined ? {} : { analysis: summary === undefined ? { by: analyst } : { by: analyst, summary } }), shownPlies: at, onShow: goTo, onStep: step, ...(studied === undefined ? {} : { onDownload: () => download(studied.pgn, pgnName(live.record.tags, "analysis")) }), ...(total === 0 ? {} : { onSaveMoves: () => download(writePgn(movesOnly(live.record)), pgnName(live.record.tags)) }), ...(between && game !== null && replayed.record.moves.length > 0 ? { onAnalyse: () => void studyGame({ last: true }) } : {}), text: text })] })] }) }));
|
|
377
414
|
}
|
|
378
415
|
/** The picture of a player of the game in analysis: the player's own, the AI's Torretta with its mood, or the plain face of someone else. */
|
|
379
416
|
function pictureOf({ who }, profile, size, mood) {
|
package/dist/page/app/keys.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
// The page's keys and wheel: [ and ] fold the side columns, M holds the helps' map (D-78), the arrows, Home and End step
|
|
2
|
-
// and x turn the board, and the wheel over the board steps too.
|
|
1
|
+
// The page's keys and wheel: [ and ] fold the side columns, M holds the helps' map (D-78), ? opens the legend (D-99), the arrows, Home and End step
|
|
2
|
+
// through the moves and f and x turn the board, and the wheel over the board steps too.
|
|
3
3
|
import { useEffect, useRef } from "preact/hooks";
|
|
4
4
|
import { flipKeys, stepKeys } from "../game/browse.js";
|
|
5
5
|
import { foldKeys, gameUnderBoard, oneColumn } from "../layout/folds.js";
|
|
@@ -52,6 +52,20 @@ export function useMapKey(helping, map) {
|
|
|
52
52
|
};
|
|
53
53
|
}, [helping]);
|
|
54
54
|
}
|
|
55
|
+
/** ? opens the legend of the marks on the board and closes it, while the page shows its ? (D-99). */
|
|
56
|
+
export function useLegendKey(toggle) {
|
|
57
|
+
useEffect(() => {
|
|
58
|
+
const press = (event) => {
|
|
59
|
+
const shown = [...document.querySelectorAll(".legend-button")].some((button) => button.getClientRects().length > 0);
|
|
60
|
+
if (shown && forThePage(event) && event.key === "?") {
|
|
61
|
+
event.preventDefault();
|
|
62
|
+
toggle();
|
|
63
|
+
}
|
|
64
|
+
};
|
|
65
|
+
window.addEventListener("keydown", press);
|
|
66
|
+
return () => window.removeEventListener("keydown", press);
|
|
67
|
+
});
|
|
68
|
+
}
|
|
55
69
|
/** The keys of Lichess and chess.com: the arrows, Home and End step through the moves, f and x turn the board round. */
|
|
56
70
|
export function useBrowseKeys(step, flip) {
|
|
57
71
|
useEffect(() => {
|
package/dist/page/app/served.js
CHANGED
|
@@ -68,6 +68,6 @@ export function sendMove(move, words, text, name) {
|
|
|
68
68
|
return post("/api/move", message === "" ? { move } : { move, message }, text, name);
|
|
69
69
|
}
|
|
70
70
|
/** Sends the player's words alone: the reason they were refused, or undefined. */
|
|
71
|
-
export function sendMessage(words, text, name) {
|
|
72
|
-
return post("/api/message", { text: words.trim() }, text, name);
|
|
71
|
+
export function sendMessage(words, text, name, shown) {
|
|
72
|
+
return post("/api/message", { text: words.trim(), ...shown }, text, name);
|
|
73
73
|
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
// The tab's title names the game and the move shown, Stefano – Claude, 16. ♞f3, so that the player's bookmarks and tabs tell its games apart.
|
|
2
|
+
import { useEffect } from "preact/hooks";
|
|
3
|
+
import { figurinesOf } from "../../core/index.js";
|
|
4
|
+
/** The title of a game: its players by their first names, White first, so that the move fits in a tab (Zoe), and the move shown with its number, none before the first. */
|
|
5
|
+
export function tabTitle(white, black, shown) {
|
|
6
|
+
const players = `${firstName(white)} – ${firstName(black)}`;
|
|
7
|
+
return shown === undefined ? players : `${players}, ${shown.number}${shown.side === "white" ? "." : "..."} ${figurinesOf(shown.san)}`;
|
|
8
|
+
}
|
|
9
|
+
/** Keeps the tab's title, Chesspal's name while no game shows. */
|
|
10
|
+
export function useTabTitle(title) {
|
|
11
|
+
useEffect(() => {
|
|
12
|
+
document.title = title ?? "Chesspal";
|
|
13
|
+
}, [title]);
|
|
14
|
+
}
|
|
15
|
+
function firstName(name) {
|
|
16
|
+
return name.trim().split(/\s+/)[0] || name;
|
|
17
|
+
}
|
|
@@ -18,8 +18,8 @@ function useChanged(mood) {
|
|
|
18
18
|
function Face({ mood }) {
|
|
19
19
|
switch (mood) {
|
|
20
20
|
case "happy":
|
|
21
|
-
// Eyes closed in two arcs, a wide open smile.
|
|
22
|
-
return (_jsxs(_Fragment, { children: [_jsx("path", { class: "eyes line", d: "M50.5
|
|
21
|
+
// Eyes closed in two low arcs, a wide open smile with its corners just rounded.
|
|
22
|
+
return (_jsxs(_Fragment, { children: [_jsx("path", { class: "eyes line", d: "M50 77.5q5-5.4 10 0M68 77.5q5-5.4 10 0" }), _jsx("path", { class: "mouth", d: "M56.6 83.2H71.4Q73 83.2 72.9 84.6A9 7.8 0 0 1 55.1 84.6Q55 83.2 56.6 83.2Z" })] }));
|
|
23
23
|
case "surprised":
|
|
24
24
|
// Wide eyes and a round mouth.
|
|
25
25
|
return (_jsxs(_Fragment, { children: [_jsxs("g", { class: "eyes", children: [_jsx("circle", { cx: "54.5", cy: "75.5", r: "5.8" }), _jsx("circle", { cx: "73.5", cy: "75.5", r: "5.8" })] }), _jsx("ellipse", { class: "mouth", cx: "64", cy: "88.5", rx: "3.4", ry: "4" })] }));
|
|
@@ -27,8 +27,8 @@ function Face({ mood }) {
|
|
|
27
27
|
// Brows raised in the middle, smaller eyes, a frown; the light turns red.
|
|
28
28
|
return (_jsxs(_Fragment, { children: [_jsx("path", { class: "brows", d: "M50 70L58 67M70 67L78 70" }), _jsxs("g", { class: "eyes", children: [_jsx("circle", { cx: "55", cy: "78.5", r: "3.8" }), _jsx("circle", { cx: "73", cy: "78.5", r: "3.8" })] }), _jsx("path", { class: "mouth line", d: "M56.5 91q7.5-6 15 0" })] }));
|
|
29
29
|
case "thoughtful":
|
|
30
|
-
//
|
|
31
|
-
return (_jsxs(
|
|
30
|
+
// The neutral face tilted on the screen, a smaller mouth: the head bent of one who wonders; the light breathes.
|
|
31
|
+
return (_jsxs("g", { transform: "rotate(-12 64 79)", children: [_jsxs("g", { class: "eyes", children: [_jsx("circle", { cx: "55", cy: "74.5", r: "4.4" }), _jsx("circle", { cx: "73", cy: "74.5", r: "4.4" })] }), _jsx("rect", { class: "mouth", x: "60", y: "86.3", width: "8", height: "3.4", rx: "1.7" })] }));
|
|
32
32
|
case "sad":
|
|
33
33
|
// No brows, the face sunk low: eyes half shut under lids that droop outwards, a deep frown; the light goes out.
|
|
34
34
|
return (_jsxs(_Fragment, { children: [_jsx("path", { class: "eyes", d: "M60.3 77.1A5.6 5.6 0 0 1 49.7 80.9ZM78.3 80.9A5.6 5.6 0 0 1 67.7 77.1Z" }), _jsx("path", { class: "mouth line", d: "M57.5 92.5q6.5-6.5 13 0" })] }));
|
package/dist/page/board/board.js
CHANGED
|
@@ -6,9 +6,10 @@ import { BadgeIcon } from "../game/badges.js";
|
|
|
6
6
|
import { pinOn, riskOn, skewerOn } from "../helps/view.js";
|
|
7
7
|
import { Piece } from "../pieces/pieces.js";
|
|
8
8
|
import { bubblePlace, keptSquares } from "./explain.js";
|
|
9
|
-
import { Arrows, Badge, Bubble, ControlCount, controlOf,
|
|
9
|
+
import { Arrows, Badge, Bubble, ControlCount, controlOf, Lifted, Notch, PinsAndSkewers, PromotionChoice, Pushpin, Skewer } from "./marks.js";
|
|
10
10
|
import { backOf, motionOf, noMotion, playedOn, rearranged } from "./motion.js";
|
|
11
11
|
import { useBoardPointer } from "./pointer.js";
|
|
12
|
+
import { kingSigns, noSigns } from "./signs.js";
|
|
12
13
|
import { files, kingToMove, piecesOf } from "./squares.js";
|
|
13
14
|
/**
|
|
14
15
|
* The board of a position, seen from one side, with the last move's squares marked. While the player may move, their pieces answer the pointer: a press
|
package/dist/page/board/marks.js
CHANGED
|
@@ -5,7 +5,10 @@ import { Piece } from "../pieces/pieces.js";
|
|
|
5
5
|
export function Notch({ tone }) {
|
|
6
6
|
return (_jsx("span", { class: tone === undefined ? "help-notch" : `help-notch ${tone}`, "aria-hidden": "true", children: _jsxs("svg", { viewBox: "0 0 10 10", children: [_jsx("path", { d: "M0.5 0.5V9.5H9.5Z" }), tone === undefined ? null : _jsx("path", { class: "edge", d: "M0.5 0.5V9.5H9.5Z" }), _jsx("circle", { cx: "2.9", cy: "7.1", r: "1.1" })] }) }));
|
|
7
7
|
}
|
|
8
|
-
/**
|
|
8
|
+
/**
|
|
9
|
+
* The pushpin stuck in a pinned piece from the top left corner, its needle toward the piece, in the tone of its corner at risk (D-91); in ink in no side's;
|
|
10
|
+
* in the card's line, in its colour.
|
|
11
|
+
*/
|
|
9
12
|
export function Pushpin({ tone }) {
|
|
10
13
|
return _jsx(Stuck, { kind: "help-pin", shape: pushpin, tone: tone });
|
|
11
14
|
}
|
|
@@ -122,29 +125,6 @@ export function Lifted({ drag, letter }) {
|
|
|
122
125
|
const classes = ["lifted", up && !drag.back ? "up" : "", drag.touch ? "touch" : "", drag.back ? "back" : ""].filter((name) => name !== "").join(" ");
|
|
123
126
|
return (_jsx("div", { class: classes, style: { "--x": `${drag.at[0]}px`, "--y": `${drag.at[1]}px` }, children: _jsx(Piece, { letter: letter }) }));
|
|
124
127
|
}
|
|
125
|
-
/** The kings' signs at the end, by their squares: the loser's king lies down when mated or resigning; both kings of a draw stand, the stalemated one stuck. */
|
|
126
|
-
export function kingSigns(pieces, toMove, end) {
|
|
127
|
-
const kingOf = (side) => [...pieces].find(([, letter]) => letter === (side === "white" ? "K" : "k"))?.[0];
|
|
128
|
-
const signs = new Map();
|
|
129
|
-
const sign = (side, badge, classes) => {
|
|
130
|
-
const square = kingOf(side);
|
|
131
|
-
if (square !== undefined) {
|
|
132
|
-
signs.set(square, { badge, classes });
|
|
133
|
-
}
|
|
134
|
-
};
|
|
135
|
-
if (end.winner === undefined) {
|
|
136
|
-
sign("white", "half", end.how === "stalemate" && toMove === "white" ? "drawn stalled" : "drawn");
|
|
137
|
-
sign("black", "half", end.how === "stalemate" && toMove === "black" ? "drawn stalled" : "drawn");
|
|
138
|
-
return signs;
|
|
139
|
-
}
|
|
140
|
-
const loser = end.winner === "white" ? "black" : "white";
|
|
141
|
-
sign(end.winner, "crown", "winner");
|
|
142
|
-
if (end.how === "checkmate" || end.how === "resignation") {
|
|
143
|
-
sign(loser, end.how === "checkmate" ? "mate" : "flag", "fallen");
|
|
144
|
-
}
|
|
145
|
-
return signs;
|
|
146
|
-
}
|
|
147
|
-
export const noSigns = new Map();
|
|
148
128
|
/** Arrows off one square, the AI's on a white edge (D-78): every edge under every arrow, so one edge goes round them all where they meet. */
|
|
149
129
|
function ArrowGroup({ shapes, edged }) {
|
|
150
130
|
return (_jsxs("g", { children: [edged
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
/** The kings' signs at the end, by their squares: the loser's king lies down when mated or resigning; both kings of a draw stand, the stalemated one stuck. */
|
|
2
|
+
export function kingSigns(pieces, toMove, end) {
|
|
3
|
+
const kingOf = (side) => [...pieces].find(([, letter]) => letter === (side === "white" ? "K" : "k"))?.[0];
|
|
4
|
+
const signs = new Map();
|
|
5
|
+
const sign = (side, badge, classes) => {
|
|
6
|
+
const square = kingOf(side);
|
|
7
|
+
if (square !== undefined) {
|
|
8
|
+
signs.set(square, { badge, classes });
|
|
9
|
+
}
|
|
10
|
+
};
|
|
11
|
+
if (end.winner === undefined) {
|
|
12
|
+
sign("white", "half", end.how === "stalemate" && toMove === "white" ? "drawn stalled" : "drawn");
|
|
13
|
+
sign("black", "half", end.how === "stalemate" && toMove === "black" ? "drawn stalled" : "drawn");
|
|
14
|
+
return signs;
|
|
15
|
+
}
|
|
16
|
+
const loser = end.winner === "white" ? "black" : "white";
|
|
17
|
+
sign(end.winner, "crown", "winner");
|
|
18
|
+
if (end.how === "checkmate" || end.how === "resignation") {
|
|
19
|
+
sign(loser, end.how === "checkmate" ? "mate" : "flag", "fallen");
|
|
20
|
+
}
|
|
21
|
+
return signs;
|
|
22
|
+
}
|
|
23
|
+
export const noSigns = new Map();
|
|
@@ -45,7 +45,7 @@ export function Analyses({ onStudy, onLoad, you, accounts, ai, text }) {
|
|
|
45
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
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
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,
|
|
48
|
+
? (_jsx(RowMenu, { anchor: menu.anchor, withReview: item.reviewed || item.begun === true, onPick: (asked) => {
|
|
49
49
|
setMenu(undefined);
|
|
50
50
|
setAsking({ name: item.name, asked });
|
|
51
51
|
}, onClose: () => setMenu(undefined), words: words }))
|
|
@@ -53,9 +53,9 @@ export function Analyses({ onStudy, onLoad, you, accounts, ai, text }) {
|
|
|
53
53
|
}
|
|
54
54
|
/**
|
|
55
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.
|
|
56
|
+
* analysis, for a game reviewed or whose review was begun; delete the game, apart and in red. Scrolling closes it, which would leave its button.
|
|
57
57
|
*/
|
|
58
|
-
function RowMenu({ anchor,
|
|
58
|
+
function RowMenu({ anchor, withReview, onPick, onClose, words }) {
|
|
59
59
|
const menu = useRef(null);
|
|
60
60
|
useLayoutEffect(() => {
|
|
61
61
|
const element = menu.current;
|
|
@@ -69,7 +69,7 @@ function RowMenu({ anchor, reviewed, onPick, onClose, words }) {
|
|
|
69
69
|
window.removeEventListener("resize", onClose);
|
|
70
70
|
};
|
|
71
71
|
}, []);
|
|
72
|
-
return (_jsxs("div", { ref: menu, class: "row-menu", popover: "manual", children: [
|
|
72
|
+
return (_jsxs("div", { ref: menu, class: "row-menu", popover: "manual", children: [withReview
|
|
73
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
74
|
: null, _jsxs("button", { class: "danger", type: "button", onClick: () => onPick("remove"), children: [_jsx(TrashIcon, {}), _jsx("span", { children: words.remove })] })] }));
|
|
75
75
|
}
|
|
@@ -3,6 +3,9 @@ import { useEffect, useRef } from "preact/hooks";
|
|
|
3
3
|
/** A dialog over the page, as the settings' one: Esc and a click outside close it; a step inside it goes back with the arrow beside its title. */
|
|
4
4
|
export function Dialog({ open, title, onBack, onClose, text, children }) {
|
|
5
5
|
const dialog = useRef(null);
|
|
6
|
+
// Open as the page wants it: a dialog the page closed itself, to open another, does not close that one when the browser says it closed.
|
|
7
|
+
const wanted = useRef(open);
|
|
8
|
+
wanted.current = open;
|
|
6
9
|
useEffect(() => {
|
|
7
10
|
const element = dialog.current;
|
|
8
11
|
if (open && !element.open) {
|
|
@@ -22,5 +25,5 @@ export function Dialog({ open, title, onBack, onClose, text, children }) {
|
|
|
22
25
|
onClose();
|
|
23
26
|
}
|
|
24
27
|
};
|
|
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] }));
|
|
28
|
+
return (_jsxs("dialog", { ref: dialog, class: "dialog", "aria-label": title, onClose: () => wanted.current && 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
29
|
}
|
|
@@ -1005,12 +1005,6 @@ h3 + .switch {
|
|
|
1005
1005
|
outline-offset: 2px;
|
|
1006
1006
|
}
|
|
1007
1007
|
|
|
1008
|
-
.rating {
|
|
1009
|
-
display: grid;
|
|
1010
|
-
grid-template-columns: 1fr 1fr;
|
|
1011
|
-
gap: 8px;
|
|
1012
|
-
}
|
|
1013
|
-
|
|
1014
1008
|
.profile-form .start {
|
|
1015
1009
|
width: 100%;
|
|
1016
1010
|
padding: 13px;
|
|
@@ -1089,13 +1083,53 @@ h3 + .switch {
|
|
|
1089
1083
|
height: 32px;
|
|
1090
1084
|
}
|
|
1091
1085
|
|
|
1092
|
-
/* The
|
|
1093
|
-
.
|
|
1086
|
+
/* The player in use, with the pencil that opens their profile beside their line */
|
|
1087
|
+
.profile-row {
|
|
1088
|
+
display: flex;
|
|
1089
|
+
gap: 6px;
|
|
1090
|
+
}
|
|
1091
|
+
|
|
1092
|
+
.profile-row .choice {
|
|
1093
|
+
flex: 1;
|
|
1094
|
+
}
|
|
1095
|
+
|
|
1096
|
+
.profile-row .edit-profile {
|
|
1097
|
+
flex: none;
|
|
1098
|
+
align-self: center;
|
|
1099
|
+
}
|
|
1100
|
+
|
|
1101
|
+
/* The profile window: the name with its tick once saved, as the names on the sites have */
|
|
1102
|
+
.profile-edit .field {
|
|
1094
1103
|
display: grid;
|
|
1095
|
-
grid-template-columns:
|
|
1104
|
+
grid-template-columns: 1fr 18px;
|
|
1105
|
+
align-items: center;
|
|
1106
|
+
gap: 0 10px;
|
|
1107
|
+
}
|
|
1108
|
+
|
|
1109
|
+
.profile-edit .field > span {
|
|
1110
|
+
grid-column: 1 / -1;
|
|
1111
|
+
}
|
|
1112
|
+
|
|
1113
|
+
.profile-edit .field .in-use {
|
|
1114
|
+
width: 18px;
|
|
1115
|
+
height: 18px;
|
|
1116
|
+
color: var(--accent);
|
|
1117
|
+
}
|
|
1118
|
+
|
|
1119
|
+
.profile-edit input[type="text"]:invalid {
|
|
1120
|
+
border-color: var(--low-time);
|
|
1121
|
+
}
|
|
1122
|
+
|
|
1123
|
+
/* The names on Lichess and chess.com: the site and the name; in the profile window the rating read there, and a tick once saved (D-93, D-105) */
|
|
1124
|
+
.accounts {
|
|
1125
|
+
display: grid;
|
|
1126
|
+
grid-template-columns: auto 1fr;
|
|
1096
1127
|
align-items: center;
|
|
1097
1128
|
gap: 6px 10px;
|
|
1098
|
-
|
|
1129
|
+
}
|
|
1130
|
+
|
|
1131
|
+
.accounts.read {
|
|
1132
|
+
grid-template-columns: auto 1fr minmax(4ch, auto) 18px;
|
|
1099
1133
|
}
|
|
1100
1134
|
|
|
1101
1135
|
.accounts label {
|
|
@@ -1132,6 +1166,46 @@ h3 + .switch {
|
|
|
1132
1166
|
grid-column: 1 / -1;
|
|
1133
1167
|
}
|
|
1134
1168
|
|
|
1169
|
+
/* The rating a site gives, its speed in its title; a cross for a name it does not know, a dash or the rating soft when it did not answer, dots while read */
|
|
1170
|
+
.accounts .site-mark {
|
|
1171
|
+
color: var(--ink);
|
|
1172
|
+
font-size: 15px;
|
|
1173
|
+
font-weight: 600;
|
|
1174
|
+
font-variant-numeric: tabular-nums;
|
|
1175
|
+
text-align: right;
|
|
1176
|
+
}
|
|
1177
|
+
|
|
1178
|
+
.accounts .site-mark span {
|
|
1179
|
+
color: inherit;
|
|
1180
|
+
font-size: inherit;
|
|
1181
|
+
}
|
|
1182
|
+
|
|
1183
|
+
.accounts .site-mark.missing {
|
|
1184
|
+
color: var(--low-time);
|
|
1185
|
+
}
|
|
1186
|
+
|
|
1187
|
+
.accounts .site-mark.unreached,
|
|
1188
|
+
.accounts .site-mark.pending {
|
|
1189
|
+
color: var(--soft);
|
|
1190
|
+
}
|
|
1191
|
+
|
|
1192
|
+
/* The cross and the dash in words, under the names */
|
|
1193
|
+
.site-problem {
|
|
1194
|
+
margin: 6px 0 0;
|
|
1195
|
+
color: var(--low-time);
|
|
1196
|
+
font-size: 13px;
|
|
1197
|
+
}
|
|
1198
|
+
|
|
1199
|
+
.site-problem.unreached {
|
|
1200
|
+
color: var(--soft);
|
|
1201
|
+
}
|
|
1202
|
+
|
|
1203
|
+
.privacy {
|
|
1204
|
+
margin: 8px 0 0;
|
|
1205
|
+
color: var(--soft);
|
|
1206
|
+
font-size: 13px;
|
|
1207
|
+
}
|
|
1208
|
+
|
|
1135
1209
|
.dialog .new-profile {
|
|
1136
1210
|
border-color: var(--line);
|
|
1137
1211
|
color: var(--ink);
|