Compare commits

...

13 Commits

Author SHA1 Message Date
74647bcdfb remove duplicate lines of code 2025-11-18 21:13:02 +11:00
0ee55ee73d fixed BUG-143, disable next/prev as first thing we do when going next/prev Page 2025-10-28 23:04:33 +11:00
89e8c5d9f7 fix BUG-125: right-click changes highlight if its not in the highlight set 2025-10-28 22:31:15 +11:00
76b0745cc3 remove face data after transform (BUG-142), also only reset viewing image if we are viewing, and remove debugs 2025-10-28 22:16:26 +11:00
bc2d4384c9 fix BUG-141, crashing pa_job_manager when transforming a non-Image 2025-10-28 22:04:47 +11:00
d247ecec54 fix up bug where sucess used instead of success, also if we delete/restore/move a file from inside the viewer, then adjust the files* divs, and go out of the viewer (back/up) and show updated files div 2025-10-27 22:06:54 +11:00
bb43cc5623 new TODOs 2025-10-27 22:04:31 +11:00
a6edbd184b fixed mistaken removal of shift/ctrl buttons on mobiles for file* views 2025-10-26 20:30:44 +11:00
09e7b8eea7 one more old BUG gone, reordered file 2025-10-26 20:30:22 +11:00
f2ef55a58a cleaned up old bugs that are fixed by new entry_amendments logic 2025-10-26 15:55:13 +11:00
4d7f3dfed9 Fixing BUGs 144/145, (needed a parseInt), and force MovedBox path selection to Storage always - if moving from Import path, likely storing, if in Storage path, likely moving inside the Storage area, user can always override 2025-10-26 15:44:13 +11:00
4421da0d1d make move_files have EntryAmendments, do not remove from the UI instantly, and handle just like delete_files, remove restriction on forcing the page to go back to / on search, with new logic its not an issue, also force MoveDBox to start with Storage path rather than another, MOST moves should be to Storage, but should tweak this to be the opposite of current path type 2025-10-26 13:32:31 +11:00
6eba21e028 added BUG-143 around fast page changes 2025-10-25 20:26:32 +11:00
10 changed files with 94 additions and 84 deletions

29
BUGs
View File

