Compare commits
7 Commits
2e952deda0
...
b0c738fcc1
| Author | SHA1 | Date | |
|---|---|---|---|
| b0c738fcc1 | |||
| 2b9e0e19a2 | |||
| e526d99389 | |||
| 5a923359bc | |||
| a147308b64 | |||
| 9e943c7e1f | |||
| 87651e80a0 |
2
TODO
2
TODO
@@ -1,5 +1,5 @@
|
||||
###
|
||||
# get search to work
|
||||
# get override data into view
|
||||
###
|
||||
|
||||
### major fix - go to everywhere I call GetEntries(), and redo the logic totally...
|
||||
|
||||
451
files.py
451
files.py
@@ -2,7 +2,7 @@ from flask_wtf import FlaskForm
|
||||
from flask import request, render_template, redirect, send_from_directory, url_for, jsonify, make_response
|
||||
from marshmallow import Schema, fields
|
||||
from main import db, app, ma
|
||||
from sqlalchemy import Sequence, text, select
|
||||
from sqlalchemy import Sequence, text, select, union
|
||||
from sqlalchemy.exc import SQLAlchemyError
|
||||
from sqlalchemy.orm import joinedload
|
||||
import os
|
||||
@@ -252,173 +252,6 @@ def UpdatePref( pref, OPT ):
|
||||
pref.last_used=last_used
|
||||
db.session.add(pref)
|
||||
db.session.commit()
|
||||
|
||||
################################################################################
|
||||
# GetEntriesInFlatView: func. to retrieve DB entries appropriate for flat view
|
||||
################################################################################
|
||||
def GetEntriesInFlatView( OPT, prefix ):
|
||||
entries=[]
|
||||
num_entries=0
|
||||
|
||||
join = "Entry.query.join(File).join(EntryDirLink).join(Dir).join(PathDirLink).join(Path).filter(Path.path_prefix==prefix)"
|
||||
entries = eval( f"{join}.{OPT.order}.offset({OPT.offset}).limit({OPT.how_many}).all()" )
|
||||
|
||||
if OPT.first_eid == 0 and OPT.offset == 0 and len(entries):
|
||||
OPT.first_eid = entries[0].id
|
||||
|
||||
if OPT.last_eid==0:
|
||||
num_entries = eval( f"{join}.count()" )
|
||||
last_entry = eval( f"{join}.{OPT.last_order}.limit(1).first()" )
|
||||
if last_entry:
|
||||
OPT.last_eid = last_entry.id
|
||||
|
||||
return entries, num_entries
|
||||
|
||||
################################################################################
|
||||
# GetEntriesInFolderView: func. to retrieve DB entries appropriate for folder view
|
||||
# read inline comments to deal with variations / ordering...
|
||||
################################################################################
|
||||
def GetEntriesInFolderView( OPT, prefix ):
|
||||
entries=[]
|
||||
num_entries=0
|
||||
# okay the root cwd is fake, so treat it specially - its Dir can be found by path with dir.rel_path=''
|
||||
if os.path.dirname(OPT.cwd) == 'static':
|
||||
dir=Entry.query.join(Dir).join(PathDirLink).join(Path).filter(Dir.rel_path=='').filter(Path.path_prefix==prefix).order_by(Entry.name).first()
|
||||
# this can occur if the path in settings does not exist as it wont be in # the DB
|
||||
if not dir:
|
||||
return entries, num_entries
|
||||
# although this is 1 entry, needs to come back via all() to be iterable
|
||||
entries+= Entry.query.filter(Entry.id==dir.id).all()
|
||||
else:
|
||||
rp = OPT.cwd.replace( prefix, '' )
|
||||
# when in subdirs, replacing prefix will leave the first char as /, get rid of it
|
||||
if len(rp) and rp[0] == '/':
|
||||
rp=rp[1:]
|
||||
dir=Entry.query.join(Dir).join(PathDirLink).join(Path).filter(Dir.rel_path==rp).filter(Path.path_prefix==prefix).order_by(Entry.name).first()
|
||||
# this can occur if the path in settings does not exist as it wont be in # the DB
|
||||
if not dir:
|
||||
return entries, 0
|
||||
# dirs cant be sorted by date really, so do best I can for now
|
||||
if OPT.noo == "Z to A" or OPT.noo == "Newest":
|
||||
entries+= Entry.query.join(EntryDirLink).join(FileType).filter(EntryDirLink.dir_eid==dir.id).filter(FileType.name=='Directory').order_by(Entry.name.desc()).all()
|
||||
# just do A to Z / Oldest by default or if no valid option
|
||||
else:
|
||||
entries+= Entry.query.join(EntryDirLink).join(FileType).filter(EntryDirLink.dir_eid==dir.id).filter(FileType.name=='Directory').order_by(Entry.name).all()
|
||||
|
||||
# add any files at the current CWD (based on dir_eid in DB)
|
||||
join="Entry.query.join(File).join(EntryDirLink).filter(EntryDirLink.dir_eid==dir.id)"
|
||||
file_entries= eval( f"{join}.{OPT.order}.offset(OPT.offset).limit(OPT.how_many).all()")
|
||||
|
||||
if OPT.offset == 0 and len(file_entries):
|
||||
OPT.first_eid = file_entries[0].id
|
||||
num_entries = eval( f"{join}.count()" )
|
||||
last_entry = eval( f"{join}.{OPT.last_order}.limit(1).first()" )
|
||||
if last_entry:
|
||||
OPT.last_eid = last_entry.id
|
||||
|
||||
entries += file_entries;
|
||||
return entries, num_entries
|
||||
|
||||
|
||||
################################################################################
|
||||
# GetEntriesInSearchView: func. to retrieve DB entries appropriate for Search view
|
||||
# Defaults search is for any matching filename, contents of any matching dirname
|
||||
# and any match with AI / face for that term. Explicit, only AI match via
|
||||
# AI:<tag> syntax
|
||||
################################################################################
|
||||
def GetEntriesInSearchView( OPT ):
|
||||
search_term=OPT.orig_search_term
|
||||
# turn * wildcard into sql wildcard of %
|
||||
search_term=search_term.replace('*', '%' )
|
||||
if 'AI:' in OPT.orig_search_term:
|
||||
search_term = search_term.replace('AI:','')
|
||||
join=f"Entry.query.join(File).join(FaceFileLink).join(Face).join(FaceRefimgLink).join(Refimg).join(PersonRefimgLink).join(Person).filter(Person.tag == search_term)"
|
||||
else:
|
||||
join=f"Entry.query.join(File).join(FaceFileLink).join(Face).join(FaceRefimgLink).join(Refimg).join(PersonRefimgLink).join(Person).filter(Person.tag.ilike('%{search_term}%'))"
|
||||
if 'AI:' in OPT.orig_search_term:
|
||||
all_entries = eval( f"{join}.{OPT.order}.offset(OPT.offset).limit(OPT.how_many).all()")
|
||||
else:
|
||||
file_data=eval( f"Entry.query.join(File).filter(Entry.name.ilike('%{search_term}%')).{OPT.order}.offset({OPT.offset}).limit({OPT.how_many}).all()" )
|
||||
dir_data =eval( f"Entry.query.join(File).join(EntryDirLink).join(Dir).filter(Dir.rel_path.ilike('%{search_term}%')).{OPT.order}.offset({OPT.offset}).limit({OPT.how_many}).all()" )
|
||||
ai_data =eval( f"{join}.{OPT.order}.offset({OPT.offset}).limit({OPT.how_many}).all()")
|
||||
|
||||
# remove any duplicates from combined data
|
||||
all_entries = []
|
||||
for f in file_data:
|
||||
all_entries.append(f)
|
||||
for d in dir_data:
|
||||
add_it=1
|
||||
for f in file_data:
|
||||
if d.name == f.name:
|
||||
add_it=0
|
||||
break
|
||||
if add_it:
|
||||
all_entries.append(d)
|
||||
for a in ai_data:
|
||||
add_it=1
|
||||
for f in file_data:
|
||||
if a.name == f.name:
|
||||
add_it=0
|
||||
break
|
||||
if add_it:
|
||||
all_entries.append(a)
|
||||
|
||||
# nothing found, just return now
|
||||
if len(all_entries) == 0:
|
||||
OPT.num_entries = 0
|
||||
return []
|
||||
|
||||
# for all searches first_entry is worked out when first_eid not set yet & offset is 0 and we have some entries
|
||||
if OPT.first_eid == 0 and OPT.offset == 0 and len(all_entries):
|
||||
OPT.first_eid = all_entries[0].id
|
||||
if OPT.last_eid == 0:
|
||||
by_fname= f"select e.id from entry e where e.name ilike '%%{search_term}%%'"
|
||||
by_dirname=f"select e.id from entry e, entry_dir_link edl where edl.entry_id = e.id and edl.dir_eid in ( select d.eid from dir d where d.rel_path ilike '%%{search_term}%%' )"
|
||||
by_ai =f"select e.id from entry e, face_file_link ffl, face_refimg_link frl, person_refimg_link prl, person p where e.id = ffl.file_eid and frl.face_id = ffl.face_id and frl.refimg_id = prl.refimg_id and prl.person_id = p.id and p.tag = '{search_term}'"
|
||||
|
||||
if 'AI:' in OPT.orig_search_term:
|
||||
sel_no_order=f"select e.*, f.* from entry e, file f where e.id=f.eid and e.id in ( {by_ai} ) "
|
||||
else:
|
||||
sel_no_order=f"select e.*, f.* from entry e, file f where e.id=f.eid and e.id in ( {by_fname} union {by_dirname} union {by_ai} ) "
|
||||
|
||||
#num_entries
|
||||
num_e_sql = f"select count(1) from ( {by_fname} union {by_dirname} union {by_ai} ) as foo"
|
||||
with db.engine.connect() as conn:
|
||||
OPT.num_entries = conn.execute( text( num_e_sql ) ).first().count
|
||||
|
||||
if OPT.num_entries == 0:
|
||||
return []
|
||||
|
||||
last_entry_sql= f"{sel_no_order} order by {OPT.last_order_raw} limit 1"
|
||||
with db.engine.connect() as conn:
|
||||
OPT.last_eid = conn.execute( text( last_entry_sql ) ).first().id
|
||||
# store first/last eid into prefs
|
||||
pref=PA_UserState.query.filter(PA_UserState.pa_user_dn==current_user.dn,PA_UserState.path_type==OPT.path_type,PA_UserState.orig_ptype==OPT.orig_ptype,PA_UserState.orig_search_term==OPT.orig_search_term).first()
|
||||
UpdatePref( pref, OPT )
|
||||
return all_entries
|
||||
|
||||
################################################################################
|
||||
# set up "order strings" to use in ORM and raw queries as needed for
|
||||
# GetEntries*Search*, GetEntries*Flat*, GetEntries*Fold*
|
||||
################################################################################
|
||||
def SetOrderStrings( OPT ):
|
||||
if OPT.noo == "Newest":
|
||||
OPT.order="order_by(File.year.desc(),File.month.desc(),File.day.desc(),Entry.name.desc())"
|
||||
OPT.last_order="order_by(File.year,File.month,File.day,Entry.name)"
|
||||
OPT.last_order_raw=f"f.year, f.month, f.day, e.name"
|
||||
elif OPT.noo == "Oldest":
|
||||
OPT.order="order_by(File.year,File.month,File.day,Entry.name)"
|
||||
OPT.last_order="order_by(File.year.desc(),File.month.desc(),File.day.desc(),Entry.name.desc())"
|
||||
OPT.last_order_raw=f"f.year desc, f.month desc, f.day desc, e.name desc"
|
||||
elif OPT.noo == "Z to A":
|
||||
OPT.order="order_by(Entry.name.desc())"
|
||||
OPT.last_order="order_by(Entry.name)"
|
||||
OPT.last_order_raw=f"e.name"
|
||||
else:
|
||||
# A to Z
|
||||
OPT.order="order_by(Entry.name)"
|
||||
OPT.last_order="order_by(Entry.name.desc())"
|
||||
OPT.last_order_raw=f"e.name desc"
|
||||
return
|
||||
|
||||
################################################################################
|
||||
@@ -431,9 +264,6 @@ def process_ids():
|
||||
data = request.get_json() # Parse JSON body
|
||||
ids = data.get('ids', []) # Extract list of ids
|
||||
|
||||
# DDP: debate here, do I get query_id, do I validate whether we are asking
|
||||
# for ids not in the query? OR, dont even make/store/have query?
|
||||
|
||||
# Query DB for matching entries
|
||||
stmt = (
|
||||
select(Entry)
|
||||
@@ -473,23 +303,63 @@ def get_dir_entries():
|
||||
|
||||
# get content of dir_id
|
||||
stmt=( select(Entry.id).join(EntryDirLink).filter(EntryDirLink.dir_eid==dir_id) )
|
||||
stmt=stmt.order_by(*order_map.get(OPT.noo) )
|
||||
ids=db.session.execute(stmt).scalars().all()
|
||||
entries_schema = EntrySchema(many=True)
|
||||
entries = Entry.query.filter(Entry.id.in_(ids)).all()
|
||||
return jsonify(entries_schema.dump(entries))
|
||||
|
||||
|
||||
################################################################################
|
||||
# Call this ONCE on first menu choice of View files, or search box submission
|
||||
# create the list of entry ids that matcht the required viewing/list
|
||||
# Get all relevant Entry.ids based on search_term passed in and OPT visuals
|
||||
################################################################################
|
||||
def GetSearchQueryData(OPT):
|
||||
query_data={}
|
||||
query_data['entry_list']=None
|
||||
query_data['root_eid']=0
|
||||
|
||||
search_term = OPT.search_term
|
||||
# turn * wildcard into sql wildcard of %
|
||||
search_term = search_term.replace('*', '%')
|
||||
if 'AI:' in search_term:
|
||||
search_term = search_term.replace('AI:', '')
|
||||
|
||||
# AI searches are for specific ppl/joins in the DB AND we do them for ALL types of searches, define this once
|
||||
ai_query = (
|
||||
select(Entry.id)
|
||||
.join(File).join(FaceFileLink).join(Face).join(FaceRefimgLink).join(Refimg).join(PersonRefimgLink).join(Person)
|
||||
.where(Person.tag == search_term)
|
||||
.order_by(*order_map.get(OPT.noo) )
|
||||
)
|
||||
|
||||
if 'AI:' in search_term:
|
||||
all_entries = db.session.execute(ai_query).scalars().all()
|
||||
else:
|
||||
# match name of File
|
||||
file_query = select(Entry.id).join(File).where(Entry.name.ilike(f'%{search_term}%')).order_by(*order_map.get(OPT.noo))
|
||||
# match name of Dir
|
||||
dir_query = select(Entry.id).join(File).join(EntryDirLink).join(Dir).where(Dir.rel_path.ilike(f'%{search_term}%')).order_by(*order_map.get(OPT.noo))
|
||||
|
||||
# Combine ai, file & dir matches with union() to dedup and then order them
|
||||
combined_query = union( file_query, dir_query, ai_query )
|
||||
all_entries = db.session.execute(combined_query).scalars().all()
|
||||
|
||||
query_data['entry_list']=all_entries
|
||||
return query_data
|
||||
|
||||
|
||||
#################################################################################
|
||||
# Get all relevant Entry.ids based on files_ip/files_sp/files_rbp and OPT visuals
|
||||
#################################################################################
|
||||
def GetQueryData( OPT ):
|
||||
query_data={}
|
||||
query_data['entry_list']=None
|
||||
|
||||
# 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)
|
||||
.where(Dir.rel_path == '').where(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
|
||||
dir_arr=db.session.execute(dir_stmt).scalars().all()
|
||||
dir_id=dir_arr[0]
|
||||
@@ -501,75 +371,28 @@ def GetQueryData( OPT ):
|
||||
stmt=( select(Entry.id).join(EntryDirLink).filter(EntryDirLink.dir_eid==dir_id) )
|
||||
else:
|
||||
# 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)
|
||||
.where(Path.path_prefix == OPT.prefix)
|
||||
)
|
||||
|
||||
stmt=stmt.order_by(*order_map.get(OPT.noo) )
|
||||
query_data['entry_list']=db.session.execute(stmt).scalars().all()
|
||||
|
||||
return query_data
|
||||
|
||||
################################################################################
|
||||
# /GetEntries -> helper function that Gets Entries for required files to show
|
||||
# for several routes (ifles_ip, files_sp, files_rbp, search, view_list)
|
||||
################################################################################
|
||||
def GetEntries( OPT ):
|
||||
entries=[]
|
||||
|
||||
SetOrderStrings( OPT )
|
||||
if OPT.path_type == 'Search' or (OPT.path_type == 'View' and OPT.orig_ptype=='Search'):
|
||||
return GetEntriesInSearchView( OPT )
|
||||
|
||||
# if we are a view, then it will be of something else, e.g. a list of
|
||||
# import, storage, or bin images, reset OPT.path_type so that the paths array below works
|
||||
if 'View' in OPT.path_type:
|
||||
eid = OPT.url[6:]
|
||||
OPT.path_type= OPT.orig_ptype
|
||||
|
||||
paths = []
|
||||
if OPT.path_type == 'Storage':
|
||||
path = SettingsSPath()
|
||||
elif OPT.path_type == 'Import':
|
||||
path = SettingsIPath()
|
||||
elif OPT.path_type == 'Bin':
|
||||
path = SettingsRBPath()
|
||||
|
||||
num_entries=0
|
||||
path_cnt=1
|
||||
|
||||
# if we have not set last_eid yet, then we need to 'reset' it during the
|
||||
# path loop below (if we have more than one dir in (say) Import path)
|
||||
if OPT.last_eid == 0 or OPT.folders:
|
||||
update_last_eid = True
|
||||
else:
|
||||
update_last_eid = False
|
||||
prefix = SymlinkName(OPT.path_type,path,path+'/')
|
||||
if OPT.folders:
|
||||
tmp_ents, tmp_num_ents = GetEntriesInFolderView( OPT, prefix )
|
||||
else:
|
||||
tmp_ents, tmp_num_ents = GetEntriesInFlatView( OPT, prefix )
|
||||
entries += tmp_ents
|
||||
num_entries += tmp_num_ents
|
||||
|
||||
if update_last_eid:
|
||||
# find pref... via path_type if we are here
|
||||
OPT.num_entries=num_entries
|
||||
pref=PA_UserState.query.filter(PA_UserState.pa_user_dn==current_user.dn,PA_UserState.path_type==OPT.path_type).first()
|
||||
UpdatePref( pref, OPT )
|
||||
|
||||
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"])
|
||||
@login_required
|
||||
def change_file_opts2():
|
||||
def change_file_opts():
|
||||
data = request.get_json() # Parse JSON body
|
||||
# allow dot-notation for OPT
|
||||
OPT = SimpleNamespace(**data)
|
||||
if OPT.folders == 'True':
|
||||
if hasattr(OPT, 'folders') and OPT.folders == 'True':
|
||||
OPT.folders=True
|
||||
else:
|
||||
OPT.folders=False
|
||||
@@ -581,26 +404,20 @@ def change_file_opts2():
|
||||
################################################################################
|
||||
# /file_list -> show detailed file list of files from import_path(s)
|
||||
################################################################################
|
||||
@app.route("/file_list_ip", methods=["GET", "POST"])
|
||||
@app.route("/file_list_ip", methods=["GET"])
|
||||
@login_required
|
||||
def file_list_ip():
|
||||
OPT=States( request )
|
||||
# now we have reset the offset, etc. into the prefs, we can use a GET and this will be back/forward browser button safe
|
||||
if request.method=='POST':
|
||||
redirect("/file_list_ip")
|
||||
entries=GetEntries( OPT )
|
||||
return render_template("file_list.html", page_title='View File Details (Import Path)', entry_data=entries, OPT=OPT )
|
||||
query_data = GetQueryData( OPT )
|
||||
return render_template("file_list.html", page_title='View File Details (Import Path)', query_data=query_data, OPT=OPT )
|
||||
|
||||
################################################################################
|
||||
# /files -> show thumbnail view of files from import_path(s)
|
||||
################################################################################
|
||||
@app.route("/files_ip", methods=["GET", "POST"])
|
||||
@app.route("/files_ip", methods=["GET"])
|
||||
@login_required
|
||||
def files_ip():
|
||||
OPT=States( request )
|
||||
# now we have reset the offset, etc. into the prefs, we can use a GET and this will be back/forward browser button safe
|
||||
if request.method=='POST':
|
||||
redirect("/files_ip")
|
||||
people = Person.query.all()
|
||||
move_paths = MovePathDetails()
|
||||
query_data = GetQueryData( OPT )
|
||||
@@ -609,13 +426,10 @@ def files_ip():
|
||||
################################################################################
|
||||
# /files -> show thumbnail view of files from storage_path
|
||||
################################################################################
|
||||
@app.route("/files_sp", methods=["GET", "POST"])
|
||||
@app.route("/files_sp", methods=["GET"])
|
||||
@login_required
|
||||
def files_sp():
|
||||
OPT=States( request )
|
||||
# now we have reset the offset, etc. into the prefs, we can use a GET and this will be back/forward browser button safe
|
||||
if request.method=='POST':
|
||||
redirect("/files_sp")
|
||||
people = Person.query.all()
|
||||
move_paths = MovePathDetails()
|
||||
query_data = GetQueryData( OPT )
|
||||
@@ -625,13 +439,10 @@ def files_sp():
|
||||
################################################################################
|
||||
# /files -> show thumbnail view of files from recycle_bin_path
|
||||
################################################################################
|
||||
@app.route("/files_rbp", methods=["GET", "POST"])
|
||||
@app.route("/files_rbp", methods=["GET"])
|
||||
@login_required
|
||||
def files_rbp():
|
||||
OPT=States( request )
|
||||
# now we have reset the offset, etc. into the prefs, we can use a GET and this will be back/forward browser button safe
|
||||
if request.method=='POST':
|
||||
redirect("/files_rbp")
|
||||
people = Person.query.all()
|
||||
move_paths = MovePathDetails()
|
||||
query_data = GetQueryData( OPT )
|
||||
@@ -645,19 +456,13 @@ def files_rbp():
|
||||
@app.route("/search/<search_term>", methods=["GET", "POST"])
|
||||
@login_required
|
||||
def search(search_term):
|
||||
# print( f"req={request}" )
|
||||
OPT=States( request )
|
||||
# print( f"OPT={OPT}" )
|
||||
|
||||
# if we posted to get here, its a change in State, so save it to pa_user_state, and go back to the GET version or URL
|
||||
if request.method=="POST":
|
||||
redirect("/search/"+search_term)
|
||||
OPT.search_term = search_term
|
||||
# always show flat results for search to start with
|
||||
OPT.folders=False
|
||||
entries=GetEntries( OPT )
|
||||
OPT.folders = False
|
||||
|
||||
query_data=GetSearchQueryData( OPT )
|
||||
move_paths = MovePathDetails()
|
||||
return render_template("files.html", page_title='View Files', search_term=search_term, entry_data=entries, OPT=OPT, move_paths=move_paths )
|
||||
return render_template("files.html", page_title='View Files', search_term=search_term, query_data=query_data, OPT=OPT, move_paths=move_paths )
|
||||
|
||||
################################################################################
|
||||
# /files/scan_ip -> allows us to force a check for new files
|
||||
@@ -786,80 +591,8 @@ def move_files():
|
||||
return make_response( jsonify( job_id=job.id ) )
|
||||
|
||||
@login_required
|
||||
@app.route("/view_list", methods=["POST"])
|
||||
def view_list():
|
||||
OPT=States( request )
|
||||
# Get next/prev set of data - e.g. if next set, then it will use orig_url
|
||||
# to go forward how_many from offset and then use viewer.html to show that
|
||||
# first obj of the new list of entries
|
||||
entries=GetEntries( OPT )
|
||||
# this occurs when we went from the last image on a page (with how_many on
|
||||
# it) and it just happened to also be the last in the DB...
|
||||
if not entries:
|
||||
SetFELog( message="DDP: DONT think this can happen anymore", level="danger", job=None, persistent=True, cant_close=True )
|
||||
|
||||
# undo the skip by how_many and getentries again
|
||||
OPT.offset -= int(OPT.how_many)
|
||||
entries=GetEntries( OPT )
|
||||
# now flag we are at the last in db, to reset current below
|
||||
objs = {}
|
||||
eids=""
|
||||
resp={}
|
||||
resp['objs']={}
|
||||
for e in entries:
|
||||
if not e.file_details:
|
||||
continue
|
||||
eids=eids+f"{e.id},"
|
||||
resp['objs'][e.id]={}
|
||||
resp['objs'][e.id]['url'] = e.FullPathOnFS()
|
||||
resp['objs'][e.id]['name'] = e.name
|
||||
resp['objs'][e.id]['type'] = e.type.name
|
||||
if e.file_details.faces:
|
||||
# model is used for whole file, so set it at that level (based on first face)
|
||||
resp['objs'][e.id]['face_model'] = e.file_details.faces[0].facefile_lnk.model_used
|
||||
resp['objs'][e.id]['faces'] = []
|
||||
|
||||
# put face data back into array format (for js processing)
|
||||
for face in e.file_details.faces:
|
||||
fd= {}
|
||||
fd['x'] = face.face_left
|
||||
fd['y'] = face.face_top
|
||||
fd['w'] = face.w
|
||||
fd['h'] = face.h
|
||||
if face.refimg:
|
||||
fd['pid'] = face.refimg.person.id
|
||||
fd['who'] = face.refimg.person.tag
|
||||
fd['distance'] = round(face.refimg_lnk.face_distance,2)
|
||||
resp['objs'][e.id]['faces'].append(fd)
|
||||
|
||||
eids=eids.rstrip(",")
|
||||
lst = eids.split(',')
|
||||
if 'next' in request.form:
|
||||
OPT.current = int(lst[0])
|
||||
if 'prev' in request.form:
|
||||
OPT.current = int(lst[-1])
|
||||
|
||||
resp['current']=OPT.current
|
||||
# OPT.first_eid can still be 0 IF we have gone past the first page, I could
|
||||
# better set this in states rather than kludge this if... think about it
|
||||
if OPT.first_eid>0:
|
||||
resp['first_eid']=OPT.first_eid
|
||||
resp['last_eid']=OPT.last_eid
|
||||
resp['eids']=eids
|
||||
resp['offset']=OPT.offset
|
||||
# print( f"BUG-DEBUG: /view_list route #1 - OPT={OPT}, eids={eids} ")
|
||||
# save pref to keep the new current value, first/last
|
||||
pref=PA_UserState.query.filter(PA_UserState.pa_user_dn==current_user.dn,PA_UserState.orig_ptype==OPT.orig_ptype,PA_UserState.view_eid==OPT.view_eid).first()
|
||||
# print( f"BUG-DEBUG: /view_list route #2 - OPT={OPT}, eids={eids} ")
|
||||
UpdatePref( pref, OPT )
|
||||
# print( f"BUG-DEBUG: /view_list route #3 - OPT={OPT}, eids={eids} ")
|
||||
|
||||
return make_response( resp )
|
||||
|
||||
|
||||
@login_required
|
||||
@app.route("/newview/", methods=["POST"])
|
||||
def newview():
|
||||
@app.route("/view/", methods=["POST"])
|
||||
def view():
|
||||
data = request.get_json() # Parse JSON body
|
||||
eid = data.get('eid', 0) # Extract list of ids
|
||||
|
||||
@@ -882,22 +615,9 @@ def newview():
|
||||
data=db.session.execute(stmt).unique().scalars().all()
|
||||
return jsonify(entries_schema.dump(data))
|
||||
|
||||
################################################################################
|
||||
# /view/id -> grabs data from DB and views it (GET)
|
||||
################################################################################
|
||||
@login_required
|
||||
@app.route("/view/<id>", methods=["GET"])
|
||||
def view(id):
|
||||
OPT=States( request )
|
||||
objs = {}
|
||||
entries=GetEntries( OPT )
|
||||
eids=""
|
||||
for e in entries:
|
||||
objs[e.id]=e
|
||||
eids += f"{e.id},"
|
||||
# if this is a dir, we wont view it with a click anyway, so move on...
|
||||
if not e.file_details:
|
||||
continue
|
||||
|
||||
####
|
||||
"""
|
||||
# process any overrides
|
||||
for face in e.file_details.faces:
|
||||
# now get any relevant override and store it in objs...
|
||||
@@ -909,20 +629,6 @@ def view(id):
|
||||
mo.type = FaceOverrideType.query.filter( FaceOverrideType.name== 'Manual match to existing person' ).first()
|
||||
face.manual_override=mo
|
||||
|
||||
eids=eids.rstrip(",")
|
||||
# jic, sometimes we trip this, and rather than show broken pages / destroy
|
||||
if id not in eids:
|
||||
# SetFELog( message=f"ERROR: viewing an id, but its not in eids OPT={OPT}, id={id}, eids={eids}", level="danger", persistent=True, cant_close=False)
|
||||
# msg="Sorry, viewing data is confused, cannot view this image now"
|
||||
# if os.environ['ENV'] == "production":
|
||||
# msg += "Clearing out all states. This means browser back buttons will not work, please start a new tab and try again"
|
||||
# PA_UserState.query.delete()
|
||||
# db.session.commit()
|
||||
# SetFELog( msg, "warning", persistent=True, cant_close=False )
|
||||
# return redirect("/")
|
||||
print( f"id={id}, eids={eids}" )
|
||||
return "200"
|
||||
else:
|
||||
NMO_data = FaceOverrideType.query.all()
|
||||
setting = Settings.query.first()
|
||||
imp_path = setting.import_path
|
||||
@@ -930,18 +636,7 @@ def view(id):
|
||||
bin_path = setting.recycle_bin_path
|
||||
# print( f"BUG-DEBUG: /view/id GET route - OPT={OPT}, eids={eids}, current={int(id)} ")
|
||||
return render_template("viewer.html", current=int(id), eids=eids, objs=objs, OPT=OPT, NMO_data=NMO_data, imp_path=imp_path, st_path=st_path, bin_path=bin_path )
|
||||
|
||||
##################################################################################
|
||||
# /view/id -> grabs data from DB and views it (POST -> set state, redirect to GET)
|
||||
##################################################################################
|
||||
@app.route("/view/<id>", methods=["POST"])
|
||||
@login_required
|
||||
def view_img_post(id):
|
||||
# set pa_user_states...
|
||||
OPT=States( request )
|
||||
# print( f"BUG-DEBUG: /view/id POST route - OPT={OPT}, id={id} ")
|
||||
# then use back-button friendly URL (and use pa_user_states to view the right image in the right list
|
||||
return redirect( "/view/" + id );
|
||||
"""
|
||||
|
||||
# route called from front/end - if multiple images are being transformed, each transorm == a separate call
|
||||
# to this route (and therefore a separate transorm job. Each reponse allows the f/e to check the
|
||||
@@ -1006,14 +701,6 @@ def _jinja2_filter_toplevelfolderof(path, cwd):
|
||||
else:
|
||||
return False
|
||||
|
||||
###############################################################################
|
||||
# This func creates a new filter in jinja2 to test to hand back the parent path
|
||||
# from a given path
|
||||
################################################################################
|
||||
@app.template_filter('ParentPath')
|
||||
def _jinja2_filter_parentpath(path):
|
||||
return os.path.dirname(path)
|
||||
|
||||
###############################################################################
|
||||
# route to allow the Move Dialog Box to pass a date (YYYYMMDD) and returns a
|
||||
# json list of existing dir names that could be near it in time. Starting
|
||||
|
||||
@@ -351,11 +351,6 @@ function addFigure( obj, last, ecnt)
|
||||
last.printed = obj.file_details.month;
|
||||
}
|
||||
}
|
||||
/*
|
||||
{% if not entry_data %}
|
||||
<span class="alert alert-danger p-2 col-auto"> No matches for: '{{search_term}}'</span>
|
||||
{% endif %}
|
||||
*/
|
||||
|
||||
// Image/Video/Unknown entry
|
||||
if (obj.type.name === "Image" || obj.type.name === "Video" || obj.type.name === "Unknown") {
|
||||
@@ -466,9 +461,9 @@ function getDirEntries(dir_id, back)
|
||||
$.ajax({
|
||||
type: 'POST',
|
||||
url: '/get_dir_entries',
|
||||
data: JSON.stringify(data), // Stringify the data
|
||||
contentType: 'application/json', // Set content type
|
||||
dataType: 'json', // Expect JSON response
|
||||
data: JSON.stringify(data),
|
||||
contentType: 'application/json',
|
||||
dataType: 'json',
|
||||
success: function(res) {
|
||||
document.entries=res
|
||||
// rebuild entryList/pageList as each dir comes with new entries
|
||||
@@ -525,6 +520,8 @@ function drawPageOfFigures()
|
||||
addFigure( obj, last, ecnt )
|
||||
ecnt++
|
||||
}
|
||||
if( document.entries.length == 0 && OPT.search_term != '' )
|
||||
$('#figures').append( `<span class="alert alert-danger p-2 col-auto"> No matches for: '${OPT.search_term}'</span>` )
|
||||
$('.figure').click( function(e) { DoSel(e, this ); SetButtonState(); return false; });
|
||||
$('.figure').dblclick( function(e) { dblClickToViewEntry( $(this).attr('id') ) } )
|
||||
// for dir, getDirEntries 2nd param is back (or "up" a dir)
|
||||
@@ -532,8 +529,47 @@ function drawPageOfFigures()
|
||||
$(".back").click( function(e) { getDirEntries(this.id,true) } )
|
||||
}
|
||||
|
||||
function getPageFileList(res, viewingIdx)
|
||||
{
|
||||
$('#file_list_div').empty()
|
||||
html='<table class="table table-striped table-sm col-12">'
|
||||
html+='<thead><tr class="table-primary"><th>Name</th><th>Size (MB)</th><th>Path Prefix</th><th>Hash</th></tr></thead><tbody>'
|
||||
for (const obj of res) {
|
||||
html+=`<tr>
|
||||
<td>
|
||||
<div class="d-flex align-items-center">
|
||||
<a href="${obj.in_dir.in_path.path_prefix}/${obj.in_dir.rel_path}/${obj.name}">
|
||||
<img class="img-fluid me-2" style="max-width: 100px;"
|
||||
src="data:image/jpeg;base64,${obj.file_details.thumbnail}"></img>
|
||||
</a>
|
||||
<span>${obj.name}</span>
|
||||
</div>
|
||||
<td>${obj.file_details.size_mb}</td>
|
||||
<td>${obj.in_dir.in_path.path_prefix.replace("static/","")}/${obj.in_dir.rel_path}</td>
|
||||
<td>${obj.file_details.hash}</td>
|
||||
</tr>`
|
||||
}
|
||||
html+='</tbody></table>'
|
||||
$('#file_list_div').append(html)
|
||||
}
|
||||
|
||||
// function called when we get another page from inside the files view
|
||||
function getPageFigures(res, viewingIdx)
|
||||
{
|
||||
// add all the figures to files_div
|
||||
drawPageOfFigures()
|
||||
}
|
||||
|
||||
// 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()
|
||||
}
|
||||
|
||||
// Function to get the 'page' of entry ids out of entryList
|
||||
function getPage(pageNumber,viewing_idx=0)
|
||||
function getPage(pageNumber, successCallback, viewingIdx=0)
|
||||
{
|
||||
// before we do anything, disabled left/right arrows on viewer to stop
|
||||
// getting another event before we have the data for the page back
|
||||
@@ -546,29 +582,14 @@ function getPage(pageNumber,viewing_idx=0)
|
||||
// set up data to send to server to get the entry data for entries in pageList
|
||||
data={}
|
||||
data.ids = pageList
|
||||
data.query = 99999
|
||||
|
||||
$.ajax({
|
||||
type: 'POST',
|
||||
url: '/get_entries_by_ids',
|
||||
data: JSON.stringify(data), // Stringify the data
|
||||
contentType: 'application/json', // Set content type
|
||||
dataType: 'json', // Expect JSON response
|
||||
success: function(res) {
|
||||
document.entries=res
|
||||
// add all the figures to files_div
|
||||
drawPageOfFigures()
|
||||
// noting we could have been in files_div, or viewer_div, update both jic
|
||||
// and fix viewer_div - update viewing, arrows and image/video too
|
||||
document.viewing=document.entries[viewing_idx]
|
||||
resetNextPrevButtons()
|
||||
ViewImageOrVideo()
|
||||
},
|
||||
error: function(xhr, status, error) {
|
||||
console.error("Error:", error);
|
||||
}
|
||||
});
|
||||
|
||||
type: 'POST', url: '/get_entries_by_ids',
|
||||
data: JSON.stringify(data), contentType: 'application/json',
|
||||
dataType: 'json',
|
||||
success: function(res) { document.entries=res; successCallback(res,viewingIdx); },
|
||||
error: function(xhr, status, error) { console.error("Error:", error); } });
|
||||
resetNextPrevButtons()
|
||||
return
|
||||
}
|
||||
|
||||
@@ -611,7 +632,7 @@ function resetNextPrevButtons()
|
||||
}
|
||||
|
||||
// get list of eids for the next page, also make sure next/prev buttons make sense for page we are on
|
||||
function nextPage()
|
||||
function nextPage(successCallback)
|
||||
{
|
||||
// pageList[0] is the first entry on this page
|
||||
const currentPage=getPageNumberForId( pageList[0] )
|
||||
@@ -621,13 +642,12 @@ function nextPage()
|
||||
console.error( "WARNING: seems first on pg=" + firstEntryOnPage + " of how many=" + OPT.how_many + " gives currentPage=" + currentPage + " and we cant go next page?" )
|
||||
return
|
||||
}
|
||||
getPage( currentPage+1 )
|
||||
resetNextPrevButtons()
|
||||
getPage( currentPage+1, successCallback )
|
||||
return
|
||||
}
|
||||
|
||||
// get list of eids for the prev page, also make sure next/prev buttons make sense for page we are on
|
||||
function prevPage()
|
||||
function prevPage(successCallback)
|
||||
{
|
||||
// pageList[0] is the first entry on this page
|
||||
const currentPage=getPageNumberForId( pageList[0] )
|
||||
@@ -637,8 +657,7 @@ function prevPage()
|
||||
console.error( "WARNING: seems first on pg=" + firstEntryOnPage + " of how many=" + OPT.how_many + " gives currentPage=" + currentPage + " and we cant go prev page?" )
|
||||
return
|
||||
}
|
||||
getPage( currentPage-1 )
|
||||
resetNextPrevButtons()
|
||||
getPage( currentPage-1, successCallback )
|
||||
return
|
||||
}
|
||||
|
||||
@@ -646,3 +665,45 @@ function isMobile() {
|
||||
try{ document.createEvent("TouchEvent"); return true; }
|
||||
catch(e){ return false; }
|
||||
}
|
||||
|
||||
|
||||
function changeOPT(successCallback) {
|
||||
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( ` ${OPT.how_many} files ` )
|
||||
OPT.root_eid=parseInt(OPT.root_eid)
|
||||
OPT.size=parseInt(OPT.size)
|
||||
getPage(1,successCallback)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
@@ -513,42 +513,6 @@ class PA_JobManager_FE_Message(Base):
|
||||
return "<id: {}, job_id: {}, level: {}, message: {}".format(self.id, self.job_id, self.level, self.message)
|
||||
|
||||
|
||||
##############################################################################
|
||||
# Class describing PA_UserState and in the DB (via sqlalchemy)
|
||||
# the state for a User defines a series of remembered states for a user
|
||||
# to optimise their viewing, etc. If we scan and fine new files, we need to
|
||||
# invalidate these cached values, so we have this class here just for that
|
||||
##############################################################################
|
||||
class PA_UserState(Base):
|
||||
__tablename__ = "pa_user_state"
|
||||
id = Column(Integer, Sequence('pa_user_state_id_seq'), primary_key=True )
|
||||
pa_user_dn = Column(String, ForeignKey('pa_user.dn'), primary_key=True )
|
||||
last_used = Column(DateTime(timezone=True))
|
||||
path_type = Column(String, primary_key=True, unique=False, nullable=False )
|
||||
noo = Column(String, unique=False, nullable=False )
|
||||
grouping = Column(String, unique=False, nullable=False )
|
||||
how_many = Column(Integer, unique=False, nullable=False )
|
||||
st_offset = Column(Integer, unique=False, nullable=False )
|
||||
size = Column(Integer, unique=False, nullable=False )
|
||||
folders = Column(Boolean, unique=False, nullable=False )
|
||||
root = Column(String, unique=False, nullable=False )
|
||||
cwd = Column(String, unique=False, nullable=False )
|
||||
## for now being lazy and not doing a separate table until I settle on needed fields and when
|
||||
# only used if ptype == View
|
||||
view_eid = Column(Integer, unique=False, nullable=False )
|
||||
orig_ptype = Column(String, unique=False, nullable=False )
|
||||
# only used if view and orig_ptype was search
|
||||
orig_search_term = Column(String, unique=False, nullable=False )
|
||||
orig_url = Column(String, unique=False, nullable=False )
|
||||
current = Column(Integer)
|
||||
first_eid = Column(Integer)
|
||||
last_eid = Column(Integer)
|
||||
num_entries = Column(Integer)
|
||||
|
||||
def __repr__(self):
|
||||
return f"<pa_user_dn: {self.pa_user_dn}, path_type: {self.path_type}, noo: {self.noo}, grouping: {self.grouping}, how_many: {self.how_many}, st_offset: {self.st_offset}, size: {self.size}, folders: {self.folders}, root: {self.root}, cwd: {self.cwd}, view_eid: {self.view_eid}, 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}>"
|
||||
|
||||
|
||||
##############################################################################
|
||||
# PAprint(): convenience function to prepend a timestamp to a printed string
|
||||
##############################################################################
|
||||
@@ -1131,7 +1095,6 @@ def DisconnectAllOverrides(job):
|
||||
def JobForceScan(job):
|
||||
JobProgressState( job, "In Progress" )
|
||||
DisconnectAllOverrides(job)
|
||||
session.query(PA_UserState).delete()
|
||||
session.query(FaceFileLink).delete()
|
||||
session.query(FaceRefimgLink).delete()
|
||||
session.query(Face).delete()
|
||||
@@ -1668,18 +1631,6 @@ def find_last_successful_ai_scan(job):
|
||||
return ai_job.last_update.timestamp()
|
||||
return 0
|
||||
|
||||
####################################################################################################################################
|
||||
# when an import job actually finds new files, then the pa_user_state caches will become invalid (offsets are now wrong)
|
||||
####################################################################################################################################
|
||||
def DeleteOldPA_UserState(job):
|
||||
# clear them out for now - this is 'dumb', just delete ALL. Eventually, can do this based on just the path &/or whether the last_used is
|
||||
# newer than this delete moment (only would be a race condition between an import changing things and someone simultaneously viewing)
|
||||
# path=[jex.value for jex in job.extra if jex.name == "path"][0]
|
||||
session.query(PA_UserState).delete()
|
||||
return
|
||||
|
||||
|
||||
|
||||
####################################################################################################################################
|
||||
# JobImportDir(): job that scan import dir and processes entries in there - key function that uses os.walk() to traverse the
|
||||
# file system and calls AddFile()/AddDir() as necessary
|
||||
@@ -1788,8 +1739,6 @@ def JobImportDir(job):
|
||||
if found_new_files:
|
||||
job.extra.append( JobExtra( name="new_files", value=str(found_new_files) ) )
|
||||
session.add(job)
|
||||
# this will invalidate pa_user_state for this path's contents (offsets are now wrong), clear them out
|
||||
DeleteOldPA_UserState(job)
|
||||
|
||||
rm_cnt=HandleAnyFSDeletions(job)
|
||||
|
||||
|
||||
24
states.py
24
states.py
@@ -19,30 +19,18 @@ class PA_UserState(db.Model):
|
||||
__tablename__ = "pa_user_state"
|
||||
id = db.Column(db.Integer, db.Sequence('pa_user_state_id_seq'), primary_key=True )
|
||||
pa_user_dn = db.Column(db.String, db.ForeignKey('pa_user.dn'), primary_key=True )
|
||||
last_used = db.Column(db.DateTime(timezone=True))
|
||||
path_type = db.Column(db.String, primary_key=True, unique=False, nullable=False )
|
||||
noo = db.Column(db.String, unique=False, nullable=False )
|
||||
grouping = db.Column(db.String, unique=False, nullable=False )
|
||||
how_many = db.Column(db.Integer, unique=False, nullable=False )
|
||||
st_offset = db.Column(db.Integer, unique=False, nullable=False )
|
||||
size = db.Column(db.Integer, unique=False, nullable=False )
|
||||
folders = db.Column(db.Boolean, unique=False, nullable=False )
|
||||
root = db.Column(db.String, unique=False, nullable=False )
|
||||
cwd = db.Column(db.String, unique=False, nullable=False )
|
||||
## for now being lazy and not doing a separate table until I settle on needed fields and when
|
||||
# only used if ptype == View
|
||||
view_eid = db.Column(db.Integer, unique=False, nullable=False )
|
||||
orig_ptype = db.Column(db.String, unique=False, nullable=False )
|
||||
# only used if view and orig_ptype was search
|
||||
orig_search_term = db.Column(db.String, unique=False, nullable=False )
|
||||
orig_url = db.Column(db.String, unique=False, nullable=False )
|
||||
current = db.Column(db.Integer)
|
||||
first_eid = db.Column(db.Integer)
|
||||
last_eid = db.Column(db.Integer)
|
||||
num_entries = db.Column(db.Integer)
|
||||
|
||||
search_term = db.Column(db.String, unique=False, nullable=False )
|
||||
|
||||
def __repr__(self):
|
||||
return f"<pa_user_dn: {self.pa_user_dn}, path_type: {self.path_type}, noo: {self.noo}, grouping: {self.grouping}, how_many: {self.how_many}, st_offset: {self.st_offset}, size: {self.size}, folders: {self.folders}, root: {self.root}, cwd: {self.cwd}, view_eid: {self.view_eid}, 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}>"
|
||||
return f"<pa_user_dn: {self.pa_user_dn}, path_type: {self.path_type}, noo: {self.noo}, grouping: {self.grouping}, how_many: {self.how_many}, size: {self.size}, folders: {self.folders}, root: {self.root}, cwd: {self.cwd}, search_term: {self.orig_search_term}>"
|
||||
|
||||
|
||||
################################################################################
|
||||
@@ -97,9 +85,13 @@ class States(PA):
|
||||
self.default_folder_noo=u.default_storage_noo
|
||||
self.default_search_noo=u.default_search_noo
|
||||
self.cwd=self.root
|
||||
|
||||
return
|
||||
|
||||
def to_dict(self):
|
||||
# Automatically include all instance attributes
|
||||
return {key: value for key, value in vars(self).items()}
|
||||
|
||||
|
||||
################################################################################
|
||||
# /states -> GET only -> prints out list of all prefs (simple for now)
|
||||
################################################################################
|
||||
|
||||
12
tables.sql
12
tables.sql
@@ -27,7 +27,7 @@ INSERT INTO ai_model VALUES ( 2, 'CNN', 'MORE ACCURATE / MUCH SLOWER' );
|
||||
CREATE TABLE settings(
|
||||
id INTEGER,
|
||||
base_path VARCHAR, import_path VARCHAR, storage_path VARCHAR, recycle_bin_path VARCHAR, metadata_path VARCHAR,
|
||||
auto_rotate bOOLEAN,
|
||||
auto_rotate BOOLEAN,
|
||||
default_refimg_model INTEGER, default_scan_model INTEGER, default_threshold FLOAT,
|
||||
face_size_limit INTEGER,
|
||||
scheduled_import_scan INTEGER, scheduled_storage_scan INTEGER,
|
||||
@@ -46,17 +46,15 @@ CREATE TABLE pa_user(
|
||||
default_grouping VARCHAR(16),
|
||||
default_how_many INTEGER,
|
||||
default_size INTEGER,
|
||||
default_import_folders bOOLEAN,
|
||||
default_storage_folders bOOLEAN,
|
||||
default_import_folders BOOLEAN,
|
||||
default_storage_folders BOOLEAN,
|
||||
CONSTRAINT pk_pa_user_id PRIMARY KEY(id) );
|
||||
|
||||
-- this is totally not 3rd normal form, but when I made it that, it was so complex, it was stupid
|
||||
-- so for the little data here, I'm deliberately doing a redundant data structure
|
||||
CREATE TABLE pa_user_state ( id INTEGER, pa_user_dn VARCHAR(128), path_type VARCHAR(16),
|
||||
noo VARCHAR(16), grouping VARCHAR(16), how_many INTEGER, st_offset INTEGER, size INTEGER, folders bOOLEAN,
|
||||
root VARCHAR, cwd VARCHAR,
|
||||
orig_ptype VARCHAR, orig_search_term VARCHAR, orig_url VARCHAR,
|
||||
view_eid INTEGER, current INTEGER, first_eid INTEGER, last_eid INTEGER, num_entries INTEGER, last_used TIMESTAMPTZ,
|
||||
noo VARCHAR(16), grouping VARCHAR(16), how_many INTEGER, size INTEGER, folders BOOLEAN,
|
||||
root VARCHAR, cwd VARCHAR, search_term VARCHAR,
|
||||
CONSTRAINT fk_pa_user_dn FOREIGN KEY (pa_user_dn) REFERENCES pa_user(dn),
|
||||
CONSTRAINT pk_pa_user_states_id PRIMARY KEY(id ) );
|
||||
|
||||
|
||||
@@ -1,64 +1,49 @@
|
||||
{% extends "base.html" %} {% block main_content %}
|
||||
<script src="{{ url_for( 'internal', filename='js/files_support.js')}}"></script>
|
||||
|
||||
<div class="container-fluid">
|
||||
<h3 class="offset-2">{{page_title}}</h3>
|
||||
<form id="main_form" method="POST">
|
||||
<input id="offset" type="hidden" name="offset" value="{{OPT.offset}}">
|
||||
<input id="grouping" type="hidden" name="grouping" value="">
|
||||
<input id="folders" type="hidden" name="folders" value="False">
|
||||
<div class="col col-auto">
|
||||
<div class="input-group">
|
||||
{{CreateSelect( "noo", OPT.noo, ["Oldest", "Newest","A to Z", "Z to A"], "$('#offset').val(0)", "rounded-start py-1 my-1")|safe }}
|
||||
{{CreateSelect( "how_many", OPT.how_many|string, ["10", "25", "50", "75", "100", "150", "200", "500"], "", "rounded-end py-1 my-1" )|safe }}
|
||||
{{CreateSelect( "noo", OPT.noo, ["Oldest", "Newest","A to Z", "Z to A"], "changeOPT(getPageFileList); return false", "rounded-start py-1 my-1")|safe }}
|
||||
{{CreateSelect( "how_many", OPT.how_many|string, ["10", "25", "50", "75", "100", "150", "200", "500"], "changeOPT(getPageFileList); return false", "rounded-end py-1 my-1" )|safe }}
|
||||
<div class="mb-1 col my-auto d-flex justify-content-center">
|
||||
{% set prv_disabled="" %}
|
||||
{% if OPT.offset|int == 0 %}
|
||||
{% set prv_disabled="disabled" %}
|
||||
{% endif %}
|
||||
<button id="prev" {{prv_disabled}} name="prev" class="prev sm-txt btn btn-outline-secondary">
|
||||
<button id="prev" name="prev" class="prev sm-txt btn btn-outline-secondary" onClick="prevPage(getPageFileList)">
|
||||
<svg width="16" height="16" fill="currentColor"><use xlink:href="{{url_for('internal', filename='icons.svg')}}#prev"/></svg>
|
||||
</button>
|
||||
<span class="sm-txt my-auto"> {{OPT.how_many}} files </span>
|
||||
{% set nxt_disabled="" %}
|
||||
{% if entry_data|length < OPT.how_many|int %}
|
||||
{% set nxt_disabled="disabled" %}
|
||||
{% endif %}
|
||||
<button id="next" {{nxt_disabled}} name="next" class="next sm-txt btn btn-outline-secondary">
|
||||
<span class="how_many_text sm-txt my-auto"> {{OPT.how_many}} files </span>
|
||||
<button id="next" name="next" class="next sm-txt btn btn-outline-secondary" onClick="nextPage(getPageFileList)">
|
||||
<svg width="16" height="16" fill="currentColor"><use xlink:href="{{url_for('internal', filename='icons.svg')}}#next"/></svg>
|
||||
</button>
|
||||
</div class="col...">
|
||||
</div class="input-group...">
|
||||
</div class="col col-auto">
|
||||
</form
|
||||
<div class="row">
|
||||
<table class="table table-striped table-sm col-xl-12">
|
||||
<thead><tr class="table-primary"><th>Name</th><th>Size (MB)</th><th>Path Prefix</th><th>Hash</th></tr></thead><tbody>
|
||||
{% for obj in entry_data %}
|
||||
<tr><td>
|
||||
{% if obj.type.name == "Image" or obj.type.name == "Video" %}
|
||||
<figure class="figure" font-size: 24px;>
|
||||
<div style="position:relative; width:100%">
|
||||
{% if obj.type.name=="Image" %}
|
||||
<a href="{{obj.in_dir.in_path.path_prefix}}/{{obj.in_dir.rel_path}}/{{obj.name}}">
|
||||
{% elif obj.type.name == "Video" %}
|
||||
<a href="{{obj.in_dir.in_path.path_prefix}}/{{obj.in_dir.rel_path}}/{{obj.name}}">
|
||||
{% endif %}
|
||||
<img class="thumb" style="display:block" height="48" src="data:image/jpeg;base64,{{obj.file_details.thumbnail}}"></img>
|
||||
{% if obj.type.name=="Image" or obj.type.name == "Video" %}
|
||||
</a>
|
||||
{% endif %}
|
||||
</div>
|
||||
<figcaption class="figure-caption">{{obj.name}}</figcaption>
|
||||
</figure>
|
||||
{% endif %}
|
||||
</td>
|
||||
{% if obj.type.name != "Directory" %}
|
||||
<td>{{obj.file_details.size_mb}}</td><td>{{obj.in_dir.in_path.path_prefix.replace("static/","")}}/{{obj.in_dir.rel_path}}</td><td>{{obj.file_details.hash}}</td>
|
||||
{% else %}
|
||||
<td></td><td></td><td></td>
|
||||
{% endif %}
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody></table>
|
||||
</div class="row">
|
||||
</div class="container">
|
||||
<div id="file_list_div" class="container-fluid">
|
||||
</div class="container">
|
||||
<div class="container-fluid">
|
||||
<input type="hidden" name="cwd" id="cwd" value="{{OPT.cwd}}">
|
||||
<div class="row">
|
||||
<div class="col my-auto d-flex justify-content-center">
|
||||
<button aria-label="prev" id="prev" name="prev" class="prev sm-txt btn btn-outline-secondary disabled" onClick="prevPage(getPageFileList)" disabled>
|
||||
<svg width="16" height="16" fill="currentColor"><use xlink:href="{{url_for('internal', filename='icons.svg')}}#prev"/></svg>
|
||||
</button>
|
||||
<span class="how_many_text sm-txt my-auto"> {{OPT.how_many}} files </span>
|
||||
<button aria-label="next" id="next" name="next" class="next sm-txt btn btn-outline-secondary" onClick="nextPage(getPageFileList)">
|
||||
<svg width="16" height="16" fill="currentColor"><use xlink:href="{{url_for('internal', filename='icons.svg')}}#next"/></svg>
|
||||
</button>
|
||||
</div class="col my-auto"> </div class="row">
|
||||
</div class="container-fluid">
|
||||
{% endblock main_content %}
|
||||
{% block script_content %}
|
||||
<script>
|
||||
// this is the list of entry ids for the images for ALL matches for this query
|
||||
var entryList={{query_data.entry_list}}
|
||||
var OPT = {{ OPT.to_dict()|tojson }};
|
||||
|
||||
// pageList is just those entries shown on this page from the full entryList
|
||||
var pageList=[]
|
||||
// force pageList to set pageList for & render the first page
|
||||
getPage( 1, getPageFileList )
|
||||
</script>
|
||||
{% endblock script_content %}
|
||||
|
||||
@@ -23,19 +23,8 @@
|
||||
document.viewing_eid=null;
|
||||
document.viewing=null;
|
||||
|
||||
var OPT={}
|
||||
OPT.noo='{{OPT.noo}}'
|
||||
OPT.how_many={{OPT.how_many}}
|
||||
OPT.folders="{{OPT.folders}}" === "True"
|
||||
OPT.grouping='{{OPT.grouping}}'
|
||||
OPT.cwd='{{OPT.cwd}}'
|
||||
OPT.root_eid={{query_data.root_eid}}
|
||||
OPT.search_term='{{OPT.orig_search_term}}'
|
||||
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}}'
|
||||
var OPT = {{ OPT.to_dict()|tojson }};
|
||||
OPT.root_eid = {{ query_data.root_eid }};
|
||||
|
||||
// this is the list of entry ids for the images for ALL matches for this query
|
||||
var entryList={{query_data.entry_list}}
|
||||
@@ -43,57 +32,17 @@
|
||||
// pageList is just those entries shown on this page from the full entryList
|
||||
var pageList=[]
|
||||
// force pageList to set pageList for & render the first page
|
||||
getPage(1)
|
||||
getPage(1,getPageFigures)
|
||||
|
||||
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( ` ${OPT.how_many} files ` )
|
||||
OPT.root_eid=parseInt(OPT.root_eid)
|
||||
OPT.size=parseInt(OPT.size)
|
||||
getPage(1)
|
||||
}
|
||||
})
|
||||
function changeSize()
|
||||
{
|
||||
sz=$('input[name="size"]:checked').val();
|
||||
$('.thumb').prop('height',sz);
|
||||
}
|
||||
|
||||
</script>
|
||||
|
||||
<div id="files_div">
|
||||
<div class="container-fluid">
|
||||
<input type="hidden" name="cwd" id="cwd" value="{{OPT.cwd}}">
|
||||
{% if search_term is defined %}
|
||||
<input type="hidden" name="search_term" id="view_term" value="{{search_term}}">
|
||||
{% endif %}
|
||||
<div class="d-flex row mb-2">
|
||||
{% if OPT.folders %}
|
||||
<div class="my-auto col col-auto">
|
||||
@@ -113,13 +62,12 @@
|
||||
{% endif %}
|
||||
<div class="col col-auto">
|
||||
<div class="input-group">
|
||||
{{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"], "cFO(); return false" )|safe }}
|
||||
{{CreateSelect( "noo", OPT.noo, ["Oldest", "Newest","A to Z", "Z to A"], "changeOPT(getPageFigures); return false", "rounded-start py-2")|safe }}
|
||||
{{CreateSelect( "how_many", OPT.how_many|string, ["10", "25", "50", "75", "100", "150", "200", "500"], "changeOPT(getPageFigures); return false" )|safe }}
|
||||
{% if OPT.folders %}
|
||||
<input type="hidden" name="grouping" id="grouping" value="{{OPT.grouping}}">
|
||||
{{CreateFoldersSelect( OPT.folders, "cFO(); return false", "rounded-end" )|safe }}
|
||||
{{CreateFoldersSelect( OPT.folders, "changeOPT(getPageFigures); return false", "rounded-end" )|safe }}
|
||||
{% else %}
|
||||
{{CreateFoldersSelect( OPT.folders, "cFO(); return false" )|safe }}
|
||||
{{CreateFoldersSelect( OPT.folders, "changeOPT(getPageFigures); return false" )|safe }}
|
||||
<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 }}
|
||||
{% endif %}
|
||||
@@ -134,11 +82,11 @@
|
||||
</script>
|
||||
{% endif %}
|
||||
<div class="col flex-grow-1 my-auto d-flex justify-content-center w-100">
|
||||
<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(getPageFigures)" disabled>
|
||||
<svg width="16" height="16" fill="currentColor"><use xlink:href="{{url_for('internal', filename='icons.svg')}}#prev"/></svg>
|
||||
</button>
|
||||
<span class="how_many_text sm-txt my-auto"> {{OPT.how_many}} files </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(getPageFigures)">
|
||||
<svg width="16" height="16" fill="currentColor"><use xlink:href="{{url_for('internal', filename='icons.svg')}}#next"/></svg>
|
||||
</button>
|
||||
<button aria-label="move" id="move" disabled name="move" class="sm-txt btn btn-outline-primary ms-4" onClick="MoveDBox(move_paths,'{{url_for('internal', filename='icons.svg')}}'); return false;">
|
||||
@@ -156,60 +104,35 @@
|
||||
<button style="visibility:hidden" class="btn btn-outline-secondary" aria-label="ctrl-key" id="ctrl-key" onclick="document.fake_ctrl=1-document.fake_ctrl; event.stopPropagation(); return false">ctrl</button>
|
||||
</div class="col flex-grow-1">
|
||||
<div class="d-flex col col-auto justify-content-end">
|
||||
<div class="btn-group">
|
||||
{% if OPT.size == 64 %}
|
||||
{% set bt="btn-info text-white" %}
|
||||
{% else %}
|
||||
{% set bt="btn-outline-info" %}
|
||||
{% endif %}
|
||||
<button aria-label="extra small" id="64" class="px-2 sm-txt sz-but btn {{bt}}" onClick="$('#size').val(64)">XS</button>
|
||||
{% if OPT.size == 96 %}
|
||||
{% set bt="btn-info text-white" %}
|
||||
{% else %}
|
||||
{% set bt="btn-outline-info" %}
|
||||
{% endif %}
|
||||
<button aria-label="small" id="96" class="px-2 sm-txt sz-but btn {{bt}}" onClick="$('#size').val(96)">S</button>
|
||||
{% if OPT.size == 128 %}
|
||||
{% set bt="btn-info text-white" %}
|
||||
{% else %}
|
||||
{% set bt="btn-outline-info" %}
|
||||
{% endif %}
|
||||
<button aria-label="medium" id="128" class="px-2 sm-txt sz-but btn {{bt}}" onClick="$('#size').val(128)">M</button>
|
||||
{% if OPT.size == 192 %}
|
||||
{% set bt="btn-info text-white" %}
|
||||
{% else %}
|
||||
{% set bt="btn-outline-info" %}
|
||||
{% endif %}
|
||||
<button aria-label="large" id="192" class="px-2 sm-txt sz-but btn {{bt}}" onClick="$('#size').val(192)">L</button>
|
||||
{% if OPT.size == 256 %}
|
||||
{% set bt="btn-info text-white" %}
|
||||
{% else %}
|
||||
{% set bt="btn-outline-info" %}
|
||||
{% endif %}
|
||||
<button aria-label="extra large" id="256" class="px-2 sm-txt sz-but btn {{bt}}" onClick="$('#size').val(256)">XL</button>
|
||||
</div class="btn-group">
|
||||
<div class="btn-group" role="group" aria-label="Size radio button group">
|
||||
<input type="radio" class="btn-check" name="size" id="size-xs" onCLick="changeSize()" autocomplete="off" value="64">
|
||||
<label class="btn btn-outline-info btn-radio" for="size-xs">XS</label>
|
||||
|
||||
<input type="radio" class="btn-check" name="size" id="size-s" onCLick="changeSize()" autocomplete="off" value="96">
|
||||
<label class="btn btn-outline-info btn-radio" for="size-s">S</label>
|
||||
|
||||
<input type="radio" class="btn-check" name="size" id="size-m" onCLick="changeSize()" autocomplete="off" value="128">
|
||||
<label class="btn btn-outline-info btn-radio" for="size-m">M</label>
|
||||
|
||||
<input type="radio" class="btn-check" name="size" id="size-l" onCLick="changeSize()" autocomplete="off" value="192">
|
||||
<label class="btn btn-outline-info btn-radio" for="size-l">L</label>
|
||||
|
||||
<input type="radio" class="btn-check" name="size" id="size-xl" onCLick="changeSize()" autocomplete="off" value="256">
|
||||
<label class="btn btn-outline-info btn-radio" for="size-xl">XL</label>
|
||||
</div>
|
||||
</div class="d-flex col">
|
||||
<input id="offset" type="hidden" name="offset" value="{{OPT.offset}}">
|
||||
<input id="size" type="hidden" name="size" value="{{OPT.size}}">
|
||||
</div class="d-flex row mb-2">
|
||||
{% set eids=namespace( str="" ) %}
|
||||
{# gather all the file eids and collect them in case we go gallery mode #}
|
||||
{% for obj in query_data.entry_list %}
|
||||
{% set eids.str = eids.str + obj|string +"," %}
|
||||
{% endfor %}
|
||||
<input name="eids" id="eids" type="hidden" value="{{eids.str}}">
|
||||
</div container="fluid">
|
||||
<div id="figures" class="row ms-2">
|
||||
</div>
|
||||
<div class="container-fluid">
|
||||
<input type="hidden" name="cwd" id="cwd" value="{{OPT.cwd}}">
|
||||
<div class="row">
|
||||
<div class="col my-auto d-flex justify-content-center">
|
||||
<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(getPageFigures)" disabled>
|
||||
<svg width="16" height="16" fill="currentColor"><use xlink:href="{{url_for('internal', filename='icons.svg')}}#prev"/></svg>
|
||||
</button>
|
||||
<span class="how_many_text sm-txt my-auto"> {{OPT.how_many}} files </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(getPageFigures)">
|
||||
<svg width="16" height="16" fill="currentColor"><use xlink:href="{{url_for('internal', filename='icons.svg')}}#next"/></svg>
|
||||
</button>
|
||||
</div class="col my-auto">
|
||||
@@ -219,6 +142,8 @@
|
||||
<div id="viewer_div" class="d-none">
|
||||
|
||||
<style>
|
||||
|
||||
|
||||
.norm-txt { font-size: 1.0rem }
|
||||
.form-check-input:checked {
|
||||
background-color: #39C0ED;
|
||||
@@ -280,7 +205,7 @@
|
||||
// pref page, load it
|
||||
if( oldPageOffset != pageOffset )
|
||||
// pref page is pageOffset+1 now
|
||||
getPage(pageOffset+1,currentIndex)
|
||||
getPage(pageOffset+1,getPageViewer,currentIndex)
|
||||
else
|
||||
document.viewing=document.entries[currentIndex]
|
||||
}
|
||||
@@ -297,7 +222,7 @@
|
||||
// next page, load it
|
||||
if( oldPageOffset != pageOffset )
|
||||
// next page is pageOffset+1 now
|
||||
getPage(pageOffset+1,currentIndex)
|
||||
getPage(pageOffset+1,getPageViewer,currentIndex)
|
||||
else
|
||||
document.viewing=document.entries[currentIndex]
|
||||
}
|
||||
@@ -434,7 +359,9 @@
|
||||
</div class="row">
|
||||
</div id="viewer">
|
||||
</div id="viewer_div">
|
||||
{% endblock main_content %}
|
||||
|
||||
{% block script_content %}
|
||||
<script>
|
||||
$( document ).keydown(function(event) {
|
||||
// if dbox is visible, dont process this hot-key, we are inputting text
|
||||
@@ -477,12 +404,6 @@ $( document ).keydown(function(event) {
|
||||
});
|
||||
|
||||
var fullscreen=false;
|
||||
</script>
|
||||
|
||||
{% endblock main_content %}
|
||||
{% block script_content %}
|
||||
|
||||
<script>
|
||||
|
||||
$(document).on('click', function(e) { $('.highlight').removeClass('highlight') ; SetButtonState() });
|
||||
|
||||
@@ -493,28 +414,6 @@ function dblClickToViewEntry(id) {
|
||||
ViewImageOrVideo()
|
||||
}
|
||||
|
||||
function CallViewRouteWrapper()
|
||||
{
|
||||
// CallViewRoute( document.viewing.id )
|
||||
}
|
||||
|
||||
function CallViewRoute(id)
|
||||
{
|
||||
s='<form id="_fm" method="POST" action="/view/' + id + '">'
|
||||
s+='<input type="hidden" name="eids" value="'+$("#eids").val() + '">'
|
||||
s+='<input type="hidden" name="cwd" value="{{OPT.cwd}}">'
|
||||
s+='<input type="hidden" name="root" value="{{OPT.root}}">'
|
||||
s+='<input type="hidden" name="offset" value="{{OPT.offset}}">'
|
||||
s+='<input type="hidden" name="how_many" value="{{OPT.how_many}}">'
|
||||
s+='<input type="hidden" name="orig_url" value="{{request.path}}">'
|
||||
s+='<input type="hidden" name="view_eid" value="'+id+'">'
|
||||
{% if search_term is defined %}
|
||||
s+='<input type="hidden" name="search_term" value="{{search_term}}">'
|
||||
{% endif %}
|
||||
s+='</form>'
|
||||
$(s).appendTo('body').submit();
|
||||
}
|
||||
|
||||
// different context menu on files
|
||||
$.contextMenu({
|
||||
selector: '.entry',
|
||||
@@ -615,5 +514,7 @@ if( isMobile() )
|
||||
$('#ctrl-key').css('visibility', 'visible');
|
||||
}
|
||||
|
||||
// check the size radiobutton
|
||||
$(`input[name="size"][value="${OPT.size}"]`).prop('checked', true)
|
||||
</script>
|
||||
{% endblock script_content %}
|
||||
|
||||
@@ -69,24 +69,12 @@
|
||||
<tbody>
|
||||
{% for st in states %}
|
||||
<tr>
|
||||
<td>{{st.path_type}}
|
||||
{% if st.path_type == 'Search' %}
|
||||
"{{st.orig_search_term}}"
|
||||
{% endif %}
|
||||
{% if st.path_type == 'View' %}
|
||||
(orig: id={{st.view_eid}} in {{st.orig_ptype}})
|
||||
{% if st.orig_ptype == 'Search' %}
|
||||
"{{st.orig_search_term}}"
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
|
||||
</td>
|
||||
<td>{{st.path_type}}</td>
|
||||
<td>{{st.noo}}</td>
|
||||
<td>{{st.how_many}}</td>
|
||||
<td>{{st.folders}}</td>
|
||||
<td>{{st.grouping}}</td>
|
||||
<td>{{st.size}}</td>
|
||||
<td>{{st.st_offset}}</td>
|
||||
<td>{{st.root}}</td>
|
||||
<td>{{st.cwd}}</td>
|
||||
</tr>
|
||||
|
||||
Reference in New Issue
Block a user