commit
f76f66ed19
86 changed files with 1510 additions and 928 deletions
2
Gemfile
2
Gemfile
|
@ -24,7 +24,7 @@ gem 'pundit'
|
||||||
gem 'pundit_extra'
|
gem 'pundit_extra'
|
||||||
gem 'rack-attack'
|
gem 'rack-attack'
|
||||||
gem 'rack-cors'
|
gem 'rack-cors'
|
||||||
gem 'redis'
|
gem 'redis', '~> 3.3.3'
|
||||||
gem 'slack-notifier'
|
gem 'slack-notifier'
|
||||||
gem 'snorlax'
|
gem 'snorlax'
|
||||||
gem 'sucker_punch'
|
gem 'sucker_punch'
|
||||||
|
|
|
@ -220,7 +220,7 @@ GEM
|
||||||
rb-fsevent (0.10.2)
|
rb-fsevent (0.10.2)
|
||||||
rb-inotify (0.9.10)
|
rb-inotify (0.9.10)
|
||||||
ffi (>= 0.5.0, < 2)
|
ffi (>= 0.5.0, < 2)
|
||||||
redis (4.0.0)
|
redis (3.3.3)
|
||||||
responders (2.4.0)
|
responders (2.4.0)
|
||||||
actionpack (>= 4.2.0, < 5.3)
|
actionpack (>= 4.2.0, < 5.3)
|
||||||
railties (>= 4.2.0, < 5.3)
|
railties (>= 4.2.0, < 5.3)
|
||||||
|
@ -331,7 +331,7 @@ DEPENDENCIES
|
||||||
rack-attack
|
rack-attack
|
||||||
rack-cors
|
rack-cors
|
||||||
rails (~> 5.0.0)
|
rails (~> 5.0.0)
|
||||||
redis
|
redis (~> 3.3.3)
|
||||||
rspec-rails
|
rspec-rails
|
||||||
rubocop
|
rubocop
|
||||||
sass-rails
|
sass-rails
|
||||||
|
@ -348,4 +348,4 @@ RUBY VERSION
|
||||||
ruby 2.3.0p0
|
ruby 2.3.0p0
|
||||||
|
|
||||||
BUNDLED WITH
|
BUNDLED WITH
|
||||||
1.14.6
|
1.15.4
|
||||||
|
|
|
@ -56,16 +56,15 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
li.toggledOff {
|
li.toggledOff {
|
||||||
opacity: 0.4;
|
opacity: 0.6;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.blackBox {
|
.centerContent {
|
||||||
width: 760px;
|
width: 760px;
|
||||||
margin: 0 auto;
|
margin: 0 auto;
|
||||||
padding: 80px 0 60px 20px;
|
padding: 80px 0 60px 20px;
|
||||||
background: rgba(0, 0, 0, 0.4);
|
background: rgba(125, 125, 125, 0.4);
|
||||||
color: white;
|
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
position: relative;
|
position: relative;
|
||||||
|
|
||||||
|
@ -85,10 +84,10 @@
|
||||||
display: table-row;
|
display: table-row;
|
||||||
}
|
}
|
||||||
tr:nth-child(odd) {
|
tr:nth-child(odd) {
|
||||||
background: rgba(0, 0, 0, 0.2);
|
background: rgba(125, 125, 125, 0.2);
|
||||||
}
|
}
|
||||||
tr:nth-child(even) {
|
tr:nth-child(even) {
|
||||||
background: rgba(0, 0, 0, 0.3);
|
background: rgba(125, 125, 125, 0.3);
|
||||||
}
|
}
|
||||||
th,
|
th,
|
||||||
td {
|
td {
|
||||||
|
|
|
@ -826,6 +826,7 @@ label {
|
||||||
font-size: 14px;
|
font-size: 14px;
|
||||||
line-height: 14px;
|
line-height: 14px;
|
||||||
color: #757575;
|
color: #757575;
|
||||||
|
cursor: pointer;
|
||||||
}
|
}
|
||||||
.accountListItem:hover {
|
.accountListItem:hover {
|
||||||
color: #424242;
|
color: #424242;
|
||||||
|
@ -2929,136 +2930,6 @@ and it won't be important on password protected instances */
|
||||||
color: #424242;
|
color: #424242;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Admin Pages */
|
|
||||||
|
|
||||||
.blackBox {
|
|
||||||
width: 760px;
|
|
||||||
margin: 0 auto;
|
|
||||||
padding: 20px 0 60px 20px;
|
|
||||||
background: rgba(0, 0, 0, 0.4);
|
|
||||||
color: white;
|
|
||||||
overflow: hidden;
|
|
||||||
position: relative;
|
|
||||||
}
|
|
||||||
.blackBox .metacodeSetsDescription {
|
|
||||||
width: 314px;
|
|
||||||
}
|
|
||||||
.blackBox td.metacodeSetDesc {
|
|
||||||
width: 314px;
|
|
||||||
word-wrap: break-word;
|
|
||||||
}
|
|
||||||
.blackBox .metacodeSetImage {
|
|
||||||
width: 36px;
|
|
||||||
height: 36px;
|
|
||||||
float: left;
|
|
||||||
}
|
|
||||||
.blackBox tr {
|
|
||||||
display: table-row;
|
|
||||||
}
|
|
||||||
.blackBox tr:nth-child(odd) {
|
|
||||||
background: rgba(0, 0, 0, 0.2);
|
|
||||||
}
|
|
||||||
.blackBox tr:nth-child(even) {
|
|
||||||
background: rgba(0, 0, 0, 0.3);
|
|
||||||
}
|
|
||||||
.blackBox th,
|
|
||||||
.blackBox td {
|
|
||||||
padding: 10px;
|
|
||||||
}
|
|
||||||
.blackBox td.iconURL {
|
|
||||||
max-width: 415px;
|
|
||||||
word-wrap: break-word;
|
|
||||||
}
|
|
||||||
.blackBox td.iconColor {
|
|
||||||
|
|
||||||
}
|
|
||||||
.blackBox .field {
|
|
||||||
margin: 15px 0 5px;
|
|
||||||
}
|
|
||||||
.blackBox label {
|
|
||||||
float: left;
|
|
||||||
width: 100px;
|
|
||||||
margin-right: 15px;
|
|
||||||
}
|
|
||||||
.blackBox input[type="text"] {
|
|
||||||
width: 336px;
|
|
||||||
height: 32px;
|
|
||||||
font-size: 15px;
|
|
||||||
direction: ltr;
|
|
||||||
-webkit-appearance: none;
|
|
||||||
appearance: none;
|
|
||||||
display: inline-block;
|
|
||||||
margin: 0;
|
|
||||||
padding: 0 8px;
|
|
||||||
background: #fff;
|
|
||||||
border: 1px solid #d9d9d9;
|
|
||||||
border-top: 1px solid #c0c0c0;
|
|
||||||
-webkit-box-sizing: border-box;
|
|
||||||
-moz-box-sizing: border-box;
|
|
||||||
box-sizing: border-box;
|
|
||||||
-webkit-border-radius: 1px;
|
|
||||||
-moz-border-radius: 1px;
|
|
||||||
border-radius: 1px;
|
|
||||||
font: -webkit-small-control;
|
|
||||||
color: initial;
|
|
||||||
letter-spacing: normal;
|
|
||||||
word-spacing: normal;
|
|
||||||
text-transform: none;
|
|
||||||
text-indent: 0px;
|
|
||||||
text-shadow: none;
|
|
||||||
display: inline-block;
|
|
||||||
text-align: start;
|
|
||||||
font-family: arial;
|
|
||||||
}
|
|
||||||
.blackBox input[type="text"]:hover,
|
|
||||||
.blackBox textarea:hover {
|
|
||||||
border: 1px solid #b9b9b9;
|
|
||||||
border-top: 1px solid #a0a0a0;
|
|
||||||
-webkit-box-shadow: inset 0 1px 2px rgba(0, 0, 0, 0.1);
|
|
||||||
-moz-box-shadow: inset 0 1px 2px rgba(0, 0, 0, 0.1);
|
|
||||||
box-shadow: inset 0 1px 2px rgba(0, 0, 0, 0.1);
|
|
||||||
}
|
|
||||||
.blackBox textarea {
|
|
||||||
padding: 8px;
|
|
||||||
border: 1px solid #d9d9d9;
|
|
||||||
border-top: 1px solid #c0c0c0;
|
|
||||||
resize: none;
|
|
||||||
font: -webkit-small-control;
|
|
||||||
letter-spacing: normal;
|
|
||||||
word-spacing: normal;
|
|
||||||
text-transform: none;
|
|
||||||
text-indent: 0px;
|
|
||||||
text-shadow: none;
|
|
||||||
text-align: start;
|
|
||||||
font-family: arial;
|
|
||||||
font-size: 15px;
|
|
||||||
line-height: 17px;
|
|
||||||
width: 318px;
|
|
||||||
}
|
|
||||||
.blackBox .allMetacodes {
|
|
||||||
padding: 5px 0;
|
|
||||||
}
|
|
||||||
.blackBox a.button {
|
|
||||||
margin-right: 20px;
|
|
||||||
line-height: 40px;
|
|
||||||
}
|
|
||||||
.blackBox a.button,
|
|
||||||
.blackBox input.add {
|
|
||||||
float: left;
|
|
||||||
margin-top: 5px;
|
|
||||||
height: 40px;
|
|
||||||
font-size: 17px;
|
|
||||||
width: auto;
|
|
||||||
padding: 0 30px;
|
|
||||||
cursor: pointer;
|
|
||||||
font-weight: normal;
|
|
||||||
}
|
|
||||||
.blackBox a.button:hover,
|
|
||||||
.blackBox input.add:hover {
|
|
||||||
-webkit-box-shadow: none;
|
|
||||||
box-shadow: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* request */
|
/* request */
|
||||||
|
|
||||||
.requestInvite {
|
.requestInvite {
|
||||||
|
|
|
@ -127,6 +127,10 @@ $mid-gray-opacity: rgba(66, 66, 66, 0.6);
|
||||||
a.mdSupport {
|
a.mdSupport {
|
||||||
color: #4fb5c0;
|
color: #4fb5c0;
|
||||||
font-size: 11px;
|
font-size: 11px;
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
.riek-editing + .mdSupport {
|
||||||
|
display: block;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.CardOnGraph.hasAttachment .scroll {
|
.CardOnGraph.hasAttachment .scroll {
|
||||||
|
@ -139,14 +143,12 @@ $mid-gray-opacity: rgba(66, 66, 66, 0.6);
|
||||||
font-family: helvetica, sans-serif;
|
font-family: helvetica, sans-serif;
|
||||||
color: #424242;
|
color: #424242;
|
||||||
padding: 0;
|
padding: 0;
|
||||||
width: 100%;
|
width: 258px;
|
||||||
margin: 0;
|
margin: 0;
|
||||||
border: 0;
|
border: 0;
|
||||||
outline: none;
|
outline: none;
|
||||||
font-size: 12px;
|
|
||||||
line-height: 15px;
|
|
||||||
background: none;
|
background: none;
|
||||||
resize: none;
|
overflow-y: scroll;
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|
@ -180,11 +182,9 @@ $mid-gray-opacity: rgba(66, 66, 66, 0.6);
|
||||||
|
|
||||||
.CardOnGraph .riek_desc {
|
.CardOnGraph .riek_desc {
|
||||||
display:block;
|
display:block;
|
||||||
margin-top:2px;
|
padding-right: 26px;
|
||||||
padding-right: 18px;
|
|
||||||
margin-right: 8px;
|
|
||||||
}
|
}
|
||||||
.canEdit .CardOnGraph .riek_desc:hover {
|
.canEdit .CardOnGraph .riek_desc:not(.riek-editing):hover {
|
||||||
background-image: url(<%= asset_data_uri('edit.png') %>);
|
background-image: url(<%= asset_data_uri('edit.png') %>);
|
||||||
background-position: top right;
|
background-position: top right;
|
||||||
background-repeat: no-repeat;
|
background-repeat: no-repeat;
|
||||||
|
|
|
@ -668,19 +668,19 @@
|
||||||
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);
|
||||||
}
|
}
|
||||||
|
|
||||||
#exploreMapsHeader {
|
#navBar {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
.exploreMapsBar {
|
.navBarContainer {
|
||||||
z-index:2;
|
z-index:2;
|
||||||
background-color:#FAFAFA;
|
background-color:#FAFAFA;
|
||||||
height: 42px;
|
height: 42px;
|
||||||
padding-top: 52px;
|
padding-top: 52px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.exploreMapsMenu {
|
.navBarMenu {
|
||||||
display: block;
|
display: block;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height:42px;
|
height:42px;
|
||||||
|
@ -689,30 +689,29 @@
|
||||||
text-align: center;
|
text-align: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
.exploreMapsCenter {
|
.navBarCenter {
|
||||||
display: block;
|
display: block;
|
||||||
}
|
}
|
||||||
|
|
||||||
.exploreMapsButton {
|
.navBarButton {
|
||||||
color: #757575;
|
color: #757575;
|
||||||
cursor: default;
|
cursor: default;
|
||||||
font-weight: normal;
|
font-weight: normal;
|
||||||
font-family: 'din-medium';
|
font-family: 'din-medium';
|
||||||
font-size: 14px;
|
font-size: 14px;
|
||||||
height: 14px;
|
padding: 0 8px;
|
||||||
padding: 14px 8px 12px 40px;
|
border-bottom: 2px solid rgba(0,0,0,0);
|
||||||
border-bottom: 2px solid rgba(0,0,0,0);
|
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
position:relative;
|
position:relative;
|
||||||
}
|
}
|
||||||
.exploreMapsButton:hover, .exploreMapsButton.active {
|
.navBarButton:hover, .navBarButton.active {
|
||||||
text-decoration: none;
|
text-decoration: none;
|
||||||
color: #424242;
|
color: #424242;
|
||||||
border-bottom: 2px solid #00BCD4;
|
border-bottom: 2px solid #00BCD4;
|
||||||
}
|
}
|
||||||
|
|
||||||
.exploreMapsButton.mapperButton {
|
.navBarButton.mapperButton {
|
||||||
height: 40px;
|
height: 40px;
|
||||||
padding: 0;
|
padding: 0;
|
||||||
}
|
}
|
||||||
|
@ -729,62 +728,69 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
.exploreMapsButton .exploreMapsIcon {
|
.navBarButton .navBarIcon {
|
||||||
background-repeat: no-repeat;
|
background-repeat: no-repeat;
|
||||||
width:32px;
|
width:32px;
|
||||||
height:32px;
|
height:32px;
|
||||||
position:absolute;
|
margin-top:5px;
|
||||||
top:5px;
|
margin-left:5px;
|
||||||
left:5px;
|
margin-right: 5px;
|
||||||
|
display: inline-block;
|
||||||
|
vertical-align: top;
|
||||||
}
|
}
|
||||||
|
|
||||||
.exploreMapsCenter .authedApps .exploreMapsIcon {
|
.navBarLinkText {
|
||||||
|
padding: 11px 0 12px 0;
|
||||||
|
display: inline-block;
|
||||||
|
}
|
||||||
|
|
||||||
|
.navBarCenter .authedApps .navBarIcon {
|
||||||
background-image: url(<%= asset_path('user_sprite.png') %>);
|
background-image: url(<%= asset_path('user_sprite.png') %>);
|
||||||
background-position: 0 -32px;
|
background-position: 0 -32px;
|
||||||
}
|
}
|
||||||
.exploreMapsCenter .myMaps .exploreMapsIcon {
|
.navBarCenter .myMaps .navBarIcon {
|
||||||
background-image: url(<%= asset_path 'exploremaps_sprite.png' %>);
|
background-image: url(<%= asset_path 'exploremaps_sprite.png' %>);
|
||||||
background-position: -32px 0;
|
background-position: -32px 0;
|
||||||
}
|
}
|
||||||
.exploreMapsCenter .sharedMaps .exploreMapsIcon {
|
.navBarCenter .sharedMaps .navBarIcon {
|
||||||
background-image: url(<%= asset_path 'exploremaps_sprite.png' %>);
|
background-image: url(<%= asset_path 'exploremaps_sprite.png' %>);
|
||||||
background-position: -128px 0;
|
background-position: -128px 0;
|
||||||
}
|
}
|
||||||
.exploreMapsCenter .activeMaps .exploreMapsIcon {
|
.navBarCenter .activeMaps .navBarIcon {
|
||||||
background-image: url(<%= asset_path 'exploremaps_sprite.png' %>);
|
background-image: url(<%= asset_path 'exploremaps_sprite.png' %>);
|
||||||
background-position: 0 0;
|
background-position: 0 0;
|
||||||
}
|
}
|
||||||
.exploreMapsCenter .featuredMaps .exploreMapsIcon {
|
.navBarCenter .featuredMaps .navBarIcon {
|
||||||
background-image: url(<%= asset_path 'exploremaps_sprite.png' %>);
|
background-image: url(<%= asset_path 'exploremaps_sprite.png' %>);
|
||||||
background-position: -96px 0;
|
background-position: -96px 0;
|
||||||
}
|
}
|
||||||
.exploreMapsCenter .starredMaps .exploreMapsIcon {
|
.navBarCenter .starredMaps .navBarIcon {
|
||||||
background-image: url(<%= asset_path 'exploremaps_sprite.png' %>);
|
background-image: url(<%= asset_path 'exploremaps_sprite.png' %>);
|
||||||
background-position: -96px 0;
|
background-position: -96px 0;
|
||||||
}
|
}
|
||||||
.exploreMapsCenter .notificationsLink .exploreMapsIcon {
|
.navBarCenter .notificationsLink .navBarIcon {
|
||||||
background-image: url(<%= asset_path 'topright_sprite.png' %>);
|
background-image: url(<%= asset_path 'topright_sprite.png' %>);
|
||||||
background-position: -128px 0;
|
background-position: -128px 0;
|
||||||
}
|
}
|
||||||
.authedApps:hover .exploreMapsIcon, .authedApps.active .exploreMapsIcon {
|
.authedApps:hover .navBarIcon, .authedApps.active .navBarIcon {
|
||||||
background-position-x: -32px;
|
background-position-x: -32px;
|
||||||
}
|
}
|
||||||
.myMaps:hover .exploreMapsIcon, .myMaps.active .exploreMapsIcon {
|
.myMaps:hover .navBarIcon, .myMaps.active .navBarIcon {
|
||||||
background-position: -32px -32px;
|
background-position: -32px -32px;
|
||||||
}
|
}
|
||||||
.activeMaps:hover .exploreMapsIcon, .activeMaps.active .exploreMapsIcon {
|
.activeMaps:hover .navBarIcon, .activeMaps.active .navBarIcon {
|
||||||
background-position: 0 -32px;
|
background-position: 0 -32px;
|
||||||
}
|
}
|
||||||
.featuredMaps:hover .exploreMapsIcon, .featuredMaps.active .exploreMapsIcon {
|
.featuredMaps:hover .navBarIcon, .featuredMaps.active .navBarIcon {
|
||||||
background-position: -96px -32px;
|
background-position: -96px -32px;
|
||||||
}
|
}
|
||||||
.starredMaps:hover .exploreMapsIcon, .starredMaps.active .exploreMapsIcon {
|
.starredMaps:hover .navBarIcon, .starredMaps.active .navBarIcon {
|
||||||
background-position: -96px -32px;
|
background-position: -96px -32px;
|
||||||
}
|
}
|
||||||
.sharedMaps:hover .exploreMapsIcon, .sharedMaps.active .exploreMapsIcon {
|
.sharedMaps:hover .navBarIcon, .sharedMaps.active .navBarIcon {
|
||||||
background-position: -128px -32px;
|
background-position: -128px -32px;
|
||||||
}
|
}
|
||||||
.notificationsLink:hover .exploreMapsIcon, .notificationsLink.active .exploreMapsIcon {
|
.notificationsLink:hover .navBarIcon, .notificationsLink.active .navBarIcon {
|
||||||
background-position-y: -32px;
|
background-position-y: -32px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -32,7 +32,7 @@
|
||||||
|
|
||||||
/* Smartphones (portrait and landscape) ----------- the minimum space that two map cards can fit side by side */
|
/* Smartphones (portrait and landscape) ----------- the minimum space that two map cards can fit side by side */
|
||||||
@media only screen and (max-width : 504px) {
|
@media only screen and (max-width : 504px) {
|
||||||
.upperLeftUI, .upperRightUI, .openCheatsheet, .mapInfoIcon, .feedback-icon, .chat-box, #exploreMapsHeader {
|
.upperLeftUI, .upperRightUI, .openCheatsheet, .mapInfoIcon, .feedback-icon, .chat-box, #navBar {
|
||||||
display: none !important;
|
display: none !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,4 +1,7 @@
|
||||||
|
$notifications-border-color: #DDDDDD;
|
||||||
|
$notifications-hover-color: #F6F6F6;
|
||||||
$unread_notifications_dot_size: 8px;
|
$unread_notifications_dot_size: 8px;
|
||||||
|
|
||||||
.unread-notifications-dot {
|
.unread-notifications-dot {
|
||||||
width: $unread_notifications_dot_size;
|
width: $unread_notifications_dot_size;
|
||||||
height: $unread_notifications_dot_size;
|
height: $unread_notifications_dot_size;
|
||||||
|
@ -13,13 +16,72 @@ $unread_notifications_dot_size: 8px;
|
||||||
.notificationsIcon {
|
.notificationsIcon {
|
||||||
position: relative;
|
position: relative;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.notificationsBox {
|
||||||
|
position: absolute;
|
||||||
|
background: #FFFFFF;
|
||||||
|
border-radius: 2px;
|
||||||
|
width: 350px;
|
||||||
|
right: 0;
|
||||||
|
top: 50px;
|
||||||
|
box-shadow: 0 3px 6px rgba(0,0,0,0.16);
|
||||||
|
border: 1px solid $notifications-border-color;
|
||||||
|
|
||||||
|
.notificationsBoxTriangle {
|
||||||
|
min-width: 0 !important;
|
||||||
|
display: block;
|
||||||
|
position: absolute;
|
||||||
|
right: 48px;
|
||||||
|
width: 20px !important;
|
||||||
|
height: 20px !important;
|
||||||
|
margin-left: -10px;
|
||||||
|
top: -11px;
|
||||||
|
border-left: 1px solid $notifications-border-color;
|
||||||
|
border-top: 1px solid $notifications-border-color;
|
||||||
|
border-bottom: 0 !important;
|
||||||
|
border-right: 0 !important;
|
||||||
|
background-color: #fff;
|
||||||
|
transform: rotate(45deg);
|
||||||
|
-webkit-transform: rotate(45deg);
|
||||||
|
-ms-transform: rotate(45deg);
|
||||||
|
}
|
||||||
|
|
||||||
|
ul.notifications {
|
||||||
|
max-height: 500px;
|
||||||
|
overflow-y: auto;
|
||||||
|
|
||||||
|
.notification {
|
||||||
|
font-size: 13px;
|
||||||
|
|
||||||
|
.notification-body {
|
||||||
|
border-bottom: 1px solid $notifications-border-color;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.notificationsEmpty {
|
||||||
|
font-family: din-regular, helvetica, sans-serif;
|
||||||
|
margin: 50px 10px;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.notificationsBoxSeeAll {
|
||||||
|
display: block;
|
||||||
|
width: 100%;
|
||||||
|
text-align: center;
|
||||||
|
padding: 6px 0;
|
||||||
|
font-family: din-regular, helvetica, sans-serif;
|
||||||
|
border-top: 1px solid rgba(0, 0, 0, 0.1);
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
color: #333;
|
||||||
|
background: $notifications-hover-color;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.controller-notifications {
|
.controller-notifications {
|
||||||
ul.notifications {
|
|
||||||
list-style: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
.notificationPage,
|
.notificationPage,
|
||||||
.notificationsPage {
|
.notificationsPage {
|
||||||
font-family: 'din-regular', Sans-Serif;
|
font-family: 'din-regular', Sans-Serif;
|
||||||
|
@ -47,89 +109,9 @@ $unread_notifications_dot_size: 8px;
|
||||||
.emptyInbox {
|
.emptyInbox {
|
||||||
padding-top: 15px;
|
padding-top: 15px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.notification {
|
|
||||||
padding: 10px;
|
|
||||||
position: relative;
|
|
||||||
|
|
||||||
&:hover {
|
|
||||||
background: #F6F6F6;
|
|
||||||
|
|
||||||
.notification-read-unread {
|
|
||||||
display:block;
|
|
||||||
}
|
|
||||||
|
|
||||||
.notification-date {
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
& > a {
|
|
||||||
float: left;
|
|
||||||
width: 85%;
|
|
||||||
box-sizing: border-box;
|
|
||||||
padding-right: 10px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.notification-actor {
|
|
||||||
float: left;
|
|
||||||
|
|
||||||
img {
|
|
||||||
width: 32px;
|
|
||||||
height: 32px;
|
|
||||||
border-radius: 16px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.notification-body {
|
|
||||||
margin-left: 50px;
|
|
||||||
line-height: 20px;
|
|
||||||
|
|
||||||
.in-bold {
|
|
||||||
font-family: 'din-medium', Sans-Serif;
|
|
||||||
}
|
|
||||||
|
|
||||||
.action {
|
|
||||||
background: #4fb5c0;
|
|
||||||
color: #FFF;
|
|
||||||
padding: 2px 6px;
|
|
||||||
border-radius: 3px;
|
|
||||||
display: inline-block;
|
|
||||||
margin: 5px 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.notification-date {
|
|
||||||
position: absolute;
|
|
||||||
top: 50%;
|
|
||||||
right: 10px;
|
|
||||||
color: #607d8b;
|
|
||||||
font-size: 13px;
|
|
||||||
line-height: 13px;
|
|
||||||
margin-top: -6px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.notification-read-unread {
|
|
||||||
display: none;
|
|
||||||
float: left;
|
|
||||||
width: 15%;
|
|
||||||
|
|
||||||
a {
|
|
||||||
position: absolute;
|
|
||||||
top: 50%;
|
|
||||||
margin-top: -10px;
|
|
||||||
text-align: center;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
&.unread {
|
|
||||||
background: #EEE;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
.notificationPage {
|
.notificationPage {
|
||||||
.thirty-two-avatar {
|
.thirty-two-avatar {
|
||||||
|
@ -139,14 +121,14 @@ $unread_notifications_dot_size: 8px;
|
||||||
border-radius: 16px;
|
border-radius: 16px;
|
||||||
vertical-align: middle;
|
vertical-align: middle;
|
||||||
}
|
}
|
||||||
|
|
||||||
.button {
|
.button {
|
||||||
line-height: 32px;
|
line-height: 32px;
|
||||||
|
|
||||||
img {
|
img {
|
||||||
margin-top: 8px;
|
margin-top: 8px;
|
||||||
}
|
}
|
||||||
|
|
||||||
&.decline {
|
&.decline {
|
||||||
background: #DB5D5D;
|
background: #DB5D5D;
|
||||||
&:hover {
|
&:hover {
|
||||||
|
@ -154,7 +136,7 @@ $unread_notifications_dot_size: 8px;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.notification-body {
|
.notification-body {
|
||||||
p, div {
|
p, div {
|
||||||
margin: 1em auto;
|
margin: 1em auto;
|
||||||
|
@ -163,3 +145,93 @@ $unread_notifications_dot_size: 8px;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ul.notifications {
|
||||||
|
list-style: none;
|
||||||
|
|
||||||
|
li:nth-last-child(2) {
|
||||||
|
.notification-body {
|
||||||
|
border-bottom: none !important;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.notification {
|
||||||
|
padding: 10px 10px 0 10px;
|
||||||
|
position: relative;
|
||||||
|
font-family: 'din-regular', Sans-Serif;
|
||||||
|
|
||||||
|
&.unread {
|
||||||
|
background: #EEE;
|
||||||
|
}
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
background: $notifications-hover-color;
|
||||||
|
|
||||||
|
.notification-read-unread {
|
||||||
|
display:block;
|
||||||
|
}
|
||||||
|
|
||||||
|
.notification-date {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
& > a {
|
||||||
|
float: left;
|
||||||
|
width: 85%;
|
||||||
|
box-sizing: border-box;
|
||||||
|
padding-right: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.notification-actor {
|
||||||
|
float: left;
|
||||||
|
|
||||||
|
img {
|
||||||
|
width: 32px;
|
||||||
|
height: 32px;
|
||||||
|
border-radius: 16px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.notification-body {
|
||||||
|
margin-left: 50px;
|
||||||
|
line-height: 20px;
|
||||||
|
padding-bottom: 10px;
|
||||||
|
|
||||||
|
.in-bold {
|
||||||
|
font-family: 'din-medium', Sans-Serif;
|
||||||
|
}
|
||||||
|
|
||||||
|
.action {
|
||||||
|
background: #4fb5c0;
|
||||||
|
color: #FFF;
|
||||||
|
padding: 2px 6px;
|
||||||
|
border-radius: 3px;
|
||||||
|
display: inline-block;
|
||||||
|
margin: 5px 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.notification-date {
|
||||||
|
position: absolute;
|
||||||
|
top: 50%;
|
||||||
|
right: 10px;
|
||||||
|
color: #607d8b;
|
||||||
|
margin-top: -6px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.notification-read-unread {
|
||||||
|
display: none;
|
||||||
|
float: left;
|
||||||
|
width: 15%;
|
||||||
|
|
||||||
|
a, div {
|
||||||
|
position: absolute;
|
||||||
|
top: 50%;
|
||||||
|
margin-top: -10px;
|
||||||
|
text-align: center;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -6,13 +6,17 @@ class NotificationsController < ApplicationController
|
||||||
|
|
||||||
def index
|
def index
|
||||||
@notifications = current_user.mailbox.notifications.page(params[:page]).per(25)
|
@notifications = current_user.mailbox.notifications.page(params[:page]).per(25)
|
||||||
|
|
||||||
respond_to do |format|
|
respond_to do |format|
|
||||||
format.html
|
format.html
|
||||||
format.json do
|
format.json do
|
||||||
render json: @notifications.map do |notification|
|
notifications = @notifications.map do |notification|
|
||||||
receipt = @receipts.find_by(notification_id: notification.id)
|
receipt = @receipts.find_by(notification_id: notification.id)
|
||||||
notification.as_json.merge(is_read: receipt.is_read)
|
NotificationDecorator.decorate(notification, receipt)
|
||||||
|
end
|
||||||
|
if !notifications.empty?
|
||||||
|
render json: notifications
|
||||||
|
else
|
||||||
|
render json: [].to_json
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
@ -34,9 +38,7 @@ class NotificationsController < ApplicationController
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
format.json do
|
format.json do
|
||||||
render json: @notification.as_json.merge(
|
render json: NotificationDecorator.decorate(@notification, @receipt)
|
||||||
is_read: @receipt.is_read
|
|
||||||
)
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
@ -46,9 +48,7 @@ class NotificationsController < ApplicationController
|
||||||
respond_to do |format|
|
respond_to do |format|
|
||||||
format.js
|
format.js
|
||||||
format.json do
|
format.json do
|
||||||
render json: @notification.as_json.merge(
|
render json: NotificationDecorator.decorate(@notification, @receipt)
|
||||||
is_read: @receipt.is_read
|
|
||||||
)
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
@ -58,9 +58,7 @@ class NotificationsController < ApplicationController
|
||||||
respond_to do |format|
|
respond_to do |format|
|
||||||
format.js
|
format.js
|
||||||
format.json do
|
format.json do
|
||||||
render json: @notification.as_json.merge(
|
render json: NotificationDecorator.decorate(@notification, @receipt)
|
||||||
is_read: @receipt.is_read
|
|
||||||
)
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
50
app/decorators/notification_decorator.rb
Normal file
50
app/decorators/notification_decorator.rb
Normal file
|
@ -0,0 +1,50 @@
|
||||||
|
# frozen_string_literal: true
|
||||||
|
class NotificationDecorator
|
||||||
|
class << self
|
||||||
|
def decorate(notification, receipt)
|
||||||
|
result = {
|
||||||
|
id: notification.id,
|
||||||
|
type: notification.notification_code,
|
||||||
|
subject: notification.subject,
|
||||||
|
is_read: receipt.is_read,
|
||||||
|
created_at: notification.created_at,
|
||||||
|
actor: notification.sender,
|
||||||
|
data: {
|
||||||
|
object: notification.notified_object
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
case notification.notification_code
|
||||||
|
when MAP_ACCESS_APPROVED, MAP_ACCESS_REQUEST, MAP_INVITE_TO_EDIT
|
||||||
|
map = notification.notified_object&.map
|
||||||
|
result[:data][:map] = {
|
||||||
|
id: map&.id,
|
||||||
|
name: map&.name
|
||||||
|
}
|
||||||
|
when TOPIC_ADDED_TO_MAP
|
||||||
|
topic = notification.notified_object&.eventable
|
||||||
|
map = notification.notified_object&.map
|
||||||
|
result[:data][:topic] = {
|
||||||
|
id: topic&.id,
|
||||||
|
name: topic&.name
|
||||||
|
}
|
||||||
|
result[:data][:map] = {
|
||||||
|
id: map&.id,
|
||||||
|
name: map&.name
|
||||||
|
}
|
||||||
|
when TOPIC_CONNECTED_1, TOPIC_CONNECTED_2
|
||||||
|
topic1 = notification.notified_object&.topic1
|
||||||
|
topic2 = notification.notified_object&.topic2
|
||||||
|
result[:data][:topic1] = {
|
||||||
|
id: topic1&.id,
|
||||||
|
name: topic1&.name
|
||||||
|
}
|
||||||
|
result[:data][:topic2] = {
|
||||||
|
id: topic2&.id,
|
||||||
|
name: topic2&.name
|
||||||
|
}
|
||||||
|
end
|
||||||
|
result
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
|
@ -23,7 +23,7 @@ class UserPreference
|
||||||
def initialize_follow_settings
|
def initialize_follow_settings
|
||||||
@follow_topic_on_created = false
|
@follow_topic_on_created = false
|
||||||
@follow_topic_on_contributed = false
|
@follow_topic_on_contributed = false
|
||||||
@follow_map_on_created = false
|
@follow_map_on_created = true
|
||||||
@follow_map_on_contributed = false
|
@follow_map_on_contributed = false
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -1,6 +0,0 @@
|
||||||
<%= link_to 'Metacode Sets', metacode_sets_path, { :class => 'button' }%>
|
|
||||||
<%= link_to 'New Set', new_metacode_set_path, { :class => 'button' }%>
|
|
||||||
<%= link_to 'Metacodes', metacodes_path, { :class => 'button' }%>
|
|
||||||
<%= link_to 'New Metacode', new_metacode_path, { :class => 'button' }%>
|
|
||||||
<div class='clearfloat'></div>
|
|
||||||
<br />
|
|
|
@ -6,10 +6,10 @@
|
||||||
#%>
|
#%>
|
||||||
|
|
||||||
<%= render :partial => 'layouts/head' %>
|
<%= render :partial => 'layouts/head' %>
|
||||||
<body class="<%= authenticated? ? "authenticated" : "unauthenticated" %> controller-<%= controller_name %> action-<%= action_name %>">
|
<body class="<%= current_user ? "authenticated" : "unauthenticated" %> controller-<%= controller_name %> action-<%= action_name %>">
|
||||||
<div class="main" id="react-app"></div>
|
<div class="main" id="react-app"></div>
|
||||||
<%= yield %>
|
<%= yield %>
|
||||||
<% if authenticated? %>
|
<% if current_user %>
|
||||||
<% # 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" %>
|
||||||
<%= render :partial => 'maps/newtopicsecret' %>
|
<%= render :partial => 'maps/newtopicsecret' %>
|
||||||
|
|
|
@ -1,31 +0,0 @@
|
||||||
<%#
|
|
||||||
# @file
|
|
||||||
# Main application file. Holds scaffolding present on every page.
|
|
||||||
# Then a certain non-partial view (no _ preceding filename) will be
|
|
||||||
# displayed within, based on URL
|
|
||||||
#%>
|
|
||||||
|
|
||||||
<%= render :partial => 'layouts/head' %>
|
|
||||||
<body class="<%= current_user ? 'authenticated' : 'unauthenticated' %>">
|
|
||||||
<div class="main" id="react-app"></div>
|
|
||||||
<%= yield %>
|
|
||||||
<div id="exploreMapsHeader">
|
|
||||||
<div class="exploreMapsBar exploreElement">
|
|
||||||
<div class="exploreMapsMenu">
|
|
||||||
<div class="exploreMapsCenter">
|
|
||||||
<% if current_user && current_user.admin %>
|
|
||||||
<a href="<%= oauth_applications_path %>" class="activeMaps exploreMapsButton <%= params[:controller] == 'doorkeeper/applications' ? 'active' : nil %>">
|
|
||||||
<div class="exploreMapsIcon"></div>Registered Apps
|
|
||||||
</a>
|
|
||||||
<% end %>
|
|
||||||
<a href="<%= oauth_authorized_applications_path %>" class="authedApps exploreMapsButton <%= params[:controller] == 'doorkeeper/authorized_applications' ? 'active' : nil %>">
|
|
||||||
<div class="exploreMapsIcon"></div>Authorized Apps
|
|
||||||
</a>
|
|
||||||
<a href="/" class="myMaps exploreMapsButton">
|
|
||||||
<div class="exploreMapsIcon"></div>Maps
|
|
||||||
</a>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<%= render :partial => 'layouts/foot' %>
|
|
|
@ -15,22 +15,22 @@
|
||||||
<%= link_to @map.name, map_url(@map) %>
|
<%= link_to @map.name, map_url(@map) %>
|
||||||
</p>
|
</p>
|
||||||
<div>
|
<div>
|
||||||
<% if @summary_data[:stats][:messages_sent] > 0 %>
|
<% if @summary_data[:stats][:messages_sent] %>
|
||||||
<p style="margin:6px 0;color:#a354cd;"><%= pluralize(@summary_data[:stats][:messages_sent], 'message') %></p>
|
<p style="margin:6px 0;color:#a354cd;"><%= pluralize(@summary_data[:stats][:messages_sent], 'message') %></p>
|
||||||
<% end %>
|
<% end %>
|
||||||
<% if @summary_data[:stats][:topics_added] > 0 %>
|
<% if @summary_data[:stats][:topics_added] %>
|
||||||
<p style="margin:6px 0;color:#4FC059;"><%= pluralize(@summary_data[:stats][:topics_added], 'topic') %> added</p>
|
<p style="margin:6px 0;color:#4FC059;"><%= pluralize(@summary_data[:stats][:topics_added], 'topic') %> added</p>
|
||||||
<% end %>
|
<% end %>
|
||||||
<% if @summary_data[:stats][:synapses_added] > 0 %>
|
<% if @summary_data[:stats][:synapses_added] %>
|
||||||
<p style="margin:6px 0;color:#4FC059;"><%= pluralize(@summary_data[:stats][:synapses_added], 'synapse') %> added</p>
|
<p style="margin:6px 0;color:#4FC059;"><%= pluralize(@summary_data[:stats][:synapses_added], 'synapse') %> added</p>
|
||||||
<% end %>
|
<% end %>
|
||||||
<% if @summary_data[:stats][:topics_moved] > 0 %>
|
<% if @summary_data[:stats][:topics_moved] %>
|
||||||
<p style="margin:6px 0;color:#00BCD4;"><%= pluralize(@summary_data[:stats][:topics_moved], 'topic') %> moved</p>
|
<p style="margin:6px 0;color:#00BCD4;"><%= pluralize(@summary_data[:stats][:topics_moved], 'topic') %> moved</p>
|
||||||
<% end %>
|
<% end %>
|
||||||
<% if @summary_data[:stats][:topics_removed] > 0 %>
|
<% if @summary_data[:stats][:topics_removed] %>
|
||||||
<p style="margin:6px 0;color:#c04f4f;"><%= pluralize(@summary_data[:stats][:topics_removed], 'topic') %> removed</p>
|
<p style="margin:6px 0;color:#c04f4f;"><%= pluralize(@summary_data[:stats][:topics_removed], 'topic') %> removed</p>
|
||||||
<% end %>
|
<% end %>
|
||||||
<% if @summary_data[:stats][:synapses_removed] > 0 %>
|
<% if @summary_data[:stats][:synapses_removed] %>
|
||||||
<p style="margin:6px 0;color:#c04f4f;"><%= pluralize(@summary_data[:stats][:synapses_removed], 'synapse') %> removed</p>
|
<p style="margin:6px 0;color:#c04f4f;"><%= pluralize(@summary_data[:stats][:synapses_removed], 'synapse') %> removed</p>
|
||||||
<% end %>
|
<% end %>
|
||||||
</div>
|
</div>
|
||||||
|
@ -61,7 +61,7 @@
|
||||||
</div>
|
</div>
|
||||||
<% end %>
|
<% end %>
|
||||||
|
|
||||||
|
|
||||||
<% if @summary_data[:topics_removed] || @summary_data[:synapses_removed] %>
|
<% if @summary_data[:topics_removed] || @summary_data[:synapses_removed] %>
|
||||||
<div style="background:rgba(192, 79, 79, 0.2); padding:8px;">
|
<div style="background:rgba(192, 79, 79, 0.2); padding:8px;">
|
||||||
<% if @summary_data[:topics_removed] %>
|
<% if @summary_data[:topics_removed] %>
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
<div id="yield">
|
<div id="yield">
|
||||||
<div class='blackBox'>
|
<div class='centerContent'>
|
||||||
<%= render 'form' %>
|
<%= render 'form' %>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
|
@ -1,37 +1,36 @@
|
||||||
<div id="yield">
|
<div id="yield">
|
||||||
<div class='blackBox'>
|
<div class='centerContent'>
|
||||||
<%= render :partial => 'admin/adminpanel' %>
|
<br />
|
||||||
<br />
|
<table>
|
||||||
<table>
|
<tr>
|
||||||
<tr>
|
<th>Name</th>
|
||||||
<th>Name</th>
|
<th class='metacodeSetsDescription'>Description</th>
|
||||||
<th class='metacodeSetsDescription'>Description</th>
|
<th>Metacodes</th>
|
||||||
<th>Metacodes</th>
|
</tr>
|
||||||
</tr>
|
|
||||||
|
|
||||||
<% @metacode_sets.each do |metacode_set| %>
|
<% @metacode_sets.each do |metacode_set| %>
|
||||||
<tr>
|
<tr>
|
||||||
<td>
|
<td>
|
||||||
<%= metacode_set.name %><br />
|
<%= metacode_set.name %><br />
|
||||||
<%= link_to 'Edit',
|
<%= link_to 'Edit',
|
||||||
edit_metacode_set_path(metacode_set) %>
|
edit_metacode_set_path(metacode_set) %>
|
||||||
<br />
|
<br />
|
||||||
<%= link_to 'Delete',
|
<%= link_to 'Delete',
|
||||||
metacode_set, method: :delete,
|
metacode_set, method: :delete,
|
||||||
data: { confirm: 'Are you sure?' } %>
|
data: { confirm: 'Are you sure?' } %>
|
||||||
</td>
|
</td>
|
||||||
<td class='metacodeSetDesc'><%= metacode_set.desc %></td>
|
<td class='metacodeSetDesc'><%= metacode_set.desc %></td>
|
||||||
<td>
|
<td>
|
||||||
<% metacode_set.metacodes.each_with_index do |metacode, index| %>
|
<% metacode_set.metacodes.each_with_index do |metacode, index| %>
|
||||||
<img class='metacodeSetImage' src='<%= asset_path metacode.icon %>' />
|
<img class='metacodeSetImage' src='<%= asset_path metacode.icon %>' />
|
||||||
<% if (index+1)%4 == 0 %>
|
<% if (index+1)%4 == 0 %>
|
||||||
<div class='clearfloat'></div>
|
<div class='clearfloat'></div>
|
||||||
|
<% end %>
|
||||||
<% end %>
|
<% end %>
|
||||||
<% end %>
|
<div class='clearfloat'></div>
|
||||||
<div class='clearfloat'></div>
|
</td>
|
||||||
</td>
|
</tr>
|
||||||
</tr>
|
<% end %>
|
||||||
<% end %>
|
</table>
|
||||||
</table>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
<div id="yield">
|
<div id="yield">
|
||||||
<div class='blackBox'>
|
<div class='centerContent'>
|
||||||
<%= render 'form' %>
|
<%= render 'form' %>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
@ -11,4 +11,4 @@
|
||||||
<% end %>
|
<% end %>
|
||||||
Metamaps.Admin.allMetacodes.push("<%= m.id %>");
|
Metamaps.Admin.allMetacodes.push("<%= m.id %>");
|
||||||
<% end %>
|
<% end %>
|
||||||
</script>
|
</script>
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
<div id="yield">
|
<div id="yield">
|
||||||
<div class='blackBox'>
|
<div class='centerContent'>
|
||||||
<%= render 'form' %>
|
<%= render 'form' %>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -1,31 +1,30 @@
|
||||||
<div id="yield">
|
<div id="yield">
|
||||||
<div class='blackBox'>
|
<div class='centerContent'>
|
||||||
<%= render :partial => 'admin/adminpanel' %>
|
<br />
|
||||||
<br />
|
<table>
|
||||||
<table>
|
<tr>
|
||||||
<tr>
|
<th>Name</th>
|
||||||
<th>Name</th>
|
<th>Icon</th>
|
||||||
<th>Icon</th>
|
<th>Color</th>
|
||||||
<th>Color</th>
|
<th></th>
|
||||||
<th></th>
|
<th></th>
|
||||||
<th></th>
|
</tr>
|
||||||
</tr>
|
|
||||||
|
|
||||||
<% @metacodes.each do |metacode| %>
|
<% @metacodes.each do |metacode| %>
|
||||||
<tr>
|
<tr>
|
||||||
<td><%= metacode.name %></td>
|
<td><%= metacode.name %></td>
|
||||||
<td class="iconURL"><%= metacode.icon %></td>
|
<td class="iconURL"><%= metacode.icon %></td>
|
||||||
<% if metacode.color %>
|
<% if metacode.color %>
|
||||||
<td class="iconColor" style="background-color: <%= metacode.color %>">
|
<td class="iconColor" style="background-color: <%= metacode.color %>">
|
||||||
<%= metacode.color %>
|
<%= metacode.color %>
|
||||||
</td>
|
</td>
|
||||||
<% else %>
|
<% else %>
|
||||||
<td></td>
|
<td></td>
|
||||||
<% end %>
|
<% end %>
|
||||||
<td><%= image_tag metacode.icon, width: 40 %></td>
|
<td><%= image_tag metacode.icon, width: 40 %></td>
|
||||||
<td><%= link_to 'Edit', edit_metacode_path(metacode) %></td>
|
<td><%= link_to 'Edit', edit_metacode_path(metacode) %></td>
|
||||||
</tr>
|
</tr>
|
||||||
<% end %>
|
<% end %>
|
||||||
</table>
|
</table>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
<div id="yield">
|
<div id="yield">
|
||||||
<div class='blackBox'>
|
<div class='centerContent'>
|
||||||
<%= render 'form' %>
|
<%= render 'form' %>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -1,14 +0,0 @@
|
||||||
<div id="exploreMapsHeader">
|
|
||||||
<div class="exploreMapsBar exploreElement">
|
|
||||||
<div class="exploreMapsMenu">
|
|
||||||
<div class="exploreMapsCenter">
|
|
||||||
<a href="<%= notifications_path %>" class="notificationsLink exploreMapsButton active">
|
|
||||||
<div class="exploreMapsIcon"></div>Notifications
|
|
||||||
</a>
|
|
||||||
<a href="/" class="exploreMapsButton myMaps">
|
|
||||||
<div class="exploreMapsIcon"></div>Maps
|
|
||||||
</a>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
|
@ -8,7 +8,7 @@
|
||||||
</header>
|
</header>
|
||||||
<ul class="notifications">
|
<ul class="notifications">
|
||||||
<% blacklist = [MAP_ACCESS_REQUEST, MAP_ACCESS_APPROVED, MAP_INVITE_TO_EDIT] %>
|
<% blacklist = [MAP_ACCESS_REQUEST, MAP_ACCESS_APPROVED, MAP_INVITE_TO_EDIT] %>
|
||||||
<% notifications = @notifications.to_a.delete_if{|n| blacklist.include?(n.notification_code) && (n.notified_object.nil? || n.notified_object.map.nil?) }%>
|
<% notifications = @notifications.to_a.delete_if{|n| blacklist.include?(n.notification_code) && (n.notified_object.nil? || n.notified_object.map.nil?) }%>
|
||||||
<% notifications.each do |notification| %>
|
<% notifications.each do |notification| %>
|
||||||
<% receipt = @receipts.find_by(notification_id: notification.id) %>
|
<% receipt = @receipts.find_by(notification_id: notification.id) %>
|
||||||
<li class="notification <%= receipt.is_read? ? 'read' : 'unread' %>" id="notification-<%= notification.id %>">
|
<li class="notification <%= receipt.is_read? ? 'read' : 'unread' %>" id="notification-<%= notification.id %>">
|
||||||
|
@ -76,5 +76,3 @@
|
||||||
</div>
|
</div>
|
||||||
<% end %>
|
<% end %>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<%= render partial: 'notifications/header' %>
|
|
||||||
|
|
|
@ -4,4 +4,4 @@ $('#notification-<%= @notification.id %> .notification-read-unread > a')
|
||||||
$('#notification-<%= @notification.id %>')
|
$('#notification-<%= @notification.id %>')
|
||||||
.removeClass('unread')
|
.removeClass('unread')
|
||||||
.addClass('read')
|
.addClass('read')
|
||||||
Metamaps.GlobalUI.NotificationIcon.render(Metamaps.GlobalUI.NotificationIcon.unreadNotificationsCount - 1)
|
Metamaps.GlobalUI.Notifications.decrementUnread(Metamaps.GlobalUI.ReactApp.render)
|
||||||
|
|
|
@ -4,4 +4,4 @@ $('#notification-<%= @notification.id %> .notification-read-unread > a')
|
||||||
$('#notification-<%= @notification.id %>')
|
$('#notification-<%= @notification.id %>')
|
||||||
.removeClass('read')
|
.removeClass('read')
|
||||||
.addClass('unread')
|
.addClass('unread')
|
||||||
Metamaps.GlobalUI.NotificationIcon.render(Metamaps.GlobalUI.NotificationIcon.unreadNotificationsCount + 1)
|
Metamaps.GlobalUI.Notifications.incrementUnread(Metamaps.GlobalUI.ReactApp.render)
|
||||||
|
|
|
@ -9,7 +9,7 @@
|
||||||
<h2 class="notification-title">
|
<h2 class="notification-title">
|
||||||
<% case @notification.notification_code
|
<% case @notification.notification_code
|
||||||
when MAP_ACCESS_REQUEST
|
when MAP_ACCESS_REQUEST
|
||||||
request = @notification.notified_object
|
request = @notification.notified_object
|
||||||
map = request.map %>
|
map = request.map %>
|
||||||
<%= image_tag @notification.sender.image(:thirtytwo), class: 'thirty-two-avatar' %> <span style='font-weight:bold;' class='requesterName'><%= request.user.name %></span> wants to collaborate on map <span style='font-weight:bold;'><%= map.name %></span>
|
<%= image_tag @notification.sender.image(:thirtytwo), class: 'thirty-two-avatar' %> <span style='font-weight:bold;' class='requesterName'><%= request.user.name %></span> wants to collaborate on map <span style='font-weight:bold;'><%= map.name %></span>
|
||||||
<% else %>
|
<% else %>
|
||||||
|
@ -24,7 +24,7 @@
|
||||||
<% if request.approved %>
|
<% if request.approved %>
|
||||||
You already responded to this access request, and allowed access.
|
You already responded to this access request, and allowed access.
|
||||||
<% elsif !request.approved %>
|
<% elsif !request.approved %>
|
||||||
You already responded to this access request, and declined access. If you changed your mind, you can still grant
|
You already responded to this access request, and declined access. If you changed your mind, you can still grant
|
||||||
them access by going to the map and adding them as a collaborator.
|
them access by going to the map and adding them as a collaborator.
|
||||||
<% end %>
|
<% end %>
|
||||||
<% else %>
|
<% else %>
|
||||||
|
@ -50,5 +50,3 @@
|
||||||
<% end %>
|
<% end %>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<%= render partial: 'notifications/header' %>
|
|
||||||
|
|
|
@ -52,7 +52,7 @@
|
||||||
<% end %>
|
<% end %>
|
||||||
<%= settings.label :follow_topic_on_contributed, class: 'firstFieldText' do %>
|
<%= settings.label :follow_topic_on_contributed, class: 'firstFieldText' do %>
|
||||||
<%= settings.check_box :follow_topic_on_contributed, class: 'inline' %>
|
<%= settings.check_box :follow_topic_on_contributed, class: 'inline' %>
|
||||||
Auto-follow topics you edit
|
Auto-follow topics you edit.
|
||||||
<% end %>
|
<% end %>
|
||||||
<%= settings.label :follow_map_on_created, class: 'firstFieldText' do %>
|
<%= settings.label :follow_map_on_created, class: 'firstFieldText' do %>
|
||||||
<%= settings.check_box :follow_map_on_created, class: 'inline' %>
|
<%= settings.check_box :follow_map_on_created, class: 'inline' %>
|
||||||
|
|
|
@ -19,15 +19,15 @@ module Metamaps
|
||||||
end
|
end
|
||||||
|
|
||||||
# Custom directories with classes and modules you want to be autoloadable.
|
# Custom directories with classes and modules you want to be autoloadable.
|
||||||
config.autoload_paths << Rails.root.join('app', 'services')
|
config.autoload_paths << Rails.root.join('app', 'decorators', 'services')
|
||||||
|
|
||||||
# Configure the default encoding used in templates for Ruby 1.9.
|
# Configure the default encoding used in templates for Ruby 1.9.
|
||||||
config.encoding = 'utf-8'
|
config.encoding = 'utf-8'
|
||||||
|
|
||||||
config.to_prepare do
|
config.to_prepare do
|
||||||
Doorkeeper::ApplicationsController.layout 'doorkeeper'
|
Doorkeeper::ApplicationsController.layout 'application'
|
||||||
Doorkeeper::AuthorizationsController.layout 'doorkeeper'
|
Doorkeeper::AuthorizationsController.layout 'application'
|
||||||
Doorkeeper::AuthorizedApplicationsController.layout 'doorkeeper'
|
Doorkeeper::AuthorizedApplicationsController.layout 'application'
|
||||||
Doorkeeper::ApplicationController.helper ApplicationHelper
|
Doorkeeper::ApplicationController.helper ApplicationHelper
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
|
@ -1,9 +1,14 @@
|
||||||
Delayed::Worker.class_eval do
|
# frozen_string_literal: true
|
||||||
|
|
||||||
def handle_failed_job_with_notification(job, error)
|
|
||||||
handle_failed_job_without_notification(job, error)
|
|
||||||
ExceptionNotifier.notify_exception(error)
|
|
||||||
end
|
|
||||||
alias_method_chain :handle_failed_job, :notification
|
|
||||||
|
|
||||||
|
module ExceptionNotifierInDelayedJob
|
||||||
|
def handle_failed_job(job, error)
|
||||||
|
super
|
||||||
|
ExceptionNotfier.notify_exception(error)
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
Delayed::Worker.class_eval do
|
||||||
|
prepend ExceptionNotifierInDelayedJob
|
||||||
|
end
|
||||||
|
|
||||||
|
Delayed::Worker.logger = Logger.new(File.join(Rails.root, 'log', 'delayed_job.log'))
|
||||||
|
|
|
@ -163,9 +163,9 @@ If your system uses systemd for init scripts, ptu the following code into `/etc/
|
||||||
User=metamaps
|
User=metamaps
|
||||||
Group=metamaps
|
Group=metamaps
|
||||||
Environment=HOME=/home/metamaps
|
Environment=HOME=/home/metamaps
|
||||||
Environment=PATH="/usr/local/rvm/gems/ruby-2.3.0@metamaps/bin:/usr/local/rvm/gems/ruby-2.3.0@global/bin:/usr/local/rvm/rubies/ruby-2.3.0/bin:/usr/local/rvm/bin:/usr/local/bin:/usr/bin:/bin"
|
Environment=PATH=/usr/local/rvm/gems/ruby-2.3.0@metamaps/bin:/usr/local/rvm/gems/ruby-2.3.0@global/bin:/usr/local/rvm/rubies/ruby-2.3.0/bin:/usr/local/rvm/bin:/usr/local/bin:/usr/bin:/bin
|
||||||
Environment=GEM_PATH="/usr/local/rvm/gems/ruby-2.3.0@metamaps:/usr/local/rvm/gems/ruby-2.3.0@global"
|
Environment=GEM_PATH=/usr/local/rvm/gems/ruby-2.3.0@metamaps:/usr/local/rvm/gems/ruby-2.3.0@global
|
||||||
Environment=RAILS_ENV="production"
|
Environment=RAILS_ENV=production
|
||||||
|
|
||||||
[Install]
|
[Install]
|
||||||
WantedBy=multi-user.target
|
WantedBy=multi-user.target
|
||||||
|
@ -174,3 +174,13 @@ Then start the service and check the last ten lines of the log file to make sure
|
||||||
|
|
||||||
sudo systemctl start metamaps_delayed_job
|
sudo systemctl start metamaps_delayed_job
|
||||||
# ??? how the heck do you check systemd logs??
|
# ??? how the heck do you check systemd logs??
|
||||||
|
|
||||||
|
##### initial service startup
|
||||||
|
sudo systemctl enable metamaps_delayed_job
|
||||||
|
sudo systemctl start metamaps_delayed_job
|
||||||
|
sudo systemctl status metamaps_delayed_job
|
||||||
|
|
||||||
|
##### after changing
|
||||||
|
sudo systemctl daemon-reload
|
||||||
|
sudo systemctl restart metamaps_delayed_job
|
||||||
|
sudo systemctl status metamaps_delayed_job
|
||||||
|
|
|
@ -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/MapView/ImportDialogBox'
|
import ImportDialogBox from '../../routes/MapView/ImportDialogBox'
|
||||||
|
|
||||||
import PasteInput from '../PasteInput'
|
import PasteInput from '../PasteInput'
|
||||||
import Map from '../Map'
|
import Map from '../Map'
|
||||||
|
|
63
frontend/src/Metamaps/GlobalUI/Notifications.js
Normal file
63
frontend/src/Metamaps/GlobalUI/Notifications.js
Normal file
|
@ -0,0 +1,63 @@
|
||||||
|
/* global $ */
|
||||||
|
import GlobalUI from './index'
|
||||||
|
|
||||||
|
const Notifications = {
|
||||||
|
notifications: null,
|
||||||
|
unreadNotificationsCount: 0,
|
||||||
|
init: serverData => {
|
||||||
|
Notifications.unreadNotificationsCount = serverData.unreadNotificationsCount
|
||||||
|
},
|
||||||
|
fetch: render => {
|
||||||
|
$.ajax({
|
||||||
|
url: '/notifications.json',
|
||||||
|
success: function(data) {
|
||||||
|
Notifications.notifications = data
|
||||||
|
render()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
},
|
||||||
|
incrementUnread: (render) => {
|
||||||
|
Notifications.unreadNotificationsCount++
|
||||||
|
render()
|
||||||
|
},
|
||||||
|
decrementUnread: (render) => {
|
||||||
|
Notifications.unreadNotificationsCount--
|
||||||
|
render()
|
||||||
|
},
|
||||||
|
markAsRead: (render, id) => {
|
||||||
|
const n = Notifications.notifications.find(n => n.id === id)
|
||||||
|
$.ajax({
|
||||||
|
url: `/notifications/${id}/mark_read.json`,
|
||||||
|
method: 'PUT',
|
||||||
|
success: function(r) {
|
||||||
|
if (n) {
|
||||||
|
Notifications.unreadNotificationsCount--
|
||||||
|
n.is_read = true
|
||||||
|
render()
|
||||||
|
}
|
||||||
|
},
|
||||||
|
error: function() {
|
||||||
|
GlobalUI.notifyUser('There was an error marking that notification as read')
|
||||||
|
}
|
||||||
|
})
|
||||||
|
},
|
||||||
|
markAsUnread: (render, id) => {
|
||||||
|
const n = Notifications.notifications.find(n => n.id === id)
|
||||||
|
$.ajax({
|
||||||
|
url: `/notifications/${id}/mark_unread.json`,
|
||||||
|
method: 'PUT',
|
||||||
|
success: function() {
|
||||||
|
if (n) {
|
||||||
|
Notifications.unreadNotificationsCount++
|
||||||
|
n.is_read = false
|
||||||
|
render()
|
||||||
|
}
|
||||||
|
},
|
||||||
|
error: function() {
|
||||||
|
GlobalUI.notifyUser('There was an error marking that notification as read')
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default Notifications
|
|
@ -4,19 +4,21 @@ import React from 'react'
|
||||||
import ReactDOM from 'react-dom'
|
import ReactDOM from 'react-dom'
|
||||||
import { Router, browserHistory } from 'react-router'
|
import { Router, browserHistory } from 'react-router'
|
||||||
import { merge } from 'lodash'
|
import { merge } from 'lodash'
|
||||||
|
import apply from 'async/apply'
|
||||||
|
|
||||||
import { notifyUser } from './index.js'
|
import { notifyUser } from './index.js'
|
||||||
import ImportDialog from './ImportDialog'
|
import ImportDialog from './ImportDialog'
|
||||||
|
import Notifications from './Notifications'
|
||||||
import Active from '../Active'
|
import Active from '../Active'
|
||||||
import DataModel from '../DataModel'
|
import DataModel from '../DataModel'
|
||||||
import { ExploreMaps, ChatView, TopicCard } from '../Views'
|
import { ExploreMaps, ChatView, TopicCard, ContextMenu } from '../Views'
|
||||||
import Filter from '../Filter'
|
import Filter from '../Filter'
|
||||||
import JIT from '../JIT'
|
import JIT from '../JIT'
|
||||||
import Realtime from '../Realtime'
|
import Realtime from '../Realtime'
|
||||||
import Map, { InfoBox } from '../Map'
|
import Map, { InfoBox } from '../Map'
|
||||||
import Topic from '../Topic'
|
import Topic from '../Topic'
|
||||||
import Visualize from '../Visualize'
|
import Visualize from '../Visualize'
|
||||||
import makeRoutes from '../../components/makeRoutes'
|
import makeRoutes from '../../routes/makeRoutes'
|
||||||
let routes
|
let routes
|
||||||
|
|
||||||
// 220 wide + 16 padding on both sides
|
// 220 wide + 16 padding on both sides
|
||||||
|
@ -29,7 +31,6 @@ const ReactApp = {
|
||||||
serverData: {},
|
serverData: {},
|
||||||
mapId: null,
|
mapId: null,
|
||||||
topicId: null,
|
topicId: null,
|
||||||
unreadNotificationsCount: 0,
|
|
||||||
mapsWidth: 0,
|
mapsWidth: 0,
|
||||||
toast: '',
|
toast: '',
|
||||||
mobile: false,
|
mobile: false,
|
||||||
|
@ -39,7 +40,6 @@ const ReactApp = {
|
||||||
init: function(serverData, openLightbox) {
|
init: function(serverData, openLightbox) {
|
||||||
const self = ReactApp
|
const self = ReactApp
|
||||||
self.serverData = serverData
|
self.serverData = serverData
|
||||||
self.unreadNotificationsCount = serverData.unreadNotificationsCount
|
|
||||||
self.mobileTitle = serverData.mobileTitle
|
self.mobileTitle = serverData.mobileTitle
|
||||||
self.openLightbox = openLightbox
|
self.openLightbox = openLightbox
|
||||||
self.metacodeSets = serverData.metacodeSets
|
self.metacodeSets = serverData.metacodeSets
|
||||||
|
@ -98,7 +98,7 @@ const ReactApp = {
|
||||||
getProps: function() {
|
getProps: function() {
|
||||||
const self = ReactApp
|
const self = ReactApp
|
||||||
return merge({
|
return merge({
|
||||||
unreadNotificationsCount: self.unreadNotificationsCount,
|
unreadNotificationsCount: Notifications.unreadNotificationsCount,
|
||||||
currentUser: Active.Mapper,
|
currentUser: Active.Mapper,
|
||||||
toast: self.toast,
|
toast: self.toast,
|
||||||
mobile: self.mobile,
|
mobile: self.mobile,
|
||||||
|
@ -106,13 +106,18 @@ const ReactApp = {
|
||||||
mobileTitleWidth: self.mobileTitleWidth,
|
mobileTitleWidth: self.mobileTitleWidth,
|
||||||
mobileTitleClick: (e) => Active.Map && InfoBox.toggleBox(e),
|
mobileTitleClick: (e) => Active.Map && InfoBox.toggleBox(e),
|
||||||
openInviteLightbox: () => self.openLightbox('invite'),
|
openInviteLightbox: () => self.openLightbox('invite'),
|
||||||
serverData: self.serverData
|
serverData: self.serverData,
|
||||||
|
notifications: Notifications.notifications,
|
||||||
|
fetchNotifications: apply(Notifications.fetch, ReactApp.render),
|
||||||
|
markAsRead: apply(Notifications.markAsRead, ReactApp.render),
|
||||||
|
markAsUnread: apply(Notifications.markAsUnread, ReactApp.render)
|
||||||
},
|
},
|
||||||
self.getMapProps(),
|
self.getMapProps(),
|
||||||
self.getTopicProps(),
|
self.getTopicProps(),
|
||||||
self.getFilterProps(),
|
self.getFilterProps(),
|
||||||
self.getCommonProps(),
|
self.getCommonProps(),
|
||||||
self.getMapsProps(),
|
self.getMapsProps(),
|
||||||
|
self.getContextMenuProps(),
|
||||||
self.getTopicCardProps(),
|
self.getTopicCardProps(),
|
||||||
self.getChatProps())
|
self.getChatProps())
|
||||||
},
|
},
|
||||||
|
@ -155,6 +160,28 @@ const ReactApp = {
|
||||||
onTopicFollow: Topic.onTopicFollow
|
onTopicFollow: Topic.onTopicFollow
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
getContextMenuProps: function() {
|
||||||
|
const { render } = ReactApp
|
||||||
|
return {
|
||||||
|
// values
|
||||||
|
contextMenu: !!(ContextMenu.clickedNode || ContextMenu.clickedEdge),
|
||||||
|
contextNode: ContextMenu.clickedNode,
|
||||||
|
contextEdge: ContextMenu.clickedEdge,
|
||||||
|
contextPos: ContextMenu.pos,
|
||||||
|
contextFetchingSiblingsData: ContextMenu.fetchingSiblingsData,
|
||||||
|
contextSiblingsData: ContextMenu.siblingsData,
|
||||||
|
// functions
|
||||||
|
contextDelete: apply(ContextMenu.delete, render),
|
||||||
|
contextRemove: apply(ContextMenu.remove, render),
|
||||||
|
contextHide: apply(ContextMenu.hide, render),
|
||||||
|
contextCenterOn: apply(ContextMenu.centerOn, render),
|
||||||
|
contextPopoutTopic: apply(ContextMenu.popoutTopic, render),
|
||||||
|
contextUpdatePermissions: apply(ContextMenu.updatePermissions, render),
|
||||||
|
contextOnMetacodeSelect: apply(ContextMenu.onMetacodeSelect, render),
|
||||||
|
contextFetchSiblings: apply(ContextMenu.fetchSiblings, render),
|
||||||
|
contextPopulateSiblings: apply(ContextMenu.populateSiblings, render)
|
||||||
|
}
|
||||||
|
},
|
||||||
getTopicProps: function() {
|
getTopicProps: function() {
|
||||||
const self = ReactApp
|
const self = ReactApp
|
||||||
return {
|
return {
|
||||||
|
|
|
@ -4,6 +4,7 @@ import clipboard from 'clipboard-js'
|
||||||
|
|
||||||
import Create from '../Create'
|
import Create from '../Create'
|
||||||
|
|
||||||
|
import Notifications from './Notifications'
|
||||||
import ReactApp from './ReactApp'
|
import ReactApp from './ReactApp'
|
||||||
import Search from './Search'
|
import Search from './Search'
|
||||||
import CreateMap from './CreateMap'
|
import CreateMap from './CreateMap'
|
||||||
|
@ -17,6 +18,7 @@ const GlobalUI = {
|
||||||
init: function(serverData) {
|
init: function(serverData) {
|
||||||
const self = GlobalUI
|
const self = GlobalUI
|
||||||
|
|
||||||
|
self.Notifications.init(serverData)
|
||||||
self.ReactApp.init(serverData, self.openLightbox)
|
self.ReactApp.init(serverData, self.openLightbox)
|
||||||
self.CreateMap.init(serverData)
|
self.CreateMap.init(serverData)
|
||||||
self.ImportDialog.init(serverData, self.openLightbox, self.closeLightbox)
|
self.ImportDialog.init(serverData, self.openLightbox, self.closeLightbox)
|
||||||
|
@ -151,5 +153,5 @@ const GlobalUI = {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export { ReactApp, Search, CreateMap, ImportDialog }
|
export { Notifications, ReactApp, Search, CreateMap, ImportDialog }
|
||||||
export default GlobalUI
|
export default GlobalUI
|
||||||
|
|
|
@ -1,16 +1,12 @@
|
||||||
/* global $, Image, CanvasLoader */
|
/* global $, Image */
|
||||||
|
|
||||||
import _ from 'lodash'
|
import _ from 'lodash'
|
||||||
import outdent from 'outdent'
|
|
||||||
import clipboard from 'clipboard-js'
|
import clipboard from 'clipboard-js'
|
||||||
import React from 'react'
|
|
||||||
import ReactDOM from 'react-dom'
|
|
||||||
|
|
||||||
import $jit from '../patched/JIT'
|
import $jit from '../patched/JIT'
|
||||||
|
|
||||||
import MetacodeSelect from '../components/MetacodeSelect'
|
|
||||||
|
|
||||||
import Active from './Active'
|
import Active from './Active'
|
||||||
|
import ContextMenu from './Views/ContextMenu'
|
||||||
import Control from './Control'
|
import Control from './Control'
|
||||||
import Create from './Create'
|
import Create from './Create'
|
||||||
import DataModel from './DataModel'
|
import DataModel from './DataModel'
|
||||||
|
@ -349,7 +345,7 @@ const JIT = {
|
||||||
// Add also a click handler to nodes
|
// Add also a click handler to nodes
|
||||||
onClick: function(node, eventInfo, e) {
|
onClick: function(node, eventInfo, e) {
|
||||||
// remove the rightclickmenu
|
// remove the rightclickmenu
|
||||||
$('.rightclickmenu').remove()
|
ContextMenu.reset(ReactApp.render)
|
||||||
|
|
||||||
if (Mouse.boxStartCoordinates) {
|
if (Mouse.boxStartCoordinates) {
|
||||||
if (e.ctrlKey) {
|
if (e.ctrlKey) {
|
||||||
|
@ -390,7 +386,7 @@ const JIT = {
|
||||||
// Add also a click handler to nodes
|
// Add also a click handler to nodes
|
||||||
onRightClick: function(node, eventInfo, e) {
|
onRightClick: function(node, eventInfo, e) {
|
||||||
// remove the rightclickmenu
|
// remove the rightclickmenu
|
||||||
$('.rightclickmenu').remove()
|
ContextMenu.reset(ReactApp.render)
|
||||||
|
|
||||||
if (Mouse.boxStartCoordinates) {
|
if (Mouse.boxStartCoordinates) {
|
||||||
Create.newSynapse.hide()
|
Create.newSynapse.hide()
|
||||||
|
@ -1006,7 +1002,7 @@ const JIT = {
|
||||||
TopicCard.hideCard()
|
TopicCard.hideCard()
|
||||||
SynapseCard.hideCard()
|
SynapseCard.hideCard()
|
||||||
Create.newTopic.hide()
|
Create.newTopic.hide()
|
||||||
$('.rightclickmenu').remove()
|
ContextMenu.reset(ReactApp.render)
|
||||||
// reset the draw synapse positions to false
|
// reset the draw synapse positions to false
|
||||||
Mouse.synapseStartCoordinates = []
|
Mouse.synapseStartCoordinates = []
|
||||||
Mouse.synapseEndCoordinates = null
|
Mouse.synapseEndCoordinates = null
|
||||||
|
@ -1346,230 +1342,12 @@ const JIT = {
|
||||||
selectNodeOnRightClickHandler: function(node, e) {
|
selectNodeOnRightClickHandler: function(node, e) {
|
||||||
// the 'node' variable is a JIT node, the one that was clicked on
|
// the 'node' variable is a JIT node, the one that was clicked on
|
||||||
// the 'e' variable is the click event
|
// the 'e' variable is the click event
|
||||||
|
|
||||||
e.preventDefault()
|
e.preventDefault()
|
||||||
e.stopPropagation()
|
e.stopPropagation()
|
||||||
|
|
||||||
if (Visualize.mGraph.busy) return
|
if (Visualize.mGraph.busy) return
|
||||||
|
|
||||||
// select the node
|
|
||||||
Control.selectNode(node, e)
|
Control.selectNode(node, e)
|
||||||
|
ContextMenu.selectNode(ReactApp.render, node, {x: e.clientX, y: e.clientY})
|
||||||
// delete old right click menu
|
|
||||||
$('.rightclickmenu').remove()
|
|
||||||
// create new menu for clicked on node
|
|
||||||
const rightclickmenu = document.createElement('div')
|
|
||||||
rightclickmenu.className = 'rightclickmenu'
|
|
||||||
// prevent the custom context menu from immediately opening the default context menu as well
|
|
||||||
rightclickmenu.setAttribute('oncontextmenu', 'return false')
|
|
||||||
|
|
||||||
// add the proper options to the menu
|
|
||||||
let menustring = '<ul>'
|
|
||||||
|
|
||||||
const authorized = Active.Map && Active.Map.authorizeToEdit(Active.Mapper)
|
|
||||||
|
|
||||||
const disabled = authorized ? '' : 'disabled'
|
|
||||||
|
|
||||||
if (Active.Map) menustring += '<li class="rc-hide"><div class="rc-icon"></div>Hide until refresh<div class="rc-keyboard">Ctrl+H</div></li>'
|
|
||||||
if (Active.Map && Active.Mapper) menustring += '<li class="rc-remove ' + disabled + '"><div class="rc-icon"></div>Remove from map<div class="rc-keyboard">Ctrl+M</div></li>'
|
|
||||||
if (Active.Topic) menustring += '<li class="rc-remove"><div class="rc-icon"></div>Remove from view<div class="rc-keyboard">Ctrl+M</div></li>'
|
|
||||||
if (Active.Map && Active.Mapper) menustring += '<li class="rc-delete ' + disabled + '"><div class="rc-icon"></div>Delete<div class="rc-keyboard">Ctrl+D</div></li>'
|
|
||||||
|
|
||||||
if (Active.Topic) {
|
|
||||||
menustring += '<li class="rc-center"><div class="rc-icon"></div>Center this topic<div class="rc-keyboard">Alt+E</div></li>'
|
|
||||||
}
|
|
||||||
|
|
||||||
menustring += '<li class="rc-popout"><div class="rc-icon"></div>Open in new tab</li>'
|
|
||||||
|
|
||||||
if (Active.Mapper) {
|
|
||||||
const options = outdent`
|
|
||||||
<ul>
|
|
||||||
<li class="changeP toCommons"><div class="rc-perm-icon"></div>commons</li>
|
|
||||||
<li class="changeP toPublic"><div class="rc-perm-icon"></div>public</li>
|
|
||||||
<li class="changeP toPrivate"><div class="rc-perm-icon"></div>private</li>
|
|
||||||
</ul>`
|
|
||||||
|
|
||||||
menustring += '<li class="rc-spacer"></li>'
|
|
||||||
|
|
||||||
menustring += outdent`
|
|
||||||
<li class="rc-permission">
|
|
||||||
<div class="rc-icon"></div>
|
|
||||||
Change permissions
|
|
||||||
${options}
|
|
||||||
<div class="expandLi"></div>
|
|
||||||
</li>`
|
|
||||||
|
|
||||||
menustring += '<li class="rc-metacode"><div class="rc-icon"></div>Change metacode<div id="metacodeOptionsWrapper"></div><div class="expandLi"></div></li>'
|
|
||||||
}
|
|
||||||
if (Active.Topic) {
|
|
||||||
if (!Active.Mapper) {
|
|
||||||
menustring += '<li class="rc-spacer"></li>'
|
|
||||||
}
|
|
||||||
|
|
||||||
// set up the get sibling menu as a "lazy load"
|
|
||||||
// only fill in the submenu when they hover over the get siblings list item
|
|
||||||
const siblingMenu = outdent`
|
|
||||||
<ul id="fetchSiblingList">
|
|
||||||
<li class="fetchAll">All<div class="rc-keyboard">Alt+R</div></li>
|
|
||||||
<li id="loadingSiblings"></li>
|
|
||||||
</ul>`
|
|
||||||
menustring += '<li class="rc-siblings"><div class="rc-icon"></div>Reveal siblings' + siblingMenu + '<div class="expandLi"></div></li>'
|
|
||||||
}
|
|
||||||
|
|
||||||
menustring += '</ul>'
|
|
||||||
rightclickmenu.innerHTML = menustring
|
|
||||||
|
|
||||||
// position the menu where the click happened
|
|
||||||
const position = {}
|
|
||||||
const RIGHTCLICK_WIDTH = 300
|
|
||||||
const RIGHTCLICK_HEIGHT = 144 // this does vary somewhat, but we can use static
|
|
||||||
const SUBMENUS_WIDTH = 256
|
|
||||||
const MAX_SUBMENU_HEIGHT = 270
|
|
||||||
const windowWidth = $(window).width()
|
|
||||||
const windowHeight = $(window).height()
|
|
||||||
|
|
||||||
if (windowWidth - e.clientX < SUBMENUS_WIDTH) {
|
|
||||||
position.right = windowWidth - e.clientX
|
|
||||||
$(rightclickmenu).addClass('moveMenusToLeft')
|
|
||||||
} else if (windowWidth - e.clientX < RIGHTCLICK_WIDTH) {
|
|
||||||
position.right = windowWidth - e.clientX
|
|
||||||
} else if (windowWidth - e.clientX < RIGHTCLICK_WIDTH + SUBMENUS_WIDTH) {
|
|
||||||
position.left = e.clientX
|
|
||||||
$(rightclickmenu).addClass('moveMenusToLeft')
|
|
||||||
} else {
|
|
||||||
position.left = e.clientX
|
|
||||||
}
|
|
||||||
|
|
||||||
if (windowHeight - e.clientY < MAX_SUBMENU_HEIGHT) {
|
|
||||||
position.bottom = windowHeight - e.clientY
|
|
||||||
$(rightclickmenu).addClass('moveMenusUp')
|
|
||||||
} else if (windowHeight - e.clientY < RIGHTCLICK_HEIGHT + MAX_SUBMENU_HEIGHT) {
|
|
||||||
position.top = e.clientY
|
|
||||||
$(rightclickmenu).addClass('moveMenusUp')
|
|
||||||
} else {
|
|
||||||
position.top = e.clientY
|
|
||||||
}
|
|
||||||
|
|
||||||
$(rightclickmenu).css(position)
|
|
||||||
// add the menu to the page
|
|
||||||
$('#wrapper').append(rightclickmenu)
|
|
||||||
|
|
||||||
ReactDOM.render(
|
|
||||||
React.createElement(MetacodeSelect, {
|
|
||||||
onMetacodeSelect: metacodeId => {
|
|
||||||
if (Selected.Nodes.length > 1) {
|
|
||||||
// batch update multiple topics
|
|
||||||
Control.updateSelectedMetacodes(metacodeId)
|
|
||||||
} else {
|
|
||||||
const topic = DataModel.Topics.get(node.id)
|
|
||||||
topic.save({
|
|
||||||
metacode_id: metacodeId
|
|
||||||
})
|
|
||||||
}
|
|
||||||
$(rightclickmenu).remove()
|
|
||||||
},
|
|
||||||
metacodeSets: ReactApp.metacodeSets
|
|
||||||
}),
|
|
||||||
document.getElementById('metacodeOptionsWrapper')
|
|
||||||
)
|
|
||||||
|
|
||||||
// attach events to clicks on the list items
|
|
||||||
|
|
||||||
// delete the selected things from the database
|
|
||||||
if (authorized) {
|
|
||||||
$('.rc-delete').click(function() {
|
|
||||||
$('.rightclickmenu').remove()
|
|
||||||
Control.deleteSelected()
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
// remove the selected things from the map
|
|
||||||
if (Active.Topic || authorized) {
|
|
||||||
$('.rc-remove').click(function() {
|
|
||||||
$('.rightclickmenu').remove()
|
|
||||||
Control.removeSelectedEdges()
|
|
||||||
Control.removeSelectedNodes()
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
// hide selected nodes and synapses until refresh
|
|
||||||
$('.rc-hide').click(function() {
|
|
||||||
$('.rightclickmenu').remove()
|
|
||||||
Control.hideSelectedEdges()
|
|
||||||
Control.hideSelectedNodes()
|
|
||||||
})
|
|
||||||
|
|
||||||
// when in radial, center on the topic you picked
|
|
||||||
$('.rc-center').click(function() {
|
|
||||||
$('.rightclickmenu').remove()
|
|
||||||
Topic.centerOn(node.id)
|
|
||||||
})
|
|
||||||
|
|
||||||
// open the entity in a new tab
|
|
||||||
$('.rc-popout').click(function() {
|
|
||||||
$('.rightclickmenu').remove()
|
|
||||||
const win = window.open('/topics/' + node.id, '_blank')
|
|
||||||
win.focus()
|
|
||||||
})
|
|
||||||
|
|
||||||
// change the permission of all the selected nodes and synapses that you were the originator of
|
|
||||||
$('.rc-permission li').click(function() {
|
|
||||||
$('.rightclickmenu').remove()
|
|
||||||
// $(this).text() will be 'commons' 'public' or 'private'
|
|
||||||
Control.updateSelectedPermissions($(this).text())
|
|
||||||
})
|
|
||||||
|
|
||||||
// fetch relatives
|
|
||||||
let fetchSent = false
|
|
||||||
$('.rc-siblings').hover(function() {
|
|
||||||
if (!fetchSent) {
|
|
||||||
JIT.populateRightClickSiblings(node)
|
|
||||||
fetchSent = true
|
|
||||||
}
|
|
||||||
})
|
|
||||||
$('.rc-siblings .fetchAll').click(function() {
|
|
||||||
$('.rightclickmenu').remove()
|
|
||||||
// data-id is a metacode id
|
|
||||||
Topic.fetchRelatives(node)
|
|
||||||
})
|
|
||||||
}, // selectNodeOnRightClickHandler,
|
}, // selectNodeOnRightClickHandler,
|
||||||
populateRightClickSiblings: function(node) {
|
|
||||||
// depending on how many topics are selected, do different things
|
|
||||||
const topic = node.getData('topic')
|
|
||||||
|
|
||||||
// add a loading icon for now
|
|
||||||
const loader = new CanvasLoader('loadingSiblings')
|
|
||||||
loader.setColor('#4FC059') // default is '#000000'
|
|
||||||
loader.setDiameter(15) // default is 40
|
|
||||||
loader.setDensity(41) // default is 40
|
|
||||||
loader.setRange(0.9) // default is 1.3
|
|
||||||
loader.show() // Hidden by default
|
|
||||||
|
|
||||||
const topics = DataModel.Topics.map(function(t) { return t.id })
|
|
||||||
const topicsString = topics.join()
|
|
||||||
|
|
||||||
const successCallback = function(data) {
|
|
||||||
$('#loadingSiblings').remove()
|
|
||||||
|
|
||||||
for (var key in data) {
|
|
||||||
const string = `${DataModel.Metacodes.get(key).get('name')} (${data[key]})`
|
|
||||||
$('#fetchSiblingList').append(`<li class="getSiblings" data-id="${key}">${string}</li>`)
|
|
||||||
}
|
|
||||||
|
|
||||||
$('.rc-siblings .getSiblings').click(function() {
|
|
||||||
$('.rightclickmenu').remove()
|
|
||||||
// data-id is a metacode id
|
|
||||||
Topic.fetchRelatives(node, $(this).attr('data-id'))
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
$.ajax({
|
|
||||||
type: 'GET',
|
|
||||||
url: '/topics/' + topic.id + '/relative_numbers.json?network=' + topicsString,
|
|
||||||
success: successCallback,
|
|
||||||
error: function() {}
|
|
||||||
})
|
|
||||||
},
|
|
||||||
selectEdgeOnClickHandler: function(adj, e) {
|
selectEdgeOnClickHandler: function(adj, e) {
|
||||||
if (Visualize.mGraph.busy) return
|
if (Visualize.mGraph.busy) return
|
||||||
|
|
||||||
|
@ -1611,113 +1389,14 @@ const JIT = {
|
||||||
}
|
}
|
||||||
}, // selectEdgeOnClickHandler
|
}, // selectEdgeOnClickHandler
|
||||||
selectEdgeOnRightClickHandler: function(adj, e) {
|
selectEdgeOnRightClickHandler: function(adj, e) {
|
||||||
// the 'node' variable is a JIT node, the one that was clicked on
|
// the 'adj' variable is a JIT adjacency, the one that was clicked on
|
||||||
// the 'e' variable is the click event
|
// the 'e' variable is the click event
|
||||||
|
|
||||||
if (adj.getData('alpha') === 0) return // don't do anything if the edge is filtered
|
if (adj.getData('alpha') === 0) return // don't do anything if the edge is filtered
|
||||||
|
|
||||||
e.preventDefault()
|
e.preventDefault()
|
||||||
e.stopPropagation()
|
e.stopPropagation()
|
||||||
|
|
||||||
if (Visualize.mGraph.busy) return
|
if (Visualize.mGraph.busy) return
|
||||||
|
|
||||||
Control.selectEdge(adj)
|
Control.selectEdge(adj)
|
||||||
|
ContextMenu.selectEdge(ReactApp.render, adj, {x: e.clientX, y: e.clientY})
|
||||||
// delete old right click menu
|
|
||||||
$('.rightclickmenu').remove()
|
|
||||||
// create new menu for clicked on node
|
|
||||||
const rightclickmenu = document.createElement('div')
|
|
||||||
rightclickmenu.className = 'rightclickmenu'
|
|
||||||
// prevent the custom context menu from immediately opening the default context menu as well
|
|
||||||
rightclickmenu.setAttribute('oncontextmenu', 'return false')
|
|
||||||
|
|
||||||
// add the proper options to the menu
|
|
||||||
let menustring = '<ul>'
|
|
||||||
|
|
||||||
const authorized = Active.Map && Active.Map.authorizeToEdit(Active.Mapper)
|
|
||||||
|
|
||||||
const disabled = authorized ? '' : 'disabled'
|
|
||||||
|
|
||||||
if (Active.Map) menustring += '<li class="rc-hide"><div class="rc-icon"></div>Hide until refresh<div class="rc-keyboard">Ctrl+H</div></li>'
|
|
||||||
if (Active.Map && Active.Mapper) menustring += '<li class="rc-remove ' + disabled + '"><div class="rc-icon"></div>Remove from map<div class="rc-keyboard">Ctrl+M</div></li>'
|
|
||||||
if (Active.Topic) menustring += '<li class="rc-remove"><div class="rc-icon"></div>Remove from view<div class="rc-keyboard">Ctrl+M</div></li>'
|
|
||||||
if (Active.Map && Active.Mapper) menustring += '<li class="rc-delete ' + disabled + '"><div class="rc-icon"></div>Delete<div class="rc-keyboard">Ctrl+D</div></li>'
|
|
||||||
|
|
||||||
if (Active.Map && Active.Mapper) menustring += '<li class="rc-spacer"></li>'
|
|
||||||
|
|
||||||
if (Active.Mapper) {
|
|
||||||
const permOptions = outdent`
|
|
||||||
<ul>
|
|
||||||
<li class="changeP toCommons"><div class="rc-perm-icon"></div>commons</li>
|
|
||||||
<li class="changeP toPublic"><div class="rc-perm-icon"></div>public</li> <li class="changeP toPrivate"><div class="rc-perm-icon"></div>private</li> </ul>`
|
|
||||||
|
|
||||||
menustring += '<li class="rc-permission"><div class="rc-icon"></div>Change permissions' + permOptions + '<div class="expandLi"></div></li>'
|
|
||||||
}
|
|
||||||
|
|
||||||
menustring += '</ul>'
|
|
||||||
rightclickmenu.innerHTML = menustring
|
|
||||||
|
|
||||||
// position the menu where the click happened
|
|
||||||
const position = {}
|
|
||||||
const RIGHTCLICK_WIDTH = 300
|
|
||||||
const RIGHTCLICK_HEIGHT = 144 // this does vary somewhat, but we can use static
|
|
||||||
const SUBMENUS_WIDTH = 256
|
|
||||||
const MAX_SUBMENU_HEIGHT = 270
|
|
||||||
const windowWidth = $(window).width()
|
|
||||||
const windowHeight = $(window).height()
|
|
||||||
|
|
||||||
if (windowWidth - e.clientX < SUBMENUS_WIDTH) {
|
|
||||||
position.right = windowWidth - e.clientX
|
|
||||||
$(rightclickmenu).addClass('moveMenusToLeft')
|
|
||||||
} else if (windowWidth - e.clientX < RIGHTCLICK_WIDTH) {
|
|
||||||
position.right = windowWidth - e.clientX
|
|
||||||
} else position.left = e.clientX
|
|
||||||
|
|
||||||
if (windowHeight - e.clientY < MAX_SUBMENU_HEIGHT) {
|
|
||||||
position.bottom = windowHeight - e.clientY
|
|
||||||
$(rightclickmenu).addClass('moveMenusUp')
|
|
||||||
} else if (windowHeight - e.clientY < RIGHTCLICK_HEIGHT + MAX_SUBMENU_HEIGHT) {
|
|
||||||
position.top = e.clientY
|
|
||||||
$(rightclickmenu).addClass('moveMenusUp')
|
|
||||||
} else position.top = e.clientY
|
|
||||||
|
|
||||||
$(rightclickmenu).css(position)
|
|
||||||
|
|
||||||
// add the menu to the page
|
|
||||||
$('#wrapper').append(rightclickmenu)
|
|
||||||
|
|
||||||
// attach events to clicks on the list items
|
|
||||||
|
|
||||||
// delete the selected things from the database
|
|
||||||
if (authorized) {
|
|
||||||
$('.rc-delete').click(function() {
|
|
||||||
$('.rightclickmenu').remove()
|
|
||||||
Control.deleteSelected()
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
// remove the selected things from the map
|
|
||||||
if (authorized) {
|
|
||||||
$('.rc-remove').click(function() {
|
|
||||||
$('.rightclickmenu').remove()
|
|
||||||
Control.removeSelectedEdges()
|
|
||||||
Control.removeSelectedNodes()
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
// hide selected nodes and synapses until refresh
|
|
||||||
$('.rc-hide').click(function() {
|
|
||||||
$('.rightclickmenu').remove()
|
|
||||||
Control.hideSelectedEdges()
|
|
||||||
Control.hideSelectedNodes()
|
|
||||||
})
|
|
||||||
|
|
||||||
// change the permission of all the selected nodes and synapses that you were the originator of
|
|
||||||
$('.rc-permission li').click(function() {
|
|
||||||
$('.rightclickmenu').remove()
|
|
||||||
// $(this).text() will be 'commons' 'public' or 'private'
|
|
||||||
Control.updateSelectedPermissions($(this).text())
|
|
||||||
})
|
|
||||||
}, // selectEdgeOnRightClickHandler
|
}, // selectEdgeOnRightClickHandler
|
||||||
SmoothPanning: function() {
|
SmoothPanning: function() {
|
||||||
const sx = Visualize.mGraph.canvas.scaleOffsetX
|
const sx = Visualize.mGraph.canvas.scaleOffsetX
|
||||||
|
|
|
@ -152,12 +152,12 @@ const Listeners = {
|
||||||
var node = nodes[nodes.length - 1]
|
var node = nodes[nodes.length - 1]
|
||||||
if (opts.center && opts.reveal) {
|
if (opts.center && opts.reveal) {
|
||||||
Topic.centerOn(node.id, function() {
|
Topic.centerOn(node.id, function() {
|
||||||
Topic.fetchRelatives(nodes)
|
Topic.fetchSiblings(nodes)
|
||||||
})
|
})
|
||||||
} else if (opts.center) {
|
} else if (opts.center) {
|
||||||
Topic.centerOn(node.id)
|
Topic.centerOn(node.id)
|
||||||
} else if (opts.reveal) {
|
} else if (opts.reveal) {
|
||||||
Topic.fetchRelatives(nodes)
|
Topic.fetchSiblings(nodes)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -16,6 +16,7 @@ import Loading from '../Loading'
|
||||||
import Realtime from '../Realtime'
|
import Realtime from '../Realtime'
|
||||||
import Selected from '../Selected'
|
import Selected from '../Selected'
|
||||||
import SynapseCard from '../SynapseCard'
|
import SynapseCard from '../SynapseCard'
|
||||||
|
import ContextMenu from '../Views/ContextMenu'
|
||||||
import TopicCard from '../Views/TopicCard'
|
import TopicCard from '../Views/TopicCard'
|
||||||
import Visualize from '../Visualize'
|
import Visualize from '../Visualize'
|
||||||
|
|
||||||
|
@ -137,7 +138,7 @@ const Map = {
|
||||||
if (Active.Map) {
|
if (Active.Map) {
|
||||||
$('.main').removeClass('compressed')
|
$('.main').removeClass('compressed')
|
||||||
AutoLayout.resetSpiral()
|
AutoLayout.resetSpiral()
|
||||||
$('.rightclickmenu').remove()
|
ContextMenu.reset(ReactApp.render)
|
||||||
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)
|
||||||
|
|
|
@ -15,6 +15,7 @@ import Selected from './Selected'
|
||||||
import Settings from './Settings'
|
import Settings from './Settings'
|
||||||
import SynapseCard from './SynapseCard'
|
import SynapseCard from './SynapseCard'
|
||||||
import TopicCard from './Views/TopicCard'
|
import TopicCard from './Views/TopicCard'
|
||||||
|
import ContextMenu from './Views/ContextMenu'
|
||||||
import Util from './Util'
|
import Util from './Util'
|
||||||
import Visualize from './Visualize'
|
import Visualize from './Visualize'
|
||||||
|
|
||||||
|
@ -68,13 +69,13 @@ const Topic = {
|
||||||
},
|
},
|
||||||
end: function() {
|
end: function() {
|
||||||
if (Active.Topic) {
|
if (Active.Topic) {
|
||||||
$('.rightclickmenu').remove()
|
ContextMenu.reset(ReactApp.render)
|
||||||
TopicCard.hideCard()
|
TopicCard.hideCard()
|
||||||
SynapseCard.hideCard()
|
SynapseCard.hideCard()
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
centerOn: function(nodeid, callback) {
|
centerOn: function(nodeid, callback) {
|
||||||
// don't clash with fetchRelatives
|
// don't clash with fetchSiblings
|
||||||
if (!Visualize.mGraph.busy) {
|
if (!Visualize.mGraph.busy) {
|
||||||
Visualize.mGraph.onClick(nodeid, {
|
Visualize.mGraph.onClick(nodeid, {
|
||||||
hideLabels: false,
|
hideLabels: false,
|
||||||
|
@ -100,10 +101,10 @@ const Topic = {
|
||||||
}
|
}
|
||||||
ReactApp.render()
|
ReactApp.render()
|
||||||
},
|
},
|
||||||
fetchRelatives: function(nodes, metacodeId) {
|
fetchSiblings: function(nodes, metacodeId) {
|
||||||
var self = this
|
var self = this
|
||||||
|
|
||||||
var node = $.isArray(nodes) ? nodes[0] : nodes
|
var node = Array.isArray(nodes) ? nodes[0] : nodes
|
||||||
|
|
||||||
var topics = DataModel.Topics.map(function(t) { return t.id })
|
var topics = DataModel.Topics.map(function(t) { return t.id })
|
||||||
var topicsString = topics.join()
|
var topicsString = topics.join()
|
||||||
|
@ -155,8 +156,8 @@ const Topic = {
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
if ($.isArray(nodes) && nodes.length > 1) {
|
if (Array.isArray(nodes) && nodes.length > 1) {
|
||||||
self.fetchRelatives(nodes.slice(1), metacodeId)
|
self.fetchSiblings(nodes.slice(1), metacodeId)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
108
frontend/src/Metamaps/Views/ContextMenu.js
Normal file
108
frontend/src/Metamaps/Views/ContextMenu.js
Normal file
|
@ -0,0 +1,108 @@
|
||||||
|
/* global $ */
|
||||||
|
import Control from '../Control'
|
||||||
|
import DataModel from '../DataModel'
|
||||||
|
import Selected from '../Selected'
|
||||||
|
import Topic from '../Topic'
|
||||||
|
|
||||||
|
const ContextMenu = {
|
||||||
|
clickedNode: null,
|
||||||
|
clickedEdge: null,
|
||||||
|
pos: {x: 0, y: 0},
|
||||||
|
fetchingSiblingsData: false,
|
||||||
|
siblingsData: null,
|
||||||
|
selectNode: (render, node, pos) => {
|
||||||
|
ContextMenu.pos = pos
|
||||||
|
ContextMenu.clickedNode = node
|
||||||
|
ContextMenu.clickedEdge = null
|
||||||
|
ContextMenu.fetchingSiblingsData = false
|
||||||
|
ContextMenu.siblingsData = null
|
||||||
|
render()
|
||||||
|
},
|
||||||
|
selectEdge: (render, edge, pos) => {
|
||||||
|
ContextMenu.pos = pos
|
||||||
|
ContextMenu.clickedNode = null
|
||||||
|
ContextMenu.clickedEdge = edge
|
||||||
|
ContextMenu.fetchingSiblingsData = false
|
||||||
|
ContextMenu.siblingsData = null
|
||||||
|
render()
|
||||||
|
},
|
||||||
|
reset: (render) => {
|
||||||
|
ContextMenu.fetchingSiblingsData = false
|
||||||
|
ContextMenu.siblingsData = null
|
||||||
|
ContextMenu.clickedNode = null
|
||||||
|
ContextMenu.clickedEdge = null
|
||||||
|
render()
|
||||||
|
},
|
||||||
|
delete: (render) => {
|
||||||
|
Control.deleteSelected()
|
||||||
|
ContextMenu.reset(render)
|
||||||
|
},
|
||||||
|
remove: (render) => {
|
||||||
|
Control.removeSelectedEdges()
|
||||||
|
Control.removeSelectedNodes()
|
||||||
|
ContextMenu.reset(render)
|
||||||
|
},
|
||||||
|
hide: (render) => {
|
||||||
|
Control.hideSelectedEdges()
|
||||||
|
Control.hideSelectedNodes()
|
||||||
|
ContextMenu.reset(render)
|
||||||
|
},
|
||||||
|
centerOn: (render, id) => {
|
||||||
|
Topic.centerOn(id)
|
||||||
|
ContextMenu.reset(render)
|
||||||
|
},
|
||||||
|
popoutTopic: (render, id) => {
|
||||||
|
ContextMenu.reset(render)
|
||||||
|
const win = window.open(`/topics/${id}`, '_blank')
|
||||||
|
win.focus()
|
||||||
|
},
|
||||||
|
updatePermissions: (render, permission) => {
|
||||||
|
// will be 'commons' 'public' or 'private'
|
||||||
|
Control.updateSelectedPermissions(permission)
|
||||||
|
ContextMenu.reset(render)
|
||||||
|
},
|
||||||
|
onMetacodeSelect: (render, id, metacodeId) => {
|
||||||
|
if (Selected.Nodes.length > 1) {
|
||||||
|
// batch update multiple topics
|
||||||
|
Control.updateSelectedMetacodes(metacodeId)
|
||||||
|
} else {
|
||||||
|
const topic = DataModel.Topics.get(id)
|
||||||
|
topic.save({
|
||||||
|
metacode_id: metacodeId
|
||||||
|
})
|
||||||
|
}
|
||||||
|
ContextMenu.reset(render)
|
||||||
|
},
|
||||||
|
fetchSiblings: (render, node, metacodeId) => {
|
||||||
|
Topic.fetchSiblings(node, metacodeId)
|
||||||
|
ContextMenu.reset(render)
|
||||||
|
},
|
||||||
|
populateSiblings: (render, id) => {
|
||||||
|
// depending on how many topics are selected, do different things
|
||||||
|
ContextMenu.fetchingSiblingsData = true
|
||||||
|
render()
|
||||||
|
|
||||||
|
const topics = DataModel.Topics.map(function(t) { return t.id })
|
||||||
|
const topicsString = topics.join()
|
||||||
|
|
||||||
|
const successCallback = function(data) {
|
||||||
|
ContextMenu.fetchingSiblingsData = false
|
||||||
|
|
||||||
|
// adjust the data for consumption by react
|
||||||
|
for (var key in data) {
|
||||||
|
data[key] = `${DataModel.Metacodes.get(key).get('name')} (${data[key]})`
|
||||||
|
}
|
||||||
|
ContextMenu.siblingsData = data
|
||||||
|
render()
|
||||||
|
}
|
||||||
|
|
||||||
|
$.ajax({
|
||||||
|
type: 'GET',
|
||||||
|
url: `/topics/${id}/relative_numbers.json?network=${topicsString}`,
|
||||||
|
success: successCallback,
|
||||||
|
error: function() {}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default ContextMenu
|
|
@ -1,5 +1,6 @@
|
||||||
/* global $ */
|
/* global $ */
|
||||||
|
|
||||||
|
import ContextMenu from './ContextMenu'
|
||||||
import ExploreMaps from './ExploreMaps'
|
import ExploreMaps from './ExploreMaps'
|
||||||
import ChatView from './ChatView'
|
import ChatView from './ChatView'
|
||||||
import VideoView from './VideoView'
|
import VideoView from './VideoView'
|
||||||
|
@ -12,6 +13,7 @@ const Views = {
|
||||||
$(document).on(JUNTO_UPDATED, () => ExploreMaps.render())
|
$(document).on(JUNTO_UPDATED, () => ExploreMaps.render())
|
||||||
ChatView.init([serverData['sounds/MM_sounds.mp3'], serverData['sounds/MM_sounds.ogg']])
|
ChatView.init([serverData['sounds/MM_sounds.mp3'], serverData['sounds/MM_sounds.ogg']])
|
||||||
},
|
},
|
||||||
|
ContextMenu,
|
||||||
ExploreMaps,
|
ExploreMaps,
|
||||||
ChatView,
|
ChatView,
|
||||||
VideoView,
|
VideoView,
|
||||||
|
@ -19,5 +21,5 @@ const Views = {
|
||||||
TopicCard
|
TopicCard
|
||||||
}
|
}
|
||||||
|
|
||||||
export { ExploreMaps, ChatView, VideoView, Room, TopicCard }
|
export { ContextMenu, ExploreMaps, ChatView, VideoView, Room, TopicCard }
|
||||||
export default Views
|
export default Views
|
||||||
|
|
|
@ -9,7 +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, {
|
||||||
ReactApp, Search, CreateMap, ImportDialog
|
Notifications, ReactApp, Search, CreateMap, ImportDialog
|
||||||
} from './GlobalUI'
|
} from './GlobalUI'
|
||||||
import Import from './Import'
|
import Import from './Import'
|
||||||
import JIT from './JIT'
|
import JIT from './JIT'
|
||||||
|
@ -42,6 +42,7 @@ Metamaps.DataModel = DataModel
|
||||||
Metamaps.Debug = Debug
|
Metamaps.Debug = Debug
|
||||||
Metamaps.Filter = Filter
|
Metamaps.Filter = Filter
|
||||||
Metamaps.GlobalUI = GlobalUI
|
Metamaps.GlobalUI = GlobalUI
|
||||||
|
Metamaps.GlobalUI.Notifications = Notifications
|
||||||
Metamaps.GlobalUI.ReactApp = ReactApp
|
Metamaps.GlobalUI.ReactApp = ReactApp
|
||||||
Metamaps.GlobalUI.Search = Search
|
Metamaps.GlobalUI.Search = Search
|
||||||
Metamaps.GlobalUI.CreateMap = CreateMap
|
Metamaps.GlobalUI.CreateMap = CreateMap
|
||||||
|
|
248
frontend/src/components/ContextMenu.js
Normal file
248
frontend/src/components/ContextMenu.js
Normal file
|
@ -0,0 +1,248 @@
|
||||||
|
import React, { Component } from 'react'
|
||||||
|
import PropTypes from 'prop-types'
|
||||||
|
|
||||||
|
import MetacodeSelect from './MetacodeSelect'
|
||||||
|
|
||||||
|
class ContextMenu extends Component {
|
||||||
|
static propTypes = {
|
||||||
|
topicId: PropTypes.string,
|
||||||
|
mapId: PropTypes.string,
|
||||||
|
currentUser: PropTypes.object,
|
||||||
|
map: PropTypes.object,
|
||||||
|
contextNode: PropTypes.object,
|
||||||
|
contextEdge: PropTypes.object,
|
||||||
|
contextPos: PropTypes.object,
|
||||||
|
contextFetchingSiblingsData: PropTypes.bool,
|
||||||
|
contextSiblingsData: PropTypes.object,
|
||||||
|
metacodeSets: PropTypes.array,
|
||||||
|
contextDelete: PropTypes.func,
|
||||||
|
contextRemove: PropTypes.func,
|
||||||
|
contextHide: PropTypes.func,
|
||||||
|
contextCenterOn: PropTypes.func,
|
||||||
|
contextPopoutTopic: PropTypes.func,
|
||||||
|
contextUpdatePermissions: PropTypes.func,
|
||||||
|
contextOnMetacodeSelect: PropTypes.func,
|
||||||
|
contextFetchSiblings: PropTypes.func,
|
||||||
|
contextPopulateSiblings: PropTypes.func
|
||||||
|
}
|
||||||
|
|
||||||
|
constructor(props) {
|
||||||
|
super(props)
|
||||||
|
this.state = {
|
||||||
|
populateSiblingsSent: false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
getPositionData = () => {
|
||||||
|
const { contextPos } = this.props
|
||||||
|
let extraClasses = []
|
||||||
|
const position = {}
|
||||||
|
// TODO: make these dynamic values so that the ContextMenu can
|
||||||
|
// change height and still work properly
|
||||||
|
const RIGHTCLICK_WIDTH = 300
|
||||||
|
const RIGHTCLICK_HEIGHT = 144 // this does vary somewhat, but we can use static
|
||||||
|
const SUBMENUS_WIDTH = 256
|
||||||
|
const MAX_SUBMENU_HEIGHT = 270
|
||||||
|
const windowWidth = document.documentElement.clientWidth
|
||||||
|
const windowHeight = document.documentElement.clientHeight
|
||||||
|
|
||||||
|
if (windowWidth - contextPos.x < SUBMENUS_WIDTH) {
|
||||||
|
position.right = windowWidth - contextPos.x
|
||||||
|
extraClasses.push('moveMenusToLeft')
|
||||||
|
} else if (windowWidth - contextPos.x < RIGHTCLICK_WIDTH) {
|
||||||
|
position.right = windowWidth - contextPos.x
|
||||||
|
} else if (windowWidth - contextPos.x < RIGHTCLICK_WIDTH + SUBMENUS_WIDTH) {
|
||||||
|
position.left = contextPos.x
|
||||||
|
extraClasses.push('moveMenusToLeft')
|
||||||
|
} else {
|
||||||
|
position.left = contextPos.x
|
||||||
|
}
|
||||||
|
|
||||||
|
if (windowHeight - contextPos.y < MAX_SUBMENU_HEIGHT) {
|
||||||
|
position.bottom = windowHeight - contextPos.y
|
||||||
|
extraClasses.push('moveMenusUp')
|
||||||
|
} else if (windowHeight - contextPos.y < RIGHTCLICK_HEIGHT + MAX_SUBMENU_HEIGHT) {
|
||||||
|
position.top = contextPos.y
|
||||||
|
extraClasses.push('moveMenusUp')
|
||||||
|
} else {
|
||||||
|
position.top = contextPos.y
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
pos: {
|
||||||
|
top: position.top && position.top + 'px',
|
||||||
|
bottom: position.bottom && position.bottom + 'px',
|
||||||
|
left: position.left && position.left + 'px',
|
||||||
|
right: position.right && position.right + 'px'
|
||||||
|
},
|
||||||
|
extraClasses
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
hide = () => {
|
||||||
|
const { contextHide } = this.props
|
||||||
|
return <li className='rc-hide' onClick={contextHide}>
|
||||||
|
<div className='rc-icon' />
|
||||||
|
Hide until refresh
|
||||||
|
<div className='rc-keyboard'>Ctrl+H</div>
|
||||||
|
</li>
|
||||||
|
}
|
||||||
|
|
||||||
|
remove = () => {
|
||||||
|
const { contextRemove, map, currentUser } = this.props
|
||||||
|
const canEditMap = map && map.authorizeToEdit(currentUser)
|
||||||
|
if (!canEditMap) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
return <li className='rc-remove' onClick={contextRemove}>
|
||||||
|
<div className='rc-icon' />
|
||||||
|
Remove from map
|
||||||
|
<div className='rc-keyboard'>Ctrl+M</div>
|
||||||
|
</li>
|
||||||
|
}
|
||||||
|
|
||||||
|
delete = () => {
|
||||||
|
const { contextDelete, map, currentUser } = this.props
|
||||||
|
const canEditMap = map && map.authorizeToEdit(currentUser)
|
||||||
|
if (!canEditMap) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
return <li className='rc-delete' onClick={contextDelete}>
|
||||||
|
<div className='rc-icon' />
|
||||||
|
Delete
|
||||||
|
<div className='rc-keyboard'>Ctrl+D</div>
|
||||||
|
</li>
|
||||||
|
}
|
||||||
|
|
||||||
|
center = () => {
|
||||||
|
const { contextCenterOn, contextNode, topicId } = this.props
|
||||||
|
if (!(contextNode && topicId)) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
return <li className='rc-center'
|
||||||
|
onClick={() => contextCenterOn(contextNode.id)}>
|
||||||
|
<div className='rc-icon' />
|
||||||
|
Center this topic
|
||||||
|
<div className='rc-keyboard'>Alt+E</div>
|
||||||
|
</li>
|
||||||
|
}
|
||||||
|
|
||||||
|
popout = () => {
|
||||||
|
const { contextPopoutTopic, contextNode } = this.props
|
||||||
|
if (!contextNode) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
return <li className='rc-popout'
|
||||||
|
onClick={() => contextPopoutTopic(contextNode.id)}>
|
||||||
|
<div className='rc-icon' />
|
||||||
|
Open in new tab
|
||||||
|
</li>
|
||||||
|
}
|
||||||
|
|
||||||
|
permission = () => {
|
||||||
|
const { currentUser, contextUpdatePermissions } = this.props
|
||||||
|
if (!currentUser) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
return <li className='rc-permission'>
|
||||||
|
<div className='rc-icon' />
|
||||||
|
Change permissions
|
||||||
|
<ul>
|
||||||
|
<li className='changeP toCommons'
|
||||||
|
onClick={() => contextUpdatePermissions('commons')}>
|
||||||
|
<div className='rc-perm-icon' />
|
||||||
|
commons
|
||||||
|
</li>
|
||||||
|
<li className='changeP toPublic'
|
||||||
|
onClick={() => contextUpdatePermissions('public')}>
|
||||||
|
<div className='rc-perm-icon' />
|
||||||
|
public
|
||||||
|
</li>
|
||||||
|
<li className='changeP toPrivate'
|
||||||
|
onClick={() => contextUpdatePermissions('private')}>
|
||||||
|
<div className='rc-perm-icon' />
|
||||||
|
private
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
<div className='expandLi' />
|
||||||
|
</li>
|
||||||
|
}
|
||||||
|
|
||||||
|
metacode = () => {
|
||||||
|
const { metacodeSets, contextOnMetacodeSelect,
|
||||||
|
currentUser, contextNode } = this.props
|
||||||
|
if (!currentUser) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
return <li className='rc-metacode'>
|
||||||
|
<div className='rc-icon' />
|
||||||
|
Change metacode
|
||||||
|
<div id='metacodeOptionsWrapper'>
|
||||||
|
<MetacodeSelect
|
||||||
|
onMetacodeSelect={id => {
|
||||||
|
contextOnMetacodeSelect(contextNode && contextNode.id, id)
|
||||||
|
}}
|
||||||
|
metacodeSets={metacodeSets} />
|
||||||
|
</div>
|
||||||
|
<div className='expandLi' />
|
||||||
|
</li>
|
||||||
|
}
|
||||||
|
|
||||||
|
siblings = () => {
|
||||||
|
const { contextPopulateSiblings, contextFetchSiblings,
|
||||||
|
contextSiblingsData, contextFetchingSiblingsData,
|
||||||
|
topicId, contextNode } = this.props
|
||||||
|
const populateSiblings = () => {
|
||||||
|
if (!this.state.populateSiblingsSent) {
|
||||||
|
contextPopulateSiblings(contextNode.id)
|
||||||
|
this.setState({populateSiblingsSent: true})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!(contextNode && topicId)) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
return <li className='rc-siblings'
|
||||||
|
onMouseOver={populateSiblings}>
|
||||||
|
<div className='rc-icon' />
|
||||||
|
Reveal siblings
|
||||||
|
<ul id='fetchSiblingList'>
|
||||||
|
<li className='fetchAll'
|
||||||
|
onClick={() => contextFetchSiblings(contextNode)}>
|
||||||
|
All
|
||||||
|
<div className='rc-keyboard'>Alt+R</div>
|
||||||
|
</li>
|
||||||
|
{contextSiblingsData && Object.keys(contextSiblingsData).map(key => {
|
||||||
|
return <li key={key}
|
||||||
|
onClick={() => contextFetchSiblings(contextNode, key)}>
|
||||||
|
{contextSiblingsData[key]}
|
||||||
|
</li>
|
||||||
|
})}
|
||||||
|
{contextFetchingSiblingsData && <li id='loadingSiblings'>loading...</li>}
|
||||||
|
</ul>
|
||||||
|
<div className='expandLi' />
|
||||||
|
</li>
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
const { contextNode, currentUser, topicId } = this.props
|
||||||
|
const positionData = this.getPositionData()
|
||||||
|
const style = Object.assign({}, {position: 'absolute'}, positionData.pos)
|
||||||
|
const showSpacer = currentUser || (contextNode && topicId)
|
||||||
|
|
||||||
|
return <div style={style}
|
||||||
|
className={'rightclickmenu ' + positionData.extraClasses.join(' ')}>
|
||||||
|
<ul>
|
||||||
|
{this.hide()}
|
||||||
|
{this.remove()}
|
||||||
|
{this.delete()}
|
||||||
|
{this.center()}
|
||||||
|
{this.popout()}
|
||||||
|
{showSpacer && <li className='rc-spacer' />}
|
||||||
|
{this.permission()}
|
||||||
|
{this.metacode()}
|
||||||
|
{this.siblings()}
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default ContextMenu
|
|
@ -1,7 +1,7 @@
|
||||||
import React, { Component } from 'react'
|
import React, { Component } from 'react'
|
||||||
import PropTypes from 'prop-types'
|
import PropTypes from 'prop-types'
|
||||||
|
|
||||||
import MapInfoBox from '../MapView/MapInfoBox'
|
import MapInfoBox from '../routes/MapView/MapInfoBox'
|
||||||
|
|
||||||
class InfoAndHelp extends Component {
|
class InfoAndHelp extends Component {
|
||||||
static propTypes = {
|
static propTypes = {
|
92
frontend/src/components/Loading.js
Normal file
92
frontend/src/components/Loading.js
Normal file
|
@ -0,0 +1,92 @@
|
||||||
|
import React from 'react'
|
||||||
|
import PropTypes from 'prop-types'
|
||||||
|
|
||||||
|
// based on https://www.npmjs.com/package/react-loading-animation
|
||||||
|
|
||||||
|
const loadingStyle = {
|
||||||
|
position: 'relative',
|
||||||
|
margin: '0 auto',
|
||||||
|
width: '30px',
|
||||||
|
height: '30px'
|
||||||
|
}
|
||||||
|
|
||||||
|
const svgStyle = {
|
||||||
|
animation: 'rotate 2s linear infinite',
|
||||||
|
height: '100%',
|
||||||
|
transformOrigin: 'center center',
|
||||||
|
width: '100%',
|
||||||
|
position: 'absolute',
|
||||||
|
top: 0,
|
||||||
|
bottom: 0,
|
||||||
|
left: 0,
|
||||||
|
right: 0,
|
||||||
|
margin: 'auto'
|
||||||
|
}
|
||||||
|
|
||||||
|
const circleStyle = {
|
||||||
|
strokeDasharray: '1,200',
|
||||||
|
strokeDashoffset: '0',
|
||||||
|
animation: 'dash 1.5s ease-in-out infinite, color 6s ease-in-out infinite',
|
||||||
|
strokeLinecap: 'round'
|
||||||
|
}
|
||||||
|
|
||||||
|
const animation = `@keyframes rotate {
|
||||||
|
100% {
|
||||||
|
transform: rotate(360deg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@keyframes dash {
|
||||||
|
0% {
|
||||||
|
stroke-dasharray: 1,200;
|
||||||
|
stroke-dashoffset: 0;
|
||||||
|
}
|
||||||
|
50% {
|
||||||
|
stroke-dasharray: 89,200;
|
||||||
|
stroke-dashoffset: -35px;
|
||||||
|
}
|
||||||
|
100% {
|
||||||
|
stroke-dasharray: 89,200;
|
||||||
|
stroke-dashoffset: -124px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@keyframes color {
|
||||||
|
100%, 0% {
|
||||||
|
stroke: #a354cd;
|
||||||
|
}
|
||||||
|
50% {
|
||||||
|
stroke: #4fb5c0;
|
||||||
|
}
|
||||||
|
}`
|
||||||
|
|
||||||
|
class Loading extends React.Component {
|
||||||
|
static propTypes = {
|
||||||
|
style: PropTypes.object,
|
||||||
|
width: PropTypes.string,
|
||||||
|
height: PropTypes.string,
|
||||||
|
margin: PropTypes.string
|
||||||
|
}
|
||||||
|
|
||||||
|
static defaultProps = {
|
||||||
|
style: {},
|
||||||
|
width: '30px',
|
||||||
|
height: '30px',
|
||||||
|
margin: '0 auto'
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
let { width, height, margin, style } = this.props
|
||||||
|
|
||||||
|
loadingStyle.width = width
|
||||||
|
loadingStyle.height = height
|
||||||
|
loadingStyle.margin = margin
|
||||||
|
|
||||||
|
return <div style={Object.assign({}, loadingStyle, style)}>
|
||||||
|
<style>{animation}</style>
|
||||||
|
<svg style={svgStyle} viewBox="25 25 50 50">
|
||||||
|
<circle style={circleStyle} cx="50" cy="50" r="20" fill="none" strokeWidth="4" strokeMiterlimit="10"/>
|
||||||
|
</svg>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default Loading
|
|
@ -1,88 +0,0 @@
|
||||||
import React, { Component } from 'react'
|
|
||||||
import PropTypes from 'prop-types'
|
|
||||||
import { Link } from 'react-router'
|
|
||||||
import _ from 'lodash'
|
|
||||||
|
|
||||||
const MapLink = props => {
|
|
||||||
const { show, text, href, linkClass } = props
|
|
||||||
const otherProps = _.omit(props, ['show', 'text', 'href', 'linkClass'])
|
|
||||||
if (!show) {
|
|
||||||
return null
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Link { ...otherProps } to={href} className={linkClass}>
|
|
||||||
<div className="exploreMapsIcon"></div>
|
|
||||||
{text}
|
|
||||||
</Link>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
class Header extends Component {
|
|
||||||
render = () => {
|
|
||||||
const { signedIn, section, user } = this.props
|
|
||||||
|
|
||||||
const activeClass = (title) => {
|
|
||||||
let forClass = 'exploreMapsButton'
|
|
||||||
forClass += ' ' + title + 'Maps'
|
|
||||||
if (title === 'my' && section === 'mine' ||
|
|
||||||
title === section) forClass += ' active'
|
|
||||||
return forClass
|
|
||||||
}
|
|
||||||
|
|
||||||
const explore = section === 'mine' || section === 'active' || section === 'starred' || section === 'shared' || section === 'featured'
|
|
||||||
const mapper = section === 'mapper'
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div id="exploreMapsHeader">
|
|
||||||
<div className="exploreMapsBar exploreElement">
|
|
||||||
<div className="exploreMapsMenu">
|
|
||||||
<div className="exploreMapsCenter">
|
|
||||||
<MapLink show={explore}
|
|
||||||
href={signedIn ? '/' : '/explore/active'}
|
|
||||||
linkClass={activeClass('active')}
|
|
||||||
text="All Maps"
|
|
||||||
/>
|
|
||||||
<MapLink show={signedIn && explore}
|
|
||||||
href="/explore/mine"
|
|
||||||
linkClass={activeClass('my')}
|
|
||||||
text="My Maps"
|
|
||||||
/>
|
|
||||||
<MapLink show={signedIn && explore}
|
|
||||||
href="/explore/shared"
|
|
||||||
linkClass={activeClass('shared')}
|
|
||||||
text="Shared With Me"
|
|
||||||
/>
|
|
||||||
<MapLink show={signedIn && explore}
|
|
||||||
href="/explore/starred"
|
|
||||||
linkClass={activeClass('starred')}
|
|
||||||
text="Starred By Me"
|
|
||||||
/>
|
|
||||||
<MapLink show={!signedIn && explore}
|
|
||||||
href="/explore/featured"
|
|
||||||
linkClass={activeClass('featured')}
|
|
||||||
text="Featured Maps"
|
|
||||||
/>
|
|
||||||
|
|
||||||
{mapper ? (
|
|
||||||
<div className='exploreMapsButton active mapperButton'>
|
|
||||||
{user && <img className='exploreMapperImage' width='24' height='24' src={user.image} />}
|
|
||||||
{user && <div className='exploreMapperName'>{user.name}’s Maps</div>}
|
|
||||||
<div className='clearfloat'></div>
|
|
||||||
</div>
|
|
||||||
) : null }
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Header.propTypes = {
|
|
||||||
signedIn: PropTypes.bool.isRequired,
|
|
||||||
section: PropTypes.string.isRequired,
|
|
||||||
user: PropTypes.object
|
|
||||||
}
|
|
||||||
|
|
||||||
export default Header
|
|
|
@ -2,7 +2,7 @@ import React, { Component } from 'react'
|
||||||
import PropTypes from 'prop-types'
|
import PropTypes from 'prop-types'
|
||||||
import { Link } from 'react-router'
|
import { Link } from 'react-router'
|
||||||
|
|
||||||
import Sprite from '../common/Sprite'
|
import Sprite from './Sprite'
|
||||||
|
|
||||||
class MobileHeader extends Component {
|
class MobileHeader extends Component {
|
||||||
static propTypes = {
|
static propTypes = {
|
19
frontend/src/components/NavBar.js
Normal file
19
frontend/src/components/NavBar.js
Normal file
|
@ -0,0 +1,19 @@
|
||||||
|
import React, { Component } from 'react'
|
||||||
|
|
||||||
|
class NavBar extends Component {
|
||||||
|
render() {
|
||||||
|
return (
|
||||||
|
<div id="navBar">
|
||||||
|
<div className="navBarContainer">
|
||||||
|
<div className="navBarMenu">
|
||||||
|
<div className="navBarCenter">
|
||||||
|
{this.props.children}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default NavBar
|
65
frontend/src/components/NavBarLink.js
Normal file
65
frontend/src/components/NavBarLink.js
Normal file
|
@ -0,0 +1,65 @@
|
||||||
|
import React, { Component } from 'react'
|
||||||
|
import PropTypes from 'prop-types'
|
||||||
|
import { Link } from 'react-router'
|
||||||
|
import _ from 'lodash'
|
||||||
|
|
||||||
|
const PROP_LIST = [
|
||||||
|
'matchChildRoutes',
|
||||||
|
'hardReload',
|
||||||
|
'show',
|
||||||
|
'text',
|
||||||
|
'href',
|
||||||
|
'linkClass'
|
||||||
|
]
|
||||||
|
|
||||||
|
class NavBarLink extends Component {
|
||||||
|
static propTypes = {
|
||||||
|
matchChildRoutes: PropTypes.bool,
|
||||||
|
hardReload: PropTypes.bool,
|
||||||
|
show: PropTypes.bool,
|
||||||
|
text: PropTypes.string,
|
||||||
|
href: PropTypes.string,
|
||||||
|
linkClass: PropTypes.string
|
||||||
|
}
|
||||||
|
|
||||||
|
static contextTypes = {
|
||||||
|
location: PropTypes.object
|
||||||
|
}
|
||||||
|
|
||||||
|
render = () => {
|
||||||
|
const {
|
||||||
|
matchChildRoutes,
|
||||||
|
hardReload,
|
||||||
|
show,
|
||||||
|
text,
|
||||||
|
href,
|
||||||
|
linkClass
|
||||||
|
} = this.props
|
||||||
|
const { location } = this.context
|
||||||
|
const otherProps = _.omit(this.props, PROP_LIST)
|
||||||
|
const classes = ['navBarButton', linkClass]
|
||||||
|
const active = matchChildRoutes ?
|
||||||
|
location.pathname.startsWith(href) :
|
||||||
|
location.pathname === href
|
||||||
|
if (active) classes.push('active')
|
||||||
|
if (!show) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
if (hardReload) {
|
||||||
|
return (
|
||||||
|
<a { ...otherProps } href={href} className={classes.join(' ')}>
|
||||||
|
{linkClass && <div className="navBarIcon"></div>}
|
||||||
|
<div className="navBarLinkText">{text}</div>
|
||||||
|
</a>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
return (
|
||||||
|
<Link { ...otherProps } to={href} className={classes.join(' ')}>
|
||||||
|
{linkClass && <div className="navBarIcon"></div>}
|
||||||
|
<div className="navBarLinkText">{text}</div>
|
||||||
|
</Link>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default NavBarLink
|
127
frontend/src/components/Notification.js
Normal file
127
frontend/src/components/Notification.js
Normal file
|
@ -0,0 +1,127 @@
|
||||||
|
import React, { Component } from 'react'
|
||||||
|
import PropTypes from 'prop-types'
|
||||||
|
import outdent from 'outdent'
|
||||||
|
|
||||||
|
class Notification extends Component {
|
||||||
|
static propTypes = {
|
||||||
|
markAsRead: PropTypes.func,
|
||||||
|
markAsUnread: PropTypes.func,
|
||||||
|
notification: PropTypes.shape({
|
||||||
|
id: PropTypes.number.isRequired,
|
||||||
|
type: PropTypes.string.isRequired,
|
||||||
|
subject: PropTypes.string.isRequired,
|
||||||
|
is_read: PropTypes.bool.isRequired,
|
||||||
|
created_at: PropTypes.string.isRequired,
|
||||||
|
actor: PropTypes.shape({
|
||||||
|
id: PropTypes.number,
|
||||||
|
name: PropTypes.string,
|
||||||
|
image: PropTypes.string,
|
||||||
|
admin: PropTypes.boolean
|
||||||
|
}),
|
||||||
|
object: PropTypes.object,
|
||||||
|
map: PropTypes.object,
|
||||||
|
topic: PropTypes.object,
|
||||||
|
topic1: PropTypes.object,
|
||||||
|
topic2: PropTypes.object
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
notificationTextHtml = () => {
|
||||||
|
const { notification } = this.props
|
||||||
|
let map, topic, topic1, topic2
|
||||||
|
let result = `<div class='in-bold'>${notification.actor.name}</div>`
|
||||||
|
|
||||||
|
switch (notification.type) {
|
||||||
|
case 'ACCESS_APPROVED':
|
||||||
|
map = notification.data.map
|
||||||
|
result += outdent`granted your request to edit map
|
||||||
|
<span class='in-bold'>${map.name}</span>`
|
||||||
|
break
|
||||||
|
case 'ACCESS_REQUEST':
|
||||||
|
map = notification.data.map
|
||||||
|
result += outdent`wants permission to map with you on
|
||||||
|
<span class='in-bold'>${map.name}</span>`
|
||||||
|
if (!notification.data.object.answered) {
|
||||||
|
result += '<br /><div class="action">Offer a response</div>'
|
||||||
|
}
|
||||||
|
break
|
||||||
|
case 'INVITE_TO_EDIT':
|
||||||
|
map = notification.data.map
|
||||||
|
result += outdent`gave you edit access to map
|
||||||
|
<span class='in-bold'>${map.name}</span>`
|
||||||
|
break
|
||||||
|
case 'TOPIC_ADDED_TO_MAP':
|
||||||
|
map = notification.data.map
|
||||||
|
topic = notification.data.topic
|
||||||
|
result += outdent`added topic <span class='in-bold'>${topic.name}</span>
|
||||||
|
to map <span class='in-bold'>${map.name}</span>`
|
||||||
|
break
|
||||||
|
case 'TOPIC_CONNECTED_1':
|
||||||
|
topic1 = notification.data.topic1
|
||||||
|
topic2 = notification.data.topic2
|
||||||
|
result += outdent`connected <span class='in-bold'>${topic1.name}</span>
|
||||||
|
to <span class='in-bold'>${topic2.name}</span>`
|
||||||
|
break
|
||||||
|
case 'TOPIC_CONNECTED_2':
|
||||||
|
topic1 = notification.data.topic1
|
||||||
|
topic2 = notification.data.topic2
|
||||||
|
result += outdent`connected <span class='in-bold'>${topic2.name}</span>
|
||||||
|
to <span class='in-bold'>${topic1.name}</span>`
|
||||||
|
break
|
||||||
|
case 'MESSAGE_FROM_DEVS':
|
||||||
|
result += notification.subject
|
||||||
|
}
|
||||||
|
return {__html: result}
|
||||||
|
}
|
||||||
|
|
||||||
|
getDate = () => {
|
||||||
|
const { notification: {created_at} } = this.props
|
||||||
|
const months = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun',
|
||||||
|
'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec']
|
||||||
|
const created = new Date(created_at)
|
||||||
|
return `${months[created.getMonth()]} ${created.getDate()}`
|
||||||
|
}
|
||||||
|
|
||||||
|
markAsRead = () => {
|
||||||
|
const { notification, markAsRead } = this.props
|
||||||
|
markAsRead(notification.id)
|
||||||
|
}
|
||||||
|
|
||||||
|
markAsUnread = () => {
|
||||||
|
const { notification, markAsUnread } = this.props
|
||||||
|
markAsUnread(notification.id)
|
||||||
|
}
|
||||||
|
|
||||||
|
render = () => {
|
||||||
|
const { notification } = this.props
|
||||||
|
const classes = `notification ${notification.is_read ? 'read' : 'unread'}`
|
||||||
|
|
||||||
|
if (!notification.data.object) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
return <li className={classes}>
|
||||||
|
<a href={`/notifications/${notification.id}`}>
|
||||||
|
<div className='notification-actor'>
|
||||||
|
<img src={notification.actor.image} />
|
||||||
|
</div>
|
||||||
|
<div className='notification-body'
|
||||||
|
dangerouslySetInnerHTML={this.notificationTextHtml()} />
|
||||||
|
</a>
|
||||||
|
<div className='notification-read-unread'>
|
||||||
|
{!notification.is_read && <div onClick={this.markAsRead}>
|
||||||
|
mark read
|
||||||
|
</div>}
|
||||||
|
{notification.is_read && <div onClick={this.markAsUnread}>
|
||||||
|
mark unread
|
||||||
|
</div>}
|
||||||
|
</div>
|
||||||
|
<div className='notification-date'>
|
||||||
|
{this.getDate()}
|
||||||
|
</div>
|
||||||
|
<div className='clearfloat'></div>
|
||||||
|
</li>
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default Notification
|
74
frontend/src/components/NotificationBox.js
Normal file
74
frontend/src/components/NotificationBox.js
Normal file
|
@ -0,0 +1,74 @@
|
||||||
|
import React, { Component } from 'react'
|
||||||
|
import PropTypes from 'prop-types'
|
||||||
|
|
||||||
|
import onClickOutsideAddon from 'react-onclickoutside'
|
||||||
|
import Notification from './Notification'
|
||||||
|
import Loading from './Loading'
|
||||||
|
|
||||||
|
class NotificationBox extends Component {
|
||||||
|
static propTypes = {
|
||||||
|
notifications: PropTypes.array,
|
||||||
|
fetchNotifications: PropTypes.func.isRequired,
|
||||||
|
toggleNotificationsBox: PropTypes.func.isRequired,
|
||||||
|
markAsRead: PropTypes.func.isRequired,
|
||||||
|
markAsUnread: PropTypes.func.isRequired
|
||||||
|
}
|
||||||
|
|
||||||
|
componentDidMount = () => {
|
||||||
|
const { notifications, fetchNotifications } = this.props
|
||||||
|
if (!notifications) {
|
||||||
|
fetchNotifications()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
handleClickOutside = () => {
|
||||||
|
this.props.toggleNotificationsBox()
|
||||||
|
}
|
||||||
|
|
||||||
|
hasSomeNotifications = () => {
|
||||||
|
const { notifications } = this.props
|
||||||
|
return notifications && notifications.length > 0
|
||||||
|
}
|
||||||
|
|
||||||
|
showLoading = () => {
|
||||||
|
return <li><Loading margin='30px auto' /></li>
|
||||||
|
}
|
||||||
|
|
||||||
|
showEmpty = () => {
|
||||||
|
return <li className='notificationsEmpty'>
|
||||||
|
You have no notifications. <br />
|
||||||
|
More time for dancing.
|
||||||
|
</li>
|
||||||
|
}
|
||||||
|
|
||||||
|
showNotifications = () => {
|
||||||
|
const { notifications, markAsRead, markAsUnread } = this.props
|
||||||
|
if (!this.hasSomeNotifications()) {
|
||||||
|
return this.showEmpty()
|
||||||
|
}
|
||||||
|
return notifications.slice(0, 10).map(
|
||||||
|
n => <Notification notification={n}
|
||||||
|
markAsRead={markAsRead}
|
||||||
|
markAsUnread={markAsUnread}
|
||||||
|
key={`notification-${n.id}`} />
|
||||||
|
).concat([
|
||||||
|
<li key='notification-see-all'>
|
||||||
|
<a href='/notifications' className='notificationsBoxSeeAll'>
|
||||||
|
See all
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
])
|
||||||
|
}
|
||||||
|
|
||||||
|
render = () => {
|
||||||
|
const { notifications } = this.props
|
||||||
|
return <div className='notificationsBox'>
|
||||||
|
<div className='notificationsBoxTriangle' />
|
||||||
|
<ul className='notifications'>
|
||||||
|
{notifications ? this.showNotifications() : this.showLoading()}
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default onClickOutsideAddon(NotificationBox)
|
|
@ -4,18 +4,14 @@ import PropTypes from 'prop-types'
|
||||||
class NotificationIcon extends Component {
|
class NotificationIcon extends Component {
|
||||||
|
|
||||||
static propTypes = {
|
static propTypes = {
|
||||||
unreadNotificationsCount: PropTypes.number
|
unreadNotificationsCount: PropTypes.number,
|
||||||
}
|
toggleNotificationsBox: PropTypes.func
|
||||||
|
|
||||||
constructor(props) {
|
|
||||||
super(props)
|
|
||||||
|
|
||||||
this.state = {
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
render = () => {
|
render = () => {
|
||||||
|
const { toggleNotificationsBox } = this.props
|
||||||
let linkClasses = 'notificationsIcon upperRightEl upperRightIcon '
|
let linkClasses = 'notificationsIcon upperRightEl upperRightIcon '
|
||||||
|
linkClasses += 'ignore-react-onclickoutside '
|
||||||
|
|
||||||
if (this.props.unreadNotificationsCount > 0) {
|
if (this.props.unreadNotificationsCount > 0) {
|
||||||
linkClasses += 'unread'
|
linkClasses += 'unread'
|
||||||
|
@ -24,14 +20,14 @@ class NotificationIcon extends Component {
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<a className={linkClasses} href="/notifications" target="_blank">
|
<div className={linkClasses} onClick={toggleNotificationsBox}>
|
||||||
<div className="tooltipsUnder">
|
<div className="tooltipsUnder">
|
||||||
Notifications
|
Notifications
|
||||||
</div>
|
</div>
|
||||||
{this.props.unreadNotificationsCount === 0 ? null : (
|
{this.props.unreadNotificationsCount === 0 ? null : (
|
||||||
<div className="unread-notifications-dot"></div>
|
<div className="unread-notifications-dot"></div>
|
||||||
)}
|
)}
|
||||||
</a>
|
</div>
|
||||||
|
|
||||||
)
|
)
|
||||||
}
|
}
|
|
@ -38,6 +38,7 @@ class Desc extends Component {
|
||||||
change={this.props.onChange}
|
change={this.props.onChange}
|
||||||
className="riek_desc"
|
className="riek_desc"
|
||||||
classEditing="riek-editing"
|
classEditing="riek-editing"
|
||||||
|
rows="6"
|
||||||
editProps={{
|
editProps={{
|
||||||
onKeyPress: e => {
|
onKeyPress: e => {
|
||||||
const ENTER = 13
|
const ENTER = 13
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
import React, { Component } from 'react'
|
import React, { Component } from 'react'
|
||||||
import PropTypes from 'prop-types'
|
import PropTypes from 'prop-types'
|
||||||
|
|
||||||
import FilterBox from '../common/FilterBox'
|
import FilterBox from './FilterBox'
|
||||||
|
|
||||||
export default class UpperOptions extends Component {
|
export default class UpperOptions extends Component {
|
||||||
static propTypes = {
|
static propTypes = {
|
|
@ -4,31 +4,54 @@ import PropTypes from 'prop-types'
|
||||||
import AccountMenu from './AccountMenu'
|
import AccountMenu from './AccountMenu'
|
||||||
import LoginForm from './LoginForm'
|
import LoginForm from './LoginForm'
|
||||||
import NotificationIcon from './NotificationIcon'
|
import NotificationIcon from './NotificationIcon'
|
||||||
|
import NotificationBox from './NotificationBox'
|
||||||
|
|
||||||
class UpperRightUI extends Component {
|
class UpperRightUI extends Component {
|
||||||
static propTypes = {
|
static propTypes = {
|
||||||
currentUser: PropTypes.object,
|
currentUser: PropTypes.object,
|
||||||
signInPage: PropTypes.bool,
|
signInPage: PropTypes.bool,
|
||||||
unreadNotificationsCount: PropTypes.number,
|
unreadNotificationsCount: PropTypes.number,
|
||||||
|
fetchNotifications: PropTypes.func,
|
||||||
|
notifications: PropTypes.array,
|
||||||
|
markAsRead: PropTypes.func.isRequired,
|
||||||
|
markAsUnread: PropTypes.func.isRequired,
|
||||||
openInviteLightbox: PropTypes.func
|
openInviteLightbox: PropTypes.func
|
||||||
}
|
}
|
||||||
|
|
||||||
constructor(props) {
|
constructor(props) {
|
||||||
super(props)
|
super(props)
|
||||||
this.state = {accountBoxOpen: false}
|
this.state = {
|
||||||
|
accountBoxOpen: false,
|
||||||
|
notificationsBoxOpen: false
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
reset = () => {
|
reset = () => {
|
||||||
this.setState({accountBoxOpen: false})
|
this.setState({
|
||||||
|
accountBoxOpen: false,
|
||||||
|
notificationsBoxOpen: false
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
toggleAccountBox = () => {
|
toggleAccountBox = () => {
|
||||||
this.setState({accountBoxOpen: !this.state.accountBoxOpen})
|
this.setState({
|
||||||
|
accountBoxOpen: !this.state.accountBoxOpen,
|
||||||
|
notificationsBoxOpen: false
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
toggleNotificationsBox = () => {
|
||||||
|
this.setState({
|
||||||
|
notificationsBoxOpen: !this.state.notificationsBoxOpen,
|
||||||
|
accountBoxOpen: false
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
render () {
|
render () {
|
||||||
const { currentUser, signInPage, unreadNotificationsCount, openInviteLightbox } = this.props
|
const { currentUser, signInPage, unreadNotificationsCount,
|
||||||
const { accountBoxOpen } = this.state
|
notifications, fetchNotifications, openInviteLightbox,
|
||||||
|
markAsRead, markAsUnread } = this.props
|
||||||
|
const { accountBoxOpen, notificationsBoxOpen } = this.state
|
||||||
return <div className="upperRightUI">
|
return <div className="upperRightUI">
|
||||||
{currentUser && <a href="/maps/new" target="_blank" className="addMap upperRightEl upperRightIcon">
|
{currentUser && <a href="/maps/new" target="_blank" className="addMap upperRightEl upperRightIcon">
|
||||||
<div className="tooltipsUnder">
|
<div className="tooltipsUnder">
|
||||||
|
@ -36,7 +59,15 @@ class UpperRightUI extends Component {
|
||||||
</div>
|
</div>
|
||||||
</a>}
|
</a>}
|
||||||
{currentUser && <span id="notification_icon">
|
{currentUser && <span id="notification_icon">
|
||||||
<NotificationIcon unreadNotificationsCount={unreadNotificationsCount} />
|
<NotificationIcon
|
||||||
|
unreadNotificationsCount={unreadNotificationsCount}
|
||||||
|
toggleNotificationsBox={this.toggleNotificationsBox}/>
|
||||||
|
{notificationsBoxOpen && <NotificationBox
|
||||||
|
notifications={notifications}
|
||||||
|
fetchNotifications={fetchNotifications}
|
||||||
|
markAsRead={markAsRead}
|
||||||
|
markAsUnread={markAsUnread}
|
||||||
|
toggleNotificationsBox={this.toggleNotificationsBox}/>}
|
||||||
</span>}
|
</span>}
|
||||||
{!signInPage && <div className="sidebarAccount upperRightEl">
|
{!signInPage && <div className="sidebarAccount upperRightEl">
|
||||||
<div className="sidebarAccountIcon ignore-react-onclickoutside" onClick={this.toggleAccountBox}>
|
<div className="sidebarAccountIcon ignore-react-onclickoutside" onClick={this.toggleAccountBox}>
|
18
frontend/src/routes/Admin.js
Normal file
18
frontend/src/routes/Admin.js
Normal file
|
@ -0,0 +1,18 @@
|
||||||
|
import React, { Component } from 'react'
|
||||||
|
import NavBar from '../components/NavBar'
|
||||||
|
import NavBarLink from '../components/NavBarLink'
|
||||||
|
|
||||||
|
class Admin extends Component {
|
||||||
|
render = () => {
|
||||||
|
return (
|
||||||
|
<NavBar>
|
||||||
|
<NavBarLink show hardReload href="/metacode_sets" text="Metacode Sets" />
|
||||||
|
<NavBarLink show hardReload href="/metacode_sets/new" text="New Set" />
|
||||||
|
<NavBarLink show hardReload href="/metacodes" text="Metacodes" />
|
||||||
|
<NavBarLink show hardReload href="/metacodes/new" text="New Metacode" />
|
||||||
|
</NavBar>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default Admin
|
|
@ -1,16 +1,20 @@
|
||||||
import React, { Component } from 'react'
|
import React, { Component } from 'react'
|
||||||
import PropTypes from 'prop-types'
|
import PropTypes from 'prop-types'
|
||||||
|
|
||||||
import MobileHeader from './MobileHeader'
|
import MobileHeader from '../components/MobileHeader'
|
||||||
import UpperLeftUI from './UpperLeftUI'
|
import UpperLeftUI from '../components/UpperLeftUI'
|
||||||
import UpperRightUI from './UpperRightUI'
|
import UpperRightUI from '../components/UpperRightUI'
|
||||||
import Toast from './Toast'
|
import Toast from '../components/Toast'
|
||||||
|
|
||||||
class App extends Component {
|
class App extends Component {
|
||||||
static propTypes = {
|
static propTypes = {
|
||||||
children: PropTypes.object,
|
children: PropTypes.object,
|
||||||
toast: PropTypes.string,
|
toast: PropTypes.string,
|
||||||
unreadNotificationsCount: PropTypes.number,
|
unreadNotificationsCount: PropTypes.number,
|
||||||
|
notifications: PropTypes.array,
|
||||||
|
fetchNotifications: PropTypes.func,
|
||||||
|
markAsRead: PropTypes.func,
|
||||||
|
markAsUnread: PropTypes.func,
|
||||||
location: PropTypes.object,
|
location: PropTypes.object,
|
||||||
mobile: PropTypes.bool,
|
mobile: PropTypes.bool,
|
||||||
mobileTitle: PropTypes.string,
|
mobileTitle: PropTypes.string,
|
||||||
|
@ -34,16 +38,38 @@ class App extends Component {
|
||||||
return {location}
|
return {location}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
constructor (props) {
|
||||||
|
super(props)
|
||||||
|
this.state = {
|
||||||
|
yieldHTML: null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
componentDidMount () {
|
||||||
|
this.setYield()
|
||||||
|
}
|
||||||
|
|
||||||
|
setYield () {
|
||||||
|
const yieldHTML = document.getElementById('yield')
|
||||||
|
if (yieldHTML) {
|
||||||
|
this.setState({yieldHTML: yieldHTML.innerHTML})
|
||||||
|
document.body.removeChild(yieldHTML)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
render () {
|
render () {
|
||||||
const { children, toast, unreadNotificationsCount, openInviteLightbox,
|
const { children, toast, unreadNotificationsCount, openInviteLightbox,
|
||||||
mobile, mobileTitle, mobileTitleWidth, mobileTitleClick, location,
|
mobile, mobileTitle, mobileTitleWidth, mobileTitleClick, location,
|
||||||
map, userRequested, requestAnswered, requestApproved, serverData,
|
map, userRequested, requestAnswered, requestApproved, serverData,
|
||||||
onRequestAccess } = this.props
|
onRequestAccess, notifications, fetchNotifications,
|
||||||
|
markAsRead, markAsUnread } = this.props
|
||||||
|
const { yieldHTML } = this.state
|
||||||
const { pathname } = location || {}
|
const { pathname } = location || {}
|
||||||
// this fixes a bug that happens otherwise when you logout
|
// this fixes a bug that happens otherwise when you logout
|
||||||
const currentUser = this.props.currentUser && this.props.currentUser.id ? this.props.currentUser : null
|
const currentUser = this.props.currentUser && this.props.currentUser.id ? this.props.currentUser : null
|
||||||
const unauthedHome = pathname === '/' && !currentUser
|
const unauthedHome = pathname === '/' && !currentUser
|
||||||
return <div className="wrapper" id="wrapper">
|
return <div className="wrapper" id="wrapper">
|
||||||
|
{yieldHTML && <div id="yield" dangerouslySetInnerHTML={{__html: yieldHTML}}></div>}
|
||||||
{mobile && <MobileHeader currentUser={currentUser}
|
{mobile && <MobileHeader currentUser={currentUser}
|
||||||
unreadNotificationsCount={unreadNotificationsCount}
|
unreadNotificationsCount={unreadNotificationsCount}
|
||||||
mobileTitle={mobileTitle}
|
mobileTitle={mobileTitle}
|
||||||
|
@ -58,6 +84,10 @@ class App extends Component {
|
||||||
onRequestClick={onRequestAccess} />}
|
onRequestClick={onRequestAccess} />}
|
||||||
{!mobile && <UpperRightUI currentUser={currentUser}
|
{!mobile && <UpperRightUI currentUser={currentUser}
|
||||||
unreadNotificationsCount={unreadNotificationsCount}
|
unreadNotificationsCount={unreadNotificationsCount}
|
||||||
|
notifications={notifications}
|
||||||
|
fetchNotifications={fetchNotifications}
|
||||||
|
markAsRead={markAsRead}
|
||||||
|
markAsUnread={markAsUnread}
|
||||||
openInviteLightbox={openInviteLightbox}
|
openInviteLightbox={openInviteLightbox}
|
||||||
signInPage={pathname === '/login'} />}
|
signInPage={pathname === '/login'} />}
|
||||||
<Toast message={toast} />
|
<Toast message={toast} />
|
24
frontend/src/routes/Apps.js
Normal file
24
frontend/src/routes/Apps.js
Normal file
|
@ -0,0 +1,24 @@
|
||||||
|
import React, { Component } from 'react'
|
||||||
|
import PropTypes from 'prop-types'
|
||||||
|
import NavBar from '../components/NavBar'
|
||||||
|
import NavBarLink from '../components/NavBarLink'
|
||||||
|
|
||||||
|
class Apps extends Component {
|
||||||
|
render = () => {
|
||||||
|
const { currentUser } = this.props
|
||||||
|
|
||||||
|
return (
|
||||||
|
<NavBar>
|
||||||
|
{currentUser && currentUser.get('admin') && <NavBarLink show hardReload
|
||||||
|
matchChildRoutes href="/oauth/applications" linkClass="activeMaps"
|
||||||
|
text="Registered Apps" />}
|
||||||
|
<NavBarLink show hardReload matchChildRoutes
|
||||||
|
href="/oauth/authorized_applications"
|
||||||
|
linkClass="authedApps" text="Authorized Apps" />
|
||||||
|
<NavBarLink show href="/" linkClass="myMaps" text="Maps" />
|
||||||
|
</NavBar>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default Apps
|
|
@ -110,7 +110,7 @@ class MapChat extends Component {
|
||||||
</div>
|
</div>
|
||||||
{chatOpen && <div className="chat-panel">
|
{chatOpen && <div className="chat-panel">
|
||||||
<div className="junto-header">
|
<div className="junto-header">
|
||||||
PARTICIPANTS
|
Participants
|
||||||
<div onClick={this.toggleVideosShowing} className={`video-toggle ${videosShowing ? '' : 'active'}`} />
|
<div onClick={this.toggleVideosShowing} className={`video-toggle ${videosShowing ? '' : 'active'}`} />
|
||||||
<div onClick={this.toggleCursorsShowing} className={`cursor-toggle ${cursorsShowing ? '' : 'active'}`} />
|
<div onClick={this.toggleCursorsShowing} className={`cursor-toggle ${cursorsShowing ? '' : 'active'}`} />
|
||||||
</div>
|
</div>
|
||||||
|
@ -134,7 +134,7 @@ class MapChat extends Component {
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
<div className="chat-header">
|
<div className="chat-header">
|
||||||
CHAT
|
Chat
|
||||||
<div onClick={this.toggleAlertSound} className={`sound-toggle ${alertSound ? '' : 'active'}`}></div>
|
<div onClick={this.toggleAlertSound} className={`sound-toggle ${alertSound ? '' : 'active'}`}></div>
|
||||||
</div>
|
</div>
|
||||||
<div className="chat-messages" ref={div => { this.messagesDiv = div }}>
|
<div className="chat-messages" ref={div => { this.messagesDiv = div }}>
|
|
@ -1,17 +1,19 @@
|
||||||
import React, { Component } from 'react'
|
import React, { Component } from 'react'
|
||||||
import PropTypes from 'prop-types'
|
import PropTypes from 'prop-types'
|
||||||
|
|
||||||
import DataVis from '../common/DataVis'
|
import ContextMenu from '../../components/ContextMenu'
|
||||||
import UpperOptions from '../common/UpperOptions'
|
import DataVis from '../../components/DataVis'
|
||||||
import InfoAndHelp from '../common/InfoAndHelp'
|
import UpperOptions from '../../components/UpperOptions'
|
||||||
|
import InfoAndHelp from '../../components/InfoAndHelp'
|
||||||
import Instructions from './Instructions'
|
import Instructions from './Instructions'
|
||||||
import VisualizationControls from '../common/VisualizationControls'
|
import VisualizationControls from '../../components/VisualizationControls'
|
||||||
import MapChat from './MapChat'
|
import MapChat from './MapChat'
|
||||||
import TopicCard from '../TopicCard'
|
import TopicCard from '../../components/TopicCard'
|
||||||
|
|
||||||
export default class MapView extends Component {
|
export default class MapView extends Component {
|
||||||
|
|
||||||
static propTypes = {
|
static propTypes = {
|
||||||
|
contextMenu: PropTypes.bool,
|
||||||
mobile: PropTypes.bool,
|
mobile: PropTypes.bool,
|
||||||
mapId: PropTypes.string,
|
mapId: PropTypes.string,
|
||||||
map: PropTypes.object,
|
map: PropTypes.object,
|
||||||
|
@ -79,7 +81,8 @@ export default class MapView extends Component {
|
||||||
filterAllMappers, filterAllSynapses, filterData,
|
filterAllMappers, filterAllSynapses, filterData,
|
||||||
openImportLightbox, forkMap, openHelpLightbox,
|
openImportLightbox, forkMap, openHelpLightbox,
|
||||||
mapIsStarred, onMapStar, onMapUnstar, openTopic,
|
mapIsStarred, onMapStar, onMapUnstar, openTopic,
|
||||||
onZoomExtents, onZoomIn, onZoomOut, hasLearnedTopicCreation } = this.props
|
onZoomExtents, onZoomIn, onZoomOut, hasLearnedTopicCreation,
|
||||||
|
contextMenu } = this.props
|
||||||
const { chatOpen } = this.state
|
const { chatOpen } = this.state
|
||||||
const onChatOpen = () => {
|
const onChatOpen = () => {
|
||||||
this.setState({chatOpen: true})
|
this.setState({chatOpen: true})
|
||||||
|
@ -109,6 +112,7 @@ export default class MapView extends Component {
|
||||||
filterAllSynapses={filterAllSynapses} />
|
filterAllSynapses={filterAllSynapses} />
|
||||||
<DataVis />
|
<DataVis />
|
||||||
{openTopic && <TopicCard {...this.props} />}
|
{openTopic && <TopicCard {...this.props} />}
|
||||||
|
{contextMenu && <ContextMenu {...this.props} />}
|
||||||
{currentUser && <Instructions mobile={mobile} hasLearnedTopicCreation={hasLearnedTopicCreation} />}
|
{currentUser && <Instructions mobile={mobile} hasLearnedTopicCreation={hasLearnedTopicCreation} />}
|
||||||
{currentUser && <MapChat {...this.props} onOpen={onChatOpen} onClose={onChatClose} chatOpen={chatOpen} ref={x => this.mapChat = x} />}
|
{currentUser && <MapChat {...this.props} onOpen={onChatOpen} onClose={onChatClose} chatOpen={chatOpen} ref={x => this.mapChat = x} />}
|
||||||
<VisualizationControls map={map}
|
<VisualizationControls map={map}
|
58
frontend/src/routes/Maps/Header.js
Normal file
58
frontend/src/routes/Maps/Header.js
Normal file
|
@ -0,0 +1,58 @@
|
||||||
|
import React, { Component } from 'react'
|
||||||
|
import PropTypes from 'prop-types'
|
||||||
|
import NavBar from '../../components/NavBar'
|
||||||
|
import NavBarLink from '../../components/NavBarLink'
|
||||||
|
|
||||||
|
class Header extends Component {
|
||||||
|
render = () => {
|
||||||
|
const { signedIn, section, user } = this.props
|
||||||
|
|
||||||
|
const explore = section === 'mine' || section === 'active' || section === 'starred' || section === 'shared' || section === 'featured'
|
||||||
|
const mapper = section === 'mapper'
|
||||||
|
|
||||||
|
return (
|
||||||
|
<NavBar>
|
||||||
|
<NavBarLink show={explore}
|
||||||
|
href={signedIn ? '/' : '/explore/active'}
|
||||||
|
linkClass="activeMaps"
|
||||||
|
text="All Maps"
|
||||||
|
/>
|
||||||
|
<NavBarLink show={signedIn && explore}
|
||||||
|
href="/explore/mine"
|
||||||
|
linkClass="myMaps"
|
||||||
|
text="My Maps"
|
||||||
|
/>
|
||||||
|
<NavBarLink show={signedIn && explore}
|
||||||
|
href="/explore/shared"
|
||||||
|
linkClass="sharedMaps"
|
||||||
|
text="Shared With Me"
|
||||||
|
/>
|
||||||
|
<NavBarLink show={signedIn && explore}
|
||||||
|
href="/explore/starred"
|
||||||
|
linkClass="starredMaps"
|
||||||
|
text="Starred By Me"
|
||||||
|
/>
|
||||||
|
<NavBarLink show={!signedIn && explore}
|
||||||
|
href="/explore/featured"
|
||||||
|
linkClass="featuredMaps"
|
||||||
|
text="Featured Maps"
|
||||||
|
/>
|
||||||
|
{mapper ? (
|
||||||
|
<div className='navBarButton active mapperButton'>
|
||||||
|
{user && <img className='exploreMapperImage' width='24' height='24' src={user.image} />}
|
||||||
|
{user && <div className='exploreMapperName'>{user.name}’s Maps</div>}
|
||||||
|
<div className='clearfloat'></div>
|
||||||
|
</div>
|
||||||
|
) : null }
|
||||||
|
</NavBar>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Header.propTypes = {
|
||||||
|
signedIn: PropTypes.bool.isRequired,
|
||||||
|
section: PropTypes.string.isRequired,
|
||||||
|
user: PropTypes.object
|
||||||
|
}
|
||||||
|
|
||||||
|
export default Header
|
|
@ -6,7 +6,6 @@ import MapperCard from './MapperCard'
|
||||||
import MapCard from './MapCard'
|
import MapCard from './MapCard'
|
||||||
|
|
||||||
class Maps extends Component {
|
class Maps extends Component {
|
||||||
|
|
||||||
static propTypes = {
|
static propTypes = {
|
||||||
section: PropTypes.string,
|
section: PropTypes.string,
|
||||||
maps: PropTypes.object,
|
maps: PropTypes.object,
|
17
frontend/src/routes/Notifications.js
Normal file
17
frontend/src/routes/Notifications.js
Normal file
|
@ -0,0 +1,17 @@
|
||||||
|
import React, { Component } from 'react'
|
||||||
|
import NavBar from '../components/NavBar'
|
||||||
|
import NavBarLink from '../components/NavBarLink'
|
||||||
|
|
||||||
|
class Notifications extends Component {
|
||||||
|
render = () => {
|
||||||
|
return (
|
||||||
|
<NavBar>
|
||||||
|
<NavBarLink show matchChildRoutes href="/notifications"
|
||||||
|
linkClass="notificationsLink" text="Notifications" />
|
||||||
|
<NavBarLink show href="/" linkClass="myMaps" text="Maps" />
|
||||||
|
</NavBar>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default Notifications
|
|
@ -1,15 +1,17 @@
|
||||||
import React, { Component } from 'react'
|
import React, { Component } from 'react'
|
||||||
import PropTypes from 'prop-types'
|
import PropTypes from 'prop-types'
|
||||||
|
|
||||||
import DataVis from '../common/DataVis'
|
import ContextMenu from '../components/ContextMenu'
|
||||||
import UpperOptions from '../common/UpperOptions'
|
import DataVis from '../components/DataVis'
|
||||||
import InfoAndHelp from '../common/InfoAndHelp'
|
import UpperOptions from '../components/UpperOptions'
|
||||||
import VisualizationControls from '../common/VisualizationControls'
|
import InfoAndHelp from '../components/InfoAndHelp'
|
||||||
import TopicCard from '../TopicCard'
|
import VisualizationControls from '../components/VisualizationControls'
|
||||||
|
import TopicCard from '../components/TopicCard'
|
||||||
|
|
||||||
export default class TopicView extends Component {
|
export default class TopicView extends Component {
|
||||||
|
|
||||||
static propTypes = {
|
static propTypes = {
|
||||||
|
contextMenu: PropTypes.bool,
|
||||||
mobile: PropTypes.bool,
|
mobile: PropTypes.bool,
|
||||||
topicId: PropTypes.string,
|
topicId: PropTypes.string,
|
||||||
topic: PropTypes.object,
|
topic: PropTypes.object,
|
||||||
|
@ -55,7 +57,7 @@ export default class TopicView extends Component {
|
||||||
const { mobile, topic, currentUser, allForFiltering, visibleForFiltering,
|
const { mobile, topic, currentUser, allForFiltering, visibleForFiltering,
|
||||||
toggleMetacode, toggleMapper, toggleSynapse, filterAllMetacodes,
|
toggleMetacode, toggleMapper, toggleSynapse, filterAllMetacodes,
|
||||||
filterAllMappers, filterAllSynapses, filterData, forkMap,
|
filterAllMappers, filterAllSynapses, filterData, forkMap,
|
||||||
openHelpLightbox, onZoomIn, onZoomOut } = this.props
|
openHelpLightbox, onZoomIn, onZoomOut, contextMenu } = this.props
|
||||||
// TODO: stop using {...this.props} and make explicit
|
// TODO: stop using {...this.props} and make explicit
|
||||||
return <div className="topicWrapper">
|
return <div className="topicWrapper">
|
||||||
<UpperOptions ref={x => this.upperOptions = x}
|
<UpperOptions ref={x => this.upperOptions = x}
|
||||||
|
@ -73,6 +75,7 @@ export default class TopicView extends Component {
|
||||||
filterAllSynapses={filterAllSynapses} />
|
filterAllSynapses={filterAllSynapses} />
|
||||||
<DataVis />
|
<DataVis />
|
||||||
<TopicCard {...this.props} />
|
<TopicCard {...this.props} />
|
||||||
|
{contextMenu && <ContextMenu {...this.props} />}
|
||||||
<VisualizationControls onClickZoomIn={onZoomIn}
|
<VisualizationControls onClickZoomIn={onZoomIn}
|
||||||
onClickZoomOut={onZoomOut} />
|
onClickZoomOut={onZoomOut} />
|
||||||
<InfoAndHelp topic={topic}
|
<InfoAndHelp topic={topic}
|
|
@ -1,8 +1,11 @@
|
||||||
import React from 'react'
|
import React from 'react'
|
||||||
import { Route, IndexRoute } from 'react-router'
|
import { Route, IndexRoute } from 'react-router'
|
||||||
|
import Admin from './Admin'
|
||||||
import App from './App'
|
import App from './App'
|
||||||
|
import Apps from './Apps'
|
||||||
import Maps from './Maps'
|
import Maps from './Maps'
|
||||||
import MapView from './MapView'
|
import MapView from './MapView'
|
||||||
|
import Notifications from './Notifications'
|
||||||
import TopicView from './TopicView'
|
import TopicView from './TopicView'
|
||||||
|
|
||||||
function nullComponent(props) {
|
function nullComponent(props) {
|
||||||
|
@ -31,8 +34,8 @@ export default function makeRoutes (currentUser) {
|
||||||
<Route path="join" component={nullComponent} />
|
<Route path="join" component={nullComponent} />
|
||||||
<Route path="request" component={nullComponent} />
|
<Route path="request" component={nullComponent} />
|
||||||
<Route path="notifications">
|
<Route path="notifications">
|
||||||
<IndexRoute component={nullComponent} />
|
<IndexRoute component={Notifications} />
|
||||||
<Route path=":id" component={nullComponent} />
|
<Route path=":id" component={Notifications} />
|
||||||
</Route>
|
</Route>
|
||||||
<Route path="users">
|
<Route path="users">
|
||||||
<Route path=":id/edit" component={nullComponent} />
|
<Route path=":id/edit" component={nullComponent} />
|
||||||
|
@ -40,30 +43,30 @@ export default function makeRoutes (currentUser) {
|
||||||
<Route path="password/edit" component={nullComponent} />
|
<Route path="password/edit" component={nullComponent} />
|
||||||
</Route>
|
</Route>
|
||||||
<Route path="metacodes">
|
<Route path="metacodes">
|
||||||
<IndexRoute component={nullComponent} />
|
<IndexRoute component={Admin} />
|
||||||
<Route path="new" component={nullComponent} />
|
<Route path="new" component={Admin} />
|
||||||
<Route path=":id/edit" component={nullComponent} />
|
<Route path=":id/edit" component={Admin} />
|
||||||
</Route>
|
</Route>
|
||||||
<Route path="metacode_sets">
|
<Route path="metacode_sets">
|
||||||
<IndexRoute component={nullComponent} />
|
<IndexRoute component={Admin} />
|
||||||
<Route path="new" component={nullComponent} />
|
<Route path="new" component={Admin} />
|
||||||
<Route path=":id/edit" component={nullComponent} />
|
<Route path=":id/edit" component={Admin} />
|
||||||
</Route>
|
</Route>
|
||||||
<Route path="oauth">
|
<Route path="oauth">
|
||||||
<Route path="token/info" component={nullComponent} />
|
<Route path="token/info" component={Apps} />
|
||||||
<Route path="authorize">
|
<Route path="authorize">
|
||||||
<IndexRoute component={nullComponent} />
|
<IndexRoute component={nullComponent} />
|
||||||
<Route path=":code" component={nullComponent} />
|
<Route path=":code" component={nullComponent} />
|
||||||
</Route>
|
</Route>
|
||||||
<Route path="authorized_applications">
|
<Route path="authorized_applications">
|
||||||
<IndexRoute component={nullComponent} />
|
<IndexRoute component={Apps} />
|
||||||
<Route path=":id" component={nullComponent} />
|
<Route path=":id" component={Apps} />
|
||||||
</Route>
|
</Route>
|
||||||
<Route path="applications">
|
<Route path="applications">
|
||||||
<IndexRoute component={nullComponent} />
|
<IndexRoute component={Apps} />
|
||||||
<Route path="new" component={nullComponent} />
|
<Route path="new" component={Apps} />
|
||||||
<Route path=":id" component={nullComponent} />
|
<Route path=":id" component={Apps} />
|
||||||
<Route path=":id/edit" component={nullComponent} />
|
<Route path=":id/edit" component={Apps} />
|
||||||
</Route>
|
</Route>
|
||||||
</Route>
|
</Route>
|
||||||
</Route>
|
</Route>
|
|
@ -4,8 +4,8 @@ import { expect } from 'chai'
|
||||||
import { shallow } from 'enzyme'
|
import { shallow } from 'enzyme'
|
||||||
import sinon from 'sinon'
|
import sinon from 'sinon'
|
||||||
|
|
||||||
import InfoAndHelp from '../../../src/components/common/InfoAndHelp.js'
|
import InfoAndHelp from '../../src/components/InfoAndHelp.js'
|
||||||
import MapInfoBox from '../../../src/components/MapView/MapInfoBox.js'
|
import MapInfoBox from '../../src/routes/MapView/MapInfoBox.js'
|
||||||
|
|
||||||
function assertTooltip({ wrapper, description, cssClass, tooltipText, callback }) {
|
function assertTooltip({ wrapper, description, cssClass, tooltipText, callback }) {
|
||||||
it(description, function() {
|
it(description, function() {
|
|
@ -1,6 +1,6 @@
|
||||||
/* global describe, it */
|
/* global describe, it */
|
||||||
import React from 'react'
|
import React from 'react'
|
||||||
import ImportDialogBox from '../../../src/components/MapView/ImportDialogBox.js'
|
import ImportDialogBox from '../../../src/routes/MapView/ImportDialogBox.js'
|
||||||
import Dropzone from 'react-dropzone'
|
import Dropzone from 'react-dropzone'
|
||||||
import { expect } from 'chai'
|
import { expect } from 'chai'
|
||||||
import { shallow } from 'enzyme'
|
import { shallow } from 'enzyme'
|
|
@ -3,8 +3,13 @@ namespace :assets do
|
||||||
task :js_compile do
|
task :js_compile do
|
||||||
system 'npm install'
|
system 'npm install'
|
||||||
system 'npm run build'
|
system 'npm run build'
|
||||||
|
end
|
||||||
|
|
||||||
|
task :production_ready do
|
||||||
system 'bin/build-apidocs.sh' if Rails.env.production?
|
system 'bin/build-apidocs.sh' if Rails.env.production?
|
||||||
|
Rake::Task['perms:fix'].invoke if Rails.env.production?
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
Rake::Task[:'assets:precompile'].enhance([:'assets:js_compile'])
|
Rake::Task[:'assets:precompile'].enhance([:'assets:js_compile'])
|
||||||
|
Rake::Task[:'assets:precompile'].enhance([:'assets:production_ready'])
|
||||||
|
|
|
@ -21,6 +21,7 @@
|
||||||
"homepage": "https://github.com/metamaps/metamaps#readme",
|
"homepage": "https://github.com/metamaps/metamaps#readme",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"ajaxq": "0.0.7",
|
"ajaxq": "0.0.7",
|
||||||
|
"async": "2.5.0",
|
||||||
"attachmediastream": "2.0.0",
|
"attachmediastream": "2.0.0",
|
||||||
"autolinker": "1.4.3",
|
"autolinker": "1.4.3",
|
||||||
"babel-cli": "6.26.0",
|
"babel-cli": "6.26.0",
|
||||||
|
@ -72,6 +73,6 @@
|
||||||
"sinon": "2.2.0"
|
"sinon": "2.2.0"
|
||||||
},
|
},
|
||||||
"optionalDependencies": {
|
"optionalDependencies": {
|
||||||
"raml2html": "6.4.1"
|
"raml2html": "4.0.5"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,17 @@
|
||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
require 'rails_helper'
|
||||||
|
|
||||||
|
RSpec.describe 'map_activity_mailer/daily_summary.html.erb' do
|
||||||
|
it 'displays messages sent' do
|
||||||
|
assign(:user, create(:user))
|
||||||
|
assign(:map, create(:map))
|
||||||
|
assign(:summary_data, stats: {
|
||||||
|
messages_sent: 5
|
||||||
|
})
|
||||||
|
|
||||||
|
render
|
||||||
|
|
||||||
|
expect(rendered).to match(/5 messages/)
|
||||||
|
end
|
||||||
|
end
|
Loading…
Reference in a new issue