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"));
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();
}

View file

@ -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;
}
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
);
_setCallListeners(call);
_setCallState(call, call.roomId, "ringback");
if (payload.type === 'voice') {
call.placeVoiceCall();
placeCall(call);
}
else if (payload.type === 'video') {
call.placeVideoCall(
payload.remote_element,
payload.local_element
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);
});
}
else {
console.error("Unknown call type: %s", payload.type);
}
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;
}
};

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.
*/
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;
}

View file

@ -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();
};