/********************************************************************** * * Ribbon Crop API * * **********************************************************************/ var CROP_STACK = [] var CROP_MODES = [] /******************************************************* Crop Data ***/ function isViewCropped(){ return CROP_STACK.length != 0 } function getAllData(){ if(!isViewCropped()){ return DATA } else { return CROP_STACK[0] } } // NOTE: this will not update .current state... // NOTE: when keep_ribbons is set, this may generate empty ribbons... // NOTE: this requieres all data to be present, if currently viewing // server-side data, then cropping is a server-side operation... // XXX another way to go here is to save the crop method and take // it into account when loading new sections of data... // // XXX should this set the .current to anything but null or the first elem??? function makeCroppedData(gids, keep_ribbons, keep_unloaded_gids){ var res = { varsion: '2.0', current: null, ribbons: [], order: DATA.order.slice(), } // remove any gid that is not in IMAGES or is not loaded... if(!keep_unloaded_gids){ var loaded = [] $.each(DATA.ribbons, function(i, e){ loaded = loaded.concat(e) }) // NOTE: if IMAGES contains only part of the data loadable this will // be wrong... gids = gids.filter(function(e){ return e in IMAGES && loaded.indexOf(e) >= 0 }) } // flat single ribbon crop... if(!keep_ribbons){ res.ribbons[0] = gids // keep the ribbon structure... } else { $.each(DATA.ribbons, function(_, e){ e = e.filter(function(ee){ return gids.indexOf(ee) >= 0 }) // skip empty ribbons... if(e.length != 0){ res.ribbons.push(e) } }) } return res } // NOTE: if keep_ribbons is not set this will ALWAYS build a single ribbon // data-set... function cropDataTo(gids, keep_ribbons, keep_unloaded_gids){ var prev_state = DATA var cur = DATA.current var r = keep_ribbons ? getRibbonIndex() : 0 var new_data = makeCroppedData(gids, keep_ribbons, keep_unloaded_gids) // do nothing if there is no change... // XXX is there a better way to compare states??? if(JSON.stringify(DATA.ribbons) == JSON.stringify(new_data.ribbons)){ return DATA } CROP_STACK.push(prev_state) DATA = new_data cur = getGIDBefore(cur, r) cur = cur == null ? DATA.ribbons[r][0] : cur DATA.current = cur reloadViewer() updateImages() return prev_state } function uncropData(){ if(!isViewCropped()){ return DATA } var prev_state = DATA var cur = DATA.current DATA = CROP_STACK.pop() // check if cur exists in data being loaded... if($.map(DATA.ribbons, function(e, i){ return e.indexOf(cur) >= 0 }).indexOf(true) >= 0){ // keep the current position... DATA.current = cur } reloadViewer() updateImages() return prev_state } function showAllData(){ var prev_state = DATA var cur = DATA.current if(CROP_STACK.length != 0){ DATA = getAllData() CROP_STACK = [] // XXX do we need to check if this exists??? // ...in theory, as long as there are no global destructive // operations, no. // keep the current position... DATA.current = cur reloadViewer() updateImages() } return prev_state } // Helpers for making crop modes and using crop... // Make a generic crop mode toggler // // NOTE: This will add the toggler to CROP_MODES, for use by // uncropLastState(...) // NOTE: crop modes are exclusive -- it is not possible to enter one crop // mode from a different crop mode // // XXX add "exclusive" crop option -- prevent other crop modes to enter... function makeCropModeToggler(cls, crop){ var res = createCSSClassToggler( '.viewer', //cls + ' cropped-mode', cls, /* XXX make this an option... function(action){ // prevent mixing marked-only and single-ribbon modes... if(action == 'on' && isViewCropped() && res('?') != 'on'){ return false } }, */ function(action){ if(action == 'on'){ showStatusQ('Cropping current ribbon...') crop() } else { showStatusQ('Uncropping to all data...') showAllData() } }) CROP_MODES.push(res) return res } // Uncrop to last state and there is no states to uncrop then exit // cropped mode. // // NOTE: this will exit all crop modes when uncropping the last step. function uncropLastState(){ // do nothing if we aren't in a crop mode... if(!isViewCropped()){ return } // exit cropped all modes... if(CROP_STACK.length == 1){ $.each(CROP_MODES, function(_, e){ e('off') }) // ucrop one state... } else { showStatusQ('Uncropping...') uncropData() } } /********************************************************************** * Dialogs... */ function cropImagesDialog(){ updateStatus('Crop...').show() var alg = 'Crop ribbons: |'+ 'Use Esc and Shift-Esc to exit crop modes.'+ '\n\n'+ 'NOTE: all crop modes will produce a single ribbon unless\n'+ 'otherwise stated.' cfg = {} cfg[alg] = [ 'Marked images', 'Marked images (keep ribbons)', 'Bookmarked images', 'Bookmarked images (keep ribbons)', 'Current ribbon', 'Current ribbon and above | Will merge the images into a single ribbon.', 'Current ribbon and above (keep ribbons)' ] formDialog(null, '', cfg, 'OK', 'cropImagesDialog') .done(function(res){ res = res[alg] // NOTE: these must be in order of least-specific last... if(/Marked.*keep ribbons/.test(res)){ var method = toggleMarkedOnlyWithRibbonsView } else if(/Marked/.test(res)){ var method = toggleMarkedOnlyView } else if(/Bookmarked.*keep ribbons/i.test(res)){ var method = toggleBookmarkedOnlyWithRibbonsView } else if(/Bookmarked/.test(res)){ var method = toggleBookmarkedOnlyView } else if(/Current ribbon and above.*keep ribbons/.test(res)){ var method = toggleCurrenAndAboveRibbonsMode } else if(/Current ribbon and above/.test(res)){ var method = toggleCurrenAndAboveRibbonMode } else if(/Current ribbon/.test(res)){ var method = toggleSingleRibbonMode } showStatusQ('Cropped: '+res+'...') method('on') }) .fail(function(){ showStatusQ('Crop: canceled.') }) } /********************************************************************** * vim:set ts=4 sw=4 : */