From b68665ead5130bdda5ce3bf12e41cbf8f04ffbfa Mon Sep 17 00:00:00 2001 From: David Baker Date: Wed, 30 Sep 2015 16:50:46 +0100 Subject: [PATCH] Add support for the basic notion of conference calls and an experimental concept of modules to provide the actual functionality. Rejig Skinner to be simpler. --- reskindex.js | 36 +-------------- src/CallHandler.js | 112 +++++++++++++++++++++++++++++++++------------ src/Modulator.js | 111 ++++++++++++++++++++++++++++++++++++++++++++ src/Skinner.js | 14 +----- src/index.js | 5 ++ 5 files changed, 201 insertions(+), 77 deletions(-) create mode 100644 src/Modulator.js diff --git a/reskindex.js b/reskindex.js index e077726f83..f8d45493d3 100755 --- a/reskindex.js +++ b/reskindex.js @@ -57,47 +57,15 @@ strm.write(" */\n\n"); var mySkinfo = JSON.parse(fs.readFileSync(skinfoFile, "utf8")); -strm.write("var skin = {\n"); -strm.write(" atoms: {},\n"); -strm.write(" molecules: {},\n"); -strm.write(" organisms: {},\n"); -strm.write(" templates: {},\n"); -strm.write(" pages: {}\n"); -strm.write("};\n"); +strm.write("var skin = {};\n"); strm.write('\n'); -var tree = { - atoms: {}, - molecules: {}, - organisms: {}, - templates: {}, - pages: {} -}; - var files = glob.sync('**/*.js', {cwd: viewsDir}); for (var i = 0; i < files.length; ++i) { var file = files[i].replace('.js', ''); var module = (file.replace(/\//g, '.')); - // create objects for submodules - // NB. that we do not support creating additional - // top level modules. Perhaps we should? - var subtree = tree[module.split('.')[0]]; - var restOfPath = module.split('.').slice(0, -1); - var currentPath = restOfPath[0]; - restOfPath = restOfPath.slice(1); - while (restOfPath.length) { - currentPath += '.'+restOfPath[0]; - if (subtree[restOfPath[0]] == undefined) { - strm.write('skin.'+currentPath+' = {};\n'); - strm.uncork(); - subtree[restOfPath[0]] = {}; - } - subtree = subtree[restOfPath[0]]; - restOfPath = restOfPath.slice(1); - } - - strm.write('skin.'+module+" = require('./views/"+file+"');\n"); + strm.write("skin['"+module+"'] = require('./views/"+file+"');\n"); strm.uncork(); } diff --git a/src/CallHandler.js b/src/CallHandler.js index b56137dee9..db2cb9a5e6 100644 --- a/src/CallHandler.js +++ b/src/CallHandler.js @@ -51,11 +51,12 @@ limitations under the License. * } */ -var MatrixClientPeg = require("./MatrixClientPeg"); -var Modal = require("./Modal"); -var sdk = require("./index"); +var MatrixClientPeg = require('./MatrixClientPeg'); +var Modal = require('./Modal'); +var sdk = require('./index'); var Matrix = require("matrix-js-sdk"); var dis = require("./dispatcher"); +var Modulator = require("./Modulator"); var calls = { //room_id: MatrixCall @@ -102,7 +103,7 @@ function _setCallListeners(call) { play("ringbackAudio"); } else if (newState === "ended" && oldState === "connected") { - _setCallState(call, call.roomId, "ended"); + _setCallState(undefined, call.roomId, "ended"); pause("ringbackAudio"); play("callendAudio"); } @@ -113,7 +114,6 @@ function _setCallListeners(call) { _setCallState(call, call.roomId, "busy"); pause("ringbackAudio"); play("busyAudio"); - var ErrorDialog = sdk.getComponent("organisms.ErrorDialog"); Modal.createDialog(ErrorDialog, { title: "Call Timeout", @@ -150,9 +150,31 @@ function _setCallState(call, roomId, status) { } dis.register(function(payload) { + function placeCall(newCall) { + _setCallListeners(newCall); + _setCallState(newCall, newCall.roomId, "ringback"); + if (payload.type === 'voice') { + newCall.placeVoiceCall(); + } + else if (payload.type === 'video') { + newCall.placeVideoCall( + payload.remote_element, + payload.local_element + ); + } + else { + console.error("Unknown conf call type: %s", payload.type); + } + } + switch (payload.action) { case 'place_call': - if (calls[payload.room_id]) { + if (module.exports.getAnyActiveCall()) { + var ErrorDialog = sdk.getComponent("organisms.ErrorDialog"); + Modal.createDialog(ErrorDialog, { + title: "Existing Call", + description: "You are already in a call." + }); return; // don't allow >1 call to be placed. } var room = MatrixClientPeg.get().getRoom(payload.room_id); @@ -160,41 +182,53 @@ dis.register(function(payload) { console.error("Room %s does not exist.", payload.room_id); return; } + var members = room.getJoinedMembers(); - if (members.length !== 2) { - var text = members.length === 1 ? "yourself." : "more than 2 people."; + if (members.length <= 1) { var ErrorDialog = sdk.getComponent("organisms.ErrorDialog"); Modal.createDialog(ErrorDialog, { - description: "You cannot place a call with " + text + description: "You cannot place a call with yourself." }); - console.error( - "Fail: There are %s joined members in this room, not 2.", - room.getJoinedMembers().length - ); return; } - console.log("Place %s call in %s", payload.type, payload.room_id); - var call = Matrix.createNewMatrixCall( - MatrixClientPeg.get(), payload.room_id - ); - _setCallListeners(call); - _setCallState(call, call.roomId, "ringback"); - if (payload.type === 'voice') { - call.placeVoiceCall(); - } - else if (payload.type === 'video') { - call.placeVideoCall( - payload.remote_element, - payload.local_element + else if (members.length === 2) { + console.log("Place %s call in %s", payload.type, payload.room_id); + var call = Matrix.createNewMatrixCall( + MatrixClientPeg.get(), payload.room_id ); + placeCall(call); } - else { - console.error("Unknown call type: %s", payload.type); + else { // > 2 + dis.dispatch({ + action: "place_conference_call", + room_id: payload.room_id, + type: payload.type, + remote_element: payload.remote_element, + local_element: payload.local_element + }); + } + break; + case 'place_conference_call': + console.log("Place conference call in %s", payload.room_id); + if (!Modulator.hasConferenceHandler()) { + var ErrorDialog = sdk.getComponent("organisms.ErrorDialog"); + Modal.createDialog(ErrorDialog, { + description: "Conference calls are not supported in this client" + }); + } else { + var ConferenceHandler = Modulator.getConferenceHandler(); + var confCall = ConferenceHandler.createNewMatrixCall( + MatrixClientPeg.get(), payload.room_id + ); + confCall.setup().done(function(call) { + placeCall(call); + }, function(err) { + console.error("Failed to setup conference call: %s", err); + }); } - break; case 'incoming_call': - if (calls[payload.call.roomId]) { + if (module.exports.getAnyActiveCall()) { payload.call.hangup("busy"); return; // don't allow >1 call to be received, hangup newer one. } @@ -224,7 +258,25 @@ dis.register(function(payload) { }); module.exports = { + + getCallForRoom: function(roomId) { + return ( + module.exports.getCall(roomId) + ); + }, + getCall: function(roomId) { return calls[roomId] || null; + }, + + getAnyActiveCall: function() { + var roomsWithCalls = Object.keys(calls); + for (var i = 0; i < roomsWithCalls.length; i++) { + if (calls[roomsWithCalls[i]] && + calls[roomsWithCalls[i]].call_state !== "ended") { + return calls[roomsWithCalls[i]]; + } + } + return null; } }; diff --git a/src/Modulator.js b/src/Modulator.js new file mode 100644 index 0000000000..72fcc14d89 --- /dev/null +++ b/src/Modulator.js @@ -0,0 +1,111 @@ +/* +Copyright 2015 OpenMarket Ltd + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +/** + * The modulator stores 'modules': classes that provide + * functionality and are not React UI components. + * Modules go into named slots, eg. a conference calling + * module goes into the 'conference' slot. If two modules + * that use the same slot are loaded, this is considered + * to be an error. + * + * There are some module slots that the react SDK knows + * about natively: these have explicit getters. + * + * A module must define: + * - 'slot' (string): The name of the slot it goes into + * and may define: + * - 'start' (function): Called on module load + * - 'stop' (function): Called on module unload + */ +class Modulator { + constructor() { + this.modules = {}; + } + + getModule(name) { + var m = this.getModuleOrNull(name); + if (m === null) { + throw new Error("No such module: "+name); + } + return m; + } + + getModuleOrNull(name) { + if (this.modules == {}) { + throw new Error( + "Attempted to get a module before a skin has been loaded."+ + "This is probably because a component has called "+ + "getModule at the root level." + ); + } + var module = this.modules[name]; + if (module) { + return module; + } + return null; + } + + hasModule(name) { + var m = this.getModuleOrNull(name); + return m !== null; + } + + loadModule(moduleObject) { + if (!moduleObject.slot) { + throw new Error( + "Attempted to load something that is not a module "+ + "(does not have a slot name)" + ); + } + if (this.modules[moduleObject.slot] !== undefined) { + throw new Error( + "Cannot load module: slot '"+moduleObject.slot+"' is occupied!" + ); + } + this.modules[moduleObject.slot] = moduleObject; + } + + reset() { + var keys = Object.keys(this.modules); + for (var i = 0; i < keys.length; ++i) { + var k = keys[i]; + var m = this.modules[k]; + + if (m.stop) m.stop(); + } + this.modules = {}; + } + + // *********** + // known slots + // *********** + + getConferenceHandler() { + return this.getModule('conference'); + } + + hasConferenceHandler() { + return this.hasModule('conference'); + } +} + +// Define one Modulator globally (see Skinner.js) +if (global.mxModulator === undefined) { + global.mxModulator = new Modulator(); +} +module.exports = global.mxModulator; + diff --git a/src/Skinner.js b/src/Skinner.js index a9be3795c9..ae48d85633 100644 --- a/src/Skinner.js +++ b/src/Skinner.js @@ -14,18 +14,6 @@ See the License for the specific language governing permissions and limitations under the License. */ -function extractComponent(object, path) { - var subObject = object[path[0]] - if (subObject === undefined) { - return undefined; - } - if (path.length == 1) { - return subObject; - } else { - return extractComponent(subObject, path.slice(1)); - } -} - class Skinner { constructor() { this.components = null; @@ -40,7 +28,7 @@ class Skinner { " b) A component has called getComponent at the root level" ); } - var comp = extractComponent(this.components, name.split('.')); + var comp = this.components[name]; if (comp) { return comp; } diff --git a/src/index.js b/src/index.js index a631538622..5412c051d6 100644 --- a/src/index.js +++ b/src/index.js @@ -15,11 +15,16 @@ limitations under the License. */ var Skinner = require('./Skinner'); +var Modulator = require('./Modulator'); module.exports.loadSkin = function(skinObject) { Skinner.load(skinObject); }; +module.exports.loadModule = function(moduleObject) { + Modulator.loadModule(moduleObject); +}; + module.exports.resetSkin = function() { Skinner.reset(); };