Compare commits
4 Commits
40f0b5d369
...
e5d6ce9b73
| Author | SHA1 | Date | |
|---|---|---|---|
| e5d6ce9b73 | |||
| e0654edd21 | |||
| 24c2922f74 | |||
| 24c3762c61 |
21
TODO
21
TODO
@@ -1,15 +1,20 @@
|
|||||||
###
|
###
|
||||||
# get override data into view
|
#
|
||||||
# should start with an empty DB and test
|
#1 get override data into view
|
||||||
|
# also all the add ref img/add override, etc are non-functional - FIX the override* stuff first to get table/naming consistency as that is half the problem
|
||||||
|
# NMO data -> there is an NMO object (just NMO names/types - |json), then there is per face level data - this should be a reference from Face and Schema/marshmallow
|
||||||
|
#
|
||||||
|
#2 should start with an empty DB and test
|
||||||
# definitely no dirs in storage_sp I now pass root_eid=0 for this
|
# definitely no dirs in storage_sp I now pass root_eid=0 for this
|
||||||
# BUT - need GUI to work - may even be good to put an alert up - its so odd to have not root dir ONLY happens when no data
|
# BUT - need GUI to work - may even be good to put an alert up - its so odd to have not root dir ONLY happens when no data
|
||||||
# empty directories (2017/20171015-test/...) showing "No matches for: 'undefined'" <- should only comes up for search in URL???
|
#3 empty directories (2017/20171015-test/...) showing "No matches for: 'undefined'" <- should only comes up for search in URL???
|
||||||
# think I killed pa_job_manager without passing an eid to a transform job, shouldn't crash
|
#
|
||||||
|
#4 TEST everything (don't forget keybindings,e.g. delete)
|
||||||
|
# -- go into viewer code from a files_rbp - had red bin, bot green on viewer.
|
||||||
|
#
|
||||||
|
#5 think I killed pa_job_manager without passing an eid to a transform job, shouldn't crash
|
||||||
# SHOULD JUST get AI to help clean-up and write defensive code here...
|
# SHOULD JUST get AI to help clean-up and write defensive code here...
|
||||||
# also all the add ref img/add override, etc are non-functional - FIX the override* stuff first to get table/naming consistency as that is half the problem
|
#
|
||||||
# convert move_paths to a json setup
|
|
||||||
# ALSO revisit this move_paths to be as safe as possible ultimately, triple-check there are no leading / or .. 's
|
|
||||||
# TEST everything (don't forget keybindings,e.g. delete)
|
|
||||||
###
|
###
|
||||||
|
|
||||||
### 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...
|
||||||
|
|||||||
61
files.py
61
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 flask import request, render_template, redirect, send_from_directory, url_for, jsonify, make_response
|
||||||
from marshmallow import Schema, fields
|
from marshmallow import Schema, fields
|
||||||
from main import db, app, ma
|
from main import db, app, ma
|
||||||
from sqlalchemy import Sequence, text, select, union
|
from sqlalchemy import Sequence, text, select, union, or_
|
||||||
from sqlalchemy.exc import SQLAlchemyError
|
from sqlalchemy.exc import SQLAlchemyError
|
||||||
from sqlalchemy.orm import joinedload
|
from sqlalchemy.orm import joinedload
|
||||||
import os
|
import os
|
||||||
@@ -28,10 +28,10 @@ from types import SimpleNamespace
|
|||||||
from states import States, PA_UserState
|
from states import States, PA_UserState
|
||||||
from query import Query
|
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
|
||||||
from person import Refimg, Person, PersonRefimgLink
|
from person import Refimg, Person, PersonRefimgLink
|
||||||
from settings import Settings, SettingsIPath, SettingsSPath, SettingsRBPath
|
from settings import Settings, SettingsIPath, SettingsSPath, SettingsRBPath
|
||||||
from shared import SymlinkName
|
from shared import SymlinkName, ICON
|
||||||
from dups import Duplicates
|
from dups import Duplicates
|
||||||
from face import Face, FaceFileLink, FaceRefimgLink, FaceOverrideType, FaceNoMatchOverride, FaceForceMatchOverride
|
from face import Face, FaceFileLink, FaceRefimgLink, FaceOverrideType, FaceNoMatchOverride, FaceForceMatchOverride
|
||||||
|
|
||||||
@@ -170,6 +170,14 @@ class PathSchema(ma.SQLAlchemyAutoSchema):
|
|||||||
class Meta: model = Path
|
class Meta: model = Path
|
||||||
load_instance = True
|
load_instance = True
|
||||||
type = ma.Nested(PathType)
|
type = ma.Nested(PathType)
|
||||||
|
root_dir = fields.Method("get_root_dir")
|
||||||
|
icon_url = fields.Method("get_icon_url")
|
||||||
|
def get_icon_url(self, obj):
|
||||||
|
return url_for("internal", filename="icons.svg") + "#" + ICON[obj.type.name]
|
||||||
|
def get_root_dir(self, obj):
|
||||||
|
parts = obj.path_prefix.split('/')
|
||||||
|
return ''.join(parts[2:])
|
||||||
|
|
||||||
|
|
||||||
class FileTypeSchema(ma.SQLAlchemyAutoSchema):
|
class FileTypeSchema(ma.SQLAlchemyAutoSchema):
|
||||||
class Meta: model = FileType
|
class Meta: model = FileType
|
||||||
@@ -216,6 +224,11 @@ class FileSchema(ma.SQLAlchemyAutoSchema):
|
|||||||
load_instance = True
|
load_instance = True
|
||||||
faces = ma.Nested(FaceSchema,many=True,allow_none=True)
|
faces = ma.Nested(FaceSchema,many=True,allow_none=True)
|
||||||
|
|
||||||
|
# used just in NMO var
|
||||||
|
class FaceOverrideTypeSchema(ma.SQLAlchemyAutoSchema):
|
||||||
|
class Meta: model = FaceOverrideType
|
||||||
|
load_instance = True
|
||||||
|
|
||||||
################################################################################
|
################################################################################
|
||||||
# Schema for Entry so we can json for data to the client
|
# Schema for Entry so we can json for data to the client
|
||||||
################################################################################
|
################################################################################
|
||||||
@@ -238,6 +251,9 @@ class EntrySchema(ma.SQLAlchemyAutoSchema):
|
|||||||
|
|
||||||
# global - this will be use more than once below, so do it once for efficiency
|
# global - this will be use more than once below, so do it once for efficiency
|
||||||
entries_schema = EntrySchema(many=True)
|
entries_schema = EntrySchema(many=True)
|
||||||
|
FOT_Schema = FaceOverrideTypeSchema(many=True)
|
||||||
|
path_Schema = PathSchema(many=True)
|
||||||
|
person_Schema = PersonSchema(many=True)
|
||||||
|
|
||||||
################################################################################
|
################################################################################
|
||||||
# util function to just update the current/first/last positions needed for
|
# util function to just update the current/first/last positions needed for
|
||||||
@@ -314,6 +330,25 @@ def get_dir_entries():
|
|||||||
entries = Entry.query.filter(Entry.id.in_(ids)).all()
|
entries = Entry.query.filter(Entry.id.in_(ids)).all()
|
||||||
return jsonify(entries_schema.dump(entries))
|
return jsonify(entries_schema.dump(entries))
|
||||||
|
|
||||||
|
# get Face overrid details
|
||||||
|
def getFOT():
|
||||||
|
stmt = select(FaceOverrideType)
|
||||||
|
fot=db.session.execute(stmt).scalars().all()
|
||||||
|
return FOT_Schema.dump(fot)
|
||||||
|
|
||||||
|
|
||||||
|
# get import/storage path details for move dbox
|
||||||
|
def getMoveDetails():
|
||||||
|
stmt = select(Path).where( or_( Path.type.has(name="Import"), Path.type.has(name="Storage")))
|
||||||
|
mp=db.session.execute(stmt).scalars().all()
|
||||||
|
return path_Schema.dump(mp)
|
||||||
|
|
||||||
|
# get people data for the menu for AI matching (of person.tag)
|
||||||
|
def getPeople():
|
||||||
|
stmt = select(Person)
|
||||||
|
people=db.session.execute(stmt).scalars().all()
|
||||||
|
return person_Schema.dump(people)
|
||||||
|
|
||||||
|
|
||||||
################################################################################
|
################################################################################
|
||||||
# Get all relevant Entry.ids based on search_term passed in and OPT visuals
|
# Get all relevant Entry.ids based on search_term passed in and OPT visuals
|
||||||
@@ -322,6 +357,9 @@ def GetSearchQueryData(OPT):
|
|||||||
query_data={}
|
query_data={}
|
||||||
query_data['entry_list']=None
|
query_data['entry_list']=None
|
||||||
query_data['root_eid']=0
|
query_data['root_eid']=0
|
||||||
|
query_data['NMO'] = getFOT()
|
||||||
|
query_data['move_paths'] = getMoveDetails()
|
||||||
|
query_data['people'] = getPeople()
|
||||||
|
|
||||||
search_term = OPT.search_term
|
search_term = OPT.search_term
|
||||||
# turn * wildcard into sql wildcard of %
|
# turn * wildcard into sql wildcard of %
|
||||||
@@ -352,13 +390,15 @@ def GetSearchQueryData(OPT):
|
|||||||
query_data['entry_list']=all_entries
|
query_data['entry_list']=all_entries
|
||||||
return query_data
|
return query_data
|
||||||
|
|
||||||
|
|
||||||
#################################################################################
|
#################################################################################
|
||||||
# Get all relevant Entry.ids based on files_ip/files_sp/files_rbp and OPT visuals
|
# Get all relevant Entry.ids based on files_ip/files_sp/files_rbp and OPT visuals
|
||||||
#################################################################################
|
#################################################################################
|
||||||
def GetQueryData( OPT ):
|
def GetQueryData( OPT ):
|
||||||
query_data={}
|
query_data={}
|
||||||
query_data['entry_list']=None
|
query_data['entry_list']=None
|
||||||
|
query_data['NMO'] = getFOT()
|
||||||
|
query_data['move_paths'] = getMoveDetails()
|
||||||
|
query_data['people'] = getPeople()
|
||||||
|
|
||||||
# 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=(
|
dir_stmt=(
|
||||||
@@ -388,7 +428,6 @@ def GetQueryData( OPT ):
|
|||||||
|
|
||||||
stmt=stmt.order_by(*order_map.get(OPT.noo) )
|
stmt=stmt.order_by(*order_map.get(OPT.noo) )
|
||||||
query_data['entry_list']=db.session.execute(stmt).scalars().all()
|
query_data['entry_list']=db.session.execute(stmt).scalars().all()
|
||||||
|
|
||||||
return query_data
|
return query_data
|
||||||
|
|
||||||
################################################################################
|
################################################################################
|
||||||
@@ -428,9 +467,8 @@ def file_list_ip():
|
|||||||
def files_ip():
|
def files_ip():
|
||||||
OPT=States( request )
|
OPT=States( request )
|
||||||
people = Person.query.all()
|
people = Person.query.all()
|
||||||
move_paths = MovePathDetails()
|
|
||||||
query_data = GetQueryData( OPT )
|
query_data = GetQueryData( OPT )
|
||||||
return render_template("files.html", page_title=f"View Files ({OPT.path_type} Path)", OPT=OPT, people=people, move_paths=move_paths, query_data=query_data )
|
return render_template("files.html", page_title=f"View Files ({OPT.path_type} Path)", OPT=OPT, people=people, query_data=query_data )
|
||||||
|
|
||||||
################################################################################
|
################################################################################
|
||||||
# /files -> show thumbnail view of files from storage_path
|
# /files -> show thumbnail view of files from storage_path
|
||||||
@@ -440,9 +478,8 @@ def files_ip():
|
|||||||
def files_sp():
|
def files_sp():
|
||||||
OPT=States( request )
|
OPT=States( request )
|
||||||
people = Person.query.all()
|
people = Person.query.all()
|
||||||
move_paths = MovePathDetails()
|
|
||||||
query_data = GetQueryData( OPT )
|
query_data = GetQueryData( OPT )
|
||||||
return render_template("files.html", page_title=f"View Files ({OPT.path_type} Path)", OPT=OPT, people=people, move_paths=move_paths, query_data=query_data )
|
return render_template("files.html", page_title=f"View Files ({OPT.path_type} Path)", OPT=OPT, people=people, query_data=query_data )
|
||||||
|
|
||||||
|
|
||||||
################################################################################
|
################################################################################
|
||||||
@@ -453,9 +490,8 @@ def files_sp():
|
|||||||
def files_rbp():
|
def files_rbp():
|
||||||
OPT=States( request )
|
OPT=States( request )
|
||||||
people = Person.query.all()
|
people = Person.query.all()
|
||||||
move_paths = MovePathDetails()
|
|
||||||
query_data = GetQueryData( OPT )
|
query_data = GetQueryData( OPT )
|
||||||
return render_template("files.html", page_title=f"View Files ({OPT.path_type} Path)", OPT=OPT, people=people, move_paths=move_paths, query_data=query_data )
|
return render_template("files.html", page_title=f"View Files ({OPT.path_type} Path)", OPT=OPT, people=people, query_data=query_data )
|
||||||
|
|
||||||
################################################################################
|
################################################################################
|
||||||
# search -> GET version -> has search_term in the URL and is therefore able to
|
# search -> GET version -> has search_term in the URL and is therefore able to
|
||||||
@@ -470,8 +506,7 @@ def search(search_term):
|
|||||||
OPT.folders = False
|
OPT.folders = False
|
||||||
|
|
||||||
query_data=GetSearchQueryData( OPT )
|
query_data=GetSearchQueryData( OPT )
|
||||||
move_paths = MovePathDetails()
|
return render_template("files.html", page_title='View Files', search_term=search_term, query_data=query_data, OPT=OPT )
|
||||||
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
|
# /files/scan_ip -> allows us to force a check for new files
|
||||||
|
|||||||
@@ -109,7 +109,7 @@ function MoveOrDelCleanUpUI()
|
|||||||
|
|
||||||
// show the DBox for a move file, includes all thumbnails of selected files to move
|
// show the DBox for a move file, includes all thumbnails of selected files to move
|
||||||
// and a pre-populated folder to move them into, with text field to add a suffix
|
// and a pre-populated folder to move them into, with text field to add a suffix
|
||||||
function MoveDBox(path_details, db_url)
|
function MoveDBox(path_details)
|
||||||
{
|
{
|
||||||
$('#dbox-title').html('Move Selected File(s) to new directory in Storage Path')
|
$('#dbox-title').html('Move Selected File(s) to new directory in Storage Path')
|
||||||
div =`
|
div =`
|
||||||
@@ -119,12 +119,12 @@ function MoveDBox(path_details, db_url)
|
|||||||
<form id="mv_fm" class="form form-control-inline col-12">
|
<form id="mv_fm" class="form form-control-inline col-12">
|
||||||
<input id="move_path_type" name="move_path_type" type="hidden"
|
<input id="move_path_type" name="move_path_type" type="hidden"
|
||||||
`
|
`
|
||||||
div += ' value="' + path_details[0].type + '"></input>'
|
div += ' value="' + path_details[0].type.name + '"></input>'
|
||||||
div+=GetSelnAsDiv()
|
div+=GetSelnAsDiv()
|
||||||
yr=$('.highlight').first().attr('yr')
|
yr=$('.highlight').first().attr('yr')
|
||||||
dt=$('.highlight').first().attr('date')
|
dt=$('.highlight').first().attr('date')
|
||||||
div+='<div class="row">Use Existing Directory (in the chosen path):</div><div id="existing"></div>'
|
div+='<div class="row">Use Existing Directory (in the chosen path):</div><div id="existing"></div>'
|
||||||
GetExistingDirsAsDiv( dt, "existing", path_details[0].type )
|
GetExistingDirsAsDiv( dt, "existing", path_details[0].type.name )
|
||||||
div+=`
|
div+=`
|
||||||
<div class="input-group my-3">
|
<div class="input-group my-3">
|
||||||
<alert class="alert alert-primary my-auto py-1">
|
<alert class="alert alert-primary my-auto py-1">
|
||||||
@@ -133,7 +133,7 @@ function MoveDBox(path_details, db_url)
|
|||||||
div+= '<svg id="move_path_icon" width="20" height="20" fill="currentColor"><use xlink:href="' + path_details[0].icon_url + '"></svg>'
|
div+= '<svg id="move_path_icon" width="20" height="20" fill="currentColor"><use xlink:href="' + path_details[0].icon_url + '"></svg>'
|
||||||
div+= '<select id="rp_sel" name="rel_path" class="text-primary alert-primary py-1 border border-primary rounded" onChange="change_rp_sel()">'
|
div+= '<select id="rp_sel" name="rel_path" class="text-primary alert-primary py-1 border border-primary rounded" onChange="change_rp_sel()">'
|
||||||
for(p of path_details) {
|
for(p of path_details) {
|
||||||
div+= '<option path_type="'+p.type+'" icon_url="'+p.icon_url+'">'+p.path+'</option>'
|
div+= '<option path_type="'+p.type.name+'" icon_url="'+p.icon_url+'">'+p.root_dir+'</option>'
|
||||||
}
|
}
|
||||||
div+= '</select>'
|
div+= '</select>'
|
||||||
div+=`
|
div+=`
|
||||||
@@ -838,3 +838,87 @@ function dblClickToViewEntry(id) {
|
|||||||
setEntryById( id )
|
setEntryById( id )
|
||||||
ViewImageOrVideo()
|
ViewImageOrVideo()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// different context menu on files
|
||||||
|
$.contextMenu({
|
||||||
|
selector: '.entry',
|
||||||
|
itemClickEvent: "click",
|
||||||
|
build: function($triggerElement, e) {
|
||||||
|
// when right-clicking & no selection add one OR deal with ctrl/shift right-lick as it always changes seln
|
||||||
|
if( NoSel() || e.ctrlKey || e.shiftKey )
|
||||||
|
{
|
||||||
|
DoSel(e, e.currentTarget )
|
||||||
|
SetButtonState();
|
||||||
|
}
|
||||||
|
|
||||||
|
if( FiguresOrDirsOrBoth() == "figure" )
|
||||||
|
{
|
||||||
|
item_list = {
|
||||||
|
details: { name: "Details..." },
|
||||||
|
view: { name: "View File" },
|
||||||
|
sep: "---",
|
||||||
|
}
|
||||||
|
if( e.currentTarget.getAttribute('type') == 'Image' )
|
||||||
|
{
|
||||||
|
item_list['transform'] = {
|
||||||
|
name: "Transform",
|
||||||
|
items: {
|
||||||
|
"r90": { "name" : "Rotate 90 degrees" },
|
||||||
|
"r180": { "name" : "Rotate 180 degrees" },
|
||||||
|
"r270": { "name" : "Rotate 270 degrees" },
|
||||||
|
"fliph": { "name" : "Flip horizontally" },
|
||||||
|
"flipv": { "name" : "Flip vertically" }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
item_list['move'] = { name: "Move selected file(s) to new folder" }
|
||||||
|
item_list['sep2'] = { sep: "---" }
|
||||||
|
}
|
||||||
|
else
|
||||||
|
item_list = {
|
||||||
|
move: { name: "Move selection(s) to new folder" }
|
||||||
|
}
|
||||||
|
|
||||||
|
item_list['ai'] = {
|
||||||
|
name: "Scan file for faces",
|
||||||
|
items: {
|
||||||
|
"ai-all": { name: "all" }
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Dynamically add entries for each person in the `people` array
|
||||||
|
people.forEach(person => {
|
||||||
|
item_list['ai'].items[`ai-${person.tag}`] = { name: person.tag };
|
||||||
|
});
|
||||||
|
|
||||||
|
if( SelContainsBinAndNotBin() ) {
|
||||||
|
item_list['both']= { name: 'Cannot delete and restore at same time', disabled: true }
|
||||||
|
} else {
|
||||||
|
if (e.currentTarget.getAttribute('path_type') == 'Bin' )
|
||||||
|
item_list['undel']= { name: "Restore selected file(s)" }
|
||||||
|
else if( e.currentTarget.getAttribute('type') != 'Directory' )
|
||||||
|
item_list['del']= { name: "Delete Selected file(s)" }
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
callback: function( key, options) {
|
||||||
|
if( key == "details" ) { DetailsDBox() }
|
||||||
|
if( key == "view" ) { dblClickToViewEntry( $(this).attr('id') ) }
|
||||||
|
if( key == "move" ) { MoveDBox(move_paths) }
|
||||||
|
if( key == "del" ) { DelDBox('Delete') }
|
||||||
|
if( key == "undel") { DelDBox('Restore') }
|
||||||
|
if( key == "r90" ) { Transform(90) }
|
||||||
|
if( key == "r180" ) { Transform(180) }
|
||||||
|
if( key == "r270" ) { Transform(270) }
|
||||||
|
if( key == "fliph" ) { Transform("fliph") }
|
||||||
|
if( key == "flipv" ) { Transform("flipv") }
|
||||||
|
if( key.startsWith("ai")) { RunAIOnSeln(key) }
|
||||||
|
// dont flow this event through the dom
|
||||||
|
e.stopPropagation()
|
||||||
|
},
|
||||||
|
items: item_list
|
||||||
|
};
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|||||||
50
path.py
50
path.py
@@ -42,53 +42,3 @@ class Path(db.Model):
|
|||||||
|
|
||||||
def __repr__(self):
|
def __repr__(self):
|
||||||
return f"<id: {self.id}, path_prefix: {self.path_prefix}, num_files={self.num_files}, type={self.type}>"
|
return f"<id: {self.id}, path_prefix: {self.path_prefix}, num_files={self.num_files}, type={self.type}>"
|
||||||
|
|
||||||
|
|
||||||
################################################################################
|
|
||||||
# Class describing PathDetail (quick connvenence class for MovePathDetails())
|
|
||||||
################################################################################
|
|
||||||
class PathDetail(PA):
|
|
||||||
"""Class describing details of a Path [internal class used in MovePathDetais()]"""
|
|
||||||
|
|
||||||
def __init__(self,ptype,path):
|
|
||||||
"""Initialisation function for PathDetail class
|
|
||||||
|
|
||||||
Args:
|
|
||||||
id (int): database id of row in PathDetail table / primary key
|
|
||||||
ptype (int): database id of row in PathType table / foreign key
|
|
||||||
"""
|
|
||||||
|
|
||||||
self.type:int=ptype
|
|
||||||
self.path:str=path
|
|
||||||
# construct icon_url based on type of storage path (icons.svg contains icons for each)
|
|
||||||
self.icon_url:str=url_for("internal", filename="icons.svg") + "#" + ICON[self.type]
|
|
||||||
|
|
||||||
def to_dict(self):
|
|
||||||
return {key: value for key, value in vars(self).items()}
|
|
||||||
|
|
||||||
################################################################################
|
|
||||||
# helper function to find path details for move destinations - used in html
|
|
||||||
# for move DBox to show potential storage paths to move files into
|
|
||||||
################################################################################
|
|
||||||
def MovePathDetails():
|
|
||||||
"""helper function to find path details for move destinations
|
|
||||||
|
|
||||||
used in html/javascript for move Dialog Box to show potential storage paths to move files into
|
|
||||||
|
|
||||||
Args:
|
|
||||||
None
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
ret (List[PathDetail]): a list of Path Details for where files can be moved
|
|
||||||
|
|
||||||
"""
|
|
||||||
ret=[]
|
|
||||||
sps=Path.query.join(PathType).filter(PathType.name=="Storage").all()
|
|
||||||
for p in sps:
|
|
||||||
obj = PathDetail( ptype="Storage", path=p.path_prefix.replace("static/Storage/","") )
|
|
||||||
ret.append( obj.to_dict() )
|
|
||||||
ips=Path.query.join(PathType).filter(PathType.name=="Import").all()
|
|
||||||
for p in ips:
|
|
||||||
obj = PathDetail( ptype="Import", path=p.path_prefix.replace("static/Import/","") )
|
|
||||||
ret.append( obj.to_dict() )
|
|
||||||
return ret
|
|
||||||
|
|||||||
@@ -64,7 +64,7 @@
|
|||||||
<button aria-label="next" id="next" name="next" class="next sm-txt btn btn-outline-secondary" onClick="nextPage(getPageFigures)">
|
<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>
|
<svg width="16" height="16" fill="currentColor"><use xlink:href="{{url_for('internal', filename='icons.svg')}}#next"/></svg>
|
||||||
</button>
|
</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;">
|
<button aria-label="move" id="move" disabled name="move" class="sm-txt btn btn-outline-primary ms-4" onClick="MoveDBox(move_paths); return false;">
|
||||||
<svg width="16" height="16" fill="currentColor"><use xlink:href="{{url_for('internal', filename='icons.svg')}}#folder_plus"/></svg>
|
<svg width="16" height="16" fill="currentColor"><use xlink:href="{{url_for('internal', filename='icons.svg')}}#folder_plus"/></svg>
|
||||||
</button>
|
</button>
|
||||||
{% if "files_rbp" in request.url %}
|
{% if "files_rbp" in request.url %}
|
||||||
@@ -221,19 +221,24 @@
|
|||||||
|
|
||||||
{% block script_content %}
|
{% block script_content %}
|
||||||
<script>
|
<script>
|
||||||
|
// GLOBALS
|
||||||
document.fake_shift=0
|
document.fake_shift=0
|
||||||
document.fake_ctrl=0
|
document.fake_ctrl=0
|
||||||
|
|
||||||
// FIXME: used by viewer code - should probably get rid of this?
|
// FIXME: used by viewer code - should probably get rid of this?
|
||||||
var fullscreen=false;
|
var fullscreen=false;
|
||||||
var move_paths = {{ move_paths|tojson }};
|
|
||||||
|
|
||||||
// GLOBALS
|
|
||||||
// this is the current entry (object) we are viewing - an image/video (used when we dbl-click to view & then in next/prev in view)
|
// this is the current entry (object) we are viewing - an image/video (used when we dbl-click to view & then in next/prev in view)
|
||||||
document.viewing=null;
|
document.viewing=null;
|
||||||
|
|
||||||
var OPT = {{ OPT.to_dict()|tojson }};
|
var OPT = {{ OPT.to_dict()|tojson }};
|
||||||
OPT.root_eid = {{ query_data.root_eid }};
|
OPT.root_eid = {{ query_data.root_eid }};
|
||||||
|
|
||||||
|
// get items out of query_data into convenience javascript vars...
|
||||||
|
var move_paths = {{ query_data.move_paths|tojson }};
|
||||||
|
var NMO={{query_data.NMO|tojson}}
|
||||||
|
var people={{query_data.people|tojson}}
|
||||||
|
|
||||||
// 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}}
|
||||||
|
|
||||||
@@ -247,7 +252,6 @@
|
|||||||
var grayscale=0
|
var grayscale=0
|
||||||
var throbber=0
|
var throbber=0
|
||||||
|
|
||||||
var NMO=[]
|
|
||||||
var imp_path="static/Import/{{imp_path}}"
|
var imp_path="static/Import/{{imp_path}}"
|
||||||
var st_path="static/Storage/{{st_path}}"
|
var st_path="static/Storage/{{st_path}}"
|
||||||
var bin_path="static/Bin/{{bin_path}}"
|
var bin_path="static/Bin/{{bin_path}}"
|
||||||
@@ -274,108 +278,11 @@
|
|||||||
return s
|
return s
|
||||||
}
|
}
|
||||||
|
|
||||||
// different context menu on files
|
|
||||||
$.contextMenu({
|
|
||||||
selector: '.entry',
|
|
||||||
itemClickEvent: "click",
|
|
||||||
build: function($triggerElement, e) {
|
|
||||||
// when right-clicking & no selection add one OR deal with ctrl/shift right-lick as it always changes seln
|
|
||||||
if( NoSel() || e.ctrlKey || e.shiftKey )
|
|
||||||
{
|
|
||||||
DoSel(e, e.currentTarget )
|
|
||||||
SetButtonState();
|
|
||||||
}
|
|
||||||
|
|
||||||
if( FiguresOrDirsOrBoth() == "figure" )
|
|
||||||
{
|
|
||||||
item_list = {
|
|
||||||
details: { name: "Details..." },
|
|
||||||
view: { name: "View File" },
|
|
||||||
sep: "---",
|
|
||||||
}
|
|
||||||
if( e.currentTarget.getAttribute('type') == 'Image' )
|
|
||||||
{
|
|
||||||
item_list['transform'] = {
|
|
||||||
name: "Transform",
|
|
||||||
items: {
|
|
||||||
"r90": { "name" : "Rotate 90 degrees" },
|
|
||||||
"r180": { "name" : "Rotate 180 degrees" },
|
|
||||||
"r270": { "name" : "Rotate 270 degrees" },
|
|
||||||
"fliph": { "name" : "Flip horizontally" },
|
|
||||||
"flipv": { "name" : "Flip vertically" }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
item_list['move'] = { name: "Move selected file(s) to new folder" }
|
|
||||||
item_list['sep2'] = { sep: "---" }
|
|
||||||
}
|
|
||||||
else
|
|
||||||
item_list = {
|
|
||||||
move: { name: "Move selection(s) to new folder" }
|
|
||||||
}
|
|
||||||
|
|
||||||
item_list['ai'] = {
|
|
||||||
name: "Scan file for faces",
|
|
||||||
items: {
|
|
||||||
"ai-all": {"name": "all"},
|
|
||||||
{% for p in people %}
|
|
||||||
"ai-{{p.tag}}": {"name": "{{p.tag}}"},
|
|
||||||
{% endfor %}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if( SelContainsBinAndNotBin() ) {
|
|
||||||
item_list['both']= { name: 'Cannot delete and restore at same time', disabled: true }
|
|
||||||
} else {
|
|
||||||
if (e.currentTarget.getAttribute('path_type') == 'Bin' )
|
|
||||||
item_list['undel']= { name: "Restore selected file(s)" }
|
|
||||||
else if( e.currentTarget.getAttribute('type') != 'Directory' )
|
|
||||||
item_list['del']= { name: "Delete Selected file(s)" }
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
|
||||||
callback: function( key, options) {
|
|
||||||
if( key == "details" ) { DetailsDBox() }
|
|
||||||
if( key == "view" ) { dblClickToViewEntry( $(this).attr('id') ) }
|
|
||||||
if( key == "move" ) { MoveDBox(move_paths, "{{url_for('internal', filename='icons.svg')}}") }
|
|
||||||
if( key == "del" ) { DelDBox('Delete') }
|
|
||||||
if( key == "undel") { DelDBox('Restore') }
|
|
||||||
if( key == "r90" ) { Transform(90) }
|
|
||||||
if( key == "r180" ) { Transform(180) }
|
|
||||||
if( key == "r270" ) { Transform(270) }
|
|
||||||
if( key == "fliph" ) { Transform("fliph") }
|
|
||||||
if( key == "flipv" ) { Transform("flipv") }
|
|
||||||
if( key.startsWith("ai")) { RunAIOnSeln(key) }
|
|
||||||
// dont flow this event through the dom
|
|
||||||
e.stopPropagation()
|
|
||||||
},
|
|
||||||
items: item_list
|
|
||||||
};
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
$( document ).keydown(function(event) { switch (event.key)
|
|
||||||
{
|
|
||||||
case "Delete":
|
|
||||||
{% if "files_rbp" in request.url %}
|
|
||||||
if( ! NoSel() ) DelDBox('Restore');
|
|
||||||
{% else %}
|
|
||||||
if( ! NoSel() ) DelDBox('Delete');
|
|
||||||
{% endif %}
|
|
||||||
break;
|
|
||||||
} })
|
|
||||||
|
|
||||||
if( isMobile() )
|
|
||||||
{
|
|
||||||
$('#shift-key').css('visibility', 'visible');
|
|
||||||
$('#ctrl-key').css('visibility', 'visible');
|
|
||||||
}
|
|
||||||
|
|
||||||
// check the size radiobutton
|
// check the size radiobutton
|
||||||
$(`input[name="size"][value="${OPT.size}"]`).prop('checked', true)
|
$(`input[name="size"][value="${OPT.size}"]`).prop('checked', true)
|
||||||
|
|
||||||
window.addEventListener('resize', DrawImg, false);
|
window.addEventListener('resize', DrawImg, false);
|
||||||
window.addEventListener('resize', ResizeVideo, false);
|
window.addEventListener('resize', ResizeVideo, false);
|
||||||
|
|
||||||
</script>
|
</script>
|
||||||
{% endblock script_content %}
|
{% endblock script_content %}
|
||||||
|
|||||||
Reference in New Issue
Block a user