added code to support changing noo/how_many/folders and do this with json data back and forth, update the UI, all works - only search is missing now. Lots of dead code can still be deleted

This commit is contained in:
2025-10-01 23:48:19 +10:00
parent b9b7a24326
commit 2e952deda0
2 changed files with 96 additions and 67 deletions

View File

@@ -21,11 +21,12 @@ from datetime import datetime, timedelta
import pytz import pytz
import html import html
from flask_login import login_required, current_user from flask_login import login_required, current_user
from states import States, PA_UserState from types import SimpleNamespace
from query import Query
# Local Class imports # Local Class imports
################################################################################ ################################################################################
from states import States, PA_UserState
from query import Query
from job import Job, JobExtra, Joblog, NewJob, SetFELog from job import Job, JobExtra, Joblog, NewJob, SetFELog
from path import PathType, Path, MovePathDetails from path import PathType, Path, MovePathDetails
from person import Refimg, Person, PersonRefimgLink from person import Refimg, Person, PersonRefimgLink
@@ -472,8 +473,7 @@ def get_dir_entries():
# get content of dir_id # get content of dir_id
stmt=( select(Entry.id).join(EntryDirLink).filter(EntryDirLink.dir_eid==dir_id) ) stmt=( select(Entry.id).join(EntryDirLink).filter(EntryDirLink.dir_eid==dir_id) )
# FIXME: what do we do with ordering anyway??? stmt=stmt.order_by(*order_map.get(OPT.noo) )
#stmt=stmt.order_by(*order_map.get(OPT.noo) )
ids=db.session.execute(stmt).scalars().all() ids=db.session.execute(stmt).scalars().all()
entries_schema = EntrySchema(many=True) entries_schema = EntrySchema(many=True)
entries = Entry.query.filter(Entry.id.in_(ids)).all() entries = Entry.query.filter(Entry.id.in_(ids)).all()
@@ -485,39 +485,28 @@ def get_dir_entries():
################################################################################ ################################################################################
def GetQueryData( OPT ): def GetQueryData( OPT ):
query_data={} query_data={}
query_data['query_id']=None
query_data['entry_list']=None query_data['entry_list']=None
if OPT.path_type == 'Search':
print ("NOT YET")
return query_data
# always get the top of the (OPT.prefix) Path's eid and keep it for OPT.folders toggling/use # always get the top of the (OPT.prefix) Path's eid and keep it for OPT.folders toggling/use
dir_stmt=( select(Entry.id).join(Dir).join(PathDirLink).join(Path).filter(Dir.rel_path == '').filter(Path.path_prefix==OPT.prefix) ) dir_stmt=( select(Entry.id).join(Dir).join(PathDirLink).join(Path).
filter(Dir.rel_path == '').filter(Path.path_prefix==OPT.prefix) )
# this should return the 1 Dir (that we want to see the content of) - and with only 1, no need to worry about order # this should return the 1 Dir (that we want to see the content of) - and with only 1, no need to worry about order
dir_arr=db.session.execute(dir_stmt).scalars().all() dir_arr=db.session.execute(dir_stmt).scalars().all()
dir_id=dir_arr[0] dir_id=dir_arr[0]
# used to know the parent/root (in folder view), in flat view - just ignore/safe though
query_data['root_eid']=dir_id query_data['root_eid']=dir_id
if OPT.folders: if OPT.folders:
# start folder view with only the root folder # start folder view with only the root folder
stmt=( select(Entry.id).join(EntryDirLink).filter(EntryDirLink.dir_eid==dir_id) ) stmt=( select(Entry.id).join(EntryDirLink).filter(EntryDirLink.dir_eid==dir_id) )
query_data['entry_list']=db.session.execute(stmt).scalars().all()
else: else:
# get every File that is in the OPT.prefix Path # get every File that is in the OPT.prefix Path
stmt=( select(Entry.id).join(File).join(EntryDirLink).join(Dir).join(PathDirLink).join(Path).filter(Path.path_prefix == OPT.prefix) ) stmt=( select(Entry.id).join(File).join(EntryDirLink).join(Dir).join(PathDirLink).join(Path).
stmt=stmt.order_by(*order_map.get(OPT.noo) ) filter(Path.path_prefix == OPT.prefix) )
query_data['entry_list']= db.session.execute(stmt).scalars().all()
stmt=stmt.order_by(*order_map.get(OPT.noo) )
query_data['entry_list']=db.session.execute(stmt).scalars().all()
# not sure I need this in hindsight - any value at all???
# # first time we get the data q_offset is 0, current=first one, search never gets here, so search_term=''
# # FIXME: Doubt we need cwd -- I only need originals to either invalidate this list, or recreate it... need to think about that a lot more
# query = Query( path_type=OPT.path_type, noo=OPT.noo, q_offset=0, folder=OPT.folders, grouping=OPT.grouping, root=OPT.root, cwd=OPT.cwd, search_term='',
# entry_list=query_data['entry_list'], current=query_data['entry_list'][0], created=datetime.now(pytz.utc) )
# db.session.add(query)
# db.session.commit()
#
# query_data['query_id']=query.id
return query_data return query_data
################################################################################ ################################################################################
@@ -570,12 +559,24 @@ def GetEntries( OPT ):
return entries return entries
################################################################################
# /change_file_opts -> allow sort order, how_many per page, etc. to change, and
# then send back the new query_data to update entryList
################################################################################
@app.route("/change_file_opts", methods=["POST"]) @app.route("/change_file_opts", methods=["POST"])
@login_required @login_required
def change_file_opts(): def change_file_opts2():
# reset options based on form post, then redirect back to orig page (with a GET to allow back button to work) data = request.get_json() # Parse JSON body
OPT=States( request ) # allow dot-notation for OPT
return redirect( request.referrer ) OPT = SimpleNamespace(**data)
if OPT.folders == 'True':
OPT.folders=True
else:
OPT.folders=False
# so create a new entryList, and handle that on the client
query_data = GetQueryData( OPT )
return make_response( jsonify( query_data=query_data ) )
################################################################################ ################################################################################
# /file_list -> show detailed file list of files from import_path(s) # /file_list -> show detailed file list of files from import_path(s)
@@ -862,12 +863,6 @@ def newview():
data = request.get_json() # Parse JSON body data = request.get_json() # Parse JSON body
eid = data.get('eid', 0) # Extract list of ids 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 = ( stmt = (
select(Entry) select(Entry)
.options( .options(
@@ -877,8 +872,7 @@ def newview():
.where(Entry.id == eid) .where(Entry.id == eid)
) )
print( stmt ) # this needs unique() because:
# this needs unique because:
# entry (one row for id=660) # entry (one row for id=660)
# file (one row, since file_details is a one-to-one relationship) # file (one row, since file_details is a one-to-one relationship)
# face (many rows, since a file can have many faces) # face (many rows, since a file can have many faces)
@@ -886,7 +880,6 @@ def newview():
# The SQL query returns a Cartesian product for the joins involving collections (like faces). For example, if your file has 3 faces, # 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. # 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() data=db.session.execute(stmt).unique().scalars().all()
print( data )
return jsonify(entries_schema.dump(data)) return jsonify(entries_schema.dump(data))
################################################################################ ################################################################################

