Move inline template scripts into webpack-built assets
Several templates had their own <script> blocks instead of going through webpack like game1.js already does. Extracted the waiting page, lobby page, and admin session-log tab scripts into their own files under assets/, imported from the main app.js entry. Since app.js is already loaded on every page, each module just reads its own data-* attributes and no-ops if its target element isn't present on the current page - same pattern game1.js already uses. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,27 @@
|
|||||||
|
document.addEventListener('DOMContentLoaded', () => {
|
||||||
|
const buttons = document.querySelectorAll('[data-tab-target]');
|
||||||
|
if (!buttons.length) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const activate = (id) => {
|
||||||
|
document.querySelectorAll('[id^="player-"]').forEach(el => el.style.display = 'none');
|
||||||
|
const target = document.getElementById(id);
|
||||||
|
if (target) {
|
||||||
|
target.style.display = 'block';
|
||||||
|
}
|
||||||
|
|
||||||
|
buttons.forEach(btn => {
|
||||||
|
const active = btn.dataset.tabTarget === id;
|
||||||
|
btn.style.background = active ? '#fff' : '#f8fafc';
|
||||||
|
btn.style.color = active ? '#1e40af' : '#64748b';
|
||||||
|
btn.style.fontWeight = active ? '600' : '400';
|
||||||
|
btn.style.borderColor = active ? '#3b82f6' : '#e2e8f0';
|
||||||
|
btn.style.borderBottom = active ? '1px solid #fff' : '1px solid #e2e8f0';
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
buttons.forEach(btn => {
|
||||||
|
btn.addEventListener('click', () => activate(btn.dataset.tabTarget));
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -5,3 +5,6 @@ import './styles/app.scss';
|
|||||||
import 'bootstrap/js/dist/collapse';
|
import 'bootstrap/js/dist/collapse';
|
||||||
import 'bootstrap/js/dist/alert';
|
import 'bootstrap/js/dist/alert';
|
||||||
import 'bootstrap/js/dist/dropdown';
|
import 'bootstrap/js/dist/dropdown';
|
||||||
|
import './game-waiting';
|
||||||
|
import './game-lobby';
|
||||||
|
import './admin-session-tabs';
|
||||||
|
|||||||
@@ -0,0 +1,62 @@
|
|||||||
|
document.addEventListener('DOMContentLoaded', () => {
|
||||||
|
const config = document.getElementById('game-lobby-config');
|
||||||
|
if (!config) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const publicUrl = config.dataset.mercurePublicUrl;
|
||||||
|
const topic = config.dataset.topic;
|
||||||
|
const chatLog = document.getElementById('lobby-chat-log');
|
||||||
|
|
||||||
|
function appendLobbyMessage(username, content, createdAt) {
|
||||||
|
if (!chatLog) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const emptyNotice = document.getElementById('lobby-chat-empty');
|
||||||
|
if (emptyNotice) {
|
||||||
|
emptyNotice.remove();
|
||||||
|
}
|
||||||
|
|
||||||
|
const time = createdAt
|
||||||
|
? new Date(createdAt).toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' })
|
||||||
|
: '';
|
||||||
|
|
||||||
|
const wrapper = document.createElement('div');
|
||||||
|
wrapper.className = 'lobby-message';
|
||||||
|
|
||||||
|
const author = document.createElement('strong');
|
||||||
|
author.textContent = username;
|
||||||
|
|
||||||
|
const timestamp = document.createElement('span');
|
||||||
|
timestamp.className = 'text-muted small';
|
||||||
|
timestamp.textContent = ' ' + time;
|
||||||
|
|
||||||
|
const body = document.createElement('div');
|
||||||
|
body.textContent = content;
|
||||||
|
|
||||||
|
wrapper.appendChild(author);
|
||||||
|
wrapper.appendChild(timestamp);
|
||||||
|
wrapper.appendChild(body);
|
||||||
|
chatLog.appendChild(wrapper);
|
||||||
|
chatLog.scrollTop = chatLog.scrollHeight;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (publicUrl && topic) {
|
||||||
|
const url = new URL(publicUrl);
|
||||||
|
url.searchParams.append('topic', topic);
|
||||||
|
|
||||||
|
const eventSource = new EventSource(url);
|
||||||
|
eventSource.onmessage = event => {
|
||||||
|
const data = JSON.parse(event.data);
|
||||||
|
if (data.type === 'lobby_message') {
|
||||||
|
appendLobbyMessage(data.username, data.content, data.createdAt);
|
||||||
|
} else if (data.type === 'player_joined' || data.type === 'session_started') {
|
||||||
|
window.location.reload();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
if (chatLog) {
|
||||||
|
chatLog.scrollTop = chatLog.scrollHeight;
|
||||||
|
}
|
||||||
|
});
|
||||||
@@ -0,0 +1,68 @@
|
|||||||
|
document.addEventListener('DOMContentLoaded', () => {
|
||||||
|
const config = document.getElementById('game-waiting-config');
|
||||||
|
if (!config) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const publicUrl = config.dataset.mercurePublicUrl;
|
||||||
|
const topic = config.dataset.topic;
|
||||||
|
const readyAt = config.dataset.readyAt;
|
||||||
|
|
||||||
|
let reloading = false;
|
||||||
|
const reloadOnce = (eventSource) => {
|
||||||
|
if (reloading) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
reloading = true;
|
||||||
|
if (eventSource) {
|
||||||
|
eventSource.close();
|
||||||
|
}
|
||||||
|
window.location.reload();
|
||||||
|
};
|
||||||
|
|
||||||
|
if (publicUrl && topic) {
|
||||||
|
const url = new URL(publicUrl);
|
||||||
|
url.searchParams.append('topic', topic);
|
||||||
|
|
||||||
|
const eventSource = new EventSource(url);
|
||||||
|
eventSource.onmessage = event => {
|
||||||
|
const data = JSON.parse(event.data);
|
||||||
|
if (data.type === 'all_ready' || data.type === 'player_ready') {
|
||||||
|
reloadOnce(eventSource);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// Our own ready status expires 60s after we set it - proactively tell the
|
||||||
|
// server as close to that deadline as possible, so the other players find
|
||||||
|
// out live instead of only whenever someone else's request happens to
|
||||||
|
// trigger the lazy check.
|
||||||
|
if (readyAt) {
|
||||||
|
const timeoutMs = 61000; // slightly more than the server-side 60s
|
||||||
|
const readyAtMs = readyAt * 1000;
|
||||||
|
const countdownEl = document.getElementById('ready-countdown');
|
||||||
|
const expireForm = document.getElementById('expire-ready-form');
|
||||||
|
|
||||||
|
const updateCountdown = () => {
|
||||||
|
const remaining = Math.max(0, Math.ceil((readyAtMs + timeoutMs - Date.now()) / 1000));
|
||||||
|
if (countdownEl) {
|
||||||
|
const m = Math.floor(remaining / 60);
|
||||||
|
const s = remaining % 60;
|
||||||
|
countdownEl.textContent = m + ':' + s.toString().padStart(2, '0');
|
||||||
|
}
|
||||||
|
return remaining;
|
||||||
|
};
|
||||||
|
|
||||||
|
const remaining = updateCountdown();
|
||||||
|
if (remaining <= 0) {
|
||||||
|
expireForm?.submit();
|
||||||
|
} else {
|
||||||
|
const countdownInterval = setInterval(() => {
|
||||||
|
if (updateCountdown() <= 0) {
|
||||||
|
clearInterval(countdownInterval);
|
||||||
|
expireForm?.submit();
|
||||||
|
}
|
||||||
|
}, 1000);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
@@ -90,74 +90,10 @@
|
|||||||
<input type="hidden" name="expire_ready" value="1">
|
<input type="hidden" name="expire_ready" value="1">
|
||||||
</form>
|
</form>
|
||||||
|
|
||||||
<div id="mercure-config"
|
<div id="game-waiting-config"
|
||||||
data-mercure-public-url="{{ mercure_public_url|e('html_attr') }}"
|
data-mercure-public-url="{{ mercure_public_url|e('html_attr') }}"
|
||||||
data-topic="/game/hub/{{ session.id|e('html_attr') }}"
|
data-topic="/game/hub/{{ session.id|e('html_attr') }}"
|
||||||
data-ready-at="{{ readyAt|e('html_attr') }}"
|
data-ready-at="{{ readyAt|e('html_attr') }}"
|
||||||
style="display:none">
|
style="display:none">
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<script>
|
|
||||||
const config = document.getElementById('mercure-config');
|
|
||||||
const publicUrl = config.dataset.mercurePublicUrl;
|
|
||||||
const topic = config.dataset.topic;
|
|
||||||
const readyAt = config.dataset.readyAt;
|
|
||||||
|
|
||||||
let reloading = false;
|
|
||||||
const reloadOnce = (eventSource) => {
|
|
||||||
if (reloading) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
reloading = true;
|
|
||||||
if (eventSource) {
|
|
||||||
eventSource.close();
|
|
||||||
}
|
|
||||||
window.location.reload();
|
|
||||||
};
|
|
||||||
|
|
||||||
if (publicUrl && topic) {
|
|
||||||
const url = new URL(publicUrl);
|
|
||||||
url.searchParams.append('topic', topic);
|
|
||||||
|
|
||||||
const eventSource = new EventSource(url);
|
|
||||||
eventSource.onmessage = event => {
|
|
||||||
const data = JSON.parse(event.data);
|
|
||||||
if (data.type === 'all_ready' || data.type === 'player_ready') {
|
|
||||||
reloadOnce(eventSource);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
// Our own ready status expires 60s after we set it - proactively tell the
|
|
||||||
// server as close to that deadline as possible, so the other players find
|
|
||||||
// out live instead of only whenever someone else's request happens to
|
|
||||||
// trigger the lazy check.
|
|
||||||
if (readyAt) {
|
|
||||||
const timeoutMs = 61000; // slightly more than the server-side 60s
|
|
||||||
const readyAtMs = readyAt * 1000;
|
|
||||||
const countdownEl = document.getElementById('ready-countdown');
|
|
||||||
|
|
||||||
const updateCountdown = () => {
|
|
||||||
const remaining = Math.max(0, Math.ceil((readyAtMs + timeoutMs - Date.now()) / 1000));
|
|
||||||
if (countdownEl) {
|
|
||||||
const m = Math.floor(remaining / 60);
|
|
||||||
const s = remaining % 60;
|
|
||||||
countdownEl.textContent = m + ':' + s.toString().padStart(2, '0');
|
|
||||||
}
|
|
||||||
return remaining;
|
|
||||||
};
|
|
||||||
|
|
||||||
const remaining = updateCountdown();
|
|
||||||
if (remaining <= 0) {
|
|
||||||
document.getElementById('expire-ready-form').submit();
|
|
||||||
} else {
|
|
||||||
const countdownInterval = setInterval(() => {
|
|
||||||
if (updateCountdown() <= 0) {
|
|
||||||
clearInterval(countdownInterval);
|
|
||||||
document.getElementById('expire-ready-form').submit();
|
|
||||||
}
|
|
||||||
}, 1000);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|||||||
Reference in New Issue
Block a user