github.com/olivere/camlistore@v0.0.0-20140121221811-1b7ac2da0199/server/camlistored/ui/blob_item.js (about)

     1  /*
     2  Copyright 2013 The Camlistore Authors
     3  
     4  Licensed under the Apache License, Version 2.0 (the "License");
     5  you may not use this file except in compliance with the License.
     6  You may obtain a copy of the License at
     7  
     8  	http://www.apache.org/licenses/LICENSE-2.0
     9  
    10  Unless required by applicable law or agreed to in writing, software
    11  distributed under the License is distributed on an "AS IS" BASIS,
    12  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    13  See the License for the specific language governing permissions and
    14  limitations under the License.
    15  */
    16  
    17  goog.provide('cam.BlobItem');
    18  
    19  goog.require('goog.dom');
    20  goog.require('goog.dom.classes');
    21  goog.require('goog.events.EventHandler');
    22  goog.require('goog.events.EventType');
    23  goog.require('goog.ui.Control');
    24  
    25  goog.require('cam.imageUtil');
    26  goog.require('cam.ServerType');
    27  
    28  
    29  // @fileoverview An item showing in a blob item container; represents a blob that has already been uploaded in the system, or acts as a placeholder for a new blob.
    30  // @param {string} blobRef BlobRef for the item.
    31  // @param {cam.ServerType.IndexerMetaBag} metaBag Maps blobRefs to metadata for this blob and related blobs.
    32  // @param {string} opt_contentLink if "true", use the contained file blob as link when decorating
    33  // @param {goog.dom.DomHelper=} opt_domHelper DOM helper to use.
    34  // @extends {goog.ui.Control}
    35  // @constructor
    36  cam.BlobItem = function(blobRef, metaBag, opt_contentLink, opt_domHelper) {
    37  	goog.base(this, null, null, opt_domHelper);
    38  
    39  	this.update(blobRef, metaBag, opt_contentLink);
    40  
    41  	this.setSupportedState(goog.ui.Component.State.CHECKED, true);
    42  	this.setSupportedState(goog.ui.Component.State.DISABLED, true);
    43  	this.setAutoStates(goog.ui.Component.State.CHECKED, false);
    44  
    45  	// Blob items dispatch state when checked.
    46  	this.setDispatchTransitionEvents(goog.ui.Component.State.CHECKED, true);
    47  };
    48  goog.inherits(cam.BlobItem, goog.ui.Control);
    49  
    50  cam.BlobItem.prototype.update = function(blobRef, metaBag, opt_contentLink) {
    51  	// TODO(mpl): Hack so we know when to decorate with the blobref of the contained file, instead of with the permanode, as the link. Idiomatic alternative suggestion very welcome.
    52  
    53  	this.useContentAsLink_ = "false";
    54  	if (typeof opt_contentLink !== "undefined" && opt_contentLink == "true") {
    55  		this.useContentAsLink_ = opt_contentLink;
    56  	}
    57  
    58  	this.blobRef_ = blobRef;
    59  	this.metaBag_ = metaBag;
    60  	this.metaData_ = this.metaBag_[this.blobRef_];
    61  	this.resolvedMetaData_ = cam.BlobItem.resolve(this.blobRef_, this.metaBag_);
    62  	this.currentIntrinsicImageHeight_ = 0;
    63  };
    64  
    65  cam.BlobItem.TITLE_HEIGHT = 21;
    66  
    67  // TODO(bslatkin): Handle more permanode types.
    68  // @param {string} blobRef string BlobRef to resolve.
    69  // @param {cam.ServerType.IndexerMetaBag} metaBag Metadata bag to use for resolving the blobref.
    70  // @return {cam.ServerType.IndexerMeta?}
    71  cam.BlobItem.resolve = function(blobRef, metaBag) {
    72  	var metaData = metaBag[blobRef];
    73  	if (metaData.camliType == 'permanode' && metaData.permanode && metaData.permanode.attr) {
    74  		if (metaData.permanode.attr.camliContent) {
    75  			// Permanode is pointing at another blob.
    76  			var content = metaData.permanode.attr.camliContent;
    77  			if (content.length == 1) {
    78  				return metaBag[content[0]];
    79  			}
    80  		} else {
    81  			// Permanode is its own content.
    82  			return metaData;
    83  		}
    84  	}
    85  	return null;
    86  };
    87  
    88  cam.BlobItem.prototype.isCollection = function() {
    89  	// TODO(mpl): for now disallow being a collection if it
    90  	// has members. What else to check?
    91  	if (!this.resolvedMetaData_ || this.resolvedMetaData_.camliType != 'permanode' || !this.resolvedMetaData_.permanode || !this.resolvedMetaData_.permanode.attr || this.resolvedMetaData_.permanode.attr.camliContent) {
    92  			return false;
    93  	}
    94  	return true;
    95  };
    96  
    97  cam.BlobItem.prototype.getBlobRef = function() {
    98  	return this.blobRef_;
    99  };
   100  
   101  cam.BlobItem.prototype.getThumbAspect = function() {
   102  	if (!this.metaData_.thumbnailWidth || !this.metaData_.thumbnailHeight) {
   103  		return 0;
   104  	}
   105  	return this.metaData_.thumbnailWidth / this.metaData_.thumbnailHeight;
   106  };
   107  
   108  cam.BlobItem.prototype.getWidth = function() {
   109  	return parseInt(this.getElement().style.width);
   110  };
   111  
   112  cam.BlobItem.prototype.getHeight = function() {
   113  	return parseInt(this.getElement().style.height);
   114  };
   115  
   116  cam.BlobItem.prototype.setWidth = function(w) {
   117  	this.setSize(w, this.getHeight());
   118  };
   119  
   120  cam.BlobItem.prototype.setHeight = function(h) {
   121  	this.setSize(this.getWidth(), h);
   122  };
   123  
   124  // Sets the display size of the item. The thumbnail will be scaled, centered,
   125  // and clipped within this size as appropriate.
   126  // @param {number} w
   127  // @param {number} h
   128  cam.BlobItem.prototype.setSize = function(w, h) {
   129  	this.getElement().style.width = w + 'px';
   130  	this.getElement().style.height = h + 'px';
   131  
   132  	var thumbHeight = h;
   133  	if (!this.isImage()) {
   134  		thumbHeight -= this.constructor.TITLE_HEIGHT;
   135  	}
   136  	this.setThumbSize(w, thumbHeight);
   137  };
   138  
   139  // Sets the display size of just the thumbnail. It will be scaled, centered, and
   140  // clipped within this size as appropriate.
   141  // @param {number} w
   142  // @param {number} h
   143  cam.BlobItem.prototype.setThumbSize = function(w, h) {
   144  	// In the case of images, we want a full bleed to both w and h, so we clip the bigger dimension as necessary. It's not easy to notice that a few pixels have been shaved off the edge of a photo.
   145  	// In the case of non-images, we have an icon with text underneath, so we cannot clip. Instead, just constrain the icon to fit the available space.
   146  	var adjustedHeight;
   147  	if (this.isImage()) {
   148  		adjustedHeight = this.getThumbAspect() < w / h ? w / this.getThumbAspect() : h;
   149  	} else {
   150  		adjustedHeight = this.getThumbAspect() < w / h ? h : w / this.getThumbAspect();
   151  	}
   152  	var adjustedWidth = adjustedHeight * this.getThumbAspect();
   153  
   154  	this.thumb_.width = Math.round(adjustedWidth);
   155  	this.thumb_.height = Math.round(adjustedHeight);
   156  
   157  	this.thumbClip_.style.width = w + 'px';
   158  	this.thumbClip_.style.height = h + 'px';
   159  
   160  	this.thumb_.style.top = Math.round((h - adjustedHeight) / 2) + 'px';
   161  	this.thumb_.style.left = Math.round((w - adjustedWidth) / 2) + 'px';
   162  
   163  	this.loading_.style.top = Math.round((h - 85) / 2) + 'px';
   164  	this.loading_.style.left = Math.round((w - 70) / 2) + 'px';
   165  
   166  	// Load a differently sized image from server if necessary.
   167  	if (!this.thumb_.src || adjustedWidth > parseInt(this.thumbClip_.style.width) || adjustedHeight > parseInt(this.thumbClip_.style.height)) {
   168  		this.currentIntrinsicImageHeight_ = cam.imageUtil.getSizeToRequest(adjustedHeight, this.currentIntrinsicImageHeight_);
   169  
   170  		var tv = '';
   171  		if (window.CAMLISTORE_CONFIG) {
   172  			tv = CAMLISTORE_CONFIG.thumbVersion || '';
   173  		}
   174  
   175  		// TODO(aa): The mh param is kind of a hack, it would be better if the server just returned the base URL and the aspect ratio, rather than specific dimensions.
   176  		var newThumb = this.getThumbSrc_().split('?')[0] + '?mh=' +
   177  				this.currentIntrinsicImageHeight_ + '&tv=' + tv;
   178  
   179  		// It's important to only assign the new src if it has changed. Assigning a src causes layout and style recalc.
   180  		if (newThumb != this.thumb_.getAttribute('src')) {
   181  			this.thumb_.src = newThumb;
   182  		}
   183  	}
   184  };
   185  
   186  cam.BlobItem.prototype.isImage = function() {
   187  	return Boolean(this.resolvedMetaData_.image);
   188  };
   189  
   190  cam.BlobItem.prototype.getThumbSrc_ = function() {
   191  	return './' + this.metaData_.thumbnailSrc;
   192  };
   193  
   194  cam.BlobItem.prototype.getLink_ = function() {
   195  	if (this.useContentAsLink_ == "true") {
   196  		var b = this.getFileBlobref_();
   197  		if (b == "") {
   198  			b = this.getDirBlobref_();
   199  		}
   200  		return './?b=' + b;
   201  	}
   202  
   203  	// The new detail page looks ridiculous for non-images, so don't go to it for those yet.
   204  	var uri = new goog.Uri(location.href);
   205  	uri.setParameterValue('p', this.blobRef_);
   206  	if (this.isImage()) {
   207  		uri.setParameterValue('newui', '1');
   208  	}
   209  	return uri.toString();
   210  };
   211  
   212  cam.BlobItem.prototype.getFileBlobref_ = function() {
   213  	if (this.resolvedMetaData_ && this.resolvedMetaData_.camliType == 'file') {
   214  		return this.resolvedMetaData_.blobRef;
   215  	}
   216  	return "";
   217  }
   218  
   219  cam.BlobItem.prototype.getDirBlobref_ = function() {
   220  	if (this.resolvedMetaData_ && this.resolvedMetaData_.camliType == 'directory') {
   221  		return this.resolvedMetaData_.blobRef;
   222  	}
   223  	return "";
   224  }
   225  
   226  cam.BlobItem.prototype.getTitle_ = function() {
   227  	if (this.metaData_) {
   228  		if (this.metaData_.camliType == 'permanode' &&
   229  			!!this.metaData_.permanode &&
   230  			!!this.metaData_.permanode.attr &&
   231  			!!this.metaData_.permanode.attr.title) {
   232  			return this.metaData_.permanode.attr.title;
   233  		}
   234  	}
   235  	if (this.resolvedMetaData_) {
   236  		if (this.resolvedMetaData_.camliType == 'file' &&
   237  			!!this.resolvedMetaData_.file) {
   238  			return this.resolvedMetaData_.file.fileName;
   239  		}
   240  		if (this.resolvedMetaData_.camliType == 'directory' &&
   241  					!!this.resolvedMetaData_.dir) {
   242  				return this.resolvedMetaData_.dir.fileName;
   243  			}
   244  		if (this.resolvedMetaData_.camliType == 'permanode' &&
   245  			!!this.resolvedMetaData_.permanode &&
   246  			!!this.resolvedMetaData_.permanode.attr &&
   247  			!!this.resolvedMetaData_.permanode.attr.title) {
   248  			return this.resolvedMetaData_.permanode.attr.title;
   249  		}
   250  	}
   251  	return 'Unknown title';
   252  };
   253  
   254  cam.BlobItem.prototype.createDom = function() {
   255  	this.decorateInternal(this.dom_.createElement('div'));
   256  };
   257  
   258  cam.BlobItem.prototype.decorateInternal = function(element) {
   259  	cam.BlobItem.superClass_.decorateInternal.call(this, element);
   260  
   261  	var el = this.getElement();
   262  	goog.dom.classes.add(el, 'cam-blobitem');
   263  
   264  	this.link_ = this.dom_.createDom('a');
   265  
   266  	this.thumbClip_ = this.dom_.createDom('div', 'cam-blobitem-thumbclip cam-blobitem-loading');
   267  	this.link_.appendChild(this.thumbClip_);
   268  
   269  	this.loading_ = this.dom_.createDom('div', 'cam-blobitem-progress',
   270  		this.dom_.createDom('div', 'lefttop'),
   271  		this.dom_.createDom('div', 'leftbottom'),
   272  		this.dom_.createDom('div', 'righttop'),
   273  		this.dom_.createDom('div', 'rightbottom'));
   274  	this.thumbClip_.appendChild(this.loading_);
   275  
   276  	this.thumb_ = this.dom_.createDom('img', 'cam-blobitem-thumb');
   277  	this.thumb_.onload = function(e){
   278  		goog.dom.removeNode(this.loading_);
   279  		goog.dom.classes.remove(this.thumbClip_, 'cam-blobitem-loading');
   280  	}.bind(this);
   281  	this.thumbClip_.appendChild(this.thumb_);
   282  
   283  	el.appendChild(this.link_);
   284  
   285  	this.checkmark_ = this.dom_.createDom('div', 'checkmark');
   286  	this.getElement().appendChild(this.checkmark_);
   287  
   288  	this.label_ = this.dom_.createDom('span', 'cam-blobitem-thumbtitle');
   289  	this.link_.appendChild(this.label_);
   290  
   291  	this.updateDom();
   292  
   293  	this.getElement().addEventListener('click', this.handleClick_.bind(this));
   294  	this.setEnabled(false);
   295  };
   296  
   297  // The image src is not set here because that depends on layout. Instead, it
   298  // gets set as a side-effect of BlobItemContainer.prototype.layout().
   299  cam.BlobItem.prototype.updateDom = function() {
   300  	this.link_.href = this.getLink_();
   301  
   302  	if (this.isImage()) {
   303  		this.addClassName('cam-blobitem-image');
   304  		this.thumb_.title = this.getTitle_();
   305  		this.label_.textContent = '';
   306  	} else {
   307  		this.removeClassName('cam-blobitem-image');
   308  		this.label_.textContent = this.getTitle_();
   309  	}
   310  };
   311  
   312  cam.BlobItem.prototype.handleClick_ = function(e) {
   313  	if (!this.checkmark_) {
   314  		return;
   315  	}
   316  
   317  	if (e.target == this.checkmark_ || this.checkmark_.contains(e.target)) {
   318  		this.setChecked(!this.isChecked());
   319  		e.preventDefault();
   320  	}
   321  };