View File

@@ -5,6 +5,8 @@
<script src="{{ url_for( 'internal', filename='js/files_transform.js')}}"></script> <script src="{{ url_for( 'internal', filename='js/files_transform.js')}}"></script>
<script> <script>
// FIXME: used by viewer code - should probably get rid of this?
var fullscreen=false;
document.fake_shift=0 document.fake_shift=0
document.fake_ctrl=0 document.fake_ctrl=0
var move_paths=[] var move_paths=[]
@@ -17,22 +19,23 @@
{% endfor %} {% endfor %}
// GLOBALS // GLOBALS
// OPTions set via GUI, will change if we alter drop-downs, etc. in GUI
// 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
// this is which eid we are viewing an image/video (when we dbl-click & then next/prev) // this is which eid we are viewing an image/video (when we dbl-click & then next/prev)
document.viewing_eid=null; document.viewing_eid=null;
document.viewing=null; document.viewing=null;
var OPT={} var OPT={}
OPT.noo='{{OPT.noo}}'
OPT.how_many={{OPT.how_many}}
OPT.folders="{{OPT.folders}}" === "True"
OPT.grouping='{{OPT.grouping}}' OPT.grouping='{{OPT.grouping}}'
OPT.cwd='{{OPT.cwd}}' OPT.cwd='{{OPT.cwd}}'
OPT.root_eid={{query_data.root_eid}} OPT.root_eid={{query_data.root_eid}}
OPT.search_term='{{OPT.orig_search_term}}' OPT.search_term='{{OPT.orig_search_term}}'
OPT.folders="{{OPT.folders}}" === "True"
OPT.howMany={{OPT.how_many}}
OPT.size={{OPT.size}} OPT.size={{OPT.size}}
OPT.prefix='{{OPT.prefix}}'
OPT.default_flat_noo='{{OPT.default_flat_noo}}'
OPT.default_folder_noo='{{OPT.default_folder_noo}}'
OPT.default_search_noo='{{OPT.default_search_noo}}'
// this is the list of entry ids for the images for ALL matches for this query // this is the list of entry ids for the images for ALL matches for this query
var entryList={{query_data.entry_list}} var entryList={{query_data.entry_list}}
@@ -41,6 +44,48 @@
var pageList=[] var pageList=[]
// force pageList to set pageList for & render the first page // force pageList to set pageList for & render the first page
getPage(1) getPage(1)
function cFO() {
OPT.how_many=$('#how_many').val()
new_f=$('#folders').val()
new_f=( new_f == 'True' )
// if change to/from folders, also fix the noo menu
if( new_f != OPT.folders )
{
if( new_f )
{
$('#noo option:lt(2)').prop('disabled', true);
$('#noo').val(OPT.default_folder_noo)
}
else
{
$('#noo option:lt(2)').prop('disabled', false);
$('#noo').val(OPT.default_flat_noo)
}
}
OPT.noo=$('#noo').val()
OPT.folders=new_f
OPT.folders=$('#folders').val()
OPT.grouping=$('#grouping').val()
OPT.size=$('#size').val()
$.ajax({
type: 'POST',
url: '/change_file_opts',
data: JSON.stringify(OPT),
contentType: 'application/json',
success: function(resp) {
entryList=resp.query_data.entry_list
// put data back into booleans, ints, etc
OPT.folders=( OPT.folders == 'True' )
OPT.how_many=parseInt(OPT.how_many)
$('.how_many_text').html( `&nbsp;${OPT.how_many} files&nbsp;` )
OPT.root_eid=parseInt(OPT.root_eid)
OPT.size=parseInt(OPT.size)
getPage(1)
}
})
}
</script> </script>
<div id="files_div"> <div id="files_div">
@@ -68,13 +113,13 @@
{% endif %} {% endif %}
<div class="col col-auto"> <div class="col col-auto">
<div class="input-group"> <div class="input-group">
{{CreateSelect( "noo", OPT.noo, ["Oldest", "Newest","A to Z", "Z to A"], "$('#offset').val(0)", "rounded-start py-2")|safe }} {{CreateSelect( "noo", OPT.noo, ["Oldest", "Newest","A to Z", "Z to A"], "cFO(); return false", "rounded-start py-2")|safe }}
{{CreateSelect( "how_many", OPT.how_many|string, ["10", "25", "50", "75", "100", "150", "200", "500"])|safe }} {{CreateSelect( "how_many", OPT.how_many|string, ["10", "25", "50", "75", "100", "150", "200", "500"], "cFO(); return false" )|safe }}
{% if OPT.folders %} {% if OPT.folders %}
<input type="hidden" name="grouping" id="grouping" value="{{OPT.grouping}}"> <input type="hidden" name="grouping" id="grouping" value="{{OPT.grouping}}">
{{CreateFoldersSelect( OPT.folders, "rounded-end" )|safe }} {{CreateFoldersSelect( OPT.folders, "cFO(); return false", "rounded-end" )|safe }}
{% else %} {% else %}
{{CreateFoldersSelect( OPT.folders )|safe }} {{CreateFoldersSelect( OPT.folders, "cFO(); return false" )|safe }}
<span class="sm-txt my-auto btn btn-outline-info disabled border-top border-bottom">grouped by:</span> <span class="sm-txt my-auto btn btn-outline-info disabled border-top border-bottom">grouped by:</span>
{{CreateSelect( "grouping", OPT.grouping, ["None", "Day", "Week", "Month"], "OPT.grouping=$('#grouping').val();drawPageOfFigures();return false", "rounded-end")|safe }} {{CreateSelect( "grouping", OPT.grouping, ["None", "Day", "Week", "Month"], "OPT.grouping=$('#grouping').val();drawPageOfFigures();return false", "rounded-end")|safe }}
{% endif %} {% endif %}
@@ -92,7 +137,7 @@
<button aria-label="prev" id="prev" name="prev" class="prev sm-txt btn btn-outline-secondary disabled" onClick="prevPage()" disabled> <button aria-label="prev" id="prev" name="prev" class="prev sm-txt btn btn-outline-secondary disabled" onClick="prevPage()" disabled>
<svg width="16" height="16" fill="currentColor"><use xlink:href="{{url_for('internal', filename='icons.svg')}}#prev"/></svg> <svg width="16" height="16" fill="currentColor"><use xlink:href="{{url_for('internal', filename='icons.svg')}}#prev"/></svg>
</button> </button>
<span class="sm-txt my-auto">&nbsp;{{OPT.how_many}} files&nbsp;</span> <span class="how_many_text sm-txt my-auto">&nbsp;{{OPT.how_many}} files&nbsp;</span>
<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>
@@ -163,7 +208,7 @@
<button aria-label="prev" id="prev" name="prev" class="prev sm-txt btn btn-outline-secondary disabled" onClick="prevPage()" disabled> <button aria-label="prev" id="prev" name="prev" class="prev sm-txt btn btn-outline-secondary disabled" onClick="prevPage()" disabled>
<svg width="16" height="16" fill="currentColor"><use xlink:href="{{url_for('internal', filename='icons.svg')}}#prev"/></svg> <svg width="16" height="16" fill="currentColor"><use xlink:href="{{url_for('internal', filename='icons.svg')}}#prev"/></svg>
</button> </button>
<span class="sm-txt my-auto">&nbsp;{{OPT.how_many}} files&nbsp;</span> <span class="how_many_text sm-txt my-auto">&nbsp;{{OPT.how_many}} files&nbsp;</span>
<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>
@@ -227,11 +272,11 @@
function getPreviousEntry() { function getPreviousEntry() {
var currentIndex = entryList.indexOf(document.viewing.id); var currentIndex = entryList.indexOf(document.viewing.id);
oldPageOffset=Math.floor(currentIndex / OPT.howMany) oldPageOffset=Math.floor(currentIndex / OPT.how_many)
if (currentIndex > 0) { if (currentIndex > 0) {
currentIndex--; currentIndex--;
pageOffset=Math.floor(currentIndex / OPT.howMany) pageOffset=Math.floor(currentIndex / OPT.how_many)
currentIndex=currentIndex-(pageOffset*OPT.howMany) currentIndex=currentIndex-(pageOffset*OPT.how_many)
// pref page, load it // pref page, load it
if( oldPageOffset != pageOffset ) if( oldPageOffset != pageOffset )
// pref page is pageOffset+1 now // pref page is pageOffset+1 now
@@ -244,11 +289,11 @@
function getNextEntry() { function getNextEntry() {
var currentIndex = entryList.indexOf(document.viewing.id); var currentIndex = entryList.indexOf(document.viewing.id);
oldPageOffset=Math.floor(currentIndex / OPT.howMany) oldPageOffset=Math.floor(currentIndex / OPT.how_many)
if (currentIndex < entryList.length - 1) { if (currentIndex < entryList.length - 1) {
currentIndex++ currentIndex++
pageOffset=Math.floor(currentIndex / OPT.howMany) pageOffset=Math.floor(currentIndex / OPT.how_many)
currentIndex=currentIndex-(pageOffset*OPT.howMany) currentIndex=currentIndex-(pageOffset*OPT.how_many)
// next page, load it // next page, load it
if( oldPageOffset != pageOffset ) if( oldPageOffset != pageOffset )
// next page is pageOffset+1 now // next page is pageOffset+1 now
@@ -268,9 +313,9 @@
function setEntryById(id) { function setEntryById(id) {
var currentIndex = entryList.indexOf(parseInt(id)); var currentIndex = entryList.indexOf(parseInt(id));
// if we are on a different page, adjust as document.entries only has <= howMany // if we are on a different page, adjust as document.entries only has <= how_many
pageOffset=Math.floor(currentIndex / OPT.howMany) pageOffset=Math.floor(currentIndex / OPT.how_many)
currentIndex = currentIndex-(pageOffset*OPT.howMany) currentIndex = currentIndex-(pageOffset*OPT.how_many)
document.viewing=document.entries[currentIndex] document.viewing=document.entries[currentIndex]
} }
@@ -533,7 +578,7 @@ $.contextMenu({
return { return {
callback: function( key, options) { callback: function( key, options) {
if( key == "details" ) { DetailsDBox() } if( key == "details" ) { DetailsDBox() }
if( key == "view" ) { CallViewRoute( $(this).attr('id') ) } if( key == "view" ) { dblClickToViewEntry( $(this).attr('id') ) }
if( key == "move" ) { MoveDBox(move_paths, "{{url_for('internal', filename='icons.svg')}}") } if( key == "move" ) { MoveDBox(move_paths, "{{url_for('internal', filename='icons.svg')}}") }
if( key == "del" ) { DelDBox('Delete') } if( key == "del" ) { DelDBox('Delete') }
if( key == "undel") { DelDBox('Restore') } if( key == "undel") { DelDBox('Restore') }
@@ -552,15 +597,6 @@ $.contextMenu({
}); });
$(document).ready(function() {
if( {{OPT.offset}} == 0 )
{
$('.prev').addClass('disabled')
$('.prev').prop('disabled', true)
}
$(".dir").click( function(e) { $('#offset').val(0) ; $('#cwd').val( $(this).attr('dir') ) ; $('#main_form').submit() } )
} )
$( document ).keydown(function(event) { $( document ).keydown(function(event) {
switch (event.key) switch (event.key)
{ {