initial restructuring

This commit is contained in:
Connor Turland 2017-03-11 01:49:27 -05:00
parent 1124d76475
commit 701198ef14
46 changed files with 760 additions and 796 deletions

View file

@ -775,6 +775,7 @@ label {
} }
.sidebarAccountIcon img { .sidebarAccountIcon img {
border-radius: 16px; border-radius: 16px;
width: 32px;
} }
.sidebarAccountBox { .sidebarAccountBox {
display: none; display: none;

View file

@ -54,7 +54,6 @@
width:100%; width:100%;
height:100%; height:100%;
position: absolute; position: absolute;
display: none;
} }
.showcard .permission { .showcard .permission {

View file

@ -46,26 +46,9 @@
transition-timing-function: ease-in-out; transition-timing-function: ease-in-out;
}*/ }*/
.mapElement {
display: none;
}
.mapPage .mapElement,
.topicPage .mapElement {
display: block;
}
.mapPage .mapElementHidden,
.topicPage .mapElement.mapInfoBox,
.topicPage .mapElement.importDialog {
display:none;
}
.topicPage .starMap {
display: none;
}
/* loading */ /* loading */
#loading { #loading {
display: none;
width: 28px; width: 28px;
height: 28px; height: 28px;
position: fixed; position: fixed;
@ -236,6 +219,14 @@
/* end upperRightUI */ /* end upperRightUI */
/* map wrapper */
.mapWrapper {
position:absolute;
width: 100%;
height: 100%;
}
/* end map wrapper */
/* yield */ /* yield */
@ -356,22 +347,15 @@
/* infoAndHelp */ /* infoAndHelp */
.mapPage .infoAndHelp, .topicPage .infoAndHelp {
right: 70px;
}
.mapPage .openCheatsheet .tooltipsAbove, .topicPage .openCheatsheet .tooltipsAbove { .mapPage .openCheatsheet .tooltipsAbove, .topicPage .openCheatsheet .tooltipsAbove {
right: 1px; right: 1px;
left: auto; left: auto;
} }
.unauthenticated .homePage .infoAndHelp {
display:none;
}
.infoAndHelp { .infoAndHelp {
position: absolute; position: absolute;
bottom: 20px; bottom: 20px;
right: 20px; right: 70px;
z-index: 3; z-index: 3;
width: auto; width: auto;
font-style: italic; font-style: italic;
@ -392,16 +376,12 @@
} }
.mapInfoIcon { .mapInfoIcon {
position: relative; position: relative;
top: 56px; /* puts it just offscreen */ background-image: url(<%= asset_path('mapinfo_sprite.png') %>);
background-image: url(<%= asset_path('mapinfo_sprite.png') %>); background-repeat:no-repeat;
background-repeat:no-repeat;
} }
.mapInfoIcon:hover { .mapInfoIcon:hover {
background-position: 0 -32px; background-position: 0 -32px;
} }
.mapPage .mapInfoIcon {
top: 0;
}
.starMap { .starMap {
background-image: url(<%= asset_path('starmap_sprite.png') %>); background-image: url(<%= asset_path('starmap_sprite.png') %>);
@ -430,24 +410,17 @@
.mapControls { .mapControls {
position: absolute; position: absolute;
bottom: 24px; bottom: 24px;
right:-32px; /* puts it just offscreen */ right:24px;
width:32px; width:32px;
z-index: 3; z-index: 3;
} }
.mapPage .mapControls, .topicPage .mapControls {
right: 24px;
}
.topicPage .zoomExtents {
display: none;
}
.mapControl { .mapControl {
width:32px; width:32px;
height:32px; height:32px;
background-color: #424242; background-color: #424242;
background-repeat: no-repeat; background-repeat: no-repeat;
background-position: 0 0; background-position: 0 0;
cursor:pointer; cursor:pointer;
} }
@ -671,8 +644,11 @@
/* explore maps */ /* explore maps */
#explore { #react-app {
display: none; position: absolute;
height: 100%;
width: 100%;
overflow-y: auto;
} }
#exploreMaps { #exploreMaps {

View file

@ -4,7 +4,7 @@ class ApplicationController < ActionController::Base
include Pundit include Pundit
include PunditExtra include PunditExtra
rescue_from Pundit::NotAuthorizedError, with: :handle_unauthorized rescue_from Pundit::NotAuthorizedError, with: :handle_unauthorized
protect_from_forgery(with: :exception) #protect_from_forgery(with: :exception)
before_action :invite_link before_action :invite_link
before_action :prepare_exception_notifier before_action :prepare_exception_notifier

View file

@ -1,60 +0,0 @@
<%#
# @file
# The inner HTML of the account box that comes up in the bottom left
#%>
<% if current_user %>
<% account = current_user %>
<%= image_tag account.image.url(:sixtyfour), :size => "48x48", :class => "sidebarAccountImage" %>
<h3 class="accountHeader"><%= account.name.split[0...1][0] %></h3>
<ul>
<li class="accountListItem accountSettings">
<div class="accountIcon"></div>
<%= link_to "Settings", edit_user_url(account) %>
</li>
<% if account.admin %>
<li class="accountListItem accountAdmin">
<div class="accountIcon"></div>
<%= link_to "Admin", metacodes_path %>
</li>
<% end %>
<li class="accountListItem accountApps">
<div class="accountIcon"></div>
<%= link_to "Apps", oauth_authorized_applications_path %>
</li>
<li class="accountListItem accountInvite openLightbox" data-open="invite">
<div class="accountIcon"></div>
<span>Share Invite</span>
</li>
<li class="accountListItem accountLogout">
<div class="accountIcon"></div>
<%= link_to "Sign Out", "/logout", id: "Logout" %>
</li>
</ul>
<% else %>
<%= form_for(resource, :as => resource_name, :url => session_path(resource_name), :html => { class: "loginAnywhere" }) do |f| %>
<div class="accountImage"></div>
<div class="accountInput accountEmail">
<%= f.email_field :email, :placeholder => "Email" %>
</div>
<div class="accountInput accountPassword">
<%= f.password_field :password, :placeholder => "Password" %>
</div>
<div class="accountSubmit"><%= f.submit "SIGN IN" %></div>
<% if devise_mapping.rememberable? -%>
<div class="accountRememberMe">
<%= f.label :remember_me, "Stay signed in" %>
<%= f.check_box :remember_me %>
<div class="clearfloat"></div>
</div>
<% end -%>
<div class="clearfloat"></div>
<div class="accountForgotPass">
<%- if devise_mapping.recoverable? && controller_name != 'passwords' %>
<%= link_to "Forgot password?", new_password_path(resource_name) %>
<% end -%>
</div>
<% end %>
<% end %>
<% # Rails.logger.info(stored_location_for(:user)) %>

View file

@ -1,17 +0,0 @@
<div class="mapControls mapElement">
<div class="zoomExtents mapControl"><div class="tooltips">Center View</div></div>
<div class="zoomIn mapControl"><div class="tooltips">Zoom In</div></div>
<div class="zoomOut mapControl"><div class="tooltips">Zoom Out</div></div>
</div>
<div class="infoAndHelp">
<%= render :partial => 'maps/mapinfobox' %>
<% starred = current_user && @map && current_user.starred_map?(@map)
starClass = starred ? 'starred' : ''
tooltip = starred ? 'Star' : 'Unstar' %>
<div class="starMap infoElement mapElement <%= starClass %>"><div class="tooltipsAbove"><%= tooltip %></div></div>
<div class="mapInfoIcon infoElement mapElement"><div class="tooltipsAbove">Map Info</div></div>
<div class="openCheatsheet openLightbox infoElement mapElement" data-open="cheatsheet"><div class="tooltipsAbove">Help</div></div>
<div class="clearfloat"></div>
</div>

View file

