github.com/olivere/camlistore@v0.0.0-20140121221811-1b7ac2da0199/server/camlistored/ui/blob_item_react.js (about) 1 /* 2 Copyright 2014 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.BlobItemReact'); 18 goog.provide('cam.BlobItemReactData'); 19 20 goog.require('goog.array'); 21 goog.require('goog.object'); 22 goog.require('goog.string'); 23 goog.require('goog.math.Coordinate'); 24 goog.require('goog.math.Size'); 25 26 goog.require('cam.imageUtil'); 27 goog.require('cam.math'); 28 goog.require('cam.PyramidThrobber'); 29 30 // Extracts the bits of metadata that BlobItemReact needs. 31 cam.BlobItemReactData = function(blobref, metabag) { 32 this.blobref = blobref; 33 this.metabag = metabag; 34 this.m = metabag[blobref]; 35 this.rm = this.constructor.getResolvedMeta_(this.m, metabag); 36 this.im = this.constructor.getImageMeta_(this.rm); 37 this.isStaticCollection = this.constructor.isStaticCollection_(this.rm); 38 this.isDynamicCollection = this.constructor.isDynamicCollection_(this.m); 39 this.thumbType = this.constructor.getThumbType_(this); 40 this.aspect = this.constructor.getAspect_(this.im, this.thumbType); 41 this.title = this.constructor.getTitle_(this.m, this.rm); 42 }; 43 44 cam.BlobItemReactData.getTitle_ = function(m, rm) { 45 if (m) { 46 if (m.camliType == 'permanode' && m.permanode && m.permanode.attr && m.permanode.attr.title) { 47 return goog.isArray(m.permanode.attr.title) ? m.permanode.attr.title[0] : m.permanode.attr.title; 48 } 49 } 50 if (rm) { 51 if (rm.camliType == 'file' && rm.file) { 52 return rm.file.fileName; 53 } 54 if (rm.camliType == 'directory' && rm.dir) { 55 return rm.dir.fileName; 56 } 57 if (rm.camliType == 'permanode' && rm.permanode && rm.permanode.attr && rm.permanode.attr.title) { 58 return goog.isArray(m.permanode.attr.title) ? m.permanode.attr.title[0] : m.permanode.attr.title; 59 } 60 } 61 return 'Unknown title'; 62 }; 63 64 cam.BlobItemReactData.getAspect_ = function(im, tt) { 65 if (tt == 'image') { 66 return im.width / im.height; 67 } 68 69 // These are the dimensions of the static icons we use for each of these cases. 70 if (tt == 'file') { 71 return 260 / 300; 72 } else if (tt == 'folder') { 73 return 300 / 300; 74 } else if (tt == 'node') { 75 return 100 / 100; 76 } 77 78 throw new Error('Unexpected thumb type: ' + tt); 79 }; 80 81 cam.BlobItemReactData.isStaticCollection_ = function(rm) { 82 return rm.camliType == 'directory' || rm.camliType == 'static-set'; 83 }; 84 85 cam.BlobItemReactData.isDynamicCollection_ = function(m) { 86 if (m.camliType == 'permanode') { 87 if (goog.object.findKey(m.permanode.attr, function(v, k) { return k == 'camliMember' || goog.string.startsWith(k, 'camliPath:') })) { 88 return true; 89 } 90 } 91 return false; 92 }; 93 94 cam.BlobItemReactData.getThumbType_ = function(data) { 95 if (data.im) { 96 return 'image'; 97 } 98 99 if (data.rm.camliType == 'file') { 100 return 'file'; 101 } 102 103 if (data.isStaticCollection || data.isDynamicCollection) { 104 return 'folder'; 105 } 106 107 if (data.m.camliType == 'permanode') { 108 return 'node'; 109 } 110 111 return 'file'; 112 }; 113 114 cam.BlobItemReactData.getImageMeta_ = function(rm) { 115 if (rm && rm.image) { 116 return rm.image; 117 } else { 118 return null; 119 } 120 }; 121 122 cam.BlobItemReactData.getResolvedMeta_ = function(m, metabag) { 123 if (m.camliType == 'permanode' && m.permanode && m.permanode.attr && m.permanode.attr.camliContent && m.permanode.attr.camliContent.length == 1) { 124 return metabag[m.permanode.attr.camliContent[0]]; 125 } else { 126 return m; 127 } 128 }; 129 130 131 cam.BlobItemReact = React.createClass({ 132 displayName: 'BlobItemReact', 133 134 TITLE_HEIGHT: 22, 135 136 propTypes: { 137 blobref: React.PropTypes.string.isRequired, 138 checked: React.PropTypes.bool.isRequired, 139 href: React.PropTypes.string.isRequired, 140 data: React.PropTypes.instanceOf(cam.BlobItemReactData).isRequired, 141 onCheckClick: React.PropTypes.func.isRequired, // (string,event)->void 142 position: React.PropTypes.instanceOf(goog.math.Coordinate).isRequired, 143 size: React.PropTypes.instanceOf(goog.math.Size).isRequired, 144 thumbnailVersion: React.PropTypes.number.isRequired, 145 }, 146 147 getInitialState: function() { 148 return { 149 loaded: false, 150 hovered: false, 151 }; 152 }, 153 154 componentWillMount: function() { 155 this.currentIntrinsicThumbHeight_ = 0; 156 }, 157 158 componentDidMount: function() { 159 this.refs.thumb.getDOMNode().addEventListener('load', this.onThumbLoad_); 160 this.refs.thumb.getDOMNode().addEventListener('error', this.onThumbLoad_); 161 }, 162 163 componentDidUpdate: function(prevProps, prevState) { 164 if (prevProps.blobref != this.props.blobref) { 165 this.currentIntrinsicThumbHeight_ = 0; 166 this.setState({loaded: false}); 167 } 168 }, 169 170 render: function() { 171 var thumbClipSize = this.getThumbClipSize_(); 172 173 return React.DOM.div({ 174 className: this.getRootClassName_(), 175 style: this.getRootStyle_(), 176 onMouseEnter: this.handleMouseEnter_, 177 onMouseLeave: this.handleMouseLeave_ 178 }, 179 React.DOM.div({className:'checkmark', onClick:this.handleCheckClick_}), 180 React.DOM.a({href:this.props.href}, 181 React.DOM.div({className:this.getThumbClipClassName_(), style:thumbClipSize}, 182 this.getThrobber_(thumbClipSize), 183 this.getThumb_(thumbClipSize) 184 ), 185 this.getLabel_() 186 ) 187 ); 188 }, 189 190 componentWillUnmount: function() { 191 this.refs.thumb.getDOMNode().removeEventListener('load', this.onThumbLoad_); 192 }, 193 194 onThumbLoad_: function() { 195 this.setState({loaded:true}); 196 }, 197 198 getRootClassName_: function() { 199 return React.addons.classSet({ 200 'cam-blobitem': true, 201 'cam-blobitem-image': Boolean(this.props.data.im), 202 'goog-control-hover': this.state.hovered, 203 'goog-control-checked': this.props.checked, 204 }); 205 }, 206 207 getRootStyle_: function() { 208 return { 209 left: this.props.position.x, 210 top: this.props.position.y, 211 }; 212 }, 213 214 handleMouseEnter_: function() { 215 this.setState({hovered:true}); 216 }, 217 218 handleMouseLeave_: function() { 219 this.setState({hovered:false}); 220 }, 221 222 handleCheckClick_: function(e) { 223 this.props.onCheckClick(this.props.blobref, e); 224 }, 225 226 getThumbClipClassName_: function() { 227 return React.addons.classSet({ 228 'cam-blobitem-thumbclip': true, 229 'cam-blobitem-loading': !this.state.loaded, 230 }); 231 }, 232 233 getThrobber_: function(thumbClipSize) { 234 if (this.state.loaded) { 235 return null; 236 } 237 return cam.PyramidThrobber({pos:cam.math.center(cam.PyramidThrobber.SIZE, thumbClipSize)}); 238 }, 239 240 getThumb_: function(thumbClipSize) { 241 var thumbSize = this.getThumbSize_(thumbClipSize); 242 var pos = cam.math.center(thumbSize, thumbClipSize); 243 return React.DOM.img({ 244 className: 'cam-blobitem-thumb', 245 ref: 'thumb', 246 src: this.getThumbSrc_(thumbSize), 247 style: {left:pos.x, top:pos.y, visibility:(this.state.loaded ? 'visible' : 'hidden')}, 248 title: this.props.data.title, 249 width: thumbSize.width, 250 height: thumbSize.height, 251 }) 252 }, 253 254 getThumbSrc_: function(thumbSize) { 255 var baseName = ''; 256 if (this.props.data.thumbType == 'image') { 257 var m = this.props.data.m; 258 var rm = this.props.data.rm; 259 baseName = goog.string.subs('thumbnail/%s/%s.jpg', m.permanode.attr.camliContent, rm.file && rm.file.fileName ? rm.file.fileName : m.permanode.attr.camliContent); 260 } else { 261 baseName = this.props.data.thumbType + '.png'; 262 } 263 264 this.currentIntrinsicThumbHeight_ = cam.imageUtil.getSizeToRequest(thumbSize.height, this.currentIntrinsicThumbHeight_); 265 return goog.string.subs('%s?mh=%s&tv=%s', baseName, this.currentIntrinsicThumbHeight_, this.props.thumbnailVersion); 266 }, 267 268 getLabel_: function() { 269 // We don't show the label at all for images. 270 if (this.props.data.im) { 271 return null; 272 } 273 return React.DOM.span({className:'cam-blobitem-thumbtitle', style:{width:this.props.size.width}}, 274 this.props.data.title); 275 }, 276 277 getThumbSize_: function(thumbClipSize) { 278 return cam.math.scaleToFit(new goog.math.Size(this.props.data.aspect, 1), thumbClipSize, Boolean(this.props.data.im)); 279 }, 280 281 getThumbClipSize_: function() { 282 var h = this.props.size.height; 283 if (!this.props.data.im) { 284 h -= this.TITLE_HEIGHT; 285 } 286 return new goog.math.Size(this.props.size.width, h); 287 }, 288 });