Compare commits

...

5 Commits

6 changed files with 144 additions and 354 deletions

24
TODO
View File

@@ -1,20 +1,8 @@
###
# get search to work
###
### major fix - go to everywhere I call GetEntries(), and redo the logic totally... ### major fix - go to everywhere I call GetEntries(), and redo the logic totally...
* firstly, run the query as per normal, but get just the matched eids into an entry_lst
* make a unique query_id for this entry_lst, and store entry_ids into "query" table, with a unique query_id
* take most of pa_user_state that relates to query state and move it to the "query" table per query_id
* pa_user_state then becomes defaults for next query (so how_many, noo, etc)
* we can age out queries form the query_table after a few months?
* client side always has query_id. IF DB does not have query_id, then its really old? - just say so...
* client side takes query_id, entry_lst, current_eid, offset, first/last_eid, etc. as part of its first route / html creation.
* get this data as a json blob? or ask chatgpt to see how best to take the data and turn it into jscript data
* it then decides based on all this to GetEntryDetails( subset of entry_lst ) <- needs new route
* IN THEORY some of the subset of entry_lst don't exist -- BUT, we can handle that on response, e.g. say my query used to have 1,2,3, and since then another user/action deleted 2:
- I ask for details on 1,2,3 and get back details on 1,3 only.
- On client-side, I can say, since you ran this query, data in PA has changed - image#2 is no longer in PA.
Please run a new query (or bonus points, maybe have enough of the original query to note this and ask, do you want to ignore changes, or re-run query and get latest data?)
* client can go fwd or back in the entry_lst same as now (disabling buttons as needed), BUT as entry_lst is NOT recreated per page move, then no chance to get confused about first/last
* client side: * client side:
* for real chance to stop confusion, instead of removing deleted images from DOM, we should gray them out and put a big Del (red circle with line?) though it as overlay. * for real chance to stop confusion, instead of removing deleted images from DOM, we should gray them out and put a big Del (red circle with line?) though it as overlay.
* Create another table is entry_ammendments - note the deletions, rotations, flips of specific eids - then reproduce that on the client side visually as needed * Create another table is entry_ammendments - note the deletions, rotations, flips of specific eids - then reproduce that on the client side visually as needed
@@ -22,10 +10,6 @@
- When job that flips, rotates, deletes completes then lets update the query details (e.g. remove eids, or remove the ammendments) - When job that flips, rotates, deletes completes then lets update the query details (e.g. remove eids, or remove the ammendments)
- this actually is quite an improvement, if someone is deleting 2 as per above, I will see that as a pending change in my unrelated query, ditto flips, etc. - this actually is quite an improvement, if someone is deleting 2 as per above, I will see that as a pending change in my unrelated query, ditto flips, etc.
* NEED to work through how we deal with directories when we do the json data versions above?
- e.g. does entry_list only contain files? OR filter the details in the jscript?
- how do we do dirs in this context? (when folders=True)
### 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

@@ -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

