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.

This commit is contained in:
David Baker 2015-09-30 16:50:46 +01:00
parent 9fb5702c2f
commit b68665ead5
5 changed files with 201 additions and 77 deletions

View file

@ -57,47 +57,15 @@ strm.write(" */\n\n");
var mySkinfo = JSON.parse(fs.readFileSync(skinfoFile, "utf8")); var mySkinfo = JSON.parse(fs.readFileSync(skinfoFile, "utf8"));
strm.write("var skin = {\n"); 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('\n'); strm.write('\n');
var tree = {
atoms: {},
molecules: {},
organisms: {},
templates: {},
pages: {}
};
var files = glob.sync('**/*.js', {cwd: viewsDir}); var files = glob.sync('**/*.js', {cwd: viewsDir});
for (var i = 0; i < files.length; ++i) { for (var i = 0; i < files.length; ++i) {
var file = files[i].replace('.js', ''); var file = files[i].replace('.js', '');
var module = (file.replace(/\//g, '.')); var module = (file.replace(/\//g, '.'));
// create objects for submodules strm.write("skin['"+module+"'] = require('./views/"+file+"');\n");
// 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.uncork(); strm.uncork();
} }

View file

@ -51,11 +51,12 @@ limitations under the License.
* } * }
*/ */
var MatrixClientPeg = require("./MatrixClientPeg"); var MatrixClientPeg = require('./MatrixClientPeg');
var Modal = require("./Modal"); var Modal = require('./Modal');
var sdk = require("./index"); var sdk = require('./index');
var Matrix = require("matrix-js-sdk"); var Matrix = require("matrix-js-sdk");
var dis = require("./dispatcher"); var dis = require("./dispatcher");
var Modulator = require("./Modulator");
var calls = { var calls = {
//room_id: MatrixCall //room_id: MatrixCall
@ -102,7 +103,7 @@ function _setCallListeners(call) {
play("ringbackAudio"); play("ringbackAudio");
} }
else if (newState === "ended" && oldState === "connected") { else if (newState === "ended" && oldState === "connected") {
_setCallState(call, call.roomId, "ended"); _setCallState(undefined, call.roomId, "ended");
pause("ringbackAudio"); pause("ringbackAudio");
play("callendAudio"); play("callendAudio");
} }
@ -113,7 +114,6 @@ function _setCallListeners(call) {
_setCallState(call, call.roomId, "busy"); _setCallState(call, call.roomId, "busy");
pause("ringbackAudio"); pause("ringbackAudio");
play("busyAudio"); play("busyAudio");
var ErrorDialog = sdk.getComponent("organisms.ErrorDialog"); var ErrorDialog = sdk.getComponent("organisms.ErrorDialog");
Modal.createDialog(ErrorDialog, { Modal.createDialog(ErrorDialog, {
title: "Call Timeout", title: "Call Timeout",
@ -150,9 +150,31 @@ function _setCallState(call, roomId, status) {
} }
dis.register(function(payload) { 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) { switch (payload.action) {
case 'place_call': 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. return; // don't allow >1 call to be placed.
} }
var room = MatrixClientPeg.get().getRoom(payload.room_id); 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); console.error("Room %s does not exist.", payload.room_id);
return; return;
} }
var members = room.getJoinedMembers(); var members = room.getJoinedMembers();
if (members.length !== 2) { if (members.length <= 1) {
var text = members.length === 1 ? "yourself." : "more than 2 people.";
var ErrorDialog = sdk.getComponent("organisms.ErrorDialog"); var ErrorDialog = sdk.getComponent("organisms.ErrorDialog");
Modal.createDialog(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; return;
} }
else if (members.length === 2) {
console.log("Place %s call in %s", payload.type, payload.room_id); console.log("Place %s call in %s", payload.type, payload.room_id);
var call = Matrix.createNewMatrixCall( var call = Matrix.createNewMatrixCall(
MatrixClientPeg.get(), payload.room_id MatrixClientPeg.get(), payload.room_id
); );
_setCallListeners(call); placeCall(call);
_setCallState(call, call.roomId, "ringback");
if (payload.type === 'voice') {
call.placeVoiceCall();
} }
else if (payload.type === 'video') { else { // > 2
call.placeVideoCall( dis.dispatch({
payload.remote_element, action: "place_conference_call",
payload.local_element 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);
});
} }
else {
console.error("Unknown call type: %s", payload.type);
}
break; break;
case 'incoming_call': case 'incoming_call':
if (calls[payload.call.roomId]) { if (module.exports.getAnyActiveCall()) {
payload.call.hangup("busy"); payload.call.hangup("busy");
return; // don't allow >1 call to be received, hangup newer one. return; // don't allow >1 call to be received, hangup newer one.
} }
@ -224,7 +258,25 @@ dis.register(function(payload) {
}); });
module.exports = { module.exports = {
getCallForRoom: function(roomId) {
return (
module.exports.getCall(roomId)
);
},
getCall: function(roomId) { getCall: function(roomId) {
return calls[roomId] || null; 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;
} }
}; };

111
src/Modulator.js Normal file
View file

@ -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;

View file

@ -14,18 +14,6 @@ See the License for the specific language governing permissions and
limitations under the License. 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 { class Skinner {
constructor() { constructor() {
this.components = null; this.components = null;
@ -40,7 +28,7 @@ class Skinner {
" b) A component has called getComponent at the root level" " b) A component has called getComponent at the root level"
); );
} }
var comp = extractComponent(this.components, name.split('.')); var comp = this.components[name];
if (comp) { if (comp) {
return comp; return comp;
} }

View file

@ -15,11 +15,16 @@ limitations under the License.
*/ */
var Skinner = require('./Skinner'); var Skinner = require('./Skinner');
var Modulator = require('./Modulator');
module.exports.loadSkin = function(skinObject) { module.exports.loadSkin = function(skinObject) {
Skinner.load(skinObject); Skinner.load(skinObject);
}; };
module.exports.loadModule = function(moduleObject) {
Modulator.loadModule(moduleObject);
};
module.exports.resetSkin = function() { module.exports.resetSkin = function() {
Skinner.reset(); Skinner.reset();
}; };