vimp-engine 0.7.2 → 0.9.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/package.json +11 -4
- package/src/client/InputListener.js +36 -0
- package/src/client/SoundManager.js +498 -0
- package/src/client/boot.js +113 -0
- package/src/client/components/controller/Auth.js +44 -0
- package/src/client/components/controller/CanvasManager.js +26 -0
- package/src/client/components/controller/Chat.js +56 -0
- package/src/client/components/controller/Controls.js +51 -0
- package/src/client/components/controller/Game.js +37 -0
- package/src/client/components/controller/Lobby.js +81 -0
- package/src/client/components/controller/LobbyAuth.js +41 -0
- package/src/client/components/controller/Panel.js +23 -0
- package/src/client/components/controller/Stat.js +33 -0
- package/src/client/components/controller/Vote.js +55 -0
- package/src/client/components/model/Auth.js +92 -0
- package/src/client/components/model/CanvasManager.js +319 -0
- package/src/client/components/model/Chat.js +119 -0
- package/src/client/components/model/Controls.js +120 -0
- package/src/client/components/model/Game.js +172 -0
- package/src/client/components/model/Lobby.js +226 -0
- package/src/client/components/model/LobbyAuth.js +161 -0
- package/src/client/components/model/Panel.js +70 -0
- package/src/client/components/model/Stat.js +77 -0
- package/src/client/components/model/Vote.js +216 -0
- package/src/client/components/view/Auth.js +167 -0
- package/src/client/components/view/CanvasManager.js +44 -0
- package/src/client/components/view/Chat.js +90 -0
- package/src/client/components/view/Controls.js +30 -0
- package/src/client/components/view/Game.js +48 -0
- package/src/client/components/view/Lobby.js +303 -0
- package/src/client/components/view/LobbyAuth.js +111 -0
- package/src/client/components/view/Panel.js +314 -0
- package/src/client/components/view/Stat.js +238 -0
- package/src/client/components/view/Vote.js +108 -0
- package/src/client/debug.js +158 -0
- package/src/client/lib/autostart.js +61 -0
- package/src/client/lib/contextTracker.js +46 -0
- package/src/client/lib/formBuilder.js +280 -0
- package/src/client/lib/hostGate.js +16 -0
- package/src/client/main.js +2016 -0
- package/src/client/network/HostConnectionManager.js +196 -0
- package/src/client/network/HostController.js +422 -0
- package/src/client/network/InlineHostBridge.js +152 -0
- package/src/client/network/LoopbackTransport.js +51 -0
- package/src/client/network/SignalingClient.js +146 -0
- package/src/client/network/WebRtcManager.js +147 -0
- package/src/client/network/WebSocketTransport.js +78 -0
- package/src/client/network/policyClose.js +54 -0
- package/src/client/network/workerSupport.js +34 -0
- package/src/client/providers/BakingProvider.js +88 -0
- package/src/client/providers/DependencyProvider.js +41 -0
- package/src/client/style.css +898 -0
- package/src/client/views/gameShell.js +190 -0
- package/src/client/views/includes/auth.pug +20 -0
- package/src/client/views/includes/chat.pug +5 -0
- package/src/client/views/includes/informer.pug +2 -0
- package/src/client/views/includes/lobby.pug +48 -0
- package/src/client/views/includes/lobbyAuth.pug +19 -0
- package/src/client/views/includes/panel.pug +3 -0
- package/src/client/views/includes/stat.pug +2 -0
- package/src/client/views/index.pug +8 -0
- package/src/config/closeCodes.js +18 -0
- package/src/config/env.js +61 -0
- package/src/devtools/ScenarioRunner.js +3 -8
- package/src/devtools/pluginLoader.js +8 -93
- package/src/host/HostGame.js +41 -3
- package/src/host/PortMachine.js +294 -0
- package/src/host/host.worker.js +29 -246
- package/src/host/identity.js +100 -0
- package/src/lib/clientIp.js +39 -0
- package/src/lib/loadGamePackage.js +122 -0
- package/src/lib/offlinePlayerData.js +18 -0
- package/src/standalone/index.js +125 -0
- package/tests/fixtures/miniGame/config/game.js +4 -1
|
@@ -0,0 +1,303 @@
|
|
|
1
|
+
import Publisher from '../../../lib/Publisher.js';
|
|
2
|
+
|
|
3
|
+
// Singleton LobbyView
|
|
4
|
+
|
|
5
|
+
let lobbyView;
|
|
6
|
+
|
|
7
|
+
// неизвестная задержка сортируется в конец списка
|
|
8
|
+
const UNKNOWN_LATENCY = Infinity;
|
|
9
|
+
|
|
10
|
+
// Представление лобби: рендер списка серверов, поиск, «Загрузить ещё» и
|
|
11
|
+
// умный пинг через IntersectionObserver (пинг шлётся только для карточек,
|
|
12
|
+
// попавших в видимую область). Observer инъектируется ради тестируемости.
|
|
13
|
+
export default class LobbyView {
|
|
14
|
+
constructor(model, elems, observerFactory) {
|
|
15
|
+
if (lobbyView) {
|
|
16
|
+
return lobbyView;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
lobbyView = this;
|
|
20
|
+
|
|
21
|
+
this._lobby = document.getElementById(elems.lobbyId);
|
|
22
|
+
this._list = document.getElementById(elems.listId);
|
|
23
|
+
this._search = document.getElementById(elems.searchId);
|
|
24
|
+
this._more = document.getElementById(elems.moreId);
|
|
25
|
+
this._empty = document.getElementById(elems.emptyId);
|
|
26
|
+
|
|
27
|
+
// вкладки правой панели (lobby-page-plan)
|
|
28
|
+
this._tabServersBtn = document.getElementById(elems.tabServersBtnId);
|
|
29
|
+
this._tabLeaderboardBtn = document.getElementById(elems.tabLeaderboardBtnId);
|
|
30
|
+
this._serversContent = document.getElementById(elems.serversContentId);
|
|
31
|
+
this._leaderboardContent = document.getElementById(elems.leaderboardContentId);
|
|
32
|
+
this._leaderboardList = document.getElementById(elems.leaderboardListId);
|
|
33
|
+
this._leaderboardTitle = document.getElementById(elems.leaderboardTitleId);
|
|
34
|
+
this._leaderboardTotal = document.getElementById(elems.leaderboardTotalId);
|
|
35
|
+
this._myPlacement = document.getElementById(elems.myPlacementId);
|
|
36
|
+
|
|
37
|
+
// заголовок игры для "<TITLE> TOP-N" (SVG-ориентир) — задаётся controller'ом
|
|
38
|
+
// при выборе игры (сама модель не хранит title манифеста)
|
|
39
|
+
this._gameTitle = '';
|
|
40
|
+
|
|
41
|
+
// N в "TOP-N" (code review lobby-page: настроенный лимит, а не
|
|
42
|
+
// leaderboard.length — иначе заголовок мелькал бы TOP-0 до первого
|
|
43
|
+
// ответа и показывал бы TOP-2 вместо TOP-10, когда ранжировано меньше
|
|
44
|
+
// игроков, чем лимит)
|
|
45
|
+
this._leaderboardLimit = 0;
|
|
46
|
+
|
|
47
|
+
// ник вызывающего (lobby-page-plan, code review M4-остаток) — задаётся
|
|
48
|
+
// main.js один раз при открытии лобби; используется, чтобы решить,
|
|
49
|
+
// виден ли вызывающий уже в отрисованном топе (по нику, не по числу
|
|
50
|
+
// placement/leaderboard.length — их шкалы расходятся при ничьих на
|
|
51
|
+
// границе LIMIT, см. комментарий в renderLeaderboard)
|
|
52
|
+
this._selfNick = '';
|
|
53
|
+
|
|
54
|
+
this._cards = new Map(); // hostId -> { card, latencyEl, latency }
|
|
55
|
+
|
|
56
|
+
this.publisher = new Publisher();
|
|
57
|
+
|
|
58
|
+
const makeObserver =
|
|
59
|
+
observerFactory ||
|
|
60
|
+
(cb => new IntersectionObserver(cb, { root: this._list }));
|
|
61
|
+
|
|
62
|
+
// видимая карточка → запрос пинга (hostId в data-атрибуте)
|
|
63
|
+
this._observer = makeObserver(entries => {
|
|
64
|
+
entries.forEach(entry => {
|
|
65
|
+
if (entry.isIntersecting) {
|
|
66
|
+
this.publisher.emit('visible', entry.target.dataset.hostId);
|
|
67
|
+
}
|
|
68
|
+
});
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
this._search.oninput = () => this.publisher.emit('search', this._search.value);
|
|
72
|
+
this._more.onclick = () => this.publisher.emit('more');
|
|
73
|
+
|
|
74
|
+
this._tabServersBtn.onclick = () => this.publisher.emit('show-tab', 'servers');
|
|
75
|
+
this._tabLeaderboardBtn.onclick = () => this.publisher.emit('show-tab', 'leaderboard');
|
|
76
|
+
|
|
77
|
+
this._mPublic = model.publisher;
|
|
78
|
+
this._mPublic.on('list', 'renderList', this);
|
|
79
|
+
this._mPublic.on('ping-update', 'updatePing', this);
|
|
80
|
+
this._mPublic.on('leaderboard', 'renderLeaderboard', this);
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
show() {
|
|
84
|
+
this._lobby.style.display = 'flex';
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
hide() {
|
|
88
|
+
this._lobby.style.display = 'none';
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
// название активной игры для заголовка Leaderboard ("<TITLE> TOP-N");
|
|
92
|
+
// применится следующим renderLeaderboard (после смены игры main.js тут же
|
|
93
|
+
// запрашивает свежий leaderboard)
|
|
94
|
+
setGameTitle(title) {
|
|
95
|
+
this._gameTitle = title || '';
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
// N в "TOP-N" (main.js вызывает один раз из lobbyConfig.leaderboardLimit)
|
|
99
|
+
setLeaderboardLimit(limit) {
|
|
100
|
+
this._leaderboardLimit = limit || 0;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
// ник вызывающего (main.js вызывает один раз при открытии лобби) — по
|
|
104
|
+
// нему решается видимость плашки "You" в renderLeaderboard
|
|
105
|
+
setSelfNick(nick) {
|
|
106
|
+
this._selfNick = nick || '';
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
// переключает вкладки Active Servers / Leaderboard (lobby-page-plan)
|
|
110
|
+
showTab(tab) {
|
|
111
|
+
const showLeaderboard = tab === 'leaderboard';
|
|
112
|
+
|
|
113
|
+
this._tabServersBtn.classList.toggle('active', !showLeaderboard);
|
|
114
|
+
this._tabLeaderboardBtn.classList.toggle('active', showLeaderboard);
|
|
115
|
+
this._serversContent.style.display = showLeaderboard ? 'none' : 'flex';
|
|
116
|
+
this._leaderboardContent.style.display = showLeaderboard ? 'flex' : 'none';
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
// рендер топ-N + позиции вызывающего (lobby-page-plan)
|
|
120
|
+
renderLeaderboard({ leaderboard, total, myPlacement, loaded = true }) {
|
|
121
|
+
this._leaderboardTitle.textContent = `${this._gameTitle.toUpperCase()} TOP-${this._leaderboardLimit}`;
|
|
122
|
+
this._leaderboardTotal.textContent = `Total: ${total} players`;
|
|
123
|
+
|
|
124
|
+
this._leaderboardList.textContent = '';
|
|
125
|
+
|
|
126
|
+
// code review L7 + мелочь из review-status: пока ответ не пришёл
|
|
127
|
+
// (clearLeaderboard уже обнулила список — M1), список пуст, но это не
|
|
128
|
+
// то же самое, что "пусто и ответ уже пришёл" — не показывать заглушку
|
|
129
|
+
// раньше времени
|
|
130
|
+
if (leaderboard.length === 0) {
|
|
131
|
+
const empty = document.createElement('li');
|
|
132
|
+
|
|
133
|
+
empty.className = 'lobby-leaderboard-empty';
|
|
134
|
+
empty.textContent = loaded ? 'No ranked players yet' : 'Loading…';
|
|
135
|
+
this._leaderboardList.appendChild(empty);
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
// code review M3: номер строки — серверный competition-ranking `place`
|
|
139
|
+
// (RANK() OVER(ORDER BY rank DESC) в UserRepository.getLeaderboard), не
|
|
140
|
+
// index+1 — иначе ничьи по rank давали бы расхождение со строкой "You"
|
|
141
|
+
leaderboard.forEach(entry => {
|
|
142
|
+
const li = document.createElement('li');
|
|
143
|
+
|
|
144
|
+
const name = document.createElement('span');
|
|
145
|
+
|
|
146
|
+
name.textContent = `${entry.place}. ${entry.nick}`;
|
|
147
|
+
|
|
148
|
+
const pts = document.createElement('span');
|
|
149
|
+
|
|
150
|
+
pts.textContent = `${entry.rank} pts`;
|
|
151
|
+
|
|
152
|
+
li.append(name, pts);
|
|
153
|
+
this._leaderboardList.appendChild(li);
|
|
154
|
+
});
|
|
155
|
+
|
|
156
|
+
if (!myPlacement) {
|
|
157
|
+
this._myPlacement.textContent = '';
|
|
158
|
+
this._myPlacement.classList.remove('lobby-placement-gap');
|
|
159
|
+
return;
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
if (myPlacement.placement === null) {
|
|
163
|
+
this._myPlacement.textContent = 'Not ranked yet';
|
|
164
|
+
this._myPlacement.classList.remove('lobby-placement-gap');
|
|
165
|
+
return;
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
// code review M4 (доработка): видимость плашки решается членством
|
|
169
|
+
// собственного ника в отрисованном списке, а не сравнением
|
|
170
|
+
// myPlacement.placement с leaderboard.length — это разные шкалы
|
|
171
|
+
// (placement — competition ranking с разрывами при ничьих,
|
|
172
|
+
// leaderboard.length — просто число строк после LIMIT) и при ничьих на
|
|
173
|
+
// границе страницы могли разойтись так, что игрок не находил себя ни в
|
|
174
|
+
// списке, ни в плашке. Ники уникальны (users_nick_lower_unique_idx),
|
|
175
|
+
// так что membership по нику однозначен
|
|
176
|
+
const inTop =
|
|
177
|
+
this._selfNick !== '' && leaderboard.some(entry => entry.nick === this._selfNick);
|
|
178
|
+
|
|
179
|
+
if (inTop) {
|
|
180
|
+
this._myPlacement.textContent = '';
|
|
181
|
+
this._myPlacement.classList.remove('lobby-placement-gap');
|
|
182
|
+
return;
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
const name = document.createElement('span');
|
|
186
|
+
|
|
187
|
+
name.textContent = `${myPlacement.placement}. You`;
|
|
188
|
+
|
|
189
|
+
const pts = document.createElement('span');
|
|
190
|
+
|
|
191
|
+
pts.textContent = `${myPlacement.rank} pts`;
|
|
192
|
+
|
|
193
|
+
this._myPlacement.textContent = '';
|
|
194
|
+
this._myPlacement.append(name, pts);
|
|
195
|
+
// разделитель «…» перед плашкой, показанной только вне топа (SVG-ориентир)
|
|
196
|
+
this._myPlacement.classList.add('lobby-placement-gap');
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
// полный рендер списка серверов
|
|
200
|
+
renderList({ servers, hasMore }) {
|
|
201
|
+
this._observer.disconnect();
|
|
202
|
+
this._cards.clear();
|
|
203
|
+
this._list.textContent = '';
|
|
204
|
+
|
|
205
|
+
servers.forEach(server => this._appendCard(server));
|
|
206
|
+
|
|
207
|
+
this._empty.style.display = servers.length === 0 ? 'block' : 'none';
|
|
208
|
+
this._more.style.display = hasMore ? 'block' : 'none';
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
// обновляет задержку карточки и переставляет её по возрастанию latency
|
|
212
|
+
updatePing({ hostId, latency }) {
|
|
213
|
+
const entry = this._cards.get(hostId);
|
|
214
|
+
|
|
215
|
+
if (!entry) {
|
|
216
|
+
return;
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
entry.latency = latency;
|
|
220
|
+
entry.latencyEl.textContent = `${latency} ms`;
|
|
221
|
+
|
|
222
|
+
this._reorderCard(hostId);
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
_appendCard(server) {
|
|
226
|
+
const card = document.createElement('li');
|
|
227
|
+
|
|
228
|
+
card.className = 'lobby-card';
|
|
229
|
+
card.dataset.hostId = server.hostId;
|
|
230
|
+
|
|
231
|
+
const name = document.createElement('span');
|
|
232
|
+
|
|
233
|
+
name.className = 'lobby-card-name';
|
|
234
|
+
// формат "gameId/name" (lobby-page-plan): готовит серверный поиск
|
|
235
|
+
// "gameId/name" к тому, чтобы искать по тому же, что видно на карточке;
|
|
236
|
+
// gameId nullable для хостов до Этапа 6.4 (code review L6)
|
|
237
|
+
name.textContent = `${server.gameId ?? '?'}/${server.name}`;
|
|
238
|
+
|
|
239
|
+
const info = document.createElement('span');
|
|
240
|
+
|
|
241
|
+
info.className = 'lobby-card-info';
|
|
242
|
+
info.textContent = `${server.mapName} · ${server.currentPlayers}/${server.maxPlayers} · ${server.region}`;
|
|
243
|
+
|
|
244
|
+
// рейтинг хостера (server-rating этап 3) — сервер уже отдаёт кэшированное
|
|
245
|
+
// значение в GET /servers, диапазон известен из конфига движка
|
|
246
|
+
const ratingEl = document.createElement('span');
|
|
247
|
+
|
|
248
|
+
ratingEl.className = 'lobby-card-rating';
|
|
249
|
+
ratingEl.classList.toggle('is-negative', server.rating < 0);
|
|
250
|
+
ratingEl.textContent = server.rating > 0 ? `+${server.rating}` : `${server.rating}`;
|
|
251
|
+
|
|
252
|
+
const latencyEl = document.createElement('span');
|
|
253
|
+
|
|
254
|
+
latencyEl.className = 'lobby-card-latency';
|
|
255
|
+
latencyEl.textContent =
|
|
256
|
+
server.latency === null ? '…' : `${server.latency} ms`;
|
|
257
|
+
|
|
258
|
+
card.appendChild(name);
|
|
259
|
+
card.appendChild(info);
|
|
260
|
+
card.appendChild(ratingEl);
|
|
261
|
+
card.appendChild(latencyEl);
|
|
262
|
+
|
|
263
|
+
card.onclick = () => this.publisher.emit('join', server.hostId);
|
|
264
|
+
|
|
265
|
+
this._list.appendChild(card);
|
|
266
|
+
this._observer.observe(card);
|
|
267
|
+
|
|
268
|
+
this._cards.set(server.hostId, {
|
|
269
|
+
card,
|
|
270
|
+
latencyEl,
|
|
271
|
+
latency: server.latency === null ? UNKNOWN_LATENCY : server.latency,
|
|
272
|
+
rating: server.rating,
|
|
273
|
+
});
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
// вставляет карточку перед первым соседом с большей задержкой; при равном
|
|
277
|
+
// пинге тай-брейк по rating (убыв.) — lobby-page-plan
|
|
278
|
+
_reorderCard(hostId) {
|
|
279
|
+
const entry = this._cards.get(hostId);
|
|
280
|
+
const { card } = entry;
|
|
281
|
+
|
|
282
|
+
let before = null;
|
|
283
|
+
|
|
284
|
+
for (const sibling of this._list.children) {
|
|
285
|
+
if (sibling === card) {
|
|
286
|
+
continue;
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
const other = this._cards.get(sibling.dataset.hostId);
|
|
290
|
+
|
|
291
|
+
if (
|
|
292
|
+
other &&
|
|
293
|
+
(other.latency > entry.latency ||
|
|
294
|
+
(other.latency === entry.latency && other.rating < entry.rating))
|
|
295
|
+
) {
|
|
296
|
+
before = sibling;
|
|
297
|
+
break;
|
|
298
|
+
}
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
this._list.insertBefore(card, before);
|
|
302
|
+
}
|
|
303
|
+
}
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
import Publisher from '../../../lib/Publisher.js';
|
|
2
|
+
|
|
3
|
+
// Singleton LobbyAuthView
|
|
4
|
+
|
|
5
|
+
let lobbyAuthView;
|
|
6
|
+
|
|
7
|
+
const NICK_ERROR_MESSAGES = {
|
|
8
|
+
nickTaken: 'This nick is already taken',
|
|
9
|
+
invalidNick: 'Invalid nick',
|
|
10
|
+
sessionExpired: 'Session expired, please sign in again',
|
|
11
|
+
network: 'Network error, try again',
|
|
12
|
+
};
|
|
13
|
+
|
|
14
|
+
const LOGIN_ERROR_MESSAGES = {
|
|
15
|
+
tokenExpired: 'Session expired, please sign in again',
|
|
16
|
+
invalidToken: 'Session expired, please sign in again',
|
|
17
|
+
oauthFailed: 'Sign-in failed, please try again',
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
// Представление логина лобби: переключает login/nick секции lobbyAuth.pug и
|
|
21
|
+
// бейдж пользователя в lobby.pug (#lobby-user), скрывает/показывает #lobby
|
|
22
|
+
// целиком — до авторизации список серверов недоступен (B2)
|
|
23
|
+
export default class LobbyAuthView {
|
|
24
|
+
constructor(model, config) {
|
|
25
|
+
if (lobbyAuthView) {
|
|
26
|
+
return lobbyAuthView;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
lobbyAuthView = this;
|
|
30
|
+
|
|
31
|
+
const { elems, providerButtonClass } = config;
|
|
32
|
+
|
|
33
|
+
this._elems = elems;
|
|
34
|
+
this._providerButtons = document.querySelectorAll(`.${providerButtonClass}`);
|
|
35
|
+
|
|
36
|
+
this._container = document.getElementById(elems.containerId);
|
|
37
|
+
this._loginSection = document.getElementById(elems.loginSectionId);
|
|
38
|
+
this._loginError = document.getElementById(elems.loginErrorId);
|
|
39
|
+
this._nickSection = document.getElementById(elems.nickSectionId);
|
|
40
|
+
this._nickInput = document.getElementById(elems.nickInputId);
|
|
41
|
+
this._nickError = document.getElementById(elems.nickErrorId);
|
|
42
|
+
this._nickSubmit = document.getElementById(elems.nickSubmitId);
|
|
43
|
+
this._lobby = document.getElementById(elems.lobbyId);
|
|
44
|
+
this._user = document.getElementById(elems.userId);
|
|
45
|
+
this._userNick = document.getElementById(elems.userNickId);
|
|
46
|
+
this._userLogout = document.getElementById(elems.userLogoutId);
|
|
47
|
+
|
|
48
|
+
this.publisher = new Publisher();
|
|
49
|
+
|
|
50
|
+
this._providerButtons.forEach(btn => {
|
|
51
|
+
btn.onclick = () => this.publisher.emit('login', btn.dataset.provider);
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
this._nickSubmit.onclick = () => this._emitNick();
|
|
55
|
+
this._nickInput.onkeydown = e => {
|
|
56
|
+
if (e.key === 'Enter') {
|
|
57
|
+
this._emitNick();
|
|
58
|
+
}
|
|
59
|
+
};
|
|
60
|
+
|
|
61
|
+
this._userLogout.onclick = () => this.publisher.emit('logout');
|
|
62
|
+
|
|
63
|
+
const mp = model.publisher;
|
|
64
|
+
|
|
65
|
+
mp.on('login-required', 'showLogin', this);
|
|
66
|
+
mp.on('nick-required', 'showNick', this);
|
|
67
|
+
mp.on('authenticated', 'showLobby', this);
|
|
68
|
+
mp.on('login-error', 'renderLoginError', this);
|
|
69
|
+
mp.on('nick-error', 'renderNickError', this);
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
showLogin(providers = []) {
|
|
73
|
+
this._container.style.display = 'block';
|
|
74
|
+
this._loginSection.style.display = 'block';
|
|
75
|
+
this._nickSection.style.display = 'none';
|
|
76
|
+
this._lobby.style.display = 'none';
|
|
77
|
+
this._user.style.display = 'none';
|
|
78
|
+
|
|
79
|
+
this._providerButtons.forEach(btn => {
|
|
80
|
+
btn.style.display = providers.includes(btn.dataset.provider) ? '' : 'none';
|
|
81
|
+
});
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
showNick() {
|
|
85
|
+
this._container.style.display = 'block';
|
|
86
|
+
this._loginSection.style.display = 'none';
|
|
87
|
+
this._nickSection.style.display = 'block';
|
|
88
|
+
this._nickError.textContent = '';
|
|
89
|
+
this._nickInput.value = '';
|
|
90
|
+
this._nickInput.focus();
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
showLobby({ nick }) {
|
|
94
|
+
this._container.style.display = 'none';
|
|
95
|
+
this._lobby.style.display = 'flex';
|
|
96
|
+
this._user.style.display = 'flex';
|
|
97
|
+
this._userNick.textContent = nick;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
renderLoginError(code) {
|
|
101
|
+
this._loginError.textContent = LOGIN_ERROR_MESSAGES[code] || 'Sign-in failed';
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
renderNickError(code) {
|
|
105
|
+
this._nickError.textContent = NICK_ERROR_MESSAGES[code] || 'Something went wrong';
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
_emitNick() {
|
|
109
|
+
this.publisher.emit('nick', this._nickInput.value.trim());
|
|
110
|
+
}
|
|
111
|
+
}
|