Add ttstoy cog
This commit is contained in:
@@ -0,0 +1,107 @@
|
||||
{% extends "base.html" %}
|
||||
{% block title %}SFX Manager — TtsToy{% endblock %}
|
||||
{% block content %}
|
||||
<div style="margin-bottom:1.25rem;">
|
||||
<h1 style="font-size:1rem;font-weight:300;color:#e0ecff;text-transform:uppercase;letter-spacing:2px;text-shadow:0 0 16px rgba(80,150,255,0.2);">Sound Effects Manager</h1>
|
||||
<div class="muted" style="margin-top:0.3rem;">Each SFX has one emoji trigger and one audio file. Users insert the emoji in their TTS text to play the sound.</div>
|
||||
</div>
|
||||
|
||||
<div class="card">
|
||||
<h2>Add New SFX</h2>
|
||||
<div class="row">
|
||||
<div class="col form-group" style="max-width:180px;">
|
||||
<label>Name</label>
|
||||
<input type="text" id="new-name" placeholder="e.g. airhorn">
|
||||
</div>
|
||||
<div class="col form-group" style="max-width:80px;">
|
||||
<label>Emoji</label>
|
||||
<input type="text" id="new-emoji" placeholder="📢" style="font-size:1.1rem;text-align:center;">
|
||||
</div>
|
||||
<div class="col form-group">
|
||||
<label>Audio File (.mp3 / .wav / .ogg)</label>
|
||||
<input type="file" id="new-file" accept=".mp3,.wav,.ogg">
|
||||
</div>
|
||||
</div>
|
||||
<div style="display:flex;gap:0.5rem;align-items:center;">
|
||||
<button class="btn btn-primary btn-sm" onclick="addSfx()">Add SFX</button>
|
||||
<span id="add-status" class="muted"></span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="card">
|
||||
<h2>SFX Library</h2>
|
||||
<table id="sfx-table">
|
||||
<thead><tr><th>Emoji</th><th>Name</th><th>File</th><th></th></tr></thead>
|
||||
<tbody id="sfx-body"><tr><td colspan="4" class="muted">Loading...</td></tr></tbody>
|
||||
</table>
|
||||
</div>
|
||||
{% endblock %}
|
||||
{% block scripts %}
|
||||
<script>
|
||||
let sfxData = [];
|
||||
|
||||
async function loadSfx() {
|
||||
const r = await apiFetch('/api/sfx');
|
||||
sfxData = await r.json();
|
||||
render();
|
||||
}
|
||||
|
||||
function render() {
|
||||
const tb = document.getElementById('sfx-body');
|
||||
if (!sfxData.length) { tb.innerHTML = '<tr><td colspan="4" class="muted">No SFX yet.</td></tr>'; return; }
|
||||
tb.innerHTML = sfxData.map(s => {
|
||||
const emoji = s.emoji || '';
|
||||
const file = s.file || '';
|
||||
return `<tr>
|
||||
<td style="font-size:1.3rem;text-align:center;width:50px;">
|
||||
<input type="text" value="${emoji}" style="width:40px;font-size:1.2rem;text-align:center;padding:2px;"
|
||||
onchange="setEmoji('${s.name}', this.value)">
|
||||
</td>
|
||||
<td style="color:#c8daf0;">${s.name}</td>
|
||||
<td>${file ? `<span style="font-size:0.82rem;color:#b0c8e0;">${file}</span>` : '<span class="muted">none</span>'}</td>
|
||||
<td style="text-align:right;">
|
||||
<button class="btn btn-danger btn-sm" onclick="deleteSfx('${s.name}')">Delete</button>
|
||||
</td>
|
||||
</tr>`;
|
||||
}).join('');
|
||||
}
|
||||
|
||||
async function setEmoji(name, emoji) {
|
||||
emoji = emoji.trim();
|
||||
const r = await apiFetch('/api/sfx/emoji_map', {method:'POST', body: JSON.stringify({emoji, folder: emoji ? name : '', name})});
|
||||
const d = await r.json();
|
||||
if (!d.ok) alert('Error: ' + (d.error || 'unknown'));
|
||||
await loadSfx();
|
||||
}
|
||||
|
||||
async function addSfx() {
|
||||
const name = document.getElementById('new-name').value.trim().toLowerCase().replace(/[^a-z0-9_-]/g, '');
|
||||
const emoji = document.getElementById('new-emoji').value.trim();
|
||||
const file = document.getElementById('new-file').files[0];
|
||||
const status = document.getElementById('add-status');
|
||||
if (!name) { status.textContent = 'Enter a name.'; return; }
|
||||
if (!emoji) { status.textContent = 'Enter an emoji.'; return; }
|
||||
if (!file) { status.textContent = 'Select an audio file.'; return; }
|
||||
status.textContent = 'Uploading...';
|
||||
const fd = new FormData();
|
||||
fd.append('file', file);
|
||||
const r = await fetch('/api/sfx/' + encodeURIComponent(name) + '/upload', {method:'POST', body: fd});
|
||||
const d = await r.json();
|
||||
if (!d.ok) { status.textContent = 'Error: ' + d.error; return; }
|
||||
await apiFetch('/api/sfx/emoji_map', {method:'POST', body: JSON.stringify({emoji, folder: name})});
|
||||
status.textContent = 'Added.';
|
||||
document.getElementById('new-name').value = '';
|
||||
document.getElementById('new-emoji').value = '';
|
||||
document.getElementById('new-file').value = '';
|
||||
await loadSfx();
|
||||
}
|
||||
|
||||
async function deleteSfx(name) {
|
||||
if (!confirm('Delete SFX "' + name + '"?')) return;
|
||||
await fetch('/api/sfx/' + encodeURIComponent(name), {method:'DELETE'});
|
||||
await loadSfx();
|
||||
}
|
||||
|
||||
loadSfx();
|
||||
</script>
|
||||
{% endblock %}
|
||||
Reference in New Issue
Block a user