@@ -473,7 +473,7 @@ function getDirEntries(dir_id, back)
document.entries=res document.entries=res
// rebuild entryList/pageList as each dir comes with new entries // rebuild entryList/pageList as each dir comes with new entries
entryList=res.map(obj => obj.id); entryList=res.map(obj => obj.id);
pageList=entryList.slice(0, OPT.howMany) pageList=entryList.slice(0, OPT.how_many)
if( back ) if( back )
document.back_id = res[0].in_dir.eid document.back_id = res[0].in_dir.eid
drawPageOfFigures() drawPageOfFigures()
@@ -539,8 +539,8 @@ function getPage(pageNumber,viewing_idx=0)
// getting another event before we have the data for the page back // getting another event before we have the data for the page back
$('#la').prop('disabled', true) $('#la').prop('disabled', true)
$('#ra').prop('disabled', true) $('#ra').prop('disabled', true)
const startIndex = (pageNumber - 1) * OPT.howMany; const startIndex = (pageNumber - 1) * OPT.how_many;
const endIndex = startIndex + OPT.howMany; const endIndex = startIndex + OPT.how_many;
pageList = entryList.slice(startIndex, endIndex); pageList = entryList.slice(startIndex, endIndex);
// set up data to send to server to get the entry data for entries in pageList // set up data to send to server to get the entry data for entries in pageList
@@ -581,7 +581,7 @@ function isFirstPage(pageNumber)
// Function to check if we are on the last page // Function to check if we are on the last page
function isLastPage(pageNumber) function isLastPage(pageNumber)
{ {
const totalPages = Math.ceil(entryList.length / OPT.howMany); const totalPages = Math.ceil(entryList.length / OPT.how_many);
return pageNumber >= totalPages; return pageNumber >= totalPages;
} }
@@ -592,7 +592,7 @@ function getPageNumberForId(id) {
if (idx === -1) { if (idx === -1) {
return -1; // or null, if you prefer return -1; // or null, if you prefer
} }
return Math.floor(idx / OPT.howMany) + 1; return Math.floor(idx / OPT.how_many) + 1;
} }
// if we are on first page, disable prev, it not ensure next is enabled // if we are on first page, disable prev, it not ensure next is enabled
@@ -618,7 +618,7 @@ function nextPage()
// should never happen / just return pageList unchanged // should never happen / just return pageList unchanged
if ( currentPage === -1 || isLastPage( currentPage ) ) if ( currentPage === -1 || isLastPage( currentPage ) )
{ {
console.log( "WARNING: seems first on pg=" + firstEntryOnPage + " of how many=" + OPT.howMany + " gives currentPage=" + currentPage + " and we cant go next page?" ) console.error( "WARNING: seems first on pg=" + firstEntryOnPage + " of how many=" + OPT.how_many + " gives currentPage=" + currentPage + " and we cant go next page?" )
return return
} }
getPage( currentPage+1 ) getPage( currentPage+1 )
@@ -634,7 +634,7 @@ function prevPage()
// should never happen / just return pageList unchanged // should never happen / just return pageList unchanged
if (currentPage === 1 || currentPage === -1 ) if (currentPage === 1 || currentPage === -1 )
{ {
console.log( "WARNING: seems first on pg=" + firstEntryOnPage + " of how many=" + OPT.howMany + " gives currentPage=" + currentPage + " and we cant go prev page?" ) console.error( "WARNING: seems first on pg=" + firstEntryOnPage + " of how many=" + OPT.how_many + " gives currentPage=" + currentPage + " and we cant go prev page?" )
return return
} }
getPage( currentPage-1 ) getPage( currentPage-1 )

View File

@@ -93,8 +93,8 @@ def CreateSelect(name, selected, list, js="", add_class="", vals={} ):
# TODO: can this be collapsed into using above - probably if the 'selected' passed in was 'In Folder' or 'Flat View' -- but I think that isn't in a var??? # TODO: can this be collapsed into using above - probably if the 'selected' passed in was 'In Folder' or 'Flat View' -- but I think that isn't in a var???
# Helper function used in html files to create a bootstrap'd select with options. Same as CreateSelect() really, only contains # Helper function used in html files to create a bootstrap'd select with options. Same as CreateSelect() really, only contains
# hard-coded True/False around the if selected part, but with string based "True"/"False" in the vals={}, and list has "In Folders", "Flat View" # hard-coded True/False around the if selected part, but with string based "True"/"False" in the vals={}, and list has "In Folders", "Flat View"
def CreateFoldersSelect(selected, add_class=""): def CreateFoldersSelect(selected, js="", add_class=""):
str = f'<select id="folders" name="folders" class="{add_class} sm-txt bg-white text-info border-info border-1 p-1" onChange="this.form.submit()">' str = f'<select id="folders" name="folders" class="{add_class} sm-txt bg-white text-info border-info border-1 p-1" onChange="{js};this.form.submit()">'
# if selected is true, then folders == true, so make this the selected option # if selected is true, then folders == true, so make this the selected option
if( selected ): if( selected ):
str += '<option selected value="True">In Folders</option>' str += '<option selected value="True">In Folders</option>'

271
states.py
View File

