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 type { SeatWho } from './Seat';
9import { SeatQr } from './SeatQr';
10import { SoundToggle } from './SoundToggle';
11
12/**
13 * One player's own handful of table controls, opened from the gear beside the
14 * `?` in their nameplate.
15 *
16 * These used to sit in a bar in the middle of the table, which is the one place
17 * on a table that belongs to everybody: the pile lands there, four people are
18 * looking at it, and it is upside down to two of them. So they moved out to the
19 * edges — same treatment as the rules panel, anchored in the seat's own strip
20 * and turned to face it, so what you open reads the right way up to you and to
21 * nobody else.
22 *
23 * Undo is deliberately in all four of them rather than only the seat that made
24 * the mistake: whoever spots a mis-tap should be able to take it back, which is
25 * how it works when four people share one screen.
26 */
27export function TableMenu({
28 game,
29 prefs,
30 pos,
31 compact,
32 seatUrl,
33 who,
34 onPrefs,
35 onSettings,
36 onClose,
37}: {
38 game: TableCtl;
39 prefs: Prefs;
40 /** The edge this is drawn at — the seat's own, so it faces its player. */
41 pos: SeatId;
42 compact?: boolean;
43 /**
44 * The QR for taking *this hand* onto a phone, once there is a room to take
45 * it into. Absent until then — `who.onPhone` is the button that opens one.
46 */
47 seatUrl?: string;
48 /** Who is playing this hand, and what can be done about that. */
49 who?: SeatWho;
50 onPrefs: (p: Prefs) => void;
51 onSettings: () => void;
52 onClose: () => void;
53}) {
54 const t = useT();
55 const lang = useLang();
56 const s = game.state;
57 const undoable = s.phase === 'gameEnd' ? null : game.undoable();
58
59 return (
60 <div className={`help-slot ${SEAT_SLOT[pos]}`} onClick={onClose}>
61 <div
62 className="help menu"
63 style={{
64 transform: compact
65 ? 'translate(-50%, -50%)'
66 : `translate(-50%, -50%) rotate(${SEAT_ROT[pos]}deg)`,
67 }}
68 onClick={(e) => e.stopPropagation()}
69 >
70 <div className="help-head">
71 <h3>{t('桌面', 'Table', ' · ')}</h3>
72 <button className="btn ghost" onClick={onClose}>
73 {t('關閉', 'Close')}
74 </button>
75 </div>
76 <div className="menu-body">
77 <button
78 className="btn ghost undo"
79 disabled={!undoable}
80 onClick={() => {
81 game.undo();
82 onClose();
83 }}
84 >
85 <span className="undo-arrow" aria-hidden>
86 ↺
87 </span>
88 <span className="undo-what">
89 {undoable ? (
90 <>
91 {lang !== 'en' && (
92 <span className="zh" lang="zh-TW">
93 復原 {undoable.zh}
94 </span>
95 )}
96 {lang !== 'zh' && (
97 <span className="en" lang="en">
98 Undo · {undoable.en}
99 </span>
100 )}
101 </>
102 ) : (
103 <span>{t('沒有可復原的', 'Nothing to undo', ' · ')}</span>
104 )}
105 </span>
106 </button>
107
108 <SoundToggle prefs={prefs} onPrefs={onPrefs} />
109 <FullscreenToggle prefs={prefs} onPrefs={onPrefs} />
110
111 {/* Who is playing this hand. All three answers live in one place,
112 and each is one tap away from either other: the computer has it,
113 somebody at this screen has it, or it is on a phone. Nothing here
114 deals anything, mid-hand included — the tiles simply change
115 hands, which is what happens when somebody sits down. */}
116 {who?.onSit && (
117 <button
118 className="btn ghost"
119 onClick={() => {
120 who.onSit!();
121 onClose();
122 }}
123 >
124 <Bi zh="我來打" en="I'll play it" sub={t('從電腦手上接過來', 'take it off the computer')} />
125 </button>
126 )}
127 {who?.onBot && (
128 <button
129 className="btn ghost"
130 onClick={() => {
131 who.onBot!();
132 onClose();
133 }}
134 >
135 <Bi zh="電腦代打" en="Hand to computer" />
136 </button>
137 )}
138
139 {/* Taking this hand off the shared screen. The QR is the seat's own,
140 so scanning it lands on this chair without anybody having to say
141 which one they are in — and the tiles here go face down for good
142 rather than under a card that lifts. */}
143 {who?.onPhone &&
144 (seatUrl ? (
145 <div className="menu-qr">
146 <SeatQr url={seatUrl} />
147 <span className="menu-qr-hint">
148 {lang !== 'en' && '掃碼把這副牌拿到手機上'}
149 {lang !== 'zh' && <em>scan to hold this hand on your phone</em>}
150 </span>
151 </div>
152 ) : (
153 // No room yet, and none was needed: a table of people and
154 // computers never says a word to a server. The first person who
155 // wants a phone in the game is the one who opens it.
156 <button className="btn ghost" onClick={who.onPhone}>
157 <Bi
158 zh="用手機拿牌"
159 en="Onto a phone"
160 sub={t('開房間,出 QR', 'opens the room and puts up a QR')}
161 />
162 </button>
163 ))}
164
165 {/* The moment anybody wants a name or the stakes changed is the
166 moment they went looking for a gear. */}
167 <button
168 className="btn ghost"
169 onClick={() => {
170 onClose();
171 onSettings();
172 }}
173 >
174 <Bi zh="設定" en="Settings" />
175 </button>
176 </div>
177 </div>
178 </div>
179 );
180}