mirror of
https://github.com/flynx/ImageGrid.git
synced 2025-10-28 18:00:09 +00:00
527 lines
13 KiB
JavaScript
Executable File
527 lines
13 KiB
JavaScript
Executable File
/**********************************************************************
|
|
*
|
|
*
|
|
*
|
|
**********************************************************************/
|
|
|
|
//var DEBUG = DEBUG != null ? DEBUG : true
|
|
|
|
var SLIDESHOW_INTERVAL = 3000
|
|
var SLIDESHOW_LOOP = true
|
|
var SLIDESHOW_DIRECTION = 'next'
|
|
|
|
|
|
|
|
/**********************************************************************
|
|
* Utils...
|
|
*/
|
|
|
|
// NOTE: this expects a certain structure, this it is not generic...
|
|
//function makeDrawerToggler(contentRenderer, root, element_class, mode_class){
|
|
function makeDrawerToggler(contentRenderer, root){
|
|
var element_class = '.drawer-block'
|
|
var toggler = createCSSClassToggler(
|
|
root,
|
|
'drawer-mode overlay',
|
|
function(action){
|
|
// XXX
|
|
var body = $(document.body)
|
|
var win = $(window)
|
|
|
|
// on...
|
|
if(action == 'on'){
|
|
// remove helo when we scroll to the top...
|
|
var scroll_handler = function(){
|
|
if(body.scrollTop() <= 0){
|
|
toggler('off')
|
|
}
|
|
}
|
|
|
|
// prepare and cleanup...
|
|
$(element_class).remove()
|
|
showInOverlay($(root))
|
|
|
|
// build the help...
|
|
var doc = contentRenderer()
|
|
.addClass(element_class.replace('.', ' '))
|
|
.on('click', function(){
|
|
event.stopImmediatePropagation()
|
|
return false
|
|
})
|
|
.css({
|
|
cursor: 'auto',
|
|
})
|
|
// XXX depends on body...
|
|
.appendTo(body)
|
|
|
|
// add exit by click...
|
|
// XXX depends on body...
|
|
body
|
|
.one('click', function(){
|
|
toggler('off')
|
|
})
|
|
.css({
|
|
cursor: 'hand',
|
|
})
|
|
|
|
// scroll to the help...
|
|
// NOTE: need to set the scroll handler AFTER we
|
|
// scroll down, or it will be more of a
|
|
// tease than a help...
|
|
var t = getRelativeVisualPosition($(root), doc).top
|
|
body
|
|
.animate({
|
|
scrollTop: Math.abs(t) - 40,
|
|
}, function(){
|
|
// XXX depends on window...
|
|
win
|
|
.on('scroll', scroll_handler)
|
|
})
|
|
|
|
// off...
|
|
} else {
|
|
// things to cleanup...
|
|
var _cleanup = function(){
|
|
$(element_class).remove()
|
|
hideOverlay($(root))
|
|
// XXX depends on body...
|
|
body.click()
|
|
win.off('scroll', scroll_handler)
|
|
}
|
|
|
|
// animate things if we are not at the top...
|
|
if(body.scrollTop() > 0){
|
|
// XXX depends on body...
|
|
body
|
|
.css({
|
|
cursor: '',
|
|
})
|
|
.animate({
|
|
scrollTop: 0,
|
|
}, _cleanup)
|
|
|
|
// if we are at the top do things fast...
|
|
} else {
|
|
_cleanup()
|
|
}
|
|
}
|
|
})
|
|
return toggler
|
|
}
|
|
|
|
|
|
|
|
/**********************************************************************
|
|
* Modes
|
|
*/
|
|
|
|
// XXX make this save and restore settings...
|
|
var toggleSingleImageMode = createCSSClassToggler(
|
|
'.viewer',
|
|
'single-image-mode',
|
|
function(action){
|
|
// prevent reentering...
|
|
if(action == toggleSingleImageMode('?')){
|
|
return false
|
|
}
|
|
},
|
|
function(action){
|
|
var w = getScreenWidthInImages()
|
|
|
|
// single image mode...
|
|
if(action == 'on'){
|
|
TRANSITION_MODE_DEFAULT = 'css'
|
|
|
|
// save things...
|
|
UI_STATE['ribbon-mode-screen-images'] = w
|
|
UI_STATE['ribbon-mode-image-info'] = toggleImageInfo('?')
|
|
|
|
// load things...
|
|
w = UI_STATE['single-image-mode-screen-images']
|
|
w = w == null ? 1 : w
|
|
|
|
// set stuff...
|
|
fitNImages(w)
|
|
toggleImageInfo('off')
|
|
|
|
// ribbon mode...
|
|
} else {
|
|
TRANSITION_MODE_DEFAULT = 'animate'
|
|
|
|
// save things...
|
|
UI_STATE['single-image-mode-screen-images'] = w
|
|
|
|
// load things...
|
|
w = UI_STATE['ribbon-mode-screen-images']
|
|
w = w == null
|
|
? getScreenWidthInImages(CONFIG.default_image_size)
|
|
: w
|
|
|
|
fitNImages(w)
|
|
var i = UI_STATE['ribbon-mode-image-info'] == 'on' ? 'on' : 'off'
|
|
toggleImageInfo(i)
|
|
UI_STATE['ribbon-mode-image-info'] = i
|
|
|
|
centerRibbons()
|
|
}
|
|
})
|
|
|
|
|
|
// TODO transitions...
|
|
// TODO a real setup UI (instead of prompt)
|
|
//
|
|
// XXX avoid using globals:
|
|
// _pre_slideshow_marks_view
|
|
// _slideshow_timer
|
|
var toggleSlideShowMode = createCSSClassToggler(
|
|
'.viewer',
|
|
'.slideshow-mode',
|
|
function(action){
|
|
if(action == 'on'){
|
|
updateStatus('Slideshow...').show()
|
|
|
|
// XXX hackish...
|
|
_pre_slideshow_marks_view = toggleMarksView('?')
|
|
|
|
// interval from user...
|
|
//var interval = prompt('Slideshow interval (sec):', SLIDESHOW_INTERVAL/1000)
|
|
formDialog($('.viewer'), 'Slideshow', {
|
|
'Interval': (SLIDESHOW_INTERVAL/1000) + 'sec',
|
|
'Looping': SLIDESHOW_LOOP ? true : false,
|
|
'Reverse direction': SLIDESHOW_DIRECTION == 'prev' ? true : false
|
|
}, 'Start')
|
|
.done(function(data){
|
|
var looping = data['Looping']
|
|
var reverse = data['Reverse direction']
|
|
|
|
SLIDESHOW_LOOP = looping
|
|
SLIDESHOW_DIRECTION = reverse == true ? 'prev' : 'next'
|
|
|
|
// parse interval...
|
|
var interval_raw = data['Interval']
|
|
// units...
|
|
var M = 1000
|
|
if(/ms|msec|milsec|millisecond[s]/i.test(interval_raw)){
|
|
M = 1
|
|
} else if(/(s|sec|second[s])/i.test(interval_raw)){
|
|
M = 1000
|
|
} else if(/m|min|minute[s]/i.test(interval_raw)){
|
|
M = 1000*60
|
|
}
|
|
// fractions...
|
|
if(/[0-9]+\/[0-9]+/.test(interval_raw)){
|
|
var parts = interval_raw.split('/')
|
|
var interval = parseFloat(parts[0]) / parseFloat(parts[1])
|
|
} else {
|
|
var interval = parseFloat(interval_raw)
|
|
}
|
|
SLIDESHOW_INTERVAL = isNaN(interval) ? 3000 : interval*M
|
|
|
|
showStatus('Slideshow: starting:', SLIDESHOW_INTERVAL/1000 +'sec,', SLIDESHOW_LOOP ? 'looped...' : 'unlooped...')
|
|
|
|
// XXX is this the correct way to go???
|
|
hideOverlay($('.viewer'))
|
|
|
|
toggleSingleImageMode('on')
|
|
toggleMarksView('off')
|
|
|
|
_slideshow_timer = setInterval(function(){
|
|
var cur = getImage()
|
|
// advance the image...
|
|
var next = SLIDESHOW_DIRECTION == 'next' ? nextImage() : prevImage()
|
|
|
|
// handle slideshow end...
|
|
if(getImageGID(cur) == getImageGID(next)){
|
|
if(SLIDESHOW_LOOP){
|
|
SLIDESHOW_DIRECTION == 'next' ? firstImage() : lastImage()
|
|
} else {
|
|
toggleSlideShowMode('off')
|
|
toggleMarksView(window._pre_slideshow_marks_view == null ? 'on'
|
|
: window._pre_slideshow_marks_view)
|
|
return
|
|
}
|
|
}
|
|
|
|
// center and trigger load events...
|
|
centerRibbon()
|
|
}, SLIDESHOW_INTERVAL)
|
|
})
|
|
// user cancelled...
|
|
.fail(function(){
|
|
toggleSlideShowMode('off')
|
|
toggleMarksView(window._pre_slideshow_marks_view == null ? 'on'
|
|
: window._pre_slideshow_marks_view)
|
|
})
|
|
|
|
} else {
|
|
window._slideshow_timer != null && clearInterval(_slideshow_timer)
|
|
showStatus('Slideshow: canceled.')
|
|
toggleMarksView(window._pre_slideshow_marks_view == null ? 'on'
|
|
: window._pre_slideshow_marks_view)
|
|
hideOverlay($('.viewer'))
|
|
}
|
|
})
|
|
|
|
|
|
var toggleTheme = createCSSClassToggler(
|
|
'.viewer',
|
|
[
|
|
'gray',
|
|
'dark',
|
|
'light'
|
|
],
|
|
// XXX does this get called for default state (gray)???
|
|
function(action){
|
|
UI_STATE['global-theme'] = action
|
|
})
|
|
|
|
|
|
var toggleImageInfo = createCSSClassToggler(
|
|
'.viewer',
|
|
'.image-info-visible',
|
|
function(action){
|
|
if(toggleSingleImageMode('?') == 'off'){
|
|
UI_STATE['ribbon-mode-image-info'] = action
|
|
}
|
|
})
|
|
|
|
|
|
var toggleInlineImageInfo = createCSSClassToggler(
|
|
'.viewer',
|
|
'.image-info-inline-visible',
|
|
function(action){
|
|
if(action == 'on'){
|
|
$(document)
|
|
.on('mouseover', inlineImageInfoHoverHandler)
|
|
} else {
|
|
$(document)
|
|
.off('mouseover', inlineImageInfoHoverHandler)
|
|
$('.inline-image-info').remove()
|
|
}
|
|
})
|
|
|
|
|
|
// Toggle image container proportions mode
|
|
//
|
|
// Available modes:
|
|
// - none : square proportions
|
|
// - fit-viewer : calculate proportions
|
|
//
|
|
// If CONFIG.proportions_ratio_threshold is null or if ignore_thresholds,
|
|
// is set, this willsimply switch between square and viewer proportions.
|
|
//
|
|
// If CONFIG.proportions_ratio_threshold is set to a list of two values,
|
|
// this will use the screen width in images (S) to calculate the
|
|
// proportions:
|
|
// S < min : viewer proportions
|
|
// S > max : square proportions
|
|
// min > S < max : transitional, proportions between
|
|
// square and viewer...
|
|
//
|
|
// NOTE: if n is not passed, getScreenWidthInImages() will be used...
|
|
// NOTE: if ignore_thresholds is set or the threshold is not a list, this
|
|
// will ignore the threshold...
|
|
//
|
|
// XXX is this the right place to calculate proportions??? (revise)
|
|
var toggleImageProportions = createCSSClassToggler(
|
|
'.viewer',
|
|
[
|
|
'none',
|
|
'fit-viewer'
|
|
],
|
|
function(action, viewer, n, ignore_thresholds){
|
|
var image = $('.image')
|
|
// viewer proportions...
|
|
if(action == 'fit-viewer'){
|
|
// NOTE: we care about n only in fit-viewer mode...
|
|
n = n == null ? getScreenWidthInImages() : n
|
|
var threshold = CONFIG.proportions_ratio_threshold
|
|
|
|
// image proportions between square and viewer indicator...
|
|
//
|
|
// must be between 0 and 1:
|
|
// - 1 is square proportions
|
|
// - 0 is viewer proportions
|
|
var c = 0
|
|
|
|
// calculate c...
|
|
if(!ignore_thresholds
|
|
&& (threshold != null
|
|
|| threshold.length == 2)){
|
|
var min = Math.min.apply(null, threshold)
|
|
var max = Math.max.apply(null, threshold)
|
|
var c = (n - min) / (max - min)
|
|
c = c < 0 ? 0
|
|
: c > 1 ? 1
|
|
: c
|
|
}
|
|
|
|
var W = viewer.innerWidth()
|
|
var H = viewer.innerHeight()
|
|
|
|
// landscape viewer...
|
|
if(W > H){
|
|
var h = image.outerHeight(true)
|
|
var scale = h/H
|
|
var tw = W * scale
|
|
var d = tw - h
|
|
|
|
image.css({
|
|
//width: W * scale,
|
|
width: tw - (d * c),
|
|
height: '',
|
|
})
|
|
|
|
// portrait viewer...
|
|
} else {
|
|
var w = image.outerWidth(true)
|
|
var scale = w/W
|
|
var th = H * scale
|
|
var d = th - w
|
|
|
|
image.css({
|
|
width: '',
|
|
//height: H * scale,
|
|
height: th - d * c,
|
|
})
|
|
}
|
|
|
|
// square proportions...
|
|
// NOTE: this will reset the size to default (defined in CSS)
|
|
} else {
|
|
image.css({
|
|
width: '',
|
|
height: ''
|
|
})
|
|
}
|
|
|
|
// account for rotation...
|
|
correctImageProportionsForRotation(image)
|
|
centerView(null, 'css')
|
|
|
|
viewer.trigger('updatingImageProportions')
|
|
})
|
|
|
|
|
|
var toggleHelp = makeDrawerToggler(
|
|
function(){
|
|
// XXX populate...
|
|
// ...load from file.
|
|
return $('<h1>Help</h1>')
|
|
}, '.viewer')
|
|
|
|
|
|
var toggleKeyboardHelp = makeDrawerToggler(
|
|
function(){
|
|
return buildKeybindingsHelpHTML(KEYBOARD_CONFIG)
|
|
}, '.viewer')
|
|
|
|
|
|
var toggleOptionsUI = makeDrawerToggler(
|
|
function(){
|
|
// XXX populate...
|
|
return $('<h1>Options</h1>')
|
|
}, '.viewer')
|
|
|
|
|
|
// XXX needs styling and cleanup...
|
|
// XXX add a preview...
|
|
var toggleImageInfoDrawer = makeDrawerToggler(
|
|
function(){
|
|
var gid = getImageGID(getImage())
|
|
var r = getRibbonIndex(getRibbon())
|
|
var data = IMAGES[gid]
|
|
var orientation = data.orientation
|
|
orientation = orientation == null ? 0 : orientation
|
|
var order = DATA.order.indexOf(gid)
|
|
var name = getImageFileName(gid)
|
|
|
|
return $('<div>'+
|
|
'<h1>"'+ name +'"</h1>'+
|
|
|
|
'Orientation: '+ orientation +'deg<br>'+
|
|
'GID: '+ gid +'<br>'+
|
|
'Path: "'+ data.path +'"<br>'+
|
|
'Order: '+ order +'<br>'+
|
|
'Position (ribbon): '+ (DATA.ribbons[r].indexOf(gid)+1) +
|
|
'/'+ DATA.ribbons[r].length +'<br>'+
|
|
'Position (global): '+ (order+1) +'/'+ DATA.order.length +'<br>'+
|
|
'</div>')
|
|
}, '.viewer')
|
|
|
|
|
|
|
|
/**********************************************************************
|
|
* Experimental...
|
|
*/
|
|
|
|
function getImageProportions(gid){
|
|
gid = gid == null ? getImageGID() : gid
|
|
var o = IMAGES[gid].orientation
|
|
o = o == null ? 0 : o
|
|
|
|
var res = $.Deferred()
|
|
|
|
var i = new Image()
|
|
i.onload = function(){
|
|
if(o == 0 || o == 180){
|
|
var w = i.width/i.height
|
|
} else {
|
|
var w = i.height/i.width
|
|
}
|
|
res.resolve(w)
|
|
}
|
|
i.src = getBestPreview(gid).url
|
|
|
|
return res
|
|
}
|
|
|
|
|
|
function _fitImageToRibbonHeight(gid, image){
|
|
setTimeout(function(){
|
|
getImageProportions(gid).done(function(r){
|
|
var h = image.height()
|
|
image.css({
|
|
width: h * r,
|
|
})
|
|
correctImageProportionsForRotation(image, image)
|
|
})
|
|
}, 0)
|
|
return image
|
|
}
|
|
|
|
|
|
// XXX this does not work yet + I'm not sure if we need it...
|
|
var toggleRibbonImageProportions = createCSSClassToggler(
|
|
'.viewer',
|
|
[
|
|
'none',
|
|
'ribbon-image-proportions'
|
|
],
|
|
function(action){
|
|
var image = $('.image')
|
|
|
|
if(action == 'ribbon-image-proportions'){
|
|
// register the updater...
|
|
IMAGE_UPDATERS.push(_fitImageToRibbonHeight)
|
|
updateImages()
|
|
|
|
} else {
|
|
// unregister the updater...
|
|
IMAGE_UPDATERS.splice(IMAGE_UPDATERS.indexOf(_fitImageToRibbonHeight), 1)
|
|
image.css({
|
|
width: '',
|
|
height: ''
|
|
})
|
|
// account for rotation...
|
|
correctImageProportionsForRotation(image)
|
|
}
|
|
|
|
centerView(null, 'css')
|
|
})
|
|
|
|
|
|
|
|
/**********************************************************************
|
|
* vim:set ts=4 sw=4 : */
|