@@ -1,28 +1,9 @@
### Next: 143 ### Next: 146
BUG-142: after transforming, the face data is still in the old spots, really should delete it / make it recalc
BUG-141: can currently try to flip a video (in a highlighted group)
BUG-140: When db is restarted underneath PA, it crashes job mgr... It should just accept timeouts, and keep trying to reconnect every 2? mins BUG-140: When db is restarted underneath PA, it crashes job mgr... It should just accept timeouts, and keep trying to reconnect every 2? mins
BUG-118: can move files from Bin path, but it leaves the del_file entry for it - need to remove it
BUG-100: I managed to get 2 photos matching mich in the NOT_WORKING photo (probably dif refimgs but same p.tag?) BUG-117: when search returns files that can be deleted and/or restored, the icon stays as delete and tries to delete!
= /photos/2012/20120414-damien/IMG_8467.JPG
BUG-106: cant add trudy /pat? as refimgs via FaceDBox BUG-106: cant add trudy /pat? as refimgs via FaceDBox
- seems the cropped trudy face is not sufficient to find a face, how odd... - seems the cropped trudy face is not sufficient to find a face, how odd...
(it came from a face bbox, BUT, I have grown the face seln by 10%?) (it came from a face bbox, BUT, I have grown the face seln by 10%?)
BUG-117: when search returns files that can be deleted and/or restored, the icon stays as delete and tries to delete! BUG-100: I managed to get 2 photos matching mich in the NOT_WORKING photo (probably dif refimgs but same p.tag?)
BUG-118: can move files from Bin path, but it leaves the del_file entry for it - need to remove it = /photos/2012/20120414-damien/IMG_8467.JPG
BUG-119: "Uncaught (in promise) Error: A listener indicated an asynchronous
response by returning true, but the message channel closed before a response
was received"
investigate this (possible I'm calling check_for_jobs and maybe not doing the async right?)
BUG-123: pa_job_manager crashed with timeout on connection (probably when I turned off traefik for a bit?). Regardless, should be more fault tolerant --> maybe offer to restart pa_job_manager IF its crashed?
this definitely happened also, when I shutdown the DB back-end mid job, and it was able to be restarted, so could get f/e to at least suggest a restart of the contianer, or auto-restart job_mgr?
BUG-125: when an image is highlighted, then post the contextmenu on a different image - the highlight does not move to the new image
and the selected menu function processes the original or the new depending on the way the code works.
There is a chance we need to change the document on click to a mouse down (or whatever the context menu
uses for default), rather than just fix the highlight
BUG-132: right arrow to go to next photo in viewer ALSO scrolls to the right, needs a return somewhere in the jscript
BUG-134: when moving set of photos on page, then move another set of photos on page, the first set reappears. Could really delete them from the dom?
BUG-137: after moving/refiling photos, the next shift-click is out of order (reload fixes it)

3
TODO
View File

@@ -1,6 +1,3 @@
? get rid of style and just use class -- think this should work, so change in
templates/files.html for throbber, etc. and dont set style as much in view_support.js
### GENERAL ### GENERAL
* jobs for AI should show path name * jobs for AI should show path name
* rm dups job should show progress bar * rm dups job should show progress bar

View File

@@ -674,8 +674,7 @@ def move_files():
for el in request.form: for el in request.form:
jex.append( JobExtra( name=f"{el}", value=str(request.form[el]) ) ) jex.append( JobExtra( name=f"{el}", value=str(request.form[el]) ) )
job=NewJob( name="move_files", num_files=0, wait_for=None, jex=jex, desc="to move selected file(s)" ) job=NewJob( name="move_files", num_files=0, wait_for=None, jex=jex, desc="to move selected file(s)" )
# data is not used, but send response to trigger CheckForJobs() return jsonify( job=job_schema.dump(job) )
return jsonify( job_id=job.id )
@login_required @login_required
@app.route("/view/", methods=["POST"]) @app.route("/view/", methods=["POST"])

View File

@@ -87,21 +87,9 @@ function GetExistingDirsAsDiv( dt, divname, ptype )
} ) } )
} }
// wrapper to do some clean up before POST to /move_files or /delete_files
// used to remove the highlighted item(s) && reset the numbering so highlighting continues to work
function MoveOrDelCleanUpUI()
{
// remove the images being moved (so UI immediately 'sees' the move)
$("[name^=eid-]").each( function() { $('#'+$(this).attr('value')).remove() } )
// reorder the images via ecnt again, so future highlighting can work
// document.mf_id=0; $('.figure').each( function() { $(this).attr('ecnt', document.mf_id ); document.mf_id++ } )
$('#dbox').modal('hide')
}
// show the DBox for a move file, includes all thumbnails of selected files to move // show the DBox for a move file, includes all thumbnails of selected files to move
// and a pre-populated folder to move them into, with text field to add a suffix // and a pre-populated folder to move them into, with text field to add a suffix
function MoveDBox(path_details) function MoveDBox()
{ {
$('#dbox-title').html('Move Selected File(s) to new directory in Storage Path') $('#dbox-title').html('Move Selected File(s) to new directory in Storage Path')
div =` div =`
@@ -111,21 +99,21 @@ function MoveDBox(path_details)
<form id="mv_fm" class="form form-control-inline col-12"> <form id="mv_fm" class="form form-control-inline col-12">
<input id="move_path_type" name="move_path_type" type="hidden" <input id="move_path_type" name="move_path_type" type="hidden"
` `
div += ' value="' + path_details[0].type.name + '"></input>' div += ' value="' + move_paths[0].type.name + '"></input>'
div+=GetSelnAsDiv() div+=GetSelnAsDiv()
yr=$('.highlight').first().attr('yr') yr=$('.highlight').first().attr('yr')
dt=$('.highlight').first().attr('date') dt=$('.highlight').first().attr('date')
div+='<div class="row">Use Existing Directory (in the chosen path):</div><div id="existing"></div>' div+='<div class="row">Use Existing Directory (in the chosen path):</div><div id="existing"></div>'
GetExistingDirsAsDiv( dt, "existing", path_details[0].type.name ) GetExistingDirsAsDiv( dt, "existing", 'Storage' )
div+=` div+=`
<div class="input-group my-3"> <div class="input-group my-3">
<alert class="alert alert-primary my-auto py-1"> <alert class="alert alert-primary my-auto py-1">
` `
// NB: alert-primary here is a hack to get the bg the same color as the alert primary by // NB: alert-primary here is a hack to get the bg the same color as the alert primary by
div+= '<svg id="move_path_icon" width="20" height="20" fill="currentColor"><use xlink:href="' + path_details[0].icon_url + '"></svg>' div+= '<svg id="move_path_icon" width="20" height="20" fill="currentColor"><use xlink:href="' + move_paths[0].icon_url + '"></svg>'
div+= '<select id="rp_sel" name="rel_path" class="text-primary alert-primary py-1 border border-primary rounded" onChange="change_rp_sel()">' div+= '<select id="rp_sel" name="rel_path" class="text-primary alert-primary py-1 border border-primary rounded" onChange="change_rp_sel()">'
for(p of path_details) { for(p of move_paths) {
div+= '<option path_type="'+p.type.name+'" icon_url="'+p.icon_url+'">'+p.root_dir+'</option>' div+= `<option path_type="${p.type.name}" icon_url="${p.icon_url}">${p.root_dir}</option>`
} }
div+= '</select>' div+= '</select>'
div+=` div+=`
@@ -139,11 +127,26 @@ function MoveDBox(path_details)
</div> </div>
<div class="form-row col-12 mt-2"> <div class="form-row col-12 mt-2">
<button onClick="$('#dbox').modal('hide'); return false;" class="btn btn-outline-secondary offset-1 col-2">Cancel</button> <button onClick="$('#dbox').modal('hide'); return false;" class="btn btn-outline-secondary offset-1 col-2">Cancel</button>
<button id="move_submit" onClick="MoveOrDelCleanUpUI(); $.ajax({ type: 'POST', data: $('#mv_fm').serialize(), url: '/move_files', success: function(data) { <button onClick="
if( $(location).attr('pathname').match('search') !== null ) { window.location='/' }; CheckForJobs() } }); return false" class="btn btn-outline-primary col-2">Ok</button> $.ajax({ type: 'POST', data: $('#mv_fm').serialize(), url: '/move_files',
success: function(data) {
processAmendments( data.job.amendments )
checkForAmendmentJobToComplete(data.job.id)
}
});
$('#dbox').modal('hide')
return false"
class="btn btn-outline-secondary col-2">Ok</button>
</div> </div>
</form> </form>
` `
// force to Storage always - if in Import, liekly storing, if in Storage, likely moving, user can always override
div+=`
<script>
storage_rp = move_paths.find(item => item.type.name === "Storage")?.root_dir;
$('#rp_sel').val(storage_rp);change_rp_sel()
</script>
`
$('#dbox-content').html(div) $('#dbox-content').html(div)
$('#dbox').modal('show') $('#dbox').modal('show')
@@ -183,7 +186,7 @@ function processAmendments( ams )
// function to add data for document.amendment based on id and amt // function to add data for document.amendment based on id and amt
// used when we transform several images in files_*, or single image in viewer // used when we transform several images in files_*, or single image in viewer
// show the DBox for a delete/restore file, includes all thumbnails of selected files // show the DBox for a delete/restore file, includes all thumbnails of selected files
// with appropriate coloured button to Delete or Restore files` // with appropriate coloured button to Delete or Restore files
function DelDBox(del_or_undel) function DelDBox(del_or_undel)
{ {
to_del = GetSelnAsData() to_del = GetSelnAsData()
@@ -198,7 +201,7 @@ function DelDBox(del_or_undel)
else else
{ {
which="restore" which="restore"
col="sucess" col="success"
} }
document.ents_to_del=[] document.ents_to_del=[]
@@ -208,10 +211,6 @@ function DelDBox(del_or_undel)
<button onClick=" <button onClick="
$.ajax({ type: 'POST', data: to_del, url: '/${which}_files', $.ajax({ type: 'POST', data: to_del, url: '/${which}_files',
success: function(data) { success: function(data) {
// FIXME: what is the ! search stuff for???
// FIXME: really, also why not show 'delete' throbber, and on success of actual delete go back to /
if( $(location).attr('pathname').match('search') !== null || document.viewing ) { window.location='/' }
processAmendments( data.job.amendments ) processAmendments( data.job.amendments )
checkForAmendmentJobToComplete(data.job.id) checkForAmendmentJobToComplete(data.job.id)
} }
@@ -760,6 +759,10 @@ function resetNextPrevButtons()
// get list of eids for the next page, also make sure next/prev buttons make sense for page we are on // get list of eids for the next page, also make sure next/prev buttons make sense for page we are on
function nextPage(successCallback) function nextPage(successCallback)
{ {
// start with disabling more next presses until we are ready to process them
$('.prev').prop('disabled', true).addClass('disabled');
$('.next').prop('disabled', true).addClass('disabled');
// pageList[0] is the first entry on this page // pageList[0] is the first entry on this page
const currentPage=getPageNumberForId( pageList[0] ) const currentPage=getPageNumberForId( pageList[0] )
// should never happen / just return pageList unchanged // should never happen / just return pageList unchanged
@@ -775,6 +778,10 @@ function nextPage(successCallback)
// get list of eids for the prev page, also make sure next/prev buttons make sense for page we are on // get list of eids for the prev page, also make sure next/prev buttons make sense for page we are on
function prevPage(successCallback) function prevPage(successCallback)
{ {
// start with disabling more prev presses until we are ready to process them
$('.prev').prop('disabled', true).addClass('disabled');
$('.next').prop('disabled', true).addClass('disabled');
// pageList[0] is the first entry on this page // pageList[0] is the first entry on this page
const currentPage=getPageNumberForId( pageList[0] ) const currentPage=getPageNumberForId( pageList[0] )
// should never happen / just return pageList unchanged // should never happen / just return pageList unchanged
@@ -854,13 +861,13 @@ function changeSize()
// when a delete or restore files job has completed successfullly, then get ids // when a delete or restore files job has completed successfullly, then get ids
// find the page we are on, remove amendments & ids from entryList and re-get page // find the page we are on, remove amendments & ids from entryList and re-get page
// which will reset pageList and the UI of images for that page // which will reset pageList and the UI of images for that page
function handleDeleteOrRestoreFileJobCompleted(job) function handleMoveOrDeleteOrRestoreFileJobCompleted(job)
{ {
// this grabs the values from the object attributes of eid-0, eid-1, etc. // this grabs the values from the object attributes of eid-0, eid-1, etc.
const ids = job.extra.map(item => item.value) const ids = job.extra.filter(item => item.name.startsWith("eid-")).map(item => item.value);
// find page number of first element to delete (this is the page we will return too) // find page number of first element to delete (this is the page we will return too)
pnum=getPageNumberForId( ids[0] ) pnum=getPageNumberForId( parseInt(ids[0]) )
// remove amendment data // remove amendment data
for (const ent of ids) for (const ent of ids)
@@ -903,8 +910,11 @@ function handleCheckAmendmentJobStatus(data)
// transforms contain the single transformed entry data for convenience // transforms contain the single transformed entry data for convenience
if( data.job.name == 'transform_image' ) if( data.job.name == 'transform_image' )
handleTransformImageJobCompleted(data.job, data.entry) handleTransformImageJobCompleted(data.job, data.entry)
else if ( data.job.name == 'delete_files' || data.job.name == 'restore_files' ) else if ( data.job.name == 'delete_files' || data.job.name == 'restore_files' || data.job.name == 'move_files' )
handleDeleteOrRestoreFileJobCompleted(data.job) handleMoveOrDeleteOrRestoreFileJobCompleted(data.job)
// if we are viewing this file, then just go up / back,b/c this file is "gone" from this view
if( document.viewing )
goOutOfViewer()
} }
else { setTimeout( function() { checkForAmendmentJobToComplete(data.job.id) }, 1000 ); } else { setTimeout( function() { checkForAmendmentJobToComplete(data.job.id) }, 1000 ); }
} }
@@ -914,6 +924,12 @@ $.contextMenu({
selector: '.entry', selector: '.entry',
itemClickEvent: "click", itemClickEvent: "click",
build: function($triggerElement, e) { build: function($triggerElement, e) {
// if we are not in the highlight set, then move the highlight to this element
if( ! $(e.currentTarget).is('.highlight') )
{
$('.highlight').removeClass('highlight');
$(e.currentTarget).addClass('highlight')
}
// when right-clicking & no selection add one OR deal with ctrl/shift right-lick as it always changes seln // 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 ) if( NoSel() || e.ctrlKey || e.shiftKey )
{ {
@@ -975,7 +991,7 @@ $.contextMenu({
callback: function( key, options) { callback: function( key, options) {
if( key == "details" ) { DetailsDBox() } if( key == "details" ) { DetailsDBox() }
if( key == "view" ) { startViewing( $(this).attr('id') ) } if( key == "view" ) { startViewing( $(this).attr('id') ) }
if( key == "move" ) { MoveDBox(move_paths) } if( key == "move" ) { MoveDBox() }
if( key == "del" ) { DelDBox('Delete') } if( key == "del" ) { DelDBox('Delete') }
if( key == "undel") { DelDBox('Restore') } if( key == "undel") { DelDBox('Restore') }
if( key == "r90" ) { Transform(90) } if( key == "r90" ) { Transform(90) }

View File

@@ -2,7 +2,6 @@
// can only have 1 ammendment per image, its grayed out for other changes // can only have 1 ammendment per image, its grayed out for other changes
function removeAmendment( id ) function removeAmendment( id )
{ {
console.log( 'removing amendment for: ' + id )
document.amendments=document.amendments.filter(obj => obj.eid !== id) document.amendments=document.amendments.filter(obj => obj.eid !== id)
} }
@@ -11,11 +10,16 @@ function removeAmendment( id )
function handleTransformImageJobCompleted(job, entry) function handleTransformImageJobCompleted(job, entry)
{ {
removeAmendment( entry.id ) removeAmendment( entry.id )
// update viewer and files* views, in case we view/go up without a new page load // update viewer if we are viewing an image
if( document.viewing )
{
// force reload with timestamped version of im.src // force reload with timestamped version of im.src
im.src=im.src + '?t=' + new Date().getTime(); im.src=im.src + '?t=' + new Date().getTime();
DrawImg() DrawImg()
}
// ALWAYS update files* div as we could go back to this from a viewer, and
// the thumbnail needs the updated data
idx = entryList.indexOf(entry.id) idx = entryList.indexOf(entry.id)
// replace data for this entry now its been transformed // replace data for this entry now its been transformed
document.entries[idx]=entry document.entries[idx]=entry

View File

@@ -80,16 +80,19 @@ function DrawImg()
// -50 is a straight up hack, no idea why this works, but its good enough for me // -50 is a straight up hack, no idea why this works, but its good enough for me
if (!Array.isArray(am)) if (!Array.isArray(am))
{ {
const style = 'position: absolute; left: 50%; top: 50%; transform: translate(-50%, -50%);'; $('#throbber').show()
$('#throbber').attr('style', style + ' height: 96px;'); $('#white-circle').show()
$('#white-circle').attr('style', style + ' height: 72px;');
if(am.type.which == 'img' ) if(am.type.which == 'img' )
$('#inside-img').attr('style', style + ' height: 64px;').attr('src', '/internal/'+am.type.what ); {
$('#inside-img').attr('src', '/internal/'+am.type.what );
$('#inside-img').show()
}
else else
{ {
$('#inside-icon').attr('style', `${style} color:${am.type.colour}; height: 64px;`) $('#inside-icon').attr('style', `color:${am.type.colour};height:64px` )
$('#inside-icon').attr('fill', am.type.colour ) $('#inside-icon').attr('fill', am.type.colour )
$('#inside-icon use').attr('xlink:href', `/internal/icons.svg#${am.type.what}`); $('#inside-icon use').attr('xlink:href', `/internal/icons.svg#${am.type.what}`);
$('#inside-icon').show()
} }
} else { } else {
$('#throbber').hide() $('#throbber').hide()

4
job.py
View File

@@ -127,13 +127,11 @@ def NewJob(name, num_files="0", wait_for=None, jex=None, desc="No description pr
ea=EntryAmendment( eid=id, job_id=job.id, amend_type=at_id ) ea=EntryAmendment( eid=id, job_id=job.id, amend_type=at_id )
db.session.add(ea) db.session.add(ea)
job.amendments.append(ea) job.amendments.append(ea)
# FIXME: add ea to job.amend elif job.name == 'delete_files' or job.name == 'restore_files' or job.name == 'move_files':
elif job.name == 'delete_files' or job.name == 'restore_files':
for j in jex: for j in jex:
if 'eid-' in j.name: if 'eid-' in j.name:
ea=EntryAmendment( eid=j.value, job_id=job.id, amend_type=at_id ) ea=EntryAmendment( eid=j.value, job_id=job.id, amend_type=at_id )
db.session.add(ea) db.session.add(ea)
# FIXME: add ea to job.amend
job.amendments.append(ea) job.amendments.append(ea)
SetFELog( message=f'Created <a class="link-light" href="/job/{job.id}">Job #{job.id}</a> to {desc}', level="success" ) SetFELog( message=f'Created <a class="link-light" href="/job/{job.id}">Job #{job.id}</a> to {desc}', level="success" )

View File

@@ -66,8 +66,6 @@ app.config['LDAP_USER_DN'] = 'ou=users'
app.config['LDAP_GROUP_DN'] = 'ou=groups' app.config['LDAP_GROUP_DN'] = 'ou=groups'
app.config['LDAP_USER_RDN_ATTR'] = 'uid' app.config['LDAP_USER_RDN_ATTR'] = 'uid'
app.config['LDAP_USER_LOGIN_ATTR'] = 'uid' app.config['LDAP_USER_LOGIN_ATTR'] = 'uid'
app.config['LDAP_BIND_USER_DN'] = None
app.config['LDAP_BIND_USER_PASSWORD'] = None
app.config['LDAP_GROUP_OBJECT_FILTER'] = '(objectclass=posixGroup)' app.config['LDAP_GROUP_OBJECT_FILTER'] = '(objectclass=posixGroup)'
app.config['LDAP_BIND_USER_DN'] = None app.config['LDAP_BIND_USER_DN'] = None
app.config['LDAP_BIND_USER_PASSWORD'] = None app.config['LDAP_BIND_USER_PASSWORD'] = None

View File

@@ -1897,6 +1897,11 @@ def JobTransformImage(job):
amt=[jex.value for jex in job.extra if jex.name == "amt"][0] amt=[jex.value for jex in job.extra if jex.name == "amt"][0]
e=session.query(Entry).join(File).filter(Entry.id==id).first() e=session.query(Entry).join(File).filter(Entry.id==id).first()
PAprint( f"JobTransformImage: job={job.id}, id={id}, amt={amt}" ) PAprint( f"JobTransformImage: job={job.id}, id={id}, amt={amt}" )
# cant transfer non-image, but may get here if multi-select includes non-Image
if e.type.name != 'Image':
removeEntryAmendment( job, id )
FinishJob(job, "Cannot rotate file as it is not an Image","Failed")
return
if amt == "fliph": if amt == "fliph":
AddLogForJob(job, f"INFO: Flipping {e.FullPathOnFS()} horizontally" ) AddLogForJob(job, f"INFO: Flipping {e.FullPathOnFS()} horizontally" )
@@ -1920,6 +1925,8 @@ def JobTransformImage(job):
e.file_details.hash = md5( job, e ) e.file_details.hash = md5( job, e )
PAprint( f"JobTransformImage DONE thumb: job={job.id}, id={id}, amt={amt}" ) PAprint( f"JobTransformImage DONE thumb: job={job.id}, id={id}, amt={amt}" )
session.add(e) session.add(e)
# any faces in this file are no longer valid, remove them
session.query(FaceFileLink).filter(FaceFileLink.file_eid==e.id).delete()
removeEntryAmendment( job, id ) removeEntryAmendment( job, id )
FinishJob(job, "Finished Processesing image rotation/flip") FinishJob(job, "Finished Processesing image rotation/flip")
@@ -2199,6 +2206,7 @@ def JobMoveFiles(job):
if 'eid-' in jex.name: if 'eid-' in jex.name:
move_me=session.query(Entry).get(jex.value) move_me=session.query(Entry).get(jex.value)
MoveEntriesToOtherFolder( job, move_me, dst_storage_path, f"{prefix}{suffix}" ) MoveEntriesToOtherFolder( job, move_me, dst_storage_path, f"{prefix}{suffix}" )
removeEntryAmendment( job, move_me.id )
NewJob( name="check_dups", num_files=0, wait_for=None, jex=None, parent_job=None, desc="check for duplicate files" ) NewJob( name="check_dups", num_files=0, wait_for=None, jex=None, parent_job=None, desc="check for duplicate files" )
FinishJob(job, f"Finished move selected file(s)") FinishJob(job, f"Finished move selected file(s)")
return return

View File

@@ -140,10 +140,12 @@
<figure style="position: relative;" class="col col-auto border border-info rounded m-0 p-1" id="figure"> <figure style="position: relative;" class="col col-auto border border-info rounded m-0 p-1" id="figure">
<canvas id="canvas"></canvas> <canvas id="canvas"></canvas>
<!-- next 4 are placeholders and called on during amendments only in viewer code --> <!-- next 4 are placeholders and called on during amendments only in viewer code -->
<img id="throbber" src="{{url_for('internal', filename='throbber.gif')}}?v={{js_vers[th]}}" style="display:none;"> <img id="throbber" src="{{url_for('internal', filename='throbber.gif')}}?v={{js_vers[th]}}" style="display:none;height:96px"
<img id="white-circle" src="{{url_for('internal', filename='white-circle.png')}}?v={{js_vers[th]}}" style="display:none;"> class="position-absolute top-50 start-50 translate-middle">
<img id="inside-img" style="display:none;"> <img id="white-circle" src="{{url_for('internal', filename='white-circle.png')}}?v={{js_vers[th]}}" style="display:none;height:72px"
<svg id="inside-icon" style="display:none;" fill="currentColor"> class="position-absolute top-50 start-50 translate-middle">
<img id="inside-img" style="display:none;height:64px" class="position-absolute top-50 start-50 translate-middle">
<svg id="inside-icon" style="display:none;height:64px" class="position-absolute top-50 start-50 translate-middle">
<use xlink:href="{{url_for('internal', filename='icons.svg')}}?v={{js_vers['ic']}}#flip_v"> <use xlink:href="{{url_for('internal', filename='icons.svg')}}?v={{js_vers['ic']}}#flip_v">
</use></svg> </use></svg>
<script> <script>
@@ -274,10 +276,8 @@
// force pageList to set pageList for & render the first page // force pageList to set pageList for & render the first page
getPage(1,getPageFigures) getPage(1,getPageFigures)
// FIXME: doco, but also gather all globals together, many make them all document. to be obviously global (and add fullscreen) // gap is used to keep some space around video in viewer - tbh, not sure why anymore
var gap=0.8 var gap=0.8
var grayscale=0
var throbber=0
function PrettyFname(fname) function PrettyFname(fname)
{ {
@@ -327,5 +327,11 @@
$('#viewer_del').on('click', function() { DelDBox('Restore') } ) $('#viewer_del').on('click', function() { DelDBox('Restore') } )
} }
if( isMobile() )
{
$('#shift-key').css('visibility', 'visible');
$('#ctrl-key').css('visibility', 'visible');
}
</script> </script>
{% endblock script_content %} {% endblock script_content %}