better last row resizing + docs...

Signed-off-by: Alex A. Naanou <alex.nanou@gmail.com>
This commit is contained in:
Alex A. Naanou 2023-07-28 23:19:39 +03:00
parent cafb50054c
commit fd3d2d2e5e
3 changed files with 112 additions and 58 deletions

View File

@ -129,8 +129,8 @@ body {
right: 0;
}
/* marker: selected */
.gallery .images img+.mark.selected:after {
/* marker: marked */
.gallery .images img+.mark.marked:after {
content: "";
position: absolute;
display: block;

View File

@ -47,7 +47,7 @@
- drop files/images
- drag to sort
- <s>Gallery: remove image</s>
- mark images for deletion + delete marked
- UI: mark images for deletion + delete marked
- <s>Gallery: serialize / deserialize</s>
- <s>Lightbox: navigation (keyboard / mouse)</s>
- <s>Lightbox: fullscreen mode</s>

View File

@ -17,9 +17,8 @@
//---------------------------------------------------------------------
// XXX need to account for scrollbar -- add hysteresis???
var patchFlexRows =
function(elems, prevent_row_expansion=false){
function(elems, prevent_row_expansion=false, last_row_resize=1.5){
if(elems.length == 0){
return }
// NOTE: -1 here is to compensate for rounding errors...
@ -28,7 +27,44 @@ function(elems, prevent_row_expansion=false){
var h
var row = []
var top = elems[0].offsetTop
// NOTE: this will by design skip the last row.
// modes:
// 'as-is' | false
// 'expand'
// 'close'
// closure: W, w, h, row, elem
var max = 0
var handleRow = function(mode='justify'){
if(!mode || mode == 'as-is'){
return false }
if(typeof(mode) == 'number'){
var r = Math.min(
W / w,
max * mode)
} else {
var r = W / w }
var expanded
if(mode == 'expand'){
var r2 = W / (w + elem.offsetWidth)
// NOTE: we are checking which will require a lesser resize
// the current row or it with the next image...
if(1/r < r2){
expanded = true
var r = r2
row.push(elem) } }
max = Math.max(max, r)
// patch the row...
var nw = 0
for(var e of row){
e.style.height = (h * r) + 'px'
nw += e.offsetWidth }
return !!expanded }
// NOTE: this will by design skip the last row...
// ...because we handle the row only when we see an image at a
// different vertical offset...
for(var elem of elems){
elem.style.height = ''
elem.style.width = ''
@ -40,26 +76,12 @@ function(elems, prevent_row_expansion=false){
if(elem.offsetTop == top){
w += elem.offsetWidth
row.push(elem)
// row donw + prep for next...
// row done + prep for next...
} else {
// NOTE: we are checking which will require a lesser resize
// the current row or it with the next image...
var r1 = W / w
var r2 = W / (w + elem.offsetWidth)
var expanded_row =
prevent_row_expansion ?
false
: 1/r1 < r2
if(!expanded_row){
var r = r1
} else {
var r = r2
row.push(elem) }
// patch the row...
var nw = 0
for(var e of row){
e.style.height = (h * r) + 'px'
nw += e.offsetWidth }
handleRow()
: handleRow('expand')
// prep for next row...
if(!expanded_row){
w = elem.offsetWidth
@ -70,7 +92,10 @@ function(elems, prevent_row_expansion=false){
w = 0
h = null
top = null
row = [] }}}}
row = [] }}}
// handle last row...
last_row_resize
&& handleRow(last_row_resize) }
var getScrollParent =
function(elem){
@ -125,14 +150,14 @@ var keyboard = {
gallery.lightbox.hide()
// XXX should we remember which image was current and select
// it again when needed???
: gallery.deselect_current ?
: gallery.unmark_current ?
(gallery.current = null)
: null },
// selection...
' ': function(evt){
gallery.current
&& evt.preventDefault()
gallery.toggleSelect() },
gallery.toggleMark() },
// XXX use key codes...
'a': function(evt){
evt.preventDefault()
@ -141,11 +166,11 @@ var keyboard = {
'd': function(evt){
evt.preventDefault()
if(evt.ctrlKey){
gallery.deselectAll() } },
gallery.unmarkAll() } },
'i': function(evt){
evt.preventDefault()
if(evt.ctrlKey){
gallery.selectInverse() } },
gallery.markInverse() } },
}
@ -156,16 +181,33 @@ var Gallery = {
// Options...
//
deselect_current: true,
unmark_current: true,
// If true navigation will loop over top/bottom rows and first/last
// images...
// This is mainly usefull for small galleries that do not need paging.
// XXX might be a good idea to autodisable this when paging is required.
// XXX might be a good idea to auto-disable this when paging is required.
loop_images: false,
// If true for each row two versions will be compared, as-is and with
// one image from the next row and the closest resiae will be chosen.
//
// This produses a more uniform image grid but will have greater
// effect on gallery height / slower.
allow_row_expansion: true,
// define how to handle last row
//
// can be:
// 'as-is' - do not resize
// 'justify' - fit width
// <number> - maximum ration relative to largest row (must be >=1)
//
// Recomended value close to 1
last_row_resize: 1,
// If true first click on image will select it the second click will
// open it...
click_to_select: true,
exit_fullscreen_on_lightbox_close: true,
@ -261,6 +303,9 @@ var Gallery = {
.replace(/\/[0-9]+px\//, '/') }) },
//*/
get marked(){
return [...this.dom.querySelectorAll('.images img.marked')] },
get length(){
return this.images.length },
get index(){
@ -283,7 +328,7 @@ var Gallery = {
this.getRow(this.images.at(-1))
: undefined
} else if(direction == 'below'){
// special case: nothing selected...
// special case: nothing marked...
if(img == null){
return this.getRow() }
var row = this.getRow(img)
@ -434,56 +479,55 @@ var Gallery = {
return this },
// selection...
get selected(){
return this.dom.querySelectorAll('.images img.selected') },
//
// NOTE: this is here because we can't use :before / :after directly
// on the img tag...
// XXX make this generic and use a .marks list...
updateMarkers: function(){
var that = this
// select...
for(var img of this.dom.querySelectorAll('.images img.selected')){
for(var img of this.dom.querySelectorAll('.images img.marked')){
var mark = img.nextElementSibling
while(mark && mark.tagName != 'IMG' && !mark.classList.contains('mark')){
mark = img.nextElementSibling }
if(!mark || !mark.classList.contains('mark')){
mark = document.createElement('div')
mark.classList.add('selected', 'mark')
mark.classList.add('marked', 'mark')
mark.addEventListener('click', function(evt){
evt.stopPropagation()
that.deselect(mark) })
that.unmark(mark) })
img.after(mark) } }
// clear deselected...
for(var mark of this.dom.querySelectorAll('.images img:not(.selected)+.mark')){
// clear unmarked...
for(var mark of this.dom.querySelectorAll('.images img:not(.marked)+.mark')){
mark.remove() }
// update lightbox...
this.lightbox.shown
&& this.lightbox.update()
return this },
select: function(img){
mark: function(img){
img = img ?? this.current
img?.classList.add('selected')
img?.classList.add('marked')
return this.updateMarkers() },
deselect: function(img){
unmark: function(img){
img = img ?? this.current
img?.classList.remove('selected')
img?.classList.remove('marked')
return this.updateMarkers() },
toggleSelect: function(img){
toggleMark: function(img){
img = img ?? this.current
img?.classList.toggle('selected')
img?.classList.toggle('marked')
this.updateMarkers()
return this },
selectAll: function(){
for(var img of this.images){
img.classList.add('selected') }
img.classList.add('marked') }
return this.updateMarkers() },
deselectAll: function(){
unmarkAll: function(){
for(var img of this.images){
img.classList.remove('selected') }
img.classList.remove('marked') }
return this.updateMarkers() },
selectInverse: function(){
markInverse: function(){
for(var img of this.images){
img.classList.toggle('selected') }
img.classList.toggle('marked') }
return this.updateMarkers() },
show: function(){
@ -491,7 +535,9 @@ var Gallery = {
return this },
update: function(){
patchFlexRows(this.images, !this.allow_row_expansion)
patchFlexRows(this.images,
!this.allow_row_expansion,
this.last_row_resize ?? 1.2)
return this },
// .load(<image>)
@ -546,9 +592,17 @@ var Gallery = {
'caption',
],
// XXX do we handle previews here???
json: function(){
json: function(images=undefined){
var that = this
return this.images
images =
(images == 'all' || images == '*') ?
this.images
: images == 'marked' ?
this.marked
: !images ?
this.images
: images
return images
.map(function(img){
var res = { url: img.src }
for(var key of that.__image_attributes__){
@ -589,7 +643,7 @@ var Gallery = {
if(target.tagName == 'IMG'){
// shift+click: toggle selections...
if(evt.shiftKey){
that.toggleSelect(target)
that.toggleMark(target)
// first click selects, second shows...
} else if(that.click_to_select){
target.classList.contains('current') ?
@ -599,11 +653,11 @@ var Gallery = {
} else {
that.current = target
that.show() }
} else if(that.deselect_current){
} else if(that.unmark_current){
that.current = null } })
this.dom
.addEventListener('click', function(evt){
that.deselect_current
that.unmark_current
&& (that.current = null) })
// drag...
this.dom
@ -692,9 +746,9 @@ var Lightbox = {
.replace(/\${CAPTION}/, caption)
.replace(/\${INDEX}/, index))
// set selection...
this.gallery.current.classList.contains('selected') ?
this.dom.classList.add('selected')
: this.dom.classList.remove('selected')
this.gallery.current.classList.contains('marked') ?
this.dom.classList.add('marked')
: this.dom.classList.remove('marked')
return this },
prev: function(){