@ -1,107 +0,0 @@
<!-- from left to right on the screen -->
<div class="upperLeftUI">
<!-- home button -->
<div class="homeButton">
<a href="<%= root_url %>" <% if current_user && !noHardHomeLink %><%= 'data-router=true' %><% end %>>METAMAPS</a>
</div> <!-- end homeButton -->
<!-- search box -->
<div class="sidebarSearch">
<input type="text" class="sidebarSearchField" placeholder="Search for topics, maps, and mappers..." />
<div id="searchLoading"></div>
<div class="sidebarSearchIcon"></div>
<div class="clearfloat"></div>
</div> <!-- end sidebarSearch -->
<% request = current_user && @map && @allrequests.find{|a| a.user == current_user}
className = (@map and not policy(@map).update?) ? 'isViewOnly ' : ''
if @map
className += 'sendRequest' if not request
className += 'sentRequest' if request and not request.answered
className += 'requestDenied' if request and request.answered and not request.approved
end %>
<div class="viewOnly <%= className %>">
<div class="eyeball">View Only</div>
<% if current_user %>
<div class="requestAccess requestNotice">Request Access</div>
<div class="requestPending requestNotice">Request Pending</div>
<div class="requestNotAccepted requestNotice">Request Not Accepted</div>
<% end %>
</div>
<div class="clearfloat"></div>
</div><!-- end upperLeftUI -->
<div class="upperRightUI">
<div class="mapElement upperRightEl upperRightMapButtons">
<% if current_user %>
<div class="importDialog upperRightEl upperRightIcon mapElement openLightbox" data-open="import-dialog-lightbox">
<div class="tooltipsUnder">
Import Data
</div>
</div>
<% end %>
<!-- filtering -->
<div class="sidebarFilter upperRightEl">
<div class="sidebarFilterIcon upperRightIcon"><div class="tooltipsUnder">Filter</div></div>
<div class="sidebarFilterBox upperRightBox">
<%= render :partial => 'shared/filterBox' %>
</div>
</div> <!-- end sidebarFilter -->
<% if current_user %>
<!-- fork map -->
<div class="sidebarFork upperRightEl">
<div class="sidebarForkIcon upperRightIcon"><div class="tooltipsUnder">Save To New Map</div></div>
</div> <!-- end sidebarFork -->
<% end %>
<div class="clearfloat"></div>
</div> <!-- end mapElement -->
<% if current_user %>
<!-- create new map -->
<a href="<%= new_map_path %>" target="_blank" class="addMap upperRightEl upperRightIcon">
<div class="tooltipsUnder">
Create New Map
</div>
</a><!-- end addMap -->
<% end %>
<script type="text/javascript">
Metamaps.ServerData.unreadNotificationsCount = <%= user_unread_notification_count %>
</script>
<% if current_user.present? %>
<span id="notification_icon">
<%= link_to notifications_path, class: "notificationsIcon upperRightEl upperRightIcon #{user_unread_notification_count > 0 ? 'unread' : 'read'}" do %>
<div class="tooltipsUnder">
Notifications
</div>
<% if user_unread_notification_count > 0 %>
<div class="unread-notifications-dot"></div>
<% end %>
<% end %>
</span>
<% end %>
<!-- Account / Sign in -->
<% if !(controller_name == "sessions" && action_name == "new") %>
<div class="sidebarAccount upperRightEl">
<div class="sidebarAccountIcon"><div class="tooltipsUnder">Account</div>
<% if current_user && current_user.image %>
<%= image_tag current_user.image.url(:thirtytwo), :size => "32x32" %>
<% elsif !current_user %>
SIGN IN
<div class="accountInnerArrow"></div>
<% end %>
</div>
<div class="sidebarAccountBox upperRightBox">
<%= render :partial => 'layouts/account' %>
</div>
</div><!-- end sidebarAccount -->
<% end %>
<div class="clearfloat"></div>
</div><!-- end upperRightUI -->

View file

@ -6,83 +6,34 @@
#%> #%>
<%= render :partial => 'layouts/head' %> <%= render :partial => 'layouts/head' %>
<body class="<%= authenticated? ? "authenticated" : "unauthenticated" %> controller-<%= controller_name %> action-<%= action_name %>"> <body class="<%= authenticated? ? "authenticated" : "unauthenticated" %> controller-<%= controller_name %> action-<%= action_name %>">
<%= content_tag :div, class: "main", id: "react-app" do %>
<div id="chat-box-wrapper"></div> <%= yield %>
<%= render :partial => 'layouts/mobilemenu' %>
<a class='feedback-icon' target='_blank' href='https://hylo.com/c/metamaps'></a>
<%= content_tag :div, class: "main" do %>
<% classes = action_name == "home" ? "homePage" : ""
classes += action_name == "home" && authenticated? ? " explorePage" : ""
classes += controller_name == "maps" && action_name == "index" ? " explorePage" : ""
if controller_name == "maps" && action_name == "show"
classes += " mapPage"
if policy(@map).update?
classes += " canEditMap"
end
if @map.permission == "commons"
classes += " commonsMap"
end
end
classes += controller_name == "topics" && action_name == "show" ? " topicPage" : ""
%>
<div class="wrapper <%= classes %>" id="wrapper">
<%= render :partial => 'layouts/upperelements', :locals => { :noHardHomeLink => controller_name == "notifications" ? true : false } %>
<%= yield %>
<div class="showcard mapElement mapElementHidden" id="showcard"></div> <!-- the topic card -->
<% if authenticated? %>
<% # for creating and pulling in topics and synapses %>
<% if controller_name == 'maps' && action_name == "conversation" %>
<%= render :partial => 'maps/newtopicsecret' %>
<% else %>
<%= render :partial => 'maps/newtopic' %>
<% end %>
<%= render :partial => 'maps/newsynapse' %>
<% # for populating the change metacode list on the topic card %>
<%= render :partial => 'shared/metacodeoptions' %>
<% end %>
<%= render :partial => 'layouts/lowermapelements' %>
<div id="explore"></div>
<% if !(controller_name == 'maps' && action_name == "conversation") %>
<div id="instructions">
<div class="addTopic">
Double-click to<br>add a topic
</div>
<div class="tabKey">
Use Tab & Shift+Tab to select a metacode
</div>
<div class="enterKey">
Press Enter to add the topic
</div>
</div>
<% end %>
<div id="infovis"></div>
<%= render :partial => 'layouts/mobilemenu' %>
<p id="toast" class="toast">
<% if devise_error_messages? %>
<%= devise_error_messages! %>
<% end %>
<% if notice %>
<%= notice %>
<% end %>
<% if alert %>
<%= alert %>
<% end %>
</p>
<div id="loading"></div>
</div>
<% end %> <% end %>
<% if devise_error_messages? %>
<%= devise_error_messages! %>
<% end %>
<% if notice %>
<%= notice %>
<% end %>
<% if alert %>
<%= alert %>
<% end %>
<% if authenticated? %>
<% # for creating and pulling in topics and synapses %>
<% if controller_name == 'maps' && action_name == "conversation" %>
<%= render :partial => 'maps/newtopicsecret' %>
<% else %>
<%= render :partial => 'maps/newtopic' %>
<% end %>
<%= render :partial => 'maps/newsynapse' %>
<% # for populating the change metacode list on the topic card %>
<%= render :partial => 'shared/metacodeoptions' %>
<% end %>
<div id="loading"></div>
<script type="text/javascript">
Metamaps.ServerData.unreadNotificationsCount = <%= user_unread_notification_count %>
Metamaps.ServerData.mapIsStarred = <%= current_user && @map && current_user.starred_map?(@map) ? true : false %>
</script>
<%= render :partial => 'layouts/foot' %> <%= render :partial => 'layouts/foot' %>

View file

@ -4,7 +4,7 @@ import React from 'react'
import ReactDOM from 'react-dom' import ReactDOM from 'react-dom'
import outdent from 'outdent' import outdent from 'outdent'
import ImportDialogBox from '../../components/ImportDialogBox' import ImportDialogBox from '../../components/MapView/ImportDialogBox'
import PasteInput from '../PasteInput' import PasteInput from '../PasteInput'
import Map from '../Map' import Map from '../Map'

View file

@ -1,30 +0,0 @@
/* global $ */
import React from 'react'
import ReactDOM from 'react-dom'
import Active from '../Active'
import NotificationIconComponent from '../../components/NotificationIcon'
const NotificationIcon = {
unreadNotificationsCount: null,
init: function(serverData) {
const self = NotificationIcon
self.unreadNotificationsCount = serverData.unreadNotificationsCount
self.render()
},
render: function(newUnreadCount = null) {
if (newUnreadCount !== null) {
NotificationIcon.unreadNotificationsCount = newUnreadCount
}
if (Active.Mapper !== null) {
ReactDOM.render(React.createElement(NotificationIconComponent, {
unreadNotificationsCount: NotificationIcon.unreadNotificationsCount
}), $('#notification_icon').get(0))
}
}
}
export default NotificationIcon

View file

@ -0,0 +1,133 @@
/* global $ */
import React from 'react'
import ReactDOM from 'react-dom'
import { Router, browserHistory } from 'react-router'
import { merge } from 'lodash'
import { notifyUser } from './index.js'
import Active from '../Active'
import DataModel from '../DataModel'
import { ExploreMaps, ChatView, TopicCard } from '../Views'
import Realtime from '../Realtime'
import Map from '../Map'
import Topic from '../Topic'
import makeRoutes from '../../components/makeRoutes'
let routes
const ReactApp = {
mapId: null,
unreadNotificationsCount: 0,
mapIsStarred: false,
init: function(serverData) {
const self = ReactApp
self.unreadNotificationsCount = serverData.unreadNotificationsCount
self.mapIsStarred = serverData.mapIsStarred
routes = makeRoutes()
self.render()
},
handleUpdate: function(location) {
const self = ReactApp
// TODO: also handle page title updates
switch (this.state.location.pathname.split('/')[1]) {
case '':
case 'explore':
ExploreMaps.updateFromPath(this.state.location.pathname)
self.mapId = null
Active.Map = null
Active.Topic = null
break
case 'topics':
break
case 'maps':
self.mapId = this.state.location.pathname.split('/')[2]
break
}
self.render()
// track using google analytics here
//window.ga && window.ga('send', 'pageview', location.pathname, {title: document.title})
},
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: self.unreadNotificationsCount,
currentUser: Active.Mapper
},
self.getMapProps(),
self.getTopicProps(),
self.getMapsProps(),
self.getTopicCardProps(),
self.getChatProps())
},
getMapProps: function() {
const self = ReactApp
return {
mapId: self.mapId,
map: Active.Map,
mapIsStarred: self.mapIsStarred,
endActiveMap: Map.end,
launchNewMap: Map.launch
}
},
getTopicCardProps: function() {
const self = ReactApp
return {
openTopic: TopicCard.openTopic,
metacodeSets: TopicCard.metacodeSets,
updateTopic: TopicCard.updateTopic,
onTopicFollow: TopicCard.onTopicFollow,
redrawCanvas: TopicCard.redrawCanvas
}
},
getTopicProps: function() {
const self = ReactApp
return {
topic: Active.Topic
}
},
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
}
},
getChatProps: function() {
const self = ReactApp
return {
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
}
}
}
export default ReactApp

