anvilsign in

collin/mahjong

1import type { TableCtl } from '../game/ctl';
2import type { Prefs } from '../game/prefs';
3import type { SeatId } from '../game/types';
4import { Bi } from './Bi';
5import { FullscreenToggle } from './FullscreenToggle';
6import { useLang, useT } from './lang';
7import { SEAT_ROT, SEAT_SLOT } from './rotation';
8import { SoundToggle } from './SoundToggle';
9
10/**
11 * One player's own handful of table controls, opened from the gear beside the
12 * `?` in their nameplate.
13 *
14 * These used to sit in a bar in the middle of the table, which is the one place
15 * on a table that belongs to everybody: the pile lands there, four people are
16 * looking at it, and it is upside down to two of them. So they moved out to the
17 * edges — same treatment as the rules panel, anchored in the seat's own strip
18 * and turned to face it, so what you open reads the right way up to you and to
19 * nobody else.
20 *
21 * Undo is deliberately in all four of them rather than only the seat that made
22 * the mistake: whoever spots a mis-tap should be able to take it back, which is
23 * how it works when four people share one screen.
24 */
25export function TableMenu({
26 game,
27 prefs,
28 seat,
29 compact,
30 onPrefs,
31 onSettings,
32 onClose,
33}: {
34 game: TableCtl;
35 prefs: Prefs;
36 /** The edge this is drawn at — the seat's own, so it faces its player. */
37 seat: SeatId;
38 compact?: boolean;
39 onPrefs: (p: Prefs) => void;
40 onSettings: () => void;
41 onClose: () => void;
42}) {
43 const t = useT();
44 const lang = useLang();
45 const s = game.state;
46 const undoable = s.phase === 'gameEnd' ? null : game.undoable();
47
48 return (
49 <div className={`help-slot ${SEAT_SLOT[seat]}`} onClick={onClose}>
50 <div
51 className="help menu"
52 style={{
53 transform: compact
54 ? 'translate(-50%, -50%)'
55 : `translate(-50%, -50%) rotate(${SEAT_ROT[seat]}deg)`,
56 }}
57 onClick={(e) => e.stopPropagation()}
58 >
59 <div className="help-head">
60 <h3>{t('桌面', 'Table', ' · ')}</h3>
61 <button className="btn ghost" onClick={onClose}>
62 {t('關閉', 'Close')}
63 </button>
64 </div>
65 <div className="menu-body">
66 <button
67 className="btn ghost undo"
68 disabled={!undoable}
69 onClick={() => {
70 game.undo();
71 onClose();
72 }}
73 >
74 <span className="undo-arrow" aria-hidden>
75 ↺
76 </span>
77 <span className="undo-what">
78 {undoable ? (
79 <>
80 {lang !== 'en' && (
81 <span className="zh" lang="zh-TW">
82 復原 {undoable.zh}
83 </span>
84 )}
85 {lang !== 'zh' && (
86 <span className="en" lang="en">
87 Undo · {undoable.en}
88 </span>
89 )}
90 </>
91 ) : (
92 <span>{t('沒有可復原的', 'Nothing to undo', ' · ')}</span>
93 )}
94 </span>
95 </button>
96
97 <SoundToggle prefs={prefs} onPrefs={onPrefs} />
98 <FullscreenToggle prefs={prefs} onPrefs={onPrefs} />
99
100 {/* The moment anybody wants a name or the stakes changed is the
101 moment they went looking for a gear. */}
102 <button
103 className="btn ghost"
104 onClick={() => {
105 onClose();
106 onSettings();
107 }}
108 >
109 <Bi zh="設定" en="Settings" />
110 </button>
111 </div>
112 </div>
113 </div>
114 );
115}