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