extra.js 21 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498
  1. function login_then_go_to(url) {
  2. const domain_name = document.location.hostname.substring(document.location.hostname.indexOf('.') + 1);
  3. document.cookie = `login_to=${encodeURIComponent(url)}; path=/; domain=${domain_name}; secure; SameSite=Strict; Max-Age=30000`;
  4. document.location = '/osu_login';
  5. }
  6. function ask_for_manual_lobby_creation(lobby_settings) {
  7. document.querySelector('main .loading-placeholder').replaceWith(
  8. document.querySelector('#manual-lobby-creation-template').content.cloneNode(true),
  9. );
  10. document.querySelector('main .create-lobby-with-ref-btn').addEventListener('click', (evt) => {
  11. evt.preventDefault();
  12. evt.stopPropagation();
  13. // Input shown if bot can't create any more lobbies and needs a user-made lobby
  14. const match_input = document.querySelector('main input[name="tournament-url"]');
  15. if (match_input.value) {
  16. lobby_settings.match_id = parseInt(match_input.value.split('/').reverse()[0], 10);
  17. }
  18. try_creating_lobby(lobby_settings);
  19. });
  20. }
  21. async function try_creating_lobby(lobby_settings) {
  22. document.querySelector('main').innerHTML = `
  23. <div class="loading-placeholder text-center mt-24">
  24. <p>Creating lobby...</p>
  25. <svg class="animate-spin m-auto my-4 h-10 w-10 text-orange-400" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24">
  26. <circle class="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" stroke-width="4"></circle>
  27. <path class="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"></path>
  28. </svg>
  29. </div>`;
  30. try {
  31. const res = await fetch('/api/create-lobby/', {
  32. body: JSON.stringify(lobby_settings),
  33. credentials: 'include',
  34. headers: {
  35. 'Accept': 'application/json',
  36. 'Content-Type': 'application/json',
  37. },
  38. method: 'POST',
  39. });
  40. const json_res = await res.json();
  41. if (json_res.error) {
  42. if (json_res.details == 'Cannot create any more matches.') {
  43. ask_for_manual_lobby_creation(lobby_settings);
  44. return;
  45. }
  46. throw new Error(json_res.details || json_res.error);
  47. }
  48. document.querySelector('main .loading-placeholder').replaceWith(
  49. document.querySelector('#lobby-created-template').content.cloneNode(true),
  50. );
  51. document.querySelector('.lobby').replaceWith(render_lobby(json_res.lobby));
  52. document.querySelector('.info').appendChild(document.querySelector('#command-list-template').content.cloneNode(true));
  53. } catch (err) {
  54. document.querySelector('main').innerHTML = `
  55. An error occurred while creating the lobby:
  56. <div class="error-msg mt-4"></div>`;
  57. document.querySelector('.error-msg').innerText = err.message;
  58. }
  59. }
  60. function render_lobby(lobby, user_has_lobby_open) {
  61. const lobby_div = document.createElement('div');
  62. let map_pool_info = 'All maps';
  63. if (lobby.map_pool == 'collection') {
  64. const pool = document.createElement('a');
  65. pool.href = 'https://osucollector.com/collections/' + lobby.collection_id;
  66. pool.target = '_blank';
  67. pool.innerText = lobby.collection_name;
  68. map_pool_info = pool.outerHTML;
  69. }
  70. let map_selection_info = 'Random';
  71. if (lobby.map_selection_algo == 'pp') {
  72. map_selection_info = 'Average player pp';
  73. } else if (lobby.map_selection_algo == 'elo') {
  74. map_selection_info = 'Average player elo';
  75. }
  76. if (lobby.filters.length > 0) {
  77. const filter_span = document.createElement('span');
  78. filter_span.classList = 'cursor-help underline underline-offset-2 decoration-1 decoration-dotted';
  79. filter_span.innerText = lobby.filters.length + ' filters';
  80. filter_span.title = '';
  81. for (const filter of lobby.filters) {
  82. filter_span.title += `${filter.name} between ${filter.min} and ${filter.max}\n`;
  83. }
  84. map_selection_info += ' (' + filter_span.outerHTML + ')';
  85. }
  86. let mods_info = 'None';
  87. if (lobby.mod_list.length > 0) {
  88. mods_info = lobby.mod_list.join(', ');
  89. }
  90. if (lobby.freemod) {
  91. mods_info += ' (freemod)';
  92. }
  93. let status = '';
  94. if (lobby.end_time) {
  95. function reltime(diff) {
  96. const rtf = new Intl.RelativeTimeFormat('en', {numeric: 'auto'});
  97. if (diff > -60) {
  98. return rtf.format(Math.round(diff), 'second');
  99. } else if (diff > -3600) {
  100. return rtf.format(Math.round(diff / 60), 'minute');
  101. } else if (diff > -86400) {
  102. return rtf.format(Math.round(diff / 3600), 'hour');
  103. } else {
  104. return rtf.format(Math.round(diff / 86400), 'day');
  105. }
  106. }
  107. const seconds_ago = reltime((lobby.end_time - Date.now()) / 1000);
  108. const closing_time = new Intl.DateTimeFormat(undefined, {
  109. year: 'numeric', month: 'numeric', day: 'numeric',
  110. hour: 'numeric', minute: 'numeric', second: 'numeric',
  111. }).format(new Date(lobby.end_time));
  112. status = `Closed <span title="${closing_time}">${seconds_ago}</span>`;
  113. } else {
  114. status = `${lobby.nb_players}/16 players`;
  115. }
  116. lobby_div.style = `border: solid ${lobby.color} 2px`;
  117. lobby_div.innerHTML += `
  118. <div class="lobby-info min-w-[28rem] p-2">
  119. <div class="lobby-title font-bold"></div>
  120. <div><strong>Map pool:</strong> ${map_pool_info}</div>
  121. <div><strong>Map selection:</strong> ${map_selection_info}</div>
  122. <div><strong>Mods:</strong> ${mods_info}</div>
  123. <div class="mania-keycounts hidden"><strong>Keys:</strong> ${lobby.key_counts.join(', ')}</div>
  124. <div>${status} · Created by <a href="/u/${lobby.creator_id}"><img class="h-5 text-bottom rounded-full inline" src="https://s.ppy.sh/a/${lobby.creator_id}" alt="Lobby creator"> ${lobby.creator_name}</a></div>
  125. </div>`;
  126. if (lobby.end_time) {
  127. lobby_div.className = 'flex-1 m-2 rounded-md';
  128. lobby_div.innerHTML += `
  129. <div class="mb-2" ${user_has_lobby_open ? 'hidden' : ''}>
  130. <button class="go-to-create-lobby text-white rounded-lg py-1.5 px-6">Reopen</button>
  131. </div>`;
  132. // TODO: make "Copy settings" buttons instead of too basic "reopen"
  133. lobby_div.querySelector('.go-to-create-lobby').style = `background-color: ${lobby.color}; margin: auto; display: block`;
  134. lobby_div.querySelector('.go-to-create-lobby').addEventListener('click', (evt) => {
  135. evt.preventDefault();
  136. evt.stopPropagation();
  137. if (window.logged_user_id) {
  138. document.location = `/reopen-lobby/${lobby.match_id}`;
  139. } else {
  140. return login_then_go_to(`${location.origin}/reopen-lobby/${lobby.match_id}`);
  141. }
  142. });
  143. } else {
  144. // Font Awesome Free 6.1.1 by @fontawesome - https://fontawesome.com
  145. // License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License)
  146. // Copyright 2022 Fonticons, Inc.
  147. const fa_open = '<svg xmlns="http://www.w3.org/2000/svg" fill="#fff" height="0.75em" viewBox="0 0 512 512"><path d="M320 0c-17.7 0-32 14.3-32 32s14.3 32 32 32h82.7L201.4 265.4c-12.5 12.5-12.5 32.8 0 45.3s32.8 12.5 45.3 0L448 109.3V192c0 17.7 14.3 32 32 32s32-14.3 32-32V32c0-17.7-14.3-32-32-32H320zM80 32C35.8 32 0 67.8 0 112V432c0 44.2 35.8 80 80 80H400c44.2 0 80-35.8 80-80V320c0-17.7-14.3-32-32-32s-32 14.3-32 32V432c0 8.8-7.2 16-16 16H80c-8.8 0-16-7.2-16-16V112c0-8.8 7.2-16 16-16H192c17.7 0 32-14.3 32-32s-14.3-32-32-32H80z"/></svg>';
  148. const fa_invite = '<svg xmlns="http://www.w3.org/2000/svg" fill="#fff" height="0.75em" viewBox="0 0 512 512"><path d="M48 64C21.5 64 0 85.5 0 112c0 15.1 7.1 29.3 19.2 38.4L236.8 313.6c11.4 8.5 27 8.5 38.4 0L492.8 150.4c12.1-9.1 19.2-23.3 19.2-38.4c0-26.5-21.5-48-48-48H48zM0 176V384c0 35.3 28.7 64 64 64H448c35.3 0 64-28.7 64-64V176L294.4 339.2c-22.8 17.1-54 17.1-76.8 0L0 176z"/></svg>';
  149. lobby_div.className = 'flex m-2 rounded-md';
  150. lobby_div.innerHTML += `
  151. <div class="lobby-links flex flex-col justify-evenly" style="background-color:${lobby.color}">
  152. <div class="group relative text-center"><a class="!text-white text-2xl p-1.5 pl-2 flex" href="osu://mp/${lobby.bancho_id}">${fa_open}</a><span class="tooltip top-[-0.2rem]" style="background-color:${lobby.color}">Join</span></div>
  153. <div class="group relative text-center"><button class="!text-white text-2xl p-1.5 pl-2 get-invite-btn">${fa_invite}</button><span class="tooltip top-[-0.1rem]" style="background-color:${lobby.color}">Get invite</span></div>
  154. </div>`;
  155. lobby_div.querySelector('.get-invite-btn').addEventListener('click', (evt) => {
  156. evt.preventDefault();
  157. evt.stopPropagation();
  158. if (!window.logged_user_id) {
  159. return login_then_go_to(`${location.origin}/lobbies/`);
  160. }
  161. async function request_invite() {
  162. try {
  163. const res = await fetch(`/get-invite/${lobby.bancho_id}`, {
  164. credentials: 'include',
  165. headers: {
  166. 'Accept': 'application/json',
  167. 'Content-Type': 'application/json',
  168. },
  169. method: 'POST',
  170. });
  171. const json_res = await res.json();
  172. if (json_res.error) throw new Error(json_res.error);
  173. alert('An invite to the lobby has been sent. Check your in-game messages.');
  174. } catch (err) {
  175. alert(err.message);
  176. }
  177. }
  178. request_invite();
  179. });
  180. }
  181. lobby_div.querySelector('.lobby-title').innerText = lobby.name;
  182. // Mania: display keycounts
  183. if (window.selected_ruleset == 3) {
  184. lobby_div.querySelector('.mania-keycounts').classList.remove('hidden');
  185. }
  186. return lobby_div;
  187. }
  188. async function render_lobbies() {
  189. const template = document.querySelector('#lobbies-template').content.cloneNode(true);
  190. const res = await fetch(`/api/lobbies/`, {
  191. method: 'GET',
  192. headers: {
  193. 'Accept': 'application/json',
  194. 'Content-Type': 'application/json',
  195. },
  196. });
  197. const json = await res.json();
  198. let user_has_lobby_open = false;
  199. const open_lobbies = [];
  200. const closed_lobbies = [];
  201. for (const lobby of json) {
  202. if (lobby.end_time) {
  203. closed_lobbies.push(lobby);
  204. } else {
  205. open_lobbies.push(lobby);
  206. if (lobby.creator_id == window.logged_user_id) {
  207. // User already created a lobby: hide the "Create lobby" button
  208. template.querySelector('.lobby-creation-banner').hidden = true;
  209. user_has_lobby_open = true;
  210. }
  211. }
  212. }
  213. if (open_lobbies.length == 0) {
  214. template.querySelector('.lobby-creation-banner span').innerText = 'No lobbies are open right now.';
  215. } else {
  216. template.querySelector('.lobby-creation-banner span').innerText = 'Not satisfied?';
  217. }
  218. template.querySelector('.go-to-create-lobby').addEventListener('click', (evt) => {
  219. evt.preventDefault();
  220. evt.stopPropagation();
  221. if (window.logged_user_id) {
  222. document.location = '/create-lobby/';
  223. } else {
  224. return login_then_go_to(`${location.origin}/create-lobby/`);
  225. }
  226. });
  227. // Display open lobbies
  228. let list = template.querySelector('.open-lobby-list');
  229. for (const lobby of open_lobbies) {
  230. list.appendChild(render_lobby(lobby));
  231. }
  232. // Display closed lobbies
  233. list = template.querySelector('.closed-lobby-list');
  234. for (const lobby of closed_lobbies) {
  235. list.appendChild(render_lobby(lobby, user_has_lobby_open));
  236. }
  237. document.querySelector('main').replaceChildren(template);
  238. setTimeout(render_lobbies, 10000);
  239. }
  240. async function render_create_lobby() {
  241. const rulesets = ['osu', 'taiko', 'catch', 'mania'];
  242. const template = document.querySelector('#lobby-creation-template').content.cloneNode(true);
  243. template.querySelector('h1').innerText = `New ${rulesets[window.selected_ruleset]} lobby`;
  244. document.querySelector('main .loading-placeholder').replaceWith(template);
  245. const filter_list = document.querySelector('.filter-list');
  246. const render_filter = (options) => `
  247. <div class="filter flex flex-col border-2 border-orange-600 rounded-md m-1 max-w-sm">
  248. <div class="pl-2 bg-orange-600 text-white font-bold select-none">
  249. <input type="checkbox" name="${options.name}_filter" id="${options.name}_filter">
  250. <label class="inline-block w-80 cursor-pointer" for="${options.name}_filter">Limit ${options.label}</label>
  251. </div>
  252. <fieldset class="p-2 flex justify-evenly" disabled>
  253. <div><label for="min_${options.name}">Minimum </label><input class="ml-1 w-16 text-right" type="number" name="min_${options.name}" min="${options.min}" max="${options.max - options.step}" value="${options.default_min}" step="${options.step}" id="min_${options.name}"></div>
  254. <div><label for="max_${options.name}">Maximum </label><input class="ml-1 w-16 text-right" type="number" name="max_${options.name}" min="${options.min + options.step}" max="${options.max}" value="${options.default_max}" step="${options.step}" id="max_${options.name}"></div>
  255. </fieldset>
  256. </div>`;
  257. filter_list.innerHTML += render_filter({name: 'pp', label: 'performance points (PP)', min: 0, max: 2000, default_min: 150, default_max: 200, step: 25});
  258. filter_list.innerHTML += render_filter({name: 'sr', label: 'star rating (SR)', min: 0, max: 20, default_min: 5, default_max: 6, step: 0.5});
  259. filter_list.innerHTML += render_filter({name: 'ar', label: 'approach rate (AR)', min: 0, max: 11, default_min: 9, default_max: 9.5, step: 0.1});
  260. filter_list.innerHTML += render_filter({name: 'cs', label: 'circle size (CS)', min: 0, max: 10, default_min: 5, default_max: 6, step: 1});
  261. filter_list.innerHTML += render_filter({name: 'od', label: 'overall difficulty (OD)', min: 0, max: 10, default_min: 6, default_max: 8, step: 1});
  262. filter_list.innerHTML += render_filter({name: 'bpm', label: 'beats per minute (BPM)', min: 0, max: 1000, default_min: 160, default_max: 220, step: 10});
  263. filter_list.innerHTML += render_filter({name: 'length', label: 'length (in seconds)', min: 0, max: 3600, default_min: 30, default_max: 500, step: 30});
  264. document.querySelector('#length_filter').checked = true;
  265. document.querySelector('input[name="title"]').addEventListener('input', (evt) => {
  266. let title_preview = evt.target.value;
  267. const replacements = [
  268. ['$min_stars', '0'], ['$avg_stars', '5.5'], ['$max_stars', '11'], ['$stars', '0-11'],
  269. ['$min_elo', '1200'], ['$avg_elo', '1500'], ['$max_elo', '1800'], ['$elo', '1500'],
  270. ['$min_pp', '100'], ['$avg_pp', '150'], ['$max_pp', '200'], ['$pp', '150'],
  271. ];
  272. for (let i = 0; i < replacements.length; i++) {
  273. title_preview = title_preview.replaceAll(replacements[i][0], replacements[i][1]);
  274. }
  275. title_preview = title_preview.substring(0, 50);
  276. document.querySelector('.preview').innerText = title_preview;
  277. });
  278. document.querySelectorAll('.filter').forEach((filter) => {
  279. filter.querySelector('input[type="checkbox"]').addEventListener('change', function() {
  280. const fieldset = filter.querySelector('fieldset');
  281. fieldset.disabled = !fieldset.disabled;
  282. });
  283. });
  284. // Mania-specific mods
  285. if (window.selected_ruleset == 3) {
  286. document.querySelector('.mr').classList.remove('hidden');
  287. document.querySelector('.co').classList.remove('hidden');
  288. document.querySelector('.fi').classList.remove('hidden');
  289. let mods = '';
  290. for (let i = 1; i <= 9; i++) {
  291. mods += `
  292. <div class="mod-btn">
  293. <img class="h-8" src="/images/mod_${i}[email protected]" title="${i}K" alt="${i}K" />
  294. <div>${i}K</div>
  295. </div>`;
  296. }
  297. document.querySelector('.mania-keycount-settings').innerHTML = `
  298. <div class="flex mt-2 cursor-pointer select-none">
  299. <div class="collapser"></div>
  300. <h3 class="underline">Key count</h3>
  301. </div>
  302. <div class="hidden">${mods}</div>
  303. `;
  304. document.querySelectorAll('.mania-keycount-settings .mod-btn').forEach((btn) => btn.addEventListener('click', function() {
  305. this.classList.toggle('mod-btn-selected');
  306. }));
  307. }
  308. // Click to toggle collapse
  309. document.querySelectorAll('.collapser').forEach((collapser) => {
  310. collapser.parentElement.addEventListener('click', () => {
  311. collapser.classList.toggle('rotated');
  312. collapser.parentElement.nextElementSibling.classList.toggle('hidden');
  313. });
  314. });
  315. // Circle size does not apply for taiko/manio
  316. if (window.selected_ruleset == 1 || window.selected_ruleset == 3) {
  317. document.querySelector('#cs_filter').parentElement.parentElement.classList.add('hidden');
  318. }
  319. document.querySelectorAll('.mod-settings .mod-btn').forEach((btn) => btn.addEventListener('click', function() {
  320. const NM = document.querySelector('.nm');
  321. const DT = document.querySelector('.dt');
  322. const NC = document.querySelector('.nc');
  323. const HT = document.querySelector('.ht');
  324. const EZ = document.querySelector('.ez');
  325. const HR = document.querySelector('.hr');
  326. const mod = this.querySelector('div').innerText;
  327. this.classList.toggle('mod-btn-selected');
  328. let selected = this.classList.contains('mod-btn-selected');
  329. if (mod != 'NM' && mod != 'DT' && mod != 'NC' && mod != 'HT') {
  330. NM.classList.remove('mod-btn-selected');
  331. }
  332. if (mod == 'NM' && selected) {
  333. document.querySelectorAll('.mod-settings .mod-btn').forEach((btn) => {
  334. if (btn.classList.contains('dt') || btn.classList.contains('nc') || btn.classList.contains('ht')) return;
  335. btn.classList.remove('mod-btn-selected');
  336. });
  337. this.classList.add('mod-btn-selected');
  338. }
  339. if (mod == 'DT' && !selected) {
  340. DT.classList.toggle('hidden');
  341. NC.classList.toggle('hidden');
  342. NC.classList.add('mod-btn-selected');
  343. selected = true;
  344. }
  345. if (mod == 'NC') {
  346. DT.classList.toggle('hidden');
  347. NC.classList.toggle('hidden');
  348. }
  349. if (selected && (mod == 'DT' || mod == 'NC')) {
  350. HT.classList.remove('mod-btn-selected');
  351. }
  352. if (selected && mod == 'HT') {
  353. DT.classList.remove('mod-btn-selected');
  354. DT.classList.remove('hidden');
  355. NC.classList.remove('mod-btn-selected');
  356. NC.classList.add('hidden');
  357. }
  358. if (selected && mod == 'EZ') {
  359. HR.classList.remove('mod-btn-selected');
  360. }
  361. if (selected && mod == 'HR') {
  362. EZ.classList.remove('mod-btn-selected');
  363. }
  364. }));
  365. if (window.last_match_id) {
  366. const recreate_lobby_div = document.createElement('div');
  367. recreate_lobby_div.className = 'border-2 border-orange-600 rounded-lg p-3 text-center mx-2 mt-4';
  368. recreate_lobby_div.innerHTML = `
  369. <p>You have already created a lobby, but it has been closed due to inactivity.</p>
  370. <p class="mb-2">If you wish to keep the same settings, you can just reopen it:</p>
  371. <button class="reopen-lobby default-btn">Reopen last lobby</button>`;
  372. document.querySelector('main .lobby-settings').append(recreate_lobby_div);
  373. document.querySelector('.reopen-lobby').addEventListener('click', (evt) => {
  374. evt.preventDefault();
  375. evt.stopPropagation();
  376. if (window.logged_user_id) {
  377. document.location = `/reopen-lobby/${window.last_match_id}`;
  378. } else {
  379. return login_then_go_to(`${location.origin}/reopen-lobby/${window.last_match_id}`);
  380. }
  381. });
  382. }
  383. document.querySelector('main .create-lobby-btn').addEventListener('click', (evt) => {
  384. evt.preventDefault();
  385. evt.stopPropagation();
  386. const lobby_settings = {
  387. title: document.querySelector('input[name="title"]').value,
  388. map_selection_algo: document.querySelector('main input[name="map-selection-type"]:checked').value,
  389. map_pool: document.querySelector('main input[name="map-pool"]:checked').value,
  390. collection_id: null,
  391. mod_list: [],
  392. filters: [],
  393. key_count: [],
  394. };
  395. if (lobby_settings.map_pool == 'collection') {
  396. const collection_input = document.querySelector('main input[name="collection-url"]');
  397. lobby_settings.collection_id = parseInt(collection_input.value.split('/').reverse()[0], 10);
  398. }
  399. const selected_mods = document.querySelectorAll('.mod-settings .mod-btn-selected');
  400. for (const mod of selected_mods) {
  401. lobby_settings.mod_list.push(mod.innerText.trim());
  402. }
  403. const filters = document.querySelectorAll('.filter');
  404. for (const filter of filters) {
  405. const checkbox = filter.querySelector('input[type="checkbox"]');
  406. if (!checkbox.checked) continue;
  407. const name = checkbox.id.substring(0, checkbox.id.indexOf('_filter'));
  408. const min = document.querySelector(`input[name="min_${name}"]`).value;
  409. const max = document.querySelector(`input[name="max_${name}"]`).value;
  410. lobby_settings.filters.push({name, min, max});
  411. }
  412. if (window.selected_ruleset == 3) {
  413. const selected_keys = document.querySelectorAll('.mania-keycount-settings .mod-btn-selected');
  414. for (const key of selected_keys) {
  415. lobby_settings.key_count.push(parseInt(key.innerText, 10));
  416. }
  417. }
  418. try_creating_lobby(lobby_settings);
  419. });
  420. const radios = document.querySelectorAll('.radio-area');
  421. for (const area of radios) {
  422. area.addEventListener('click', function() {
  423. this.querySelector('input[type="radio"]').click();
  424. });
  425. }
  426. }
  427. let m;
  428. if (m = location.pathname.match(/\/create-lobby\//)) {
  429. render_create_lobby();
  430. } else if (m = location.pathname.match(/\/reopen-lobby\/(\d+)/)) {
  431. try_creating_lobby({old_match_id: parseInt(m[1], 10)});
  432. } else if (m = location.pathname.match(/\/lobbies\//)) {
  433. render_lobbies();
  434. }