metamaps--metamaps/frontend/src/Metamaps/GlobalUI/ReactApp.js

265 lines
9 KiB
JavaScript

/* global $ */
import React from 'react'
import ReactDOM from 'react-dom'
import { Router, browserHistory } from 'react-router'
import { merge } from 'lodash'
import apply from 'async/apply'
import { notifyUser } from './index.js'
import ImportDialog from './ImportDialog'
import Notifications from './Notifications'
import Active from '../Active'
import DataModel from '../DataModel'
import { ExploreMaps, ChatView, TopicCard, ContextMenu } from '../Views'
import Filter from '../Filter'
import JIT from '../JIT'
import Realtime from '../Realtime'
import Map, { InfoBox } from '../Map'
import Topic from '../Topic'
import Visualize from '../Visualize'
import makeRoutes from '../../routes/makeRoutes'
let routes
// 220 wide + 16 padding on both sides
const MAP_WIDTH = 252
const MOBILE_VIEW_BREAKPOINT = 504
const MOBILE_VIEW_PADDING = 40
const MAX_COLUMNS = 4
const ReactApp = {
serverData: {},
mapId: null,
topicId: null,
mapsWidth: 0,
toast: '',
mobile: false,
mobileTitle: '',
mobileTitleWidth: 0,
metacodeSets: [],
init: function(serverData, openLightbox) {
const self = ReactApp
self.serverData = serverData
self.mobileTitle = serverData.mobileTitle
self.openLightbox = openLightbox
self.metacodeSets = serverData.metacodeSets
routes = makeRoutes(serverData.ActiveMapper)
self.resize()
window && window.addEventListener('resize', self.resize)
},
handleUpdate: function(location) {
const self = ReactApp
const pathname = this.state.location.pathname
switch (pathname.split('/')[1]) {
case '':
if (Active.Mapper && Active.Mapper.id) {
$('#yield').hide()
ExploreMaps.updateFromPath(pathname)
self.mapId = null
Active.Map = null
Active.Topic = null
}
break
case 'explore':
$('#yield').hide()
ExploreMaps.updateFromPath(pathname)
self.mapId = null
self.topicId = null
Active.Map = null
Active.Topic = null
break
case 'topics':
$('#yield').hide()
Active.Map = null
self.mapId = null
self.topicId = pathname.split('/')[2]
break
case 'maps':
if (!pathname.includes('request_access')) {
$('#yield').hide()
Active.Topic = null
self.topicId = null
self.mapId = pathname.split('/')[2]
}
break
default:
$('#yield').show()
break
}
self.render()
window.ga && window.ga('send', 'pageview', pathname)
},
render: function() {
const self = ReactApp
const createElement = (Component, props) => <Component {...props} {...self.getProps()}/>
const app = <Router createElement={createElement} routes={routes} history={browserHistory} onUpdate={self.handleUpdate} />
ReactDOM.render(app, document.getElementById('react-app'))
},
getProps: function() {
const self = ReactApp
return merge({
unreadNotificationsCount: Notifications.unreadNotificationsCount,
currentUser: Active.Mapper,
toast: self.toast,
mobile: self.mobile,
mobileTitle: self.mobileTitle,
mobileTitleWidth: self.mobileTitleWidth,
mobileTitleClick: (e) => Active.Map && InfoBox.toggleBox(e),
openInviteLightbox: () => self.openLightbox('invite'),
serverData: self.serverData,
notifications: Notifications.notifications,
fetchNotifications: apply(Notifications.fetch, ReactApp.render),
markAsRead: apply(Notifications.markAsRead, ReactApp.render),
markAsUnread: apply(Notifications.markAsUnread, ReactApp.render)
},
self.getMapProps(),
self.getTopicProps(),
self.getFilterProps(),
self.getCommonProps(),
self.getMapsProps(),
self.getContextMenuProps(),
self.getTopicCardProps(),
self.getChatProps())
},
getMapProps: function() {
const self = ReactApp
return {
mapId: self.mapId,
map: Active.Map,
hasLearnedTopicCreation: Map.hasLearnedTopicCreation,
userRequested: Map.userRequested,
requestAnswered: Map.requestAnswered,
requestApproved: Map.requestApproved,
onRequestAccess: Map.requestAccess,
mapIsStarred: Map.mapIsStarred,
endActiveMap: Map.end,
launchNewMap: Map.launch,
toggleMapInfoBox: InfoBox.toggleBox,
infoBoxHtml: InfoBox.html,
openImportLightbox: () => ImportDialog.show(),
forkMap: Map.fork,
onMapStar: Map.star,
onMapUnstar: Map.unstar
}
},
getCommonProps: function() {
const self = ReactApp
return {
openHelpLightbox: () => self.openLightbox('cheatsheet'),
onZoomExtents: event => JIT.zoomExtents(event, Visualize.mGraph.canvas),
onZoomIn: JIT.zoomIn,
onZoomOut: JIT.zoomOut
}
},
getTopicCardProps: function() {
const self = ReactApp
return {
openTopic: TopicCard.openTopic,
metacodeSets: self.metacodeSets,
updateTopic: (topic, obj) => topic.save(obj),
onTopicFollow: Topic.onTopicFollow
}
},
getContextMenuProps: function() {
const { render } = ReactApp
return {
// values
contextMenu: !!(ContextMenu.clickedNode || ContextMenu.clickedEdge),
contextNode: ContextMenu.clickedNode,
contextEdge: ContextMenu.clickedEdge,
contextPos: ContextMenu.pos,
contextFetchingSiblingsData: ContextMenu.fetchingSiblingsData,
contextSiblingsData: ContextMenu.siblingsData,
// functions
contextDelete: apply(ContextMenu.delete, render),
contextRemove: apply(ContextMenu.remove, render),
contextHide: apply(ContextMenu.hide, render),
contextCenterOn: apply(ContextMenu.centerOn, render),
contextPopoutTopic: apply(ContextMenu.popoutTopic, render),
contextUpdatePermissions: apply(ContextMenu.updatePermissions, render),
contextOnMetacodeSelect: apply(ContextMenu.onMetacodeSelect, render),
contextFetchSiblings: apply(ContextMenu.fetchSiblings, render),
contextPopulateSiblings: apply(ContextMenu.populateSiblings, render)
}
},
getTopicProps: function() {
const self = ReactApp
return {
topicId: self.topicId,
topic: Active.Topic,
endActiveTopic: Topic.end,
launchNewTopic: Topic.launch
}
},
getMapsProps: function() {
const self = ReactApp
return {
section: ExploreMaps.collection && ExploreMaps.collection.id,
maps: ExploreMaps.collection,
juntoState: Realtime.juntoState,
moreToLoad: ExploreMaps.collection && ExploreMaps.collection.page !== 'loadedAll',
user: ExploreMaps.collection && ExploreMaps.collection.id === 'mapper' ? ExploreMaps.mapper : null,
loadMore: ExploreMaps.loadMore,
pending: ExploreMaps.pending,
onStar: ExploreMaps.onStar,
onRequest: ExploreMaps.onRequest,
onMapFollow: ExploreMaps.onMapFollow,
mapsWidth: ReactApp.mapsWidth
}
},
getChatProps: function() {
const self = ReactApp
return {
unreadMessages: ChatView.unreadMessages,
conversationLive: ChatView.conversationLive,
isParticipating: ChatView.isParticipating,
onOpen: ChatView.onOpen,
onClose: ChatView.onClose,
leaveCall: Realtime.leaveCall,
joinCall: Realtime.joinCall,
inviteACall: Realtime.inviteACall,
inviteToJoin: Realtime.inviteToJoin,
participants: ChatView.participants ? ChatView.participants.models.map(p => p.attributes) : [],
messages: ChatView.messages ? ChatView.messages.models.map(m => m.attributes) : [],
videoToggleClick: ChatView.videoToggleClick,
cursorToggleClick: ChatView.cursorToggleClick,
soundToggleClick: ChatView.soundToggleClick,
inputBlur: ChatView.inputBlur,
inputFocus: ChatView.inputFocus,
handleInputMessage: ChatView.handleInputMessage
}
},
getFilterProps: function() {
const self = ReactApp
return {
filterData: Filter.dataForPresentation,
allForFiltering: Filter.filters,
visibleForFiltering: Filter.visible,
toggleMetacode: Filter.toggleMetacode,
toggleMapper: Filter.toggleMapper,
toggleSynapse: Filter.toggleSynapse,
filterAllMetacodes: Filter.filterAllMetacodes,
filterAllMappers: Filter.filterAllMappers,
filterAllSynapses: Filter.filterAllSynapses
}
},
resize: function() {
const self = ReactApp
const maps = ExploreMaps.collection
const currentUser = Active.Mapper
const user = maps && maps.id === 'mapper' ? ExploreMaps.mapper : null
const numCards = (maps ? maps.length : 0) + (user || currentUser ? 1 : 0)
const mapSpaces = Math.floor(document.body.clientWidth / MAP_WIDTH)
const mapsWidth = document.body.clientWidth <= MOBILE_VIEW_BREAKPOINT
? document.body.clientWidth - MOBILE_VIEW_PADDING
: Math.min(MAX_COLUMNS, Math.min(numCards, mapSpaces)) * MAP_WIDTH
self.mapsWidth = mapsWidth
self.mobileTitleWidth = document ? document.body.clientWidth - 70 : 0
self.mobile = document && document.body.clientWidth <= MOBILE_VIEW_BREAKPOINT
self.render()
}
}
export default ReactApp