metamaps--metamaps/frontend/src/components/Maps/MapCard.js
Connor Turland 7ee96bf6c6 Into master: two finger pan/zoom, map and topic follows (for internal testing) on the UI, map activity emails (#1084)
* fix topic spec

* fix synapse/mapping spec

* brakeman csrf warning suppressed :|

* follows for maps in the ui for internal testing only still (#1072)

* follows for maps in the ui for testers

* require user for these actions

* match how map follow works

* include ability to unfollow from email

* fixup templates

* add unfollow_from_email to the policies

* Update _cheatsheet.html.erb

Clean up text, clarify, and bring in line with current functionality

* topicsRegex and synapsesRegex should allow commas (#1073)

* even better import csv regexes

* prevent double prompt on file drop import

* topic card in react (#1031)

* its coming along

* links bar

* scssify a bunch

* metacode image working a bit better

* metacode selector in react topic card

* riek editing for name field on topic card

* riek submit on enter

* factor out Title and Links from Topic Card component, but not the listeners

* create working Desc editor

* styling is much better now

* textarea min height for desc

* disallow images in topic card markdown

* shift enter is linebreak, enter is save

* attachments split out, but it's pretty buggy

* move listeners into Links.js

* slightly wider metacodeTitle

* fix positioning on metacode selector

* fix metacode selection

* move metacode and permissions into subcomponents

* fixes

* prevent editing on desc/title if not authorized to edit

* fix topic card draggability

* fix embedly

* fix md test

* remove the removed link card manually with jquery

* fix test syntax

* eslint

* more eslin

* reuse authorizedToEdit

* convert metacode sets to a json object for react

* add the html in react whoop

* fix metacode styling

* sort wasn't working

* finishing metacode select

* readd the above link input border

* fix syntax

* multiline title editable textarea

* more portable metacode selector component

* factor out #metacodeOptions into one react component with a callback :D:D:D

* render metacodeOptions in right click menu with react

* render metacodeOptions in right click menu with react

* fix up right click menu's metacode editing

* fix topic card title character counter

* ignore metamaps secret bundle in ag

* simplify Attachments props

* factor out embedly card into its own component; it seems to help

* link resetter

* fix edit icon on title in topic card

* move mapCount and synapseCount hover/click logic to react

* fix up the showMore control

* metacode selection tweaks

* tweak links bar spacing in topic card

* rubocop

* remove TODOs

* more badass permissions selector

* close permission selector when you click outside

* fix overeager metacode selector

* more modular attachments component

* fix bug in Desc.js

* fix right click styling

* permission changes are different than edit rights

* bad module ref

* ensure maxLength on topic titles

* hellz yeah (#1074)

* fix drop from two touches to one

* don't commit activity service

* ability to select/unselect all metacodes in custom set with keyboard shortcut (fix #390) (#1078)

* ability to select/unselect all metacodes in custom set with keyboard shortcut

* select all button

* nicer all/none buttons

* set up react testing (#1080)

* install mocha-webpack. also switch hark to npm version instead of github version

* well, mocha-webpack runs

* add jsdom for tests

* upgrade to webpack 2

* fix npm run test errors

* ImportDialogBox component tests

* Fixes bug where pressing delete key while editing text will suggest... (#1083)

* Fixes bug where pressing delete key while editing text will suggest the deletion of selected map entities

* Changed the DEL key to remove entities instead of delete them

* temporarily disable code climate duplication engine

* add topic following for internal testing

* daily map activity emails (#1081)

* data prepared, task setup

* add the basics of the email template

* cover granular permissions

* unfollow this map

* break out permissions tests better

* rename so test runs
2017-03-06 22:49:46 -05:00

157 lines
6.2 KiB
JavaScript

import React, { Component, PropTypes } from 'react'
import { find, values } from 'lodash'
import Util from '../../Metamaps/Util'
const IN_CONVERSATION = 1 // shared with /realtime/reducer.js
const MapperList = (props) => {
return <ul className='mapperList'>
<li className='live'>LIVE</li>
{ props.mappers.map(mapper => <li key={ mapper.id } ><img src={ mapper.avatar } /><span>{ mapper.username }</span></li>) }
</ul>
}
class Menu extends Component {
constructor(props) {
super(props)
this.state = { open: false }
}
toggle = () => {
this.setState({ open: !this.state.open })
return true
}
render = () => {
const { currentUser, map, onStar, onRequest, onFollow } = this.props
const isFollowing = map.isFollowedBy(currentUser)
const style = { display: this.state.open ? 'block' : 'none' }
return <div className='dropdownMenu'>
<div className='menuToggle' onClick={ this.toggle }>
<div className='circle'></div>
<div className='circle'></div>
<div className='circle'></div>
</div>
<ul className='menuItems' style={ style }>
<li className='star' onClick={ () => { this.toggle() && onStar(map) }}>Star Map</li>
{ !map.authorizeToEdit(currentUser) && <li className='request' onClick={ () => { this.toggle() && onRequest(map) }}>Request Access</li> }
{ Util.isTester(currentUser) && <li className='follow' onClick={ () => { this.toggle() && onFollow(map) }}>{isFollowing ? 'Unfollow' : 'Follow'}</li> }
</ul>
</div>
}
}
Menu.propTypes = {
currentUser: PropTypes.object.isRequired,
map: PropTypes.object.isRequired,
onStar: PropTypes.func.isRequired,
onRequest: PropTypes.func.isRequired,
onFollow: PropTypes.func.isRequired
}
const Metadata = (props) => {
const { map } = props
return (<div>
<div className="metadataSection numTopics">
<div className="numTopicsIcon"></div>
{ map.get('topic_count') }<br/>
{ map.get('topic_count') === 1 ? 'topic' : 'topics' }
</div>
<div className="metadataSection numStars">
<div className="numStarsIcon"></div>
{ map.get('star_count') }<br/>
{ map.get('star_count') === 1 ? 'star' : 'stars' }
</div>
<div className="metadataSection numSynapses">
<div className="numSynapsesIcon"></div>
{ map.get('synapse_count') }<br/>
{ map.get('synapse_count') === 1 ? 'synapse' : 'synapses' }
</div>
<div className="metadataSection numContributors">
<div className="numContributorsIcon"></div>
{ map.get('contributor_count') }<br/>
{ map.get('contributor_count') === 1 ? 'contributor' : 'contributors' }
</div>
<div className="clearfloat"></div>
</div>)
}
const checkAndWrapInA = (shouldWrap, classString, mapId, element) => {
if (shouldWrap) return <a className={ classString } href={ `/maps/${mapId}` } data-router="true">{ element }</a>
else return element
}
class MapCard extends Component {
render = () => {
const { map, mobile, juntoState, currentUser, onRequest, onStar, onFollow } = this.props
const hasMap = (juntoState.liveMaps[map.id] && values(juntoState.liveMaps[map.id]).length) || null
const realtimeMap = juntoState.liveMaps[map.id]
const hasConversation = hasMap && find(values(realtimeMap), v => v === IN_CONVERSATION)
const hasMapper = hasMap && !hasConversation
const mapperList = hasMap && Object.keys(realtimeMap).map(id => juntoState.connectedPeople[id])
const n = map.get('name')
const d = map.get('desc')
const maxNameLength = 32
const maxDescLength = 180
const truncatedName = n ? (n.length > maxNameLength ? n.substring(0, maxNameLength) + '...' : n) : ''
const truncatedDesc = d ? (d.length > maxDescLength ? d.substring(0, maxDescLength) + '...' : d) : ''
const editPermission = map.authorizeToEdit(currentUser) ? 'canEdit' : 'cannotEdit'
return (
<div className="map" id={ map.id }>
{ checkAndWrapInA(mobile, '', map.id,
<div className={ 'permission ' + editPermission }>
<div className='mapCard'>
<div className='mainContent'>
{ !mobile && <div className='mapScreenshot'>
<img src={ map.get('screenshot_url') } />
</div> }
<div className='title' title={ map.get('name') }>
<div className='innerTitle'>{ truncatedName }</div>
</div>
{ mobile && hasMapper && <div className='mobileHasMapper'><MapperList mappers={ mapperList } /></div> }
{ mobile && hasConversation && <div className='mobileHasConversation'><MapperList mappers={ mapperList } /></div> }
{ mobile && d && <div className="desc">{ d }</div> }
{ mobile && <div className='mobileMetadata'><Metadata map={ map } /></div> }
<div className={`creatorAndPerm ${map.authorizeToEdit(currentUser) ? '' : 'cardHasViewOnly'}`}>
<img className='creatorImage' src={ map.get('user_image') } />
<span className='creatorName'>{ map.get('user_name') }</span>
{ !map.authorizeToEdit(currentUser) && <div className='cardViewOnly'>View Only</div> }
</div>
</div>
{ !mobile && checkAndWrapInA(true, 'mapMetadata', map.id,
<div>
<Metadata map={ map } />
<div className="scroll">
<div className="desc">
{ truncatedDesc }
<div className="clearfloat"></div>
</div>
</div>
</div>) }
{ !mobile && hasMapper && <div className='mapHasMapper'><MapperList mappers={ mapperList } /></div> }
{ !mobile && hasConversation && <div className='mapHasConversation'><MapperList mappers={ mapperList } /></div> }
{ !mobile && currentUser && <Menu currentUser={ currentUser } map={ map } onStar= { onStar } onRequest={ onRequest } onFollow={ onFollow } /> }
</div>
</div>) }
</div>
)
}
}
MapCard.propTypes = {
map: PropTypes.object.isRequired,
mobile: PropTypes.bool.isRequired,
juntoState: PropTypes.object,
currentUser: PropTypes.object,
onStar: PropTypes.func.isRequired,
onRequest: PropTypes.func.isRequired,
onFollow: PropTypes.func.isRequired
}
export default MapCard