760 lines
28 KiB
JavaScript
760 lines
28 KiB
JavaScript
// image, then use the width of the image (with the specified gap) otherwise
|
|
// the height > width, so scale the new width based on height ratio of
|
|
// image to window
|
|
function NewWidth()
|
|
{
|
|
w_r=im.width/(window.innerWidth*gap)
|
|
h_r=im.height/(window.innerHeight*gap)
|
|
if( w_r > h_r )
|
|
return window.innerWidth*gap
|
|
else
|
|
return im.width*gap / (im.height/window.innerHeight)
|
|
}
|
|
|
|
// work out new height for canvas. depending on whether height > width of the
|
|
// image, then use the height of the image (with the specified gap) otherwise
|
|
// the width > height, so scale the new height based on width ratio of
|
|
// image to window
|
|
function NewHeight()
|
|
{
|
|
w_r=im.width/(window.innerWidth*gap)
|
|
h_r=im.height/(window.innerHeight*gap)
|
|
if( h_r > w_r )
|
|
return window.innerHeight*gap
|
|
else
|
|
return im.height*gap / (im.width/window.innerWidth)
|
|
}
|
|
|
|
// draw 'str' as a label above the bounding box of the face (with a white
|
|
// transparent background to enhance readability of str)
|
|
function DrawLabelOnFace(str)
|
|
{
|
|
// finish face box, need to clear out new settings for // transparent backed-name tag
|
|
context.stroke();
|
|
context.beginPath()
|
|
context.lineWidth = 0.1
|
|
context.font = "30px Arial"
|
|
context.globalAlpha = 0.6
|
|
|
|
bbox = context.measureText(str);
|
|
f_h=bbox.fontBoundingBoxAscent
|
|
if( bbox.fontBoundingBoxDescent )
|
|
f_h += bbox.fontBoundingBoxDescent
|
|
f_h -= 8
|
|
context.rect( x+w/2-bbox.width/2, y-f_h, bbox.width, f_h )
|
|
context.fillStyle="white"
|
|
context.fill()
|
|
context.stroke();
|
|
context.beginPath()
|
|
context.globalAlpha = 1.0
|
|
context.font = "30px Arial"
|
|
context.textAlign = "center"
|
|
context.fillStyle = context.strokeStyle
|
|
context.fillText(str, x+w/2, y-2)
|
|
}
|
|
|
|
// This draws the image, it can be called on resize events, img.src finishing
|
|
// loading or explicitly on page load. Will also deal with all state/toggles
|
|
// for items like name, grayscale, etc.
|
|
function DrawImg()
|
|
{
|
|
// another call to this func will occur on load, so skip this one
|
|
if( im.width == 0 )
|
|
return
|
|
|
|
// find any matching ammendment
|
|
am=document.amendments.filter(obj => obj.eid === document.viewing.id)
|
|
if( am.length )
|
|
am=am[0]
|
|
|
|
canvas.width=NewWidth(im)
|
|
canvas.height=NewHeight(im)
|
|
|
|
// dont let caption be wider than image
|
|
$('#img-cap').width(canvas.width)
|
|
|
|
// actually draw the pixel images to the canvas at the right size
|
|
if (!Array.isArray(am))
|
|
context.filter='grayscale(1)'
|
|
context.drawImage(im, 0, 0, canvas.width, canvas.height )
|
|
// -50 is a straight up hack, no idea why this works, but its good enough for me
|
|
if (!Array.isArray(am))
|
|
{
|
|
$('#throbber').show()
|
|
$('#white-circle').show()
|
|
if(am.type.which == 'img' )
|
|
{
|
|
$('#inside-img').attr('src', '/internal/'+am.type.what );
|
|
$('#inside-img').show()
|
|
}
|
|
else
|
|
{
|
|
$('#inside-icon').attr('style', `color:${am.type.colour};height:64px` )
|
|
$('#inside-icon').attr('fill', am.type.colour )
|
|
$('#inside-icon use').attr('xlink:href', `/internal/icons.svg#${am.type.what}`);
|
|
$('#inside-icon').show()
|
|
}
|
|
} else {
|
|
$('#throbber').hide()
|
|
$('#white-circle').hide()
|
|
$('#inside-img').hide()
|
|
$('#inside-icon').hide()
|
|
}
|
|
|
|
// show (or not) the whole figcaption with fname in it - based on state of fname_toggle
|
|
if( $('#fname_toggle').prop('checked' ) )
|
|
{
|
|
// reset fname for new image (if navigated left/right to get here)
|
|
$('.figcaption').show()
|
|
}
|
|
else
|
|
$('.figcaption').hide()
|
|
|
|
// if we have faces, the enable the toggles, otherwise disable them and reset model select too
|
|
if( document.viewing.file_details.faces.length )
|
|
{
|
|
$('#faces').attr('disabled', false)
|
|
$('#distance').attr('disabled', false)
|
|
// first face is good enough as whole file has to have used same model
|
|
$('#model').val( document.viewing.file_details.faces[0].facefile_lnk.model_used )
|
|
}
|
|
else
|
|
{
|
|
$('#faces').attr('disabled', true)
|
|
$('#distance').attr('disabled', true)
|
|
// if no faces, then model is N/A (always 1st element - or 0 in select)
|
|
$('#model').val(0)
|
|
}
|
|
|
|
// okay, we want faces drawn so lets do it
|
|
if( $('#faces').prop('checked') && document.viewing.file_details.faces )
|
|
{
|
|
faces=document.viewing.file_details.faces
|
|
// draw rect on each face
|
|
for( i=0; i<faces.length; i++ )
|
|
{
|
|
x = faces[i].face_left / ( im.width/canvas.width )
|
|
y = faces[i].face_top / ( im.height/canvas.height )
|
|
w = faces[i].w / ( im.width/canvas.width )
|
|
h = faces[i].h / ( im.height/canvas.height )
|
|
context.beginPath()
|
|
context.rect( x, y, w, h )
|
|
context.lineWidth = 2
|
|
|
|
// this face has an override so diff colour
|
|
if( faces[i].fnmo.length || faces[i].ffmo.length )
|
|
{
|
|
context.strokeStyle = 'blue'
|
|
if( faces[i].ffmo.length )
|
|
DrawLabelOnFace( faces[i].ffmo[0].person.tag )
|
|
else
|
|
DrawLabelOnFace( faces[i].fnmo[0].type.name )
|
|
}
|
|
else
|
|
{
|
|
context.strokeStyle = 'green'
|
|
if( faces[i].refimg )
|
|
{
|
|
str=faces[i].refimg.person.tag
|
|
if( $('#distance').prop('checked') )
|
|
str += "("+faces[i].refimg_lnk.face_distance.toFixed(2)+")"
|
|
DrawLabelOnFace( str )
|
|
}
|
|
}
|
|
context.stroke();
|
|
}
|
|
}
|
|
}
|
|
|
|
// resize a video based when event calls this -- TODO: is this right for ratios
|
|
// where vid is different w/h ratio?
|
|
function ResizeVideo()
|
|
{
|
|
$('#video').height(window.innerHeight*gap)
|
|
}
|
|
|
|
// change faces being shown or not (if not showing faces, then cant set distance on anyway)
|
|
// and a face or not, needs to redraw the image to add/remove green box, etc.
|
|
function FaceToggle()
|
|
{
|
|
$('#distance').prop('disabled', function(i, v) { return !v; });
|
|
DrawImg()
|
|
}
|
|
|
|
// func to set show/hide the image or video div, and set the URL appropriately
|
|
// also deals with fullsecreen if needed
|
|
function ViewImageOrVideo()
|
|
{
|
|
// can happen if no content to display
|
|
if( ! document.viewing ) return
|
|
if( document.viewing.type.name == 'Image' )
|
|
{
|
|
im.src='../' + document.viewing.FullPathOnFS + '?t=' + new Date().getTime();
|
|
$('#video_div').hide()
|
|
if( $('#fname_toggle').prop('checked' ) )
|
|
$('#img-cap').show()
|
|
$('#fname_i').html(PrettyFname(document.viewing.FullPathOnFS))
|
|
$('#figure').show()
|
|
if( fullscreen )
|
|
$('#canvas').get(0).requestFullscreen()
|
|
else
|
|
if( document.fullscreen )
|
|
document.exitFullscreen()
|
|
}
|
|
if( document.viewing.type.name == 'Video' )
|
|
{
|
|
$('#figure').hide()
|
|
$('#video').prop('src', '../' + document.viewing.FullPathOnFS )
|
|
$('#fname_v').html(PrettyFname(document.viewing.FullPathOnFS))
|
|
if( $('#fname_toggle').prop('checked' ) )
|
|
$('#img-cap').hide()
|
|
ResizeVideo()
|
|
$('#video_div').show()
|
|
if( fullscreen )
|
|
$('#video').get(0).requestFullscreen()
|
|
else
|
|
if( document.fullscreen )
|
|
document.exitFullscreen()
|
|
}
|
|
}
|
|
|
|
var offsetX,offsetY;
|
|
|
|
// find the edge of the canvas, so when we have a PAGE event with x,y we can see
|
|
// where we clicked in it (PAGE.x - canvas.x to see where in canvas, etc)
|
|
function reOffset()
|
|
{
|
|
var BB=$('#canvas').get(0).getBoundingClientRect();
|
|
offsetX=BB.left;
|
|
offsetY=BB.top;
|
|
}
|
|
|
|
// when we are ready,
|
|
$(document).ready( function()
|
|
{
|
|
var cw=$('#canvas').width;
|
|
var ch=$('#canvas').height;
|
|
reOffset();
|
|
// if we scroll or resize the window, the canvas moves on the page, reset the offsets
|
|
window.onscroll=function(e){ reOffset(); }
|
|
window.onresize=function(e){ reOffset(); }
|
|
|
|
// clicking in the viewer canvas gets its own handlers to handle faces (or not)
|
|
$.contextMenu({
|
|
selector: '#canvas',
|
|
trigger: 'left',
|
|
// trigger: 'none',
|
|
hideOnSecondTrigger: true,
|
|
|
|
// go through each face, and add appropriate 'left-click' menu.
|
|
// e.g if known face, say name, offer add refimg to person, etc.
|
|
// this is a bit complex, the item_list var has a key (which is what we
|
|
// will do if we are chosen from the menu), and data to process the action
|
|
build: function($triggerElement, e) {
|
|
reOffset();
|
|
// get mouse position relative to the canvas (left-click uses page*)
|
|
var x=parseInt(e.pageX-offsetX);
|
|
var y=parseInt(e.pageY-offsetY);
|
|
|
|
item_list = { not_a_face: { name: "Not a face", which_face: '-1' } }
|
|
|
|
faces=document.viewing.file_details.faces
|
|
for( i=0; i<faces.length; i++ )
|
|
{
|
|
fx = faces[i].face_left / ( im.width/canvas.width )
|
|
fy = faces[i].face_top / ( im.height/canvas.height )
|
|
fw = faces[i].w / ( im.width/canvas.width )
|
|
fh = faces[i].h / ( im.height/canvas.height )
|
|
|
|
if( x >= fx && x <= fx+fw && y >= fy && y <= fy+fh )
|
|
{
|
|
if( faces[i].ffmo.length || faces[i].fnmo.length )
|
|
{
|
|
item_list['remove_force_match_override']={ 'name': 'Remove override for this face', 'which_face': i, 'id': faces[i].id }
|
|
}
|
|
else if( faces[i].refimg )
|
|
{
|
|
item_list['match']={ 'name': faces[i].refimg.person.tag, 'which_face': i, 'id': faces[i].id }
|
|
item_list['match_add_refimg']={ 'name': 'Add this as refimg for ' + faces[i].refimg.person.tag,
|
|
'person_id': faces[i].refimg.person.id, 'who': faces[i].refimg.person.tag, 'which_face': i, 'id': faces[i].id, }
|
|
item_list['wrong_person']={ 'name': 'wrong person', 'which_face': i, 'id': faces[i].id }
|
|
}
|
|
else
|
|
{
|
|
item_list['no_match_new_person']={ 'name': 'Add as reference image to NEW person', 'which_face': i, 'id': faces[i].id }
|
|
item_list['no_match_new_refimg']={ 'name': 'Add as reference image to EXISTING person', 'which_face': i, 'id': faces[i].id }
|
|
for( var el in NMO ) {
|
|
item_list['NMO_'+el]={'type_id': NMO[el].id, 'name': 'Override: ' + NMO[el].name, 'which_face': i, 'id': faces[i].id }
|
|
}
|
|
}
|
|
delete item_list['not_a_face']
|
|
$('#canvas').prop('menu_item', item_list )
|
|
break
|
|
}
|
|
}
|
|
return {
|
|
callback: function( key, options) {
|
|
if( key == 'not_a_face' ) { return true }
|
|
item=$('#canvas').prop( 'menu_item' );
|
|
FaceDBox( key, item )
|
|
},
|
|
items: item_list
|
|
};
|
|
}
|
|
} )
|
|
} );
|
|
|
|
// POST to the server to force a match for this face to person_id
|
|
// FIXME: could I not pass person_id, and use // ...[item[key].which_face].refimg.person.id
|
|
function OverrideForceMatch( person_id, key )
|
|
{
|
|
ofm='&person_id='+person_id+'&face_id='+item[key].id
|
|
$.ajax({ type: 'POST', data: ofm, url: '/add_force_match_override', success: function(data) {
|
|
document.viewing.file_details.faces[item[key].which_face].ffmo=[]
|
|
document.viewing.file_details.faces[item[key].which_face].ffmo[0]={}
|
|
document.viewing.file_details.faces[item[key].which_face].ffmo[0].person=data.person
|
|
$('#dbox').modal('hide')
|
|
$('#faces').prop('checked',true)
|
|
DrawImg()
|
|
CheckForJobs()
|
|
}
|
|
} )
|
|
}
|
|
|
|
// function that handles the POSTed data that comes back when we add a
|
|
// reference image to a new or existing person (right-click on a face)
|
|
// used in success callbacks from CreatePersonAndRefimg() and AddRefimgTo()
|
|
function handleAddRefimgData(key, data)
|
|
{
|
|
document.viewing.file_details.faces[item[key].which_face].refimg=data.refimg
|
|
document.viewing.file_details.faces[item[key].which_face].refimg_lnk={}
|
|
// if we used this img, for now set distance to 0 - it is an exact match!
|
|
document.viewing.file_details.faces[item[key].which_face].refimg_lnk.face_distance=0.0
|
|
$('#dbox').modal('hide')
|
|
$('#faces').prop('checked',true)
|
|
DrawImg()
|
|
CheckForJobs()
|
|
}
|
|
|
|
// when we right-click a face and make a new person, this code creates and
|
|
// associates the face
|
|
function CreatePersonAndRefimg( key )
|
|
{
|
|
d='&face_id='+item[key].id
|
|
+'&tag='+$('#tag').val()
|
|
+'&firstname='+$('#firstname').val()
|
|
+'&surname='+$('#surname').val()
|
|
+'&refimg_data='+item[key].refimg_data
|
|
$.ajax({ type: 'POST', data: d, url: '/match_with_create_person',
|
|
success: function(data) { handleAddRefimgData(key, data ) },
|
|
})
|
|
}
|
|
|
|
// when we right-click a face and connect to an existing person, this connects
|
|
// the refimg and associates the face
|
|
function AddRefimgTo( person_id, key, 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',
|
|
success: function(data) { handleAddRefimgData(key, data ) },
|
|
})
|
|
}
|
|
|
|
// function to facilitate adding a face match override to this "found" person
|
|
// uses Ajax to the f/e to get any person matching #stext's content (via any name/tag)
|
|
// and displays results in #search_person_results
|
|
function SearchForPerson(content, key, face_id, face_pos, type_id)
|
|
{
|
|
// make URI safe
|
|
who = encodeURIComponent( $('#stext').val() )
|
|
// call ajax to find ppl
|
|
$.ajax({ type: 'POST', data: null, url: '/find_persons/'+ who,
|
|
success: function(data) {
|
|
for( var el in data ) {
|
|
content+='<div class="row">'
|
|
var person = data[el];
|
|
if( item[key].name == "Override: Manual match to existing person" )
|
|
{
|
|
func='OverrideForceMatch('+person.id+',\''+key+'\' )'
|
|
content+= '<div class="col">' + person.tag + ' (' + person.firstname+' '+person.surname+ ') </div>'
|
|
content+= '<button onClick="'+func+'" class="col btn btn-success py-1 input-group-prepend">Add Override</button>'
|
|
}
|
|
if( key == 'no_match_new_refimg' )
|
|
{
|
|
func='AddRefimgTo('+person.id+',\''+key+'\''
|
|
func_sn=func+ ', true )'
|
|
func_ao=func+ ', false )'
|
|
content+= '<div class="col">' + person.tag + ' (' + person.firstname+' '+person.surname+ ') </div><div class="col input-group">'
|
|
content+= '<button onClick="'+func_sn+'" class="btn btn-success py-1 input-group-prepend">Add & search now</button> '
|
|
content+= '<button onClick="'+func_ao+'" class="btn btn-outline-success py-1 input-group-append">Add only</button></div>'
|
|
}
|
|
content+='</div class="row">'
|
|
}
|
|
$('#search_person_results').html( content )
|
|
}
|
|
} )
|
|
return false
|
|
}
|
|
|
|
// if we force a match, this func allows us to POST to the server to remove the override
|
|
function RemoveOverrideForceMatch(face_pos)
|
|
{
|
|
if( document.viewing.file_details.faces[face_pos].ffmo.length )
|
|
who=document.viewing.file_details.faces[face_pos].ffmo[0].person.tag
|
|
else
|
|
who=document.viewing.file_details.faces[face_pos].refimg.person.tag
|
|
|
|
d='&face_id='+document.viewing.file_details.faces[face_pos].id+'&person_tag='+who+'&file_eid='+document.viewing.id
|
|
$.ajax({ type: 'POST', data: d, url: '/remove_force_match_override',
|
|
success: function(data) {
|
|
// force/delete the ffmo cleanly
|
|
document.viewing.file_details.faces[face_pos].ffmo.length=0
|
|
$('#dbox').modal('hide')
|
|
DrawImg()
|
|
CheckForJobs()
|
|
return false
|
|
}
|
|
} )
|
|
return false
|
|
}
|
|
|
|
// if we force NO match, this func allows us to POST to the server to remove the override
|
|
function RemoveOverrideNoMatch(face_pos, type_id)
|
|
{
|
|
d='&face_id='+document.viewing.file_details.faces[face_pos].id+'&type_id='+type_id
|
|
$.ajax({ type: 'POST', data: d, url: '/remove_no_match_override',
|
|
success: function(data) {
|
|
document.viewing.file_details.faces[face_pos].fnmo.length=0
|
|
$('#dbox').modal('hide')
|
|
DrawImg()
|
|
CheckForJobs()
|
|
return false
|
|
}
|
|
} )
|
|
return false
|
|
}
|
|
|
|
// POST to the server to force NO match for this face
|
|
function AddNoMatchOverride(type_id, face_id, face_pos, type_id)
|
|
{
|
|
d='&type_id='+type_id+'&face_id='+face_id
|
|
$.ajax({ type: 'POST', data: d, url: '/add_no_match_override',
|
|
success: function(data) {
|
|
document.viewing.file_details.faces[face_pos].fnmo[0]=data
|
|
$('#dbox').modal('hide')
|
|
$('#faces').prop('checked',true)
|
|
DrawImg()
|
|
CheckForJobs()
|
|
}
|
|
} )
|
|
}
|
|
|
|
// generate html for the appropriate content to search for a person when adding
|
|
// override DBox. has a button that when clicked calls SeachForPerson() which
|
|
// POSTs to the server, and fills in the 'search_person_results' div with content
|
|
function AddSearch( content, key, face_pos )
|
|
{
|
|
html='<h5>search for existing person:</h5>'
|
|
html+=`
|
|
<div class="input-group mb-3"><input type="text" class="form-control" id="stext" placeholder="tag/name">
|
|
<button id="search_person_btn" class="btn btn-outline-success" type="button"
|
|
onClick="SearchForPerson( `
|
|
html+= "'" + content + "', " + "'" +key+"'" + ', ' + item[key].id + ',' + face_pos + ',' + item[key].type_id
|
|
html+=`)">Search</button>
|
|
</div>
|
|
<div id="search_person_results">
|
|
</div>
|
|
<script>
|
|
$("#stext").keypress(function (e) { if (e.which == 13) { $("#search_person_btn").click(); return false; } } )
|
|
</script>`
|
|
return html
|
|
}
|
|
|
|
// function that is called when we click on a face in the viewer and we want to
|
|
// potentially override the non-match / match... it shows the face, and then
|
|
// based on which menu item got us here, shows appropriate text to do next action
|
|
function FaceDBox(key, item)
|
|
{
|
|
face_pos=item[key]['which_face']
|
|
div ='<div class="col-6"><p>'
|
|
div+='Face position #' + face_pos
|
|
// use w-100 to force width to not make face image wider than half the dbox
|
|
div+='</p><div class="col-6 w-100"> <img id="face_img" class="w-100"></img></div>'
|
|
$.ajax({ type: 'POST', data: null, url: '/get_face_from_image/'+item[key].id,
|
|
success: function(img_data) {
|
|
item[key].refimg_data=img_data
|
|
$('#face_img').prop('src', 'data:image/jpeg;base64,' + img_data )
|
|
// used for create_new_person only, so this will do nothing for option menu items
|
|
$('#refimg_data').val(img_data)
|
|
}
|
|
} )
|
|
div+='</div><div class="col-6">'
|
|
if ( key == 'remove_force_match_override' )
|
|
{
|
|
if( document.viewing.file_details.faces[face_pos].ffmo.length )
|
|
div+='<div class="row col-12">remove this override (force match to: ' + document.viewing.file_details.faces[face_pos].ffmo[0].person.tag + ')</div>'
|
|
else
|
|
div+='<div class="row col-12">remove this override (' + document.viewing.file_details.faces[face_pos].fnmo[0].type.name + ')</div>'
|
|
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-danger col-6" type="button" '
|
|
if( document.viewing.file_details.faces[face_pos].ffmo.length )
|
|
div+='onClick="RemoveOverrideForceMatch(' +face_pos+ ')">Remove</button>'
|
|
else
|
|
div+='onClick="RemoveOverrideNoMatch(' +face_pos+','+document.viewing.file_details.faces[face_pos].fnmo[0].type.id+ ')">Remove</button>'
|
|
div+='</div>'
|
|
}
|
|
if ( key == 'no_match_new_person' )
|
|
{
|
|
div+='<input type="hidden" id="refimg_data" name="refimg_data" value=""></input>'
|
|
div+=`
|
|
<div class="row col-12">
|
|
<label class="col-3" for="tag">Tag (searchable name):</label>
|
|
<input class="form-control col" id="tag" name="tag" required="" type="text" value="">
|
|
</div>
|
|
<div class="row col-12">
|
|
<label class="col-3" for="firstname">FirstName(s):</label>
|
|
<input class="form-control col" id="firstname" name="firstname" required="" type="text" value="">
|
|
</div>
|
|
<div class="row col-12">
|
|
<label class="col-3" for="surname">Surname:</label>
|
|
<input class="form-control col" id="surname" name="surname" required="" type="text" value="">
|
|
</div>
|
|
<div class="row col-12">
|
|
`
|
|
div+='<button class="btn btn-primary offset-3 col-2" type="button"'
|
|
div+='onClick="CreatePersonAndRefimg(\''+key+'\')">Save</button>'
|
|
div+='</div>'
|
|
}
|
|
if ( key == 'no_match_new_refimg' )
|
|
{
|
|
div+=AddSearch( 'Click one of the link(s) below to add this face as a reference image to the person:<br><br>', key, face_pos );
|
|
}
|
|
if ( key == 'match_add_refimg' )
|
|
{
|
|
func='AddRefimgTo('+item[key]['person_id']+',\''+key+'\''
|
|
func_sn=func+ ', true )'
|
|
func_ao=func+ ', false )'
|
|
div+="Confirm you wish to add this face as a reference image for " + item[key]['who']
|
|
div+= '<div class="col">' + item[key]['who'] + '</div><div class="col input-group">'
|
|
div+= '<button onClick="'+func_sn+'" class="btn btn-success py-1 input-group-prepend">Add & search now</button> '
|
|
div+= '<button onClick="'+func_ao+'" class="btn btn-outline-success py-1 input-group-append">Add only</button></div>'
|
|
|
|
}
|
|
if ( key == 'wrong_person' )
|
|
{
|
|
div+='<br>wrong person, so mark this as the wrong person/refimg connection, for face#' + item[key]['which_face']
|
|
div+='<br>face db id: ' + item[key]['id']
|
|
div += '<br>not yet'
|
|
}
|
|
if( /NMO_/.test(key) )
|
|
{
|
|
if( item[key].name == 'Override: Manual match to existing person' )
|
|
{
|
|
div+=AddSearch( 'Click one of the link(s) below to manually connect this face as once-off connection to the person:<br><br>', key, face_pos );
|
|
}
|
|
else
|
|
{
|
|
type_id=item[key].type_id
|
|
face_id=item[key].id
|
|
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-danger col-6" type="button" '+
|
|
'onClick="AddNoMatchOverride('+type_id+','+face_id+','+face_pos+','+type_id+ ')">Apply Override</button>'
|
|
div+='</div>'
|
|
}
|
|
}
|
|
div+='</div>'
|
|
$('#dbox-title').html(item[key]['name'])
|
|
$('#dbox-content').html(div)
|
|
$('#dbox').modal('show')
|
|
}
|
|
|
|
|
|
// func called to show logs relating to this filename from viewer
|
|
// pops results up in a dbox
|
|
function JoblogSearch()
|
|
{
|
|
data="eid="+document.viewing.id
|
|
$.ajax({ type: 'POST', data: data, url: '/joblog_search', success: function(res) {
|
|
data = JSON.parse(res)
|
|
div ='<div><table class="table table-striped table-sm sm-txt">'
|
|
div+='<tr><th>Log</th><th>When</th><th>Job</th></tr>'
|
|
for( i=0; i<data.length; i++ )
|
|
{
|
|
div+='<tr><td>' + data[i].log + '</td><td>' + data[i].log_date + '</td><td>'
|
|
div+='<a href="/job/' + data[i].id + '">' + data[i].name + ' #:'+data[i].id+'</a></td></tr>'
|
|
}
|
|
div+='</table></div>'
|
|
$('#dbox-title').html("Logs relating to this filename")
|
|
$('#dbox-content').html(div)
|
|
$('#dbox').modal('show')
|
|
}
|
|
})
|
|
}
|
|
|
|
// helper func to resert the src on the video div
|
|
function setVideoSource(newSrc) {
|
|
$('#videoSource').attr('src', newSrc);
|
|
$('#video')[0].load();
|
|
}
|
|
|
|
// 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()
|
|
}
|
|
|
|
// handler used when we double click an entry to show it in the viewer
|
|
function dblClickToViewEntry(id) {
|
|
$('#files_div').addClass('d-none')
|
|
$('#viewer_div').removeClass('d-none')
|
|
setEntryById( id )
|
|
ViewImageOrVideo()
|
|
}
|
|
|
|
// quick function that allows us to go out of the viewer and back, the viewercomes from files_ip/sp
|
|
// so just redraw the page with drawPageOfFigures() as we have all the data
|
|
function goOutOfViewer()
|
|
{
|
|
// if this returns -1, we have used arrows to go onto a new page(s)
|
|
if( getPageNumberForId( $('#figures').find('.figure').first().prop('id') ) == -1 )
|
|
drawPageOfFigures()
|
|
|
|
// hide viewer div, then show files_div
|
|
$('#viewer_div').addClass('d-none')
|
|
$('#files_div').removeClass('d-none')
|
|
// no longer viewing an image too
|
|
document.viewing=null
|
|
}
|
|
|
|
// change the viewer to the previous entry (handle page change too)
|
|
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]
|
|
}
|
|
}
|
|
|
|
// change the viewer to the next entry (handle page change too)
|
|
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]
|
|
}
|
|
}
|
|
|
|
// check if we are viewing the very first entry (helps to disable la)
|
|
function entryIsAtStart() {
|
|
return document.viewing.id === entryList[0];
|
|
}
|
|
|
|
// check if we are viewing the very last entry (helps to disable ra)
|
|
function entryIsAtEnd() {
|
|
return document.viewing.id === entryList[entryList.length - 1];
|
|
}
|
|
|
|
// helper func to ensure document.viewing is the right entry from document.entries array
|
|
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]
|
|
}
|
|
|
|
// disable la button if we are viewing first entry and/or ra button if we are viewing last entry
|
|
function setDisabledForViewingNextPrevBttons()
|
|
{
|
|
$('#la').attr('disabled', entryIsAtStart());
|
|
$('#ra').attr('disabled', entryIsAtEnd());
|
|
}
|
|
|
|
// when we go into the view, the keybindings are set here for items like 'f' for face box/name
|
|
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":
|
|
$('#la').click()
|
|
break;
|
|
case "Right": // IE/Edge specific value
|
|
case "ArrowRight":
|
|
$('#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.
|
|
}
|
|
});
|
|
}
|
|
|
|
// left arrow onclick handler to go to prev image from inside the viewer
|
|
function prevImageInViewer()
|
|
{
|
|
getPreviousEntry()
|
|
setDisabledForViewingNextPrevBttons()
|
|
ViewImageOrVideo()
|
|
}
|
|
|
|
// right arrow onclick handler to go to next image from inside the viewer
|
|
function nextImageInViewer()
|
|
{
|
|
getNextEntry()
|
|
setDisabledForViewingNextPrevBttons()
|
|
ViewImageOrVideo()
|
|
}
|
|
|
|
// wrapper func to start the viewer - needed as we have a dbl-click & View file
|
|
// to start the viewer
|
|
function startViewing(eid)
|
|
{
|
|
dblClickToViewEntry( eid );
|
|
setDisabledForViewingNextPrevBttons();
|
|
addViewerKeyHandler()
|
|
}
|