move infobox template into react

Co-authored-by: Connor Turland <connorturland@gmail.com>
Co-authored-by: Robert Best <chessscholar@gmail.com>
This commit is contained in:
Connor Turland 2018-03-13 18:49:05 -04:00
parent f941c1c362
commit 4cab56d653
8 changed files with 127 additions and 188 deletions

View file

@ -88,7 +88,7 @@
<script src="/lib/jquery.typing-0.2.0.min.js"></script> <script src="/lib/jquery.typing-0.2.0.min.js"></script>
<script src="/lib/typeahead.bundle.js"></script> <script src="/lib/typeahead.bundle.js"></script>
<script type="text/javascript" src="/metamaps.bundle.js"></script> <script type="text/javascript" src="/metamaps.bundle.js"></script>
<!-- <!--
Metamaps.ServerData.mobileTitle = "{ yield(:mobile_title) }" Metamaps.ServerData.mobileTitle = "{ yield(:mobile_title) }"
{ if devise_error_messages? } { if devise_error_messages? }
Metamaps.ServerData.toast = "{ devise_error_messages! }" Metamaps.ServerData.toast = "{ devise_error_messages! }"
@ -125,53 +125,6 @@
</script> </script>
<div class="templates"> <div class="templates">
<script type="text/template" id="mapInfoBoxTemplate">
<div class="requestTitle">Click here to name this map</div>
<div class="mapInfoName" id="mapInfoName">{{{name}}}</div>
<div class="mapInfoStat">
<div class="infoStatIcon mapContributors hoverForTip">
<img id="mapContribs" class="{{contributors_class}}"
width="25" height="25" src="{{contributor_image}}" />
<span class="count">{{contributor_count}}</span>
<div class="tip">{{{contributor_list}}}</div>
</div>
<div class="infoStatIcon mapTopics">
{{topic_count}}
</div>
<div class="infoStatIcon mapSynapses">
{{synapse_count}}
</div>
<div class="infoStatIcon mapPermission {{permission}} hoverForTip">
{{{map_creator_tip}}}
</div>
<div class="clearfloat"></div>
</div>
<div class="mapInfoDesc" id="mapInfoDesc">
{{{desc}}}
</div>
<div class="mapInfoMeta">
<p class="mapCreatedAt"><span>Created by:</span> {{user_name}} on {{created_at}}</p>
<p class="mapEditedAt"><span>Last edited:</span> {{updated_at}}</p>
<div class="mapInfoButtonsWrapper">
<div class="mapInfoThumbnail">
<div class="thumbnail"></div>
<div class="tooltip">Update Thumbnail</div>
<span>Thumb</span>
</div>
<div class="mapInfoDelete">
<div class="deleteMap"></div>
<span>Delete</span>
</div>
<div class="mapInfoShare">
<div class="mapInfoShareIcon"></div>
<span>Share</span>
</div>
</div>
</div>
</script>
<script type="text/template" id="topicSearchTemplate"> <script type="text/template" id="topicSearchTemplate">
<div class="result{{rtype}}"> <div class="result{{rtype}}">
<div class="topicMetacode searchResIconWrapper"> <div class="topicMetacode searchResIconWrapper">
@ -304,4 +257,4 @@
</script> </script>
</div> </div>
</body> </body>
</html> </html>

View file

