Bug 1248492 - Land version 1.1.2 of the Loop system add-on in mozilla-central - code updates. rs=Standard8 for already reviewed code
176
browser/extensions/loop/bootstrap.js
vendored
@ -24,11 +24,26 @@ XPCOMUtils.defineLazyModuleGetter(this, "CustomizableUI",
|
|||||||
XPCOMUtils.defineLazyModuleGetter(this, "Task",
|
XPCOMUtils.defineLazyModuleGetter(this, "Task",
|
||||||
"resource://gre/modules/Task.jsm");
|
"resource://gre/modules/Task.jsm");
|
||||||
|
|
||||||
|
// See LOG_LEVELS in Console.jsm. Common examples: "All", "Info", "Warn", & "Error".
|
||||||
|
const PREF_LOG_LEVEL = "loop.debug.loglevel";
|
||||||
|
|
||||||
|
XPCOMUtils.defineLazyGetter(this, "log", () => {
|
||||||
|
let ConsoleAPI = Cu.import("resource://gre/modules/Console.jsm", {}).ConsoleAPI;
|
||||||
|
let consoleOptions = {
|
||||||
|
maxLogLevelPref: PREF_LOG_LEVEL,
|
||||||
|
prefix: "Loop"
|
||||||
|
};
|
||||||
|
return new ConsoleAPI(consoleOptions);
|
||||||
|
});
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This window listener gets loaded into each browser.xul window and is used
|
* This window listener gets loaded into each browser.xul window and is used
|
||||||
* to provide the required loop functions for the window.
|
* to provide the required loop functions for the window.
|
||||||
*/
|
*/
|
||||||
var WindowListener = {
|
var WindowListener = {
|
||||||
|
// Records the add-on version once we know it.
|
||||||
|
addonVersion: "unknown",
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Sets up the chrome integration within browser windows for Loop.
|
* Sets up the chrome integration within browser windows for Loop.
|
||||||
*
|
*
|
||||||
@ -40,6 +55,7 @@ var WindowListener = {
|
|||||||
let xhrClass = Cc["@mozilla.org/xmlextras/xmlhttprequest;1"];
|
let xhrClass = Cc["@mozilla.org/xmlextras/xmlhttprequest;1"];
|
||||||
let FileReader = window.FileReader;
|
let FileReader = window.FileReader;
|
||||||
let menuItem = null;
|
let menuItem = null;
|
||||||
|
let isSlideshowOpen = false;
|
||||||
|
|
||||||
// the "exported" symbols
|
// the "exported" symbols
|
||||||
var LoopUI = {
|
var LoopUI = {
|
||||||
@ -74,6 +90,30 @@ var WindowListener = {
|
|||||||
return browser;
|
return browser;
|
||||||
},
|
},
|
||||||
|
|
||||||
|
get isSlideshowOpen() {
|
||||||
|
return isSlideshowOpen;
|
||||||
|
},
|
||||||
|
|
||||||
|
set isSlideshowOpen(aOpen) {
|
||||||
|
isSlideshowOpen = aOpen;
|
||||||
|
this.updateToolbarState();
|
||||||
|
},
|
||||||
|
/**
|
||||||
|
* @return {Object} Getter for the Loop constants
|
||||||
|
*/
|
||||||
|
get constants() {
|
||||||
|
if (!this._constants) {
|
||||||
|
// GetAllConstants is synchronous even though it's using a callback.
|
||||||
|
this.LoopAPI.sendMessageToHandler({
|
||||||
|
name: "GetAllConstants"
|
||||||
|
}, result => {
|
||||||
|
this._constants = result;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return this._constants;
|
||||||
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @return {Promise}
|
* @return {Promise}
|
||||||
*/
|
*/
|
||||||
@ -114,6 +154,10 @@ var WindowListener = {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (this.isSlideshowOpen) {
|
||||||
|
return Promise.resolve();
|
||||||
|
}
|
||||||
|
|
||||||
return this.openPanel(event).then(mm => {
|
return this.openPanel(event).then(mm => {
|
||||||
if (mm) {
|
if (mm) {
|
||||||
mm.sendAsyncMessage("Social:EnsureFocusElement");
|
mm.sendAsyncMessage("Social:EnsureFocusElement");
|
||||||
@ -141,6 +185,12 @@ var WindowListener = {
|
|||||||
|
|
||||||
mm.sendAsyncMessage("Social:WaitForDocumentVisible");
|
mm.sendAsyncMessage("Social:WaitForDocumentVisible");
|
||||||
mm.addMessageListener("Social:DocumentVisible", () => resolve(mm));
|
mm.addMessageListener("Social:DocumentVisible", () => resolve(mm));
|
||||||
|
|
||||||
|
let buckets = this.constants.LOOP_MAU_TYPE;
|
||||||
|
this.LoopAPI.sendMessageToHandler({
|
||||||
|
name: "TelemetryAddValue",
|
||||||
|
data: ["LOOP_MAU", buckets.OPEN_PANEL]
|
||||||
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
// Used to clear the temporary "login" state from the button.
|
// Used to clear the temporary "login" state from the button.
|
||||||
@ -171,6 +221,17 @@ var WindowListener = {
|
|||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Wrapper for openPanel - to support Firefox 46 and 45.
|
||||||
|
*
|
||||||
|
* @param {event} event The event opening the panel, used to anchor
|
||||||
|
* the panel to the button which triggers it.
|
||||||
|
* @return {Promise}
|
||||||
|
*/
|
||||||
|
openCallPanel: function(event) {
|
||||||
|
return this.openPanel(event);
|
||||||
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Method to know whether actions to open the panel should instead resume the tour.
|
* Method to know whether actions to open the panel should instead resume the tour.
|
||||||
*
|
*
|
||||||
@ -229,7 +290,7 @@ var WindowListener = {
|
|||||||
init: function() {
|
init: function() {
|
||||||
// This is a promise for test purposes, but we don't want to be logging
|
// This is a promise for test purposes, but we don't want to be logging
|
||||||
// expected errors to the console, so we catch them here.
|
// expected errors to the console, so we catch them here.
|
||||||
this.MozLoopService.initialize().catch(ex => {
|
this.MozLoopService.initialize(WindowListener.addonVersion).catch(ex => {
|
||||||
if (!ex.message ||
|
if (!ex.message ||
|
||||||
(!ex.message.contains("not enabled") &&
|
(!ex.message.contains("not enabled") &&
|
||||||
!ex.message.contains("not needed"))) {
|
!ex.message.contains("not needed"))) {
|
||||||
@ -313,6 +374,8 @@ var WindowListener = {
|
|||||||
if (this.MozLoopService.errors.size) {
|
if (this.MozLoopService.errors.size) {
|
||||||
state = "error";
|
state = "error";
|
||||||
mozL10nId += "-error";
|
mozL10nId += "-error";
|
||||||
|
} else if (this.isSlideshowOpen) {
|
||||||
|
state = "slideshow";
|
||||||
} else if (this.MozLoopService.screenShareActive) {
|
} else if (this.MozLoopService.screenShareActive) {
|
||||||
state = "action";
|
state = "action";
|
||||||
mozL10nId += "-screensharing";
|
mozL10nId += "-screensharing";
|
||||||
@ -481,8 +544,88 @@ var WindowListener = {
|
|||||||
gBrowser.tabContainer.removeEventListener("TabSelect", this);
|
gBrowser.tabContainer.removeEventListener("TabSelect", this);
|
||||||
gBrowser.removeEventListener("DOMTitleChanged", this);
|
gBrowser.removeEventListener("DOMTitleChanged", this);
|
||||||
gBrowser.removeEventListener("mousemove", this);
|
gBrowser.removeEventListener("mousemove", this);
|
||||||
|
this.removeRemoteCursor();
|
||||||
this._listeningToTabSelect = false;
|
this._listeningToTabSelect = false;
|
||||||
this._browserSharePaused = false;
|
this._browserSharePaused = false;
|
||||||
|
this._sendTelemetryEventsIfNeeded();
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sends telemetry events for pause/ resume buttons if needed.
|
||||||
|
*/
|
||||||
|
_sendTelemetryEventsIfNeeded: function() {
|
||||||
|
// The user can't click Resume button without clicking Pause button first.
|
||||||
|
if (!this._pauseButtonClicked) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let buckets = this.constants.SHARING_SCREEN;
|
||||||
|
this.LoopAPI.sendMessageToHandler({
|
||||||
|
name: "TelemetryAddValue",
|
||||||
|
data: [
|
||||||
|
"LOOP_INFOBAR_ACTION_BUTTONS",
|
||||||
|
buckets.PAUSED
|
||||||
|
]
|
||||||
|
});
|
||||||
|
|
||||||
|
if (this._resumeButtonClicked) {
|
||||||
|
this.LoopAPI.sendMessageToHandler({
|
||||||
|
name: "TelemetryAddValue",
|
||||||
|
data: [
|
||||||
|
"LOOP_INFOBAR_ACTION_BUTTONS",
|
||||||
|
buckets.RESUMED
|
||||||
|
]
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
this._pauseButtonClicked = false;
|
||||||
|
this._resumeButtonClicked = false;
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* If sharing is active, paints and positions the remote cursor
|
||||||
|
* over the screen
|
||||||
|
*
|
||||||
|
* @param cursorData Object with the correct position for the cursor
|
||||||
|
* {
|
||||||
|
* ratioX: position on the X axis (percentage value)
|
||||||
|
* ratioY: position on the Y axis (percentage value)
|
||||||
|
* }
|
||||||
|
*/
|
||||||
|
addRemoteCursor: function(cursorData) {
|
||||||
|
if (!this._listeningToTabSelect) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let browser = gBrowser.selectedBrowser;
|
||||||
|
|
||||||
|
let cursor = document.getElementById("loop-remote-cursor");
|
||||||
|
if (!cursor) {
|
||||||
|
cursor = document.createElement("image");
|
||||||
|
cursor.setAttribute("id", "loop-remote-cursor");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update the cursor's position.
|
||||||
|
cursor.setAttribute("left",
|
||||||
|
cursorData.ratioX * browser.boxObject.width);
|
||||||
|
cursor.setAttribute("top",
|
||||||
|
cursorData.ratioY * browser.boxObject.height);
|
||||||
|
|
||||||
|
// browser's parent is a xul:stack, so positioning with left/top works.
|
||||||
|
browser.parentNode.appendChild(cursor);
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Removes the remote cursor from the screen
|
||||||
|
*
|
||||||
|
* @param browser OPT browser where the cursor should be removed from.
|
||||||
|
*/
|
||||||
|
removeRemoteCursor: function() {
|
||||||
|
let cursor = document.getElementById("loop-remote-cursor");
|
||||||
|
|
||||||
|
if (cursor) {
|
||||||
|
cursor.parentNode.removeChild(cursor);
|
||||||
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -539,6 +682,11 @@ var WindowListener = {
|
|||||||
buttonNode.label = stringObj.label;
|
buttonNode.label = stringObj.label;
|
||||||
buttonNode.accessKey = stringObj.accesskey;
|
buttonNode.accessKey = stringObj.accesskey;
|
||||||
LoopUI.MozLoopService.toggleBrowserSharing(this._browserSharePaused);
|
LoopUI.MozLoopService.toggleBrowserSharing(this._browserSharePaused);
|
||||||
|
if (this._browserSharePaused) {
|
||||||
|
this._pauseButtonClicked = true;
|
||||||
|
} else {
|
||||||
|
this._resumeButtonClicked = true;
|
||||||
|
}
|
||||||
return true;
|
return true;
|
||||||
},
|
},
|
||||||
type: "pause"
|
type: "pause"
|
||||||
@ -605,7 +753,10 @@ var WindowListener = {
|
|||||||
let wasVisible = false;
|
let wasVisible = false;
|
||||||
// Hide the infobar from the previous tab.
|
// Hide the infobar from the previous tab.
|
||||||
if (event.detail.previousTab) {
|
if (event.detail.previousTab) {
|
||||||
wasVisible = this._hideBrowserSharingInfoBar(event.detail.previousTab.linkedBrowser);
|
wasVisible = this._hideBrowserSharingInfoBar(
|
||||||
|
event.detail.previousTab.linkedBrowser);
|
||||||
|
// And remove the cursor.
|
||||||
|
this.removeRemoteCursor();
|
||||||
}
|
}
|
||||||
|
|
||||||
// We've changed the tab, so get the new window id.
|
// We've changed the tab, so get the new window id.
|
||||||
@ -713,10 +864,18 @@ var WindowListener = {
|
|||||||
window.LoopUI = LoopUI;
|
window.LoopUI = LoopUI;
|
||||||
},
|
},
|
||||||
|
|
||||||
tearDownBrowserUI: function() {
|
/**
|
||||||
// Take any steps to remove UI or anything from the browser window
|
* Take any steps to remove UI or anything from the browser window
|
||||||
// document.getElementById() etc. will work here
|
* document.getElementById() etc. will work here.
|
||||||
// XXX Add in tear-down of the panel.
|
*
|
||||||
|
* @param {Object} window The window to remove the integration from.
|
||||||
|
*/
|
||||||
|
tearDownBrowserUI: function(window) {
|
||||||
|
if (window.LoopUI) {
|
||||||
|
window.LoopUI.removeMenuItem();
|
||||||
|
|
||||||
|
// XXX Bug 1229352 - Add in tear-down of the panel.
|
||||||
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
// nsIWindowMediatorListener functions.
|
// nsIWindowMediatorListener functions.
|
||||||
@ -815,7 +974,10 @@ function loadDefaultPrefs() {
|
|||||||
/**
|
/**
|
||||||
* Called when the add-on is started, e.g. when installed or when Firefox starts.
|
* Called when the add-on is started, e.g. when installed or when Firefox starts.
|
||||||
*/
|
*/
|
||||||
function startup() {
|
function startup(data) {
|
||||||
|
// Record the add-on version for when the UI is initialised.
|
||||||
|
WindowListener.addonVersion = data.version;
|
||||||
|
|
||||||
loadDefaultPrefs();
|
loadDefaultPrefs();
|
||||||
|
|
||||||
createLoopButton();
|
createLoopButton();
|
||||||
|
@ -22,9 +22,6 @@ XPCOMUtils.defineLazyGetter(this, "eventEmitter", function() {
|
|||||||
const { EventEmitter } = Cu.import("resource://devtools/shared/event-emitter.js", {});
|
const { EventEmitter } = Cu.import("resource://devtools/shared/event-emitter.js", {});
|
||||||
return new EventEmitter();
|
return new EventEmitter();
|
||||||
});
|
});
|
||||||
XPCOMUtils.defineLazyGetter(this, "gLoopBundle", function() {
|
|
||||||
return Services.strings.createBundle("chrome://loop/locale/loop.properties");
|
|
||||||
});
|
|
||||||
|
|
||||||
XPCOMUtils.defineLazyModuleGetter(this, "LoopRoomsCache",
|
XPCOMUtils.defineLazyModuleGetter(this, "LoopRoomsCache",
|
||||||
"chrome://loop/content/modules/LoopRoomsCache.jsm");
|
"chrome://loop/content/modules/LoopRoomsCache.jsm");
|
||||||
@ -772,19 +769,13 @@ var LoopRoomsInternal = {
|
|||||||
* Joins a room. The sessionToken that is returned by the server will be stored
|
* Joins a room. The sessionToken that is returned by the server will be stored
|
||||||
* locally, for future use.
|
* locally, for future use.
|
||||||
*
|
*
|
||||||
* @param {String} roomToken The room token.
|
* @param {String} roomToken The room token.
|
||||||
* @param {Function} callback Function that will be invoked once the operation
|
* @param {String} displayName The user's display name.
|
||||||
* finished. The first argument passed will be an
|
* @param {Function} callback Function that will be invoked once the operation
|
||||||
* `Error` object or `null`.
|
* finished. The first argument passed will be an
|
||||||
|
* `Error` object or `null`.
|
||||||
*/
|
*/
|
||||||
join: function(roomToken, callback) {
|
join: function(roomToken, displayName, callback) {
|
||||||
let displayName;
|
|
||||||
if (MozLoopService.userProfile && MozLoopService.userProfile.email) {
|
|
||||||
displayName = MozLoopService.userProfile.email;
|
|
||||||
} else {
|
|
||||||
displayName = gLoopBundle.GetStringFromName("display_name_guest");
|
|
||||||
}
|
|
||||||
|
|
||||||
this._postToRoom(roomToken, {
|
this._postToRoom(roomToken, {
|
||||||
action: "join",
|
action: "join",
|
||||||
displayName: displayName,
|
displayName: displayName,
|
||||||
@ -1067,8 +1058,8 @@ this.LoopRooms = {
|
|||||||
return LoopRoomsInternal.delete(roomToken, callback);
|
return LoopRoomsInternal.delete(roomToken, callback);
|
||||||
},
|
},
|
||||||
|
|
||||||
join: function(roomToken, callback) {
|
join: function(roomToken, displayName, callback) {
|
||||||
return LoopRoomsInternal.join(roomToken, callback);
|
return LoopRoomsInternal.join(roomToken, displayName, callback);
|
||||||
},
|
},
|
||||||
|
|
||||||
refreshMembership: function(roomToken, sessionToken, callback) {
|
refreshMembership: function(roomToken, sessionToken, callback) {
|
||||||
|
@ -138,6 +138,12 @@ const kMessageName = "Loop:Message";
|
|||||||
const kPushMessageName = "Loop:Message:Push";
|
const kPushMessageName = "Loop:Message:Push";
|
||||||
const kPushSubscription = "pushSubscription";
|
const kPushSubscription = "pushSubscription";
|
||||||
const kRoomsPushPrefix = "Rooms:";
|
const kRoomsPushPrefix = "Rooms:";
|
||||||
|
const kMauPrefMap = new Map(
|
||||||
|
Object.getOwnPropertyNames(LOOP_MAU_TYPE).map(name => {
|
||||||
|
let parts = name.toLowerCase().split("_");
|
||||||
|
return [LOOP_MAU_TYPE[name], parts[0] + parts[1].charAt(0).toUpperCase() + parts[1].substr(1)];
|
||||||
|
})
|
||||||
|
);
|
||||||
const kMessageHandlers = {
|
const kMessageHandlers = {
|
||||||
/**
|
/**
|
||||||
* Start browser sharing, which basically means to start listening for tab
|
* Start browser sharing, which basically means to start listening for tab
|
||||||
@ -185,6 +191,30 @@ const kMessageHandlers = {
|
|||||||
reply();
|
reply();
|
||||||
},
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a layout for the remote cursor on the browser chrome,
|
||||||
|
* and positions it on the received coordinates.
|
||||||
|
*
|
||||||
|
* @param {Object} message Message meant for the handler function, containing
|
||||||
|
* the following parameters in its 'data' property:
|
||||||
|
* {
|
||||||
|
* ratioX: cursor's X position (between 0-1)
|
||||||
|
* ratioY: cursor's Y position (between 0-1)
|
||||||
|
* }
|
||||||
|
*
|
||||||
|
* @param {Function} reply Callback function, invoked with the result of the
|
||||||
|
* message handler. The result will be sent back to
|
||||||
|
* the senders' channel.
|
||||||
|
*/
|
||||||
|
AddRemoteCursorOverlay: function(message, reply) {
|
||||||
|
let win = Services.wm.getMostRecentWindow("navigator:browser");
|
||||||
|
if (win) {
|
||||||
|
win.LoopUI.addRemoteCursor(message.data[0]);
|
||||||
|
}
|
||||||
|
|
||||||
|
reply();
|
||||||
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Associates a session-id and a call-id with a window for debugging.
|
* Associates a session-id and a call-id with a window for debugging.
|
||||||
*
|
*
|
||||||
@ -366,9 +396,11 @@ const kMessageHandlers = {
|
|||||||
GetAllConstants: function(message, reply) {
|
GetAllConstants: function(message, reply) {
|
||||||
reply({
|
reply({
|
||||||
LOOP_SESSION_TYPE: LOOP_SESSION_TYPE,
|
LOOP_SESSION_TYPE: LOOP_SESSION_TYPE,
|
||||||
|
LOOP_MAU_TYPE: LOOP_MAU_TYPE,
|
||||||
ROOM_CREATE: ROOM_CREATE,
|
ROOM_CREATE: ROOM_CREATE,
|
||||||
ROOM_DELETE: ROOM_DELETE,
|
ROOM_DELETE: ROOM_DELETE,
|
||||||
SHARING_ROOM_URL: SHARING_ROOM_URL,
|
SHARING_ROOM_URL: SHARING_ROOM_URL,
|
||||||
|
SHARING_SCREEN: SHARING_SCREEN,
|
||||||
TWO_WAY_MEDIA_CONN_LENGTH: TWO_WAY_MEDIA_CONN_LENGTH
|
TWO_WAY_MEDIA_CONN_LENGTH: TWO_WAY_MEDIA_CONN_LENGTH
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
@ -779,16 +811,13 @@ const kMessageHandlers = {
|
|||||||
*
|
*
|
||||||
* @param {Object} message Message meant for the handler function, containing
|
* @param {Object} message Message meant for the handler function, containing
|
||||||
* the following parameters in its `data` property:
|
* the following parameters in its `data` property:
|
||||||
* [
|
* []
|
||||||
* {String} src Origin that starts or resumes the tour
|
|
||||||
* ]
|
|
||||||
* @param {Function} reply Callback function, invoked with the result of this
|
* @param {Function} reply Callback function, invoked with the result of this
|
||||||
* message handler. The result will be sent back to
|
* message handler. The result will be sent back to
|
||||||
* the senders' channel.
|
* the senders' channel.
|
||||||
*/
|
*/
|
||||||
OpenGettingStartedTour: function(message, reply) {
|
OpenGettingStartedTour: function(message, reply) {
|
||||||
var src = message.data[0];
|
MozLoopService.openGettingStartedTour();
|
||||||
MozLoopService.openGettingStartedTour(src);
|
|
||||||
reply();
|
reply();
|
||||||
},
|
},
|
||||||
|
|
||||||
@ -1001,7 +1030,30 @@ const kMessageHandlers = {
|
|||||||
*/
|
*/
|
||||||
TelemetryAddValue: function(message, reply) {
|
TelemetryAddValue: function(message, reply) {
|
||||||
let [histogramId, value] = message.data;
|
let [histogramId, value] = message.data;
|
||||||
Services.telemetry.getHistogramById(histogramId).add(value);
|
|
||||||
|
if (histogramId === "LOOP_MAU") {
|
||||||
|
let pref = "mau." + kMauPrefMap.get(value);
|
||||||
|
let prefDate = MozLoopService.getLoopPref(pref) * 1000;
|
||||||
|
let delta = Date.now() - prefDate;
|
||||||
|
|
||||||
|
// Send telemetry event if period (30 days) passed.
|
||||||
|
// 0 is default value for pref.
|
||||||
|
// 2592000 seconds in 30 days
|
||||||
|
if (pref === 0 || delta >= 2592000 * 1000) {
|
||||||
|
try {
|
||||||
|
Services.telemetry.getHistogramById(histogramId).add(value);
|
||||||
|
} catch (ex) {
|
||||||
|
MozLoopService.log.error("TelemetryAddValue failed for histogram '" + histogramId + "'", ex);
|
||||||
|
}
|
||||||
|
MozLoopService.setLoopPref(pref, Math.floor(Date.now() / 1000));
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
try {
|
||||||
|
Services.telemetry.getHistogramById(histogramId).add(value);
|
||||||
|
} catch (ex) {
|
||||||
|
MozLoopService.log.error("TelemetryAddValue failed for histogram '" + histogramId + "'", ex);
|
||||||
|
}
|
||||||
|
}
|
||||||
reply();
|
reply();
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@ -1020,7 +1072,11 @@ const LoopAPIInternal = {
|
|||||||
|
|
||||||
Cu.import("resource://gre/modules/RemotePageManager.jsm");
|
Cu.import("resource://gre/modules/RemotePageManager.jsm");
|
||||||
|
|
||||||
gPageListeners = [new RemotePages("about:looppanel"), new RemotePages("about:loopconversation")];
|
gPageListeners = [new RemotePages("about:looppanel"),
|
||||||
|
new RemotePages("about:loopconversation"),
|
||||||
|
// Slideshow added here to expose the loop api to make L10n work.
|
||||||
|
// XXX Can remove once slideshow is made remote.
|
||||||
|
new RemotePages("chrome://loop/content/panels/slideshow.html")];
|
||||||
for (let page of gPageListeners) {
|
for (let page of gPageListeners) {
|
||||||
page.addMessageListener(kMessageName, this.handleMessage.bind(this));
|
page.addMessageListener(kMessageName, this.handleMessage.bind(this));
|
||||||
}
|
}
|
||||||
|
@ -59,6 +59,30 @@ const ROOM_DELETE = {
|
|||||||
DELETE_FAIL: 1
|
DELETE_FAIL: 1
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Values that we segment sharing screen pause/ resume action telemetry probes into.
|
||||||
|
*
|
||||||
|
* @type {{PAUSED: Number, RESUMED: Number}}
|
||||||
|
*/
|
||||||
|
const SHARING_SCREEN = {
|
||||||
|
PAUSED: 0,
|
||||||
|
RESUMED: 1
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Values that we segment MAUs telemetry probes into.
|
||||||
|
*
|
||||||
|
* @type {{OPEN_PANEL: Number, OPEN_CONVERSATION: Number,
|
||||||
|
* ROOM_OPEN: Number, ROOM_SHARE: Number, ROOM_DELETE: Number}}
|
||||||
|
*/
|
||||||
|
const LOOP_MAU_TYPE = {
|
||||||
|
OPEN_PANEL: 0,
|
||||||
|
OPEN_CONVERSATION: 1,
|
||||||
|
ROOM_OPEN: 2,
|
||||||
|
ROOM_SHARE: 3,
|
||||||
|
ROOM_DELETE: 4
|
||||||
|
};
|
||||||
|
|
||||||
// See LOG_LEVELS in Console.jsm. Common examples: "All", "Info", "Warn", & "Error".
|
// See LOG_LEVELS in Console.jsm. Common examples: "All", "Info", "Warn", & "Error".
|
||||||
const PREF_LOG_LEVEL = "loop.debug.loglevel";
|
const PREF_LOG_LEVEL = "loop.debug.loglevel";
|
||||||
|
|
||||||
@ -81,14 +105,17 @@ Cu.import("resource://gre/modules/FxAccountsOAuthClient.jsm");
|
|||||||
|
|
||||||
Cu.importGlobalProperties(["URL"]);
|
Cu.importGlobalProperties(["URL"]);
|
||||||
|
|
||||||
this.EXPORTED_SYMBOLS = ["MozLoopService", "LOOP_SESSION_TYPE",
|
this.EXPORTED_SYMBOLS = ["MozLoopService", "LOOP_SESSION_TYPE", "LOOP_MAU_TYPE",
|
||||||
"TWO_WAY_MEDIA_CONN_LENGTH", "SHARING_ROOM_URL", "ROOM_CREATE", "ROOM_DELETE"];
|
"TWO_WAY_MEDIA_CONN_LENGTH", "SHARING_ROOM_URL", "SHARING_SCREEN",
|
||||||
|
"ROOM_CREATE", "ROOM_DELETE"];
|
||||||
|
|
||||||
XPCOMUtils.defineConstant(this, "LOOP_SESSION_TYPE", LOOP_SESSION_TYPE);
|
XPCOMUtils.defineConstant(this, "LOOP_SESSION_TYPE", LOOP_SESSION_TYPE);
|
||||||
XPCOMUtils.defineConstant(this, "TWO_WAY_MEDIA_CONN_LENGTH", TWO_WAY_MEDIA_CONN_LENGTH);
|
XPCOMUtils.defineConstant(this, "TWO_WAY_MEDIA_CONN_LENGTH", TWO_WAY_MEDIA_CONN_LENGTH);
|
||||||
XPCOMUtils.defineConstant(this, "SHARING_ROOM_URL", SHARING_ROOM_URL);
|
XPCOMUtils.defineConstant(this, "SHARING_ROOM_URL", SHARING_ROOM_URL);
|
||||||
|
XPCOMUtils.defineConstant(this, "SHARING_SCREEN", SHARING_SCREEN);
|
||||||
XPCOMUtils.defineConstant(this, "ROOM_CREATE", ROOM_CREATE);
|
XPCOMUtils.defineConstant(this, "ROOM_CREATE", ROOM_CREATE);
|
||||||
XPCOMUtils.defineConstant(this, "ROOM_DELETE", ROOM_DELETE);
|
XPCOMUtils.defineConstant(this, "ROOM_DELETE", ROOM_DELETE);
|
||||||
|
XPCOMUtils.defineConstant(this, "LOOP_MAU_TYPE", LOOP_MAU_TYPE);
|
||||||
|
|
||||||
XPCOMUtils.defineLazyModuleGetter(this, "LoopAPI",
|
XPCOMUtils.defineLazyModuleGetter(this, "LoopAPI",
|
||||||
"chrome://loop/content/modules/MozLoopAPI.jsm");
|
"chrome://loop/content/modules/MozLoopAPI.jsm");
|
||||||
@ -117,9 +144,6 @@ XPCOMUtils.defineLazyModuleGetter(this, "HawkClient",
|
|||||||
XPCOMUtils.defineLazyModuleGetter(this, "deriveHawkCredentials",
|
XPCOMUtils.defineLazyModuleGetter(this, "deriveHawkCredentials",
|
||||||
"resource://services-common/hawkrequest.js");
|
"resource://services-common/hawkrequest.js");
|
||||||
|
|
||||||
XPCOMUtils.defineLazyModuleGetter(this, "hookWindowCloseForPanelClose",
|
|
||||||
"resource://gre/modules/MozSocialAPI.jsm");
|
|
||||||
|
|
||||||
XPCOMUtils.defineLazyModuleGetter(this, "LoopRooms",
|
XPCOMUtils.defineLazyModuleGetter(this, "LoopRooms",
|
||||||
"chrome://loop/content/modules/LoopRooms.jsm");
|
"chrome://loop/content/modules/LoopRooms.jsm");
|
||||||
|
|
||||||
@ -171,6 +195,7 @@ var gFxAOAuthClientPromise = null;
|
|||||||
var gFxAOAuthClient = null;
|
var gFxAOAuthClient = null;
|
||||||
var gErrors = new Map();
|
var gErrors = new Map();
|
||||||
var gConversationWindowData = new Map();
|
var gConversationWindowData = new Map();
|
||||||
|
var gAddonVersion = "unknown";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Internal helper methods and state
|
* Internal helper methods and state
|
||||||
@ -614,7 +639,11 @@ var MozLoopServiceInternal = {
|
|||||||
throw error;
|
throw error;
|
||||||
};
|
};
|
||||||
|
|
||||||
return gHawkClient.request(path, method, credentials, payloadObj).then(
|
var extraHeaders = {
|
||||||
|
"x-loop-addon-ver": gAddonVersion
|
||||||
|
};
|
||||||
|
|
||||||
|
return gHawkClient.request(path, method, credentials, payloadObj, extraHeaders).then(
|
||||||
(result) => {
|
(result) => {
|
||||||
this.clearError("network");
|
this.clearError("network");
|
||||||
return result;
|
return result;
|
||||||
@ -904,157 +933,174 @@ var MozLoopServiceInternal = {
|
|||||||
* window when it opens.
|
* window when it opens.
|
||||||
* @param {Function} windowCloseCallback Callback function that's invoked
|
* @param {Function} windowCloseCallback Callback function that's invoked
|
||||||
* when the window closes.
|
* when the window closes.
|
||||||
* @returns {Number} The id of the window, null if a window could not
|
* @returns {Promise} That is resolved with the id of the window, null if a
|
||||||
* be opened.
|
* window could not be opened.
|
||||||
*/
|
*/
|
||||||
openChatWindow: function(conversationWindowData, windowCloseCallback) {
|
openChatWindow: function(conversationWindowData, windowCloseCallback) {
|
||||||
// So I guess the origin is the loop server!?
|
return new Promise(resolve => {
|
||||||
let origin = this.loopServerUri;
|
// So I guess the origin is the loop server!?
|
||||||
let windowId = this.getChatWindowID(conversationWindowData);
|
let origin = this.loopServerUri;
|
||||||
|
let windowId = this.getChatWindowID(conversationWindowData);
|
||||||
|
|
||||||
gConversationWindowData.set(windowId, conversationWindowData);
|
gConversationWindowData.set(windowId, conversationWindowData);
|
||||||
|
|
||||||
let url = this.getChatURL(windowId);
|
let url = this.getChatURL(windowId);
|
||||||
|
|
||||||
Chat.registerButton(kChatboxHangupButton);
|
Chat.registerButton(kChatboxHangupButton);
|
||||||
|
|
||||||
let callback = chatbox => {
|
let callback = chatbox => {
|
||||||
let mm = chatbox.content.messageManager;
|
let mm = chatbox.content.messageManager;
|
||||||
|
|
||||||
let loaded = () => {
|
let loaded = () => {
|
||||||
mm.removeMessageListener("DOMContentLoaded", loaded);
|
mm.removeMessageListener("DOMContentLoaded", loaded);
|
||||||
mm.sendAsyncMessage("Social:ListenForEvents", {
|
mm.sendAsyncMessage("Social:ListenForEvents", {
|
||||||
eventNames: ["LoopChatEnabled", "LoopChatMessageAppended",
|
eventNames: ["LoopChatEnabled", "LoopChatMessageAppended",
|
||||||
"LoopChatDisabledMessageAppended", "socialFrameAttached",
|
"LoopChatDisabledMessageAppended", "socialFrameAttached",
|
||||||
"socialFrameDetached", "socialFrameHide", "socialFrameShow"]
|
"socialFrameDetached", "socialFrameHide", "socialFrameShow"]
|
||||||
});
|
});
|
||||||
|
|
||||||
let chatbar = chatbox.parentNode;
|
const kEventNamesMap = {
|
||||||
|
socialFrameAttached: "Loop:ChatWindowAttached",
|
||||||
|
socialFrameDetached: "Loop:ChatWindowDetached",
|
||||||
|
socialFrameHide: "Loop:ChatWindowHidden",
|
||||||
|
socialFrameShow: "Loop:ChatWindowShown",
|
||||||
|
unload: "Loop:ChatWindowClosed"
|
||||||
|
};
|
||||||
|
|
||||||
const kEventNamesMap = {
|
const kSizeMap = {
|
||||||
socialFrameAttached: "Loop:ChatWindowAttached",
|
LoopChatEnabled: "loopChatEnabled",
|
||||||
socialFrameDetached: "Loop:ChatWindowDetached",
|
LoopChatDisabledMessageAppended: "loopChatDisabledMessageAppended",
|
||||||
socialFrameHide: "Loop:ChatWindowHidden",
|
LoopChatMessageAppended: "loopChatMessageAppended"
|
||||||
socialFrameShow: "Loop:ChatWindowShown",
|
};
|
||||||
unload: "Loop:ChatWindowClosed"
|
|
||||||
|
let listeners = {};
|
||||||
|
|
||||||
|
let messageName = "Social:CustomEvent";
|
||||||
|
mm.addMessageListener(messageName, listeners[messageName] = message => {
|
||||||
|
let eventName = message.data.name;
|
||||||
|
if (kEventNamesMap[eventName]) {
|
||||||
|
eventName = kEventNamesMap[eventName];
|
||||||
|
|
||||||
|
UITour.clearAvailableTargetsCache();
|
||||||
|
UITour.notify(eventName);
|
||||||
|
} else {
|
||||||
|
// When the chat box or messages are shown, resize the panel or window
|
||||||
|
// to be slightly higher to accomodate them.
|
||||||
|
let customSize = kSizeMap[eventName];
|
||||||
|
let currSize = chatbox.getAttribute("customSize");
|
||||||
|
// If the size is already at the requested one or at the maximum size
|
||||||
|
// already, don't do anything. Especially don't make it shrink.
|
||||||
|
if (customSize && currSize != customSize && currSize != "loopChatMessageAppended") {
|
||||||
|
chatbox.setAttribute("customSize", customSize);
|
||||||
|
chatbox.parentNode.setAttribute("customSize", customSize);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Handle window.close correctly on the chatbox.
|
||||||
|
mm.sendAsyncMessage("Social:HookWindowCloseForPanelClose");
|
||||||
|
messageName = "DOMWindowClose";
|
||||||
|
mm.addMessageListener(messageName, listeners[messageName] = () => {
|
||||||
|
// Remove message listeners.
|
||||||
|
for (let name of Object.getOwnPropertyNames(listeners)) {
|
||||||
|
mm.removeMessageListener(name, listeners[name]);
|
||||||
|
}
|
||||||
|
listeners = {};
|
||||||
|
|
||||||
|
windowCloseCallback();
|
||||||
|
|
||||||
|
if (conversationWindowData.type == "room") {
|
||||||
|
// NOTE: if you add something here, please also consider if something
|
||||||
|
// needs to be done on the content side as well (e.g.
|
||||||
|
// activeRoomStore#windowUnload).
|
||||||
|
LoopAPI.sendMessageToHandler({
|
||||||
|
name: "HangupNow",
|
||||||
|
data: [conversationWindowData.roomToken, windowId]
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
chatbox.close();
|
||||||
|
});
|
||||||
|
|
||||||
|
mm.sendAsyncMessage("Loop:MonitorPeerConnectionLifecycle");
|
||||||
|
messageName = "Loop:PeerConnectionLifecycleChange";
|
||||||
|
mm.addMessageListener(messageName, listeners[messageName] = message => {
|
||||||
|
// Chat Window Id, this is different that the internal winId
|
||||||
|
let chatWindowId = message.data.locationHash.slice(1);
|
||||||
|
var context = this.conversationContexts.get(chatWindowId);
|
||||||
|
var peerConnectionID = message.data.peerConnectionID;
|
||||||
|
var exists = peerConnectionID.match(/session=(\S+)/);
|
||||||
|
if (context && !exists) {
|
||||||
|
// Not ideal but insert our data amidst existing data like this:
|
||||||
|
// - 000 (id=00 url=http)
|
||||||
|
// + 000 (session=000 call=000 id=00 url=http)
|
||||||
|
var pair = peerConnectionID.split("(");
|
||||||
|
if (pair.length == 2) {
|
||||||
|
peerConnectionID = pair[0] + "(session=" + context.sessionId +
|
||||||
|
(context.callId ? " call=" + context.callId : "") + " " + pair[1];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (message.data.type == "iceconnectionstatechange") {
|
||||||
|
switch (message.data.iceConnectionState) {
|
||||||
|
case "failed":
|
||||||
|
case "disconnected":
|
||||||
|
if (Services.telemetry.canRecordExtended) {
|
||||||
|
this.stageForTelemetryUpload(chatbox.content, message.data);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// When a chat window is attached or detached, the docShells hosting
|
||||||
|
// about:loopconverstation is swapped to the newly created chat window.
|
||||||
|
// (Be it inside a popup or back inside a chatbox element attached to the
|
||||||
|
// chatbar.)
|
||||||
|
// Since a swapDocShells call does not swap the messageManager instances
|
||||||
|
// attached to a browser, we'll need to add the message listeners to
|
||||||
|
// the new messageManager. This is not a bug in swapDocShells, merely
|
||||||
|
// a design decision.
|
||||||
|
chatbox.content.addEventListener("SwapDocShells", function swapped(ev) {
|
||||||
|
chatbox.content.removeEventListener("SwapDocShells", swapped);
|
||||||
|
|
||||||
|
let otherBrowser = ev.detail;
|
||||||
|
chatbox = otherBrowser.ownerDocument.getBindingParent(otherBrowser);
|
||||||
|
mm = otherBrowser.messageManager;
|
||||||
|
otherBrowser.addEventListener("SwapDocShells", swapped);
|
||||||
|
|
||||||
|
for (let name of Object.getOwnPropertyNames(listeners)) {
|
||||||
|
mm.addMessageListener(name, listeners[name]);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
UITour.notify("Loop:ChatWindowOpened");
|
||||||
|
resolve(windowId);
|
||||||
};
|
};
|
||||||
|
|
||||||
const kSizeMap = {
|
mm.sendAsyncMessage("WaitForDOMContentLoaded");
|
||||||
LoopChatEnabled: "loopChatEnabled",
|
mm.addMessageListener("DOMContentLoaded", loaded);
|
||||||
LoopChatDisabledMessageAppended: "loopChatDisabledMessageAppended",
|
|
||||||
LoopChatMessageAppended: "loopChatMessageAppended"
|
|
||||||
};
|
|
||||||
|
|
||||||
let listeners = {};
|
|
||||||
|
|
||||||
let messageName = "Social:CustomEvent";
|
|
||||||
mm.addMessageListener(messageName, listeners[messageName] = message => {
|
|
||||||
let eventName = message.data.name;
|
|
||||||
if (kEventNamesMap[eventName]) {
|
|
||||||
eventName = kEventNamesMap[eventName];
|
|
||||||
|
|
||||||
UITour.clearAvailableTargetsCache();
|
|
||||||
UITour.notify(eventName);
|
|
||||||
|
|
||||||
if (eventName == "Loop:ChatWindowDetached" || eventName == "Loop:ChatWindowAttached") {
|
|
||||||
// After detach, re-attach of the chatbox, refresh its reference so
|
|
||||||
// we can keep using it here.
|
|
||||||
let ref = chatbar.chatboxForURL.get(chatbox.src);
|
|
||||||
chatbox = ref && ref.get() || chatbox;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// When the chat box or messages are shown, resize the panel or window
|
|
||||||
// to be slightly higher to accomodate them.
|
|
||||||
let customSize = kSizeMap[eventName];
|
|
||||||
let currSize = chatbox.getAttribute("customSize");
|
|
||||||
// If the size is already at the requested one or at the maximum size
|
|
||||||
// already, don't do anything. Especially don't make it shrink.
|
|
||||||
if (customSize && currSize != customSize && currSize != "loopChatMessageAppended") {
|
|
||||||
chatbox.setAttribute("customSize", customSize);
|
|
||||||
chatbox.parentNode.setAttribute("customSize", customSize);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// Handle window.close correctly on the chatbox.
|
|
||||||
hookWindowCloseForPanelClose(chatbox.content);
|
|
||||||
messageName = "DOMWindowClose";
|
|
||||||
mm.addMessageListener(messageName, listeners[messageName] = () => {
|
|
||||||
// Remove message listeners.
|
|
||||||
for (let name of Object.getOwnPropertyNames(listeners)) {
|
|
||||||
mm.removeMessageListener(name, listeners[name]);
|
|
||||||
}
|
|
||||||
listeners = {};
|
|
||||||
|
|
||||||
windowCloseCallback();
|
|
||||||
|
|
||||||
if (conversationWindowData.type == "room") {
|
|
||||||
// NOTE: if you add something here, please also consider if something
|
|
||||||
// needs to be done on the content side as well (e.g.
|
|
||||||
// activeRoomStore#windowUnload).
|
|
||||||
LoopAPI.sendMessageToHandler({
|
|
||||||
name: "HangupNow",
|
|
||||||
data: [conversationWindowData.roomToken, windowId]
|
|
||||||
});
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
mm.sendAsyncMessage("Loop:MonitorPeerConnectionLifecycle");
|
|
||||||
messageName = "Loop:PeerConnectionLifecycleChange";
|
|
||||||
mm.addMessageListener(messageName, listeners[messageName] = message => {
|
|
||||||
// Chat Window Id, this is different that the internal winId
|
|
||||||
let chatWindowId = message.data.locationHash.slice(1);
|
|
||||||
var context = this.conversationContexts.get(chatWindowId);
|
|
||||||
var peerConnectionID = message.data.peerConnectionID;
|
|
||||||
var exists = peerConnectionID.match(/session=(\S+)/);
|
|
||||||
if (context && !exists) {
|
|
||||||
// Not ideal but insert our data amidst existing data like this:
|
|
||||||
// - 000 (id=00 url=http)
|
|
||||||
// + 000 (session=000 call=000 id=00 url=http)
|
|
||||||
var pair = peerConnectionID.split("(");
|
|
||||||
if (pair.length == 2) {
|
|
||||||
peerConnectionID = pair[0] + "(session=" + context.sessionId +
|
|
||||||
(context.callId ? " call=" + context.callId : "") + " " + pair[1];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (message.data.type == "iceconnectionstatechange") {
|
|
||||||
switch (message.data.iceConnectionState) {
|
|
||||||
case "failed":
|
|
||||||
case "disconnected":
|
|
||||||
if (Services.telemetry.canRecordExtended) {
|
|
||||||
this.stageForTelemetryUpload(chatbox.content, message.data);
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
UITour.notify("Loop:ChatWindowOpened");
|
|
||||||
};
|
};
|
||||||
|
|
||||||
mm.sendAsyncMessage("WaitForDOMContentLoaded");
|
LoopAPI.initialize();
|
||||||
mm.addMessageListener("DOMContentLoaded", loaded);
|
let chatboxInstance = Chat.open(null, {
|
||||||
};
|
origin: origin,
|
||||||
|
title: "",
|
||||||
LoopAPI.initialize();
|
url: url,
|
||||||
let chatboxInstance = Chat.open(null, {
|
remote: MozLoopService.getLoopPref("remote.autostart")
|
||||||
origin: origin,
|
}, callback);
|
||||||
title: "",
|
if (!chatboxInstance) {
|
||||||
url: url,
|
resolve(null);
|
||||||
remote: MozLoopService.getLoopPref("remote.autostart")
|
// It's common for unit tests to overload Chat.open.
|
||||||
}, callback);
|
} else if (chatboxInstance.setAttribute) {
|
||||||
if (!chatboxInstance) {
|
// Set properties that influence visual appearance of the chatbox right
|
||||||
return null;
|
// away to circumvent glitches.
|
||||||
// It's common for unit tests to overload Chat.open.
|
chatboxInstance.setAttribute("customSize", "loopDefault");
|
||||||
} else if (chatboxInstance.setAttribute) {
|
chatboxInstance.parentNode.setAttribute("customSize", "loopDefault");
|
||||||
// Set properties that influence visual appearance of the chatbox right
|
Chat.loadButtonSet(chatboxInstance, "minimize,swap," + kChatboxHangupButton.id);
|
||||||
// away to circumvent glitches.
|
resolve(windowId);
|
||||||
chatboxInstance.setAttribute("customSize", "loopDefault");
|
}
|
||||||
chatboxInstance.parentNode.setAttribute("customSize", "loopDefault");
|
});
|
||||||
Chat.loadButtonSet(chatboxInstance, "minimize,swap," + kChatboxHangupButton.id);
|
|
||||||
}
|
|
||||||
return windowId;
|
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -1247,14 +1293,18 @@ this.MozLoopService = {
|
|||||||
*
|
*
|
||||||
* Note: this returns a promise for unit test purposes.
|
* Note: this returns a promise for unit test purposes.
|
||||||
*
|
*
|
||||||
|
* @param {String} addonVersion The name of the add-on
|
||||||
|
*
|
||||||
* @return {Promise}
|
* @return {Promise}
|
||||||
*/
|
*/
|
||||||
initialize: Task.async(function*() {
|
initialize: Task.async(function*(addonVersion) {
|
||||||
// Ensure we don't setup things like listeners more than once.
|
// Ensure we don't setup things like listeners more than once.
|
||||||
if (gServiceInitialized) {
|
if (gServiceInitialized) {
|
||||||
return Promise.resolve();
|
return Promise.resolve();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
gAddonVersion = addonVersion;
|
||||||
|
|
||||||
gServiceInitialized = true;
|
gServiceInitialized = true;
|
||||||
|
|
||||||
// Do this here, rather than immediately after definition, so that we can
|
// Do this here, rather than immediately after definition, so that we can
|
||||||
@ -1605,6 +1655,19 @@ this.MozLoopService = {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Returns current FTU version
|
||||||
|
*
|
||||||
|
* @return {Number}
|
||||||
|
*
|
||||||
|
* XXX must match number in panel.jsx; expose this via MozLoopAPI
|
||||||
|
* and kill that constant.
|
||||||
|
*/
|
||||||
|
get FTU_VERSION()
|
||||||
|
{
|
||||||
|
return 2;
|
||||||
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Set any preference under "loop.".
|
* Set any preference under "loop.".
|
||||||
*
|
*
|
||||||
@ -1868,20 +1931,65 @@ this.MozLoopService = {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Opens the Getting Started tour in the browser.
|
* Opens the Getting Started tour in the browser.
|
||||||
*
|
|
||||||
* @param {String} [aSrc] A string representing the entry point to begin the tour, optional.
|
|
||||||
*/
|
*/
|
||||||
openGettingStartedTour: Task.async(function(aSrc = null) {
|
openGettingStartedTour: Task.async(function() {
|
||||||
try {
|
const kNSXUL = "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul";
|
||||||
let url = this.getTourURL(aSrc);
|
|
||||||
let win = Services.wm.getMostRecentWindow("navigator:browser");
|
// User will have _just_ clicked the tour menu item or the FTU
|
||||||
win.switchToTabHavingURI(url, true, {
|
// button in the panel, (or else it wouldn't be visible), so...
|
||||||
ignoreFragment: true,
|
let xulWin = Services.wm.getMostRecentWindow("navigator:browser");
|
||||||
replaceQueryString: true
|
let xulDoc = xulWin.document;
|
||||||
});
|
|
||||||
} catch (ex) {
|
let box = xulDoc.createElementNS(kNSXUL, "box");
|
||||||
log.error("Error opening Getting Started tour", ex);
|
box.setAttribute("id", "loop-slideshow-container");
|
||||||
|
|
||||||
|
let appContent = xulDoc.getElementById("appcontent");
|
||||||
|
let tabBrowser = xulDoc.getElementById("content");
|
||||||
|
appContent.insertBefore(box, tabBrowser);
|
||||||
|
|
||||||
|
var xulBrowser = xulDoc.createElementNS(kNSXUL, "browser");
|
||||||
|
xulBrowser.setAttribute("id", "loop-slideshow-browser");
|
||||||
|
xulBrowser.setAttribute("flex", "1");
|
||||||
|
xulBrowser.setAttribute("type", "content");
|
||||||
|
box.appendChild(xulBrowser);
|
||||||
|
|
||||||
|
// Notify the UI, which has the side effect of disabling panel opening
|
||||||
|
// and updating the toolbar icon to visually indicate difference.
|
||||||
|
xulWin.LoopUI.isSlideshowOpen = true;
|
||||||
|
|
||||||
|
var removeSlideshow = function() {
|
||||||
|
try {
|
||||||
|
appContent.removeChild(box);
|
||||||
|
} catch (ex) {
|
||||||
|
log.error(ex);
|
||||||
|
}
|
||||||
|
|
||||||
|
this.setLoopPref("gettingStarted.latestFTUVersion", this.FTU_VERSION);
|
||||||
|
|
||||||
|
// Notify the UI, which has the side effect of re-enabling panel opening
|
||||||
|
// and updating the toolbar.
|
||||||
|
xulWin.LoopUI.isSlideshowOpen = false;
|
||||||
|
|
||||||
|
xulWin.removeEventListener("CloseSlideshow", removeSlideshow);
|
||||||
|
|
||||||
|
log.info("slideshow removed");
|
||||||
|
}.bind(this);
|
||||||
|
|
||||||
|
function xulLoadListener() {
|
||||||
|
xulBrowser.contentWindow.addEventListener("CloseSlideshow",
|
||||||
|
removeSlideshow);
|
||||||
|
log.info("CloseSlideshow handler added");
|
||||||
|
|
||||||
|
xulBrowser.removeEventListener("load", xulLoadListener, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
xulBrowser.addEventListener("load", xulLoadListener, true);
|
||||||
|
|
||||||
|
// XXX we are loading the slideshow page with chrome privs.
|
||||||
|
// To make this remote, we'll need to think through a better
|
||||||
|
// security model.
|
||||||
|
xulBrowser.setAttribute("src",
|
||||||
|
"chrome://loop/content/panels/slideshow.html");
|
||||||
}),
|
}),
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -28,22 +28,26 @@
|
|||||||
|
|
||||||
<script type="text/javascript" src="shared/js/loopapi-client.js"></script>
|
<script type="text/javascript" src="shared/js/loopapi-client.js"></script>
|
||||||
<script type="text/javascript" src="shared/js/utils.js"></script>
|
<script type="text/javascript" src="shared/js/utils.js"></script>
|
||||||
|
<script type="text/javascript" src="shared/js/urlRegExps.js"></script>
|
||||||
<script type="text/javascript" src="shared/js/mixins.js"></script>
|
<script type="text/javascript" src="shared/js/mixins.js"></script>
|
||||||
<script type="text/javascript" src="shared/js/actions.js"></script>
|
<script type="text/javascript" src="shared/js/actions.js"></script>
|
||||||
<script type="text/javascript" src="shared/js/validate.js"></script>
|
<script type="text/javascript" src="shared/js/validate.js"></script>
|
||||||
<script type="text/javascript" src="shared/js/dispatcher.js"></script>
|
<script type="text/javascript" src="shared/js/dispatcher.js"></script>
|
||||||
<script type="text/javascript" src="shared/js/otSdkDriver.js"></script>
|
<script type="text/javascript" src="shared/js/otSdkDriver.js"></script>
|
||||||
|
|
||||||
|
<!-- Stores need to be loaded before the views that uses them -->
|
||||||
<script type="text/javascript" src="shared/js/store.js"></script>
|
<script type="text/javascript" src="shared/js/store.js"></script>
|
||||||
|
<script type="text/javascript" src="panels/js/roomStore.js"></script>
|
||||||
<script type="text/javascript" src="shared/js/activeRoomStore.js"></script>
|
<script type="text/javascript" src="shared/js/activeRoomStore.js"></script>
|
||||||
<script type="text/javascript" src="shared/js/views.js"></script>
|
<script type="text/javascript" src="panels/js/conversationAppStore.js"></script>
|
||||||
<script type="text/javascript" src="shared/js/textChatStore.js"></script>
|
<script type="text/javascript" src="shared/js/textChatStore.js"></script>
|
||||||
|
<script type="text/javascript" src="shared/js/remoteCursorStore.js"></script>
|
||||||
|
|
||||||
|
<!-- Views -->
|
||||||
|
<script type="text/javascript" src="shared/js/views.js"></script>
|
||||||
<script type="text/javascript" src="shared/js/textChatView.js"></script>
|
<script type="text/javascript" src="shared/js/textChatView.js"></script>
|
||||||
<script type="text/javascript" src="shared/js/linkifiedTextView.js"></script>
|
<script type="text/javascript" src="shared/js/linkifiedTextView.js"></script>
|
||||||
<script type="text/javascript" src="shared/js/urlRegExps.js"></script>
|
|
||||||
<script type="text/javascript" src="panels/js/conversationAppStore.js"></script>
|
|
||||||
<script type="text/javascript" src="panels/js/feedbackViews.js"></script>
|
<script type="text/javascript" src="panels/js/feedbackViews.js"></script>
|
||||||
<script type="text/javascript" src="panels/js/roomStore.js"></script>
|
|
||||||
<script type="text/javascript" src="shared/js/remoteCursorStore.js"></script>
|
|
||||||
<script type="text/javascript" src="panels/js/roomViews.js"></script>
|
<script type="text/javascript" src="panels/js/roomViews.js"></script>
|
||||||
<script type="text/javascript" src="panels/js/conversation.js"></script>
|
<script type="text/javascript" src="panels/js/conversation.js"></script>
|
||||||
</body>
|
</body>
|
||||||
|
@ -0,0 +1,97 @@
|
|||||||
|
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||||
|
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||||
|
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||||
|
|
||||||
|
.slideshow {
|
||||||
|
-moz-user-select: none;
|
||||||
|
cursor: default;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* slide content */
|
||||||
|
.slide > div {
|
||||||
|
height: 100%;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.slide-layout {
|
||||||
|
padding-top: 50px;
|
||||||
|
padding-left: 50px;
|
||||||
|
padding-bottom: 20px;
|
||||||
|
width: 600px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.slide-layout > h2 {
|
||||||
|
font-family: sans-serif;
|
||||||
|
font-size: 3.0rem;
|
||||||
|
font-weight: 500;
|
||||||
|
white-space: normal;
|
||||||
|
color: #fff;
|
||||||
|
line-height: 3rem;
|
||||||
|
margin-bottom: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.slide-layout > .slide-text {
|
||||||
|
font-family: sans-serif;
|
||||||
|
font-size: 2.1rem;
|
||||||
|
font-weight: 300;
|
||||||
|
white-space: normal;
|
||||||
|
color: #fff;
|
||||||
|
line-height: 3.0rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.slide-layout > img {
|
||||||
|
display: block;
|
||||||
|
-moz-box-sizing: border-box;
|
||||||
|
box-sizing: border-box;
|
||||||
|
border: none;
|
||||||
|
background-color: transparent;
|
||||||
|
background-repeat: no-repeat;
|
||||||
|
background-position: center;
|
||||||
|
padding: 0;
|
||||||
|
float: right;
|
||||||
|
margin-top: -20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.slide1 {
|
||||||
|
background: #06a6e0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.slide1-image {
|
||||||
|
background-image: url(../../shared/img/firefox-hello_tour-slide-01.svg);
|
||||||
|
height: 260px;
|
||||||
|
width: 295px;
|
||||||
|
background-size: 310px 310px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.slide2 {
|
||||||
|
background: #0183b2;
|
||||||
|
}
|
||||||
|
|
||||||
|
.slide2-image {
|
||||||
|
background-image: url(../../shared/img/firefox-hello_tour-slide-02.svg);
|
||||||
|
height: 245px;
|
||||||
|
width: 245px;
|
||||||
|
background-size: 320px 320px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.slide3 {
|
||||||
|
background: #005da5;
|
||||||
|
}
|
||||||
|
|
||||||
|
.slide3-image {
|
||||||
|
background-image: url(../../shared/img/firefox-hello_tour-slide-03.svg);
|
||||||
|
height: 260px;
|
||||||
|
width: 260px;
|
||||||
|
background-size: 310px 310px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.slide4 {
|
||||||
|
background: #7ec24c;
|
||||||
|
}
|
||||||
|
|
||||||
|
.slide4-image {
|
||||||
|
background-image: url(../../shared/img/firefox-hello_tour-slide-04.svg);
|
||||||
|
height: 240px;
|
||||||
|
width: 240px;
|
||||||
|
background-size: 320px 320px;
|
||||||
|
}
|
@ -24,10 +24,19 @@ loop.conversation = function (mozL10n) {
|
|||||||
mixins: [Backbone.Events, loop.store.StoreMixin("conversationAppStore"), sharedMixins.DocumentTitleMixin, sharedMixins.WindowCloseMixin],
|
mixins: [Backbone.Events, loop.store.StoreMixin("conversationAppStore"), sharedMixins.DocumentTitleMixin, sharedMixins.WindowCloseMixin],
|
||||||
|
|
||||||
propTypes: {
|
propTypes: {
|
||||||
|
cursorStore: React.PropTypes.instanceOf(loop.store.RemoteCursorStore).isRequired,
|
||||||
dispatcher: React.PropTypes.instanceOf(loop.Dispatcher).isRequired,
|
dispatcher: React.PropTypes.instanceOf(loop.Dispatcher).isRequired,
|
||||||
roomStore: React.PropTypes.instanceOf(loop.store.RoomStore)
|
roomStore: React.PropTypes.instanceOf(loop.store.RoomStore)
|
||||||
},
|
},
|
||||||
|
|
||||||
|
componentWillMount: function () {
|
||||||
|
this.listenTo(this.props.cursorStore, "change:remoteCursorPosition", this._onRemoteCursorChange);
|
||||||
|
},
|
||||||
|
|
||||||
|
_onRemoteCursorChange: function () {
|
||||||
|
return loop.request("AddRemoteCursorOverlay", this.props.cursorStore.getStoreState("remoteCursorPosition"));
|
||||||
|
},
|
||||||
|
|
||||||
getInitialState: function () {
|
getInitialState: function () {
|
||||||
return this.getStoreState();
|
return this.getStoreState();
|
||||||
},
|
},
|
||||||
@ -66,6 +75,7 @@ loop.conversation = function (mozL10n) {
|
|||||||
{
|
{
|
||||||
return React.createElement(DesktopRoomConversationView, {
|
return React.createElement(DesktopRoomConversationView, {
|
||||||
chatWindowDetached: this.state.chatWindowDetached,
|
chatWindowDetached: this.state.chatWindowDetached,
|
||||||
|
cursorStore: this.props.cursorStore,
|
||||||
dispatcher: this.props.dispatcher,
|
dispatcher: this.props.dispatcher,
|
||||||
facebookEnabled: this.state.facebookEnabled,
|
facebookEnabled: this.state.facebookEnabled,
|
||||||
onCallTerminated: this.handleCallTerminated,
|
onCallTerminated: this.handleCallTerminated,
|
||||||
@ -188,6 +198,7 @@ loop.conversation = function (mozL10n) {
|
|||||||
});
|
});
|
||||||
|
|
||||||
React.render(React.createElement(AppControllerView, {
|
React.render(React.createElement(AppControllerView, {
|
||||||
|
cursorStore: remoteCursorStore,
|
||||||
dispatcher: dispatcher,
|
dispatcher: dispatcher,
|
||||||
roomStore: roomStore }), document.querySelector("#main"));
|
roomStore: roomStore }), document.querySelector("#main"));
|
||||||
|
|
||||||
@ -198,6 +209,8 @@ loop.conversation = function (mozL10n) {
|
|||||||
dispatcher.dispatch(new sharedActions.GetWindowData({
|
dispatcher.dispatch(new sharedActions.GetWindowData({
|
||||||
windowId: windowId
|
windowId: windowId
|
||||||
}));
|
}));
|
||||||
|
|
||||||
|
loop.request("TelemetryAddValue", "LOOP_MAU", constants.LOOP_MAU_TYPE.OPEN_CONVERSATION);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -12,7 +12,9 @@ loop.panel = function (_, mozL10n) {
|
|||||||
var sharedActions = loop.shared.actions;
|
var sharedActions = loop.shared.actions;
|
||||||
var Button = sharedViews.Button;
|
var Button = sharedViews.Button;
|
||||||
|
|
||||||
var FTU_VERSION = 1;
|
// XXX This must be kept in sync with the number in MozLoopService.jsm.
|
||||||
|
// We should expose that one through MozLoopAPI and kill this constant.
|
||||||
|
var FTU_VERSION = 2;
|
||||||
|
|
||||||
var GettingStartedView = React.createClass({
|
var GettingStartedView = React.createClass({
|
||||||
displayName: "GettingStartedView",
|
displayName: "GettingStartedView",
|
||||||
@ -20,10 +22,7 @@ loop.panel = function (_, mozL10n) {
|
|||||||
mixins: [sharedMixins.WindowCloseMixin],
|
mixins: [sharedMixins.WindowCloseMixin],
|
||||||
|
|
||||||
handleButtonClick: function () {
|
handleButtonClick: function () {
|
||||||
loop.requestMulti(["OpenGettingStartedTour", "getting-started"], ["SetLoopPref", "gettingStarted.latestFTUVersion", FTU_VERSION]).then(function () {
|
loop.request("OpenGettingStartedTour");
|
||||||
var event = new CustomEvent("GettingStartedSeen");
|
|
||||||
window.dispatchEvent(event);
|
|
||||||
}.bind(this));
|
|
||||||
this.closeWindow();
|
this.closeWindow();
|
||||||
},
|
},
|
||||||
|
|
||||||
@ -292,7 +291,7 @@ loop.panel = function (_, mozL10n) {
|
|||||||
},
|
},
|
||||||
|
|
||||||
openGettingStartedTour: function () {
|
openGettingStartedTour: function () {
|
||||||
loop.request("OpenGettingStartedTour", "settings-menu");
|
loop.request("OpenGettingStartedTour");
|
||||||
this.closeWindow();
|
this.closeWindow();
|
||||||
},
|
},
|
||||||
|
|
||||||
@ -907,7 +906,6 @@ loop.panel = function (_, mozL10n) {
|
|||||||
propTypes: {
|
propTypes: {
|
||||||
onClick: React.PropTypes.func.isRequired
|
onClick: React.PropTypes.func.isRequired
|
||||||
},
|
},
|
||||||
|
|
||||||
componentWillMount: function () {
|
componentWillMount: function () {
|
||||||
loop.request("SetPanelHeight", 262);
|
loop.request("SetPanelHeight", 262);
|
||||||
},
|
},
|
||||||
@ -1003,26 +1001,32 @@ loop.panel = function (_, mozL10n) {
|
|||||||
},
|
},
|
||||||
|
|
||||||
_onStatusChanged: function () {
|
_onStatusChanged: function () {
|
||||||
loop.requestMulti(["GetUserProfile"], ["GetHasEncryptionKey"]).then(function (results) {
|
loop.requestMulti(["GetUserProfile"], ["GetHasEncryptionKey"], ["GetLoopPref", "gettingStarted.latestFTUVersion"]).then(function (results) {
|
||||||
var profile = results[0];
|
var profile = results[0];
|
||||||
var hasEncryptionKey = results[1];
|
var hasEncryptionKey = results[1];
|
||||||
|
var prefFTUVersion = results[2];
|
||||||
|
|
||||||
|
var stateToUpdate = {};
|
||||||
|
|
||||||
|
// It's possible that this state change was slideshow related
|
||||||
|
// so update that if the pref has changed.
|
||||||
|
var prefGettingStartedSeen = prefFTUVersion >= FTU_VERSION;
|
||||||
|
if (prefGettingStartedSeen !== this.state.gettingStartedSeen) {
|
||||||
|
stateToUpdate.gettingStartedSeen = prefGettingStartedSeen;
|
||||||
|
}
|
||||||
|
|
||||||
var currUid = this.state.userProfile ? this.state.userProfile.uid : null;
|
var currUid = this.state.userProfile ? this.state.userProfile.uid : null;
|
||||||
var newUid = profile ? profile.uid : null;
|
var newUid = profile ? profile.uid : null;
|
||||||
if (currUid === newUid) {
|
if (currUid === newUid) {
|
||||||
// Update the state of hasEncryptionKey as this might have changed now.
|
// Update the state of hasEncryptionKey as this might have changed now.
|
||||||
this.setState({ hasEncryptionKey: hasEncryptionKey });
|
stateToUpdate.hasEncryptionKey = hasEncryptionKey;
|
||||||
} else {
|
} else {
|
||||||
this.setState({ userProfile: profile });
|
stateToUpdate.userProfile = profile;
|
||||||
}
|
}
|
||||||
this.updateServiceErrors();
|
|
||||||
}.bind(this));
|
|
||||||
},
|
|
||||||
|
|
||||||
_gettingStartedSeen: function () {
|
this.setState(stateToUpdate);
|
||||||
loop.request("GetLoopPref", "gettingStarted.latestFTUVersion").then(function (result) {
|
|
||||||
this.setState({
|
this.updateServiceErrors();
|
||||||
gettingStartedSeen: result >= FTU_VERSION
|
|
||||||
});
|
|
||||||
}.bind(this));
|
}.bind(this));
|
||||||
},
|
},
|
||||||
|
|
||||||
@ -1032,12 +1036,10 @@ loop.panel = function (_, mozL10n) {
|
|||||||
|
|
||||||
componentDidMount: function () {
|
componentDidMount: function () {
|
||||||
loop.subscribe("LoopStatusChanged", this._onStatusChanged);
|
loop.subscribe("LoopStatusChanged", this._onStatusChanged);
|
||||||
window.addEventListener("GettingStartedSeen", this._gettingStartedSeen);
|
|
||||||
},
|
},
|
||||||
|
|
||||||
componentWillUnmount: function () {
|
componentWillUnmount: function () {
|
||||||
loop.unsubscribe("LoopStatusChanged", this._onStatusChanged);
|
loop.unsubscribe("LoopStatusChanged", this._onStatusChanged);
|
||||||
window.removeEventListener("GettingStartedSeen", this._gettingStartedSeen);
|
|
||||||
},
|
},
|
||||||
|
|
||||||
handleContextMenu: function (e) {
|
handleContextMenu: function (e) {
|
||||||
|
@ -322,7 +322,10 @@ loop.store = loop.store || {};
|
|||||||
console.error("No URL sharing type bucket found for '" + from + "'");
|
console.error("No URL sharing type bucket found for '" + from + "'");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
loop.request("TelemetryAddValue", "LOOP_SHARING_ROOM_URL", bucket);
|
loop.requestMulti(
|
||||||
|
["TelemetryAddValue", "LOOP_SHARING_ROOM_URL", bucket],
|
||||||
|
["TelemetryAddValue", "LOOP_MAU", this._constants.LOOP_MAU_TYPE.ROOM_SHARE]
|
||||||
|
);
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -335,14 +338,18 @@ loop.store = loop.store || {};
|
|||||||
loop.shared.utils.composeCallUrlEmail(actionData.roomUrl, null,
|
loop.shared.utils.composeCallUrlEmail(actionData.roomUrl, null,
|
||||||
actionData.roomDescription);
|
actionData.roomDescription);
|
||||||
|
|
||||||
var bucket = this._constants.SHARING_ROOM_URL["EMAIL_FROM_" + (from || "").toUpperCase()];
|
var bucket = this._constants.SHARING_ROOM_URL[
|
||||||
|
"EMAIL_FROM_" + (from || "").toUpperCase()
|
||||||
|
];
|
||||||
if (typeof bucket === "undefined") {
|
if (typeof bucket === "undefined") {
|
||||||
console.error("No URL sharing type bucket found for '" + from + "'");
|
console.error("No URL sharing type bucket found for '" + from + "'");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
loop.requestMulti(
|
loop.requestMulti(
|
||||||
["NotifyUITour", "Loop:RoomURLEmailed"],
|
["NotifyUITour", "Loop:RoomURLEmailed"],
|
||||||
["TelemetryAddValue", "LOOP_SHARING_ROOM_URL", bucket]);
|
["TelemetryAddValue", "LOOP_SHARING_ROOM_URL", bucket],
|
||||||
|
["TelemetryAddValue", "LOOP_MAU", this._constants.LOOP_MAU_TYPE.ROOM_SHARE]
|
||||||
|
);
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -352,12 +359,25 @@ loop.store = loop.store || {};
|
|||||||
*/
|
*/
|
||||||
facebookShareRoomUrl: function(actionData) {
|
facebookShareRoomUrl: function(actionData) {
|
||||||
var encodedRoom = encodeURIComponent(actionData.roomUrl);
|
var encodedRoom = encodeURIComponent(actionData.roomUrl);
|
||||||
loop.request("GetLoopPref", "facebook.shareUrl")
|
|
||||||
.then(shareUrl => {
|
loop.requestMulti(
|
||||||
loop.request("OpenURL", shareUrl.replace("%ROOM_URL%", encodedRoom));
|
["GetLoopPref", "facebook.appId"],
|
||||||
}).then(() => {
|
["GetLoopPref", "facebook.fallbackUrl"],
|
||||||
loop.request("NotifyUITour", "Loop:RoomURLShared");
|
["GetLoopPref", "facebook.shareUrl"]
|
||||||
});
|
).then(results => {
|
||||||
|
var app_id = results[0];
|
||||||
|
var fallback_url = results[1];
|
||||||
|
var redirect_url = encodeURIComponent(actionData.originUrl ||
|
||||||
|
fallback_url);
|
||||||
|
|
||||||
|
var finalURL = results[2].replace("%ROOM_URL%", encodedRoom)
|
||||||
|
.replace("%APP_ID%", app_id)
|
||||||
|
.replace("%REDIRECT_URI%", redirect_url);
|
||||||
|
|
||||||
|
return loop.request("OpenURL", finalURL);
|
||||||
|
}).then(() => {
|
||||||
|
loop.request("NotifyUITour", "Loop:RoomURLShared");
|
||||||
|
});
|
||||||
|
|
||||||
var from = actionData.from;
|
var from = actionData.from;
|
||||||
var bucket = this._constants.SHARING_ROOM_URL["FACEBOOK_FROM_" + from.toUpperCase()];
|
var bucket = this._constants.SHARING_ROOM_URL["FACEBOOK_FROM_" + from.toUpperCase()];
|
||||||
@ -365,7 +385,10 @@ loop.store = loop.store || {};
|
|||||||
console.error("No URL sharing type bucket found for '" + from + "'");
|
console.error("No URL sharing type bucket found for '" + from + "'");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
loop.request("TelemetryAddValue", "LOOP_SHARING_ROOM_URL", bucket);
|
loop.requestMulti(
|
||||||
|
["TelemetryAddValue", "LOOP_SHARING_ROOM_URL", bucket],
|
||||||
|
["TelemetryAddValue", "LOOP_MAU", this._constants.LOOP_MAU_TYPE.ROOM_SHARE]
|
||||||
|
);
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -419,8 +442,11 @@ loop.store = loop.store || {};
|
|||||||
this.dispatchAction(new sharedActions.DeleteRoomError({ error: result }));
|
this.dispatchAction(new sharedActions.DeleteRoomError({ error: result }));
|
||||||
}
|
}
|
||||||
var buckets = this._constants.ROOM_DELETE;
|
var buckets = this._constants.ROOM_DELETE;
|
||||||
loop.request("TelemetryAddValue", "LOOP_ROOM_DELETE", buckets[isError ?
|
loop.requestMulti(
|
||||||
"DELETE_FAIL" : "DELETE_SUCCESS"]);
|
["TelemetryAddValue", "LOOP_ROOM_DELETE", buckets[isError ?
|
||||||
|
"DELETE_FAIL" : "DELETE_SUCCESS"]],
|
||||||
|
["TelemetryAddValue", "LOOP_MAU", this._constants.LOOP_MAU_TYPE.ROOM_DELETE]
|
||||||
|
);
|
||||||
}.bind(this));
|
}.bind(this));
|
||||||
},
|
},
|
||||||
|
|
||||||
@ -483,7 +509,10 @@ loop.store = loop.store || {};
|
|||||||
* @param {sharedActions.OpenRoom} actionData The action data.
|
* @param {sharedActions.OpenRoom} actionData The action data.
|
||||||
*/
|
*/
|
||||||
openRoom: function(actionData) {
|
openRoom: function(actionData) {
|
||||||
loop.request("Rooms:Open", actionData.roomToken);
|
loop.requestMulti(
|
||||||
|
["Rooms:Open", actionData.roomToken],
|
||||||
|
["TelemetryAddValue", "LOOP_MAU", this._constants.LOOP_MAU_TYPE.ROOM_OPEN]
|
||||||
|
);
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -246,7 +246,7 @@ loop.roomViews = function (mozL10n) {
|
|||||||
displayName: "DesktopRoomInvitationView",
|
displayName: "DesktopRoomInvitationView",
|
||||||
|
|
||||||
statics: {
|
statics: {
|
||||||
TRIGGERED_RESET_DELAY: 2000
|
TRIGGERED_RESET_DELAY: 3000
|
||||||
},
|
},
|
||||||
|
|
||||||
mixins: [sharedMixins.DropdownMenuMixin(".room-invitation-overlay")],
|
mixins: [sharedMixins.DropdownMenuMixin(".room-invitation-overlay")],
|
||||||
@ -324,6 +324,7 @@ loop.roomViews = function (mozL10n) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
var cx = classNames;
|
var cx = classNames;
|
||||||
|
|
||||||
return React.createElement(
|
return React.createElement(
|
||||||
"div",
|
"div",
|
||||||
{ className: "room-invitation-overlay" },
|
{ className: "room-invitation-overlay" },
|
||||||
@ -331,18 +332,43 @@ loop.roomViews = function (mozL10n) {
|
|||||||
"div",
|
"div",
|
||||||
{ className: "room-invitation-content" },
|
{ className: "room-invitation-content" },
|
||||||
React.createElement(
|
React.createElement(
|
||||||
"p",
|
"div",
|
||||||
|
{ className: "room-context-header" },
|
||||||
|
mozL10n.get("invite_header_text_bold2")
|
||||||
|
),
|
||||||
|
React.createElement(
|
||||||
|
"div",
|
||||||
null,
|
null,
|
||||||
|
mozL10n.get("invite_header_text4")
|
||||||
|
)
|
||||||
|
),
|
||||||
|
React.createElement(
|
||||||
|
"div",
|
||||||
|
{ className: "input-button-group" },
|
||||||
|
React.createElement(
|
||||||
|
"div",
|
||||||
|
{ className: "input-button-group-label" },
|
||||||
|
mozL10n.get("invite_your_link")
|
||||||
|
),
|
||||||
|
React.createElement(
|
||||||
|
"div",
|
||||||
|
{ className: "input-button-content" },
|
||||||
React.createElement(
|
React.createElement(
|
||||||
"span",
|
"div",
|
||||||
{ className: "room-context-header" },
|
{ className: "input-group group-item-top" },
|
||||||
mozL10n.get("invite_header_text_bold")
|
React.createElement("input", { readOnly: true, type: "text", value: this.props.roomData.roomUrl })
|
||||||
),
|
),
|
||||||
" ",
|
|
||||||
React.createElement(
|
React.createElement(
|
||||||
"span",
|
"div",
|
||||||
null,
|
{ className: cx({
|
||||||
mozL10n.get("invite_header_text3")
|
"group-item-bottom": true,
|
||||||
|
"btn": true,
|
||||||
|
"invite-button": true,
|
||||||
|
"btn-copy": true,
|
||||||
|
"triggered": this.state.copiedUrl
|
||||||
|
}),
|
||||||
|
onClick: this.handleCopyButtonClick },
|
||||||
|
mozL10n.get(this.state.copiedUrl ? "invite_copied_link_button" : "invite_copy_link_button")
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
),
|
),
|
||||||
@ -350,23 +376,8 @@ loop.roomViews = function (mozL10n) {
|
|||||||
"div",
|
"div",
|
||||||
{ className: cx({
|
{ className: cx({
|
||||||
"btn-group": true,
|
"btn-group": true,
|
||||||
"call-action-group": true
|
"share-action-group": true
|
||||||
}) },
|
}) },
|
||||||
React.createElement(
|
|
||||||
"div",
|
|
||||||
{ className: cx({
|
|
||||||
"btn-copy": true,
|
|
||||||
"invite-button": true,
|
|
||||||
"triggered": this.state.copiedUrl
|
|
||||||
}),
|
|
||||||
onClick: this.handleCopyButtonClick },
|
|
||||||
React.createElement("img", { src: "shared/img/glyph-link-16x16.svg" }),
|
|
||||||
React.createElement(
|
|
||||||
"p",
|
|
||||||
null,
|
|
||||||
mozL10n.get(this.state.copiedUrl ? "invite_copied_link_button" : "invite_copy_link_button")
|
|
||||||
)
|
|
||||||
),
|
|
||||||
React.createElement(
|
React.createElement(
|
||||||
"div",
|
"div",
|
||||||
{ className: "btn-email invite-button",
|
{ className: "btn-email invite-button",
|
||||||
@ -374,7 +385,7 @@ loop.roomViews = function (mozL10n) {
|
|||||||
onMouseOver: this.resetTriggeredButtons },
|
onMouseOver: this.resetTriggeredButtons },
|
||||||
React.createElement("img", { src: "shared/img/glyph-email-16x16.svg" }),
|
React.createElement("img", { src: "shared/img/glyph-email-16x16.svg" }),
|
||||||
React.createElement(
|
React.createElement(
|
||||||
"p",
|
"div",
|
||||||
null,
|
null,
|
||||||
mozL10n.get("invite_email_link_button")
|
mozL10n.get("invite_email_link_button")
|
||||||
)
|
)
|
||||||
@ -388,7 +399,7 @@ loop.roomViews = function (mozL10n) {
|
|||||||
onMouseOver: this.resetTriggeredButtons },
|
onMouseOver: this.resetTriggeredButtons },
|
||||||
React.createElement("img", { src: "shared/img/glyph-facebook-16x16.svg" }),
|
React.createElement("img", { src: "shared/img/glyph-facebook-16x16.svg" }),
|
||||||
React.createElement(
|
React.createElement(
|
||||||
"p",
|
"div",
|
||||||
null,
|
null,
|
||||||
mozL10n.get("invite_facebook_button3")
|
mozL10n.get("invite_facebook_button3")
|
||||||
)
|
)
|
||||||
@ -416,6 +427,7 @@ loop.roomViews = function (mozL10n) {
|
|||||||
|
|
||||||
propTypes: {
|
propTypes: {
|
||||||
chatWindowDetached: React.PropTypes.bool.isRequired,
|
chatWindowDetached: React.PropTypes.bool.isRequired,
|
||||||
|
cursorStore: React.PropTypes.instanceOf(loop.store.RemoteCursorStore).isRequired,
|
||||||
dispatcher: React.PropTypes.instanceOf(loop.Dispatcher).isRequired,
|
dispatcher: React.PropTypes.instanceOf(loop.Dispatcher).isRequired,
|
||||||
facebookEnabled: React.PropTypes.bool.isRequired,
|
facebookEnabled: React.PropTypes.bool.isRequired,
|
||||||
// The poster URLs are for UI-showcase testing and development.
|
// The poster URLs are for UI-showcase testing and development.
|
||||||
@ -454,19 +466,6 @@ loop.roomViews = function (mozL10n) {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
|
||||||
* Used to control publishing a stream - i.e. to mute a stream
|
|
||||||
*
|
|
||||||
* @param {String} type The type of stream, e.g. "audio" or "video".
|
|
||||||
* @param {Boolean} enabled True to enable the stream, false otherwise.
|
|
||||||
*/
|
|
||||||
publishStream: function (type, enabled) {
|
|
||||||
this.props.dispatcher.dispatch(new sharedActions.SetMute({
|
|
||||||
type: type,
|
|
||||||
enabled: enabled
|
|
||||||
}));
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Determine if the invitation controls should be shown.
|
* Determine if the invitation controls should be shown.
|
||||||
*
|
*
|
||||||
@ -596,6 +595,7 @@ loop.roomViews = function (mozL10n) {
|
|||||||
React.createElement(
|
React.createElement(
|
||||||
sharedViews.MediaLayoutView,
|
sharedViews.MediaLayoutView,
|
||||||
{
|
{
|
||||||
|
cursorStore: this.props.cursorStore,
|
||||||
dispatcher: this.props.dispatcher,
|
dispatcher: this.props.dispatcher,
|
||||||
displayScreenShare: false,
|
displayScreenShare: false,
|
||||||
isLocalLoading: this._isLocalLoading(),
|
isLocalLoading: this._isLocalLoading(),
|
||||||
@ -616,7 +616,6 @@ loop.roomViews = function (mozL10n) {
|
|||||||
audio: { enabled: !this.state.audioMuted, visible: true },
|
audio: { enabled: !this.state.audioMuted, visible: true },
|
||||||
dispatcher: this.props.dispatcher,
|
dispatcher: this.props.dispatcher,
|
||||||
hangup: this.leaveRoom,
|
hangup: this.leaveRoom,
|
||||||
publishStream: this.publishStream,
|
|
||||||
showHangup: this.props.chatWindowDetached,
|
showHangup: this.props.chatWindowDetached,
|
||||||
video: { enabled: !this.state.videoMuted, visible: true } }),
|
video: { enabled: !this.state.videoMuted, visible: true } }),
|
||||||
React.createElement(DesktopRoomInvitationView, {
|
React.createElement(DesktopRoomInvitationView, {
|
||||||
|
@ -0,0 +1,81 @@
|
|||||||
|
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||||
|
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
|
||||||
|
* You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||||
|
|
||||||
|
var loop = loop || {};
|
||||||
|
loop.slideshow = function (mozL10n) {
|
||||||
|
"use strict";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Slideshow initialisation.
|
||||||
|
*/
|
||||||
|
|
||||||
|
function init() {
|
||||||
|
var requests = [["GetAllStrings"], ["GetLocale"], ["GetPluralRule"]];
|
||||||
|
return loop.requestMulti.apply(null, requests).then(function (results) {
|
||||||
|
// `requestIdx` is keyed off the order of the `requests`
|
||||||
|
// array. Be careful to update both when making changes.
|
||||||
|
var requestIdx = 0;
|
||||||
|
// Do the initial L10n setup, we do this before anything
|
||||||
|
// else to ensure the L10n environment is setup correctly.
|
||||||
|
var stringBundle = results[requestIdx];
|
||||||
|
var locale = results[++requestIdx];
|
||||||
|
var pluralRule = results[++requestIdx];
|
||||||
|
mozL10n.initialize({
|
||||||
|
locale: locale,
|
||||||
|
pluralRule: pluralRule,
|
||||||
|
getStrings: function (key) {
|
||||||
|
if (!(key in stringBundle)) {
|
||||||
|
return "{ textContent: '' }";
|
||||||
|
}
|
||||||
|
|
||||||
|
return JSON.stringify({
|
||||||
|
textContent: stringBundle[key]
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
document.documentElement.setAttribute("lang", mozL10n.language.code);
|
||||||
|
document.documentElement.setAttribute("dir", mozL10n.language.direction);
|
||||||
|
document.body.setAttribute("platform", loop.shared.utils.getPlatform());
|
||||||
|
var clientSuperShortname = mozL10n.get("clientSuperShortname");
|
||||||
|
var data = [{
|
||||||
|
id: "slide1",
|
||||||
|
imageClass: "slide1-image",
|
||||||
|
title: mozL10n.get("fte_slide_1_title"),
|
||||||
|
text: mozL10n.get("fte_slide_1_copy", {
|
||||||
|
clientShortname2: mozL10n.get("clientShortname2")
|
||||||
|
})
|
||||||
|
}, {
|
||||||
|
id: "slide2",
|
||||||
|
imageClass: "slide2-image",
|
||||||
|
title: mozL10n.get("fte_slide_2_title"),
|
||||||
|
text: mozL10n.get("fte_slide_2_copy")
|
||||||
|
}, {
|
||||||
|
id: "slide3",
|
||||||
|
imageClass: "slide3-image",
|
||||||
|
title: mozL10n.get("fte_slide_3_title"),
|
||||||
|
text: mozL10n.get("fte_slide_3_copy", {
|
||||||
|
clientSuperShortname: clientSuperShortname
|
||||||
|
})
|
||||||
|
}, {
|
||||||
|
id: "slide4",
|
||||||
|
imageClass: "slide4-image",
|
||||||
|
title: mozL10n.get("fte_slide_4_title", {
|
||||||
|
clientSuperShortname: clientSuperShortname
|
||||||
|
}),
|
||||||
|
text: mozL10n.get("fte_slide_4_copy", {
|
||||||
|
brandShortname: mozL10n.get("brandShortname")
|
||||||
|
})
|
||||||
|
}];
|
||||||
|
|
||||||
|
loop.SimpleSlideshow.init("#main", data);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
init: init
|
||||||
|
};
|
||||||
|
}(document.mozL10n);
|
||||||
|
|
||||||
|
document.addEventListener("DOMContentLoaded", loop.slideshow.init);
|
@ -24,14 +24,18 @@
|
|||||||
<script type="text/javascript" src="shared/js/utils.js"></script>
|
<script type="text/javascript" src="shared/js/utils.js"></script>
|
||||||
<script type="text/javascript" src="shared/js/models.js"></script>
|
<script type="text/javascript" src="shared/js/models.js"></script>
|
||||||
<script type="text/javascript" src="shared/js/mixins.js"></script>
|
<script type="text/javascript" src="shared/js/mixins.js"></script>
|
||||||
<script type="text/javascript" src="shared/js/store.js"></script>
|
|
||||||
<script type="text/javascript" src="shared/js/activeRoomStore.js"></script>
|
|
||||||
<script type="text/javascript" src="shared/js/remoteCursorStore.js"></script>
|
|
||||||
<script type="text/javascript" src="shared/js/views.js"></script>
|
|
||||||
<script type="text/javascript" src="shared/js/validate.js"></script>
|
|
||||||
<script type="text/javascript" src="shared/js/actions.js"></script>
|
<script type="text/javascript" src="shared/js/actions.js"></script>
|
||||||
<script type="text/javascript" src="shared/js/dispatcher.js"></script>
|
<script type="text/javascript" src="shared/js/dispatcher.js"></script>
|
||||||
|
|
||||||
|
<!-- Stores need to be loaded before the views that uses them -->
|
||||||
|
<script type="text/javascript" src="shared/js/store.js"></script>
|
||||||
<script type="text/javascript" src="panels/js/roomStore.js"></script>
|
<script type="text/javascript" src="panels/js/roomStore.js"></script>
|
||||||
|
<script type="text/javascript" src="shared/js/activeRoomStore.js"></script>
|
||||||
|
<script type="text/javascript" src="shared/js/remoteCursorStore.js"></script>
|
||||||
|
|
||||||
|
<!-- Views -->
|
||||||
|
<script type="text/javascript" src="shared/js/views.js"></script>
|
||||||
|
<script type="text/javascript" src="shared/js/validate.js"></script>
|
||||||
<script type="text/javascript" src="panels/js/panel.js"></script>
|
<script type="text/javascript" src="panels/js/panel.js"></script>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
40
browser/extensions/loop/chrome/content/panels/slideshow.html
Normal file
@ -0,0 +1,40 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<!-- This Source Code Form is subject to the terms of the Mozilla Public
|
||||||
|
- License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||||
|
- file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8">
|
||||||
|
<base href="chrome://loop/content">
|
||||||
|
<link rel="stylesheet" type="text/css" href="shared/css/reset.css">
|
||||||
|
<link rel="stylesheet" type="text/css" href="shared/css/common.css">
|
||||||
|
<link rel="stylesheet" type="text/css" href="panels/vendor/simpleSlideshow.css">
|
||||||
|
<link rel="stylesheet" type="text/css" href="panels/css/slideshow.css">
|
||||||
|
</head>
|
||||||
|
<body class="panel">
|
||||||
|
|
||||||
|
<div id="main"></div>
|
||||||
|
|
||||||
|
<script type="text/javascript" src="panels/vendor/l10n.js"></script>
|
||||||
|
<script type="text/javascript" src="shared/vendor/react.js"></script>
|
||||||
|
<script type="text/javascript" src="shared/vendor/lodash.js"></script>
|
||||||
|
<script type="text/javascript" src="shared/js/utils.js"></script>
|
||||||
|
<script type="text/javascript" src="shared/js/loopapi-client.js"></script>
|
||||||
|
<script type="text/javascript" src="shared/vendor/classnames.js"></script>
|
||||||
|
<script type="text/javascript" src="panels/vendor/simpleSlideshow.js"></script>
|
||||||
|
<script type="text/javascript" src="panels/js/slideshow.js"></script>
|
||||||
|
|
||||||
|
<script type="text/javascript">
|
||||||
|
function clickHandler() {
|
||||||
|
try {
|
||||||
|
var e = new CustomEvent("CloseSlideshow"); window.dispatchEvent(e);
|
||||||
|
} catch (ex) {
|
||||||
|
console.warn('CloseSlideshow dispatch exploded' + ex);
|
||||||
|
}
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
<button onclick="clickHandler();" class="button-close" />
|
||||||
|
</body>
|
||||||
|
</html>
|
@ -9,13 +9,14 @@ describe("loop.conversation", function() {
|
|||||||
var expect = chai.expect;
|
var expect = chai.expect;
|
||||||
var TestUtils = React.addons.TestUtils;
|
var TestUtils = React.addons.TestUtils;
|
||||||
var sharedActions = loop.shared.actions;
|
var sharedActions = loop.shared.actions;
|
||||||
var fakeWindow, sandbox, setLoopPrefStub, mozL10nGet, remoteCursorStore, dispatcher;
|
var fakeWindow, sandbox, setLoopPrefStub, mozL10nGet,
|
||||||
|
remoteCursorStore, dispatcher, requestStubs;
|
||||||
|
|
||||||
beforeEach(function() {
|
beforeEach(function() {
|
||||||
sandbox = LoopMochaUtils.createSandbox();
|
sandbox = LoopMochaUtils.createSandbox();
|
||||||
setLoopPrefStub = sandbox.stub();
|
setLoopPrefStub = sandbox.stub();
|
||||||
|
|
||||||
LoopMochaUtils.stubLoopRequest({
|
LoopMochaUtils.stubLoopRequest(requestStubs = {
|
||||||
GetDoNotDisturb: function() { return true; },
|
GetDoNotDisturb: function() { return true; },
|
||||||
GetAllStrings: function() {
|
GetAllStrings: function() {
|
||||||
return JSON.stringify({ textContent: "fakeText" });
|
return JSON.stringify({ textContent: "fakeText" });
|
||||||
@ -38,6 +39,13 @@ describe("loop.conversation", function() {
|
|||||||
LOOP_SESSION_TYPE: {
|
LOOP_SESSION_TYPE: {
|
||||||
GUEST: 1,
|
GUEST: 1,
|
||||||
FXA: 2
|
FXA: 2
|
||||||
|
},
|
||||||
|
LOOP_MAU_TYPE: {
|
||||||
|
OPEN_PANEL: 0,
|
||||||
|
OPEN_CONVERSATION: 1,
|
||||||
|
ROOM_OPEN: 2,
|
||||||
|
ROOM_SHARE: 3,
|
||||||
|
ROOM_DELETE: 4
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
@ -57,7 +65,8 @@ describe("loop.conversation", function() {
|
|||||||
},
|
},
|
||||||
GetConversationWindowData: function() {
|
GetConversationWindowData: function() {
|
||||||
return {};
|
return {};
|
||||||
}
|
},
|
||||||
|
TelemetryAddValue: sinon.stub()
|
||||||
});
|
});
|
||||||
|
|
||||||
fakeWindow = {
|
fakeWindow = {
|
||||||
@ -144,18 +153,30 @@ describe("loop.conversation", function() {
|
|||||||
windowId: "42"
|
windowId: "42"
|
||||||
}));
|
}));
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("should log a telemetry event when opening the conversation window", function() {
|
||||||
|
var constants = requestStubs.GetAllConstants();
|
||||||
|
loop.conversation.init();
|
||||||
|
|
||||||
|
sinon.assert.calledOnce(requestStubs["TelemetryAddValue"]);
|
||||||
|
sinon.assert.calledWithExactly(requestStubs["TelemetryAddValue"],
|
||||||
|
"LOOP_MAU", constants.LOOP_MAU_TYPE.OPEN_CONVERSATION);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe("AppControllerView", function() {
|
describe("AppControllerView", function() {
|
||||||
var activeRoomStore, ccView;
|
var activeRoomStore, ccView, addRemoteCursorStub;
|
||||||
var conversationAppStore, roomStore, feedbackPeriodMs = 15770000000;
|
var conversationAppStore,
|
||||||
|
roomStore,
|
||||||
|
feedbackPeriodMs = 15770000000;
|
||||||
var ROOM_STATES = loop.store.ROOM_STATES;
|
var ROOM_STATES = loop.store.ROOM_STATES;
|
||||||
|
|
||||||
function mountTestComponent() {
|
function mountTestComponent() {
|
||||||
return TestUtils.renderIntoDocument(
|
return TestUtils.renderIntoDocument(
|
||||||
React.createElement(loop.conversation.AppControllerView, {
|
React.createElement(loop.conversation.AppControllerView, {
|
||||||
roomStore: roomStore,
|
cursorStore: remoteCursorStore,
|
||||||
dispatcher: dispatcher
|
dispatcher: dispatcher,
|
||||||
|
roomStore: roomStore
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -168,6 +189,9 @@ describe("loop.conversation", function() {
|
|||||||
activeRoomStore: activeRoomStore,
|
activeRoomStore: activeRoomStore,
|
||||||
constants: {}
|
constants: {}
|
||||||
});
|
});
|
||||||
|
remoteCursorStore = new loop.store.RemoteCursorStore(dispatcher, {
|
||||||
|
sdkDriver: {}
|
||||||
|
});
|
||||||
conversationAppStore = new loop.store.ConversationAppStore({
|
conversationAppStore = new loop.store.ConversationAppStore({
|
||||||
activeRoomStore: activeRoomStore,
|
activeRoomStore: activeRoomStore,
|
||||||
dispatcher: dispatcher,
|
dispatcher: dispatcher,
|
||||||
@ -179,12 +203,43 @@ describe("loop.conversation", function() {
|
|||||||
loop.store.StoreMixin.register({
|
loop.store.StoreMixin.register({
|
||||||
conversationAppStore: conversationAppStore
|
conversationAppStore: conversationAppStore
|
||||||
});
|
});
|
||||||
|
|
||||||
|
addRemoteCursorStub = sandbox.stub();
|
||||||
|
LoopMochaUtils.stubLoopRequest({
|
||||||
|
AddRemoteCursorOverlay: addRemoteCursorStub
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
afterEach(function() {
|
afterEach(function() {
|
||||||
ccView = undefined;
|
ccView = undefined;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("should request AddRemoteCursorOverlay when cursor position changes", function() {
|
||||||
|
|
||||||
|
mountTestComponent();
|
||||||
|
remoteCursorStore.setStoreState({
|
||||||
|
"remoteCursorPosition": {
|
||||||
|
"ratioX": 10,
|
||||||
|
"ratioY": 10
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
sinon.assert.calledOnce(addRemoteCursorStub);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should NOT request AddRemoteCursorOverlay when cursor position DOES NOT changes", function() {
|
||||||
|
|
||||||
|
mountTestComponent();
|
||||||
|
remoteCursorStore.setStoreState({
|
||||||
|
"realVideoSize": {
|
||||||
|
"height": 400,
|
||||||
|
"width": 600
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
sinon.assert.notCalled(addRemoteCursorStub);
|
||||||
|
});
|
||||||
|
|
||||||
it("should display the RoomView for rooms", function() {
|
it("should display the RoomView for rooms", function() {
|
||||||
conversationAppStore.setStoreState({ windowType: "room" });
|
conversationAppStore.setStoreState({ windowType: "room" });
|
||||||
activeRoomStore.setStoreState({ roomState: ROOM_STATES.READY });
|
activeRoomStore.setStoreState({ roomState: ROOM_STATES.READY });
|
||||||
|
@ -49,18 +49,22 @@
|
|||||||
<script src="/add-on/shared/js/validate.js"></script>
|
<script src="/add-on/shared/js/validate.js"></script>
|
||||||
<script src="/add-on/shared/js/dispatcher.js"></script>
|
<script src="/add-on/shared/js/dispatcher.js"></script>
|
||||||
<script src="/add-on/shared/js/otSdkDriver.js"></script>
|
<script src="/add-on/shared/js/otSdkDriver.js"></script>
|
||||||
|
|
||||||
|
<!-- Stores need to be loaded before the views that uses them -->
|
||||||
<script src="/add-on/shared/js/store.js"></script>
|
<script src="/add-on/shared/js/store.js"></script>
|
||||||
<script src="/add-on/shared/js/activeRoomStore.js"></script>
|
|
||||||
<script src="/add-on/shared/js/views.js"></script>
|
|
||||||
<script src="/add-on/shared/js/textChatStore.js"></script>
|
|
||||||
<script src="/add-on/shared/js/textChatView.js"></script>
|
|
||||||
<script src="/add-on/panels/js/conversationAppStore.js"></script>
|
|
||||||
<script src="/add-on/panels/js/roomStore.js"></script>
|
<script src="/add-on/panels/js/roomStore.js"></script>
|
||||||
|
<script src="/add-on/shared/js/activeRoomStore.js"></script>
|
||||||
|
<script src="/add-on/panels/js/conversationAppStore.js"></script>
|
||||||
|
<script src="/add-on/shared/js/textChatStore.js"></script>
|
||||||
|
<script src="/add-on/shared/js/remoteCursorStore.js"></script>
|
||||||
|
|
||||||
|
<!-- Views -->
|
||||||
|
<script src="/add-on/shared/js/views.js"></script>
|
||||||
|
<script src="/add-on/shared/js/textChatView.js"></script>
|
||||||
<script src="/add-on/panels/js/roomViews.js"></script>
|
<script src="/add-on/panels/js/roomViews.js"></script>
|
||||||
<script src="/add-on/panels/js/feedbackViews.js"></script>
|
<script src="/add-on/panels/js/feedbackViews.js"></script>
|
||||||
<script src="/add-on/panels/js/conversation.js"></script>
|
<script src="/add-on/panels/js/conversation.js"></script>
|
||||||
<script src="/add-on/panels/js/panel.js"></script>
|
<script src="/add-on/panels/js/panel.js"></script>
|
||||||
<script src="/add-on/shared/js/remoteCursorStore.js"></script>
|
|
||||||
|
|
||||||
<!-- Test scripts -->
|
<!-- Test scripts -->
|
||||||
<script src="conversationAppStore_test.js"></script>
|
<script src="conversationAppStore_test.js"></script>
|
||||||
|
@ -85,7 +85,7 @@ describe("loop.panel", function() {
|
|||||||
GetHasEncryptionKey: true,
|
GetHasEncryptionKey: true,
|
||||||
GetUserProfile: null,
|
GetUserProfile: null,
|
||||||
GetDoNotDisturb: false,
|
GetDoNotDisturb: false,
|
||||||
"GetLoopPref|gettingStarted.latestFTUVersion": 1,
|
"GetLoopPref|gettingStarted.latestFTUVersion": 2,
|
||||||
"GetLoopPref|legal.ToS_url": "",
|
"GetLoopPref|legal.ToS_url": "",
|
||||||
"GetLoopPref|legal.privacy_url": "",
|
"GetLoopPref|legal.privacy_url": "",
|
||||||
"GetLoopPref|remote.autostart": false,
|
"GetLoopPref|remote.autostart": false,
|
||||||
|
@ -43,10 +43,18 @@ describe("loop.store.RoomStore", function() {
|
|||||||
ROOM_DELETE: {
|
ROOM_DELETE: {
|
||||||
DELETE_SUCCESS: 0,
|
DELETE_SUCCESS: 0,
|
||||||
DELETE_FAIL: 1
|
DELETE_FAIL: 1
|
||||||
|
},
|
||||||
|
LOOP_MAU_TYPE: {
|
||||||
|
OPEN_PANEL: 0,
|
||||||
|
OPEN_CONVERSATION: 1,
|
||||||
|
ROOM_OPEN: 2,
|
||||||
|
ROOM_SHARE: 3,
|
||||||
|
ROOM_DELETE: 4
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
CopyString: sinon.stub(),
|
CopyString: sinon.stub(),
|
||||||
|
ComposeEmail: sinon.stub(),
|
||||||
GetLoopPref: function(prefName) {
|
GetLoopPref: function(prefName) {
|
||||||
if (prefName === "debug.dispatcher") {
|
if (prefName === "debug.dispatcher") {
|
||||||
return false;
|
return false;
|
||||||
@ -60,6 +68,7 @@ describe("loop.store.RoomStore", function() {
|
|||||||
"Rooms:Open": sinon.stub(),
|
"Rooms:Open": sinon.stub(),
|
||||||
"Rooms:Rename": sinon.stub(),
|
"Rooms:Rename": sinon.stub(),
|
||||||
"Rooms:PushSubscription": sinon.stub(),
|
"Rooms:PushSubscription": sinon.stub(),
|
||||||
|
"SetLoopPref": sinon.stub(),
|
||||||
TelemetryAddValue: sinon.stub()
|
TelemetryAddValue: sinon.stub()
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -433,8 +442,8 @@ describe("loop.store.RoomStore", function() {
|
|||||||
roomToken: fakeRoomToken
|
roomToken: fakeRoomToken
|
||||||
}));
|
}));
|
||||||
|
|
||||||
sinon.assert.calledOnce(requestStubs.TelemetryAddValue);
|
sinon.assert.calledTwice(requestStubs.TelemetryAddValue);
|
||||||
sinon.assert.calledWithExactly(requestStubs.TelemetryAddValue,
|
sinon.assert.calledWithExactly(requestStubs.TelemetryAddValue.getCall(0),
|
||||||
"LOOP_ROOM_DELETE", 0);
|
"LOOP_ROOM_DELETE", 0);
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -447,8 +456,8 @@ describe("loop.store.RoomStore", function() {
|
|||||||
roomToken: fakeRoomToken
|
roomToken: fakeRoomToken
|
||||||
}));
|
}));
|
||||||
|
|
||||||
sinon.assert.calledOnce(requestStubs.TelemetryAddValue);
|
sinon.assert.calledTwice(requestStubs.TelemetryAddValue);
|
||||||
sinon.assert.calledWithExactly(requestStubs.TelemetryAddValue,
|
sinon.assert.calledWithExactly(requestStubs.TelemetryAddValue.getCall(0),
|
||||||
"LOOP_ROOM_DELETE", 1);
|
"LOOP_ROOM_DELETE", 1);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
@ -470,8 +479,8 @@ describe("loop.store.RoomStore", function() {
|
|||||||
from: "panel"
|
from: "panel"
|
||||||
}));
|
}));
|
||||||
|
|
||||||
sinon.assert.calledOnce(requestStubs.TelemetryAddValue);
|
sinon.assert.calledTwice(requestStubs.TelemetryAddValue);
|
||||||
sinon.assert.calledWithExactly(requestStubs.TelemetryAddValue,
|
sinon.assert.calledWithExactly(requestStubs.TelemetryAddValue.getCall(0),
|
||||||
"LOOP_SHARING_ROOM_URL", 0);
|
"LOOP_SHARING_ROOM_URL", 0);
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -481,8 +490,8 @@ describe("loop.store.RoomStore", function() {
|
|||||||
from: "conversation"
|
from: "conversation"
|
||||||
}));
|
}));
|
||||||
|
|
||||||
sinon.assert.calledOnce(requestStubs.TelemetryAddValue);
|
sinon.assert.calledTwice(requestStubs.TelemetryAddValue);
|
||||||
sinon.assert.calledWithExactly(requestStubs.TelemetryAddValue,
|
sinon.assert.calledWithExactly(requestStubs.TelemetryAddValue.getCall(0),
|
||||||
"LOOP_SHARING_ROOM_URL", 1);
|
"LOOP_SHARING_ROOM_URL", 1);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
@ -520,26 +529,53 @@ describe("loop.store.RoomStore", function() {
|
|||||||
|
|
||||||
describe("#facebookShareRoomUrl", function() {
|
describe("#facebookShareRoomUrl", function() {
|
||||||
var getLoopPrefStub;
|
var getLoopPrefStub;
|
||||||
|
var sharingSite = "www.sharing-site.com",
|
||||||
|
shareURL = sharingSite +
|
||||||
|
"?app_id=%APP_ID%" +
|
||||||
|
"&link=%ROOM_URL%" +
|
||||||
|
"&redirect_uri=%REDIRECT_URI%",
|
||||||
|
appId = "1234567890",
|
||||||
|
fallback = "www.fallback.com";
|
||||||
|
|
||||||
beforeEach(function() {
|
beforeEach(function() {
|
||||||
getLoopPrefStub = function() {
|
getLoopPrefStub = sinon.stub();
|
||||||
return "https://shared.site/?u=%ROOM_URL%";
|
getLoopPrefStub.withArgs("facebook.appId").returns(appId);
|
||||||
};
|
getLoopPrefStub.withArgs("facebook.shareUrl").returns(shareURL);
|
||||||
|
getLoopPrefStub.withArgs("facebook.fallbackUrl").returns(fallback);
|
||||||
|
|
||||||
LoopMochaUtils.stubLoopRequest({
|
LoopMochaUtils.stubLoopRequest({
|
||||||
GetLoopPref: getLoopPrefStub
|
GetLoopPref: getLoopPrefStub
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
it("should open the facebook url with room URL", function() {
|
it("should open the facebook share url with correct room and redirection", function() {
|
||||||
|
var room = "invalid.room",
|
||||||
|
origin = "origin.url";
|
||||||
|
|
||||||
store.facebookShareRoomUrl(new sharedActions.FacebookShareRoomUrl({
|
store.facebookShareRoomUrl(new sharedActions.FacebookShareRoomUrl({
|
||||||
from: "conversation",
|
from: "conversation",
|
||||||
roomUrl: "http://invalid"
|
originUrl: origin,
|
||||||
|
roomUrl: room
|
||||||
}));
|
}));
|
||||||
|
|
||||||
sinon.assert.calledOnce(requestStubs.OpenURL);
|
sinon.assert.calledOnce(requestStubs.OpenURL);
|
||||||
sinon.assert.calledWithExactly(requestStubs.OpenURL, "https://shared.site/?u=http%3A%2F%2Finvalid");
|
sinon.assert.calledWithMatch(requestStubs.OpenURL, sharingSite);
|
||||||
|
sinon.assert.calledWithMatch(requestStubs.OpenURL, room);
|
||||||
|
sinon.assert.calledWithMatch(requestStubs.OpenURL, origin);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("if no origin URL, send fallback URL", function() {
|
||||||
|
var room = "invalid.room";
|
||||||
|
|
||||||
|
store.facebookShareRoomUrl(new sharedActions.FacebookShareRoomUrl({
|
||||||
|
from: "conversation",
|
||||||
|
roomUrl: room
|
||||||
|
}));
|
||||||
|
|
||||||
|
sinon.assert.calledOnce(requestStubs.OpenURL);
|
||||||
|
sinon.assert.calledWithMatch(requestStubs.OpenURL, sharingSite);
|
||||||
|
sinon.assert.calledWithMatch(requestStubs.OpenURL, room);
|
||||||
|
sinon.assert.calledWithMatch(requestStubs.OpenURL, fallback);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("should send a telemetry event for facebook share from conversation", function() {
|
it("should send a telemetry event for facebook share from conversation", function() {
|
||||||
@ -548,8 +584,8 @@ describe("loop.store.RoomStore", function() {
|
|||||||
roomUrl: "http://invalid"
|
roomUrl: "http://invalid"
|
||||||
}));
|
}));
|
||||||
|
|
||||||
sinon.assert.calledOnce(requestStubs.TelemetryAddValue);
|
sinon.assert.calledTwice(requestStubs.TelemetryAddValue);
|
||||||
sinon.assert.calledWithExactly(requestStubs.TelemetryAddValue,
|
sinon.assert.calledWithExactly(requestStubs.TelemetryAddValue.getCall(0),
|
||||||
"LOOP_SHARING_ROOM_URL", 4);
|
"LOOP_SHARING_ROOM_URL", 4);
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -731,7 +767,9 @@ describe("loop.store.RoomStore", function() {
|
|||||||
var store;
|
var store;
|
||||||
|
|
||||||
beforeEach(function() {
|
beforeEach(function() {
|
||||||
store = new loop.store.RoomStore(dispatcher, { constants: {} });
|
store = new loop.store.RoomStore(dispatcher, {
|
||||||
|
constants: requestStubs.GetAllConstants()
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
it("should open the room via mozLoop", function() {
|
it("should open the room via mozLoop", function() {
|
||||||
@ -881,4 +919,76 @@ describe("loop.store.RoomStore", function() {
|
|||||||
expect(store.getStoreState().savingContext).to.eql(false);
|
expect(store.getStoreState().savingContext).to.eql(false);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe("MAU telemetry events", function() {
|
||||||
|
var getLoopPrefStub, store;
|
||||||
|
|
||||||
|
beforeEach(function() {
|
||||||
|
getLoopPrefStub = function(pref) {
|
||||||
|
if (pref === "facebook.shareUrl") {
|
||||||
|
return "https://shared.site/?u=%ROOM_URL%";
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
LoopMochaUtils.stubLoopRequest({
|
||||||
|
GetLoopPref: getLoopPrefStub
|
||||||
|
});
|
||||||
|
|
||||||
|
store = new loop.store.RoomStore(dispatcher, {
|
||||||
|
constants: requestStubs.GetAllConstants()
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should log telemetry event when opening a room", function() {
|
||||||
|
store.openRoom(new sharedActions.OpenRoom({ roomToken: "42abc" }));
|
||||||
|
|
||||||
|
sinon.assert.calledOnce(requestStubs["TelemetryAddValue"]);
|
||||||
|
sinon.assert.calledWithExactly(requestStubs["TelemetryAddValue"],
|
||||||
|
"LOOP_MAU", store._constants.LOOP_MAU_TYPE.ROOM_OPEN);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should log telemetry event when sharing a room (copy link)", function() {
|
||||||
|
store.copyRoomUrl(new sharedActions.CopyRoomUrl({
|
||||||
|
roomUrl: "http://invalid",
|
||||||
|
from: "conversation"
|
||||||
|
}));
|
||||||
|
|
||||||
|
sinon.assert.calledTwice(requestStubs["TelemetryAddValue"]);
|
||||||
|
sinon.assert.calledWithExactly(requestStubs["TelemetryAddValue"].getCall(1),
|
||||||
|
"LOOP_MAU", store._constants.LOOP_MAU_TYPE.ROOM_SHARE);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should log telemetry event when sharing a room (email)", function() {
|
||||||
|
store.emailRoomUrl(new sharedActions.EmailRoomUrl({
|
||||||
|
roomUrl: "http://invalid",
|
||||||
|
from: "conversation"
|
||||||
|
}));
|
||||||
|
|
||||||
|
sinon.assert.calledTwice(requestStubs["TelemetryAddValue"]);
|
||||||
|
sinon.assert.calledWithExactly(requestStubs["TelemetryAddValue"].getCall(1),
|
||||||
|
"LOOP_MAU", store._constants.LOOP_MAU_TYPE.ROOM_SHARE);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should log telemetry event when sharing a room (facebook)", function() {
|
||||||
|
store.facebookShareRoomUrl(new sharedActions.FacebookShareRoomUrl({
|
||||||
|
roomUrl: "http://invalid",
|
||||||
|
from: "conversation"
|
||||||
|
}));
|
||||||
|
|
||||||
|
sinon.assert.calledTwice(requestStubs["TelemetryAddValue"]);
|
||||||
|
sinon.assert.calledWithExactly(requestStubs["TelemetryAddValue"].getCall(1),
|
||||||
|
"LOOP_MAU", store._constants.LOOP_MAU_TYPE.ROOM_SHARE);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should log telemetry event when deleting a room", function() {
|
||||||
|
store.deleteRoom(new sharedActions.DeleteRoom({
|
||||||
|
roomToken: "42abc"
|
||||||
|
}));
|
||||||
|
|
||||||
|
sinon.assert.calledTwice(requestStubs["TelemetryAddValue"]);
|
||||||
|
sinon.assert.calledWithExactly(requestStubs["TelemetryAddValue"].getCall(1),
|
||||||
|
"LOOP_MAU", store._constants.LOOP_MAU_TYPE.ROOM_DELETE);
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
@ -11,7 +11,12 @@ describe("loop.roomViews", function() {
|
|||||||
var ROOM_STATES = loop.store.ROOM_STATES;
|
var ROOM_STATES = loop.store.ROOM_STATES;
|
||||||
var FAILURE_DETAILS = loop.shared.utils.FAILURE_DETAILS;
|
var FAILURE_DETAILS = loop.shared.utils.FAILURE_DETAILS;
|
||||||
|
|
||||||
var sandbox, dispatcher, roomStore, activeRoomStore, view;
|
var sandbox,
|
||||||
|
dispatcher,
|
||||||
|
roomStore,
|
||||||
|
activeRoomStore,
|
||||||
|
remoteCursorStore,
|
||||||
|
view;
|
||||||
var clock, fakeWindow, requestStubs;
|
var clock, fakeWindow, requestStubs;
|
||||||
var favicon = "data:image/x-icon;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==";
|
var favicon = "data:image/x-icon;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==";
|
||||||
|
|
||||||
@ -69,6 +74,9 @@ describe("loop.roomViews", function() {
|
|||||||
constants: {},
|
constants: {},
|
||||||
activeRoomStore: activeRoomStore
|
activeRoomStore: activeRoomStore
|
||||||
});
|
});
|
||||||
|
remoteCursorStore = new loop.store.RemoteCursorStore(dispatcher, {
|
||||||
|
sdkDriver: {}
|
||||||
|
});
|
||||||
var textChatStore = new loop.store.TextChatStore(dispatcher, {
|
var textChatStore = new loop.store.TextChatStore(dispatcher, {
|
||||||
sdkDriver: {}
|
sdkDriver: {}
|
||||||
});
|
});
|
||||||
@ -352,6 +360,7 @@ describe("loop.roomViews", function() {
|
|||||||
function mountTestComponent(props) {
|
function mountTestComponent(props) {
|
||||||
props = _.extend({
|
props = _.extend({
|
||||||
chatWindowDetached: false,
|
chatWindowDetached: false,
|
||||||
|
cursorStore: remoteCursorStore,
|
||||||
dispatcher: dispatcher,
|
dispatcher: dispatcher,
|
||||||
facebookEnabled: false,
|
facebookEnabled: false,
|
||||||
roomStore: roomStore,
|
roomStore: roomStore,
|
||||||
|
109
browser/extensions/loop/chrome/content/panels/vendor/simpleSlideshow.css
vendored
Normal file
@ -0,0 +1,109 @@
|
|||||||
|
/* This is derived from PIOTR F's code,
|
||||||
|
currently available at https://github.com/piotrf/simple-react-slideshow
|
||||||
|
|
||||||
|
Simple React Slideshow Example
|
||||||
|
|
||||||
|
Original Author: PIOTR F.
|
||||||
|
License: MIT
|
||||||
|
|
||||||
|
Copyright (c) 2015 Piotr
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person
|
||||||
|
obtaining a copy of this software and associated documentation
|
||||||
|
files (the "Software"), to deal in the Software without
|
||||||
|
restriction, including without limitation the rights to use,
|
||||||
|
copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
copies of the Software, and to permit persons to whom the
|
||||||
|
Software is furnished to do so, subject to the following
|
||||||
|
conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be
|
||||||
|
included in all copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||||
|
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
|
||||||
|
OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||||
|
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
|
||||||
|
HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
|
||||||
|
WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||||
|
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
|
||||||
|
OTHER DEALINGS IN THE SOFTWARE.
|
||||||
|
*/
|
||||||
|
|
||||||
|
html {
|
||||||
|
font-size: 10px;
|
||||||
|
font-family: menu;
|
||||||
|
color: #fff;
|
||||||
|
}
|
||||||
|
|
||||||
|
body {
|
||||||
|
background: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
* {
|
||||||
|
box-sizing: border-box;
|
||||||
|
padding: 0;
|
||||||
|
margin: 0;
|
||||||
|
line-height: 2.4rem; /* or 1.3rem original*/
|
||||||
|
}
|
||||||
|
|
||||||
|
.slides {
|
||||||
|
display: block;
|
||||||
|
width: 100%;
|
||||||
|
height: 390px;
|
||||||
|
overflow: hidden;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
.slide {
|
||||||
|
display: none;
|
||||||
|
height: 100%;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
.slide.slide--active {
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
|
||||||
|
.control-panel {
|
||||||
|
height: 60px;
|
||||||
|
background: #fff;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.toggle {
|
||||||
|
color: white;
|
||||||
|
display: block;
|
||||||
|
padding: 0px;
|
||||||
|
position: absolute;
|
||||||
|
bottom: 20px;
|
||||||
|
background-color: transparent;
|
||||||
|
background-image: url(../../shared/img/arrow-01.svg);
|
||||||
|
background-repeat: no-repeat;
|
||||||
|
background-size: 20px 20px;
|
||||||
|
border: none;
|
||||||
|
/*padding: 0;*/
|
||||||
|
height: 20px;
|
||||||
|
width: 20px;
|
||||||
|
}
|
||||||
|
.toggle-prev {
|
||||||
|
left: 20px;
|
||||||
|
transform: scaleX(-1);
|
||||||
|
}
|
||||||
|
.toggle-next {
|
||||||
|
right: 20px;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
.button-close {
|
||||||
|
display: block;
|
||||||
|
position: absolute;
|
||||||
|
background-color: transparent;
|
||||||
|
background-image: url(../../shared/img/close-02.svg);
|
||||||
|
background-repeat: no-repeat;
|
||||||
|
background-size: 20px 20px;
|
||||||
|
border: none;
|
||||||
|
padding: 0;
|
||||||
|
height: 20px;
|
||||||
|
width: 20px;
|
||||||
|
top: 20px;
|
||||||
|
right: 20px;
|
||||||
|
}
|
217
browser/extensions/loop/chrome/content/panels/vendor/simpleSlideshow.js
vendored
Normal file
@ -0,0 +1,217 @@
|
|||||||
|
// This is derived from PIOTR F's code,
|
||||||
|
// currently available at https://github.com/piotrf/simple-react-slideshow
|
||||||
|
|
||||||
|
// Simple React Slideshow Example
|
||||||
|
//
|
||||||
|
// Original Author: PIOTR F.
|
||||||
|
// License: MIT
|
||||||
|
//
|
||||||
|
// Copyright (c) 2015 Piotr
|
||||||
|
//
|
||||||
|
// Permission is hereby granted, free of charge, to any person
|
||||||
|
// obtaining a copy of this software and associated documentation
|
||||||
|
// files (the "Software"), to deal in the Software without
|
||||||
|
// restriction, including without limitation the rights to use,
|
||||||
|
// copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
// copies of the Software, and to permit persons to whom the
|
||||||
|
// Software is furnished to do so, subject to the following
|
||||||
|
// conditions:
|
||||||
|
//
|
||||||
|
// The above copyright notice and this permission notice shall be
|
||||||
|
// included in all copies or substantial portions of the Software.
|
||||||
|
//
|
||||||
|
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||||
|
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
|
||||||
|
// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||||
|
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
|
||||||
|
// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
|
||||||
|
// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||||
|
// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
|
||||||
|
// OTHER DEALINGS IN THE SOFTWARE.
|
||||||
|
//
|
||||||
|
|
||||||
|
var loop = loop || {};
|
||||||
|
loop.SimpleSlideshow = function () {
|
||||||
|
"use strict";
|
||||||
|
|
||||||
|
// App state
|
||||||
|
|
||||||
|
var state = {
|
||||||
|
currentSlide: 0,
|
||||||
|
data: []
|
||||||
|
};
|
||||||
|
|
||||||
|
// State transitions
|
||||||
|
var actions = {
|
||||||
|
toggleNext: function () {
|
||||||
|
var current = state.currentSlide;
|
||||||
|
var next = current + 1;
|
||||||
|
if (next < state.data.length) {
|
||||||
|
state.currentSlide = next;
|
||||||
|
}
|
||||||
|
render();
|
||||||
|
},
|
||||||
|
togglePrev: function () {
|
||||||
|
var current = state.currentSlide;
|
||||||
|
var prev = current - 1;
|
||||||
|
if (prev >= 0) {
|
||||||
|
state.currentSlide = prev;
|
||||||
|
}
|
||||||
|
render();
|
||||||
|
},
|
||||||
|
toggleSlide: function (id) {
|
||||||
|
var index = state.data.map(function (el) {
|
||||||
|
return el.id;
|
||||||
|
});
|
||||||
|
var currentIndex = index.indexOf(id);
|
||||||
|
state.currentSlide = currentIndex;
|
||||||
|
render();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
var Slideshow = React.createClass({
|
||||||
|
displayName: "Slideshow",
|
||||||
|
|
||||||
|
propTypes: {
|
||||||
|
data: React.PropTypes.array.isRequired
|
||||||
|
},
|
||||||
|
render: function () {
|
||||||
|
return React.createElement(
|
||||||
|
"div",
|
||||||
|
{ className: "slideshow" },
|
||||||
|
React.createElement(Slides, { data: this.props.data }),
|
||||||
|
React.createElement(
|
||||||
|
"div",
|
||||||
|
{ className: "control-panel" },
|
||||||
|
React.createElement(Controls, null)
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
var Slides = React.createClass({
|
||||||
|
displayName: "Slides",
|
||||||
|
|
||||||
|
propTypes: {
|
||||||
|
data: React.PropTypes.array.isRequired
|
||||||
|
},
|
||||||
|
render: function () {
|
||||||
|
var slidesNodes = this.props.data.map(function (slideNode, index) {
|
||||||
|
var isActive = state.currentSlide === index;
|
||||||
|
return React.createElement(Slide, { active: isActive,
|
||||||
|
imageAlt: slideNode.imageAlt,
|
||||||
|
imageClass: slideNode.imageClass,
|
||||||
|
indexClass: slideNode.id,
|
||||||
|
key: slideNode.id,
|
||||||
|
text: slideNode.text,
|
||||||
|
title: slideNode.title });
|
||||||
|
});
|
||||||
|
return React.createElement(
|
||||||
|
"div",
|
||||||
|
{ className: "slides" },
|
||||||
|
slidesNodes
|
||||||
|
);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
var Slide = React.createClass({
|
||||||
|
displayName: "Slide",
|
||||||
|
|
||||||
|
propTypes: {
|
||||||
|
active: React.PropTypes.bool.isRequired,
|
||||||
|
imageClass: React.PropTypes.string.isRequired,
|
||||||
|
indexClass: React.PropTypes.string.isRequired,
|
||||||
|
text: React.PropTypes.string.isRequired,
|
||||||
|
title: React.PropTypes.string.isRequired
|
||||||
|
},
|
||||||
|
render: function () {
|
||||||
|
var classes = classNames({
|
||||||
|
"slide": true,
|
||||||
|
"slide--active": this.props.active
|
||||||
|
});
|
||||||
|
return React.createElement(
|
||||||
|
"div",
|
||||||
|
{ className: classes },
|
||||||
|
React.createElement(
|
||||||
|
"div",
|
||||||
|
{ className: this.props.indexClass },
|
||||||
|
React.createElement(
|
||||||
|
"div",
|
||||||
|
{ className: "slide-layout" },
|
||||||
|
React.createElement("img", { className: this.props.imageClass }),
|
||||||
|
React.createElement(
|
||||||
|
"h2",
|
||||||
|
null,
|
||||||
|
this.props.title
|
||||||
|
),
|
||||||
|
React.createElement(
|
||||||
|
"div",
|
||||||
|
{ className: "slide-text" },
|
||||||
|
this.props.text
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
var Controls = React.createClass({
|
||||||
|
displayName: "Controls",
|
||||||
|
|
||||||
|
togglePrev: function () {
|
||||||
|
actions.togglePrev();
|
||||||
|
},
|
||||||
|
toggleNext: function () {
|
||||||
|
actions.toggleNext();
|
||||||
|
},
|
||||||
|
render: function () {
|
||||||
|
var showPrev, showNext;
|
||||||
|
var current = state.currentSlide;
|
||||||
|
var last = state.data.length;
|
||||||
|
if (current > 0) {
|
||||||
|
showPrev = React.createElement("div", { className: "toggle toggle-prev", onClick: this.togglePrev });
|
||||||
|
}
|
||||||
|
if (current < last - 1) {
|
||||||
|
showNext = React.createElement("div", { className: "toggle toggle-next", onClick: this.toggleNext });
|
||||||
|
}
|
||||||
|
return React.createElement(
|
||||||
|
"div",
|
||||||
|
{ className: "controls" },
|
||||||
|
showPrev,
|
||||||
|
showNext
|
||||||
|
);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
var EmptyMessage = React.createClass({
|
||||||
|
displayName: "EmptyMessage",
|
||||||
|
|
||||||
|
render: function () {
|
||||||
|
return React.createElement(
|
||||||
|
"div",
|
||||||
|
{ className: "empty-message" },
|
||||||
|
"No Data"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
function render(renderTo) {
|
||||||
|
var hasData = state.data.length > 0;
|
||||||
|
var component;
|
||||||
|
if (hasData) {
|
||||||
|
component = React.createElement(Slideshow, { data: state.data });
|
||||||
|
} else {
|
||||||
|
component = React.createElement(EmptyMessage, null);
|
||||||
|
}
|
||||||
|
React.render(component, document.querySelector(renderTo ? renderTo : "#main"));
|
||||||
|
}
|
||||||
|
|
||||||
|
function init(renderTo, data) {
|
||||||
|
state.data = data;
|
||||||
|
render(renderTo);
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
init: init
|
||||||
|
};
|
||||||
|
}();
|
@ -3,7 +3,6 @@ pref("loop.remote.autostart", false);
|
|||||||
pref("loop.server", "https://loop.services.mozilla.com/v0");
|
pref("loop.server", "https://loop.services.mozilla.com/v0");
|
||||||
pref("loop.linkClicker.url", "https://hello.firefox.com/");
|
pref("loop.linkClicker.url", "https://hello.firefox.com/");
|
||||||
pref("loop.gettingStarted.latestFTUVersion", 1);
|
pref("loop.gettingStarted.latestFTUVersion", 1);
|
||||||
pref("loop.facebook.shareUrl", "https://www.facebook.com/sharer/sharer.php?u=%ROOM_URL%");
|
|
||||||
pref("loop.gettingStarted.url", "https://www.mozilla.org/%LOCALE%/firefox/%VERSION%/hello/start/");
|
pref("loop.gettingStarted.url", "https://www.mozilla.org/%LOCALE%/firefox/%VERSION%/hello/start/");
|
||||||
pref("loop.gettingStarted.resumeOnFirstJoin", false);
|
pref("loop.gettingStarted.resumeOnFirstJoin", false);
|
||||||
pref("loop.legal.ToS_url", "https://www.mozilla.org/about/legal/terms/firefox-hello/");
|
pref("loop.legal.ToS_url", "https://www.mozilla.org/about/legal/terms/firefox-hello/");
|
||||||
@ -21,6 +20,11 @@ pref("loop.feedback.dateLastSeenSec", 0);
|
|||||||
pref("loop.feedback.periodSec", 15770000); // 6 months.
|
pref("loop.feedback.periodSec", 15770000); // 6 months.
|
||||||
pref("loop.feedback.formURL", "https://www.mozilla.org/firefox/hello/npssurvey/");
|
pref("loop.feedback.formURL", "https://www.mozilla.org/firefox/hello/npssurvey/");
|
||||||
pref("loop.feedback.manualFormURL", "https://www.mozilla.org/firefox/hello/feedbacksurvey/");
|
pref("loop.feedback.manualFormURL", "https://www.mozilla.org/firefox/hello/feedbacksurvey/");
|
||||||
|
pref("loop.mau.openPanel", 0);
|
||||||
|
pref("loop.mau.openConversation", 0);
|
||||||
|
pref("loop.mau.roomOpen", 0);
|
||||||
|
pref("loop.mau.roomShare", 0);
|
||||||
|
pref("loop.mau.roomDelete", 0);
|
||||||
#ifdef DEBUG
|
#ifdef DEBUG
|
||||||
pref("loop.CSP", "default-src 'self' about: file: chrome: http://localhost:*; img-src * data:; font-src 'none'; connect-src wss://*.tokbox.com https://*.opentok.com https://*.tokbox.com wss://*.mozilla.com https://*.mozilla.org wss://*.mozaws.net http://localhost:* ws://localhost:*; media-src blob:");
|
pref("loop.CSP", "default-src 'self' about: file: chrome: http://localhost:*; img-src * data:; font-src 'none'; connect-src wss://*.tokbox.com https://*.opentok.com https://*.tokbox.com wss://*.mozilla.com https://*.mozilla.org wss://*.mozaws.net http://localhost:* ws://localhost:*; media-src blob:");
|
||||||
#else
|
#else
|
||||||
@ -29,8 +33,7 @@ pref("loop.CSP", "default-src 'self' about: file: chrome:; img-src * data:; font
|
|||||||
pref("loop.fxa_oauth.tokendata", "");
|
pref("loop.fxa_oauth.tokendata", "");
|
||||||
pref("loop.fxa_oauth.profile", "");
|
pref("loop.fxa_oauth.profile", "");
|
||||||
pref("loop.support_url", "https://support.mozilla.org/1/firefox/%VERSION%/%OS%/%LOCALE%/cobrowsing");
|
pref("loop.support_url", "https://support.mozilla.org/1/firefox/%VERSION%/%OS%/%LOCALE%/cobrowsing");
|
||||||
#ifdef LOOP_BETA
|
|
||||||
pref("loop.facebook.enabled", true);
|
pref("loop.facebook.enabled", true);
|
||||||
#else
|
pref("loop.facebook.appId", "1519239075036718");
|
||||||
pref("loop.facebook.enabled", false);
|
pref("loop.facebook.shareUrl", "https://www.facebook.com/dialog/send?app_id=%APP_ID%&link=%ROOM_URL%&redirect_uri=%REDIRECT_URI%");
|
||||||
#endif
|
pref("loop.facebook.fallbackUrl", "https://hello.firefox.com/");
|
||||||
|
@ -181,43 +181,6 @@ html[dir="rtl"] .conversation-toolbar-media-btn-group-box > button:first-child:h
|
|||||||
width: 100%;
|
width: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
.call-action-group > .invite-button {
|
|
||||||
cursor: pointer;
|
|
||||||
margin: 0 4px;
|
|
||||||
position: relative;
|
|
||||||
}
|
|
||||||
|
|
||||||
.call-action-group > .invite-button > img {
|
|
||||||
background-color: #00a9dc;
|
|
||||||
border-radius: 100%;
|
|
||||||
height: 28px;
|
|
||||||
width: 28px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.call-action-group > .invite-button:hover > img {
|
|
||||||
background-color: #5cccee;
|
|
||||||
}
|
|
||||||
|
|
||||||
.call-action-group > .invite-button.triggered > img {
|
|
||||||
background-color: #56b397;
|
|
||||||
}
|
|
||||||
|
|
||||||
.call-action-group > .invite-button > p {
|
|
||||||
display: none;
|
|
||||||
/* Position the text under the button while centering it without impacting the
|
|
||||||
* rest of the layout */
|
|
||||||
left: -10rem;
|
|
||||||
margin: .5rem 0 0;
|
|
||||||
position: absolute;
|
|
||||||
right: -10rem;
|
|
||||||
font-size: 0.8rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.call-action-group > .invite-button.triggered > p,
|
|
||||||
.call-action-group > .invite-button:hover > p {
|
|
||||||
display: block;
|
|
||||||
}
|
|
||||||
|
|
||||||
.room-failure {
|
.room-failure {
|
||||||
/* This flex allows us to not calculate the height of the logo area
|
/* This flex allows us to not calculate the height of the logo area
|
||||||
versus the buttons */
|
versus the buttons */
|
||||||
@ -443,22 +406,133 @@ html, .fx-embedded, #main,
|
|||||||
height: 100%;
|
height: 100%;
|
||||||
right: 0;
|
right: 0;
|
||||||
left: 0;
|
left: 0;
|
||||||
text-align: center;
|
|
||||||
color: #000;
|
color: #000;
|
||||||
z-index: 1010;
|
z-index: 1010;
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-flow: column nowrap;
|
flex-flow: column nowrap;
|
||||||
justify-content: center;
|
|
||||||
align-items: stretch;
|
align-items: stretch;
|
||||||
}
|
}
|
||||||
|
|
||||||
.room-invitation-content {
|
.room-invitation-content {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-flow: column nowrap;
|
flex-flow: column nowrap;
|
||||||
|
margin: 12px 0;
|
||||||
|
font-size: 1.4rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.room-invitation-content > * {
|
||||||
|
width: 100%;
|
||||||
|
margin: 0 15px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.room-context-header {
|
||||||
|
font-weight: bold;
|
||||||
|
font-size: 1.6rem;
|
||||||
|
margin-bottom: 10px;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Input Button Combo group */
|
||||||
|
.input-button-content {
|
||||||
|
margin: 0 15px 10px 15px;
|
||||||
|
min-width: 64px;
|
||||||
|
border-radius: 4px;
|
||||||
|
border: 1px solid #d2cece;;
|
||||||
|
}
|
||||||
|
|
||||||
|
.input-button-group-label {
|
||||||
|
color: #898a8a;
|
||||||
|
margin: 0 15px;
|
||||||
|
margin-bottom: 2px;
|
||||||
|
font-size: 1.2rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.input-button-content > * {
|
||||||
|
width: 100%;
|
||||||
|
padding: 0 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.input-button-content > .input-group input {
|
||||||
|
font-size: 1.4rem;
|
||||||
|
padding: 0.7rem;
|
||||||
|
width: 100%;
|
||||||
|
border: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.input-button-content > .group-item-top {
|
||||||
|
border-radius: 4px 4px 0 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.input-button-content > .group-item-bottom {
|
||||||
|
border-radius: 0 0 4px 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.input-button-content > .input-group {
|
||||||
|
background: #FFF;
|
||||||
|
}
|
||||||
|
|
||||||
|
.input-button-content > .invite-button {
|
||||||
|
background: #00a9dc;
|
||||||
|
height: 34px;
|
||||||
|
text-align: center;
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: nowrap;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.input-button-content > .invite-button.triggered {
|
||||||
|
background-color: #00a9dc;
|
||||||
|
}
|
||||||
|
|
||||||
|
.input-button-content > .invite-button:hover {
|
||||||
|
background-color: #008ACB;
|
||||||
|
}
|
||||||
|
|
||||||
|
.share-action-group {
|
||||||
|
display: flex;
|
||||||
|
padding: 0 15px;
|
||||||
|
width: 100%;
|
||||||
|
flex-wrap: nowrap;
|
||||||
|
flex-direction: row;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.share-action-group > .invite-button {
|
||||||
|
cursor: pointer;
|
||||||
|
height: 34px;
|
||||||
|
border-radius: 4px;
|
||||||
|
background-color: #ebebeb;
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: nowrap;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
flex-grow: 1;
|
||||||
|
margin-right: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.share-action-group > .invite-button:last-child {
|
||||||
|
margin-right: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.share-action-group > .invite-button:hover {
|
||||||
|
background-color: #d4d4d4;
|
||||||
|
}
|
||||||
|
|
||||||
|
.share-action-group > .invite-button.triggered {
|
||||||
|
background-color: #d4d4d4;
|
||||||
|
}
|
||||||
|
|
||||||
|
.share-action-group > .invite-button > img {
|
||||||
|
height: 28px;
|
||||||
|
width: 28px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.share-action-group > .invite-button > div {
|
||||||
|
display: inline;
|
||||||
|
color: #4a4a4a;
|
||||||
|
}
|
||||||
|
|
||||||
.share-service-dropdown {
|
.share-service-dropdown {
|
||||||
color: #000;
|
color: #000;
|
||||||
text-align: start;
|
text-align: start;
|
||||||
@ -500,17 +574,6 @@ body[platform="win"] .share-service-dropdown.overflow > .dropdown-menu-item {
|
|||||||
margin: 0 1rem 1.5rem 1rem;
|
margin: 0 1rem 1.5rem 1rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
.room-invitation-content {
|
|
||||||
color: #333;
|
|
||||||
font-size: 1rem;
|
|
||||||
margin: 1rem auto;
|
|
||||||
}
|
|
||||||
|
|
||||||
.room-invitation-content > p {
|
|
||||||
margin-left: 10px;
|
|
||||||
margin-right: 10px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.media-layout {
|
.media-layout {
|
||||||
height: 100%;
|
height: 100%;
|
||||||
}
|
}
|
||||||
@ -796,7 +859,7 @@ body[platform="win"] .share-service-dropdown.overflow > .dropdown-menu-item {
|
|||||||
.text-chat-view > .text-chat-entries {
|
.text-chat-view > .text-chat-entries {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
overflow: auto;
|
overflow: auto;
|
||||||
padding-top: .6rem;
|
padding-top: 15px;
|
||||||
/* 40px is the height of .text-chat-box. */
|
/* 40px is the height of .text-chat-box. */
|
||||||
height: calc(100% - 40px);
|
height: calc(100% - 40px);
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 50 50"><path fill="#55565A" d="M47,25c0-1-0.4-2-1.1-2.7L26.4,2.9c-0.7-0.7-1.7-1.1-2.7-1.1c-1,0-2,0.4-2.7,1.1l-2.2,2.2 c-0.7,0.7-1.1,1.7-1.1,2.7s0.4,2,1.1,2.7l8.8,8.8h-21c-2.2,0-3.5,1.8-3.5,3.8v3.8c0,2,1.3,3.8,3.5,3.8h21l-8.8,8.8 c-0.7,0.7-1.1,1.7-1.1,2.7c0,1,0.4,2,1.1,2.7l2.2,2.2c0.7,0.7,1.7,1.1,2.7,1.1c1,0,2-0.4,2.7-1.1l19.5-19.5C46.6,27,47,26,47,25z"/></svg>
|
After Width: | Height: | Size: 415 B |
@ -0,0 +1 @@
|
|||||||
|
<svg width="31" height="24" viewBox="0 0 31 24" xmlns="http://www.w3.org/2000/svg"><g fill="none"><path d="M9.07 17.683l.644.644c.056.042.112.07.182.07.071 0 .126-.027.182-.07l1.975-1.989c.574.35 1.191.56 1.849.63v1.037h-2.003c-.14 0-.252.042-.35.154-.098.098-.154.21-.154.336 0 .14.056.252.154.35.098.098.21.154.35.154h5.001c.14 0 .252-.056.35-.154.098-.098.154-.21.154-.35 0-.126-.056-.238-.154-.336-.098-.112-.21-.154-.35-.154h-2.003v-1.037c1.135-.126 2.088-.602 2.858-1.457.771-.855 1.149-1.864 1.149-3.012v-.995c0-.14-.056-.252-.154-.35-.098-.098-.21-.154-.336-.154-.14 0-.252.056-.35.154-.098.098-.154.21-.154.35v.995c0 .967-.35 1.793-1.037 2.48-.687.687-1.513 1.022-2.465 1.022-.574 0-1.107-.126-1.611-.392l.757-.756c.28.098.56.154.854.154.687 0 1.274-.252 1.765-.743.49-.491.728-1.078.728-1.765v-.995l2.83-2.83c.042-.042.07-.112.07-.182 0-.056-.028-.126-.07-.168l-.644-.644c-.056-.056-.112-.084-.182-.084-.07 0-.126.028-.182.084l-9.652 9.637c-.042.056-.07.112-.07.182 0 .07.028.126.07.182zm1.149-3.502l.784-.798c-.07-.308-.112-.602-.112-.882v-.995c0-.14-.056-.252-.154-.35-.098-.098-.21-.154-.336-.154-.14 0-.252.056-.35.154-.098.098-.154.21-.154.35v.995c0 .574.112 1.135.323 1.681zm6.542-6.527c-.182-.491-.491-.882-.911-1.191-.434-.308-.911-.462-1.443-.462-.701 0-1.289.238-1.779.728-.49.491-.728 1.079-.728 1.765v4.007l4.861-4.847z" fill="#333"/></g></svg>
|
After Width: | Height: | Size: 1.3 KiB |
@ -1 +1 @@
|
|||||||
<svg width="31" height="24" viewBox="0 0 31 24" xmlns="http://www.w3.org/2000/svg"><g fill="none"><path d="M19.002 11.5c0-.273-.226-.5-.5-.5-.273 0-.5.226-.5.5v1c0 1.93-1.57 3.5-3.5 3.5s-3.5-1.57-3.5-3.5v-1c0-.273-.226-.5-.5-.5-.273 0-.5.226-.5.5v1c0 2.312 1.75 4.219 4 4.468v1.031h-2c-.273 0-.5.226-.5.5 0 .273.226.5.5.5h5c.273 0 .5-.226.5-.5 0-.273-.226-.5-.5-.5h-2v-1.031c2.25-.25 4-2.156 4-4.468v-1zm-2-2.999c0-1.375-1.125-2.5-2.5-2.5s-2.5 1.125-2.5 2.5v4c0 1.375 1.125 2.5 2.5 2.5s2.5-1.125 2.5-2.5v-4z" fill="#00A9DC"/></g></svg>
|
<svg width="31" height="24" viewBox="0 0 31 24" xmlns="http://www.w3.org/2000/svg"><g fill="none"><path d="M19.002 11.5c0-.273-.226-.5-.5-.5-.273 0-.5.226-.5.5v1c0 1.93-1.57 3.5-3.5 3.5s-3.5-1.57-3.5-3.5v-1c0-.273-.226-.5-.5-.5-.273 0-.5.226-.5.5v1c0 2.312 1.75 4.219 4 4.468v1.031h-2c-.273 0-.5.226-.5.5 0 .273.226.5.5.5h5c.273 0 .5-.226.5-.5 0-.273-.226-.5-.5-.5h-2v-1.031c2.25-.25 4-2.156 4-4.468v-1zm-2-2.999c0-1.375-1.125-2.5-2.5-2.5s-2.5 1.125-2.5 2.5v4c0 1.375 1.125 2.5 2.5 2.5s2.5-1.125 2.5-2.5v-4z" fill="#00A9DC"/></g></svg>
|
||||||
|
Before Width: | Height: | Size: 535 B After Width: | Height: | Size: 536 B |
@ -0,0 +1 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 50 50"><path fill="#ffffff" d="M47.3,38.6c0-0.9-0.4-1.9-1.1-2.6l-11-11l11-11c0.7-0.7,1.1-1.6,1.1-2.6c0-0.9-0.4-1.9-1.1-2.6l-5.1-5.1 c-0.7-0.7-1.6-1.1-2.6-1.1c-0.9,0-1.9,0.4-2.6,1.1l-11,11l-11-11c-0.7-0.7-1.6-1.1-2.6-1.1c-0.9,0-1.9,0.4-2.6,1.1L3.8,8.9 c-0.7,0.7-1.1,1.6-1.1,2.6c0,0.9,0.4,1.9,1.1,2.6l11,11l-11,11c-0.7,0.7-1.1,1.6-1.1,2.6c0,0.9,0.4,1.9,1.1,2.6l5.1,5.1 c0.7,0.7,1.6,1.1,2.6,1.1c0.9,0,1.9-0.4,2.6-1.1l11-11l11,11c0.7,0.7,1.6,1.1,2.6,1.1c0.9,0,1.9-0.4,2.6-1.1l5.1-5.1 C46.9,40.5,47.3,39.5,47.3,38.6z"/></svg>
|
After Width: | Height: | Size: 573 B |
@ -0,0 +1 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 225 50"><path fill="#1E92CE" d="M146.3,20.2c-0.1-0.1-0.2-0.2-0.4-0.3c-0.2-0.1-0.3-0.1-0.5-0.1c-0.2,0-0.3,0-0.5,0.1 c-0.2,0.1-0.3,0.2-0.4,0.3c-0.1,0.1-0.2,0.3-0.3,0.4c-0.1,0.2-0.1,0.3-0.1,0.5c0,0.2,0,0.4,0.1,0.5c0.1,0.2,0.2,0.3,0.3,0.4 c0.1,0.1,0.2,0.2,0.4,0.3c0.2,0.1,0.3,0.1,0.5,0.1c0.2,0,0.3,0,0.5-0.1c0.2-0.1,0.3-0.2,0.4-0.3c0.1-0.1,0.2-0.3,0.3-0.4 c0.1-0.2,0.1-0.3,0.1-0.5c0-0.2,0-0.3-0.1-0.5C146.5,20.4,146.4,20.3,146.3,20.2z M146.3,21.5c-0.1,0.1-0.1,0.2-0.2,0.3 c-0.1,0.1-0.2,0.2-0.3,0.2c-0.1,0.1-0.3,0.1-0.4,0.1c-0.1,0-0.3,0-0.4-0.1c-0.1-0.1-0.2-0.1-0.3-0.2c-0.1-0.1-0.2-0.2-0.2-0.3 c-0.1-0.1-0.1-0.3-0.1-0.4c0-0.2,0-0.3,0.1-0.4c0.1-0.1,0.1-0.2,0.2-0.3c0.1-0.1,0.2-0.2,0.3-0.2c0.1-0.1,0.3-0.1,0.4-0.1 c0.1,0,0.3,0,0.4,0.1c0.1,0.1,0.2,0.1,0.3,0.2c0.1,0.1,0.2,0.2,0.2,0.3c0.1,0.1,0.1,0.3,0.1,0.4C146.4,21.2,146.4,21.4,146.3,21.5 z M145.7,21.4C145.7,21.3,145.6,21.3,145.7,21.4c-0.1-0.1-0.1-0.1-0.1-0.2c0,0,0,0,0,0c0.1,0,0.2,0,0.3-0.1 c0.1-0.1,0.1-0.2,0.1-0.3c0-0.1,0-0.1,0-0.2c0,0,0-0.1-0.1-0.1c0,0-0.1-0.1-0.1-0.1c-0.1,0-0.1,0-0.2,0H145v1.4h0.2v-0.6 c0,0,0,0,0.1,0c0,0,0,0,0,0c0,0,0.1,0.1,0.1,0.1c0,0.1,0.1,0.1,0.1,0.2l0.1,0.2h0.3L145.7,21.4C145.7,21.4,145.7,21.4,145.7,21.4z M145.3,21h-0.1v-0.5h0.1c0.1,0,0.2,0,0.2,0.1c0,0,0.1,0.1,0.1,0.2c0,0.1,0,0.1-0.1,0.2c0,0-0.1,0-0.1,0.1 C145.4,21,145.4,21,145.3,21z M54.4,37.8h4.1V26.5h6.8v-3.3h-6.8v-6.7h8.4l0.5-3.3h-13V37.8z M71.6,11.9c-1.5,0-2.6,1.2-2.6,2.6 c0,1.4,1.1,2.6,2.5,2.6c1.5,0,2.6-1.2,2.6-2.6C74.1,13.1,73,11.9,71.6,11.9z M69.5,37.8h3.9V19.4l-3.9,0.7V37.8z M85.4,19.3 c-1.7,0-3.2,0.9-4.6,2.9c0-1-0.2-2-0.7-2.8l-3.6,0.9c0.4,1.1,0.6,2.6,0.6,4.8v12.7h3.9V25.6c0.4-1.5,1.7-2.7,3.4-2.7 c0.4,0,0.7,0.1,1.1,0.2l1.2-3.6C86.3,19.4,86,19.3,85.4,19.3z M94,19.4c-2.3,0-4,0.7-5.5,2.4c-1.6,1.8-2.2,3.9-2.2,7 c0,5.7,3.2,9.4,8.3,9.4c2.4,0,4.6-0.8,6.4-2.4l-1.5-2.4c-1.3,1.2-2.8,1.8-4.5,1.8c-3.5,0-4.4-2.6-4.4-5.1v-0.3h10.7V29 c0-4.2-0.8-6.4-2.3-7.8C97.4,19.9,95.7,19.4,94,19.4z M90.6,27c0-2.9,1.2-4.6,3.4-4.6c1.9,0,3.2,1.7,3.2,4.6H90.6z M108,19.8v-2.7 c0-1.6,0.9-2.5,2.2-2.5c0.7,0,1.3,0.2,2.2,0.6l1.3-2.5c-1.2-0.7-2.5-1-4-1c-3.2,0-5.5,1.7-5.5,5.5c0,1.7,0.1,2.7,0.1,2.7h-1.7v2.7 h1.7v15.3h3.9V22.5h3.7l1-2.7H108z M119.9,19.4c-4.7,0-7.8,3.7-7.8,9.4c0,5.7,3,9.4,7.9,9.4c4.9,0,7.9-3.6,7.9-9.4 C127.9,23.2,125,19.4,119.9,19.4z M120.1,35.3c-2.3,0-3.6-1.5-3.6-6.7c0-4.4,1.1-6.2,3.5-6.2c2.3,0,3.7,1.5,3.7,6.6 C123.6,33.4,122.4,35.3,120.1,35.3z M143.2,19.8h-4.5c-0.5,0.7-2.3,4.3-2.8,5.5c-0.9-1.7-2.4-4.5-3.3-5.8l-4.2,0.9l5.2,7.7 l-6.7,9.7h4.9c0.7-1,3.2-5.4,3.9-6.7c0.4,0.6,3.3,5.7,3.9,6.7h4.9L138,28L143.2,19.8z M165.8,23.6h-10.3V13.3h-2.9v24.6h2.9V26 h10.3v11.9h2.9V13.3h-2.9V23.6z M179,19.5c-2.1,0-3.9,0.8-5.3,2.5c-1.5,1.8-2.1,3.7-2.1,6.7c0,5.9,3,9.5,7.9,9.5 c2.3,0,4.4-0.8,6-2.2l-1.1-1.8c-1.3,1.1-2.6,1.7-4.4,1.7c-1.8,0-3.4-0.6-4.4-2.2c-0.6-0.9-0.8-2.2-0.8-3.9v-0.4h11.1V29 c-0.1-4.3-0.5-5.9-2-7.5C182.6,20.2,181,19.5,179,19.5z M174.8,27.3c0.1-3.7,1.6-5.6,4.1-5.6c1.4,0,2.6,0.6,3.2,1.6 c0.5,0.9,0.8,2,0.8,4H174.8z M193.1,36.1c-0.8,0-1-0.4-1-1.8V16c0-2.7-0.5-4.2-0.5-4.2l-2.8,0.5c0,0,0.4,1.3,0.4,3.7v18.9 c0,1.3,0.3,2,0.9,2.5c0.5,0.5,1.3,0.8,2.1,0.8c0.8,0,1.1-0.1,1.8-0.4l-0.6-1.8C193.4,36,193.2,36.1,193.1,36.1z M200.3,36.1 c-0.8,0-1-0.4-1-1.8V16c0-2.7-0.5-4.2-0.5-4.2l-2.8,0.5c0,0,0.4,1.3,0.4,3.7v18.9c0,1.3,0.3,2,0.9,2.5c0.5,0.5,1.2,0.8,2.1,0.8 c0.7,0,1.1-0.1,1.8-0.4l-0.6-1.8C200.6,36,200.4,36.1,200.3,36.1z M216.3,22.6c-1.2-1.8-3.1-3.1-6.1-3.1c-4.7,0-7.6,3.5-7.6,9.4 c0,5.9,2.8,9.5,7.7,9.5c4.5,0,7.7-3.2,7.7-9.1C218,26.3,217.5,24.2,216.3,22.6z M214.3,33.3c-0.6,1.7-2,2.7-3.9,2.7 c-1.5,0-3-0.7-3.6-1.8c-0.7-1.1-1.1-3.3-1.1-5.8c0-2.1,0.3-3.5,0.9-4.7c0.6-1.2,2.1-1.9,3.6-1.9c1.5,0,3,0.7,3.8,2.3 c0.6,1.2,0.8,2.9,0.8,5.4C214.8,31.2,214.7,32.2,214.3,33.3z M26.1,7.7C16.1,7.7,8,14.9,8,23.6c0,4.4,2,8.3,5.2,11.2 c-0.6,2-1.7,4.7-3.9,7.3c0.4,0.7,6.6-1.7,11-3.4c1.8,0.5,3.7,0.8,5.7,0.8c10,0,18.1-7.1,18.1-15.9C44.1,14.9,36,7.7,26.1,7.7z M31.5,17.3c1.3,0,2.4,1.1,2.4,2.4c0,1.3-1.1,2.4-2.4,2.4c-1.3,0-2.4-1.1-2.4-2.4C29.1,18.3,30.2,17.3,31.5,17.3z M20.6,17.3 c1.3,0,2.4,1.1,2.4,2.4c0,1.3-1.1,2.4-2.4,2.4c-1.3,0-2.4-1.1-2.4-2.4C18.2,18.3,19.3,17.3,20.6,17.3z M26.1,34.7 C26.1,34.7,26,34.7,26.1,34.7c-0.1,0-0.1,0-0.1,0c-4.8,0-10.2-3.1-11.5-8.4c3.3,1.5,7.8,2.2,11.5,2.2c3.7,0,8.3-0.7,11.5-2.2 C36.3,31.6,30.9,34.7,26.1,34.7z"/></svg>
|
After Width: | Height: | Size: 4.2 KiB |
After Width: | Height: | Size: 8.1 KiB |
@ -0,0 +1 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="0 0 310 310"><defs><path id="a" d="M155,63.5c55.2,0,100,44.8,100,100s-44.8,100-100,100c-55.2,0-100-44.8-100-100S99.8,63.5,155,63.5z"/></defs><clipPath id="b"><use xlink:href="#a" overflow="visible"/></clipPath><path fill="#FFF" fill-rule="evenodd" d="M155,63.5c55.2,0,100,44.8,100,100 s-44.8,100-100,100c-55.2,0-100-44.8-100-100S99.8,63.5,155,63.5z" clip-path="url(#b)" clip-rule="evenodd"/><g fill="#E7C1A8" clip-path="url(#b)"><path d="M80.3 199.7H123.3V254.7H80.3z" transform="rotate(71.16 101.822 227.175)"/><path d="M84.6,163c7.1-1,13.8,1.9,15,11.4l3,22.5c0.1,0.9,1.5,1.5,2.4,1.4c0.9-0.1,1.6-1,1.4-1.9l-10-74.2l0,0 l-0.5-3.5c-0.5-3.4,2-6.6,5.4-7.1c3.5-0.5,6.7,1.9,7.1,5.4l0.5,3.5l5.8,42.7l3-0.4l-7-51.6l0,0l-0.5-3.5c-0.5-3.4,2-6.6,5.4-7.1 c3.5-0.5,6.7,1.9,7.1,5.4l0.5,3.5l0,0l7,51.6l4.1-0.6l-5.8-42.7l-0.5-3.5c-0.5-3.4,2-6.6,5.4-7.1c3.5-0.5,6.7,1.9,7.1,5.4 l0.5,3.5l0,0l5.8,42.7l3.9-0.5L147,130l0,0l-0.5-3.5c-0.5-3.4,2-6.6,5.4-7.1c3.5-0.5,6.7,1.9,7.1,5.4l0.5,3.5c0,0,0,0,0,0 c1.2,9.3,8.9,65.7,9.1,67.8c2.9,21.5-12.3,41.3-33.9,44.2c-21.6,2.9-41.5-12.2-44.4-33.7C90.1,205.1,84.6,163,84.6,163z"/></g><g clip-path="url(#b)"><defs><path id="c" d="M84.6,163c7.1-1,13.8,1.9,15,11.4l3,22.5c0.1,0.9,1.5,1.5,2.4,1.4c0.9-0.1,1.6-1,1.4-1.9l-10-74.2l0,0 l-0.5-3.5c-0.5-3.4,2-6.6,5.4-7.1c3.5-0.5,6.7,1.9,7.1,5.4l0.5,3.5l5.8,42.7l3-0.4l-7-51.6l0,0l-0.5-3.5c-0.5-3.4,2-6.6,5.4-7.1 c3.5-0.5,6.7,1.9,7.1,5.4l0.5,3.5l0,0l7,51.6l4.1-0.6l-5.8-42.7l-0.5-3.5c-0.5-3.4,2-6.6,5.4-7.1c3.5-0.5,6.7,1.9,7.1,5.4 l0.5,3.5l0,0l5.8,42.7l3.9-0.5L147,130l0,0l-0.5-3.5c-0.5-3.4,2-6.6,5.4-7.1c3.5-0.5,6.7,1.9,7.1,5.4l0.5,3.5c0,0,0,0,0,0 c1.2,9.3,8.9,65.7,9.1,67.8c2.9,21.5-12.3,41.3-33.9,44.2c-21.6,2.9-41.5-12.2-44.4-33.7C90.1,205.1,84.6,163,84.6,163z"/></defs><clipPath id="d"><use xlink:href="#c" overflow="visible"/></clipPath><path fill="#F4D2BB" d="M104.7,196.9c-0.2,0-0.3,0.1-0.5,0.1l-6.4-47.6L78,152.1l7.1,52.8 c-12,8.9-19,23.9-16.8,39.8c3.1,23.3,24.5,39.6,47.8,36.4s39.6-24.5,36.4-47.8S127.9,193.8,104.7,196.9z" clip-path="url(#d)"/></g><path fill="#CE7C32" d="M215.7,212.9c0.8-2.7,1.4-5.5,1.6-8.5c0.2-2,4.4-58.9,5.1-68.2c0,0,0,0,0,0 l0.3-3.6c0.3-3.5-2.4-6.5-5.8-6.7c-3.5-0.3-6.5,2.3-6.8,5.8l-0.3,3.6l0,0l-2.1,28.5l-3.9-0.3l3.2-43l0,0l0.3-3.6 c0.3-3.5-2.4-6.5-5.8-6.7c-3.5-0.3-6.5,2.3-6.8,5.8l-0.3,3.6l-3.2,43l-4.1-0.3l3.9-52l0,0l0.3-3.6c0.3-3.5-2.4-6.5-5.8-6.7 c-3.5-0.3-6.5,2.3-6.8,5.8l-0.3,3.6l0,0l-3.9,52l-3-0.2l3.2-43l0.3-3.6c0.3-3.5-2.4-6.5-5.8-6.7c-3.5-0.3-6.5,2.3-6.8,5.8 l-0.3,3.6l0,0l-5.6,74.7c-0.1,0.9-0.9,1.6-1.8,1.6c-0.9-0.1-2.1-0.9-2.1-1.8l1.7-22.7c0.7-9.6-5.2-13.7-12.4-14.3 c0,0-3.3,42.3-3.4,43.9c-1.5,19.8,12.1,37.2,31,41.3l0,0.1l50.6,21.6l16.9-39.6L215.7,212.9z" clip-path="url(#b)"/><g fill="#71C4EA"><path d="M153.8,79.4c0,1.5,1.2,2.6,2.6,2.6c1.5,0,2.6-1.2,2.6-2.6V49.1c0-1.5-1.2-2.6-2.6-2.6 c-1.5,0-2.6,1.2-2.6,2.6V79.4z"/><path d="M174.7,86.4c-0.8,1.2-0.6,2.8,0.6,3.7c1.2,0.8,2.8,0.6,3.7-0.6l17.5-24.7c0.8-1.2,0.6-2.8-0.6-3.7 c-1.2-0.8-2.8-0.6-3.7,0.6L174.7,86.4z"/><path d="M138.1,86.4c0.8,1.2,0.6,2.8-0.6,3.7c-1.2,0.8-2.8,0.6-3.7-0.6l-17.5-24.7c-0.8-1.2-0.6-2.8,0.6-3.7 c1.2-0.8,2.8-0.6,3.7,0.6L138.1,86.4z"/></g></svg>
|
After Width: | Height: | Size: 3.1 KiB |
@ -0,0 +1 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="0 0 310 310"><path fill="#71C4EA" d="M265.5,53.9c0-2.6-2.1-3.7-4.7-3.7h-79.6c-2.6,0-4.7,2.1-4.7,4.7V64h89V53.9z"/><circle cx="184.1" cy="56.9" r="1.6" fill="#FFF"/><circle cx="190.2" cy="56.9" r="1.6" fill="#FFF"/><circle cx="196.3" cy="56.9" r="1.6" fill="#FFF"/><path fill="#FFF" d="M181.2,118h79.6c2.6,0,4.7-2.1,4.7-4.7V64h-89v49.3C176.5,115.9,178.6,118,181.2,118z"/><path fill="#D6DCE5" d="M176.5 64H265.5V73H176.5z"/><path fill="#06A6E0" fill-rule="evenodd" d="M153.5,59.9c55.2,0,100,44.8,100,100s-44.8,100-100,100 c-55.2,0-100-44.8-100-100S98.3,59.9,153.5,59.9z" clip-rule="evenodd"/><path fill="none" d="M153.5,59.9c55.2,0,100,44.8,100,100s-44.8,100-100,100 c-55.2,0-100-44.8-100-100S98.3,59.9,153.5,59.9z" clip-rule="evenodd"/><defs><path id="a" d="M153.5,59.9c55.2,0,100,44.8,100,100s-44.8,100-100,100c-55.2,0-100-44.8-100-100S98.3,59.9,153.5,59.9z"/></defs><clipPath id="b"><use xlink:href="#a" overflow="visible"/></clipPath><g clip-path="url(#b)"><path fill="#0283B1" d="M259.4,134.6L259.4,134.6C259.4,134.6,259.4,134.6,259.4,134.6c0-0.1,0-0.2-0.1-0.2h0 c-4.3-13.9-11.2-26.9-20.1-38.1l-2.2,0.8l-1.8,3.9L223,98l-6.8,5.9l1.8,3.4l-11.2-6.9l5.5-5.8l9.7-3.4l-1.5-3.4L210.3,92 l-7.3,7.5l6.3,7.2l-9.4,0l-14.2,9.3l-4.8-5l7.3,0.7l3.3-5.1l-18.5-4.3l-12.4,3l-11.2,15.1l-0.4,5.8l6.8-0.8l1.5,3.9l6.4-1.8 l0.4-5.1l-2.3-1.9l6.4-8.3l2.9,3l-5,3.9l2,4.3l6.8-2.4l1.9,2.4l-10.8,4.1l-0.4,2.8l-9.9,0.5l-1.9-2.9l-2.9,1.4l1.5,3.9l-1.6,1.6 l-4,3.6l-2.9,0.4l-5,3.9l2.9,3l-1.4,3.4l-7.9-0.5l4,11l11.2-10.4l2,1.1l3.3-3l5.9,4.4l1.4,5.8l3.4-4.8l-5.3-7.3l1.2-1.3l6.6,7.6 l1,5.8l5.5,4l-1-7.9l5.8,7.9l4.4-5.9v5.9l-2,2l-10.7-0.5l-3-2.5l-3.9,3.4l-7.9-2.5l0.5-2.5l-2.5-2.8l-9.8,0.9l-17.7,15.3 l0.4,15.1l8,7l13.7,0.4l6.8,4.4l-2.9,1.9l7,8.3v5.8l-2.6,3.4l8.3,22.6l9.4,0.5l11.7-14.2l0.5-5.5l4.5-3.4l-0.6-7.3l-1.8-1.9 l15.2-22.1l-8.4,0.6l-5.5-4.1l-7.2-14.6l0.4-4h2.9l10.8,20.7l10.3-3.9l4.4-5.5l-4.4-4.3l-4,1.4l-3.9-6.4l23.6,4.3l-0.4,6.6 l2.8,0.9l4,11.2l6.5-1.8v-5.9l8.2-6.4l1.1-4.5h2.4v5l8.4,18.2l-5.9-0.4l9.9,10.8c0.6-1.9,1.2-3.8,1.7-5.7l-1.8-0.2l-1-3l3.4,0.9 c0.3-1,0.5-2.1,0.7-3.1l-4.1-2.7l-1.5-6.4l1.5-0.4l3,5.7l2.1-1.2c0.5-2.9,0.9-5.9,1.2-8.9c0-0.3,0.1-0.6,0.1-0.9 c0.3-3.5,0.5-7,0.5-10.5C264.5,156.8,262.7,145.4,259.4,134.6z M181.9,149.5h-3l-3.9,3l-0.9-3.4l4.4-5.5l2.5,3.2l2.9-0.3 l-0.2-2.6l3.3,0.6l3.6,5.7L181.9,149.5z M201.7,157.9l-0.6-10.4l2.8-2.2l1.4,1.5l-2.7,3.7l3.2,6.4L201.7,157.9z M211.9,150.8 l-0.3-4l2.5-0.3l0.5,4L211.9,150.8z M192.3,219.7l-1.8,6.4l6.4,4.4l4.4-14.1l-2.6-1.6L192.3,219.7z M130.3,134.4l-0.2,0.4l0,0 l-0.3,0.5l3.3,3l3-3.3l-0.5-0.6h0l-3-3.8L130.3,134.4L130.3,134.4z M86.9,200.7l-0.9-7h-7.4l-0.6-6l-9.2-0.3l-4.8-1.8l-6.8,2.9 h-7l-1-5.9l-3.9-0.4l1-5.5l-2.6-1.4l-3.9,2.4H38l-4.1-3.9l4.1-7.3h10.8l2.9,4.4h3.4l-2-6.5l7-5.9l-0.6-3.9l2.9-2.9l3,0.4v-5.3 l17.2-9.8l-2.1-2.5h0l-10-12.1l-15.2-2.5l0,7.3l1.4,1.5l-1.9,5.8h-0.1l-0.5,1.5l-1.3-1.5h-0.1l-5-5.8l-6.4-1.9 c-1,2.5-1.9,5.1-2.7,7.8h0c-3.3,10.8-5.1,22.3-5.1,34.2c0,4.9,0.3,9.7,0.9,14.4l6.5,3.7l3.9-0.4l3,5.9h6.8v5l-2.9,4.4l6.8,17 l6.5,3l-3.8,22c3.1,3.6,6.4,7,9.9,10.2l29.1-31.1l1.1-8.8l3.9-4.5L92,200.7H86.9z M155.8,156.2l3,1.1l-1.2-4L155.8,156.2z M76.3,182.5l-13.9-5.9l-0.9,2.5l12.3,4.4L76.3,182.5z M50.5,176.6l7.5,1.6l1-3l-7.8-2.3L50.5,176.6z M58.1,167.7v3.4l3.9,1.9 L58.1,167.7z M126.9,110.6l-9.8,2.5l5.3,7.9l8.8-6.3L126.9,110.6z M138.8,134.8l-1.4,2.7h6.9l0.3-2.6l0.2-1.7l-4.3-3.5l-2-4.8 l-3.3,3.4l4.5,4.9L138.8,134.8z M123.5,83.7l5.8-4.9l-10.7-8.8l-36.4,4.4c-6.4,4.5-12.2,9.7-17.5,15.4l2.4,3.3l6.5-2l5.9,4.4 l2.9,15.2l7.3,11.3l6.4,0.4l3.5-8.4l14.1-4.3l8.9-13.7L123.5,83.7z M50.9,112.5l1.9-5.5l4.5,0l-1.6,8l12.8,2.3l6.8-7.3 L58.8,96.4c-5,6.3-9.4,13.1-13,20.3l6.5,0.6L50.9,112.5z"/></g><g fill="none" stroke="#F5C825" stroke-width="4" stroke-miterlimit="10" stroke-linecap="round"><path d="M128.5 57L128.5 57"/><path d="M117,53.3c-22.1-6-41.2-4-52.7,7.5c-25.5,25.5-4.4,87.8,47,139.3s113.8,72.5,139.3,47c11.6-11.6,13.6-30.9,7.3-53.4" stroke-dasharray="0,12.1246"/><path d="M256.2 187.9L256.2 187.9"/></g><path fill="#7DC14C" d="M166.5,172.1c0-3.5-2.9-5.1-6.4-5.1H50.9c-3.5,0-6.4,2.9-6.4,6.4V186h122V172.1z"/><circle cx="54.6" cy="176.2" r="2.2" fill="#FFF"/><circle cx="62.9" cy="176.2" r="2.2" fill="#FFF"/><circle cx="71.3" cy="176.2" r="2.2" fill="#FFF"/><path fill="#FFF" d="M50.9,260h109.2c3.5,0,6.4-2.9,6.4-6.4V186h-122v67.6C44.5,257.1,47.4,260,50.9,260z"/><path fill="#D6DCE5" d="M44.5 186H166.5V199H44.5z"/></svg>
|
After Width: | Height: | Size: 4.4 KiB |
@ -0,0 +1 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" x="0px" y="0px" xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="0 0 310 310"><path fill="#A3D189" fill-rule="evenodd" d="M142.5,55c55.2,0,100,44.8,100,100s-44.8,100-100,100 c-55.2,0-100-44.8-100-100S87.3,55,142.5,55z" clip-rule="evenodd"/><defs><path id="a" d="M267.5,66h-79.4c-13.7-7-29.2-11-45.6-11c-55.2,0-100,44.8-100,100c0,55.2,44.8,100,100,100 c20.7,0,39.9-6.3,55.8-17h69.2V66z"/></defs><clipPath id="b"><use xlink:href="#a" overflow="visible"/></clipPath><g clip-path="url(#b)"><path fill="#D6DCE5" d="M14.5 111H234.5V142H14.5z"/><path fill="#383F44" d="M234.5,90c0-5.5-4.5-10-10-10h-200c-5.5,0-10,4.5-10,10v21h220V90z"/><path fill="#FFF" d="M14.5,219c0,5.5,4.5,10,10,10h200c5.5,0,10-4.5,10-10v-77h-220V219z"/><path fill="#D6DCE5" fill-rule="evenodd" d="M92.5,101c-0.8-4.2-2.1-12-12.6-12H54.1 c-10.4,0-11.8,7.8-12.6,12c-1.2,6.6-3.8,10.1-10.7,10.1c4.6,0,67.8,0,72.5,0C96.3,111.1,93.7,107.6,92.5,101z" clip-rule="evenodd"/><path fill="#FFF" d="M29.5 118H165.5V135H29.5z"/><circle cx="32" cy="126.5" r="10.5" fill="#41484E"/><path fill="#FFF" d="M210.5 119.5c0 .8.7 1.5 1.5 1.5h14c.8 0 1.5-.7 1.5-1.5 0-.8-.7-1.5-1.5-1.5h-14C211.2 118 210.5 118.7 210.5 119.5 210.5 119.5 210.5 118.7 210.5 119.5zM210.5 126.5c0 .8.7 1.5 1.5 1.5h14c.8 0 1.5-.7 1.5-1.5 0-.8-.7-1.5-1.5-1.5h-14C211.2 125 210.5 125.7 210.5 126.5 210.5 126.5 210.5 125.7 210.5 126.5zM210.5 133.5c0 .8.7 1.5 1.5 1.5h14c.8 0 1.5-.7 1.5-1.5s-.7-1.5-1.5-1.5h-14C211.2 132 210.5 132.7 210.5 133.5 210.5 133.5 210.5 132.7 210.5 133.5z"/><path fill="#FFF" fill-rule="evenodd" d="M35.7,125.1h-4.4l2-2c0.3-0.3,0.4-0.8,0.1-1l-0.9-0.9 c-0.2-0.2-0.7-0.2-1,0.1l-4.6,4.7c-0.1,0.1-0.1,0.1-0.1,0.2l0,0c0,0,0,0,0,0c0,0,0,0.1-0.1,0.1c0,0.1-0.1,0.1-0.1,0.2 c0,0,0,0.1,0,0.1c0,0,0,0.1,0,0.1c0,0.1,0,0.1,0.1,0.2c0,0,0,0.1,0.1,0.1c0,0,0,0,0,0l0,0c0,0.1,0.1,0.1,0.1,0.2l4.6,4.7 c0.3,0.3,0.8,0.4,1,0.1l0.9-0.9c0.2-0.2,0.2-0.7-0.1-1l-2-2h4.5c0.4,0,0.7-0.3,0.7-0.7v-1.5C36.4,125.4,36.1,125.1,35.7,125.1z" clip-rule="evenodd"/><circle cx="187.5" cy="127" r="9" fill="#FFF"/><path fill="#0283B1" d="M187.5,114.4c-7.2,0-13,5.1-13,11.3c0,3.1,1.4,5.9,3.8,8c-0.4,1.4-1.2,3.3-2.8,5.2 c0.3,0.5,4.7-1.2,7.9-2.5c1.3,0.4,2.7,0.6,4.1,0.6c7.2,0,13-5.1,13-11.3C200.5,119.5,194.7,114.4,187.5,114.4z M191.4,121.2 c0.9,0,1.7,0.8,1.7,1.7c0,0.9-0.8,1.7-1.7,1.7c-0.9,0-1.7-0.8-1.7-1.7C189.7,121.9,190.4,121.2,191.4,121.2z M183.5,121.2 c0.9,0,1.7,0.8,1.7,1.7c0,0.9-0.8,1.7-1.7,1.7c-1,0-1.7-0.8-1.7-1.7C181.8,121.9,182.6,121.2,183.5,121.2z M187.5,133.6 C187.5,133.6,187.5,133.6,187.5,133.6c-0.1,0-0.1,0-0.1,0c-3.5,0-7.3-2.2-8.2-6c2.3,1.1,5.6,1.5,8.3,1.5c2.7,0,5.9-0.5,8.3-1.5 C194.9,131.4,191,133.6,187.5,133.6z"/></g><path fill="#383F44" d="M195.5,155.8v-19.5l17,12.5l-7.4,1.2l3,7.6l-3.3,1.4l-3.9-6.8L195.5,155.8z"/></svg>
|
After Width: | Height: | Size: 2.7 KiB |
@ -1 +1 @@
|
|||||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16"><path fill="#FFF" d="M12 10.4c0 .2-.1.4-.2.5-.1.1-.3.2-.5.2H4.7c-.2 0-.4-.1-.5-.2-.1-.1-.2-.3-.2-.5V6.9c.1.1.3.3.5.4 1 .7 1.8 1.2 2.2 1.5.1.1.3.2.4.3.1.1.2.1.4.2s.3.1.5.1.3 0 .5-.1.3-.1.4-.2c.1-.1.3-.2.4-.3.5-.4 1.2-.9 2.2-1.5.2-.1.4-.3.5-.4v3.5zm-.2-4.2c-.1.2-.3.4-.5.5-1.1.8-1.8 1.3-2.1 1.5 0 0-.1.1-.2.1-.1.2-.2.2-.3.3-.1 0-.1.1-.2.1-.1.1-.2.1-.3.1h-.4c-.1 0-.2-.1-.3-.1-.1-.1-.2-.1-.2-.1-.1-.1-.2-.1-.3-.2-.1-.1-.1-.1-.1-.2-.3-.1-.7-.4-1.2-.8-.5-.3-.8-.5-.9-.6-.2-.1-.4-.3-.6-.5S4 5.9 4 5.7c0-.2.1-.4.2-.6.1-.2.3-.2.5-.2h6.6c.2 0 .4.1.5.2.1.1.2.3.2.5s-.1.4-.2.6z"/></svg>
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16"><path fill="#4a4a4a" d="M12 10.4c0 .2-.1.4-.2.5-.1.1-.3.2-.5.2H4.7c-.2 0-.4-.1-.5-.2-.1-.1-.2-.3-.2-.5V6.9c.1.1.3.3.5.4 1 .7 1.8 1.2 2.2 1.5.1.1.3.2.4.3.1.1.2.1.4.2s.3.1.5.1.3 0 .5-.1.3-.1.4-.2c.1-.1.3-.2.4-.3.5-.4 1.2-.9 2.2-1.5.2-.1.4-.3.5-.4v3.5zm-.2-4.2c-.1.2-.3.4-.5.5-1.1.8-1.8 1.3-2.1 1.5 0 0-.1.1-.2.1-.1.2-.2.2-.3.3-.1 0-.1.1-.2.1-.1.1-.2.1-.3.1h-.4c-.1 0-.2-.1-.3-.1-.1-.1-.2-.1-.2-.1-.1-.1-.2-.1-.3-.2-.1-.1-.1-.1-.1-.2-.3-.1-.7-.4-1.2-.8-.5-.3-.8-.5-.9-.6-.2-.1-.4-.3-.6-.5S4 5.9 4 5.7c0-.2.1-.4.2-.6.1-.2.3-.2.5-.2h6.6c.2 0 .4.1.5.2.1.1.2.3.2.5s-.1.4-.2.6z"/></svg>
|
Before Width: | Height: | Size: 635 B After Width: | Height: | Size: 638 B |
@ -1 +1 @@
|
|||||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16"><path fill="#FFF" d="M12 11.6c0 .1 0 .2-.1.3s-.2.1-.3.1h-2V8.9h1l.2-1.2H9.5v-.8c0-.2 0-.3.1-.4.1-.1.2-.1.5-.1h.6V5.3h-.9c-.4-.1-.8 0-1.1.3-.3.3-.4.7-.4 1.2v.9h-1v1.2h1V12H4.4c-.1 0-.2 0-.3-.1-.1-.1-.1-.2-.1-.3V4.4c0-.1 0-.2.1-.3.1-.1.2-.1.3-.1h7.1c.1 0 .2 0 .3.1.2.1.2.2.2.3v7.2z"/></svg>
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16"><path fill="#4a4a4a" d="M12 11.6c0 .1 0 .2-.1.3s-.2.1-.3.1h-2V8.9h1l.2-1.2H9.5v-.8c0-.2 0-.3.1-.4.1-.1.2-.1.5-.1h.6V5.3h-.9c-.4-.1-.8 0-1.1.3-.3.3-.4.7-.4 1.2v.9h-1v1.2h1V12H4.4c-.1 0-.2 0-.3-.1-.1-.1-.1-.2-.1-.3V4.4c0-.1 0-.2.1-.3.1-.1.2-.1.3-.1h7.1c.1 0 .2 0 .3.1.2.1.2.2.2.3v7.2z"/></svg>
|
Before Width: | Height: | Size: 348 B After Width: | Height: | Size: 351 B |
@ -0,0 +1 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16"><path fill="#5a5a5a" d="M10.716 5.643c0 1.943-2.158 1.812-2.158 3.154v.3H6.83v-.37C6.83 6.65 8.74 6.793 8.74 5.81c0-.43-.312-.683-.84-.683-.49 0-.972.24-1.403.73l-1.21-.934C5.966 4.12 6.854 3.64 8.09 3.64c1.75 0 2.626.936 2.626 2.003zm-1.92 5.625c0 .6-.478 1.092-1.078 1.092s-1.08-.492-1.08-1.092c0-.588.48-1.08 1.08-1.08s1.08.492 1.08 1.08z"/></svg>
|
After Width: | Height: | Size: 411 B |
@ -0,0 +1 @@
|
|||||||
|
<svg width="13" height="10" viewBox="0 0 13 10" xmlns="http://www.w3.org/2000/svg" xmlns:sketch="http://www.bohemiancoding.com/sketch/ns"><title>Shape Copy</title><desc>Created with Sketch.</desc><path d="M6.302 2.225h-3.067c-.602 0-1.095.49-1.095 1.095v3.287c0 .609.49 1.095 1.095 1.095h3.067v1.55c0 .617.397.811.887.449l5.458-4.036c.498-.368.49-.949 0-1.312l-5.458-4.036c-.498-.368-.887-.161-.887.449v1.46zm-6.302-2.191h5.348v1.095h-4.278v7.668h4.278v1.106h-5.348v-9.869z" sketch:type="MSShapeGroup" fill="#fff"/></svg>
|
After Width: | Height: | Size: 521 B |
@ -0,0 +1 @@
|
|||||||
|
<svg width="31" height="24" viewBox="0 0 31 24" xmlns="http://www.w3.org/2000/svg"><g fill="none"><path d="M13.506 17.003h4.478c.554 0 1.033-.202 1.424-.592.404-.403.592-.87.592-1.437v-1.16l2.836 2.836c.089.089.189.126.316.126l.176-.038c.189-.076.277-.215.277-.416v-7.652c0-.189-.088-.327-.277-.416l-.176-.025c-.126 0-.227.038-.316.126l-2.836 2.824v-.671l1.83-1.83c.042-.042.07-.112.07-.182 0-.056-.028-.126-.07-.168l-.644-.644c-.056-.056-.112-.084-.182-.084-.07 0-.126.028-.182.084l-9.652 9.637c-.042.056-.07.112-.07.182 0 .07.028.126.07.182l.644.644c.056.042.112.07.182.07.071 0 .126-.027.182-.07l1.327-1.327zm4.948-8.95c-.15-.034-.307-.05-.471-.05h-4.954c-.567 0-1.034.189-1.437.592-.391.391-.592.869-.592 1.424v4.954c0 .165.017.322.051.471l7.403-7.392z" fill="#333"/></g></svg>
|
After Width: | Height: | Size: 782 B |
@ -130,6 +130,15 @@ loop.shared.actions = (function() {
|
|||||||
// sentTimestamp: String (optional)
|
// sentTimestamp: String (optional)
|
||||||
}),
|
}),
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Used to send cursor data to the other peer
|
||||||
|
*/
|
||||||
|
SendCursorData: Action.define("sendCursorData", {
|
||||||
|
ratioX: Number,
|
||||||
|
ratioY: Number,
|
||||||
|
type: String
|
||||||
|
}),
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Notifies that cursor data has been received from the other peer.
|
* Notifies that cursor data has been received from the other peer.
|
||||||
*/
|
*/
|
||||||
@ -396,11 +405,13 @@ loop.shared.actions = (function() {
|
|||||||
* XXX: should move to some roomActions module - refs bug 1079284
|
* XXX: should move to some roomActions module - refs bug 1079284
|
||||||
* @from: where the invitation is shared from.
|
* @from: where the invitation is shared from.
|
||||||
* Possible values ['panel', 'conversation']
|
* Possible values ['panel', 'conversation']
|
||||||
* @roomUrl: the URL that is shared
|
* @roomUrl: the URL that is shared.
|
||||||
|
* @roomOrigin: the URL browsed when the sharing is started - Optional.
|
||||||
*/
|
*/
|
||||||
FacebookShareRoomUrl: Action.define("facebookShareRoomUrl", {
|
FacebookShareRoomUrl: Action.define("facebookShareRoomUrl", {
|
||||||
from: String,
|
from: String,
|
||||||
roomUrl: String
|
roomUrl: String
|
||||||
|
// roomOrigin: String
|
||||||
}),
|
}),
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -32,7 +32,7 @@ loop.store.ROOM_STATES = {
|
|||||||
CLOSING: "room-closing"
|
CLOSING: "room-closing"
|
||||||
};
|
};
|
||||||
|
|
||||||
loop.store.ActiveRoomStore = (function() {
|
loop.store.ActiveRoomStore = (function(mozL10n) {
|
||||||
"use strict";
|
"use strict";
|
||||||
|
|
||||||
var sharedActions = loop.shared.actions;
|
var sharedActions = loop.shared.actions;
|
||||||
@ -693,7 +693,8 @@ loop.store.ActiveRoomStore = (function() {
|
|||||||
gotMediaPermission: function() {
|
gotMediaPermission: function() {
|
||||||
this.setStoreState({ roomState: ROOM_STATES.JOINING });
|
this.setStoreState({ roomState: ROOM_STATES.JOINING });
|
||||||
|
|
||||||
loop.request("Rooms:Join", this._storeState.roomToken).then(function(result) {
|
loop.request("Rooms:Join", this._storeState.roomToken,
|
||||||
|
mozL10n.get("display_name_guest")).then(function(result) {
|
||||||
if (result.isError) {
|
if (result.isError) {
|
||||||
this.dispatchAction(new sharedActions.RoomFailure({
|
this.dispatchAction(new sharedActions.RoomFailure({
|
||||||
error: result,
|
error: result,
|
||||||
@ -1243,4 +1244,4 @@ loop.store.ActiveRoomStore = (function() {
|
|||||||
});
|
});
|
||||||
|
|
||||||
return ActiveRoomStore;
|
return ActiveRoomStore;
|
||||||
})();
|
})(navigator.mozL10n || document.mozL10n);
|
||||||
|
@ -4,7 +4,7 @@
|
|||||||
|
|
||||||
var loop = loop || {};
|
var loop = loop || {};
|
||||||
loop.shared = loop.shared || {};
|
loop.shared = loop.shared || {};
|
||||||
loop.shared.models = (function(l10n) {
|
loop.shared.models = (function() {
|
||||||
"use strict";
|
"use strict";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -24,71 +24,11 @@ loop.shared.models = (function(l10n) {
|
|||||||
* Notification collection
|
* Notification collection
|
||||||
*/
|
*/
|
||||||
var NotificationCollection = Backbone.Collection.extend({
|
var NotificationCollection = Backbone.Collection.extend({
|
||||||
model: NotificationModel,
|
model: NotificationModel
|
||||||
|
|
||||||
/**
|
|
||||||
* Adds a warning notification to the stack and renders it.
|
|
||||||
*
|
|
||||||
* @return {String} message
|
|
||||||
*/
|
|
||||||
warn: function(message) {
|
|
||||||
this.add({ level: "warning", message: message });
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Adds a l10n warning notification to the stack and renders it.
|
|
||||||
*
|
|
||||||
* @param {String} messageId L10n message id
|
|
||||||
*/
|
|
||||||
warnL10n: function(messageId) {
|
|
||||||
this.warn(l10n.get(messageId));
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Adds an error notification to the stack and renders it.
|
|
||||||
*
|
|
||||||
* @return {String} message
|
|
||||||
*/
|
|
||||||
error: function(message) {
|
|
||||||
this.add({ level: "error", message: message });
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Adds a l10n error notification to the stack and renders it.
|
|
||||||
*
|
|
||||||
* @param {String} messageId L10n message id
|
|
||||||
* @param {Object} [l10nProps] An object with variables to be interpolated
|
|
||||||
* into the translation. All members' values must be
|
|
||||||
* strings or numbers.
|
|
||||||
*/
|
|
||||||
errorL10n: function(messageId, l10nProps) {
|
|
||||||
this.error(l10n.get(messageId, l10nProps));
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Adds a success notification to the stack and renders it.
|
|
||||||
*
|
|
||||||
* @return {String} message
|
|
||||||
*/
|
|
||||||
success: function(message) {
|
|
||||||
this.add({ level: "success", message: message });
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Adds a l10n success notification to the stack and renders it.
|
|
||||||
*
|
|
||||||
* @param {String} messageId L10n message id
|
|
||||||
* @param {Object} [l10nProps] An object with variables to be interpolated
|
|
||||||
* into the translation. All members' values must be
|
|
||||||
* strings or numbers.
|
|
||||||
*/
|
|
||||||
successL10n: function(messageId, l10nProps) {
|
|
||||||
this.success(l10n.get(messageId, l10nProps));
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
|
|
||||||
return {
|
return {
|
||||||
NotificationCollection: NotificationCollection,
|
NotificationCollection: NotificationCollection,
|
||||||
NotificationModel: NotificationModel
|
NotificationModel: NotificationModel
|
||||||
};
|
};
|
||||||
})(navigator.mozL10n || document.mozL10n);
|
})();
|
||||||
|
@ -5,6 +5,9 @@
|
|||||||
var loop = loop || {};
|
var loop = loop || {};
|
||||||
loop.store = loop.store || {};
|
loop.store = loop.store || {};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Manages the different cursors' events being exchanged between the parts.
|
||||||
|
*/
|
||||||
loop.store.RemoteCursorStore = (function() {
|
loop.store.RemoteCursorStore = (function() {
|
||||||
"use strict";
|
"use strict";
|
||||||
|
|
||||||
@ -15,6 +18,7 @@ loop.store.RemoteCursorStore = (function() {
|
|||||||
*/
|
*/
|
||||||
var RemoteCursorStore = loop.store.createStore({
|
var RemoteCursorStore = loop.store.createStore({
|
||||||
actions: [
|
actions: [
|
||||||
|
"sendCursorData",
|
||||||
"receivedCursorData",
|
"receivedCursorData",
|
||||||
"videoDimensionsChanged",
|
"videoDimensionsChanged",
|
||||||
"videoScreenStreamChanged"
|
"videoScreenStreamChanged"
|
||||||
@ -36,7 +40,9 @@ loop.store.RemoteCursorStore = (function() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
this._sdkDriver = options.sdkDriver;
|
this._sdkDriver = options.sdkDriver;
|
||||||
loop.subscribe("CursorPositionChange", this._cursorPositionChangeListener.bind(this));
|
|
||||||
|
loop.subscribe("CursorPositionChange",
|
||||||
|
this._cursorPositionChangeListener.bind(this));
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -52,13 +58,13 @@ loop.store.RemoteCursorStore = (function() {
|
|||||||
/**
|
/**
|
||||||
* Sends cursor position through the sdk.
|
* Sends cursor position through the sdk.
|
||||||
*
|
*
|
||||||
* @param {Object} event An object containing the cursor position and stream dimensions
|
* @param {Object} event An object containing the cursor position and
|
||||||
* It should contains:
|
* stream dimensions. It should contain:
|
||||||
* - ratioX: Left position. Number between 0 and 1.
|
* - ratioX: Left position. Number between 0 and 1.
|
||||||
* - ratioY: Top position. Number between 0 and 1.
|
* - ratioY: Top position. Number between 0 and 1.
|
||||||
*/
|
*/
|
||||||
_cursorPositionChangeListener: function(event) {
|
_cursorPositionChangeListener: function(event) {
|
||||||
this._sdkDriver.sendCursorMessage({
|
this.sendCursorData({
|
||||||
ratioX: event.ratioX,
|
ratioX: event.ratioX,
|
||||||
ratioY: event.ratioY,
|
ratioY: event.ratioY,
|
||||||
type: CURSOR_MESSAGE_TYPES.POSITION
|
type: CURSOR_MESSAGE_TYPES.POSITION
|
||||||
@ -66,14 +72,32 @@ loop.store.RemoteCursorStore = (function() {
|
|||||||
},
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Receives cursor data.
|
* Sends the cursor data to the SDK for broadcasting.
|
||||||
|
*
|
||||||
|
* @param {sharedActions.SendCursorData}
|
||||||
|
* actionData Contains the updated information for the cursor's position
|
||||||
|
* {
|
||||||
|
* ratioX {[0-1]} Cursor's position on the X axis
|
||||||
|
* ratioY {[0-1]} Cursor's position on the Y axis
|
||||||
|
* type {String} Type of the data being sent
|
||||||
|
* }
|
||||||
|
*/
|
||||||
|
sendCursorData: function(actionData) {
|
||||||
|
switch (actionData.type) {
|
||||||
|
case CURSOR_MESSAGE_TYPES.POSITION:
|
||||||
|
this._sdkDriver.sendCursorMessage(actionData);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Receives cursor data and updates the store.
|
||||||
*
|
*
|
||||||
* @param {sharedActions.receivedCursorData} actionData
|
* @param {sharedActions.receivedCursorData} actionData
|
||||||
*/
|
*/
|
||||||
receivedCursorData: function(actionData) {
|
receivedCursorData: function(actionData) {
|
||||||
switch (actionData.type) {
|
switch (actionData.type) {
|
||||||
case CURSOR_MESSAGE_TYPES.POSITION:
|
case CURSOR_MESSAGE_TYPES.POSITION:
|
||||||
// TODO: handle cursor position if it's desktop instead of standalone
|
|
||||||
this.setStoreState({
|
this.setStoreState({
|
||||||
remoteCursorPosition: {
|
remoteCursorPosition: {
|
||||||
ratioX: actionData.ratioX,
|
ratioX: actionData.ratioX,
|
||||||
@ -103,8 +127,10 @@ loop.store.RemoteCursorStore = (function() {
|
|||||||
},
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Listen to screen stream changes.
|
* Listen to screen stream changes. Because the cursor's position is likely
|
||||||
*
|
* to be different respect to the new screen size, it's better to delete the
|
||||||
|
* previous position and keep waiting for the next one.
|
||||||
|
|
||||||
* @param {sharedActions.VideoScreenStreamChanged} actionData
|
* @param {sharedActions.VideoScreenStreamChanged} actionData
|
||||||
*/
|
*/
|
||||||
videoScreenStreamChanged: function(actionData) {
|
videoScreenStreamChanged: function(actionData) {
|
||||||
|
@ -6,7 +6,19 @@
|
|||||||
|
|
||||||
var loop = loop || {};
|
var loop = loop || {};
|
||||||
loop.shared = loop.shared || {};
|
loop.shared = loop.shared || {};
|
||||||
var inChrome = typeof Components != "undefined" && "utils" in Components;
|
var inChrome = typeof Components != "undefined" &&
|
||||||
|
"utils" in Components;
|
||||||
|
|
||||||
|
// The slideshow is special, and currently loads with chrome privs, but
|
||||||
|
// needs to use this module like the rest of the content does. Once we make
|
||||||
|
// it load remotely, this can go away.
|
||||||
|
if (inChrome) {
|
||||||
|
if (typeof window != "undefined" &&
|
||||||
|
window.location.href === "chrome://loop/content/panels/slideshow.html") {
|
||||||
|
|
||||||
|
inChrome = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
(function() {
|
(function() {
|
||||||
"use strict";
|
"use strict";
|
||||||
@ -191,6 +203,13 @@ var inChrome = typeof Components != "undefined" && "utils" in Components;
|
|||||||
return "blackberry";
|
return "blackberry";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Checks if the platform is Android. Due to the difficulties of detecting an
|
||||||
|
// android device, we need to rely on window.navigator.userAgent instead of
|
||||||
|
// using window.navigator.platform.
|
||||||
|
if (rootNavigator.userAgent.toLowerCase().indexOf("android") > -1) {
|
||||||
|
return "android";
|
||||||
|
}
|
||||||
|
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -47,14 +47,14 @@ loop.shared.views = function (_, mozL10n) {
|
|||||||
* - {String} scope Media scope, can be "local" or "remote".
|
* - {String} scope Media scope, can be "local" or "remote".
|
||||||
* - {String} type Media type, can be "audio" or "video".
|
* - {String} type Media type, can be "audio" or "video".
|
||||||
* - {Function} action Function to be executed on click.
|
* - {Function} action Function to be executed on click.
|
||||||
* - {Enabled} enabled Stream activation status (default: true).
|
* - {Bool} muted Stream activation status (default: false).
|
||||||
*/
|
*/
|
||||||
var MediaControlButton = React.createClass({
|
var MediaControlButton = React.createClass({
|
||||||
displayName: "MediaControlButton",
|
displayName: "MediaControlButton",
|
||||||
|
|
||||||
propTypes: {
|
propTypes: {
|
||||||
action: React.PropTypes.func.isRequired,
|
action: React.PropTypes.func.isRequired,
|
||||||
enabled: React.PropTypes.bool.isRequired,
|
muted: React.PropTypes.bool.isRequired,
|
||||||
scope: React.PropTypes.string.isRequired,
|
scope: React.PropTypes.string.isRequired,
|
||||||
title: React.PropTypes.string,
|
title: React.PropTypes.string,
|
||||||
type: React.PropTypes.string.isRequired,
|
type: React.PropTypes.string.isRequired,
|
||||||
@ -62,7 +62,7 @@ loop.shared.views = function (_, mozL10n) {
|
|||||||
},
|
},
|
||||||
|
|
||||||
getDefaultProps: function () {
|
getDefaultProps: function () {
|
||||||
return { enabled: true, visible: true };
|
return { muted: false, visible: true };
|
||||||
},
|
},
|
||||||
|
|
||||||
handleClick: function () {
|
handleClick: function () {
|
||||||
@ -77,7 +77,7 @@ loop.shared.views = function (_, mozL10n) {
|
|||||||
"media-control": true,
|
"media-control": true,
|
||||||
"transparent-button": true,
|
"transparent-button": true,
|
||||||
"local-media": this.props.scope === "local",
|
"local-media": this.props.scope === "local",
|
||||||
"muted": !this.props.enabled,
|
"muted": this.props.muted,
|
||||||
"hide": !this.props.visible
|
"hide": !this.props.visible
|
||||||
};
|
};
|
||||||
classesObj["btn-mute-" + this.props.type] = true;
|
classesObj["btn-mute-" + this.props.type] = true;
|
||||||
@ -89,7 +89,7 @@ loop.shared.views = function (_, mozL10n) {
|
|||||||
return this.props.title;
|
return this.props.title;
|
||||||
}
|
}
|
||||||
|
|
||||||
var prefix = this.props.enabled ? "mute" : "unmute";
|
var prefix = this.props.muted ? "unmute" : "mute";
|
||||||
var suffix = this.props.type === "video" ? "button_title2" : "button_title";
|
var suffix = this.props.type === "video" ? "button_title2" : "button_title";
|
||||||
var msgId = [prefix, this.props.scope, this.props.type, suffix].join("_");
|
var msgId = [prefix, this.props.scope, this.props.type, suffix].join("_");
|
||||||
return mozL10n.get(msgId);
|
return mozL10n.get(msgId);
|
||||||
@ -126,7 +126,6 @@ loop.shared.views = function (_, mozL10n) {
|
|||||||
audio: React.PropTypes.object.isRequired,
|
audio: React.PropTypes.object.isRequired,
|
||||||
dispatcher: React.PropTypes.instanceOf(loop.Dispatcher).isRequired,
|
dispatcher: React.PropTypes.instanceOf(loop.Dispatcher).isRequired,
|
||||||
hangup: React.PropTypes.func.isRequired,
|
hangup: React.PropTypes.func.isRequired,
|
||||||
publishStream: React.PropTypes.func.isRequired,
|
|
||||||
showHangup: React.PropTypes.bool,
|
showHangup: React.PropTypes.bool,
|
||||||
video: React.PropTypes.object.isRequired
|
video: React.PropTypes.object.isRequired
|
||||||
},
|
},
|
||||||
@ -135,14 +134,6 @@ loop.shared.views = function (_, mozL10n) {
|
|||||||
this.props.hangup();
|
this.props.hangup();
|
||||||
},
|
},
|
||||||
|
|
||||||
handleToggleVideo: function () {
|
|
||||||
this.props.publishStream("video", !this.props.video.enabled);
|
|
||||||
},
|
|
||||||
|
|
||||||
handleToggleAudio: function () {
|
|
||||||
this.props.publishStream("audio", !this.props.audio.enabled);
|
|
||||||
},
|
|
||||||
|
|
||||||
componentDidMount: function () {
|
componentDidMount: function () {
|
||||||
this.userActivity = false;
|
this.userActivity = false;
|
||||||
this.startIdleCountDown();
|
this.startIdleCountDown();
|
||||||
@ -230,20 +221,56 @@ loop.shared.views = function (_, mozL10n) {
|
|||||||
React.createElement(
|
React.createElement(
|
||||||
"div",
|
"div",
|
||||||
{ className: mediaButtonGroupCssClasses },
|
{ className: mediaButtonGroupCssClasses },
|
||||||
React.createElement(MediaControlButton, { action: this.handleToggleVideo,
|
React.createElement(VideoMuteButton, { dispatcher: this.props.dispatcher,
|
||||||
enabled: this.props.video.enabled,
|
muted: !this.props.video.enabled }),
|
||||||
scope: "local", type: "video",
|
React.createElement(AudioMuteButton, { dispatcher: this.props.dispatcher,
|
||||||
visible: this.props.video.visible }),
|
muted: !this.props.audio.enabled })
|
||||||
React.createElement(MediaControlButton, { action: this.handleToggleAudio,
|
|
||||||
enabled: this.props.audio.enabled,
|
|
||||||
scope: "local", type: "audio",
|
|
||||||
visible: this.props.audio.visible })
|
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
var AudioMuteButton = React.createClass({
|
||||||
|
displayName: "AudioMuteButton",
|
||||||
|
|
||||||
|
propTypes: {
|
||||||
|
dispatcher: React.PropTypes.instanceOf(loop.Dispatcher),
|
||||||
|
muted: React.PropTypes.bool.isRequired
|
||||||
|
},
|
||||||
|
|
||||||
|
toggleAudio: function () {
|
||||||
|
this.props.dispatcher.dispatch(new sharedActions.SetMute({ type: "audio", enabled: this.props.muted }));
|
||||||
|
},
|
||||||
|
|
||||||
|
render: function () {
|
||||||
|
return React.createElement(MediaControlButton, { action: this.toggleAudio,
|
||||||
|
muted: this.props.muted,
|
||||||
|
scope: "local",
|
||||||
|
type: "audio" });
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
var VideoMuteButton = React.createClass({
|
||||||
|
displayName: "VideoMuteButton",
|
||||||
|
|
||||||
|
propTypes: {
|
||||||
|
dispatcher: React.PropTypes.instanceOf(loop.Dispatcher),
|
||||||
|
muted: React.PropTypes.bool.isRequired
|
||||||
|
},
|
||||||
|
|
||||||
|
toggleVideo: function () {
|
||||||
|
this.props.dispatcher.dispatch(new sharedActions.SetMute({ type: "video", enabled: this.props.muted }));
|
||||||
|
},
|
||||||
|
|
||||||
|
render: function () {
|
||||||
|
return React.createElement(MediaControlButton, { action: this.toggleVideo,
|
||||||
|
muted: this.props.muted,
|
||||||
|
scope: "local",
|
||||||
|
type: "video" });
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Notification view.
|
* Notification view.
|
||||||
*/
|
*/
|
||||||
@ -633,11 +660,13 @@ loop.shared.views = function (_, mozL10n) {
|
|||||||
mixins: [React.addons.PureRenderMixin],
|
mixins: [React.addons.PureRenderMixin],
|
||||||
|
|
||||||
propTypes: {
|
propTypes: {
|
||||||
|
cursorStore: React.PropTypes.instanceOf(loop.store.RemoteCursorStore),
|
||||||
|
dispatcher: React.PropTypes.object,
|
||||||
displayAvatar: React.PropTypes.bool.isRequired,
|
displayAvatar: React.PropTypes.bool.isRequired,
|
||||||
isLoading: React.PropTypes.bool.isRequired,
|
isLoading: React.PropTypes.bool.isRequired,
|
||||||
mediaType: React.PropTypes.string.isRequired,
|
mediaType: React.PropTypes.string.isRequired,
|
||||||
posterUrl: React.PropTypes.string,
|
posterUrl: React.PropTypes.string,
|
||||||
shouldRenderRemoteCursor: React.PropTypes.bool,
|
shareCursor: React.PropTypes.bool,
|
||||||
// Expecting "local" or "remote".
|
// Expecting "local" or "remote".
|
||||||
srcMediaElement: React.PropTypes.object
|
srcMediaElement: React.PropTypes.object
|
||||||
},
|
},
|
||||||
@ -653,7 +682,7 @@ loop.shared.views = function (_, mozL10n) {
|
|||||||
this.attachVideo(this.props.srcMediaElement);
|
this.attachVideo(this.props.srcMediaElement);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this.props.shouldRenderRemoteCursor) {
|
if (this.props.shareCursor) {
|
||||||
this.handleVideoDimensions();
|
this.handleVideoDimensions();
|
||||||
window.addEventListener("resize", this.handleVideoDimensions);
|
window.addEventListener("resize", this.handleVideoDimensions);
|
||||||
}
|
}
|
||||||
@ -661,12 +690,13 @@ loop.shared.views = function (_, mozL10n) {
|
|||||||
|
|
||||||
componentWillUnmount: function () {
|
componentWillUnmount: function () {
|
||||||
var videoElement = this.getDOMNode().querySelector("video");
|
var videoElement = this.getDOMNode().querySelector("video");
|
||||||
if (!this.props.shouldRenderRemoteCursor || !videoElement) {
|
if (!this.props.shareCursor || !videoElement) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
window.removeEventListener("resize", this.handleVideoDimensions);
|
window.removeEventListener("resize", this.handleVideoDimensions);
|
||||||
videoElement.removeEventListener("loadeddata", this.handleVideoDimensions);
|
videoElement.removeEventListener("loadeddata", this.handleVideoDimensions);
|
||||||
|
videoElement.removeEventListener("mousemove", this.handleMousemove);
|
||||||
},
|
},
|
||||||
|
|
||||||
componentDidUpdate: function () {
|
componentDidUpdate: function () {
|
||||||
@ -689,6 +719,42 @@ loop.shared.views = function (_, mozL10n) {
|
|||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
|
MIN_CURSOR_DELTA: 3,
|
||||||
|
MIN_CURSOR_INTERVAL: 100,
|
||||||
|
lastCursorTime: 0,
|
||||||
|
lastCursorX: -1,
|
||||||
|
lastCursorY: -1,
|
||||||
|
|
||||||
|
handleMouseMove: function (event) {
|
||||||
|
// Only update every so often.
|
||||||
|
var now = Date.now();
|
||||||
|
if (now - this.lastCursorTime < this.MIN_CURSOR_INTERVAL) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this.lastCursorTime = now;
|
||||||
|
|
||||||
|
var storeState = this.props.cursorStore.getStoreState();
|
||||||
|
|
||||||
|
var deltaX = event.clientX - storeState.videoLetterboxing.left;
|
||||||
|
var deltaY = event.clientY - storeState.videoLetterboxing.top;
|
||||||
|
|
||||||
|
// Skip the update if cursor is out of bounds
|
||||||
|
if (deltaX < 0 || deltaX > storeState.streamVideoWidth || deltaY < 0 || deltaY > storeState.streamVideoHeight ||
|
||||||
|
// or the cursor didn't move the minimum.
|
||||||
|
Math.abs(deltaX - this.lastCursorX) < this.MIN_CURSOR_DELTA && Math.abs(deltaY - this.lastCursorY) < this.MIN_CURSOR_DELTA) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.lastCursorX = deltaX;
|
||||||
|
this.lastCursorY = deltaY;
|
||||||
|
|
||||||
|
this.props.dispatcher.dispatch(new sharedActions.SendCursorData({
|
||||||
|
ratioX: deltaX / storeState.streamVideoWidth,
|
||||||
|
ratioY: deltaY / storeState.streamVideoHeight,
|
||||||
|
type: loop.shared.utils.CURSOR_MESSAGE_TYPES.POSITION
|
||||||
|
}));
|
||||||
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Attaches a video stream from a donor video element to this component's
|
* Attaches a video stream from a donor video element to this component's
|
||||||
* video element if the component is displaying one.
|
* video element if the component is displaying one.
|
||||||
@ -706,16 +772,16 @@ loop.shared.views = function (_, mozL10n) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
var videoElement = this.getDOMNode().querySelector("video");
|
var videoElement = this.getDOMNode().querySelector("video");
|
||||||
|
|
||||||
if (this.props.shouldRenderRemoteCursor) {
|
|
||||||
videoElement.addEventListener("loadeddata", this.handleVideoDimensions);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!videoElement || videoElement.tagName.toLowerCase() !== "video") {
|
if (!videoElement || videoElement.tagName.toLowerCase() !== "video") {
|
||||||
// Must be displaying the avatar view, so don't try and attach video.
|
// Must be displaying the avatar view, so don't try and attach video.
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (this.props.shareCursor) {
|
||||||
|
videoElement.addEventListener("loadeddata", this.handleVideoDimensions);
|
||||||
|
videoElement.addEventListener("mousemove", this.handleMouseMove);
|
||||||
|
}
|
||||||
|
|
||||||
// Set the src of our video element
|
// Set the src of our video element
|
||||||
var attrName = "";
|
var attrName = "";
|
||||||
if ("srcObject" in videoElement) {
|
if ("srcObject" in videoElement) {
|
||||||
@ -736,6 +802,7 @@ loop.shared.views = function (_, mozL10n) {
|
|||||||
if (videoElement[attrName] !== srcMediaElement[attrName]) {
|
if (videoElement[attrName] !== srcMediaElement[attrName]) {
|
||||||
videoElement[attrName] = srcMediaElement[attrName];
|
videoElement[attrName] = srcMediaElement[attrName];
|
||||||
}
|
}
|
||||||
|
|
||||||
videoElement.play();
|
videoElement.play();
|
||||||
},
|
},
|
||||||
|
|
||||||
@ -769,7 +836,7 @@ loop.shared.views = function (_, mozL10n) {
|
|||||||
return React.createElement(
|
return React.createElement(
|
||||||
"div",
|
"div",
|
||||||
{ className: "remote-video-box" },
|
{ className: "remote-video-box" },
|
||||||
this.state.videoElementSize && this.props.shouldRenderRemoteCursor ? React.createElement(RemoteCursorView, {
|
this.state.videoElementSize && this.props.shareCursor ? React.createElement(RemoteCursorView, {
|
||||||
videoElementSize: this.state.videoElementSize }) : null,
|
videoElementSize: this.state.videoElementSize }) : null,
|
||||||
React.createElement("video", _extends({}, optionalProps, {
|
React.createElement("video", _extends({}, optionalProps, {
|
||||||
className: this.props.mediaType + "-video",
|
className: this.props.mediaType + "-video",
|
||||||
@ -783,6 +850,7 @@ loop.shared.views = function (_, mozL10n) {
|
|||||||
|
|
||||||
propTypes: {
|
propTypes: {
|
||||||
children: React.PropTypes.node,
|
children: React.PropTypes.node,
|
||||||
|
cursorStore: React.PropTypes.instanceOf(loop.store.RemoteCursorStore).isRequired,
|
||||||
dispatcher: React.PropTypes.instanceOf(loop.Dispatcher).isRequired,
|
dispatcher: React.PropTypes.instanceOf(loop.Dispatcher).isRequired,
|
||||||
displayScreenShare: React.PropTypes.bool.isRequired,
|
displayScreenShare: React.PropTypes.bool.isRequired,
|
||||||
isLocalLoading: React.PropTypes.bool.isRequired,
|
isLocalLoading: React.PropTypes.bool.isRequired,
|
||||||
@ -903,11 +971,13 @@ loop.shared.views = function (_, mozL10n) {
|
|||||||
"div",
|
"div",
|
||||||
{ className: screenShareStreamClasses },
|
{ className: screenShareStreamClasses },
|
||||||
React.createElement(MediaView, {
|
React.createElement(MediaView, {
|
||||||
|
cursorStore: this.props.cursorStore,
|
||||||
|
dispatcher: this.props.dispatcher,
|
||||||
displayAvatar: false,
|
displayAvatar: false,
|
||||||
isLoading: this.props.isScreenShareLoading,
|
isLoading: this.props.isScreenShareLoading,
|
||||||
mediaType: "screen-share",
|
mediaType: "screen-share",
|
||||||
posterUrl: this.props.screenSharePosterUrl,
|
posterUrl: this.props.screenSharePosterUrl,
|
||||||
shouldRenderRemoteCursor: true,
|
shareCursor: true,
|
||||||
srcMediaElement: this.props.screenShareMediaElement }),
|
srcMediaElement: this.props.screenShareMediaElement }),
|
||||||
this.props.displayScreenShare ? this.props.children : null
|
this.props.displayScreenShare ? this.props.children : null
|
||||||
),
|
),
|
||||||
@ -931,10 +1001,7 @@ loop.shared.views = function (_, mozL10n) {
|
|||||||
},
|
},
|
||||||
|
|
||||||
getInitialState: function () {
|
getInitialState: function () {
|
||||||
return {
|
return this.getStoreState();
|
||||||
realVideoSize: null,
|
|
||||||
videoLetterboxing: null
|
|
||||||
};
|
|
||||||
},
|
},
|
||||||
|
|
||||||
componentWillMount: function () {
|
componentWillMount: function () {
|
||||||
@ -963,7 +1030,7 @@ loop.shared.views = function (_, mozL10n) {
|
|||||||
if (!this.state.videoLetterboxing) {
|
if (!this.state.videoLetterboxing) {
|
||||||
// If this is the first time we receive the event, we must calculate the
|
// If this is the first time we receive the event, we must calculate the
|
||||||
// video letterboxing.
|
// video letterboxing.
|
||||||
this._calculateVideoLetterboxing();
|
this._calculateVideoLetterboxing(nextState.realVideoSize);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -992,7 +1059,7 @@ loop.shared.views = function (_, mozL10n) {
|
|||||||
var streamVideoHeight = isWider ? clientHeight : clientWidth / realVideoRatio;
|
var streamVideoHeight = isWider ? clientHeight : clientWidth / realVideoRatio;
|
||||||
var streamVideoWidth = isWider ? clientHeight * realVideoRatio : clientWidth;
|
var streamVideoWidth = isWider ? clientHeight * realVideoRatio : clientWidth;
|
||||||
|
|
||||||
this.setState({
|
this.getStore().setStoreState({
|
||||||
videoLetterboxing: {
|
videoLetterboxing: {
|
||||||
left: (clientWidth - streamVideoWidth) / 2,
|
left: (clientWidth - streamVideoWidth) / 2,
|
||||||
top: (clientHeight - streamVideoHeight) / 2
|
top: (clientHeight - streamVideoHeight) / 2
|
||||||
@ -1028,17 +1095,20 @@ loop.shared.views = function (_, mozL10n) {
|
|||||||
});
|
});
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
AudioMuteButton: AudioMuteButton,
|
||||||
AvatarView: AvatarView,
|
AvatarView: AvatarView,
|
||||||
Button: Button,
|
Button: Button,
|
||||||
ButtonGroup: ButtonGroup,
|
ButtonGroup: ButtonGroup,
|
||||||
Checkbox: Checkbox,
|
Checkbox: Checkbox,
|
||||||
ContextUrlView: ContextUrlView,
|
ContextUrlView: ContextUrlView,
|
||||||
ConversationToolbar: ConversationToolbar,
|
ConversationToolbar: ConversationToolbar,
|
||||||
|
HangUpControlButton: HangUpControlButton,
|
||||||
MediaControlButton: MediaControlButton,
|
MediaControlButton: MediaControlButton,
|
||||||
MediaLayoutView: MediaLayoutView,
|
MediaLayoutView: MediaLayoutView,
|
||||||
MediaView: MediaView,
|
MediaView: MediaView,
|
||||||
LoadingView: LoadingView,
|
LoadingView: LoadingView,
|
||||||
NotificationListView: NotificationListView,
|
NotificationListView: NotificationListView,
|
||||||
RemoteCursorView: RemoteCursorView
|
RemoteCursorView: RemoteCursorView,
|
||||||
|
VideoMuteButton: VideoMuteButton
|
||||||
};
|
};
|
||||||
}(_, navigator.mozL10n || document.mozL10n);
|
}(_, navigator.mozL10n || document.mozL10n);
|
||||||
|
@ -68,6 +68,10 @@ describe("loop.store.ActiveRoomStore", function() {
|
|||||||
store = new loop.store.ActiveRoomStore(dispatcher, {
|
store = new loop.store.ActiveRoomStore(dispatcher, {
|
||||||
sdkDriver: fakeSdkDriver
|
sdkDriver: fakeSdkDriver
|
||||||
});
|
});
|
||||||
|
|
||||||
|
sandbox.stub(document.mozL10n ? document.mozL10n : navigator.mozL10n, "get", function(x) {
|
||||||
|
return x;
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
afterEach(function() {
|
afterEach(function() {
|
||||||
@ -1003,7 +1007,7 @@ describe("loop.store.ActiveRoomStore", function() {
|
|||||||
store.gotMediaPermission();
|
store.gotMediaPermission();
|
||||||
|
|
||||||
sinon.assert.calledOnce(requestStubs["Rooms:Join"]);
|
sinon.assert.calledOnce(requestStubs["Rooms:Join"]);
|
||||||
sinon.assert.calledWith(requestStubs["Rooms:Join"], "tokenFake");
|
sinon.assert.calledWith(requestStubs["Rooms:Join"], "tokenFake", "display_name_guest");
|
||||||
});
|
});
|
||||||
|
|
||||||
it("should dispatch `JoinedRoom` on success", function() {
|
it("should dispatch `JoinedRoom` on success", function() {
|
||||||
|
@ -43,6 +43,7 @@
|
|||||||
<!-- App scripts -->
|
<!-- App scripts -->
|
||||||
<script src="/shared/js/loopapi-client.js"></script>
|
<script src="/shared/js/loopapi-client.js"></script>
|
||||||
<script src="/shared/js/utils.js"></script>
|
<script src="/shared/js/utils.js"></script>
|
||||||
|
<script src="/shared/js/urlRegExps.js"></script>
|
||||||
<script src="/shared/js/models.js"></script>
|
<script src="/shared/js/models.js"></script>
|
||||||
<script src="/shared/js/mixins.js"></script>
|
<script src="/shared/js/mixins.js"></script>
|
||||||
<script src="/shared/js/crypto.js"></script>
|
<script src="/shared/js/crypto.js"></script>
|
||||||
@ -50,31 +51,33 @@
|
|||||||
<script src="/shared/js/actions.js"></script>
|
<script src="/shared/js/actions.js"></script>
|
||||||
<script src="/shared/js/dispatcher.js"></script>
|
<script src="/shared/js/dispatcher.js"></script>
|
||||||
<script src="/shared/js/otSdkDriver.js"></script>
|
<script src="/shared/js/otSdkDriver.js"></script>
|
||||||
|
|
||||||
|
<!-- Stores need to be loaded before the views that uses them -->
|
||||||
<script src="/shared/js/store.js"></script>
|
<script src="/shared/js/store.js"></script>
|
||||||
<script src="/shared/js/activeRoomStore.js"></script>
|
<script src="/shared/js/activeRoomStore.js"></script>
|
||||||
<script src="/shared/js/views.js"></script>
|
|
||||||
<script src="/shared/js/textChatStore.js"></script>
|
<script src="/shared/js/textChatStore.js"></script>
|
||||||
<script src="/shared/js/textChatView.js"></script>
|
|
||||||
<script src="/shared/js/urlRegExps.js"></script>
|
|
||||||
<script src="/shared/js/linkifiedTextView.js"></script>
|
|
||||||
<script src="/shared/js/remoteCursorStore.js"></script>
|
<script src="/shared/js/remoteCursorStore.js"></script>
|
||||||
|
|
||||||
|
<!-- Views -->
|
||||||
|
<script src="/shared/js/views.js"></script>
|
||||||
|
<script src="/shared/js/textChatView.js"></script>
|
||||||
|
<script src="/shared/js/linkifiedTextView.js"></script>
|
||||||
|
|
||||||
<!-- Test scripts -->
|
<!-- Test scripts -->
|
||||||
<script src="models_test.js"></script>
|
|
||||||
<script src="mixins_test.js"></script>
|
<script src="mixins_test.js"></script>
|
||||||
<script src="utils_test.js"></script>
|
<script src="utils_test.js"></script>
|
||||||
<script src="crypto_test.js"></script>
|
<script src="crypto_test.js"></script>
|
||||||
<script src="views_test.js"></script>
|
|
||||||
<script src="validate_test.js"></script>
|
<script src="validate_test.js"></script>
|
||||||
<script src="dispatcher_test.js"></script>
|
<script src="dispatcher_test.js"></script>
|
||||||
<script src="activeRoomStore_test.js"></script>
|
|
||||||
<script src="otSdkDriver_test.js"></script>
|
<script src="otSdkDriver_test.js"></script>
|
||||||
<script src="store_test.js"></script>
|
<script src="store_test.js"></script>
|
||||||
|
<script src="activeRoomStore_test.js"></script>
|
||||||
<script src="textChatStore_test.js"></script>
|
<script src="textChatStore_test.js"></script>
|
||||||
|
<script src="remoteCursorStore_test.js"></script>
|
||||||
|
<script src="views_test.js"></script>
|
||||||
<script src="textChatView_test.js"></script>
|
<script src="textChatView_test.js"></script>
|
||||||
<script src="linkifiedTextView_test.js"></script>
|
<script src="linkifiedTextView_test.js"></script>
|
||||||
<script src="loopapi-client_test.js"></script>
|
<script src="loopapi-client_test.js"></script>
|
||||||
<script src="remoteCursorStore_test.js"></script>
|
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
LoopMochaUtils.addErrorCheckingTests();
|
LoopMochaUtils.addErrorCheckingTests();
|
||||||
|
@ -1,80 +0,0 @@
|
|||||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
|
||||||
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
|
|
||||||
* You can obtain one at http://mozilla.org/MPL/2.0/. */
|
|
||||||
|
|
||||||
describe("loop.shared.models", function() {
|
|
||||||
"use strict";
|
|
||||||
|
|
||||||
var expect = chai.expect;
|
|
||||||
var l10n = navigator.mozL10n || document.mozL10n;
|
|
||||||
var sharedModels = loop.shared.models;
|
|
||||||
var sandbox;
|
|
||||||
|
|
||||||
beforeEach(function() {
|
|
||||||
sandbox = sinon.sandbox.create();
|
|
||||||
});
|
|
||||||
|
|
||||||
afterEach(function() {
|
|
||||||
sandbox.restore();
|
|
||||||
});
|
|
||||||
|
|
||||||
describe("NotificationCollection", function() {
|
|
||||||
var collection;
|
|
||||||
|
|
||||||
beforeEach(function() {
|
|
||||||
collection = new sharedModels.NotificationCollection();
|
|
||||||
sandbox.stub(l10n, "get", function(x, y) {
|
|
||||||
return "translated:" + x + (y ? ":" + y : "");
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe("#warn", function() {
|
|
||||||
it("should add a warning notification to the stack", function() {
|
|
||||||
collection.warn("watch out");
|
|
||||||
|
|
||||||
expect(collection).to.have.length.of(1);
|
|
||||||
expect(collection.at(0).get("level")).eql("warning");
|
|
||||||
expect(collection.at(0).get("message")).eql("watch out");
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe("#warnL10n", function() {
|
|
||||||
it("should warn using a l10n string id", function() {
|
|
||||||
collection.warnL10n("fakeId");
|
|
||||||
|
|
||||||
expect(collection).to.have.length.of(1);
|
|
||||||
expect(collection.at(0).get("level")).eql("warning");
|
|
||||||
expect(collection.at(0).get("message")).eql("translated:fakeId");
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe("#error", function() {
|
|
||||||
it("should add an error notification to the stack", function() {
|
|
||||||
collection.error("wrong");
|
|
||||||
|
|
||||||
expect(collection).to.have.length.of(1);
|
|
||||||
expect(collection.at(0).get("level")).eql("error");
|
|
||||||
expect(collection.at(0).get("message")).eql("wrong");
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe("#errorL10n", function() {
|
|
||||||
it("should notify an error using a l10n string id", function() {
|
|
||||||
collection.errorL10n("fakeId");
|
|
||||||
|
|
||||||
expect(collection).to.have.length.of(1);
|
|
||||||
expect(collection.at(0).get("level")).eql("error");
|
|
||||||
expect(collection.at(0).get("message")).eql("translated:fakeId");
|
|
||||||
});
|
|
||||||
|
|
||||||
it("should notify an error using a l10n string id + l10n properties",
|
|
||||||
function() {
|
|
||||||
collection.errorL10n("fakeId", "fakeProp");
|
|
||||||
|
|
||||||
expect(collection).to.have.length.of(1);
|
|
||||||
expect(collection.at(0).get("level")).eql("error");
|
|
||||||
expect(collection.at(0).get("message")).eql("translated:fakeId:fakeProp");
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
@ -66,7 +66,52 @@ describe("loop.store.RemoteCursorStore", function() {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe("#sendCursorData", function() {
|
||||||
|
it("should do nothing if not a proper event", function() {
|
||||||
|
var fakeData = {
|
||||||
|
ratioX: 10,
|
||||||
|
ratioY: 10,
|
||||||
|
type: "not-a-position-event"
|
||||||
|
};
|
||||||
|
|
||||||
|
store.sendCursorData(new sharedActions.SendCursorData(fakeData));
|
||||||
|
|
||||||
|
sinon.assert.notCalled(fakeSdkDriver.sendCursorMessage);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should send cursor data through the sdk", function() {
|
||||||
|
var fakeData = {
|
||||||
|
ratioX: 10,
|
||||||
|
ratioY: 10,
|
||||||
|
type: CURSOR_MESSAGE_TYPES.POSITION
|
||||||
|
};
|
||||||
|
|
||||||
|
store.sendCursorData(new sharedActions.SendCursorData(fakeData));
|
||||||
|
|
||||||
|
sinon.assert.calledOnce(fakeSdkDriver.sendCursorMessage);
|
||||||
|
sinon.assert.calledWith(fakeSdkDriver.sendCursorMessage, {
|
||||||
|
name: "sendCursorData",
|
||||||
|
type: fakeData.type,
|
||||||
|
ratioX: fakeData.ratioX,
|
||||||
|
ratioY: fakeData.ratioY
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
describe("#receivedCursorData", function() {
|
describe("#receivedCursorData", function() {
|
||||||
|
|
||||||
|
it("should do nothing if not a proper event", function() {
|
||||||
|
sandbox.stub(store, "setStoreState");
|
||||||
|
|
||||||
|
store.receivedCursorData(new sharedActions.ReceivedCursorData({
|
||||||
|
ratioX: 10,
|
||||||
|
ratioY: 10,
|
||||||
|
type: "not-a-position-event"
|
||||||
|
}));
|
||||||
|
|
||||||
|
sinon.assert.notCalled(store.setStoreState);
|
||||||
|
});
|
||||||
|
|
||||||
it("should save the state", function() {
|
it("should save the state", function() {
|
||||||
store.receivedCursorData(new sharedActions.ReceivedCursorData({
|
store.receivedCursorData(new sharedActions.ReceivedCursorData({
|
||||||
type: CURSOR_MESSAGE_TYPES.POSITION,
|
type: CURSOR_MESSAGE_TYPES.POSITION,
|
||||||
|
@ -50,7 +50,7 @@ describe("loop.shared.views", function() {
|
|||||||
scope: "local",
|
scope: "local",
|
||||||
type: "audio",
|
type: "audio",
|
||||||
action: function() {},
|
action: function() {},
|
||||||
enabled: true
|
muted: false
|
||||||
}));
|
}));
|
||||||
|
|
||||||
expect(comp.getDOMNode().classList.contains("muted")).eql(false);
|
expect(comp.getDOMNode().classList.contains("muted")).eql(false);
|
||||||
@ -62,7 +62,7 @@ describe("loop.shared.views", function() {
|
|||||||
scope: "local",
|
scope: "local",
|
||||||
type: "audio",
|
type: "audio",
|
||||||
action: function() {},
|
action: function() {},
|
||||||
enabled: false
|
muted: true
|
||||||
}));
|
}));
|
||||||
|
|
||||||
expect(comp.getDOMNode().classList.contains("muted")).eql(true);
|
expect(comp.getDOMNode().classList.contains("muted")).eql(true);
|
||||||
@ -74,7 +74,7 @@ describe("loop.shared.views", function() {
|
|||||||
scope: "local",
|
scope: "local",
|
||||||
type: "video",
|
type: "video",
|
||||||
action: function() {},
|
action: function() {},
|
||||||
enabled: true
|
muted: false
|
||||||
}));
|
}));
|
||||||
|
|
||||||
expect(comp.getDOMNode().classList.contains("muted")).eql(false);
|
expect(comp.getDOMNode().classList.contains("muted")).eql(false);
|
||||||
@ -86,15 +86,123 @@ describe("loop.shared.views", function() {
|
|||||||
scope: "local",
|
scope: "local",
|
||||||
type: "video",
|
type: "video",
|
||||||
action: function() {},
|
action: function() {},
|
||||||
enabled: false
|
muted: true
|
||||||
}));
|
}));
|
||||||
|
|
||||||
expect(comp.getDOMNode().classList.contains("muted")).eql(true);
|
expect(comp.getDOMNode().classList.contains("muted")).eql(true);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe("AudioMuteButton", function() {
|
||||||
|
it("should set the muted class when not enabled", function() {
|
||||||
|
var comp = TestUtils.renderIntoDocument(
|
||||||
|
React.createElement(sharedViews.AudioMuteButton, {
|
||||||
|
muted: true
|
||||||
|
}));
|
||||||
|
|
||||||
|
var node = comp.getDOMNode();
|
||||||
|
expect(node.classList.contains("muted")).eql(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should not set the muted class when enabled", function() {
|
||||||
|
var comp = TestUtils.renderIntoDocument(
|
||||||
|
React.createElement(sharedViews.AudioMuteButton, {
|
||||||
|
muted: false
|
||||||
|
}));
|
||||||
|
|
||||||
|
var node = comp.getDOMNode();
|
||||||
|
expect(node.classList.contains("muted")).eql(false);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should dispatch SetMute('audio', false) if clicked when audio is disabled",
|
||||||
|
function() {
|
||||||
|
var comp = TestUtils.renderIntoDocument(
|
||||||
|
React.createElement(sharedViews.AudioMuteButton, {
|
||||||
|
dispatcher: dispatcher,
|
||||||
|
muted: false
|
||||||
|
}));
|
||||||
|
|
||||||
|
TestUtils.Simulate.click(comp.getDOMNode());
|
||||||
|
|
||||||
|
sinon.assert.calledOnce(dispatcher.dispatch);
|
||||||
|
sinon.assert.calledWithExactly(dispatcher.dispatch,
|
||||||
|
new sharedActions.SetMute({ type: "audio", enabled: false })
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should dispatch SetMute('audio', true) if clicked when audio is enabled",
|
||||||
|
function() {
|
||||||
|
var comp = TestUtils.renderIntoDocument(
|
||||||
|
React.createElement(sharedViews.AudioMuteButton, {
|
||||||
|
dispatcher: dispatcher,
|
||||||
|
muted: true
|
||||||
|
}));
|
||||||
|
|
||||||
|
TestUtils.Simulate.click(comp.getDOMNode());
|
||||||
|
|
||||||
|
sinon.assert.calledOnce(dispatcher.dispatch);
|
||||||
|
sinon.assert.calledWithExactly(dispatcher.dispatch,
|
||||||
|
new sharedActions.SetMute({ type: "audio", enabled: true })
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("VideoMuteButton", function() {
|
||||||
|
it("should set the muted class when not enabled", function() {
|
||||||
|
var comp = TestUtils.renderIntoDocument(
|
||||||
|
React.createElement(sharedViews.VideoMuteButton, {
|
||||||
|
muted: true
|
||||||
|
}));
|
||||||
|
|
||||||
|
var node = comp.getDOMNode();
|
||||||
|
expect(node.classList.contains("muted")).eql(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should not set the muted class when enabled", function() {
|
||||||
|
var comp = TestUtils.renderIntoDocument(
|
||||||
|
React.createElement(sharedViews.VideoMuteButton, {
|
||||||
|
muted: false
|
||||||
|
}));
|
||||||
|
|
||||||
|
var node = comp.getDOMNode();
|
||||||
|
expect(node.classList.contains("muted")).eql(false);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should dispatch SetMute('audio', false) if clicked when audio is disabled",
|
||||||
|
function() {
|
||||||
|
var comp = TestUtils.renderIntoDocument(
|
||||||
|
React.createElement(sharedViews.VideoMuteButton, {
|
||||||
|
dispatcher: dispatcher,
|
||||||
|
muted: false
|
||||||
|
}));
|
||||||
|
|
||||||
|
TestUtils.Simulate.click(comp.getDOMNode());
|
||||||
|
|
||||||
|
sinon.assert.calledOnce(dispatcher.dispatch);
|
||||||
|
sinon.assert.calledWithExactly(dispatcher.dispatch,
|
||||||
|
new sharedActions.SetMute({ type: "video", enabled: false })
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should dispatch SetMute('audio', true) if clicked when audio is enabled",
|
||||||
|
function() {
|
||||||
|
var comp = TestUtils.renderIntoDocument(
|
||||||
|
React.createElement(sharedViews.VideoMuteButton, {
|
||||||
|
dispatcher: dispatcher,
|
||||||
|
muted: true
|
||||||
|
}));
|
||||||
|
|
||||||
|
TestUtils.Simulate.click(comp.getDOMNode());
|
||||||
|
|
||||||
|
sinon.assert.calledOnce(dispatcher.dispatch);
|
||||||
|
sinon.assert.calledWithExactly(dispatcher.dispatch,
|
||||||
|
new sharedActions.SetMute({ type: "video", enabled: true })
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
describe("ConversationToolbar", function() {
|
describe("ConversationToolbar", function() {
|
||||||
var hangup, publishStream;
|
var hangup;
|
||||||
|
|
||||||
function mountTestComponent(props) {
|
function mountTestComponent(props) {
|
||||||
props = _.extend({
|
props = _.extend({
|
||||||
@ -106,14 +214,12 @@ describe("loop.shared.views", function() {
|
|||||||
|
|
||||||
beforeEach(function() {
|
beforeEach(function() {
|
||||||
hangup = sandbox.stub();
|
hangup = sandbox.stub();
|
||||||
publishStream = sandbox.stub();
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it("should start no idle", function() {
|
it("should start no idle", function() {
|
||||||
var comp = mountTestComponent({
|
var comp = mountTestComponent({
|
||||||
hangupButtonLabel: "foo",
|
hangupButtonLabel: "foo",
|
||||||
hangup: hangup,
|
hangup: hangup
|
||||||
publishStream: publishStream
|
|
||||||
});
|
});
|
||||||
expect(comp.getDOMNode().classList.contains("idle")).eql(false);
|
expect(comp.getDOMNode().classList.contains("idle")).eql(false);
|
||||||
});
|
});
|
||||||
@ -121,8 +227,7 @@ describe("loop.shared.views", function() {
|
|||||||
it("should be on idle state after 6 seconds", function() {
|
it("should be on idle state after 6 seconds", function() {
|
||||||
var comp = mountTestComponent({
|
var comp = mountTestComponent({
|
||||||
hangupButtonLabel: "foo",
|
hangupButtonLabel: "foo",
|
||||||
hangup: hangup,
|
hangup: hangup
|
||||||
publishStream: publishStream
|
|
||||||
});
|
});
|
||||||
expect(comp.getDOMNode().classList.contains("idle")).eql(false);
|
expect(comp.getDOMNode().classList.contains("idle")).eql(false);
|
||||||
|
|
||||||
@ -133,8 +238,7 @@ describe("loop.shared.views", function() {
|
|||||||
it("should remove idle state when the user moves the mouse", function() {
|
it("should remove idle state when the user moves the mouse", function() {
|
||||||
var comp = mountTestComponent({
|
var comp = mountTestComponent({
|
||||||
hangupButtonLabel: "foo",
|
hangupButtonLabel: "foo",
|
||||||
hangup: hangup,
|
hangup: hangup
|
||||||
publishStream: publishStream
|
|
||||||
});
|
});
|
||||||
|
|
||||||
clock.tick(6001);
|
clock.tick(6001);
|
||||||
@ -148,8 +252,7 @@ describe("loop.shared.views", function() {
|
|||||||
it("should accept a showHangup optional prop", function() {
|
it("should accept a showHangup optional prop", function() {
|
||||||
var comp = mountTestComponent({
|
var comp = mountTestComponent({
|
||||||
showHangup: false,
|
showHangup: false,
|
||||||
hangup: hangup,
|
hangup: hangup
|
||||||
publishStream: publishStream
|
|
||||||
});
|
});
|
||||||
|
|
||||||
expect(comp.getDOMNode().querySelector(".btn-hangup-entry")).to.eql(null);
|
expect(comp.getDOMNode().querySelector(".btn-hangup-entry")).to.eql(null);
|
||||||
@ -158,7 +261,6 @@ describe("loop.shared.views", function() {
|
|||||||
it("should hangup when hangup button is clicked", function() {
|
it("should hangup when hangup button is clicked", function() {
|
||||||
var comp = mountTestComponent({
|
var comp = mountTestComponent({
|
||||||
hangup: hangup,
|
hangup: hangup,
|
||||||
publishStream: publishStream,
|
|
||||||
audio: { enabled: true }
|
audio: { enabled: true }
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -168,62 +270,6 @@ describe("loop.shared.views", function() {
|
|||||||
sinon.assert.calledOnce(hangup);
|
sinon.assert.calledOnce(hangup);
|
||||||
sinon.assert.calledWithExactly(hangup);
|
sinon.assert.calledWithExactly(hangup);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("should unpublish audio when audio mute btn is clicked", function() {
|
|
||||||
var comp = mountTestComponent({
|
|
||||||
hangup: hangup,
|
|
||||||
publishStream: publishStream,
|
|
||||||
audio: { enabled: true }
|
|
||||||
});
|
|
||||||
|
|
||||||
TestUtils.Simulate.click(
|
|
||||||
comp.getDOMNode().querySelector(".btn-mute-audio"));
|
|
||||||
|
|
||||||
sinon.assert.calledOnce(publishStream);
|
|
||||||
sinon.assert.calledWithExactly(publishStream, "audio", false);
|
|
||||||
});
|
|
||||||
|
|
||||||
it("should publish audio when audio mute btn is clicked", function() {
|
|
||||||
var comp = mountTestComponent({
|
|
||||||
hangup: hangup,
|
|
||||||
publishStream: publishStream,
|
|
||||||
audio: { enabled: false }
|
|
||||||
});
|
|
||||||
|
|
||||||
TestUtils.Simulate.click(
|
|
||||||
comp.getDOMNode().querySelector(".btn-mute-audio"));
|
|
||||||
|
|
||||||
sinon.assert.calledOnce(publishStream);
|
|
||||||
sinon.assert.calledWithExactly(publishStream, "audio", true);
|
|
||||||
});
|
|
||||||
|
|
||||||
it("should unpublish video when video mute btn is clicked", function() {
|
|
||||||
var comp = mountTestComponent({
|
|
||||||
hangup: hangup,
|
|
||||||
publishStream: publishStream,
|
|
||||||
video: { enabled: true }
|
|
||||||
});
|
|
||||||
|
|
||||||
TestUtils.Simulate.click(
|
|
||||||
comp.getDOMNode().querySelector(".btn-mute-video"));
|
|
||||||
|
|
||||||
sinon.assert.calledOnce(publishStream);
|
|
||||||
sinon.assert.calledWithExactly(publishStream, "video", false);
|
|
||||||
});
|
|
||||||
|
|
||||||
it("should publish video when video mute btn is clicked", function() {
|
|
||||||
var comp = mountTestComponent({
|
|
||||||
hangup: hangup,
|
|
||||||
publishStream: publishStream,
|
|
||||||
video: { enabled: false }
|
|
||||||
});
|
|
||||||
|
|
||||||
TestUtils.Simulate.click(
|
|
||||||
comp.getDOMNode().querySelector(".btn-mute-video"));
|
|
||||||
|
|
||||||
sinon.assert.calledOnce(publishStream);
|
|
||||||
sinon.assert.calledWithExactly(publishStream, "video", true);
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
|
|
||||||
describe("NotificationListView", function() {
|
describe("NotificationListView", function() {
|
||||||
@ -518,6 +564,7 @@ describe("loop.shared.views", function() {
|
|||||||
|
|
||||||
describe("MediaView", function() {
|
describe("MediaView", function() {
|
||||||
var view;
|
var view;
|
||||||
|
var remoteCursorStore;
|
||||||
|
|
||||||
function mountTestComponent(props) {
|
function mountTestComponent(props) {
|
||||||
props = _.extend({
|
props = _.extend({
|
||||||
@ -527,6 +574,13 @@ describe("loop.shared.views", function() {
|
|||||||
React.createElement(sharedViews.MediaView, props));
|
React.createElement(sharedViews.MediaView, props));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
beforeEach(function() {
|
||||||
|
remoteCursorStore = new loop.store.RemoteCursorStore(dispatcher, {
|
||||||
|
sdkDriver: {}
|
||||||
|
});
|
||||||
|
loop.store.StoreMixin.register({ remoteCursorStore: remoteCursorStore });
|
||||||
|
});
|
||||||
|
|
||||||
it("should display an avatar view", function() {
|
it("should display an avatar view", function() {
|
||||||
view = mountTestComponent({
|
view = mountTestComponent({
|
||||||
displayAvatar: true,
|
displayAvatar: true,
|
||||||
@ -580,13 +634,14 @@ describe("loop.shared.views", function() {
|
|||||||
// We test this function by itself, as otherwise we'd be into creating fake
|
// We test this function by itself, as otherwise we'd be into creating fake
|
||||||
// streams etc.
|
// streams etc.
|
||||||
describe("#attachVideo", function() {
|
describe("#attachVideo", function() {
|
||||||
var fakeViewElement, fakeVideoElement;
|
var fakeViewElement,
|
||||||
|
fakeVideoElement;
|
||||||
|
|
||||||
beforeEach(function() {
|
beforeEach(function() {
|
||||||
fakeVideoElement = {
|
fakeVideoElement = {
|
||||||
play: sinon.stub(),
|
play: sinon.stub(),
|
||||||
tagName: "VIDEO",
|
tagName: "VIDEO",
|
||||||
addEventListener: function() {}
|
addEventListener: sinon.stub()
|
||||||
};
|
};
|
||||||
|
|
||||||
fakeViewElement = {
|
fakeViewElement = {
|
||||||
@ -597,8 +652,10 @@ describe("loop.shared.views", function() {
|
|||||||
};
|
};
|
||||||
|
|
||||||
view = mountTestComponent({
|
view = mountTestComponent({
|
||||||
|
cursorStore: remoteCursorStore,
|
||||||
displayAvatar: false,
|
displayAvatar: false,
|
||||||
mediaType: "local",
|
mediaType: "local",
|
||||||
|
shareCursor: true,
|
||||||
srcMediaElement: {
|
srcMediaElement: {
|
||||||
fake: 1
|
fake: 1
|
||||||
}
|
}
|
||||||
@ -616,7 +673,8 @@ describe("loop.shared.views", function() {
|
|||||||
tagName: "DIV",
|
tagName: "DIV",
|
||||||
querySelector: function() {
|
querySelector: function() {
|
||||||
return {
|
return {
|
||||||
tagName: "DIV"
|
tagName: "DIV",
|
||||||
|
addEventListener: sinon.stub()
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@ -638,6 +696,18 @@ describe("loop.shared.views", function() {
|
|||||||
expect(fakeVideoElement.srcObject).eql({ fake: 1 });
|
expect(fakeVideoElement.srcObject).eql({ fake: 1 });
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("should attach events to the video", function() {
|
||||||
|
fakeVideoElement.srcObject = null;
|
||||||
|
sinon.stub(view, "getDOMNode").returns(fakeViewElement);
|
||||||
|
view.attachVideo({
|
||||||
|
src: { fake: 1 }
|
||||||
|
});
|
||||||
|
|
||||||
|
sinon.assert.calledTwice(fakeVideoElement.addEventListener);
|
||||||
|
sinon.assert.calledWith(fakeVideoElement.addEventListener, "loadeddata");
|
||||||
|
sinon.assert.calledWith(fakeVideoElement.addEventListener, "mousemove");
|
||||||
|
});
|
||||||
|
|
||||||
it("should attach a video object for Firefox", function() {
|
it("should attach a video object for Firefox", function() {
|
||||||
fakeVideoElement.mozSrcObject = null;
|
fakeVideoElement.mozSrcObject = null;
|
||||||
|
|
||||||
@ -705,10 +775,13 @@ describe("loop.shared.views", function() {
|
|||||||
});
|
});
|
||||||
|
|
||||||
describe("MediaLayoutView", function() {
|
describe("MediaLayoutView", function() {
|
||||||
var textChatStore, view;
|
var textChatStore,
|
||||||
|
remoteCursorStore,
|
||||||
|
view;
|
||||||
|
|
||||||
function mountTestComponent(extraProps) {
|
function mountTestComponent(extraProps) {
|
||||||
var defaultProps = {
|
var defaultProps = {
|
||||||
|
cursorStore: remoteCursorStore,
|
||||||
dispatcher: dispatcher,
|
dispatcher: dispatcher,
|
||||||
displayScreenShare: false,
|
displayScreenShare: false,
|
||||||
isLocalLoading: false,
|
isLocalLoading: false,
|
||||||
@ -730,6 +803,9 @@ describe("loop.shared.views", function() {
|
|||||||
textChatStore = new loop.store.TextChatStore(dispatcher, {
|
textChatStore = new loop.store.TextChatStore(dispatcher, {
|
||||||
sdkDriver: {}
|
sdkDriver: {}
|
||||||
});
|
});
|
||||||
|
remoteCursorStore = new loop.store.RemoteCursorStore(dispatcher, {
|
||||||
|
sdkDriver: {}
|
||||||
|
});
|
||||||
|
|
||||||
loop.store.StoreMixin.register({ textChatStore: textChatStore });
|
loop.store.StoreMixin.register({ textChatStore: textChatStore });
|
||||||
});
|
});
|
||||||
|
@ -50,6 +50,15 @@
|
|||||||
-moz-image-region: rect(1px, 125px, 17px, 109px);
|
-moz-image-region: rect(1px, 125px, 17px, 109px);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* The slideshow state disables the button for that window and makes it look
|
||||||
|
the same as the hover state to visually indicate that it's in-use.
|
||||||
|
*/
|
||||||
|
#loop-button[state="slideshow"] {
|
||||||
|
background: var(--toolbarbutton-hover-background);
|
||||||
|
border-color: var(--toolbarbutton-hover-bordercolor);
|
||||||
|
box-shadow: var(--toolbarbutton-hover-boxshadow);
|
||||||
|
}
|
||||||
|
|
||||||
@media (min-resolution: 1.1dppx) {
|
@media (min-resolution: 1.1dppx) {
|
||||||
#loop-button {
|
#loop-button {
|
||||||
list-style-image: url("chrome://loop/skin/toolbar@2x.png");
|
list-style-image: url("chrome://loop/skin/toolbar@2x.png");
|
||||||
@ -276,4 +285,38 @@
|
|||||||
background-color: #ef6745;
|
background-color: #ef6745;
|
||||||
border-color: #ef6745;
|
border-color: #ef6745;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#loop-remote-cursor {
|
||||||
|
background: url("chrome://loop/content/shared/img/cursor.svg#blue") no-repeat;
|
||||||
|
height: 20px;
|
||||||
|
width: 15px;
|
||||||
|
/*
|
||||||
|
* Svg cursor has a white outline so we need to get rid off it to ensure
|
||||||
|
* that the cursor points at a more precise position
|
||||||
|
*/
|
||||||
|
margin: -2px;
|
||||||
|
pointer-events: none;
|
||||||
|
position: absolute;
|
||||||
|
}
|
||||||
|
|
||||||
|
#loop-slideshow-container {
|
||||||
|
/* cover the entire viewport, mouse interaction with non-slideshow
|
||||||
|
content is now impossible. */
|
||||||
|
position: fixed;
|
||||||
|
z-index: 1;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
|
||||||
|
/* darken the background content */
|
||||||
|
background: rgba(0, 0, 0, .8);
|
||||||
|
}
|
||||||
|
|
||||||
|
#loop-slideshow-browser {
|
||||||
|
width: 620px;
|
||||||
|
height:450px;
|
||||||
|
margin-top: 10%;
|
||||||
|
|
||||||
|
/* XXX derived from width, so should be 50% - (620px / 2)? */
|
||||||
|
-moz-margin-start: calc(50% - 310px);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -41,7 +41,7 @@ function* checkFxA401() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
add_task(function* setup() {
|
add_task(function* setup() {
|
||||||
Services.prefs.setIntPref("loop.gettingStarted.latestFTUVersion", 1);
|
Services.prefs.setIntPref("loop.gettingStarted.latestFTUVersion", 2);
|
||||||
MozLoopServiceInternal.mocks.pushHandler = mockPushHandler;
|
MozLoopServiceInternal.mocks.pushHandler = mockPushHandler;
|
||||||
// Normally the same pushUrl would be registered but we change it in the test
|
// Normally the same pushUrl would be registered but we change it in the test
|
||||||
// to be able to check for success on the second registration.
|
// to be able to check for success on the second registration.
|
||||||
|
@ -18,7 +18,7 @@ add_task(function* test_mozLoop_nochat() {
|
|||||||
});
|
});
|
||||||
|
|
||||||
add_task(function* test_mozLoop_openchat() {
|
add_task(function* test_mozLoop_openchat() {
|
||||||
let windowId = LoopRooms.open("fake1234");
|
let windowId = yield LoopRooms.open("fake1234");
|
||||||
Assert.ok(isAnyLoopChatOpen(), "chat window should have been opened");
|
Assert.ok(isAnyLoopChatOpen(), "chat window should have been opened");
|
||||||
|
|
||||||
let chatboxesForRoom = [...Chat.chatboxes].filter(chatbox => {
|
let chatboxesForRoom = [...Chat.chatboxes].filter(chatbox => {
|
||||||
@ -28,7 +28,7 @@ add_task(function* test_mozLoop_openchat() {
|
|||||||
});
|
});
|
||||||
|
|
||||||
add_task(function* test_mozLoop_hangupAllChatWindows() {
|
add_task(function* test_mozLoop_hangupAllChatWindows() {
|
||||||
LoopRooms.open("fake2345");
|
yield LoopRooms.open("fake2345");
|
||||||
|
|
||||||
yield promiseWaitForCondition(() => {
|
yield promiseWaitForCondition(() => {
|
||||||
MozLoopService.hangupAllChatWindows();
|
MozLoopService.hangupAllChatWindows();
|
||||||
@ -49,7 +49,7 @@ add_task(function* test_mozLoop_hangupOnClose() {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
let windowId = LoopRooms.open(roomToken);
|
let windowId = yield LoopRooms.open(roomToken);
|
||||||
|
|
||||||
yield promiseWaitForCondition(() => {
|
yield promiseWaitForCondition(() => {
|
||||||
MozLoopService.hangupAllChatWindows();
|
MozLoopService.hangupAllChatWindows();
|
||||||
|
@ -132,3 +132,106 @@ add_task(function* test_mozLoop_telemetryAdd_roomSessionWithChat() {
|
|||||||
Assert.strictEqual(snapshot.counts[0], i);
|
Assert.strictEqual(snapshot.counts[0], i);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Skip until bug 1208416 has landed.
|
||||||
|
/*
|
||||||
|
add_task(function* test_mozLoop_telemetryAdd_infobarActionButtons() {
|
||||||
|
let histogramId = "LOOP_INFOBAR_ACTION_BUTTONS";
|
||||||
|
let histogram = Services.telemetry.getHistogramById(histogramId);
|
||||||
|
const ACTION_TYPES = gConstants.SHARING_SCREEN;
|
||||||
|
|
||||||
|
histogram.clear();
|
||||||
|
|
||||||
|
for (let value of [ACTION_TYPES.PAUSED,
|
||||||
|
ACTION_TYPES.PAUSED,
|
||||||
|
ACTION_TYPES.RESUMED]) {
|
||||||
|
gHandlers.TelemetryAddValue({ data: [histogramId, value] }, () => {});
|
||||||
|
}
|
||||||
|
|
||||||
|
let snapshot = histogram.snapshot();
|
||||||
|
Assert.strictEqual(snapshot.counts[ACTION_TYPES.RESUMED], 1,
|
||||||
|
"SHARING_SCREEN.RESUMED");
|
||||||
|
Assert.strictEqual(snapshot.counts[ACTION_TYPES.PAUSED], 2,
|
||||||
|
"SHARING_SCREEN.PAUSED");
|
||||||
|
});
|
||||||
|
|
||||||
|
add_task(function* test_mozLoop_telemetryAdd_loopMauType_buckets() {
|
||||||
|
let histogramId = "LOOP_MAU";
|
||||||
|
let histogram = Services.telemetry.getHistogramById(histogramId);
|
||||||
|
const ACTION_TYPES = gConstants.LOOP_MAU_TYPE;
|
||||||
|
|
||||||
|
histogram.clear();
|
||||||
|
|
||||||
|
for (let value of [ACTION_TYPES.OPEN_PANEL,
|
||||||
|
ACTION_TYPES.OPEN_CONVERSATION,
|
||||||
|
ACTION_TYPES.ROOM_OPEN,
|
||||||
|
ACTION_TYPES.ROOM_SHARE,
|
||||||
|
ACTION_TYPES.ROOM_DELETE]) {
|
||||||
|
gHandlers.TelemetryAddValue({ data: [histogramId, value] }, () => {});
|
||||||
|
}
|
||||||
|
|
||||||
|
let snapshot = histogram.snapshot();
|
||||||
|
Assert.strictEqual(snapshot.counts[ACTION_TYPES.OPEN_PANEL], 1,
|
||||||
|
"LOOP_MAU_TYPE.OPEN_PANEL");
|
||||||
|
Assert.strictEqual(snapshot.counts[ACTION_TYPES.OPEN_CONVERSATION], 1,
|
||||||
|
"LOOP_MAU_TYPE.OPEN_CONVERSATION");
|
||||||
|
Assert.strictEqual(snapshot.counts[ACTION_TYPES.ROOM_OPEN], 1,
|
||||||
|
"LOOP_MAU_TYPE.ROOM_OPEN");
|
||||||
|
Assert.strictEqual(snapshot.counts[ACTION_TYPES.ROOM_SHARE], 1,
|
||||||
|
"LOOP_MAU_TYPE.ROOM_SHARE");
|
||||||
|
Assert.strictEqual(snapshot.counts[ACTION_TYPES.ROOM_DELETE], 1,
|
||||||
|
"LOOP_MAU_TYPE.ROOM_DELETE");
|
||||||
|
});
|
||||||
|
*/
|
||||||
|
/**
|
||||||
|
* Tests that only one event is sent every 30 days
|
||||||
|
*/
|
||||||
|
// Skip until bug 1208416 has landed.
|
||||||
|
/*
|
||||||
|
add_task(function* test_mozLoop_telemetryAdd_loopMau_more_than_30_days() {
|
||||||
|
let histogramId = "LOOP_MAU";
|
||||||
|
let histogram = Services.telemetry.getHistogramById(histogramId);
|
||||||
|
const ACTION_TYPES = gConstants.LOOP_MAU_TYPE;
|
||||||
|
|
||||||
|
histogram.clear();
|
||||||
|
gHandlers.TelemetryAddValue({ data: [histogramId, ACTION_TYPES.OPEN_PANEL] }, () => {});
|
||||||
|
|
||||||
|
let snapshot = histogram.snapshot();
|
||||||
|
Assert.strictEqual(snapshot.counts[ACTION_TYPES.OPEN_PANEL], 1,
|
||||||
|
"LOOP_MAU_TYPE.OPEN_PANEL");
|
||||||
|
|
||||||
|
// Let's be sure that the last event was sent a month ago
|
||||||
|
let timestamp = (Math.floor(Date.now() / 1000)) - 2593000;
|
||||||
|
Services.prefs.setIntPref("loop.mau.openPanel", timestamp);
|
||||||
|
|
||||||
|
gHandlers.TelemetryAddValue({ data: [histogramId, ACTION_TYPES.OPEN_PANEL] }, () => {});
|
||||||
|
snapshot = histogram.snapshot();
|
||||||
|
Assert.strictEqual(snapshot.counts[ACTION_TYPES.OPEN_PANEL], 2,
|
||||||
|
"LOOP_MAU_TYPE.OPEN_PANEL");
|
||||||
|
|
||||||
|
Services.prefs.clearUserPref("loop.mau.openPanel");
|
||||||
|
});
|
||||||
|
|
||||||
|
add_task.skip(function* test_mozLoop_telemetryAdd_loopMau_less_than_30_days() {
|
||||||
|
let histogramId = "LOOP_MAU";
|
||||||
|
let histogram = Services.telemetry.getHistogramById(histogramId);
|
||||||
|
const ACTION_TYPES = gConstants.LOOP_MAU_TYPE;
|
||||||
|
|
||||||
|
histogram.clear();
|
||||||
|
gHandlers.TelemetryAddValue({ data: [histogramId, ACTION_TYPES.OPEN_PANEL] }, () => {});
|
||||||
|
|
||||||
|
let snapshot = histogram.snapshot();
|
||||||
|
Assert.strictEqual(snapshot.counts[ACTION_TYPES.OPEN_PANEL], 1,
|
||||||
|
"LOOP_MAU_TYPE.OPEN_PANEL");
|
||||||
|
|
||||||
|
let timestamp = (Math.floor(Date.now() / 1000)) - 1000;
|
||||||
|
Services.prefs.setIntPref("loop.mau.openPanel", timestamp);
|
||||||
|
|
||||||
|
gHandlers.TelemetryAddValue({ data: [histogramId, ACTION_TYPES.OPEN_PANEL] }, () => {});
|
||||||
|
snapshot = histogram.snapshot();
|
||||||
|
Assert.strictEqual(snapshot.counts[ACTION_TYPES.OPEN_PANEL], 1,
|
||||||
|
"LOOP_MAU_TYPE.OPEN_PANEL");
|
||||||
|
|
||||||
|
Services.prefs.clearUserPref("loop.mau.openPanel");
|
||||||
|
});
|
||||||
|
*/
|
||||||
|
@ -8,7 +8,7 @@
|
|||||||
"use strict";
|
"use strict";
|
||||||
|
|
||||||
Components.utils.import("resource://gre/modules/Promise.jsm", this);
|
Components.utils.import("resource://gre/modules/Promise.jsm", this);
|
||||||
Services.prefs.setIntPref("loop.gettingStarted.latestFTUVersion", 1);
|
Services.prefs.setIntPref("loop.gettingStarted.latestFTUVersion", 2);
|
||||||
|
|
||||||
const fxASampleToken = {
|
const fxASampleToken = {
|
||||||
token_type: "bearer",
|
token_type: "bearer",
|
||||||
|
@ -14,7 +14,7 @@ add_test(function test_intialize() {
|
|||||||
|
|
||||||
LoopAPIInternal.initialize();
|
LoopAPIInternal.initialize();
|
||||||
let [, , pageListeners2] = LoopAPI.inspect();
|
let [, , pageListeners2] = LoopAPI.inspect();
|
||||||
Assert.equal(pageListeners2.length, 2, "Two page listeners should be added");
|
Assert.equal(pageListeners2.length, 3, "Three page listeners should be added");
|
||||||
|
|
||||||
let pageListenersStub = {};
|
let pageListenersStub = {};
|
||||||
LoopAPI.stub([pageListenersStub]);
|
LoopAPI.stub([pageListenersStub]);
|
||||||
|
@ -522,25 +522,10 @@ add_task(function* test_joinRoomGuest() {
|
|||||||
MozLoopServiceInternal.fxAOAuthProfile = null;
|
MozLoopServiceInternal.fxAOAuthProfile = null;
|
||||||
|
|
||||||
let roomToken = "_nxD4V4FflQ";
|
let roomToken = "_nxD4V4FflQ";
|
||||||
let joinedData = yield LoopRooms.promise("join", roomToken);
|
let joinedData = yield LoopRooms.promise("join", roomToken, "guest");
|
||||||
Assert.equal(joinedData.action, "join");
|
Assert.equal(joinedData.action, "join");
|
||||||
});
|
});
|
||||||
|
|
||||||
// Test if joining a room as FxA user works as expected.
|
|
||||||
add_task(function* test_joinRoom() {
|
|
||||||
// We need these set up for getting the email address.
|
|
||||||
MozLoopServiceInternal.fxAOAuthTokenData = { token_type: "bearer" };
|
|
||||||
MozLoopServiceInternal.fxAOAuthProfile = { email: "fake@invalid.com" };
|
|
||||||
|
|
||||||
let roomToken = "_nxD4V4FflQ";
|
|
||||||
let joinedData = yield LoopRooms.promise("join", roomToken);
|
|
||||||
Assert.equal(joinedData.action, "join");
|
|
||||||
Assert.equal(joinedData.displayName, "fake@invalid.com");
|
|
||||||
|
|
||||||
MozLoopServiceInternal.fxAOAuthTokenData = null;
|
|
||||||
MozLoopServiceInternal.fxAOAuthProfile = null;
|
|
||||||
});
|
|
||||||
|
|
||||||
// Test if refreshing a room works as expected.
|
// Test if refreshing a room works as expected.
|
||||||
add_task(function* test_refreshMembership() {
|
add_task(function* test_refreshMembership() {
|
||||||
let roomToken = "_nxD4V4FflQ";
|
let roomToken = "_nxD4V4FflQ";
|
||||||
|
@ -29,6 +29,32 @@ add_task(function* request_with_unicode() {
|
|||||||
() => Assert.ok(false, "Should have accepted"));
|
() => Assert.ok(false, "Should have accepted"));
|
||||||
});
|
});
|
||||||
|
|
||||||
|
add_task(function* request_with_unicode() {
|
||||||
|
loopServer.registerPathHandler("/fake", (request, response) => {
|
||||||
|
Assert.ok(request.hasHeader("x-loop-addon-ver"), "Should have an add-on version header");
|
||||||
|
Assert.equal(request.getHeader("x-loop-addon-ver"), "3.1", "Should have the correct add-on version");
|
||||||
|
|
||||||
|
response.setStatusLine(null, 200, "OK");
|
||||||
|
response.processAsync();
|
||||||
|
response.finish();
|
||||||
|
});
|
||||||
|
|
||||||
|
// Pretend we're not enabled so full initialisation doesn't take place.
|
||||||
|
Services.prefs.setBoolPref("loop.enabled", false);
|
||||||
|
|
||||||
|
try {
|
||||||
|
yield MozLoopService.initialize("3.1");
|
||||||
|
} catch (ex) {
|
||||||
|
// Do nothing - this will throw due to being disabled, that's ok.
|
||||||
|
}
|
||||||
|
|
||||||
|
Services.prefs.clearUserPref("loop.enabled");
|
||||||
|
|
||||||
|
yield MozLoopServiceInternal.hawkRequestInternal(LOOP_SESSION_TYPE.GUEST, "/fake", "POST", {}).then(
|
||||||
|
() => Assert.ok(true, "Should have accepted"),
|
||||||
|
() => Assert.ok(false, "Should have accepted"));
|
||||||
|
});
|
||||||
|
|
||||||
function run_test() {
|
function run_test() {
|
||||||
setupFakeLoopServer();
|
setupFakeLoopServer();
|
||||||
|
|
||||||
|
@ -9,7 +9,7 @@
|
|||||||
<Description about="urn:mozilla:install-manifest">
|
<Description about="urn:mozilla:install-manifest">
|
||||||
<em:id>loop@mozilla.org</em:id>
|
<em:id>loop@mozilla.org</em:id>
|
||||||
<em:bootstrap>true</em:bootstrap>
|
<em:bootstrap>true</em:bootstrap>
|
||||||
<em:version>0.3.0</em:version>
|
<em:version>1.1.2</em:version>
|
||||||
<em:type>2</em:type>
|
<em:type>2</em:type>
|
||||||
|
|
||||||
<!-- Target Application this extension can install into,
|
<!-- Target Application this extension can install into,
|
||||||
|
@ -14,7 +14,7 @@ FIREFOX_PREFERENCES = {
|
|||||||
"devtools.debugger.prompt-connection": False,
|
"devtools.debugger.prompt-connection": False,
|
||||||
"devtools.debugger.remote-enabled": True,
|
"devtools.debugger.remote-enabled": True,
|
||||||
"media.volume_scale": "0",
|
"media.volume_scale": "0",
|
||||||
"loop.gettingStarted.latestFTUVersion": 1,
|
"loop.gettingStarted.latestFTUVersion": 2,
|
||||||
|
|
||||||
# this dialog is fragile, and likely to introduce intermittent failures
|
# this dialog is fragile, and likely to introduce intermittent failures
|
||||||
"media.navigator.permission.disabled": True,
|
"media.navigator.permission.disabled": True,
|
||||||
|