Compare commits
4 Commits
00e42447fc
...
40f0b5d369
| Author | SHA1 | Date | |
|---|---|---|---|
| 40f0b5d369 | |||
| 2f5c6ec949 | |||
| 81ebf6fa01 | |||
| 7a4f5ddb17 |
15
TODO
15
TODO
@@ -1,20 +1,15 @@
|
||||
###
|
||||
# get override data into view
|
||||
# should start with an empty DB and test - definitely no dirs in storage_sp gives:
|
||||
# dir_id=dir_arr[0]
|
||||
# IndexError: list index out of range
|
||||
# TEST this, code in files will work, but now passes root_eid=0 for
|
||||
# this - so need GUI to work - may even be good to put an alert up - its so odd to hvae not root dir ONLY happens when no data
|
||||
# empty directories are sometimes showing "No matches for: 'undefined'" <- should only comes up for search in URL???
|
||||
# should start with an empty DB and test
|
||||
# definitely no dirs in storage_sp I now pass root_eid=0 for this
|
||||
# BUT - need GUI to work - may even be good to put an alert up - its so odd to have not root dir ONLY happens when no data
|
||||
# empty directories (2017/20171015-test/...) showing "No matches for: 'undefined'" <- should only comes up for search in URL???
|
||||
# think I killed pa_job_manager without passing an eid to a transform job, shouldn't crash
|
||||
# SHOULD JUST get AI to help clean-up and write defensive code here...
|
||||
# also all the add ref img/add override, etc are non-functional - FIX the override* stuff first to get table/naming consistency as that is half the problem
|
||||
# delete button also uses current? (eid is empty anyway)
|
||||
# move as much jscript into *_support, as possible (if there are no more {{ - # then move it)
|
||||
# convert move_paths to a json setup
|
||||
# ALSO revisit this move_paths to be as safe as possible ultimately, triple-check there are no leading / or .. 's
|
||||
# TEST everything (don't forget keybindings)
|
||||
# transforms -> should consider removing the pull logic to see if a job is finished (have job-mgr push data back? while more rest, not sure b/e EVER sends data to F/E at moment)
|
||||
# TEST everything (don't forget keybindings,e.g. delete)
|
||||
###
|
||||
|
||||
### major fix - go to everywhere I call GetEntries(), and redo the logic totally...
|
||||
|
||||
@@ -4,6 +4,21 @@ ICON["Import"]="import"
|
||||
ICON["Storage"]="db"
|
||||
ICON["Bin"]="trash"
|
||||
|
||||
// function called when we get another page from inside the files view
|
||||
function getPageFigures(res, viewingIdx)
|
||||
{
|
||||
// add all the figures to files_div
|
||||
drawPageOfFigures()
|
||||
}
|
||||
|
||||
// function called when we get another page from inside the viewer
|
||||
function getPageViewer(res, viewingIdx)
|
||||
{
|
||||
document.viewing=document.entries[viewingIdx]
|
||||
// update viewing, arrows and image/video too
|
||||
ViewImageOrVideo()
|
||||
}
|
||||
|
||||
// grab all selected thumbnails and return a <div> containing the thumbnails
|
||||
// with extra yr and date attached as attributes so we can set the default
|
||||
// dir name for a move directory - not used in del, but no harm to include them
|
||||
@@ -520,10 +535,10 @@ function drawPageOfFigures()
|
||||
addFigure( obj, last, ecnt )
|
||||
ecnt++
|
||||
}
|
||||
if( document.entries.length == 0 && OPT.search_term != '' )
|
||||
if( document.entries.length == 0 && OPT.search_term )
|
||||
$('#figures').append( `<span class="alert alert-danger p-2 col-auto"> No matches for: '${OPT.search_term}'</span>` )
|
||||
$('.figure').click( function(e) { DoSel(e, this ); SetButtonState(); return false; });
|
||||
$('.figure').dblclick( function(e) { dblClickToViewEntry( $(this).attr('id') ) } )
|
||||
$('.figure').dblclick( function(e) { dblClickToViewEntry( $(this).attr('id') ); setDisabledForViewingNextPrevBttons(); addViewerKeyHandler() } )
|
||||
// for dir, getDirEntries 2nd param is back (or "up" a dir)
|
||||
$(".dir").click( function(e) { document.back_id=this.id; getDirEntries(this.id,false) } )
|
||||
$(".back").click( function(e) { getDirEntries(this.id,true) } )
|
||||
@@ -553,21 +568,6 @@ function getPageFileList(res, viewingIdx)
|
||||
$('#file_list_div').append(html)
|
||||
}
|
||||
|
||||
// function called when we get another page from inside the files view
|
||||
function getPageFigures(res, viewingIdx)
|
||||
{
|
||||
// add all the figures to files_div
|
||||
drawPageOfFigures()
|
||||
}
|
||||
|
||||
// function called when we get another page from inside the viewer
|
||||
function getPageViewer(res, viewingIdx)
|
||||
{
|
||||
document.viewing=document.entries[viewingIdx]
|
||||
// update viewing, arrows and image/video too
|
||||
ViewImageOrVideo()
|
||||
}
|
||||
|
||||
// Function to get the 'page' of entry ids out of entryList
|
||||
function getPage(pageNumber, successCallback, viewingIdx=0)
|
||||
{
|
||||
@@ -587,9 +587,15 @@ function getPage(pageNumber, successCallback, viewingIdx=0)
|
||||
type: 'POST', url: '/get_entries_by_ids',
|
||||
data: JSON.stringify(data), contentType: 'application/json',
|
||||
dataType: 'json',
|
||||
success: function(res) { document.entries=res; successCallback(res,viewingIdx); },
|
||||
success: function(res) {
|
||||
document.entries=res;
|
||||
successCallback(res,viewingIdx);
|
||||
resetNextPrevButtons()
|
||||
// if search, disable folders
|
||||
if( OPT.search_term )
|
||||
$('#folders').prop('disabled', 'disabled').removeClass('border-info').addClass('border-secondary').removeClass('text-info').addClass('text-secondary');
|
||||
},
|
||||
error: function(xhr, status, error) { console.error("Error:", error); } });
|
||||
resetNextPrevButtons()
|
||||
return
|
||||
}
|
||||
|
||||
@@ -620,6 +626,13 @@ function getPageNumberForId(id) {
|
||||
// if we are on last page, disable next, it not ensure prev is enabled
|
||||
function resetNextPrevButtons()
|
||||
{
|
||||
// no data, so disabled both
|
||||
if( getPageNumberForId(pageList[0]) == -1 )
|
||||
{
|
||||
$('.prev').prop('disabled', true).addClass('disabled');
|
||||
$('.next').prop('disabled', true).addClass('disabled');
|
||||
return
|
||||
}
|
||||
if ( isFirstPage( getPageNumberForId(pageList[0]) ) )
|
||||
$('.prev').prop('disabled', true).addClass('disabled');
|
||||
else
|
||||
@@ -661,12 +674,14 @@ function prevPage(successCallback)
|
||||
return
|
||||
}
|
||||
|
||||
// function to see if we are on a phone or tablet (where we dont have ctrl or shift keys - helps to display fake buttons to allow multiselect on mobiles)
|
||||
function isMobile() {
|
||||
try{ document.createEvent("TouchEvent"); return true; }
|
||||
catch(e){ return false; }
|
||||
}
|
||||
|
||||
|
||||
// when we change one of the options (noo, how_many, folders) - then update '{how_many} files' str,
|
||||
// tweak noo menu for folders/flat view then reset the page contents based on current OPT values
|
||||
function changeOPT(successCallback) {
|
||||
OPT.how_many=$('#how_many').val()
|
||||
new_f=$('#folders').val()
|
||||
@@ -707,3 +722,119 @@ function changeOPT(successCallback) {
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// function to change the size of thumbnails when user clicks xs/s/m/l/xl buttons
|
||||
function changeSize()
|
||||
{
|
||||
sz=$('input[name="size"]:checked').val();
|
||||
$('.thumb').prop('height',sz);
|
||||
}
|
||||
|
||||
|
||||
function getPreviousEntry() {
|
||||
var currentIndex = entryList.indexOf(document.viewing.id);
|
||||
|
||||
oldPageOffset=Math.floor(currentIndex / OPT.how_many)
|
||||
if (currentIndex > 0) {
|
||||
currentIndex--;
|
||||
pageOffset=Math.floor(currentIndex / OPT.how_many)
|
||||
currentIndex=currentIndex-(pageOffset*OPT.how_many)
|
||||
// pref page, load it
|
||||
if( oldPageOffset != pageOffset )
|
||||
// pref page is pageOffset+1 now
|
||||
getPage(pageOffset+1,getPageViewer,currentIndex)
|
||||
else
|
||||
document.viewing=document.entries[currentIndex]
|
||||
}
|
||||
}
|
||||
|
||||
function getNextEntry() {
|
||||
var currentIndex = entryList.indexOf(document.viewing.id);
|
||||
|
||||
oldPageOffset=Math.floor(currentIndex / OPT.how_many)
|
||||
if (currentIndex < entryList.length - 1) {
|
||||
currentIndex++
|
||||
pageOffset=Math.floor(currentIndex / OPT.how_many)
|
||||
currentIndex=currentIndex-(pageOffset*OPT.how_many)
|
||||
// next page, load it
|
||||
if( oldPageOffset != pageOffset )
|
||||
// next page is pageOffset+1 now
|
||||
getPage(pageOffset+1,getPageViewer,currentIndex)
|
||||
else
|
||||
document.viewing=document.entries[currentIndex]
|
||||
}
|
||||
}
|
||||
|
||||
function entryIsAtStart() {
|
||||
return document.viewing.id === entryList[0];
|
||||
}
|
||||
|
||||
function entryIsAtEnd() {
|
||||
return document.viewing.id === entryList[entryList.length - 1];
|
||||
}
|
||||
|
||||
function setEntryById(id) {
|
||||
var currentIndex = entryList.indexOf(parseInt(id));
|
||||
// if we are on a different page, adjust as document.entries only has <= how_many
|
||||
pageOffset=Math.floor(currentIndex / OPT.how_many)
|
||||
currentIndex = currentIndex-(pageOffset*OPT.how_many)
|
||||
document.viewing=document.entries[currentIndex]
|
||||
}
|
||||
|
||||
function setDisabledForViewingNextPrevBttons()
|
||||
{
|
||||
$('#la').attr('disabled', entryIsAtStart());
|
||||
$('#ra').attr('disabled', entryIsAtEnd());
|
||||
}
|
||||
|
||||
function addViewerKeyHandler() {
|
||||
// allow a keypress on the viewer_div
|
||||
$(document).keydown(function(event) {
|
||||
// if dbox is visible, dont process this hot-key, we are inputting text
|
||||
// into inputs instead
|
||||
if( $("#dbox").is(':visible') )
|
||||
return
|
||||
switch (event.key)
|
||||
{
|
||||
case "Left": // IE/Edge specific value
|
||||
case "ArrowLeft":
|
||||
if( $('#la').prop('disabled') == false )
|
||||
$('#la').click()
|
||||
break;
|
||||
case "Right": // IE/Edge specific value
|
||||
case "ArrowRight":
|
||||
if( $('#ra').prop('disabled') == false )
|
||||
$('#ra').click()
|
||||
break;
|
||||
case "d":
|
||||
$('#distance').click()
|
||||
break;
|
||||
case "f":
|
||||
$('#faces').click()
|
||||
break;
|
||||
case "n":
|
||||
$('#fname_toggle').click()
|
||||
break;
|
||||
case "F":
|
||||
fullscreen=!document.fullscreen
|
||||
ViewImageOrVideo()
|
||||
break;
|
||||
case "l":
|
||||
JoblogSearch()
|
||||
break;
|
||||
case "Delete":
|
||||
$('#del').click()
|
||||
default:
|
||||
return; // Quit when this doesn't handle the key event.
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
$(document).on('click', function(e) { $('.highlight').removeClass('highlight') ; SetButtonState() });
|
||||
|
||||
function dblClickToViewEntry(id) {
|
||||
$('#files_div').addClass('d-none')
|
||||
$('#viewer_div').removeClass('d-none')
|
||||
setEntryById( id )
|
||||
ViewImageOrVideo()
|
||||
}
|
||||
|
||||
5
job.py
5
job.py
@@ -3,7 +3,7 @@ from flask_wtf import FlaskForm
|
||||
from flask import request, render_template, redirect, make_response, jsonify, url_for
|
||||
from settings import Settings
|
||||
from main import db, app, ma
|
||||
from sqlalchemy import Sequence, func
|
||||
from sqlalchemy import Sequence, func, select
|
||||
from sqlalchemy.exc import SQLAlchemyError
|
||||
from datetime import datetime, timedelta
|
||||
import pytz
|
||||
@@ -280,7 +280,8 @@ def joblog_search():
|
||||
from sqlalchemy import text
|
||||
|
||||
eid=request.form['eid']
|
||||
ent=Entry.query.get(eid)
|
||||
stmt = select(Entry).where(Entry.id == eid)
|
||||
ent = db.session.scalars(stmt).one_or_none()
|
||||
logs=Joblog.query.join(Job).filter(Joblog.log.ilike(text(f"'%%{ent.name}%%'"))).with_entities(Joblog.log, Job.id, Job.name, Job.state, Joblog.log_date).all()
|
||||
|
||||
# turn DB output into json and return it to the f/e
|
||||
|
||||
7
path.py
7
path.py
@@ -63,6 +63,9 @@ class PathDetail(PA):
|
||||
# construct icon_url based on type of storage path (icons.svg contains icons for each)
|
||||
self.icon_url:str=url_for("internal", filename="icons.svg") + "#" + ICON[self.type]
|
||||
|
||||
def to_dict(self):
|
||||
return {key: value for key, value in vars(self).items()}
|
||||
|
||||
################################################################################
|
||||
# helper function to find path details for move destinations - used in html
|
||||
# for move DBox to show potential storage paths to move files into
|
||||
@@ -83,9 +86,9 @@ def MovePathDetails():
|
||||
sps=Path.query.join(PathType).filter(PathType.name=="Storage").all()
|
||||
for p in sps:
|
||||
obj = PathDetail( ptype="Storage", path=p.path_prefix.replace("static/Storage/","") )
|
||||
ret.append( obj )
|
||||
ret.append( obj.to_dict() )
|
||||
ips=Path.query.join(PathType).filter(PathType.name=="Import").all()
|
||||
for p in ips:
|
||||
obj = PathDetail( ptype="Import", path=p.path_prefix.replace("static/Import/","") )
|
||||
ret.append( obj )
|
||||
ret.append( obj.to_dict() )
|
||||
return ret
|
||||
|
||||
@@ -136,7 +136,7 @@
|
||||
|
||||
{% if not InDBox %}
|
||||
{%block script_content %}{% endblock script_content %}
|
||||
<div id="status_container" class="position-fixed top-0 end-0 p-0 my-5" "z-index: 11"> </div>
|
||||
<div id="status_container" class="position-fixed top-0 end-0 p-0 my-5" style="z-index: 9999"> </div>
|
||||
<!-- CheckForJobs(), will see if there are any messages/jobs and keep doing this until there are 0 more and then stop -->
|
||||
<script>
|
||||
$(document).ready(function() { CheckForJobs() } )
|
||||
|
||||
@@ -1,49 +1,24 @@
|
||||
{% extends "base.html" %}
|
||||
{% block main_content %}
|
||||
|
||||
<style>
|
||||
.norm-txt { font-size: 1.0rem }
|
||||
.form-check-input:checked {
|
||||
background-color: #39C0ED;
|
||||
border-color: #CFF4FC;
|
||||
}
|
||||
.form-switch .form-check-input {
|
||||
background-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='-4 -4 8 8'%3e%3ccircle r='3' fill='%2339C0ED'/%3e%3c/svg%3e");
|
||||
}
|
||||
.form-switch .form-check-input:focus {
|
||||
background-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='-4 -4 8 8'%3e%3ccircle r='3' fill='%23CFF4FC'/%3e%3c/svg%3e");
|
||||
}
|
||||
#tst90:hover,#tst90:focus { filter: invert(73%) sepia(27%) saturate(3970%) hue-rotate(146deg) brightness(94%) contrast(100%); }
|
||||
</style>
|
||||
<script src="{{ url_for( 'internal', filename='js/files_transform.js')}}"></script>
|
||||
<script src="{{ url_for( 'internal', filename='js/files_support.js')}}"></script>
|
||||
<script src="{{ url_for( 'internal', filename='js/view_support.js')}}"></script>
|
||||
|
||||
<script>
|
||||
// FIXME: why is this script block at the top? and not at the bottom
|
||||
document.fake_shift=0
|
||||
document.fake_ctrl=0
|
||||
// FIXME: used by viewer code - should probably get rid of this?
|
||||
var fullscreen=false;
|
||||
// FIXME: doco / and convert to json
|
||||
var move_paths=[]
|
||||
{% for p in move_paths %}
|
||||
p = new Object()
|
||||
p.type = '{{p.type}}'
|
||||
p.path = '{{p.path}}'
|
||||
p.icon_url = '{{p.icon_url}}'
|
||||
move_paths.push(p)
|
||||
{% endfor %}
|
||||
|
||||
// GLOBALS
|
||||
// this is the current entry (object) we are viewing - an image/video (used when we dbl-click to view & then in next/prev in view)
|
||||
document.viewing=null;
|
||||
|
||||
var OPT = {{ OPT.to_dict()|tojson }};
|
||||
OPT.root_eid = {{ query_data.root_eid }};
|
||||
|
||||
// this is the list of entry ids for the images for ALL matches for this query
|
||||
var entryList={{query_data.entry_list}}
|
||||
|
||||
// pageList is just those entries shown on this page from the full entryList
|
||||
var pageList=[]
|
||||
// force pageList to set pageList for & render the first page
|
||||
getPage(1,getPageFigures)
|
||||
|
||||
// FIXME: MOVE to file_support.js
|
||||
function changeSize()
|
||||
{
|
||||
sz=$('input[name="size"]:checked').val();
|
||||
$('.thumb').prop('height',sz);
|
||||
}
|
||||
</script>
|
||||
|
||||
<div id="files_div">
|
||||
<div class="container-fluid">
|
||||
<div class="d-flex row mb-2">
|
||||
@@ -80,10 +55,6 @@
|
||||
<div class="col col-auto my-auto">
|
||||
<span class="alert alert-primary p-2">Searched for: '{{search_term}}'</span>
|
||||
</div class="col my-auto">
|
||||
<script>
|
||||
// FIXME: need to move this into getPage (or similar), so its picked up always not just on first load
|
||||
$('#folders').prop('disabled', 'disabled').removeClass('border-info').addClass('border-secondary').removeClass('text-info').addClass('text-secondary');
|
||||
</script>
|
||||
{% endif %}
|
||||
<div class="col flex-grow-1 my-auto d-flex justify-content-center w-100">
|
||||
<button aria-label="prev" id="prev" name="prev" class="prev sm-txt btn btn-outline-secondary disabled" onClick="prevPage(getPageFigures)" disabled>
|
||||
@@ -144,38 +115,143 @@
|
||||
</div class="container-fluid">
|
||||
</div id="files_div">
|
||||
<div id="viewer_div" class="d-none">
|
||||
<div id="viewer" class="container-fluid">
|
||||
<div class="row">
|
||||
<button title="Show previous image" class="col-auto btn btn-outline-info px-2"
|
||||
style="padding: 10%" id="la"
|
||||
onClick="
|
||||
getPreviousEntry()
|
||||
setDisabledForViewingNextPrevBttons()
|
||||
ViewImageOrVideo()
|
||||
">
|
||||
<svg width="16" height="16" fill="currentColor">
|
||||
<use xlink:href="{{url_for('internal', filename='icons.svg')}}#prev"/></svg>
|
||||
</button>
|
||||
<figure class="col col-auto border border-info rounded m-0 p-1" id="figure">
|
||||
<canvas id="canvas"></canvas>
|
||||
<img id="throbber" src="{{url_for('internal', filename='throbber.gif')}}" style="display:none;">
|
||||
<script>
|
||||
var im=new Image();
|
||||
im.onload=DrawImg
|
||||
var context = canvas.getContext('2d')
|
||||
</script>
|
||||
<figcaption id="img-cap" class="figure-caption text-center text-wrap text-break">
|
||||
<span id="fname_i"></span></figcaption>
|
||||
</figure>
|
||||
<div id="video_div" class="col col-auto">
|
||||
<video id="video" class="col col-auto" controls>
|
||||
<source id="videoSource" src="" type="video/mp4">
|
||||
Your browser does not support the video tag.
|
||||
</video>
|
||||
<figcaption id="vid-cap" class="figure-caption text-center text-wrap text-break">
|
||||
<span id="fname_v"></span></figcaption>
|
||||
</div>
|
||||
<button title="Show next image" class="col-auto btn btn-outline-info px-2" style="padding: 10%" id="ra"
|
||||
onClick="
|
||||
getNextEntry()
|
||||
setDisabledForViewingNextPrevBttons()
|
||||
ViewImageOrVideo()
|
||||
">
|
||||
<svg width="16" height="16" fill="currentColor">
|
||||
<use xlink:href="{{url_for('internal', filename='icons.svg')}}#next"/></svg>
|
||||
</button>
|
||||
</div class="row">
|
||||
<div class="row">
|
||||
{# this whole div, just takes up the same space as the left button and is hidden for alignment only #}
|
||||
<div class="col-auto px-0">
|
||||
<button class="btn btn-outline-info px-2 invisible" disabled>
|
||||
<svg width="16" height="16" fill="currentColor"><use xlink:href="{{url_for('internal', filename='icons.svg')}}#next"/></svg>
|
||||
</button>
|
||||
</div>
|
||||
<span class="col-auto my-auto">Show:</span>
|
||||
<div title="Toggle showing filename (hotkey: n)" class="d-flex form-check form-switch border border-info rounded col col-auto my-auto py-1 justify-content-center ps-5">
|
||||
<input class="form-check-input" type="checkbox" id="fname_toggle" onChange="$('.figure-caption').toggle()" checked>
|
||||
<label class="form-check-label ps-1" for="fname_toggle">Filename</label>
|
||||
</div>
|
||||
<div title="Toggle showing matched faces (hotkey: f)" class="d-flex form-check form-switch border border-info rounded col col-auto my-auto py-1 justify-content-center ps-5">
|
||||
<input class="form-check-input" type="checkbox" onChange="FaceToggle()" id="faces">
|
||||
<label class="form-check-label ps-1" for="faces">Faces</label>
|
||||
</div>
|
||||
<div title="Toggle showing 'distance' on matched faces (hotkey: d)" class="d-flex form-check form-switch border border-info rounded col col-auto my-auto py-1 justify-content-center ps-5">
|
||||
<input class="form-check-input" type="checkbox" onChange="DrawImg()" id="distance">
|
||||
<label class="form-check-label ps-1" for="distance">Distance</label>
|
||||
</div>
|
||||
<div title="Change the model used to detect faces" class="col col-auto my-auto">
|
||||
AI Model:
|
||||
{# can use 0 as default, it will be (re)set correctly in DrawImg() anyway #}
|
||||
{{CreateSelect( "model", 0, ["N/A", "normal", "slow/accurate"], "", "rounded norm-txt", [0,1,2])|safe }}
|
||||
</div>
|
||||
<div class="col col-auto pt-1">
|
||||
<button class="btn btn-outline-info p-1" title="Rotate by 90 degrees" onClick="Transform(90)">
|
||||
<img src="{{url_for('internal', filename='rot90.png')}}" width="32" height="32" onMouseOver="this.src='{{url_for('internal', filename='rot90-invert.png')}}'"
|
||||
onMouseOut="this.src='{{url_for('internal', filename='rot90.png')}}'" />
|
||||
</button>
|
||||
<button class="btn btn-outline-info p-1" title="Rotate by 180 degrees" onClick="Transform(180)">
|
||||
<img src="{{url_for('internal', filename='rot180.png')}}" width="32" height="32" onMouseOver="this.src='{{url_for('internal', filename='rot180-invert.png')}}'"
|
||||
onMouseOut="this.src='{{url_for('internal', filename='rot180.png')}}'" />
|
||||
</button>
|
||||
<button class="btn btn-outline-info p-1" title="Rotate by 270 degrees" onClick="Transform(270)">
|
||||
<img src="{{url_for('internal', filename='rot270.png')}}" width="32" height="32" onMouseOver="this.src='{{url_for('internal', filename='rot270-invert.png')}}'"
|
||||
onMouseOut="this.src='{{url_for('internal', filename='rot270.png')}}'" />
|
||||
</button>
|
||||
<button class="btn btn-outline-info p-1" title="Flip horizontally" onClick="Transform('fliph')">
|
||||
<svg width="32" height="32" fill="currentColor"><use xlink:href="{{url_for('internal', filename='icons.svg')}}#flip_h"/></svg>
|
||||
</button>
|
||||
<button class="btn btn-outline-info p-1" title="Flip vertically" onClick="Transform('flipv')">
|
||||
<svg width="32" height="32" fill="currentColor"><use xlink:href="{{url_for('internal', filename='icons.svg')}}#flip_v"/></svg>
|
||||
</button>
|
||||
<button class="btn btn-outline-info p-1" title="View in Fullscreen mode (hotkey: F)" onClick="fullscreen=true; ViewImageOrVideo()">
|
||||
<svg width="32" height="32" fill="currentColor"><use xlink:href="{{url_for('internal', filename='icons.svg')}}#fullscreen"/></svg>
|
||||
</button>
|
||||
<button class="btn btn-outline-info p-1" title="Show logs relating to this filename (hotkey: l)" onClick="JoblogSearch()">
|
||||
<svg width="32" height="32" fill="currentColor"><use xlink:href="{{url_for('internal', filename='icons.svg')}}#log"/></svg>
|
||||
</button>
|
||||
<button class="btn btn-outline-info p-1" title="View Original" onClick="window.location='/'+document.viewing.FullPathOnFS">
|
||||
<svg width="32" height="32" fill="currentColor"><use xlink:href="{{url_for('internal', filename='icons.svg')}}#download"/></svg>
|
||||
</button>
|
||||
<button id="del" class="btn btn-outline-danger p-1" title="Delete (hotkey: Del)"
|
||||
onClick="$.ajax({ type: 'POST', data: '&eid-0='+document.viewing.id, url: '/delete_files', success: function(data){ window.location='/'; return false; } })">
|
||||
<svg width="32" height="32" fill="currentColor"><use xlink:href="{{url_for('internal', filename='icons.svg')}}#trash"/></svg>
|
||||
</button>
|
||||
</div>
|
||||
</div class="row">
|
||||
</div id="viewer">
|
||||
</div id="viewer_div">
|
||||
{% endblock main_content %}
|
||||
|
||||
<style>
|
||||
|
||||
|
||||
.norm-txt { font-size: 1.0rem }
|
||||
.form-check-input:checked {
|
||||
background-color: #39C0ED;
|
||||
border-color: #CFF4FC;
|
||||
}
|
||||
.form-switch .form-check-input {
|
||||
background-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='-4 -4 8 8'%3e%3ccircle r='3' fill='%2339C0ED'/%3e%3c/svg%3e");
|
||||
}
|
||||
.form-switch .form-check-input:focus {
|
||||
background-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='-4 -4 8 8'%3e%3ccircle r='3' fill='%23CFF4FC'/%3e%3c/svg%3e");
|
||||
}
|
||||
#tst90:hover,#tst90:focus { filter: invert(73%) sepia(27%) saturate(3970%) hue-rotate(146deg) brightness(94%) contrast(100%); }
|
||||
</style>
|
||||
|
||||
|
||||
{% block script_content %}
|
||||
<script>
|
||||
document.fake_shift=0
|
||||
document.fake_ctrl=0
|
||||
// FIXME: used by viewer code - should probably get rid of this?
|
||||
var fullscreen=false;
|
||||
var move_paths = {{ move_paths|tojson }};
|
||||
|
||||
// GLOBALS
|
||||
// this is the current entry (object) we are viewing - an image/video (used when we dbl-click to view & then in next/prev in view)
|
||||
document.viewing=null;
|
||||
|
||||
var OPT = {{ OPT.to_dict()|tojson }};
|
||||
OPT.root_eid = {{ query_data.root_eid }};
|
||||
|
||||
// this is the list of entry ids for the images for ALL matches for this query
|
||||
var entryList={{query_data.entry_list}}
|
||||
|
||||
// pageList is just those entries shown on this page from the full entryList
|
||||
var pageList=[]
|
||||
// force pageList to set pageList for & render the first page
|
||||
getPage(1,getPageFigures)
|
||||
|
||||
// FIXME: doco, but also gather all globals together, many make them all document. to be obviously global (and add fullscreen)
|
||||
var gap=0.8
|
||||
var grayscale=0
|
||||
var throbber=0
|
||||
|
||||
var objs=[]
|
||||
var NMO=[]
|
||||
var imp_path="static/Import/{{imp_path}}"
|
||||
var st_path="static/Storage/{{st_path}}"
|
||||
var bin_path="static/Bin/{{bin_path}}"
|
||||
|
||||
// FIXME: MOVE these functions to file_support.js
|
||||
function PrettyFname(fname)
|
||||
{
|
||||
s='<span class="alert alert-secondary py-2">'
|
||||
@@ -198,309 +274,88 @@
|
||||
return s
|
||||
}
|
||||
|
||||
function getPreviousEntry() {
|
||||
var currentIndex = entryList.indexOf(document.viewing.id);
|
||||
|
||||
oldPageOffset=Math.floor(currentIndex / OPT.how_many)
|
||||
if (currentIndex > 0) {
|
||||
currentIndex--;
|
||||
pageOffset=Math.floor(currentIndex / OPT.how_many)
|
||||
currentIndex=currentIndex-(pageOffset*OPT.how_many)
|
||||
// pref page, load it
|
||||
if( oldPageOffset != pageOffset )
|
||||
// pref page is pageOffset+1 now
|
||||
getPage(pageOffset+1,getPageViewer,currentIndex)
|
||||
else
|
||||
document.viewing=document.entries[currentIndex]
|
||||
}
|
||||
}
|
||||
|
||||
function getNextEntry() {
|
||||
var currentIndex = entryList.indexOf(document.viewing.id);
|
||||
|
||||
oldPageOffset=Math.floor(currentIndex / OPT.how_many)
|
||||
if (currentIndex < entryList.length - 1) {
|
||||
currentIndex++
|
||||
pageOffset=Math.floor(currentIndex / OPT.how_many)
|
||||
currentIndex=currentIndex-(pageOffset*OPT.how_many)
|
||||
// next page, load it
|
||||
if( oldPageOffset != pageOffset )
|
||||
// next page is pageOffset+1 now
|
||||
getPage(pageOffset+1,getPageViewer,currentIndex)
|
||||
else
|
||||
document.viewing=document.entries[currentIndex]
|
||||
}
|
||||
}
|
||||
|
||||
function entryIsAtStart() {
|
||||
return document.viewing.id === entryList[0];
|
||||
}
|
||||
|
||||
function entryIsAtEnd() {
|
||||
return document.viewing.id === entryList[entryList.length - 1];
|
||||
}
|
||||
|
||||
function setEntryById(id) {
|
||||
var currentIndex = entryList.indexOf(parseInt(id));
|
||||
// if we are on a different page, adjust as document.entries only has <= how_many
|
||||
pageOffset=Math.floor(currentIndex / OPT.how_many)
|
||||
currentIndex = currentIndex-(pageOffset*OPT.how_many)
|
||||
document.viewing=document.entries[currentIndex]
|
||||
}
|
||||
|
||||
function setDisabledForViewingNextPrevBttons()
|
||||
{
|
||||
$('#la').attr('disabled', entryIsAtStart());
|
||||
$('#ra').attr('disabled', entryIsAtEnd());
|
||||
}
|
||||
</script>
|
||||
|
||||
<div id="viewer" class="container-fluid">
|
||||
<div class="row">
|
||||
<button title="Show previous image" class="col-auto btn btn-outline-info px-2"
|
||||
style="padding: 10%" id="la"
|
||||
onClick="
|
||||
getPreviousEntry()
|
||||
setDisabledForViewingNextPrevBttons()
|
||||
ViewImageOrVideo()
|
||||
">
|
||||
<svg width="16" height="16" fill="currentColor">
|
||||
<use xlink:href="{{url_for('internal', filename='icons.svg')}}#prev"/></svg>
|
||||
</button>
|
||||
<figure class="col col-auto border border-info rounded m-0 p-1" id="figure">
|
||||
<canvas id="canvas"></canvas>
|
||||
<img id="throbber" src="{{url_for('internal', filename='throbber.gif')}}" style="display:none;">
|
||||
<script>
|
||||
var im=new Image();
|
||||
im.onload=DrawImg
|
||||
var context = canvas.getContext('2d')
|
||||
</script>
|
||||
<figcaption id="img-cap" class="figure-caption text-center text-wrap text-break">
|
||||
<span id="fname_i"></span></figcaption>
|
||||
</figure>
|
||||
<div id="video_div" class="col col-auto">
|
||||
<video id="video" class="col col-auto" controls>
|
||||
<source id="videoSource" src="" type="video/mp4">
|
||||
Your browser does not support the video tag.
|
||||
</video>
|
||||
<figcaption id="vid-cap" class="figure-caption text-center text-wrap text-break">
|
||||
<span id="fname_v"></span></figcaption>
|
||||
</div>
|
||||
<script>
|
||||
window.addEventListener('resize', DrawImg, false);
|
||||
window.addEventListener('resize', ResizeVideo, false);
|
||||
</script>
|
||||
|
||||
<button title="Show next image" class="col-auto btn btn-outline-info px-2" style="padding: 10%" id="ra"
|
||||
onClick="
|
||||
getNextEntry()
|
||||
setDisabledForViewingNextPrevBttons()
|
||||
ViewImageOrVideo()
|
||||
">
|
||||
<svg width="16" height="16" fill="currentColor">
|
||||
<use xlink:href="{{url_for('internal', filename='icons.svg')}}#next"/></svg>
|
||||
</button>
|
||||
</div class="row">
|
||||
{# use this for color of toggles: https://www.codeply.com/p/4sL9uhevwJ #}
|
||||
<div class="row">
|
||||
{# this whole div, just takes up the same space as the left button and is hidden for alignment only #}
|
||||
<div class="col-auto px-0">
|
||||
<button class="btn btn-outline-info px-2 invisible" disabled>
|
||||
<svg width="16" height="16" fill="currentColor"><use xlink:href="{{url_for('internal', filename='icons.svg')}}#next"/></svg>
|
||||
</button>
|
||||
</div>
|
||||
<span class="col-auto my-auto">Show:</span>
|
||||
<div title="Toggle showing filename (hotkey: n)" class="d-flex form-check form-switch border border-info rounded col col-auto my-auto py-1 justify-content-center ps-5">
|
||||
<input class="form-check-input" type="checkbox" id="fname_toggle" onChange="$('.figure-caption').toggle()" checked>
|
||||
<label class="form-check-label ps-1" for="fname_toggle">Filename</label>
|
||||
</div>
|
||||
<div title="Toggle showing matched faces (hotkey: f)" class="d-flex form-check form-switch border border-info rounded col col-auto my-auto py-1 justify-content-center ps-5">
|
||||
<input class="form-check-input" type="checkbox" onChange="FaceToggle()" id="faces">
|
||||
<label class="form-check-label ps-1" for="faces">Faces</label>
|
||||
</div>
|
||||
<div title="Toggle showing 'distance' on matched faces (hotkey: d)" class="d-flex form-check form-switch border border-info rounded col col-auto my-auto py-1 justify-content-center ps-5">
|
||||
<input class="form-check-input" type="checkbox" onChange="DrawImg()" id="distance">
|
||||
<label class="form-check-label ps-1" for="distance">Distance</label>
|
||||
</div>
|
||||
<div title="Change the model used to detect faces" class="col col-auto my-auto">
|
||||
AI Model:
|
||||
{# can use 0 as default, it will be (re)set correctly in DrawImg() anyway #}
|
||||
{{CreateSelect( "model", 0, ["N/A", "normal", "slow/accurate"], "", "rounded norm-txt", [0,1,2])|safe }}
|
||||
</div>
|
||||
<div class="col col-auto pt-1">
|
||||
<button class="btn btn-outline-info p-1" title="Rotate by 90 degrees" onClick="Transform(90)">
|
||||
<img src="{{url_for('internal', filename='rot90.png')}}" width="32" height="32" onMouseOver="this.src='{{url_for('internal', filename='rot90-invert.png')}}'"
|
||||
onMouseOut="this.src='{{url_for('internal', filename='rot90.png')}}'" />
|
||||
</button>
|
||||
<button class="btn btn-outline-info p-1" title="Rotate by 180 degrees" onClick="Transform(180)">
|
||||
<img src="{{url_for('internal', filename='rot180.png')}}" width="32" height="32" onMouseOver="this.src='{{url_for('internal', filename='rot180-invert.png')}}'"
|
||||
onMouseOut="this.src='{{url_for('internal', filename='rot180.png')}}'" />
|
||||
</button>
|
||||
<button class="btn btn-outline-info p-1" title="Rotate by 270 degrees" onClick="Transform(270)">
|
||||
<img src="{{url_for('internal', filename='rot270.png')}}" width="32" height="32" onMouseOver="this.src='{{url_for('internal', filename='rot270-invert.png')}}'"
|
||||
onMouseOut="this.src='{{url_for('internal', filename='rot270.png')}}'" />
|
||||
</button>
|
||||
<button class="btn btn-outline-info p-1" title="Flip horizontally" onClick="Transform('fliph')">
|
||||
<svg width="32" height="32" fill="currentColor"><use xlink:href="{{url_for('internal', filename='icons.svg')}}#flip_h"/></svg>
|
||||
</button>
|
||||
<button class="btn btn-outline-info p-1" title="Flip vertically" onClick="Transform('flipv')">
|
||||
<svg width="32" height="32" fill="currentColor"><use xlink:href="{{url_for('internal', filename='icons.svg')}}#flip_v"/></svg>
|
||||
</button>
|
||||
<button class="btn btn-outline-info p-1" title="View in Fullscreen mode (hotkey: F)" onClick="fullscreen=true; ViewImageOrVideo()">
|
||||
<svg width="32" height="32" fill="currentColor"><use xlink:href="{{url_for('internal', filename='icons.svg')}}#fullscreen"/></svg>
|
||||
</button>
|
||||
<button class="btn btn-outline-info p-1" title="Show logs relating to this filename (hotkey: l)" onClick="JoblogSearch()">
|
||||
<svg width="32" height="32" fill="currentColor"><use xlink:href="{{url_for('internal', filename='icons.svg')}}#log"/></svg>
|
||||
</button>
|
||||
<button class="btn btn-outline-info p-1" title="View Original" onClick="window.location='/'+document.viewing.FullPathOnFS">
|
||||
<svg width="32" height="32" fill="currentColor"><use xlink:href="{{url_for('internal', filename='icons.svg')}}#download"/></svg>
|
||||
</button>
|
||||
<button id="del" class="btn btn-outline-danger p-1" title="Delete (hotkey: Del)"
|
||||
onClick="$.ajax({ type: 'POST', data: '&eid-0='+document.viewing.id, url: '/delete_files', success: function(data){ window.location='/'; return false; } })">
|
||||
<svg width="32" height="32" fill="currentColor"><use xlink:href="{{url_for('internal', filename='icons.svg')}}#trash"/></svg>
|
||||
</button>
|
||||
</div>
|
||||
</div class="row">
|
||||
</div id="viewer">
|
||||
</div id="viewer_div">
|
||||
{% endblock main_content %}
|
||||
|
||||
{% block script_content %}
|
||||
<script>
|
||||
// FIXME: MOVE the majorty of below to files_support/view_support. MIGHT help to have urls go into a global too, and then can ignore many of the above items needing url_for
|
||||
$( document ).keydown(function(event) {
|
||||
// if dbox is visible, dont process this hot-key, we are inputting text
|
||||
// into inputs instead
|
||||
if( $("#dbox").is(':visible') )
|
||||
return
|
||||
switch (event.key)
|
||||
{
|
||||
case "Left": // IE/Edge specific value
|
||||
case "ArrowLeft":
|
||||
if( $('#la').prop('disabled') == false )
|
||||
$('#la').click()
|
||||
break;
|
||||
case "Right": // IE/Edge specific value
|
||||
case "ArrowRight":
|
||||
if( $('#ra').prop('disabled') == false )
|
||||
$('#ra').click()
|
||||
break;
|
||||
case "d":
|
||||
$('#distance').click()
|
||||
break;
|
||||
case "f":
|
||||
$('#faces').click()
|
||||
break;
|
||||
case "n":
|
||||
$('#fname_toggle').click()
|
||||
break;
|
||||
case "F":
|
||||
fullscreen=!document.fullscreen
|
||||
ViewImageOrVideo()
|
||||
break;
|
||||
case "l":
|
||||
JoblogSearch()
|
||||
break;
|
||||
case "Delete":
|
||||
$('#del').click()
|
||||
default:
|
||||
return; // Quit when this doesn't handle the key event.
|
||||
}
|
||||
});
|
||||
|
||||
$(document).on('click', function(e) { $('.highlight').removeClass('highlight') ; SetButtonState() });
|
||||
|
||||
function dblClickToViewEntry(id) {
|
||||
$('#files_div').addClass('d-none')
|
||||
$('#viewer_div').removeClass('d-none')
|
||||
setEntryById( id )
|
||||
ViewImageOrVideo()
|
||||
}
|
||||
|
||||
// different context menu on files
|
||||
$.contextMenu({
|
||||
selector: '.entry',
|
||||
itemClickEvent: "click",
|
||||
build: function($triggerElement, e) {
|
||||
// when right-clicking & no selection add one OR deal with ctrl/shift right-lick as it always changes seln
|
||||
if( NoSel() || e.ctrlKey || e.shiftKey )
|
||||
{
|
||||
DoSel(e, e.currentTarget )
|
||||
SetButtonState();
|
||||
}
|
||||
|
||||
if( FiguresOrDirsOrBoth() == "figure" )
|
||||
{
|
||||
item_list = {
|
||||
details: { name: "Details..." },
|
||||
view: { name: "View File" },
|
||||
sep: "---",
|
||||
}
|
||||
if( e.currentTarget.getAttribute('type') == 'Image' )
|
||||
// different context menu on files
|
||||
$.contextMenu({
|
||||
selector: '.entry',
|
||||
itemClickEvent: "click",
|
||||
build: function($triggerElement, e) {
|
||||
// when right-clicking & no selection add one OR deal with ctrl/shift right-lick as it always changes seln
|
||||
if( NoSel() || e.ctrlKey || e.shiftKey )
|
||||
{
|
||||
item_list['transform'] = {
|
||||
name: "Transform",
|
||||
DoSel(e, e.currentTarget )
|
||||
SetButtonState();
|
||||
}
|
||||
|
||||
if( FiguresOrDirsOrBoth() == "figure" )
|
||||
{
|
||||
item_list = {
|
||||
details: { name: "Details..." },
|
||||
view: { name: "View File" },
|
||||
sep: "---",
|
||||
}
|
||||
if( e.currentTarget.getAttribute('type') == 'Image' )
|
||||
{
|
||||
item_list['transform'] = {
|
||||
name: "Transform",
|
||||
items: {
|
||||
"r90": { "name" : "Rotate 90 degrees" },
|
||||
"r180": { "name" : "Rotate 180 degrees" },
|
||||
"r270": { "name" : "Rotate 270 degrees" },
|
||||
"fliph": { "name" : "Flip horizontally" },
|
||||
"flipv": { "name" : "Flip vertically" }
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
item_list['move'] = { name: "Move selected file(s) to new folder" }
|
||||
item_list['sep2'] = { sep: "---" }
|
||||
}
|
||||
else
|
||||
item_list = {
|
||||
move: { name: "Move selection(s) to new folder" }
|
||||
}
|
||||
|
||||
item_list['ai'] = {
|
||||
name: "Scan file for faces",
|
||||
items: {
|
||||
"r90": { "name" : "Rotate 90 degrees" },
|
||||
"r180": { "name" : "Rotate 180 degrees" },
|
||||
"r270": { "name" : "Rotate 270 degrees" },
|
||||
"fliph": { "name" : "Flip horizontally" },
|
||||
"flipv": { "name" : "Flip vertically" }
|
||||
"ai-all": {"name": "all"},
|
||||
{% for p in people %}
|
||||
"ai-{{p.tag}}": {"name": "{{p.tag}}"},
|
||||
{% endfor %}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
if( SelContainsBinAndNotBin() ) {
|
||||
item_list['both']= { name: 'Cannot delete and restore at same time', disabled: true }
|
||||
} else {
|
||||
if (e.currentTarget.getAttribute('path_type') == 'Bin' )
|
||||
item_list['undel']= { name: "Restore selected file(s)" }
|
||||
else if( e.currentTarget.getAttribute('type') != 'Directory' )
|
||||
item_list['del']= { name: "Delete Selected file(s)" }
|
||||
}
|
||||
item_list['move'] = { name: "Move selected file(s) to new folder" }
|
||||
item_list['sep2'] = { sep: "---" }
|
||||
}
|
||||
else
|
||||
item_list = {
|
||||
move: { name: "Move selection(s) to new folder" }
|
||||
}
|
||||
|
||||
item_list['ai'] = {
|
||||
name: "Scan file for faces",
|
||||
items: {
|
||||
"ai-all": {"name": "all"},
|
||||
{% for p in people %}
|
||||
"ai-{{p.tag}}": {"name": "{{p.tag}}"},
|
||||
{% endfor %}
|
||||
}
|
||||
}
|
||||
|
||||
if( SelContainsBinAndNotBin() ) {
|
||||
item_list['both']= { name: 'Cannot delete and restore at same time', disabled: true }
|
||||
} else {
|
||||
if (e.currentTarget.getAttribute('path_type') == 'Bin' )
|
||||
item_list['undel']= { name: "Restore selected file(s)" }
|
||||
else if( e.currentTarget.getAttribute('type') != 'Directory' )
|
||||
item_list['del']= { name: "Delete Selected file(s)" }
|
||||
|
||||
return {
|
||||
callback: function( key, options) {
|
||||
if( key == "details" ) { DetailsDBox() }
|
||||
if( key == "view" ) { dblClickToViewEntry( $(this).attr('id') ) }
|
||||
if( key == "move" ) { MoveDBox(move_paths, "{{url_for('internal', filename='icons.svg')}}") }
|
||||
if( key == "del" ) { DelDBox('Delete') }
|
||||
if( key == "undel") { DelDBox('Restore') }
|
||||
if( key == "r90" ) { Transform(90) }
|
||||
if( key == "r180" ) { Transform(180) }
|
||||
if( key == "r270" ) { Transform(270) }
|
||||
if( key == "fliph" ) { Transform("fliph") }
|
||||
if( key == "flipv" ) { Transform("flipv") }
|
||||
if( key.startsWith("ai")) { RunAIOnSeln(key) }
|
||||
// dont flow this event through the dom
|
||||
e.stopPropagation()
|
||||
},
|
||||
items: item_list
|
||||
};
|
||||
}
|
||||
});
|
||||
|
||||
return {
|
||||
callback: function( key, options) {
|
||||
if( key == "details" ) { DetailsDBox() }
|
||||
if( key == "view" ) { dblClickToViewEntry( $(this).attr('id') ) }
|
||||
if( key == "move" ) { MoveDBox(move_paths, "{{url_for('internal', filename='icons.svg')}}") }
|
||||
if( key == "del" ) { DelDBox('Delete') }
|
||||
if( key == "undel") { DelDBox('Restore') }
|
||||
if( key == "r90" ) { Transform(90) }
|
||||
if( key == "r180" ) { Transform(180) }
|
||||
if( key == "r270" ) { Transform(270) }
|
||||
if( key == "fliph" ) { Transform("fliph") }
|
||||
if( key == "flipv" ) { Transform("flipv") }
|
||||
if( key.startsWith("ai")) { RunAIOnSeln(key) }
|
||||
// dont flow this event through the dom
|
||||
e.stopPropagation()
|
||||
},
|
||||
items: item_list
|
||||
};
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
$( document ).keydown(function(event) {
|
||||
switch (event.key)
|
||||
$( document ).keydown(function(event) { switch (event.key)
|
||||
{
|
||||
case "Delete":
|
||||
{% if "files_rbp" in request.url %}
|
||||
@@ -511,13 +366,16 @@ $( document ).keydown(function(event) {
|
||||
break;
|
||||
} })
|
||||
|
||||
if( isMobile() )
|
||||
{
|
||||
$('#shift-key').css('visibility', 'visible');
|
||||
$('#ctrl-key').css('visibility', 'visible');
|
||||
}
|
||||
if( isMobile() )
|
||||
{
|
||||
$('#shift-key').css('visibility', 'visible');
|
||||
$('#ctrl-key').css('visibility', 'visible');
|
||||
}
|
||||
|
||||
// check the size radiobutton
|
||||
$(`input[name="size"][value="${OPT.size}"]`).prop('checked', true)
|
||||
// check the size radiobutton
|
||||
$(`input[name="size"][value="${OPT.size}"]`).prop('checked', true)
|
||||
|
||||
window.addEventListener('resize', DrawImg, false);
|
||||
window.addEventListener('resize', ResizeVideo, false);
|
||||
</script>
|
||||
{% endblock script_content %}
|
||||
|
||||
Reference in New Issue
Block a user