diff --git a/css/grid-n-view.css b/css/grid-n-view.css
index 9f10002..b8589a0 100644
--- a/css/grid-n-view.css
+++ b/css/grid-n-view.css
@@ -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;
diff --git a/grid-n-view.html b/grid-n-view.html
index bc740f8..c49708e 100644
--- a/grid-n-view.html
+++ b/grid-n-view.html
@@ -47,7 +47,7 @@
- drop files/images
- drag to sort
- Gallery: remove image
- - mark images for deletion + delete marked
+ - UI: mark images for deletion + delete marked
- Gallery: serialize / deserialize
- Lightbox: navigation (keyboard / mouse)
- Lightbox: fullscreen mode
diff --git a/grid-n-view.js b/grid-n-view.js
index 039035f..5325b92 100644
--- a/grid-n-view.js
+++ b/grid-n-view.js
@@ -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
+ // - 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()
@@ -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(){