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 };