TTW_Bot_GO/internal/webui/templates/notifications.html

156 lines
6.2 KiB
HTML
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

{{define "content"}}
<h2>Звуковые уведомления</h2>
<div class="card">
<h3>События чата и Twitch</h3>
<table id="notif-table">
<thead>
<tr><th>Событие</th><th>Звуковой файл</th><th>Громкость</th><th>Вкл.</th><th>Действия</th></tr>
</thead>
<tbody></tbody>
</table>
<button id="add-defaults"> Добавить события по умолчанию</button>
</div>
<div class="card">
<h3>Управление звуковыми файлами</h3>
<input type="file" id="sound-upload" accept=".mp3,.wav">
<button id="upload-btn">Загрузить</button>
<ul id="sound-list"></ul>
</div>
<script>
let settings = [];
let soundFiles = [];
async function loadSettings() {
const res = await fetch('/api/notifications');
settings = await res.json();
renderTable();
}
async function loadSounds() {
const res = await fetch('/api/sounds');
soundFiles = await res.json();
const list = document.getElementById('sound-list');
if (soundFiles.length === 0) {
list.innerHTML = '<li>Нет загруженных звуков. Загрузите MP3 или WAV.</li>';
} else {
list.innerHTML = soundFiles.map(f => `<li>${escapeHtml(f)} <button class="delete-sound" data-file="${f}">🗑️</button></li>`).join('');
document.querySelectorAll('.delete-sound').forEach(btn => {
btn.addEventListener('click', () => deleteSound(btn.dataset.file));
});
}
// Обновляем выпадающие списки в таблице
renderTable();
}
function renderTable() {
const tbody = document.querySelector('#notif-table tbody');
if (!settings.length) {
tbody.innerHTML = '<tr><td colspan="5">Нет настроек. Нажмите "Добавить события по умолчанию".</td></tr>';
return;
}
tbody.innerHTML = settings.map(s => `
<tr data-event="${s.event_name}">
<td>${escapeHtml(s.event_name)}</td>
<td>
<select class="sound-select" data-event="${s.event_name}">
<option value="">— без звука —</option>
${soundFiles.map(f => `<option value="data/sounds/${f}" ${s.sound_file === `data/sounds/${f}` ? 'selected' : ''}>${f}</option>`).join('')}
</select>
</td>
<td>
<input type="range" class="volume-slider" min="0" max="100" value="${s.volume}" data-event="${s.event_name}">
<span class="vol-value">${s.volume}</span>%
</td>
<td><input type="checkbox" class="enable-checkbox" ${s.enabled ? 'checked' : ''} data-event="${s.event_name}"></td>
<td><button class="test-btn" data-event="${s.event_name}">🔊 Тест</button></td>
</tr>
`).join('');
// Привязываем обработчики
document.querySelectorAll('.sound-select').forEach(sel => {
sel.addEventListener('change', (e) => updateSetting(e.target.dataset.event, 'sound_file', sel.value));
});
document.querySelectorAll('.volume-slider').forEach(slider => {
slider.addEventListener('input', (e) => {
const val = e.target.value;
const span = e.target.parentElement.querySelector('.vol-value');
span.innerText = val;
updateSetting(e.target.dataset.event, 'volume', parseInt(val));
});
});
document.querySelectorAll('.enable-checkbox').forEach(cb => {
cb.addEventListener('change', (e) => updateSetting(e.target.dataset.event, 'enabled', cb.checked));
});
document.querySelectorAll('.test-btn').forEach(btn => {
btn.addEventListener('click', () => testSound(btn.dataset.event));
});
}
async function updateSetting(eventName, field, value) {
const setting = settings.find(s => s.event_name === eventName);
if (!setting) return;
setting[field] = value;
const res = await fetch('/api/notifications', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(setting)
});
if (!res.ok) alert('Ошибка сохранения');
}
async function testSound(eventName) {
await fetch(`/api/notifications/test?event=${encodeURIComponent(eventName)}`);
}
async function deleteSound(filename) {
if (!confirm(`Удалить ${filename}?`)) return;
const res = await fetch(`/api/sounds?file=${encodeURIComponent(filename)}`, { method: 'DELETE' });
if (res.ok) {
await loadSounds();
await loadSettings();
} else {
alert('Ошибка удаления');
}
}
document.getElementById('upload-btn').onclick = async () => {
const fileInput = document.getElementById('sound-upload');
if (!fileInput.files.length) return;
const formData = new FormData();
formData.append('sound', fileInput.files[0]);
const res = await fetch('/api/sounds/upload', { method: 'POST', body: formData });
if (res.ok) {
await loadSounds();
await loadSettings();
fileInput.value = '';
} else {
alert('Ошибка загрузки');
}
};
document.getElementById('add-defaults').onclick = async () => {
const res = await fetch('/api/notifications/defaults', { method: 'POST' });
if (res.ok) {
await loadSettings();
await loadSounds();
} else {
alert('Ошибка добавления событий по умолчанию');
}
};
function escapeHtml(str) {
if (!str) return '';
return str.replace(/[&<>]/g, function(m) {
if (m === '&') return '&amp;';
if (m === '<') return '&lt;';
if (m === '>') return '&gt;';
return m;
});
}
loadSettings();
loadSounds();
</script>
{{end}}