View file

@ -1,7 +1,6 @@
/* global $, Hogan, Bloodhound, CanvasLoader */ /* global $, Hogan, Bloodhound, CanvasLoader */
import Active from '../Active' import Active from '../Active'
import Router from '../Router'
const Search = { const Search = {
locked: false, locked: false,
@ -189,11 +188,11 @@ const Search = {
if (['topic', 'map', 'mapper'].indexOf(datum.rtype) !== -1) { if (['topic', 'map', 'mapper'].indexOf(datum.rtype) !== -1) {
if (datum.rtype === 'topic') { if (datum.rtype === 'topic') {
Router.topics(datum.id) // TODO: navigate to topic datum.id
} else if (datum.rtype === 'map') { } else if (datum.rtype === 'map') {
Router.maps(datum.id) // TODO: navigate to map datum.id
} else if (datum.rtype === 'mapper') { } else if (datum.rtype === 'mapper') {
Router.explore('mapper', datum.id) // TODO: navigate to mapper section datum.id
} }
} }
}, },

View file

@ -4,11 +4,11 @@ import clipboard from 'clipboard-js'
import Create from '../Create' import Create from '../Create'
import ReactApp from './ReactApp'
import Search from './Search' import Search from './Search'
import CreateMap from './CreateMap' import CreateMap from './CreateMap'
import Account from './Account' import Account from './Account'
import ImportDialog from './ImportDialog' import ImportDialog from './ImportDialog'
import NotificationIcon from './NotificationIcon'
const GlobalUI = { const GlobalUI = {
notifyTimeout: null, notifyTimeout: null,
@ -18,11 +18,11 @@ const GlobalUI = {
init: function(serverData) { init: function(serverData) {
const self = GlobalUI const self = GlobalUI
self.ReactApp.init(serverData)
self.Search.init(serverData) self.Search.init(serverData)
self.CreateMap.init(serverData) self.CreateMap.init(serverData)
self.Account.init(serverData) self.Account.init(serverData)
self.ImportDialog.init(serverData, self.openLightbox, self.closeLightbox) self.ImportDialog.init(serverData, self.openLightbox, self.closeLightbox)
self.NotificationIcon.init(serverData)
if ($('#toast').html().trim()) self.notifyUser($('#toast').html()) if ($('#toast').html().trim()) self.notifyUser($('#toast').html())
@ -153,5 +153,5 @@ const GlobalUI = {
} }
} }
export { Search, CreateMap, Account, ImportDialog, NotificationIcon } export { ReactApp, Search, CreateMap, Account, ImportDialog }
export default GlobalUI export default GlobalUI

View file

@ -5,7 +5,6 @@ import outdent from 'outdent'
import Active from '../Active' import Active from '../Active'
import DataModel from '../DataModel' import DataModel from '../DataModel'
import GlobalUI from '../GlobalUI' import GlobalUI from '../GlobalUI'
import Router from '../Router'
import Util from '../Util' import Util from '../Util'
const InfoBox = { const InfoBox = {
@ -192,7 +191,6 @@ const InfoBox = {
$('.mapContributors .tip').unbind().click(function(event) { $('.mapContributors .tip').unbind().click(function(event) {
event.stopPropagation() event.stopPropagation()
}) })
$('.mapContributors .tip li a').click(Router.intercept)
$('.mapInfoBox').unbind('.hideTip').bind('click.hideTip', function() { $('.mapInfoBox').unbind('.hideTip').bind('click.hideTip', function() {
$('.mapContributors .tip').hide() $('.mapContributors .tip').hide()
@ -393,7 +391,7 @@ const InfoBox = {
DataModel.Maps.Mine.remove(map) DataModel.Maps.Mine.remove(map)
DataModel.Maps.Shared.remove(map) DataModel.Maps.Shared.remove(map)
map.destroy() map.destroy()
Router.home() // TODO: navigate home
GlobalUI.notifyUser('Map eliminated') GlobalUI.notifyUser('Map eliminated')
} else if (!authorized) { } else if (!authorized) {
window.alert("Hey now. We can't just go around willy nilly deleting other people's maps now can we? Run off and find something constructive to do, eh?") window.alert("Hey now. We can't just go around willy nilly deleting other people's maps now can we? Run off and find something constructive to do, eh?")

View file

@ -13,7 +13,6 @@ import GlobalUI from '../GlobalUI'
import JIT from '../JIT' import JIT from '../JIT'
import Loading from '../Loading' import Loading from '../Loading'
import Realtime from '../Realtime' import Realtime from '../Realtime'
import Router from '../Router'
import Selected from '../Selected' import Selected from '../Selected'
import SynapseCard from '../SynapseCard' import SynapseCard from '../SynapseCard'
import TopicCard from '../Views/TopicCard' import TopicCard from '../Views/TopicCard'
@ -132,7 +131,7 @@ const Map = {
// for mobile // for mobile
$('#header_content').html(map.get('name')) $('#header_content').html(map.get('name'))
} }
Loading.show()
$.ajax({ $.ajax({
url: '/maps/' + id + '/contains.json', url: '/maps/' + id + '/contains.json',
success: start success: start
@ -232,7 +231,7 @@ const Map = {
var map = Active.Map var map = Active.Map
DataModel.Maps.Active.remove(map) DataModel.Maps.Active.remove(map)
DataModel.Maps.Featured.remove(map) DataModel.Maps.Featured.remove(map)
Router.home() // TODO: navigate home
GlobalUI.notifyUser('Sorry! That map has been changed to Private.') GlobalUI.notifyUser('Sorry! That map has been changed to Private.')
}, },
cantEditNow: function() { cantEditNow: function() {
@ -245,7 +244,7 @@ const Map = {
confirmString += 'Do you want to reload and enable realtime collaboration?' confirmString += 'Do you want to reload and enable realtime collaboration?'
var c = window.confirm(confirmString) var c = window.confirm(confirmString)
if (c) { if (c) {
Router.maps(Active.Map.id) // TODO: reload the map somehow
} }
}, },
editedByActiveMapper: function() { editedByActiveMapper: function() {

View file

@ -1,244 +0,0 @@
/* global $ */
import Backbone from 'backbone'
try { Backbone.$ = window.$ } catch (err) {}
import Active from './Active'
import DataModel from './DataModel'
import GlobalUI from './GlobalUI'
import Loading from './Loading'
import Map from './Map'
import Topic from './Topic'
import Views from './Views'
import Visualize from './Visualize'
const _Router = Backbone.Router.extend({
currentPage: '',
currentSection: '',
timeoutId: undefined,
routes: {
'': 'home', // #home
'explore/:section': 'explore', // #explore/active
'explore/:section/:id': 'explore', // #explore/mapper/1234
'maps/:id': 'maps', // #maps/7
'topics/:id': 'topics' // #topics/7
},
home: function() {
let self = this
clearTimeout(this.timeoutId)
if (Active.Mapper) document.title = 'Explore Active Maps | Metamaps'
else document.title = 'Home | Metamaps'
this.currentSection = ''
this.currentPage = ''
$('.wrapper').removeClass('mapPage topicPage')
var classes = Active.Mapper ? 'homePage explorePage' : 'homePage'
$('.wrapper').addClass(classes)
var navigate = function() {
self.timeoutId = setTimeout(function() {
self.navigateAndTrack('')
}, 300)
}
// all this only for the logged in home page
if (Active.Mapper) {
$('.homeButton a').attr('href', '/')
GlobalUI.hideDiv('#yield')
GlobalUI.showDiv('#explore')
Views.ExploreMaps.setCollection(DataModel.Maps.Active)
if (DataModel.Maps.Active.length === 0) {
Views.ExploreMaps.pending = true
DataModel.Maps.Active.getMaps(navigate) // this will trigger an explore maps render
} else {
Views.ExploreMaps.render(navigate)
}
} else {
// logged out home page
GlobalUI.hideDiv('#explore')
GlobalUI.showDiv('#yield')
this.timeoutId = setTimeout(navigate, 500)
}
GlobalUI.hideDiv('#infovis')
GlobalUI.hideDiv('#instructions')
Map.end()
Topic.end()
Active.Map = null
Active.Topic = null
},
explore: function(section, id) {
var self = this
clearTimeout(this.timeoutId)
// just capitalize the variable section
// either 'featured', 'mapper', or 'active'
var capitalize = section.charAt(0).toUpperCase() + section.slice(1)
if (section === 'shared' || section === 'featured' || section === 'active' || section === 'starred') {
document.title = 'Explore ' + capitalize + ' Maps | Metamaps'
} else if (section === 'mapper') {
$.ajax({
url: '/users/' + id + '.json',
success: function(response) {
document.title = response.name + ' | Metamaps'
},
error: function() {}
})
} else if (section === 'mine') {
document.title = 'Explore My Maps | Metamaps'
}
if (Active.Mapper && section !== 'mapper') $('.homeButton a').attr('href', '/explore/' + section)
$('.wrapper').removeClass('homePage mapPage topicPage')
$('.wrapper').addClass('explorePage')
this.currentSection = 'explore'
this.currentPage = section
// this will mean it's a mapper page being loaded
if (id) {
if (DataModel.Maps.Mapper.mapperId !== id) {
// empty the collection if we are trying to load the maps
// collection of a different mapper than we had previously
DataModel.Maps.Mapper.reset()
DataModel.Maps.Mapper.page = 1
}
DataModel.Maps.Mapper.mapperId = id
}
Views.ExploreMaps.setCollection(DataModel.Maps[capitalize])
var navigate = function() {
var path = '/explore/' + self.currentPage
// alter url if for mapper profile page
if (self.currentPage === 'mapper') {
path += '/' + DataModel.Maps.Mapper.mapperId
}
self.navigateAndTrack(path)
}
var navigateTimeout = function() {
self.timeoutId = setTimeout(navigate, 300)
}
if (DataModel.Maps[capitalize].length === 0) {
Loading.show()
Views.ExploreMaps.pending = true
setTimeout(function() {
DataModel.Maps[capitalize].getMaps(navigate) // this will trigger an explore maps render
}, 300) // wait 300 milliseconds till the other animations are done to do the fetch
} else {
if (id) {
Views.ExploreMaps.fetchUserThenRender(navigateTimeout)
} else {
Views.ExploreMaps.render(navigateTimeout)
}
}
GlobalUI.showDiv('#explore')
GlobalUI.hideDiv('#yield')
GlobalUI.hideDiv('#infovis')
GlobalUI.hideDiv('#instructions')
Map.end()
Topic.end()
Active.Map = null
Active.Topic = null
},
maps: function(id) {
clearTimeout(this.timeoutId)
this.currentSection = 'map'
this.currentPage = id
$('.wrapper').removeClass('homePage explorePage topicPage')
$('.wrapper').addClass('mapPage')
// another class will be added to wrapper if you
// can edit this map '.canEditMap'
GlobalUI.hideDiv('#yield')
GlobalUI.hideDiv('#explore')
// clear the visualization, if there was one, before showing its div again
if (Visualize.mGraph) {
Visualize.clearVisualization()
}
GlobalUI.showDiv('#infovis')
Topic.end()
Active.Topic = null
Loading.show()
Map.end()
Map.launch(id)
},
topics: function(id) {
clearTimeout(this.timeoutId)
this.currentSection = 'topic'
this.currentPage = id
$('.wrapper').removeClass('homePage explorePage mapPage')
$('.wrapper').addClass('topicPage')
GlobalUI.hideDiv('#yield')
GlobalUI.hideDiv('#explore')
// clear the visualization, if there was one, before showing its div again
if (Visualize.mGraph) {
Visualize.clearVisualization()
}
GlobalUI.showDiv('#infovis')
Map.end()
Active.Map = null
Topic.end()
Topic.launch(id)
}
})
const Router = new _Router()
Router.navigateAndTrack = (fragment, options) => {
Router.navigate(fragment, options)
window.ga && window.ga('send', 'pageview', location.pathname, {title: document.title})
}
Router.intercept = function(evt) {
var segments
var href = {
prop: $(this).prop('href'),
attr: $(this).attr('href')
}
var root = window.location.protocol + '//' + window.location.host + Backbone.history.options.root
if (href.prop && href.prop === root) href.attr = ''
if (href.prop && href.prop.slice(0, root.length) === root) {
evt.preventDefault()
segments = href.attr.split('/')
segments.splice(0, 1) // pop off the element created by the first /
if (href.attr === '') {
Router.home()
} else {
Router[segments[0]](segments[1], segments[2])
}
}
}
Router.init = function() {
Backbone.history.start({
silent: true,
pushState: true,
root: '/'
})
$(document).on('click', 'a[data-router="true"]', Router.intercept)
}
export default Router

View file

@ -10,7 +10,6 @@ import Filter from './Filter'
import GlobalUI from './GlobalUI' import GlobalUI from './GlobalUI'
import JIT from './JIT' import JIT from './JIT'
import Map from './Map' import Map from './Map'
import Router from './Router'
import Selected from './Selected' import Selected from './Selected'
import Settings from './Settings' import Settings from './Settings'
import SynapseCard from './SynapseCard' import SynapseCard from './SynapseCard'
@ -90,7 +89,6 @@ const Topic = {
if (callback) callback() if (callback) callback()
} }
}) })
Router.navigate('/topics/' + nodeid)
Active.Topic = DataModel.Topics.get(nodeid) Active.Topic = DataModel.Topics.get(nodeid)
} }
}, },

View file

@ -10,7 +10,7 @@ import ReactDOM from 'react-dom'
import Active from '../Active' import Active from '../Active'
import DataModel from '../DataModel' import DataModel from '../DataModel'
import Realtime from '../Realtime' import Realtime from '../Realtime'
import MapChat from '../../components/MapChat' import ReactApp from '../GlobalUI/ReactApp'
const ChatView = { const ChatView = {
isOpen: false, isOpen: false,
@ -51,24 +51,7 @@ const ChatView = {
render: () => { render: () => {
if (!Active.Map) return if (!Active.Map) return
const self = ChatView const self = ChatView
self.mapChat = ReactDOM.render(React.createElement(MapChat, { ReactApp.render()
conversationLive: self.conversationLive,
isParticipating: self.isParticipating,
onOpen: self.onOpen,
onClose: self.onClose,
leaveCall: Realtime.leaveCall,
joinCall: Realtime.joinCall,
inviteACall: Realtime.inviteACall,
inviteToJoin: Realtime.inviteToJoin,
participants: self.participants.models.map(p => p.attributes),
messages: self.messages.models.map(m => m.attributes),
videoToggleClick: self.videoToggleClick,
cursorToggleClick: self.cursorToggleClick,
soundToggleClick: self.soundToggleClick,
inputBlur: self.inputBlur,
inputFocus: self.inputFocus,
handleInputMessage: self.handleInputMessage
}), document.getElementById(ChatView.domId))
}, },
onOpen: () => { onOpen: () => {
$(document).trigger(ChatView.events.openTray) $(document).trigger(ChatView.events.openTray)

View file

@ -6,6 +6,7 @@ import ReactDOM from 'react-dom' // TODO ensure this isn't a double import
import Active from '../Active' import Active from '../Active'
import DataModel from '../DataModel' import DataModel from '../DataModel'
import GlobalUI from '../GlobalUI' import GlobalUI from '../GlobalUI'
import { ReactApp } from '../GlobalUI'
import Realtime from '../Realtime' import Realtime from '../Realtime'
import Loading from '../Loading' import Loading from '../Loading'
import Maps from '../../components/Maps' import Maps from '../../components/Maps'
@ -13,6 +14,37 @@ import Maps from '../../components/Maps'
const ExploreMaps = { const ExploreMaps = {
pending: false, pending: false,
mapper: null, mapper: null,
updateFromPath: function(path) {
const self = ExploreMaps
const test = path.split('/')[1]
const section = path.split('/')[2]
const id = path.split('/')[3]
if (test === 'explore') {
const capitalize = section.charAt(0).toUpperCase() + section.slice(1)
self.setCollection(DataModel.Maps[capitalize])
} else if (test === '') {
self.setCollection(DataModel.Maps.Active)
}
if (id) {
if (self.collection.mapperId !== id) {
// empty the collection if we are trying to load the maps
// collection of a different mapper than we had previously
self.collection.reset()
self.collection.page = 1
self.render()
}
self.collection.mapperId = id
}
if (self.collection.length === 0) {
Loading.show()
self.pending = true
self.collection.getMaps()
} else {
id ? self.fetchUserThenRender() : self.render()
}
},
setCollection: function(collection) { setCollection: function(collection) {
var self = ExploreMaps var self = ExploreMaps
@ -26,55 +58,9 @@ const ExploreMaps = {
self.collection.on('successOnFetch', self.handleSuccess) self.collection.on('successOnFetch', self.handleSuccess)
self.collection.on('errorOnFetch', self.handleError) self.collection.on('errorOnFetch', self.handleError)
}, },
render: function(cb) { render: function() {
var self = ExploreMaps var self = ExploreMaps
ReactApp.render()
if (!self.collection) return
var exploreObj = {
currentUser: Active.Mapper,
section: self.collection.id,
maps: self.collection,
juntoState: Realtime.juntoState,
moreToLoad: self.collection.page !== 'loadedAll',
user: self.collection.id === 'mapper' ? self.mapper : null,
loadMore: self.loadMore,
pending: self.pending,
onStar: function(map) {
$.post('/maps/' + map.id + '/star')
map.set('star_count', map.get('star_count') + 1)
if (DataModel.Stars) DataModel.Stars.push({ user_id: Active.Mapper.id, map_id: map.id })
DataModel.Maps.Starred.add(map)
GlobalUI.notifyUser('Map is now starred')
self.render()
},
onRequest: function(map) {
$.post({
url: `/maps/${map.id}/access_request`
})
GlobalUI.notifyUser('You will be notified by email if request accepted')
},
onFollow: function(map) {
const isFollowing = map.isFollowedBy(Active.Mapper)
$.post({
url: `/maps/${map.id}/${isFollowing ? 'un' : ''}follow`
})
if (isFollowing) {
GlobalUI.notifyUser('You are no longer following this map')
Active.Mapper.unfollowMap(map.id)
} else {
GlobalUI.notifyUser('You are now following this map')
Active.Mapper.followMap(map.id)
}
self.render()
}
}
ReactDOM.render(
React.createElement(Maps, exploreObj),
document.getElementById('explore')
).resize()
if (cb) cb()
Loading.hide() Loading.hide()
}, },
loadMore: function() { loadMore: function() {
@ -85,14 +71,13 @@ const ExploreMaps = {
} }
self.render() self.render()
}, },
handleSuccess: function(cb) { handleSuccess: function() {
var self = ExploreMaps var self = ExploreMaps
self.pending = false self.pending = false
if (self.collection && self.collection.id === 'mapper') { if (self.collection && self.collection.id === 'mapper') {
self.fetchUserThenRender(cb) self.fetchUserThenRender()
} else { } else {
self.render(cb) self.render()
Loading.hide()
} }
}, },
handleError: function() { handleError: function() {
@ -103,8 +88,8 @@ const ExploreMaps = {
var self = ExploreMaps var self = ExploreMaps
if (self.mapper && self.mapper.id === self.collection.mapperId) { if (self.mapper && self.mapper.id === self.collection.mapperId) {
self.render(cb) self.render()
return Loading.hide() return
} }
// first load the mapper object and then call the render function // first load the mapper object and then call the render function
@ -112,14 +97,40 @@ const ExploreMaps = {
url: '/users/' + self.collection.mapperId + '/details.json', url: '/users/' + self.collection.mapperId + '/details.json',
success: function(response) { success: function(response) {
self.mapper = response self.mapper = response
self.render(cb) self.render()
Loading.hide()
}, },
error: function() { error: function() {
self.render(cb) self.render()
Loading.hide()
} }
}) })
},
onStar: function(map) {
$.post('/maps/' + map.id + '/star')
map.set('star_count', map.get('star_count') + 1)
if (DataModel.Stars) DataModel.Stars.push({ user_id: Active.Mapper.id, map_id: map.id })
DataModel.Maps.Starred.add(map)
GlobalUI.notifyUser('Map is now starred')
ReactApp.render()
},
onRequest: function(map) {
$.post({
url: `/maps/${map.id}/access_request`
})
GlobalUI.notifyUser('You will be notified by email if request accepted')
},
onMapFollow: function(map) {
const isFollowing = map.isFollowedBy(Active.Mapper)
$.post({
url: `/maps/${map.id}/${isFollowing ? 'un' : ''}follow`
})
if (isFollowing) {
GlobalUI.notifyUser('You are no longer following this map')
Active.Mapper.unfollowMap(map.id)
} else {
GlobalUI.notifyUser('You are now following this map')
Active.Mapper.followMap(map.id)
}
ReactApp.render()
} }
} }

View file

@ -5,48 +5,39 @@ import ReactDOM from 'react-dom'
import Active from '../Active' import Active from '../Active'
import Visualize from '../Visualize' import Visualize from '../Visualize'
import GlobalUI from '../GlobalUI' import GlobalUI, { ReactApp } from '../GlobalUI'
import ReactTopicCard from '../../components/TopicCard'
const TopicCard = { const TopicCard = {
openTopicCard: null, // stores the topic that's currently open openTopic: null, // stores the topic that's currently open
metacodeSets: [], metacodeSets: [],
updateTopic: () => {},
onTopicFollow: () => {},
redrawCanvas: () => {
Visualize.mGraph.plot()
},
init: function(serverData) { init: function(serverData) {
const self = TopicCard const self = TopicCard
self.metacodeSets = serverData.metacodeSets self.metacodeSets = serverData.metacodeSets
}, },
populateShowCard: function(topic) { populateShowCard: function(topic) {
const self = TopicCard const self = TopicCard
ReactDOM.render( TopicCard.updateTopic = obj => {
React.createElement(ReactTopicCard, { topic.save(obj, { success: topic => self.populateShowCard(topic) })
topic: topic, }
ActiveMapper: Active.Mapper, TopicCard.onTopicFollow = () => {
updateTopic: obj => { const isFollowing = topic.isFollowedBy(Active.Mapper)
topic.save(obj, { success: topic => self.populateShowCard(topic) }) $.post({
}, url: `/topics/${topic.id}/${isFollowing ? 'un' : ''}follow`
onFollow: () => { })
const isFollowing = topic.isFollowedBy(Active.Mapper) if (isFollowing) {
$.post({ GlobalUI.notifyUser('You are no longer following this topic')
url: `/topics/${topic.id}/${isFollowing ? 'un' : ''}follow` Active.Mapper.unfollowTopic(topic.id)
}) } else {
if (isFollowing) { GlobalUI.notifyUser('You are now following this topic')
GlobalUI.notifyUser('You are no longer following this topic') Active.Mapper.followTopic(topic.id)
Active.Mapper.unfollowTopic(topic.id) }
} else { self.populateShowCard(topic)
GlobalUI.notifyUser('You are now following this topic') }
Active.Mapper.followTopic(topic.id)
}
self.populateShowCard(topic)
},
metacodeSets: self.metacodeSets,
redrawCanvas: () => {
Visualize.mGraph.plot()
}
}),
document.getElementById('showcard')
)
// initialize draggability // initialize draggability
$('.showcard').draggable({ $('.showcard').draggable({
handle: '.metacodeImage', handle: '.metacodeImage',
@ -54,12 +45,12 @@ const TopicCard = {
$(this).height('auto') $(this).height('auto')
} }
}) })
ReactApp.render()
}, },
showCard: function(node, opts) { showCard: function(node, opts = {}) {
var self = TopicCard var self = TopicCard
if (!opts) opts = {}
var topic = node.getData('topic') var topic = node.getData('topic')
self.openTopicCard = topic self.openTopic = topic
// populate the card that's about to show with the right topics data // populate the card that's about to show with the right topics data
self.populateShowCard(topic) self.populateShowCard(topic)
return $('.showcard').fadeIn('fast', () => opts.complete && opts.complete()) return $('.showcard').fadeIn('fast', () => opts.complete && opts.complete())
@ -67,7 +58,7 @@ const TopicCard = {
hideCard: function() { hideCard: function() {
var self = TopicCard var self = TopicCard
$('.showcard').fadeOut('fast') $('.showcard').fadeOut('fast')
self.openTopicCard = null self.openTopic = null
} }
} }

View file

@ -8,7 +8,6 @@ import Active from './Active'
import DataModel from './DataModel' import DataModel from './DataModel'
import JIT from './JIT' import JIT from './JIT'
import Loading from './Loading' import Loading from './Loading'
import Router from './Router'
import TopicCard from './Views/TopicCard' import TopicCard from './Views/TopicCard'
const Visualize = { const Visualize = {
@ -198,19 +197,6 @@ const Visualize = {
} }
} }
hold() hold()
// update the url now that the map is ready
clearTimeout(Router.timeoutId)
Router.timeoutId = setTimeout(function() {
var m = Active.Map
var t = Active.Topic
if (m && window.location.pathname !== '/maps/' + m.id) {
Router.navigateAndTrack('/maps/' + m.id)
} else if (t && window.location.pathname !== '/topics/' + t.id) {
Router.navigateAndTrack('/topics/' + t.id)
}
}, 800)
}, },
clearVisualization: function() { clearVisualization: function() {
Visualize.mGraph.graph.empty() Visualize.mGraph.graph.empty()

View file

@ -9,8 +9,7 @@ import DataModel from './DataModel'
import Debug from './Debug' import Debug from './Debug'
import Filter from './Filter' import Filter from './Filter'
import GlobalUI, { import GlobalUI, {
Search, CreateMap, ImportDialog, Account as GlobalUIAccount, ReactApp, Search, CreateMap, ImportDialog, Account as GlobalUIAccount
NotificationIcon
} from './GlobalUI' } from './GlobalUI'
import Import from './Import' import Import from './Import'
import JIT from './JIT' import JIT from './JIT'
@ -23,7 +22,6 @@ import Mouse from './Mouse'
import Organize from './Organize' import Organize from './Organize'
import PasteInput from './PasteInput' import PasteInput from './PasteInput'
import Realtime from './Realtime' import Realtime from './Realtime'
import Router from './Router'
import Selected from './Selected' import Selected from './Selected'
import Settings from './Settings' import Settings from './Settings'
import Synapse from './Synapse' import Synapse from './Synapse'
@ -45,11 +43,11 @@ Metamaps.DataModel = DataModel
Metamaps.Debug = Debug Metamaps.Debug = Debug
Metamaps.Filter = Filter Metamaps.Filter = Filter
Metamaps.GlobalUI = GlobalUI Metamaps.GlobalUI = GlobalUI
Metamaps.GlobalUI.ReactApp = ReactApp
Metamaps.GlobalUI.Search = Search Metamaps.GlobalUI.Search = Search
Metamaps.GlobalUI.CreateMap = CreateMap Metamaps.GlobalUI.CreateMap = CreateMap
Metamaps.GlobalUI.Account = GlobalUIAccount Metamaps.GlobalUI.Account = GlobalUIAccount
Metamaps.GlobalUI.ImportDialog = ImportDialog Metamaps.GlobalUI.ImportDialog = ImportDialog
Metamaps.GlobalUI.NotificationIcon = NotificationIcon
Metamaps.Import = Import Metamaps.Import = Import
Metamaps.JIT = JIT Metamaps.JIT = JIT
Metamaps.Listeners = Listeners Metamaps.Listeners = Listeners
@ -64,7 +62,6 @@ Metamaps.Mouse = Mouse
Metamaps.Organize = Organize Metamaps.Organize = Organize
Metamaps.PasteInput = PasteInput Metamaps.PasteInput = PasteInput
Metamaps.Realtime = Realtime Metamaps.Realtime = Realtime
Metamaps.Router = Router
Metamaps.Selected = Selected Metamaps.Selected = Selected
Metamaps.Settings = Settings Metamaps.Settings = Settings
Metamaps.Synapse = Synapse Metamaps.Synapse = Synapse
@ -86,26 +83,6 @@ document.addEventListener('DOMContentLoaded', function() {
Metamaps[prop].init(Metamaps.ServerData) Metamaps[prop].init(Metamaps.ServerData)
} }
} }
// load whichever page you are on
if (Metamaps.currentSection === 'explore') {
const capitalize = Metamaps.currentPage.charAt(0).toUpperCase() + Metamaps.currentPage.slice(1)
Views.ExploreMaps.setCollection(DataModel.Maps[capitalize])
if (Metamaps.currentPage === 'mapper') {
Views.ExploreMaps.fetchUserThenRender()
} else {
Views.ExploreMaps.render()
}
GlobalUI.showDiv('#explore')
} else if (Metamaps.currentSection === '' && Active.Mapper) {
Views.ExploreMaps.setCollection(DataModel.Maps.Active)
Views.ExploreMaps.render()
GlobalUI.showDiv('#explore')
} else if (Active.Map || Active.Topic) {
Loading.show()
JIT.prepareVizData()
GlobalUI.showDiv('#infovis')
}
}) })
export default Metamaps export default Metamaps

View file

@ -0,0 +1,38 @@
import React, { Component, PropTypes } from 'react'
class AccountMenu extends Component {
static propTypes = {
currentUser: PropTypes.object
}
render () {
return <div>
<img className="sidebarAccountImage" src="https://metamaps-live.s3.amazonaws.com/users/images/555/629/996/sixtyfour/11835c3.png?1417298429" alt="11835c3" width="48" height="48" />
<h3 className="accountHeader">Connor</h3>
<ul>
<li className="accountListItem accountSettings">
<div className="accountIcon"></div>
<a href="https://metamaps.cc/users/555629996/edit">Settings</a>
</li>
<li className="accountListItem accountAdmin">
<div className="accountIcon"></div>
<a href="/metacodes">Admin</a>
</li>
<li className="accountListItem accountApps">
<div className="accountIcon"></div>
<a href="/oauth/authorized_applications">Apps</a>
</li>
<li className="accountListItem accountInvite openLightbox" data-open="invite">
<div className="accountIcon"></div>
<span>Share Invite</span>
</li>
<li className="accountListItem accountLogout">
<div className="accountIcon"></div>
<a id="Logout" href="/logout">Sign Out</a>
</li>
</ul>
</div>
}
}
export default AccountMenu

View file

@ -0,0 +1,12 @@
import React, { Component, PropTypes } from 'react'
class FilterBox extends Component {
static propTypes = {
}
render () {
return null
}
}
export default FilterBox

View file

@ -0,0 +1,36 @@
import React, { Component, PropTypes } from 'react'
class LoginForm extends Component {
static propTypes = {
loginFormAuthToken: PropTypes.string
}
render () {
return <form className="loginAnywhere" id="new_user" action="/login" acceptCharset="UTF-8" method="post">
<input name="utf8" type="hidden" value="✓" />
<input type="hidden" name="authenticity_token" value="9z5D3vUGKM5ExKJ0CmhweE8qysvUqjFMwgMvbYXIlrnvg9sqJWIWgCt9lq28NZgyCaNudF+w+dRPD1pybeT4mg==" />
<div className="accountImage"></div>
<div className="accountInput accountEmail">
<input placeholder="Email" type="email" name="user[email]" id="user_email" />
</div>
<div className="accountInput accountPassword">
<input placeholder="Password" type="password" name="user[password]" id="user_password" />
</div>
<div className="accountSubmit">
<input type="submit" name="commit" value="SIGN IN" />
</div>
<div className="accountRememberMe">
<label htmlFor="user_remember_me">Stay signed in</label>
<input name="user[remember_me]" type="hidden" value="0" />
<input type="checkbox" value="1" name="user[remember_me]" id="user_remember_me" />
<div className="clearfloat"></div>
</div>
<div className="clearfloat"></div>
<div className="accountForgotPass">
<a href="/users/password/new">Forgot password?</a>
</div>
</form>
}
}
export default LoginForm

View file

@ -1,6 +1,11 @@
import React, { PropTypes, Component } from 'react' import React, { PropTypes, Component } from 'react'
class NotificationIcon extends Component { class NotificationIcon extends Component {
static propTypes = {
unreadNotificationsCount: PropTypes.number
}
constructor(props) { constructor(props) {
super(props) super(props)
@ -31,8 +36,4 @@ class NotificationIcon extends Component {
} }
} }
NotificationIcon.propTypes = {
unreadNotificationsCount: PropTypes.number
}
export default NotificationIcon export default NotificationIcon

View file

@ -0,0 +1,14 @@
import React, { Component, PropTypes } from 'react'
class Toast extends Component {
static propTypes = {
message: PropTypes.string
}
render () {
const html = {__html: this.props.html}
return <p id="toast" className="toast" dangerouslySetInnerHTML={html} />
}
}
export default Toast

View file

@ -0,0 +1,43 @@
import React, { Component, PropTypes } from 'react'
import { Link } from 'react-router'
class UpperLeftUI extends Component {
static propTypes = {
currentUser: PropTypes.object
}
render () {
return <div className="upperLeftUI">
<div className="homeButton">
<Link to="/">METAMAPS</Link>
</div>
<div className="sidebarSearch">
<input type="text" className="sidebarSearchField" placeholder="Search for topics, maps, and mappers..." />
<div id="searchLoading"></div>
<div className="sidebarSearchIcon"></div>
<div className="clearfloat"></div>
</div>
<div className="viewOnly">
<div className="eyeball">View Only</div>
<div className="requestAccess requestNotice">Request Access</div>
<div className="requestPending requestNotice">Request Pending</div>
<div className="requestNotAccepted requestNotice">Request Not Accepted</div>
</div>
<div className="clearfloat"></div>
</div>
}
}
export default UpperLeftUI
/*
<% request = current_user && @map && @allrequests.find{|a| a.user == current_user}
className = (@map and not policy(@map).update?) ? 'isViewOnly ' : ''
if @map
className += 'sendRequest' if not request
className += 'sentRequest' if request and not request.answered
className += 'requestDenied' if request and request.answered and not request.approved
end %>
*/

View file

@ -0,0 +1,66 @@
import React, { Component, PropTypes } from 'react'
import FilterBox from './FilterBox'
import AccountMenu from './AccountMenu'
import LoginForm from './LoginForm'
import NotificationIcon from './NotificationIcon'
class UpperRightUI extends Component {
static propTypes = {
currentUser: PropTypes.object,
signInPage: PropTypes.bool,
unreadNotificationsCount: PropTypes.number
}
static contextTypes = {
location: PropTypes.object
}
render () {
const { currentUser, signInPage, unreadNotificationsCount } = this.props
const { location } = this.context
// TODO: get these map related things out of this file
// TODO: only show 'import' if you have edit rights for the map
const isMapPage = location.pathname.slice(0, 6) === '/maps/'
return <div className="upperRightUI">
{isMapPage && <div className="mapElement upperRightEl upperRightMapButtons">
{currentUser && <div className="importDialog upperRightEl upperRightIcon mapElement openLightbox" data-open="import-dialog-lightbox">
<div className="tooltipsUnder">
Import Data
</div>
</div>}
<div className="sidebarFilter upperRightEl">
<div className="sidebarFilterIcon upperRightIcon"><div className="tooltipsUnder">Filter</div></div>
<div className="sidebarFilterBox upperRightBox">
<FilterBox />
</div>
</div>
{currentUser && <div className="sidebarFork upperRightEl">
<div className="sidebarForkIcon upperRightIcon"><div className="tooltipsUnder">Save To New Map</div></div>
</div>}
<div className="clearfloat"></div>
</div>}
{currentUser && <a href="/maps/new" target="_blank" className="addMap upperRightEl upperRightIcon">
<div className="tooltipsUnder">
Create New Map
</div>
</a>}
{currentUser && <span id="notification_icon">
<NotificationIcon unreadNotificationsCount={unreadNotificationsCount} />
</span>}
{!signInPage && <div className="sidebarAccount upperRightEl">
<div className="sidebarAccountIcon"><div className="tooltipsUnder">Account</div>
{currentUser && <img src={currentUser.get('image')} />}
{!currentUser && 'SIGN IN'}
{!currentUser && <div className="accountInnerArrow"></div>}
</div>
<div className="sidebarAccountBox upperRightBox">
{currentUser ? <AccountMenu /> : <LoginForm />}
</div>
</div>}
<div className="clearfloat"></div>
</div>
}
}
export default UpperRightUI

View file

@ -0,0 +1,54 @@
import React, { Component, PropTypes } from 'react'
import Toast from './Toast'
import UpperLeftUI from './UpperLeftUI'
import UpperRightUI from './UpperRightUI'
class App extends Component {
static propTypes = {
children: PropTypes.object,
toast: PropTypes.string,
unreadNotificationsCount: PropTypes.number,
location: PropTypes.object
}
static childContextTypes = {
currentUser: PropTypes.object,
location: PropTypes.object
}
getChildContext () {
const { route, location } = this.props
return {currentUser: route.currentUser, location}
}
render () {
const { toast, currentUser, unreadNotificationsCount } = this.props
return <div className="wrapper" id="wrapper">
<UpperLeftUI currentUser={currentUser} />
<UpperRightUI currentUser={currentUser} unreadNotificationsCount={unreadNotificationsCount} />
<Toast message={toast} />
{currentUser && <a className='feedback-icon' target='_blank' href='https://hylo.com/c/metamaps'></a>}
{this.props.children}
</div>
}
}
export default App
/*
<% classes = action_name == "home" ? "homePage" : ""
classes += action_name == "home" && authenticated? ? " explorePage" : ""
classes += controller_name == "maps" && action_name == "index" ? " explorePage" : ""
if controller_name == "maps" && action_name == "show"
classes += " mapPage"
if policy(@map).update?
classes += " canEditMap"
end
if @map.permission == "commons"
classes += " commonsMap"
end
end
classes += controller_name == "topics" && action_name == "show" ? " topicPage" : ""
%>
*/

View file

@ -1,6 +1,6 @@
import React from 'react' import React from 'react'
import Autolinker from 'autolinker' import Autolinker from 'autolinker'
import Util from '../../Metamaps/Util' import Util from '../../../Metamaps/Util'
const linker = new Autolinker({ newWindow: true, truncate: 50, email: false, phone: false }) const linker = new Autolinker({ newWindow: true, truncate: 50, email: false, phone: false })

View file

@ -3,7 +3,7 @@ import Unread from './Unread'
import Participant from './Participant' import Participant from './Participant'
import Message from './Message' import Message from './Message'
import NewMessage from './NewMessage' import NewMessage from './NewMessage'
import Util from '../../Metamaps/Util' import Util from '../../../Metamaps/Util'
function makeList(messages) { function makeList(messages) {
let currentHeader let currentHeader

View file

@ -0,0 +1,12 @@
import React, { Component, PropTypes } from 'react'
class MapInfoBox extends Component {
static propTypes = {
}
render () {
return null
}
}
export default MapInfoBox

View file

@ -0,0 +1,114 @@
import React, { Component, PropTypes } from 'react'
import MapInfoBox from './MapInfoBox'
import MapChat from './MapChat'
import TopicCard from '../TopicCard'
class MapView extends Component {
static propTypes = {
mapId: PropTypes.string,
map: PropTypes.object,
mapIsStarred: PropTypes.bool,
currentUser: PropTypes.object,
endActiveMap: PropTypes.func,
launchNewMap: PropTypes.func
}
constructor(props) {
super(props)
this.state = {}
}
componentDidMount() {
window && window.addEventListener('resize', this.resize)
this.resize()
}
componentDidUpdate(prevProps) {
const oldMapId = prevProps.mapId
const { mapId, endActiveMap, launchNewMap } = this.props
if (!oldMapId && mapId) launchNewMap(mapId)
else if (oldMapId && mapId && oldMapId !== mapId) {
endActiveMap()
launchNewMap(mapId)
}
else if (oldMapId && !mapId) endActiveMap()
}
componentWillUnmount() {
window && window.removeEventListener('resize', this.resize)
}
resize = () => {
}
render = () => {
const { mapIsStarred } = this.props
const starclassName = mapIsStarred ? 'starred' : ''
const tooltip = mapIsStarred ? 'Star' : 'Unstar'
return <div className="mapWrapper">
<div id="infovis" />
<div className="showcard mapElement mapElementHidden" id="showcard">
<TopicCard {...this.props} />
</div>
<div id="chat-box-wrapper">
<MapChat {...this.props} />
</div>
<div className="mapControls mapElement">
<div className="zoomExtents mapControl"><div className="tooltips">Center View</div></div>
<div className="zoomIn mapControl"><div className="tooltips">Zoom In</div></div>
<div className="zoomOut mapControl"><div className="tooltips">Zoom Out</div></div>
</div>
<div className="infoAndHelp">
<MapInfoBox />
<div className={`starMap infoElement mapElement ${starclassName}`}>
<div className="tooltipsAbove">{tooltip}</div>
</div>
<div className="mapInfoIcon infoElement mapElement">
<div className="tooltipsAbove">Map Info</div>
</div>
<div className="openCheatsheet openLightbox infoElement mapElement" data-open="cheatsheet">
<div className="tooltipsAbove">Help</div>
</div>
<div className="clearfloat"></div>
</div>
</div>
}
}
export default MapView
/*
<div className="showcard mapElement mapElementHidden" id="showcard"></div>
<% if authenticated? %>
<% # for creating and pulling in topics and synapses %>
<% if controller_name == 'maps' && action_name == "conversation" %>
<%= render :partial => 'maps/newtopicsecret' %>
<% else %>
<%= render :partial => 'maps/newtopic' %>
<% end %>
<%= render :partial => 'maps/newsynapse' %>
<% # for populating the change metacode list on the topic card %>
<%= render :partial => 'shared/metacodeoptions' %>
<% end %>
<%= render :partial => 'layouts/lowermapelements' %>
<div id="loading"></div>
<div id="instructions">
<div className="addTopic">
Double-click to<br>add a topic
</div>
<div className="tabKey">
Use Tab & Shift+Tab to select a metacode
</div>
<div className="enterKey">
Press Enter to add the topic
</div>
</div>
*/

View file

@ -1,4 +1,5 @@
import React, { Component, PropTypes } from 'react' import React, { Component, PropTypes } from 'react'
import { Link } from 'react-router'
import _ from 'lodash' import _ from 'lodash'
const MapLink = props => { const MapLink = props => {
@ -9,10 +10,10 @@ const MapLink = props => {
} }
return ( return (
<a { ...otherProps } href={href} className={linkClass}> <Link { ...otherProps } to={href} className={linkClass}>
<div className="exploreMapsIcon"></div> <div className="exploreMapsIcon"></div>
{text} {text}
</a> </Link>
) )
} }
@ -39,31 +40,26 @@ class Header extends Component {
<MapLink show={explore} <MapLink show={explore}
href={signedIn ? '/' : '/explore/active'} href={signedIn ? '/' : '/explore/active'}
linkClass={activeClass('active')} linkClass={activeClass('active')}
data-router="true"
text="All Maps" text="All Maps"
/> />
<MapLink show={signedIn && explore} <MapLink show={signedIn && explore}
href="/explore/mine" href="/explore/mine"
linkClass={activeClass('my')} linkClass={activeClass('my')}
data-router="true"
text="My Maps" text="My Maps"
/> />
<MapLink show={signedIn && explore} <MapLink show={signedIn && explore}
href="/explore/shared" href="/explore/shared"
linkClass={activeClass('shared')} linkClass={activeClass('shared')}
data-router="true"
text="Shared With Me" text="Shared With Me"
/> />
<MapLink show={signedIn && explore} <MapLink show={signedIn && explore}
href="/explore/starred" href="/explore/starred"
linkClass={activeClass('starred')} linkClass={activeClass('starred')}
data-router="true"
text="Starred By Me" text="Starred By Me"
/> />
<MapLink show={!signedIn && explore} <MapLink show={!signedIn && explore}
href="/explore/featured" href="/explore/featured"
linkClass={activeClass('featured')} linkClass={activeClass('featured')}
data-router="true"
text="Featured Maps" text="Featured Maps"
/> />

View file

@ -1,4 +1,5 @@
import React, { Component, PropTypes } from 'react' import React, { Component, PropTypes } from 'react'
import { Link } from 'react-router'
import { find, values } from 'lodash' import { find, values } from 'lodash'
import Util from '../../Metamaps/Util' import Util from '../../Metamaps/Util'
@ -78,7 +79,7 @@ const Metadata = (props) => {
} }
const checkAndWrapInA = (shouldWrap, classString, mapId, element) => { const checkAndWrapInA = (shouldWrap, classString, mapId, element) => {
if (shouldWrap) return <a className={ classString } href={ `/maps/${mapId}` } data-router="true">{ element }</a> if (shouldWrap) return <Link className={ classString } to={ `/maps/${mapId}` } >{ element }</Link>
else return element else return element
} }

View file

@ -12,6 +12,25 @@ const MAX_COLUMNS = 4
class Maps extends Component { class Maps extends Component {
static propTypes = {
section: PropTypes.string,
maps: PropTypes.object,
juntoState: PropTypes.object,
moreToLoad: PropTypes.bool,
user: PropTypes.object,
currentUser: PropTypes.object,
loadMore: PropTypes.func,
pending: PropTypes.bool,
setCollection: PropTypes.func,
onStar: PropTypes.func,
onRequest: PropTypes.func,
onMapFollow: PropTypes.func
}
static contextTypes = {
location: PropTypes.object
}
constructor(props) { constructor(props) {
super(props) super(props)
this.state = { mapsWidth: 0 } this.state = { mapsWidth: 0 }
@ -19,7 +38,7 @@ class Maps extends Component {
componentDidMount() { componentDidMount() {
window && window.addEventListener('resize', this.resize) window && window.addEventListener('resize', this.resize)
this.refs.maps.addEventListener('scroll', throttle(this.scroll, 500, { leading: true, trailing: false })) this.refs.maps && this.refs.maps.addEventListener('scroll', throttle(this.scroll, 500, { leading: true, trailing: false }))
this.resize() this.resize()
} }
@ -29,6 +48,7 @@ class Maps extends Component {
resize = () => { resize = () => {
const { maps, user, currentUser } = this.props const { maps, user, currentUser } = this.props
if (!maps) return
const numCards = maps.length + (user || currentUser ? 1 : 0) const numCards = maps.length + (user || currentUser ? 1 : 0)
const mapSpaces = Math.floor(document.body.clientWidth / MAP_WIDTH) const mapSpaces = Math.floor(document.body.clientWidth / MAP_WIDTH)
const mapsWidth = document.body.clientWidth <= MOBILE_VIEW_BREAKPOINT const mapsWidth = document.body.clientWidth <= MOBILE_VIEW_BREAKPOINT
@ -46,41 +66,30 @@ class Maps extends Component {
} }
render = () => { render = () => {
const { maps, currentUser, juntoState, pending, section, user, onStar, onRequest, onFollow } = this.props const { maps, currentUser, juntoState, pending, section, user, onStar, onRequest, onMapFollow } = this.props
const style = { width: this.state.mapsWidth + 'px' } const style = { width: this.state.mapsWidth + 'px' }
const mobile = document && document.body.clientWidth <= MOBILE_VIEW_BREAKPOINT const mobile = document && document.body.clientWidth <= MOBILE_VIEW_BREAKPOINT
if (!maps) return null // do loading here instead
return ( return (
<div> <div>
<div id='exploreMaps' ref='maps'> <div id='exploreMaps' ref='maps'>
<div style={ style }> <div style={ style }>
{ user ? <MapperCard user={ user } /> : null } { user ? <MapperCard user={ user } /> : null }
{ currentUser && !user && !(pending && maps.length === 0) ? <div className="map newMap"><a href="/maps/new"><div className="newMapImage"></div><span>Create new map...</span></a></div> : null } { currentUser && !user && !(pending && maps.length === 0) ? <div className="map newMap"><a href="/maps/new"><div className="newMapImage"></div><span>Create new map...</span></a></div> : null }
{ maps.models.map(map => <MapCard key={ map.id } map={ map } mobile={ mobile } juntoState={ juntoState } currentUser={ currentUser } onStar={ onStar } onRequest={ onRequest } onFollow={ onFollow } />) } { maps.models.map(map => <MapCard key={ map.id } map={ map } mobile={ mobile } juntoState={ juntoState } currentUser={ currentUser } onStar={ onStar } onRequest={ onRequest } onMapFollow={ onMapFollow } />) }
<div className='clearfloat'></div> <div className='clearfloat'></div>
</div> </div>
</div> </div>
<Header signedIn={ !!currentUser } <Header signedIn={ !!currentUser }
section={ section } section={ section }
user={ user } user={ user }
setCollection={ setCollection }
/> />
</div> </div>
) )
} }
} }
Maps.propTypes = {
section: PropTypes.string.isRequired,
maps: PropTypes.object.isRequired,
juntoState: PropTypes.object.isRequired,
moreToLoad: PropTypes.bool.isRequired,
user: PropTypes.object,
currentUser: PropTypes.object,
loadMore: PropTypes.func,
pending: PropTypes.bool.isRequired,
onStar: PropTypes.func.isRequired,
onRequest: PropTypes.func.isRequired,
onFollow: PropTypes.func.isRequired
}
export default Maps export default Maps

View file

@ -2,8 +2,8 @@ import React, { PropTypes, Component } from 'react'
class Follow extends Component { class Follow extends Component {
render = () => { render = () => {
const { isFollowing, onFollow } = this.props const { isFollowing, onTopicFollow } = this.props
return <div className='topicFollow' onClick={onFollow}> return <div className='topicFollow' onClick={onTopicFollow}>
{isFollowing ? 'Unfollow' : 'Follow'} {isFollowing ? 'Unfollow' : 'Follow'}
</div> </div>
} }
@ -11,7 +11,7 @@ class Follow extends Component {
Follow.propTypes = { Follow.propTypes = {
isFollowing: PropTypes.bool, isFollowing: PropTypes.bool,
onFollow: PropTypes.func onTopicFollow: PropTypes.func
} }
export default Follow export default Follow

View file

@ -7,12 +7,15 @@ import Attachments from './Attachments'
import Follow from './Follow' import Follow from './Follow'
import Util from '../../Metamaps/Util' import Util from '../../Metamaps/Util'
class ReactTopicCard extends Component { class ReactTopicCard extends Component {
render = () => { render = () => {
const { topic, ActiveMapper, onFollow } = this.props const { currentUser, onTopicFollow } = this.props
const authorizedToEdit = topic.authorizeToEdit(ActiveMapper) const topic = this.props.openTopic
const isFollowing = topic.isFollowedBy(ActiveMapper)
if (!topic) return null
const authorizedToEdit = topic.authorizeToEdit(currentUser)
const isFollowing = topic.isFollowedBy(currentUser)
const hasAttachment = topic.get('link') && topic.get('link') !== '' const hasAttachment = topic.get('link') && topic.get('link') !== ''
let classname = 'permission' let classname = 'permission'
@ -21,7 +24,7 @@ class ReactTopicCard extends Component {
} else { } else {
classname += ' cannotEdit' classname += ' cannotEdit'
} }
if (topic.authorizePermissionChange(ActiveMapper)) classname += ' yourTopic' if (topic.authorizePermissionChange(currentUser)) classname += ' yourTopic'
return ( return (
<div className={classname}> <div className={classname}>
@ -31,7 +34,7 @@ class ReactTopicCard extends Component {
onChange={this.props.updateTopic} onChange={this.props.updateTopic}
/> />
<Links topic={topic} <Links topic={topic}
ActiveMapper={this.props.ActiveMapper} ActiveMapper={this.props.currentUser}
updateTopic={this.props.updateTopic} updateTopic={this.props.updateTopic}
metacodeSets={this.props.metacodeSets} metacodeSets={this.props.metacodeSets}
redrawCanvas={this.props.redrawCanvas} redrawCanvas={this.props.redrawCanvas}
@ -44,7 +47,7 @@ class ReactTopicCard extends Component {
authorizedToEdit={authorizedToEdit} authorizedToEdit={authorizedToEdit}
updateTopic={this.props.updateTopic} updateTopic={this.props.updateTopic}
/> />
{Util.isTester(ActiveMapper) && <Follow isFollowing={isFollowing} onFollow={onFollow} />} {Util.isTester(currentUser) && <Follow isFollowing={isFollowing} onTopicFollow={onTopicFollow} />}
<div className="clearfloat"></div> <div className="clearfloat"></div>
</div> </div>
</div> </div>
@ -53,10 +56,10 @@ class ReactTopicCard extends Component {
} }
ReactTopicCard.propTypes = { ReactTopicCard.propTypes = {
topic: PropTypes.object, openTopic: PropTypes.object,
ActiveMapper: PropTypes.object, currentUser: PropTypes.object,
updateTopic: PropTypes.func, updateTopic: PropTypes.func,
onFollow: PropTypes.func, onTopicFollow: PropTypes.func,
metacodeSets: PropTypes.arrayOf(PropTypes.shape({ metacodeSets: PropTypes.arrayOf(PropTypes.shape({
name: PropTypes.string, name: PropTypes.string,
metacodes: PropTypes.arrayOf(PropTypes.shape({ metacodes: PropTypes.arrayOf(PropTypes.shape({

View file

@ -0,0 +1,20 @@
import React from 'react'
import { Route, IndexRoute } from 'react-router'
import App from './App'
import Maps from './Maps'
import MapView from './MapView'
export default function makeRoutes () {
return <Route path="/" component={App} >
<IndexRoute component={Maps} />
<Route path="explore">
<Route path="active" component={Maps} />
<Route path="featured" component={Maps} />
<Route path="mine" component={Maps} />
<Route path="shared" component={Maps} />
<Route path="starred" component={Maps} />
<Route path="mapper/:id" component={Maps} />
</Route>
<Route path="maps/:id" component={MapView} />
</Route>
}

View file

@ -46,6 +46,7 @@
"react-dom": "15.4.2", "react-dom": "15.4.2",
"react-dropzone": "3.9.1", "react-dropzone": "3.9.1",
"react-onclickoutside": "5.9.0", "react-onclickoutside": "5.9.0",
"react-router": "^3.0.2",
"redux": "3.6.0", "redux": "3.6.0",
"riek": "1.0.7", "riek": "1.0.7",
"simplewebrtc": "2.2.2", "simplewebrtc": "2.2.2",