2014-01-08 05:28:01 +00:00
goog . provide ( 'cam.BlobItemContainer' ) ;
2012-12-23 21:54:42 +00:00
goog . require ( 'goog.dom' ) ;
goog . require ( 'goog.dom.classes' ) ;
2012-12-23 23:18:09 +00:00
goog . require ( 'goog.events.Event' ) ;
2012-12-23 21:54:42 +00:00
goog . require ( 'goog.events.EventHandler' ) ;
goog . require ( 'goog.events.EventType' ) ;
2013-01-20 21:56:13 +00:00
goog . require ( 'goog.events.FileDropHandler' ) ;
2012-12-23 21:54:42 +00:00
goog . require ( 'goog.ui.Container' ) ;
2014-01-08 05:28:01 +00:00
goog . require ( 'cam.BlobItem' ) ;
goog . require ( 'cam.SearchSession' ) ;
goog . require ( 'cam.ServerConnection' ) ;
2012-12-23 21:54:42 +00:00
2014-01-07 04:52:30 +00:00
// An infinite scrolling list of BlobItem. The heights of rows and clip of individual items is adjusted to get a fully justified appearance.
2014-01-08 05:28:01 +00:00
cam . BlobItemContainer = function ( connection , opt _domHelper ) {
2013-12-20 17:59:47 +00:00
goog . base ( this , opt _domHelper ) ;
this . checkedBlobItems _ = [ ] ;
this . connection _ = connection ;
2014-01-01 10:04:14 +00:00
this . searchSession _ = null ;
2013-12-20 17:59:47 +00:00
this . eh _ = new goog . events . EventHandler ( this ) ;
// BlobRef of the permanode defined as the current collection/set. Selected blobitems will be added as members of that collection upon relevant actions (e.g click on the 'Add to Set' toolbar button).
this . currentCollec _ = "" ;
// Whether our content has changed since last layout.
this . isLayoutDirty _ = false ;
// An id for a timer we use to know when the drag has ended.
this . dragEndTimer _ = 0 ;
// Whether the blobItems within can be selected.
this . isSelectionEnabled = false ;
// Whether users can drag files onto the container to upload.
this . isFileDragEnabled = false ;
2014-01-08 05:28:01 +00:00
// A lookup of blobRef->cam.BlobItem. This allows us to quickly find and reuse existing controls when we're updating the UI in response to a server push.
2013-12-20 17:59:47 +00:00
this . itemCache _ = { } ;
this . setFocusable ( false ) ;
2012-12-23 21:54:42 +00:00
} ;
2014-01-08 05:28:01 +00:00
goog . inherits ( cam . BlobItemContainer , goog . ui . Container ) ;
2012-12-23 21:54:42 +00:00
2013-12-20 17:59:47 +00:00
// Margin between items in the layout.
2014-01-08 05:28:01 +00:00
cam . BlobItemContainer . BLOB _ITEM _MARGIN = 7 ;
2013-10-04 02:28:13 +00:00
2013-12-20 17:59:47 +00:00
// If the last row uses at least this much of the available width before adjustments, we'll call it "close enough" and adjust things so that it fills the entire row. Less than this, and we'll leave the last row unaligned.
2014-01-08 05:28:01 +00:00
cam . BlobItemContainer . LAST _ROW _CLOSE _ENOUGH _TO _FULL = 0.85 ;
2013-10-04 02:28:13 +00:00
2014-01-08 05:28:01 +00:00
cam . BlobItemContainer . THUMBNAIL _SIZES _ = [ 75 , 100 , 150 , 200 , 250 ] ;
2012-12-24 01:52:09 +00:00
2013-12-20 17:59:47 +00:00
// Distance from the bottom of the page at which we will trigger loading more data.
2014-01-08 05:28:01 +00:00
cam . BlobItemContainer . INFINITE _SCROLL _THRESHOLD _PX _ = 100 ;
2013-12-02 06:56:09 +00:00
2014-01-08 05:28:01 +00:00
cam . BlobItemContainer . NUM _ITEMS _PER _PAGE = 50 ;
2013-12-12 09:04:47 +00:00
2014-01-08 05:28:01 +00:00
cam . BlobItemContainer . prototype . fileDropHandler _ = null ;
2013-01-20 21:56:13 +00:00
2014-01-08 05:28:01 +00:00
cam . BlobItemContainer . prototype . dragActiveElement _ = null ;
2013-01-20 21:56:13 +00:00
2013-12-20 17:59:47 +00:00
// Constants for events fired by BlobItemContainer
2014-01-08 05:28:01 +00:00
cam . BlobItemContainer . EventType = {
2013-12-20 17:59:47 +00:00
SELECTION _CHANGED : 'Camlistore_BlobItemContainer_SelectionChanged' ,
2013-01-20 22:43:17 +00:00
} ;
2014-01-08 05:28:01 +00:00
cam . BlobItemContainer . prototype . thumbnailSize _ = 200 ;
2012-12-24 01:52:09 +00:00
2014-01-08 05:28:01 +00:00
cam . BlobItemContainer . prototype . smaller = function ( ) {
var index = cam . BlobItemContainer . THUMBNAIL _SIZES _ . indexOf ( this . thumbnailSize _ ) ;
2013-12-20 17:59:47 +00:00
if ( index == 0 ) {
return false ;
}
var el = this . getElement ( ) ;
goog . dom . classes . remove ( el , 'cam-blobitemcontainer-' + this . thumbnailSize _ ) ;
2014-01-08 05:28:01 +00:00
this . thumbnailSize _ = cam . BlobItemContainer . THUMBNAIL _SIZES _ [ index - 1 ] ;
2013-12-20 17:59:47 +00:00
goog . dom . classes . add ( el , 'cam-blobitemcontainer-' + this . thumbnailSize _ ) ;
return true ;
2012-12-24 01:52:09 +00:00
} ;
2014-01-08 05:28:01 +00:00
cam . BlobItemContainer . prototype . bigger = function ( ) {
var index = cam . BlobItemContainer . THUMBNAIL _SIZES _ . indexOf (
2013-12-20 17:59:47 +00:00
this . thumbnailSize _ ) ;
2014-01-08 05:28:01 +00:00
if ( index == cam . BlobItemContainer . THUMBNAIL _SIZES _ . length - 1 ) {
2013-12-20 17:59:47 +00:00
return false ;
}
var el = this . getElement ( ) ;
goog . dom . classes . remove ( el , 'cam-blobitemcontainer-' + this . thumbnailSize _ ) ;
2014-01-08 05:28:01 +00:00
this . thumbnailSize _ = cam . BlobItemContainer . THUMBNAIL _SIZES _ [ index + 1 ] ;
2013-12-20 17:59:47 +00:00
goog . dom . classes . add ( el , 'cam-blobitemcontainer-' + this . thumbnailSize _ ) ;
return true ;
2012-12-23 23:59:01 +00:00
} ;
2014-01-08 05:28:01 +00:00
cam . BlobItemContainer . prototype . createDom = function ( ) {
2013-12-20 17:59:47 +00:00
this . decorateInternal ( this . dom _ . createElement ( 'div' ) ) ;
2012-12-23 21:54:42 +00:00
} ;
2014-01-08 05:28:01 +00:00
cam . BlobItemContainer . prototype . decorateInternal = function ( element ) {
cam . BlobItemContainer . superClass _ . decorateInternal . call ( this , element ) ;
2013-12-20 17:59:47 +00:00
this . layout _ ( ) ;
2012-12-23 21:54:42 +00:00
2013-12-20 17:59:47 +00:00
var el = this . getElement ( ) ;
goog . dom . classes . add ( el , 'cam-blobitemcontainer' ) ;
goog . dom . classes . add ( el , 'cam-blobitemcontainer-' + this . thumbnailSize _ ) ;
2012-12-23 21:54:42 +00:00
} ;
2014-01-08 05:28:01 +00:00
cam . BlobItemContainer . prototype . disposeInternal = function ( ) {
cam . BlobItemContainer . superClass _ . disposeInternal . call ( this ) ;
2013-12-20 17:59:47 +00:00
this . eh _ . dispose ( ) ;
2012-12-23 21:54:42 +00:00
} ;
2014-01-08 05:28:01 +00:00
cam . BlobItemContainer . prototype . addChildAt = function ( child , index , opt _render ) {
2013-12-20 17:59:47 +00:00
goog . base ( this , "addChildAt" , child , index , opt _render ) ;
child . setEnabled ( this . isSelectionEnabled ) ;
if ( ! this . isLayoutDirty _ ) {
var raf = window . requestAnimationFrame || window . mozRequestAnimationFrame || window . webkitRequestAnimationFrame || window . msRequestAnimationFrame ;
// It's OK if raf not supported, the timer loop we have going will pick up the layout a little later.
if ( raf ) {
raf ( goog . bind ( this . layout _ , this , false ) ) ;
}
2012-12-23 21:54:42 +00:00
2013-12-20 17:59:47 +00:00
this . isLayoutDirty _ = true ;
}
2013-08-05 06:55:17 +00:00
} ;
2014-01-08 05:28:01 +00:00
cam . BlobItemContainer . prototype . removeChildAt = function ( index , opt _render ) {
2013-12-29 19:43:35 +00:00
goog . base ( this , "removeChildAt" , index , opt _render ) ;
this . isLayoutDirty _ = true ;
} ;
2014-01-08 05:28:01 +00:00
cam . BlobItemContainer . prototype . enterDocument = function ( ) {
cam . BlobItemContainer . superClass _ . enterDocument . call ( this ) ;
2012-12-23 22:36:59 +00:00
2013-12-20 17:59:47 +00:00
this . resetChildren _ ( ) ;
this . listenToBlobItemEvents _ ( ) ;
2013-01-20 21:56:13 +00:00
2013-12-20 17:59:47 +00:00
if ( this . isFileDragEnabled ) {
this . fileDragListener _ = goog . bind ( this . handleFileDrag _ , this ) ;
this . eh _ . listen ( document , goog . events . EventType . DRAGOVER , this . fileDragListener _ ) ;
this . eh _ . listen ( document , goog . events . EventType . DRAGENTER , this . fileDragListener _ ) ;
2013-09-29 09:01:46 +00:00
2013-12-20 17:59:47 +00:00
this . fileDropHandler _ = new goog . events . FileDropHandler ( document ) ;
this . registerDisposable ( this . fileDropHandler _ ) ;
this . eh _ . listen ( this . fileDropHandler _ , goog . events . FileDropHandler . EventType . DROP , this . handleFileDrop _ ) ;
}
2013-07-29 08:02:40 +00:00
2013-12-20 17:59:47 +00:00
this . eh _ . listen ( document , goog . events . EventType . SCROLL , this . handleScroll _ ) ;
2013-12-02 06:56:09 +00:00
2013-12-20 17:59:47 +00:00
// We can't catch everything that could cause us to need to relayout. Instead, be lazy and just poll every second.
window . setInterval ( goog . bind ( this . layout _ , this , false ) , 1000 ) ;
2012-12-23 21:54:42 +00:00
} ;
2014-01-08 05:28:01 +00:00
cam . BlobItemContainer . prototype . exitDocument = function ( ) {
cam . BlobItemContainer . superClass _ . exitDocument . call ( this ) ;
2013-12-20 17:59:47 +00:00
this . eh _ . removeAll ( ) ;
2012-12-23 21:54:42 +00:00
} ;
2012-12-23 23:18:09 +00:00
2014-01-08 05:28:01 +00:00
cam . BlobItemContainer . prototype . showSearchSession = function ( session ) {
var changeType = cam . SearchSession . SEARCH _SESSION _CHANGE _TYPE . APPEND ;
2013-12-20 17:59:47 +00:00
2014-01-02 05:56:03 +00:00
if ( this . searchSession _ != session ) {
if ( this . searchSession _ ) {
2014-01-08 05:28:01 +00:00
this . eh _ . unlisten ( this . searchSession _ , cam . SearchSession . SEARCH _SESSION _CHANGED , this . searchDone _ ) ;
2014-01-02 05:56:03 +00:00
}
this . resetChildren _ ( ) ;
this . itemCache _ = { } ;
this . layout _ ( ) ;
this . searchSession _ = session ;
2014-01-08 05:28:01 +00:00
this . eh _ . listen ( session , cam . SearchSession . SEARCH _SESSION _CHANGED , this . searchDone _ ) ;
changeType = cam . SearchSession . SEARCH _SESSION _CHANGE _TYPE . NEW ;
2013-12-20 17:59:47 +00:00
}
2014-01-02 05:56:03 +00:00
this . searchDone _ ( { changeType : changeType } ) ;
2013-12-20 17:59:47 +00:00
} ;
2014-01-08 05:28:01 +00:00
cam . BlobItemContainer . prototype . getSearchSession = function ( ) {
2014-01-02 05:56:03 +00:00
return this . searchSession _ ;
2013-12-29 19:43:35 +00:00
} ;
2014-01-08 05:28:01 +00:00
cam . BlobItemContainer . prototype . searchDone _ = function ( e ) {
if ( e . changeType == cam . SearchSession . SEARCH _SESSION _CHANGE _TYPE . NEW ) {
2013-12-20 17:59:47 +00:00
this . resetChildren _ ( ) ;
this . itemCache _ = { } ;
}
2014-01-08 05:28:01 +00:00
this . populateChildren _ ( this . searchSession _ . getCurrentResults ( ) , e . changeType == cam . SearchSession . SEARCH _SESSION _CHANGE _TYPE . APPEND ) ;
2014-01-02 05:56:03 +00:00
if ( this . searchSession _ . isComplete ( ) ) {
2013-12-20 17:59:47 +00:00
return ;
}
2014-01-02 05:56:03 +00:00
// If we haven't filled the window with results, add some more.
this . layout _ ( ) ;
var docHeight = goog . dom . getDocumentHeight ( ) ;
var viewportHeight = goog . dom . getViewportSize ( ) . height ;
if ( docHeight < ( viewportHeight * 1.5 ) ) {
this . searchSession _ . loadMoreResults ( ) ;
2013-12-22 05:02:12 +00:00
}
2013-12-20 17:59:47 +00:00
} ;
2014-01-08 05:28:01 +00:00
cam . BlobItemContainer . prototype . findByBlobref _ = function ( blobref ) {
2013-12-20 17:59:47 +00:00
this . connection _ . describeWithThumbnails (
blobref , this . thumbnailSize _ ,
goog . bind ( this . findByBlobrefDone _ , this , blobref ) ,
function ( msg ) { alert ( msg ) ; } ) ;
2013-05-21 18:40:14 +00:00
} ;
2014-01-08 05:28:01 +00:00
cam . BlobItemContainer . prototype . getCheckedBlobItems = function ( ) {
2013-12-20 17:59:47 +00:00
return this . checkedBlobItems _ ;
2013-01-20 22:43:17 +00:00
} ;
2014-01-08 05:28:01 +00:00
cam . BlobItemContainer . prototype . listenToBlobItemEvents _ = function ( ) {
2013-12-20 17:59:47 +00:00
var doc = goog . dom . getOwnerDocument ( this . element _ ) ;
this . eh _ . listen ( this , goog . ui . Component . EventType . CHECK , this . handleBlobItemChecked _ ) ;
this . eh _ . listen ( this , goog . ui . Component . EventType . UNCHECK , this . handleBlobItemChecked _ ) ;
this . eh _ . listen ( doc , goog . events . EventType . KEYDOWN , this . handleKeyDownEvent _ ) ;
this . eh _ . listen ( doc , goog . events . EventType . KEYUP , this . handleKeyUpEvent _ ) ;
2013-01-20 22:43:17 +00:00
} ;
2014-01-08 05:28:01 +00:00
cam . BlobItemContainer . prototype . isShiftKeyDown _ = false ;
2013-01-20 22:43:17 +00:00
2014-01-08 05:28:01 +00:00
cam . BlobItemContainer . prototype . isCtrlKeyDown _ = false ;
2013-05-20 14:04:56 +00:00
2013-12-20 17:59:47 +00:00
// Sets state for whether or not the shift or ctrl key is down.
2014-01-08 05:28:01 +00:00
cam . BlobItemContainer . prototype . handleKeyDownEvent _ = function ( e ) {
2013-12-20 17:59:47 +00:00
if ( e . keyCode == goog . events . KeyCodes . SHIFT ) {
this . isShiftKeyDown _ = true ;
this . isCtrlKeyDown _ = false ;
return ;
}
if ( e . keyCode == goog . events . KeyCodes . CTRL ) {
this . isCtrlKeyDown _ = true ;
this . isShiftKeyDown _ = false ;
return ;
}
} ;
// Sets state for whether or not the shift or ctrl key is up.
2014-01-08 05:28:01 +00:00
cam . BlobItemContainer . prototype . handleKeyUpEvent _ = function ( e ) {
2013-12-20 17:59:47 +00:00
this . isShiftKeyDown _ = false ;
this . isCtrlKeyDown _ = false ;
2013-01-20 22:43:17 +00:00
} ;
2014-01-08 05:28:01 +00:00
cam . BlobItemContainer . prototype . handleBlobItemChecked _ = function ( e ) {
2013-12-20 17:59:47 +00:00
// Because the CHECK/UNCHECK event dispatches before isChecked is set.
// We stop the default behaviour because want to control manually here whether
2014-01-08 05:28:01 +00:00
// the source blobitem gets checked or not. See http://cam.org/issue/134
2013-12-20 17:59:47 +00:00
e . preventDefault ( ) ;
var blobItem = e . target ;
var isCheckingItem = ! blobItem . isChecked ( ) ;
var isShiftMultiSelect = this . isShiftKeyDown _ ;
var isCtrlMultiSelect = this . isCtrlKeyDown _ ;
if ( isShiftMultiSelect || isCtrlMultiSelect ) {
var lastChildSelected = this . checkedBlobItems _ [ this . checkedBlobItems _ . length - 1 ] ;
var firstChildSelected = this . checkedBlobItems _ [ 0 ] ;
var lastChosenIndex = this . indexOfChild ( lastChildSelected ) ;
var firstChosenIndex = this . indexOfChild ( firstChildSelected ) ;
var thisIndex = this . indexOfChild ( blobItem ) ;
}
if ( isShiftMultiSelect ) {
// deselect all items after the chosen one
for ( var i = lastChosenIndex ; i > thisIndex ; i -- ) {
var item = this . getChildAt ( i ) ;
item . setState ( goog . ui . Component . State . CHECKED , false ) ;
if ( goog . array . contains ( this . checkedBlobItems _ , item ) ) {
goog . array . remove ( this . checkedBlobItems _ , item ) ;
}
}
// make sure all the others are selected.
for ( var i = firstChosenIndex ; i <= thisIndex ; i ++ ) {
var item = this . getChildAt ( i ) ;
item . setState ( goog . ui . Component . State . CHECKED , true ) ;
if ( ! goog . array . contains ( this . checkedBlobItems _ , item ) ) {
this . checkedBlobItems _ . push ( item ) ;
}
}
} else if ( isCtrlMultiSelect ) {
if ( isCheckingItem ) {
blobItem . setState ( goog . ui . Component . State . CHECKED , true ) ;
if ( ! goog . array . contains ( this . checkedBlobItems _ , blobItem ) ) {
var pos = - 1 ;
for ( var i = 0 ; i <= this . checkedBlobItems _ . length ; i ++ ) {
var idx = this . indexOfChild ( this . checkedBlobItems _ [ i ] ) ;
if ( idx > thisIndex ) {
pos = i ;
break ;
}
}
if ( pos != - 1 ) {
goog . array . insertAt ( this . checkedBlobItems _ , blobItem , pos )
} else {
this . checkedBlobItems _ . push ( blobItem ) ;
}
}
} else {
blobItem . setState ( goog . ui . Component . State . CHECKED , false ) ;
if ( goog . array . contains ( this . checkedBlobItems _ , blobItem ) ) {
var done = goog . array . remove ( this . checkedBlobItems _ , blobItem ) ;
if ( ! done ) {
alert ( "Failed to remove item from selection" ) ;
}
}
}
} else {
blobItem . setState ( goog . ui . Component . State . CHECKED , isCheckingItem ) ;
if ( isCheckingItem ) {
this . checkedBlobItems _ . push ( blobItem ) ;
} else {
goog . array . remove ( this . checkedBlobItems _ , blobItem ) ;
}
}
2014-01-08 05:28:01 +00:00
this . dispatchEvent ( cam . BlobItemContainer . EventType . SELECTION _CHANGED ) ;
2013-01-20 22:43:17 +00:00
} ;
2014-01-08 05:28:01 +00:00
cam . BlobItemContainer . prototype . unselectAll = function ( ) {
2013-12-20 17:59:47 +00:00
goog . array . forEach ( this . checkedBlobItems _ , function ( item ) {
item . setState ( goog . ui . Component . State . CHECKED , false ) ;
} ) ;
this . checkedBlobItems _ = [ ] ;
2014-01-08 05:28:01 +00:00
this . dispatchEvent ( cam . BlobItemContainer . EventType . SELECTION _CHANGED ) ;
2013-12-20 17:59:47 +00:00
} ;
2014-01-08 05:28:01 +00:00
cam . BlobItemContainer . prototype . populateChildren _ = function ( result , append ) {
2014-01-01 10:04:14 +00:00
var i = append ? this . getChildCount ( ) : 0 ;
for ( var blob ; blob = result . blobs [ i ] ; i ++ ) {
2013-12-20 17:59:47 +00:00
var blobRef = blob . blob ;
var item = this . itemCache _ [ blobRef ] ;
2013-12-22 05:02:12 +00:00
var render = true ;
2013-12-20 17:59:47 +00:00
// If there's already an item for this blob, reuse it so that we don't lose any of the UI state (like whether it is selected).
if ( item ) {
item . update ( blobRef , result . description . meta ) ;
item . updateDom ( ) ;
2013-12-22 05:02:12 +00:00
render = false ;
2013-12-20 17:59:47 +00:00
} else {
2014-01-08 05:28:01 +00:00
item = new cam . BlobItem ( blobRef , result . description . meta ) ;
2013-12-20 17:59:47 +00:00
this . itemCache _ [ blobRef ] = item ;
2013-12-22 05:02:12 +00:00
}
if ( append ) {
this . addChild ( item , render ) ;
} else {
this . addChildAt ( item , i , render ) ;
2013-12-20 17:59:47 +00:00
}
}
// Remove any children we don't need anymore.
2013-12-22 05:02:12 +00:00
if ( ! append ) {
var numBlobs = result . blobs . length ;
while ( this . getChildCount ( ) > numBlobs ) {
this . itemCache _ [ this . getChildAt ( numBlobs ) . getBlobRef ( ) ] = null ;
this . removeChildAt ( numBlobs , true ) ;
}
2013-12-20 17:59:47 +00:00
}
2013-07-29 08:02:40 +00:00
} ;
2014-01-08 05:28:01 +00:00
cam . BlobItemContainer . prototype . layout _ = function ( force ) {
2013-12-20 17:59:47 +00:00
var el = this . getElement ( ) ;
var availWidth = el . clientWidth ;
2014-01-02 05:56:03 +00:00
if ( ! this . isVisible ( ) ) {
return ;
}
2013-12-20 17:59:47 +00:00
if ( ! force && ! this . isLayoutDirty _ && availWidth == this . lastClientWidth _ ) {
return ;
}
this . isLayoutDirty _ = false ;
this . lastClientWidth _ = availWidth ;
var currentTop = this . constructor . BLOB _ITEM _MARGIN ;
var currentWidth = this . constructor . BLOB _ITEM _MARGIN ;
var rowStart = 0 ;
var lastItem = this . getChildCount ( ) - 1 ;
for ( var i = rowStart ; i <= lastItem ; i ++ ) {
var item = this . getChildAt ( i ) ;
var nextWidth = currentWidth + this . thumbnailSize _ * item . getThumbAspect ( ) + this . constructor . BLOB _ITEM _MARGIN ;
if ( i != lastItem && nextWidth < availWidth ) {
currentWidth = nextWidth ;
continue ;
}
// Decide how many items are going to be in this row. We choose the number that will result in the smallest adjustment to the image sizes having to be done.
var rowEnd , rowWidth ;
if ( i == lastItem ) {
rowEnd = lastItem ;
rowWidth = nextWidth ;
if ( nextWidth / availWidth <
this . constructor . LAST _ROW _CLOSE _ENOUGH _TO _FULL ) {
availWidth = nextWidth ;
}
} else if ( availWidth - currentWidth <= nextWidth - availWidth ) {
rowEnd = i - 1 ;
rowWidth = currentWidth ;
} else {
rowEnd = i ;
rowWidth = nextWidth ;
}
currentTop += this . layoutRow _ ( rowStart , rowEnd , availWidth , rowWidth , currentTop ) + this . constructor . BLOB _ITEM _MARGIN ;
currentWidth = this . constructor . BLOB _ITEM _MARGIN ;
rowStart = rowEnd + 1 ;
i = rowEnd ;
}
el . style . height = currentTop + this . constructor . BLOB _ITEM _MARGIN + 'px' ;
} ;
// @param {Number} startIndex The index of the first item in the row.
// @param {Number} endIndex The index of the last item in the row.
// @param {Number} availWidth The width available to the row for layout.
// @param {Number} usedWidth The width that the contents of the row consume
// using their initial dimensions, before any scaling or clipping.
// @param {Number} top The position of the top of the row.
// @return {Number} The height of the row after layout.
2014-01-08 05:28:01 +00:00
cam . BlobItemContainer . prototype . layoutRow _ = function ( startIndex , endIndex , availWidth , usedWidth , top ) {
2013-12-20 17:59:47 +00:00
var currentLeft = 0 ;
var rowHeight = Number . POSITIVE _INFINITY ;
var numItems = endIndex - startIndex + 1 ;
var availThumbWidth = availWidth - ( this . constructor . BLOB _ITEM _MARGIN * ( numItems + 1 ) ) ;
var usedThumbWidth = usedWidth - ( this . constructor . BLOB _ITEM _MARGIN * ( numItems + 1 ) ) ;
for ( var i = startIndex ; i <= endIndex ; i ++ ) {
var item = this . getChildAt ( i ) ;
// We figure out the amount to adjust each item in this slightly non- intuitive way so that the adjustment is split up as fairly as possible. Figuring out a ratio up front and applying it to all items uniformly can end up with a large amount left over because of rounding.
var numItemsLeft = ( endIndex + 1 ) - i ;
var delta = Math . round ( ( availThumbWidth - usedThumbWidth ) / numItemsLeft ) ;
var originalWidth = this . thumbnailSize _ * item . getThumbAspect ( ) ;
var width = originalWidth + delta ;
var ratio = width / originalWidth ;
var height = Math . round ( this . thumbnailSize _ * ratio ) ;
var elm = item . getElement ( ) ;
elm . style . left = currentLeft + this . constructor . BLOB _ITEM _MARGIN + 'px' ;
elm . style . top = top + 'px' ;
item . setSize ( width , height ) ;
currentLeft += width + this . constructor . BLOB _ITEM _MARGIN ;
usedThumbWidth += delta ;
rowHeight = Math . min ( rowHeight , height ) ;
}
for ( var i = startIndex ; i <= endIndex ; i ++ ) {
this . getChildAt ( i ) . setHeight ( rowHeight ) ;
}
return rowHeight ;
2013-07-29 08:02:40 +00:00
} ;
2014-01-08 05:28:01 +00:00
cam . BlobItemContainer . prototype . handleScroll _ = function ( ) {
2014-01-02 05:56:03 +00:00
if ( ! this . isVisible ( ) ) {
return ;
}
2013-12-20 17:59:47 +00:00
var docHeight = goog . dom . getDocumentHeight ( ) ;
var scroll = goog . dom . getDocumentScroll ( ) ;
var viewportSize = goog . dom . getViewportSize ( ) ;
if ( ( docHeight - scroll . y - viewportSize . height ) >
this . constructor . INFINITE _SCROLL _THRESHOLD _PX _ ) {
return ;
}
2014-01-01 10:04:14 +00:00
if ( this . searchSession _ ) {
this . searchSession _ . loadMoreResults ( ) ;
2013-12-20 17:59:47 +00:00
}
} ;
2014-01-08 05:28:01 +00:00
cam . BlobItemContainer . prototype . findByBlobrefDone _ = function ( permanode , result ) {
2013-12-20 17:59:47 +00:00
this . resetChildren _ ( ) ;
if ( ! result ) {
return ;
}
var meta = result . meta ;
if ( ! meta || ! meta [ permanode ] ) {
return ;
}
2014-01-08 05:28:01 +00:00
var item = new cam . BlobItem ( permanode , meta ) ;
2013-12-20 17:59:47 +00:00
this . addChild ( item , true ) ;
} ;
// Clears all children from this container, reseting to the default state.
2014-01-08 05:28:01 +00:00
cam . BlobItemContainer . prototype . resetChildren _ = function ( ) {
2013-12-20 17:59:47 +00:00
this . removeChildren ( true ) ;
2012-12-24 01:52:09 +00:00
} ;
2013-01-20 21:56:13 +00:00
2014-01-08 05:28:01 +00:00
cam . BlobItemContainer . prototype . handleFileDrop _ = function ( e ) {
2013-12-20 17:59:47 +00:00
var recipient = this . dragActiveElement _ ;
if ( ! recipient ) {
console . log ( "No valid target to drag and drop on." ) ;
return ;
}
2013-09-29 09:01:46 +00:00
2013-12-20 17:59:47 +00:00
goog . dom . classes . remove ( recipient . getElement ( ) , 'cam-dropactive' ) ;
this . dragActiveElement _ = null ;
2013-09-29 09:01:46 +00:00
2013-12-20 17:59:47 +00:00
var files = e . getBrowserEvent ( ) . dataTransfer . files ;
for ( var i = 0 , n = files . length ; i < n ; i ++ ) {
var file = files [ i ] ;
// TODO(bslatkin): Add an uploading item placeholder while the upload is in progress. Somehow pipe through the POST progress.
this . connection _ . uploadFile ( file , goog . bind ( this . handleUploadSuccess _ , this , file , recipient . blobRef _ ) ) ;
}
2013-01-20 21:56:13 +00:00
} ;
2014-01-08 05:28:01 +00:00
cam . BlobItemContainer . prototype . handleUploadSuccess _ = function ( file , recipient , blobRef ) {
2013-12-20 17:59:47 +00:00
this . connection _ . createPermanode (
goog . bind ( this . handleCreatePermanodeSuccess _ , this , file , recipient , blobRef ) ) ;
2013-01-20 21:56:13 +00:00
} ;
2014-01-08 05:28:01 +00:00
cam . BlobItemContainer . prototype . handleCreatePermanodeSuccess _ = function ( file , recipient , blobRef , permanode ) {
2013-12-20 17:59:47 +00:00
this . connection _ . newSetAttributeClaim ( permanode , 'camliContent' , blobRef ,
goog . bind ( this . handleSetAttributeSuccess _ , this , file , recipient , blobRef , permanode ) ) ;
2013-01-20 21:56:13 +00:00
} ;
2014-01-08 05:28:01 +00:00
cam . BlobItemContainer . prototype . handleSetAttributeSuccess _ = function ( file , recipient , blobRef , permanode ) {
2013-12-20 17:59:47 +00:00
this . connection _ . describeWithThumbnails ( permanode , this . thumbnailSize _ ,
goog . bind ( this . handleDescribeSuccess _ , this , recipient , permanode ) ) ;
2013-01-20 21:56:13 +00:00
} ;
2014-01-08 05:28:01 +00:00
cam . BlobItemContainer . prototype . handleDescribeSuccess _ = function ( recipient , permanode , describeResult ) {
2013-12-24 04:17:16 +00:00
if ( recipient ) {
this . connection _ . newAddAttributeClaim ( recipient , 'camliMember' , permanode ) ;
}
2014-01-01 10:04:14 +00:00
if ( this . searchSession _ && this . searchSession _ . supportsChangeNotifications ( ) ) {
2013-12-24 04:17:16 +00:00
// We'll find this when we reload.
return ;
}
2014-01-08 05:28:01 +00:00
var item = new cam . BlobItem ( permanode , describeResult . meta ) ;
2013-12-20 17:59:47 +00:00
this . addChildAt ( item , 0 , true ) ;
if ( ! recipient ) {
return ;
}
2013-01-20 21:56:13 +00:00
} ;
2014-01-08 05:28:01 +00:00
cam . BlobItemContainer . prototype . handleFileDrag _ = function ( e ) {
2013-12-20 17:59:47 +00:00
if ( this . dragEndTimer _ ) {
this . dragEndTimer _ = window . clearTimeout ( this . dragEndTimer _ ) ;
}
this . dragEndTimer _ = window . setTimeout ( this . fileDragListener _ , 2000 ) ;
2013-01-20 21:56:13 +00:00
2013-12-20 17:59:47 +00:00
var activeElement = e ? this . getOwnerControl ( e . target ) : e ;
if ( activeElement ) {
if ( ! activeElement . isCollection ( ) ) {
activeElement = this ;
}
} else if ( e ) {
activeElement = this ;
}
2013-07-09 23:04:08 +00:00
2013-12-20 17:59:47 +00:00
if ( activeElement == this . dragActiveElement _ ) {
return ;
}
2013-01-20 21:56:13 +00:00
2013-12-20 17:59:47 +00:00
if ( this . dragActiveElement _ ) {
goog . dom . classes . remove ( this . dragActiveElement _ . getElement ( ) , 'cam-dropactive' ) ;
}
2013-01-20 21:56:13 +00:00
2013-12-20 17:59:47 +00:00
this . dragActiveElement _ = activeElement ;
2013-01-20 21:56:13 +00:00
2013-12-20 17:59:47 +00:00
if ( this . dragActiveElement _ ) {
goog . dom . classes . add ( this . dragActiveElement _ . getElement ( ) , 'cam-dropactive' ) ;
}
2013-01-20 21:56:13 +00:00
} ;
2013-05-22 16:38:58 +00:00
2014-01-08 05:28:01 +00:00
cam . BlobItemContainer . prototype . hide _ = function ( ) {
2013-12-20 17:59:47 +00:00
goog . dom . classes . remove ( this . getElement ( ) , 'cam-blobitemcontainer-' + this . thumbnailSize _ ) ;
goog . dom . classes . add ( this . getElement ( ) , 'cam-blobitemcontainer-hidden' ) ;
2013-05-22 16:38:58 +00:00
} ;
2014-01-08 05:28:01 +00:00
cam . BlobItemContainer . prototype . show _ = function ( ) {
2013-12-20 17:59:47 +00:00
goog . dom . classes . remove ( this . getElement ( ) , 'cam-blobitemcontainer-hidden' ) ;
goog . dom . classes . add ( this . getElement ( ) , 'cam-blobitemcontainer-' + this . thumbnailSize _ ) ;
this . layout _ ( true ) ;
2013-05-22 16:38:58 +00:00
} ;