diff --git a/README.md b/README.md index 38401cd9..8d3ad916 100644 --- a/README.md +++ b/README.md @@ -27,14 +27,21 @@ Checklist - [x] Figure out how authentication of requests from the frontend to the API works - [x] Figure out how to combine the nodejs realtime server into server.js - [x] Notifications: make sure loading states are working for popup and page +- [x] Request unreadNotificationCount +- [x] Request invite code +- [x] Request user object itself +- [x] Load the metacodes + +- [ ] create topic form +- [ ] create synapse form +- [ ] move ImportDialog lightbox into main app - [ ] Notifications: make sure notifications either look nice, or redirect - [ ] Notifications: pagination - [ ] Get actioncable working - [ ] lightboxes -- [x] Request unreadNotificationCount -- [x] Request invite code -- [x] Request user object itself - [ ] About lightbox +- [ ] Switch Metacodes lightbox / component +- [ ] Fork map lightbox / component - [ ] break up index.html into parts - [ ] Handle CSS metacode colors - [ ] Fix Request An Invite page @@ -53,7 +60,5 @@ Checklist - [ ] authorize - [ ] user passwords - [ ] Modify the RubyOnRails app to only serve JSON responses, no HTML pages anymore -- [ ] Modify the RubyOnRails app to include an endpoint that responds with basic data the front end needs to display (such as the invite code for the user, and the current metamaps build) (a bunch of the data found here: https://github.com/metamaps/metamaps/blob/frontendonly/Metamaps.ServerData.js.erb) - [ ] Modify the frontend to request that data from the API which is necessary at first to load the page -- [x] Load the metacodes -- [ ] Load the metacode sets + - [ ] Load the metacode sets diff --git a/sass/notifications.scss b/sass/notifications.scss index 3cec306b..777d724e 100644 --- a/sass/notifications.scss +++ b/sass/notifications.scss @@ -117,6 +117,7 @@ $unread_notifications_dot_size: 8px; height: 32px; border-radius: 16px; vertical-align: middle; + margin-right: 8px; } .button { @@ -127,6 +128,7 @@ $unread_notifications_dot_size: 8px; } &.decline { + margin-left: 8px; background: #DB5D5D; &:hover { background: #DC4B4B; @@ -139,6 +141,10 @@ $unread_notifications_dot_size: 8px; margin: 1em auto; line-height: 20px; } + + .accessRequestError { + color: #DB5D5D; + } } } diff --git a/src/Metamaps/Create.js b/src/Metamaps/Create.js index 454ab331..b18d71cc 100644 --- a/src/Metamaps/Create.js +++ b/src/Metamaps/Create.js @@ -19,8 +19,6 @@ const Create = { newSelectedMetacodes: [], init: function() { var self = Create - self.newTopic.init() - self.newSynapse.init() // // SWITCHING METACODE SETS diff --git a/src/Metamaps/DataFetcher.js b/src/Metamaps/DataFetcher.js index 3bea2f2c..7a9f2fae 100644 --- a/src/Metamaps/DataFetcher.js +++ b/src/Metamaps/DataFetcher.js @@ -2,6 +2,14 @@ function fetchWithCookies(url) { return fetch(url, { credentials: 'same-origin' }) } +function postWithCookies(url, data = {}) { + return fetch(url, { + credentials: 'same-origin', + method: 'POST', + body: JSON.stringify(data) + }) +} + async function getMetacodes() { const res = await fetchWithCookies('/metacodes.json') const data = await res.json() @@ -14,7 +22,19 @@ async function getCurrentUser() { return data } +async function approveAccessRequest(mapId, requestId) { + const res = await postWithCookies(`/maps/${mapId}/approve_access/${requestId}`) + return res.status === 200 +} + +async function denyAccessRequest(mapId, requestId) { + const res = await postWithCookies(`/maps/${mapId}/deny_access/${requestId}`) + return res.status === 200 +} + module.exports = { getMetacodes, - getCurrentUser + getCurrentUser, + approveAccessRequest, + denyAccessRequest } \ No newline at end of file diff --git a/src/Metamaps/GlobalUI/Notifications.js b/src/Metamaps/GlobalUI/Notifications.js index 1b93d2cd..0ab962a8 100644 --- a/src/Metamaps/GlobalUI/Notifications.js +++ b/src/Metamaps/GlobalUI/Notifications.js @@ -7,7 +7,9 @@ const Notifications = { notificationsLoading: false, unreadNotificationsCount: 0, init: serverData => { - Notifications.unreadNotificationsCount = serverData.ActiveMapper.unread_notifications_count + if (serverData.ActiveMapper) { + Notifications.unreadNotificationsCount = serverData.ActiveMapper.unread_notifications_count + } }, fetchNotifications: render => { Notifications.notificationsLoading = true diff --git a/src/Metamaps/GlobalUI/ReactApp.js b/src/Metamaps/GlobalUI/ReactApp.js index cb5dc947..08752098 100644 --- a/src/Metamaps/GlobalUI/ReactApp.js +++ b/src/Metamaps/GlobalUI/ReactApp.js @@ -10,7 +10,9 @@ import { notifyUser } from './index.js' import ImportDialog from './ImportDialog' import Notifications from './Notifications' import Active from '../Active' +import Create from '../Create' import DataModel from '../DataModel' +import DataFetcher from '../DataFetcher' import { ExploreMaps, ChatView, TopicCard, ContextMenu } from '../Views' import Filter from '../Filter' import JIT from '../JIT' @@ -107,7 +109,9 @@ const ReactApp = { fetchNotifications: apply(Notifications.fetchNotifications, ReactApp.render), fetchNotification: apply(Notifications.fetchNotification, ReactApp.render), markAsRead: apply(Notifications.markAsRead, ReactApp.render), - markAsUnread: apply(Notifications.markAsUnread, ReactApp.render) + markAsUnread: apply(Notifications.markAsUnread, ReactApp.render), + denyAccessRequest: DataFetcher.denyAccessRequest, + approveAccessRequest: DataFetcher.approveAccessRequest }, self.getMapProps(), self.getTopicProps(), @@ -134,9 +138,12 @@ const ReactApp = { toggleMapInfoBox: InfoBox.toggleBox, infoBoxHtml: InfoBox.html, openImportLightbox: () => ImportDialog.show(), + openMetacodeSwitcher: () => self.openLightbox('metacodeSwitcher'), forkMap: Map.fork, onMapStar: Map.star, - onMapUnstar: Map.unstar + onMapUnstar: Map.unstar, + initNewTopic: Create.newTopic.init, + initNewSynapse: Create.newSynapse.init } }, getCommonProps: function() { diff --git a/src/components/LightBoxes/CheatSheet.js b/src/components/LightBoxes/CheatSheet.js index c82a1504..ade782fe 100644 --- a/src/components/LightBoxes/CheatSheet.js +++ b/src/components/LightBoxes/CheatSheet.js @@ -3,7 +3,7 @@ import React, { Component } from 'react' class CheatSheet extends Component { render = () => { return ( -
+

HELP

-
Enter Topic (radial) View: Click on a Topic result from Search, or click the synapse icon inside open Topic Card on map
+
Enter Topic (radial) View: Click on a Topic result from Search, or click the synapse icon inside open Topic Card on map
Recenter Topics around chosen Topic: Alt + click on the topic OR Alt + E
Reveal the siblings for a Topic: Right-click and choose 'Reveal siblings' OR Alt + R
Center topic and reveal siblings: Alt + T
@@ -60,7 +60,7 @@ class CheatSheet extends Component { Change Topic permission: Click on 'Permission' icon (only for topic creator)
- Open Topic view: Click on icon within topic card bar + Open Topic view: Click on icon within topic card bar
Close Topic card: Click on canvas @@ -131,7 +131,7 @@ class CheatSheet extends Component {
- +

For more information about Metamaps.cc, visit our Knowledge Base or skip directly to a section by clicking on one of the categories below.

diff --git a/src/components/NewSynapse.js b/src/components/NewSynapse.js new file mode 100644 index 00000000..10e926f8 --- /dev/null +++ b/src/components/NewSynapse.js @@ -0,0 +1,18 @@ +import React, { Component } from 'react' + +class NewSynapse extends Component { + componentDidMount() { + this.props.initNewSynapse() + } + + render = () => { + return ( +
+ + +
+ ) + } +} + +export default NewSynapse \ No newline at end of file diff --git a/src/components/NewTopic.js b/src/components/NewTopic.js new file mode 100644 index 00000000..3e33af27 --- /dev/null +++ b/src/components/NewTopic.js @@ -0,0 +1,44 @@ +import React, { Component } from 'react' + +class NewTopic extends Component { + componentDidMount() { + this.props.initNewTopic() + } + + render = () => { + const metacodes = [ + { + "id": 1, + "name": "Action", + "created_at": "2017-03-04T17:33:07.394Z", + "updated_at": "2017-03-04T17:33:07.394Z", + "color": "#BD6C85", + "icon": "https://s3.amazonaws.com/metamaps-assets/metacodes/blueprint/96px/bp_action.png" + } + ] + return ( +
+ +
this.props.openMetacodeSwitcher()}> +
Switch Metacodes
+
+ +
+
Pin Open
+
Unpin
+
+ +
+ {metacodes.map(m => {m.name})} +
+ + + +
+
+
+ ) + } +} + +export default NewTopic \ No newline at end of file diff --git a/src/routes/MapView/index.js b/src/routes/MapView/index.js index 027b395d..64a1a05e 100644 --- a/src/routes/MapView/index.js +++ b/src/routes/MapView/index.js @@ -9,6 +9,8 @@ import Instructions from './Instructions' import VisualizationControls from '../../components/VisualizationControls' import MapChat from './MapChat' import TopicCard from '../../components/TopicCard' +import NewTopic from '../../components/NewTopic' +import NewSynapse from '../../components/NewSynapse' export default class MapView extends Component { @@ -82,7 +84,7 @@ export default class MapView extends Component { openImportLightbox, forkMap, openHelpLightbox, mapIsStarred, onMapStar, onMapUnstar, openTopic, onZoomExtents, onZoomIn, onZoomOut, hasLearnedTopicCreation, - contextMenu } = this.props + contextMenu, initNewTopic, initNewSynapse, openMetacodeSwitcher } = this.props const { chatOpen } = this.state const onChatOpen = () => { this.setState({chatOpen: true}) @@ -111,6 +113,8 @@ export default class MapView extends Component { filterAllMappers={filterAllMappers} filterAllSynapses={filterAllSynapses} /> + + {openTopic && } {contextMenu && } {currentUser && } diff --git a/src/routes/Notifications/NotificationPage.js b/src/routes/Notifications/NotificationPage.js index f85ab89f..8df550c4 100644 --- a/src/routes/Notifications/NotificationPage.js +++ b/src/routes/Notifications/NotificationPage.js @@ -7,12 +7,18 @@ import LoadingPage from '../helpers/LoadingPage' import Loading from '../../components/Loading' import NotificationBody from '../../components/NotificationBody' -/* TODO: - allow / decline access loading states - make backend serve HTML for raw body too -*/ - class NotificationPage extends Component { + constructor(props) { + super(props) + this.state = { + allowPending: false, + declinePending: false, + allowed: false, + declined: false, + error: false + } + } + componentDidMount() { // the notification id const id = parseInt(this.props.params.id, 10) @@ -20,6 +26,35 @@ class NotificationPage extends Component { this.props.fetchNotification(id) } } + + deny = async () => { + const id = parseInt(this.props.params.id, 10) + const notification = this.props.notifications.find(n => n.id === id) + const request = notification.data.object + const map = notification.data.map + this.setState({ declinePending: true }) + const success = await this.props.denyAccessRequest(map.id, request.id) + if (success) { + this.setState({ declined: true, declinePending: false }) + } else { + this.setState({ error: true }) + } + } + + approve = async () => { + const id = parseInt(this.props.params.id, 10) + const notification = this.props.notifications.find(n => n.id === id) + const request = notification.data.object + const map = notification.data.map + this.setState({ allowPending: true }) + const success = await this.props.approveAccessRequest(map.id, request.id) + if (success) { + this.setState({ allowed: true, allowPending: false }) + } else { + this.setState({ error: true }) + } + } + render = () => { const id = parseInt(this.props.params.id, 10) const notification = this.props.notifications.find(n => n.id === id) @@ -36,6 +71,7 @@ class NotificationPage extends Component { const subject = notification.type === MAP_ACCESS_REQUEST ? ({notification.actor.name} wants to collaborate on map { map.name }) : notification.subject + const localAnswered = this.state.allowed || this.state.declined return (
@@ -48,21 +84,34 @@ class NotificationPage extends Component { {subject} {notification.type === MAP_ACCESS_REQUEST &&
-

- {request.answered && +

+ {this.state.error &&
There was an error, please refresh and try again
} + {request.answered &&
{request.approved && You already responded to this access request, and allowed access.} {!request.approved && 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.} - } - {!request.answered && +
} + {!localAnswered && !request.answered &&
- Allow - Decline - } -

- Go to map -    - View mapper profile + {!this.state.declined && !this.state.declinePending && } + {!this.state.allowed && !this.state.allowPending && } +
} + {this.state.allowed &&
+ {notification.actor.name} has been shared on the map and notified. +
} + {this.state.declined &&
+ Fair enough. +
} +
+
+ Go to map +    + View mapper profile +
} {notification.type !== MAP_ACCESS_REQUEST && }