github.com/slspeek/camlistore_namedsearch@v0.0.0-20140519202248-ed6f70f7721a/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.ServerType');
    26  goog.require('cam.Thumber');
    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  
    63  	if (this.resolvedMetaData_.image) {
    64  		this.thumber_ = cam.Thumber.fromImageMeta(this.resolvedMetaData_);
    65  	} else {
    66  		this.thumber_ = new cam.Thumber(this.metaData_.thumbnailSrc);
    67  	}
    68  };
    69  
    70  cam.BlobItem.TITLE_HEIGHT = 21;
    71  
    72  // TODO(bslatkin): Handle more permanode types.
    73  // @param {string} blobRef string BlobRef to resolve.
    74  // @param {cam.ServerType.IndexerMetaBag} metaBag Metadata bag to use for resolving the blobref.
    75  // @return {cam.ServerType.IndexerMeta?}
    76  cam.BlobItem.resolve = function(blobRef, metaBag) {
    77  	var metaData = metaBag[blobRef];
    78  	if (metaData.camliType == 'permanode' && metaData.permanode && metaData.permanode.attr) {
    79  		if (metaData.permanode.attr.camliContent) {
    80  			// Permanode is pointing at another blob.
    81  			var content = metaData.permanode.attr.camliContent;
    82  			if (content.length == 1) {
    83  				return metaBag[content[0]];
    84  			}
    85  		} else {
    86  			// Permanode is its own content.
    87  			return metaData;
    88  		}
    89  	}
    90  	return null;
    91  };
    92  
    93  cam.BlobItem.prototype.isCollection = function() {
    94  	// TODO(mpl): for now disallow being a collection if it
    95  	// has members. What else to check?
    96  	if (!this.resolvedMetaData_ || this.resolvedMetaData_.camliType != 'permanode' || !this.resolvedMetaData_.permanode || !this.resolvedMetaData_.permanode.attr || this.resolvedMetaData_.permanode.attr.camliContent) {
    97  			return false;
    98  	}
    99  	return true;
   100  };
   101  
   102  cam.BlobItem.prototype.getBlobRef = function() {
   103  	return this.blobRef_;
   104  };
   105  
   106  cam.BlobItem.prototype.getThumbAspect = function() {
   107  	if (!this.metaData_.thumbnailWidth || !this.metaData_.thumbnailHeight) {
   108  		return 0;
   109  	}
   110  	return this.metaData_.thumbnailWidth / this.metaData_.thumbnailHeight;
   111  };
   112  
   113  cam.BlobItem.prototype.getWidth = function() {
   114  	return parseInt(this.getElement().style.width);
   115  };
   116  
   117  cam.BlobItem.prototype.getHeight = function() {
   118  	return parseInt(this.getElement().style.height);
   119  };
   120  
   121  cam.BlobItem.prototype.setWidth = function(w) {
   122  	this.setSize(w, this.getHeight());
   123  };
   124  
   125  cam.BlobItem.prototype.setHeight = function(h) {
   126  	this.setSize(this.getWidth(), h);
   127  };
   128  
   129  // Sets the display size of the item. The thumbnail will be scaled, centered,
   130  // and clipped within this size as appropriate.
   131  // @param {number} w
   132  // @param {number} h
   133  cam.BlobItem.prototype.setSize = function(w, h) {
   134  	this.getElement().style.width = w + 'px';
   135  	this.getElement().style.height = h + 'px';
   136  
   137  	var thumbHeight = h;
   138  	if (!this.isImage()) {
   139  		thumbHeight -= this.constructor.TITLE_HEIGHT;
   140  	}
   141  	this.setThumbSize(w, thumbHeight);
   142  };
   143  
   144  // Sets the display size of just the thumbnail. It will be scaled, centered, and
   145  // clipped within this size as appropriate.
   146  // @param {number} w
   147  // @param {number} h
   148  cam.BlobItem.prototype.setThumbSize = function(w, h) {
   149  	// 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.
   150  	// 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.
   151  	var adjustedHeight;
   152  	if (this.isImage()) {
   153  		adjustedHeight = this.getThumbAspect() < w / h ? w / this.getThumbAspect() : h;
   154  	} else {
   155  		adjustedHeight = this.getThumbAspect() < w / h ? h : w / this.getThumbAspect();
   156  	}
   157  	var adjustedWidth = adjustedHeight * this.getThumbAspect();
   158  
   159  	this.thumb_.width = Math.round(adjustedWidth);
   160  	this.thumb_.height = Math.round(adjustedHeight);
   161  
   162  	this.thumbClip_.style.width = w + 'px';
   163  	this.thumbClip_.style.height = h + 'px';
   164  
   165  	this.thumb_.style.top = Math.round((h - adjustedHeight) / 2) + 'px';
   166  	this.thumb_.style.left = Math.round((w - adjustedWidth) / 2) + 'px';
   167  
   168  	this.loading_.style.top = Math.round((h - 85) / 2) + 'px';
   169  	this.loading_.style.left = Math.round((w - 70) / 2) + 'px';
   170  
   171  	// It's important to only assign the new src if it has changed. Assigning a src causes layout and style recalc.
   172  	var newThumb = this.thumber_.getSrc(adjustedHeight);
   173  	if (newThumb != this.thumb_.getAttribute('src')) {
   174  		this.thumb_.src = newThumb;
   175  	}
   176  };
   177  
   178  cam.BlobItem.prototype.isImage = function() {
   179  	return Boolean(this.resolvedMetaData_.image);
   180  };
   181  
   182  cam.BlobItem.prototype.getLink_ = function() {
   183  	if (this.useContentAsLink_ == "true") {
   184  		var b = this.getFileBlobref_();
   185  		if (b == "") {
   186  			b = this.getDirBlobref_();
   187  		}
   188  		return './?b=' + b;
   189  	}
   190  
   191  	// The new detail page looks ridiculous for non-images, so don't go to it for those yet.
   192  	var uri = new goog.Uri(location.href);
   193  	uri.setParameterValue('p', this.blobRef_);
   194  	if (this.isImage()) {
   195  		uri.setParameterValue('newui', '1');
   196  	}
   197  	return uri.toString();
   198  };
   199  
   200  cam.BlobItem.prototype.getFileBlobref_ = function() {
   201  	if (this.resolvedMetaData_ && this.resolvedMetaData_.camliType == 'file') {
   202  		return this.resolvedMetaData_.blobRef;
   203  	}
   204  	return "";
   205  }
   206  
   207  cam.BlobItem.prototype.getDirBlobref_ = function() {
   208  	if (this.resolvedMetaData_ && this.resolvedMetaData_.camliType == 'directory') {
   209  		return this.resolvedMetaData_.blobRef;
   210  	}
   211  	return "";
   212  }
   213  
   214  cam.BlobItem.prototype.getTitle_ = function() {
   215  	if (this.metaData_) {
   216  		if (this.metaData_.camliType == 'permanode' &&
   217  			!!this.metaData_.permanode &&
   218  			!!this.metaData_.permanode.attr &&
   219  			!!this.metaData_.permanode.attr.title) {
   220  			return this.metaData_.permanode.attr.title;
   221  		}
   222  	}
   223  	if (this.resolvedMetaData_) {
   224  		if (this.resolvedMetaData_.camliType == 'file' &&
   225  			!!this.resolvedMetaData_.file) {
   226  			return this.resolvedMetaData_.file.fileName;
   227  		}
   228  		if (this.resolvedMetaData_.camliType == 'directory' &&
   229  					!!this.resolvedMetaData_.dir) {
   230  				return this.resolvedMetaData_.dir.fileName;
   231  			}
   232  		if (this.resolvedMetaData_.camliType == 'permanode' &&
   233  			!!this.resolvedMetaData_.permanode &&
   234  			!!this.resolvedMetaData_.permanode.attr &&
   235  			!!this.resolvedMetaData_.permanode.attr.title) {
   236  			return this.resolvedMetaData_.permanode.attr.title;
   237  		}
   238  	}
   239  	return 'Unknown title';
   240  };
   241  
   242  cam.BlobItem.prototype.createDom = function() {
   243  	this.decorateInternal(this.dom_.createElement('div'));
   244  };
   245  
   246  cam.BlobItem.prototype.decorateInternal = function(element) {
   247  	cam.BlobItem.superClass_.decorateInternal.call(this, element);
   248  
   249  	var el = this.getElement();
   250  	goog.dom.classes.add(el, 'cam-blobitem');
   251  
   252  	this.link_ = this.dom_.createDom('a');
   253  
   254  	this.thumbClip_ = this.dom_.createDom('div', 'cam-blobitem-thumbclip cam-blobitem-loading');
   255  	this.link_.appendChild(this.thumbClip_);
   256  
   257  	this.loading_ = this.dom_.createDom('div', 'cam-blobitem-progress',
   258  		this.dom_.createDom('div', 'lefttop'),
   259  		this.dom_.createDom('div', 'leftbottom'),
   260  		this.dom_.createDom('div', 'righttop'),
   261  		this.dom_.createDom('div', 'rightbottom'));
   262  	this.thumbClip_.appendChild(this.loading_);
   263  
   264  	this.thumb_ = this.dom_.createDom('img', 'cam-blobitem-thumb');
   265  	this.thumb_.onload = function(e){
   266  		goog.dom.removeNode(this.loading_);
   267  		goog.dom.classes.remove(this.thumbClip_, 'cam-blobitem-loading');
   268  	}.bind(this);
   269  	this.thumbClip_.appendChild(this.thumb_);
   270  
   271  	el.appendChild(this.link_);
   272  
   273  	this.checkmark_ = this.dom_.createDom('div', 'checkmark');
   274  	this.getElement().appendChild(this.checkmark_);
   275  
   276  	this.label_ = this.dom_.createDom('span', 'cam-blobitem-thumbtitle');
   277  	this.link_.appendChild(this.label_);
   278  
   279  	this.updateDom();
   280  
   281  	this.getElement().addEventListener('click', this.handleClick_.bind(this));
   282  	this.setEnabled(false);
   283  };
   284  
   285  // The image src is not set here because that depends on layout. Instead, it
   286  // gets set as a side-effect of BlobItemContainer.prototype.layout().
   287  cam.BlobItem.prototype.updateDom = function() {
   288  	this.link_.href = this.getLink_();
   289  
   290  	if (this.isImage()) {
   291  		this.addClassName('cam-blobitem-image');
   292  		this.thumb_.title = this.getTitle_();
   293  		this.label_.textContent = '';
   294  	} else {
   295  		this.removeClassName('cam-blobitem-image');
   296  		this.label_.textContent = this.getTitle_();
   297  	}
   298  };
   299  
   300  cam.BlobItem.prototype.handleClick_ = function(e) {
   301  	if (!this.checkmark_) {
   302  		return;
   303  	}
   304  
   305  	if (e.target == this.checkmark_ || this.checkmark_.contains(e.target)) {
   306  		this.setChecked(!this.isChecked());
   307  		e.preventDefault();
   308  	}
   309  };