react-router and rebuild app structure in react (#1091)
* initial restructuring * stuff * lock version number * just keep using current mapinfobox * fix map upperRightUI layout * make mapsWidth work and add mobile * remove filterBoxOpen for now * redo the mobile menu in react * get account menu and invite lightbox working * fixed maps scrolling * make other routes work * fix signed out home page * fix accountbox toggling * add metacode edit routes * lots of fixes * fix map chat layout and tab bug * improve topic card readability and fix dragging bug * fixup mapchat stuff * fix up navigation to use react-router * jquery no longer handling access requests * handle case where user hasn't loaded yet * this shouldn't have been removed * add frame for topic view * rewrite map instructions * fix toast (and sign out bug) * fix apps pages and missing routes * made our request invite page look nice * filter box in react * forgot to add one proptype * remove extra comments * handle page title and mobile title updates * reenable google analytics * make filterbox use onclickoutside * reenable topic view in react * fix csrf auth token * fix little homepage styling issue * try putting preparevizdata in a timeout * installing render log to count * little fixes * fixup filters * make filter map function names more readable * eslint helps * renaming for clarity * use onclickoutside for account/sign in box * add some logging to see whether this is source of many renders * turns out chatview was heavily hogging memory * tiimeout not needed
This commit is contained in:
parent
33276444c7
commit
47a74dd77b
80 changed files with 1934 additions and 1911 deletions
|
@ -193,10 +193,6 @@ button.button.btn-no:hover {
|
|||
display: block;
|
||||
width: 830px;
|
||||
}
|
||||
.requestInvite {
|
||||
display: block;
|
||||
margin: -720px auto 0;
|
||||
}
|
||||
.new_session,
|
||||
.new_user,
|
||||
.edit_user,
|
||||
|
@ -672,9 +668,21 @@ label {
|
|||
position: relative;
|
||||
/*overflow:hidden; */
|
||||
}
|
||||
.main.compressed {
|
||||
width: calc(100% - 300px);
|
||||
.compressed {
|
||||
.upperRightUI {
|
||||
right: 324px;
|
||||
}
|
||||
.upperRightMapButtons {
|
||||
right: 434px;
|
||||
}
|
||||
.mapControls {
|
||||
right: 324px;
|
||||
}
|
||||
.infoAndHelp {
|
||||
right: 370px;
|
||||
}
|
||||
}
|
||||
|
||||
#infovis-canvas {
|
||||
-webkit-touch-callout: none;
|
||||
-webkit-user-select: none;
|
||||
|
@ -775,9 +783,9 @@ label {
|
|||
}
|
||||
.sidebarAccountIcon img {
|
||||
border-radius: 16px;
|
||||
width: 32px;
|
||||
}
|
||||
.sidebarAccountBox {
|
||||
display: none;
|
||||
height: auto;
|
||||
}
|
||||
.authenticated .sidebarAccountBox {
|
||||
|
@ -1039,7 +1047,6 @@ label[for="user_remember_me"] {
|
|||
}
|
||||
|
||||
.sidebarFilterBox {
|
||||
display:none;
|
||||
width: 319px;
|
||||
padding: 16px 0;
|
||||
overflow-y: auto;
|
||||
|
@ -3058,14 +3065,16 @@ and it won't be important on password protected instances */
|
|||
|
||||
/* request */
|
||||
|
||||
#wrapper .requestInvite {
|
||||
.requestInvite {
|
||||
width: 700px;
|
||||
margin: 0 auto;
|
||||
padding: 0 0 60px 0;
|
||||
background: #FFFFFF;
|
||||
color: white;
|
||||
height: 100%;
|
||||
overflow: hidden;
|
||||
height: calc(100% - 52px);
|
||||
z-index: 1;
|
||||
position: relative;
|
||||
left: 50%;
|
||||
margin-left: -350px;
|
||||
margin-top: 52px;
|
||||
}
|
||||
|
||||
.home_bg {
|
||||
|
@ -3148,4 +3157,4 @@ script.data-gratipay-username {
|
|||
background: #FFF;
|
||||
cursor: pointer;
|
||||
font-family: din-regular;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -54,7 +54,6 @@
|
|||
width:100%;
|
||||
height:100%;
|
||||
position: absolute;
|
||||
display: none;
|
||||
}
|
||||
|
||||
.showcard .permission {
|
||||
|
@ -233,7 +232,7 @@
|
|||
background-repeat:no-repeat;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
.contributor {
|
||||
bottom: 7px;
|
||||
margin-left: 40px;
|
||||
|
|
|
@ -30,6 +30,7 @@
|
|||
height: 100%;
|
||||
box-sizing: border-box;
|
||||
padding-top: 92px;
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
/*.animations {
|
||||
|
@ -46,26 +47,9 @@
|
|||
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 {
|
||||
display: none;
|
||||
width: 28px;
|
||||
height: 28px;
|
||||
position: fixed;
|
||||
|
@ -184,10 +168,10 @@
|
|||
}
|
||||
|
||||
.upperRightMapButtons {
|
||||
top: -42px; /* puts it just offscreen */
|
||||
right: 134px;
|
||||
}
|
||||
.mapPage .upperRightMapButtons, .topicPage .upperRightMapButtons {
|
||||
top: 0;
|
||||
.unauthenticated .upperRightMapButtons {
|
||||
right: 115px;
|
||||
}
|
||||
|
||||
.upperRightIcon {
|
||||
|
@ -197,13 +181,7 @@
|
|||
background-repeat: no-repeat;
|
||||
cursor: pointer;
|
||||
}
|
||||
.mapPage .mapElement .importDialog {
|
||||
display: none;
|
||||
background-position: 0 0;
|
||||
}
|
||||
.mapPage.canEditMap .mapElement .importDialog {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.sidebarFilterIcon {
|
||||
background-position: -32px 0;
|
||||
}
|
||||
|
@ -236,6 +214,14 @@
|
|||
|
||||
/* end upperRightUI */
|
||||
|
||||
/* map wrapper */
|
||||
.mapWrapper {
|
||||
position:absolute;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
/* end map wrapper */
|
||||
|
||||
/* yield */
|
||||
|
||||
|
@ -356,22 +342,15 @@
|
|||
|
||||
/* infoAndHelp */
|
||||
|
||||
.mapPage .infoAndHelp, .topicPage .infoAndHelp {
|
||||
right: 70px;
|
||||
}
|
||||
.mapPage .openCheatsheet .tooltipsAbove, .topicPage .openCheatsheet .tooltipsAbove {
|
||||
.openCheatsheet .tooltipsAbove {
|
||||
right: 1px;
|
||||
left: auto;
|
||||
}
|
||||
|
||||
.unauthenticated .homePage .infoAndHelp {
|
||||
display:none;
|
||||
}
|
||||
|
||||
.infoAndHelp {
|
||||
position: absolute;
|
||||
bottom: 20px;
|
||||
right: 20px;
|
||||
right: 70px;
|
||||
z-index: 3;
|
||||
width: auto;
|
||||
font-style: italic;
|
||||
|
@ -392,16 +371,12 @@
|
|||
}
|
||||
.mapInfoIcon {
|
||||
position: relative;
|
||||
top: 56px; /* puts it just offscreen */
|
||||
background-image: url(<%= asset_path('mapinfo_sprite.png') %>);
|
||||
background-repeat:no-repeat;
|
||||
background-image: url(<%= asset_path('mapinfo_sprite.png') %>);
|
||||
background-repeat:no-repeat;
|
||||
}
|
||||
.mapInfoIcon:hover {
|
||||
background-position: 0 -32px;
|
||||
}
|
||||
.mapPage .mapInfoIcon {
|
||||
top: 0;
|
||||
}
|
||||
|
||||
.starMap {
|
||||
background-image: url(<%= asset_path('starmap_sprite.png') %>);
|
||||
|
@ -419,9 +394,6 @@
|
|||
background-position: 0 0;
|
||||
}
|
||||
|
||||
.unauthenticated .mapPage .starMap {
|
||||
display: none;
|
||||
}
|
||||
/* end infoAndHelp */
|
||||
|
||||
|
||||
|
@ -430,24 +402,17 @@
|
|||
.mapControls {
|
||||
position: absolute;
|
||||
bottom: 24px;
|
||||
right:-32px; /* puts it just offscreen */
|
||||
right:24px;
|
||||
width:32px;
|
||||
z-index: 3;
|
||||
}
|
||||
.mapPage .mapControls, .topicPage .mapControls {
|
||||
right: 24px;
|
||||
}
|
||||
|
||||
.topicPage .zoomExtents {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.mapControl {
|
||||
width:32px;
|
||||
height:32px;
|
||||
background-color: #424242;
|
||||
background-repeat: no-repeat;
|
||||
background-position: 0 0;
|
||||
background-repeat: no-repeat;
|
||||
background-position: 0 0;
|
||||
cursor:pointer;
|
||||
}
|
||||
|
||||
|
@ -587,10 +552,6 @@
|
|||
left: -8px;
|
||||
}
|
||||
|
||||
.openCheatsheet .tooltipsAbove {
|
||||
left: -4px;
|
||||
}
|
||||
|
||||
.sidebarAccountIcon .tooltipsUnder {
|
||||
margin-left: -12px;
|
||||
margin-top: 40px;
|
||||
|
@ -671,8 +632,11 @@
|
|||
|
||||
/* explore maps */
|
||||
|
||||
#explore {
|
||||
display: none;
|
||||
#react-app {
|
||||
position: absolute;
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
#exploreMaps {
|
||||
|
@ -691,8 +655,13 @@
|
|||
display: block;
|
||||
}
|
||||
|
||||
.appsPage #exploreMapsHeader {
|
||||
display: block;
|
||||
.requestInviteHeader {
|
||||
position: absolute;
|
||||
width: 100%;
|
||||
z-index:2;
|
||||
background-color:#FAFAFA;
|
||||
height: 52px;
|
||||
box-shadow: 0px 3px 3px rgba(0,0,0,0.23), 0 3px 3px rgba(0,0,0,0.16);
|
||||
}
|
||||
|
||||
#exploreMapsHeader {
|
||||
|
@ -829,7 +798,6 @@
|
|||
height: 80px;
|
||||
font-family: 'din-regular', helvetica, sans-serif;
|
||||
font-size: 32px;
|
||||
display: none;
|
||||
text-align: center;
|
||||
color: #999999;
|
||||
z-index: 0;
|
||||
|
@ -845,7 +813,6 @@
|
|||
/* toast */
|
||||
|
||||
.toast {
|
||||
display: none;
|
||||
position: fixed;
|
||||
bottom: 20px;
|
||||
left: 20px;
|
||||
|
|
|
@ -1,7 +1,3 @@
|
|||
#mobile_header {
|
||||
display: none;
|
||||
}
|
||||
|
||||
@media only screen and (max-width : 752px) and (min-width : 504px) {
|
||||
.sidebarSearch .tt-hint, .sidebarSearch .sidebarSearchField {
|
||||
width: 160px !important;
|
||||
|
@ -51,10 +47,6 @@
|
|||
display: none;
|
||||
}
|
||||
|
||||
#mobile_header {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.homeWrapper {
|
||||
width: 96%;
|
||||
padding: 0 2%;
|
||||
|
@ -72,7 +64,7 @@
|
|||
height: auto;
|
||||
}
|
||||
.homeVideo {
|
||||
width: 100%;
|
||||
width: 100% !important;
|
||||
height: auto;
|
||||
}
|
||||
.fullWidthWrapper.withPartners {
|
||||
|
@ -108,15 +100,23 @@
|
|||
max-width: 360px;
|
||||
}
|
||||
|
||||
#wrapper .requestInvite {
|
||||
.requestInviteHeader {
|
||||
display: none;
|
||||
}
|
||||
.requestInvite {
|
||||
width: 100%;
|
||||
padding: 0;
|
||||
height: calc(100% - 50px);
|
||||
z-index: 1;
|
||||
position: relative;
|
||||
left: 0;
|
||||
margin-left: 0px;
|
||||
margin-top: 50px;
|
||||
}
|
||||
|
||||
#exploreMaps > div {
|
||||
margin-top: 70px;
|
||||
}
|
||||
|
||||
|
||||
.mapper {
|
||||
width: 100%;
|
||||
margin: 0 0 30px 0;
|
||||
|
@ -217,6 +217,7 @@
|
|||
width: 100%;
|
||||
box-shadow: 0px 3px 3px rgba(0,0,0,0.23), 0 3px 3px rgba(0,0,0,0.16);
|
||||
position: fixed;
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
#menu_icon {
|
||||
|
@ -249,7 +250,6 @@
|
|||
}
|
||||
|
||||
#mobile_menu {
|
||||
display: none;
|
||||
background: #EEE;
|
||||
position: fixed;
|
||||
top: 50px;
|
||||
|
@ -257,6 +257,7 @@
|
|||
padding: 10px;
|
||||
width: 200px;
|
||||
box-shadow: 3px 3px 3px rgba(0,0,0,0.23), 3px 3px 3px rgba(0,0,0,0.16);
|
||||
z-index: 2;
|
||||
|
||||
li {
|
||||
padding: 10px;
|
||||
|
@ -274,16 +275,6 @@
|
|||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* the mobile menu, even if it's been opened by a user, should
|
||||
* not show up if they resize their browser back to full size
|
||||
*/
|
||||
@media only screen and (max-width : 504px) {
|
||||
#mobile_menu.visible {
|
||||
display: block;
|
||||
}
|
||||
}
|
||||
|
||||
li.mobileMenuUser {
|
||||
border-bottom: 1px solid #BBB;
|
||||
}
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
.viewOnly {
|
||||
float: left;
|
||||
margin-left: 16px;
|
||||
display: none;
|
||||
height: 32px;
|
||||
border: 1px solid #BDBDBD;
|
||||
border-radius: 2px;
|
||||
|
@ -23,7 +22,7 @@
|
|||
}
|
||||
|
||||
.requestNotice {
|
||||
display: none;
|
||||
display: inline-block;
|
||||
padding: 0 8px;
|
||||
}
|
||||
|
||||
|
@ -42,16 +41,6 @@
|
|||
.requestNotAccepted {
|
||||
background-color: #c04f4f;
|
||||
}
|
||||
|
||||
&.sendRequest .requestAccess {
|
||||
display: inline-block;
|
||||
}
|
||||
&.sentRequest .requestPending {
|
||||
display: inline-block;
|
||||
}
|
||||
&.requestDenied .requestNotAccepted {
|
||||
display: inline-block;
|
||||
}
|
||||
}
|
||||
|
||||
.request_access {
|
||||
|
|
|
@ -7,7 +7,7 @@ class Message < ApplicationRecord
|
|||
|
||||
after_create :after_created
|
||||
#after_create :after_created_async
|
||||
|
||||
|
||||
|
||||
def user_image
|
||||
user.image.url
|
||||
|
@ -21,7 +21,7 @@ class Message < ApplicationRecord
|
|||
def after_created
|
||||
ActionCable.server.broadcast 'map_' + resource.id.to_s, type: 'messageCreated', message: as_json
|
||||
end
|
||||
|
||||
|
||||
def after_created_async
|
||||
FollowService.follow(resource, user, 'commented')
|
||||
NotificationService.notify_followers(resource, 'map_message', self)
|
||||
|
|
|
@ -5,7 +5,7 @@
|
|||
# %>
|
||||
|
||||
<script>
|
||||
<% content_for :title, "Explore My Maps | Metamaps" %>
|
||||
<% content_for :title, "My Maps | Metamaps" %>
|
||||
<% content_for :mobile_title, "My Maps" %>
|
||||
|
||||
Metamaps.currentPage = "mine";
|
||||
|
|
|
@ -5,7 +5,7 @@
|
|||
# %>
|
||||
|
||||
<script>
|
||||
<% content_for :title, "Explore Shared Maps | Metamaps" %>
|
||||
<% content_for :title, "Shared Maps | Metamaps" %>
|
||||
<% content_for :mobile_title, "Shared With Me" %>
|
||||
|
||||
Metamaps.currentPage = "shared";
|
||||
|
|
|
@ -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)) %>
|
|
@ -1,15 +1,21 @@
|
|||
<div id="loading"></div>
|
||||
<script type="text/javascript">
|
||||
Metamaps.ServerData.unreadNotificationsCount = <%= current_user ? user_unread_notification_count : 0 %>
|
||||
Metamaps.ServerData.mapIsStarred = <%= current_user && @map && current_user.starred_map?(@map) ? true : false %>
|
||||
Metamaps.ServerData.mobileTitle = "<%= yield(:mobile_title) %>"
|
||||
Metamaps.ServerData.ActiveMapper = <%= current_user ? current_user.to_json({follows: true, email: true, follow_settings: true}).html_safe : nil %>
|
||||
<% if devise_error_messages? %>
|
||||
Metamaps.ServerData.toast = "<%= devise_error_messages! %>"
|
||||
<% elsif notice %>
|
||||
Metamaps.ServerData.toast = "<%= notice %>"
|
||||
<% elsif alert %>
|
||||
Metamaps.ServerData.toast = "<%= alert %>"
|
||||
<% end %>
|
||||
Metamaps.Loading.setup()
|
||||
</script>
|
||||
<%= render :partial => 'layouts/lightboxes' %>
|
||||
<%= render :partial => 'layouts/templates' %>
|
||||
<%= render :partial => 'shared/metacodeBgColors' %>
|
||||
<script type="text/javascript" charset="utf-8">
|
||||
<% if current_user %>
|
||||
Metamaps.ServerData.ActiveMapper = <%= current_user.to_json({follows: true, email: true, follow_settings: true}).html_safe %>
|
||||
<% else %>
|
||||
Metamaps.ServerData.ActiveMapper = null
|
||||
<% end %>
|
||||
|
||||
Metamaps.Loading.setup()
|
||||
</script>
|
||||
<%= render :partial => 'layouts/googleanalytics' if ENV["GA_TRACKING_CODE"].present? %>
|
||||
</body>
|
||||
</html>
|
||||
|
|
|
@ -10,6 +10,5 @@
|
|||
})(window,document,'script','https://www.google-analytics.com/analytics.js','ga');
|
||||
|
||||
ga('create', '<%= ENV["GA_TRACKING_CODE"] %>', 'auto');
|
||||
ga('send', 'pageview');
|
||||
|
||||
</script>
|
||||
|
|
|
@ -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>
|
|
@ -1,67 +0,0 @@
|
|||
<div id="mobile_header">
|
||||
<div id="header_content">
|
||||
<%= yield(:mobile_title) %>
|
||||
</div>
|
||||
<div id="menu_icon">
|
||||
<% if user_unread_notification_count > 0 %>
|
||||
<div class="unread-notifications-dot"></div>
|
||||
<% end %>
|
||||
</div>
|
||||
</div>
|
||||
<div id="mobile_menu">
|
||||
<ul>
|
||||
<% if not current_user %>
|
||||
<li>
|
||||
<%= link_to "Home", root_path %>
|
||||
</li>
|
||||
<% end %>
|
||||
<% if current_user %>
|
||||
<li class="mobileMenuUser">
|
||||
<%= image_tag current_user.image.url(:sixtyfour), :size => "32x32" %>
|
||||
<span><%= current_user.name %></span>
|
||||
</li>
|
||||
<li>
|
||||
<%= link_to "New Map", new_map_path %>
|
||||
</li>
|
||||
<li>
|
||||
<%= link_to "My Maps", explore_mine_path, :data => { :router => 'true'} %>
|
||||
</li>
|
||||
<li>
|
||||
<%= link_to "Shared With Me", explore_shared_path, :data => { :router => 'true'} %>
|
||||
</li>
|
||||
<li>
|
||||
<%= link_to "Starred By Me", explore_starred_path, :data => { :router => 'true'} %>
|
||||
</li>
|
||||
<% end %>
|
||||
<li>
|
||||
<%= link_to "All Maps", explore_active_path, :data => { :router => 'true'} %>
|
||||
</li>
|
||||
<% if not current_user %>
|
||||
<li>
|
||||
<%= link_to "Featured Maps", explore_featured_path, :data => { :router => 'true'} %>
|
||||
</li>
|
||||
<% end %>
|
||||
<% if not current_user %>
|
||||
<li>
|
||||
<%= link_to "Request Invite", request_path %>
|
||||
</li>
|
||||
<li>
|
||||
<%= link_to "Login", sign_in_path %>
|
||||
</li>
|
||||
<% end %>
|
||||
<% if current_user %>
|
||||
<li>
|
||||
<%= link_to "Account", edit_user_url(current_user) %>
|
||||
</li>
|
||||
<li class="notifications">
|
||||
<%= link_to "Notifications", notifications_path %>
|
||||
<% if user_unread_notification_count > 0 %>
|
||||
<div class="unread-notifications-dot"></div>
|
||||
<% end %>
|
||||
</li>
|
||||
<li>
|
||||
<%= link_to "Sign Out", "/logout", id: "Logout" %>
|
||||
</li>
|
||||
<% end %>
|
||||
</ul>
|
||||
</div>
|
|
@ -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 -->
|
|
@ -6,83 +6,18 @@
|
|||
#%>
|
||||
|
||||
<%= render :partial => 'layouts/head' %>
|
||||
|
||||
<body class="<%= authenticated? ? "authenticated" : "unauthenticated" %> controller-<%= controller_name %> action-<%= action_name %>">
|
||||
|
||||
<div id="chat-box-wrapper"></div>
|
||||
|
||||
<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>
|
||||
|
||||
<div class="main" id="react-app"></div>
|
||||
<%= yield %>
|
||||
<% 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/foot' %>
|
||||
|
|
|
@ -6,55 +6,26 @@
|
|||
#%>
|
||||
|
||||
<%= render :partial => 'layouts/head' %>
|
||||
|
||||
<body class="<%= current_user ? 'authenticated' : 'unauthenticated' %>">
|
||||
|
||||
<a class='feedback-icon' target='_blank' href='https://hylo.com/c/metamaps'></a>
|
||||
|
||||
<%= content_tag :div, class: "main" do %>
|
||||
|
||||
<% if params[:controller] == 'doorkeeper/applications' || params[:controller] == 'doorkeeper/authorized_applications'
|
||||
classes = 'appsPage'
|
||||
else
|
||||
classes = ''
|
||||
end
|
||||
%>
|
||||
|
||||
<div class="wrapper <%= classes %>" id="wrapper">
|
||||
|
||||
<%= render :partial => 'layouts/upperelements', :locals => {:noHardHomeLink => true } %>
|
||||
|
||||
<%= yield %>
|
||||
|
||||
<div id="exploreMapsHeader">
|
||||
<div class="exploreMapsBar exploreElement">
|
||||
<div class="exploreMapsMenu">
|
||||
<div class="exploreMapsCenter">
|
||||
<% if current_user && current_user.admin %>
|
||||
<a href="<%= oauth_applications_path %>" class="activeMaps exploreMapsButton <%= params[:controller] == 'doorkeeper/applications' ? 'active' : nil %>">
|
||||
<div class="exploreMapsIcon"></div>Registered Apps
|
||||
</a>
|
||||
<% end %>
|
||||
<a href="<%= oauth_authorized_applications_path %>" class="authedApps exploreMapsButton <%= params[:controller] == 'doorkeeper/authorized_applications' ? 'active' : nil %>">
|
||||
<div class="exploreMapsIcon"></div>Authorized Apps
|
||||
</a>
|
||||
<a href="/" class="myMaps exploreMapsButton">
|
||||
<div class="exploreMapsIcon"></div>Maps
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<p id="toast" class="toast">
|
||||
<% if devise_error_messages? %>
|
||||
<%= devise_error_messages! %>
|
||||
<% elsif notice %>
|
||||
<%= notice %>
|
||||
<% end %>
|
||||
</p>
|
||||
<div id="loading"></div>
|
||||
</div>
|
||||
|
||||
<% end %>
|
||||
|
||||
<div class="main" id="react-app"></div>
|
||||
<%= yield %>
|
||||
<div id="exploreMapsHeader">
|
||||
<div class="exploreMapsBar exploreElement">
|
||||
<div class="exploreMapsMenu">
|
||||
<div class="exploreMapsCenter">
|
||||
<% if current_user && current_user.admin %>
|
||||
<a href="<%= oauth_applications_path %>" class="activeMaps exploreMapsButton <%= params[:controller] == 'doorkeeper/applications' ? 'active' : nil %>">
|
||||
<div class="exploreMapsIcon"></div>Registered Apps
|
||||
</a>
|
||||
<% end %>
|
||||
<a href="<%= oauth_authorized_applications_path %>" class="authedApps exploreMapsButton <%= params[:controller] == 'doorkeeper/authorized_applications' ? 'active' : nil %>">
|
||||
<div class="exploreMapsIcon"></div>Authorized Apps
|
||||
</a>
|
||||
<a href="/" class="myMaps exploreMapsButton">
|
||||
<div class="exploreMapsIcon"></div>Maps
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<%= render :partial => 'layouts/foot' %>
|
||||
|
|
|
@ -6,7 +6,5 @@
|
|||
|
||||
<% content_for :title, "Request Invite | Metamaps" %>
|
||||
<% content_for :mobile_title, "Request Invite" %>
|
||||
|
||||
<div id="yield">
|
||||
<div class="requestInviteHeader"></div>
|
||||
<iframe class="requestInvite" src="https://docs.google.com/forms/d/1lWoKPFHErsDfV5l7-SvcHxwX3vDi9nNNVW0rFMgJwgg/viewform?embedded=true" width="700" frameborder="0" marginheight="0" marginwidth="0">Loading...</iframe>
|
||||
</div>
|
||||
|
|
|
@ -18,5 +18,6 @@
|
|||
Metamaps.ServerData.Mappings = <%= @allmappings.to_json.html_safe %>;
|
||||
Metamaps.ServerData.Messages = <%= @allmessages.to_json.html_safe %>;
|
||||
Metamaps.ServerData.Stars = <%= @allstars.to_json.html_safe %>;
|
||||
Metamaps.ServerData.requests = <%= @allrequests.to_json.html_safe %>;
|
||||
Metamaps.ServerData.VisualizeType = "ForceDirected";
|
||||
</script>
|
||||
|
|
|
@ -18,5 +18,6 @@
|
|||
Metamaps.ServerData.Mappings = <%= @allmappings.to_json.html_safe %>;
|
||||
Metamaps.ServerData.Messages = <%= @allmessages.to_json.html_safe %>;
|
||||
Metamaps.ServerData.Stars = <%= @allstars.to_json.html_safe %>;
|
||||
Metamaps.ServerData.requests = <%= @allrequests.to_json.html_safe %>;
|
||||
Metamaps.ServerData.VisualizeType = "ForceDirected";
|
||||
</script>
|
||||
|
|
|
@ -1,125 +0,0 @@
|
|||
<%#
|
||||
# @file
|
||||
# this code generates the list of icons in the filter box in the upper right menu area
|
||||
#%>
|
||||
|
||||
<%
|
||||
@mappers = []
|
||||
@synapses = []
|
||||
@metacodes = []
|
||||
@metacodelist = ''
|
||||
@mapperlist = ''
|
||||
@synapselist = ''
|
||||
# There are essentially three functions happening here one to fill data to
|
||||
#@mappers with all people who have mapped on the selected map, which
|
||||
#actually gets checked twice once for topics or within @metacodes and once
|
||||
#for synapses on the map. @synapses get filled with all synapses on the map
|
||||
#and metacodes is filled with all the metacodes that are being used on the map.
|
||||
|
||||
if @map
|
||||
@alltopics.each_with_index do |topic, index|
|
||||
if @metacodes.index(topic.metacode) == nil
|
||||
@metacodes.push(topic.metacode)
|
||||
end
|
||||
end
|
||||
@allsynapses.each_with_index do |synapse, index|
|
||||
if @synapses.index{|s| s.desc == synapse.desc} == nil
|
||||
@synapses.push(synapse)
|
||||
end
|
||||
end
|
||||
@allmappings.each_with_index do |mapping, index|
|
||||
if @mappers.index(mapping.user) == nil
|
||||
@mappers.push(mapping.user)
|
||||
end
|
||||
end
|
||||
elsif @topic
|
||||
@alltopics.each_with_index do |topic, index|
|
||||
if @metacodes.index(topic.metacode) == nil
|
||||
@metacodes.push(topic.metacode)
|
||||
end
|
||||
if @mappers.index(topic.user) == nil
|
||||
@mappers.push(topic.user)
|
||||
end
|
||||
end
|
||||
@allsynapses.each_with_index do |synapse, index|
|
||||
if @synapses.index{|s| s.desc == synapse.desc} == nil
|
||||
@synapses.push(synapse)
|
||||
end
|
||||
if @mappers.index(synapse.user) == nil
|
||||
@mappers.push(synapse.user)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
if @map || @topic
|
||||
@metacodes.sort! {|x,y|
|
||||
n1 = x.name || ""
|
||||
n2 = y.name || ""
|
||||
n1 <=> n2
|
||||
}
|
||||
@synapses.sort! {|x,y|
|
||||
d1 = x.desc || ""
|
||||
d2 = y.desc || ""
|
||||
d1 <=> d2
|
||||
}
|
||||
@mappers.sort! {|x,y|
|
||||
n1 = x.name || ""
|
||||
n2 = y.name || ""
|
||||
n1 <=> n2
|
||||
}
|
||||
|
||||
@metacodes.each_with_index do |metacode, index|
|
||||
@metacodelist += '<li data-id="' + metacode.id.to_s + '">'
|
||||
@metacodelist += '<img src="' + asset_path(metacode.icon) + '" data-id="' + metacode.id.to_s + '" alt="' + metacode.name + '" />'
|
||||
@metacodelist += '<p>' + metacode.name.downcase + '</p></li>'
|
||||
end
|
||||
@synapses.each_with_index do |synapse, index|
|
||||
d = synapse.desc || ""
|
||||
@synapselist += '<li data-id="' + d + '">'
|
||||
@synapselist += '<img src="' + asset_path('synapse16.png') + '" alt="synapse icon" /><p>' + d
|
||||
@synapselist += '</p></li>'
|
||||
end
|
||||
@mappers.each_with_index do |mapper, index|
|
||||
@mapperlist += '<li data-id="' + mapper.id.to_s + '">'
|
||||
@mapperlist += '<img src="' + mapper.image.url(:sixtyfour) + '" data-id="' + mapper.id.to_s + '" alt="' + mapper.name + '" />'
|
||||
@mapperlist += '<p>' + mapper.name + '</p></li>'
|
||||
end
|
||||
end
|
||||
%>
|
||||
<div class="filterBox">
|
||||
<h2>FILTER BY</h2>
|
||||
<div id="filter_by_mapper" class="filterBySection">
|
||||
<h3><%= @map ? "MAPPERS" : @topic ? "CREATORS" : "" %></h3>
|
||||
<span class="hideAll hideAllMappers">NONE</span>
|
||||
<span class="active showAll showAllMappers">ALL</span>
|
||||
<div class="clearfloat"></div>
|
||||
<ul>
|
||||
<%= @mapperlist.html_safe %>
|
||||
</ul>
|
||||
<div class="clearfloat"></div>
|
||||
</div>
|
||||
|
||||
<div id="filter_by_metacode" class="filterBySection">
|
||||
<h3>METACODES</h3>
|
||||
<span class="hideAll hideAllMetacodes">NONE</span>
|
||||
<span class="active showAll showAllMetacodes">ALL</span>
|
||||
<div class="clearfloat"></div>
|
||||
<ul>
|
||||
<%= @metacodelist.html_safe %>
|
||||
</ul>
|
||||
<div class="clearfloat"></div>
|
||||
</div>
|
||||
|
||||
<div id="filter_by_synapse" class="filterBySection">
|
||||
<h3>SYNAPSES</h3>
|
||||
<span class="hideAll hideAllSynapses">NONE</span>
|
||||
<span class="active showAll showAllSynapses">ALL</span>
|
||||
<div class="clearfloat"></div>
|
||||
<ul>
|
||||
<%= @synapselist.html_safe %>
|
||||
</ul>
|
||||
<div class="clearfloat"></div>
|
||||
</div>
|
||||
|
||||
</div> <!-- end .filterBox -->
|
||||
|
|
@ -76,7 +76,7 @@ const Control = {
|
|||
}
|
||||
|
||||
if (DataModel.Topics.length === 0) {
|
||||
GlobalUI.showDiv('#instructions')
|
||||
Map.setHasLearnedTopicCreation(false)
|
||||
}
|
||||
},
|
||||
deleteSelectedNodes: function() { // refers to deleting topics permanently
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
/* global $, Hogan, Bloodhound */
|
||||
|
||||
import DataModel from './DataModel'
|
||||
import Map from './Map'
|
||||
import Mouse from './Mouse'
|
||||
import Selected from './Selected'
|
||||
import Synapse from './Synapse'
|
||||
|
@ -270,7 +271,7 @@ const Create = {
|
|||
})
|
||||
Create.newTopic.beingCreated = true
|
||||
Create.newTopic.name = ''
|
||||
GlobalUI.hideDiv('#instructions')
|
||||
Map.setHasLearnedTopicCreation(true)
|
||||
},
|
||||
hide: function(force) {
|
||||
if (force || !Create.newTopic.pinned) {
|
||||
|
@ -281,7 +282,7 @@ const Create = {
|
|||
Create.newTopic.pinned = false
|
||||
}
|
||||
if (DataModel.Topics.length === 0) {
|
||||
GlobalUI.showDiv('#instructions')
|
||||
Map.setHasLearnedTopicCreation(false)
|
||||
}
|
||||
Create.newTopic.beingCreated = false
|
||||
},
|
||||
|
|
|
@ -9,12 +9,12 @@ const Mapper = Backbone.Model.extend({
|
|||
toJSON: function(options) {
|
||||
return _.omit(this.attributes, this.blacklist)
|
||||
},
|
||||
prepareLiForFilter: function() {
|
||||
return outdent`
|
||||
<li data-id="${this.id}">
|
||||
<img src="${this.get('image')}" data-id="${this.id}" alt="${this.get('name')}" />
|
||||
<p>${this.get('name')}</p>
|
||||
</li>`
|
||||
prepareDataForFilter: function() {
|
||||
return {
|
||||
id: this.id,
|
||||
image: this.get('image'),
|
||||
name: this.get('name')
|
||||
}
|
||||
},
|
||||
followMap: function(id) {
|
||||
const idIndex = this.get('follows').maps.indexOf(id)
|
||||
|
|
|
@ -9,12 +9,12 @@ const Metacode = Backbone.Model.extend({
|
|||
image.src = this.get('icon')
|
||||
this.set('image', image)
|
||||
},
|
||||
prepareLiForFilter: function() {
|
||||
return outdent`
|
||||
<li data-id="${this.id}">
|
||||
<img src="${this.get('icon')}" data-id="${this.id}" alt="${this.get('name')}" />
|
||||
<p>${this.get('name').toLowerCase()}</p>
|
||||
</li>`
|
||||
prepareDataForFilter: function() {
|
||||
return {
|
||||
id: this.id,
|
||||
name: this.get('name'),
|
||||
icon: this.get('icon')
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
|
|
|
@ -28,12 +28,11 @@ const Synapse = Backbone.Model.extend({
|
|||
this.on('change', this.updateEdgeView)
|
||||
this.on('change:desc', Filter.checkSynapses, this)
|
||||
},
|
||||
prepareLiForFilter: function() {
|
||||
return outdent`
|
||||
<li data-id="${this.get('desc')}">
|
||||
<img src="${DataModel.synapseIconUrl}" alt="synapse icon" />
|
||||
<p>${this.get('desc')}</p>
|
||||
</li>`
|
||||
prepareDataForFilter: function() {
|
||||
return {
|
||||
desc: this.get('desc'),
|
||||
icon: DataModel.synapseIconUrl
|
||||
}
|
||||
},
|
||||
authorizeToEdit: function(mapper) {
|
||||
if (mapper && (this.get('permission') === 'commons' || this.get('collaborator_ids').includes(mapper.get('id')) || this.get('user_id') === mapper.get('id'))) return true
|
||||
|
|
|
@ -101,16 +101,19 @@ const DataModel = {
|
|||
},
|
||||
attachCollectionEvents: function() {
|
||||
DataModel.Topics.on('add remove', function(topic) {
|
||||
console.log('updating infobox and filters due to topic add or remove')
|
||||
InfoBox.updateNumbers()
|
||||
Filter.checkMetacodes()
|
||||
Filter.checkMappers()
|
||||
})
|
||||
DataModel.Synapses.on('add remove', function(synapse) {
|
||||
console.log('updating infobox and filters due to synapse add or remove')
|
||||
InfoBox.updateNumbers()
|
||||
Filter.checkSynapses()
|
||||
Filter.checkMappers()
|
||||
})
|
||||
DataModel.Mappings.on('add remove', function(mapping) {
|
||||
console.log('updating infobox and filters due to mapping add or remove')
|
||||
InfoBox.updateNumbers()
|
||||
Filter.checkSynapses()
|
||||
Filter.checkMetacodes()
|
||||
|
|
|
@ -5,13 +5,17 @@ import _ from 'lodash'
|
|||
import Active from './Active'
|
||||
import Control from './Control'
|
||||
import DataModel from './DataModel'
|
||||
import GlobalUI from './GlobalUI'
|
||||
import GlobalUI, { ReactApp } from './GlobalUI'
|
||||
import Settings from './Settings'
|
||||
import Visualize from './Visualize'
|
||||
|
||||
const Filter = {
|
||||
dataForPresentation: {
|
||||
metacodes: {},
|
||||
mappers: {},
|
||||
synapses: {}
|
||||
},
|
||||
filters: {
|
||||
name: '',
|
||||
metacodes: [],
|
||||
mappers: [],
|
||||
synapses: []
|
||||
|
@ -23,119 +27,26 @@ const Filter = {
|
|||
},
|
||||
isOpen: false,
|
||||
changing: false,
|
||||
init: function() {
|
||||
var self = Filter
|
||||
|
||||
$('.sidebarFilterIcon').click(self.toggleBox)
|
||||
|
||||
$('.sidebarFilterBox .showAllMetacodes').click(self.filterNoMetacodes)
|
||||
$('.sidebarFilterBox .showAllSynapses').click(self.filterNoSynapses)
|
||||
$('.sidebarFilterBox .showAllMappers').click(self.filterNoMappers)
|
||||
$('.sidebarFilterBox .hideAllMetacodes').click(self.filterAllMetacodes)
|
||||
$('.sidebarFilterBox .hideAllSynapses').click(self.filterAllSynapses)
|
||||
$('.sidebarFilterBox .hideAllMappers').click(self.filterAllMappers)
|
||||
|
||||
self.bindLiClicks()
|
||||
self.getFilterData()
|
||||
},
|
||||
toggleBox: function(event) {
|
||||
var self = Filter
|
||||
|
||||
if (self.isOpen) self.close()
|
||||
else self.open()
|
||||
|
||||
event.stopPropagation()
|
||||
},
|
||||
open: function() {
|
||||
var self = Filter
|
||||
|
||||
GlobalUI.Account.close()
|
||||
$('.sidebarFilterIcon div').addClass('hide')
|
||||
|
||||
if (!self.isOpen && !self.changing) {
|
||||
self.changing = true
|
||||
|
||||
var height = $(document).height() - 108
|
||||
$('.sidebarFilterBox').css('max-height', height + 'px').fadeIn(200, function() {
|
||||
self.changing = false
|
||||
self.isOpen = true
|
||||
})
|
||||
}
|
||||
},
|
||||
close: function() {
|
||||
var self = Filter
|
||||
$('.sidebarFilterIcon div').removeClass('hide')
|
||||
|
||||
if (!self.changing) {
|
||||
self.changing = true
|
||||
|
||||
$('.sidebarFilterBox').fadeOut(200, function() {
|
||||
self.changing = false
|
||||
self.isOpen = false
|
||||
})
|
||||
}
|
||||
},
|
||||
reset: function() {
|
||||
var self = Filter
|
||||
|
||||
self.filters.metacodes = []
|
||||
self.filters.mappers = []
|
||||
self.filters.synapses = []
|
||||
self.visible.metacodes = []
|
||||
self.visible.mappers = []
|
||||
self.visible.synapses = []
|
||||
|
||||
$('#filter_by_metacode ul').empty()
|
||||
$('#filter_by_mapper ul').empty()
|
||||
$('#filter_by_synapse ul').empty()
|
||||
|
||||
$('.filterBox .showAll').addClass('active')
|
||||
},
|
||||
/*
|
||||
Most of this data essentially depends on the ruby function which are happening for filter inside view filterBox
|
||||
But what these function do is load this data into three accessible array within java : metacodes, mappers and synapses
|
||||
*/
|
||||
getFilterData: function() {
|
||||
var self = Filter
|
||||
|
||||
var metacode, mapper, synapse
|
||||
|
||||
$('#filter_by_metacode li').each(function() {
|
||||
metacode = $(this).attr('data-id')
|
||||
self.filters.metacodes.push(metacode)
|
||||
self.visible.metacodes.push(metacode)
|
||||
})
|
||||
|
||||
$('#filter_by_mapper li').each(function() {
|
||||
mapper = ($(this).attr('data-id'))
|
||||
self.filters.mappers.push(mapper)
|
||||
self.visible.mappers.push(mapper)
|
||||
})
|
||||
|
||||
$('#filter_by_synapse li').each(function() {
|
||||
synapse = ($(this).attr('data-id'))
|
||||
self.filters.synapses.push(synapse)
|
||||
self.visible.synapses.push(synapse)
|
||||
})
|
||||
},
|
||||
bindLiClicks: function() {
|
||||
var self = Filter
|
||||
$('#filter_by_metacode ul li').unbind().click(self.toggleMetacode)
|
||||
$('#filter_by_mapper ul li').unbind().click(self.toggleMapper)
|
||||
$('#filter_by_synapse ul li').unbind().click(self.toggleSynapse)
|
||||
self.dataForPresentation.metacodes = {}
|
||||
self.dataForPresentation.mappers = {}
|
||||
self.dataForPresentation.synapses = {}
|
||||
ReactApp.render()
|
||||
},
|
||||
// an abstraction function for checkMetacodes, checkMappers, checkSynapses to reduce
|
||||
// code redundancy
|
||||
/*
|
||||
@param
|
||||
*/
|
||||
updateFilters: function(collection, propertyToCheck, correlatedModel, filtersToUse, listToModify) {
|
||||
var self = Filter
|
||||
|
||||
var newList = []
|
||||
var removed = []
|
||||
var added = []
|
||||
|
||||
// the first option enables us to accept
|
||||
// ['Topics', 'Synapses'] as 'collection'
|
||||
if (typeof collection === 'object') {
|
||||
|
@ -168,41 +79,24 @@ const Filter = {
|
|||
}
|
||||
})
|
||||
}
|
||||
|
||||
removed = _.difference(self.filters[filtersToUse], newList)
|
||||
added = _.difference(newList, self.filters[filtersToUse])
|
||||
|
||||
// remove the list items for things no longer present on the map
|
||||
_.each(removed, function(identifier) {
|
||||
$('#filter_by_' + listToModify + ' li[data-id="' + identifier + '"]').fadeOut('fast', function() {
|
||||
$(this).remove()
|
||||
})
|
||||
const index = self.visible[filtersToUse].indexOf(identifier)
|
||||
self.visible[filtersToUse].splice(index, 1)
|
||||
delete self.dataForPresentation[filtersToUse][identifier]
|
||||
})
|
||||
|
||||
var model, li, jQueryLi
|
||||
function sortAlpha(a, b) {
|
||||
return a.childNodes[1].innerHTML.toLowerCase() > b.childNodes[1].innerHTML.toLowerCase() ? 1 : -1
|
||||
}
|
||||
// for each new filter to be added, create a list item for it and fade it in
|
||||
_.each(added, function(identifier) {
|
||||
model = DataModel[correlatedModel].get(identifier) ||
|
||||
DataModel[correlatedModel].find(function(model) {
|
||||
return model.get(propertyToCheck) === identifier
|
||||
const model = DataModel[correlatedModel].get(identifier) ||
|
||||
DataModel[correlatedModel].find(function(m) {
|
||||
return m.get(propertyToCheck) === identifier
|
||||
})
|
||||
li = model.prepareLiForFilter()
|
||||
jQueryLi = $(li).hide()
|
||||
$('li', '#filter_by_' + listToModify + ' ul').add(jQueryLi.fadeIn('fast'))
|
||||
.sort(sortAlpha).appendTo('#filter_by_' + listToModify + ' ul')
|
||||
self.dataForPresentation[filtersToUse][identifier] = model.prepareDataForFilter()
|
||||
self.visible[filtersToUse].push(identifier)
|
||||
})
|
||||
|
||||
// update the list of filters with the new list we just generated
|
||||
self.filters[filtersToUse] = newList
|
||||
|
||||
// make sure clicks on list items still trigger the right events
|
||||
self.bindLiClicks()
|
||||
ReactApp.render()
|
||||
},
|
||||
checkMetacodes: function() {
|
||||
var self = Filter
|
||||
|
@ -221,114 +115,49 @@ const Filter = {
|
|||
var self = Filter
|
||||
self.updateFilters('Synapses', 'desc', 'Synapses', 'synapses', 'synapse')
|
||||
},
|
||||
filterAllMetacodes: function(e) {
|
||||
filterAllMetacodes: function(toVisible) {
|
||||
var self = Filter
|
||||
$('#filter_by_metacode ul li').addClass('toggledOff')
|
||||
$('.showAllMetacodes').removeClass('active')
|
||||
$('.hideAllMetacodes').addClass('active')
|
||||
self.visible.metacodes = []
|
||||
self.visible.metacodes = toVisible ? self.filters.metacodes.slice() : []
|
||||
ReactApp.render()
|
||||
self.passFilters()
|
||||
},
|
||||
filterNoMetacodes: function(e) {
|
||||
filterAllMappers: function(toVisible) {
|
||||
var self = Filter
|
||||
$('#filter_by_metacode ul li').removeClass('toggledOff')
|
||||
$('.showAllMetacodes').addClass('active')
|
||||
$('.hideAllMetacodes').removeClass('active')
|
||||
self.visible.metacodes = self.filters.metacodes.slice()
|
||||
self.visible.mappers = toVisible ? self.filters.mappers.slice() : []
|
||||
ReactApp.render()
|
||||
self.passFilters()
|
||||
},
|
||||
filterAllMappers: function(e) {
|
||||
filterAllSynapses: function(toVisible) {
|
||||
var self = Filter
|
||||
$('#filter_by_mapper ul li').addClass('toggledOff')
|
||||
$('.showAllMappers').removeClass('active')
|
||||
$('.hideAllMappers').addClass('active')
|
||||
self.visible.mappers = []
|
||||
self.passFilters()
|
||||
},
|
||||
filterNoMappers: function(e) {
|
||||
var self = Filter
|
||||
$('#filter_by_mapper ul li').removeClass('toggledOff')
|
||||
$('.showAllMappers').addClass('active')
|
||||
$('.hideAllMappers').removeClass('active')
|
||||
self.visible.mappers = self.filters.mappers.slice()
|
||||
self.passFilters()
|
||||
},
|
||||
filterAllSynapses: function(e) {
|
||||
var self = Filter
|
||||
$('#filter_by_synapse ul li').addClass('toggledOff')
|
||||
$('.showAllSynapses').removeClass('active')
|
||||
$('.hideAllSynapses').addClass('active')
|
||||
self.visible.synapses = []
|
||||
self.passFilters()
|
||||
},
|
||||
filterNoSynapses: function(e) {
|
||||
var self = Filter
|
||||
$('#filter_by_synapse ul li').removeClass('toggledOff')
|
||||
$('.showAllSynapses').addClass('active')
|
||||
$('.hideAllSynapses').removeClass('active')
|
||||
self.visible.synapses = self.filters.synapses.slice()
|
||||
self.visible.synapses = toVisible ? self.filters.synapses.slice() : []
|
||||
ReactApp.render()
|
||||
self.passFilters()
|
||||
},
|
||||
// an abstraction function for toggleMetacode, toggleMapper, toggleSynapse
|
||||
// to reduce code redundancy
|
||||
// gets called in the context of a list item in a filter box
|
||||
toggleLi: function(whichToFilter) {
|
||||
toggleLi: function(whichToFilter, id) {
|
||||
var self = Filter
|
||||
var id = $(this).attr('data-id')
|
||||
if (self.visible[whichToFilter].indexOf(id) === -1) {
|
||||
self.visible[whichToFilter].push(id)
|
||||
$(this).removeClass('toggledOff')
|
||||
} else {
|
||||
const index = self.visible[whichToFilter].indexOf(id)
|
||||
self.visible[whichToFilter].splice(index, 1)
|
||||
$(this).addClass('toggledOff')
|
||||
}
|
||||
ReactApp.render()
|
||||
self.passFilters()
|
||||
},
|
||||
toggleMetacode: function() {
|
||||
toggleMetacode: function(id) {
|
||||
var self = Filter
|
||||
self.toggleLi.call(this, 'metacodes')
|
||||
|
||||
if (self.visible.metacodes.length === self.filters.metacodes.length) {
|
||||
$('.showAllMetacodes').addClass('active')
|
||||
$('.hideAllMetacodes').removeClass('active')
|
||||
} else if (self.visible.metacodes.length === 0) {
|
||||
$('.showAllMetacodes').removeClass('active')
|
||||
$('.hideAllMetacodes').addClass('active')
|
||||
} else {
|
||||
$('.showAllMetacodes').removeClass('active')
|
||||
$('.hideAllMetacodes').removeClass('active')
|
||||
}
|
||||
self.toggleLi('metacodes', id)
|
||||
},
|
||||
toggleMapper: function() {
|
||||
toggleMapper: function(id) {
|
||||
var self = Filter
|
||||
self.toggleLi.call(this, 'mappers')
|
||||
|
||||
if (self.visible.mappers.length === self.filters.mappers.length) {
|
||||
$('.showAllMappers').addClass('active')
|
||||
$('.hideAllMappers').removeClass('active')
|
||||
} else if (self.visible.mappers.length === 0) {
|
||||
$('.showAllMappers').removeClass('active')
|
||||
$('.hideAllMappers').addClass('active')
|
||||
} else {
|
||||
$('.showAllMappers').removeClass('active')
|
||||
$('.hideAllMappers').removeClass('active')
|
||||
}
|
||||
self.toggleLi('mappers', id)
|
||||
},
|
||||
toggleSynapse: function() {
|
||||
toggleSynapse: function(id) {
|
||||
var self = Filter
|
||||
self.toggleLi.call(this, 'synapses')
|
||||
|
||||
if (self.visible.synapses.length === self.filters.synapses.length) {
|
||||
$('.showAllSynapses').addClass('active')
|
||||
$('.hideAllSynapses').removeClass('active')
|
||||
} else if (self.visible.synapses.length === 0) {
|
||||
$('.showAllSynapses').removeClass('active')
|
||||
$('.hideAllSynapses').addClass('active')
|
||||
} else {
|
||||
$('.showAllSynapses').removeClass('active')
|
||||
$('.hideAllSynapses').removeClass('active')
|
||||
}
|
||||
self.toggleLi('synapses', id)
|
||||
},
|
||||
passFilters: function() {
|
||||
var self = Filter
|
||||
|
|
|
@ -1,55 +0,0 @@
|
|||
/* global $ */
|
||||
|
||||
import Filter from '../Filter'
|
||||
|
||||
const Account = {
|
||||
isOpen: false,
|
||||
changing: false,
|
||||
init: function() {
|
||||
var self = Account
|
||||
|
||||
$('.sidebarAccountIcon').click(self.toggleBox)
|
||||
$('.sidebarAccountBox').click(function(event) {
|
||||
event.stopPropagation()
|
||||
})
|
||||
$('body').click(self.close)
|
||||
},
|
||||
toggleBox: function(event) {
|
||||
var self = Account
|
||||
|
||||
if (self.isOpen) self.close()
|
||||
else self.open()
|
||||
|
||||
event.stopPropagation()
|
||||
},
|
||||
open: function() {
|
||||
var self = Account
|
||||
|
||||
Filter.close()
|
||||
$('.sidebarAccountIcon .tooltipsUnder').addClass('hide')
|
||||
|
||||
if (!self.isOpen && !self.changing) {
|
||||
self.changing = true
|
||||
$('.sidebarAccountBox').fadeIn(200, function() {
|
||||
self.changing = false
|
||||
self.isOpen = true
|
||||
$('.sidebarAccountBox #user_email').focus()
|
||||
})
|
||||
}
|
||||
},
|
||||
close: function() {
|
||||
var self = Account
|
||||
|
||||
$('.sidebarAccountIcon .tooltipsUnder').removeClass('hide')
|
||||
if (!self.changing) {
|
||||
self.changing = true
|
||||
$('.sidebarAccountBox #user_email').blur()
|
||||
$('.sidebarAccountBox').fadeOut(200, function() {
|
||||
self.changing = false
|
||||
self.isOpen = false
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export default Account
|
|
@ -61,7 +61,7 @@ const CreateMap = {
|
|||
if (GlobalUI.lightbox === 'forkmap') {
|
||||
self.newMap.set('topicsToMap', self.topicsToMap)
|
||||
self.newMap.set('synapsesToMap', self.synapsesToMap)
|
||||
self.newMap.set('source_id', Active.Map.id)
|
||||
if (Active.Map) self.newMap.set('source_id', Active.Map.id)
|
||||
}
|
||||
|
||||
var formId = GlobalUI.lightbox === 'forkmap' ? '#fork_map' : '#new_map'
|
||||
|
|
|
@ -4,7 +4,7 @@ import React from 'react'
|
|||
import ReactDOM from 'react-dom'
|
||||
import outdent from 'outdent'
|
||||
|
||||
import ImportDialogBox from '../../components/ImportDialogBox'
|
||||
import ImportDialogBox from '../../components/MapView/ImportDialogBox'
|
||||
|
||||
import PasteInput from '../PasteInput'
|
||||
import Map from '../Map'
|
||||
|
@ -19,7 +19,7 @@ const ImportDialog = {
|
|||
self.closeLightbox = closeLightbox
|
||||
|
||||
$('#lightbox_content').append($(outdent`
|
||||
<div class="lightboxContent" id="import-dialog-lightbox">
|
||||
<div class="lightboxContent" id="import-dialog">
|
||||
<div class="importDialogWrapper" />
|
||||
</div>
|
||||
`))
|
||||
|
|
|
@ -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
|
234
frontend/src/Metamaps/GlobalUI/ReactApp.js
Normal file
234
frontend/src/Metamaps/GlobalUI/ReactApp.js
Normal file
|
@ -0,0 +1,234 @@
|
|||
/* 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 ImportDialog from './ImportDialog'
|
||||
import Active from '../Active'
|
||||
import DataModel from '../DataModel'
|
||||
import { ExploreMaps, ChatView, TopicCard } 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 '../../components/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 = {
|
||||
mapId: null,
|
||||
topicId: null,
|
||||
unreadNotificationsCount: 0,
|
||||
mapsWidth: 0,
|
||||
toast: '',
|
||||
mobile: false,
|
||||
mobileTitle: '',
|
||||
mobileTitleWidth: 0,
|
||||
init: function(serverData, openLightbox) {
|
||||
const self = ReactApp
|
||||
self.unreadNotificationsCount = serverData.unreadNotificationsCount
|
||||
self.mobileTitle = serverData.mobileTitle
|
||||
self.openLightbox = openLightbox
|
||||
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} />
|
||||
console.log('rendering')
|
||||
ReactDOM.render(app, document.getElementById('react-app'))
|
||||
},
|
||||
getProps: function() {
|
||||
const self = ReactApp
|
||||
return merge({
|
||||
unreadNotificationsCount: self.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')
|
||||
},
|
||||
self.getMapProps(),
|
||||
self.getTopicProps(),
|
||||
self.getFilterProps(),
|
||||
self.getCommonProps(),
|
||||
self.getMapsProps(),
|
||||
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: TopicCard.metacodeSets,
|
||||
updateTopic: TopicCard.updateTopic,
|
||||
onTopicFollow: TopicCard.onTopicFollow,
|
||||
redrawCanvas: TopicCard.redrawCanvas
|
||||
}
|
||||
},
|
||||
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
|
|
@ -1,7 +1,8 @@
|
|||
/* global $, Hogan, Bloodhound, CanvasLoader */
|
||||
|
||||
import { browserHistory } from 'react-router'
|
||||
|
||||
import Active from '../Active'
|
||||
import Router from '../Router'
|
||||
|
||||
const Search = {
|
||||
locked: false,
|
||||
|
@ -17,6 +18,7 @@ const Search = {
|
|||
self.userIconUrl = serverData['user.png']
|
||||
|
||||
// this is similar to Metamaps.Loading, but it's for the search element
|
||||
if (!document.getElementById('searchLoading')) return
|
||||
var loader = new CanvasLoader('searchLoading')
|
||||
loader.setColor('#4fb5c0') // default is '#000000'
|
||||
loader.setDiameter(24) // default is 40
|
||||
|
@ -189,11 +191,11 @@ const Search = {
|
|||
|
||||
if (['topic', 'map', 'mapper'].indexOf(datum.rtype) !== -1) {
|
||||
if (datum.rtype === 'topic') {
|
||||
Router.topics(datum.id)
|
||||
browserHistory.push(`/topics/${datum.id}`)
|
||||
} else if (datum.rtype === 'map') {
|
||||
Router.maps(datum.id)
|
||||
browserHistory.push(`/maps/${datum.id}`)
|
||||
} else if (datum.rtype === 'mapper') {
|
||||
Router.explore('mapper', datum.id)
|
||||
browserHistory.push(`/explore/mapper/${datum.id}`)
|
||||
}
|
||||
}
|
||||
},
|
||||
|
|
|
@ -4,11 +4,10 @@ import clipboard from 'clipboard-js'
|
|||
|
||||
import Create from '../Create'
|
||||
|
||||
import ReactApp from './ReactApp'
|
||||
import Search from './Search'
|
||||
import CreateMap from './CreateMap'
|
||||
import Account from './Account'
|
||||
import ImportDialog from './ImportDialog'
|
||||
import NotificationIcon from './NotificationIcon'
|
||||
|
||||
const GlobalUI = {
|
||||
notifyTimeout: null,
|
||||
|
@ -18,13 +17,12 @@ const GlobalUI = {
|
|||
init: function(serverData) {
|
||||
const self = GlobalUI
|
||||
|
||||
self.Search.init(serverData)
|
||||
self.ReactApp.init(serverData, self.openLightbox)
|
||||
self.CreateMap.init(serverData)
|
||||
self.Account.init(serverData)
|
||||
self.ImportDialog.init(serverData, self.openLightbox, self.closeLightbox)
|
||||
self.NotificationIcon.init(serverData)
|
||||
self.Search.init(serverData)
|
||||
|
||||
if ($('#toast').html().trim()) self.notifyUser($('#toast').html())
|
||||
if (serverData.toast) self.notifyUser(serverData.toast)
|
||||
|
||||
// bind lightbox clicks
|
||||
$('.openLightbox').click(function(event) {
|
||||
|
@ -112,10 +110,9 @@ const GlobalUI = {
|
|||
_notifyUser: function(message, opts = {}) {
|
||||
const self = GlobalUI
|
||||
|
||||
const { leaveOpen = false, timeOut = 8000 } = opts
|
||||
|
||||
$('#toast').html(message)
|
||||
self.showDiv('#toast')
|
||||
const { leaveOpen = false, timeOut = 5000 } = opts
|
||||
ReactApp.toast = message
|
||||
ReactApp.render()
|
||||
clearTimeout(self.notifyTimeOut)
|
||||
|
||||
if (!leaveOpen) {
|
||||
|
@ -134,7 +131,8 @@ const GlobalUI = {
|
|||
const { message, opts } = self.notifyQueue.shift()
|
||||
self._notifyUser(message, opts)
|
||||
} else {
|
||||
self.hideDiv('#toast')
|
||||
ReactApp.toast = null
|
||||
ReactApp.render()
|
||||
self.notifying = false
|
||||
}
|
||||
},
|
||||
|
@ -153,5 +151,5 @@ const GlobalUI = {
|
|||
}
|
||||
}
|
||||
|
||||
export { Search, CreateMap, Account, ImportDialog, NotificationIcon }
|
||||
export { ReactApp, Search, CreateMap, ImportDialog }
|
||||
export default GlobalUI
|
||||
|
|
|
@ -316,7 +316,7 @@ const Import = {
|
|||
success: opts.success
|
||||
})
|
||||
|
||||
GlobalUI.hideDiv('#instructions')
|
||||
Map.setHasLearnedTopicCreation(true)
|
||||
},
|
||||
|
||||
createSynapseWithParameters: function(desc, category, permission,
|
||||
|
|
|
@ -50,15 +50,6 @@ const JIT = {
|
|||
*/
|
||||
init: function(serverData) {
|
||||
const self = JIT
|
||||
|
||||
$('.zoomIn').click(self.zoomIn)
|
||||
$('.zoomOut').click(self.zoomOut)
|
||||
|
||||
const zoomExtents = function(event) {
|
||||
self.zoomExtents(event, Visualize.mGraph.canvas)
|
||||
}
|
||||
$('.zoomExtents').click(zoomExtents)
|
||||
|
||||
self.topicDescImage = new Image()
|
||||
self.topicDescImage.src = serverData['topic_description_signifier.png']
|
||||
|
||||
|
@ -123,36 +114,22 @@ const JIT = {
|
|||
prepareVizData: function() {
|
||||
const self = JIT
|
||||
let mapping
|
||||
|
||||
// reset/empty vizData
|
||||
self.vizData = []
|
||||
Visualize.loadLater = false
|
||||
|
||||
const results = self.convertModelsToJIT(DataModel.Topics, DataModel.Synapses)
|
||||
|
||||
self.vizData = results[0]
|
||||
|
||||
// clean up the synapses array in case of any faulty data
|
||||
_.each(results[1], function(synapse) {
|
||||
mapping = synapse.getMapping()
|
||||
DataModel.Synapses.remove(synapse)
|
||||
if (DataModel.Mappings) DataModel.Mappings.remove(mapping)
|
||||
})
|
||||
|
||||
// set up addTopic instructions in case they delete all the topics
|
||||
// i.e. if there are 0 topics at any time, it should have instructions again
|
||||
$('#instructions div').hide()
|
||||
if (Active.Map && Active.Map.authorizeToEdit(Active.Mapper)) {
|
||||
$('#instructions div.addTopic').show()
|
||||
}
|
||||
|
||||
if (self.vizData.length === 0) {
|
||||
GlobalUI.showDiv('#instructions')
|
||||
Map.setHasLearnedTopicCreation(false)
|
||||
Visualize.loadLater = true
|
||||
} else {
|
||||
GlobalUI.hideDiv('#instructions')
|
||||
Map.setHasLearnedTopicCreation(true)
|
||||
}
|
||||
|
||||
Visualize.render()
|
||||
}, // prepareVizData
|
||||
edgeRender: function(adj, canvas) {
|
||||
|
@ -1026,7 +1003,6 @@ const JIT = {
|
|||
Create.newTopic.open()
|
||||
} else if (!Mouse.didPan) {
|
||||
// SINGLE CLICK, no pan
|
||||
Filter.close()
|
||||
TopicCard.hideCard()
|
||||
SynapseCard.hideCard()
|
||||
Create.newTopic.hide()
|
||||
|
|
|
@ -146,7 +146,6 @@ const Listeners = {
|
|||
}
|
||||
|
||||
if (Active.Map && Realtime.inConversation) Realtime.positionVideos()
|
||||
Mobile.resizeTitle()
|
||||
})
|
||||
},
|
||||
centerAndReveal: function(nodes, opts) {
|
||||
|
|
|
@ -15,6 +15,7 @@ const Loading = {
|
|||
Loading.loader.setDensity(41) // default is 40
|
||||
Loading.loader.setRange(0.9) // default is 1.3
|
||||
Loading.loader.show() // Hidden by default
|
||||
$('#loading').hide()
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -1,16 +1,15 @@
|
|||
/* global $, Hogan, Bloodhound, Countable */
|
||||
|
||||
import outdent from 'outdent'
|
||||
import { browserHistory } from 'react-router'
|
||||
|
||||
import Active from '../Active'
|
||||
import DataModel from '../DataModel'
|
||||
import GlobalUI from '../GlobalUI'
|
||||
import Router from '../Router'
|
||||
import GlobalUI, { ReactApp } from '../GlobalUI'
|
||||
import Util from '../Util'
|
||||
|
||||
const InfoBox = {
|
||||
isOpen: false,
|
||||
changing: 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`
|
||||
|
@ -35,12 +34,12 @@ const InfoBox = {
|
|||
data-bip-value="{{desc}}"
|
||||
>{{desc}}</span>`,
|
||||
userImageUrl: '',
|
||||
html: '',
|
||||
init: function(serverData, updateThumbnail) {
|
||||
var self = InfoBox
|
||||
|
||||
self.updateThumbnail = updateThumbnail
|
||||
|
||||
$('.mapInfoIcon').click(self.toggleBox)
|
||||
$('.mapInfoBox').click(function(event) {
|
||||
event.stopPropagation()
|
||||
})
|
||||
|
@ -72,27 +71,18 @@ const InfoBox = {
|
|||
open: function() {
|
||||
var self = InfoBox
|
||||
$('.mapInfoIcon div').addClass('hide')
|
||||
if (!self.isOpen && !self.changing) {
|
||||
self.changing = true
|
||||
$('.mapInfoBox').fadeIn(200, function() {
|
||||
self.changing = false
|
||||
self.isOpen = true
|
||||
})
|
||||
}
|
||||
$('.mapInfoBox').fadeIn(200, function() {
|
||||
self.isOpen = true
|
||||
})
|
||||
},
|
||||
close: function() {
|
||||
var self = InfoBox
|
||||
|
||||
$('.mapInfoIcon div').removeClass('hide')
|
||||
if (!self.changing) {
|
||||
self.changing = true
|
||||
$('.mapInfoBox').fadeOut(200, function() {
|
||||
self.changing = false
|
||||
self.isOpen = false
|
||||
self.hidePermissionSelect()
|
||||
$('.mapContributors .tip').hide()
|
||||
})
|
||||
}
|
||||
$('.mapInfoBox').fadeOut(200, function() {
|
||||
self.isOpen = false
|
||||
self.hidePermissionSelect()
|
||||
$('.mapContributors .tip').hide()
|
||||
})
|
||||
},
|
||||
load: function() {
|
||||
var self = InfoBox
|
||||
|
@ -120,13 +110,8 @@ const InfoBox = {
|
|||
obj['created_at'] = map.get('created_at_clean')
|
||||
obj['updated_at'] = map.get('updated_at_clean')
|
||||
|
||||
var classes = isCreator ? 'yourMap' : ''
|
||||
classes += canEdit ? ' canEdit' : ''
|
||||
classes += shareable ? ' shareable' : ''
|
||||
$('.mapInfoBox').removeClass('shareable yourMap canEdit')
|
||||
.addClass(classes)
|
||||
.html(self.generateBoxHTML.render(obj))
|
||||
|
||||
self.html = self.generateBoxHTML.render(obj)
|
||||
ReactApp.render()
|
||||
self.attachEventListeners()
|
||||
},
|
||||
attachEventListeners: function() {
|
||||
|
@ -192,7 +177,6 @@ const InfoBox = {
|
|||
$('.mapContributors .tip').unbind().click(function(event) {
|
||||
event.stopPropagation()
|
||||
})
|
||||
$('.mapContributors .tip li a').click(Router.intercept)
|
||||
|
||||
$('.mapInfoBox').unbind('.hideTip').bind('click.hideTip', function() {
|
||||
$('.mapContributors .tip').hide()
|
||||
|
@ -393,7 +377,7 @@ const InfoBox = {
|
|||
DataModel.Maps.Mine.remove(map)
|
||||
DataModel.Maps.Shared.remove(map)
|
||||
map.destroy()
|
||||
Router.home()
|
||||
browserHistory.push('/')
|
||||
GlobalUI.notifyUser('Map eliminated')
|
||||
} 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?")
|
||||
|
|
|
@ -2,6 +2,7 @@
|
|||
|
||||
import outdent from 'outdent'
|
||||
import { find as _find } from 'lodash'
|
||||
import { browserHistory } from 'react-router'
|
||||
|
||||
import Active from '../Active'
|
||||
import AutoLayout from '../AutoLayout'
|
||||
|
@ -9,11 +10,10 @@ import Create from '../Create'
|
|||
import DataModel from '../DataModel'
|
||||
import DataModelMap from '../DataModel/Map'
|
||||
import Filter from '../Filter'
|
||||
import GlobalUI from '../GlobalUI'
|
||||
import GlobalUI, { ReactApp } from '../GlobalUI'
|
||||
import JIT from '../JIT'
|
||||
import Loading from '../Loading'
|
||||
import Realtime from '../Realtime'
|
||||
import Router from '../Router'
|
||||
import Selected from '../Selected'
|
||||
import SynapseCard from '../SynapseCard'
|
||||
import TopicCard from '../Views/TopicCard'
|
||||
|
@ -26,143 +26,126 @@ const Map = {
|
|||
events: {
|
||||
editedByActiveMapper: 'Metamaps:Map:events:editedByActiveMapper'
|
||||
},
|
||||
mapIsStarred: false,
|
||||
requests: [],
|
||||
userRequested: false,
|
||||
requestAnswered: false,
|
||||
requestApproved: false,
|
||||
hasLearnedTopicCreation: true,
|
||||
init: function(serverData) {
|
||||
var self = Map
|
||||
|
||||
self.mapIsStarred = serverData.mapIsStarred
|
||||
self.requests = serverData.requests
|
||||
self.setAccessRequest()
|
||||
$('#wrapper').mousedown(function(e) {
|
||||
if (e.button === 1) return false
|
||||
})
|
||||
|
||||
$('.starMap').click(function() {
|
||||
if ($(this).is('.starred')) self.unstar()
|
||||
else self.star()
|
||||
})
|
||||
|
||||
$('.sidebarFork').click(function() {
|
||||
self.fork()
|
||||
})
|
||||
|
||||
GlobalUI.CreateMap.emptyForkMapForm = $('#fork_map').html()
|
||||
|
||||
self.updateStar()
|
||||
|
||||
InfoBox.init(serverData, function updateThumbnail() {
|
||||
self.uploadMapScreenshot()
|
||||
})
|
||||
CheatSheet.init(serverData)
|
||||
|
||||
$('.viewOnly .requestAccess').click(self.requestAccess)
|
||||
|
||||
$(document).on(Map.events.editedByActiveMapper, self.editedByActiveMapper)
|
||||
},
|
||||
setHasLearnedTopicCreation: function(value) {
|
||||
const self = Map
|
||||
self.hasLearnedTopicCreation = value
|
||||
ReactApp.render()
|
||||
},
|
||||
requestAccess: function() {
|
||||
$('.viewOnly').removeClass('sendRequest').addClass('sentRequest')
|
||||
const self = Map
|
||||
self.requests.push({
|
||||
user_id: Active.Mapper.id,
|
||||
answered: false,
|
||||
approved: false
|
||||
})
|
||||
self.setAccessRequest()
|
||||
const mapId = Active.Map.id
|
||||
$.post({
|
||||
url: `/maps/${mapId}/access_request`
|
||||
})
|
||||
GlobalUI.notifyUser('Map creator will be notified of your request')
|
||||
},
|
||||
setAccessRequest: function(requests, activeMapper) {
|
||||
let className = 'isViewOnly '
|
||||
if (activeMapper) {
|
||||
const request = _find(requests, r => r.user_id === activeMapper.id)
|
||||
if (!request) className += 'sendRequest'
|
||||
else if (request && !request.answered) className += 'sentRequest'
|
||||
else if (request && request.answered && !request.approved) className += 'requestDenied'
|
||||
setAccessRequest: function() {
|
||||
const self = Map
|
||||
if (Active.Mapper) {
|
||||
const request = _find(self.requests, r => r.user_id === Active.Mapper.id)
|
||||
if (!request) {
|
||||
self.userRequested = false
|
||||
self.requestAnswered = false
|
||||
self.requestApproved = false
|
||||
}
|
||||
else if (request && !request.answered) {
|
||||
self.userRequested = true
|
||||
self.requestAnswered = false
|
||||
self.requestApproved = false
|
||||
}
|
||||
else if (request && request.answered && !request.approved) {
|
||||
self.userRequested = true
|
||||
self.requestAnswered = true
|
||||
self.requestApproved = false
|
||||
}
|
||||
}
|
||||
$('.viewOnly').removeClass('sendRequest sentRequest requestDenied').addClass(className)
|
||||
ReactApp.render()
|
||||
},
|
||||
launch: function(id) {
|
||||
var start = function(data) {
|
||||
Active.Map = new DataModelMap(data.map)
|
||||
DataModel.Mappers = new DataModel.MapperCollection(data.mappers)
|
||||
DataModel.Collaborators = new DataModel.MapperCollection(data.collaborators)
|
||||
DataModel.Topics = new DataModel.TopicCollection(data.topics)
|
||||
DataModel.Synapses = new DataModel.SynapseCollection(data.synapses)
|
||||
DataModel.Mappings = new DataModel.MappingCollection(data.mappings)
|
||||
DataModel.Messages = data.messages
|
||||
DataModel.Stars = data.stars
|
||||
DataModel.attachCollectionEvents()
|
||||
|
||||
var map = Active.Map
|
||||
var mapper = Active.Mapper
|
||||
|
||||
document.title = map.get('name') + ' | Metamaps'
|
||||
|
||||
// add class to .wrapper for specifying whether you can edit the map
|
||||
if (map.authorizeToEdit(mapper)) {
|
||||
$('.wrapper').addClass('canEditMap')
|
||||
} else {
|
||||
Map.setAccessRequest(data.requests, mapper)
|
||||
}
|
||||
|
||||
// add class to .wrapper for specifying if the map can
|
||||
// be collaborated on
|
||||
if (map.get('permission') === 'commons') {
|
||||
$('.wrapper').addClass('commonsMap')
|
||||
}
|
||||
|
||||
Map.updateStar()
|
||||
|
||||
// set filter mapper H3 text
|
||||
$('#filter_by_mapper h3').html('MAPPERS')
|
||||
|
||||
// build and render the visualization
|
||||
const self = Map
|
||||
var dataIsReadySetupMap = function() {
|
||||
Map.setAccessRequest()
|
||||
Visualize.type = 'ForceDirected'
|
||||
JIT.prepareVizData()
|
||||
|
||||
// update filters
|
||||
Filter.reset()
|
||||
|
||||
// reset selected arrays
|
||||
Selected.reset()
|
||||
|
||||
// set the proper mapinfobox content
|
||||
InfoBox.load()
|
||||
|
||||
// these three update the actual filter box with the right list items
|
||||
Filter.reset()
|
||||
Filter.checkMetacodes()
|
||||
Filter.checkSynapses()
|
||||
Filter.checkMappers()
|
||||
|
||||
Realtime.startActiveMap()
|
||||
Loading.hide()
|
||||
|
||||
// for mobile
|
||||
$('#header_content').html(map.get('name'))
|
||||
document.title = Active.Map.get('name') + ' | Metamaps'
|
||||
ReactApp.mobileTitle = Active.Map.get('name')
|
||||
ReactApp.render()
|
||||
}
|
||||
function isLoaded() {
|
||||
if (InfoBox.generateBoxHTML) dataIsReadySetupMap()
|
||||
else setTimeout(() => isLoaded(), 50)
|
||||
}
|
||||
if (Active.Map && Active.Map.id === id) {
|
||||
isLoaded()
|
||||
}
|
||||
else {
|
||||
Loading.show()
|
||||
$.ajax({
|
||||
url: '/maps/' + id + '/contains.json',
|
||||
success: function(data) {
|
||||
Active.Map = new DataModelMap(data.map)
|
||||
DataModel.Mappers = new DataModel.MapperCollection(data.mappers)
|
||||
DataModel.Collaborators = new DataModel.MapperCollection(data.collaborators)
|
||||
DataModel.Topics = new DataModel.TopicCollection(data.topics)
|
||||
DataModel.Synapses = new DataModel.SynapseCollection(data.synapses)
|
||||
DataModel.Mappings = new DataModel.MappingCollection(data.mappings)
|
||||
DataModel.Messages = data.messages
|
||||
DataModel.Stars = data.stars
|
||||
DataModel.attachCollectionEvents()
|
||||
self.requests = data.requests
|
||||
isLoaded()
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
$.ajax({
|
||||
url: '/maps/' + id + '/contains.json',
|
||||
success: start
|
||||
})
|
||||
},
|
||||
end: function() {
|
||||
if (Active.Map) {
|
||||
$('.wrapper').removeClass('canEditMap commonsMap')
|
||||
$('.main').removeClass('compressed')
|
||||
AutoLayout.resetSpiral()
|
||||
|
||||
$('.rightclickmenu').remove()
|
||||
TopicCard.hideCard()
|
||||
SynapseCard.hideCard()
|
||||
Create.newTopic.hide(true) // true means force (and override pinned)
|
||||
Create.newSynapse.hide()
|
||||
Filter.close()
|
||||
InfoBox.close()
|
||||
Realtime.endActiveMap()
|
||||
$('.viewOnly').removeClass('isViewOnly')
|
||||
}
|
||||
},
|
||||
updateStar: function() {
|
||||
if (!Active.Mapper || !DataModel.Stars) return
|
||||
// update the star/unstar icon
|
||||
if (DataModel.Stars.find(function(s) { return s.user_id === Active.Mapper.id })) {
|
||||
$('.starMap').addClass('starred')
|
||||
$('.starMap .tooltipsAbove').html('Unstar')
|
||||
} else {
|
||||
$('.starMap').removeClass('starred')
|
||||
$('.starMap .tooltipsAbove').html('Star')
|
||||
self.requests = []
|
||||
self.hasLearnedTopicCreation = true
|
||||
}
|
||||
},
|
||||
star: function() {
|
||||
|
@ -173,7 +156,8 @@ const Map = {
|
|||
DataModel.Stars.push({ user_id: Active.Mapper.id, map_id: Active.Map.id })
|
||||
DataModel.Maps.Starred.add(Active.Map)
|
||||
GlobalUI.notifyUser('Map is now starred')
|
||||
self.updateStar()
|
||||
self.mapIsStarred = true
|
||||
ReactApp.render()
|
||||
},
|
||||
unstar: function() {
|
||||
var self = Map
|
||||
|
@ -182,7 +166,8 @@ const Map = {
|
|||
$.post('/maps/' + Active.Map.id + '/unstar')
|
||||
DataModel.Stars = DataModel.Stars.filter(function(s) { return s.user_id !== Active.Mapper.id })
|
||||
DataModel.Maps.Starred.remove(Active.Map)
|
||||
self.updateStar()
|
||||
self.mapIsStarred = false
|
||||
ReactApp.render()
|
||||
},
|
||||
fork: function() {
|
||||
GlobalUI.openLightbox('forkmap')
|
||||
|
@ -232,7 +217,7 @@ const Map = {
|
|||
var map = Active.Map
|
||||
DataModel.Maps.Active.remove(map)
|
||||
DataModel.Maps.Featured.remove(map)
|
||||
Router.home()
|
||||
browserHistory.push('/')
|
||||
GlobalUI.notifyUser('Sorry! That map has been changed to Private.')
|
||||
},
|
||||
cantEditNow: function() {
|
||||
|
@ -245,7 +230,7 @@ const Map = {
|
|||
confirmString += 'Do you want to reload and enable realtime collaboration?'
|
||||
var c = window.confirm(confirmString)
|
||||
if (c) {
|
||||
Router.maps(Active.Map.id)
|
||||
window.location.reload()
|
||||
}
|
||||
},
|
||||
editedByActiveMapper: function() {
|
||||
|
|
|
@ -151,8 +151,6 @@ let Realtime = {
|
|||
config: { DOUBLE_CLICK_TOLERANCE: 200 }
|
||||
})
|
||||
self.room.videoAdded(self.handleVideoAdded)
|
||||
|
||||
self.startActiveMap()
|
||||
} // if Active.Mapper
|
||||
},
|
||||
addJuntoListeners: function() {
|
||||
|
@ -201,9 +199,6 @@ let Realtime = {
|
|||
self.leaveMap()
|
||||
$('.collabCompass').remove()
|
||||
if (self.room) self.room.leave()
|
||||
ChatView.hide()
|
||||
ChatView.close()
|
||||
ChatView.reset()
|
||||
Cable.unsubscribeFromMap()
|
||||
},
|
||||
turnOn: function(notify) {
|
||||
|
@ -228,7 +223,6 @@ let Realtime = {
|
|||
ChatView.setNewMap()
|
||||
ChatView.addParticipant(self.activeMapper)
|
||||
ChatView.addMessages(new DataModel.MessageCollection(DataModel.Messages), true)
|
||||
ChatView.show()
|
||||
},
|
||||
setupLocalEvents: function() {
|
||||
var self = Realtime
|
||||
|
|
|
@ -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
|
|
@ -7,10 +7,10 @@ import AutoLayout from './AutoLayout'
|
|||
import Create from './Create'
|
||||
import DataModel from './DataModel'
|
||||
import Filter from './Filter'
|
||||
import GlobalUI from './GlobalUI'
|
||||
import GlobalUI, { ReactApp } from './GlobalUI'
|
||||
import JIT from './JIT'
|
||||
import Loading from './Loading'
|
||||
import Map from './Map'
|
||||
import Router from './Router'
|
||||
import Selected from './Selected'
|
||||
import Settings from './Settings'
|
||||
import SynapseCard from './SynapseCard'
|
||||
|
@ -36,48 +36,41 @@ const Topic = {
|
|||
} else callback(DataModel.Topics.get(id))
|
||||
},
|
||||
launch: function(id) {
|
||||
var start = function(data) {
|
||||
Active.Topic = new DataModel.Topic(data.topic)
|
||||
DataModel.Creators = new DataModel.MapperCollection(data.creators)
|
||||
DataModel.Topics = new DataModel.TopicCollection([data.topic].concat(data.relatives))
|
||||
DataModel.Synapses = new DataModel.SynapseCollection(data.synapses)
|
||||
DataModel.attachCollectionEvents()
|
||||
|
||||
document.title = Active.Topic.get('name') + ' | Metamaps'
|
||||
|
||||
// set filter mapper H3 text
|
||||
$('#filter_by_mapper h3').html('CREATORS')
|
||||
|
||||
// build and render the visualization
|
||||
var dataIsReadySetupTopic = function() {
|
||||
Visualize.type = 'RGraph'
|
||||
JIT.prepareVizData()
|
||||
|
||||
// update filters
|
||||
Filter.reset()
|
||||
|
||||
// reset selected arrays
|
||||
Selected.reset()
|
||||
|
||||
// these three update the actual filter box with the right list items
|
||||
Filter.reset()
|
||||
Filter.checkMetacodes()
|
||||
Filter.checkSynapses()
|
||||
Filter.checkMappers()
|
||||
|
||||
// for mobile
|
||||
$('#header_content').html(Active.Topic.get('name'))
|
||||
document.title = Active.Topic.get('name') + ' | Metamaps'
|
||||
ReactApp.mobileTitle = Active.Topic.get('name')
|
||||
ReactApp.render()
|
||||
}
|
||||
if (Active.Topic && Active.Topic.id === id) {
|
||||
dataIsReadySetupTopic()
|
||||
}
|
||||
else {
|
||||
Loading.show()
|
||||
$.ajax({
|
||||
url: '/topics/' + id + '/network.json',
|
||||
success: function(data) {
|
||||
Active.Topic = new DataModel.Topic(data.topic)
|
||||
DataModel.Creators = new DataModel.MapperCollection(data.creators)
|
||||
DataModel.Topics = new DataModel.TopicCollection([data.topic].concat(data.relatives))
|
||||
DataModel.Synapses = new DataModel.SynapseCollection(data.synapses)
|
||||
DataModel.attachCollectionEvents()
|
||||
dataIsReadySetupTopic()
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
$.ajax({
|
||||
url: '/topics/' + id + '/network.json',
|
||||
success: start
|
||||
})
|
||||
},
|
||||
end: function() {
|
||||
if (Active.Topic) {
|
||||
$('.rightclickmenu').remove()
|
||||
TopicCard.hideCard()
|
||||
SynapseCard.hideCard()
|
||||
Filter.close()
|
||||
}
|
||||
},
|
||||
centerOn: function(nodeid, callback) {
|
||||
|
@ -90,7 +83,6 @@ const Topic = {
|
|||
if (callback) callback()
|
||||
}
|
||||
})
|
||||
Router.navigate('/topics/' + nodeid)
|
||||
Active.Topic = DataModel.Topics.get(nodeid)
|
||||
}
|
||||
},
|
||||
|
@ -293,8 +285,7 @@ const Topic = {
|
|||
return
|
||||
}
|
||||
|
||||
// hide the 'double-click to add a topic' message
|
||||
GlobalUI.hideDiv('#instructions')
|
||||
Map.setHasLearnedTopicCreation(true)
|
||||
|
||||
$(document).trigger(Map.events.editedByActiveMapper)
|
||||
|
||||
|
@ -327,8 +318,7 @@ const Topic = {
|
|||
getTopicFromAutocomplete: function(id) {
|
||||
var self = Topic
|
||||
|
||||
// hide the 'double-click to add a topic' message
|
||||
GlobalUI.hideDiv('#instructions')
|
||||
Map.setHasLearnedTopicCreation(true)
|
||||
|
||||
$(document).trigger(Map.events.editedByActiveMapper)
|
||||
|
||||
|
|
|
@ -10,14 +10,14 @@ import ReactDOM from 'react-dom'
|
|||
import Active from '../Active'
|
||||
import DataModel from '../DataModel'
|
||||
import Realtime from '../Realtime'
|
||||
import MapChat from '../../components/MapChat'
|
||||
import ReactApp from '../GlobalUI/ReactApp'
|
||||
|
||||
const ChatView = {
|
||||
isOpen: false,
|
||||
unreadMessages: 0,
|
||||
messages: new Backbone.Collection(),
|
||||
conversationLive: false,
|
||||
isParticipating: false,
|
||||
mapChat: null,
|
||||
domId: 'chat-box-wrapper',
|
||||
init: function(urls) {
|
||||
const self = ChatView
|
||||
|
@ -34,46 +34,32 @@ const ChatView = {
|
|||
},
|
||||
setNewMap: function() {
|
||||
const self = ChatView
|
||||
self.unreadMessages = 0
|
||||
self.isOpen = false
|
||||
self.conversationLive = false
|
||||
self.isParticipating = false
|
||||
self.alertSound = true // whether to play sounds on arrival of new messages or not
|
||||
self.cursorsShowing = true
|
||||
self.videosShowing = true
|
||||
self.participants = new Backbone.Collection()
|
||||
self.messages = new Backbone.Collection()
|
||||
self.render()
|
||||
},
|
||||
show: () => {
|
||||
$('#' + ChatView.domId).show()
|
||||
},
|
||||
hide: () => {
|
||||
$('#' + ChatView.domId).hide()
|
||||
},
|
||||
render: () => {
|
||||
if (!Active.Map) return
|
||||
const self = ChatView
|
||||
self.mapChat = ReactDOM.render(React.createElement(MapChat, {
|
||||
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))
|
||||
ReactApp.render()
|
||||
},
|
||||
onOpen: () => {
|
||||
const self = ChatView
|
||||
self.isOpen = true
|
||||
self.unreadMessages = 0
|
||||
self.render()
|
||||
$(document).trigger(ChatView.events.openTray)
|
||||
},
|
||||
onClose: () => {
|
||||
const self = ChatView
|
||||
self.isOpen = false
|
||||
$(document).trigger(ChatView.events.closeTray)
|
||||
},
|
||||
addParticipant: participant => {
|
||||
|
@ -119,12 +105,6 @@ const ChatView = {
|
|||
ChatView.participants.forEach(p => p.set({isParticipating: false, isPending: false}))
|
||||
ChatView.render()
|
||||
},
|
||||
close: () => {
|
||||
ChatView.mapChat && ChatView.mapChat.close()
|
||||
},
|
||||
open: () => {
|
||||
ChatView.mapChat && ChatView.mapChat.open()
|
||||
},
|
||||
videoToggleClick: function() {
|
||||
ChatView.videosShowing = !ChatView.videosShowing
|
||||
$(document).trigger(ChatView.videosShowing ? ChatView.events.videosOn : ChatView.events.videosOff)
|
||||
|
@ -144,11 +124,10 @@ const ChatView = {
|
|||
},
|
||||
addMessage: (message, isInitial, wasMe) => {
|
||||
const self = ChatView
|
||||
if (!isInitial) self.mapChat.newMessage()
|
||||
if (!isInitial && !self.isOpen) self.unreadMessages += 1
|
||||
if (!wasMe && !isInitial && self.alertSound) self.sound.play('receivechat')
|
||||
self.messages.add(message)
|
||||
self.render()
|
||||
if (!isInitial) self.mapChat.scroll()
|
||||
if (!isInitial && self.isOpen) self.render()
|
||||
},
|
||||
sendChatMessage: message => {
|
||||
var self = ChatView
|
||||
|
@ -174,23 +153,9 @@ const ChatView = {
|
|||
// passed to this function
|
||||
addMessages: (messages, isInitial, wasMe) => {
|
||||
messages.models.forEach(m => ChatView.addMessage(m, isInitial, wasMe))
|
||||
},
|
||||
reset: () => {
|
||||
ChatView.mapChat && ChatView.mapChat.reset()
|
||||
ChatView.participants && ChatView.participants.reset()
|
||||
ChatView.messages && ChatView.messages.reset()
|
||||
ChatView.render()
|
||||
}
|
||||
}
|
||||
|
||||
// ChatView.prototype.scrollMessages = function(duration) {
|
||||
// duration = duration || 0
|
||||
|
||||
// this.$messages.animate({
|
||||
// scrollTop: this.$messages[0].scrollHeight
|
||||
// }, duration)
|
||||
// }
|
||||
|
||||
/**
|
||||
* @class
|
||||
* @static
|
||||
|
|
|
@ -1,21 +1,68 @@
|
|||
/* global $ */
|
||||
|
||||
import React from 'react'
|
||||
import ReactDOM from 'react-dom' // TODO ensure this isn't a double import
|
||||
|
||||
import Active from '../Active'
|
||||
import DataModel from '../DataModel'
|
||||
import GlobalUI from '../GlobalUI'
|
||||
import Realtime from '../Realtime'
|
||||
import GlobalUI, { ReactApp } from '../GlobalUI'
|
||||
import Loading from '../Loading'
|
||||
import Maps from '../../components/Maps'
|
||||
|
||||
const ExploreMaps = {
|
||||
pending: false,
|
||||
mapper: null,
|
||||
updateFromPath: function(path) {
|
||||
const self = ExploreMaps
|
||||
const [_unused, generalSection, specificSection, id] = path.split('/')
|
||||
|
||||
if (generalSection === 'explore') {
|
||||
const capitalize = specificSection.charAt(0).toUpperCase() + specificSection.slice(1)
|
||||
self.setCollection(DataModel.Maps[capitalize])
|
||||
switch (capitalize) {
|
||||
case 'Active':
|
||||
document.title = 'Explore Active Maps | Metamaps'
|
||||
ReactApp.mobileTitle = 'Recently Active'
|
||||
break
|
||||
case 'Featured':
|
||||
document.title = 'Explore Featured Maps | Metamaps'
|
||||
ReactApp.mobileTitle = 'Featured Maps'
|
||||
break
|
||||
case 'Starred':
|
||||
document.title = 'Starred Maps | Metamaps'
|
||||
ReactApp.mobileTitle = 'Starred Maps'
|
||||
break
|
||||
case 'Shared':
|
||||
document.title = 'Shared Maps | Metamaps'
|
||||
ReactApp.mobileTitle = 'Shared With Me'
|
||||
break
|
||||
case 'Mine':
|
||||
document.title = 'My Maps | Metamaps'
|
||||
ReactApp.mobileTitle = 'My Maps'
|
||||
break
|
||||
}
|
||||
} else if (generalSection === '') {
|
||||
self.setCollection(DataModel.Maps.Active)
|
||||
document.title = 'Explore Active Maps | Metamaps'
|
||||
ReactApp.mobileTitle = 'Recently 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) {
|
||||
var self = ExploreMaps
|
||||
|
||||
if (self.collection) {
|
||||
self.collection.off('add', self.render)
|
||||
self.collection.off('successOnFetch', self.handleSuccess)
|
||||
|
@ -26,55 +73,9 @@ const ExploreMaps = {
|
|||
self.collection.on('successOnFetch', self.handleSuccess)
|
||||
self.collection.on('errorOnFetch', self.handleError)
|
||||
},
|
||||
render: function(cb) {
|
||||
var self = ExploreMaps
|
||||
|
||||
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()
|
||||
render: function() {
|
||||
ReactApp.resize()
|
||||
ReactApp.render()
|
||||
Loading.hide()
|
||||
},
|
||||
loadMore: function() {
|
||||
|
@ -85,14 +86,13 @@ const ExploreMaps = {
|
|||
}
|
||||
self.render()
|
||||
},
|
||||
handleSuccess: function(cb) {
|
||||
handleSuccess: function() {
|
||||
var self = ExploreMaps
|
||||
self.pending = false
|
||||
if (self.collection && self.collection.id === 'mapper') {
|
||||
self.fetchUserThenRender(cb)
|
||||
self.fetchUserThenRender()
|
||||
} else {
|
||||
self.render(cb)
|
||||
Loading.hide()
|
||||
self.render()
|
||||
}
|
||||
},
|
||||
handleError: function() {
|
||||
|
@ -103,8 +103,8 @@ const ExploreMaps = {
|
|||
var self = ExploreMaps
|
||||
|
||||
if (self.mapper && self.mapper.id === self.collection.mapperId) {
|
||||
self.render(cb)
|
||||
return Loading.hide()
|
||||
self.render()
|
||||
return
|
||||
}
|
||||
|
||||
// first load the mapper object and then call the render function
|
||||
|
@ -112,14 +112,42 @@ const ExploreMaps = {
|
|||
url: '/users/' + self.collection.mapperId + '/details.json',
|
||||
success: function(response) {
|
||||
self.mapper = response
|
||||
self.render(cb)
|
||||
Loading.hide()
|
||||
document.title = self.mapper.name + ' | Metamaps'
|
||||
ReactApp.mobileTitle = self.mapper.name
|
||||
self.render()
|
||||
},
|
||||
error: function() {
|
||||
self.render(cb)
|
||||
Loading.hide()
|
||||
self.render()
|
||||
}
|
||||
})
|
||||
},
|
||||
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()
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -5,69 +5,59 @@ import ReactDOM from 'react-dom'
|
|||
|
||||
import Active from '../Active'
|
||||
import Visualize from '../Visualize'
|
||||
import GlobalUI from '../GlobalUI'
|
||||
|
||||
import ReactTopicCard from '../../components/TopicCard'
|
||||
import GlobalUI, { ReactApp } from '../GlobalUI'
|
||||
|
||||
const TopicCard = {
|
||||
openTopicCard: null, // stores the topic that's currently open
|
||||
openTopic: null, // stores the topic that's currently open
|
||||
metacodeSets: [],
|
||||
redrawCanvas: () => {
|
||||
Visualize.mGraph.plot()
|
||||
},
|
||||
init: function(serverData) {
|
||||
const self = TopicCard
|
||||
self.metacodeSets = serverData.metacodeSets
|
||||
},
|
||||
populateShowCard: function(topic) {
|
||||
onTopicFollow: topic => {
|
||||
const self = TopicCard
|
||||
ReactDOM.render(
|
||||
React.createElement(ReactTopicCard, {
|
||||
topic: topic,
|
||||
ActiveMapper: Active.Mapper,
|
||||
updateTopic: obj => {
|
||||
topic.save(obj, { success: topic => self.populateShowCard(topic) })
|
||||
},
|
||||
onFollow: () => {
|
||||
const isFollowing = topic.isFollowedBy(Active.Mapper)
|
||||
$.post({
|
||||
url: `/topics/${topic.id}/${isFollowing ? 'un' : ''}follow`
|
||||
})
|
||||
if (isFollowing) {
|
||||
GlobalUI.notifyUser('You are no longer following this topic')
|
||||
Active.Mapper.unfollowTopic(topic.id)
|
||||
} else {
|
||||
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
|
||||
$('.showcard').draggable({
|
||||
handle: '.metacodeImage',
|
||||
stop: function() {
|
||||
$(this).height('auto')
|
||||
}
|
||||
const isFollowing = topic.isFollowedBy(Active.Mapper)
|
||||
$.post({
|
||||
url: `/topics/${topic.id}/${isFollowing ? 'un' : ''}follow`
|
||||
})
|
||||
if (isFollowing) {
|
||||
GlobalUI.notifyUser('You are no longer following this topic')
|
||||
Active.Mapper.unfollowTopic(topic.id)
|
||||
} else {
|
||||
GlobalUI.notifyUser('You are now following this topic')
|
||||
Active.Mapper.followTopic(topic.id)
|
||||
}
|
||||
self.render()
|
||||
},
|
||||
showCard: function(node, opts) {
|
||||
updateTopic: (topic, obj) => {
|
||||
const self = TopicCard
|
||||
topic.save(obj, { success: self.render })
|
||||
},
|
||||
render: function() {
|
||||
ReactApp.render()
|
||||
},
|
||||
showCard: function(node, opts = {}) {
|
||||
var self = TopicCard
|
||||
if (!opts) opts = {}
|
||||
var topic = node.getData('topic')
|
||||
self.openTopicCard = topic
|
||||
// populate the card that's about to show with the right topics data
|
||||
self.populateShowCard(topic)
|
||||
return $('.showcard').fadeIn('fast', () => opts.complete && opts.complete())
|
||||
self.openTopic = topic
|
||||
self.render()
|
||||
$('.showcard').fadeIn('fast', () => {
|
||||
$('.showcard').draggable({
|
||||
handle: '.metacodeImage',
|
||||
stop: function() {
|
||||
$(this).height('auto')
|
||||
}
|
||||
})
|
||||
opts.complete && opts.complete()
|
||||
})
|
||||
},
|
||||
hideCard: function() {
|
||||
var self = TopicCard
|
||||
$('.showcard').fadeOut('fast')
|
||||
self.openTopicCard = null
|
||||
self.openTopic = null
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -8,7 +8,6 @@ import Active from './Active'
|
|||
import DataModel from './DataModel'
|
||||
import JIT from './JIT'
|
||||
import Loading from './Loading'
|
||||
import Router from './Router'
|
||||
import TopicCard from './Views/TopicCard'
|
||||
|
||||
const Visualize = {
|
||||
|
@ -198,19 +197,6 @@ const Visualize = {
|
|||
}
|
||||
}
|
||||
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() {
|
||||
Visualize.mGraph.graph.empty()
|
||||
|
|
|
@ -9,8 +9,7 @@ import DataModel from './DataModel'
|
|||
import Debug from './Debug'
|
||||
import Filter from './Filter'
|
||||
import GlobalUI, {
|
||||
Search, CreateMap, ImportDialog, Account as GlobalUIAccount,
|
||||
NotificationIcon
|
||||
ReactApp, Search, CreateMap, ImportDialog
|
||||
} from './GlobalUI'
|
||||
import Import from './Import'
|
||||
import JIT from './JIT'
|
||||
|
@ -18,12 +17,10 @@ import Listeners from './Listeners'
|
|||
import Loading from './Loading'
|
||||
import Map, { CheatSheet, InfoBox } from './Map'
|
||||
import Mapper from './Mapper'
|
||||
import Mobile from './Mobile'
|
||||
import Mouse from './Mouse'
|
||||
import Organize from './Organize'
|
||||
import PasteInput from './PasteInput'
|
||||
import Realtime from './Realtime'
|
||||
import Router from './Router'
|
||||
import Selected from './Selected'
|
||||
import Settings from './Settings'
|
||||
import Synapse from './Synapse'
|
||||
|
@ -45,11 +42,10 @@ Metamaps.DataModel = DataModel
|
|||
Metamaps.Debug = Debug
|
||||
Metamaps.Filter = Filter
|
||||
Metamaps.GlobalUI = GlobalUI
|
||||
Metamaps.GlobalUI.ReactApp = ReactApp
|
||||
Metamaps.GlobalUI.Search = Search
|
||||
Metamaps.GlobalUI.CreateMap = CreateMap
|
||||
Metamaps.GlobalUI.Account = GlobalUIAccount
|
||||
Metamaps.GlobalUI.ImportDialog = ImportDialog
|
||||
Metamaps.GlobalUI.NotificationIcon = NotificationIcon
|
||||
Metamaps.Import = Import
|
||||
Metamaps.JIT = JIT
|
||||
Metamaps.Listeners = Listeners
|
||||
|
@ -59,12 +55,10 @@ Metamaps.Map.CheatSheet = CheatSheet
|
|||
Metamaps.Map.InfoBox = InfoBox
|
||||
Metamaps.Maps = {}
|
||||
Metamaps.Mapper = Mapper
|
||||
Metamaps.Mobile = Mobile
|
||||
Metamaps.Mouse = Mouse
|
||||
Metamaps.Organize = Organize
|
||||
Metamaps.PasteInput = PasteInput
|
||||
Metamaps.Realtime = Realtime
|
||||
Metamaps.Router = Router
|
||||
Metamaps.Selected = Selected
|
||||
Metamaps.Settings = Settings
|
||||
Metamaps.Synapse = Synapse
|
||||
|
@ -86,26 +80,6 @@ document.addEventListener('DOMContentLoaded', function() {
|
|||
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
|
||||
|
|
47
frontend/src/components/App/AccountMenu.js
Normal file
47
frontend/src/components/App/AccountMenu.js
Normal file
|
@ -0,0 +1,47 @@
|
|||
import React, { Component, PropTypes } from 'react'
|
||||
|
||||
import onClickOutsideAddon from 'react-onclickoutside'
|
||||
|
||||
class AccountMenu extends Component {
|
||||
static propTypes = {
|
||||
currentUser: PropTypes.object,
|
||||
onInviteClick: PropTypes.func,
|
||||
closeBox: PropTypes.func
|
||||
}
|
||||
|
||||
handleClickOutside = () => {
|
||||
this.props.closeBox()
|
||||
}
|
||||
|
||||
render () {
|
||||
const { currentUser, onInviteClick } = this.props
|
||||
return <div>
|
||||
<img className="sidebarAccountImage" src={currentUser.get('image')} width="48" height="48" />
|
||||
<h3 className="accountHeader">{currentUser.get('name')}</h3>
|
||||
<ul>
|
||||
<li className="accountListItem accountSettings">
|
||||
<div className="accountIcon"></div>
|
||||
<a href={`/users/${currentUser.id}/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" onClick={onInviteClick}>
|
||||
<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 onClickOutsideAddon(AccountMenu)
|
57
frontend/src/components/App/LoginForm.js
Normal file
57
frontend/src/components/App/LoginForm.js
Normal file
|
@ -0,0 +1,57 @@
|
|||
import React, { Component, PropTypes } from 'react'
|
||||
|
||||
import onClickOutsideAddon from 'react-onclickoutside'
|
||||
|
||||
class LoginForm extends Component {
|
||||
static propTypes = {
|
||||
loginFormAuthToken: PropTypes.string,
|
||||
closeBox: PropTypes.func
|
||||
}
|
||||
|
||||
constructor(props) {
|
||||
super(props)
|
||||
this.state = { token: '' }
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
const token = document.head.getElementsByTagName('meta')['csrf-token'].content
|
||||
this.setState({token})
|
||||
}
|
||||
|
||||
emailInputDidMount(node) {
|
||||
node.focus()
|
||||
}
|
||||
|
||||
handleClickOutside = () => {
|
||||
this.props.closeBox()
|
||||
}
|
||||
|
||||
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={this.state.token} />
|
||||
<div className="accountImage"></div>
|
||||
<div className="accountInput accountEmail">
|
||||
<input placeholder="Email" type="email" name="user[email]" id="user_email" ref={this.emailInputDidMount}/>
|
||||
</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 onClickOutsideAddon(LoginForm)
|
66
frontend/src/components/App/MobileHeader.js
Normal file
66
frontend/src/components/App/MobileHeader.js
Normal file
|
@ -0,0 +1,66 @@
|
|||
import React, { Component, PropTypes } from 'react'
|
||||
import { Link } from 'react-router'
|
||||
|
||||
class MobileHeader extends Component {
|
||||
static propTypes = {
|
||||
unreadNotificationsCount: PropTypes.number,
|
||||
currentUser: PropTypes.object,
|
||||
mobileTitle: PropTypes.string,
|
||||
mobileTitleWidth: PropTypes.number,
|
||||
onTitleClick: PropTypes.func
|
||||
}
|
||||
|
||||
constructor(props) {
|
||||
super(props)
|
||||
this.state = {open: false}
|
||||
}
|
||||
|
||||
toggle = () => {
|
||||
this.setState({open: !this.state.open})
|
||||
}
|
||||
|
||||
render() {
|
||||
const { unreadNotificationsCount, currentUser, mobileTitle, mobileTitleWidth, onTitleClick } = this.props
|
||||
const { open } = this.state
|
||||
return <div>
|
||||
<div id="mobile_header">
|
||||
<div id="header_content" style={{width: `${mobileTitleWidth}px`}} onClick={onTitleClick}>
|
||||
{mobileTitle}
|
||||
</div>
|
||||
<div id="menu_icon" onClick={this.toggle}>
|
||||
{unreadNotificationsCount > 0 && <div className="unread-notifications-dot"></div>}
|
||||
</div>
|
||||
</div>
|
||||
{open && <div id="mobile_menu">
|
||||
{currentUser && <ul onClick={this.toggle}>
|
||||
<li className="mobileMenuUser">
|
||||
<Link to={`/explore/mapper/${currentUser.id}`}>
|
||||
<img src={currentUser.get('image')} width="32" height="32" />
|
||||
<span>{currentUser.get('name')}</span>
|
||||
</Link>
|
||||
</li>
|
||||
<li><a href="/maps/new">New Map</a></li>
|
||||
<li><Link to="/explore/mine">My Maps</Link></li>
|
||||
<li><Link to="/explore/shared">Shared With Me</Link></li>
|
||||
<li><Link to="/explore/starred">Starred By Me</Link></li>
|
||||
<li><Link to="/explore/active">All Maps</Link></li>
|
||||
<li><a href={`/users/${currentUser.id}/edit`}>Account</a></li>
|
||||
<li className="notifications">
|
||||
<a href="/notifications">Notifications</a>
|
||||
{unreadNotificationsCount > 0 && <div className="unread-notifications-dot"></div>}
|
||||
</li>
|
||||
<li><a id="Logout" href="/logout">Sign Out</a></li>
|
||||
</ul>}
|
||||
{!currentUser && <ul onClick={this.toggle}>
|
||||
<li><a href="/">Home</a></li>
|
||||
<li><Link to="/explore/active">All Maps</Link></li>
|
||||
<li><Link to="/explore/featured">Featured Maps</Link></li>
|
||||
<li><a href="/request">Request Invite</a></li>
|
||||
<li><a href="/login">Login</a></li>
|
||||
</ul>}
|
||||
</div>}
|
||||
</div>
|
||||
}
|
||||
}
|
||||
|
||||
export default MobileHeader
|
|
@ -1,6 +1,11 @@
|
|||
import React, { PropTypes, Component } from 'react'
|
||||
|
||||
class NotificationIcon extends Component {
|
||||
|
||||
static propTypes = {
|
||||
unreadNotificationsCount: PropTypes.number
|
||||
}
|
||||
|
||||
constructor(props) {
|
||||
super(props)
|
||||
|
||||
|
@ -31,8 +36,4 @@ class NotificationIcon extends Component {
|
|||
}
|
||||
}
|
||||
|
||||
NotificationIcon.propTypes = {
|
||||
unreadNotificationsCount: PropTypes.number
|
||||
}
|
||||
|
||||
export default NotificationIcon
|
15
frontend/src/components/App/Toast.js
Normal file
15
frontend/src/components/App/Toast.js
Normal file
|
@ -0,0 +1,15 @@
|
|||
import React, { Component, PropTypes } from 'react'
|
||||
|
||||
class Toast extends Component {
|
||||
static propTypes = {
|
||||
message: PropTypes.string
|
||||
}
|
||||
|
||||
render () {
|
||||
const { message } = this.props
|
||||
const html = {__html: message}
|
||||
return message ? <p id="toast" className="toast" dangerouslySetInnerHTML={html} /> : null
|
||||
}
|
||||
}
|
||||
|
||||
export default Toast
|
38
frontend/src/components/App/UpperLeftUI.js
Normal file
38
frontend/src/components/App/UpperLeftUI.js
Normal file
|
@ -0,0 +1,38 @@
|
|||
import React, { Component, PropTypes } from 'react'
|
||||
import { Link } from 'react-router'
|
||||
|
||||
class UpperLeftUI extends Component {
|
||||
static propTypes = {
|
||||
currentUser: PropTypes.object,
|
||||
map: PropTypes.object,
|
||||
userRequested: PropTypes.bool,
|
||||
requestAnswered: PropTypes.bool,
|
||||
requestApproved: PropTypes.bool,
|
||||
onRequestClick: PropTypes.func
|
||||
}
|
||||
|
||||
render () {
|
||||
const { map, currentUser, userRequested, requestAnswered, requestApproved, onRequestClick } = this.props
|
||||
return <div className="upperLeftUI">
|
||||
<div className="homeButton">
|
||||
{currentUser && <Link to="/">METAMAPS</Link>}
|
||||
{!currentUser && <a href="/">METAMAPS</a>}
|
||||
</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>
|
||||
{map && !map.authorizeToEdit(currentUser) && <div className="viewOnly">
|
||||
<div className="eyeball">View Only</div>
|
||||
{currentUser && !userRequested && <div className="requestAccess requestNotice" onClick={onRequestClick}>Request Access</div>}
|
||||
{userRequested && !requestAnswered && <div className="requestPending requestNotice">Request Pending</div>}
|
||||
{userRequested && requestAnswered && !requestApproved && <div className="requestNotAccepted requestNotice">Request Not Accepted</div>}
|
||||
</div>}
|
||||
<div className="clearfloat"></div>
|
||||
</div>
|
||||
}
|
||||
}
|
||||
|
||||
export default UpperLeftUI
|
58
frontend/src/components/App/UpperRightUI.js
Normal file
58
frontend/src/components/App/UpperRightUI.js
Normal file
|
@ -0,0 +1,58 @@
|
|||
import React, { Component, PropTypes } from 'react'
|
||||
|
||||
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,
|
||||
openInviteLightbox: PropTypes.func
|
||||
}
|
||||
|
||||
constructor(props) {
|
||||
super(props)
|
||||
this.state = {accountBoxOpen: false}
|
||||
}
|
||||
|
||||
reset = () => {
|
||||
this.setState({accountBoxOpen: false})
|
||||
}
|
||||
|
||||
toggleAccountBox = () => {
|
||||
this.setState({accountBoxOpen: !this.state.accountBoxOpen})
|
||||
}
|
||||
|
||||
render () {
|
||||
const { currentUser, signInPage, unreadNotificationsCount, openInviteLightbox } = this.props
|
||||
const { accountBoxOpen } = this.state
|
||||
return <div className="upperRightUI">
|
||||
{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 ignore-react-onclickoutside" onClick={this.toggleAccountBox}>
|
||||
<div className="tooltipsUnder">Account</div>
|
||||
{currentUser && <img src={currentUser.get('image')} />}
|
||||
{!currentUser && 'SIGN IN'}
|
||||
{!currentUser && <div className="accountInnerArrow"></div>}
|
||||
</div>
|
||||
{accountBoxOpen && <div className="sidebarAccountBox upperRightBox">
|
||||
{currentUser
|
||||
? <AccountMenu onInviteClick={openInviteLightbox} currentUser={currentUser} closeBox={this.reset} />
|
||||
: <LoginForm closeBox={this.reset} />}
|
||||
</div>}
|
||||
</div>}
|
||||
<div className="clearfloat"></div>
|
||||
</div>
|
||||
}
|
||||
}
|
||||
|
||||
export default UpperRightUI
|
67
frontend/src/components/App/index.js
Normal file
67
frontend/src/components/App/index.js
Normal file
|
@ -0,0 +1,67 @@
|
|||
import React, { Component, PropTypes } from 'react'
|
||||
|
||||
import MobileHeader from './MobileHeader'
|
||||
import UpperLeftUI from './UpperLeftUI'
|
||||
import UpperRightUI from './UpperRightUI'
|
||||
import Toast from './Toast'
|
||||
|
||||
class App extends Component {
|
||||
static propTypes = {
|
||||
children: PropTypes.object,
|
||||
toast: PropTypes.string,
|
||||
unreadNotificationsCount: PropTypes.number,
|
||||
location: PropTypes.object,
|
||||
mobile: PropTypes.bool,
|
||||
mobileTitle: PropTypes.string,
|
||||
mobileTitleWidth: PropTypes.number,
|
||||
mobileTitleClick: PropTypes.func,
|
||||
openInviteLightbox: PropTypes.func,
|
||||
map: PropTypes.object,
|
||||
userRequested: PropTypes.bool,
|
||||
requestAnswered: PropTypes.bool,
|
||||
requestApproved: PropTypes.bool,
|
||||
onRequestAccess: PropTypes.func
|
||||
}
|
||||
|
||||
static childContextTypes = {
|
||||
location: PropTypes.object
|
||||
}
|
||||
|
||||
getChildContext () {
|
||||
const { location } = this.props
|
||||
return {location}
|
||||
}
|
||||
|
||||
render () {
|
||||
const { children, toast, unreadNotificationsCount, openInviteLightbox,
|
||||
mobile, mobileTitle, mobileTitleWidth, mobileTitleClick, location,
|
||||
map, userRequested, requestAnswered, requestApproved,
|
||||
onRequestAccess } = this.props
|
||||
const { pathname } = location || {}
|
||||
// this fixes a bug that happens otherwise when you logout
|
||||
const currentUser = this.props.currentUser && this.props.currentUser.id ? this.props.currentUser : null
|
||||
const unauthedHome = pathname === '/' && !currentUser
|
||||
return <div className="wrapper" id="wrapper">
|
||||
{mobile && <MobileHeader currentUser={currentUser}
|
||||
unreadNotificationsCount={unreadNotificationsCount}
|
||||
mobileTitle={mobileTitle}
|
||||
mobileTitleWidth={mobileTitleWidth}
|
||||
onTitleClick={mobileTitleClick} />}
|
||||
{!unauthedHome && <UpperLeftUI currentUser={currentUser}
|
||||
map={map}
|
||||
userRequested={userRequested}
|
||||
requestAnswered={requestAnswered}
|
||||
requestApproved={requestApproved}
|
||||
onRequestClick={onRequestAccess} />}
|
||||
{!mobile && <UpperRightUI currentUser={currentUser}
|
||||
unreadNotificationsCount={unreadNotificationsCount}
|
||||
openInviteLightbox={openInviteLightbox}
|
||||
signInPage={pathname === '/login'} />}
|
||||
<Toast message={toast} />
|
||||
{!mobile && currentUser && <a className='feedback-icon' target='_blank' href='https://hylo.com/c/metamaps'></a>}
|
||||
{children}
|
||||
</div>
|
||||
}
|
||||
}
|
||||
|
||||
export default App
|
|
@ -1,193 +0,0 @@
|
|||
import React, { PropTypes, Component } from 'react'
|
||||
import Unread from './Unread'
|
||||
import Participant from './Participant'
|
||||
import Message from './Message'
|
||||
import NewMessage from './NewMessage'
|
||||
import Util from '../../Metamaps/Util'
|
||||
|
||||
function makeList(messages) {
|
||||
let currentHeader
|
||||
return messages ? messages.map(m => {
|
||||
let heading = false
|
||||
if (!currentHeader) {
|
||||
heading = true
|
||||
currentHeader = m
|
||||
} else {
|
||||
// not same user or time diff of greater than 3 minutes
|
||||
heading = m.user_id !== currentHeader.user_id || Math.floor(Math.abs(new Date(currentHeader.created_at) - new Date(m.created_at)) / 60000) > 3
|
||||
currentHeader = heading ? m : currentHeader
|
||||
}
|
||||
return <Message {...m} key={m.id} heading={heading}/>
|
||||
}) : null
|
||||
}
|
||||
|
||||
class MapChat extends Component {
|
||||
constructor(props) {
|
||||
super(props)
|
||||
|
||||
this.state = {
|
||||
unreadMessages: 0,
|
||||
open: false,
|
||||
messageText: '',
|
||||
alertSound: true, // whether to play sounds on arrival of new messages or not
|
||||
cursorsShowing: true,
|
||||
videosShowing: true
|
||||
}
|
||||
}
|
||||
|
||||
reset = () => {
|
||||
this.setState({
|
||||
unreadMessages: 0,
|
||||
open: false,
|
||||
messageText: '',
|
||||
alertSound: true, // whether to play sounds on arrival of new messages or not
|
||||
cursorsShowing: true,
|
||||
videosShowing: true
|
||||
})
|
||||
}
|
||||
|
||||
close = () => {
|
||||
this.setState({open: false})
|
||||
this.props.onClose()
|
||||
this.messageInput.blur()
|
||||
}
|
||||
|
||||
open = () => {
|
||||
this.scroll()
|
||||
this.setState({open: true, unreadMessages: 0})
|
||||
this.props.onOpen()
|
||||
this.messageInput.focus()
|
||||
}
|
||||
|
||||
newMessage = () => {
|
||||
if (!this.state.open) this.setState({unreadMessages: this.state.unreadMessages + 1})
|
||||
}
|
||||
|
||||
scroll = () => {
|
||||
this.messagesDiv.scrollTop = this.messagesDiv.scrollHeight
|
||||
}
|
||||
|
||||
toggleDrawer = () => {
|
||||
if (this.state.open) this.close()
|
||||
else if (!this.state.open) this.open()
|
||||
}
|
||||
|
||||
toggleAlertSound = () => {
|
||||
this.setState({alertSound: !this.state.alertSound})
|
||||
this.props.soundToggleClick()
|
||||
}
|
||||
|
||||
toggleCursorsShowing = () => {
|
||||
this.setState({cursorsShowing: !this.state.cursorsShowing})
|
||||
this.props.cursorToggleClick()
|
||||
}
|
||||
|
||||
toggleVideosShowing = () => {
|
||||
this.setState({videosShowing: !this.state.videosShowing})
|
||||
this.props.videoToggleClick()
|
||||
}
|
||||
|
||||
handleChange = key => e => {
|
||||
this.setState({
|
||||
[key]: e.target.value
|
||||
})
|
||||
}
|
||||
|
||||
handleTextareaKeyUp = e => {
|
||||
if (e.which === 13) {
|
||||
e.preventDefault()
|
||||
const text = Util.removeEmoji(this.state.messageText)
|
||||
this.props.handleInputMessage(text)
|
||||
this.setState({ messageText: '' })
|
||||
}
|
||||
}
|
||||
|
||||
focusMessageInput = () => {
|
||||
if (!this.messageInput) return
|
||||
this.messageInput.focus()
|
||||
}
|
||||
|
||||
render = () => {
|
||||
const rightOffset = this.state.open ? '0' : '-300px'
|
||||
const { conversationLive, isParticipating, participants, messages, inviteACall, inviteToJoin } = this.props
|
||||
const { videosShowing, cursorsShowing, alertSound, unreadMessages } = this.state
|
||||
return (
|
||||
<div className="chat-box"
|
||||
style={{ right: rightOffset }}
|
||||
>
|
||||
<div className="junto-header">
|
||||
PARTICIPANTS
|
||||
<div onClick={this.toggleVideosShowing} className={`video-toggle ${videosShowing ? '' : 'active'}`} />
|
||||
<div onClick={this.toggleCursorsShowing} className={`cursor-toggle ${cursorsShowing ? '' : 'active'}`} />
|
||||
</div>
|
||||
<div className="participants">
|
||||
{conversationLive && <div className="conversation-live">
|
||||
LIVE
|
||||
{isParticipating && <span className="call-action leave" onClick={this.props.leaveCall}>
|
||||
LEAVE
|
||||
</span>}
|
||||
{!isParticipating && <span className="call-action join" onClick={this.props.joinCall}>
|
||||
JOIN
|
||||
</span>}
|
||||
</div>}
|
||||
{participants.map(participant => <Participant
|
||||
key={participant.id}
|
||||
{...participant}
|
||||
inviteACall={inviteACall}
|
||||
inviteToJoin={inviteToJoin}
|
||||
conversationLive={conversationLive}
|
||||
mapperIsLive={isParticipating}/>
|
||||
)}
|
||||
</div>
|
||||
<div className="chat-header">
|
||||
CHAT
|
||||
<div onClick={this.toggleAlertSound} className={`sound-toggle ${alertSound ? '' : 'active'}`}></div>
|
||||
</div>
|
||||
<div className={`chat-button ${conversationLive ? 'active' : ''}`} onClick={this.toggleDrawer}>
|
||||
<div className="tooltips">Chat</div>
|
||||
<Unread count={unreadMessages} />
|
||||
</div>
|
||||
<div className="chat-messages" ref={div => { this.messagesDiv = div }}>
|
||||
{makeList(messages)}
|
||||
</div>
|
||||
<NewMessage messageText={this.state.messageText}
|
||||
focusMessageInput={this.focusMessageInput}
|
||||
handleChange={this.handleChange('messageText')}
|
||||
textAreaProps={{
|
||||
className: 'chat-input',
|
||||
ref: textarea => { this.messageInput = textarea },
|
||||
placeholder: 'Send a message...',
|
||||
onKeyUp: this.handleTextareaKeyUp,
|
||||
onFocus: this.props.inputFocus,
|
||||
onBlur: this.props.inputBlur
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
MapChat.propTypes = {
|
||||
conversationLive: PropTypes.bool,
|
||||
isParticipating: PropTypes.bool,
|
||||
onOpen: PropTypes.func,
|
||||
onClose: PropTypes.func,
|
||||
leaveCall: PropTypes.func,
|
||||
joinCall: PropTypes.func,
|
||||
inviteACall: PropTypes.func,
|
||||
inviteToJoin: PropTypes.func,
|
||||
videoToggleClick: PropTypes.func,
|
||||
cursorToggleClick: PropTypes.func,
|
||||
soundToggleClick: PropTypes.func,
|
||||
participants: PropTypes.arrayOf(PropTypes.shape({
|
||||
color: PropTypes.string, // css color
|
||||
id: PropTypes.number,
|
||||
image: PropTypes.string, // image url
|
||||
self: PropTypes.bool,
|
||||
username: PropTypes.string,
|
||||
isParticipating: PropTypes.bool,
|
||||
isPending: PropTypes.bool
|
||||
}))
|
||||
}
|
||||
|
||||
export default MapChat
|
23
frontend/src/components/MapView/Instructions.js
Normal file
23
frontend/src/components/MapView/Instructions.js
Normal file
|
@ -0,0 +1,23 @@
|
|||
import React, { Component, PropTypes } from 'react'
|
||||
|
||||
class Instructions extends Component {
|
||||
|
||||
static propTypes = {
|
||||
mobile: PropTypes.bool,
|
||||
hasLearnedTopicCreation: PropTypes.bool
|
||||
}
|
||||
|
||||
render() {
|
||||
const { hasLearnedTopicCreation, mobile } = this.props
|
||||
return hasLearnedTopicCreation ? null : <div id="instructions">
|
||||
{!mobile && <div className="addTopic">
|
||||
Double-click to<br/>add a topic
|
||||
</div>}
|
||||
{mobile && <div className="addTopic">
|
||||
Double-tap to<br/>add a topic
|
||||
</div>}
|
||||
</div>
|
||||
}
|
||||
}
|
||||
|
||||
export default Instructions
|
|
@ -1,6 +1,6 @@
|
|||
import React from 'react'
|
||||
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 })
|
||||
|
188
frontend/src/components/MapView/MapChat/index.js
Normal file
188
frontend/src/components/MapView/MapChat/index.js
Normal file
|
@ -0,0 +1,188 @@
|
|||
import React, { PropTypes, Component } from 'react'
|
||||
import Unread from './Unread'
|
||||
import Participant from './Participant'
|
||||
import Message from './Message'
|
||||
import NewMessage from './NewMessage'
|
||||
import Util from '../../../Metamaps/Util'
|
||||
|
||||
function makeList(messages) {
|
||||
let currentHeader
|
||||
return messages ? messages.map(m => {
|
||||
let heading = false
|
||||
if (!currentHeader) {
|
||||
heading = true
|
||||
currentHeader = m
|
||||
} else {
|
||||
// not same user or time diff of greater than 3 minutes
|
||||
heading = m.user_id !== currentHeader.user_id || Math.floor(Math.abs(new Date(currentHeader.created_at) - new Date(m.created_at)) / 60000) > 3
|
||||
currentHeader = heading ? m : currentHeader
|
||||
}
|
||||
return <Message {...m} key={m.id} heading={heading}/>
|
||||
}) : null
|
||||
}
|
||||
|
||||
class MapChat extends Component {
|
||||
constructor(props) {
|
||||
super(props)
|
||||
|
||||
this.state = {
|
||||
messageText: '',
|
||||
alertSound: true, // whether to play sounds on arrival of new messages or not
|
||||
cursorsShowing: true,
|
||||
videosShowing: true
|
||||
}
|
||||
}
|
||||
|
||||
componentDidUpdate(prevProps) {
|
||||
const { messages } = this.props
|
||||
const prevMessages = prevProps.messages
|
||||
if (messages.length !== prevMessages.length) setTimeout(() => this.scroll(), 50)
|
||||
}
|
||||
|
||||
reset = () => {
|
||||
this.setState({
|
||||
messageText: '',
|
||||
alertSound: true, // whether to play sounds on arrival of new messages or not
|
||||
cursorsShowing: true,
|
||||
videosShowing: true
|
||||
})
|
||||
}
|
||||
|
||||
close = () => {
|
||||
this.props.onClose()
|
||||
}
|
||||
|
||||
open = () => {
|
||||
this.props.onOpen()
|
||||
setTimeout(() => this.scroll(), 50)
|
||||
}
|
||||
|
||||
scroll = () => {
|
||||
// hack: figure out how to do this right
|
||||
this.messagesDiv.scrollTop = this.messagesDiv.scrollHeight + 100
|
||||
}
|
||||
|
||||
toggleDrawer = () => {
|
||||
if (this.props.chatOpen) this.close()
|
||||
else if (!this.props.chatOpen) this.open()
|
||||
}
|
||||
|
||||
toggleAlertSound = () => {
|
||||
this.setState({alertSound: !this.state.alertSound})
|
||||
this.props.soundToggleClick()
|
||||
}
|
||||
|
||||
toggleCursorsShowing = () => {
|
||||
this.setState({cursorsShowing: !this.state.cursorsShowing})
|
||||
this.props.cursorToggleClick()
|
||||
}
|
||||
|
||||
toggleVideosShowing = () => {
|
||||
this.setState({videosShowing: !this.state.videosShowing})
|
||||
this.props.videoToggleClick()
|
||||
}
|
||||
|
||||
handleChange = key => e => {
|
||||
this.setState({
|
||||
[key]: e.target.value
|
||||
})
|
||||
}
|
||||
|
||||
handleTextareaKeyUp = e => {
|
||||
if (e.which === 13) {
|
||||
e.preventDefault()
|
||||
const text = Util.removeEmoji(this.state.messageText)
|
||||
this.props.handleInputMessage(text)
|
||||
this.setState({ messageText: '' })
|
||||
}
|
||||
}
|
||||
|
||||
render = () => {
|
||||
const { unreadMessages, chatOpen, conversationLive,
|
||||
isParticipating, participants, messages, inviteACall, inviteToJoin } = this.props
|
||||
const { videosShowing, cursorsShowing, alertSound } = this.state
|
||||
const rightOffset = chatOpen ? '0' : '-300px'
|
||||
return (
|
||||
<div id="chat-box-wrapper">
|
||||
<div className="chat-box"
|
||||
style={{ right: rightOffset }}
|
||||
>
|
||||
<div className="junto-header">
|
||||
PARTICIPANTS
|
||||
<div onClick={this.toggleVideosShowing} className={`video-toggle ${videosShowing ? '' : 'active'}`} />
|
||||
<div onClick={this.toggleCursorsShowing} className={`cursor-toggle ${cursorsShowing ? '' : 'active'}`} />
|
||||
</div>
|
||||
<div className="participants">
|
||||
{conversationLive && <div className="conversation-live">
|
||||
LIVE
|
||||
{isParticipating && <span className="call-action leave" onClick={this.props.leaveCall}>
|
||||
LEAVE
|
||||
</span>}
|
||||
{!isParticipating && <span className="call-action join" onClick={this.props.joinCall}>
|
||||
JOIN
|
||||
</span>}
|
||||
</div>}
|
||||
{participants.map(participant => <Participant
|
||||
key={participant.id}
|
||||
{...participant}
|
||||
inviteACall={inviteACall}
|
||||
inviteToJoin={inviteToJoin}
|
||||
conversationLive={conversationLive}
|
||||
mapperIsLive={isParticipating}/>
|
||||
)}
|
||||
</div>
|
||||
<div className="chat-header">
|
||||
CHAT
|
||||
<div onClick={this.toggleAlertSound} className={`sound-toggle ${alertSound ? '' : 'active'}`}></div>
|
||||
</div>
|
||||
<div className={`chat-button ${conversationLive ? 'active' : ''}`} onClick={this.toggleDrawer}>
|
||||
<div className="tooltips">Chat</div>
|
||||
<Unread count={unreadMessages} />
|
||||
</div>
|
||||
<div className="chat-messages" ref={div => { this.messagesDiv = div }}>
|
||||
{makeList(messages)}
|
||||
</div>
|
||||
{chatOpen && <NewMessage messageText={this.state.messageText}
|
||||
focusMessageInput={this.focusMessageInput}
|
||||
handleChange={this.handleChange('messageText')}
|
||||
textAreaProps={{
|
||||
className: 'chat-input',
|
||||
ref: textarea => { textarea && textarea.focus() },
|
||||
placeholder: 'Send a message...',
|
||||
onKeyUp: this.handleTextareaKeyUp,
|
||||
onFocus: this.props.inputFocus,
|
||||
onBlur: this.props.inputBlur
|
||||
}}
|
||||
/>}
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
MapChat.propTypes = {
|
||||
unreadMessages: PropTypes.number,
|
||||
chatOpen: PropTypes.bool,
|
||||
conversationLive: PropTypes.bool,
|
||||
isParticipating: PropTypes.bool,
|
||||
onOpen: PropTypes.func,
|
||||
onClose: PropTypes.func,
|
||||
leaveCall: PropTypes.func,
|
||||
joinCall: PropTypes.func,
|
||||
inviteACall: PropTypes.func,
|
||||
inviteToJoin: PropTypes.func,
|
||||
videoToggleClick: PropTypes.func,
|
||||
cursorToggleClick: PropTypes.func,
|
||||
soundToggleClick: PropTypes.func,
|
||||
participants: PropTypes.arrayOf(PropTypes.shape({
|
||||
color: PropTypes.string, // css color
|
||||
id: PropTypes.number,
|
||||
image: PropTypes.string, // image url
|
||||
self: PropTypes.bool,
|
||||
username: PropTypes.string,
|
||||
isParticipating: PropTypes.bool,
|
||||
isPending: PropTypes.bool
|
||||
}))
|
||||
}
|
||||
|
||||
export default MapChat
|
23
frontend/src/components/MapView/MapInfoBox.js
Normal file
23
frontend/src/components/MapView/MapInfoBox.js
Normal file
|
@ -0,0 +1,23 @@
|
|||
import React, { Component, PropTypes } from 'react'
|
||||
|
||||
class MapInfoBox extends Component {
|
||||
static propTypes = {
|
||||
currentUser: PropTypes.object,
|
||||
map: PropTypes.object,
|
||||
infoBoxHtml: PropTypes.string
|
||||
}
|
||||
|
||||
render () {
|
||||
const { currentUser, map, infoBoxHtml } = this.props
|
||||
if (!map) return null
|
||||
const html = {__html: infoBoxHtml}
|
||||
const isCreator = map.authorizePermissionChange(currentUser)
|
||||
const canEdit = map.authorizeToEdit(currentUser)
|
||||
let classes = 'mapInfoBox mapElement mapElementHidden permission '
|
||||
classes += isCreator ? 'yourMap' : ''
|
||||
classes += canEdit ? ' canEdit' : ''
|
||||
return <div className={classes} dangerouslySetInnerHTML={html}></div>
|
||||
}
|
||||
}
|
||||
|
||||
export default MapInfoBox
|
127
frontend/src/components/MapView/index.js
Normal file
127
frontend/src/components/MapView/index.js
Normal file
|
@ -0,0 +1,127 @@
|
|||
import React, { Component, PropTypes } from 'react'
|
||||
|
||||
import DataVis from '../common/DataVis'
|
||||
import UpperOptions from '../common/UpperOptions'
|
||||
import InfoAndHelp from '../common/InfoAndHelp'
|
||||
import Instructions from './Instructions'
|
||||
import VisualizationControls from '../common/VisualizationControls'
|
||||
import MapChat from './MapChat'
|
||||
import TopicCard from '../TopicCard'
|
||||
|
||||
export default class MapView extends Component {
|
||||
|
||||
static propTypes = {
|
||||
mobile: PropTypes.bool,
|
||||
mapId: PropTypes.string,
|
||||
map: PropTypes.object,
|
||||
mapIsStarred: PropTypes.bool,
|
||||
onMapStar: PropTypes.func,
|
||||
onMapUnstar: PropTypes.func,
|
||||
filterData: PropTypes.object,
|
||||
allForFiltering: PropTypes.object,
|
||||
visibleForFiltering: PropTypes.object,
|
||||
toggleMetacode: PropTypes.func,
|
||||
toggleMapper: PropTypes.func,
|
||||
toggleSynapse: PropTypes.func,
|
||||
filterAllMetacodes: PropTypes.func,
|
||||
filterAllMappers: PropTypes.func,
|
||||
filterAllSynapses: PropTypes.func,
|
||||
toggleMapInfoBox: PropTypes.func,
|
||||
infoBoxHtml: PropTypes.string,
|
||||
currentUser: PropTypes.object,
|
||||
endActiveMap: PropTypes.func,
|
||||
launchNewMap: PropTypes.func,
|
||||
openImportLightbox: PropTypes.func,
|
||||
forkMap: PropTypes.func,
|
||||
openHelpLightbox: PropTypes.func,
|
||||
onZoomExtents: PropTypes.func,
|
||||
onZoomIn: PropTypes.func,
|
||||
onZoomOut: PropTypes.func,
|
||||
hasLearnedTopicCreation: PropTypes.bool
|
||||
}
|
||||
|
||||
constructor(props) {
|
||||
super(props)
|
||||
this.state = {
|
||||
chatOpen: false
|
||||
}
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
this.endMap()
|
||||
}
|
||||
|
||||
endMap() {
|
||||
this.setState({
|
||||
chatOpen: false
|
||||
})
|
||||
this.mapChat.reset()
|
||||
this.upperOptions.reset()
|
||||
this.props.endActiveMap()
|
||||
}
|
||||
|
||||
componentDidUpdate(prevProps) {
|
||||
const oldMapId = prevProps.mapId
|
||||
const { mapId, launchNewMap } = this.props
|
||||
if (!oldMapId && mapId) launchNewMap(mapId)
|
||||
else if (oldMapId && mapId && oldMapId !== mapId) {
|
||||
this.endMap()
|
||||
launchNewMap(mapId)
|
||||
}
|
||||
else if (oldMapId && !mapId) this.endMap()
|
||||
}
|
||||
|
||||
render = () => {
|
||||
const { mobile, map, currentUser, onOpen, onClose,
|
||||
toggleMapInfoBox, infoBoxHtml, allForFiltering, visibleForFiltering,
|
||||
toggleMetacode, toggleMapper, toggleSynapse, filterAllMetacodes,
|
||||
filterAllMappers, filterAllSynapses, filterData,
|
||||
openImportLightbox, forkMap, openHelpLightbox,
|
||||
mapIsStarred, onMapStar, onMapUnstar,
|
||||
onZoomExtents, onZoomIn, onZoomOut, hasLearnedTopicCreation } = this.props
|
||||
const { chatOpen } = this.state
|
||||
const onChatOpen = () => {
|
||||
this.setState({chatOpen: true})
|
||||
onOpen()
|
||||
}
|
||||
const onChatClose = () => {
|
||||
this.setState({chatOpen: false})
|
||||
onClose()
|
||||
}
|
||||
const canEditMap = map && map.authorizeToEdit(currentUser)
|
||||
// TODO: stop using {...this.props} and make explicit
|
||||
return <div className="mapWrapper">
|
||||
<UpperOptions ref={x => this.upperOptions = x}
|
||||
map={map}
|
||||
currentUser={currentUser}
|
||||
onImportClick={openImportLightbox}
|
||||
onForkClick={forkMap}
|
||||
canEditMap={canEditMap}
|
||||
filterData={filterData}
|
||||
allForFiltering={allForFiltering}
|
||||
visibleForFiltering={visibleForFiltering}
|
||||
toggleMetacode={toggleMetacode}
|
||||
toggleMapper={toggleMapper}
|
||||
toggleSynapse={toggleSynapse}
|
||||
filterAllMetacodes={filterAllMetacodes}
|
||||
filterAllMappers={filterAllMappers}
|
||||
filterAllSynapses={filterAllSynapses} />
|
||||
<DataVis />
|
||||
<TopicCard {...this.props} />
|
||||
{currentUser && <Instructions mobile={mobile} hasLearnedTopicCreation={hasLearnedTopicCreation} />}
|
||||
{currentUser && <MapChat {...this.props} onOpen={onChatOpen} onClose={onChatClose} chatOpen={chatOpen} ref={x => this.mapChat = x} />}
|
||||
<VisualizationControls map={map}
|
||||
onClickZoomExtents={onZoomExtents}
|
||||
onClickZoomIn={onZoomIn}
|
||||
onClickZoomOut={onZoomOut} />
|
||||
<InfoAndHelp mapIsStarred={mapIsStarred}
|
||||
currentUser={currentUser}
|
||||
map={map}
|
||||
onInfoClick={toggleMapInfoBox}
|
||||
onMapStar={onMapStar}
|
||||
onMapUnstar={onMapUnstar}
|
||||
onHelpClick={openHelpLightbox}
|
||||
infoBoxHtml={infoBoxHtml} />
|
||||
</div>
|
||||
}
|
||||
}
|
|
@ -1,4 +1,5 @@
|
|||
import React, { Component, PropTypes } from 'react'
|
||||
import { Link } from 'react-router'
|
||||
import _ from 'lodash'
|
||||
|
||||
const MapLink = props => {
|
||||
|
@ -9,16 +10,16 @@ const MapLink = props => {
|
|||
}
|
||||
|
||||
return (
|
||||
<a { ...otherProps } href={href} className={linkClass}>
|
||||
<Link { ...otherProps } to={href} className={linkClass}>
|
||||
<div className="exploreMapsIcon"></div>
|
||||
{text}
|
||||
</a>
|
||||
</Link>
|
||||
)
|
||||
}
|
||||
|
||||
class Header extends Component {
|
||||
render = () => {
|
||||
const { signedIn, section } = this.props
|
||||
const { signedIn, section, user } = this.props
|
||||
|
||||
const activeClass = (title) => {
|
||||
let forClass = 'exploreMapsButton'
|
||||
|
@ -39,38 +40,33 @@ class Header extends Component {
|
|||
<MapLink show={explore}
|
||||
href={signedIn ? '/' : '/explore/active'}
|
||||
linkClass={activeClass('active')}
|
||||
data-router="true"
|
||||
text="All Maps"
|
||||
/>
|
||||
<MapLink show={signedIn && explore}
|
||||
href="/explore/mine"
|
||||
linkClass={activeClass('my')}
|
||||
data-router="true"
|
||||
text="My Maps"
|
||||
/>
|
||||
<MapLink show={signedIn && explore}
|
||||
href="/explore/shared"
|
||||
linkClass={activeClass('shared')}
|
||||
data-router="true"
|
||||
text="Shared With Me"
|
||||
/>
|
||||
<MapLink show={signedIn && explore}
|
||||
href="/explore/starred"
|
||||
linkClass={activeClass('starred')}
|
||||
data-router="true"
|
||||
text="Starred By Me"
|
||||
/>
|
||||
<MapLink show={!signedIn && explore}
|
||||
href="/explore/featured"
|
||||
linkClass={activeClass('featured')}
|
||||
data-router="true"
|
||||
text="Featured Maps"
|
||||
/>
|
||||
|
||||
{mapper ? (
|
||||
<div className='exploreMapsButton active mapperButton'>
|
||||
<img className='exploreMapperImage' width='24' height='24' src={this.props.user.image} />
|
||||
<div className='exploreMapperName'>{this.props.user.name}’s Maps</div>
|
||||
{user && <img className='exploreMapperImage' width='24' height='24' src={user.image} />}
|
||||
{user && <div className='exploreMapperName'>{user.name}’s Maps</div>}
|
||||
<div className='clearfloat'></div>
|
||||
</div>
|
||||
) : null }
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
import React, { Component, PropTypes } from 'react'
|
||||
import { Link } from 'react-router'
|
||||
import { find, values } from 'lodash'
|
||||
import Util from '../../Metamaps/Util'
|
||||
|
||||
|
@ -24,7 +25,7 @@ class Menu extends Component {
|
|||
}
|
||||
|
||||
render = () => {
|
||||
const { currentUser, map, onStar, onRequest, onFollow } = this.props
|
||||
const { currentUser, map, onStar, onRequest, onMapFollow } = this.props
|
||||
const isFollowing = map.isFollowedBy(currentUser)
|
||||
const style = { display: this.state.open ? 'block' : 'none' }
|
||||
|
||||
|
@ -37,7 +38,7 @@ class Menu extends Component {
|
|||
<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> }
|
||||
{ Util.isTester(currentUser) && <li className='follow' onClick={ () => { this.toggle() && onMapFollow(map) }}>{isFollowing ? 'Unfollow' : 'Follow'}</li> }
|
||||
</ul>
|
||||
</div>
|
||||
}
|
||||
|
@ -47,7 +48,7 @@ Menu.propTypes = {
|
|||
map: PropTypes.object.isRequired,
|
||||
onStar: PropTypes.func.isRequired,
|
||||
onRequest: PropTypes.func.isRequired,
|
||||
onFollow: PropTypes.func.isRequired
|
||||
onMapFollow: PropTypes.func.isRequired
|
||||
}
|
||||
|
||||
const Metadata = (props) => {
|
||||
|
@ -78,13 +79,13 @@ const Metadata = (props) => {
|
|||
}
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
class MapCard extends Component {
|
||||
render = () => {
|
||||
const { map, mobile, juntoState, currentUser, onRequest, onStar, onFollow } = this.props
|
||||
const { map, mobile, juntoState, currentUser, onRequest, onStar, onMapFollow } = this.props
|
||||
|
||||
const hasMap = (juntoState.liveMaps[map.id] && values(juntoState.liveMaps[map.id]).length) || null
|
||||
const realtimeMap = juntoState.liveMaps[map.id]
|
||||
|
@ -135,7 +136,7 @@ class MapCard extends Component {
|
|||
</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 } /> }
|
||||
{ !mobile && currentUser && <Menu currentUser={ currentUser } map={ map } onStar= { onStar } onRequest={ onRequest } onMapFollow={ onMapFollow } /> }
|
||||
</div>
|
||||
</div>) }
|
||||
</div>
|
||||
|
@ -150,7 +151,7 @@ MapCard.propTypes = {
|
|||
currentUser: PropTypes.object,
|
||||
onStar: PropTypes.func.isRequired,
|
||||
onRequest: PropTypes.func.isRequired,
|
||||
onFollow: PropTypes.func.isRequired
|
||||
onMapFollow: PropTypes.func.isRequired
|
||||
}
|
||||
|
||||
export default MapCard
|
||||
|
|
|
@ -4,59 +4,56 @@ import Header from './Header'
|
|||
import MapperCard from './MapperCard'
|
||||
import MapCard from './MapCard'
|
||||
|
||||
// 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
|
||||
|
||||
class Maps extends Component {
|
||||
|
||||
constructor(props) {
|
||||
super(props)
|
||||
this.state = { mapsWidth: 0 }
|
||||
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,
|
||||
onStar: PropTypes.func,
|
||||
onRequest: PropTypes.func,
|
||||
onMapFollow: PropTypes.func,
|
||||
mapsWidth: PropTypes.number,
|
||||
mobile: PropTypes.bool
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
window && window.addEventListener('resize', this.resize)
|
||||
this.refs.maps.addEventListener('scroll', throttle(this.scroll, 500, { leading: true, trailing: false }))
|
||||
this.resize()
|
||||
static contextTypes = {
|
||||
location: PropTypes.object
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
window && window.removeEventListener('resize', this.resize)
|
||||
}
|
||||
|
||||
resize = () => {
|
||||
const { maps, user, currentUser } = this.props
|
||||
const numCards = maps.length + (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
|
||||
this.setState({ mapsWidth })
|
||||
mapsDidMount = (node) => {
|
||||
if (node) {
|
||||
this.mapsDiv = node
|
||||
node.addEventListener('scroll', throttle(this.scroll, 500, { leading: true, trailing: false }))
|
||||
}
|
||||
}
|
||||
|
||||
scroll = () => {
|
||||
const { loadMore, moreToLoad, pending } = this.props
|
||||
const { maps } = this.refs
|
||||
if (moreToLoad && !pending && maps.scrollTop + maps.offsetHeight > maps.scrollHeight - 300) {
|
||||
const { mapsDiv } = this
|
||||
if (moreToLoad && !pending && mapsDiv.scrollTop + mapsDiv.offsetHeight > mapsDiv.scrollHeight - 300) {
|
||||
loadMore()
|
||||
}
|
||||
}
|
||||
|
||||
render = () => {
|
||||
const { maps, currentUser, juntoState, pending, section, user, onStar, onRequest, onFollow } = this.props
|
||||
const style = { width: this.state.mapsWidth + 'px' }
|
||||
const mobile = document && document.body.clientWidth <= MOBILE_VIEW_BREAKPOINT
|
||||
const { mobile, maps, mapsWidth, currentUser, juntoState, pending, section, user, onStar, onRequest, onMapFollow } = this.props
|
||||
const style = { width: mapsWidth + 'px' }
|
||||
|
||||
if (!maps) return null // do loading here instead
|
||||
|
||||
return (
|
||||
<div>
|
||||
<div id='exploreMaps' ref='maps'>
|
||||
<div id='exploreMaps' ref={this.mapsDidMount}>
|
||||
<div style={ style }>
|
||||
{ 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 }
|
||||
{ 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>
|
||||
</div>
|
||||
|
@ -69,18 +66,4 @@ class Maps extends Component {
|
|||
}
|
||||
}
|
||||
|
||||
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
|
||||
|
|
|
@ -2,8 +2,8 @@ import React, { PropTypes, Component } from 'react'
|
|||
|
||||
class Follow extends Component {
|
||||
render = () => {
|
||||
const { isFollowing, onFollow } = this.props
|
||||
return <div className='topicFollow' onClick={onFollow}>
|
||||
const { isFollowing, onTopicFollow } = this.props
|
||||
return <div className='topicFollow' onClick={onTopicFollow}>
|
||||
{isFollowing ? 'Unfollow' : 'Follow'}
|
||||
</div>
|
||||
}
|
||||
|
@ -11,7 +11,7 @@ class Follow extends Component {
|
|||
|
||||
Follow.propTypes = {
|
||||
isFollowing: PropTypes.bool,
|
||||
onFollow: PropTypes.func
|
||||
onTopicFollow: PropTypes.func
|
||||
}
|
||||
|
||||
export default Follow
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
/* global $ */
|
||||
|
||||
import React, { PropTypes, Component } from 'react'
|
||||
import { Link } from 'react-router'
|
||||
|
||||
import MetacodeSelect from '../MetacodeSelect'
|
||||
import Permission from './Permission'
|
||||
|
@ -52,21 +53,21 @@ class Links extends Component {
|
|||
}
|
||||
|
||||
let output = []
|
||||
|
||||
|
||||
firstFiveLinks.forEach(obj => {
|
||||
output.push(<li key={obj.mapId}><a href={`/maps/${obj.mapId}`}>{obj.mapName}</a></li>)
|
||||
output.push(<li key={obj.mapId}><Link to={`/maps/${obj.mapId}`}>{obj.mapName}</Link></li>)
|
||||
})
|
||||
|
||||
if (extraLinks.length > 0) {
|
||||
if (this.state.showMoreMaps) {
|
||||
extraLinks.forEach(obj => {
|
||||
output.push(<li key={obj.mapId} className="hideExtra extraText"><a href={`/maps/${obj.mapId}`}>{obj.mapName}</a></li>)
|
||||
output.push(<li key={obj.mapId} className="hideExtra extraText"><Link to={`/maps/${obj.mapId}`}>{obj.mapName}</Link></li>)
|
||||
})
|
||||
}
|
||||
const text = this.state.showMoreMaps ? 'See less...' : `See ${extraLinks.length} more...`
|
||||
output.push(<li key="showMore"><span className="showMore" onClick={this.toggleShowMoreMaps}>{text}</span></li>)
|
||||
}
|
||||
|
||||
|
||||
return output
|
||||
}
|
||||
|
||||
|
|
|
@ -7,12 +7,18 @@ import Attachments from './Attachments'
|
|||
import Follow from './Follow'
|
||||
import Util from '../../Metamaps/Util'
|
||||
|
||||
|
||||
class ReactTopicCard extends Component {
|
||||
render = () => {
|
||||
const { topic, ActiveMapper, onFollow } = this.props
|
||||
const authorizedToEdit = topic.authorizeToEdit(ActiveMapper)
|
||||
const isFollowing = topic.isFollowedBy(ActiveMapper)
|
||||
const { currentUser, onTopicFollow, updateTopic } = this.props
|
||||
const topic = this.props.openTopic
|
||||
|
||||
if (!topic) return null
|
||||
|
||||
const wrappedUpdateTopic = obj => updateTopic(topic, obj)
|
||||
const wrappedTopicFollow = () => onTopicFollow(topic)
|
||||
|
||||
const authorizedToEdit = topic.authorizeToEdit(currentUser)
|
||||
const isFollowing = topic.isFollowedBy(currentUser)
|
||||
const hasAttachment = topic.get('link') && topic.get('link') !== ''
|
||||
|
||||
let classname = 'permission'
|
||||
|
@ -21,31 +27,33 @@ class ReactTopicCard extends Component {
|
|||
} else {
|
||||
classname += ' cannotEdit'
|
||||
}
|
||||
if (topic.authorizePermissionChange(ActiveMapper)) classname += ' yourTopic'
|
||||
if (topic.authorizePermissionChange(currentUser)) classname += ' yourTopic'
|
||||
|
||||
return (
|
||||
<div className={classname}>
|
||||
<div className={`CardOnGraph ${hasAttachment ? 'hasAttachment' : ''}`} id={`topic_${topic.id}`}>
|
||||
<Title name={topic.get('name')}
|
||||
authorizedToEdit={authorizedToEdit}
|
||||
onChange={this.props.updateTopic}
|
||||
/>
|
||||
<Links topic={topic}
|
||||
ActiveMapper={this.props.ActiveMapper}
|
||||
updateTopic={this.props.updateTopic}
|
||||
metacodeSets={this.props.metacodeSets}
|
||||
redrawCanvas={this.props.redrawCanvas}
|
||||
/>
|
||||
<Desc desc={topic.get('desc')}
|
||||
authorizedToEdit={authorizedToEdit}
|
||||
onChange={this.props.updateTopic}
|
||||
/>
|
||||
<Attachments topic={topic}
|
||||
authorizedToEdit={authorizedToEdit}
|
||||
updateTopic={this.props.updateTopic}
|
||||
/>
|
||||
{Util.isTester(ActiveMapper) && <Follow isFollowing={isFollowing} onFollow={onFollow} />}
|
||||
<div className="clearfloat"></div>
|
||||
<div className="showcard mapElement mapElementHidden" id="showcard">
|
||||
<div className={classname}>
|
||||
<div className={`CardOnGraph ${hasAttachment ? 'hasAttachment' : ''}`} id={`topic_${topic.id}`}>
|
||||
<Title name={topic.get('name')}
|
||||
authorizedToEdit={authorizedToEdit}
|
||||
onChange={wrappedUpdateTopic}
|
||||
/>
|
||||
<Links topic={topic}
|
||||
ActiveMapper={this.props.currentUser}
|
||||
updateTopic={wrappedUpdateTopic}
|
||||
metacodeSets={this.props.metacodeSets}
|
||||
redrawCanvas={this.props.redrawCanvas}
|
||||
/>
|
||||
<Desc desc={topic.get('desc')}
|
||||
authorizedToEdit={authorizedToEdit}
|
||||
onChange={wrappedUpdateTopic}
|
||||
/>
|
||||
<Attachments topic={topic}
|
||||
authorizedToEdit={authorizedToEdit}
|
||||
updateTopic={wrappedUpdateTopic}
|
||||
/>
|
||||
{Util.isTester(currentUser) && <Follow isFollowing={isFollowing} onTopicFollow={wrappedTopicFollow} />}
|
||||
<div className="clearfloat"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
|
@ -53,10 +61,10 @@ class ReactTopicCard extends Component {
|
|||
}
|
||||
|
||||
ReactTopicCard.propTypes = {
|
||||
topic: PropTypes.object,
|
||||
ActiveMapper: PropTypes.object,
|
||||
openTopic: PropTypes.object,
|
||||
currentUser: PropTypes.object,
|
||||
updateTopic: PropTypes.func,
|
||||
onFollow: PropTypes.func,
|
||||
onTopicFollow: PropTypes.func,
|
||||
metacodeSets: PropTypes.arrayOf(PropTypes.shape({
|
||||
name: PropTypes.string,
|
||||
metacodes: PropTypes.arrayOf(PropTypes.shape({
|
||||
|
|
81
frontend/src/components/TopicView/index.js
Normal file
81
frontend/src/components/TopicView/index.js
Normal file
|
@ -0,0 +1,81 @@
|
|||
import React, { Component, PropTypes } from 'react'
|
||||
|
||||
import DataVis from '../common/DataVis'
|
||||
import UpperOptions from '../common/UpperOptions'
|
||||
import InfoAndHelp from '../common/InfoAndHelp'
|
||||
import VisualizationControls from '../common/VisualizationControls'
|
||||
import TopicCard from '../TopicCard'
|
||||
|
||||
export default class TopicView extends Component {
|
||||
|
||||
static propTypes = {
|
||||
mobile: PropTypes.bool,
|
||||
topicId: PropTypes.string,
|
||||
topic: PropTypes.object,
|
||||
filterData: PropTypes.object,
|
||||
allForFiltering: PropTypes.object,
|
||||
visibleForFiltering: PropTypes.object,
|
||||
toggleMetacode: PropTypes.func,
|
||||
toggleMapper: PropTypes.func,
|
||||
toggleSynapse: PropTypes.func,
|
||||
filterAllMetacodes: PropTypes.func,
|
||||
filterAllMappers: PropTypes.func,
|
||||
filterAllSynapses: PropTypes.func,
|
||||
currentUser: PropTypes.object,
|
||||
endActiveTopic: PropTypes.func,
|
||||
launchNewTopic: PropTypes.func,
|
||||
openHelpLightbox: PropTypes.func,
|
||||
forkMap: PropTypes.func,
|
||||
onZoomIn: PropTypes.func,
|
||||
onZoomOut: PropTypes.func
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
this.endTopic()
|
||||
}
|
||||
|
||||
endTopic() {
|
||||
this.upperOptions.reset()
|
||||
this.props.endActiveTopic()
|
||||
}
|
||||
|
||||
componentDidUpdate(prevProps) {
|
||||
const oldTopicId = prevProps.topicId
|
||||
const { topicId, launchNewTopic } = this.props
|
||||
if (!oldTopicId && topicId) launchNewTopic(topicId)
|
||||
else if (oldTopicId && topicId && oldTopicId !== topicId) {
|
||||
this.endTopic()
|
||||
launchNewTopic(topicId)
|
||||
}
|
||||
else if (oldTopicId && !topicId) this.endTopic()
|
||||
}
|
||||
|
||||
render = () => {
|
||||
const { mobile, topic, currentUser, allForFiltering, visibleForFiltering,
|
||||
toggleMetacode, toggleMapper, toggleSynapse, filterAllMetacodes,
|
||||
filterAllMappers, filterAllSynapses, filterData, forkMap,
|
||||
openHelpLightbox, onZoomIn, onZoomOut } = this.props
|
||||
// TODO: stop using {...this.props} and make explicit
|
||||
return <div className="topicWrapper">
|
||||
<UpperOptions ref={x => this.upperOptions = x}
|
||||
currentUser={currentUser}
|
||||
topic={topic}
|
||||
onForkClick={forkMap}
|
||||
filterData={filterData}
|
||||
allForFiltering={allForFiltering}
|
||||
visibleForFiltering={visibleForFiltering}
|
||||
toggleMetacode={toggleMetacode}
|
||||
toggleMapper={toggleMapper}
|
||||
toggleSynapse={toggleSynapse}
|
||||
filterAllMetacodes={filterAllMetacodes}
|
||||
filterAllMappers={filterAllMappers}
|
||||
filterAllSynapses={filterAllSynapses} />
|
||||
<DataVis />
|
||||
<TopicCard {...this.props} />
|
||||
<VisualizationControls onClickZoomIn={onZoomIn}
|
||||
onClickZoomOut={onZoomOut} />
|
||||
<InfoAndHelp topic={topic}
|
||||
onHelpClick={openHelpLightbox} />
|
||||
</div>
|
||||
}
|
||||
}
|
12
frontend/src/components/common/DataVis.js
Normal file
12
frontend/src/components/common/DataVis.js
Normal file
|
@ -0,0 +1,12 @@
|
|||
import React, { Component, PropTypes } from 'react'
|
||||
|
||||
class DataVis extends Component {
|
||||
static propTypes = {
|
||||
}
|
||||
|
||||
render () {
|
||||
return <div id="infovis" />
|
||||
}
|
||||
}
|
||||
|
||||
export default DataVis
|
118
frontend/src/components/common/FilterBox.js
Normal file
118
frontend/src/components/common/FilterBox.js
Normal file
|
@ -0,0 +1,118 @@
|
|||
import React, { Component, PropTypes } from 'react'
|
||||
|
||||
import onClickOutsideAddon from 'react-onclickoutside'
|
||||
|
||||
class FilterBox extends Component {
|
||||
static propTypes = {
|
||||
topic: PropTypes.object,
|
||||
map: PropTypes.object,
|
||||
filterData: PropTypes.object,
|
||||
allForFiltering: PropTypes.object,
|
||||
visibleForFiltering: PropTypes.object,
|
||||
toggleMetacode: PropTypes.func,
|
||||
toggleMapper: PropTypes.func,
|
||||
toggleSynapse: PropTypes.func,
|
||||
filterAllMetacodes: PropTypes.func,
|
||||
filterAllMappers: PropTypes.func,
|
||||
filterAllSynapses: PropTypes.func,
|
||||
closeBox: PropTypes.func
|
||||
}
|
||||
|
||||
handleClickOutside = () => {
|
||||
this.props.closeBox()
|
||||
}
|
||||
|
||||
render () {
|
||||
const { topic, map, filterData, allForFiltering, visibleForFiltering, toggleMetacode,
|
||||
toggleMapper, toggleSynapse, filterAllMetacodes,
|
||||
filterAllMappers, filterAllSynapses } = this.props
|
||||
const style = {
|
||||
maxHeight: document.body.clientHeight - 108 + 'px'
|
||||
}
|
||||
const mapperAllClass = "showAll showAllMappers"
|
||||
+ (allForFiltering.mappers.length === visibleForFiltering.mappers.length ? ' active' : '')
|
||||
const mapperNoneClass = "hideAll hideAllMappers"
|
||||
+ (visibleForFiltering.mappers.length === 0 ? ' active' : '')
|
||||
const metacodeAllClass = "showAll showAllMetacodes"
|
||||
+ (allForFiltering.metacodes.length === visibleForFiltering.metacodes.length ? ' active' : '')
|
||||
const metacodeNoneClass = "hideAll hideAllMetacodes"
|
||||
+ (visibleForFiltering.metacodes.length === 0 ? ' active' : '')
|
||||
const synapseAllClass = "showAll showAllSynapses"
|
||||
+ (allForFiltering.synapses.length === visibleForFiltering.synapses.length ? ' active' : '')
|
||||
const synapseNoneClass = "hideAll hideAllSynapses"
|
||||
+ (visibleForFiltering.synapses.length === 0 ? ' active' : '')
|
||||
return map || topic ? <div className="sidebarFilterBox upperRightBox" style={style}>
|
||||
<div className="filterBox">
|
||||
<h2>FILTER BY</h2>
|
||||
<div id="filter_by_mapper" className="filterBySection">
|
||||
{map && <h3>MAPPERS</h3>}
|
||||
{topic && <h3>CREATORS</h3>}
|
||||
<span className={mapperNoneClass} onClick={() => filterAllMappers()}>NONE</span>
|
||||
<span className={mapperAllClass} onClick={() => filterAllMappers(true)}>ALL</span>
|
||||
<div className="clearfloat"></div>
|
||||
<ul>
|
||||
{allForFiltering.mappers.map(mapperId => {
|
||||
const data = filterData.mappers[mapperId]
|
||||
const isVisible = visibleForFiltering.mappers.indexOf(mapperId) > -1
|
||||
return <Mapper visible={isVisible} id={mapperId} image={data.image} name={data.name} toggle={toggleMapper} />
|
||||
})}
|
||||
</ul>
|
||||
<div className="clearfloat"></div>
|
||||
</div>
|
||||
|
||||
<div id="filter_by_metacode" className="filterBySection">
|
||||
<h3>METACODES</h3>
|
||||
<span className={metacodeNoneClass} onClick={() => filterAllMetacodes()}>NONE</span>
|
||||
<span className={metacodeAllClass} onClick={() => filterAllMetacodes(true)}>ALL</span>
|
||||
<div className="clearfloat"></div>
|
||||
<ul>
|
||||
{allForFiltering.metacodes.map(metacodeId => {
|
||||
const data = filterData.metacodes[metacodeId]
|
||||
const isVisible = visibleForFiltering.metacodes.indexOf(metacodeId) > -1
|
||||
return <Metacode visible={isVisible} id={metacodeId} icon={data.icon} name={data.name} toggle={toggleMetacode} />
|
||||
})}
|
||||
</ul>
|
||||
<div className="clearfloat"></div>
|
||||
</div>
|
||||
|
||||
<div id="filter_by_synapse" className="filterBySection">
|
||||
<h3>SYNAPSES</h3>
|
||||
<span className={synapseNoneClass} onClick={() => filterAllSynapses()}>NONE</span>
|
||||
<span className={synapseAllClass} onClick={() => filterAllSynapses(true)}>ALL</span>
|
||||
<div className="clearfloat"></div>
|
||||
<ul>
|
||||
{allForFiltering.synapses.map(synapseDesc => {
|
||||
const data = filterData.synapses[synapseDesc]
|
||||
const isVisible = visibleForFiltering.synapses.indexOf(synapseDesc) > -1
|
||||
return <Synapse visible={isVisible} desc={synapseDesc} icon={data.icon} toggle={toggleSynapse} />
|
||||
})}
|
||||
</ul>
|
||||
<div className="clearfloat"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div> : null
|
||||
}
|
||||
}
|
||||
|
||||
function Mapper({ visible, name, id, image, toggle }) {
|
||||
return <li onClick={() => toggle(id)} key={id} className={visible ? '' : 'toggledOff'}>
|
||||
<img src={image} alt={name} />
|
||||
<p>{name}</p>
|
||||
</li>
|
||||
}
|
||||
|
||||
function Metacode({ visible, name, id, icon, toggle }) {
|
||||
return <li onClick={() => toggle(id)} key={id} className={visible ? '' : 'toggledOff'}>
|
||||
<img src={icon} alt={name} />
|
||||
<p>{name.toLowerCase()}</p>
|
||||
</li>
|
||||
}
|
||||
|
||||
function Synapse({ visible, desc, icon, toggle }) {
|
||||
return <li onClick={() => toggle(desc)} key={desc.replace(/ /g, '')} className={visible ? '' : 'toggledOff'}>
|
||||
<img src={icon} alt="synapse icon" />
|
||||
<p>{desc}</p>
|
||||
</li>
|
||||
}
|
||||
|
||||
export default onClickOutsideAddon(FilterBox)
|
38
frontend/src/components/common/InfoAndHelp.js
Normal file
38
frontend/src/components/common/InfoAndHelp.js
Normal file
|
@ -0,0 +1,38 @@
|
|||
import React, { Component, PropTypes } from 'react'
|
||||
|
||||
import MapInfoBox from '../MapView/MapInfoBox'
|
||||
|
||||
class InfoAndHelp extends Component {
|
||||
static propTypes = {
|
||||
mapIsStarred: PropTypes.bool,
|
||||
currentUser: PropTypes.object,
|
||||
map: PropTypes.object,
|
||||
onHelpClick: PropTypes.func,
|
||||
onMapStar: PropTypes.func,
|
||||
onMapUnstar: PropTypes.func,
|
||||
onInfoClick: PropTypes.func,
|
||||
infoBoxhtml: PropTypes.string
|
||||
}
|
||||
|
||||
render () {
|
||||
const { mapIsStarred, map, currentUser, onInfoClick, infoBoxHtml, onMapStar, onMapUnstar, onHelpClick } = this.props
|
||||
const starclassName = mapIsStarred ? 'starred' : ''
|
||||
const tooltip = mapIsStarred ? 'Unstar' : 'Star'
|
||||
const onStarClick = mapIsStarred ? onMapUnstar : onMapStar
|
||||
return <div className="infoAndHelp">
|
||||
{map && <MapInfoBox map={map} currentUser={currentUser} infoBoxHtml={infoBoxHtml} />}
|
||||
{map && currentUser && <div className={`starMap infoElement mapElement ${starclassName}`} onClick={onStarClick}>
|
||||
<div className="tooltipsAbove">{tooltip}</div>
|
||||
</div>}
|
||||
{map && <div className="mapInfoIcon infoElement mapElement" onClick={onInfoClick}>
|
||||
<div className="tooltipsAbove">Map Info</div>
|
||||
</div>}
|
||||
<div className="openCheatsheet infoElement mapElement" onClick={onHelpClick}>
|
||||
<div className="tooltipsAbove">Help</div>
|
||||
</div>
|
||||
<div className="clearfloat"></div>
|
||||
</div>
|
||||
}
|
||||
}
|
||||
|
||||
export default InfoAndHelp
|
73
frontend/src/components/common/UpperOptions.js
Normal file
73
frontend/src/components/common/UpperOptions.js
Normal file
|
@ -0,0 +1,73 @@
|
|||
import React, { Component, PropTypes } from 'react'
|
||||
|
||||
import FilterBox from '../common/FilterBox'
|
||||
|
||||
export default class UpperOptions extends Component {
|
||||
static propTypes = {
|
||||
currentUser: PropTypes.object,
|
||||
map: PropTypes.object,
|
||||
topic: PropTypes.object,
|
||||
canEditMap: PropTypes.bool,
|
||||
onImportClick: PropTypes.func,
|
||||
onForkClick: PropTypes.func,
|
||||
filterData: PropTypes.object,
|
||||
allForFiltering: PropTypes.object,
|
||||
visibleForFiltering: PropTypes.object,
|
||||
toggleMetacode: PropTypes.func,
|
||||
toggleMapper: PropTypes.func,
|
||||
toggleSynapse: PropTypes.func,
|
||||
filterAllMetacodes: PropTypes.func,
|
||||
filterAllMappers: PropTypes.func,
|
||||
filterAllSynapses: PropTypes.func,
|
||||
}
|
||||
|
||||
constructor(props) {
|
||||
super(props)
|
||||
this.state = {filterBoxOpen: false}
|
||||
}
|
||||
|
||||
reset = () => {
|
||||
this.setState({filterBoxOpen: false})
|
||||
}
|
||||
|
||||
toggleFilterBox = event => {
|
||||
this.setState({filterBoxOpen: !this.state.filterBoxOpen})
|
||||
}
|
||||
|
||||
render () {
|
||||
const { currentUser, map, topic, canEditMap, filterBoxHtml, onFilterClick, onImportClick, onForkClick,
|
||||
filterData, allForFiltering, visibleForFiltering, toggleMetacode, toggleMapper, toggleSynapse,
|
||||
filterAllMetacodes, filterAllMappers, filterAllSynapses } = this.props
|
||||
const { filterBoxOpen } = this.state
|
||||
return <div className="mapElement upperRightEl upperRightMapButtons upperRightUI">
|
||||
{map && canEditMap && <div className="importDialog upperRightEl upperRightIcon mapElement" onClick={onImportClick}>
|
||||
<div className="tooltipsUnder">
|
||||
Import Data
|
||||
</div>
|
||||
</div>}
|
||||
<div className="sidebarFilter upperRightEl">
|
||||
<div className="sidebarFilterIcon upperRightIcon ignore-react-onclickoutside" onClick={this.toggleFilterBox}>
|
||||
<div className="tooltipsUnder">Filter</div>
|
||||
</div>
|
||||
{filterBoxOpen && <FilterBox filterData={filterData}
|
||||
map={map}
|
||||
topic={topic}
|
||||
allForFiltering={allForFiltering}
|
||||
visibleForFiltering={visibleForFiltering}
|
||||
toggleMetacode={toggleMetacode}
|
||||
toggleMapper={toggleMapper}
|
||||
toggleSynapse={toggleSynapse}
|
||||
filterAllMetacodes={filterAllMetacodes}
|
||||
filterAllMappers={filterAllMappers}
|
||||
filterAllSynapses={filterAllSynapses}
|
||||
closeBox={() => this.reset()} />}
|
||||
</div>
|
||||
{currentUser && <div className="sidebarFork upperRightEl">
|
||||
<div className="sidebarForkIcon upperRightIcon" onClick={onForkClick}>
|
||||
<div className="tooltipsUnder">Save To New Map</div>
|
||||
</div>
|
||||
</div>}
|
||||
<div className="clearfloat"></div>
|
||||
</div>
|
||||
}
|
||||
}
|
25
frontend/src/components/common/VisualizationControls.js
Normal file
25
frontend/src/components/common/VisualizationControls.js
Normal file
|
@ -0,0 +1,25 @@
|
|||
import React, { Component, PropTypes } from 'react'
|
||||
|
||||
export default class VisualizationControls extends Component {
|
||||
static propTypes = {
|
||||
map: PropTypes.object,
|
||||
onClickZoomExtents: PropTypes.func,
|
||||
onClickZoomIn: PropTypes.func,
|
||||
onClickZoomOut: PropTypes.func
|
||||
}
|
||||
|
||||
render () {
|
||||
const { map, onClickZoomExtents, onClickZoomIn, onClickZoomOut } = this.props
|
||||
return <div className="mapControls mapElement">
|
||||
{map && <div className="zoomExtents mapControl" onClick={onClickZoomExtents}>
|
||||
<div className="tooltips">Center View</div>
|
||||
</div>}
|
||||
<div className="zoomIn mapControl" onClick={onClickZoomIn}>
|
||||
<div className="tooltips">Zoom In</div>
|
||||
</div>
|
||||
<div className="zoomOut mapControl" onClick={onClickZoomOut}>
|
||||
<div className="tooltips">Zoom Out</div>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
}
|
70
frontend/src/components/makeRoutes.js
Normal file
70
frontend/src/components/makeRoutes.js
Normal file
|
@ -0,0 +1,70 @@
|
|||
import React from 'react'
|
||||
import { Route, IndexRoute } from 'react-router'
|
||||
import App from './App'
|
||||
import Maps from './Maps'
|
||||
import MapView from './MapView'
|
||||
import TopicView from './TopicView'
|
||||
|
||||
function nullComponent(props) {
|
||||
return null
|
||||
}
|
||||
|
||||
export default function makeRoutes (currentUser) {
|
||||
const homeComponent = currentUser && currentUser.id ? Maps : nullComponent
|
||||
return <Route path="/" component={App} >
|
||||
<IndexRoute component={homeComponent} />
|
||||
<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">
|
||||
<IndexRoute component={MapView} />
|
||||
<Route path="conversation" component={MapView} />
|
||||
<Route path="request_access" component={nullComponent} />
|
||||
</Route>
|
||||
<Route path="topics/:id" component={TopicView} />
|
||||
<Route path="login" component={nullComponent} />
|
||||
<Route path="join" component={nullComponent} />
|
||||
<Route path="request" component={nullComponent} />
|
||||
<Route path="notifications">
|
||||
<IndexRoute component={nullComponent} />
|
||||
<Route path=":id" component={nullComponent} />
|
||||
</Route>
|
||||
<Route path="users">
|
||||
<Route path=":id/edit" component={nullComponent} />
|
||||
<Route path="password/new" component={nullComponent} />
|
||||
<Route path="password/edit" component={nullComponent} />
|
||||
</Route>
|
||||
<Route path="metacodes">
|
||||
<IndexRoute component={nullComponent} />
|
||||
<Route path="new" component={nullComponent} />
|
||||
<Route path=":id/edit" component={nullComponent} />
|
||||
</Route>
|
||||
<Route path="metacode_sets">
|
||||
<IndexRoute component={nullComponent} />
|
||||
<Route path="new" component={nullComponent} />
|
||||
<Route path=":id/edit" component={nullComponent} />
|
||||
</Route>
|
||||
<Route path="oauth">
|
||||
<Route path="token/info" component={nullComponent} />
|
||||
<Route path="authorize">
|
||||
<IndexRoute component={nullComponent} />
|
||||
<Route path=":code" component={nullComponent} />
|
||||
</Route>
|
||||
<Route path="authorized_applications">
|
||||
<IndexRoute component={nullComponent} />
|
||||
<Route path=":id" component={nullComponent} />
|
||||
</Route>
|
||||
<Route path="applications">
|
||||
<IndexRoute component={nullComponent} />
|
||||
<Route path="new" component={nullComponent} />
|
||||
<Route path=":id" component={nullComponent} />
|
||||
<Route path=":id/edit" component={nullComponent} />
|
||||
</Route>
|
||||
</Route>
|
||||
</Route>
|
||||
}
|
|
@ -46,6 +46,7 @@
|
|||
"react-dom": "15.4.2",
|
||||
"react-dropzone": "3.9.1",
|
||||
"react-onclickoutside": "5.9.0",
|
||||
"react-router": "3.0.2",
|
||||
"redux": "3.6.0",
|
||||
"riek": "1.0.7",
|
||||
"simplewebrtc": "2.2.2",
|
||||
|
|
Loading…
Reference in a new issue