@ -29,7 +29,7 @@ const Cable = {
}, },
unsubscribeFromMap: () => { unsubscribeFromMap: () => {
let self = Cable let self = Cable
self.sub.unsubscribe() if (self.sub) self.sub.unsubscribe()
delete self.sub delete self.sub
}, },
synapseAdded: event => { synapseAdded: event => {

View file

@ -140,7 +140,6 @@ const ReactApp = {
endActiveMap: Map.end, endActiveMap: Map.end,
launchNewMap: Map.launch, launchNewMap: Map.launch,
toggleMapInfoBox: InfoBox.toggleBox, toggleMapInfoBox: InfoBox.toggleBox,
infoBoxHtml: InfoBox.html,
openImportLightbox: () => ImportDialog.show(), openImportLightbox: () => ImportDialog.show(),
openMetacodeSwitcher: () => self.openLightbox('metacodeSwitcher'), openMetacodeSwitcher: () => self.openLightbox('metacodeSwitcher'),
forkMap: Map.fork, forkMap: Map.fork,

View file

@ -11,46 +11,13 @@ import Util from '../Util'
const InfoBox = { const InfoBox = {
isOpen: false, isOpen: false,
selectingPermission: false, selectingPermission: false,
changePermissionText: "<div class='tooltips'>As the creator, you can change the permission of this map, and the permission of all the topics and synapses you have authority to change will change as well.</div>",
nameHTML: outdent`
<span class="best_in_place best_in_place_name"
id="best_in_place_map_{{id}}_name"
data-bip-url="/maps/{{id}}"
data-bip-object="map"
data-bip-attribute="name"
data-bip-type="textarea"
data-bip-activator="#mapInfoName"
data-bip-value="{{name}}"
>{{name}}</span>`,
descHTML: outdent`
<span class="best_in_place best_in_place_desc"
id="best_in_place_map_{{id}}_desc"
data-bip-url="/maps/{{id}}"
data-bip-object="map"
data-bip-attribute="desc"
data-bip-nil="Click to add description..."
data-bip-type="textarea"
data-bip-activator="#mapInfoDesc"
data-bip-value="{{desc}}"
>{{desc}}</span>`,
userImageUrl: '',
html: '',
init: function(serverData, updateThumbnail) { init: function(serverData, updateThumbnail) {
var self = InfoBox var self = InfoBox
self.updateThumbnail = updateThumbnail self.updateThumbnail = updateThumbnail
$('.mapInfoBox').click(function(event) {
event.stopPropagation()
})
$('body').click(self.close) $('body').click(self.close)
self.attachEventListeners()
self.generateBoxHTML = Hogan.compile($('#mapInfoBoxTemplate').html())
self.userImageUrl = serverData['user.png']
var querystring = window.location.search.replace(/^\?/, '') var querystring = window.location.search.replace(/^\?/, '')
if (querystring === 'new') { if (querystring === 'new') {
self.open() self.open()
@ -85,34 +52,7 @@ const InfoBox = {
}) })
}, },
load: function() { load: function() {
var self = InfoBox InfoBox.attachEventListeners()
var map = Active.Map
var obj = map.pick('permission', 'topic_count', 'synapse_count')
var isCreator = map.authorizePermissionChange(Active.Mapper)
var canEdit = map.authorizeToEdit(Active.Mapper)
var relevantPeople = map.get('permission') === 'commons' ? DataModel.Mappers : DataModel.Collaborators
var shareable = map.get('permission') !== 'private'
obj['name'] = canEdit ? Hogan.compile(self.nameHTML).render({id: map.id, name: map.get('name')}) : map.get('name')
obj['desc'] = canEdit ? Hogan.compile(self.descHTML).render({id: map.id, desc: map.get('desc')}) : map.get('desc')
obj['map_creator_tip'] = isCreator ? self.changePermissionText : ''
obj['contributor_count'] = relevantPeople.length
obj['contributors_class'] = relevantPeople.length > 1 ? 'multiple' : ''
obj['contributors_class'] += relevantPeople.length === 2 ? ' mTwo' : ''
obj['contributor_image'] = relevantPeople.length > 0 ? relevantPeople.models[0].get('image') : self.userImageUrl
obj['contributor_list'] = self.createContributorList()
obj['user_name'] = isCreator ? 'You' : map.get('user_name')
obj['created_at'] = map.get('created_at_clean')
obj['updated_at'] = map.get('updated_at_clean')
self.html = self.generateBoxHTML.render(obj)
ReactApp.render()
self.attachEventListeners()
}, },
attachEventListeners: function() { attachEventListeners: function() {
var self = InfoBox var self = InfoBox
@ -238,7 +178,6 @@ const InfoBox = {
DataModel.Collaborators.remove(DataModel.Collaborators.get(collaboratorId)) DataModel.Collaborators.remove(DataModel.Collaborators.get(collaboratorId))
var mapperIds = DataModel.Collaborators.models.map(function(mapper) { return mapper.id }) var mapperIds = DataModel.Collaborators.models.map(function(mapper) { return mapper.id })
$.post('/maps/' + Active.Map.id + '/access', { access: mapperIds }) $.post('/maps/' + Active.Map.id + '/access', { access: mapperIds })
self.updateNumbers()
}, },
addCollaborator: function(newCollaboratorId) { addCollaborator: function(newCollaboratorId) {
var self = InfoBox var self = InfoBox
@ -254,7 +193,6 @@ const InfoBox = {
$.post('/maps/' + Active.Map.id + '/access', { access: mapperIds }) $.post('/maps/' + Active.Map.id + '/access', { access: mapperIds })
var name = DataModel.Collaborators.get(newCollaboratorId).get('name') var name = DataModel.Collaborators.get(newCollaboratorId).get('name')
GlobalUI.notifyUser(name + ' will be notified') GlobalUI.notifyUser(name + ' will be notified')
self.updateNumbers()
} }
$.getJSON('/users/' + newCollaboratorId + '.json', callback) $.getJSON('/users/' + newCollaboratorId + '.json', callback)
@ -271,59 +209,6 @@ const InfoBox = {
$('.mapInfoDesc .best_in_place_desc').html(desc) $('.mapInfoDesc .best_in_place_desc').html(desc)
$('.mapInfoBox .mapPermission').removeClass('commons public private').addClass(perm) $('.mapInfoBox .mapPermission').removeClass('commons public private').addClass(perm)
}, },
createContributorList: function() {
var relevantPeople = Active.Map.get('permission') === 'commons' ? DataModel.Mappers : DataModel.Collaborators
var activeMapperIsCreator = Active.Mapper && Active.Mapper.id === Active.Map.get('user_id')
var string = ''
string += '<ul>'
relevantPeople.each(function(m) {
var isCreator = Active.Map.get('user_id') === m.get('id')
string += '<li><a href="/explore/mapper/' + m.get('id') + '">' + '<img class="rtUserImage" width="25" height="25" src="' + m.get('image') + '" />' + m.get('name')
if (isCreator) string += ' (creator)'
string += '</a>'
if (activeMapperIsCreator && !isCreator) string += '<span class="removeCollaborator" data-id="' + m.get('id') + '"></span>'
string += '</li>'
})
string += '</ul>'
if (activeMapperIsCreator) {
string += '<div class="collabSearchField"><span class="addCollab"></span><input class="collaboratorSearchField" placeholder="Add a collaborator"></input></div>'
}
return string
},
updateNumbers: function() {
if (!Active.Map) return
const self = InfoBox
var relevantPeople = Active.Map.get('permission') === 'commons' ? DataModel.Mappers : DataModel.Collaborators
let contributorsClass = ''
if (relevantPeople.length === 2) {
contributorsClass = 'multiple mTwo'
} else if (relevantPeople.length > 2) {
contributorsClass = 'multiple'
}
let contributorsImage = self.userImageUrl
if (relevantPeople.length > 0) {
// get the first contributor and use their image
contributorsImage = relevantPeople.models[0].get('image')
}
$('.mapContributors img').attr('src', contributorsImage).removeClass('multiple mTwo').addClass(contributorsClass)
$('.mapContributors span').text(relevantPeople.length)
$('.mapContributors .tip').html(self.createContributorList())
self.addTypeahead()
$('.mapContributors .tip').unbind().click(function(event) {
event.stopPropagation()
})
$('.mapTopics').text(DataModel.Topics.length)
$('.mapSynapses').text(DataModel.Synapses.length)
$('.mapEditedAt').html('<span>Last edited: </span>' + Util.nowDateFormatted())
},
onPermissionClick: function(event) { onPermissionClick: function(event) {
var self = InfoBox var self = InfoBox

View file

@ -94,7 +94,6 @@ const Map = {
Visualize.type = 'ForceDirected' Visualize.type = 'ForceDirected'
JIT.prepareVizData() JIT.prepareVizData()
Selected.reset() Selected.reset()
InfoBox.load()
Filter.reset() Filter.reset()
Filter.checkMetacodes() Filter.checkMetacodes()
Filter.checkSynapses() Filter.checkSynapses()
@ -104,13 +103,10 @@ const Map = {
document.title = Active.Map.get('name') + ' | Metamaps' document.title = Active.Map.get('name') + ' | Metamaps'
ReactApp.mobileTitle = Active.Map.get('name') ReactApp.mobileTitle = Active.Map.get('name')
ReactApp.render() ReactApp.render()
} InfoBox.load()
function isLoaded() {
if (InfoBox.generateBoxHTML) dataIsReadySetupMap()
else setTimeout(() => isLoaded(), 50)
} }
if (Active.Map && Active.Map.id === id) { if (Active.Map && Active.Map.id === id) {
isLoaded() dataIsReadySetupMap()
} }
else { else {
Loading.show() Loading.show()
@ -127,7 +123,7 @@ const Map = {
DataModel.Stars = data.stars DataModel.Stars = data.stars
DataModel.attachCollectionEvents() DataModel.attachCollectionEvents()
self.requests = data.requests self.requests = data.requests
isLoaded() dataIsReadySetupMap()
}, },
error: function(res) { error: function(res) {
// forbidden // forbidden

View file

@ -11,17 +11,16 @@ class InfoAndHelp extends Component {
onHelpClick: PropTypes.func, onHelpClick: PropTypes.func,
onMapStar: PropTypes.func, onMapStar: PropTypes.func,
onMapUnstar: PropTypes.func, onMapUnstar: PropTypes.func,
onInfoClick: PropTypes.func, onInfoClick: PropTypes.func
infoBoxhtml: PropTypes.string
} }
render () { render () {
const { mapIsStarred, map, currentUser, onInfoClick, infoBoxHtml, onMapStar, onMapUnstar, onHelpClick } = this.props const { mapIsStarred, map, currentUser, onInfoClick, onMapStar, onMapUnstar, onHelpClick } = this.props
const starclassName = mapIsStarred ? 'starred' : '' const starclassName = mapIsStarred ? 'starred' : ''
const tooltip = mapIsStarred ? 'Unstar' : 'Star' const tooltip = mapIsStarred ? 'Unstar' : 'Star'
const onStarClick = mapIsStarred ? onMapUnstar : onMapStar const onStarClick = mapIsStarred ? onMapUnstar : onMapStar
return <div className="infoAndHelp"> return <div className="infoAndHelp">
{map && <MapInfoBox map={map} currentUser={currentUser} infoBoxHtml={infoBoxHtml} />} {map && <MapInfoBox map={map} currentUser={currentUser} />}
{map && currentUser && <div className={`starMap infoElement mapElement ${starclassName}`} onClick={onStarClick}> {map && currentUser && <div className={`starMap infoElement mapElement ${starclassName}`} onClick={onStarClick}>
<div className="tooltipsAbove">{tooltip}</div> <div className="tooltipsAbove">{tooltip}</div>
</div>} </div>}

View file

@ -4,20 +4,129 @@ import PropTypes from 'prop-types'
class MapInfoBox extends Component { class MapInfoBox extends Component {
static propTypes = { static propTypes = {
currentUser: PropTypes.object, currentUser: PropTypes.object,
map: PropTypes.object, map: PropTypes.object
infoBoxHtml: PropTypes.string
} }
render () { createContributorList = () => {
const { currentUser, map, infoBoxHtml } = this.props const { currentUser, map } = this.props
var relevantPeople = [] // TODO: map.get('permission') === 'commons' ? DataModel.Mappers : DataModel.Collaborators
var activeMapperIsCreator = currentUser && currentUser.id === map.get('user_id')
return (
<div>
<ul>
{relevantPeople.map((m) => {
var isCreator = map.get('user_id') === m.get('id')
return (
<li>
<a href={`/explore/mapper/${m.get('id')}`}>
<img className="rtUserImage" width="25" height="25" src={m.get('image')} />
{m.get('name')}
{isCreator && ' (creator)'}
</a>
{activeMapperIsCreator && !isCreator && <span className="removeCollaborator" data-id={m.get('id')}></span>}
</li>
)
})}
</ul>
{activeMapperIsCreator && <div className="collabSearchField">
<span className="addCollab"></span>
<input className="collaboratorSearchField" placeholder="Add a collaborator" />
</div>}
</div>
)
}
render = () => {
const { currentUser, map } = this.props
if (!map) return null if (!map) return null
const html = {__html: infoBoxHtml} const permission = map.get('permission')
const topic_count = map.get('topic_count')
const synapse_count = map.get('synapse_count')
const isCreator = map.authorizePermissionChange(currentUser) const isCreator = map.authorizePermissionChange(currentUser)
const canEdit = map.authorizeToEdit(currentUser) const canEdit = map.authorizeToEdit(currentUser)
const relevantPeople = [] // TODO: permission === 'commons' ? DataModel.Mappers : DataModel.Collaborators
const shareable = permission !== 'private'
const contributor_count = relevantPeople.length
let contributors_class = relevantPeople.length > 1 ? 'multiple' : ''
contributors_class += relevantPeople.length === 2 ? ' mTwo' : ''
const contributor_image = relevantPeople.length > 0 ? relevantPeople.models[0].get('image') : '/images/user.png'
const user_name = isCreator ? 'You' : map.get('user_name')
const created_at = map.get('created_at_clean')
const updated_at = map.get('updated_at_clean')
let classes = 'mapInfoBox mapElement mapElementHidden permission ' let classes = 'mapInfoBox mapElement mapElementHidden permission '
classes += isCreator ? 'yourMap' : '' classes += isCreator ? 'yourMap' : ''
classes += canEdit ? ' canEdit' : '' classes += canEdit ? ' canEdit' : ''
return <div className={classes} dangerouslySetInnerHTML={html}></div>
return <div className={classes}>
<div className="requestTitle">Click here to name this map</div>
<div className="mapInfoName" id="mapInfoName">
{canEdit ? <span className="best_in_place best_in_place_name"
id={`best_in_place_map_${map.id}_name`}
data-bip-url={`/maps/${map.id}`}
data-bip-object="map"
data-bip-attribute="name"
data-bip-type="textarea"
data-bip-activator="#mapInfoName"
data-bip-value={map.get('name')}
>{map.get('name')}</span> : map.get('name')}
</div>
<div className="mapInfoStat">
<div className="infoStatIcon mapContributors hoverForTip">
<img id="mapContribs" className={contributors_class}
width="25" height="25" src={contributor_image} />
<span className="count">{contributor_count}</span>
<div className="tip">{this.createContributorList()}</div>
</div>
<div className="infoStatIcon mapTopics">
{topic_count}
</div>
<div className="infoStatIcon mapSynapses">
{synapse_count}
</div>
<div className={`infoStatIcon mapPermission ${permission} hoverForTip`}>
{isCreator && <div className='tooltips'>
As the creator, you can change the permission of this map, and the permission of all the topics and synapses you have authority to change will change as well.
</div>}
</div>
<div className="clearfloat"></div>
</div>
<div className="mapInfoDesc" id="mapInfoDesc">
{canEdit ? <span className="best_in_place best_in_place_desc"
id={`best_in_place_map_${map.id}_desc`}
data-bip-url={`/maps/${map.id}`}
data-bip-object="map"
data-bip-attribute="desc"
data-bip-nil="Click to add description..."
data-bip-type="textarea"
data-bip-activator="#mapInfoDesc"
data-bip-value={map.get('desc')}
>{map.get('desc')}</span> : map.get('desc')}
</div>
<div className="mapInfoMeta">
<p className="mapCreatedAt"><span>Created by:</span> {user_name} on {created_at}</p>
<p className="mapEditedAt"><span>Last edited:</span> {updated_at}</p>
<div className="mapInfoButtonsWrapper">
<div className="mapInfoThumbnail">
<div className="thumbnail"></div>
<div className="tooltip">Update Thumbnail</div>
<span>Thumb</span>
</div>
<div className="mapInfoDelete">
<div className="deleteMap"></div>
<span>Delete</span>
</div>
<div className="mapInfoShare">
<div className="mapInfoShareIcon"></div>
<span>Share</span>
</div>
</div>
</div>
</div>
} }
} }

View file

@ -32,7 +32,6 @@ export default class MapView extends Component {
filterAllMappers: PropTypes.func, filterAllMappers: PropTypes.func,
filterAllSynapses: PropTypes.func, filterAllSynapses: PropTypes.func,
toggleMapInfoBox: PropTypes.func, toggleMapInfoBox: PropTypes.func,
infoBoxHtml: PropTypes.string,
currentUser: PropTypes.object, currentUser: PropTypes.object,
endActiveMap: PropTypes.func, endActiveMap: PropTypes.func,
launchNewMap: PropTypes.func, launchNewMap: PropTypes.func,
@ -78,7 +77,7 @@ export default class MapView extends Component {
render = () => { render = () => {
const { mobile, map, currentUser, onOpen, onClose, const { mobile, map, currentUser, onOpen, onClose,
toggleMapInfoBox, infoBoxHtml, allForFiltering, visibleForFiltering, toggleMapInfoBox, allForFiltering, visibleForFiltering,
toggleMetacode, toggleMapper, toggleSynapse, filterAllMetacodes, toggleMetacode, toggleMapper, toggleSynapse, filterAllMetacodes,
filterAllMappers, filterAllSynapses, filterData, filterAllMappers, filterAllSynapses, filterData,
openImportLightbox, forkMap, openHelpLightbox, openImportLightbox, forkMap, openHelpLightbox,
@ -129,8 +128,7 @@ export default class MapView extends Component {
onInfoClick={toggleMapInfoBox} onInfoClick={toggleMapInfoBox}
onMapStar={onMapStar} onMapStar={onMapStar}
onMapUnstar={onMapUnstar} onMapUnstar={onMapUnstar}
onHelpClick={openHelpLightbox} onHelpClick={openHelpLightbox} />
infoBoxHtml={infoBoxHtml} />
</div> </div>
} }
} }