Integrated (not yet complete) new webclient and qr code
This commit is contained in:
parent
d466e383d4
commit
f987b47d8d
27 changed files with 867 additions and 24170 deletions
|
@ -22,6 +22,7 @@ minio = "^7.1.12"
|
||||||
colored = "^1.4.4"
|
colored = "^1.4.4"
|
||||||
mutagen = "^1.46.0"
|
mutagen = "^1.46.0"
|
||||||
aiocmd = "^0.1.5"
|
aiocmd = "^0.1.5"
|
||||||
|
pyqrcode = "^1.2.1"
|
||||||
|
|
||||||
|
|
||||||
[build-system]
|
[build-system]
|
||||||
|
|
|
@ -5,6 +5,7 @@ import logging
|
||||||
from argparse import ArgumentParser
|
from argparse import ArgumentParser
|
||||||
|
|
||||||
import socketio
|
import socketio
|
||||||
|
import pyqrcode
|
||||||
|
|
||||||
from .sources import Source, configure_sources
|
from .sources import Source, configure_sources
|
||||||
from .entry import Entry
|
from .entry import Entry
|
||||||
|
@ -16,11 +17,7 @@ sources: dict[str, Source] = {}
|
||||||
|
|
||||||
|
|
||||||
currentLock = asyncio.Semaphore(0)
|
currentLock = asyncio.Semaphore(0)
|
||||||
state = {
|
state = {"current": None, "queue": [], "room": None, "server": ""}
|
||||||
"current": None,
|
|
||||||
"queue": [],
|
|
||||||
"room": None,
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
@sio.on("skip")
|
@sio.on("skip")
|
||||||
|
@ -31,7 +28,7 @@ async def handle_skip():
|
||||||
|
|
||||||
@sio.on("state")
|
@sio.on("state")
|
||||||
async def handle_state(data):
|
async def handle_state(data):
|
||||||
state["queue"] = [Entry(**entry) for entry in data]
|
state["queue"] = [Entry(**entry) for entry in data["queue"]]
|
||||||
|
|
||||||
|
|
||||||
@sio.on("connect")
|
@sio.on("connect")
|
||||||
|
@ -75,7 +72,10 @@ async def handle_play(data):
|
||||||
async def handle_register(data):
|
async def handle_register(data):
|
||||||
if data["success"]:
|
if data["success"]:
|
||||||
logging.info("Registered")
|
logging.info("Registered")
|
||||||
print(f"Join using code: {data['room']}")
|
print(f"Join here: {state['server']}/{data['room']}")
|
||||||
|
print(
|
||||||
|
pyqrcode.create(f"{state['server']}/{data['room']}").terminal(quiet_zone=1)
|
||||||
|
)
|
||||||
state["room"] = data["room"]
|
state["room"] = data["room"]
|
||||||
await sio.emit("sources", {"sources": list(sources.keys())})
|
await sio.emit("sources", {"sources": list(sources.keys())})
|
||||||
if state["current"] is None:
|
if state["current"] is None:
|
||||||
|
@ -120,6 +120,8 @@ async def aiomain():
|
||||||
if args.room:
|
if args.room:
|
||||||
state["room"] = args.room
|
state["room"] = args.room
|
||||||
|
|
||||||
|
state["server"] = args.server
|
||||||
|
|
||||||
await sio.connect(args.server)
|
await sio.connect(args.server)
|
||||||
await sio.wait()
|
await sio.wait()
|
||||||
|
|
||||||
|
|
|
@ -18,7 +18,16 @@ from .sources import Source, available_sources
|
||||||
sio = socketio.AsyncServer(cors_allowed_origins="*", logger=True, engineio_logger=False)
|
sio = socketio.AsyncServer(cors_allowed_origins="*", logger=True, engineio_logger=False)
|
||||||
app = web.Application()
|
app = web.Application()
|
||||||
sio.attach(app)
|
sio.attach(app)
|
||||||
app.add_routes([web.static("/", "syng/static/")])
|
|
||||||
|
|
||||||
|
async def root_handler(request):
|
||||||
|
return web.FileResponse("syng/static/index.html")
|
||||||
|
|
||||||
|
|
||||||
|
app.add_routes([web.static("/assets/", "syng/static/assets/")])
|
||||||
|
app.router.add_route("*", "/", root_handler)
|
||||||
|
app.router.add_route("*", "/{room}", root_handler)
|
||||||
|
app.router.add_route("*", "/{room}/", root_handler)
|
||||||
|
|
||||||
logging.basicConfig(level=logging.INFO)
|
logging.basicConfig(level=logging.INFO)
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
@ -66,6 +75,7 @@ class State:
|
||||||
sources: dict[str, Source]
|
sources: dict[str, Source]
|
||||||
sources_prio: list[str]
|
sources_prio: list[str]
|
||||||
queue: Queue
|
queue: Queue
|
||||||
|
recent: list[Entry]
|
||||||
sid: str
|
sid: str
|
||||||
|
|
||||||
|
|
||||||
|
@ -75,7 +85,14 @@ async def handle_state(sid, data: dict[str, Any] = {}):
|
||||||
room = session["room"]
|
room = session["room"]
|
||||||
state = clients[room]
|
state = clients[room]
|
||||||
|
|
||||||
await sio.emit("state", state.queue.to_dict(), room=sid)
|
await sio.emit(
|
||||||
|
"state",
|
||||||
|
{
|
||||||
|
"queue": state.queue.to_dict(),
|
||||||
|
"recent": [entry.to_dict() for entry in state.recent],
|
||||||
|
},
|
||||||
|
room=sid,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
@sio.on("append")
|
@sio.on("append")
|
||||||
|
@ -87,7 +104,14 @@ async def handle_append(sid, data: dict[str, Any]):
|
||||||
source_obj = state.sources[data["source"]]
|
source_obj = state.sources[data["source"]]
|
||||||
entry = await Entry.from_source(data["performer"], data["id"], source_obj)
|
entry = await Entry.from_source(data["performer"], data["id"], source_obj)
|
||||||
state.queue.append(entry)
|
state.queue.append(entry)
|
||||||
await sio.emit("state", state.queue.to_dict(), room=room)
|
await sio.emit(
|
||||||
|
"state",
|
||||||
|
{
|
||||||
|
"queue": state.queue.to_dict(),
|
||||||
|
"recent": [entry.to_dict() for entry in state.recent],
|
||||||
|
},
|
||||||
|
room=sid,
|
||||||
|
)
|
||||||
|
|
||||||
await sio.emit(
|
await sio.emit(
|
||||||
"buffer",
|
"buffer",
|
||||||
|
@ -107,7 +131,14 @@ async def handle_meta_info(sid, data):
|
||||||
lambda item: item.update(**data["meta"]),
|
lambda item: item.update(**data["meta"]),
|
||||||
)
|
)
|
||||||
|
|
||||||
await sio.emit("state", state.queue.to_dict(), room=room)
|
await sio.emit(
|
||||||
|
"state",
|
||||||
|
{
|
||||||
|
"queue": state.queue.to_dict(),
|
||||||
|
"recent": [entry.to_dict() for entry in state.recent],
|
||||||
|
},
|
||||||
|
room=sid,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
@sio.on("get-first")
|
@sio.on("get-first")
|
||||||
|
@ -127,8 +158,16 @@ async def handle_pop_then_get_next(sid, data={}):
|
||||||
room = session["room"]
|
room = session["room"]
|
||||||
state = clients[room]
|
state = clients[room]
|
||||||
|
|
||||||
await state.queue.popleft()
|
old_entry = await state.queue.popleft()
|
||||||
await sio.emit("state", state.queue.to_dict(), room=room)
|
state.recent.append(old_entry)
|
||||||
|
await sio.emit(
|
||||||
|
"state",
|
||||||
|
{
|
||||||
|
"queue": state.queue.to_dict(),
|
||||||
|
"recent": [entry.to_dict() for entry in state.recent],
|
||||||
|
},
|
||||||
|
room=sid,
|
||||||
|
)
|
||||||
current = await state.queue.peek()
|
current = await state.queue.peek()
|
||||||
|
|
||||||
await sio.emit("play", current.to_dict(), room=sid)
|
await sio.emit("play", current.to_dict(), room=sid)
|
||||||
|
@ -164,7 +203,7 @@ async def handle_register_client(sid, data: dict[str, Any]):
|
||||||
else:
|
else:
|
||||||
logger.info("Registerd new client %s", room)
|
logger.info("Registerd new client %s", room)
|
||||||
initial_entries = [Entry(**entry) for entry in data["queue"]]
|
initial_entries = [Entry(**entry) for entry in data["queue"]]
|
||||||
clients[room] = State(data["secret"], {}, [], Queue(initial_entries), sid)
|
clients[room] = State(data["secret"], {}, [], Queue(initial_entries), [], sid)
|
||||||
sio.enter_room(sid, room)
|
sio.enter_room(sid, room)
|
||||||
await sio.emit("client-registered", {"success": True, "room": room}, room=sid)
|
await sio.emit("client-registered", {"success": True, "room": room}, room=sid)
|
||||||
|
|
||||||
|
@ -224,7 +263,14 @@ async def handle_register_web(sid, data):
|
||||||
session["room"] = data["room"]
|
session["room"] = data["room"]
|
||||||
sio.enter_room(sid, session["room"])
|
sio.enter_room(sid, session["room"])
|
||||||
state = clients[session["room"]]
|
state = clients[session["room"]]
|
||||||
await sio.emit("state", state.queue.to_dict(), room=sid)
|
await sio.emit(
|
||||||
|
"state",
|
||||||
|
{
|
||||||
|
"queue": state.queue.to_dict(),
|
||||||
|
"recent": [entry.to_dict() for entry in state.recent],
|
||||||
|
},
|
||||||
|
room=sid,
|
||||||
|
)
|
||||||
return True
|
return True
|
||||||
else:
|
else:
|
||||||
return False
|
return False
|
||||||
|
|
787
syng/static/assets/index.533d2b73.js
Normal file
787
syng/static/assets/index.533d2b73.js
Normal file
File diff suppressed because one or more lines are too long
1
syng/static/assets/index.d713a926.css
Normal file
1
syng/static/assets/index.d713a926.css
Normal file
File diff suppressed because one or more lines are too long
|
@ -1,219 +0,0 @@
|
||||||
|
|
||||||
li {
|
|
||||||
opacity: 0.9;
|
|
||||||
margin-left: 0.2em;
|
|
||||||
margin-right: 0.2em;
|
|
||||||
}
|
|
||||||
|
|
||||||
.row {
|
|
||||||
margin:0 !important;
|
|
||||||
max-width: 100% !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
body {
|
|
||||||
height:100vh;
|
|
||||||
}
|
|
||||||
|
|
||||||
.page {
|
|
||||||
height: 100vh;
|
|
||||||
position: relative;
|
|
||||||
}
|
|
||||||
|
|
||||||
.header {
|
|
||||||
background-color: #008000;
|
|
||||||
font-weight: bold;
|
|
||||||
color: #ffffff;
|
|
||||||
display: block;
|
|
||||||
padding: 1.25rem;
|
|
||||||
font-size: .75rem;
|
|
||||||
text-align: center;
|
|
||||||
line-height: 1;
|
|
||||||
margin-bottom: .5rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.splitter {
|
|
||||||
display: flex;
|
|
||||||
height: 100vh;
|
|
||||||
}
|
|
||||||
|
|
||||||
.comp-column {
|
|
||||||
max-height: 100vh;
|
|
||||||
flex:1;
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
}
|
|
||||||
|
|
||||||
#left-side {
|
|
||||||
margin: 0.2em 0.1em 0.2em 0.2em;
|
|
||||||
}
|
|
||||||
#middle {
|
|
||||||
margin: 0.2em 0.1em 0.1em 0.2em;
|
|
||||||
}
|
|
||||||
#right-side {
|
|
||||||
margin: 0.2em 0.2em 0.1em 0.2em;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
#main-content {
|
|
||||||
height: 100vh;
|
|
||||||
max-height: 100vh;
|
|
||||||
max-width: 100vw;
|
|
||||||
}
|
|
||||||
|
|
||||||
.tabs-container {
|
|
||||||
flex: 1;
|
|
||||||
position: relative;
|
|
||||||
overflow: auto;
|
|
||||||
}
|
|
||||||
|
|
||||||
.tabs-panel {
|
|
||||||
height: 100%;
|
|
||||||
}
|
|
||||||
|
|
||||||
.vsplit {
|
|
||||||
display: flex;
|
|
||||||
height: 100%;
|
|
||||||
flex-direction: column;
|
|
||||||
}
|
|
||||||
|
|
||||||
.results {
|
|
||||||
flex: 1;
|
|
||||||
overflow-y: auto;
|
|
||||||
}
|
|
||||||
|
|
||||||
.singer {
|
|
||||||
font-size: smaller;
|
|
||||||
font-style: italic;
|
|
||||||
}
|
|
||||||
|
|
||||||
.title {
|
|
||||||
font-weight: bold;
|
|
||||||
}
|
|
||||||
|
|
||||||
.input-group {
|
|
||||||
margin-bottom: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.menu li {
|
|
||||||
padding:0.4em;
|
|
||||||
}
|
|
||||||
|
|
||||||
.small-12 {
|
|
||||||
padding: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
#search-results div {
|
|
||||||
vertical-align: middle;
|
|
||||||
height: 100%
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
.tabs-panel {
|
|
||||||
padding: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.menu li:nth-child(odd) {
|
|
||||||
background-color: #e6e6e6;
|
|
||||||
}
|
|
||||||
|
|
||||||
.menu li:nth-child(even) {
|
|
||||||
background-color: #f6f6f6;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
#current::before, #large-current::before{
|
|
||||||
content: "Now Playing";
|
|
||||||
text-align: center;
|
|
||||||
font-weight: bold;
|
|
||||||
}
|
|
||||||
|
|
||||||
.eta {
|
|
||||||
float: right;
|
|
||||||
}
|
|
||||||
|
|
||||||
.eta::before {
|
|
||||||
content: "in ";
|
|
||||||
}
|
|
||||||
|
|
||||||
.eta::after {
|
|
||||||
content: " min";
|
|
||||||
}
|
|
||||||
|
|
||||||
.artist::after{
|
|
||||||
content: " - ";
|
|
||||||
}
|
|
||||||
|
|
||||||
.album::before {
|
|
||||||
content: " [";
|
|
||||||
}
|
|
||||||
.album::after {
|
|
||||||
content: "]";
|
|
||||||
}
|
|
||||||
|
|
||||||
#large-current, #current, .button, button:focus {
|
|
||||||
background-color: #008000;
|
|
||||||
}
|
|
||||||
|
|
||||||
.button:hover, button:hover{
|
|
||||||
background-color: #3b3b3b;
|
|
||||||
}
|
|
||||||
|
|
||||||
body {
|
|
||||||
background: url(../syng.png) fixed;
|
|
||||||
background-color: #8a8a8a;
|
|
||||||
background-size: auto 50%;
|
|
||||||
background-repeat: no-repeat;
|
|
||||||
background-position: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
.warning {
|
|
||||||
padding: 10px;
|
|
||||||
background-color: #a53b2a;
|
|
||||||
}
|
|
||||||
|
|
||||||
.tabs {
|
|
||||||
border: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
.tabs-title > a{
|
|
||||||
color: #008000;
|
|
||||||
}
|
|
||||||
|
|
||||||
.tabs-title > a:hover {
|
|
||||||
background-color: #444444;
|
|
||||||
color: #FFFFFF;
|
|
||||||
}
|
|
||||||
|
|
||||||
.tabs-title > a:focus, .tabs-title > a[aria-selected="true"]{
|
|
||||||
color: #FFFFFF;
|
|
||||||
font-weight: bold;
|
|
||||||
background-color: #008000;
|
|
||||||
}
|
|
||||||
|
|
||||||
div.tabs {
|
|
||||||
display: flex;
|
|
||||||
}
|
|
||||||
|
|
||||||
.fright {
|
|
||||||
float: right;
|
|
||||||
}
|
|
||||||
|
|
||||||
div.tabs .tabs-title {
|
|
||||||
flex-grow: 1;
|
|
||||||
text-align: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
.tabs {
|
|
||||||
margin-bottom: 0.1em;
|
|
||||||
background-color: #3b3b3b;
|
|
||||||
}
|
|
||||||
|
|
||||||
.bulk-upload-label {
|
|
||||||
margin-bottom: 0px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.button-group .button {
|
|
||||||
/* margin-left: 0.5em; */
|
|
||||||
}
|
|
2337
syng/static/css/font-awesome.css
vendored
2337
syng/static/css/font-awesome.css
vendored
File diff suppressed because it is too large
Load diff
6455
syng/static/css/foundation.css
vendored
6455
syng/static/css/foundation.css
vendored
File diff suppressed because it is too large
Load diff
1
syng/static/css/foundation.min.css
vendored
1
syng/static/css/foundation.min.css
vendored
File diff suppressed because one or more lines are too long
BIN
syng/static/favicon.ico
Normal file
BIN
syng/static/favicon.ico
Normal file
Binary file not shown.
After Width: | Height: | Size: 4.2 KiB |
Binary file not shown.
Binary file not shown.
File diff suppressed because it is too large
Load diff
Before Width: | Height: | Size: 434 KiB |
Binary file not shown.
Binary file not shown.
Binary file not shown.
|
@ -1,527 +1,15 @@
|
||||||
<!DOCTYPE html>
|
<!DOCTYPE html>
|
||||||
<html lang="en">
|
<html lang="en">
|
||||||
<head>
|
<head>
|
||||||
<meta charset="UTF-8">
|
<meta charset="UTF-8">
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0"/>
|
<link rel="icon" href="/favicon.ico">
|
||||||
<title>syng 2.0.0</title>
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
<link rel="stylesheet" href="css/foundation.min.css"/>
|
<title>Vite App</title>
|
||||||
<link rel="stylesheet" href="css/app.css"/>
|
<script type="module" crossorigin src="/assets/index.533d2b73.js"></script>
|
||||||
<link rel="stylesheet" href="css/font-awesome.css"/>
|
<link rel="stylesheet" href="/assets/index.d713a926.css">
|
||||||
<script src="jquery-3.2.1.min.js"></script>
|
</head>
|
||||||
<script src="jquery.serialize-object.min.js"></script>
|
<body>
|
||||||
<script src="js/vendor/foundation.js"></script>
|
<div id="app"></div>
|
||||||
<script src="js/vendor/rivets.bundled.min.js"></script>
|
|
||||||
<script src="https://cdn.socket.io/4.5.3/socket.io.min.js"></script>
|
|
||||||
</head>
|
|
||||||
<body>
|
|
||||||
<div class="page">
|
|
||||||
<div class="row" id="main-content">
|
|
||||||
<div class="hide-for-large">
|
|
||||||
<div class="splitter">
|
|
||||||
<div class="comp-column">
|
|
||||||
<div data-tabs class="tabs" id="main-tab">
|
|
||||||
<div class="tabs-title is-active"><a href="#simplesearch"><i class="fa fa-search fa-3x"></i></a></div>
|
|
||||||
<div class="tabs-title"><a href="#queue-list" id="queue-tab"><i class="fa fa-list fa-3x"></i></a></div>
|
|
||||||
<div class="tabs-title"><a href="#recent-list"><i class="fa fa-history fa-3x"></i></a></div>
|
|
||||||
</div>
|
|
||||||
<div class="tabs-container" data-tabs-content="main-tab">
|
|
||||||
<div class="tabs-panel is-active" id="simplesearch">
|
|
||||||
<div class="vsplit">
|
|
||||||
|
|
||||||
|
</body>
|
||||||
<form id="simple-search-form" class="form">
|
|
||||||
<div class="input-group">
|
|
||||||
<input id="search-query" class="input-group-field" type="search" placeholder="Search term or YouTube link (https://www.youtube.com/watch?v=...)" name="q" />
|
|
||||||
<div class="input-group-button">
|
|
||||||
<button class="button" type="submit"><i class="fa fa-search"></i></button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</form>
|
|
||||||
|
|
||||||
<div id="search-results" class="results">
|
|
||||||
<ul class="vertical menu" >
|
|
||||||
<li rv-each-result="results.items">
|
|
||||||
<div class="row">
|
|
||||||
<div class="columns small-10">
|
|
||||||
<span class="artist">{ result.artist }</span>
|
|
||||||
<span class="title">{ result.title }</span>
|
|
||||||
<span class="album">{ result.album }</span>
|
|
||||||
</div>
|
|
||||||
<div class="columns small-2">
|
|
||||||
<button class="button alert fright" rv-if="result.type | eq 'youtube'" rv-on-click="results.openNewTab">
|
|
||||||
<i class="fa fa-play"></i>
|
|
||||||
</button>
|
|
||||||
<button class="button fright" rv-on-click="results.addToQueue">
|
|
||||||
<i class="fa fa-plus"></i>
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="tabs-panel" id="queue-list">
|
|
||||||
<div class="vsplit">
|
|
||||||
<div id="queue-list-wrapper" class="results">
|
|
||||||
<ul id="queue" class="vertical menu">
|
|
||||||
<li rv-if="queue.current" id="current">
|
|
||||||
{% if admin == True -%}
|
|
||||||
<div class="row">
|
|
||||||
<div class="columns small-9">
|
|
||||||
{%- endif %}
|
|
||||||
<div class="row">
|
|
||||||
<span class="artist">{ queue.current.artist }</span>
|
|
||||||
<span class="title">{ queue.current.title }</span>
|
|
||||||
<span class="album">{ queue.current.album }</span>
|
|
||||||
</div>
|
|
||||||
<div class="row">
|
|
||||||
<span class="singer">{ queue.current.performer }</span>
|
|
||||||
</div>
|
|
||||||
{% if admin == True -%}
|
|
||||||
</div>
|
|
||||||
<div class="columns small-3">
|
|
||||||
<div class="button-group">
|
|
||||||
<button class="button alert fright" rv-on-click="queue.abort"><i class="fa fa-step-forward"></i></button>
|
|
||||||
<button class="button alert fright" rv-on-click="queue.kill"><i class="fa fa-times"></i></button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
{%- endif %}
|
|
||||||
|
|
||||||
</li>
|
|
||||||
<li rv-each-entry="queue.queue">
|
|
||||||
{% if admin == True -%}
|
|
||||||
<div class="row">
|
|
||||||
<div class="columns small-9">
|
|
||||||
{%- endif %}
|
|
||||||
<div class="row">
|
|
||||||
<span class="artist">{ entry.artist }</span>
|
|
||||||
<span class="title">{ entry.title }</span>
|
|
||||||
<span class="album">{ entry.album }</span>
|
|
||||||
</div>
|
|
||||||
<div class="row">
|
|
||||||
<span class="singer">{ entry.performer }</span>
|
|
||||||
<span class="eta">{ entry.etamin }</span>
|
|
||||||
</div>
|
|
||||||
{% if admin == True -%}
|
|
||||||
</div>
|
|
||||||
<div class="columns small-3">
|
|
||||||
<button class="button alert fright" rv-on-click="queue.deleteFromQueue"><i class="fa fa-minus"></i></button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
{%- endif %}
|
|
||||||
</li>
|
|
||||||
{% if admin == True %}
|
|
||||||
<li>
|
|
||||||
<div class="row">
|
|
||||||
<div class="columns small-12">
|
|
||||||
<a class="button" download="queue.json" rv-href="queue.data">Save</a>
|
|
||||||
<label for="small-bulk-upload" class="button bulk-upload-label">Bulk Append</label>
|
|
||||||
<input type="file" id="small-bulk-upload" rv-on-change="queue.bulk_append" class="show-for-sr">
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</li>
|
|
||||||
{% endif %}
|
|
||||||
</ul>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="tabs-panel" id="recent-list">
|
|
||||||
<div class="vsplit">
|
|
||||||
<div id="recent-list-wrapper" class="results">
|
|
||||||
<ol id="last10" class="vertical menu">
|
|
||||||
<li rv-each-entry="queue.last10">
|
|
||||||
<div class="row">
|
|
||||||
<span class="artist">{ entry.artist }</span>
|
|
||||||
<span class="title">{ entry.title }</span>
|
|
||||||
<span class="album">{ entry.album }</span>
|
|
||||||
</div>
|
|
||||||
<div class="row">
|
|
||||||
<span class="singer">{ entry.performer }</span>
|
|
||||||
</div>
|
|
||||||
</li>
|
|
||||||
</ol>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="show-for-large">
|
|
||||||
<div class="splitter">
|
|
||||||
<div class="comp-column" id="left-side">
|
|
||||||
<div class="header">Search</div>
|
|
||||||
<div id="large-simplesearch">
|
|
||||||
<div class="vsplit">
|
|
||||||
<form id="large-simple-search-form" class="form">
|
|
||||||
<div class="input-group">
|
|
||||||
<input id="large-search-query" class="input-group-field" type="search" placeholder="Search term or YouTube link (https://www.youtube.com/watch?v=...)" name="q" />
|
|
||||||
<div class="input-group-button">
|
|
||||||
<button class="button" type="submit"><i class="fa fa-search"></i></button>
|
|
||||||
</div>
|
|
||||||
<div class="input-group-button" style="padding-left: 0.25em">
|
|
||||||
<button class="button" type="button" data-open="settings-large"><i class="fa fa-sliders"></i></button>
|
|
||||||
<div class="reveal" id="settings-large" data-reveal>
|
|
||||||
<div class="grid-x">
|
|
||||||
<div class="cell small-4 medium-2">
|
|
||||||
<div class="switch">
|
|
||||||
<input class="switch-input" name="append-karaoke" id="large-append-karaoke" type="checkbox" checked="checked"> <label class="switch-paddle" for="large-append-karaoke"></label>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="cell small-8 medium-4">
|
|
||||||
<label class="text-left middle" for="large-append-karaoke">Add "Karaoke" to Search</label>
|
|
||||||
</div>
|
|
||||||
<div class="cell small-4 medium-2">
|
|
||||||
<div class="switch">
|
|
||||||
<input class="switch-input" name="youtube" id="large-youtube" type="checkbox" checked="checked"> <label class="switch-paddle" for="large-youtube"></label>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="cell small-8 medium-4">
|
|
||||||
<label class="text-left middle" for="large-youtube">Search YouTube</label>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- {%- for channel in channels %} -->
|
|
||||||
<!-- <div class="cell small-4 medium-2"> -->
|
|
||||||
<!-- <div class="switch"> -->
|
|
||||||
<!-- <input class="switch-input" name="{{channel}}" id="large-channel-{{loop.index}}" type="checkbox" checked="checked"> <label class="switch-paddle" for="large-channel-{{loop.index}}"></label> -->
|
|
||||||
<!-- </div> -->
|
|
||||||
<!-- </div> -->
|
|
||||||
<!-- <div class="cell small-8 medium-4"> -->
|
|
||||||
<!-- <label class="text-left middle" for="large-channel-{{loop.index}}">Search {{ channel }}</label> -->
|
|
||||||
<!-- </div> -->
|
|
||||||
<!-- {%- endfor %} -->
|
|
||||||
|
|
||||||
<button class="close-button" data-close aria-label="Close modal" type="button">
|
|
||||||
<span aria-hidden="true">×</span>
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</form>
|
|
||||||
<div id="large-search-results" class="results">
|
|
||||||
<ul class="vertical menu">
|
|
||||||
<li rv-each-result="results.items">
|
|
||||||
<div class="row">
|
|
||||||
<div class="columns small-10">
|
|
||||||
<span class="artist">{ result.artist }</span>
|
|
||||||
<span class="title">{ result.title }</span>
|
|
||||||
<span class="album">{ result.album }</span>
|
|
||||||
</div>
|
|
||||||
<div class="columns small-2">
|
|
||||||
<button class="button alert fright" rv-if="result.source | eq 'youtube'" rv-on-click="results.openNewTab">
|
|
||||||
<i class="fa fa-play"></i>
|
|
||||||
</button>
|
|
||||||
<button class="button fright" rv-on-click="results.addToQueue">
|
|
||||||
<i class="fa fa-plus"></i>
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="comp-column" id="middle">
|
|
||||||
<div class="header">Queue</div>
|
|
||||||
<div id="large-queue-list">
|
|
||||||
<div class="vsplit">
|
|
||||||
<div id="large-queue-list-wrapper" class="results">
|
|
||||||
<ul id="large-queue" class="vertical menu">
|
|
||||||
<li rv-if="queue.current" id="large-current">
|
|
||||||
{% if admin == True -%}
|
|
||||||
<div class="row">
|
|
||||||
<div class="columns small-9">
|
|
||||||
{%- endif %}
|
|
||||||
<div class="row">
|
|
||||||
<span class="artist">{ queue.current.artist }</span>
|
|
||||||
<span class="title">{ queue.current.title }</span>
|
|
||||||
<span class="album">{ queue.current.album }</span>
|
|
||||||
</div>
|
|
||||||
<div class="row">
|
|
||||||
<span class="singer">{ queue.current.performer }</span>
|
|
||||||
</div>
|
|
||||||
{% if admin == True -%}
|
|
||||||
</div>
|
|
||||||
<div class="columns small-3">
|
|
||||||
<div class="button-group">
|
|
||||||
<button class="button alert fright" rv-on-click="queue.kill">
|
|
||||||
<i class="fa fa-times"></i>
|
|
||||||
</button>
|
|
||||||
<button class="button alert fright" rv-on-click="queue.abort">
|
|
||||||
<i class="fa fa-step-forward"></i>
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
{%- endif %}
|
|
||||||
|
|
||||||
</li>
|
|
||||||
<li {% if admin == True %} draggable="true" {% endif %} rv-each-entry="queue.queue">
|
|
||||||
{% if admin == True -%}
|
|
||||||
<div class="row">
|
|
||||||
<div class="columns small-9">
|
|
||||||
{%- endif %}
|
|
||||||
<div class="row">
|
|
||||||
<span class="artist">{ entry.artist }</span>
|
|
||||||
<span class="title">{ entry.title }</span>
|
|
||||||
<span class="album">{ entry.album }</span>
|
|
||||||
</div>
|
|
||||||
<div class="row">
|
|
||||||
<span class="singer">{ entry.performer }</span>
|
|
||||||
<span class="eta">{ entry.etamin }</span>
|
|
||||||
</div>
|
|
||||||
{% if admin == True -%}
|
|
||||||
</div>
|
|
||||||
<div class="columns small-3">
|
|
||||||
<div class="button-group">
|
|
||||||
<button class="button alert fright" rv-on-click="queue.moveup">
|
|
||||||
<i class="fa fa-arrow-circle-up"></i>
|
|
||||||
</button>
|
|
||||||
<button class="button alert fright" rv-on-click="queue.deleteFromQueue">
|
|
||||||
<i class="fa fa-minus"></i>
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
{%- endif %}
|
|
||||||
</li>
|
|
||||||
{% if admin == True %}
|
|
||||||
<li>
|
|
||||||
<div class="row">
|
|
||||||
<div class="columns small-6">
|
|
||||||
<a class="button" download="queue.json" rv-href="queue.data">Save</a>
|
|
||||||
<label for="large-bulk-upload" class="button bulk-upload-label">Bulk Append</label>
|
|
||||||
<input type="file" id="large-bulk-upload" rv-on-change="queue.bulk_append" class="show-for-sr">
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</li>
|
|
||||||
{% endif %}
|
|
||||||
</ul>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="comp-column" id="right-side">
|
|
||||||
<div class="header">Recent</div>
|
|
||||||
<div id="large-recent-list">
|
|
||||||
<div class="vsplit">
|
|
||||||
<div id="large-recent-list-wrapper" class="results">
|
|
||||||
<ol id="large-last10" class="vertical menu">
|
|
||||||
<li rv-each-entry="queue.last10">
|
|
||||||
<div class="row">
|
|
||||||
<span class="artist">{ entry.artist }</span>
|
|
||||||
<span class="title">{ entry.title }</span>
|
|
||||||
<span class="album">{ entry.album }</span>
|
|
||||||
</div>
|
|
||||||
<div class="row">
|
|
||||||
<span class="singer">{ entry.performer }</span>
|
|
||||||
</div>
|
|
||||||
</li>
|
|
||||||
</ol>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</body>
|
|
||||||
<script>
|
|
||||||
const socket = io();
|
|
||||||
|
|
||||||
function ytselect(event) {
|
|
||||||
console.log(event.target.value);
|
|
||||||
var ytwarning = document.getElementsById("ytwarning");
|
|
||||||
if(event.target.value === "no_channel") {
|
|
||||||
ytwarning.style.display = "list-item";
|
|
||||||
}
|
|
||||||
ytwarning.style.display = "none";
|
|
||||||
}
|
|
||||||
|
|
||||||
function getQuery() {
|
|
||||||
var query = {};
|
|
||||||
$('input').each(function() {
|
|
||||||
if(this.type == "checkbox") {
|
|
||||||
query[this.name] = this.checked;
|
|
||||||
} else {
|
|
||||||
if(this.disabled == false) {
|
|
||||||
query[this.name] = this.value;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
return query;
|
|
||||||
}
|
|
||||||
|
|
||||||
$(document).foundation();
|
|
||||||
results = {
|
|
||||||
items : [],
|
|
||||||
openNewTab : function(event, item) {
|
|
||||||
event.preventDefault();
|
|
||||||
window.open(item.result.id, '_blank');
|
|
||||||
},
|
|
||||||
addToQueue : function(event, item) {
|
|
||||||
event.preventDefault();
|
|
||||||
var singer = prompt("Performer:");
|
|
||||||
if(singer !== null) {
|
|
||||||
// $.post('queue', JSON.stringify({'id': item.result.id, 'singer': singer, 'type': item.result.type, 'location': item.result.location}), addQueueCallback, 'json');
|
|
||||||
socket.emit("append", {"performer": singer, "source": item.result.source, "id": item.result.id});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
queue = {
|
|
||||||
current: null,
|
|
||||||
last10: [],
|
|
||||||
queue: [],
|
|
||||||
moveup: function (event, item) {
|
|
||||||
event.preventDefault();
|
|
||||||
if (item.index === 0) {
|
|
||||||
alert("You cannot switch places with the currently playing song.");
|
|
||||||
} else {
|
|
||||||
$.ajax({
|
|
||||||
method: 'patch',
|
|
||||||
url: 'queue',
|
|
||||||
data: JSON.stringify({'action': 'move', 'param': {'src': item.index, 'dst': item.index - 1}}),
|
|
||||||
success: updateHandler,
|
|
||||||
dataType: 'json'
|
|
||||||
})
|
|
||||||
}
|
|
||||||
},
|
|
||||||
deleteFromQueue: function (event, item) {
|
|
||||||
event.preventDefault();
|
|
||||||
$.ajax({
|
|
||||||
method: 'patch',
|
|
||||||
url: 'queue',
|
|
||||||
data: JSON.stringify({'action': 'delete', 'param': {'index': item.index} }),
|
|
||||||
success: updateHandler,
|
|
||||||
dataType: 'json'
|
|
||||||
});
|
|
||||||
},
|
|
||||||
bulk_append: function (event) {
|
|
||||||
var file = event.target.files[0];
|
|
||||||
var reader = new FileReader();
|
|
||||||
reader.onload = (function(theFile) {
|
|
||||||
return function(e) {
|
|
||||||
new_queue = JSON.parse(e.target.result);
|
|
||||||
$.post('queue', JSON.stringify(new_queue.queue), addQueueCallback, 'json');
|
|
||||||
}
|
|
||||||
})(file);
|
|
||||||
reader.readAsText(file);
|
|
||||||
},
|
|
||||||
data: "",
|
|
||||||
kill: function(event) {
|
|
||||||
event.preventDefault();
|
|
||||||
areyousure = confirm("This will kill the players process. Did you try skipping the song?");
|
|
||||||
if(areyousure === true) {
|
|
||||||
$.ajax({
|
|
||||||
method: 'patch',
|
|
||||||
url: 'queue',
|
|
||||||
data: JSON.stringify({'action': 'kill'}),
|
|
||||||
success: updateHandler,
|
|
||||||
dataType: 'json'
|
|
||||||
});
|
|
||||||
}
|
|
||||||
},
|
|
||||||
abort: function(event) {
|
|
||||||
event.preventDefault();
|
|
||||||
$.ajax({
|
|
||||||
method: 'patch',
|
|
||||||
url: 'queue',
|
|
||||||
data: JSON.stringify({'action': 'skip'}),
|
|
||||||
success: updateHandler,
|
|
||||||
dataType: 'json'
|
|
||||||
});
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
rivets.formatters.eq = function(value, arg) {
|
|
||||||
return value === arg;
|
|
||||||
};
|
|
||||||
|
|
||||||
if(Foundation.MediaQuery.atLeast("large")) {
|
|
||||||
$('#large-search-query').focus();
|
|
||||||
$('#search-query').get(0).disabled = true;
|
|
||||||
$('#large-search-query').get(0).disabled = false;
|
|
||||||
rivets.bind(document.getElementById("large-search-results"), {results: results});
|
|
||||||
rivets.bind(document.getElementById("large-queue"), {queue: queue});
|
|
||||||
rivets.bind(document.getElementById("large-last10"), {queue: queue});
|
|
||||||
} else {
|
|
||||||
$('#search-query').focus();
|
|
||||||
$('#large-search-query').get(0).disabled = true;
|
|
||||||
$('#search-query').get(0).disabled = false;
|
|
||||||
rivets.bind(document.getElementById("search-results"), {results: results});
|
|
||||||
rivets.bind(document.getElementById("queue"), {queue: queue});
|
|
||||||
rivets.bind(document.getElementById("last10"), {queue: queue});
|
|
||||||
}
|
|
||||||
|
|
||||||
function addQueueCallback(data) {
|
|
||||||
$("#queue-tab").click();
|
|
||||||
updateHandler(data)
|
|
||||||
}
|
|
||||||
|
|
||||||
function updateHandler(data) {
|
|
||||||
queue.queue = data['queue'];
|
|
||||||
queue.current = data['current'];
|
|
||||||
queue.last10 = data['last10'];
|
|
||||||
queue.data = "data:application/json," + (JSON.stringify({
|
|
||||||
'queue': [queue.current].concat(queue.queue)
|
|
||||||
}))
|
|
||||||
}
|
|
||||||
|
|
||||||
$("#large-simple-search-form, #simple-search-form").on("submit", function (event) {
|
|
||||||
event.preventDefault();
|
|
||||||
if ($(this).serializeObject().q.startsWith("https://www.youtube.com/")) {
|
|
||||||
results.addToQueue(event, {result: {id: $(this).serializeObject().q, type: "youtube"}});
|
|
||||||
} else {
|
|
||||||
results.items = [{title: "Loading...", artist: "Loading...", album: "Loading...", type:"temp"}];
|
|
||||||
query = {"query": $("#large-search-query").val()}
|
|
||||||
socket.emit("search", query);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
$(window).on('changed.zf.mediaquery', function() {
|
|
||||||
if(Foundation.MediaQuery.atLeast("large")) {
|
|
||||||
$('#large-search-query').focus();
|
|
||||||
$('#search-query').get(0).disabled = true;
|
|
||||||
$('#large-search-query').get(0).disabled = false;
|
|
||||||
rivets.bind(document.getElementById("large-search-results"), {results: results});
|
|
||||||
rivets.bind(document.getElementById("large-queue"), {queue: queue});
|
|
||||||
rivets.bind(document.getElementById("large-last10"), {queue: queue});
|
|
||||||
} else {
|
|
||||||
$('#search-query').focus();
|
|
||||||
$('#large-search-query').get(0).disabled = true;
|
|
||||||
$('#search-query').get(0).disabled = false;
|
|
||||||
rivets.bind(document.getElementById("search-results"), {results: results});
|
|
||||||
rivets.bind(document.getElementById("queue"), {queue: queue});
|
|
||||||
rivets.bind(document.getElementById("last10"), {queue: queue});
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
socket.on("connect", () => {
|
|
||||||
socket.emit("register-web", {"room": "ABCD"})
|
|
||||||
})
|
|
||||||
|
|
||||||
socket.on("state", (data) => {
|
|
||||||
data['queue'] = data.slice(1);
|
|
||||||
data['current'] = data[0];
|
|
||||||
data['last10'] = [];
|
|
||||||
updateHandler(data);
|
|
||||||
});
|
|
||||||
|
|
||||||
socket.on("search-results", (data) => {
|
|
||||||
results.items = data;
|
|
||||||
});
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/* $.get("queue", updateHandler);
|
|
||||||
setInterval(function() {
|
|
||||||
$.get("queue", updateHandler)
|
|
||||||
}, 2000); */
|
|
||||||
</script>
|
|
||||||
</html>
|
</html>
|
||||||
|
|
4
syng/static/jquery-3.2.1.min.js
vendored
4
syng/static/jquery-3.2.1.min.js
vendored
File diff suppressed because one or more lines are too long
8
syng/static/jquery.serialize-object.min.js
vendored
8
syng/static/jquery.serialize-object.min.js
vendored
|
@ -1,8 +0,0 @@
|
||||||
/**
|
|
||||||
* jQuery serializeObject
|
|
||||||
* @copyright 2014, macek <paulmacek@gmail.com>
|
|
||||||
* @link https://github.com/macek/jquery-serialize-object
|
|
||||||
* @license BSD
|
|
||||||
* @version 2.5.0
|
|
||||||
*/
|
|
||||||
!function(e,i){if("function"==typeof define&&define.amd)define(["exports","jquery"],function(e,r){return i(e,r)});else if("undefined"!=typeof exports){var r=require("jquery");i(exports,r)}else i(e,e.jQuery||e.Zepto||e.ender||e.$)}(this,function(e,i){function r(e,r){function n(e,i,r){return e[i]=r,e}function a(e,i){for(var r,a=e.match(t.key);void 0!==(r=a.pop());)if(t.push.test(r)){var u=s(e.replace(/\[\]$/,""));i=n([],u,i)}else t.fixed.test(r)?i=n([],r,i):t.named.test(r)&&(i=n({},r,i));return i}function s(e){return void 0===h[e]&&(h[e]=0),h[e]++}function u(e){switch(i('[name="'+e.name+'"]',r).attr("type")){case"checkbox":return"on"===e.value?!0:e.value;default:return e.value}}function f(i){if(!t.validate.test(i.name))return this;var r=a(i.name,u(i));return l=e.extend(!0,l,r),this}function d(i){if(!e.isArray(i))throw new Error("formSerializer.addPairs expects an Array");for(var r=0,t=i.length;t>r;r++)this.addPair(i[r]);return this}function o(){return l}function c(){return JSON.stringify(o())}var l={},h={};this.addPair=f,this.addPairs=d,this.serialize=o,this.serializeJSON=c}var t={validate:/^[a-z_][a-z0-9_]*(?:\[(?:\d*|[a-z0-9_]+)\])*$/i,key:/[a-z0-9_]+|(?=\[\])/gi,push:/^$/,fixed:/^\d+$/,named:/^[a-z0-9_]+$/i};return r.patterns=t,r.serializeObject=function(){return new r(i,this).addPairs(this.serializeArray()).serialize()},r.serializeJSON=function(){return new r(i,this).addPairs(this.serializeArray()).serializeJSON()},"undefined"!=typeof i.fn&&(i.fn.serializeObject=r.serializeObject,i.fn.serializeJSON=r.serializeJSON),e.FormSerializer=r,r});
|
|
|
@ -1 +0,0 @@
|
||||||
$(document).foundation()
|
|
531
syng/static/js/vendor/foundation.js
vendored
531
syng/static/js/vendor/foundation.js
vendored
File diff suppressed because one or more lines are too long
1
syng/static/js/vendor/foundation.min.js
vendored
1
syng/static/js/vendor/foundation.min.js
vendored
File diff suppressed because one or more lines are too long
10881
syng/static/js/vendor/jquery.js
vendored
10881
syng/static/js/vendor/jquery.js
vendored
File diff suppressed because it is too large
Load diff
6
syng/static/js/vendor/rivets.bundled.min.js
vendored
6
syng/static/js/vendor/rivets.bundled.min.js
vendored
File diff suppressed because one or more lines are too long
517
syng/static/js/vendor/what-input.js
vendored
517
syng/static/js/vendor/what-input.js
vendored
|
@ -1,517 +0,0 @@
|
||||||
/**
|
|
||||||
* what-input - A global utility for tracking the current input method (mouse, keyboard or touch).
|
|
||||||
* @version v5.2.10
|
|
||||||
* @link https://github.com/ten1seven/what-input
|
|
||||||
* @license MIT
|
|
||||||
*/
|
|
||||||
(function webpackUniversalModuleDefinition(root, factory) {
|
|
||||||
if(typeof exports === 'object' && typeof module === 'object')
|
|
||||||
module.exports = factory();
|
|
||||||
else if(typeof define === 'function' && define.amd)
|
|
||||||
define("whatInput", [], factory);
|
|
||||||
else if(typeof exports === 'object')
|
|
||||||
exports["whatInput"] = factory();
|
|
||||||
else
|
|
||||||
root["whatInput"] = factory();
|
|
||||||
})(this, function() {
|
|
||||||
return /******/ (function(modules) { // webpackBootstrap
|
|
||||||
/******/ // The module cache
|
|
||||||
/******/ var installedModules = {};
|
|
||||||
|
|
||||||
/******/ // The require function
|
|
||||||
/******/ function __webpack_require__(moduleId) {
|
|
||||||
|
|
||||||
/******/ // Check if module is in cache
|
|
||||||
/******/ if(installedModules[moduleId])
|
|
||||||
/******/ return installedModules[moduleId].exports;
|
|
||||||
|
|
||||||
/******/ // Create a new module (and put it into the cache)
|
|
||||||
/******/ var module = installedModules[moduleId] = {
|
|
||||||
/******/ exports: {},
|
|
||||||
/******/ id: moduleId,
|
|
||||||
/******/ loaded: false
|
|
||||||
/******/ };
|
|
||||||
|
|
||||||
/******/ // Execute the module function
|
|
||||||
/******/ modules[moduleId].call(module.exports, module, module.exports, __webpack_require__);
|
|
||||||
|
|
||||||
/******/ // Flag the module as loaded
|
|
||||||
/******/ module.loaded = true;
|
|
||||||
|
|
||||||
/******/ // Return the exports of the module
|
|
||||||
/******/ return module.exports;
|
|
||||||
/******/ }
|
|
||||||
|
|
||||||
|
|
||||||
/******/ // expose the modules object (__webpack_modules__)
|
|
||||||
/******/ __webpack_require__.m = modules;
|
|
||||||
|
|
||||||
/******/ // expose the module cache
|
|
||||||
/******/ __webpack_require__.c = installedModules;
|
|
||||||
|
|
||||||
/******/ // __webpack_public_path__
|
|
||||||
/******/ __webpack_require__.p = "";
|
|
||||||
|
|
||||||
/******/ // Load entry module and return exports
|
|
||||||
/******/ return __webpack_require__(0);
|
|
||||||
/******/ })
|
|
||||||
/************************************************************************/
|
|
||||||
/******/ ([
|
|
||||||
/* 0 */
|
|
||||||
/***/ (function(module, exports) {
|
|
||||||
|
|
||||||
'use strict';
|
|
||||||
|
|
||||||
module.exports = function () {
|
|
||||||
/*
|
|
||||||
* bail out if there is no document or window
|
|
||||||
* (i.e. in a node/non-DOM environment)
|
|
||||||
*
|
|
||||||
* Return a stubbed API instead
|
|
||||||
*/
|
|
||||||
if (typeof document === 'undefined' || typeof window === 'undefined') {
|
|
||||||
return {
|
|
||||||
// always return "initial" because no interaction will ever be detected
|
|
||||||
ask: function ask() {
|
|
||||||
return 'initial';
|
|
||||||
},
|
|
||||||
|
|
||||||
// always return null
|
|
||||||
element: function element() {
|
|
||||||
return null;
|
|
||||||
},
|
|
||||||
|
|
||||||
// no-op
|
|
||||||
ignoreKeys: function ignoreKeys() {},
|
|
||||||
|
|
||||||
// no-op
|
|
||||||
specificKeys: function specificKeys() {},
|
|
||||||
|
|
||||||
// no-op
|
|
||||||
registerOnChange: function registerOnChange() {},
|
|
||||||
|
|
||||||
// no-op
|
|
||||||
unRegisterOnChange: function unRegisterOnChange() {}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
* variables
|
|
||||||
*/
|
|
||||||
|
|
||||||
// cache document.documentElement
|
|
||||||
var docElem = document.documentElement;
|
|
||||||
|
|
||||||
// currently focused dom element
|
|
||||||
var currentElement = null;
|
|
||||||
|
|
||||||
// last used input type
|
|
||||||
var currentInput = 'initial';
|
|
||||||
|
|
||||||
// last used input intent
|
|
||||||
var currentIntent = currentInput;
|
|
||||||
|
|
||||||
// UNIX timestamp of current event
|
|
||||||
var currentTimestamp = Date.now();
|
|
||||||
|
|
||||||
// check for a `data-whatpersist` attribute on either the `html` or `body` elements, defaults to `true`
|
|
||||||
var shouldPersist = 'false';
|
|
||||||
|
|
||||||
// form input types
|
|
||||||
var formInputs = ['button', 'input', 'select', 'textarea'];
|
|
||||||
|
|
||||||
// empty array for holding callback functions
|
|
||||||
var functionList = [];
|
|
||||||
|
|
||||||
// list of modifier keys commonly used with the mouse and
|
|
||||||
// can be safely ignored to prevent false keyboard detection
|
|
||||||
var ignoreMap = [16, // shift
|
|
||||||
17, // control
|
|
||||||
18, // alt
|
|
||||||
91, // Windows key / left Apple cmd
|
|
||||||
93 // Windows menu / right Apple cmd
|
|
||||||
];
|
|
||||||
|
|
||||||
var specificMap = [];
|
|
||||||
|
|
||||||
// mapping of events to input types
|
|
||||||
var inputMap = {
|
|
||||||
keydown: 'keyboard',
|
|
||||||
keyup: 'keyboard',
|
|
||||||
mousedown: 'mouse',
|
|
||||||
mousemove: 'mouse',
|
|
||||||
MSPointerDown: 'pointer',
|
|
||||||
MSPointerMove: 'pointer',
|
|
||||||
pointerdown: 'pointer',
|
|
||||||
pointermove: 'pointer',
|
|
||||||
touchstart: 'touch',
|
|
||||||
touchend: 'touch'
|
|
||||||
|
|
||||||
// boolean: true if the page is being scrolled
|
|
||||||
};var isScrolling = false;
|
|
||||||
|
|
||||||
// store current mouse position
|
|
||||||
var mousePos = {
|
|
||||||
x: null,
|
|
||||||
y: null
|
|
||||||
|
|
||||||
// map of IE 10 pointer events
|
|
||||||
};var pointerMap = {
|
|
||||||
2: 'touch',
|
|
||||||
3: 'touch', // treat pen like touch
|
|
||||||
4: 'mouse'
|
|
||||||
|
|
||||||
// check support for passive event listeners
|
|
||||||
};var supportsPassive = false;
|
|
||||||
|
|
||||||
try {
|
|
||||||
var opts = Object.defineProperty({}, 'passive', {
|
|
||||||
get: function get() {
|
|
||||||
supportsPassive = true;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
window.addEventListener('test', null, opts);
|
|
||||||
} catch (e) {}
|
|
||||||
// fail silently
|
|
||||||
|
|
||||||
|
|
||||||
/*
|
|
||||||
* set up
|
|
||||||
*/
|
|
||||||
|
|
||||||
var setUp = function setUp() {
|
|
||||||
// add correct mouse wheel event mapping to `inputMap`
|
|
||||||
inputMap[detectWheel()] = 'mouse';
|
|
||||||
|
|
||||||
addListeners();
|
|
||||||
};
|
|
||||||
|
|
||||||
/*
|
|
||||||
* events
|
|
||||||
*/
|
|
||||||
|
|
||||||
var addListeners = function addListeners() {
|
|
||||||
// `pointermove`, `MSPointerMove`, `mousemove` and mouse wheel event binding
|
|
||||||
// can only demonstrate potential, but not actual, interaction
|
|
||||||
// and are treated separately
|
|
||||||
var options = supportsPassive ? { passive: true } : false;
|
|
||||||
|
|
||||||
document.addEventListener('DOMContentLoaded', setPersist);
|
|
||||||
|
|
||||||
// pointer events (mouse, pen, touch)
|
|
||||||
if (window.PointerEvent) {
|
|
||||||
window.addEventListener('pointerdown', setInput);
|
|
||||||
window.addEventListener('pointermove', setIntent);
|
|
||||||
} else if (window.MSPointerEvent) {
|
|
||||||
window.addEventListener('MSPointerDown', setInput);
|
|
||||||
window.addEventListener('MSPointerMove', setIntent);
|
|
||||||
} else {
|
|
||||||
// mouse events
|
|
||||||
window.addEventListener('mousedown', setInput);
|
|
||||||
window.addEventListener('mousemove', setIntent);
|
|
||||||
|
|
||||||
// touch events
|
|
||||||
if ('ontouchstart' in window) {
|
|
||||||
window.addEventListener('touchstart', setInput, options);
|
|
||||||
window.addEventListener('touchend', setInput);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// mouse wheel
|
|
||||||
window.addEventListener(detectWheel(), setIntent, options);
|
|
||||||
|
|
||||||
// keyboard events
|
|
||||||
window.addEventListener('keydown', setInput);
|
|
||||||
window.addEventListener('keyup', setInput);
|
|
||||||
|
|
||||||
// focus events
|
|
||||||
window.addEventListener('focusin', setElement);
|
|
||||||
window.addEventListener('focusout', clearElement);
|
|
||||||
};
|
|
||||||
|
|
||||||
// checks if input persistence should happen and
|
|
||||||
// get saved state from session storage if true (defaults to `false`)
|
|
||||||
var setPersist = function setPersist() {
|
|
||||||
shouldPersist = !(docElem.getAttribute('data-whatpersist') || document.body.getAttribute('data-whatpersist') === 'false');
|
|
||||||
|
|
||||||
if (shouldPersist) {
|
|
||||||
// check for session variables and use if available
|
|
||||||
try {
|
|
||||||
if (window.sessionStorage.getItem('what-input')) {
|
|
||||||
currentInput = window.sessionStorage.getItem('what-input');
|
|
||||||
}
|
|
||||||
|
|
||||||
if (window.sessionStorage.getItem('what-intent')) {
|
|
||||||
currentIntent = window.sessionStorage.getItem('what-intent');
|
|
||||||
}
|
|
||||||
} catch (e) {
|
|
||||||
// fail silently
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// always run these so at least `initial` state is set
|
|
||||||
doUpdate('input');
|
|
||||||
doUpdate('intent');
|
|
||||||
};
|
|
||||||
|
|
||||||
// checks conditions before updating new input
|
|
||||||
var setInput = function setInput(event) {
|
|
||||||
var eventKey = event.which;
|
|
||||||
var value = inputMap[event.type];
|
|
||||||
|
|
||||||
if (value === 'pointer') {
|
|
||||||
value = pointerType(event);
|
|
||||||
}
|
|
||||||
|
|
||||||
var ignoreMatch = !specificMap.length && ignoreMap.indexOf(eventKey) === -1;
|
|
||||||
|
|
||||||
var specificMatch = specificMap.length && specificMap.indexOf(eventKey) !== -1;
|
|
||||||
|
|
||||||
var shouldUpdate = value === 'keyboard' && eventKey && (ignoreMatch || specificMatch) || value === 'mouse' || value === 'touch';
|
|
||||||
|
|
||||||
// prevent touch detection from being overridden by event execution order
|
|
||||||
if (validateTouch(value)) {
|
|
||||||
shouldUpdate = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (shouldUpdate && currentInput !== value) {
|
|
||||||
currentInput = value;
|
|
||||||
|
|
||||||
persistInput('input', currentInput);
|
|
||||||
doUpdate('input');
|
|
||||||
}
|
|
||||||
|
|
||||||
if (shouldUpdate && currentIntent !== value) {
|
|
||||||
// preserve intent for keyboard interaction with form fields
|
|
||||||
var activeElem = document.activeElement;
|
|
||||||
var notFormInput = activeElem && activeElem.nodeName && (formInputs.indexOf(activeElem.nodeName.toLowerCase()) === -1 || activeElem.nodeName.toLowerCase() === 'button' && !checkClosest(activeElem, 'form'));
|
|
||||||
|
|
||||||
if (notFormInput) {
|
|
||||||
currentIntent = value;
|
|
||||||
|
|
||||||
persistInput('intent', currentIntent);
|
|
||||||
doUpdate('intent');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
// updates the doc and `inputTypes` array with new input
|
|
||||||
var doUpdate = function doUpdate(which) {
|
|
||||||
docElem.setAttribute('data-what' + which, which === 'input' ? currentInput : currentIntent);
|
|
||||||
|
|
||||||
fireFunctions(which);
|
|
||||||
};
|
|
||||||
|
|
||||||
// updates input intent for `mousemove` and `pointermove`
|
|
||||||
var setIntent = function setIntent(event) {
|
|
||||||
var value = inputMap[event.type];
|
|
||||||
|
|
||||||
if (value === 'pointer') {
|
|
||||||
value = pointerType(event);
|
|
||||||
}
|
|
||||||
|
|
||||||
// test to see if `mousemove` happened relative to the screen to detect scrolling versus mousemove
|
|
||||||
detectScrolling(event);
|
|
||||||
|
|
||||||
// only execute if scrolling isn't happening
|
|
||||||
if ((!isScrolling && !validateTouch(value) || isScrolling && event.type === 'wheel' || event.type === 'mousewheel' || event.type === 'DOMMouseScroll') && currentIntent !== value) {
|
|
||||||
currentIntent = value;
|
|
||||||
|
|
||||||
persistInput('intent', currentIntent);
|
|
||||||
doUpdate('intent');
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
var setElement = function setElement(event) {
|
|
||||||
if (!event.target.nodeName) {
|
|
||||||
// If nodeName is undefined, clear the element
|
|
||||||
// This can happen if click inside an <svg> element.
|
|
||||||
clearElement();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
currentElement = event.target.nodeName.toLowerCase();
|
|
||||||
docElem.setAttribute('data-whatelement', currentElement);
|
|
||||||
|
|
||||||
if (event.target.classList && event.target.classList.length) {
|
|
||||||
docElem.setAttribute('data-whatclasses', event.target.classList.toString().replace(' ', ','));
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
var clearElement = function clearElement() {
|
|
||||||
currentElement = null;
|
|
||||||
|
|
||||||
docElem.removeAttribute('data-whatelement');
|
|
||||||
docElem.removeAttribute('data-whatclasses');
|
|
||||||
};
|
|
||||||
|
|
||||||
var persistInput = function persistInput(which, value) {
|
|
||||||
if (shouldPersist) {
|
|
||||||
try {
|
|
||||||
window.sessionStorage.setItem('what-' + which, value);
|
|
||||||
} catch (e) {
|
|
||||||
// fail silently
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
/*
|
|
||||||
* utilities
|
|
||||||
*/
|
|
||||||
|
|
||||||
var pointerType = function pointerType(event) {
|
|
||||||
if (typeof event.pointerType === 'number') {
|
|
||||||
return pointerMap[event.pointerType];
|
|
||||||
} else {
|
|
||||||
// treat pen like touch
|
|
||||||
return event.pointerType === 'pen' ? 'touch' : event.pointerType;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
// prevent touch detection from being overridden by event execution order
|
|
||||||
var validateTouch = function validateTouch(value) {
|
|
||||||
var timestamp = Date.now();
|
|
||||||
|
|
||||||
var touchIsValid = value === 'mouse' && currentInput === 'touch' && timestamp - currentTimestamp < 200;
|
|
||||||
|
|
||||||
currentTimestamp = timestamp;
|
|
||||||
|
|
||||||
return touchIsValid;
|
|
||||||
};
|
|
||||||
|
|
||||||
// detect version of mouse wheel event to use
|
|
||||||
// via https://developer.mozilla.org/en-US/docs/Web/API/Element/wheel_event
|
|
||||||
var detectWheel = function detectWheel() {
|
|
||||||
var wheelType = null;
|
|
||||||
|
|
||||||
// Modern browsers support "wheel"
|
|
||||||
if ('onwheel' in document.createElement('div')) {
|
|
||||||
wheelType = 'wheel';
|
|
||||||
} else {
|
|
||||||
// Webkit and IE support at least "mousewheel"
|
|
||||||
// or assume that remaining browsers are older Firefox
|
|
||||||
wheelType = document.onmousewheel !== undefined ? 'mousewheel' : 'DOMMouseScroll';
|
|
||||||
}
|
|
||||||
|
|
||||||
return wheelType;
|
|
||||||
};
|
|
||||||
|
|
||||||
// runs callback functions
|
|
||||||
var fireFunctions = function fireFunctions(type) {
|
|
||||||
for (var i = 0, len = functionList.length; i < len; i++) {
|
|
||||||
if (functionList[i].type === type) {
|
|
||||||
functionList[i].fn.call(undefined, type === 'input' ? currentInput : currentIntent);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
// finds matching element in an object
|
|
||||||
var objPos = function objPos(match) {
|
|
||||||
for (var i = 0, len = functionList.length; i < len; i++) {
|
|
||||||
if (functionList[i].fn === match) {
|
|
||||||
return i;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
var detectScrolling = function detectScrolling(event) {
|
|
||||||
if (mousePos.x !== event.screenX || mousePos.y !== event.screenY) {
|
|
||||||
isScrolling = false;
|
|
||||||
|
|
||||||
mousePos.x = event.screenX;
|
|
||||||
mousePos.y = event.screenY;
|
|
||||||
} else {
|
|
||||||
isScrolling = true;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
// manual version of `closest()`
|
|
||||||
var checkClosest = function checkClosest(elem, tag) {
|
|
||||||
var ElementPrototype = window.Element.prototype;
|
|
||||||
|
|
||||||
if (!ElementPrototype.matches) {
|
|
||||||
ElementPrototype.matches = ElementPrototype.msMatchesSelector || ElementPrototype.webkitMatchesSelector;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!ElementPrototype.closest) {
|
|
||||||
do {
|
|
||||||
if (elem.matches(tag)) {
|
|
||||||
return elem;
|
|
||||||
}
|
|
||||||
|
|
||||||
elem = elem.parentElement || elem.parentNode;
|
|
||||||
} while (elem !== null && elem.nodeType === 1);
|
|
||||||
|
|
||||||
return null;
|
|
||||||
} else {
|
|
||||||
return elem.closest(tag);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
/*
|
|
||||||
* init
|
|
||||||
*/
|
|
||||||
|
|
||||||
// don't start script unless browser cuts the mustard
|
|
||||||
// (also passes if polyfills are used)
|
|
||||||
if ('addEventListener' in window && Array.prototype.indexOf) {
|
|
||||||
setUp();
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
* api
|
|
||||||
*/
|
|
||||||
|
|
||||||
return {
|
|
||||||
// returns string: the current input type
|
|
||||||
// opt: 'intent'|'input'
|
|
||||||
// 'input' (default): returns the same value as the `data-whatinput` attribute
|
|
||||||
// 'intent': includes `data-whatintent` value if it's different than `data-whatinput`
|
|
||||||
ask: function ask(opt) {
|
|
||||||
return opt === 'intent' ? currentIntent : currentInput;
|
|
||||||
},
|
|
||||||
|
|
||||||
// returns string: the currently focused element or null
|
|
||||||
element: function element() {
|
|
||||||
return currentElement;
|
|
||||||
},
|
|
||||||
|
|
||||||
// overwrites ignored keys with provided array
|
|
||||||
ignoreKeys: function ignoreKeys(arr) {
|
|
||||||
ignoreMap = arr;
|
|
||||||
},
|
|
||||||
|
|
||||||
// overwrites specific char keys to update on
|
|
||||||
specificKeys: function specificKeys(arr) {
|
|
||||||
specificMap = arr;
|
|
||||||
},
|
|
||||||
|
|
||||||
// attach functions to input and intent "events"
|
|
||||||
// funct: function to fire on change
|
|
||||||
// eventType: 'input'|'intent'
|
|
||||||
registerOnChange: function registerOnChange(fn, eventType) {
|
|
||||||
functionList.push({
|
|
||||||
fn: fn,
|
|
||||||
type: eventType || 'input'
|
|
||||||
});
|
|
||||||
},
|
|
||||||
|
|
||||||
unRegisterOnChange: function unRegisterOnChange(fn) {
|
|
||||||
var position = objPos(fn);
|
|
||||||
|
|
||||||
if (position || position === 0) {
|
|
||||||
functionList.splice(position, 1);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
clearStorage: function clearStorage() {
|
|
||||||
window.sessionStorage.clear();
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}();
|
|
||||||
|
|
||||||
/***/ })
|
|
||||||
/******/ ])
|
|
||||||
});
|
|
||||||
;
|
|
Binary file not shown.
Before Width: | Height: | Size: 43 KiB |
|
@ -19,7 +19,11 @@ async def handle_search_results(data):
|
||||||
@sio.on("state")
|
@sio.on("state")
|
||||||
async def handle_state(data):
|
async def handle_state(data):
|
||||||
print("New Queue")
|
print("New Queue")
|
||||||
for raw_item in data:
|
for raw_item in data["queue"]:
|
||||||
|
item = Entry(**raw_item)
|
||||||
|
print(f"\t{item.performer}: {item.artist} - {item.title} ({item.duration})")
|
||||||
|
print("Recent")
|
||||||
|
for raw_item in data["recent"]:
|
||||||
item = Entry(**raw_item)
|
item = Entry(**raw_item)
|
||||||
print(f"\t{item.performer}: {item.artist} - {item.title} ({item.duration})")
|
print(f"\t{item.performer}: {item.artist} - {item.title} ({item.duration})")
|
||||||
|
|
||||||
|
|
Loading…
Add table
Reference in a new issue