2013-12-30 05:14:35 +00:00
/ *
Copyright 2013 Google Inc .
Licensed under the Apache License , Version 2.0 ( the "License" ) ;
you may not use this file except in compliance with the License .
You may obtain a copy of the License at
2014-01-07 04:52:30 +00:00
http : //www.apache.org/licenses/LICENSE-2.0
2013-12-30 05:14:35 +00:00
Unless required by applicable law or agreed to in writing , software
distributed under the License is distributed on an "AS IS" BASIS ,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND , either express or implied .
See the License for the specific language governing permissions and
limitations under the License .
* /
2014-01-08 06:07:26 +00:00
goog . provide ( 'cam.DetailView' ) ;
2013-12-30 05:14:35 +00:00
2014-01-02 07:01:05 +00:00
goog . require ( 'goog.array' ) ;
2014-01-02 05:56:03 +00:00
goog . require ( 'goog.events.EventHandler' ) ;
2013-12-30 05:14:35 +00:00
goog . require ( 'goog.math.Size' ) ;
2013-12-31 23:25:19 +00:00
goog . require ( 'goog.object' ) ;
2014-01-02 05:56:03 +00:00
goog . require ( 'goog.string' ) ;
2013-12-30 05:14:35 +00:00
2014-01-08 06:07:26 +00:00
goog . require ( 'cam.AnimationLoop' ) ;
2014-03-26 04:41:31 +00:00
goog . require ( 'cam.ImageDetail' ) ;
2014-01-10 09:39:14 +00:00
goog . require ( 'cam.Navigator' ) ;
2014-01-19 08:54:25 +00:00
goog . require ( 'cam.reactUtil' ) ;
2014-01-08 06:07:26 +00:00
goog . require ( 'cam.SearchSession' ) ;
goog . require ( 'cam.SpritedAnimation' ) ;
2014-04-01 02:01:44 +00:00
// Top-level control for the detail view. Handles loading data specified in URL and left/right navigation.
// The details of the actual rendering are left up to child controls which are chosen based on the type of data loaded. However, currently there is only one type of child control: cam.ImageDetail.
2014-01-08 05:28:01 +00:00
cam . DetailView = React . createClass ( {
2014-01-16 08:26:15 +00:00
displayName : 'DetailView' ,
2014-01-10 09:39:14 +00:00
propTypes : {
Aspects: A new idea for the detail page.
http://imgur.com/Re14XKc,6fYHJqp,39nnQiT#0
Camlistore is built around the idea that every object is a blob,
but blobs can also self-describe themselves as more than a blob.
For example, some blobs are also files, or images, or sets, or
permanodes, or movies, or foursquare checkins. Etc.
Here is an idea for the detail page that reflects that underlying
reality of Camlistore.
Each blob has a single canonical URL in the web UI for its detail
page. Currently this is /?p=<...>, but should ideally be just
/<blobref>.
Within the web UI, many "aspects" register interest in providing
views on blobs. Each time the user navigates to a detail page,
each aspects gets to say whether it can provide a useful view on
the blob.
Aspects are currently rendered (crappily) as tabs along the bottom
of the UI. I'm not sure how they should actually be rendered, this
is temporary.
This patch includes the following aspects:
- image (the old image detail)
- permanode (the old old permanode page)
- blob (the old old blob page)
Change-Id: Idb3cdbb203799a5d9c113d1b37b67a2724040085
2014-04-14 16:20:22 +00:00
aspects : cam . reactUtil . mapOf ( React . PropTypes . shape ( {
getTitle : React . PropTypes . func . isRequired ,
createContent : React . PropTypes . func . isRequired ,
} ) ) . isRequired ,
2014-01-10 09:39:14 +00:00
blobref : React . PropTypes . string . isRequired ,
getDetailURL : React . PropTypes . func . isRequired ,
Aspects: A new idea for the detail page.
http://imgur.com/Re14XKc,6fYHJqp,39nnQiT#0
Camlistore is built around the idea that every object is a blob,
but blobs can also self-describe themselves as more than a blob.
For example, some blobs are also files, or images, or sets, or
permanodes, or movies, or foursquare checkins. Etc.
Here is an idea for the detail page that reflects that underlying
reality of Camlistore.
Each blob has a single canonical URL in the web UI for its detail
page. Currently this is /?p=<...>, but should ideally be just
/<blobref>.
Within the web UI, many "aspects" register interest in providing
views on blobs. Each time the user navigates to a detail page,
each aspects gets to say whether it can provide a useful view on
the blob.
Aspects are currently rendered (crappily) as tabs along the bottom
of the UI. I'm not sure how they should actually be rendered, this
is temporary.
This patch includes the following aspects:
- image (the old image detail)
- permanode (the old old permanode page)
- blob (the old old blob page)
Change-Id: Idb3cdbb203799a5d9c113d1b37b67a2724040085
2014-04-14 16:20:22 +00:00
history : React . PropTypes . shape ( { go : React . PropTypes . func . isRequired } ) . isRequired ,
2014-01-19 08:54:25 +00:00
height : React . PropTypes . number . isRequired ,
2014-01-10 09:39:14 +00:00
keyEventTarget : React . PropTypes . object . isRequired , // An event target we will addEventListener() on to receive key events.
2014-01-19 08:54:25 +00:00
navigator : React . PropTypes . instanceOf ( cam . Navigator ) . isRequired ,
searchSession : React . PropTypes . instanceOf ( cam . SearchSession ) . isRequired ,
searchURL : React . PropTypes . instanceOf ( goog . Uri ) . isRequired ,
2014-01-10 09:39:14 +00:00
width : React . PropTypes . number . isRequired ,
} ,
2013-12-31 23:25:19 +00:00
getInitialState : function ( ) {
return {
2014-03-26 04:41:31 +00:00
lastNavigateWasBackward : false ,
Aspects: A new idea for the detail page.
http://imgur.com/Re14XKc,6fYHJqp,39nnQiT#0
Camlistore is built around the idea that every object is a blob,
but blobs can also self-describe themselves as more than a blob.
For example, some blobs are also files, or images, or sets, or
permanodes, or movies, or foursquare checkins. Etc.
Here is an idea for the detail page that reflects that underlying
reality of Camlistore.
Each blob has a single canonical URL in the web UI for its detail
page. Currently this is /?p=<...>, but should ideally be just
/<blobref>.
Within the web UI, many "aspects" register interest in providing
views on blobs. Each time the user navigates to a detail page,
each aspects gets to say whether it can provide a useful view on
the blob.
Aspects are currently rendered (crappily) as tabs along the bottom
of the UI. I'm not sure how they should actually be rendered, this
is temporary.
This patch includes the following aspects:
- image (the old image detail)
- permanode (the old old permanode page)
- blob (the old old blob page)
Change-Id: Idb3cdbb203799a5d9c113d1b37b67a2724040085
2014-04-14 16:20:22 +00:00
selectedAspect : '' ,
2013-12-31 23:25:19 +00:00
} ;
} ,
2013-12-30 05:14:35 +00:00
2014-03-26 04:41:31 +00:00
componentWillMount : function ( ) {
this . pendingNavigation _ = 0 ;
this . navCount _ = 1 ;
this . eh _ = new goog . events . EventHandler ( this ) ;
2014-01-02 07:01:05 +00:00
} ,
2013-12-30 05:14:35 +00:00
componentDidMount : function ( root ) {
2014-01-08 05:28:01 +00:00
this . eh _ . listen ( this . props . searchSession , cam . SearchSession . SEARCH _SESSION _CHANGED , this . searchUpdated _ ) ;
2014-01-10 09:39:14 +00:00
this . eh _ . listen ( this . props . keyEventTarget , 'keyup' , this . handleKeyUp _ ) ;
2014-01-02 05:56:03 +00:00
this . searchUpdated _ ( ) ;
2013-12-30 05:14:35 +00:00
} ,
render : function ( ) {
Aspects: A new idea for the detail page.
http://imgur.com/Re14XKc,6fYHJqp,39nnQiT#0
Camlistore is built around the idea that every object is a blob,
but blobs can also self-describe themselves as more than a blob.
For example, some blobs are also files, or images, or sets, or
permanodes, or movies, or foursquare checkins. Etc.
Here is an idea for the detail page that reflects that underlying
reality of Camlistore.
Each blob has a single canonical URL in the web UI for its detail
page. Currently this is /?p=<...>, but should ideally be just
/<blobref>.
Within the web UI, many "aspects" register interest in providing
views on blobs. Each time the user navigates to a detail page,
each aspects gets to say whether it can provide a useful view on
the blob.
Aspects are currently rendered (crappily) as tabs along the bottom
of the UI. I'm not sure how they should actually be rendered, this
is temporary.
This patch includes the following aspects:
- image (the old image detail)
- permanode (the old old permanode page)
- blob (the old old blob page)
Change-Id: Idb3cdbb203799a5d9c113d1b37b67a2724040085
2014-04-14 16:20:22 +00:00
var activeAspects = null ;
var selectedAspect = null ;
if ( this . dataIsLoaded _ ( ) ) {
activeAspects = goog . object . filter (
goog . object . map ( this . props . aspects , function ( f ) {
return f ( this . props . blobref , this . props . searchSession ) ;
} , this ) ,
function ( a ) {
return a != null ;
}
) ;
selectedAspect = activeAspects [ this . state . selectedAspect ] || goog . object . getAnyValue ( activeAspects ) ;
2014-03-26 04:41:31 +00:00
}
Aspects: A new idea for the detail page.
http://imgur.com/Re14XKc,6fYHJqp,39nnQiT#0
Camlistore is built around the idea that every object is a blob,
but blobs can also self-describe themselves as more than a blob.
For example, some blobs are also files, or images, or sets, or
permanodes, or movies, or foursquare checkins. Etc.
Here is an idea for the detail page that reflects that underlying
reality of Camlistore.
Each blob has a single canonical URL in the web UI for its detail
page. Currently this is /?p=<...>, but should ideally be just
/<blobref>.
Within the web UI, many "aspects" register interest in providing
views on blobs. Each time the user navigates to a detail page,
each aspects gets to say whether it can provide a useful view on
the blob.
Aspects are currently rendered (crappily) as tabs along the bottom
of the UI. I'm not sure how they should actually be rendered, this
is temporary.
This patch includes the following aspects:
- image (the old image detail)
- permanode (the old old permanode page)
- blob (the old old blob page)
Change-Id: Idb3cdbb203799a5d9c113d1b37b67a2724040085
2014-04-14 16:20:22 +00:00
return React . DOM . div ( { className : 'cam-detail' , style : { height : this . props . height } } ,
this . getAspectNav _ ( activeAspects ) ,
// TODO(aa): Actually pick this based on the current URL
this . getAspectView _ ( selectedAspect )
) ;
} ,
getAspectNav _ : function ( aspects ) {
if ( ! aspects ) {
return null ;
}
var items = goog . object . getValues ( goog . object . map ( aspects , function ( aspect , name ) {
// TODO(aa): URLs involving k I guess?
return React . DOM . a ( { href : '#' , onClick : this . handleAspectClick _ . bind ( this , name ) } , aspect . getTitle ( ) ) ;
} , this ) ) ;
items . push ( React . DOM . a ( { href : this . props . searchURL . toString ( ) } , 'Back to search' ) ) ;
return React . DOM . div ( { className : 'cam-detail-aspect-nav' } , items ) ;
} ,
getAspectView _ : function ( aspect ) {
if ( aspect ) {
// TODO(aa): Why doesn't parent pass us |Size| instead of width/height?
return aspect . createContent ( new goog . math . Size ( this . props . width , this . props . height - 25 ) , this . state . lastNavigateWasBackward ) ;
} else {
return null ;
}
2014-01-02 05:56:03 +00:00
} ,
componentWillUnmount : function ( ) {
2014-01-10 09:39:14 +00:00
this . eh _ . dispose ( ) ;
} ,
2014-07-01 21:27:59 +00:00
handleAspectClick _ : function ( name , e ) {
// Mathieu requests that middle and right-click do nothing until we can make real URLs work.
if ( e . button == 0 ) {
this . setState ( {
selectedAspect : name ,
} ) ;
}
return false ;
Aspects: A new idea for the detail page.
http://imgur.com/Re14XKc,6fYHJqp,39nnQiT#0
Camlistore is built around the idea that every object is a blob,
but blobs can also self-describe themselves as more than a blob.
For example, some blobs are also files, or images, or sets, or
permanodes, or movies, or foursquare checkins. Etc.
Here is an idea for the detail page that reflects that underlying
reality of Camlistore.
Each blob has a single canonical URL in the web UI for its detail
page. Currently this is /?p=<...>, but should ideally be just
/<blobref>.
Within the web UI, many "aspects" register interest in providing
views on blobs. Each time the user navigates to a detail page,
each aspects gets to say whether it can provide a useful view on
the blob.
Aspects are currently rendered (crappily) as tabs along the bottom
of the UI. I'm not sure how they should actually be rendered, this
is temporary.
This patch includes the following aspects:
- image (the old image detail)
- permanode (the old old permanode page)
- blob (the old old blob page)
Change-Id: Idb3cdbb203799a5d9c113d1b37b67a2724040085
2014-04-14 16:20:22 +00:00
} ,
2014-01-10 09:39:14 +00:00
handleKeyUp _ : function ( e ) {
if ( e . keyCode == goog . events . KeyCodes . LEFT ) {
this . navigate _ ( - 1 ) ;
} else if ( e . keyCode == goog . events . KeyCodes . RIGHT ) {
this . navigate _ ( 1 ) ;
} else if ( e . keyCode == goog . events . KeyCodes . ESC ) {
2014-01-19 08:54:25 +00:00
this . handleEscape _ ( e ) ;
2014-01-10 09:39:14 +00:00
}
2014-01-02 05:56:03 +00:00
} ,
2014-01-10 09:39:14 +00:00
navigate _ : function ( offset ) {
2014-01-02 07:01:05 +00:00
this . pendingNavigation _ = offset ;
2014-01-19 08:54:25 +00:00
++ this . navCount _ ;
2014-03-26 04:41:31 +00:00
this . setState ( { lastNavigateWasBackward : offset < 0 } ) ;
2014-01-02 07:01:05 +00:00
this . handlePendingNavigation _ ( ) ;
} ,
2014-01-19 08:54:25 +00:00
handleEscape _ : function ( e ) {
e . preventDefault ( ) ;
e . stopPropagation ( ) ;
history . go ( - this . navCount _ ) ;
} ,
2014-01-02 07:01:05 +00:00
handlePendingNavigation _ : function ( ) {
2014-01-08 11:03:11 +00:00
if ( ! this . pendingNavigation _ ) {
2014-01-02 07:01:05 +00:00
return ;
}
var results = this . props . searchSession . getCurrentResults ( ) ;
var index = goog . array . findIndex ( results . blobs , function ( elm ) {
return elm . blob == this . props . blobref ;
} . bind ( this ) ) ;
if ( index == - 1 ) {
this . props . searchSession . loadMoreResults ( ) ;
return ;
}
index += this . pendingNavigation _ ;
if ( index < 0 ) {
this . pendingNavigation _ = 0 ;
console . log ( 'Cannot navigate past beginning of search result.' ) ;
return ;
}
if ( index >= results . blobs . length ) {
if ( this . props . searchSession . isComplete ( ) ) {
this . pendingNavigation _ = 0 ;
console . log ( 'Cannot navigate past end of search result.' ) ;
} else {
this . props . searchSession . loadMoreResults ( ) ;
}
return ;
}
2014-01-10 09:39:14 +00:00
this . props . navigator . navigate ( this . props . getDetailURL ( results . blobs [ index ] . blob ) ) ;
2014-01-02 07:01:05 +00:00
} ,
2014-01-02 05:56:03 +00:00
searchUpdated _ : function ( ) {
2014-01-02 07:01:05 +00:00
this . handlePendingNavigation _ ( ) ;
2014-03-26 04:41:31 +00:00
if ( this . dataIsLoaded _ ( ) ) {
2014-01-02 07:01:05 +00:00
this . forceUpdate ( ) ;
2014-01-02 05:56:03 +00:00
return ;
}
if ( this . props . searchSession . isComplete ( ) ) {
// TODO(aa): 404 UI.
var error = goog . string . subs ( 'Could not find blobref %s in search session.' , this . props . blobref ) ;
alert ( error ) ;
throw new Error ( error ) ;
}
// TODO(aa): This can be inefficient in the case of a fresh page load if we have to load lots of pages to find the blobref.
// Our search protocol needs to be updated to handle the case of paging ahead to a particular item.
this . props . searchSession . loadMoreResults ( ) ;
2013-12-30 05:14:35 +00:00
} ,
2014-03-26 04:41:31 +00:00
dataIsLoaded _ : function ( ) {
2014-04-01 02:01:44 +00:00
return Boolean ( this . props . searchSession . getMeta ( this . props . blobref ) ) ;
2013-12-30 05:14:35 +00:00
} ,
} ) ;