This commit is contained in:
parent
be1451ce45
commit
33da605c03
13 changed files with 534 additions and 9 deletions
|
|
@ -86,6 +86,18 @@ const el = {
|
|||
channelName: document.getElementById("channel-name"),
|
||||
channelModalCancel: document.getElementById("channel-modal-cancel"),
|
||||
|
||||
// Sound Board
|
||||
soundboard: document.getElementById('soundboard'),
|
||||
soundboardGrid: document.getElementById('soundboard-grid'),
|
||||
addSoundBtn: document.getElementById('add-sound-btn'),
|
||||
soundModal: document.getElementById('sound-modal'),
|
||||
soundForm: document.getElementById('sound-form'),
|
||||
soundName: document.getElementById('sound-name'),
|
||||
soundIcon: document.getElementById('sound-icon'),
|
||||
soundFile: document.getElementById('sound-file'),
|
||||
soundModalCancel: document.getElementById('sound-modal-cancel'),
|
||||
soundSubmitBtn: document.getElementById('sound-submit-btn'),
|
||||
|
||||
// Mobile
|
||||
mobileMenuBtn: document.getElementById("mobile-menu-btn"),
|
||||
mobileMembersBtn: document.getElementById("mobile-members-btn"),
|
||||
|
|
@ -850,8 +862,10 @@ async function joinVoice() {
|
|||
const channel = state.channels.find(c => c.id === state.selectedVoiceChannelId);
|
||||
el.vcChannelName.textContent = channel ? channel.name : "Voice";
|
||||
el.voiceConnection.classList.remove("hidden");
|
||||
el.soundboard.classList.remove("hidden");
|
||||
playSound('join');
|
||||
refreshVoicePresence().catch(() => { });
|
||||
loadSounds().catch(() => { });
|
||||
};
|
||||
|
||||
ws.onmessage = async (event) => {
|
||||
|
|
@ -890,12 +904,16 @@ async function joinVoice() {
|
|||
if (videoEl) {
|
||||
videoEl.parentElement.classList.toggle('speaking', msg.is_speaking);
|
||||
}
|
||||
} else if (msg.type === "play_sound") {
|
||||
const audio = new Audio(msg.sound_url);
|
||||
audio.play().catch(console.error);
|
||||
}
|
||||
refreshVoicePresence().catch(() => { });
|
||||
};
|
||||
|
||||
ws.onclose = () => {
|
||||
el.voiceConnection.classList.add("hidden");
|
||||
el.soundboard.classList.add("hidden");
|
||||
for (const pc of state.voice.peerConnections.values()) pc.close();
|
||||
state.voice.peerConnections.clear();
|
||||
stopAndClearAudioPipeline();
|
||||
|
|
@ -1014,6 +1032,41 @@ function toggleWatchVideo() {
|
|||
lucide.createIcons();
|
||||
}
|
||||
|
||||
// --- Sound Board ---
|
||||
|
||||
async function loadSounds() {
|
||||
if (!state.selectedGuildId) return;
|
||||
try {
|
||||
const sounds = await api(`/guilds/${state.selectedGuildId}/sounds`);
|
||||
renderSounds(sounds);
|
||||
} catch (err) {
|
||||
console.error("failed to load sounds", err);
|
||||
}
|
||||
}
|
||||
|
||||
function renderSounds(sounds) {
|
||||
el.soundboardGrid.innerHTML = '';
|
||||
sounds.forEach(sound => {
|
||||
const item = document.createElement('div');
|
||||
item.className = 'sound-item';
|
||||
item.innerHTML = `
|
||||
<div class="sound-icon">${sound.icon}</div>
|
||||
<div class="sound-name">${sound.name}</div>
|
||||
`;
|
||||
item.onclick = () => playRemoteSound(sound.file_path);
|
||||
el.soundboardGrid.appendChild(item);
|
||||
});
|
||||
}
|
||||
|
||||
function playRemoteSound(url) {
|
||||
if (state.voice.ws && state.voice.ws.readyState === WebSocket.OPEN) {
|
||||
state.voice.ws.send(JSON.stringify({ type: 'play_sound', sound_url: url }));
|
||||
}
|
||||
// Also play locally immediately
|
||||
const audio = new Audio(url);
|
||||
audio.play().catch(console.error);
|
||||
}
|
||||
|
||||
// --- Mobile Logic ---
|
||||
|
||||
function toggleMobileMenu() {
|
||||
|
|
@ -1192,6 +1245,46 @@ async function init() {
|
|||
el.voiceMuteBtn.onclick = toggleMute;
|
||||
el.voiceLeaveBtn.onclick = leaveVoice;
|
||||
|
||||
el.addSoundBtn.onclick = () => {
|
||||
el.soundModal.classList.remove("hidden");
|
||||
};
|
||||
el.soundModalCancel.onclick = () => {
|
||||
el.soundModal.classList.add("hidden");
|
||||
};
|
||||
|
||||
el.soundForm.onsubmit = async (e) => {
|
||||
e.preventDefault();
|
||||
if (!state.selectedGuildId) return;
|
||||
|
||||
const formData = new FormData();
|
||||
formData.append('name', el.soundName.value);
|
||||
formData.append('icon', el.soundIcon.value);
|
||||
formData.append('file', el.soundFile.files[0]);
|
||||
|
||||
el.soundSubmitBtn.disabled = true;
|
||||
el.soundSubmitBtn.textContent = 'Uploading...';
|
||||
|
||||
try {
|
||||
// Need to use fetch directly because api() might not handle FormData correctly depending on implementation
|
||||
const response = await fetch(`/guilds/${state.selectedGuildId}/sounds`, {
|
||||
method: 'POST',
|
||||
body: formData
|
||||
});
|
||||
if (!response.ok) {
|
||||
const err = await response.json();
|
||||
throw new Error(err.error || 'Upload failed');
|
||||
}
|
||||
el.soundModal.classList.add("hidden");
|
||||
el.soundForm.reset();
|
||||
await loadSounds();
|
||||
} catch (err) {
|
||||
alert(err.message);
|
||||
} finally {
|
||||
el.soundSubmitBtn.disabled = false;
|
||||
el.soundSubmitBtn.textContent = 'Add Sound';
|
||||
}
|
||||
};
|
||||
|
||||
try {
|
||||
state.me = await api("/me");
|
||||
if (state.me && state.me.display_name) {
|
||||
|
|
|
|||
|
|
@ -91,6 +91,13 @@
|
|||
<button id="voice-leave-btn" title="Disconnect"><i data-lucide="phone-off"></i></button>
|
||||
</div>
|
||||
</div>
|
||||
<div id="soundboard" class="soundboard-container hidden">
|
||||
<div class="soundboard-header">
|
||||
<span>Sound Board</span>
|
||||
<span id="add-sound-btn" class="btn-add-sound">+ Add Sound</span>
|
||||
</div>
|
||||
<div id="soundboard-grid" class="soundboard-grid"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="user-panel">
|
||||
|
|
@ -204,6 +211,30 @@
|
|||
</div>
|
||||
</div>
|
||||
|
||||
<div id="sound-modal" class="modal-container hidden">
|
||||
<div class="modal">
|
||||
<h2>Add Sound</h2>
|
||||
<form id="sound-form" class="modal-form">
|
||||
<div class="form-item">
|
||||
<label for="sound-name">NAME</label>
|
||||
<input id="sound-name" placeholder="Quack" maxlength="32" required />
|
||||
</div>
|
||||
<div class="form-item">
|
||||
<label for="sound-icon">ICON (Emoji or Initials)</label>
|
||||
<input id="sound-icon" placeholder="🦆" maxlength="4" required />
|
||||
</div>
|
||||
<div class="form-item">
|
||||
<label for="sound-file">AUDIO FILE (MP3/WAV)</label>
|
||||
<input id="sound-file" type="file" accept="audio/*" required />
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button type="button" class="cancel-btn" id="sound-modal-cancel">Cancel</button>
|
||||
<button type="submit" class="submit-btn" id="sound-submit-btn">Add Sound</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script src="/static/app.js" defer></script>
|
||||
</body>
|
||||
|
||||
|
|
|
|||
|
|
@ -579,6 +579,75 @@ select {
|
|||
background: var(--green);
|
||||
}
|
||||
|
||||
/* Sound Board */
|
||||
.soundboard-container {
|
||||
padding: 12px;
|
||||
background: var(--bg-secondary);
|
||||
border-top: 1px solid rgba(0, 0, 0, 0.2);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.soundboard-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.soundboard-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fill, minmax(80px, 1fr));
|
||||
gap: 8px;
|
||||
max-height: 200px;
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
.sound-item {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
gap: 4px;
|
||||
padding: 8px;
|
||||
background: var(--bg-darker);
|
||||
border-radius: 8px;
|
||||
cursor: pointer;
|
||||
transition: transform 0.1s;
|
||||
}
|
||||
|
||||
.sound-item:hover {
|
||||
background: var(--bg-modifier-hover);
|
||||
transform: translateY(-2px);
|
||||
}
|
||||
|
||||
.sound-item:active {
|
||||
transform: scale(0.95);
|
||||
}
|
||||
|
||||
.sound-icon {
|
||||
font-size: 24px;
|
||||
}
|
||||
|
||||
.sound-name {
|
||||
font-size: 11px;
|
||||
text-align: center;
|
||||
color: var(--text-normal);
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.btn-add-sound {
|
||||
font-size: 12px;
|
||||
color: var(--brand);
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.btn-add-sound:hover {
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
/* Chat Pane */
|
||||
.chat-pane {
|
||||
display: flex;
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue