thumbnail view: Allow selecting multiple elements to mark as

first JavaScript supported feature
master
Felix Stupp 2 years ago
parent 9fd314115a
commit 073af5d2e6
Signed by: zocker
GPG Key ID: 93E1BD26F6B02FB7

@ -1099,3 +1099,61 @@ def api_media_element(media_id: int) -> ResponseReturnValue:
"status": False,
"error": "405 Method Not Allowed",
}, 405
def _api_media_set_x(call: Callable[[MediaElement], Any]) -> ResponseReturnValue:
data = request.form.to_dict()
ids = _parse_cs_ids(data.get("ids", "NULL"))
if not ids:
return {
"status": False,
"error": {
"msg": "Could not parse id list",
"data": {
"ids": data.get("ids"),
},
},
}
for m in _select_ids(MediaElement, ids):
call(m)
return redirect_back_or_okay()
@flask_app.route("/api/media/set_watched", methods=["POST"])
def api_media_set_watched() -> ResponseReturnValue:
def call(m: MediaElement) -> None:
m.watched = True
m.ignored = False # TODO move into class
return _api_media_set_x(call)
@flask_app.route("/api/media/set_ignored", methods=["POST"])
def api_media_set_ignored() -> ResponseReturnValue:
def call(m: MediaElement) -> None:
m.watched = False # TODO move into class
m.ignored = True
return _api_media_set_x(call)
@flask_app.route("/api/media/set_dependent", methods=["POST"])
def api_media_set_dependent() -> ResponseReturnValue:
data = request.form.to_dict()
ids = _parse_cs_ids(data.get("ids", "NULL"))
if not ids:
return {
"status": False,
"error": {
"msg": "Could not parse id list",
"data": {
"ids": data.get("ids"),
},
},
}
elements: Query[MediaElement] = _select_ids(MediaElement, ids).order_by(
MediaElement.release_date
)
for last, cur in common.iter_lookahead(common.fix_iter(elements)):
last.is_blocking.add(cur)
return redirect_back_or_okay()

File diff suppressed because one or more lines are too long

@ -0,0 +1,91 @@
// custom helper
/**
* sends a request to the specified url from a form. this will change the window location.
* @param {string} path the path to send the post request to
* @param {object} params the parameters to add to the url
* @param {string} [method=post] the method to use on the form
* @param {boolean} [redirect=true] should be redirected back to this page (useful for API calls to this app)
* original source: https://stackoverflow.com/a/133997/4015028
* - rewrote to use inline helper method prop()
* - added redirect parameter
*/
function post(path, params, method='post', redirect=true, fragment=null) {
// The rest of this code assumes you are not using a library.
// It can be made less verbose if you use one.
const form = document.createElement('form');
form.method = method;
form.action = path;
function prop(key, value) {
const hiddenField = document.createElement('input');
hiddenField.type = 'hidden';
hiddenField.name = key;
hiddenField.value = value;
form.appendChild(hiddenField);
}
if (redirect) {
prop("redirect", window.location.pathname + (fragment !== null ? "#" + fragment : ""));
}
for (const key in params) {
if (params.hasOwnProperty(key)) {
prop(key, params[key]);
}
}
document.body.appendChild(form);
form.submit();
}
// select module
function _select_post(path, ids) {
post(path, {"ids": ids}, "post", true, "media_element_" + ids[0]);
}
const select_sel_checkedBoxes = ".thumbnail_view > input.checkbox[name='element_id']:checked";
function _select_get_ids() {
return $(select_sel_checkedBoxes).map((_, o) => o.value).toArray();
}
function _select_get_ids_cs() {
return _select_get_ids().join(",");
}
function select_onChange() {
const sel_view = "#select_view";
const sel_counter = sel_view + " .counter";
const sel_button_clear = sel_view + " button#select_button_clear";
const sel_button_dependent = sel_view + " button#select_button_clear";
const element_ids = _select_get_ids();
if (element_ids.length <= 0) {
$(sel_view).attr("to_display", "false");
} else {
$(sel_button_dependent).prop("disabled", element_ids.length > 1);
$(sel_view).attr("to_display", "true");
$(sel_counter).html(element_ids.length);
}
}
function select_watch() {
const ids = _select_get_ids();
_select_post("/api/media/set_watched", ids);
}
function select_ignore() {
const ids = _select_get_ids();
_select_post("/api/media/set_ignored", ids);
}
function select_dependent() {
const ids = _select_get_ids();
_select_post("/api/media/set_dependent", ids);
}
function select_clear() {
$(select_sel_checkedBoxes).prop('checked', false);
select_onChange();
}
$(window).on("load", () => select_onChange());

