forked from server/soundboard
Merge branch 'metadata' of https://git.wiai.de/mmueller/soundboard
This commit is contained in:
commit
a18db979bb
1
.gitignore
vendored
1
.gitignore
vendored
@ -8,3 +8,4 @@
|
|||||||
|
|
||||||
# Project specific files
|
# Project specific files
|
||||||
config.py
|
config.py
|
||||||
|
metadata.sqlite*
|
||||||
|
|||||||
@ -1 +1,2 @@
|
|||||||
path = "/home/pi/sounds"
|
path = "/home/pi/sounds"
|
||||||
|
db = "metadata.sqlite"
|
||||||
|
|||||||
18
schema.sql
Normal file
18
schema.sql
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
CREATE TABLE IF NOT EXISTS tag (
|
||||||
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||||
|
name TEXT NOT NULL UNIQUE
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE TABLE IF NOT EXISTS button (
|
||||||
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||||
|
file TEXT,
|
||||||
|
checksum TEXT
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE TABLE IF NOT EXISTS button_tags (
|
||||||
|
fk_tag INTEGER,
|
||||||
|
fk_button INTEGER,
|
||||||
|
FOREIGN KEY(fk_tag) REFERENCES tag(id),
|
||||||
|
FOREIGN KEY(fk_button) REFERENCES button(id),
|
||||||
|
PRIMARY KEY(fk_tag, fk_button)
|
||||||
|
);
|
||||||
148
soundboard.py
148
soundboard.py
@ -1,7 +1,9 @@
|
|||||||
import os
|
import os
|
||||||
|
import sys
|
||||||
import subprocess
|
import subprocess
|
||||||
|
import sqlite3
|
||||||
|
|
||||||
from flask import Flask, render_template, request, redirect, url_for, send_from_directory
|
from flask import Flask, render_template, request, redirect, url_for, send_from_directory, g
|
||||||
|
|
||||||
import config
|
import config
|
||||||
|
|
||||||
@ -9,50 +11,138 @@ app = Flask(__name__)
|
|||||||
app.jinja_env.trim_blocks = True
|
app.jinja_env.trim_blocks = True
|
||||||
app.jinja_env.lstrip_blocks = True
|
app.jinja_env.lstrip_blocks = True
|
||||||
|
|
||||||
processlist = []
|
# Credits: http://flask.pocoo.org/docs/0.12/patterns/sqlite3/
|
||||||
|
def getDB():
|
||||||
|
db = getattr(g, "_database", None)
|
||||||
|
|
||||||
|
if db is None:
|
||||||
|
if os.path.isabs(config.db):
|
||||||
|
dbPath = config.db
|
||||||
|
else:
|
||||||
|
dbPath = os.path.join(sys.path[0], config.db)
|
||||||
|
|
||||||
|
db = g._database = sqlite3.connect(dbPath)
|
||||||
|
db.row_factory = sqlite3.Row
|
||||||
|
|
||||||
|
return db
|
||||||
|
|
||||||
|
@app.teardown_appcontext
|
||||||
|
def closeDBConnection(exception):
|
||||||
|
db = getattr(g, "_database", None)
|
||||||
|
|
||||||
|
if db is not None:
|
||||||
|
db.close()
|
||||||
|
|
||||||
|
def queryDB(query, args=(), one=False):
|
||||||
|
cur = getDB().execute(query, args)
|
||||||
|
result = cur.fetchall()
|
||||||
|
|
||||||
|
cur.close()
|
||||||
|
|
||||||
|
return (result[0] if result else None) if one else result
|
||||||
|
|
||||||
@app.route("/")
|
@app.route("/")
|
||||||
|
@app.route("/edit")
|
||||||
@app.route("/play/<sound>")
|
@app.route("/play/<sound>")
|
||||||
@app.route("/say/", methods=["POST"])
|
@app.route("/say/", methods=["POST"])
|
||||||
@app.route("/say/<text>")
|
@app.route("/say/<text>")
|
||||||
def index(sound=None, text=None, video=None):
|
def index(sound=None, text=None, video=None):
|
||||||
sounds = [os.fsencode(file).decode() for file in os.listdir(config.path)]
|
sounds = [os.fsencode(file).decode() for file in os.listdir(config.path)]
|
||||||
sounds = sorted(sounds)
|
sounds = sorted(sounds)
|
||||||
|
|
||||||
if sound is not None and sound in sounds:
|
tags = queryDB("SELECT name FROM tag ORDER BY name COLLATE NOCASE")
|
||||||
subprocess.Popen(["omxplayer", os.path.join(config.path, sound).encode("utf-8")], stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
|
|
||||||
|
|
||||||
if text is None:
|
if sound is not None and sound in sounds:
|
||||||
text = request.form.get("text")
|
subprocess.Popen(["omxplayer", os.path.join(config.path, sound).encode("utf-8")], stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
|
||||||
|
|
||||||
if text is not None:
|
if text is None:
|
||||||
voice = request.form.get("voice", default="")
|
text = request.form.get("text")
|
||||||
voice = voice if voice.strip() != "" else "DE"
|
|
||||||
speed = request.form.get("speed", default="")
|
|
||||||
speed = speed if speed.strip() != "" else "160"
|
|
||||||
pitch = request.form.get("pitch", default="")
|
|
||||||
pitch = pitch if pitch.strip() != "" else "50"
|
|
||||||
|
|
||||||
subprocess.Popen(["espeak", "-v", voice, "-s", speed, "-p", pitch, text.encode("utf-8")], stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
|
if text is not None:
|
||||||
return redirect("/")
|
voice = request.form.get("voice", default="")
|
||||||
|
voice = voice if voice.strip() != "" else "DE"
|
||||||
|
speed = request.form.get("speed", default="")
|
||||||
|
speed = speed if speed.strip() != "" else "160"
|
||||||
|
pitch = request.form.get("pitch", default="")
|
||||||
|
pitch = pitch if pitch.strip() != "" else "50"
|
||||||
|
|
||||||
video = request.args.get("video")
|
subprocess.Popen(["espeak", "-v", voice, "-s", speed, "-p", pitch, text.encode("utf-8")], stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
|
||||||
|
return redirect("/")
|
||||||
|
|
||||||
if video is not None:
|
video = request.args.get("video")
|
||||||
|
|
||||||
if video[-4:] == ".mp3":
|
if video is not None:
|
||||||
subprocess.Popen(["omxplayer", video], stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
|
if video[-4:] == ".mp3":
|
||||||
else:
|
subprocess.Popen(["omxplayer", video], stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
|
||||||
url = subprocess.check_output(["youtube-dl", "-g", "-f", "mp4", video]).decode()
|
else:
|
||||||
subprocess.Popen(["omxplayer", url.split("\n")[1]])
|
url = subprocess.check_output(["youtube-dl", "-g", "-f", "mp4", video]).decode()
|
||||||
subprocess.Popen(["omxplayer", "-b", url.split("\n")[0]])
|
subprocess.Popen(["omxplayer", url.split("\n")[1]])
|
||||||
|
subprocess.Popen(["omxplayer", "-b", url.split("\n")[0]])
|
||||||
|
|
||||||
killvideo = request.args.get("killvideo")
|
killvideo = request.args.get("killvideo")
|
||||||
|
|
||||||
if killvideo is not None and killvideo in ["1", "true", "yes"]:
|
if killvideo is not None and killvideo in ["1", "true", "yes"]:
|
||||||
subprocess.Popen(["pkill", "-f", "omxplayer"])
|
subprocess.Popen(["pkill", "-f", "omxplayer"])
|
||||||
|
|
||||||
return render_template("index.html", sounds=sounds)
|
if request.path == "/edit":
|
||||||
|
edit = True
|
||||||
|
else:
|
||||||
|
edit = False
|
||||||
|
|
||||||
|
return render_template("index.html", sounds=sounds, tags=tags, edit=edit)
|
||||||
|
|
||||||
|
@app.route("/edit/<sound>", methods=["GET", "POST"])
|
||||||
|
def edit(sound):
|
||||||
|
tags = queryDB("""\
|
||||||
|
SELECT
|
||||||
|
tag.id,
|
||||||
|
tag.name,
|
||||||
|
checked.id IS NOT NULL AS checked
|
||||||
|
FROM
|
||||||
|
tag
|
||||||
|
LEFT OUTER JOIN (
|
||||||
|
SELECT
|
||||||
|
tag.id
|
||||||
|
FROM
|
||||||
|
tag
|
||||||
|
JOIN
|
||||||
|
button_tags
|
||||||
|
ON
|
||||||
|
fk_tag = tag.id
|
||||||
|
WHERE fk_button = (
|
||||||
|
SELECT
|
||||||
|
button.id
|
||||||
|
FROM
|
||||||
|
button
|
||||||
|
WHERE
|
||||||
|
button.file = ?
|
||||||
|
)
|
||||||
|
) AS checked
|
||||||
|
ON tag.id = checked.id""", (sound,))
|
||||||
|
|
||||||
|
if request.method == "POST":
|
||||||
|
if not request.form.get("cancel"):
|
||||||
|
buttonId = queryDB("SELECT id FROM button WHERE file = ?", (sound,), True)
|
||||||
|
|
||||||
|
if buttonId is None:
|
||||||
|
queryDB("INSERT INTO button (file) VALUES (?)", (sound,))
|
||||||
|
getDB().commit()
|
||||||
|
|
||||||
|
buttonId = queryDB("SELECT id FROM button WHERE file = ?", (sound,), True)
|
||||||
|
|
||||||
|
for tag in tags:
|
||||||
|
checkbox = 1 if request.form.get("cb-{}".format(tag["name"])) else 0
|
||||||
|
|
||||||
|
if tag["checked"] < checkbox:
|
||||||
|
queryDB("INSERT OR REPLACE INTO button_tags (fk_button, fk_tag) VALUES (?, ?)", (buttonId[0], tag["id"]))
|
||||||
|
getDB().commit()
|
||||||
|
elif tag["checked"] > checkbox:
|
||||||
|
queryDB("DELETE FROM button_tags WHERE fk_button = ? AND fk_tag = ?", (buttonId[0], tag["id"]))
|
||||||
|
getDB().commit()
|
||||||
|
|
||||||
|
return redirect("/edit")
|
||||||
|
|
||||||
|
return render_template("edit.html", sound=sound, tags=tags)
|
||||||
|
|
||||||
@app.route("/sounds/<path:name>")
|
@app.route("/sounds/<path:name>")
|
||||||
def sounds(name):
|
def sounds(name):
|
||||||
|
|||||||
@ -20,8 +20,7 @@ h1 {
|
|||||||
}
|
}
|
||||||
|
|
||||||
form {
|
form {
|
||||||
text-align: center;
|
padding: 5px;
|
||||||
display: inline;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
input, button {
|
input, button {
|
||||||
@ -49,13 +48,70 @@ button:active {
|
|||||||
color: #000;
|
color: #000;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.editform {
|
||||||
|
display: table;
|
||||||
|
margin-left: auto;
|
||||||
|
margin-right: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.editform input {
|
||||||
|
margin: 5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.editform input[type="text"] {
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
|
||||||
|
.editform input[type="checkbox"] {
|
||||||
|
display: block;
|
||||||
|
float: left;
|
||||||
|
height: 15px;
|
||||||
|
margin: 5px;
|
||||||
|
width: 15px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.editform .tags li {
|
||||||
|
clear: left;
|
||||||
|
display: block;
|
||||||
|
line-height: 20px;
|
||||||
|
vertical-align: middle;
|
||||||
|
}
|
||||||
|
|
||||||
|
.editform .tags li span {
|
||||||
|
line-height: 25px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tags {
|
||||||
|
padding-left: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tags li {
|
||||||
|
display: inline-block;
|
||||||
|
list-style-type: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tags li a {
|
||||||
|
background-color: #333;
|
||||||
|
border: 1px solid #999;
|
||||||
|
border-radius: 2px;
|
||||||
|
color: #999;
|
||||||
|
font-size: 0.9em;
|
||||||
|
margin: 5px;
|
||||||
|
padding: 2px 5px 2px 5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tags li a:hover {
|
||||||
|
background-color: #eee;
|
||||||
|
color: #333;
|
||||||
|
}
|
||||||
|
|
||||||
label {
|
label {
|
||||||
vertical-align: middle;
|
vertical-align: middle;
|
||||||
}
|
}
|
||||||
|
|
||||||
nav {
|
nav {
|
||||||
text-align: center;
|
|
||||||
margin: 1em;
|
margin: 1em;
|
||||||
|
text-align: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
nav a {
|
nav a {
|
||||||
@ -91,8 +147,26 @@ section {
|
|||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
nav .extra {
|
||||||
|
position: absolute;
|
||||||
|
padding: 1em;
|
||||||
|
top: 1em;
|
||||||
|
right: 1em;
|
||||||
|
}
|
||||||
|
|
||||||
|
nav .extra > a {
|
||||||
|
background-color: #2ecc40;
|
||||||
|
border: 1px solid #000;
|
||||||
|
border-radius: 5px;
|
||||||
|
color: #333;
|
||||||
|
display: inline-block;
|
||||||
|
padding: 1em;
|
||||||
|
}
|
||||||
|
|
||||||
.sound {
|
.sound {
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
|
border: 1px solid transparent;
|
||||||
|
margin: 5px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.sound a {
|
.sound a {
|
||||||
@ -113,6 +187,8 @@ section {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.sound a:hover {
|
.sound a:hover {
|
||||||
|
background-color: #ff4136;
|
||||||
|
box-shadow: inset 5px 5px 5px rgba(0, 0, 0, 0.6);
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -127,6 +203,16 @@ section {
|
|||||||
box-shadow: inset 5px 5px 5px rgba(0, 0, 0, 0.6) !important;
|
box-shadow: inset 5px 5px 5px rgba(0, 0, 0, 0.6) !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.edit {
|
||||||
|
border: 1px dashed #eee;
|
||||||
|
box-sizing: border-box;
|
||||||
|
margin: 5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sound.edit a:hover {
|
||||||
|
box-shadow: inset -5px -5px 5px rgba(0, 0, 0, 0.6);
|
||||||
|
}
|
||||||
|
|
||||||
.unselectable {
|
.unselectable {
|
||||||
-webkit-touch-callout: none;
|
-webkit-touch-callout: none;
|
||||||
-webkit-user-select: none;
|
-webkit-user-select: none;
|
||||||
@ -139,4 +225,3 @@ section {
|
|||||||
#local-mode-button .local {
|
#local-mode-button .local {
|
||||||
color: red;
|
color: red;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -18,10 +18,13 @@ function sleep(ms) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
ready(function() {
|
ready(function() {
|
||||||
var sections = document.querySelectorAll("section");
|
hideSections();
|
||||||
var nav = document.querySelectorAll("nav a");
|
|
||||||
|
|
||||||
sections[0].style.display = "block";
|
var sections = document.querySelectorAll("section");
|
||||||
|
var nav = document.querySelectorAll("nav > a");
|
||||||
|
|
||||||
|
if (sections.length > 0)
|
||||||
|
sections[0].style.display = "block";
|
||||||
|
|
||||||
nav.forEach(function(item) {
|
nav.forEach(function(item) {
|
||||||
item.onclick = function(e) {
|
item.onclick = function(e) {
|
||||||
@ -39,21 +42,8 @@ ready(function() {
|
|||||||
var searchfield = document.querySelector("#search");
|
var searchfield = document.querySelector("#search");
|
||||||
searchfield.focus();
|
searchfield.focus();
|
||||||
|
|
||||||
searchfield.addEventListener("keyup", function() {
|
searchFilter(searchfield, ".sound", "inline-block");
|
||||||
var buttons = document.querySelectorAll(".sound");
|
searchFilter(searchfield, ".tag", "block");
|
||||||
|
|
||||||
buttons.forEach(function(item) {
|
|
||||||
item.style.display = "inline-block";
|
|
||||||
});
|
|
||||||
|
|
||||||
buttons.forEach(function(item) {
|
|
||||||
var name = item.firstChild.innerHTML;
|
|
||||||
|
|
||||||
if (name.toLowerCase().indexOf(searchfield.value.toLowerCase()) === -1) {
|
|
||||||
item.style.display = "none";
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
var reset = document.querySelector("#reset");
|
var reset = document.querySelector("#reset");
|
||||||
var sounds_nav = document.querySelector("#sounds-nav");
|
var sounds_nav = document.querySelector("#sounds-nav");
|
||||||
@ -64,6 +54,26 @@ ready(function() {
|
|||||||
addKeyListeners();
|
addKeyListeners();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
function searchFilter(searchfield, itemSelector, unHideStyle) {
|
||||||
|
if (searchfield !== null) {
|
||||||
|
searchfield.addEventListener("keyup", function() {
|
||||||
|
var items = document.querySelectorAll(itemSelector);
|
||||||
|
|
||||||
|
items.forEach(function(item) {
|
||||||
|
item.style.display = unHideStyle;
|
||||||
|
});
|
||||||
|
|
||||||
|
items.forEach(function(item) {
|
||||||
|
var name = item.firstChild.innerHTML;
|
||||||
|
|
||||||
|
if (name.toLowerCase().indexOf(searchfield.value.toLowerCase()) === -1) {
|
||||||
|
item.style.display = "none";
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Adds all necessary KeyListeners to document
|
* Adds all necessary KeyListeners to document
|
||||||
*/
|
*/
|
||||||
|
|||||||
56
templates/base.html
Normal file
56
templates/base.html
Normal file
@ -0,0 +1,56 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8" />
|
||||||
|
<meta name="viewport" content="width=device-width,initial-scale=1">
|
||||||
|
<title>WIAI Soundboard</title>
|
||||||
|
<link rel="stylesheet" href="/static/main.css" />
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
{% block navigation %}
|
||||||
|
<nav>
|
||||||
|
<a href="#sounds">Sounds</a>
|
||||||
|
<a href="#youtube">YouTube</a>
|
||||||
|
<a href="#voice">Voice</a>
|
||||||
|
{% if not edit %}
|
||||||
|
<div class="extra"><a href="/edit">Edit</a></div>
|
||||||
|
{% else %}
|
||||||
|
<div class="extra"><a href="/">Done</a></div>
|
||||||
|
{% endif %}
|
||||||
|
</nav>
|
||||||
|
{% endblock %}
|
||||||
|
<content>
|
||||||
|
{% block content %}
|
||||||
|
<section id="sounds">
|
||||||
|
<div><input type="text" id="search" /><span id="reset">⨯</span></div>
|
||||||
|
{% for sound in sounds %}
|
||||||
|
<div class="sound{{ ' edit' if edit }}"><a href="{{ '/' + ('edit' if edit else 'play') + '/' + sound | urlencode }}">{{ sound.split('.', 1)[0] }}</a></div>
|
||||||
|
{% endfor %}
|
||||||
|
</section>
|
||||||
|
<section id="youtube">
|
||||||
|
<form action="/" method="GET">
|
||||||
|
<label for="video">YouTube URL</label>
|
||||||
|
<input type="text" name="video" />
|
||||||
|
<input type="submit" value="Play" />
|
||||||
|
</form>
|
||||||
|
<form action="/" method="GET">
|
||||||
|
<input type="hidden" name="killvideo" value="yes" />
|
||||||
|
<input type="submit" name="submit" value="Terminate videos" />
|
||||||
|
</form>
|
||||||
|
</section>
|
||||||
|
<section id="voice">
|
||||||
|
<form action="/say/" method="POST">
|
||||||
|
<input type="text" name="text" />
|
||||||
|
<input type="text" name="voice" placeholder="DE" />
|
||||||
|
<label for="speed">Speed</label>
|
||||||
|
<input type="range" name="speed" min="80" max="450" step="10" value="160" id="speed" />
|
||||||
|
<label for="pitch">Pitch</label>
|
||||||
|
<input type="range" name="pitch" min="0" max="99" step="1" value="50" id="pitch" />
|
||||||
|
<input type="submit" value="Voice" />
|
||||||
|
</form>
|
||||||
|
</section>
|
||||||
|
{% endblock %}
|
||||||
|
</content>
|
||||||
|
<script src="/static/main.js"></script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
18
templates/edit.html
Normal file
18
templates/edit.html
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
{% extends "base.html" %}
|
||||||
|
{% block navigation %}
|
||||||
|
{% endblock %}
|
||||||
|
{% block content %}
|
||||||
|
<div class="editform">
|
||||||
|
<h1>Edit {{ sound }}</h1>
|
||||||
|
<form action="{{ request.path }}" method="POST">
|
||||||
|
<input type="text" id="search" placeholder="Search Tag" />
|
||||||
|
<ul class="tags">
|
||||||
|
{% for tag in tags %}
|
||||||
|
<li class="tag"><span>{{ tag.name }}</span><input type="checkbox" name="cb-{{ tag.name }}" {{ 'checked="true" '|safe if tag.checked }}/></li>
|
||||||
|
{% endfor %}
|
||||||
|
</ul>
|
||||||
|
<input type="submit" name="cancel" value="Cancel" />
|
||||||
|
<input type="submit" name="submit" value="Save" />
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
{% endblock %}
|
||||||
@ -33,6 +33,11 @@
|
|||||||
<a href="#sounds" id="sounds-nav">Sounds</a>
|
<a href="#sounds" id="sounds-nav">Sounds</a>
|
||||||
<a href="#streams">Streams</a>
|
<a href="#streams">Streams</a>
|
||||||
<a href="#voice">Voices</a>
|
<a href="#voice">Voices</a>
|
||||||
|
{% if not edit %}
|
||||||
|
<div class="extra"><a href="/edit">Edit</a></div>
|
||||||
|
{% else %}
|
||||||
|
<div class="extra"><a href="/">Done</a></div>
|
||||||
|
{% endif %}
|
||||||
</nav>
|
</nav>
|
||||||
<content>
|
<content>
|
||||||
<section id="sounds" style="display:none;">
|
<section id="sounds" style="display:none;">
|
||||||
@ -40,10 +45,15 @@
|
|||||||
<a onclick="toggleLocalMode();" id="local-mode-button"><button title="play sounds on server">[S]</button></a>
|
<a onclick="toggleLocalMode();" id="local-mode-button"><button title="play sounds on server">[S]</button></a>
|
||||||
<input type="text" id="search" autofocus/>
|
<input type="text" id="search" autofocus/>
|
||||||
<div id="reset">✕</div>
|
<div id="reset">✕</div>
|
||||||
</div>
|
<ul class="tags">
|
||||||
|
{% for tag in tags %}
|
||||||
|
<li><a href="#">{{ tag.name }}</a></li>
|
||||||
|
{% endfor %}
|
||||||
|
</ul>
|
||||||
|
</div><!--
|
||||||
{% for sound in sounds %}
|
{% for sound in sounds %}
|
||||||
<div class="sound"><a title="{{ sound.split('.', 1)[0] }}" tabindex="0" class="unselectable" onclick="playSound('{{ sound | urlencode }}');">{{ sound.split('.', 1)[0] }}</a></div>
|
--><div class="sound{{ ' edit' if edit }}"><a title="{{ sound.split('.', 1)[0] }}" tabindex="0" class="unselectable" {{ 'href=/edit/' + sound | urlencode if edit else 'onclick=playSound(\'' + sound | urlencode + '\');' }}>{{ sound.split('.', 1)[0] }}</a></div><!--
|
||||||
{% endfor %}
|
{% endfor %}-->
|
||||||
</section>
|
</section>
|
||||||
<section id="streams" style="display:none;">
|
<section id="streams" style="display:none;">
|
||||||
<div>
|
<div>
|
||||||
@ -71,4 +81,3 @@
|
|||||||
<script src="/static/main.js"></script>
|
<script src="/static/main.js"></script>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user