move MoveDBox into full javascript and fold it into internal/js/files_support.js, also remove unused parameter for MoveDBox, and use marshmallow to pass people in query_data - overall just cleaner more consistent code for existing functionality
This commit is contained in:
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
|
||||
# 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???
|
||||
# think I killed pa_job_manager without passing an eid to a transform job, shouldn't crash
|
||||
#3 empty directories (2017/20171015-test/...) showing "No matches for: 'undefined'" <- should only comes up for search in URL???
|
||||
#
|
||||
#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...
|
||||
# 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...
|
||||
|
||||
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 marshmallow import Schema, fields
|
||||
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.orm import joinedload
|
||||
import os
|
||||
@@ -28,10 +28,10 @@ from types import SimpleNamespace
|
||||
from states import States, PA_UserState
|
||||
from query import Query
|
||||
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 settings import Settings, SettingsIPath, SettingsSPath, SettingsRBPath
|
||||
from shared import SymlinkName
|
||||
from shared import SymlinkName, ICON
|
||||
from dups import Duplicates
|
||||
from face import Face, FaceFileLink, FaceRefimgLink, FaceOverrideType, FaceNoMatchOverride, FaceForceMatchOverride
|
||||
|
||||
@@ -170,6 +170,14 @@ class PathSchema(ma.SQLAlchemyAutoSchema):
|
||||
class Meta: model = Path
|
||||
load_instance = True
|
||||
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 Meta: model = FileType
|
||||
@@ -216,6 +224,11 @@ class FileSchema(ma.SQLAlchemyAutoSchema):
|
||||
load_instance = 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
|
||||
################################################################################
|
||||
@@ -238,6 +251,9 @@ class EntrySchema(ma.SQLAlchemyAutoSchema):
|
||||
|
||||
# global - this will be use more than once below, so do it once for efficiency
|
||||
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
|
||||
@@ -314,6 +330,25 @@ def get_dir_entries():
|
||||
entries = Entry.query.filter(Entry.id.in_(ids)).all()
|
||||
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
|
||||
@@ -322,6 +357,9 @@ def GetSearchQueryData(OPT):
|
||||
query_data={}
|
||||
query_data['entry_list']=None
|
||||
query_data['root_eid']=0
|
||||
query_data['NMO'] = getFOT()
|
||||
query_data['move_paths'] = getMoveDetails()
|
||||
query_data['people'] = getPeople()
|
||||
|
||||
search_term = OPT.search_term
|
||||
# turn * wildcard into sql wildcard of %
|
||||
@@ -352,13 +390,15 @@ def GetSearchQueryData(OPT):
|
||||
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
|
||||
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
|
||||
dir_stmt=(
|
||||
@@ -388,7 +428,6 @@ def GetQueryData( OPT ):
|
||||
|
||||
stmt=stmt.order_by(*order_map.get(OPT.noo) )
|
||||
query_data['entry_list']=db.session.execute(stmt).scalars().all()
|
||||
|
||||
return query_data
|
||||
|
||||
################################################################################
|
||||
@@ -428,9 +467,8 @@ def file_list_ip():
|
||||
def files_ip():
|
||||
OPT=States( request )
|
||||
people = Person.query.all()
|
||||
move_paths = MovePathDetails()
|
||||
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
|
||||
@@ -440,9 +478,8 @@ def files_ip():
|
||||
def files_sp():
|
||||
OPT=States( request )
|
||||
people = Person.query.all()
|
||||
move_paths = MovePathDetails()
|
||||
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():
|
||||
OPT=States( request )
|
||||
people = Person.query.all()
|
||||
move_paths = MovePathDetails()
|
||||
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
|
||||
@@ -470,8 +506,7 @@ def search(search_term):
|
||||
OPT.folders = False
|
||||
|
||||
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, move_paths=move_paths )
|
||||
return render_template("files.html", page_title='View Files', search_term=search_term, query_data=query_data, OPT=OPT )
|
||||
|
||||
################################################################################
|
||||
# /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
|
||||
// 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')
|
||||
div =`
|
||||
@@ -838,3 +838,87 @@ function dblClickToViewEntry(id) {
|
||||
setEntryById( id )
|
||||
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
|
||||
};
|
||||
}
|
||||
});
|
||||
|
||||
@@ -64,7 +64,7 @@
|
||||
<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;">
|
||||
<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>
|
||||
</button>
|
||||
{% if "files_rbp" in request.url %}
|
||||
@@ -237,6 +237,7 @@
|
||||
// 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
|
||||
var entryList={{query_data.entry_list}}
|
||||
@@ -277,108 +278,11 @@
|
||||
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
|
||||
$(`input[name="size"][value="${OPT.size}"]`).prop('checked', true)
|
||||
|
||||
window.addEventListener('resize', DrawImg, false);
|
||||
window.addEventListener('resize', ResizeVideo, false);
|
||||
|
||||
</script>
|
||||
{% endblock script_content %}
|
||||
|
||||
Reference in New Issue
Block a user