diff --git a/.babelrc b/.babelrc index f9151299..4b82985e 100644 --- a/.babelrc +++ b/.babelrc @@ -5,6 +5,8 @@ ], "plugins": [ "lodash", - "transform-class-properties" + "transform-class-properties", + "transform-es2015-destructuring", + "transform-object-rest-spread" ] } diff --git a/frontend/src/Metamaps/Realtime/events.js b/frontend/src/Metamaps/Realtime/events.js index 20265154..c2a23f52 100644 --- a/frontend/src/Metamaps/Realtime/events.js +++ b/frontend/src/Metamaps/Realtime/events.js @@ -1,8 +1,7 @@ /* EVENTS SENDABLE */ -export const REQUEST_LIVE_MAPS = 'REQUEST_LIVE_MAPS' export const JOIN_MAP = 'JOIN_MAP' -export const LEAVE_MAP = 'LEAVE_MAP' export const CHECK_FOR_CALL = 'CHECK_FOR_CALL' +export const LEAVE_MAP = 'LEAVE_MAP' export const ACCEPT_CALL = 'ACCEPT_CALL' export const DENY_CALL = 'DENY_CALL' export const DENY_INVITE = 'DENY_INVITE' @@ -25,6 +24,7 @@ export const DELETE_SYNAPSE = 'DELETE_SYNAPSE' export const UPDATE_MAP = 'UPDATE_MAP' /* EVENTS RECEIVABLE */ +export const JUNTO_UPDATED = 'JUNTO_UPDATED' export const INVITED_TO_CALL = 'INVITED_TO_CALL' export const INVITED_TO_JOIN = 'INVITED_TO_JOIN' export const CALL_ACCEPTED = 'CALL_ACCEPTED' @@ -49,6 +49,3 @@ export const SYNAPSE_REMOVED = 'SYNAPSE_REMOVED' export const SYNAPSE_DELETED = 'SYNAPSE_DELETED' export const PEER_COORDS_UPDATED = 'PEER_COORDS_UPDATED' export const MAP_UPDATED = 'MAP_UPDATED' -export const LIVE_MAPS_RECEIVED = 'LIVE_MAPS_RECEIVED' -export const MAP_WENT_LIVE = 'MAP_WENT_LIVE' -export const MAP_CEASED_LIVE = 'MAP_CEASED_LIVE' diff --git a/frontend/src/Metamaps/Realtime/index.js b/frontend/src/Metamaps/Realtime/index.js index e891263c..feefe68c 100644 --- a/frontend/src/Metamaps/Realtime/index.js +++ b/frontend/src/Metamaps/Realtime/index.js @@ -27,6 +27,7 @@ import Views from '../Views' import Visualize from '../Visualize' import { + JUNTO_UPDATED, INVITED_TO_CALL, INVITED_TO_JOIN, CALL_ACCEPTED, @@ -34,9 +35,9 @@ import { INVITE_DENIED, CALL_IN_PROGRESS, CALL_STARTED, + MAPPER_LIST_UPDATED, MAPPER_JOINED_CALL, MAPPER_LEFT_CALL, - MAPPER_LIST_UPDATED, NEW_MAPPER, LOST_MAPPER, MESSAGE_CREATED, @@ -50,13 +51,11 @@ import { SYNAPSE_REMOVED, SYNAPSE_DELETED, PEER_COORDS_UPDATED, - LIVE_MAPS_RECEIVED, - MAP_WENT_LIVE, - MAP_CEASED_LIVE, MAP_UPDATED } from './events' import { + juntoUpdated, invitedToCall, invitedToJoin, callAccepted, @@ -64,9 +63,9 @@ import { inviteDenied, callInProgress, callStarted, + mapperListUpdated, mapperJoinedCall, mapperLeftCall, - mapperListUpdated, peerCoordsUpdated, newMapper, lostMapper, @@ -81,13 +80,9 @@ import { synapseRemoved, synapseDeleted, mapUpdated, - liveMapsReceived, - mapWentLive, - mapCeasedLive } from './receivable' import { - requestLiveMaps, joinMap, leaveMap, checkForCall, @@ -98,8 +93,8 @@ import { inviteACall, joinCall, leaveCall, - sendMapperInfo, sendCoords, + sendMapperInfo, createMessage, dragTopic, createTopic, @@ -114,6 +109,7 @@ import { } from './sendable' let Realtime = { + juntoState: { connectedPeople: {}, liveMaps: {} }, videoId: 'video-wrapper', socket: null, webrtc: null, @@ -499,7 +495,6 @@ let Realtime = { } const sendables = [ - ['requestLiveMaps',requestLiveMaps], ['joinMap',joinMap], ['leaveMap',leaveMap], ['checkForCall',checkForCall], @@ -529,6 +524,7 @@ sendables.forEach(sendable => { }) const subscribeToEvents = (Realtime, socket) => { + socket.on(JUNTO_UPDATED, juntoUpdated(Realtime)) socket.on(INVITED_TO_CALL, invitedToCall(Realtime)) socket.on(INVITED_TO_JOIN, invitedToJoin(Realtime)) socket.on(CALL_ACCEPTED, callAccepted(Realtime)) @@ -536,9 +532,9 @@ const subscribeToEvents = (Realtime, socket) => { socket.on(INVITE_DENIED, inviteDenied(Realtime)) socket.on(CALL_IN_PROGRESS, callInProgress(Realtime)) socket.on(CALL_STARTED, callStarted(Realtime)) + socket.on(MAPPER_LIST_UPDATED, mapperListUpdated(Realtime)) socket.on(MAPPER_JOINED_CALL, mapperJoinedCall(Realtime)) socket.on(MAPPER_LEFT_CALL, mapperLeftCall(Realtime)) - socket.on(MAPPER_LIST_UPDATED, mapperListUpdated(Realtime)) socket.on(PEER_COORDS_UPDATED, peerCoordsUpdated(Realtime)) socket.on(NEW_MAPPER, newMapper(Realtime)) socket.on(LOST_MAPPER, lostMapper(Realtime)) @@ -553,9 +549,6 @@ const subscribeToEvents = (Realtime, socket) => { socket.on(SYNAPSE_REMOVED, synapseRemoved(Realtime)) socket.on(SYNAPSE_DELETED, synapseDeleted(Realtime)) socket.on(MAP_UPDATED, mapUpdated(Realtime)) - socket.on(LIVE_MAPS_RECEIVED, liveMapsReceived(Realtime)) - socket.on(MAP_WENT_LIVE, mapWentLive(Realtime)) - socket.on(MAP_CEASED_LIVE, mapCeasedLive(Realtime)) } export default Realtime diff --git a/frontend/src/Metamaps/Realtime/receivable.js b/frontend/src/Metamaps/Realtime/receivable.js index bf974bbe..a2f0ef59 100644 --- a/frontend/src/Metamaps/Realtime/receivable.js +++ b/frontend/src/Metamaps/Realtime/receivable.js @@ -1,7 +1,10 @@ +/* globals $ */ /* everthing in this file happens as a result of websocket events */ +import { JUNTO_UPDATED } from './events' + import Active from '../Active' import GlobalUI from '../GlobalUI' import Control from '../Control' @@ -12,6 +15,11 @@ import Synapse from '../Synapse' import Util from '../Util' import Visualize from '../Visualize' +export const juntoUpdated = self => state => { + self.juntoState = state + $(document).trigger(JUNTO_UPDATED) +} + export const synapseRemoved = self => data => { var synapse = Metamaps.Synapses.get(data.mappableid) if (synapse) { @@ -391,6 +399,3 @@ export const callStarted = self => () => { self.room.conversationInProgress() } -export const liveMapsReceived = self => () => {} -export const mapWentLive = self => () => {} -export const mapCeasedLive = self => () => {} diff --git a/frontend/src/Metamaps/Realtime/sendable.js b/frontend/src/Metamaps/Realtime/sendable.js index a1ce2ea7..882f7551 100644 --- a/frontend/src/Metamaps/Realtime/sendable.js +++ b/frontend/src/Metamaps/Realtime/sendable.js @@ -2,7 +2,6 @@ import Active from '../Active' import GlobalUI from '../GlobalUI' import { - REQUEST_LIVE_MAPS, JOIN_MAP, LEAVE_MAP, CHECK_FOR_CALL, @@ -28,15 +27,11 @@ import { UPDATE_MAP } from './events' -export const requestLiveMaps = self => () => { - self.socket.emit(REQUEST_LIVE_MAPS) -} - export const joinMap = self => () => { self.socket.emit(JOIN_MAP, { userid: Active.Mapper.id, username: Active.Mapper.get('name'), - userimage: Active.Mapper.get('image'), + avatar: Active.Mapper.get('image'), mapid: Active.Map.id, map: Active.Map.attributes }) diff --git a/frontend/src/Metamaps/Views/ExploreMaps.js b/frontend/src/Metamaps/Views/ExploreMaps.js index e843e7fe..c35877fb 100644 --- a/frontend/src/Metamaps/Views/ExploreMaps.js +++ b/frontend/src/Metamaps/Views/ExploreMaps.js @@ -3,7 +3,9 @@ import React from 'react' import ReactDOM from 'react-dom' // TODO ensure this isn't a double import +import { JUNTO_UPDATED } from '../Realtime/events' import Active from '../Active' +import Realtime from '../Realtime' import Maps from '../../components/Maps' /* @@ -27,6 +29,8 @@ const ExploreMaps = { render: function (mapperObj, cb) { var self = ExploreMaps + if (!self.collection) return + if (typeof mapperObj === 'function') { cb = mapperObj mapperObj = null @@ -36,6 +40,7 @@ const ExploreMaps = { currentUser: Active.Mapper, section: self.collection.id, maps: self.collection, + juntoState: Realtime.juntoState, moreToLoad: self.collection.page != 'loadedAll', user: mapperObj, loadMore: self.loadMore diff --git a/frontend/src/Metamaps/Views/index.js b/frontend/src/Metamaps/Views/index.js index d13482d0..329263b3 100644 --- a/frontend/src/Metamaps/Views/index.js +++ b/frontend/src/Metamaps/Views/index.js @@ -2,6 +2,15 @@ import ExploreMaps from './ExploreMaps' import ChatView from './ChatView' import VideoView from './VideoView' import Room from './Room' +import { JUNTO_UPDATED } from '../Realtime/events' -const Views = { ExploreMaps, ChatView, VideoView, Room } +const Views = { + init: () => { + $(document).on(JUNTO_UPDATED, ExploreMaps.render()) + }, + ExploreMaps, + ChatView, + VideoView, + Room +} export default Views diff --git a/frontend/src/Metamaps/index.js b/frontend/src/Metamaps/index.js index 44bbfdb6..bf2e2d60 100644 --- a/frontend/src/Metamaps/index.js +++ b/frontend/src/Metamaps/index.js @@ -88,7 +88,7 @@ document.addEventListener('DOMContentLoaded', function () { if (Metamaps.currentSection === 'explore') { const capitalize = Metamaps.currentPage.charAt(0).toUpperCase() + Metamaps.currentPage.slice(1) - Metamaps.Views.ExploreMaps.setCollection(Metamaps.Maps[capitalize]) + Views.ExploreMaps.setCollection(Metamaps.Maps[capitalize]) if (Metamaps.currentPage === 'mapper') { Views.ExploreMaps.fetchUserThenRender() } else { diff --git a/frontend/src/components/Maps/index.js b/frontend/src/components/Maps/index.js index 670f5aaf..3b81eea7 100644 --- a/frontend/src/components/Maps/index.js +++ b/frontend/src/components/Maps/index.js @@ -33,7 +33,7 @@ class Maps extends Component { } resize = () => { - const { maps, user, currentUser } = this.props + const { maps, juntoState, user, currentUser } = this.props const numCards = maps.length + (user || currentUser ? 1 : 0) const mapSpaces = Math.floor(document.body.clientWidth / MAP_WIDTH) const mapsWidth = Math.min(MAX_COLUMNS, Math.min(numCards, mapSpaces)) * MAP_WIDTH @@ -70,6 +70,7 @@ class Maps extends Component { Maps.propTypes = { section: PropTypes.string.isRequired, maps: PropTypes.object.isRequired, + juntoState: PropTypes.object.isRequired, moreToLoad: PropTypes.bool.isRequired, user: PropTypes.object, currentUser: PropTypes.object, diff --git a/package.json b/package.json index c43b7599..08421014 100644 --- a/package.json +++ b/package.json @@ -24,6 +24,8 @@ "babel-loader": "6.2.5", "babel-plugin-lodash": "^3.2.9", "babel-plugin-transform-class-properties": "6.11.5", + "babel-plugin-transform-es2015-destructuring": "^6.16.0", + "babel-plugin-transform-object-rest-spread": "^6.16.0", "babel-preset-es2015": "6.14.0", "babel-preset-react": "6.11.1", "backbone": "1.0.0", @@ -40,6 +42,7 @@ "react": "15.3.2", "react-dom": "15.3.2", "react-dropzone": "3.6.0", + "redux": "^3.6.0", "simplewebrtc": "2.2.0", "socket.io": "1.3.7", "underscore": "1.4.4", diff --git a/realtime/global.js b/realtime/global.js index 8e4d0c97..408ed654 100644 --- a/realtime/global.js +++ b/realtime/global.js @@ -5,13 +5,12 @@ import { TOPIC_DELETED, SYNAPSE_UPDATED, SYNAPSE_DELETED, - LIVE_MAPS_RECEIVED, - MAP_WENT_LIVE, - MAP_CEASED_LIVE, MAP_UPDATED, + JUNTO_UPDATED, // server receivable, client sendable - REQUEST_LIVE_MAPS, + JOIN_CALL, + LEAVE_CALL, JOIN_MAP, LEAVE_MAP, UPDATE_TOPIC, @@ -21,41 +20,20 @@ import { UPDATE_MAP } from '../frontend/src/Metamaps/Realtime/events' -const adjustAndBroadcast = (io, socket, state, event, data) => { - if (event === JOIN_MAP) { - if (!state.liveMaps[data.mapid]) { - state.liveMaps[data.mapid] = data.map // { name: '', desc: '', numTopics: '' } - state.liveMaps[data.mapid].mapper_count = 1 - io.sockets.emit(MAP_WENT_LIVE, state.liveMaps[data.mapid]) - } - else { - state.liveMaps[data.mapid].mapper_count++ - } - } - else if (event === LEAVE_MAP) { - const mapid = socket.mapid - if (state.liveMaps[mapid] && state.liveMaps[mapid].mapper_count == 1) { - delete state.liveMaps[mapid] - io.sockets.emit(MAP_CEASED_LIVE, { id: mapid }) - } - else if (state.liveMaps[mapid]) { - state.liveMaps[mapid].mapper_count-- - } - } -} -module.exports = function (io, state) { +module.exports = function (io, store) { io.on('connection', function (socket) { - socket.on(REQUEST_LIVE_MAPS, function (activeUser) { - //constrain response to maps visible to user - var maps = Object.keys(state.liveMaps).map(function(key) { return state.liveMaps[key] }) - socket.emit(LIVE_MAPS_RECEIVED, maps) + store.subscribe(() => { + console.log(store.getState()) + io.sockets.emit(JUNTO_UPDATED, store.getState()) }) - socket.on(JOIN_MAP, data => adjustAndBroadcast(io, socket, state, JOIN_MAP, data)) - socket.on(LEAVE_MAP, () => adjustAndBroadcast(io, socket, state, LEAVE_MAP)) - socket.on('disconnect', () => adjustAndBroadcast(io, socket, state, LEAVE_MAP)) + socket.on(JOIN_MAP, data => store.dispatch({ type: JOIN_MAP, payload: data})) + socket.on(LEAVE_MAP, () => store.dispatch({ type: LEAVE_MAP, payload: socket })) + socket.on(JOIN_CALL, data => store.dispatch({ type: JOIN_CALL, payload: data })) + socket.on(LEAVE_CALL, () => store.dispatch({ type: LEAVE_CALL, payload: socket })) + socket.on('disconnect', () => store.dispatch({ type: 'DISCONNECT', payload: socket })) socket.on(UPDATE_TOPIC, function (data) { socket.broadcast.emit(TOPIC_UPDATED, data) diff --git a/realtime/junto.js b/realtime/junto.js index aa7f6152..69836e22 100644 --- a/realtime/junto.js +++ b/realtime/junto.js @@ -21,7 +21,7 @@ import { const { mapRoom, userMapRoom } = require('./rooms') -module.exports = function (io, state) { +module.exports = function (io, store) { io.on('connection', function (socket) { socket.on(CHECK_FOR_CALL, function (data) { @@ -39,6 +39,8 @@ module.exports = function (io, state) { socket.on(ACCEPT_CALL, function (data) { socket.broadcast.in(userMapRoom(data.inviter, data.mapid)).emit(CALL_ACCEPTED, data.invited) + // convert this so that it broadcasts to all sockets and includes the map id + // and who's participating socket.broadcast.in(mapRoom(data.mapid)).emit(CALL_STARTED) }) @@ -51,10 +53,14 @@ module.exports = function (io, state) { }) socket.on(JOIN_CALL, function (data) { + // convert this so that it broadcasts to all sockets and includes the map id + // and info about who joined socket.broadcast.in(mapRoom(data.mapid)).emit(MAPPER_JOINED_CALL, data.id) }) socket.on(LEAVE_CALL, function (data) { + // convert this so that it broadcasts to all sockets and includes the map id + // and info about who joined socket.broadcast.in(mapRoom(data.mapid)).emit(MAPPER_LEFT_CALL, data.id) }) }) diff --git a/realtime/map.js b/realtime/map.js index d0c85a10..d7225068 100644 --- a/realtime/map.js +++ b/realtime/map.js @@ -13,8 +13,8 @@ import { JOIN_MAP, LEAVE_MAP, - SEND_MAPPER_INFO, SEND_COORDS, + SEND_MAPPER_INFO, CREATE_MESSAGE, DRAG_TOPIC, CREATE_TOPIC, @@ -25,7 +25,7 @@ import { const { mapRoom, userMapRoom } = require('./rooms') -module.exports = function (io, state) { +module.exports = function (io, store) { io.on('connection', function (socket) { // this will ping everyone on a map that there's a person just joined the map @@ -33,11 +33,11 @@ module.exports = function (io, state) { socket.mapid = data.mapid socket.userid = data.userid socket.username = data.username - socket.userimage = data.userimage + socket.avatar = data.avatar var newUser = { userid: data.userid, username: data.username, - userimage: data.userimage + avatar: data.avatar } socket.join(mapRoom(data.mapid)) socket.join(userMapRoom(data.userid, data.mapid)) diff --git a/realtime/realtime-server.js b/realtime/realtime-server.js index 7cdcd6bf..a0b3e896 100644 --- a/realtime/realtime-server.js +++ b/realtime/realtime-server.js @@ -6,13 +6,14 @@ map = require('./map'), global = require('./global'), stunservers = [{"url": "stun:stun.l.google.com:19302"}] -var state = { - connectedPeople: {}, - liveMaps: {} -} -signalling(io, stunservers, state) -junto(io, state) -map(io, state) -global(io, state) +import { createStore } from 'redux' +import { reducer } from './reducer' + +let store = createStore(reducer) + +global(io, store) +signalling(io, stunservers, store) +junto(io, store) +map(io, store) io.listen(5001) diff --git a/realtime/reducer.js b/realtime/reducer.js new file mode 100644 index 00000000..6084facc --- /dev/null +++ b/realtime/reducer.js @@ -0,0 +1,88 @@ + +import { omit, omitBy, isNil, mapValues } from 'lodash' +import { + JOIN_MAP, + LEAVE_MAP, + JOIN_CALL, + LEAVE_CALL +} from '../frontend/src/Metamaps/Realtime/events' + +const NOT_IN_CONVERSATION = 0 +const IN_CONVERSATION = 1 + +const addMapperToMap = (map, userId) => { return { ...map, [userId]: NOT_IN_CONVERSATION }} + +export const reducer = (state = { connectedPeople: {}, liveMaps: {} }, action) => { + const { type, payload } = action + const { connectedPeople, liveMaps } = state + const map = payload && liveMaps[payload.mapid] + const mapWillEmpty = map && Object.keys(map).length === 1 + const callWillFinish = map && (type === LEAVE_CALL || type === 'DISCONNECT') && Object.keys(map).length === 2 + + switch (type) { + case JOIN_MAP: + return { + connectedPeople: { + ...connectedPeople, + [payload.userid]: { + id: payload.userid, + username: payload.username, + avatar: payload.avatar + } + }, + liveMaps: { + ...liveMaps, + [payload.mapid]: addMapperToMap(map || {}, payload.userid) + } + } + case LEAVE_MAP: + return { + connectedPeople: omit(connectedPeople, payload.userid), + // if the map will empty, remove it from liveMaps, if the map will not empty, just remove the mapper + liveMaps: omitBy(mapWillEmpty ? omit(liveMaps, payload.mapid) : { ...liveMaps, [payload.mapid]: omit(liveMaps[payload.mapid], payload.userid) }, isNil) + } + case JOIN_CALL: + return { + // connectedPeople stays the same + connectedPeople, + liveMaps: { + // liveMaps stays the same, except for + ...liveMaps, + // the map in question + [payload.mapid]: { + // which stays the same, except for + ...map, + // the user in question, which is now marked as being in a conversation + [payload.id]: IN_CONVERSATION + } + } + } + case LEAVE_CALL: + return { + // connectedPeople stays the same + connectedPeople, + liveMaps: { + // liveMaps stays the same, except for + ...liveMaps, + // the map in question + [payload.mapid]: callWillFinish ? mapValues(map, () => NOT_IN_CONVERSATION) : { + // which stays the same, except for + ...map, + // the user in question, which is now marked as being NOT in conversation + [payload.userid]: NOT_IN_CONVERSATION + } + } + } + case 'DISCONNECT': + const mapWithoutUser = omit(map, payload.userid) + const newMap = callWillFinish ? mapValues(mapWithoutUser, () => NOT_IN_CONVERSATION) : mapWithoutUser + return { + connectedPeople: omit(connectedPeople, payload.userid), + // if the map will empty, remove it from liveMaps, if the map will not empty, just remove the mapper + liveMaps: omitBy(mapWillEmpty ? omit(liveMaps, payload.mapid) : + { ...liveMaps, [payload.mapid]: newMap }, isNil) + } + default: + return state + } +}