viewer now works for files_ip, still have broken bits everywhere - files_rbp, change_opts, do I want a back button? lots of dead/old code, probably cam move more js into *_support, and do I want to keep files_support separate to view_support
This commit is contained in:
123
files.py
123
files.py
@@ -1,8 +1,10 @@
|
|||||||
from flask_wtf import FlaskForm
|
from flask_wtf import FlaskForm
|
||||||
from flask import request, render_template, redirect, send_from_directory, url_for, jsonify, make_response
|
from flask import request, render_template, redirect, send_from_directory, url_for, jsonify, make_response
|
||||||
|
from marshmallow import Schema, fields
|
||||||
from main import db, app, ma
|
from main import db, app, ma
|
||||||
from sqlalchemy import Sequence, text, select
|
from sqlalchemy import Sequence, text, select
|
||||||
from sqlalchemy.exc import SQLAlchemyError
|
from sqlalchemy.exc import SQLAlchemyError
|
||||||
|
from sqlalchemy.orm import joinedload
|
||||||
import os
|
import os
|
||||||
import glob
|
import glob
|
||||||
import json
|
import json
|
||||||
@@ -172,16 +174,43 @@ class FileTypeSchema(ma.SQLAlchemyAutoSchema):
|
|||||||
class Meta: model = FileType
|
class Meta: model = FileType
|
||||||
load_instance = True
|
load_instance = True
|
||||||
|
|
||||||
class FileSchema(ma.SQLAlchemyAutoSchema):
|
|
||||||
class Meta: model = File
|
|
||||||
load_instance = True
|
|
||||||
|
|
||||||
class DirSchema(ma.SQLAlchemyAutoSchema):
|
class DirSchema(ma.SQLAlchemyAutoSchema):
|
||||||
class Meta: model = Dir
|
class Meta: model = Dir
|
||||||
load_instance = True
|
load_instance = True
|
||||||
eid = ma.auto_field() # Explicitly include eid
|
eid = ma.auto_field() # Explicitly include eid
|
||||||
in_path = ma.Nested(PathSchema)
|
in_path = ma.Nested(PathSchema)
|
||||||
|
|
||||||
|
class FaceFileLinkSchema(ma.SQLAlchemyAutoSchema):
|
||||||
|
class Meta: model = FaceFileLink
|
||||||
|
load_instance = True
|
||||||
|
|
||||||
|
class PersonSchema(ma.SQLAlchemyAutoSchema):
|
||||||
|
class Meta: model=Person
|
||||||
|
load_instance = True
|
||||||
|
|
||||||
|
class RefimgSchema(ma.SQLAlchemyAutoSchema):
|
||||||
|
class Meta:
|
||||||
|
model = Refimg
|
||||||
|
exclude = ('face',)
|
||||||
|
load_instance = True
|
||||||
|
person = ma.Nested(PersonSchema)
|
||||||
|
|
||||||
|
class FaceRefimgLinkSchema(ma.SQLAlchemyAutoSchema):
|
||||||
|
class Meta: model = FaceRefimgLink
|
||||||
|
load_instance = True
|
||||||
|
|
||||||
|
class FaceSchema(ma.SQLAlchemyAutoSchema):
|
||||||
|
class Meta:
|
||||||
|
model=Face
|
||||||
|
exclude = ('face',)
|
||||||
|
load_instance = True
|
||||||
|
refimg = ma.Nested(RefimgSchema,allow_none=True)
|
||||||
|
|
||||||
|
class FileSchema(ma.SQLAlchemyAutoSchema):
|
||||||
|
class Meta: model = File
|
||||||
|
load_instance = True
|
||||||
|
faces = ma.Nested(FaceSchema,many=True,allow_none=True)
|
||||||
|
|
||||||
################################################################################
|
################################################################################
|
||||||
# Schema for Entry so we can json for data to the client
|
# Schema for Entry so we can json for data to the client
|
||||||
################################################################################
|
################################################################################
|
||||||
@@ -191,11 +220,19 @@ class EntrySchema(ma.SQLAlchemyAutoSchema):
|
|||||||
load_instance = True
|
load_instance = True
|
||||||
|
|
||||||
type = ma.Nested(FileTypeSchema)
|
type = ma.Nested(FileTypeSchema)
|
||||||
file_details = ma.Nested(FileSchema)
|
file_details = ma.Nested(FileSchema,allow_none=True)
|
||||||
# noting dir_details needs in_path to work
|
# noting dir_details needs in_path to work
|
||||||
dir_details = ma.Nested(DirSchema)
|
dir_details = ma.Nested(DirSchema)
|
||||||
# noting in_dir needs in_path and in_path.type to work
|
# noting in_dir needs in_path and in_path.type to work
|
||||||
in_dir = ma.Nested(DirSchema)
|
in_dir = ma.Nested(DirSchema)
|
||||||
|
# allow us to use FullPathOnFS()
|
||||||
|
FullPathOnFS = fields.Method("get_full_path")
|
||||||
|
|
||||||
|
def get_full_path(self, obj):
|
||||||
|
return obj.FullPathOnFS()
|
||||||
|
|
||||||
|
# global - this will be use more than once below, so do it once for efficiency
|
||||||
|
entries_schema = EntrySchema(many=True)
|
||||||
|
|
||||||
################################################################################
|
################################################################################
|
||||||
# util function to just update the current/first/last positions needed for
|
# util function to just update the current/first/last positions needed for
|
||||||
@@ -396,14 +433,26 @@ def process_ids():
|
|||||||
# DDP: debate here, do I get query_id, do I validate whether we are asking
|
# DDP: debate here, do I get query_id, do I validate whether we are asking
|
||||||
# for ids not in the query? OR, dont even make/store/have query?
|
# for ids not in the query? OR, dont even make/store/have query?
|
||||||
|
|
||||||
# marshmallow will allow us to json the data the way we need for the client
|
|
||||||
entries_schema = EntrySchema(many=True)
|
|
||||||
|
|
||||||
# Query DB for matching entries
|
# Query DB for matching entries
|
||||||
entries = Entry.query.filter(Entry.id.in_(ids)).all()
|
stmt = (
|
||||||
|
select(Entry)
|
||||||
|
.options(
|
||||||
|
joinedload(Entry.file_details).joinedload(File.faces),
|
||||||
|
joinedload(Entry.file_details).joinedload(File.faces).joinedload(Face.refimg).joinedload(Refimg.person)
|
||||||
|
)
|
||||||
|
.where(Entry.id.in_(ids))
|
||||||
|
)
|
||||||
|
|
||||||
# return entries as json
|
# unique as the ORM query returns a Cartesian product for the joins. E.g if file has 3 faces, the result has 3 rows of the same entry and file data, but different face data
|
||||||
return jsonify(entries_schema.dump(entries))
|
data=db.session.execute(stmt).unique().scalars().all()
|
||||||
|
|
||||||
|
# data is now in whatever order the DB returns- faster in python than DB supposedly. So, create a mapping from id to entry for quick lookup
|
||||||
|
entry_map = {entry.id: entry for entry in data}
|
||||||
|
|
||||||
|
# Sort the entries according to the order of ids
|
||||||
|
sorted_data = [entry_map[id_] for id_ in ids if id_ in entry_map]
|
||||||
|
|
||||||
|
return jsonify(entries_schema.dump(sorted_data))
|
||||||
|
|
||||||
|
|
||||||
################################################################################
|
################################################################################
|
||||||
@@ -806,6 +855,40 @@ def view_list():
|
|||||||
|
|
||||||
return make_response( resp )
|
return make_response( resp )
|
||||||
|
|
||||||
|
|
||||||
|
@login_required
|
||||||
|
@app.route("/newview/", methods=["POST"])
|
||||||
|
def newview():
|
||||||
|
data = request.get_json() # Parse JSON body
|
||||||
|
eid = data.get('eid', 0) # Extract list of ids
|
||||||
|
|
||||||
|
# need appropriate schema? to get FaceData with entry, lists should just be
|
||||||
|
# what we have in entryList so it can help with next/prev
|
||||||
|
|
||||||
|
# include Entry for name/path, ffl (model_used), frl (distance), Face (for w/h, etc), Person (id,tag)
|
||||||
|
#stmt=select(Entry).filter(Entry.id==eid)
|
||||||
|
|
||||||
|
stmt = (
|
||||||
|
select(Entry)
|
||||||
|
.options(
|
||||||
|
joinedload(Entry.file_details).joinedload(File.faces),
|
||||||
|
joinedload(Entry.file_details).joinedload(File.faces).joinedload(Face.refimg).joinedload(Refimg.person)
|
||||||
|
)
|
||||||
|
.where(Entry.id == eid)
|
||||||
|
)
|
||||||
|
|
||||||
|
print( stmt )
|
||||||
|
# this needs unique because:
|
||||||
|
# entry (one row for id=660)
|
||||||
|
# file (one row, since file_details is a one-to-one relationship)
|
||||||
|
# face (many rows, since a file can have many faces)
|
||||||
|
# refimg and person (one row per face, via the link tables)
|
||||||
|
# The SQL query returns a Cartesian product for the joins involving collections (like faces). For example, if your file has 3 faces,
|
||||||
|
# the result set will have 3 rows, each with the same entry and file data, but different face, refimg, and person data.
|
||||||
|
data=db.session.execute(stmt).unique().scalars().all()
|
||||||
|
print( data )
|
||||||
|
return jsonify(entries_schema.dump(data))
|
||||||
|
|
||||||
################################################################################
|
################################################################################
|
||||||
# /view/id -> grabs data from DB and views it (GET)
|
# /view/id -> grabs data from DB and views it (GET)
|
||||||
################################################################################
|
################################################################################
|
||||||
@@ -836,14 +919,16 @@ def view(id):
|
|||||||
eids=eids.rstrip(",")
|
eids=eids.rstrip(",")
|
||||||
# jic, sometimes we trip this, and rather than show broken pages / destroy
|
# jic, sometimes we trip this, and rather than show broken pages / destroy
|
||||||
if id not in eids:
|
if id not in eids:
|
||||||
SetFELog( message=f"ERROR: viewing an id, but its not in eids OPT={OPT}, id={id}, eids={eids}", level="danger", persistent=True, cant_close=False)
|
# SetFELog( message=f"ERROR: viewing an id, but its not in eids OPT={OPT}, id={id}, eids={eids}", level="danger", persistent=True, cant_close=False)
|
||||||
msg="Sorry, viewing data is confused, cannot view this image now"
|
# msg="Sorry, viewing data is confused, cannot view this image now"
|
||||||
if os.environ['ENV'] == "production":
|
# if os.environ['ENV'] == "production":
|
||||||
msg += "Clearing out all states. This means browser back buttons will not work, please start a new tab and try again"
|
# msg += "Clearing out all states. This means browser back buttons will not work, please start a new tab and try again"
|
||||||
PA_UserState.query.delete()
|
# PA_UserState.query.delete()
|
||||||
db.session.commit()
|
# db.session.commit()
|
||||||
SetFELog( msg, "warning", persistent=True, cant_close=False )
|
# SetFELog( msg, "warning", persistent=True, cant_close=False )
|
||||||
return redirect("/")
|
# return redirect("/")
|
||||||
|
print( f"id={id}, eids={eids}" )
|
||||||
|
return "200"
|
||||||
else:
|
else:
|
||||||
NMO_data = FaceOverrideType.query.all()
|
NMO_data = FaceOverrideType.query.all()
|
||||||
setting = Settings.query.first()
|
setting = Settings.query.first()
|
||||||
|
|||||||
@@ -523,15 +523,19 @@ function drawPageOfFigures()
|
|||||||
ecnt++
|
ecnt++
|
||||||
}
|
}
|
||||||
$('.figure').click( function(e) { DoSel(e, this ); SetButtonState(); return false; });
|
$('.figure').click( function(e) { DoSel(e, this ); SetButtonState(); return false; });
|
||||||
$('.figure').dblclick( CallViewRouteWrapper )
|
$('.figure').dblclick( function(e) { dblClickToViewEntry( $(this).attr('id') ) } )
|
||||||
// for dir, getDirEntries 2nd param is back (or "up" a dir)
|
// for dir, getDirEntries 2nd param is back (or "up" a dir)
|
||||||
$(".dir").click( function(e) { document.back_id=this.id; getDirEntries(this.id,false) } )
|
$(".dir").click( function(e) { document.back_id=this.id; getDirEntries(this.id,false) } )
|
||||||
$(".back").click( function(e) { getDirEntries(this.id,true) } )
|
$(".back").click( function(e) { getDirEntries(this.id,true) } )
|
||||||
}
|
}
|
||||||
|
|
||||||
// Function to get the 'page' of entry ids out of entryList
|
// Function to get the 'page' of entry ids out of entryList
|
||||||
function getPage(pageNumber)
|
function getPage(pageNumber,viewing_idx=0)
|
||||||
{
|
{
|
||||||
|
// before we do anything, disabled left/right arrows on viewer to stop
|
||||||
|
// getting another event before we have the data for the page back
|
||||||
|
$('#la').prop('disabled', true)
|
||||||
|
$('#ra').prop('disabled', true)
|
||||||
const startIndex = (pageNumber - 1) * OPT.howMany;
|
const startIndex = (pageNumber - 1) * OPT.howMany;
|
||||||
const endIndex = startIndex + OPT.howMany;
|
const endIndex = startIndex + OPT.howMany;
|
||||||
pageList = entryList.slice(startIndex, endIndex);
|
pageList = entryList.slice(startIndex, endIndex);
|
||||||
@@ -549,7 +553,13 @@ function getPage(pageNumber)
|
|||||||
dataType: 'json', // Expect JSON response
|
dataType: 'json', // Expect JSON response
|
||||||
success: function(res) {
|
success: function(res) {
|
||||||
document.entries=res
|
document.entries=res
|
||||||
|
// add all the figures to files_div
|
||||||
drawPageOfFigures()
|
drawPageOfFigures()
|
||||||
|
// noting we could have been in files_div, or viewer_div, update both jic
|
||||||
|
// and fix viewer_div - update viewing, arrows and image/video too
|
||||||
|
document.viewing=document.entries[viewing_idx]
|
||||||
|
resetNextPrevButtons()
|
||||||
|
ViewImageOrVideo()
|
||||||
},
|
},
|
||||||
error: function(xhr, status, error) {
|
error: function(xhr, status, error) {
|
||||||
console.error("Error:", error);
|
console.error("Error:", error);
|
||||||
|
|||||||
@@ -87,11 +87,11 @@ function DrawImg()
|
|||||||
|
|
||||||
// if we have faces, the enable the toggles, otherwise disable them
|
// if we have faces, the enable the toggles, otherwise disable them
|
||||||
// and reset model select too
|
// and reset model select too
|
||||||
if( objs[current].faces )
|
if( document.viewing.faces )
|
||||||
{
|
{
|
||||||
$('#faces').attr('disabled', false)
|
$('#faces').attr('disabled', false)
|
||||||
$('#distance').attr('disabled', false)
|
$('#distance').attr('disabled', false)
|
||||||
$('#model').val( Number(objs[current].face_model) )
|
$('#model').val( Number(document.viewing.face_model) )
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
@@ -102,33 +102,33 @@ function DrawImg()
|
|||||||
}
|
}
|
||||||
|
|
||||||
// okay, we want faces drawn so lets do it
|
// okay, we want faces drawn so lets do it
|
||||||
if( $('#faces').prop('checked') && objs[current].faces )
|
if( $('#faces').prop('checked') && document.viewing.faces )
|
||||||
{
|
{
|
||||||
// draw rect on each face
|
// draw rect on each face
|
||||||
for( i=0; i<objs[current].faces.length; i++ )
|
for( i=0; i<document.viewing.faces.length; i++ )
|
||||||
{
|
{
|
||||||
x = objs[current].faces[i].x / ( im.width/canvas.width )
|
x = document.viewing.faces[i].x / ( im.width/canvas.width )
|
||||||
y = objs[current].faces[i].y / ( im.height/canvas.height )
|
y = document.viewing.faces[i].y / ( im.height/canvas.height )
|
||||||
w = objs[current].faces[i].w / ( im.width/canvas.width )
|
w = document.viewing.faces[i].w / ( im.width/canvas.width )
|
||||||
h = objs[current].faces[i].h / ( im.height/canvas.height )
|
h = document.viewing.faces[i].h / ( im.height/canvas.height )
|
||||||
context.beginPath()
|
context.beginPath()
|
||||||
context.rect( x, y, w, h )
|
context.rect( x, y, w, h )
|
||||||
context.lineWidth = 2
|
context.lineWidth = 2
|
||||||
|
|
||||||
// this face has an override so diff colour
|
// this face has an override so diff colour
|
||||||
if( objs[current].faces[i].override )
|
if( document.viewing.faces[i].override )
|
||||||
{
|
{
|
||||||
context.strokeStyle = 'blue'
|
context.strokeStyle = 'blue'
|
||||||
DrawLabelOnFace( objs[current].faces[i].override.who )
|
DrawLabelOnFace( document.viewing.faces[i].override.who )
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
context.strokeStyle = 'green'
|
context.strokeStyle = 'green'
|
||||||
if( objs[current].faces[i].who )
|
if( document.viewing.faces[i].who )
|
||||||
{
|
{
|
||||||
str=objs[current].faces[i].who
|
str=document.viewing.faces[i].who
|
||||||
if( $('#distance').prop('checked') )
|
if( $('#distance').prop('checked') )
|
||||||
str += "("+objs[current].faces[i].distance+")"
|
str += "("+document.viewing.faces[i].distance+")"
|
||||||
DrawLabelOnFace( str )
|
DrawLabelOnFace( str )
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -156,13 +156,13 @@ function FaceToggle()
|
|||||||
// also deals with fullsecreen if needed
|
// also deals with fullsecreen if needed
|
||||||
function ViewImageOrVideo()
|
function ViewImageOrVideo()
|
||||||
{
|
{
|
||||||
if( objs[current].type == 'Image' )
|
if( document.viewing.type.name == 'Image' )
|
||||||
{
|
{
|
||||||
im.src='../' + objs[current].url
|
im.src='../' + document.viewing.FullPathOnFS
|
||||||
$('#video_div').hide()
|
$('#video_div').hide()
|
||||||
if( $('#fname_toggle').prop('checked' ) )
|
if( $('#fname_toggle').prop('checked' ) )
|
||||||
$('#img-cap').show()
|
$('#img-cap').show()
|
||||||
$('#fname_i').html(PrettyFname(objs[current].url))
|
$('#fname_i').html(PrettyFname(document.viewing.FullPathOnFS))
|
||||||
$('#figure').show()
|
$('#figure').show()
|
||||||
if( fullscreen )
|
if( fullscreen )
|
||||||
$('#canvas').get(0).requestFullscreen()
|
$('#canvas').get(0).requestFullscreen()
|
||||||
@@ -170,11 +170,11 @@ function ViewImageOrVideo()
|
|||||||
if( document.fullscreen )
|
if( document.fullscreen )
|
||||||
document.exitFullscreen()
|
document.exitFullscreen()
|
||||||
}
|
}
|
||||||
if( objs[current].type == 'Video' )
|
if( document.viewing.type.name == 'Video' )
|
||||||
{
|
{
|
||||||
$('#figure').hide()
|
$('#figure').hide()
|
||||||
$('#video').prop('src', '../' + objs[current].url )
|
$('#video').prop('src', '../' + document.viewing.FullPathOnFS )
|
||||||
$('#fname_v').html(PrettyFname(objs[current].url))
|
$('#fname_v').html(PrettyFname(document.viewing.FullPathOnFS))
|
||||||
if( $('#fname_toggle').prop('checked' ) )
|
if( $('#fname_toggle').prop('checked' ) )
|
||||||
$('#img-cap').hide()
|
$('#img-cap').hide()
|
||||||
ResizeVideo()
|
ResizeVideo()
|
||||||
@@ -221,32 +221,32 @@ $(document).ready( function()
|
|||||||
|
|
||||||
item_list = { not_a_face: { name: "Not a face", which_face: '-1' } }
|
item_list = { not_a_face: { name: "Not a face", which_face: '-1' } }
|
||||||
|
|
||||||
for( i=0; i<objs[current].faces.length; i++ )
|
for( i=0; i<document.viewing.faces.length; i++ )
|
||||||
{
|
{
|
||||||
fx = objs[current].faces[i].x / ( im.width/canvas.width )
|
fx = document.viewing.faces[i].x / ( im.width/canvas.width )
|
||||||
fy = objs[current].faces[i].y / ( im.height/canvas.height )
|
fy = document.viewing.faces[i].y / ( im.height/canvas.height )
|
||||||
fw = objs[current].faces[i].w / ( im.width/canvas.width )
|
fw = document.viewing.faces[i].w / ( im.width/canvas.width )
|
||||||
fh = objs[current].faces[i].h / ( im.height/canvas.height )
|
fh = document.viewing.faces[i].h / ( im.height/canvas.height )
|
||||||
|
|
||||||
if( x >= fx && x <= fx+fw && y >= fy && y <= fy+fh )
|
if( x >= fx && x <= fx+fw && y >= fy && y <= fy+fh )
|
||||||
{
|
{
|
||||||
if( objs[current].faces[i].override )
|
if( document.viewing.faces[i].override )
|
||||||
{
|
{
|
||||||
item_list['remove_force_match_override']={ 'name': 'Remove override for this face', 'which_face': i, 'id': objs[current].faces[i].id }
|
item_list['remove_force_match_override']={ 'name': 'Remove override for this face', 'which_face': i, 'id': document.viewing.faces[i].id }
|
||||||
}
|
}
|
||||||
else if( objs[current].faces[i].who )
|
else if( document.viewing.faces[i].who )
|
||||||
{
|
{
|
||||||
item_list['match']={ 'name': objs[current].faces[i].who, 'which_face': i, 'id': objs[current].faces[i].id }
|
item_list['match']={ 'name': document.viewing.faces[i].who, 'which_face': i, 'id': document.viewing.faces[i].id }
|
||||||
item_list['match_add_refimg']={ 'name': 'Add this as refimg for ' + objs[current].faces[i].who,
|
item_list['match_add_refimg']={ 'name': 'Add this as refimg for ' + document.viewing.faces[i].who,
|
||||||
'person_id': objs[current].faces[i].pid, 'who': objs[current].faces[i].who, 'which_face': i, 'id': objs[current].faces[i].id, }
|
'person_id': document.viewing.faces[i].pid, 'who': document.viewing.faces[i].who, 'which_face': i, 'id': document.viewing.faces[i].id, }
|
||||||
item_list['wrong_person']={ 'name': 'wrong person', 'which_face': i, 'id': objs[current].faces[i].id }
|
item_list['wrong_person']={ 'name': 'wrong person', 'which_face': i, 'id': document.viewing.faces[i].id }
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
item_list['no_match_new_person']={ 'name': 'Add as reference image to NEW person', 'which_face': i, 'id': objs[current].faces[i].id }
|
item_list['no_match_new_person']={ 'name': 'Add as reference image to NEW person', 'which_face': i, 'id': document.viewing.faces[i].id }
|
||||||
item_list['no_match_new_refimg']={ 'name': 'Add as reference image to EXISTING person', 'which_face': i, 'id': objs[current].faces[i].id }
|
item_list['no_match_new_refimg']={ 'name': 'Add as reference image to EXISTING person', 'which_face': i, 'id': document.viewing.faces[i].id }
|
||||||
for( var el in NMO ) {
|
for( var el in NMO ) {
|
||||||
item_list['NMO_'+el]={'type_id': NMO[el].type_id, 'name': 'Override: ' + NMO[el].name, 'which_face': i, 'id': objs[current].faces[i].id }
|
item_list['NMO_'+el]={'type_id': NMO[el].type_id, 'name': 'Override: ' + NMO[el].name, 'which_face': i, 'id': document.viewing.faces[i].id }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
delete item_list['not_a_face']
|
delete item_list['not_a_face']
|
||||||
@@ -280,11 +280,11 @@ function OverrideForceMatch( person_id, key )
|
|||||||
}
|
}
|
||||||
ofm='&person_id='+person_id+'&face_id='+item[key].id
|
ofm='&person_id='+person_id+'&face_id='+item[key].id
|
||||||
$.ajax({ type: 'POST', data: ofm, url: '/add_force_match_override', success: function(data) {
|
$.ajax({ type: 'POST', data: ofm, url: '/add_force_match_override', success: function(data) {
|
||||||
objs[current].faces[item[key].which_face].override={}
|
document.viewing.faces[item[key].which_face].override={}
|
||||||
objs[current].faces[item[key].which_face].override.who=data.person_tag
|
document.viewing.faces[item[key].which_face].override.who=data.person_tag
|
||||||
objs[current].faces[item[key].which_face].override.distance='N/A'
|
document.viewing.faces[item[key].which_face].override.distance='N/A'
|
||||||
objs[current].faces[item[key].which_face].override.type_id=NMO[fm_idx].id
|
document.viewing.faces[item[key].which_face].override.type_id=NMO[fm_idx].id
|
||||||
objs[current].faces[item[key].which_face].override.type_name=NMO[fm_idx].name
|
document.viewing.faces[item[key].which_face].override.type_name=NMO[fm_idx].name
|
||||||
|
|
||||||
$('#dbox').modal('hide')
|
$('#dbox').modal('hide')
|
||||||
$('#faces').prop('checked',true)
|
$('#faces').prop('checked',true)
|
||||||
@@ -303,8 +303,8 @@ function CreatePersonAndRefimg( key )
|
|||||||
+'&refimg_data='+item[key].refimg_data
|
+'&refimg_data='+item[key].refimg_data
|
||||||
$.ajax({ type: 'POST', data: d, url: '/match_with_create_person',
|
$.ajax({ type: 'POST', data: d, url: '/match_with_create_person',
|
||||||
success: function(data) {
|
success: function(data) {
|
||||||
objs[current].faces[item[key].which_face].who=data.who
|
document.viewing.faces[item[key].which_face].who=data.who
|
||||||
objs[current].faces[item[key].which_face].distance=data.distance
|
document.viewing.faces[item[key].which_face].distance=data.distance
|
||||||
$('#dbox').modal('hide')
|
$('#dbox').modal('hide')
|
||||||
$('#faces').prop('checked',true)
|
$('#faces').prop('checked',true)
|
||||||
DrawImg()
|
DrawImg()
|
||||||
@@ -318,8 +318,8 @@ function AddRefimgTo( person_id, key, search )
|
|||||||
d='&face_id='+item[key].id+'&person_id='+person_id+'&refimg_data='+item[key].refimg_data+'&search='+search
|
d='&face_id='+item[key].id+'&person_id='+person_id+'&refimg_data='+item[key].refimg_data+'&search='+search
|
||||||
$.ajax({ type: 'POST', data: d, url: '/add_refimg_to_person',
|
$.ajax({ type: 'POST', data: d, url: '/add_refimg_to_person',
|
||||||
success: function(data) {
|
success: function(data) {
|
||||||
objs[current].faces[item[key].which_face].who=data.who
|
document.viewing.faces[item[key].which_face].who=data.who
|
||||||
objs[current].faces[item[key].which_face].distance=data.distance
|
document.viewing.faces[item[key].which_face].distance=data.distance
|
||||||
$('#dbox').modal('hide')
|
$('#dbox').modal('hide')
|
||||||
$('#faces').prop('checked',true)
|
$('#faces').prop('checked',true)
|
||||||
DrawImg()
|
DrawImg()
|
||||||
@@ -367,15 +367,15 @@ function SearchForPerson(content, key, face_id, face_pos, type_id)
|
|||||||
|
|
||||||
function RemoveOverrideForceMatch(face_pos)
|
function RemoveOverrideForceMatch(face_pos)
|
||||||
{
|
{
|
||||||
if( objs[current].faces[face_pos].override )
|
if( document.viewing.faces[face_pos].override )
|
||||||
who=objs[current].faces[face_pos].override.who
|
who=document.viewing.faces[face_pos].override.who
|
||||||
else
|
else
|
||||||
who=objs[current].faces[face_pos].who
|
who=document.viewing.faces[face_pos].who
|
||||||
|
|
||||||
d='&face_id='+objs[current].faces[face_pos].id+'&person_tag='+who+'&file_eid='+current
|
d='&face_id='+document.viewing.faces[face_pos].id+'&person_tag='+who+'&file_eid='+current
|
||||||
$.ajax({ type: 'POST', data: d, url: '/remove_force_match_override',
|
$.ajax({ type: 'POST', data: d, url: '/remove_force_match_override',
|
||||||
success: function(data) {
|
success: function(data) {
|
||||||
delete objs[current].faces[face_pos].override
|
delete document.viewing.faces[face_pos].override
|
||||||
$('#dbox').modal('hide')
|
$('#dbox').modal('hide')
|
||||||
DrawImg()
|
DrawImg()
|
||||||
CheckForJobs()
|
CheckForJobs()
|
||||||
@@ -387,10 +387,10 @@ function RemoveOverrideForceMatch(face_pos)
|
|||||||
|
|
||||||
function RemoveOverrideNoMatch(face_pos, type_id)
|
function RemoveOverrideNoMatch(face_pos, type_id)
|
||||||
{
|
{
|
||||||
d='&face_id='+objs[current].faces[face_pos].id+'&type_id='+type_id
|
d='&face_id='+document.viewing.faces[face_pos].id+'&type_id='+type_id
|
||||||
$.ajax({ type: 'POST', data: d, url: '/remove_no_match_override',
|
$.ajax({ type: 'POST', data: d, url: '/remove_no_match_override',
|
||||||
success: function(data) {
|
success: function(data) {
|
||||||
delete objs[current].faces[face_pos].override
|
delete document.viewing.faces[face_pos].override
|
||||||
$('#dbox').modal('hide')
|
$('#dbox').modal('hide')
|
||||||
DrawImg()
|
DrawImg()
|
||||||
CheckForJobs()
|
CheckForJobs()
|
||||||
@@ -405,11 +405,11 @@ function AddNoMatchOverride(type_id, face_id, face_pos, type_id)
|
|||||||
d='&type_id='+type_id+'&face_id='+face_id
|
d='&type_id='+type_id+'&face_id='+face_id
|
||||||
$.ajax({ type: 'POST', data: d, url: '/add_no_match_override',
|
$.ajax({ type: 'POST', data: d, url: '/add_no_match_override',
|
||||||
success: function(data) {
|
success: function(data) {
|
||||||
objs[current].faces[face_pos].override={}
|
document.viewing.faces[face_pos].override={}
|
||||||
objs[current].faces[face_pos].override.who=NMO[type_id].name
|
document.viewing.faces[face_pos].override.who=NMO[type_id].name
|
||||||
objs[current].faces[face_pos].override.distance='N/A'
|
document.viewing.faces[face_pos].override.distance='N/A'
|
||||||
objs[current].faces[face_pos].override.type_id=type_id
|
document.viewing.faces[face_pos].override.type_id=type_id
|
||||||
objs[current].faces[face_pos].override.type_name=NMO[type_id].name
|
document.viewing.faces[face_pos].override.type_name=NMO[type_id].name
|
||||||
$('#dbox').modal('hide')
|
$('#dbox').modal('hide')
|
||||||
$('#faces').prop('checked',true)
|
$('#faces').prop('checked',true)
|
||||||
DrawImg()
|
DrawImg()
|
||||||
@@ -457,17 +457,17 @@ function FaceDBox(key, item)
|
|||||||
div+='</div><div class="col-6">'
|
div+='</div><div class="col-6">'
|
||||||
if ( key == 'remove_force_match_override' )
|
if ( key == 'remove_force_match_override' )
|
||||||
{
|
{
|
||||||
if( objs[current].faces[face_pos].override.type_name == 'Manual match to existing person' )
|
if( document.viewing.faces[face_pos].override.type_name == 'Manual match to existing person' )
|
||||||
div+='<div class="row col-12">remove this override (force match to: ' + objs[current].faces[face_pos].override.who + ')</div>'
|
div+='<div class="row col-12">remove this override (force match to: ' + document.viewing.faces[face_pos].override.who + ')</div>'
|
||||||
else
|
else
|
||||||
div+='<div class="row col-12">remove this override (no match)</div>'
|
div+='<div class="row col-12">remove this override (no match)</div>'
|
||||||
div+='<div class="row">'
|
div+='<div class="row">'
|
||||||
div+='<button class="btn btn-outline-info col-6" type="button" onClick="$(\'#dbox\').modal(\'hide\'); return false">Cancel</button>'
|
div+='<button class="btn btn-outline-info col-6" type="button" onClick="$(\'#dbox\').modal(\'hide\'); return false">Cancel</button>'
|
||||||
div+='<button class="btn btn-outline-danger col-6" type="button" '
|
div+='<button class="btn btn-outline-danger col-6" type="button" '
|
||||||
if( objs[current].faces[face_pos].override.type_name == 'Manual match to existing person' )
|
if( document.viewing.faces[face_pos].override.type_name == 'Manual match to existing person' )
|
||||||
div+='onClick="RemoveOverrideForceMatch(' +face_pos+ ')">Remove</button>'
|
div+='onClick="RemoveOverrideForceMatch(' +face_pos+ ')">Remove</button>'
|
||||||
else
|
else
|
||||||
div+='onClick="RemoveOverrideNoMatch(' +face_pos+','+objs[current].faces[face_pos].override.type_id+ ')">Remove</button>'
|
div+='onClick="RemoveOverrideNoMatch(' +face_pos+','+document.viewing.faces[face_pos].override.type_id+ ')">Remove</button>'
|
||||||
div+='</div>'
|
div+='</div>'
|
||||||
}
|
}
|
||||||
if ( key == 'no_match_new_person' )
|
if ( key == 'no_match_new_person' )
|
||||||
@@ -559,3 +559,8 @@ function JoblogSearch()
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function setVideoSource(newSrc) {
|
||||||
|
$('#videoSource').attr('src', newSrc);
|
||||||
|
$('#video')[0].load();
|
||||||
|
}
|
||||||
|
|||||||
@@ -21,6 +21,10 @@
|
|||||||
// TODO: reference these from GUI, so we can finally ditch the form to submit/change them.
|
// TODO: reference these from GUI, so we can finally ditch the form to submit/change them.
|
||||||
// BUT -- must handle noo changing with a form/post as it requires a new ordering
|
// BUT -- must handle noo changing with a form/post as it requires a new ordering
|
||||||
|
|
||||||
|
// this is which eid we are viewing an image/video (when we dbl-click & then next/prev)
|
||||||
|
document.viewing_eid=null;
|
||||||
|
document.viewing=null;
|
||||||
|
|
||||||
var OPT={}
|
var OPT={}
|
||||||
OPT.grouping='{{OPT.grouping}}'
|
OPT.grouping='{{OPT.grouping}}'
|
||||||
OPT.cwd='{{OPT.cwd}}'
|
OPT.cwd='{{OPT.cwd}}'
|
||||||
@@ -105,7 +109,7 @@
|
|||||||
</button>
|
</button>
|
||||||
<button style="visibility:hidden" class="btn btn-outline-secondary" aria-label="shift-key" id="shift-key" onclick="document.fake_shift=1-document.fake_shift; event.stopPropagation(); return false">shift</button>
|
<button style="visibility:hidden" class="btn btn-outline-secondary" aria-label="shift-key" id="shift-key" onclick="document.fake_shift=1-document.fake_shift; event.stopPropagation(); return false">shift</button>
|
||||||
<button style="visibility:hidden" class="btn btn-outline-secondary" aria-label="ctrl-key" id="ctrl-key" onclick="document.fake_ctrl=1-document.fake_ctrl; event.stopPropagation(); return false">ctrl</button>
|
<button style="visibility:hidden" class="btn btn-outline-secondary" aria-label="ctrl-key" id="ctrl-key" onclick="document.fake_ctrl=1-document.fake_ctrl; event.stopPropagation(); return false">ctrl</button>
|
||||||
</div>
|
</div class="col flex-grow-1">
|
||||||
<div class="d-flex col col-auto justify-content-end">
|
<div class="d-flex col col-auto justify-content-end">
|
||||||
<div class="btn-group">
|
<div class="btn-group">
|
||||||
{% if OPT.size == 64 %}
|
{% if OPT.size == 64 %}
|
||||||
@@ -139,143 +143,18 @@
|
|||||||
{% endif %}
|
{% endif %}
|
||||||
<button aria-label="extra large" id="256" class="px-2 sm-txt sz-but btn {{bt}}" onClick="$('#size').val(256)">XL</button>
|
<button aria-label="extra large" id="256" class="px-2 sm-txt sz-but btn {{bt}}" onClick="$('#size').val(256)">XL</button>
|
||||||
</div class="btn-group">
|
</div class="btn-group">
|
||||||
</div class="col">
|
</div class="d-flex col">
|
||||||
<input id="offset" type="hidden" name="offset" value="{{OPT.offset}}">
|
<input id="offset" type="hidden" name="offset" value="{{OPT.offset}}">
|
||||||
<input id="size" type="hidden" name="size" value="{{OPT.size}}">
|
<input id="size" type="hidden" name="size" value="{{OPT.size}}">
|
||||||
</div class="form-row">
|
</div class="d-flex row mb-2">
|
||||||
{% set eids=namespace( str="" ) %}
|
{% set eids=namespace( str="" ) %}
|
||||||
{# gather all the file eids and collect them in case we go gallery mode #}
|
{# gather all the file eids and collect them in case we go gallery mode #}
|
||||||
{% for obj in query_data.entry_list %}
|
{% for obj in query_data.entry_list %}
|
||||||
{% set eids.str = eids.str + obj|string +"," %}
|
{% set eids.str = eids.str + obj|string +"," %}
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
<input name="eids" id="eids" type="hidden" value="{{eids.str}}">
|
<input name="eids" id="eids" type="hidden" value="{{eids.str}}">
|
||||||
</div>
|
</div container="fluid">
|
||||||
{% set ecnt=namespace( val=0 ) %}
|
|
||||||
<div id="figures" class="row ms-2">
|
<div id="figures" class="row ms-2">
|
||||||
<!--
|
|
||||||
{% set last = namespace(printed=0) %}
|
|
||||||
{# rare event of empty folder, still need to show back button #}
|
|
||||||
{% if OPT.folders and entry_data|length == 0 %}
|
|
||||||
{% if OPT.cwd != OPT.root %}
|
|
||||||
<figure id="_back" class="dir entry m-1" ecnt="{{ecnt.val}}" dir="{{OPT.cwd|ParentPath}}" type="Directory">
|
|
||||||
<svg class="svg" width="{{OPT.size|int-22}}" height="{{OPT.size|int-22}}"><use xlink:href="{{url_for('internal', filename='icons.svg')}}#folder_back"/></svg>
|
|
||||||
<figcaption class="figure-caption text-center">Back</figcaption>
|
|
||||||
</figure class="figure">
|
|
||||||
{% set ecnt.val=ecnt.val+1 %}
|
|
||||||
<script>f=$('#_back'); w=f.find('svg').width(); f.find('figcaption').width(w);</script>
|
|
||||||
{% else %}
|
|
||||||
<div class="col col-auto g-0 m-1">
|
|
||||||
<svg class="svg" width="{{OPT.size|int-22}}" height="{{OPT.size|int-22}}"><use xlink:href="{{url_for('internal', filename='icons.svg')}}#folder_back_gray"/></svg>
|
|
||||||
</div>
|
|
||||||
{% endif %}
|
|
||||||
{% endif %}
|
|
||||||
{% if not entry_data %}
|
|
||||||
<span class="alert alert-danger p-2 col-auto"> No matches for: '{{search_term}}'</span>
|
|
||||||
{% endif %}
|
|
||||||
{% for obj in entry_data %}
|
|
||||||
{% if loop.index==1 and OPT.folders %}
|
|
||||||
{% if OPT.cwd != OPT.root %}
|
|
||||||
<figure class="col col-auto g-0 dir entry m-1" ecnt="{{ecnt.val}}" dir="{{OPT.cwd|ParentPath}}" type="Directory">
|
|
||||||
<svg class="svg" width="{{OPT.size|int-22}}" height="{{OPT.size|int-22}}" fill="currentColor">
|
|
||||||
<use xlink:href="{{url_for('internal', filename='icons.svg')}}#folder_back"/></svg>
|
|
||||||
<figcaption class="svg_cap figure-caption text-center">Back</figcaption>
|
|
||||||
</figure class="figure">
|
|
||||||
{% set ecnt.val=ecnt.val+1 %}
|
|
||||||
{% else %}
|
|
||||||
{# create an even lighter-grey, unclickable back button - so folders dont jump around when you go into them #}
|
|
||||||
<div class="col col-auto g-0 m-1">
|
|
||||||
<svg class="svg" width="{{OPT.size|int-22}}" height="{{OPT.size|int-22}}"><use xlink:href="{{url_for('internal', filename='icons.svg')}}#folder_back_gray"/></svg>
|
|
||||||
</div>
|
|
||||||
{% endif %}
|
|
||||||
{% endif %}
|
|
||||||
{% if not OPT.folders and obj.type.name == "Directory" %}
|
|
||||||
{% continue %}
|
|
||||||
{% endif %}
|
|
||||||
{% if OPT.grouping == "Day" %}
|
|
||||||
{% if last.printed != obj.file_details.day %}
|
|
||||||
<div class="row ps-3"><h6>Day: {{obj.file_details.day}} of {{obj.file_details.month}}/{{obj.file_details.year}}</h6></div>
|
|
||||||
{% set last.printed = obj.file_details.day %}
|
|
||||||
{% endif %}
|
|
||||||
{% elif OPT.grouping == "Week" %}
|
|
||||||
{% if last.printed != obj.file_details.woy %}
|
|
||||||
<div class="row ps-3"><h6>Week #: {{obj.file_details.woy}} of {{obj.file_details.year}}</h6></div>
|
|
||||||
{% set last.printed = obj.file_details.woy %}
|
|
||||||
{% endif %}
|
|
||||||
{% elif OPT.grouping == "Month" %}
|
|
||||||
{% if last.printed != obj.file_details.month %}
|
|
||||||
<div class="row ps-3"><h6>Month: {{obj.file_details.month}} of {{obj.file_details.year}}</h6></div>
|
|
||||||
{% set last.printed = obj.file_details.month %}
|
|
||||||
{% endif %}
|
|
||||||
{% endif %}
|
|
||||||
{% if obj.type.name == "Image" or obj.type.name == "Video" or obj.type.name == "Unknown" %}
|
|
||||||
{% if (not OPT.folders) or ((obj.in_dir.in_path.path_prefix+'/'+obj.in_dir.rel_path+'/'+obj.name) | TopLevelFolderOf(OPT.cwd)) %}
|
|
||||||
<figure id="{{obj.id}}" ecnt="{{ecnt.val}}" class="col col-auto g-0 figure entry m-1" path_type="{{obj.in_dir.in_path.type.name}}" size="{{obj.file_details.size_mb}}" hash="{{obj.file_details.hash}}" in_dir="{{obj.in_dir.in_path.path_prefix}}/{{obj.in_dir.rel_path}}" fname="{{obj.name}}" yr="{{obj.file_details.year}}" date="{{obj.file_details.year}}{{"%02d" % obj.file_details.month}}{{"%02d" % obj.file_details.day}}" pretty_date="{{obj.file_details.day}}/{{obj.file_details.month}}/{{obj.file_details.year}}" type="{{obj.type.name}}">
|
|
||||||
{% if obj.type.name=="Image" or obj.type.name=="Unknown" %}
|
|
||||||
<div style="position:relative; width:100%">
|
|
||||||
{% if obj.file_details.thumbnail %}
|
|
||||||
<a href="{{obj.in_dir.in_path.path_prefix}}/{{obj.in_dir.rel_path}}/{{obj.name}}">
|
|
||||||
<img alt="{{obj.name}}" class="thumb" height="{{OPT.size}}" src="data:image/jpeg;base64,{{obj.file_details.thumbnail}}"></img></a>
|
|
||||||
{% else %}
|
|
||||||
<a href="{{obj.in_dir.in_path.path_prefix}}/{{obj.in_dir.rel_path}}/{{obj.name}}">
|
|
||||||
<svg width="{{OPT.size}}" height="{{OPT.size}}" fill="white"><use xlink:href="{{url_for('internal', filename='icons.svg')}}#unknown_ftype"/></svg>
|
|
||||||
</a>
|
|
||||||
{% endif %}
|
|
||||||
{% if search_term is defined %}
|
|
||||||
<div style="position:absolute; bottom: 0px; left: 2px;">
|
|
||||||
<svg width="16" height="16" fill="white"><use xlink:href="{{url_for('internal', filename='icons.svg')}}#{{LocationIcon(obj)}}"/></svg>
|
|
||||||
</div>
|
|
||||||
{% endif %}
|
|
||||||
<div id="s{{obj.id}}" style="display:none; position:absolute; top: 50%; left:50%; transform:translate(-50%, -50%);">
|
|
||||||
<img height="64px" src="{{url_for('internal', filename='throbber.gif')}}"></img>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
{% elif obj.type.name == "Video" %}
|
|
||||||
<div style="position:relative; width:100%">
|
|
||||||
{% if obj.file_details.thumbnail %}
|
|
||||||
<a href="{{obj.in_dir.in_path.path_prefix}}/{{obj.in_dir.rel_path}}/{{obj.name}}">
|
|
||||||
<img alt="{{obj.name}}" class="thumb" height="{{OPT.size}}" src="data:image/jpeg;base64,{{obj.file_details.thumbnail}}"></img></a>
|
|
||||||
{% else %}
|
|
||||||
<a href="{{obj.in_dir.in_path.path_prefix}}/{{obj.in_dir.rel_path}}/{{obj.name}}">
|
|
||||||
<svg width="{{OPT.size}}" height="{{OPT.size}}" fill="white"><use xlink:href="{{url_for('internal', filename='icons.svg')}}#unknown_ftype"/></svg>
|
|
||||||
</a>
|
|
||||||
{% endif %}
|
|
||||||
|
|
||||||
<div style="position:absolute; top: 0px; left: 2px;">
|
|
||||||
<svg width="16" height="16" fill="white"><use xlink:href="{{url_for('internal', filename='icons.svg')}}#film"/></svg>
|
|
||||||
</div>
|
|
||||||
{% if search_term is defined %}
|
|
||||||
<div style="position:absolute; bottom: 0px; left: 2px;">
|
|
||||||
<svg width="16" height="16" fill="white"><use xlink:href="{{url_for('internal', filename='icons.svg')}}#{{LocationIcon(obj)}}"/></svg>
|
|
||||||
</div>
|
|
||||||
{% endif %}
|
|
||||||
</div>
|
|
||||||
{% endif %}
|
|
||||||
</figure>
|
|
||||||
{% set ecnt.val=ecnt.val+1 %}
|
|
||||||
{% endif %}
|
|
||||||
{% elif obj.type.name == "Directory" %}
|
|
||||||
{% if OPT.folders %}
|
|
||||||
{% if obj.dir_details.rel_path | length %}
|
|
||||||
{% set dirname=obj.dir_details.in_path.path_prefix+'/'+obj.dir_details.rel_path %}
|
|
||||||
{% else %}
|
|
||||||
{% set dirname=obj.dir_details.in_path.path_prefix %}
|
|
||||||
{% endif %}
|
|
||||||
{# if this dir is the toplevel of the cwd, show the folder icon #}
|
|
||||||
{% if dirname| TopLevelFolderOf(OPT.cwd) %}
|
|
||||||
<figure class="col col-auto g-0 dir entry m-1" id={{obj.id}} ecnt={{ecnt.val}} dir="{{dirname}}" type="Directory">
|
|
||||||
<svg class="svg" width="{{OPT.size|int-22}}" height="{{OPT.size|int-22}}" fill="currentColor">
|
|
||||||
<use xlink:href="{{url_for('internal', filename='icons.svg')}}#Directory"/></svg>
|
|
||||||
<figcaption class="svg_cap figure-caption text-center text-wrap text-break">{{obj.name}}</figcaption>
|
|
||||||
</figure class="figure">
|
|
||||||
{% set ecnt.val=ecnt.val+1 %}
|
|
||||||
<script>f=$('#{{obj.id}}'); w=f.find('svg').width(); f.find('figcaption').width(w);</script>
|
|
||||||
{% endif %}
|
|
||||||
{% endif %}
|
|
||||||
{% endif %}
|
|
||||||
{% endfor %}
|
|
||||||
|
|
||||||
-->
|
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
<div class="container-fluid">
|
<div class="container-fluid">
|
||||||
<input type="hidden" name="cwd" id="cwd" value="{{OPT.cwd}}">
|
<input type="hidden" name="cwd" id="cwd" value="{{OPT.cwd}}">
|
||||||
@@ -288,10 +167,273 @@
|
|||||||
<button aria-label="next" id="next" name="next" class="next sm-txt btn btn-outline-secondary" onClick="nextPage()">
|
<button aria-label="next" id="next" name="next" class="next sm-txt btn btn-outline-secondary" onClick="nextPage()">
|
||||||
<svg width="16" height="16" fill="currentColor"><use xlink:href="{{url_for('internal', filename='icons.svg')}}#next"/></svg>
|
<svg width="16" height="16" fill="currentColor"><use xlink:href="{{url_for('internal', filename='icons.svg')}}#next"/></svg>
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div class="col my-auto">
|
||||||
</div>
|
</div class="row">
|
||||||
</div class="container">
|
</div class="container-fluid">
|
||||||
</div id="files_div">
|
</div id="files_div">
|
||||||
|
<div id="viewer_div" class="d-none">
|
||||||
|
|
||||||
|
<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/view_transform.js')}}"></script>
|
||||||
|
<script src="{{ url_for( 'internal', filename='js/view_support.js')}}"></script>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
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}}"
|
||||||
|
|
||||||
|
function PrettyFname(fname)
|
||||||
|
{
|
||||||
|
s='<span class="alert alert-secondary py-2">'
|
||||||
|
if( fname.indexOf( "static/Import" ) == 0 )
|
||||||
|
{
|
||||||
|
s+='<svg width="20" height="20" fill="currentColor"><use xlink:href="{{url_for('internal', filename='icons.svg')}}#import"/></svg>'
|
||||||
|
tmp_path=fname.replace(imp_path,"" )
|
||||||
|
}
|
||||||
|
if( fname.indexOf( "static/Storage" ) == 0 )
|
||||||
|
{
|
||||||
|
s+='<svg width="20" height="20" fill="currentColor"><use xlink:href="{{url_for('internal', filename='icons.svg')}}#db"/></svg>'
|
||||||
|
tmp_path=fname.replace("static/Storage","" )
|
||||||
|
}
|
||||||
|
if( fname.indexOf( "static/Bin" ) == 0 )
|
||||||
|
{
|
||||||
|
s+='<svg width="20" height="20" fill="currentColor"><use xlink:href="{{url_for('internal', filename='icons.svg')}}#trash-fill"/></svg>'
|
||||||
|
tmp_path=fname.replace("static/Bin","" )
|
||||||
|
}
|
||||||
|
s+=tmp_path+'</span>'
|
||||||
|
return s
|
||||||
|
}
|
||||||
|
|
||||||
|
function getPreviousEntry() {
|
||||||
|
var currentIndex = entryList.indexOf(document.viewing.id);
|
||||||
|
|
||||||
|
oldPageOffset=Math.floor(currentIndex / OPT.howMany)
|
||||||
|
if (currentIndex > 0) {
|
||||||
|
currentIndex--;
|
||||||
|
pageOffset=Math.floor(currentIndex / OPT.howMany)
|
||||||
|
currentIndex=currentIndex-(pageOffset*OPT.howMany)
|
||||||
|
// pref page, load it
|
||||||
|
if( oldPageOffset != pageOffset )
|
||||||
|
// pref page is pageOffset+1 now
|
||||||
|
getPage(pageOffset+1,currentIndex)
|
||||||
|
else
|
||||||
|
document.viewing=document.entries[currentIndex]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function getNextEntry() {
|
||||||
|
var currentIndex = entryList.indexOf(document.viewing.id);
|
||||||
|
|
||||||
|
oldPageOffset=Math.floor(currentIndex / OPT.howMany)
|
||||||
|
if (currentIndex < entryList.length - 1) {
|
||||||
|
currentIndex++
|
||||||
|
pageOffset=Math.floor(currentIndex / OPT.howMany)
|
||||||
|
currentIndex=currentIndex-(pageOffset*OPT.howMany)
|
||||||
|
// next page, load it
|
||||||
|
if( oldPageOffset != pageOffset )
|
||||||
|
// next page is pageOffset+1 now
|
||||||
|
getPage(pageOffset+1,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 <= howMany
|
||||||
|
pageOffset=Math.floor(currentIndex / OPT.howMany)
|
||||||
|
currentIndex = currentIndex-(pageOffset*OPT.howMany)
|
||||||
|
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='/'+objs[current].url">
|
||||||
|
<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={{current}}', 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">
|
||||||
|
|
||||||
|
<script>
|
||||||
|
$( 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.
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
var fullscreen=false;
|
||||||
|
</script>
|
||||||
|
|
||||||
{% endblock main_content %}
|
{% endblock main_content %}
|
||||||
{% block script_content %}
|
{% block script_content %}
|
||||||
|
|
||||||
@@ -299,9 +441,16 @@
|
|||||||
|
|
||||||
$(document).on('click', function(e) { $('.highlight').removeClass('highlight') ; SetButtonState() });
|
$(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()
|
||||||
|
}
|
||||||
|
|
||||||
function CallViewRouteWrapper()
|
function CallViewRouteWrapper()
|
||||||
{
|
{
|
||||||
CallViewRoute( $(this).attr("id") )
|
// CallViewRoute( document.viewing.id )
|
||||||
}
|
}
|
||||||
|
|
||||||
function CallViewRoute(id)
|
function CallViewRoute(id)
|
||||||
|
|||||||
Reference in New Issue
Block a user