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;
|
display: block;
|
||||||
width: 830px;
|
width: 830px;
|
||||||
}
|
}
|
||||||
.requestInvite {
|
|
||||||
display: block;
|
|
||||||
margin: -720px auto 0;
|
|
||||||
}
|
|
||||||
.new_session,
|
.new_session,
|
||||||
.new_user,
|
.new_user,
|
||||||
.edit_user,
|
.edit_user,
|
||||||
|
@ -672,9 +668,21 @@ label {
|
||||||
position: relative;
|
position: relative;
|
||||||
/*overflow:hidden; */
|
/*overflow:hidden; */
|
||||||
}
|
}
|
||||||
.main.compressed {
|
.compressed {
|
||||||
width: calc(100% - 300px);
|
.upperRightUI {
|
||||||
|
right: 324px;
|
||||||
|
}
|
||||||
|
.upperRightMapButtons {
|
||||||
|
right: 434px;
|
||||||
|
}
|
||||||
|
.mapControls {
|
||||||
|
right: 324px;
|
||||||
|
}
|
||||||
|
.infoAndHelp {
|
||||||
|
right: 370px;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#infovis-canvas {
|
#infovis-canvas {
|
||||||
-webkit-touch-callout: none;
|
-webkit-touch-callout: none;
|
||||||
-webkit-user-select: none;
|
-webkit-user-select: none;
|
||||||
|
@ -775,9 +783,9 @@ label {
|
||||||
}
|
}
|
||||||
.sidebarAccountIcon img {
|
.sidebarAccountIcon img {
|
||||||
border-radius: 16px;
|
border-radius: 16px;
|
||||||
|
width: 32px;
|
||||||
}
|
}
|
||||||
.sidebarAccountBox {
|
.sidebarAccountBox {
|
||||||
display: none;
|
|
||||||
height: auto;
|
height: auto;
|
||||||
}
|
}
|
||||||
.authenticated .sidebarAccountBox {
|
.authenticated .sidebarAccountBox {
|
||||||
|
@ -1039,7 +1047,6 @@ label[for="user_remember_me"] {
|
||||||
}
|
}
|
||||||
|
|
||||||
.sidebarFilterBox {
|
.sidebarFilterBox {
|
||||||
display:none;
|
|
||||||
width: 319px;
|
width: 319px;
|
||||||
padding: 16px 0;
|
padding: 16px 0;
|
||||||
overflow-y: auto;
|
overflow-y: auto;
|
||||||
|
@ -3058,14 +3065,16 @@ and it won't be important on password protected instances */
|
||||||
|
|
||||||
/* request */
|
/* request */
|
||||||
|
|
||||||
#wrapper .requestInvite {
|
.requestInvite {
|
||||||
width: 700px;
|
width: 700px;
|
||||||
margin: 0 auto;
|
|
||||||
padding: 0 0 60px 0;
|
|
||||||
background: #FFFFFF;
|
background: #FFFFFF;
|
||||||
color: white;
|
color: white;
|
||||||
height: 100%;
|
height: calc(100% - 52px);
|
||||||
overflow: hidden;
|
z-index: 1;
|
||||||
|
position: relative;
|
||||||
|
left: 50%;
|
||||||
|
margin-left: -350px;
|
||||||
|
margin-top: 52px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.home_bg {
|
.home_bg {
|
||||||
|
|
|
@ -54,7 +54,6 @@
|
||||||
width:100%;
|
width:100%;
|
||||||
height:100%;
|
height:100%;
|
||||||
position: absolute;
|
position: absolute;
|
||||||
display: none;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.showcard .permission {
|
.showcard .permission {
|
||||||
|
|
|
@ -30,6 +30,7 @@
|
||||||
height: 100%;
|
height: 100%;
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
padding-top: 92px;
|
padding-top: 92px;
|
||||||
|
overflow-y: auto;
|
||||||
}
|
}
|
||||||
|
|
||||||
/*.animations {
|
/*.animations {
|
||||||
|
@ -46,26 +47,9 @@
|
||||||
transition-timing-function: ease-in-out;
|
transition-timing-function: ease-in-out;
|
||||||
}*/
|
}*/
|
||||||
|
|
||||||
.mapElement {
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
.mapPage .mapElement,
|
|
||||||
.topicPage .mapElement {
|
|
||||||
display: block;
|
|
||||||
}
|
|
||||||
.mapPage .mapElementHidden,
|
|
||||||
.topicPage .mapElement.mapInfoBox,
|
|
||||||
.topicPage .mapElement.importDialog {
|
|
||||||
display:none;
|
|
||||||
}
|
|
||||||
.topicPage .starMap {
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* loading */
|
/* loading */
|
||||||
|
|
||||||
#loading {
|
#loading {
|
||||||
display: none;
|
|
||||||
width: 28px;
|
width: 28px;
|
||||||
height: 28px;
|
height: 28px;
|
||||||
position: fixed;
|
position: fixed;
|
||||||
|
@ -184,10 +168,10 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
.upperRightMapButtons {
|
.upperRightMapButtons {
|
||||||
top: -42px; /* puts it just offscreen */
|
right: 134px;
|
||||||
}
|
}
|
||||||
.mapPage .upperRightMapButtons, .topicPage .upperRightMapButtons {
|
.unauthenticated .upperRightMapButtons {
|
||||||
top: 0;
|
right: 115px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.upperRightIcon {
|
.upperRightIcon {
|
||||||
|
@ -197,13 +181,7 @@
|
||||||
background-repeat: no-repeat;
|
background-repeat: no-repeat;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
}
|
}
|
||||||
.mapPage .mapElement .importDialog {
|
|
||||||
display: none;
|
|
||||||
background-position: 0 0;
|
|
||||||
}
|
|
||||||
.mapPage.canEditMap .mapElement .importDialog {
|
|
||||||
display: block;
|
|
||||||
}
|
|
||||||
.sidebarFilterIcon {
|
.sidebarFilterIcon {
|
||||||
background-position: -32px 0;
|
background-position: -32px 0;
|
||||||
}
|
}
|
||||||
|
@ -236,6 +214,14 @@
|
||||||
|
|
||||||
/* end upperRightUI */
|
/* end upperRightUI */
|
||||||
|
|
||||||
|
/* map wrapper */
|
||||||
|
.mapWrapper {
|
||||||
|
position:absolute;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* end map wrapper */
|
||||||
|
|
||||||
/* yield */
|
/* yield */
|
||||||
|
|
||||||
|
@ -356,22 +342,15 @@
|
||||||
|
|
||||||
/* infoAndHelp */
|
/* infoAndHelp */
|
||||||
|
|
||||||
.mapPage .infoAndHelp, .topicPage .infoAndHelp {
|
.openCheatsheet .tooltipsAbove {
|
||||||
right: 70px;
|
|
||||||
}
|
|
||||||
.mapPage .openCheatsheet .tooltipsAbove, .topicPage .openCheatsheet .tooltipsAbove {
|
|
||||||
right: 1px;
|
right: 1px;
|
||||||
left: auto;
|
left: auto;
|
||||||
}
|
}
|
||||||
|
|
||||||
.unauthenticated .homePage .infoAndHelp {
|
|
||||||
display:none;
|
|
||||||
}
|
|
||||||
|
|
||||||
.infoAndHelp {
|
.infoAndHelp {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
bottom: 20px;
|
bottom: 20px;
|
||||||
right: 20px;
|
right: 70px;
|
||||||
z-index: 3;
|
z-index: 3;
|
||||||
width: auto;
|
width: auto;
|
||||||
font-style: italic;
|
font-style: italic;
|
||||||
|
@ -392,16 +371,12 @@
|
||||||
}
|
}
|
||||||
.mapInfoIcon {
|
.mapInfoIcon {
|
||||||
position: relative;
|
position: relative;
|
||||||
top: 56px; /* puts it just offscreen */
|
|
||||||
background-image: url(<%= asset_path('mapinfo_sprite.png') %>);
|
background-image: url(<%= asset_path('mapinfo_sprite.png') %>);
|
||||||
background-repeat:no-repeat;
|
background-repeat:no-repeat;
|
||||||
}
|
}
|
||||||
.mapInfoIcon:hover {
|
.mapInfoIcon:hover {
|
||||||
background-position: 0 -32px;
|
background-position: 0 -32px;
|
||||||
}
|
}
|
||||||
.mapPage .mapInfoIcon {
|
|
||||||
top: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.starMap {
|
.starMap {
|
||||||
background-image: url(<%= asset_path('starmap_sprite.png') %>);
|
background-image: url(<%= asset_path('starmap_sprite.png') %>);
|
||||||
|
@ -419,9 +394,6 @@
|
||||||
background-position: 0 0;
|
background-position: 0 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.unauthenticated .mapPage .starMap {
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
/* end infoAndHelp */
|
/* end infoAndHelp */
|
||||||
|
|
||||||
|
|
||||||
|
@ -430,17 +402,10 @@
|
||||||
.mapControls {
|
.mapControls {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
bottom: 24px;
|
bottom: 24px;
|
||||||
right:-32px; /* puts it just offscreen */
|
right:24px;
|
||||||
width:32px;
|
width:32px;
|
||||||
z-index: 3;
|
z-index: 3;
|
||||||
}
|
}
|
||||||
.mapPage .mapControls, .topicPage .mapControls {
|
|
||||||
right: 24px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.topicPage .zoomExtents {
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
.mapControl {
|
.mapControl {
|
||||||
width:32px;
|
width:32px;
|
||||||
|
@ -587,10 +552,6 @@
|
||||||
left: -8px;
|
left: -8px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.openCheatsheet .tooltipsAbove {
|
|
||||||
left: -4px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.sidebarAccountIcon .tooltipsUnder {
|
.sidebarAccountIcon .tooltipsUnder {
|
||||||
margin-left: -12px;
|
margin-left: -12px;
|
||||||
margin-top: 40px;
|
margin-top: 40px;
|
||||||
|
@ -671,8 +632,11 @@
|
||||||
|
|
||||||
/* explore maps */
|
/* explore maps */
|
||||||
|
|
||||||
#explore {
|
#react-app {
|
||||||
display: none;
|
position: absolute;
|
||||||
|
height: 100%;
|
||||||
|
width: 100%;
|
||||||
|
overflow-y: auto;
|
||||||
}
|
}
|
||||||
|
|
||||||
#exploreMaps {
|
#exploreMaps {
|
||||||
|
@ -691,8 +655,13 @@
|
||||||
display: block;
|
display: block;
|
||||||
}
|
}
|
||||||
|
|
||||||
.appsPage #exploreMapsHeader {
|
.requestInviteHeader {
|
||||||
display: block;
|
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 {
|
#exploreMapsHeader {
|
||||||
|
@ -829,7 +798,6 @@
|
||||||
height: 80px;
|
height: 80px;
|
||||||
font-family: 'din-regular', helvetica, sans-serif;
|
font-family: 'din-regular', helvetica, sans-serif;
|
||||||
font-size: 32px;
|
font-size: 32px;
|
||||||
display: none;
|
|
||||||
text-align: center;
|
text-align: center;
|
||||||
color: #999999;
|
color: #999999;
|
||||||
z-index: 0;
|
z-index: 0;
|
||||||
|
@ -845,7 +813,6 @@
|
||||||
/* toast */
|
/* toast */
|
||||||
|
|
||||||
.toast {
|
.toast {
|
||||||
display: none;
|
|
||||||
position: fixed;
|
position: fixed;
|
||||||
bottom: 20px;
|
bottom: 20px;
|
||||||
left: 20px;
|
left: 20px;
|
||||||
|
|
|
@ -1,7 +1,3 @@
|
||||||
#mobile_header {
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
@media only screen and (max-width : 752px) and (min-width : 504px) {
|
@media only screen and (max-width : 752px) and (min-width : 504px) {
|
||||||
.sidebarSearch .tt-hint, .sidebarSearch .sidebarSearchField {
|
.sidebarSearch .tt-hint, .sidebarSearch .sidebarSearchField {
|
||||||
width: 160px !important;
|
width: 160px !important;
|
||||||
|
@ -51,10 +47,6 @@
|
||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
#mobile_header {
|
|
||||||
display: block;
|
|
||||||
}
|
|
||||||
|
|
||||||
.homeWrapper {
|
.homeWrapper {
|
||||||
width: 96%;
|
width: 96%;
|
||||||
padding: 0 2%;
|
padding: 0 2%;
|
||||||
|
@ -72,7 +64,7 @@
|
||||||
height: auto;
|
height: auto;
|
||||||
}
|
}
|
||||||
.homeVideo {
|
.homeVideo {
|
||||||
width: 100%;
|
width: 100% !important;
|
||||||
height: auto;
|
height: auto;
|
||||||
}
|
}
|
||||||
.fullWidthWrapper.withPartners {
|
.fullWidthWrapper.withPartners {
|
||||||
|
@ -108,9 +100,17 @@
|
||||||
max-width: 360px;
|
max-width: 360px;
|
||||||
}
|
}
|
||||||
|
|
||||||
#wrapper .requestInvite {
|
.requestInviteHeader {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
.requestInvite {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
padding: 0;
|
height: calc(100% - 50px);
|
||||||
|
z-index: 1;
|
||||||
|
position: relative;
|
||||||
|
left: 0;
|
||||||
|
margin-left: 0px;
|
||||||
|
margin-top: 50px;
|
||||||
}
|
}
|
||||||
|
|
||||||
#exploreMaps > div {
|
#exploreMaps > div {
|
||||||
|
@ -217,6 +217,7 @@
|
||||||
width: 100%;
|
width: 100%;
|
||||||
box-shadow: 0px 3px 3px rgba(0,0,0,0.23), 0 3px 3px rgba(0,0,0,0.16);
|
box-shadow: 0px 3px 3px rgba(0,0,0,0.23), 0 3px 3px rgba(0,0,0,0.16);
|
||||||
position: fixed;
|
position: fixed;
|
||||||
|
z-index: 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
#menu_icon {
|
#menu_icon {
|
||||||
|
@ -249,7 +250,6 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
#mobile_menu {
|
#mobile_menu {
|
||||||
display: none;
|
|
||||||
background: #EEE;
|
background: #EEE;
|
||||||
position: fixed;
|
position: fixed;
|
||||||
top: 50px;
|
top: 50px;
|
||||||
|
@ -257,6 +257,7 @@
|
||||||
padding: 10px;
|
padding: 10px;
|
||||||
width: 200px;
|
width: 200px;
|
||||||
box-shadow: 3px 3px 3px rgba(0,0,0,0.23), 3px 3px 3px rgba(0,0,0,0.16);
|
box-shadow: 3px 3px 3px rgba(0,0,0,0.23), 3px 3px 3px rgba(0,0,0,0.16);
|
||||||
|
z-index: 2;
|
||||||
|
|
||||||
li {
|
li {
|
||||||
padding: 10px;
|
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 {
|
li.mobileMenuUser {
|
||||||
border-bottom: 1px solid #BBB;
|
border-bottom: 1px solid #BBB;
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,7 +1,6 @@
|
||||||
.viewOnly {
|
.viewOnly {
|
||||||
float: left;
|
float: left;
|
||||||
margin-left: 16px;
|
margin-left: 16px;
|
||||||
display: none;
|
|
||||||
height: 32px;
|
height: 32px;
|
||||||
border: 1px solid #BDBDBD;
|
border: 1px solid #BDBDBD;
|
||||||
border-radius: 2px;
|
border-radius: 2px;
|
||||||
|
@ -23,7 +22,7 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
.requestNotice {
|
.requestNotice {
|
||||||
display: none;
|
display: inline-block;
|
||||||
padding: 0 8px;
|
padding: 0 8px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -42,16 +41,6 @@
|
||||||
.requestNotAccepted {
|
.requestNotAccepted {
|
||||||
background-color: #c04f4f;
|
background-color: #c04f4f;
|
||||||
}
|
}
|
||||||
|
|
||||||
&.sendRequest .requestAccess {
|
|
||||||
display: inline-block;
|
|
||||||
}
|
|
||||||
&.sentRequest .requestPending {
|
|
||||||
display: inline-block;
|
|
||||||
}
|
|
||||||
&.requestDenied .requestNotAccepted {
|
|
||||||
display: inline-block;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.request_access {
|
.request_access {
|
||||||
|
|
|
@ -5,7 +5,7 @@
|
||||||
# %>
|
# %>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
<% content_for :title, "Explore My Maps | Metamaps" %>
|
<% content_for :title, "My Maps | Metamaps" %>
|
||||||
<% content_for :mobile_title, "My Maps" %>
|
<% content_for :mobile_title, "My Maps" %>
|
||||||
|
|
||||||
Metamaps.currentPage = "mine";
|
Metamaps.currentPage = "mine";
|
||||||
|
|
|
@ -5,7 +5,7 @@
|
||||||
# %>
|
# %>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
<% content_for :title, "Explore Shared Maps | Metamaps" %>
|
<% content_for :title, "Shared Maps | Metamaps" %>
|
||||||
<% content_for :mobile_title, "Shared With Me" %>
|
<% content_for :mobile_title, "Shared With Me" %>
|
||||||
|
|
||||||
Metamaps.currentPage = "shared";
|
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/lightboxes' %>
|
||||||
<%= render :partial => 'layouts/templates' %>
|
<%= render :partial => 'layouts/templates' %>
|
||||||
<%= render :partial => 'shared/metacodeBgColors' %>
|
<%= 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? %>
|
<%= render :partial => 'layouts/googleanalytics' if ENV["GA_TRACKING_CODE"].present? %>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|
|
@ -10,6 +10,5 @@
|
||||||
})(window,document,'script','https://www.google-analytics.com/analytics.js','ga');
|
})(window,document,'script','https://www.google-analytics.com/analytics.js','ga');
|
||||||
|
|
||||||
ga('create', '<%= ENV["GA_TRACKING_CODE"] %>', 'auto');
|
ga('create', '<%= ENV["GA_TRACKING_CODE"] %>', 'auto');
|
||||||
ga('send', 'pageview');
|
|
||||||
|
|
||||||
</script>
|
</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,37 +6,9 @@
|
||||||
#%>
|
#%>
|
||||||
|
|
||||||
<%= render :partial => 'layouts/head' %>
|
<%= render :partial => 'layouts/head' %>
|
||||||
|
|
||||||
<body class="<%= authenticated? ? "authenticated" : "unauthenticated" %> controller-<%= controller_name %> action-<%= action_name %>">
|
<body class="<%= authenticated? ? "authenticated" : "unauthenticated" %> controller-<%= controller_name %> action-<%= action_name %>">
|
||||||
|
<div class="main" id="react-app"></div>
|
||||||
<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 %>
|
<%= yield %>
|
||||||
|
|
||||||
<div class="showcard mapElement mapElementHidden" id="showcard"></div> <!-- the topic card -->
|
|
||||||
<% if authenticated? %>
|
<% if authenticated? %>
|
||||||
<% # for creating and pulling in topics and synapses %>
|
<% # for creating and pulling in topics and synapses %>
|
||||||
<% if controller_name == 'maps' && action_name == "conversation" %>
|
<% if controller_name == 'maps' && action_name == "conversation" %>
|
||||||
|
@ -48,41 +20,4 @@
|
||||||
<% # for populating the change metacode list on the topic card %>
|
<% # for populating the change metacode list on the topic card %>
|
||||||
<%= render :partial => 'shared/metacodeoptions' %>
|
<%= render :partial => 'shared/metacodeoptions' %>
|
||||||
<% end %>
|
<% end %>
|
||||||
<%= render :partial => 'layouts/lowermapelements' %>
|
|
||||||
|
|
||||||
<div id="explore"></div>
|
|
||||||
|
|
||||||
<% if !(controller_name == 'maps' && action_name == "conversation") %>
|
|
||||||
<div id="instructions">
|
|
||||||
<div class="addTopic">
|
|
||||||
Double-click to<br>add a topic
|
|
||||||
</div>
|
|
||||||
<div class="tabKey">
|
|
||||||
Use Tab & Shift+Tab to select a metacode
|
|
||||||
</div>
|
|
||||||
<div class="enterKey">
|
|
||||||
Press Enter to add the topic
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<% end %>
|
|
||||||
|
|
||||||
<div id="infovis"></div>
|
|
||||||
<%= render :partial => 'layouts/mobilemenu' %>
|
|
||||||
|
|
||||||
<p id="toast" class="toast">
|
|
||||||
<% if devise_error_messages? %>
|
|
||||||
<%= devise_error_messages! %>
|
|
||||||
<% end %>
|
|
||||||
<% if notice %>
|
|
||||||
<%= notice %>
|
|
||||||
<% end %>
|
|
||||||
<% if alert %>
|
|
||||||
<%= alert %>
|
|
||||||
<% end %>
|
|
||||||
</p>
|
|
||||||
<div id="loading"></div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<% end %>
|
|
||||||
|
|
||||||
<%= render :partial => 'layouts/foot' %>
|
<%= render :partial => 'layouts/foot' %>
|
||||||
|
|
|
@ -6,26 +6,9 @@
|
||||||
#%>
|
#%>
|
||||||
|
|
||||||
<%= render :partial => 'layouts/head' %>
|
<%= render :partial => 'layouts/head' %>
|
||||||
|
|
||||||
<body class="<%= current_user ? 'authenticated' : 'unauthenticated' %>">
|
<body class="<%= current_user ? 'authenticated' : 'unauthenticated' %>">
|
||||||
|
<div class="main" id="react-app"></div>
|
||||||
<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 %>
|
<%= yield %>
|
||||||
|
|
||||||
<div id="exploreMapsHeader">
|
<div id="exploreMapsHeader">
|
||||||
<div class="exploreMapsBar exploreElement">
|
<div class="exploreMapsBar exploreElement">
|
||||||
<div class="exploreMapsMenu">
|
<div class="exploreMapsMenu">
|
||||||
|
@ -45,16 +28,4 @@
|
||||||
</div>
|
</div>
|
||||||
</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 %>
|
|
||||||
|
|
||||||
<%= render :partial => 'layouts/foot' %>
|
<%= render :partial => 'layouts/foot' %>
|
||||||
|
|
|
@ -6,7 +6,5 @@
|
||||||
|
|
||||||
<% content_for :title, "Request Invite | Metamaps" %>
|
<% content_for :title, "Request Invite | Metamaps" %>
|
||||||
<% content_for :mobile_title, "Request Invite" %>
|
<% content_for :mobile_title, "Request Invite" %>
|
||||||
|
<div class="requestInviteHeader"></div>
|
||||||
<div id="yield">
|
|
||||||
<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>
|
<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.Mappings = <%= @allmappings.to_json.html_safe %>;
|
||||||
Metamaps.ServerData.Messages = <%= @allmessages.to_json.html_safe %>;
|
Metamaps.ServerData.Messages = <%= @allmessages.to_json.html_safe %>;
|
||||||
Metamaps.ServerData.Stars = <%= @allstars.to_json.html_safe %>;
|
Metamaps.ServerData.Stars = <%= @allstars.to_json.html_safe %>;
|
||||||
|
Metamaps.ServerData.requests = <%= @allrequests.to_json.html_safe %>;
|
||||||
Metamaps.ServerData.VisualizeType = "ForceDirected";
|
Metamaps.ServerData.VisualizeType = "ForceDirected";
|
||||||
</script>
|
</script>
|
||||||
|
|
|
@ -18,5 +18,6 @@
|
||||||
Metamaps.ServerData.Mappings = <%= @allmappings.to_json.html_safe %>;
|
Metamaps.ServerData.Mappings = <%= @allmappings.to_json.html_safe %>;
|
||||||
Metamaps.ServerData.Messages = <%= @allmessages.to_json.html_safe %>;
|
Metamaps.ServerData.Messages = <%= @allmessages.to_json.html_safe %>;
|
||||||
Metamaps.ServerData.Stars = <%= @allstars.to_json.html_safe %>;
|
Metamaps.ServerData.Stars = <%= @allstars.to_json.html_safe %>;
|
||||||
|
Metamaps.ServerData.requests = <%= @allrequests.to_json.html_safe %>;
|
||||||
Metamaps.ServerData.VisualizeType = "ForceDirected";
|
Metamaps.ServerData.VisualizeType = "ForceDirected";
|
||||||
</script>
|
</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) {
|
if (DataModel.Topics.length === 0) {
|
||||||
GlobalUI.showDiv('#instructions')
|
Map.setHasLearnedTopicCreation(false)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
deleteSelectedNodes: function() { // refers to deleting topics permanently
|
deleteSelectedNodes: function() { // refers to deleting topics permanently
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
/* global $, Hogan, Bloodhound */
|
/* global $, Hogan, Bloodhound */
|
||||||
|
|
||||||
import DataModel from './DataModel'
|
import DataModel from './DataModel'
|
||||||
|
import Map from './Map'
|
||||||
import Mouse from './Mouse'
|
import Mouse from './Mouse'
|
||||||
import Selected from './Selected'
|
import Selected from './Selected'
|
||||||
import Synapse from './Synapse'
|
import Synapse from './Synapse'
|
||||||
|
@ -270,7 +271,7 @@ const Create = {
|
||||||
})
|
})
|
||||||
Create.newTopic.beingCreated = true
|
Create.newTopic.beingCreated = true
|
||||||
Create.newTopic.name = ''
|
Create.newTopic.name = ''
|
||||||
GlobalUI.hideDiv('#instructions')
|
Map.setHasLearnedTopicCreation(true)
|
||||||
},
|
},
|
||||||
hide: function(force) {
|
hide: function(force) {
|
||||||
if (force || !Create.newTopic.pinned) {
|
if (force || !Create.newTopic.pinned) {
|
||||||
|
@ -281,7 +282,7 @@ const Create = {
|
||||||
Create.newTopic.pinned = false
|
Create.newTopic.pinned = false
|
||||||
}
|
}
|
||||||
if (DataModel.Topics.length === 0) {
|
if (DataModel.Topics.length === 0) {
|
||||||
GlobalUI.showDiv('#instructions')
|
Map.setHasLearnedTopicCreation(false)
|
||||||
}
|
}
|
||||||
Create.newTopic.beingCreated = false
|
Create.newTopic.beingCreated = false
|
||||||
},
|
},
|
||||||
|
|
|
@ -9,12 +9,12 @@ const Mapper = Backbone.Model.extend({
|
||||||
toJSON: function(options) {
|
toJSON: function(options) {
|
||||||
return _.omit(this.attributes, this.blacklist)
|
return _.omit(this.attributes, this.blacklist)
|
||||||
},
|
},
|
||||||
prepareLiForFilter: function() {
|
prepareDataForFilter: function() {
|
||||||
return outdent`
|
return {
|
||||||
<li data-id="${this.id}">
|
id: this.id,
|
||||||
<img src="${this.get('image')}" data-id="${this.id}" alt="${this.get('name')}" />
|
image: this.get('image'),
|
||||||
<p>${this.get('name')}</p>
|
name: this.get('name')
|
||||||
</li>`
|
}
|
||||||
},
|
},
|
||||||
followMap: function(id) {
|
followMap: function(id) {
|
||||||
const idIndex = this.get('follows').maps.indexOf(id)
|
const idIndex = this.get('follows').maps.indexOf(id)
|
||||||
|
|
|
@ -9,12 +9,12 @@ const Metacode = Backbone.Model.extend({
|
||||||
image.src = this.get('icon')
|
image.src = this.get('icon')
|
||||||
this.set('image', image)
|
this.set('image', image)
|
||||||
},
|
},
|
||||||
prepareLiForFilter: function() {
|
prepareDataForFilter: function() {
|
||||||
return outdent`
|
return {
|
||||||
<li data-id="${this.id}">
|
id: this.id,
|
||||||
<img src="${this.get('icon')}" data-id="${this.id}" alt="${this.get('name')}" />
|
name: this.get('name'),
|
||||||
<p>${this.get('name').toLowerCase()}</p>
|
icon: this.get('icon')
|
||||||
</li>`
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
|
@ -28,12 +28,11 @@ const Synapse = Backbone.Model.extend({
|
||||||
this.on('change', this.updateEdgeView)
|
this.on('change', this.updateEdgeView)
|
||||||
this.on('change:desc', Filter.checkSynapses, this)
|
this.on('change:desc', Filter.checkSynapses, this)
|
||||||
},
|
},
|
||||||
prepareLiForFilter: function() {
|
prepareDataForFilter: function() {
|
||||||
return outdent`
|
return {
|
||||||
<li data-id="${this.get('desc')}">
|
desc: this.get('desc'),
|
||||||
<img src="${DataModel.synapseIconUrl}" alt="synapse icon" />
|
icon: DataModel.synapseIconUrl
|
||||||
<p>${this.get('desc')}</p>
|
}
|
||||||
</li>`
|
|
||||||
},
|
},
|
||||||
authorizeToEdit: function(mapper) {
|
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
|
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() {
|
attachCollectionEvents: function() {
|
||||||
DataModel.Topics.on('add remove', function(topic) {
|
DataModel.Topics.on('add remove', function(topic) {
|
||||||
|
console.log('updating infobox and filters due to topic add or remove')
|
||||||
InfoBox.updateNumbers()
|
InfoBox.updateNumbers()
|
||||||
Filter.checkMetacodes()
|
Filter.checkMetacodes()
|
||||||
Filter.checkMappers()
|
Filter.checkMappers()
|
||||||
})
|
})
|
||||||
DataModel.Synapses.on('add remove', function(synapse) {
|
DataModel.Synapses.on('add remove', function(synapse) {
|
||||||
|
console.log('updating infobox and filters due to synapse add or remove')
|
||||||
InfoBox.updateNumbers()
|
InfoBox.updateNumbers()
|
||||||
Filter.checkSynapses()
|
Filter.checkSynapses()
|
||||||
Filter.checkMappers()
|
Filter.checkMappers()
|
||||||
})
|
})
|
||||||
DataModel.Mappings.on('add remove', function(mapping) {
|
DataModel.Mappings.on('add remove', function(mapping) {
|
||||||
|
console.log('updating infobox and filters due to mapping add or remove')
|
||||||
InfoBox.updateNumbers()
|
InfoBox.updateNumbers()
|
||||||
Filter.checkSynapses()
|
Filter.checkSynapses()
|
||||||
Filter.checkMetacodes()
|
Filter.checkMetacodes()
|
||||||
|
|
|
@ -5,13 +5,17 @@ import _ from 'lodash'
|
||||||
import Active from './Active'
|
import Active from './Active'
|
||||||
import Control from './Control'
|
import Control from './Control'
|
||||||
import DataModel from './DataModel'
|
import DataModel from './DataModel'
|
||||||
import GlobalUI from './GlobalUI'
|
import GlobalUI, { ReactApp } from './GlobalUI'
|
||||||
import Settings from './Settings'
|
import Settings from './Settings'
|
||||||
import Visualize from './Visualize'
|
import Visualize from './Visualize'
|
||||||
|
|
||||||
const Filter = {
|
const Filter = {
|
||||||
|
dataForPresentation: {
|
||||||
|
metacodes: {},
|
||||||
|
mappers: {},
|
||||||
|
synapses: {}
|
||||||
|
},
|
||||||
filters: {
|
filters: {
|
||||||
name: '',
|
|
||||||
metacodes: [],
|
metacodes: [],
|
||||||
mappers: [],
|
mappers: [],
|
||||||
synapses: []
|
synapses: []
|
||||||
|
@ -23,119 +27,26 @@ const Filter = {
|
||||||
},
|
},
|
||||||
isOpen: false,
|
isOpen: false,
|
||||||
changing: 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() {
|
reset: function() {
|
||||||
var self = Filter
|
var self = Filter
|
||||||
|
|
||||||
self.filters.metacodes = []
|
self.filters.metacodes = []
|
||||||
self.filters.mappers = []
|
self.filters.mappers = []
|
||||||
self.filters.synapses = []
|
self.filters.synapses = []
|
||||||
self.visible.metacodes = []
|
self.visible.metacodes = []
|
||||||
self.visible.mappers = []
|
self.visible.mappers = []
|
||||||
self.visible.synapses = []
|
self.visible.synapses = []
|
||||||
|
self.dataForPresentation.metacodes = {}
|
||||||
$('#filter_by_metacode ul').empty()
|
self.dataForPresentation.mappers = {}
|
||||||
$('#filter_by_mapper ul').empty()
|
self.dataForPresentation.synapses = {}
|
||||||
$('#filter_by_synapse ul').empty()
|
ReactApp.render()
|
||||||
|
|
||||||
$('.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)
|
|
||||||
},
|
},
|
||||||
// an abstraction function for checkMetacodes, checkMappers, checkSynapses to reduce
|
// an abstraction function for checkMetacodes, checkMappers, checkSynapses to reduce
|
||||||
// code redundancy
|
// code redundancy
|
||||||
/*
|
|
||||||
@param
|
|
||||||
*/
|
|
||||||
updateFilters: function(collection, propertyToCheck, correlatedModel, filtersToUse, listToModify) {
|
updateFilters: function(collection, propertyToCheck, correlatedModel, filtersToUse, listToModify) {
|
||||||
var self = Filter
|
var self = Filter
|
||||||
|
|
||||||
var newList = []
|
var newList = []
|
||||||
var removed = []
|
var removed = []
|
||||||
var added = []
|
var added = []
|
||||||
|
|
||||||
// the first option enables us to accept
|
// the first option enables us to accept
|
||||||
// ['Topics', 'Synapses'] as 'collection'
|
// ['Topics', 'Synapses'] as 'collection'
|
||||||
if (typeof collection === 'object') {
|
if (typeof collection === 'object') {
|
||||||
|
@ -168,41 +79,24 @@ const Filter = {
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
removed = _.difference(self.filters[filtersToUse], newList)
|
removed = _.difference(self.filters[filtersToUse], newList)
|
||||||
added = _.difference(newList, self.filters[filtersToUse])
|
added = _.difference(newList, self.filters[filtersToUse])
|
||||||
|
|
||||||
// remove the list items for things no longer present on the map
|
|
||||||
_.each(removed, function(identifier) {
|
_.each(removed, function(identifier) {
|
||||||
$('#filter_by_' + listToModify + ' li[data-id="' + identifier + '"]').fadeOut('fast', function() {
|
|
||||||
$(this).remove()
|
|
||||||
})
|
|
||||||
const index = self.visible[filtersToUse].indexOf(identifier)
|
const index = self.visible[filtersToUse].indexOf(identifier)
|
||||||
self.visible[filtersToUse].splice(index, 1)
|
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) {
|
_.each(added, function(identifier) {
|
||||||
model = DataModel[correlatedModel].get(identifier) ||
|
const model = DataModel[correlatedModel].get(identifier) ||
|
||||||
DataModel[correlatedModel].find(function(model) {
|
DataModel[correlatedModel].find(function(m) {
|
||||||
return model.get(propertyToCheck) === identifier
|
return m.get(propertyToCheck) === identifier
|
||||||
})
|
})
|
||||||
li = model.prepareLiForFilter()
|
self.dataForPresentation[filtersToUse][identifier] = model.prepareDataForFilter()
|
||||||
jQueryLi = $(li).hide()
|
|
||||||
$('li', '#filter_by_' + listToModify + ' ul').add(jQueryLi.fadeIn('fast'))
|
|
||||||
.sort(sortAlpha).appendTo('#filter_by_' + listToModify + ' ul')
|
|
||||||
self.visible[filtersToUse].push(identifier)
|
self.visible[filtersToUse].push(identifier)
|
||||||
})
|
})
|
||||||
|
|
||||||
// update the list of filters with the new list we just generated
|
// update the list of filters with the new list we just generated
|
||||||
self.filters[filtersToUse] = newList
|
self.filters[filtersToUse] = newList
|
||||||
|
ReactApp.render()
|
||||||
// make sure clicks on list items still trigger the right events
|
|
||||||
self.bindLiClicks()
|
|
||||||
},
|
},
|
||||||
checkMetacodes: function() {
|
checkMetacodes: function() {
|
||||||
var self = Filter
|
var self = Filter
|
||||||
|
@ -221,114 +115,49 @@ const Filter = {
|
||||||
var self = Filter
|
var self = Filter
|
||||||
self.updateFilters('Synapses', 'desc', 'Synapses', 'synapses', 'synapse')
|
self.updateFilters('Synapses', 'desc', 'Synapses', 'synapses', 'synapse')
|
||||||
},
|
},
|
||||||
filterAllMetacodes: function(e) {
|
filterAllMetacodes: function(toVisible) {
|
||||||
var self = Filter
|
var self = Filter
|
||||||
$('#filter_by_metacode ul li').addClass('toggledOff')
|
self.visible.metacodes = toVisible ? self.filters.metacodes.slice() : []
|
||||||
$('.showAllMetacodes').removeClass('active')
|
ReactApp.render()
|
||||||
$('.hideAllMetacodes').addClass('active')
|
|
||||||
self.visible.metacodes = []
|
|
||||||
self.passFilters()
|
self.passFilters()
|
||||||
},
|
},
|
||||||
filterNoMetacodes: function(e) {
|
filterAllMappers: function(toVisible) {
|
||||||
var self = Filter
|
var self = Filter
|
||||||
$('#filter_by_metacode ul li').removeClass('toggledOff')
|
self.visible.mappers = toVisible ? self.filters.mappers.slice() : []
|
||||||
$('.showAllMetacodes').addClass('active')
|
ReactApp.render()
|
||||||
$('.hideAllMetacodes').removeClass('active')
|
|
||||||
self.visible.metacodes = self.filters.metacodes.slice()
|
|
||||||
self.passFilters()
|
self.passFilters()
|
||||||
},
|
},
|
||||||
filterAllMappers: function(e) {
|
filterAllSynapses: function(toVisible) {
|
||||||
var self = Filter
|
var self = Filter
|
||||||
$('#filter_by_mapper ul li').addClass('toggledOff')
|
self.visible.synapses = toVisible ? self.filters.synapses.slice() : []
|
||||||
$('.showAllMappers').removeClass('active')
|
ReactApp.render()
|
||||||
$('.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.passFilters()
|
self.passFilters()
|
||||||
},
|
},
|
||||||
// an abstraction function for toggleMetacode, toggleMapper, toggleSynapse
|
// an abstraction function for toggleMetacode, toggleMapper, toggleSynapse
|
||||||
// to reduce code redundancy
|
// to reduce code redundancy
|
||||||
// gets called in the context of a list item in a filter box
|
// gets called in the context of a list item in a filter box
|
||||||
toggleLi: function(whichToFilter) {
|
toggleLi: function(whichToFilter, id) {
|
||||||
var self = Filter
|
var self = Filter
|
||||||
var id = $(this).attr('data-id')
|
|
||||||
if (self.visible[whichToFilter].indexOf(id) === -1) {
|
if (self.visible[whichToFilter].indexOf(id) === -1) {
|
||||||
self.visible[whichToFilter].push(id)
|
self.visible[whichToFilter].push(id)
|
||||||
$(this).removeClass('toggledOff')
|
|
||||||
} else {
|
} else {
|
||||||
const index = self.visible[whichToFilter].indexOf(id)
|
const index = self.visible[whichToFilter].indexOf(id)
|
||||||
self.visible[whichToFilter].splice(index, 1)
|
self.visible[whichToFilter].splice(index, 1)
|
||||||
$(this).addClass('toggledOff')
|
|
||||||
}
|
}
|
||||||
|
ReactApp.render()
|
||||||
self.passFilters()
|
self.passFilters()
|
||||||
},
|
},
|
||||||
toggleMetacode: function() {
|
toggleMetacode: function(id) {
|
||||||
var self = Filter
|
var self = Filter
|
||||||
self.toggleLi.call(this, 'metacodes')
|
self.toggleLi('metacodes', id)
|
||||||
|
|
||||||
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')
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
toggleMapper: function() {
|
toggleMapper: function(id) {
|
||||||
var self = Filter
|
var self = Filter
|
||||||
self.toggleLi.call(this, 'mappers')
|
self.toggleLi('mappers', id)
|
||||||
|
|
||||||
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')
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
toggleSynapse: function() {
|
toggleSynapse: function(id) {
|
||||||
var self = Filter
|
var self = Filter
|
||||||
self.toggleLi.call(this, 'synapses')
|
self.toggleLi('synapses', id)
|
||||||
|
|
||||||
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')
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
passFilters: function() {
|
passFilters: function() {
|
||||||
var self = Filter
|
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') {
|
if (GlobalUI.lightbox === 'forkmap') {
|
||||||
self.newMap.set('topicsToMap', self.topicsToMap)
|
self.newMap.set('topicsToMap', self.topicsToMap)
|
||||||
self.newMap.set('synapsesToMap', self.synapsesToMap)
|
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'
|
var formId = GlobalUI.lightbox === 'forkmap' ? '#fork_map' : '#new_map'
|
||||||
|
|
|
@ -4,7 +4,7 @@ import React from 'react'
|
||||||
import ReactDOM from 'react-dom'
|
import ReactDOM from 'react-dom'
|
||||||
import outdent from 'outdent'
|
import outdent from 'outdent'
|
||||||
|
|
||||||
import ImportDialogBox from '../../components/ImportDialogBox'
|
import ImportDialogBox from '../../components/MapView/ImportDialogBox'
|
||||||
|
|
||||||
import PasteInput from '../PasteInput'
|
import PasteInput from '../PasteInput'
|
||||||
import Map from '../Map'
|
import Map from '../Map'
|
||||||
|
@ -19,7 +19,7 @@ const ImportDialog = {
|
||||||
self.closeLightbox = closeLightbox
|
self.closeLightbox = closeLightbox
|
||||||
|
|
||||||
$('#lightbox_content').append($(outdent`
|
$('#lightbox_content').append($(outdent`
|
||||||
<div class="lightboxContent" id="import-dialog-lightbox">
|
<div class="lightboxContent" id="import-dialog">
|
||||||
<div class="importDialogWrapper" />
|
<div class="importDialogWrapper" />
|
||||||
</div>
|
</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 */
|
/* global $, Hogan, Bloodhound, CanvasLoader */
|
||||||
|
|
||||||
|
import { browserHistory } from 'react-router'
|
||||||
|
|
||||||
import Active from '../Active'
|
import Active from '../Active'
|
||||||
import Router from '../Router'
|
|
||||||
|
|
||||||
const Search = {
|
const Search = {
|
||||||
locked: false,
|
locked: false,
|
||||||
|
@ -17,6 +18,7 @@ const Search = {
|
||||||
self.userIconUrl = serverData['user.png']
|
self.userIconUrl = serverData['user.png']
|
||||||
|
|
||||||
// this is similar to Metamaps.Loading, but it's for the search element
|
// this is similar to Metamaps.Loading, but it's for the search element
|
||||||
|
if (!document.getElementById('searchLoading')) return
|
||||||
var loader = new CanvasLoader('searchLoading')
|
var loader = new CanvasLoader('searchLoading')
|
||||||
loader.setColor('#4fb5c0') // default is '#000000'
|
loader.setColor('#4fb5c0') // default is '#000000'
|
||||||
loader.setDiameter(24) // default is 40
|
loader.setDiameter(24) // default is 40
|
||||||
|
@ -189,11 +191,11 @@ const Search = {
|
||||||
|
|
||||||
if (['topic', 'map', 'mapper'].indexOf(datum.rtype) !== -1) {
|
if (['topic', 'map', 'mapper'].indexOf(datum.rtype) !== -1) {
|
||||||
if (datum.rtype === 'topic') {
|
if (datum.rtype === 'topic') {
|
||||||
Router.topics(datum.id)
|
browserHistory.push(`/topics/${datum.id}`)
|
||||||
} else if (datum.rtype === 'map') {
|
} else if (datum.rtype === 'map') {
|
||||||
Router.maps(datum.id)
|
browserHistory.push(`/maps/${datum.id}`)
|
||||||
} else if (datum.rtype === 'mapper') {
|
} 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 Create from '../Create'
|
||||||
|
|
||||||
|
import ReactApp from './ReactApp'
|
||||||
import Search from './Search'
|
import Search from './Search'
|
||||||
import CreateMap from './CreateMap'
|
import CreateMap from './CreateMap'
|
||||||
import Account from './Account'
|
|
||||||
import ImportDialog from './ImportDialog'
|
import ImportDialog from './ImportDialog'
|
||||||
import NotificationIcon from './NotificationIcon'
|
|
||||||
|
|
||||||
const GlobalUI = {
|
const GlobalUI = {
|
||||||
notifyTimeout: null,
|
notifyTimeout: null,
|
||||||
|
@ -18,13 +17,12 @@ const GlobalUI = {
|
||||||
init: function(serverData) {
|
init: function(serverData) {
|
||||||
const self = GlobalUI
|
const self = GlobalUI
|
||||||
|
|
||||||
self.Search.init(serverData)
|
self.ReactApp.init(serverData, self.openLightbox)
|
||||||
self.CreateMap.init(serverData)
|
self.CreateMap.init(serverData)
|
||||||
self.Account.init(serverData)
|
|
||||||
self.ImportDialog.init(serverData, self.openLightbox, self.closeLightbox)
|
self.ImportDialog.init(serverData, self.openLightbox, self.closeLightbox)
|
||||||
self.NotificationIcon.init(serverData)
|
self.Search.init(serverData)
|
||||||
|
|
||||||
if ($('#toast').html().trim()) self.notifyUser($('#toast').html())
|
if (serverData.toast) self.notifyUser(serverData.toast)
|
||||||
|
|
||||||
// bind lightbox clicks
|
// bind lightbox clicks
|
||||||
$('.openLightbox').click(function(event) {
|
$('.openLightbox').click(function(event) {
|
||||||
|
@ -112,10 +110,9 @@ const GlobalUI = {
|
||||||
_notifyUser: function(message, opts = {}) {
|
_notifyUser: function(message, opts = {}) {
|
||||||
const self = GlobalUI
|
const self = GlobalUI
|
||||||
|
|
||||||
const { leaveOpen = false, timeOut = 8000 } = opts
|
const { leaveOpen = false, timeOut = 5000 } = opts
|
||||||
|
ReactApp.toast = message
|
||||||
$('#toast').html(message)
|
ReactApp.render()
|
||||||
self.showDiv('#toast')
|
|
||||||
clearTimeout(self.notifyTimeOut)
|
clearTimeout(self.notifyTimeOut)
|
||||||
|
|
||||||
if (!leaveOpen) {
|
if (!leaveOpen) {
|
||||||
|
@ -134,7 +131,8 @@ const GlobalUI = {
|
||||||
const { message, opts } = self.notifyQueue.shift()
|
const { message, opts } = self.notifyQueue.shift()
|
||||||
self._notifyUser(message, opts)
|
self._notifyUser(message, opts)
|
||||||
} else {
|
} else {
|
||||||
self.hideDiv('#toast')
|
ReactApp.toast = null
|
||||||
|
ReactApp.render()
|
||||||
self.notifying = false
|
self.notifying = false
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
@ -153,5 +151,5 @@ const GlobalUI = {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export { Search, CreateMap, Account, ImportDialog, NotificationIcon }
|
export { ReactApp, Search, CreateMap, ImportDialog }
|
||||||
export default GlobalUI
|
export default GlobalUI
|
||||||
|
|
|
@ -316,7 +316,7 @@ const Import = {
|
||||||
success: opts.success
|
success: opts.success
|
||||||
})
|
})
|
||||||
|
|
||||||
GlobalUI.hideDiv('#instructions')
|
Map.setHasLearnedTopicCreation(true)
|
||||||
},
|
},
|
||||||
|
|
||||||
createSynapseWithParameters: function(desc, category, permission,
|
createSynapseWithParameters: function(desc, category, permission,
|
||||||
|
|
|
@ -50,15 +50,6 @@ const JIT = {
|
||||||
*/
|
*/
|
||||||
init: function(serverData) {
|
init: function(serverData) {
|
||||||
const self = JIT
|
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 = new Image()
|
||||||
self.topicDescImage.src = serverData['topic_description_signifier.png']
|
self.topicDescImage.src = serverData['topic_description_signifier.png']
|
||||||
|
|
||||||
|
@ -123,36 +114,22 @@ const JIT = {
|
||||||
prepareVizData: function() {
|
prepareVizData: function() {
|
||||||
const self = JIT
|
const self = JIT
|
||||||
let mapping
|
let mapping
|
||||||
|
|
||||||
// reset/empty vizData
|
|
||||||
self.vizData = []
|
self.vizData = []
|
||||||
Visualize.loadLater = false
|
Visualize.loadLater = false
|
||||||
|
|
||||||
const results = self.convertModelsToJIT(DataModel.Topics, DataModel.Synapses)
|
const results = self.convertModelsToJIT(DataModel.Topics, DataModel.Synapses)
|
||||||
|
|
||||||
self.vizData = results[0]
|
self.vizData = results[0]
|
||||||
|
|
||||||
// clean up the synapses array in case of any faulty data
|
// clean up the synapses array in case of any faulty data
|
||||||
_.each(results[1], function(synapse) {
|
_.each(results[1], function(synapse) {
|
||||||
mapping = synapse.getMapping()
|
mapping = synapse.getMapping()
|
||||||
DataModel.Synapses.remove(synapse)
|
DataModel.Synapses.remove(synapse)
|
||||||
if (DataModel.Mappings) DataModel.Mappings.remove(mapping)
|
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) {
|
if (self.vizData.length === 0) {
|
||||||
GlobalUI.showDiv('#instructions')
|
Map.setHasLearnedTopicCreation(false)
|
||||||
Visualize.loadLater = true
|
Visualize.loadLater = true
|
||||||
} else {
|
} else {
|
||||||
GlobalUI.hideDiv('#instructions')
|
Map.setHasLearnedTopicCreation(true)
|
||||||
}
|
}
|
||||||
|
|
||||||
Visualize.render()
|
Visualize.render()
|
||||||
}, // prepareVizData
|
}, // prepareVizData
|
||||||
edgeRender: function(adj, canvas) {
|
edgeRender: function(adj, canvas) {
|
||||||
|
@ -1026,7 +1003,6 @@ const JIT = {
|
||||||
Create.newTopic.open()
|
Create.newTopic.open()
|
||||||
} else if (!Mouse.didPan) {
|
} else if (!Mouse.didPan) {
|
||||||
// SINGLE CLICK, no pan
|
// SINGLE CLICK, no pan
|
||||||
Filter.close()
|
|
||||||
TopicCard.hideCard()
|
TopicCard.hideCard()
|
||||||
SynapseCard.hideCard()
|
SynapseCard.hideCard()
|
||||||
Create.newTopic.hide()
|
Create.newTopic.hide()
|
||||||
|
|
|
@ -146,7 +146,6 @@ const Listeners = {
|
||||||
}
|
}
|
||||||
|
|
||||||
if (Active.Map && Realtime.inConversation) Realtime.positionVideos()
|
if (Active.Map && Realtime.inConversation) Realtime.positionVideos()
|
||||||
Mobile.resizeTitle()
|
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
centerAndReveal: function(nodes, opts) {
|
centerAndReveal: function(nodes, opts) {
|
||||||
|
|
|
@ -15,6 +15,7 @@ const Loading = {
|
||||||
Loading.loader.setDensity(41) // default is 40
|
Loading.loader.setDensity(41) // default is 40
|
||||||
Loading.loader.setRange(0.9) // default is 1.3
|
Loading.loader.setRange(0.9) // default is 1.3
|
||||||
Loading.loader.show() // Hidden by default
|
Loading.loader.show() // Hidden by default
|
||||||
|
$('#loading').hide()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,16 +1,15 @@
|
||||||
/* global $, Hogan, Bloodhound, Countable */
|
/* global $, Hogan, Bloodhound, Countable */
|
||||||
|
|
||||||
import outdent from 'outdent'
|
import outdent from 'outdent'
|
||||||
|
import { browserHistory } from 'react-router'
|
||||||
|
|
||||||
import Active from '../Active'
|
import Active from '../Active'
|
||||||
import DataModel from '../DataModel'
|
import DataModel from '../DataModel'
|
||||||
import GlobalUI from '../GlobalUI'
|
import GlobalUI, { ReactApp } from '../GlobalUI'
|
||||||
import Router from '../Router'
|
|
||||||
import Util from '../Util'
|
import Util from '../Util'
|
||||||
|
|
||||||
const InfoBox = {
|
const InfoBox = {
|
||||||
isOpen: false,
|
isOpen: false,
|
||||||
changing: false,
|
|
||||||
selectingPermission: false,
|
selectingPermission: false,
|
||||||
changePermissionText: "<div class='tooltips'>As the creator, you can change the permission of this map, and the permission of all the topics and synapses you have authority to change will change as well.</div>",
|
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`
|
nameHTML: outdent`
|
||||||
|
@ -35,12 +34,12 @@ const InfoBox = {
|
||||||
data-bip-value="{{desc}}"
|
data-bip-value="{{desc}}"
|
||||||
>{{desc}}</span>`,
|
>{{desc}}</span>`,
|
||||||
userImageUrl: '',
|
userImageUrl: '',
|
||||||
|
html: '',
|
||||||
init: function(serverData, updateThumbnail) {
|
init: function(serverData, updateThumbnail) {
|
||||||
var self = InfoBox
|
var self = InfoBox
|
||||||
|
|
||||||
self.updateThumbnail = updateThumbnail
|
self.updateThumbnail = updateThumbnail
|
||||||
|
|
||||||
$('.mapInfoIcon').click(self.toggleBox)
|
|
||||||
$('.mapInfoBox').click(function(event) {
|
$('.mapInfoBox').click(function(event) {
|
||||||
event.stopPropagation()
|
event.stopPropagation()
|
||||||
})
|
})
|
||||||
|
@ -72,27 +71,18 @@ const InfoBox = {
|
||||||
open: function() {
|
open: function() {
|
||||||
var self = InfoBox
|
var self = InfoBox
|
||||||
$('.mapInfoIcon div').addClass('hide')
|
$('.mapInfoIcon div').addClass('hide')
|
||||||
if (!self.isOpen && !self.changing) {
|
|
||||||
self.changing = true
|
|
||||||
$('.mapInfoBox').fadeIn(200, function() {
|
$('.mapInfoBox').fadeIn(200, function() {
|
||||||
self.changing = false
|
|
||||||
self.isOpen = true
|
self.isOpen = true
|
||||||
})
|
})
|
||||||
}
|
|
||||||
},
|
},
|
||||||
close: function() {
|
close: function() {
|
||||||
var self = InfoBox
|
var self = InfoBox
|
||||||
|
|
||||||
$('.mapInfoIcon div').removeClass('hide')
|
$('.mapInfoIcon div').removeClass('hide')
|
||||||
if (!self.changing) {
|
|
||||||
self.changing = true
|
|
||||||
$('.mapInfoBox').fadeOut(200, function() {
|
$('.mapInfoBox').fadeOut(200, function() {
|
||||||
self.changing = false
|
|
||||||
self.isOpen = false
|
self.isOpen = false
|
||||||
self.hidePermissionSelect()
|
self.hidePermissionSelect()
|
||||||
$('.mapContributors .tip').hide()
|
$('.mapContributors .tip').hide()
|
||||||
})
|
})
|
||||||
}
|
|
||||||
},
|
},
|
||||||
load: function() {
|
load: function() {
|
||||||
var self = InfoBox
|
var self = InfoBox
|
||||||
|
@ -120,13 +110,8 @@ const InfoBox = {
|
||||||
obj['created_at'] = map.get('created_at_clean')
|
obj['created_at'] = map.get('created_at_clean')
|
||||||
obj['updated_at'] = map.get('updated_at_clean')
|
obj['updated_at'] = map.get('updated_at_clean')
|
||||||
|
|
||||||
var classes = isCreator ? 'yourMap' : ''
|
self.html = self.generateBoxHTML.render(obj)
|
||||||
classes += canEdit ? ' canEdit' : ''
|
ReactApp.render()
|
||||||
classes += shareable ? ' shareable' : ''
|
|
||||||
$('.mapInfoBox').removeClass('shareable yourMap canEdit')
|
|
||||||
.addClass(classes)
|
|
||||||
.html(self.generateBoxHTML.render(obj))
|
|
||||||
|
|
||||||
self.attachEventListeners()
|
self.attachEventListeners()
|
||||||
},
|
},
|
||||||
attachEventListeners: function() {
|
attachEventListeners: function() {
|
||||||
|
@ -192,7 +177,6 @@ const InfoBox = {
|
||||||
$('.mapContributors .tip').unbind().click(function(event) {
|
$('.mapContributors .tip').unbind().click(function(event) {
|
||||||
event.stopPropagation()
|
event.stopPropagation()
|
||||||
})
|
})
|
||||||
$('.mapContributors .tip li a').click(Router.intercept)
|
|
||||||
|
|
||||||
$('.mapInfoBox').unbind('.hideTip').bind('click.hideTip', function() {
|
$('.mapInfoBox').unbind('.hideTip').bind('click.hideTip', function() {
|
||||||
$('.mapContributors .tip').hide()
|
$('.mapContributors .tip').hide()
|
||||||
|
@ -393,7 +377,7 @@ const InfoBox = {
|
||||||
DataModel.Maps.Mine.remove(map)
|
DataModel.Maps.Mine.remove(map)
|
||||||
DataModel.Maps.Shared.remove(map)
|
DataModel.Maps.Shared.remove(map)
|
||||||
map.destroy()
|
map.destroy()
|
||||||
Router.home()
|
browserHistory.push('/')
|
||||||
GlobalUI.notifyUser('Map eliminated')
|
GlobalUI.notifyUser('Map eliminated')
|
||||||
} else if (!authorized) {
|
} else if (!authorized) {
|
||||||
window.alert("Hey now. We can't just go around willy nilly deleting other people's maps now can we? Run off and find something constructive to do, eh?")
|
window.alert("Hey now. We can't just go around willy nilly deleting other people's maps now can we? Run off and find something constructive to do, eh?")
|
||||||
|
|
|
@ -2,6 +2,7 @@
|
||||||
|
|
||||||
import outdent from 'outdent'
|
import outdent from 'outdent'
|
||||||
import { find as _find } from 'lodash'
|
import { find as _find } from 'lodash'
|
||||||
|
import { browserHistory } from 'react-router'
|
||||||
|
|
||||||
import Active from '../Active'
|
import Active from '../Active'
|
||||||
import AutoLayout from '../AutoLayout'
|
import AutoLayout from '../AutoLayout'
|
||||||
|
@ -9,11 +10,10 @@ import Create from '../Create'
|
||||||
import DataModel from '../DataModel'
|
import DataModel from '../DataModel'
|
||||||
import DataModelMap from '../DataModel/Map'
|
import DataModelMap from '../DataModel/Map'
|
||||||
import Filter from '../Filter'
|
import Filter from '../Filter'
|
||||||
import GlobalUI from '../GlobalUI'
|
import GlobalUI, { ReactApp } from '../GlobalUI'
|
||||||
import JIT from '../JIT'
|
import JIT from '../JIT'
|
||||||
import Loading from '../Loading'
|
import Loading from '../Loading'
|
||||||
import Realtime from '../Realtime'
|
import Realtime from '../Realtime'
|
||||||
import Router from '../Router'
|
|
||||||
import Selected from '../Selected'
|
import Selected from '../Selected'
|
||||||
import SynapseCard from '../SynapseCard'
|
import SynapseCard from '../SynapseCard'
|
||||||
import TopicCard from '../Views/TopicCard'
|
import TopicCard from '../Views/TopicCard'
|
||||||
|
@ -26,55 +26,98 @@ const Map = {
|
||||||
events: {
|
events: {
|
||||||
editedByActiveMapper: 'Metamaps:Map:events:editedByActiveMapper'
|
editedByActiveMapper: 'Metamaps:Map:events:editedByActiveMapper'
|
||||||
},
|
},
|
||||||
|
mapIsStarred: false,
|
||||||
|
requests: [],
|
||||||
|
userRequested: false,
|
||||||
|
requestAnswered: false,
|
||||||
|
requestApproved: false,
|
||||||
|
hasLearnedTopicCreation: true,
|
||||||
init: function(serverData) {
|
init: function(serverData) {
|
||||||
var self = Map
|
var self = Map
|
||||||
|
self.mapIsStarred = serverData.mapIsStarred
|
||||||
|
self.requests = serverData.requests
|
||||||
|
self.setAccessRequest()
|
||||||
$('#wrapper').mousedown(function(e) {
|
$('#wrapper').mousedown(function(e) {
|
||||||
if (e.button === 1) return false
|
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()
|
GlobalUI.CreateMap.emptyForkMapForm = $('#fork_map').html()
|
||||||
|
|
||||||
self.updateStar()
|
|
||||||
|
|
||||||
InfoBox.init(serverData, function updateThumbnail() {
|
InfoBox.init(serverData, function updateThumbnail() {
|
||||||
self.uploadMapScreenshot()
|
self.uploadMapScreenshot()
|
||||||
})
|
})
|
||||||
CheatSheet.init(serverData)
|
CheatSheet.init(serverData)
|
||||||
|
|
||||||
$('.viewOnly .requestAccess').click(self.requestAccess)
|
|
||||||
|
|
||||||
$(document).on(Map.events.editedByActiveMapper, self.editedByActiveMapper)
|
$(document).on(Map.events.editedByActiveMapper, self.editedByActiveMapper)
|
||||||
},
|
},
|
||||||
|
setHasLearnedTopicCreation: function(value) {
|
||||||
|
const self = Map
|
||||||
|
self.hasLearnedTopicCreation = value
|
||||||
|
ReactApp.render()
|
||||||
|
},
|
||||||
requestAccess: function() {
|
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
|
const mapId = Active.Map.id
|
||||||
$.post({
|
$.post({
|
||||||
url: `/maps/${mapId}/access_request`
|
url: `/maps/${mapId}/access_request`
|
||||||
})
|
})
|
||||||
GlobalUI.notifyUser('Map creator will be notified of your request')
|
GlobalUI.notifyUser('Map creator will be notified of your request')
|
||||||
},
|
},
|
||||||
setAccessRequest: function(requests, activeMapper) {
|
setAccessRequest: function() {
|
||||||
let className = 'isViewOnly '
|
const self = Map
|
||||||
if (activeMapper) {
|
if (Active.Mapper) {
|
||||||
const request = _find(requests, r => r.user_id === activeMapper.id)
|
const request = _find(self.requests, r => r.user_id === Active.Mapper.id)
|
||||||
if (!request) className += 'sendRequest'
|
if (!request) {
|
||||||
else if (request && !request.answered) className += 'sentRequest'
|
self.userRequested = false
|
||||||
else if (request && request.answered && !request.approved) className += 'requestDenied'
|
self.requestAnswered = false
|
||||||
|
self.requestApproved = false
|
||||||
}
|
}
|
||||||
$('.viewOnly').removeClass('sendRequest sentRequest requestDenied').addClass(className)
|
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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ReactApp.render()
|
||||||
},
|
},
|
||||||
launch: function(id) {
|
launch: function(id) {
|
||||||
var start = function(data) {
|
const self = Map
|
||||||
|
var dataIsReadySetupMap = function() {
|
||||||
|
Map.setAccessRequest()
|
||||||
|
Visualize.type = 'ForceDirected'
|
||||||
|
JIT.prepareVizData()
|
||||||
|
Selected.reset()
|
||||||
|
InfoBox.load()
|
||||||
|
Filter.reset()
|
||||||
|
Filter.checkMetacodes()
|
||||||
|
Filter.checkSynapses()
|
||||||
|
Filter.checkMappers()
|
||||||
|
Realtime.startActiveMap()
|
||||||
|
Loading.hide()
|
||||||
|
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)
|
Active.Map = new DataModelMap(data.map)
|
||||||
DataModel.Mappers = new DataModel.MapperCollection(data.mappers)
|
DataModel.Mappers = new DataModel.MapperCollection(data.mappers)
|
||||||
DataModel.Collaborators = new DataModel.MapperCollection(data.collaborators)
|
DataModel.Collaborators = new DataModel.MapperCollection(data.collaborators)
|
||||||
|
@ -84,85 +127,25 @@ const Map = {
|
||||||
DataModel.Messages = data.messages
|
DataModel.Messages = data.messages
|
||||||
DataModel.Stars = data.stars
|
DataModel.Stars = data.stars
|
||||||
DataModel.attachCollectionEvents()
|
DataModel.attachCollectionEvents()
|
||||||
|
self.requests = data.requests
|
||||||
var map = Active.Map
|
isLoaded()
|
||||||
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
|
|
||||||
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.checkMetacodes()
|
|
||||||
Filter.checkSynapses()
|
|
||||||
Filter.checkMappers()
|
|
||||||
|
|
||||||
Realtime.startActiveMap()
|
|
||||||
Loading.hide()
|
|
||||||
|
|
||||||
// for mobile
|
|
||||||
$('#header_content').html(map.get('name'))
|
|
||||||
}
|
|
||||||
|
|
||||||
$.ajax({
|
|
||||||
url: '/maps/' + id + '/contains.json',
|
|
||||||
success: start
|
|
||||||
})
|
})
|
||||||
|
}
|
||||||
},
|
},
|
||||||
end: function() {
|
end: function() {
|
||||||
if (Active.Map) {
|
if (Active.Map) {
|
||||||
$('.wrapper').removeClass('canEditMap commonsMap')
|
$('.main').removeClass('compressed')
|
||||||
AutoLayout.resetSpiral()
|
AutoLayout.resetSpiral()
|
||||||
|
|
||||||
$('.rightclickmenu').remove()
|
$('.rightclickmenu').remove()
|
||||||
TopicCard.hideCard()
|
TopicCard.hideCard()
|
||||||
SynapseCard.hideCard()
|
SynapseCard.hideCard()
|
||||||
Create.newTopic.hide(true) // true means force (and override pinned)
|
Create.newTopic.hide(true) // true means force (and override pinned)
|
||||||
Create.newSynapse.hide()
|
Create.newSynapse.hide()
|
||||||
Filter.close()
|
|
||||||
InfoBox.close()
|
InfoBox.close()
|
||||||
Realtime.endActiveMap()
|
Realtime.endActiveMap()
|
||||||
$('.viewOnly').removeClass('isViewOnly')
|
self.requests = []
|
||||||
}
|
self.hasLearnedTopicCreation = true
|
||||||
},
|
|
||||||
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')
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
star: function() {
|
star: function() {
|
||||||
|
@ -173,7 +156,8 @@ const Map = {
|
||||||
DataModel.Stars.push({ user_id: Active.Mapper.id, map_id: Active.Map.id })
|
DataModel.Stars.push({ user_id: Active.Mapper.id, map_id: Active.Map.id })
|
||||||
DataModel.Maps.Starred.add(Active.Map)
|
DataModel.Maps.Starred.add(Active.Map)
|
||||||
GlobalUI.notifyUser('Map is now starred')
|
GlobalUI.notifyUser('Map is now starred')
|
||||||
self.updateStar()
|
self.mapIsStarred = true
|
||||||
|
ReactApp.render()
|
||||||
},
|
},
|
||||||
unstar: function() {
|
unstar: function() {
|
||||||
var self = Map
|
var self = Map
|
||||||
|
@ -182,7 +166,8 @@ const Map = {
|
||||||
$.post('/maps/' + Active.Map.id + '/unstar')
|
$.post('/maps/' + Active.Map.id + '/unstar')
|
||||||
DataModel.Stars = DataModel.Stars.filter(function(s) { return s.user_id !== Active.Mapper.id })
|
DataModel.Stars = DataModel.Stars.filter(function(s) { return s.user_id !== Active.Mapper.id })
|
||||||
DataModel.Maps.Starred.remove(Active.Map)
|
DataModel.Maps.Starred.remove(Active.Map)
|
||||||
self.updateStar()
|
self.mapIsStarred = false
|
||||||
|
ReactApp.render()
|
||||||
},
|
},
|
||||||
fork: function() {
|
fork: function() {
|
||||||
GlobalUI.openLightbox('forkmap')
|
GlobalUI.openLightbox('forkmap')
|
||||||
|
@ -232,7 +217,7 @@ const Map = {
|
||||||
var map = Active.Map
|
var map = Active.Map
|
||||||
DataModel.Maps.Active.remove(map)
|
DataModel.Maps.Active.remove(map)
|
||||||
DataModel.Maps.Featured.remove(map)
|
DataModel.Maps.Featured.remove(map)
|
||||||
Router.home()
|
browserHistory.push('/')
|
||||||
GlobalUI.notifyUser('Sorry! That map has been changed to Private.')
|
GlobalUI.notifyUser('Sorry! That map has been changed to Private.')
|
||||||
},
|
},
|
||||||
cantEditNow: function() {
|
cantEditNow: function() {
|
||||||
|
@ -245,7 +230,7 @@ const Map = {
|
||||||
confirmString += 'Do you want to reload and enable realtime collaboration?'
|
confirmString += 'Do you want to reload and enable realtime collaboration?'
|
||||||
var c = window.confirm(confirmString)
|
var c = window.confirm(confirmString)
|
||||||
if (c) {
|
if (c) {
|
||||||
Router.maps(Active.Map.id)
|
window.location.reload()
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
editedByActiveMapper: function() {
|
editedByActiveMapper: function() {
|
||||||
|
|
|
@ -151,8 +151,6 @@ let Realtime = {
|
||||||
config: { DOUBLE_CLICK_TOLERANCE: 200 }
|
config: { DOUBLE_CLICK_TOLERANCE: 200 }
|
||||||
})
|
})
|
||||||
self.room.videoAdded(self.handleVideoAdded)
|
self.room.videoAdded(self.handleVideoAdded)
|
||||||
|
|
||||||
self.startActiveMap()
|
|
||||||
} // if Active.Mapper
|
} // if Active.Mapper
|
||||||
},
|
},
|
||||||
addJuntoListeners: function() {
|
addJuntoListeners: function() {
|
||||||
|
@ -201,9 +199,6 @@ let Realtime = {
|
||||||
self.leaveMap()
|
self.leaveMap()
|
||||||
$('.collabCompass').remove()
|
$('.collabCompass').remove()
|
||||||
if (self.room) self.room.leave()
|
if (self.room) self.room.leave()
|
||||||
ChatView.hide()
|
|
||||||
ChatView.close()
|
|
||||||
ChatView.reset()
|
|
||||||
Cable.unsubscribeFromMap()
|
Cable.unsubscribeFromMap()
|
||||||
},
|
},
|
||||||
turnOn: function(notify) {
|
turnOn: function(notify) {
|
||||||
|
@ -228,7 +223,6 @@ let Realtime = {
|
||||||
ChatView.setNewMap()
|
ChatView.setNewMap()
|
||||||
ChatView.addParticipant(self.activeMapper)
|
ChatView.addParticipant(self.activeMapper)
|
||||||
ChatView.addMessages(new DataModel.MessageCollection(DataModel.Messages), true)
|
ChatView.addMessages(new DataModel.MessageCollection(DataModel.Messages), true)
|
||||||
ChatView.show()
|
|
||||||
},
|
},
|
||||||
setupLocalEvents: function() {
|
setupLocalEvents: function() {
|
||||||
var self = Realtime
|
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 Create from './Create'
|
||||||
import DataModel from './DataModel'
|
import DataModel from './DataModel'
|
||||||
import Filter from './Filter'
|
import Filter from './Filter'
|
||||||
import GlobalUI from './GlobalUI'
|
import GlobalUI, { ReactApp } from './GlobalUI'
|
||||||
import JIT from './JIT'
|
import JIT from './JIT'
|
||||||
|
import Loading from './Loading'
|
||||||
import Map from './Map'
|
import Map from './Map'
|
||||||
import Router from './Router'
|
|
||||||
import Selected from './Selected'
|
import Selected from './Selected'
|
||||||
import Settings from './Settings'
|
import Settings from './Settings'
|
||||||
import SynapseCard from './SynapseCard'
|
import SynapseCard from './SynapseCard'
|
||||||
|
@ -36,48 +36,41 @@ const Topic = {
|
||||||
} else callback(DataModel.Topics.get(id))
|
} else callback(DataModel.Topics.get(id))
|
||||||
},
|
},
|
||||||
launch: function(id) {
|
launch: function(id) {
|
||||||
var start = function(data) {
|
var dataIsReadySetupTopic = function() {
|
||||||
|
Visualize.type = 'RGraph'
|
||||||
|
JIT.prepareVizData()
|
||||||
|
Selected.reset()
|
||||||
|
Filter.reset()
|
||||||
|
Filter.checkMetacodes()
|
||||||
|
Filter.checkSynapses()
|
||||||
|
Filter.checkMappers()
|
||||||
|
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)
|
Active.Topic = new DataModel.Topic(data.topic)
|
||||||
DataModel.Creators = new DataModel.MapperCollection(data.creators)
|
DataModel.Creators = new DataModel.MapperCollection(data.creators)
|
||||||
DataModel.Topics = new DataModel.TopicCollection([data.topic].concat(data.relatives))
|
DataModel.Topics = new DataModel.TopicCollection([data.topic].concat(data.relatives))
|
||||||
DataModel.Synapses = new DataModel.SynapseCollection(data.synapses)
|
DataModel.Synapses = new DataModel.SynapseCollection(data.synapses)
|
||||||
DataModel.attachCollectionEvents()
|
DataModel.attachCollectionEvents()
|
||||||
|
dataIsReadySetupTopic()
|
||||||
document.title = Active.Topic.get('name') + ' | Metamaps'
|
|
||||||
|
|
||||||
// set filter mapper H3 text
|
|
||||||
$('#filter_by_mapper h3').html('CREATORS')
|
|
||||||
|
|
||||||
// build and render the visualization
|
|
||||||
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.checkMetacodes()
|
|
||||||
Filter.checkSynapses()
|
|
||||||
Filter.checkMappers()
|
|
||||||
|
|
||||||
// for mobile
|
|
||||||
$('#header_content').html(Active.Topic.get('name'))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
$.ajax({
|
|
||||||
url: '/topics/' + id + '/network.json',
|
|
||||||
success: start
|
|
||||||
})
|
})
|
||||||
|
}
|
||||||
},
|
},
|
||||||
end: function() {
|
end: function() {
|
||||||
if (Active.Topic) {
|
if (Active.Topic) {
|
||||||
$('.rightclickmenu').remove()
|
$('.rightclickmenu').remove()
|
||||||
TopicCard.hideCard()
|
TopicCard.hideCard()
|
||||||
SynapseCard.hideCard()
|
SynapseCard.hideCard()
|
||||||
Filter.close()
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
centerOn: function(nodeid, callback) {
|
centerOn: function(nodeid, callback) {
|
||||||
|
@ -90,7 +83,6 @@ const Topic = {
|
||||||
if (callback) callback()
|
if (callback) callback()
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
Router.navigate('/topics/' + nodeid)
|
|
||||||
Active.Topic = DataModel.Topics.get(nodeid)
|
Active.Topic = DataModel.Topics.get(nodeid)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
@ -293,8 +285,7 @@ const Topic = {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// hide the 'double-click to add a topic' message
|
Map.setHasLearnedTopicCreation(true)
|
||||||
GlobalUI.hideDiv('#instructions')
|
|
||||||
|
|
||||||
$(document).trigger(Map.events.editedByActiveMapper)
|
$(document).trigger(Map.events.editedByActiveMapper)
|
||||||
|
|
||||||
|
@ -327,8 +318,7 @@ const Topic = {
|
||||||
getTopicFromAutocomplete: function(id) {
|
getTopicFromAutocomplete: function(id) {
|
||||||
var self = Topic
|
var self = Topic
|
||||||
|
|
||||||
// hide the 'double-click to add a topic' message
|
Map.setHasLearnedTopicCreation(true)
|
||||||
GlobalUI.hideDiv('#instructions')
|
|
||||||
|
|
||||||
$(document).trigger(Map.events.editedByActiveMapper)
|
$(document).trigger(Map.events.editedByActiveMapper)
|
||||||
|
|
||||||
|
|
|
@ -10,14 +10,14 @@ import ReactDOM from 'react-dom'
|
||||||
import Active from '../Active'
|
import Active from '../Active'
|
||||||
import DataModel from '../DataModel'
|
import DataModel from '../DataModel'
|
||||||
import Realtime from '../Realtime'
|
import Realtime from '../Realtime'
|
||||||
import MapChat from '../../components/MapChat'
|
import ReactApp from '../GlobalUI/ReactApp'
|
||||||
|
|
||||||
const ChatView = {
|
const ChatView = {
|
||||||
isOpen: false,
|
isOpen: false,
|
||||||
|
unreadMessages: 0,
|
||||||
messages: new Backbone.Collection(),
|
messages: new Backbone.Collection(),
|
||||||
conversationLive: false,
|
conversationLive: false,
|
||||||
isParticipating: false,
|
isParticipating: false,
|
||||||
mapChat: null,
|
|
||||||
domId: 'chat-box-wrapper',
|
domId: 'chat-box-wrapper',
|
||||||
init: function(urls) {
|
init: function(urls) {
|
||||||
const self = ChatView
|
const self = ChatView
|
||||||
|
@ -34,46 +34,32 @@ const ChatView = {
|
||||||
},
|
},
|
||||||
setNewMap: function() {
|
setNewMap: function() {
|
||||||
const self = ChatView
|
const self = ChatView
|
||||||
|
self.unreadMessages = 0
|
||||||
|
self.isOpen = false
|
||||||
self.conversationLive = false
|
self.conversationLive = false
|
||||||
self.isParticipating = false
|
self.isParticipating = false
|
||||||
self.alertSound = true // whether to play sounds on arrival of new messages or not
|
self.alertSound = true // whether to play sounds on arrival of new messages or not
|
||||||
self.cursorsShowing = true
|
self.cursorsShowing = true
|
||||||
self.videosShowing = true
|
self.videosShowing = true
|
||||||
self.participants = new Backbone.Collection()
|
self.participants = new Backbone.Collection()
|
||||||
|
self.messages = new Backbone.Collection()
|
||||||
self.render()
|
self.render()
|
||||||
},
|
},
|
||||||
show: () => {
|
|
||||||
$('#' + ChatView.domId).show()
|
|
||||||
},
|
|
||||||
hide: () => {
|
|
||||||
$('#' + ChatView.domId).hide()
|
|
||||||
},
|
|
||||||
render: () => {
|
render: () => {
|
||||||
if (!Active.Map) return
|
if (!Active.Map) return
|
||||||
const self = ChatView
|
const self = ChatView
|
||||||
self.mapChat = ReactDOM.render(React.createElement(MapChat, {
|
ReactApp.render()
|
||||||
conversationLive: self.conversationLive,
|
|
||||||
isParticipating: self.isParticipating,
|
|
||||||
onOpen: self.onOpen,
|
|
||||||
onClose: self.onClose,
|
|
||||||
leaveCall: Realtime.leaveCall,
|
|
||||||
joinCall: Realtime.joinCall,
|
|
||||||
inviteACall: Realtime.inviteACall,
|
|
||||||
inviteToJoin: Realtime.inviteToJoin,
|
|
||||||
participants: self.participants.models.map(p => p.attributes),
|
|
||||||
messages: self.messages.models.map(m => m.attributes),
|
|
||||||
videoToggleClick: self.videoToggleClick,
|
|
||||||
cursorToggleClick: self.cursorToggleClick,
|
|
||||||
soundToggleClick: self.soundToggleClick,
|
|
||||||
inputBlur: self.inputBlur,
|
|
||||||
inputFocus: self.inputFocus,
|
|
||||||
handleInputMessage: self.handleInputMessage
|
|
||||||
}), document.getElementById(ChatView.domId))
|
|
||||||
},
|
},
|
||||||
onOpen: () => {
|
onOpen: () => {
|
||||||
|
const self = ChatView
|
||||||
|
self.isOpen = true
|
||||||
|
self.unreadMessages = 0
|
||||||
|
self.render()
|
||||||
$(document).trigger(ChatView.events.openTray)
|
$(document).trigger(ChatView.events.openTray)
|
||||||
},
|
},
|
||||||
onClose: () => {
|
onClose: () => {
|
||||||
|
const self = ChatView
|
||||||
|
self.isOpen = false
|
||||||
$(document).trigger(ChatView.events.closeTray)
|
$(document).trigger(ChatView.events.closeTray)
|
||||||
},
|
},
|
||||||
addParticipant: participant => {
|
addParticipant: participant => {
|
||||||
|
@ -119,12 +105,6 @@ const ChatView = {
|
||||||
ChatView.participants.forEach(p => p.set({isParticipating: false, isPending: false}))
|
ChatView.participants.forEach(p => p.set({isParticipating: false, isPending: false}))
|
||||||
ChatView.render()
|
ChatView.render()
|
||||||
},
|
},
|
||||||
close: () => {
|
|
||||||
ChatView.mapChat && ChatView.mapChat.close()
|
|
||||||
},
|
|
||||||
open: () => {
|
|
||||||
ChatView.mapChat && ChatView.mapChat.open()
|
|
||||||
},
|
|
||||||
videoToggleClick: function() {
|
videoToggleClick: function() {
|
||||||
ChatView.videosShowing = !ChatView.videosShowing
|
ChatView.videosShowing = !ChatView.videosShowing
|
||||||
$(document).trigger(ChatView.videosShowing ? ChatView.events.videosOn : ChatView.events.videosOff)
|
$(document).trigger(ChatView.videosShowing ? ChatView.events.videosOn : ChatView.events.videosOff)
|
||||||
|
@ -144,11 +124,10 @@ const ChatView = {
|
||||||
},
|
},
|
||||||
addMessage: (message, isInitial, wasMe) => {
|
addMessage: (message, isInitial, wasMe) => {
|
||||||
const self = ChatView
|
const self = ChatView
|
||||||
if (!isInitial) self.mapChat.newMessage()
|
if (!isInitial && !self.isOpen) self.unreadMessages += 1
|
||||||
if (!wasMe && !isInitial && self.alertSound) self.sound.play('receivechat')
|
if (!wasMe && !isInitial && self.alertSound) self.sound.play('receivechat')
|
||||||
self.messages.add(message)
|
self.messages.add(message)
|
||||||
self.render()
|
if (!isInitial && self.isOpen) self.render()
|
||||||
if (!isInitial) self.mapChat.scroll()
|
|
||||||
},
|
},
|
||||||
sendChatMessage: message => {
|
sendChatMessage: message => {
|
||||||
var self = ChatView
|
var self = ChatView
|
||||||
|
@ -174,23 +153,9 @@ const ChatView = {
|
||||||
// passed to this function
|
// passed to this function
|
||||||
addMessages: (messages, isInitial, wasMe) => {
|
addMessages: (messages, isInitial, wasMe) => {
|
||||||
messages.models.forEach(m => ChatView.addMessage(m, 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
|
* @class
|
||||||
* @static
|
* @static
|
||||||
|
|
|
@ -1,21 +1,68 @@
|
||||||
/* global $ */
|
/* global $ */
|
||||||
|
|
||||||
import React from 'react'
|
|
||||||
import ReactDOM from 'react-dom' // TODO ensure this isn't a double import
|
|
||||||
|
|
||||||
import Active from '../Active'
|
import Active from '../Active'
|
||||||
import DataModel from '../DataModel'
|
import DataModel from '../DataModel'
|
||||||
import GlobalUI from '../GlobalUI'
|
import GlobalUI, { ReactApp } from '../GlobalUI'
|
||||||
import Realtime from '../Realtime'
|
|
||||||
import Loading from '../Loading'
|
import Loading from '../Loading'
|
||||||
import Maps from '../../components/Maps'
|
|
||||||
|
|
||||||
const ExploreMaps = {
|
const ExploreMaps = {
|
||||||
pending: false,
|
pending: false,
|
||||||
mapper: null,
|
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) {
|
setCollection: function(collection) {
|
||||||
var self = ExploreMaps
|
var self = ExploreMaps
|
||||||
|
|
||||||
if (self.collection) {
|
if (self.collection) {
|
||||||
self.collection.off('add', self.render)
|
self.collection.off('add', self.render)
|
||||||
self.collection.off('successOnFetch', self.handleSuccess)
|
self.collection.off('successOnFetch', self.handleSuccess)
|
||||||
|
@ -26,27 +73,61 @@ const ExploreMaps = {
|
||||||
self.collection.on('successOnFetch', self.handleSuccess)
|
self.collection.on('successOnFetch', self.handleSuccess)
|
||||||
self.collection.on('errorOnFetch', self.handleError)
|
self.collection.on('errorOnFetch', self.handleError)
|
||||||
},
|
},
|
||||||
render: function(cb) {
|
render: function() {
|
||||||
|
ReactApp.resize()
|
||||||
|
ReactApp.render()
|
||||||
|
Loading.hide()
|
||||||
|
},
|
||||||
|
loadMore: function() {
|
||||||
|
var self = ExploreMaps
|
||||||
|
if (self.collection.page !== 'loadedAll') {
|
||||||
|
self.collection.getMaps()
|
||||||
|
self.pending = true
|
||||||
|
}
|
||||||
|
self.render()
|
||||||
|
},
|
||||||
|
handleSuccess: function() {
|
||||||
|
var self = ExploreMaps
|
||||||
|
self.pending = false
|
||||||
|
if (self.collection && self.collection.id === 'mapper') {
|
||||||
|
self.fetchUserThenRender()
|
||||||
|
} else {
|
||||||
|
self.render()
|
||||||
|
}
|
||||||
|
},
|
||||||
|
handleError: function() {
|
||||||
|
console.log('error loading maps!') // TODO
|
||||||
|
Loading.hide()
|
||||||
|
},
|
||||||
|
fetchUserThenRender: function(cb) {
|
||||||
var self = ExploreMaps
|
var self = ExploreMaps
|
||||||
|
|
||||||
if (!self.collection) return
|
if (self.mapper && self.mapper.id === self.collection.mapperId) {
|
||||||
|
self.render()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
var exploreObj = {
|
// first load the mapper object and then call the render function
|
||||||
currentUser: Active.Mapper,
|
$.ajax({
|
||||||
section: self.collection.id,
|
url: '/users/' + self.collection.mapperId + '/details.json',
|
||||||
maps: self.collection,
|
success: function(response) {
|
||||||
juntoState: Realtime.juntoState,
|
self.mapper = response
|
||||||
moreToLoad: self.collection.page !== 'loadedAll',
|
document.title = self.mapper.name + ' | Metamaps'
|
||||||
user: self.collection.id === 'mapper' ? self.mapper : null,
|
ReactApp.mobileTitle = self.mapper.name
|
||||||
loadMore: self.loadMore,
|
self.render()
|
||||||
pending: self.pending,
|
},
|
||||||
|
error: function() {
|
||||||
|
self.render()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
},
|
||||||
onStar: function(map) {
|
onStar: function(map) {
|
||||||
$.post('/maps/' + map.id + '/star')
|
$.post('/maps/' + map.id + '/star')
|
||||||
map.set('star_count', map.get('star_count') + 1)
|
map.set('star_count', map.get('star_count') + 1)
|
||||||
if (DataModel.Stars) DataModel.Stars.push({ user_id: Active.Mapper.id, map_id: map.id })
|
if (DataModel.Stars) DataModel.Stars.push({ user_id: Active.Mapper.id, map_id: map.id })
|
||||||
DataModel.Maps.Starred.add(map)
|
DataModel.Maps.Starred.add(map)
|
||||||
GlobalUI.notifyUser('Map is now starred')
|
GlobalUI.notifyUser('Map is now starred')
|
||||||
self.render()
|
ReactApp.render()
|
||||||
},
|
},
|
||||||
onRequest: function(map) {
|
onRequest: function(map) {
|
||||||
$.post({
|
$.post({
|
||||||
|
@ -54,7 +135,7 @@ const ExploreMaps = {
|
||||||
})
|
})
|
||||||
GlobalUI.notifyUser('You will be notified by email if request accepted')
|
GlobalUI.notifyUser('You will be notified by email if request accepted')
|
||||||
},
|
},
|
||||||
onFollow: function(map) {
|
onMapFollow: function(map) {
|
||||||
const isFollowing = map.isFollowedBy(Active.Mapper)
|
const isFollowing = map.isFollowedBy(Active.Mapper)
|
||||||
$.post({
|
$.post({
|
||||||
url: `/maps/${map.id}/${isFollowing ? 'un' : ''}follow`
|
url: `/maps/${map.id}/${isFollowing ? 'un' : ''}follow`
|
||||||
|
@ -66,60 +147,7 @@ const ExploreMaps = {
|
||||||
GlobalUI.notifyUser('You are now following this map')
|
GlobalUI.notifyUser('You are now following this map')
|
||||||
Active.Mapper.followMap(map.id)
|
Active.Mapper.followMap(map.id)
|
||||||
}
|
}
|
||||||
self.render()
|
ReactApp.render()
|
||||||
}
|
|
||||||
}
|
|
||||||
ReactDOM.render(
|
|
||||||
React.createElement(Maps, exploreObj),
|
|
||||||
document.getElementById('explore')
|
|
||||||
).resize()
|
|
||||||
|
|
||||||
if (cb) cb()
|
|
||||||
Loading.hide()
|
|
||||||
},
|
|
||||||
loadMore: function() {
|
|
||||||
var self = ExploreMaps
|
|
||||||
if (self.collection.page !== 'loadedAll') {
|
|
||||||
self.collection.getMaps()
|
|
||||||
self.pending = true
|
|
||||||
}
|
|
||||||
self.render()
|
|
||||||
},
|
|
||||||
handleSuccess: function(cb) {
|
|
||||||
var self = ExploreMaps
|
|
||||||
self.pending = false
|
|
||||||
if (self.collection && self.collection.id === 'mapper') {
|
|
||||||
self.fetchUserThenRender(cb)
|
|
||||||
} else {
|
|
||||||
self.render(cb)
|
|
||||||
Loading.hide()
|
|
||||||
}
|
|
||||||
},
|
|
||||||
handleError: function() {
|
|
||||||
console.log('error loading maps!') // TODO
|
|
||||||
Loading.hide()
|
|
||||||
},
|
|
||||||
fetchUserThenRender: function(cb) {
|
|
||||||
var self = ExploreMaps
|
|
||||||
|
|
||||||
if (self.mapper && self.mapper.id === self.collection.mapperId) {
|
|
||||||
self.render(cb)
|
|
||||||
return Loading.hide()
|
|
||||||
}
|
|
||||||
|
|
||||||
// first load the mapper object and then call the render function
|
|
||||||
$.ajax({
|
|
||||||
url: '/users/' + self.collection.mapperId + '/details.json',
|
|
||||||
success: function(response) {
|
|
||||||
self.mapper = response
|
|
||||||
self.render(cb)
|
|
||||||
Loading.hide()
|
|
||||||
},
|
|
||||||
error: function() {
|
|
||||||
self.render(cb)
|
|
||||||
Loading.hide()
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -5,27 +5,20 @@ import ReactDOM from 'react-dom'
|
||||||
|
|
||||||
import Active from '../Active'
|
import Active from '../Active'
|
||||||
import Visualize from '../Visualize'
|
import Visualize from '../Visualize'
|
||||||
import GlobalUI from '../GlobalUI'
|
import GlobalUI, { ReactApp } from '../GlobalUI'
|
||||||
|
|
||||||
import ReactTopicCard from '../../components/TopicCard'
|
|
||||||
|
|
||||||
const TopicCard = {
|
const TopicCard = {
|
||||||
openTopicCard: null, // stores the topic that's currently open
|
openTopic: null, // stores the topic that's currently open
|
||||||
metacodeSets: [],
|
metacodeSets: [],
|
||||||
|
redrawCanvas: () => {
|
||||||
|
Visualize.mGraph.plot()
|
||||||
|
},
|
||||||
init: function(serverData) {
|
init: function(serverData) {
|
||||||
const self = TopicCard
|
const self = TopicCard
|
||||||
self.metacodeSets = serverData.metacodeSets
|
self.metacodeSets = serverData.metacodeSets
|
||||||
},
|
},
|
||||||
populateShowCard: function(topic) {
|
onTopicFollow: topic => {
|
||||||
const self = TopicCard
|
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)
|
const isFollowing = topic.isFollowedBy(Active.Mapper)
|
||||||
$.post({
|
$.post({
|
||||||
url: `/topics/${topic.id}/${isFollowing ? 'un' : ''}follow`
|
url: `/topics/${topic.id}/${isFollowing ? 'un' : ''}follow`
|
||||||
|
@ -37,37 +30,34 @@ const TopicCard = {
|
||||||
GlobalUI.notifyUser('You are now following this topic')
|
GlobalUI.notifyUser('You are now following this topic')
|
||||||
Active.Mapper.followTopic(topic.id)
|
Active.Mapper.followTopic(topic.id)
|
||||||
}
|
}
|
||||||
self.populateShowCard(topic)
|
self.render()
|
||||||
},
|
},
|
||||||
metacodeSets: self.metacodeSets,
|
updateTopic: (topic, obj) => {
|
||||||
redrawCanvas: () => {
|
const self = TopicCard
|
||||||
Visualize.mGraph.plot()
|
topic.save(obj, { success: self.render })
|
||||||
}
|
},
|
||||||
}),
|
render: function() {
|
||||||
document.getElementById('showcard')
|
ReactApp.render()
|
||||||
)
|
},
|
||||||
|
showCard: function(node, opts = {}) {
|
||||||
// initialize draggability
|
var self = TopicCard
|
||||||
|
var topic = node.getData('topic')
|
||||||
|
self.openTopic = topic
|
||||||
|
self.render()
|
||||||
|
$('.showcard').fadeIn('fast', () => {
|
||||||
$('.showcard').draggable({
|
$('.showcard').draggable({
|
||||||
handle: '.metacodeImage',
|
handle: '.metacodeImage',
|
||||||
stop: function() {
|
stop: function() {
|
||||||
$(this).height('auto')
|
$(this).height('auto')
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
},
|
opts.complete && opts.complete()
|
||||||
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())
|
|
||||||
},
|
},
|
||||||
hideCard: function() {
|
hideCard: function() {
|
||||||
var self = TopicCard
|
var self = TopicCard
|
||||||
$('.showcard').fadeOut('fast')
|
$('.showcard').fadeOut('fast')
|
||||||
self.openTopicCard = null
|
self.openTopic = null
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -8,7 +8,6 @@ import Active from './Active'
|
||||||
import DataModel from './DataModel'
|
import DataModel from './DataModel'
|
||||||
import JIT from './JIT'
|
import JIT from './JIT'
|
||||||
import Loading from './Loading'
|
import Loading from './Loading'
|
||||||
import Router from './Router'
|
|
||||||
import TopicCard from './Views/TopicCard'
|
import TopicCard from './Views/TopicCard'
|
||||||
|
|
||||||
const Visualize = {
|
const Visualize = {
|
||||||
|
@ -198,19 +197,6 @@ const Visualize = {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
hold()
|
hold()
|
||||||
|
|
||||||
// update the url now that the map is ready
|
|
||||||
clearTimeout(Router.timeoutId)
|
|
||||||
Router.timeoutId = setTimeout(function() {
|
|
||||||
var m = Active.Map
|
|
||||||
var t = Active.Topic
|
|
||||||
|
|
||||||
if (m && window.location.pathname !== '/maps/' + m.id) {
|
|
||||||
Router.navigateAndTrack('/maps/' + m.id)
|
|
||||||
} else if (t && window.location.pathname !== '/topics/' + t.id) {
|
|
||||||
Router.navigateAndTrack('/topics/' + t.id)
|
|
||||||
}
|
|
||||||
}, 800)
|
|
||||||
},
|
},
|
||||||
clearVisualization: function() {
|
clearVisualization: function() {
|
||||||
Visualize.mGraph.graph.empty()
|
Visualize.mGraph.graph.empty()
|
||||||
|
|
|
@ -9,8 +9,7 @@ import DataModel from './DataModel'
|
||||||
import Debug from './Debug'
|
import Debug from './Debug'
|
||||||
import Filter from './Filter'
|
import Filter from './Filter'
|
||||||
import GlobalUI, {
|
import GlobalUI, {
|
||||||
Search, CreateMap, ImportDialog, Account as GlobalUIAccount,
|
ReactApp, Search, CreateMap, ImportDialog
|
||||||
NotificationIcon
|
|
||||||
} from './GlobalUI'
|
} from './GlobalUI'
|
||||||
import Import from './Import'
|
import Import from './Import'
|
||||||
import JIT from './JIT'
|
import JIT from './JIT'
|
||||||
|
@ -18,12 +17,10 @@ import Listeners from './Listeners'
|
||||||
import Loading from './Loading'
|
import Loading from './Loading'
|
||||||
import Map, { CheatSheet, InfoBox } from './Map'
|
import Map, { CheatSheet, InfoBox } from './Map'
|
||||||
import Mapper from './Mapper'
|
import Mapper from './Mapper'
|
||||||
import Mobile from './Mobile'
|
|
||||||
import Mouse from './Mouse'
|
import Mouse from './Mouse'
|
||||||
import Organize from './Organize'
|
import Organize from './Organize'
|
||||||
import PasteInput from './PasteInput'
|
import PasteInput from './PasteInput'
|
||||||
import Realtime from './Realtime'
|
import Realtime from './Realtime'
|
||||||
import Router from './Router'
|
|
||||||
import Selected from './Selected'
|
import Selected from './Selected'
|
||||||
import Settings from './Settings'
|
import Settings from './Settings'
|
||||||
import Synapse from './Synapse'
|
import Synapse from './Synapse'
|
||||||
|
@ -45,11 +42,10 @@ Metamaps.DataModel = DataModel
|
||||||
Metamaps.Debug = Debug
|
Metamaps.Debug = Debug
|
||||||
Metamaps.Filter = Filter
|
Metamaps.Filter = Filter
|
||||||
Metamaps.GlobalUI = GlobalUI
|
Metamaps.GlobalUI = GlobalUI
|
||||||
|
Metamaps.GlobalUI.ReactApp = ReactApp
|
||||||
Metamaps.GlobalUI.Search = Search
|
Metamaps.GlobalUI.Search = Search
|
||||||
Metamaps.GlobalUI.CreateMap = CreateMap
|
Metamaps.GlobalUI.CreateMap = CreateMap
|
||||||
Metamaps.GlobalUI.Account = GlobalUIAccount
|
|
||||||
Metamaps.GlobalUI.ImportDialog = ImportDialog
|
Metamaps.GlobalUI.ImportDialog = ImportDialog
|
||||||
Metamaps.GlobalUI.NotificationIcon = NotificationIcon
|
|
||||||
Metamaps.Import = Import
|
Metamaps.Import = Import
|
||||||
Metamaps.JIT = JIT
|
Metamaps.JIT = JIT
|
||||||
Metamaps.Listeners = Listeners
|
Metamaps.Listeners = Listeners
|
||||||
|
@ -59,12 +55,10 @@ Metamaps.Map.CheatSheet = CheatSheet
|
||||||
Metamaps.Map.InfoBox = InfoBox
|
Metamaps.Map.InfoBox = InfoBox
|
||||||
Metamaps.Maps = {}
|
Metamaps.Maps = {}
|
||||||
Metamaps.Mapper = Mapper
|
Metamaps.Mapper = Mapper
|
||||||
Metamaps.Mobile = Mobile
|
|
||||||
Metamaps.Mouse = Mouse
|
Metamaps.Mouse = Mouse
|
||||||
Metamaps.Organize = Organize
|
Metamaps.Organize = Organize
|
||||||
Metamaps.PasteInput = PasteInput
|
Metamaps.PasteInput = PasteInput
|
||||||
Metamaps.Realtime = Realtime
|
Metamaps.Realtime = Realtime
|
||||||
Metamaps.Router = Router
|
|
||||||
Metamaps.Selected = Selected
|
Metamaps.Selected = Selected
|
||||||
Metamaps.Settings = Settings
|
Metamaps.Settings = Settings
|
||||||
Metamaps.Synapse = Synapse
|
Metamaps.Synapse = Synapse
|
||||||
|
@ -86,26 +80,6 @@ document.addEventListener('DOMContentLoaded', function() {
|
||||||
Metamaps[prop].init(Metamaps.ServerData)
|
Metamaps[prop].init(Metamaps.ServerData)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// load whichever page you are on
|
|
||||||
if (Metamaps.currentSection === 'explore') {
|
|
||||||
const capitalize = Metamaps.currentPage.charAt(0).toUpperCase() + Metamaps.currentPage.slice(1)
|
|
||||||
|
|
||||||
Views.ExploreMaps.setCollection(DataModel.Maps[capitalize])
|
|
||||||
if (Metamaps.currentPage === 'mapper') {
|
|
||||||
Views.ExploreMaps.fetchUserThenRender()
|
|
||||||
} else {
|
|
||||||
Views.ExploreMaps.render()
|
|
||||||
}
|
|
||||||
GlobalUI.showDiv('#explore')
|
|
||||||
} else if (Metamaps.currentSection === '' && Active.Mapper) {
|
|
||||||
Views.ExploreMaps.setCollection(DataModel.Maps.Active)
|
|
||||||
Views.ExploreMaps.render()
|
|
||||||
GlobalUI.showDiv('#explore')
|
|
||||||
} else if (Active.Map || Active.Topic) {
|
|
||||||
Loading.show()
|
|
||||||
JIT.prepareVizData()
|
|
||||||
GlobalUI.showDiv('#infovis')
|
|
||||||
}
|
|
||||||
})
|
})
|
||||||
|
|
||||||
export default Metamaps
|
export default Metamaps
|
||||||
|
|
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'
|
import React, { PropTypes, Component } from 'react'
|
||||||
|
|
||||||
class NotificationIcon extends Component {
|
class NotificationIcon extends Component {
|
||||||
|
|
||||||
|
static propTypes = {
|
||||||
|
unreadNotificationsCount: PropTypes.number
|
||||||
|
}
|
||||||
|
|
||||||
constructor(props) {
|
constructor(props) {
|
||||||
super(props)
|
super(props)
|
||||||
|
|
||||||
|
@ -31,8 +36,4 @@ class NotificationIcon extends Component {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
NotificationIcon.propTypes = {
|
|
||||||
unreadNotificationsCount: PropTypes.number
|
|
||||||
}
|
|
||||||
|
|
||||||
export default NotificationIcon
|
export default NotificationIcon
|
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 React from 'react'
|
||||||
import Autolinker from 'autolinker'
|
import Autolinker from 'autolinker'
|
||||||
import Util from '../../Metamaps/Util'
|
import Util from '../../../Metamaps/Util'
|
||||||
|
|
||||||
const linker = new Autolinker({ newWindow: true, truncate: 50, email: false, phone: false })
|
const linker = new Autolinker({ newWindow: true, truncate: 50, email: false, phone: false })
|
||||||
|
|
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 React, { Component, PropTypes } from 'react'
|
||||||
|
import { Link } from 'react-router'
|
||||||
import _ from 'lodash'
|
import _ from 'lodash'
|
||||||
|
|
||||||
const MapLink = props => {
|
const MapLink = props => {
|
||||||
|
@ -9,16 +10,16 @@ const MapLink = props => {
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<a { ...otherProps } href={href} className={linkClass}>
|
<Link { ...otherProps } to={href} className={linkClass}>
|
||||||
<div className="exploreMapsIcon"></div>
|
<div className="exploreMapsIcon"></div>
|
||||||
{text}
|
{text}
|
||||||
</a>
|
</Link>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
class Header extends Component {
|
class Header extends Component {
|
||||||
render = () => {
|
render = () => {
|
||||||
const { signedIn, section } = this.props
|
const { signedIn, section, user } = this.props
|
||||||
|
|
||||||
const activeClass = (title) => {
|
const activeClass = (title) => {
|
||||||
let forClass = 'exploreMapsButton'
|
let forClass = 'exploreMapsButton'
|
||||||
|
@ -39,38 +40,33 @@ class Header extends Component {
|
||||||
<MapLink show={explore}
|
<MapLink show={explore}
|
||||||
href={signedIn ? '/' : '/explore/active'}
|
href={signedIn ? '/' : '/explore/active'}
|
||||||
linkClass={activeClass('active')}
|
linkClass={activeClass('active')}
|
||||||
data-router="true"
|
|
||||||
text="All Maps"
|
text="All Maps"
|
||||||
/>
|
/>
|
||||||
<MapLink show={signedIn && explore}
|
<MapLink show={signedIn && explore}
|
||||||
href="/explore/mine"
|
href="/explore/mine"
|
||||||
linkClass={activeClass('my')}
|
linkClass={activeClass('my')}
|
||||||
data-router="true"
|
|
||||||
text="My Maps"
|
text="My Maps"
|
||||||
/>
|
/>
|
||||||
<MapLink show={signedIn && explore}
|
<MapLink show={signedIn && explore}
|
||||||
href="/explore/shared"
|
href="/explore/shared"
|
||||||
linkClass={activeClass('shared')}
|
linkClass={activeClass('shared')}
|
||||||
data-router="true"
|
|
||||||
text="Shared With Me"
|
text="Shared With Me"
|
||||||
/>
|
/>
|
||||||
<MapLink show={signedIn && explore}
|
<MapLink show={signedIn && explore}
|
||||||
href="/explore/starred"
|
href="/explore/starred"
|
||||||
linkClass={activeClass('starred')}
|
linkClass={activeClass('starred')}
|
||||||
data-router="true"
|
|
||||||
text="Starred By Me"
|
text="Starred By Me"
|
||||||
/>
|
/>
|
||||||
<MapLink show={!signedIn && explore}
|
<MapLink show={!signedIn && explore}
|
||||||
href="/explore/featured"
|
href="/explore/featured"
|
||||||
linkClass={activeClass('featured')}
|
linkClass={activeClass('featured')}
|
||||||
data-router="true"
|
|
||||||
text="Featured Maps"
|
text="Featured Maps"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
{mapper ? (
|
{mapper ? (
|
||||||
<div className='exploreMapsButton active mapperButton'>
|
<div className='exploreMapsButton active mapperButton'>
|
||||||
<img className='exploreMapperImage' width='24' height='24' src={this.props.user.image} />
|
{user && <img className='exploreMapperImage' width='24' height='24' src={user.image} />}
|
||||||
<div className='exploreMapperName'>{this.props.user.name}’s Maps</div>
|
{user && <div className='exploreMapperName'>{user.name}’s Maps</div>}
|
||||||
<div className='clearfloat'></div>
|
<div className='clearfloat'></div>
|
||||||
</div>
|
</div>
|
||||||
) : null }
|
) : null }
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
import React, { Component, PropTypes } from 'react'
|
import React, { Component, PropTypes } from 'react'
|
||||||
|
import { Link } from 'react-router'
|
||||||
import { find, values } from 'lodash'
|
import { find, values } from 'lodash'
|
||||||
import Util from '../../Metamaps/Util'
|
import Util from '../../Metamaps/Util'
|
||||||
|
|
||||||
|
@ -24,7 +25,7 @@ class Menu extends Component {
|
||||||
}
|
}
|
||||||
|
|
||||||
render = () => {
|
render = () => {
|
||||||
const { currentUser, map, onStar, onRequest, onFollow } = this.props
|
const { currentUser, map, onStar, onRequest, onMapFollow } = this.props
|
||||||
const isFollowing = map.isFollowedBy(currentUser)
|
const isFollowing = map.isFollowedBy(currentUser)
|
||||||
const style = { display: this.state.open ? 'block' : 'none' }
|
const style = { display: this.state.open ? 'block' : 'none' }
|
||||||
|
|
||||||
|
@ -37,7 +38,7 @@ class Menu extends Component {
|
||||||
<ul className='menuItems' style={ style }>
|
<ul className='menuItems' style={ style }>
|
||||||
<li className='star' onClick={ () => { this.toggle() && onStar(map) }}>Star Map</li>
|
<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> }
|
{ !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>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
|
@ -47,7 +48,7 @@ Menu.propTypes = {
|
||||||
map: PropTypes.object.isRequired,
|
map: PropTypes.object.isRequired,
|
||||||
onStar: PropTypes.func.isRequired,
|
onStar: PropTypes.func.isRequired,
|
||||||
onRequest: PropTypes.func.isRequired,
|
onRequest: PropTypes.func.isRequired,
|
||||||
onFollow: PropTypes.func.isRequired
|
onMapFollow: PropTypes.func.isRequired
|
||||||
}
|
}
|
||||||
|
|
||||||
const Metadata = (props) => {
|
const Metadata = (props) => {
|
||||||
|
@ -78,13 +79,13 @@ const Metadata = (props) => {
|
||||||
}
|
}
|
||||||
|
|
||||||
const checkAndWrapInA = (shouldWrap, classString, mapId, element) => {
|
const checkAndWrapInA = (shouldWrap, classString, mapId, element) => {
|
||||||
if (shouldWrap) return <a className={ classString } href={ `/maps/${mapId}` } data-router="true">{ element }</a>
|
if (shouldWrap) return <Link className={ classString } to={ `/maps/${mapId}` } >{ element }</Link>
|
||||||
else return element
|
else return element
|
||||||
}
|
}
|
||||||
|
|
||||||
class MapCard extends Component {
|
class MapCard extends Component {
|
||||||
render = () => {
|
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 hasMap = (juntoState.liveMaps[map.id] && values(juntoState.liveMaps[map.id]).length) || null
|
||||||
const realtimeMap = juntoState.liveMaps[map.id]
|
const realtimeMap = juntoState.liveMaps[map.id]
|
||||||
|
@ -135,7 +136,7 @@ class MapCard extends Component {
|
||||||
</div>) }
|
</div>) }
|
||||||
{ !mobile && hasMapper && <div className='mapHasMapper'><MapperList mappers={ mapperList } /></div> }
|
{ !mobile && hasMapper && <div className='mapHasMapper'><MapperList mappers={ mapperList } /></div> }
|
||||||
{ !mobile && hasConversation && <div className='mapHasConversation'><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>) }
|
</div>) }
|
||||||
</div>
|
</div>
|
||||||
|
@ -150,7 +151,7 @@ MapCard.propTypes = {
|
||||||
currentUser: PropTypes.object,
|
currentUser: PropTypes.object,
|
||||||
onStar: PropTypes.func.isRequired,
|
onStar: PropTypes.func.isRequired,
|
||||||
onRequest: PropTypes.func.isRequired,
|
onRequest: PropTypes.func.isRequired,
|
||||||
onFollow: PropTypes.func.isRequired
|
onMapFollow: PropTypes.func.isRequired
|
||||||
}
|
}
|
||||||
|
|
||||||
export default MapCard
|
export default MapCard
|
||||||
|
|
|
@ -4,59 +4,56 @@ import Header from './Header'
|
||||||
import MapperCard from './MapperCard'
|
import MapperCard from './MapperCard'
|
||||||
import MapCard from './MapCard'
|
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 {
|
class Maps extends Component {
|
||||||
|
|
||||||
constructor(props) {
|
static propTypes = {
|
||||||
super(props)
|
section: PropTypes.string,
|
||||||
this.state = { mapsWidth: 0 }
|
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() {
|
static contextTypes = {
|
||||||
window && window.addEventListener('resize', this.resize)
|
location: PropTypes.object
|
||||||
this.refs.maps.addEventListener('scroll', throttle(this.scroll, 500, { leading: true, trailing: false }))
|
|
||||||
this.resize()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
componentWillUnmount() {
|
mapsDidMount = (node) => {
|
||||||
window && window.removeEventListener('resize', this.resize)
|
if (node) {
|
||||||
|
this.mapsDiv = node
|
||||||
|
node.addEventListener('scroll', throttle(this.scroll, 500, { leading: true, trailing: false }))
|
||||||
}
|
}
|
||||||
|
|
||||||
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 })
|
|
||||||
}
|
}
|
||||||
|
|
||||||
scroll = () => {
|
scroll = () => {
|
||||||
const { loadMore, moreToLoad, pending } = this.props
|
const { loadMore, moreToLoad, pending } = this.props
|
||||||
const { maps } = this.refs
|
const { mapsDiv } = this
|
||||||
if (moreToLoad && !pending && maps.scrollTop + maps.offsetHeight > maps.scrollHeight - 300) {
|
if (moreToLoad && !pending && mapsDiv.scrollTop + mapsDiv.offsetHeight > mapsDiv.scrollHeight - 300) {
|
||||||
loadMore()
|
loadMore()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
render = () => {
|
render = () => {
|
||||||
const { maps, currentUser, juntoState, pending, section, user, onStar, onRequest, onFollow } = this.props
|
const { mobile, maps, mapsWidth, currentUser, juntoState, pending, section, user, onStar, onRequest, onMapFollow } = this.props
|
||||||
const style = { width: this.state.mapsWidth + 'px' }
|
const style = { width: mapsWidth + 'px' }
|
||||||
const mobile = document && document.body.clientWidth <= MOBILE_VIEW_BREAKPOINT
|
|
||||||
|
if (!maps) return null // do loading here instead
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
<div id='exploreMaps' ref='maps'>
|
<div id='exploreMaps' ref={this.mapsDidMount}>
|
||||||
<div style={ style }>
|
<div style={ style }>
|
||||||
{ user ? <MapperCard user={ user } /> : null }
|
{ user ? <MapperCard user={ user } /> : null }
|
||||||
{ currentUser && !user && !(pending && maps.length === 0) ? <div className="map newMap"><a href="/maps/new"><div className="newMapImage"></div><span>Create new map...</span></a></div> : null }
|
{ currentUser && !user && !(pending && maps.length === 0) ? <div className="map newMap"><a href="/maps/new"><div className="newMapImage"></div><span>Create new map...</span></a></div> : null }
|
||||||
{ maps.models.map(map => <MapCard key={ map.id } map={ map } mobile={ mobile } juntoState={ juntoState } currentUser={ currentUser } onStar={ onStar } onRequest={ onRequest } onFollow={ onFollow } />) }
|
{ maps.models.map(map => <MapCard key={ map.id } map={ map } mobile={ mobile } juntoState={ juntoState } currentUser={ currentUser } onStar={ onStar } onRequest={ onRequest } onMapFollow={ onMapFollow } />) }
|
||||||
<div className='clearfloat'></div>
|
<div className='clearfloat'></div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -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
|
export default Maps
|
||||||
|
|
|
@ -2,8 +2,8 @@ import React, { PropTypes, Component } from 'react'
|
||||||
|
|
||||||
class Follow extends Component {
|
class Follow extends Component {
|
||||||
render = () => {
|
render = () => {
|
||||||
const { isFollowing, onFollow } = this.props
|
const { isFollowing, onTopicFollow } = this.props
|
||||||
return <div className='topicFollow' onClick={onFollow}>
|
return <div className='topicFollow' onClick={onTopicFollow}>
|
||||||
{isFollowing ? 'Unfollow' : 'Follow'}
|
{isFollowing ? 'Unfollow' : 'Follow'}
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
|
@ -11,7 +11,7 @@ class Follow extends Component {
|
||||||
|
|
||||||
Follow.propTypes = {
|
Follow.propTypes = {
|
||||||
isFollowing: PropTypes.bool,
|
isFollowing: PropTypes.bool,
|
||||||
onFollow: PropTypes.func
|
onTopicFollow: PropTypes.func
|
||||||
}
|
}
|
||||||
|
|
||||||
export default Follow
|
export default Follow
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
/* global $ */
|
/* global $ */
|
||||||
|
|
||||||
import React, { PropTypes, Component } from 'react'
|
import React, { PropTypes, Component } from 'react'
|
||||||
|
import { Link } from 'react-router'
|
||||||
|
|
||||||
import MetacodeSelect from '../MetacodeSelect'
|
import MetacodeSelect from '../MetacodeSelect'
|
||||||
import Permission from './Permission'
|
import Permission from './Permission'
|
||||||
|
@ -54,13 +55,13 @@ class Links extends Component {
|
||||||
let output = []
|
let output = []
|
||||||
|
|
||||||
firstFiveLinks.forEach(obj => {
|
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 (extraLinks.length > 0) {
|
||||||
if (this.state.showMoreMaps) {
|
if (this.state.showMoreMaps) {
|
||||||
extraLinks.forEach(obj => {
|
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...`
|
const text = this.state.showMoreMaps ? 'See less...' : `See ${extraLinks.length} more...`
|
||||||
|
|
|
@ -7,12 +7,18 @@ import Attachments from './Attachments'
|
||||||
import Follow from './Follow'
|
import Follow from './Follow'
|
||||||
import Util from '../../Metamaps/Util'
|
import Util from '../../Metamaps/Util'
|
||||||
|
|
||||||
|
|
||||||
class ReactTopicCard extends Component {
|
class ReactTopicCard extends Component {
|
||||||
render = () => {
|
render = () => {
|
||||||
const { topic, ActiveMapper, onFollow } = this.props
|
const { currentUser, onTopicFollow, updateTopic } = this.props
|
||||||
const authorizedToEdit = topic.authorizeToEdit(ActiveMapper)
|
const topic = this.props.openTopic
|
||||||
const isFollowing = topic.isFollowedBy(ActiveMapper)
|
|
||||||
|
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') !== ''
|
const hasAttachment = topic.get('link') && topic.get('link') !== ''
|
||||||
|
|
||||||
let classname = 'permission'
|
let classname = 'permission'
|
||||||
|
@ -21,42 +27,44 @@ class ReactTopicCard extends Component {
|
||||||
} else {
|
} else {
|
||||||
classname += ' cannotEdit'
|
classname += ' cannotEdit'
|
||||||
}
|
}
|
||||||
if (topic.authorizePermissionChange(ActiveMapper)) classname += ' yourTopic'
|
if (topic.authorizePermissionChange(currentUser)) classname += ' yourTopic'
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
<div className="showcard mapElement mapElementHidden" id="showcard">
|
||||||
<div className={classname}>
|
<div className={classname}>
|
||||||
<div className={`CardOnGraph ${hasAttachment ? 'hasAttachment' : ''}`} id={`topic_${topic.id}`}>
|
<div className={`CardOnGraph ${hasAttachment ? 'hasAttachment' : ''}`} id={`topic_${topic.id}`}>
|
||||||
<Title name={topic.get('name')}
|
<Title name={topic.get('name')}
|
||||||
authorizedToEdit={authorizedToEdit}
|
authorizedToEdit={authorizedToEdit}
|
||||||
onChange={this.props.updateTopic}
|
onChange={wrappedUpdateTopic}
|
||||||
/>
|
/>
|
||||||
<Links topic={topic}
|
<Links topic={topic}
|
||||||
ActiveMapper={this.props.ActiveMapper}
|
ActiveMapper={this.props.currentUser}
|
||||||
updateTopic={this.props.updateTopic}
|
updateTopic={wrappedUpdateTopic}
|
||||||
metacodeSets={this.props.metacodeSets}
|
metacodeSets={this.props.metacodeSets}
|
||||||
redrawCanvas={this.props.redrawCanvas}
|
redrawCanvas={this.props.redrawCanvas}
|
||||||
/>
|
/>
|
||||||
<Desc desc={topic.get('desc')}
|
<Desc desc={topic.get('desc')}
|
||||||
authorizedToEdit={authorizedToEdit}
|
authorizedToEdit={authorizedToEdit}
|
||||||
onChange={this.props.updateTopic}
|
onChange={wrappedUpdateTopic}
|
||||||
/>
|
/>
|
||||||
<Attachments topic={topic}
|
<Attachments topic={topic}
|
||||||
authorizedToEdit={authorizedToEdit}
|
authorizedToEdit={authorizedToEdit}
|
||||||
updateTopic={this.props.updateTopic}
|
updateTopic={wrappedUpdateTopic}
|
||||||
/>
|
/>
|
||||||
{Util.isTester(ActiveMapper) && <Follow isFollowing={isFollowing} onFollow={onFollow} />}
|
{Util.isTester(currentUser) && <Follow isFollowing={isFollowing} onTopicFollow={wrappedTopicFollow} />}
|
||||||
<div className="clearfloat"></div>
|
<div className="clearfloat"></div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
ReactTopicCard.propTypes = {
|
ReactTopicCard.propTypes = {
|
||||||
topic: PropTypes.object,
|
openTopic: PropTypes.object,
|
||||||
ActiveMapper: PropTypes.object,
|
currentUser: PropTypes.object,
|
||||||
updateTopic: PropTypes.func,
|
updateTopic: PropTypes.func,
|
||||||
onFollow: PropTypes.func,
|
onTopicFollow: PropTypes.func,
|
||||||
metacodeSets: PropTypes.arrayOf(PropTypes.shape({
|
metacodeSets: PropTypes.arrayOf(PropTypes.shape({
|
||||||
name: PropTypes.string,
|
name: PropTypes.string,
|
||||||
metacodes: PropTypes.arrayOf(PropTypes.shape({
|
metacodes: PropTypes.arrayOf(PropTypes.shape({
|
||||||
|
|
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-dom": "15.4.2",
|
||||||
"react-dropzone": "3.9.1",
|
"react-dropzone": "3.9.1",
|
||||||
"react-onclickoutside": "5.9.0",
|
"react-onclickoutside": "5.9.0",
|
||||||
|
"react-router": "3.0.2",
|
||||||
"redux": "3.6.0",
|
"redux": "3.6.0",
|
||||||
"riek": "1.0.7",
|
"riek": "1.0.7",
|
||||||
"simplewebrtc": "2.2.2",
|
"simplewebrtc": "2.2.2",
|
||||||
|
|
Loading…
Add table
Reference in a new issue