@@ -52,157 +52,35 @@ class PA_UserState(db.Model):
################################################################################ ################################################################################
class States(PA): class States(PA):
def __init__(self, request): def __init__(self, request):
self.path_type=''
self.orig_search_term = ''
self.url = request.path self.url = request.path
self.view_eid = None
self.current=0 # set the prefix based on path
self.first_eid=0 path=None
self.last_eid=0 if 'files_ip' in self.url or 'file_list_ip' in self.url:
self.num_entries=0 self.path_type = 'Import'
path = SettingsIPath()
elif 'files_sp' in self.url:
self.path_type = 'Storage'
path = SettingsSPath()
elif 'files_rbp' in self.url:
self.path_type = 'Bin'
path = SettingsRBPath()
elif 'search' in self.url:
self.path_type = 'Search'
self.search_term = ''
else:
self.path_type=''
if path:
self.prefix = SymlinkName(self.path_type,path,path+'/')
else:
self.prefix=None self.prefix=None
# this is any next/prev or noo, grouping, etc. change (so use referrer to work out what to do with this)
# because this can happen on a view, or files_up, etc. change this FIRST
if 'change_file_opts' in request.path:
base=request.base_url
base=base.replace("change_file_opts", "")
self.url = "/"+request.referrer.replace(base, "" )
# if view_list, then we really are a view, and view_eid should be in the form
if 'view_list' in request.path:
self.path_type = 'View'
self.view_eid = request.form['view_eid']
self.url = request.form['orig_url']
# this occurs ONLY when a POST to /view/<id> occurs (at this stage orig_url will be from an import, storage, bin or search)
elif 'view' in request.path:
self.path_type = 'View'
self.view_eid = self.url[6:]
# use orig url to define defaults/look up states for 'last' import/storage/bin/search
if request.method == "POST":
self.url = request.form['orig_url']
else:
# GET's occur on redirect, and we don't have a form, so get it from pref
st=self.url[8:]
if request.referrer and 'search' in request.referrer:
st=re.sub( '.+/search/', '', request.referrer )
else:
st=''
pref=PA_UserState.query.filter(PA_UserState.pa_user_dn==current_user.dn,PA_UserState.path_type==self.path_type,PA_UserState.view_eid==self.view_eid,PA_UserState.orig_search_term==st).first()
if not pref:
SetFELog( message=f"ERROR: pref not found - dn={current_user.dn}, st={st}, s={self}????" , level="danger", persistent=True, cant_close=False )
SetFELog( message=f"WARNING: I think this error occurred because you reloaded a page and the server had restarted between your original page load and this page reload, is that possible?" , level="warning", persistent=True, cant_close=False )
redirect("/")
else:
if not hasattr( pref, 'orig_url' ):
SetFELog( message=f"ERROR: orig_url not in pref - dn={current_user.dn}, st={st}, self={self}, pref={pref}????" , level="danger", persistent=True, cant_close=True )
redirect("/")
self.url = pref.orig_url
if 'files_ip' in self.url or 'file_list_ip' in self.url:
if self.path_type == "View":
self.orig_ptype = 'Import'
self.orig_url = self.url
else:
self.path_type = 'Import'
elif 'files_sp' in self.url:
if self.path_type == "View":
self.orig_ptype = 'Storage'
self.orig_url = self.url
else:
self.path_type = 'Storage'
elif 'files_rbp' in self.url:
if self.path_type == "View":
self.orig_ptype = 'Bin'
self.orig_url = self.url
else:
self.path_type = 'Bin'
elif 'search' in self.url:
# okay if we are a search, but came from a view then get last_search_state form prefs and use it
m=re.match( '.*search/(.+)$', self.url )
if m == None:
SetFELog( message=f"ERROR: DDP messed up, seems we are processing a search, but cant see the search term - is this even possible?" )
return
self.orig_search_term = m[1]
if self.path_type == "View":
self.orig_ptype = 'Search'
self.orig_url = self.url
else:
self.path_type = 'Search'
elif 'view' in self.url:
# use url to get eid of viewed entry
self.view_eid = self.url[6:]
# force this to be a search so rest of code won't totally die, but also not return anything
self.path_type="Search"
self.orig_url=self.url
elif 'change_file_opts' not in self.url:
SetFELog( message=f"ERROR: DDP messed up, failed to match URL {self.url} for settings this will fail, redirecting to home" , level="danger", persistent=True, cant_close=True )
SetFELog( message=f"referrer={request.referrer}" , level="danger", persistent=True, cant_close=True )
return
if self.path_type == 'View':
pref=PA_UserState.query.filter(PA_UserState.pa_user_dn==current_user.dn,PA_UserState.path_type==self.path_type,PA_UserState.view_eid==self.view_eid,PA_UserState.orig_search_term==self.orig_search_term).first()
if not hasattr( self, 'orig_ptype' ):
self.orig_ptype='View'
self.orig_url=''
SetFELog( message=f"ERROR: No orig ptype? s={self} - pref={pref}, redirecting to home" , level="danger", persistent=True, cant_close=True )
SetFELog( message=f"referrer={request.referrer}" , level="danger", persistent=True, cant_close=True )
redirect("/")
# should find original path or search for this view (if not a search, search_term='')
orig_pref=PA_UserState.query.filter(PA_UserState.pa_user_dn==current_user.dn,PA_UserState.path_type==self.orig_ptype,PA_UserState.orig_search_term==self.orig_search_term).first()
if not orig_pref:
SetFELog( message=f"ERROR: DDP messed up 2, failed to find orig_pref for a view pt={self.path_type} for search={self.orig_search_term}" , level="danger", persistent=True, cant_close=True )
SetFELog( message=f"referrer={request.referrer}" , level="danger", persistent=True, cant_close=True )
return
elif self.path_type == 'Search':
pref=PA_UserState.query.filter(PA_UserState.pa_user_dn==current_user.dn,PA_UserState.path_type==self.path_type,PA_UserState.orig_search_term==self.orig_search_term).first()
else:
pref=PA_UserState.query.filter(PA_UserState.pa_user_dn==current_user.dn,PA_UserState.path_type==self.path_type).first()
if pref:
self.grouping=pref.grouping
self.how_many=pref.how_many
self.offset=pref.st_offset
self.size=pref.size
self.cwd=pref.cwd
self.orig_ptype=pref.orig_ptype
self.orig_search_term=pref.orig_search_term
self.orig_url = pref.orig_url
self.view_eid = pref.view_eid
self.current = pref.current
if self.path_type == "View":
self.root='static/' + self.orig_ptype
self.first_eid=orig_pref.first_eid
self.last_eid=orig_pref.last_eid
self.num_entries=orig_pref.num_entries
self.noo=orig_pref.noo
self.folders=orig_pref.folders
self.orig_search_term=orig_pref.orig_search_term
else:
self.root=pref.root
self.first_eid = pref.first_eid
self.last_eid = pref.last_eid
self.num_entries = pref.num_entries
self.noo=pref.noo
self.folders=pref.folders
else:
# retreive defaults from 'PAUser' where defaults are stored # retreive defaults from 'PAUser' where defaults are stored
u=PAUser.query.filter(PAUser.dn==current_user.dn).one() u=PAUser.query.filter(PAUser.dn==current_user.dn).one()
self.grouping=u.default_grouping self.grouping=u.default_grouping
self.how_many=u.default_how_many self.how_many=u.default_how_many
self.offset=0
self.size=u.default_size self.size=u.default_size
if self.path_type == "View":
self.root='static/' + self.orig_ptype
self.first_eid=orig_pref.first_eid
self.last_eid=orig_pref.last_eid
self.num_entries=orig_pref.num_entries
self.noo=orig_pref.noo
self.folders=orig_pref.folders
self.orig_search_term=orig_pref.orig_search_term
else:
self.root='static/' + self.path_type self.root='static/' + self.path_type
if self.path_type == 'Import': if self.path_type == 'Import':
self.noo = u.default_import_noo self.noo = u.default_import_noo
@@ -215,111 +93,10 @@ class States(PA):
self.noo=u.default_search_noo self.noo=u.default_search_noo
self.folders=False self.folders=False
self.default_flat_noo=u.default_import_noo
self.default_folder_noo=u.default_storage_noo
self.default_search_noo=u.default_search_noo
self.cwd=self.root self.cwd=self.root
if not hasattr(self, 'orig_ptype'):
self.orig_ptype=None
if not hasattr(self, 'orig_search_term'):
self.orig_search_term=None
self.orig_url = self.url
# the above are defaults, if we are here, then we have current values, use them instead if they are set -- AI: searches dont set them so then we use those in the DB first
if request.method=="POST":
if self.path_type != "View" and 'noo' in request.form:
# we are changing values based on a POST to the form, if we changed the noo option, we need to reset things
if 'change_file_opts' in request.path and self.noo != request.form['noo']:
self.noo=request.form['noo']
self.first_eid=0
self.last_eid=0
self.offset=0
if 'how_many' in request.form:
self.how_many=request.form['how_many']
if 'offset' in request.form:
self.offset=int(request.form['offset'])
if 'grouping' in request.form:
self.grouping=request.form['grouping']
# this can be null if we come from view by details
if 'size' in request.form:
self.size = request.form['size']
# seems html cant do boolean, but uses strings so convert
if self.path_type != "View" and 'folders' in request.form:
# we are changing values based on a POST to the form, if we are in folder view and we changed the folders option, we need to reset things
if 'change_file_opts' in request.path:
if self.folders and self.folders != request.form['folders']:
self.num_entries=0
self.first_eid=0
self.last_eid=0
if request.form['folders'] == "False":
self.folders=False
else:
self.folders=True
# have to force grouping to None if we flick to folders from a flat view with grouping (otherwise we print out
# group headings for child content that is not in the CWD)
self.grouping=None
if 'orig_url' in request.form:
self.orig_url = request.form['orig_url']
# possible to not be set for an AI: search
if 'cwd' in request.form:
self.cwd = request.form['cwd']
if 'prev' in request.form:
self.offset -= int(self.how_many)
# just in case we hit prev too fast, stop this...
if self.offset < 0:
self.offset=0
if 'next' in request.form:
if (self.offset + int(self.how_many)) < self.num_entries:
self.offset += int(self.how_many)
else:
# tripping this still
SetFELog( message=f"WARNING: next image requested, but would go past end of list? - ignore this" , level="warning", persistent=True, cant_close=False )
if 'current' in request.form:
self.current = int(request.form['current'])
last_used=datetime.now(pytz.utc)
# set the prefix based on path
path=None
if self.path_type == 'Storage':
path = SettingsSPath()
elif self.path_type == 'Import':
path = SettingsIPath()
elif self.path_type == 'Bin':
path = SettingsRBPath()
if path:
self.prefix = SymlinkName(self.path_type,path,path+'/')
# now save pref
if not pref:
# insert new pref for this combo (might be a new search or view, or first time for a path)
pref=PA_UserState( pa_user_dn=current_user.dn, last_used=last_used, path_type=self.path_type, view_eid=self.view_eid,
noo=self.noo, grouping=self.grouping, how_many=self.how_many, st_offset=self.offset, size=self.size,
folders=self.folders, root=self.root, cwd=self.cwd, orig_ptype=self.orig_ptype, orig_search_term=self.orig_search_term,
orig_url=self.orig_url, current=self.current, first_eid=self.first_eid, last_eid=self.last_eid, num_entries=self.num_entries )
else:
# update this pref with the values calculated above (most likely from POST to form)
pref.pa_user_dn=current_user.dn
pref.path_type=self.path_type
pref.view_eid=self.view_eid
pref.noo=self.noo
pref.grouping=self.grouping
pref.how_many=self.how_many
pref.st_offset=self.offset
pref.size=self.size
pref.folders=self.folders
pref.root = self.root
pref.cwd = self.cwd
pref.orig_ptype = self.orig_ptype
pref.orig_search_term = self.orig_search_term
pref.orig_url = self.orig_url
pref.last_used = last_used
pref.first_eid = self.first_eid
pref.last_eid = self.last_eid
pref.num_entries = self.num_entries
# only passed in (at the moment) in view_list
pref.current = self.current
db.session.add(pref)
db.session.commit()
return return

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)
{ {