@ -1,4 +1,6 @@
{% macro shared_style() %}
<script src="/static/scripts/jquery.min.js"></script>
<script src="/static/scripts/select.js"></script>
<link href="/static/stylesheets/reset.css" rel="stylesheet" />
<style>
body {
@ -86,6 +88,30 @@
font-size: 1.2rem;
}
/* select view */
#select_view {
display: flex;
position: fixed;
right: 4rem;
bottom: 0;
padding: 1rem;
background-color: #333333;
border-radius: 1rem 1rem 0 0;
box-shadow: 0 0 .2rem .4rem #444444;
align-items: center;
gap: .4rem;
opacity: 0%;
visibility: hidden;
transition: opacity .1s linear, visibility .1s linear;
z-index: 200;
}
#select_view[to_display='true'] {
opacity: 100%;
visibility: visible;
}
#select_view > .text > * {
display: inline;
}
/* thumbnail view */
.thumbnail_img {
width: 100%;
@ -164,8 +190,10 @@
transition: opacity .2s linear, visibility .2s linear;
}
.thumbnail_view:hover > .button_list,
.thumbnail_view:hover > .checkbox,
.thumbnail_view:hover > .overlay,
.thumbnail_view:active > .button_list,
.thumbnail_view:active > .checkbox,
.thumbnail_view:active > .overlay {
opacity: 100%;
visibility: visible;
@ -190,6 +218,7 @@
height: 2.8rem;
}
.thumbnail_view > .additional_info,
.thumbnail_view > .checkbox,
.thumbnail_view > .episode_info,
.thumbnail_view > .length,
.thumbnail_view > .release_date {
@ -215,10 +244,23 @@
.thumbnail_view > .episode_info[href] {
--box-color: rgba(153, 50, 204, .8);
}
.thumbnail_view > .additional_info {
.thumbnail_view > .additional_info,
.thumbnail_view > .checkbox {
top: 0;
right: 0;
}
.thumbnail_view > .checkbox {
opacity: 0%;
visibility: hidden;
transform: scale(1.4) translate(-20%, 20%);
transition: opacity .2s linear, visibility .2s linear;
}
.thumbnail_view:hover > .checkbox,
.thumbnail_view:active > .checkbox,
.thumbnail_view > .checkbox:checked {
opacity: 100%;
visibility: visible;
}
.thumbnail_view > .release_date {
left: 0;
bottom: 0;
@ -330,8 +372,21 @@
</div>
{%- endmacro %}
{% macro _select_view() %}
<div id="select_view" to_display="false">
<div class="text">
<div class="counter">X</div> videos selected
</div>
<button id="select_button_watch" onclick="select_watch();">watched</button>
<button id="select_button_ignore" onclick="select_ignore();">ignored</button>
<button id="select_button_dependent" onclick="select_dependent();">make dependent</button>
<button id="select_button_" onclick="select_clear();">clear selection</button>
</div>
{%- endmacro %}
{% macro body_header() %}
{{ _navigation() }}
{{ _select_view() }}
{%- endmacro %}
{% macro media_element_buttons(element, show_fragment=True) %}
@ -430,6 +485,8 @@
<div class="additional_info">
{{ caller() }}
</div>
{% else %}
<input class="checkbox" type="checkbox" name="element_id" value="{{ element.id }}" onchange="select_onChange();" />
{% endif %}
<span class="release_date" title="{{ element.release_date.strftime('%d.%m.%Y') }}">
{{ element.release_date | time_since }}

Loading…
Cancel
Save