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 { SeatQr } from './SeatQr';
9import { SoundToggle } from './SoundToggle';
10
11/**
12 * One player's own handful of table controls, opened from the gear beside the
13 * `?` in their nameplate.
14 *
15 * These used to sit in a bar in the middle of the table, which is the one place
16 * on a table that belongs to everybody: the pile lands there, four people are
17 * looking at it, and it is upside down to two of them. So they moved out to the
18 * edges — same treatment as the rules panel, anchored in the seat's own strip
19 * and turned to face it, so what you open reads the right way up to you and to
20 * nobody else.
21 *
22 * Undo is deliberately in all four of them rather than only the seat that made
23 * the mistake: whoever spots a mis-tap should be able to take it back, which is
24 * how it works when four people share one screen.
25 */
26export function TableMenu({
27 game,
28 prefs,
29 pos,
30 compact,
31 seatUrl,
32 onPrefs,
33 onSettings,
34 onClose,
35}: {
36 game: TableCtl;
37 prefs: Prefs;
38 /** The edge this is drawn at — the seat's own, so it faces its player. */
39 pos: SeatId;
40 compact?: boolean;
41 /**
42 * The QR for taking *this hand* onto a phone, where there is a room to take
43 * it into. Absent when the table is on its own — no server, or an online
44 * game, where everybody is already on their own device.
45 */
46 seatUrl?: string;
47 onPrefs: (p: Prefs) => void;
48 onSettings: () => void;
49 onClose: () => void;
50}) {
51 const t = useT();
52 const lang = useLang();
53 const s = game.state;
54 const undoable = s.phase === 'gameEnd' ? null : game.undoable();
55
56 return (
57 <div className={`help-slot ${SEAT_SLOT[pos]}`} onClick={onClose}>
58 <div
59 className="help menu"
60 style={{
61 transform: compact
62 ? 'translate(-50%, -50%)'
63 : `translate(-50%, -50%) rotate(${SEAT_ROT[pos]}deg)`,
64 }}
65 onClick={(e) => e.stopPropagation()}
66 >
67 <div className="help-head">
68 <h3>{t('桌面', 'Table', ' · ')}</h3>
69 <button className="btn ghost" onClick={onClose}>
70 {t('關閉', 'Close')}
71 </button>
72 </div>
73 <div className="menu-body">
74 <button
75 className="btn ghost undo"
76 disabled={!undoable}
77 onClick={() => {
78 game.undo();
79 onClose();
80 }}
81 >
82 <span className="undo-arrow" aria-hidden>
83 ↺
84 </span>
85 <span className="undo-what">
86 {undoable ? (
87 <>
88 {lang !== 'en' && (
89 <span className="zh" lang="zh-TW">
90 復原 {undoable.zh}
91 </span>
92 )}
93 {lang !== 'zh' && (
94 <span className="en" lang="en">
95 Undo · {undoable.en}
96 </span>
97 )}
98 </>
99 ) : (
100 <span>{t('沒有可復原的', 'Nothing to undo', ' · ')}</span>
101 )}
102 </span>
103 </button>
104
105 <SoundToggle prefs={prefs} onPrefs={onPrefs} />
106 <FullscreenToggle prefs={prefs} onPrefs={onPrefs} />
107
108 {/* Taking this hand off the shared screen. The QR is the seat's own,
109 so scanning it lands on this chair without anybody having to say
110 which one they are in — and the tiles here go face down for good
111 rather than under a card that lifts. */}
112 {seatUrl && (
113 <div className="menu-qr">
114 <SeatQr url={seatUrl} />
115 <span className="menu-qr-hint">
116 {lang !== 'en' && '掃碼把這副牌拿到手機上'}
117 {lang !== 'zh' && <em>scan to hold this hand on your phone</em>}
118 </span>
119 </div>
120 )}
121
122 {/* The moment anybody wants a name or the stakes changed is the
123 moment they went looking for a gear. */}
124 <button
125 className="btn ghost"
126 onClick={() => {
127 onClose();
128 onSettings();
129 }}
130 >
131 <Bi zh="設定" en="Settings" />
132 </button>
133 </div>
134 </div>
135 </div>
136 );
137}