@dxos/plugin-chess 0.8.4-main.f9ba587 → 0.8.4-main.fffef41
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/dist/lib/browser/ChessboardContainer-5YXEM4VR.mjs +96 -0
- package/dist/lib/browser/ChessboardContainer-5YXEM4VR.mjs.map +7 -0
- package/dist/lib/browser/blueprint-definition-FD4TL5EE.mjs +11 -0
- package/dist/lib/browser/blueprint-definition-FD4TL5EE.mjs.map +7 -0
- package/dist/lib/browser/chunk-5DOZV3PW.mjs +294 -0
- package/dist/lib/browser/chunk-5DOZV3PW.mjs.map +7 -0
- package/dist/lib/browser/chunk-DUO4WTAM.mjs +28 -0
- package/dist/lib/browser/chunk-DUO4WTAM.mjs.map +7 -0
- package/dist/lib/browser/chunk-FBLAVAMY.mjs +84 -0
- package/dist/lib/browser/chunk-FBLAVAMY.mjs.map +7 -0
- package/dist/lib/browser/chunk-Q5TZCIH7.mjs +8 -0
- package/dist/lib/browser/chunk-Q5TZCIH7.mjs.map +7 -0
- package/dist/lib/browser/chunk-TIIGNOGK.mjs +169 -0
- package/dist/lib/browser/chunk-TIIGNOGK.mjs.map +7 -0
- package/dist/lib/browser/index.mjs +56 -28
- package/dist/lib/browser/index.mjs.map +3 -3
- package/dist/lib/browser/intent-resolver-GXZN2EBT.mjs +25 -0
- package/dist/lib/browser/intent-resolver-GXZN2EBT.mjs.map +7 -0
- package/dist/lib/browser/meta.json +1 -1
- package/dist/lib/browser/react-surface-KTTAUHAA.mjs +38 -0
- package/dist/lib/browser/react-surface-KTTAUHAA.mjs.map +7 -0
- package/dist/lib/browser/types/index.mjs +6 -7
- package/dist/lib/node-esm/ChessboardContainer-7OQ6TOKB.mjs +97 -0
- package/dist/lib/node-esm/ChessboardContainer-7OQ6TOKB.mjs.map +7 -0
- package/dist/lib/node-esm/blueprint-definition-O7ZM3HUF.mjs +12 -0
- package/dist/lib/node-esm/blueprint-definition-O7ZM3HUF.mjs.map +7 -0
- package/dist/lib/node-esm/chunk-F4C2HVZL.mjs +295 -0
- package/dist/lib/node-esm/chunk-F4C2HVZL.mjs.map +7 -0
- package/dist/lib/node-esm/chunk-KNOOJFIR.mjs +29 -0
- package/dist/lib/node-esm/chunk-KNOOJFIR.mjs.map +7 -0
- package/dist/lib/node-esm/chunk-MP3A3AGA.mjs +85 -0
- package/dist/lib/node-esm/chunk-MP3A3AGA.mjs.map +7 -0
- package/dist/lib/node-esm/chunk-RTCAAOLF.mjs +170 -0
- package/dist/lib/node-esm/chunk-RTCAAOLF.mjs.map +7 -0
- package/dist/lib/node-esm/{chunk-TD2SWK2H.mjs → chunk-VVXTAVIS.mjs} +3 -3
- package/dist/lib/node-esm/chunk-VVXTAVIS.mjs.map +7 -0
- package/dist/lib/node-esm/index.mjs +56 -28
- package/dist/lib/node-esm/index.mjs.map +3 -3
- package/dist/lib/node-esm/intent-resolver-HK5L6P6Z.mjs +26 -0
- package/dist/lib/node-esm/intent-resolver-HK5L6P6Z.mjs.map +7 -0
- package/dist/lib/node-esm/meta.json +1 -1
- package/dist/lib/node-esm/react-surface-VO4PSY6O.mjs +39 -0
- package/dist/lib/node-esm/react-surface-VO4PSY6O.mjs.map +7 -0
- package/dist/lib/node-esm/types/index.mjs +6 -7
- package/dist/types/src/ChessPlugin.d.ts +1 -1
- package/dist/types/src/ChessPlugin.d.ts.map +1 -1
- package/dist/types/src/capabilities/artifact-definition.d.ts +1 -1
- package/dist/types/src/capabilities/artifact-definition.d.ts.map +1 -1
- package/dist/types/src/capabilities/blueprint-definition.d.ts +6 -0
- package/dist/types/src/capabilities/blueprint-definition.d.ts.map +1 -0
- package/dist/types/src/capabilities/index.d.ts +4 -3
- package/dist/types/src/capabilities/index.d.ts.map +1 -1
- package/dist/types/src/capabilities/intent-resolver.d.ts +1 -1
- package/dist/types/src/capabilities/intent-resolver.d.ts.map +1 -1
- package/dist/types/src/capabilities/react-surface.d.ts +1 -1
- package/dist/types/src/capabilities/react-surface.d.ts.map +1 -1
- package/dist/types/src/components/Card.stories.d.ts +22 -0
- package/dist/types/src/components/Card.stories.d.ts.map +1 -0
- package/dist/types/src/components/Chessboard.d.ts +38 -0
- package/dist/types/src/components/Chessboard.d.ts.map +1 -0
- package/dist/types/src/components/Chessboard.stories.d.ts +41 -5
- package/dist/types/src/components/Chessboard.stories.d.ts.map +1 -1
- package/dist/types/src/components/ChessboardArticle.d.ts +7 -0
- package/dist/types/src/components/ChessboardArticle.d.ts.map +1 -0
- package/dist/types/src/components/ChessboardContainer.d.ts +9 -0
- package/dist/types/src/components/ChessboardContainer.d.ts.map +1 -0
- package/dist/types/src/components/Info.d.ts +21 -0
- package/dist/types/src/components/Info.d.ts.map +1 -0
- package/dist/types/src/components/index.d.ts +2 -5
- package/dist/types/src/components/index.d.ts.map +1 -1
- package/dist/types/src/functions/create.d.ts +14 -0
- package/dist/types/src/functions/create.d.ts.map +1 -0
- package/dist/types/src/functions/index.d.ts +4 -0
- package/dist/types/src/functions/index.d.ts.map +1 -0
- package/dist/types/src/functions/move.d.ts +8 -0
- package/dist/types/src/functions/move.d.ts.map +1 -0
- package/dist/types/src/functions/play.d.ts +9 -0
- package/dist/types/src/functions/play.d.ts.map +1 -0
- package/dist/types/src/index.d.ts +2 -0
- package/dist/types/src/index.d.ts.map +1 -1
- package/dist/types/src/meta.d.ts +0 -1
- package/dist/types/src/meta.d.ts.map +1 -1
- package/dist/types/src/translations.d.ts +12 -2
- package/dist/types/src/translations.d.ts.map +1 -1
- package/dist/types/src/types/Chess.d.ts +27 -0
- package/dist/types/src/types/Chess.d.ts.map +1 -0
- package/dist/types/src/types/ChessAction.d.ts +24 -0
- package/dist/types/src/types/ChessAction.d.ts.map +1 -0
- package/dist/types/src/types/index.d.ts +2 -2
- package/dist/types/src/types/index.d.ts.map +1 -1
- package/dist/types/tsconfig.tsbuildinfo +1 -1
- package/package.json +44 -34
- package/src/ChessPlugin.tsx +49 -48
- package/src/capabilities/artifact-definition.ts +27 -22
- package/src/capabilities/blueprint-definition.ts +36 -0
- package/src/capabilities/index.ts +3 -1
- package/src/capabilities/intent-resolver.ts +3 -7
- package/src/capabilities/react-surface.tsx +6 -13
- package/src/components/Card.stories.tsx +66 -0
- package/src/components/Chessboard.stories.tsx +36 -26
- package/src/components/Chessboard.tsx +177 -0
- package/src/components/ChessboardArticle.tsx +68 -0
- package/src/components/ChessboardContainer.tsx +44 -0
- package/src/components/Info.tsx +236 -0
- package/src/components/index.ts +2 -2
- package/src/functions/create.ts +32 -0
- package/src/functions/index.ts +7 -0
- package/src/functions/move.ts +45 -0
- package/src/functions/play.ts +55 -0
- package/src/index.ts +2 -0
- package/src/meta.ts +7 -5
- package/src/translations.ts +12 -2
- package/src/types/Chess.ts +60 -0
- package/src/types/ChessAction.ts +19 -0
- package/src/types/index.ts +2 -2
- package/dist/lib/browser/ChessContainer-DL5J6VIK.mjs +0 -101
- package/dist/lib/browser/ChessContainer-DL5J6VIK.mjs.map +0 -7
- package/dist/lib/browser/artifact-definition-4AMZY5LQ.mjs +0 -182
- package/dist/lib/browser/artifact-definition-4AMZY5LQ.mjs.map +0 -7
- package/dist/lib/browser/chunk-2VF6U52K.mjs +0 -8
- package/dist/lib/browser/chunk-2VF6U52K.mjs.map +0 -7
- package/dist/lib/browser/chunk-A3WEUKPK.mjs +0 -61
- package/dist/lib/browser/chunk-A3WEUKPK.mjs.map +0 -7
- package/dist/lib/browser/chunk-AZSYKAFK.mjs +0 -61
- package/dist/lib/browser/chunk-AZSYKAFK.mjs.map +0 -7
- package/dist/lib/browser/intent-resolver-KQYYX44R.mjs +0 -28
- package/dist/lib/browser/intent-resolver-KQYYX44R.mjs.map +0 -7
- package/dist/lib/browser/react-surface-4TBVZUKU.mjs +0 -43
- package/dist/lib/browser/react-surface-4TBVZUKU.mjs.map +0 -7
- package/dist/lib/node-esm/ChessContainer-WVOL2KSE.mjs +0 -102
- package/dist/lib/node-esm/ChessContainer-WVOL2KSE.mjs.map +0 -7
- package/dist/lib/node-esm/artifact-definition-TT3747JB.mjs +0 -183
- package/dist/lib/node-esm/artifact-definition-TT3747JB.mjs.map +0 -7
- package/dist/lib/node-esm/chunk-2DRWEDVT.mjs +0 -63
- package/dist/lib/node-esm/chunk-2DRWEDVT.mjs.map +0 -7
- package/dist/lib/node-esm/chunk-IS3Y25PL.mjs +0 -63
- package/dist/lib/node-esm/chunk-IS3Y25PL.mjs.map +0 -7
- package/dist/lib/node-esm/chunk-TD2SWK2H.mjs.map +0 -7
- package/dist/lib/node-esm/intent-resolver-GXK2JROQ.mjs +0 -29
- package/dist/lib/node-esm/intent-resolver-GXK2JROQ.mjs.map +0 -7
- package/dist/lib/node-esm/react-surface-PAUEOIBL.mjs +0 -44
- package/dist/lib/node-esm/react-surface-PAUEOIBL.mjs.map +0 -7
- package/dist/types/src/components/ChessComponent.d.ts +0 -6
- package/dist/types/src/components/ChessComponent.d.ts.map +0 -1
- package/dist/types/src/components/ChessContainer.d.ts +0 -8
- package/dist/types/src/components/ChessContainer.d.ts.map +0 -1
- package/dist/types/src/components/ChessPanel.d.ts +0 -11
- package/dist/types/src/components/ChessPanel.d.ts.map +0 -1
- package/dist/types/src/components/PlayerSelector.d.ts +0 -8
- package/dist/types/src/components/PlayerSelector.d.ts.map +0 -1
- package/dist/types/src/types/schema.d.ts +0 -29
- package/dist/types/src/types/schema.d.ts.map +0 -1
- package/dist/types/src/types/types.d.ts +0 -32
- package/dist/types/src/types/types.d.ts.map +0 -1
- package/src/components/ChessComponent.tsx +0 -54
- package/src/components/ChessContainer.tsx +0 -35
- package/src/components/ChessPanel.tsx +0 -67
- package/src/components/PlayerSelector.tsx +0 -80
- package/src/types/schema.ts +0 -34
- package/src/types/types.ts +0 -20
|
@@ -0,0 +1,177 @@
|
|
|
1
|
+
//
|
|
2
|
+
// Copyright 2024 DXOS.org
|
|
3
|
+
//
|
|
4
|
+
|
|
5
|
+
import React, {
|
|
6
|
+
type PropsWithChildren,
|
|
7
|
+
forwardRef,
|
|
8
|
+
useCallback,
|
|
9
|
+
useEffect,
|
|
10
|
+
useImperativeHandle,
|
|
11
|
+
useMemo,
|
|
12
|
+
useRef,
|
|
13
|
+
} from 'react';
|
|
14
|
+
|
|
15
|
+
import { addEventListener } from '@dxos/async';
|
|
16
|
+
import { type ThemedClassName } from '@dxos/react-ui';
|
|
17
|
+
import {
|
|
18
|
+
ChessModel,
|
|
19
|
+
Gameboard,
|
|
20
|
+
type GameboardRootProps,
|
|
21
|
+
Chessboard as NaturalChessboard,
|
|
22
|
+
type ChessboardProps as NaturalChessboardProps,
|
|
23
|
+
getRawPgn,
|
|
24
|
+
useGameboardContext,
|
|
25
|
+
} from '@dxos/react-ui-gameboard';
|
|
26
|
+
import { useSoundEffect } from '@dxos/react-ui-sfx';
|
|
27
|
+
import { mx } from '@dxos/react-ui-theme';
|
|
28
|
+
|
|
29
|
+
import { type Chess } from '../types';
|
|
30
|
+
|
|
31
|
+
import { Info, type InfoProps } from './Info';
|
|
32
|
+
|
|
33
|
+
export interface ChessboardController {
|
|
34
|
+
setMoveNumber(index: number): void;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export class ExtendedChessModel extends ChessModel {
|
|
38
|
+
constructor(readonly object: Chess.Game) {
|
|
39
|
+
super();
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
//
|
|
44
|
+
// Root
|
|
45
|
+
//
|
|
46
|
+
|
|
47
|
+
type RootProps = PropsWithChildren<{
|
|
48
|
+
game: Chess.Game;
|
|
49
|
+
}>;
|
|
50
|
+
|
|
51
|
+
const Root = forwardRef<ChessboardController, RootProps>(({ game, children }, forwardedRef) => {
|
|
52
|
+
const model = useMemo(() => new ExtendedChessModel(game), []);
|
|
53
|
+
const click = useSoundEffect('Click');
|
|
54
|
+
|
|
55
|
+
// Controller.
|
|
56
|
+
useImperativeHandle(forwardedRef, () => {
|
|
57
|
+
return {
|
|
58
|
+
setMoveNumber: (index) => model.setMoveIndex(index),
|
|
59
|
+
};
|
|
60
|
+
}, [model]);
|
|
61
|
+
|
|
62
|
+
// External change.
|
|
63
|
+
// NOTE: Warning if user has not interacted with the board.
|
|
64
|
+
useEffect(() => {
|
|
65
|
+
if (model.pgn !== getRawPgn(game.pgn ?? '')) {
|
|
66
|
+
const silent = model.pgn === '*';
|
|
67
|
+
model.update(game.pgn);
|
|
68
|
+
if (!silent) {
|
|
69
|
+
void click.play();
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
}, [game.pgn]);
|
|
73
|
+
|
|
74
|
+
// Move.
|
|
75
|
+
const handleDrop = useCallback<NonNullable<GameboardRootProps<ChessModel>['onDrop']>>(
|
|
76
|
+
(move) => {
|
|
77
|
+
if (!model.makeMove(move)) {
|
|
78
|
+
return false;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
void click.play();
|
|
82
|
+
game.pgn = model.pgn;
|
|
83
|
+
return true;
|
|
84
|
+
},
|
|
85
|
+
[model],
|
|
86
|
+
);
|
|
87
|
+
|
|
88
|
+
return (
|
|
89
|
+
<Gameboard.Root model={model} onDrop={handleDrop}>
|
|
90
|
+
{children}
|
|
91
|
+
</Gameboard.Root>
|
|
92
|
+
);
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
//
|
|
96
|
+
// Content
|
|
97
|
+
//
|
|
98
|
+
|
|
99
|
+
type Role = 'card--popover' | 'card--intrinsic' | 'card--extrinsic';
|
|
100
|
+
|
|
101
|
+
type ContentProps = ThemedClassName<PropsWithChildren<{ role?: Role }>>;
|
|
102
|
+
|
|
103
|
+
const Content = ({ classNames, children, role }: ContentProps) => {
|
|
104
|
+
return (
|
|
105
|
+
<Gameboard.Content
|
|
106
|
+
classNames={mx(classNames, role === 'card--popover' && 'size-container popover-square')}
|
|
107
|
+
grow={!role || role === 'card--extrinsic'}
|
|
108
|
+
contain={!role || role === 'card--extrinsic' || role === 'card--popover'}
|
|
109
|
+
>
|
|
110
|
+
{children}
|
|
111
|
+
</Gameboard.Content>
|
|
112
|
+
);
|
|
113
|
+
};
|
|
114
|
+
|
|
115
|
+
//
|
|
116
|
+
// Board
|
|
117
|
+
//
|
|
118
|
+
|
|
119
|
+
type BoardProps = NaturalChessboardProps;
|
|
120
|
+
|
|
121
|
+
const Board = (props: BoardProps) => {
|
|
122
|
+
const { model } = useGameboardContext<ChessModel>(Board.displayName);
|
|
123
|
+
|
|
124
|
+
// Keyboard navigation.
|
|
125
|
+
const ref = useRef<HTMLDivElement>(null);
|
|
126
|
+
useEffect(() => {
|
|
127
|
+
if (!ref.current) {
|
|
128
|
+
return;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
// Participate in keyboard navigation (set tabIndex={0})
|
|
132
|
+
ref.current.setAttribute('data-arrow-keys', 'all');
|
|
133
|
+
|
|
134
|
+
return addEventListener(ref.current, 'keydown', (ev) => {
|
|
135
|
+
switch (ev.key) {
|
|
136
|
+
case 'ArrowUp':
|
|
137
|
+
model.setMoveIndex(0);
|
|
138
|
+
break;
|
|
139
|
+
case 'ArrowDown':
|
|
140
|
+
model.setMoveIndex(model.game.history().length);
|
|
141
|
+
break;
|
|
142
|
+
case 'ArrowLeft':
|
|
143
|
+
if (model.moveIndex.value > 0) {
|
|
144
|
+
model.setMoveIndex(model.moveIndex.value - 1);
|
|
145
|
+
}
|
|
146
|
+
break;
|
|
147
|
+
case 'ArrowRight':
|
|
148
|
+
if (model.moveIndex.value < model.game.history().length) {
|
|
149
|
+
model.setMoveIndex(model.moveIndex.value + 1);
|
|
150
|
+
}
|
|
151
|
+
break;
|
|
152
|
+
}
|
|
153
|
+
});
|
|
154
|
+
}, [model]);
|
|
155
|
+
|
|
156
|
+
return <NaturalChessboard ref={ref} {...props} />;
|
|
157
|
+
};
|
|
158
|
+
|
|
159
|
+
Board.displayName = 'Chessboard.Board';
|
|
160
|
+
|
|
161
|
+
//
|
|
162
|
+
// Chessboard
|
|
163
|
+
//
|
|
164
|
+
|
|
165
|
+
export const Chessboard = {
|
|
166
|
+
Root: Root,
|
|
167
|
+
Content: Content,
|
|
168
|
+
Board: Board,
|
|
169
|
+
Info: Info,
|
|
170
|
+
};
|
|
171
|
+
|
|
172
|
+
export type {
|
|
173
|
+
RootProps as ChessboardRootProps,
|
|
174
|
+
ContentProps as ChessboardContentProps,
|
|
175
|
+
BoardProps as ChessboardBoardProps,
|
|
176
|
+
InfoProps as ChessboardInfoProps,
|
|
177
|
+
};
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
//
|
|
2
|
+
// Copyright 2024 DXOS.org
|
|
3
|
+
//
|
|
4
|
+
|
|
5
|
+
import React, { useCallback, useRef, useState } from 'react';
|
|
6
|
+
|
|
7
|
+
import { Toolbar, useTranslation } from '@dxos/react-ui';
|
|
8
|
+
import { type Player } from '@dxos/react-ui-gameboard';
|
|
9
|
+
import { StackItem } from '@dxos/react-ui-stack';
|
|
10
|
+
import { mx } from '@dxos/react-ui-theme';
|
|
11
|
+
|
|
12
|
+
import { meta } from '../meta';
|
|
13
|
+
import { type Chess } from '../types';
|
|
14
|
+
|
|
15
|
+
import { Chessboard, type ChessboardController, type ChessboardInfoProps } from './Chessboard';
|
|
16
|
+
|
|
17
|
+
export const ChessboardArticle = ({ game, role }: { game: Chess.Game; role?: string }) => {
|
|
18
|
+
const { t } = useTranslation(meta.id);
|
|
19
|
+
const [orientation, setOrientation] = useState<Player>('white');
|
|
20
|
+
const [open, setOpen] = useState(true);
|
|
21
|
+
const controller = useRef<ChessboardController>(null);
|
|
22
|
+
|
|
23
|
+
// TODO(burdon): Keyboard handler.
|
|
24
|
+
const handleSelect = useCallback<NonNullable<ChessboardInfoProps['onSelect']>>((index) => {
|
|
25
|
+
controller.current?.setMoveNumber(index);
|
|
26
|
+
}, []);
|
|
27
|
+
|
|
28
|
+
return (
|
|
29
|
+
<StackItem.Content toolbar classNames='@container'>
|
|
30
|
+
<Chessboard.Root game={game} ref={controller}>
|
|
31
|
+
<Toolbar.Root>
|
|
32
|
+
<Toolbar.IconButton
|
|
33
|
+
icon='ph--info--regular'
|
|
34
|
+
iconOnly
|
|
35
|
+
label={t('toggle info button')}
|
|
36
|
+
disabled={open}
|
|
37
|
+
classNames={mx('invisible @3xl:visible')}
|
|
38
|
+
onClick={() => setOpen((open) => !open)}
|
|
39
|
+
/>
|
|
40
|
+
</Toolbar.Root>
|
|
41
|
+
<div
|
|
42
|
+
className={mx(
|
|
43
|
+
'grid bs-full is-full gap-2',
|
|
44
|
+
open && '@3xl:grid-cols-[1fr_320px]',
|
|
45
|
+
role === 'section' && 'aspect-square',
|
|
46
|
+
role === 'section' && open && '@3xl:aspect-auto',
|
|
47
|
+
)}
|
|
48
|
+
>
|
|
49
|
+
<Chessboard.Content>
|
|
50
|
+
<Chessboard.Board classNames='m-4 rounded-sm overflow-hidden' orientation={orientation} />
|
|
51
|
+
</Chessboard.Content>
|
|
52
|
+
{open && (
|
|
53
|
+
<div className='hidden @3xl:flex flex-col p-8 justify-center items-center overflow-hidden'>
|
|
54
|
+
<Chessboard.Info
|
|
55
|
+
orientation={orientation}
|
|
56
|
+
min={8}
|
|
57
|
+
max={8}
|
|
58
|
+
onOrientationChange={setOrientation}
|
|
59
|
+
onClose={() => setOpen(false)}
|
|
60
|
+
onSelect={handleSelect}
|
|
61
|
+
/>
|
|
62
|
+
</div>
|
|
63
|
+
)}
|
|
64
|
+
</div>
|
|
65
|
+
</Chessboard.Root>
|
|
66
|
+
</StackItem.Content>
|
|
67
|
+
);
|
|
68
|
+
};
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
//
|
|
2
|
+
// Copyright 2024 DXOS.org
|
|
3
|
+
//
|
|
4
|
+
|
|
5
|
+
import React from 'react';
|
|
6
|
+
|
|
7
|
+
import { getSpace } from '@dxos/react-client/echo';
|
|
8
|
+
|
|
9
|
+
import { type Chess } from '../types';
|
|
10
|
+
|
|
11
|
+
import { Chessboard } from './Chessboard';
|
|
12
|
+
import { ChessboardArticle } from './ChessboardArticle';
|
|
13
|
+
|
|
14
|
+
export type ChessboardContainerProps = {
|
|
15
|
+
game: Chess.Game;
|
|
16
|
+
role?: string;
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
export const ChessboardContainer = ({ game, role }: ChessboardContainerProps) => {
|
|
20
|
+
const space = getSpace(game);
|
|
21
|
+
if (!space) {
|
|
22
|
+
return null;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
switch (role) {
|
|
26
|
+
case 'card--popover':
|
|
27
|
+
case 'card--intrinsic':
|
|
28
|
+
case 'card--extrinsic': {
|
|
29
|
+
return (
|
|
30
|
+
<Chessboard.Root game={game}>
|
|
31
|
+
<Chessboard.Content role={role}>
|
|
32
|
+
<Chessboard.Board />
|
|
33
|
+
</Chessboard.Content>
|
|
34
|
+
</Chessboard.Root>
|
|
35
|
+
);
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
default: {
|
|
39
|
+
return <ChessboardArticle game={game} role={role} />;
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
};
|
|
43
|
+
|
|
44
|
+
export default ChessboardContainer;
|
|
@@ -0,0 +1,236 @@
|
|
|
1
|
+
//
|
|
2
|
+
// Copyright 2023 DXOS.org
|
|
3
|
+
//
|
|
4
|
+
|
|
5
|
+
import React, { type JSX, type PropsWithChildren, useEffect, useMemo, useRef } from 'react';
|
|
6
|
+
|
|
7
|
+
import { generateName } from '@dxos/display-name';
|
|
8
|
+
import { type SpaceMember, getSpace, useMembers } from '@dxos/react-client/echo';
|
|
9
|
+
import { Icon, IconButton, Select, type ThemedClassName, useTranslation } from '@dxos/react-ui';
|
|
10
|
+
import { type Player, useGameboardContext } from '@dxos/react-ui-gameboard';
|
|
11
|
+
import { mx } from '@dxos/react-ui-theme';
|
|
12
|
+
|
|
13
|
+
import { meta } from '../meta';
|
|
14
|
+
|
|
15
|
+
import { type ExtendedChessModel } from './Chessboard';
|
|
16
|
+
|
|
17
|
+
export type InfoProps = ThemedClassName<
|
|
18
|
+
{
|
|
19
|
+
orientation?: Player;
|
|
20
|
+
onOrientationChange?: (orientation: Player) => void;
|
|
21
|
+
onClose?: () => void;
|
|
22
|
+
} & Pick<HistoryProps, 'min' | 'max' | 'onSelect'>
|
|
23
|
+
>;
|
|
24
|
+
|
|
25
|
+
export const Info = ({ classNames, orientation = 'white', onOrientationChange, onClose, ...props }: InfoProps) => {
|
|
26
|
+
const { t } = useTranslation(meta.id);
|
|
27
|
+
const { model } = useGameboardContext<ExtendedChessModel>(Info.displayName);
|
|
28
|
+
const members = useMembers(getSpace(model.object)?.key);
|
|
29
|
+
|
|
30
|
+
return (
|
|
31
|
+
<div
|
|
32
|
+
className={mx(
|
|
33
|
+
'grid grid-rows-[min-content_1fr_min-content] is-full min-is-[18rem] p-2 overflow-hidden bg-groupSurface rounded-sm',
|
|
34
|
+
classNames,
|
|
35
|
+
)}
|
|
36
|
+
>
|
|
37
|
+
<PlayerIndicator
|
|
38
|
+
model={model}
|
|
39
|
+
player={orientation === 'white' ? 'black' : 'white'}
|
|
40
|
+
icon={
|
|
41
|
+
onClose && (
|
|
42
|
+
<IconButton icon='ph--x--regular' iconOnly label={t('close info button')} size={4} onClick={onClose} />
|
|
43
|
+
)
|
|
44
|
+
}
|
|
45
|
+
>
|
|
46
|
+
<PlayerSelector
|
|
47
|
+
value={model.object.players?.[orientation === 'white' ? 'black' : 'white']}
|
|
48
|
+
onValueChange={(value) => {
|
|
49
|
+
model.object.players![orientation === 'white' ? 'black' : 'white'] = value;
|
|
50
|
+
}}
|
|
51
|
+
members={members}
|
|
52
|
+
/>
|
|
53
|
+
</PlayerIndicator>
|
|
54
|
+
|
|
55
|
+
<History model={model} {...props} />
|
|
56
|
+
|
|
57
|
+
<PlayerIndicator
|
|
58
|
+
model={model}
|
|
59
|
+
player={orientation}
|
|
60
|
+
icon={
|
|
61
|
+
onOrientationChange && (
|
|
62
|
+
<IconButton
|
|
63
|
+
classNames={mx('transition duration-200 ease-linear', orientation === 'white' && 'rotate-180')}
|
|
64
|
+
icon='ph--arrows-clockwise--regular'
|
|
65
|
+
iconOnly
|
|
66
|
+
label={t('flip board button')}
|
|
67
|
+
size={4}
|
|
68
|
+
onClick={() => onOrientationChange(orientation === 'white' ? 'black' : 'white')}
|
|
69
|
+
/>
|
|
70
|
+
)
|
|
71
|
+
}
|
|
72
|
+
>
|
|
73
|
+
<PlayerSelector
|
|
74
|
+
value={model.object.players?.[orientation]}
|
|
75
|
+
onValueChange={(value) => {
|
|
76
|
+
model.object.players![orientation] = value;
|
|
77
|
+
}}
|
|
78
|
+
members={members}
|
|
79
|
+
/>
|
|
80
|
+
</PlayerIndicator>
|
|
81
|
+
</div>
|
|
82
|
+
);
|
|
83
|
+
};
|
|
84
|
+
|
|
85
|
+
Info.displayName = 'Chessboard.Info';
|
|
86
|
+
|
|
87
|
+
//
|
|
88
|
+
// History
|
|
89
|
+
//
|
|
90
|
+
|
|
91
|
+
type HistoryProps = ThemedClassName<{
|
|
92
|
+
model: ExtendedChessModel;
|
|
93
|
+
min?: number;
|
|
94
|
+
max?: number;
|
|
95
|
+
onSelect?: (index: number) => void;
|
|
96
|
+
}>;
|
|
97
|
+
|
|
98
|
+
const History = ({ classNames, model, min, max, onSelect }: HistoryProps) => {
|
|
99
|
+
const { t } = useTranslation(meta.id);
|
|
100
|
+
const label = model.game.isGameOver()
|
|
101
|
+
? model.game.isCheckmate()
|
|
102
|
+
? t('game.checkmate label')
|
|
103
|
+
: model.game.isStalemate()
|
|
104
|
+
? t('game.stalemate label')
|
|
105
|
+
: t('game.draw label')
|
|
106
|
+
: model.game.isCheck()
|
|
107
|
+
? t('game.check label')
|
|
108
|
+
: undefined;
|
|
109
|
+
|
|
110
|
+
const history = model.game.history();
|
|
111
|
+
const moves = useMemo(
|
|
112
|
+
() =>
|
|
113
|
+
history.reduce(
|
|
114
|
+
(acc, move, index) => {
|
|
115
|
+
if (index % 2 === 0) {
|
|
116
|
+
acc.push([{ index, move }]);
|
|
117
|
+
} else {
|
|
118
|
+
acc[acc.length - 1].push({ index, move });
|
|
119
|
+
}
|
|
120
|
+
return acc;
|
|
121
|
+
},
|
|
122
|
+
[] as { index: number; move: string }[][],
|
|
123
|
+
),
|
|
124
|
+
[history.length],
|
|
125
|
+
);
|
|
126
|
+
|
|
127
|
+
const scrollerRef = useRef<HTMLDivElement>(null);
|
|
128
|
+
useEffect(() => {
|
|
129
|
+
scrollerRef.current?.scrollTo({ top: scrollerRef.current.scrollHeight });
|
|
130
|
+
}, [history.length]);
|
|
131
|
+
|
|
132
|
+
useEffect(() => {
|
|
133
|
+
const div = scrollerRef.current?.querySelector(`[data-index="${model.moveIndex.value - 1}"]`);
|
|
134
|
+
scrollerRef.current?.classList.add('scrollbar-none');
|
|
135
|
+
div?.scrollIntoView({ behavior: 'smooth' });
|
|
136
|
+
scrollerRef.current?.classList.remove('scrollbar-none');
|
|
137
|
+
}, [model.moveIndex.value]);
|
|
138
|
+
|
|
139
|
+
return (
|
|
140
|
+
<div
|
|
141
|
+
ref={scrollerRef}
|
|
142
|
+
className={mx('overflow-y-auto', classNames)}
|
|
143
|
+
style={{
|
|
144
|
+
minHeight: min === undefined ? 'auto' : `${min * 24}px`,
|
|
145
|
+
maxHeight: max === undefined ? 'auto' : `${max * 24}px`,
|
|
146
|
+
}}
|
|
147
|
+
>
|
|
148
|
+
{moves.map(([a, b], index) => (
|
|
149
|
+
<div key={index} className='grid grid-cols-[3rem_1fr_1fr_1rem] gap-2 pis-4 leading-1'>
|
|
150
|
+
<div className='content-center text-xs text-subdued'>{index + 1}</div>
|
|
151
|
+
{a && (
|
|
152
|
+
<div
|
|
153
|
+
data-index={a.index}
|
|
154
|
+
className={mx('pis-2 cursor-pointer', a.index === model.moveIndex.value - 1 && 'bg-primary-500')}
|
|
155
|
+
onClick={() => onSelect?.(a.index + 1)}
|
|
156
|
+
>
|
|
157
|
+
{a.move}
|
|
158
|
+
</div>
|
|
159
|
+
)}
|
|
160
|
+
{b && (
|
|
161
|
+
<div
|
|
162
|
+
data-index={b.index}
|
|
163
|
+
className={mx('pis-2 cursor-pointer', b.index === model.moveIndex.value - 1 && 'bg-primary-500')}
|
|
164
|
+
onClick={() => onSelect?.(b.index + 1)}
|
|
165
|
+
>
|
|
166
|
+
{b.move}
|
|
167
|
+
</div>
|
|
168
|
+
)}
|
|
169
|
+
</div>
|
|
170
|
+
))}
|
|
171
|
+
{label && <div className='text-center'>{label}</div>}
|
|
172
|
+
</div>
|
|
173
|
+
);
|
|
174
|
+
};
|
|
175
|
+
|
|
176
|
+
//
|
|
177
|
+
// PlayerIndicator
|
|
178
|
+
//
|
|
179
|
+
|
|
180
|
+
type PlayerIndicatorProps = PropsWithChildren<{
|
|
181
|
+
model: ExtendedChessModel;
|
|
182
|
+
player: Player;
|
|
183
|
+
icon?: JSX.Element;
|
|
184
|
+
}>;
|
|
185
|
+
|
|
186
|
+
const PlayerIndicator = ({ children, model, player, icon }: PlayerIndicatorProps) => {
|
|
187
|
+
const turn = player === (model.game.turn() === 'w' ? 'white' : 'black');
|
|
188
|
+
return (
|
|
189
|
+
<div className='grid grid-cols-[2rem_1fr_2rem] gap-2 bs-[--rail-size] pli-1 flex items-center overflow-hidden'>
|
|
190
|
+
<div className='place-items-center'>
|
|
191
|
+
<Icon
|
|
192
|
+
icon={turn ? 'ph--circle--fill' : 'ph--circle--thin'}
|
|
193
|
+
size={6}
|
|
194
|
+
classNames={mx(turn && (model.game.isCheckmate() ? 'text-red-500' : 'text-green-500'))}
|
|
195
|
+
/>
|
|
196
|
+
</div>
|
|
197
|
+
<div className='truncate overflow-hidden items-center'>{children}</div>
|
|
198
|
+
{icon}
|
|
199
|
+
</div>
|
|
200
|
+
);
|
|
201
|
+
};
|
|
202
|
+
|
|
203
|
+
//
|
|
204
|
+
// PlayerSelector
|
|
205
|
+
//
|
|
206
|
+
|
|
207
|
+
type PlayerSelectorProps = {
|
|
208
|
+
value?: string;
|
|
209
|
+
onValueChange: (player: string) => void;
|
|
210
|
+
members: SpaceMember[];
|
|
211
|
+
};
|
|
212
|
+
|
|
213
|
+
const PlayerSelector = ({ value, onValueChange, members }: PlayerSelectorProps) => {
|
|
214
|
+
const { t } = useTranslation(meta.id);
|
|
215
|
+
return (
|
|
216
|
+
<Select.Root value={value} onValueChange={onValueChange}>
|
|
217
|
+
<Select.TriggerButton placeholder={t('select player button')} />
|
|
218
|
+
<Select.Portal>
|
|
219
|
+
<Select.Content>
|
|
220
|
+
<Select.Viewport>
|
|
221
|
+
{members.map((member) => {
|
|
222
|
+
const memberKey = member.identity.identityKey.toHex();
|
|
223
|
+
const displayName = member.identity?.profile?.displayName || generateName(memberKey);
|
|
224
|
+
return (
|
|
225
|
+
<Select.Option key={memberKey} value={memberKey}>
|
|
226
|
+
{displayName}
|
|
227
|
+
</Select.Option>
|
|
228
|
+
);
|
|
229
|
+
})}
|
|
230
|
+
</Select.Viewport>
|
|
231
|
+
<Select.Arrow />
|
|
232
|
+
</Select.Content>
|
|
233
|
+
</Select.Portal>
|
|
234
|
+
</Select.Root>
|
|
235
|
+
);
|
|
236
|
+
};
|
package/src/components/index.ts
CHANGED
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
//
|
|
2
|
+
// Copyright 2025 DXOS.org
|
|
3
|
+
//
|
|
4
|
+
|
|
5
|
+
import * as Effect from 'effect/Effect';
|
|
6
|
+
import * as Schema from 'effect/Schema';
|
|
7
|
+
|
|
8
|
+
import { DatabaseService, defineFunction } from '@dxos/functions';
|
|
9
|
+
|
|
10
|
+
import { Chess } from '../types';
|
|
11
|
+
|
|
12
|
+
export default defineFunction({
|
|
13
|
+
key: 'dxos.org/function/chess/create',
|
|
14
|
+
name: 'Create Chess',
|
|
15
|
+
description: 'Creates a new chess game.',
|
|
16
|
+
inputSchema: Schema.Struct({
|
|
17
|
+
pgn: Schema.optional(
|
|
18
|
+
Schema.String.annotations({
|
|
19
|
+
description: 'Portable Game Notation.',
|
|
20
|
+
}),
|
|
21
|
+
),
|
|
22
|
+
fen: Schema.optional(
|
|
23
|
+
Schema.String.annotations({
|
|
24
|
+
description: 'Forsyth-Edwards Notation.',
|
|
25
|
+
}),
|
|
26
|
+
),
|
|
27
|
+
}),
|
|
28
|
+
outputSchema: Chess.Game,
|
|
29
|
+
handler: Effect.fn(function* ({ data: { pgn, fen } }) {
|
|
30
|
+
return yield* DatabaseService.add(Chess.makeGame({ pgn, fen }));
|
|
31
|
+
}),
|
|
32
|
+
});
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
//
|
|
2
|
+
// Copyright 2025 DXOS.org
|
|
3
|
+
//
|
|
4
|
+
|
|
5
|
+
import { Chess as ChessJS } from 'chess.js';
|
|
6
|
+
import * as Effect from 'effect/Effect';
|
|
7
|
+
import * as Schema from 'effect/Schema';
|
|
8
|
+
|
|
9
|
+
import { ArtifactId } from '@dxos/assistant';
|
|
10
|
+
import { DatabaseService, defineFunction } from '@dxos/functions';
|
|
11
|
+
|
|
12
|
+
import { Chess } from '../types';
|
|
13
|
+
|
|
14
|
+
export default defineFunction({
|
|
15
|
+
key: 'dxos.org/function/chess/move',
|
|
16
|
+
name: 'Move',
|
|
17
|
+
description: 'Makes a move in the given chess game.',
|
|
18
|
+
inputSchema: Schema.Struct({
|
|
19
|
+
id: ArtifactId.annotations({
|
|
20
|
+
description: 'The ID of the chess object.',
|
|
21
|
+
}),
|
|
22
|
+
move: Schema.String.annotations({
|
|
23
|
+
description: 'The move to make in the chess game.',
|
|
24
|
+
examples: ['e4', 'Bf3'],
|
|
25
|
+
}),
|
|
26
|
+
}),
|
|
27
|
+
outputSchema: Schema.Struct({
|
|
28
|
+
pgn: Schema.String.annotations({
|
|
29
|
+
description: 'The PGN of the game after the move was played.',
|
|
30
|
+
}),
|
|
31
|
+
}),
|
|
32
|
+
handler: Effect.fn(function* ({ data: { id, move } }) {
|
|
33
|
+
const object = yield* DatabaseService.resolve(ArtifactId.toDXN(id), Chess.Game);
|
|
34
|
+
const chess = new ChessJS();
|
|
35
|
+
if (object.pgn) {
|
|
36
|
+
chess.loadPgn(object.pgn);
|
|
37
|
+
} else if (object.fen) {
|
|
38
|
+
chess.load(object.fen);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
chess.move(move, { strict: false });
|
|
42
|
+
object.pgn = chess.pgn();
|
|
43
|
+
return { pgn: object.pgn };
|
|
44
|
+
}),
|
|
45
|
+
});
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
//
|
|
2
|
+
// Copyright 2025 DXOS.org
|
|
3
|
+
//
|
|
4
|
+
|
|
5
|
+
import { Chess as ChessJS } from 'chess.js';
|
|
6
|
+
import * as Effect from 'effect/Effect';
|
|
7
|
+
import * as Schema from 'effect/Schema';
|
|
8
|
+
|
|
9
|
+
import { ArtifactId } from '@dxos/assistant';
|
|
10
|
+
import { DatabaseService, defineFunction } from '@dxos/functions';
|
|
11
|
+
|
|
12
|
+
import { Chess } from '../types';
|
|
13
|
+
|
|
14
|
+
export default defineFunction({
|
|
15
|
+
key: 'dxos.org/function/chess/play',
|
|
16
|
+
name: 'Play',
|
|
17
|
+
description: 'Uses the chess engine to play the next move.',
|
|
18
|
+
inputSchema: Schema.Struct({
|
|
19
|
+
id: ArtifactId.annotations({
|
|
20
|
+
description: 'The ID of the chess object.',
|
|
21
|
+
}),
|
|
22
|
+
side: Schema.optional(Schema.Literal('white', 'black', 'any')).annotations({
|
|
23
|
+
description: 'The side to play.',
|
|
24
|
+
defaultValue: 'any',
|
|
25
|
+
}),
|
|
26
|
+
}),
|
|
27
|
+
outputSchema: Schema.Struct({
|
|
28
|
+
pgn: Schema.String.annotations({
|
|
29
|
+
description: 'The PGN of the game after the move was played.',
|
|
30
|
+
}),
|
|
31
|
+
move: Schema.optional(Schema.String).annotations({
|
|
32
|
+
description: 'The move that was played.',
|
|
33
|
+
}),
|
|
34
|
+
}),
|
|
35
|
+
handler: Effect.fn(function* ({ data: { id, side = 'any' } }) {
|
|
36
|
+
const object = yield* DatabaseService.resolve(ArtifactId.toDXN(id), Chess.Game);
|
|
37
|
+
const chess = new ChessJS();
|
|
38
|
+
if (object.pgn) {
|
|
39
|
+
chess.loadPgn(object.pgn);
|
|
40
|
+
} else if (object.fen) {
|
|
41
|
+
chess.load(object.fen);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
if (!(side === 'any' || (chess.turn() === 'w' && side === 'white') || (chess.turn() === 'b' && side === 'black'))) {
|
|
45
|
+
return { move: undefined, pgn: object.pgn! };
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
const moves = chess.moves();
|
|
49
|
+
const move = moves[Math.floor(Math.random() * moves.length)];
|
|
50
|
+
|
|
51
|
+
chess.move(move, { strict: false });
|
|
52
|
+
object.pgn = chess.pgn();
|
|
53
|
+
return { move, pgn: object.pgn };
|
|
54
|
+
}),
|
|
55
|
+
});
|