add private room support with uniq url
This commit is contained in:
+119
-5
@@ -108,8 +108,19 @@ class CardGame {
|
||||
checkReconnect() {
|
||||
// Получаем room_id из URL
|
||||
const path = window.location.pathname;
|
||||
const match = path.match(/\/room\/([a-f0-9-]+)/);
|
||||
|
||||
// Проверяем формат /room/join/{room_id} (приглашение в приватную комнату)
|
||||
let match = path.match(/\/room\/join\/([a-f0-9-]+)/);
|
||||
if (match) {
|
||||
this.roomId = match[1];
|
||||
this.isJoiningViaLink = true;
|
||||
// Не проверяем player_id, показываем модалку для ввода никнейма
|
||||
this.connect(false);
|
||||
return;
|
||||
}
|
||||
|
||||
// Проверяем формат /room/{room_id} (обычное переподключение)
|
||||
match = path.match(/\/room\/([a-f0-9-]+)/);
|
||||
if (match) {
|
||||
this.roomId = match[1];
|
||||
// Получаем player_id из localStorage
|
||||
@@ -158,6 +169,11 @@ class CardGame {
|
||||
this.nicknameInput = document.getElementById('nickname-input');
|
||||
this.createRoomBtn = document.getElementById('create-room-btn');
|
||||
this.roomsList = document.getElementById('rooms-list');
|
||||
this.privateRoomCheckbox = document.getElementById('private-room-checkbox');
|
||||
this.inviteLinkBlock = document.getElementById('invite-link-block');
|
||||
this.inviteLink = document.getElementById('invite-link');
|
||||
this.copyLinkBtn = document.getElementById('copy-link-btn');
|
||||
this.shareLinkBtn = document.getElementById('share-link-btn');
|
||||
|
||||
// Room elements
|
||||
this.playersList = document.getElementById('players-list');
|
||||
@@ -244,6 +260,18 @@ class CardGame {
|
||||
if (e.key === 'Enter') this.createRoom();
|
||||
});
|
||||
|
||||
// Private room checkbox
|
||||
this.privateRoomCheckbox.addEventListener('change', () => this.togglePrivateRoom());
|
||||
|
||||
// Copy and share link buttons
|
||||
this.copyLinkBtn.addEventListener('click', () => this.copyInviteLink());
|
||||
this.shareLinkBtn.addEventListener('click', () => this.shareInviteLink());
|
||||
|
||||
// Show share button if Web Share API is available
|
||||
if (navigator.share) {
|
||||
this.shareLinkBtn.style.display = 'block';
|
||||
}
|
||||
|
||||
this.readyToggleBtn.addEventListener('click', () => this.toggleReady());
|
||||
this.leaveRoomBtn.addEventListener('click', () => this.leaveRoom());
|
||||
|
||||
@@ -356,6 +384,10 @@ class CardGame {
|
||||
player_id: this.playerId || 'temp_' + Date.now(),
|
||||
room_id: this.roomId
|
||||
});
|
||||
} else if (this.isJoiningViaLink && this.roomId) {
|
||||
// Переход по ссылке приглашения - показываем модалку для ввода никнейма
|
||||
this.joinModal.classList.add('active');
|
||||
this.joinNicknameInput.focus();
|
||||
} else {
|
||||
// Обычное подключение
|
||||
this.send({ type: 'get_rooms' });
|
||||
@@ -409,6 +441,7 @@ class CardGame {
|
||||
this.currentRoom = data.room;
|
||||
this.saveToLocalStorage();
|
||||
this.updateURL(this.roomId);
|
||||
|
||||
this.showScreen('room');
|
||||
this.updatePlayersInRoom(data.room.players);
|
||||
this.updateRoomSettings(data.room);
|
||||
@@ -707,9 +740,23 @@ class CardGame {
|
||||
this.deckSizeToggle.checked = data.deck_size === 52;
|
||||
this.addLogEntry(`Размер колоды карт: ${data.deck_size}`);
|
||||
break;
|
||||
case 'room_closed':
|
||||
// Комната закрыта создателем
|
||||
this.showAlert(data.message);
|
||||
setTimeout(() => this.goToLobby(), 3000);
|
||||
break;
|
||||
case 'error':
|
||||
// Если ошибка связана с комнатой/игроком - показываем экран ошибки
|
||||
if (data.message.includes('not found') || data.message.includes('не найден')) {
|
||||
// Обрабатываем ошибки по кодам
|
||||
if (data.error_code === 'room_not_found' ||
|
||||
data.error_code === 'game_started' ||
|
||||
data.error_code === 'player_not_found') {
|
||||
// Ссылка недействительна или комната не существует - показываем экран ошибки
|
||||
this.showError(data.message);
|
||||
// Очищаем данные
|
||||
this.isJoiningViaLink = false;
|
||||
this.roomId = null;
|
||||
this.clearLocalStorage();
|
||||
} else if (data.message.includes('not found') || data.message.includes('не найден')) {
|
||||
this.showError(data.message);
|
||||
} else {
|
||||
this.showAlert(data.message);
|
||||
@@ -752,10 +799,63 @@ class CardGame {
|
||||
|
||||
this.send({
|
||||
type: 'create_room',
|
||||
nickname: nickname
|
||||
nickname: nickname,
|
||||
is_private: false // По умолчанию не приватная
|
||||
});
|
||||
}
|
||||
|
||||
togglePrivateRoom() {
|
||||
const isChecked = this.privateRoomCheckbox.checked;
|
||||
|
||||
// Отправляем на сервер изменение приватности
|
||||
this.send({
|
||||
type: 'toggle_private',
|
||||
is_private: isChecked
|
||||
});
|
||||
|
||||
// Показываем/скрываем блок со ссылкой
|
||||
if (isChecked) {
|
||||
this.inviteLinkBlock.style.display = 'block';
|
||||
// Генерируем ссылку если её ещё нет
|
||||
if (!this.inviteLink.textContent && this.roomId) {
|
||||
const inviteUrl = `${window.location.origin}/room/join/${this.roomId}`;
|
||||
this.inviteLink.textContent = inviteUrl;
|
||||
}
|
||||
} else {
|
||||
this.inviteLinkBlock.style.display = 'none';
|
||||
}
|
||||
}
|
||||
|
||||
copyInviteLink() {
|
||||
const link = this.inviteLink.textContent;
|
||||
navigator.clipboard.writeText(link).then(() => {
|
||||
// Временно меняем текст кнопки
|
||||
const originalText = this.copyLinkBtn.textContent;
|
||||
this.copyLinkBtn.textContent = '✓';
|
||||
setTimeout(() => {
|
||||
this.copyLinkBtn.textContent = originalText;
|
||||
}, 1500);
|
||||
}).catch(err => {
|
||||
console.error('Failed to copy:', err);
|
||||
this.showAlert('Не удалось скопировать ссылку');
|
||||
});
|
||||
}
|
||||
|
||||
async shareInviteLink() {
|
||||
const link = this.inviteLink.textContent;
|
||||
try {
|
||||
await navigator.share({
|
||||
title: 'Погнали в чешского!',
|
||||
text: 'Присоединяйся к комнате:',
|
||||
url: link
|
||||
});
|
||||
} catch (err) {
|
||||
if (err.name !== 'AbortError') {
|
||||
console.error('Failed to share:', err);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
createBotGame(botCount) {
|
||||
const nickname = this.nicknameInput.value.trim();
|
||||
if (!nickname) {
|
||||
@@ -811,13 +911,19 @@ class CardGame {
|
||||
return;
|
||||
}
|
||||
|
||||
// Используем roomId если присоединяемся по ссылке, иначе pendingRoomId
|
||||
const roomId = this.isJoiningViaLink ? this.roomId : this.pendingRoomId;
|
||||
|
||||
this.send({
|
||||
type: 'join_room',
|
||||
room_id: this.pendingRoomId,
|
||||
room_id: roomId,
|
||||
nickname: nickname
|
||||
});
|
||||
|
||||
this.joinModal.classList.remove('active');
|
||||
|
||||
// Сбрасываем флаг после присоединения
|
||||
this.isJoiningViaLink = false;
|
||||
}
|
||||
|
||||
cancelJoin() {
|
||||
@@ -849,6 +955,14 @@ class CardGame {
|
||||
}
|
||||
|
||||
leaveRoom() {
|
||||
// Скрываем блок с ссылкой если он был показан
|
||||
if (this.inviteLinkBlock) {
|
||||
this.inviteLinkBlock.style.display = 'none';
|
||||
}
|
||||
// Сбрасываем чекбокс приватной комнаты
|
||||
if (this.privateRoomCheckbox) {
|
||||
this.privateRoomCheckbox.checked = false;
|
||||
}
|
||||
this.goToLobby();
|
||||
}
|
||||
|
||||
|
||||
@@ -73,6 +73,30 @@
|
||||
<!-- Настройки комнаты (только для создателя) -->
|
||||
<div id="room-settings" style="display: none;">
|
||||
<h3>Настройки игры</h3>
|
||||
|
||||
<!-- Чекбокс приватной комнаты -->
|
||||
<div class="private-room-toggle">
|
||||
<label>
|
||||
<input type="checkbox" id="private-room-checkbox">
|
||||
<span>Приватная комната</span>
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<!-- Блок с ссылкой для приглашения (скрыт по умолчанию) -->
|
||||
<div id="invite-link-block" style="display: none;">
|
||||
<p class="invite-hint">Поделитесь ссылкой с друзьями:</p>
|
||||
<div class="invite-link-container">
|
||||
<code id="invite-link" class="invite-link"></code>
|
||||
<button id="copy-link-btn" class="btn btn-copy" title="Копировать ссылку"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none">
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M15 1.25H10.9436C9.10583 1.24998 7.65019 1.24997 6.51098 1.40314C5.33856 1.56076 4.38961 1.89288 3.64124 2.64124C2.89288 3.38961 2.56076 4.33856 2.40314 5.51098C2.24997 6.65019 2.24998 8.10582 2.25 9.94357V16C2.25 17.8722 3.62205 19.424 5.41551 19.7047C5.55348 20.4687 5.81753 21.1208 6.34835 21.6517C6.95027 22.2536 7.70814 22.5125 8.60825 22.6335C9.47522 22.75 10.5775 22.75 11.9451 22.75H15.0549C16.4225 22.75 17.5248 22.75 18.3918 22.6335C19.2919 22.5125 20.0497 22.2536 20.6517 21.6517C21.2536 21.0497 21.5125 20.2919 21.6335 19.3918C21.75 18.5248 21.75 17.4225 21.75 16.0549V10.9451C21.75 9.57754 21.75 8.47522 21.6335 7.60825C21.5125 6.70814 21.2536 5.95027 20.6517 5.34835C20.1208 4.81753 19.4687 4.55348 18.7047 4.41551C18.424 2.62205 16.8722 1.25 15 1.25ZM17.1293 4.27117C16.8265 3.38623 15.9876 2.75 15 2.75H11C9.09318 2.75 7.73851 2.75159 6.71085 2.88976C5.70476 3.02502 5.12511 3.27869 4.7019 3.7019C4.27869 4.12511 4.02502 4.70476 3.88976 5.71085C3.75159 6.73851 3.75 8.09318 3.75 10V16C3.75 16.9876 4.38624 17.8265 5.27117 18.1293C5.24998 17.5194 5.24999 16.8297 5.25 16.0549V10.9451C5.24998 9.57754 5.24996 8.47522 5.36652 7.60825C5.48754 6.70814 5.74643 5.95027 6.34835 5.34835C6.95027 4.74643 7.70814 4.48754 8.60825 4.36652C9.47522 4.24996 10.5775 4.24998 11.9451 4.25H15.0549C15.8297 4.24999 16.5194 4.24998 17.1293 4.27117ZM7.40901 6.40901C7.68577 6.13225 8.07435 5.9518 8.80812 5.85315C9.56347 5.75159 10.5646 5.75 12 5.75H15C16.4354 5.75 17.4365 5.75159 18.1919 5.85315C18.9257 5.9518 19.3142 6.13225 19.591 6.40901C19.8678 6.68577 20.0482 7.07435 20.1469 7.80812C20.2484 8.56347 20.25 9.56458 20.25 11V16C20.25 17.4354 20.2484 18.4365 20.1469 19.1919C20.0482 19.9257 19.8678 20.3142 19.591 20.591C19.3142 20.8678 18.9257 21.0482 18.1919 21.1469C17.4365 21.2484 16.4354 21.25 15 21.25H12C10.5646 21.25 9.56347 21.2484 8.80812 21.1469C8.07435 21.0482 7.68577 20.8678 7.40901 20.591C7.13225 20.3142 6.9518 19.9257 6.85315 19.1919C6.75159 18.4365 6.75 17.4354 6.75 16V11C6.75 9.56458 6.75159 8.56347 6.85315 7.80812C6.9518 7.07435 7.13225 6.68577 7.40901 6.40901Z" fill="#FFFFFF"/>
|
||||
</svg></button>
|
||||
<button id="share-link-btn" class="btn btn-share" title="Поделиться" style="display: none;"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none">
|
||||
<path d="M12 1.25H11.9426C9.63423 1.24999 7.82519 1.24998 6.41371 1.43975C4.96897 1.63399 3.82895 2.03933 2.93414 2.93414C2.03933 3.82895 1.63399 4.96897 1.43975 6.41371C1.24998 7.82519 1.24999 9.63423 1.25 11.9426V12.0574C1.24999 14.3658 1.24998 16.1748 1.43975 17.5863C1.63399 19.031 2.03933 20.1711 2.93414 21.0659C3.82895 21.9607 4.96897 22.366 6.41371 22.5603C7.82519 22.75 9.63423 22.75 11.9426 22.75H12.0574C14.3658 22.75 16.1748 22.75 17.5863 22.5603C19.031 22.366 20.1711 21.9607 21.0659 21.0659C21.9607 20.1711 22.366 19.031 22.5603 17.5863C22.75 16.1748 22.75 14.3658 22.75 12.0574V12C22.75 11.5858 22.4142 11.25 22 11.25C21.5858 11.25 21.25 11.5858 21.25 12C21.25 14.3782 21.2484 16.0864 21.0736 17.3864C20.9018 18.6648 20.5749 19.4355 20.0052 20.0052C19.4355 20.5749 18.6648 20.9018 17.3864 21.0736C16.0864 21.2484 14.3782 21.25 12 21.25C9.62178 21.25 7.91356 21.2484 6.61358 21.0736C5.33517 20.9018 4.56445 20.5749 3.9948 20.0052C3.42514 19.4355 3.09825 18.6648 2.92637 17.3864C2.75159 16.0864 2.75 14.3782 2.75 12C2.75 9.62178 2.75159 7.91356 2.92637 6.61358C3.09825 5.33517 3.42514 4.56445 3.9948 3.9948C4.56445 3.42514 5.33517 3.09825 6.61358 2.92637C7.91356 2.75159 9.62178 2.75 12 2.75C12.4142 2.75 12.75 2.41421 12.75 2C12.75 1.58579 12.4142 1.25 12 1.25Z" fill="#FFFFFF"/>
|
||||
<path d="M12.4697 10.4697C12.1768 10.7626 12.1768 11.2374 12.4697 11.5303C12.7626 11.8232 13.2374 11.8232 13.5303 11.5303L21.25 3.81066V7.34375C21.25 7.75796 21.5858 8.09375 22 8.09375C22.4142 8.09375 22.75 7.75796 22.75 7.34375V2C22.75 1.58579 22.4142 1.25 22 1.25H16.6562C16.242 1.25 15.9062 1.58579 15.9062 2C15.9062 2.41421 16.242 2.75 16.6562 2.75H20.1893L12.4697 10.4697Z" fill="#FFFFFF"/>
|
||||
</svg></button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="deck-size-toggle">
|
||||
<label>
|
||||
<span class="deck-label">36 карт</span>
|
||||
|
||||
@@ -133,6 +133,83 @@ input[type="text"]:focus {
|
||||
margin-bottom: 40px;
|
||||
}
|
||||
|
||||
/* Private Room Toggle */
|
||||
.private-room-toggle {
|
||||
margin: 15px 0;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.private-room-toggle label {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
cursor: pointer;
|
||||
font-size: 14px;
|
||||
color: #6f6f6f;
|
||||
}
|
||||
|
||||
.private-room-toggle input[type="checkbox"] {
|
||||
width: 18px;
|
||||
height: 18px;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
/* Invite Link Block */
|
||||
#invite-link-block {
|
||||
margin: 15px 0;
|
||||
padding: 15px;
|
||||
background: rgba(50, 160, 130, 0.1);
|
||||
border: 1px solid rgba(50, 160, 130, 0.3);
|
||||
border-radius: 8px;
|
||||
}
|
||||
|
||||
.invite-hint {
|
||||
margin: 0 0 10px 0;
|
||||
font-size: 14px;
|
||||
color: #6f6f6f;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.invite-link-container {
|
||||
display: flex;
|
||||
gap: 8px;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.invite-link {
|
||||
flex: 1;
|
||||
padding: 10px;
|
||||
background: white;
|
||||
border: 1px solid #ddd;
|
||||
border-radius: 6px;
|
||||
font-size: 13px;
|
||||
color: #32a082;
|
||||
word-break: break-all;
|
||||
user-select: all;
|
||||
}
|
||||
|
||||
.btn-copy, .btn-share {
|
||||
min-width: 44px;
|
||||
height: 44px;
|
||||
padding: 8px;
|
||||
font-size: 20px;
|
||||
background: #32a082;
|
||||
color: white;
|
||||
border: none;
|
||||
border-radius: 6px;
|
||||
cursor: pointer;
|
||||
transition: all 0.3s;
|
||||
}
|
||||
|
||||
.btn-copy:hover, .btn-share:hover {
|
||||
background: #28866d;
|
||||
transform: scale(1.05);
|
||||
}
|
||||
|
||||
.btn-copy:active, .btn-share:active {
|
||||
transform: scale(0.95);
|
||||
}
|
||||
|
||||
/* Bot Game Section */
|
||||
#bot-game-section {
|
||||
margin-top: 25px;
|
||||
|
||||
Reference in New Issue
Block a user