refactor: Separate backend and frontend, avoid template-filling on the fly. Resolve #158.
This commit is contained in:
parent
9a6aaba602
commit
4e541a7622
4
.gitignore
vendored
4
.gitignore
vendored
@ -116,5 +116,7 @@ tmp/
|
|||||||
|
|
||||||
*.db
|
*.db
|
||||||
|
|
||||||
|
templates/*
|
||||||
|
|
||||||
# Pycharm
|
# Pycharm
|
||||||
.idea/
|
.idea/
|
||||||
|
37
interface.py
37
interface.py
@ -207,7 +207,7 @@ def build_tags_color_lookup():
|
|||||||
|
|
||||||
|
|
||||||
def get_all_dirs():
|
def get_all_dirs():
|
||||||
dirs = []
|
dirs = ["."]
|
||||||
paths = var.music_db.query_all_paths()
|
paths = var.music_db.query_all_paths()
|
||||||
for path in paths:
|
for path in paths:
|
||||||
pos = 0
|
pos = 0
|
||||||
@ -225,18 +225,7 @@ def get_all_dirs():
|
|||||||
@web.route("/", methods=['GET'])
|
@web.route("/", methods=['GET'])
|
||||||
@requires_auth
|
@requires_auth
|
||||||
def index():
|
def index():
|
||||||
while var.cache.dir_lock.locked():
|
return open('templates/index.html', "r").read()
|
||||||
time.sleep(0.1)
|
|
||||||
|
|
||||||
tags_color_lookup = build_tags_color_lookup()
|
|
||||||
max_upload_file_size = util.parse_file_size(var.config.get("webinterface", "max_upload_file_size", fallback="30MB"))
|
|
||||||
|
|
||||||
return render_template('index.html',
|
|
||||||
dirs=get_all_dirs(),
|
|
||||||
upload_enabled=var.config.getboolean("webinterface", "upload_enabled", fallback=True),
|
|
||||||
tags_color_lookup=tags_color_lookup,
|
|
||||||
max_upload_file_size=max_upload_file_size
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
@web.route("/playlist", methods=['GET'])
|
@web.route("/playlist", methods=['GET'])
|
||||||
@ -527,6 +516,8 @@ def build_library_query_condition(form):
|
|||||||
|
|
||||||
if form['type'] == 'file':
|
if form['type'] == 'file':
|
||||||
folder = form['dir']
|
folder = form['dir']
|
||||||
|
if folder == ".":
|
||||||
|
folder = ""
|
||||||
if not folder.endswith('/') and folder:
|
if not folder.endswith('/') and folder:
|
||||||
folder += '/'
|
folder += '/'
|
||||||
condition.and_like('path', folder + '%')
|
condition.and_like('path', folder + '%')
|
||||||
@ -552,6 +543,26 @@ def build_library_query_condition(form):
|
|||||||
abort(400)
|
abort(400)
|
||||||
|
|
||||||
|
|
||||||
|
@web.route("/library/info", methods=['GET'])
|
||||||
|
@requires_auth
|
||||||
|
def library_info():
|
||||||
|
global log
|
||||||
|
|
||||||
|
while var.cache.dir_lock.locked():
|
||||||
|
time.sleep(0.1)
|
||||||
|
|
||||||
|
tags = var.music_db.query_all_tags()
|
||||||
|
max_upload_file_size = util.parse_file_size(var.config.get("webinterface", "max_upload_file_size", fallback="30MB"))
|
||||||
|
|
||||||
|
print(get_all_dirs())
|
||||||
|
return jsonify(dict(
|
||||||
|
dirs=get_all_dirs(),
|
||||||
|
upload_enabled=var.config.getboolean("webinterface", "upload_enabled", fallback=True),
|
||||||
|
tags=tags,
|
||||||
|
max_upload_file_size=max_upload_file_size
|
||||||
|
))
|
||||||
|
|
||||||
|
|
||||||
@web.route("/library", methods=['POST'])
|
@web.route("/library", methods=['POST'])
|
||||||
@requires_auth
|
@requires_auth
|
||||||
def library():
|
def library():
|
||||||
|
File diff suppressed because one or more lines are too long
@ -1 +0,0 @@
|
|||||||
<!doctype html><head><meta name="viewport" content="width=device-width,initial-scale=1,shrink-to-fit=no"><meta charset="UTF-8"><title>botamusique web interface</title><link rel="icon" href="static/image/favicon.ico"/><link rel="stylesheet" href="static/css/main.css"></head><body><div class="container" style="max-width: 800px"><div class="col-8" style="margin: auto; padding-top: 50px;"><div class="card"><div class="card-header">Token Required</div><div class="card-body"><h3>Token Required!</h3>You are accessing the web interface of {{ name }}. A token is needed to grant you access.<br/>Please send "{{ command }}" to the bot in mumble to acquire one.<form action="."><div class="form-group mt-3"><label for="token_input">Token</label><div class="input-group"><input type="password" class="form-control btn-space" id="token_input" name="token" placeholder="xxxxxxx"> <button type="submit" class="btn btn-primary">Submit</button></div></div></form></div></div></div></div><script src="static/js/main.js"></script></body>
|
|
@ -7,6 +7,7 @@ import {
|
|||||||
Tooltip,
|
Tooltip,
|
||||||
} from 'bootstrap/js/src/index';
|
} from 'bootstrap/js/src/index';
|
||||||
import {
|
import {
|
||||||
|
getColor,
|
||||||
isOverflown,
|
isOverflown,
|
||||||
setProgressBar,
|
setProgressBar,
|
||||||
secondsToStr,
|
secondsToStr,
|
||||||
@ -416,18 +417,6 @@ function setFilterType(event, type) {
|
|||||||
updateResults();
|
updateResults();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Bind Event
|
|
||||||
$('.filter-tag').click(function (e) {
|
|
||||||
const tag = $(e.currentTarget);
|
|
||||||
if (!tag.hasClass('tag-clicked')) {
|
|
||||||
tag.addClass('tag-clicked');
|
|
||||||
tag.removeClass('tag-unclicked');
|
|
||||||
} else {
|
|
||||||
tag.addClass('tag-unclicked');
|
|
||||||
tag.removeClass('tag-clicked');
|
|
||||||
}
|
|
||||||
updateResults();
|
|
||||||
});
|
|
||||||
|
|
||||||
filter_dir.change(function () {
|
filter_dir.change(function () {
|
||||||
updateResults();
|
updateResults();
|
||||||
@ -501,6 +490,9 @@ function bindLibraryResultEvent() {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const lib_filter_tag_group = $("#filter-tags");
|
||||||
|
const lib_filter_tag_element = $(".filter-tag");
|
||||||
|
|
||||||
const lib_group = $('#library-group');
|
const lib_group = $('#library-group');
|
||||||
const id_element = $('.library-item-id');
|
const id_element = $('.library-item-id');
|
||||||
const title_element = $('.library-item-title');
|
const title_element = $('.library-item-title');
|
||||||
@ -513,6 +505,65 @@ const tag_edit_element = $('.library-item-edit');
|
|||||||
// var notag_element = $(".library-item-notag");
|
// var notag_element = $(".library-item-notag");
|
||||||
// var tag_element = $(".library-item-tag");
|
// var tag_element = $(".library-item-tag");
|
||||||
|
|
||||||
|
function updateLibraryControls(){
|
||||||
|
$.ajax({
|
||||||
|
type: 'GET',
|
||||||
|
url: 'library/info',
|
||||||
|
statusCode: {
|
||||||
|
200: displayLibraryControls,
|
||||||
|
403: function () {
|
||||||
|
location.reload(true);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function displayLibraryControls(data){
|
||||||
|
$("#maxUploadFileSize").val(data.max_upload_file_size);
|
||||||
|
if (data.upload_enabled) {
|
||||||
|
$("#uploadDisabled").val("false");
|
||||||
|
$("#upload").show();
|
||||||
|
} else {
|
||||||
|
$("#uploadDisabled").val("true");
|
||||||
|
$("#upload").hide();
|
||||||
|
}
|
||||||
|
|
||||||
|
let select = $("#filter-dir");
|
||||||
|
let dataList = $("#upload-target-dirs");
|
||||||
|
select.find("option").remove();
|
||||||
|
dataList.find("option").remove();
|
||||||
|
if (data.dirs.length > 0) {
|
||||||
|
console.log(data.dirs);
|
||||||
|
data.dirs.forEach(function (dir) {
|
||||||
|
$("<option value=\"" + dir + "\">" + dir + "</option>").appendTo(select);
|
||||||
|
$("<option value=\"" + dir + "\">").appendTo(dataList);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// ----- Tag filters -----
|
||||||
|
$(".filter-tag").remove();
|
||||||
|
if (data.tags.length > 0) {
|
||||||
|
data.tags.forEach(function (tag) {
|
||||||
|
const tag_copy = lib_filter_tag_element.clone();
|
||||||
|
tag_copy.html(tag);
|
||||||
|
tag_copy.addClass('badge-' + getColor(tag));
|
||||||
|
tag_copy.appendTo(lib_filter_tag_group);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
// Bind Event
|
||||||
|
$('.filter-tag').click(function (e) {
|
||||||
|
const tag = $(e.currentTarget);
|
||||||
|
if (!tag.hasClass('tag-clicked')) {
|
||||||
|
tag.addClass('tag-clicked');
|
||||||
|
tag.removeClass('tag-unclicked');
|
||||||
|
} else {
|
||||||
|
tag.addClass('tag-unclicked');
|
||||||
|
tag.removeClass('tag-clicked');
|
||||||
|
}
|
||||||
|
updateResults();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
function addResultItem(item) {
|
function addResultItem(item) {
|
||||||
id_element.val(item.id);
|
id_element.val(item.id);
|
||||||
title_element.html(item.title);
|
title_element.html(item.title);
|
||||||
@ -609,6 +660,8 @@ function updateResults(dest_page = 1) {
|
|||||||
opacity: 1,
|
opacity: 1,
|
||||||
}, 200);
|
}, 200);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
updateLibraryControls();
|
||||||
}
|
}
|
||||||
|
|
||||||
const download_form = $('#download-form');
|
const download_form = $('#download-form');
|
||||||
@ -1210,10 +1263,10 @@ function playheadDragged(event) {
|
|||||||
// ----- Application -----
|
// ----- Application -----
|
||||||
// -----------------------
|
// -----------------------
|
||||||
|
|
||||||
updateResults();
|
|
||||||
|
|
||||||
document.addEventListener('DOMContentLoaded', () => {
|
document.addEventListener('DOMContentLoaded', () => {
|
||||||
|
updateResults();
|
||||||
updatePlaylist();
|
updatePlaylist();
|
||||||
|
updateLibraryControls();
|
||||||
|
|
||||||
// Check the version of playlist to see if update is needed.
|
// Check the version of playlist to see if update is needed.
|
||||||
setInterval(checkForPlaylistUpdate, 3000);
|
setInterval(checkForPlaylistUpdate, 3000);
|
||||||
|
@ -2,6 +2,42 @@ export function isOverflown(element) {
|
|||||||
return element.scrollHeight > element.clientHeight || element.scrollWidth > element.clientWidth;
|
return element.scrollHeight > element.clientHeight || element.scrollWidth > element.clientWidth;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function hash(string) {
|
||||||
|
if (typeof string != "string") return 0;
|
||||||
|
let hash = 0;
|
||||||
|
if (string.length === 0) {
|
||||||
|
return hash;
|
||||||
|
}
|
||||||
|
for (let i = 0; i < string.length; i++) {
|
||||||
|
let char = string.charCodeAt(i);
|
||||||
|
hash = ((hash<<5)-hash)+char;
|
||||||
|
hash = hash & hash; // Convert to 32bit integer
|
||||||
|
}
|
||||||
|
return hash;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getColor(string) {
|
||||||
|
let num = hash(string) % 8;
|
||||||
|
switch(num) {
|
||||||
|
case 0:
|
||||||
|
return "primary";
|
||||||
|
case 1:
|
||||||
|
return "secondary";
|
||||||
|
case 2:
|
||||||
|
return "success";
|
||||||
|
case 3:
|
||||||
|
return "danger";
|
||||||
|
case 4:
|
||||||
|
return "warning";
|
||||||
|
case 5:
|
||||||
|
return "info";
|
||||||
|
case 6:
|
||||||
|
return "light";
|
||||||
|
case 7:
|
||||||
|
return "dark";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
export function setProgressBar(bar, progress, text = '') {
|
export function setProgressBar(bar, progress, text = '') {
|
||||||
const progPos = (-1 * (1 - progress) * bar.scrollWidth).toString();
|
const progPos = (-1 * (1 - progress) * bar.scrollWidth).toString();
|
||||||
const progStr = (progress * 100).toString();
|
const progStr = (progress * 100).toString();
|
||||||
@ -32,4 +68,4 @@ export function coverArtString(title) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return 'Cover art for ' + nameOfSong;
|
return 'Cover art for ' + nameOfSong;
|
||||||
}
|
}
|
||||||
|
@ -183,9 +183,6 @@
|
|||||||
<div id="filter-path" class="input-group mb-2">
|
<div id="filter-path" class="input-group mb-2">
|
||||||
<select class="form-control form-control-sm" id="filter-dir" disabled>
|
<select class="form-control form-control-sm" id="filter-dir" disabled>
|
||||||
<option value="">.</option>
|
<option value="">.</option>
|
||||||
{% for dir in dirs %}
|
|
||||||
<option value="{{ dir }}">{{ dir }}</option>
|
|
||||||
{% endfor %}
|
|
||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@ -197,12 +194,9 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="col">
|
<div class="col">
|
||||||
<fieldset id="filter-type mb-2">
|
<fieldset id="filter-tags">
|
||||||
<legend>Tags</legend>
|
<legend>Tags</legend>
|
||||||
{% for tag in tags_color_lookup.keys() %}
|
<span class="filter-tag tag-unclicked tag-click badge"></span>
|
||||||
<span id="filter-tag"
|
|
||||||
class="filter-tag tag-unclicked tag-click badge badge-{{ tags_color_lookup[tag] }}">{{ tag }}</span>
|
|
||||||
{% endfor %}
|
|
||||||
</fieldset>
|
</fieldset>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -338,12 +332,8 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{% if upload_enabled %}
|
|
||||||
<div id="upload" class="container mb-3">
|
<div id="upload" class="container mb-3">
|
||||||
{% else %}
|
<input type="hidden" id="uploadDisabled" value="false" />
|
||||||
<div id="upload" class="container mb-3" style="display: none;">
|
|
||||||
<input type="hidden" id="uploadDisabled" value="true" />
|
|
||||||
{% endif %}
|
|
||||||
<div class="card">
|
<div class="card">
|
||||||
<div class="card-header">
|
<div class="card-header">
|
||||||
<h3 class="card-title">Upload File</h3>
|
<h3 class="card-title">Upload File</h3>
|
||||||
@ -368,11 +358,8 @@
|
|||||||
<div class="input-group-prepend">
|
<div class="input-group-prepend">
|
||||||
<label for="uploadTargetDir" class="input-group-text">Upload To</label>
|
<label for="uploadTargetDir" class="input-group-text">Upload To</label>
|
||||||
</div>
|
</div>
|
||||||
<input class="form-control" list="targetdirs" id="uploadTargetDir" name="targetdir" placeholder="uploads" />
|
<input class="form-control" list="upload-target-dirs" id="uploadTargetDir" name="upload-target-dirs" placeholder="uploads" />
|
||||||
<datalist id="targetdirs">
|
<datalist id="upload-target-dirs">
|
||||||
{% for dir in dirs %}
|
|
||||||
<option value="{{ dir }}">
|
|
||||||
{% endfor %}
|
|
||||||
</datalist>
|
</datalist>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -552,7 +539,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<input type="hidden" id="maxUploadFileSize" value="{{ max_upload_file_size }}" />
|
<input type="hidden" id="maxUploadFileSize" value="" />
|
||||||
|
|
||||||
<script src="static/js/main.js"></script>
|
<script src="static/js/main.js"></script>
|
||||||
</body>
|
</body>
|
||||||
|
Loading…
x
Reference in New Issue
Block a user