зеркало из https://github.com/mozilla/gecko-dev.git
Bug 1074672 Part 1 - Implement a room list view for Loop, r=mikedeboer.
This commit is contained in:
Родитель
3f727a6695
Коммит
5a7d5e605f
|
@ -1623,6 +1623,7 @@ pref("loop.CSP", "default-src 'self' about: file: chrome:; img-src 'self' data:
|
||||||
#endif
|
#endif
|
||||||
pref("loop.oauth.google.redirect_uri", "urn:ietf:wg:oauth:2.0:oob:auto");
|
pref("loop.oauth.google.redirect_uri", "urn:ietf:wg:oauth:2.0:oob:auto");
|
||||||
pref("loop.oauth.google.scope", "https://www.google.com/m8/feeds");
|
pref("loop.oauth.google.scope", "https://www.google.com/m8/feeds");
|
||||||
|
pref("loop.rooms.enabled", false);
|
||||||
|
|
||||||
// serverURL to be assigned by services team
|
// serverURL to be assigned by services team
|
||||||
pref("services.push.serverURL", "wss://push.services.mozilla.com/");
|
pref("services.push.serverURL", "wss://push.services.mozilla.com/");
|
||||||
|
|
|
@ -14,6 +14,7 @@ loop.panel = (function(_, mozL10n) {
|
||||||
var sharedViews = loop.shared.views;
|
var sharedViews = loop.shared.views;
|
||||||
var sharedModels = loop.shared.models;
|
var sharedModels = loop.shared.models;
|
||||||
var sharedMixins = loop.shared.mixins;
|
var sharedMixins = loop.shared.mixins;
|
||||||
|
var sharedActions = loop.shared.actions;
|
||||||
var Button = sharedViews.Button;
|
var Button = sharedViews.Button;
|
||||||
var ButtonGroup = sharedViews.ButtonGroup;
|
var ButtonGroup = sharedViews.ButtonGroup;
|
||||||
var ContactsList = loop.contacts.ContactsList;
|
var ContactsList = loop.contacts.ContactsList;
|
||||||
|
@ -21,12 +22,23 @@ loop.panel = (function(_, mozL10n) {
|
||||||
var __ = mozL10n.get; // aliasing translation function as __ for concision
|
var __ = mozL10n.get; // aliasing translation function as __ for concision
|
||||||
|
|
||||||
var TabView = React.createClass({displayName: 'TabView',
|
var TabView = React.createClass({displayName: 'TabView',
|
||||||
getInitialState: function() {
|
propTypes: {
|
||||||
|
buttonsHidden: React.PropTypes.bool,
|
||||||
|
// The selectedTab prop is used by the UI showcase.
|
||||||
|
selectedTab: React.PropTypes.string
|
||||||
|
},
|
||||||
|
|
||||||
|
getDefaultProps: function() {
|
||||||
return {
|
return {
|
||||||
|
buttonsHidden: false,
|
||||||
selectedTab: "call"
|
selectedTab: "call"
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
|
||||||
|
getInitialState: function() {
|
||||||
|
return {selectedTab: this.props.selectedTab};
|
||||||
|
},
|
||||||
|
|
||||||
handleSelectTab: function(event) {
|
handleSelectTab: function(event) {
|
||||||
var tabName = event.target.dataset.tabName;
|
var tabName = event.target.dataset.tabName;
|
||||||
this.setState({selectedTab: tabName});
|
this.setState({selectedTab: tabName});
|
||||||
|
@ -37,6 +49,10 @@ loop.panel = (function(_, mozL10n) {
|
||||||
var tabButtons = [];
|
var tabButtons = [];
|
||||||
var tabs = [];
|
var tabs = [];
|
||||||
React.Children.forEach(this.props.children, function(tab, i) {
|
React.Children.forEach(this.props.children, function(tab, i) {
|
||||||
|
// Filter out null tabs (eg. rooms when the feature is disabled)
|
||||||
|
if (!tab) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
var tabName = tab.props.name;
|
var tabName = tab.props.name;
|
||||||
var isSelected = (this.state.selectedTab == tabName);
|
var isSelected = (this.state.selectedTab == tabName);
|
||||||
if (!tab.props.hidden) {
|
if (!tab.props.hidden) {
|
||||||
|
@ -442,6 +458,121 @@ loop.panel = (function(_, mozL10n) {
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Room list entry.
|
||||||
|
*/
|
||||||
|
var RoomEntry = React.createClass({displayName: 'RoomEntry',
|
||||||
|
propTypes: {
|
||||||
|
openRoom: React.PropTypes.func.isRequired,
|
||||||
|
room: React.PropTypes.instanceOf(loop.store.Room).isRequired
|
||||||
|
},
|
||||||
|
|
||||||
|
shouldComponentUpdate: function(nextProps, nextState) {
|
||||||
|
return nextProps.room.ctime > this.props.room.ctime;
|
||||||
|
},
|
||||||
|
|
||||||
|
handleClickRoom: function(event) {
|
||||||
|
event.preventDefault();
|
||||||
|
this.props.openRoom(this.props.room);
|
||||||
|
},
|
||||||
|
|
||||||
|
_isActive: function() {
|
||||||
|
// XXX bug 1074679 will implement this properly
|
||||||
|
return this.props.room.currSize > 0;
|
||||||
|
},
|
||||||
|
|
||||||
|
render: function() {
|
||||||
|
var room = this.props.room;
|
||||||
|
var roomClasses = React.addons.classSet({
|
||||||
|
"room-entry": true,
|
||||||
|
"room-active": this._isActive()
|
||||||
|
});
|
||||||
|
|
||||||
|
return (
|
||||||
|
React.DOM.div({className: roomClasses},
|
||||||
|
React.DOM.h2(null,
|
||||||
|
React.DOM.span({className: "room-notification"}),
|
||||||
|
room.roomName
|
||||||
|
),
|
||||||
|
React.DOM.p(null,
|
||||||
|
React.DOM.a({ref: "room", href: "#", onClick: this.handleClickRoom},
|
||||||
|
room.roomUrl
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Room list.
|
||||||
|
*/
|
||||||
|
var RoomList = React.createClass({displayName: 'RoomList',
|
||||||
|
mixins: [Backbone.Events],
|
||||||
|
|
||||||
|
propTypes: {
|
||||||
|
store: React.PropTypes.instanceOf(loop.store.RoomListStore).isRequired,
|
||||||
|
dispatcher: React.PropTypes.instanceOf(loop.Dispatcher).isRequired,
|
||||||
|
rooms: React.PropTypes.array
|
||||||
|
},
|
||||||
|
|
||||||
|
getInitialState: function() {
|
||||||
|
var storeState = this.props.store.getStoreState();
|
||||||
|
return {
|
||||||
|
error: this.props.error || storeState.error,
|
||||||
|
rooms: this.props.rooms || storeState.rooms,
|
||||||
|
};
|
||||||
|
},
|
||||||
|
|
||||||
|
componentWillMount: function() {
|
||||||
|
this.listenTo(this.props.store, "change", this._onRoomListChanged);
|
||||||
|
|
||||||
|
this.props.dispatcher.dispatch(new sharedActions.GetAllRooms());
|
||||||
|
},
|
||||||
|
|
||||||
|
componentWillUnmount: function() {
|
||||||
|
this.stopListening(this.props.store);
|
||||||
|
},
|
||||||
|
|
||||||
|
_onRoomListChanged: function() {
|
||||||
|
var storeState = this.props.store.getStoreState();
|
||||||
|
this.setState({
|
||||||
|
error: storeState.error,
|
||||||
|
rooms: storeState.rooms
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
_getListHeading: function() {
|
||||||
|
var numRooms = this.state.rooms.length;
|
||||||
|
if (numRooms === 0) {
|
||||||
|
return mozL10n.get("rooms_list_no_current_conversations");
|
||||||
|
}
|
||||||
|
return mozL10n.get("rooms_list_current_conversations", {num: numRooms});
|
||||||
|
},
|
||||||
|
|
||||||
|
openRoom: function(room) {
|
||||||
|
// XXX implement me; see bug 1074678
|
||||||
|
},
|
||||||
|
|
||||||
|
render: function() {
|
||||||
|
if (this.state.error) {
|
||||||
|
// XXX Better end user reporting of errors.
|
||||||
|
console.error(this.state.error);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
React.DOM.div({className: "room-list"},
|
||||||
|
React.DOM.h1(null, this._getListHeading()),
|
||||||
|
|
||||||
|
this.state.rooms.map(function(room, i) {
|
||||||
|
return RoomEntry({key: i, room: room, openRoom: this.openRoom});
|
||||||
|
}, this)
|
||||||
|
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Panel view.
|
* Panel view.
|
||||||
*/
|
*/
|
||||||
|
@ -453,6 +584,10 @@ loop.panel = (function(_, mozL10n) {
|
||||||
callUrl: React.PropTypes.string,
|
callUrl: React.PropTypes.string,
|
||||||
userProfile: React.PropTypes.object,
|
userProfile: React.PropTypes.object,
|
||||||
showTabButtons: React.PropTypes.bool,
|
showTabButtons: React.PropTypes.bool,
|
||||||
|
selectedTab: React.PropTypes.string,
|
||||||
|
dispatcher: React.PropTypes.instanceOf(loop.Dispatcher).isRequired,
|
||||||
|
roomListStore:
|
||||||
|
React.PropTypes.instanceOf(loop.store.RoomListStore).isRequired
|
||||||
},
|
},
|
||||||
|
|
||||||
getInitialState: function() {
|
getInitialState: function() {
|
||||||
|
@ -498,6 +633,22 @@ loop.panel = (function(_, mozL10n) {
|
||||||
this.updateServiceErrors();
|
this.updateServiceErrors();
|
||||||
},
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The rooms feature is hidden by default for now. Once it gets mainstream,
|
||||||
|
* this method can be safely removed.
|
||||||
|
*/
|
||||||
|
_renderRoomsTab: function() {
|
||||||
|
if (!navigator.mozLoop.getLoopBoolPref("rooms.enabled")) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return (
|
||||||
|
Tab({name: "rooms"},
|
||||||
|
RoomList({dispatcher: this.props.dispatcher,
|
||||||
|
store: this.props.roomListStore})
|
||||||
|
)
|
||||||
|
);
|
||||||
|
},
|
||||||
|
|
||||||
startForm: function(name, contact) {
|
startForm: function(name, contact) {
|
||||||
this.refs[name].initForm(contact);
|
this.refs[name].initForm(contact);
|
||||||
this.selectTab(name);
|
this.selectTab(name);
|
||||||
|
@ -527,7 +678,8 @@ loop.panel = (function(_, mozL10n) {
|
||||||
React.DOM.div(null,
|
React.DOM.div(null,
|
||||||
NotificationListView({notifications: this.props.notifications,
|
NotificationListView({notifications: this.props.notifications,
|
||||||
clearOnDocumentHidden: true}),
|
clearOnDocumentHidden: true}),
|
||||||
TabView({ref: "tabView", buttonsHidden: !this.state.userProfile && !this.props.showTabButtons},
|
TabView({ref: "tabView", selectedTab: this.props.selectedTab,
|
||||||
|
buttonsHidden: !this.state.userProfile && !this.props.showTabButtons},
|
||||||
Tab({name: "call"},
|
Tab({name: "call"},
|
||||||
React.DOM.div({className: "content-area"},
|
React.DOM.div({className: "content-area"},
|
||||||
CallUrlResult({client: this.props.client,
|
CallUrlResult({client: this.props.client,
|
||||||
|
@ -536,6 +688,7 @@ loop.panel = (function(_, mozL10n) {
|
||||||
ToSView(null)
|
ToSView(null)
|
||||||
)
|
)
|
||||||
),
|
),
|
||||||
|
this._renderRoomsTab(),
|
||||||
Tab({name: "contacts"},
|
Tab({name: "contacts"},
|
||||||
ContactsList({selectTab: this.selectTab,
|
ContactsList({selectTab: this.selectTab,
|
||||||
startForm: this.startForm})
|
startForm: this.startForm})
|
||||||
|
@ -575,11 +728,19 @@ loop.panel = (function(_, mozL10n) {
|
||||||
mozL10n.initialize(navigator.mozLoop);
|
mozL10n.initialize(navigator.mozLoop);
|
||||||
|
|
||||||
var client = new loop.Client();
|
var client = new loop.Client();
|
||||||
var notifications = new sharedModels.NotificationCollection()
|
var notifications = new sharedModels.NotificationCollection();
|
||||||
|
var dispatcher = new loop.Dispatcher();
|
||||||
|
var roomListStore = new loop.store.RoomListStore({
|
||||||
|
mozLoop: navigator.mozLoop,
|
||||||
|
dispatcher: dispatcher
|
||||||
|
});
|
||||||
|
|
||||||
React.renderComponent(PanelView({
|
React.renderComponent(PanelView({
|
||||||
client: client,
|
client: client,
|
||||||
notifications: notifications}), document.querySelector("#main"));
|
notifications: notifications,
|
||||||
|
roomListStore: roomListStore,
|
||||||
|
dispatcher: dispatcher}
|
||||||
|
), document.querySelector("#main"));
|
||||||
|
|
||||||
document.body.classList.add(loop.shared.utils.getTargetPlatform());
|
document.body.classList.add(loop.shared.utils.getTargetPlatform());
|
||||||
document.body.setAttribute("dir", mozL10n.getDirection());
|
document.body.setAttribute("dir", mozL10n.getDirection());
|
||||||
|
@ -597,6 +758,7 @@ loop.panel = (function(_, mozL10n) {
|
||||||
AvailabilityDropdown: AvailabilityDropdown,
|
AvailabilityDropdown: AvailabilityDropdown,
|
||||||
CallUrlResult: CallUrlResult,
|
CallUrlResult: CallUrlResult,
|
||||||
PanelView: PanelView,
|
PanelView: PanelView,
|
||||||
|
RoomList: RoomList,
|
||||||
SettingsDropdown: SettingsDropdown,
|
SettingsDropdown: SettingsDropdown,
|
||||||
ToSView: ToSView
|
ToSView: ToSView
|
||||||
};
|
};
|
||||||
|
|
|
@ -14,6 +14,7 @@ loop.panel = (function(_, mozL10n) {
|
||||||
var sharedViews = loop.shared.views;
|
var sharedViews = loop.shared.views;
|
||||||
var sharedModels = loop.shared.models;
|
var sharedModels = loop.shared.models;
|
||||||
var sharedMixins = loop.shared.mixins;
|
var sharedMixins = loop.shared.mixins;
|
||||||
|
var sharedActions = loop.shared.actions;
|
||||||
var Button = sharedViews.Button;
|
var Button = sharedViews.Button;
|
||||||
var ButtonGroup = sharedViews.ButtonGroup;
|
var ButtonGroup = sharedViews.ButtonGroup;
|
||||||
var ContactsList = loop.contacts.ContactsList;
|
var ContactsList = loop.contacts.ContactsList;
|
||||||
|
@ -21,12 +22,23 @@ loop.panel = (function(_, mozL10n) {
|
||||||
var __ = mozL10n.get; // aliasing translation function as __ for concision
|
var __ = mozL10n.get; // aliasing translation function as __ for concision
|
||||||
|
|
||||||
var TabView = React.createClass({
|
var TabView = React.createClass({
|
||||||
getInitialState: function() {
|
propTypes: {
|
||||||
|
buttonsHidden: React.PropTypes.bool,
|
||||||
|
// The selectedTab prop is used by the UI showcase.
|
||||||
|
selectedTab: React.PropTypes.string
|
||||||
|
},
|
||||||
|
|
||||||
|
getDefaultProps: function() {
|
||||||
return {
|
return {
|
||||||
|
buttonsHidden: false,
|
||||||
selectedTab: "call"
|
selectedTab: "call"
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
|
||||||
|
getInitialState: function() {
|
||||||
|
return {selectedTab: this.props.selectedTab};
|
||||||
|
},
|
||||||
|
|
||||||
handleSelectTab: function(event) {
|
handleSelectTab: function(event) {
|
||||||
var tabName = event.target.dataset.tabName;
|
var tabName = event.target.dataset.tabName;
|
||||||
this.setState({selectedTab: tabName});
|
this.setState({selectedTab: tabName});
|
||||||
|
@ -37,6 +49,10 @@ loop.panel = (function(_, mozL10n) {
|
||||||
var tabButtons = [];
|
var tabButtons = [];
|
||||||
var tabs = [];
|
var tabs = [];
|
||||||
React.Children.forEach(this.props.children, function(tab, i) {
|
React.Children.forEach(this.props.children, function(tab, i) {
|
||||||
|
// Filter out null tabs (eg. rooms when the feature is disabled)
|
||||||
|
if (!tab) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
var tabName = tab.props.name;
|
var tabName = tab.props.name;
|
||||||
var isSelected = (this.state.selectedTab == tabName);
|
var isSelected = (this.state.selectedTab == tabName);
|
||||||
if (!tab.props.hidden) {
|
if (!tab.props.hidden) {
|
||||||
|
@ -442,6 +458,121 @@ loop.panel = (function(_, mozL10n) {
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Room list entry.
|
||||||
|
*/
|
||||||
|
var RoomEntry = React.createClass({
|
||||||
|
propTypes: {
|
||||||
|
openRoom: React.PropTypes.func.isRequired,
|
||||||
|
room: React.PropTypes.instanceOf(loop.store.Room).isRequired
|
||||||
|
},
|
||||||
|
|
||||||
|
shouldComponentUpdate: function(nextProps, nextState) {
|
||||||
|
return nextProps.room.ctime > this.props.room.ctime;
|
||||||
|
},
|
||||||
|
|
||||||
|
handleClickRoom: function(event) {
|
||||||
|
event.preventDefault();
|
||||||
|
this.props.openRoom(this.props.room);
|
||||||
|
},
|
||||||
|
|
||||||
|
_isActive: function() {
|
||||||
|
// XXX bug 1074679 will implement this properly
|
||||||
|
return this.props.room.currSize > 0;
|
||||||
|
},
|
||||||
|
|
||||||
|
render: function() {
|
||||||
|
var room = this.props.room;
|
||||||
|
var roomClasses = React.addons.classSet({
|
||||||
|
"room-entry": true,
|
||||||
|
"room-active": this._isActive()
|
||||||
|
});
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className={roomClasses}>
|
||||||
|
<h2>
|
||||||
|
<span className="room-notification" />
|
||||||
|
{room.roomName}
|
||||||
|
</h2>
|
||||||
|
<p>
|
||||||
|
<a ref="room" href="#" onClick={this.handleClickRoom}>
|
||||||
|
{room.roomUrl}
|
||||||
|
</a>
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Room list.
|
||||||
|
*/
|
||||||
|
var RoomList = React.createClass({
|
||||||
|
mixins: [Backbone.Events],
|
||||||
|
|
||||||
|
propTypes: {
|
||||||
|
store: React.PropTypes.instanceOf(loop.store.RoomListStore).isRequired,
|
||||||
|
dispatcher: React.PropTypes.instanceOf(loop.Dispatcher).isRequired,
|
||||||
|
rooms: React.PropTypes.array
|
||||||
|
},
|
||||||
|
|
||||||
|
getInitialState: function() {
|
||||||
|
var storeState = this.props.store.getStoreState();
|
||||||
|
return {
|
||||||
|
error: this.props.error || storeState.error,
|
||||||
|
rooms: this.props.rooms || storeState.rooms,
|
||||||
|
};
|
||||||
|
},
|
||||||
|
|
||||||
|
componentWillMount: function() {
|
||||||
|
this.listenTo(this.props.store, "change", this._onRoomListChanged);
|
||||||
|
|
||||||
|
this.props.dispatcher.dispatch(new sharedActions.GetAllRooms());
|
||||||
|
},
|
||||||
|
|
||||||
|
componentWillUnmount: function() {
|
||||||
|
this.stopListening(this.props.store);
|
||||||
|
},
|
||||||
|
|
||||||
|
_onRoomListChanged: function() {
|
||||||
|
var storeState = this.props.store.getStoreState();
|
||||||
|
this.setState({
|
||||||
|
error: storeState.error,
|
||||||
|
rooms: storeState.rooms
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
_getListHeading: function() {
|
||||||
|
var numRooms = this.state.rooms.length;
|
||||||
|
if (numRooms === 0) {
|
||||||
|
return mozL10n.get("rooms_list_no_current_conversations");
|
||||||
|
}
|
||||||
|
return mozL10n.get("rooms_list_current_conversations", {num: numRooms});
|
||||||
|
},
|
||||||
|
|
||||||
|
openRoom: function(room) {
|
||||||
|
// XXX implement me; see bug 1074678
|
||||||
|
},
|
||||||
|
|
||||||
|
render: function() {
|
||||||
|
if (this.state.error) {
|
||||||
|
// XXX Better end user reporting of errors.
|
||||||
|
console.error(this.state.error);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="room-list">
|
||||||
|
<h1>{this._getListHeading()}</h1>
|
||||||
|
{
|
||||||
|
this.state.rooms.map(function(room, i) {
|
||||||
|
return <RoomEntry key={i} room={room} openRoom={this.openRoom} />;
|
||||||
|
}, this)
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Panel view.
|
* Panel view.
|
||||||
*/
|
*/
|
||||||
|
@ -453,6 +584,10 @@ loop.panel = (function(_, mozL10n) {
|
||||||
callUrl: React.PropTypes.string,
|
callUrl: React.PropTypes.string,
|
||||||
userProfile: React.PropTypes.object,
|
userProfile: React.PropTypes.object,
|
||||||
showTabButtons: React.PropTypes.bool,
|
showTabButtons: React.PropTypes.bool,
|
||||||
|
selectedTab: React.PropTypes.string,
|
||||||
|
dispatcher: React.PropTypes.instanceOf(loop.Dispatcher).isRequired,
|
||||||
|
roomListStore:
|
||||||
|
React.PropTypes.instanceOf(loop.store.RoomListStore).isRequired
|
||||||
},
|
},
|
||||||
|
|
||||||
getInitialState: function() {
|
getInitialState: function() {
|
||||||
|
@ -498,6 +633,22 @@ loop.panel = (function(_, mozL10n) {
|
||||||
this.updateServiceErrors();
|
this.updateServiceErrors();
|
||||||
},
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The rooms feature is hidden by default for now. Once it gets mainstream,
|
||||||
|
* this method can be safely removed.
|
||||||
|
*/
|
||||||
|
_renderRoomsTab: function() {
|
||||||
|
if (!navigator.mozLoop.getLoopBoolPref("rooms.enabled")) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return (
|
||||||
|
<Tab name="rooms">
|
||||||
|
<RoomList dispatcher={this.props.dispatcher}
|
||||||
|
store={this.props.roomListStore} />
|
||||||
|
</Tab>
|
||||||
|
);
|
||||||
|
},
|
||||||
|
|
||||||
startForm: function(name, contact) {
|
startForm: function(name, contact) {
|
||||||
this.refs[name].initForm(contact);
|
this.refs[name].initForm(contact);
|
||||||
this.selectTab(name);
|
this.selectTab(name);
|
||||||
|
@ -527,7 +678,8 @@ loop.panel = (function(_, mozL10n) {
|
||||||
<div>
|
<div>
|
||||||
<NotificationListView notifications={this.props.notifications}
|
<NotificationListView notifications={this.props.notifications}
|
||||||
clearOnDocumentHidden={true} />
|
clearOnDocumentHidden={true} />
|
||||||
<TabView ref="tabView" buttonsHidden={!this.state.userProfile && !this.props.showTabButtons}>
|
<TabView ref="tabView" selectedTab={this.props.selectedTab}
|
||||||
|
buttonsHidden={!this.state.userProfile && !this.props.showTabButtons}>
|
||||||
<Tab name="call">
|
<Tab name="call">
|
||||||
<div className="content-area">
|
<div className="content-area">
|
||||||
<CallUrlResult client={this.props.client}
|
<CallUrlResult client={this.props.client}
|
||||||
|
@ -536,6 +688,7 @@ loop.panel = (function(_, mozL10n) {
|
||||||
<ToSView />
|
<ToSView />
|
||||||
</div>
|
</div>
|
||||||
</Tab>
|
</Tab>
|
||||||
|
{this._renderRoomsTab()}
|
||||||
<Tab name="contacts">
|
<Tab name="contacts">
|
||||||
<ContactsList selectTab={this.selectTab}
|
<ContactsList selectTab={this.selectTab}
|
||||||
startForm={this.startForm} />
|
startForm={this.startForm} />
|
||||||
|
@ -575,11 +728,19 @@ loop.panel = (function(_, mozL10n) {
|
||||||
mozL10n.initialize(navigator.mozLoop);
|
mozL10n.initialize(navigator.mozLoop);
|
||||||
|
|
||||||
var client = new loop.Client();
|
var client = new loop.Client();
|
||||||
var notifications = new sharedModels.NotificationCollection()
|
var notifications = new sharedModels.NotificationCollection();
|
||||||
|
var dispatcher = new loop.Dispatcher();
|
||||||
|
var roomListStore = new loop.store.RoomListStore({
|
||||||
|
mozLoop: navigator.mozLoop,
|
||||||
|
dispatcher: dispatcher
|
||||||
|
});
|
||||||
|
|
||||||
React.renderComponent(<PanelView
|
React.renderComponent(<PanelView
|
||||||
client={client}
|
client={client}
|
||||||
notifications={notifications} />, document.querySelector("#main"));
|
notifications={notifications}
|
||||||
|
roomListStore={roomListStore}
|
||||||
|
dispatcher={dispatcher}
|
||||||
|
/>, document.querySelector("#main"));
|
||||||
|
|
||||||
document.body.classList.add(loop.shared.utils.getTargetPlatform());
|
document.body.classList.add(loop.shared.utils.getTargetPlatform());
|
||||||
document.body.setAttribute("dir", mozL10n.getDirection());
|
document.body.setAttribute("dir", mozL10n.getDirection());
|
||||||
|
@ -597,6 +758,7 @@ loop.panel = (function(_, mozL10n) {
|
||||||
AvailabilityDropdown: AvailabilityDropdown,
|
AvailabilityDropdown: AvailabilityDropdown,
|
||||||
CallUrlResult: CallUrlResult,
|
CallUrlResult: CallUrlResult,
|
||||||
PanelView: PanelView,
|
PanelView: PanelView,
|
||||||
|
RoomList: RoomList,
|
||||||
SettingsDropdown: SettingsDropdown,
|
SettingsDropdown: SettingsDropdown,
|
||||||
ToSView: ToSView
|
ToSView: ToSView
|
||||||
};
|
};
|
||||||
|
|
|
@ -25,6 +25,10 @@
|
||||||
<script type="text/javascript" src="loop/shared/js/models.js"></script>
|
<script type="text/javascript" src="loop/shared/js/models.js"></script>
|
||||||
<script type="text/javascript" src="loop/shared/js/mixins.js"></script>
|
<script type="text/javascript" src="loop/shared/js/mixins.js"></script>
|
||||||
<script type="text/javascript" src="loop/shared/js/views.js"></script>
|
<script type="text/javascript" src="loop/shared/js/views.js"></script>
|
||||||
|
<script type="text/javascript" src="loop/shared/js/validate.js"></script>
|
||||||
|
<script type="text/javascript" src="loop/shared/js/actions.js"></script>
|
||||||
|
<script type="text/javascript" src="loop/shared/js/dispatcher.js"></script>
|
||||||
|
<script type="text/javascript" src="loop/shared/js/roomListStore.js"></script>
|
||||||
<script type="text/javascript" src="loop/js/client.js"></script>
|
<script type="text/javascript" src="loop/js/client.js"></script>
|
||||||
<script type="text/javascript;version=1.8" src="loop/js/contacts.js"></script>
|
<script type="text/javascript;version=1.8" src="loop/js/contacts.js"></script>
|
||||||
<script type="text/javascript" src="loop/js/panel.js"></script>
|
<script type="text/javascript" src="loop/js/panel.js"></script>
|
||||||
|
|
|
@ -123,6 +123,70 @@ body {
|
||||||
box-shadow: 0 0 4px #c43c3e;
|
box-shadow: 0 0 4px #c43c3e;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Rooms */
|
||||||
|
.room-list {
|
||||||
|
background: #f5f5f5;
|
||||||
|
}
|
||||||
|
|
||||||
|
.room-list > h1 {
|
||||||
|
font-weight: bold;
|
||||||
|
color: #999;
|
||||||
|
padding: .5rem 1rem;
|
||||||
|
border-bottom: 1px solid #ddd;
|
||||||
|
}
|
||||||
|
|
||||||
|
.room-list > .room-entry {
|
||||||
|
padding: 1rem 1rem 0 .5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.room-list > .room-entry > h2 {
|
||||||
|
font-size: .85rem;
|
||||||
|
color: #777;
|
||||||
|
}
|
||||||
|
|
||||||
|
.room-list > .room-entry.room-active > h2 {
|
||||||
|
font-weight: bold;
|
||||||
|
color: #000;
|
||||||
|
}
|
||||||
|
|
||||||
|
.room-list > .room-entry > h2 > .room-notification {
|
||||||
|
display: inline-block;
|
||||||
|
background: transparent;
|
||||||
|
width: 8px;
|
||||||
|
height: 8px;
|
||||||
|
border-radius: 50%;
|
||||||
|
margin-right: .3rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.room-list > .room-entry.room-active > h2 > .room-notification {
|
||||||
|
background-color: #00a0ec;
|
||||||
|
}
|
||||||
|
|
||||||
|
.room-list > .room-entry:hover {
|
||||||
|
background: #f1f1f1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.room-list > .room-entry:not(:last-child) {
|
||||||
|
border-bottom: 1px solid #ddd;
|
||||||
|
}
|
||||||
|
|
||||||
|
.room-list > .room-entry > p {
|
||||||
|
margin: 0;
|
||||||
|
padding: .2em 0 1rem .8rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.room-list > .room-entry > p > a {
|
||||||
|
color: #777;
|
||||||
|
opacity: .5;
|
||||||
|
transition: opacity .1s ease-in-out 0s;
|
||||||
|
text-decoration: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.room-list > .room-entry > p > a:hover {
|
||||||
|
opacity: 1;
|
||||||
|
text-decoration: underline;
|
||||||
|
}
|
||||||
|
|
||||||
/* Buttons */
|
/* Buttons */
|
||||||
|
|
||||||
.button-group {
|
.button-group {
|
||||||
|
|
|
@ -118,6 +118,13 @@ loop.shared.actions = (function() {
|
||||||
type: String,
|
type: String,
|
||||||
// Whether or not to enable the stream.
|
// Whether or not to enable the stream.
|
||||||
enabled: Boolean
|
enabled: Boolean
|
||||||
|
}),
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieves room list.
|
||||||
|
* XXX: should move to some roomActions module - refs bug 1079284
|
||||||
|
*/
|
||||||
|
GetAllRooms: Action.define("getAllRooms", {
|
||||||
})
|
})
|
||||||
};
|
};
|
||||||
})();
|
})();
|
||||||
|
|
|
@ -5,8 +5,9 @@
|
||||||
/* global loop:true */
|
/* global loop:true */
|
||||||
|
|
||||||
var loop = loop || {};
|
var loop = loop || {};
|
||||||
loop.store = (function() {
|
loop.store = loop.store || {};
|
||||||
|
|
||||||
|
loop.store.ConversationStore = (function() {
|
||||||
var sharedActions = loop.shared.actions;
|
var sharedActions = loop.shared.actions;
|
||||||
var CALL_TYPES = loop.shared.utils.CALL_TYPES;
|
var CALL_TYPES = loop.shared.utils.CALL_TYPES;
|
||||||
|
|
||||||
|
@ -14,7 +15,7 @@ loop.store = (function() {
|
||||||
* Websocket states taken from:
|
* Websocket states taken from:
|
||||||
* https://docs.services.mozilla.com/loop/apis.html#call-progress-state-change-progress
|
* https://docs.services.mozilla.com/loop/apis.html#call-progress-state-change-progress
|
||||||
*/
|
*/
|
||||||
var WS_STATES = {
|
var WS_STATES = loop.store.WS_STATES = {
|
||||||
// The call is starting, and the remote party is not yet being alerted.
|
// The call is starting, and the remote party is not yet being alerted.
|
||||||
INIT: "init",
|
INIT: "init",
|
||||||
// The called party is being alerted.
|
// The called party is being alerted.
|
||||||
|
@ -31,7 +32,7 @@ loop.store = (function() {
|
||||||
CONNECTED: "connected"
|
CONNECTED: "connected"
|
||||||
};
|
};
|
||||||
|
|
||||||
var CALL_STATES = {
|
var CALL_STATES = loop.store.CALL_STATES = {
|
||||||
// The initial state of the view.
|
// The initial state of the view.
|
||||||
INIT: "cs-init",
|
INIT: "cs-init",
|
||||||
// The store is gathering the call data from the server.
|
// The store is gathering the call data from the server.
|
||||||
|
@ -52,7 +53,6 @@ loop.store = (function() {
|
||||||
TERMINATED: "cs-terminated"
|
TERMINATED: "cs-terminated"
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
var ConversationStore = Backbone.Model.extend({
|
var ConversationStore = Backbone.Model.extend({
|
||||||
defaults: {
|
defaults: {
|
||||||
// The current state of the call
|
// The current state of the call
|
||||||
|
@ -402,9 +402,5 @@ loop.store = (function() {
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
return {
|
return ConversationStore;
|
||||||
CALL_STATES: CALL_STATES,
|
|
||||||
ConversationStore: ConversationStore,
|
|
||||||
WS_STATES: WS_STATES
|
|
||||||
};
|
|
||||||
})();
|
})();
|
||||||
|
|
|
@ -0,0 +1,171 @@
|
||||||
|
/* 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/. */
|
||||||
|
|
||||||
|
/* global loop:true */
|
||||||
|
|
||||||
|
var loop = loop || {};
|
||||||
|
loop.store = loop.store || {};
|
||||||
|
|
||||||
|
(function() {
|
||||||
|
"use strict";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Room validation schema. See validate.js.
|
||||||
|
* @type {Object}
|
||||||
|
*/
|
||||||
|
var roomSchema = {
|
||||||
|
roomToken: String,
|
||||||
|
roomUrl: String,
|
||||||
|
roomName: String,
|
||||||
|
maxSize: Number,
|
||||||
|
currSize: Number,
|
||||||
|
ctime: Number
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Temporary sample raw room list data.
|
||||||
|
* XXX Should be removed when we plug the real mozLoop API for rooms.
|
||||||
|
* See bug 1074664.
|
||||||
|
* @type {Array}
|
||||||
|
*/
|
||||||
|
var temporaryRawRoomList = [{
|
||||||
|
roomToken: "_nxD4V4FflQ",
|
||||||
|
roomUrl: "http://sample/_nxD4V4FflQ",
|
||||||
|
roomName: "First Room Name",
|
||||||
|
maxSize: 2,
|
||||||
|
currSize: 0,
|
||||||
|
ctime: 1405517546
|
||||||
|
}, {
|
||||||
|
roomToken: "QzBbvGmIZWU",
|
||||||
|
roomUrl: "http://sample/QzBbvGmIZWU",
|
||||||
|
roomName: "Second Room Name",
|
||||||
|
maxSize: 2,
|
||||||
|
currSize: 0,
|
||||||
|
ctime: 1405517418
|
||||||
|
}, {
|
||||||
|
roomToken: "3jKS_Els9IU",
|
||||||
|
roomUrl: "http://sample/3jKS_Els9IU",
|
||||||
|
roomName: "Third Room Name",
|
||||||
|
maxSize: 3,
|
||||||
|
clientMaxSize: 2,
|
||||||
|
currSize: 1,
|
||||||
|
ctime: 1405518241
|
||||||
|
}];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Room type. Basically acts as a typed object constructor.
|
||||||
|
*
|
||||||
|
* @param {Object} values Room property values.
|
||||||
|
*/
|
||||||
|
function Room(values) {
|
||||||
|
var validatedData = new loop.validate.Validator(roomSchema || {})
|
||||||
|
.validate(values || {});
|
||||||
|
for (var prop in validatedData) {
|
||||||
|
this[prop] = validatedData[prop];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
loop.store.Room = Room;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Room store.
|
||||||
|
*
|
||||||
|
* Options:
|
||||||
|
* - {loop.Dispatcher} dispatcher The dispatcher for dispatching actions and
|
||||||
|
* registering to consume actions.
|
||||||
|
* - {mozLoop} mozLoop The MozLoop API object.
|
||||||
|
*
|
||||||
|
* @extends {Backbone.Events}
|
||||||
|
* @param {Object} options Options object.
|
||||||
|
*/
|
||||||
|
function RoomListStore(options) {
|
||||||
|
options = options || {};
|
||||||
|
this.storeState = {error: null, rooms: []};
|
||||||
|
|
||||||
|
if (!options.dispatcher) {
|
||||||
|
throw new Error("Missing option dispatcher");
|
||||||
|
}
|
||||||
|
this.dispatcher = options.dispatcher;
|
||||||
|
|
||||||
|
if (!options.mozLoop) {
|
||||||
|
throw new Error("Missing option mozLoop");
|
||||||
|
}
|
||||||
|
this.mozLoop = options.mozLoop;
|
||||||
|
|
||||||
|
this.dispatcher.register(this, [
|
||||||
|
"getAllRooms",
|
||||||
|
"openRoom"
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
RoomListStore.prototype = _.extend({
|
||||||
|
/**
|
||||||
|
* Retrieves current store state.
|
||||||
|
*
|
||||||
|
* @return {Object}
|
||||||
|
*/
|
||||||
|
getStoreState: function() {
|
||||||
|
return this.storeState;
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Updates store states and trigger a "change" event.
|
||||||
|
*
|
||||||
|
* @param {Object} state The new store state.
|
||||||
|
*/
|
||||||
|
setStoreState: function(state) {
|
||||||
|
this.storeState = state;
|
||||||
|
this.trigger("change");
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Proxy to navigator.mozLoop.rooms.getAll.
|
||||||
|
* XXX Could probably be removed when bug 1074664 lands.
|
||||||
|
*
|
||||||
|
* @param {Function} cb Callback(error, roomList)
|
||||||
|
*/
|
||||||
|
_fetchRoomList: function(cb) {
|
||||||
|
// Faking this.mozLoop.rooms until it's available; bug 1074664.
|
||||||
|
if (!this.mozLoop.hasOwnProperty("rooms")) {
|
||||||
|
cb(null, temporaryRawRoomList);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this.mozLoop.rooms.getAll(cb);
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Maps and sorts the raw room list received from the mozLoop API.
|
||||||
|
*
|
||||||
|
* @param {Array} rawRoomList Raw room list.
|
||||||
|
* @return {Array}
|
||||||
|
*/
|
||||||
|
_processRawRoomList: function(rawRoomList) {
|
||||||
|
if (!rawRoomList) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
return rawRoomList
|
||||||
|
.map(function(rawRoom) {
|
||||||
|
return new Room(rawRoom);
|
||||||
|
})
|
||||||
|
.slice()
|
||||||
|
.sort(function(a, b) {
|
||||||
|
return b.ctime - a.ctime;
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gather the list of all available rooms from the MozLoop API.
|
||||||
|
*/
|
||||||
|
getAllRooms: function() {
|
||||||
|
this._fetchRoomList(function(err, rawRoomList) {
|
||||||
|
this.setStoreState({
|
||||||
|
error: err,
|
||||||
|
rooms: this._processRawRoomList(rawRoomList)
|
||||||
|
});
|
||||||
|
}.bind(this));
|
||||||
|
}
|
||||||
|
}, Backbone.Events);
|
||||||
|
|
||||||
|
loop.store.RoomListStore = RoomListStore;
|
||||||
|
})();
|
|
@ -55,6 +55,7 @@ browser.jar:
|
||||||
# Shared scripts
|
# Shared scripts
|
||||||
content/browser/loop/shared/js/actions.js (content/shared/js/actions.js)
|
content/browser/loop/shared/js/actions.js (content/shared/js/actions.js)
|
||||||
content/browser/loop/shared/js/conversationStore.js (content/shared/js/conversationStore.js)
|
content/browser/loop/shared/js/conversationStore.js (content/shared/js/conversationStore.js)
|
||||||
|
content/browser/loop/shared/js/roomListStore.js (content/shared/js/roomListStore.js)
|
||||||
content/browser/loop/shared/js/dispatcher.js (content/shared/js/dispatcher.js)
|
content/browser/loop/shared/js/dispatcher.js (content/shared/js/dispatcher.js)
|
||||||
content/browser/loop/shared/js/feedbackApiClient.js (content/shared/js/feedbackApiClient.js)
|
content/browser/loop/shared/js/feedbackApiClient.js (content/shared/js/feedbackApiClient.js)
|
||||||
content/browser/loop/shared/js/models.js (content/shared/js/models.js)
|
content/browser/loop/shared/js/models.js (content/shared/js/models.js)
|
||||||
|
|
|
@ -43,6 +43,7 @@
|
||||||
<script src="../../content/shared/js/validate.js"></script>
|
<script src="../../content/shared/js/validate.js"></script>
|
||||||
<script src="../../content/shared/js/dispatcher.js"></script>
|
<script src="../../content/shared/js/dispatcher.js"></script>
|
||||||
<script src="../../content/shared/js/otSdkDriver.js"></script>
|
<script src="../../content/shared/js/otSdkDriver.js"></script>
|
||||||
|
<script src="../../content/shared/js/roomListStore.js"></script>
|
||||||
<script src="../../content/js/client.js"></script>
|
<script src="../../content/js/client.js"></script>
|
||||||
<script src="../../content/js/conversationViews.js"></script>
|
<script src="../../content/js/conversationViews.js"></script>
|
||||||
<script src="../../content/js/conversation.js"></script>
|
<script src="../../content/js/conversation.js"></script>
|
||||||
|
|
|
@ -7,13 +7,14 @@
|
||||||
|
|
||||||
var expect = chai.expect;
|
var expect = chai.expect;
|
||||||
var TestUtils = React.addons.TestUtils;
|
var TestUtils = React.addons.TestUtils;
|
||||||
|
var sharedActions = loop.shared.actions;
|
||||||
|
|
||||||
describe("loop.panel", function() {
|
describe("loop.panel", function() {
|
||||||
"use strict";
|
"use strict";
|
||||||
|
|
||||||
var sandbox, notifications, fakeXHR, requests = [];
|
var sandbox, notifications, fakeXHR, requests = [];
|
||||||
|
|
||||||
beforeEach(function() {
|
beforeEach(function(done) {
|
||||||
sandbox = sinon.sandbox.create();
|
sandbox = sinon.sandbox.create();
|
||||||
fakeXHR = sandbox.useFakeXMLHttpRequest();
|
fakeXHR = sandbox.useFakeXMLHttpRequest();
|
||||||
requests = [];
|
requests = [];
|
||||||
|
@ -32,8 +33,12 @@ describe("loop.panel", function() {
|
||||||
get locale() {
|
get locale() {
|
||||||
return "en-US";
|
return "en-US";
|
||||||
},
|
},
|
||||||
|
getLoopBoolPref: sandbox.stub(),
|
||||||
setLoopCharPref: sandbox.stub(),
|
setLoopCharPref: sandbox.stub(),
|
||||||
getLoopCharPref: sandbox.stub().returns("unseen"),
|
getLoopCharPref: sandbox.stub().returns("unseen"),
|
||||||
|
getPluralForm: function() {
|
||||||
|
return "fakeText";
|
||||||
|
},
|
||||||
copyString: sandbox.stub(),
|
copyString: sandbox.stub(),
|
||||||
noteCallUrlExpiry: sinon.spy(),
|
noteCallUrlExpiry: sinon.spy(),
|
||||||
composeEmail: sinon.spy(),
|
composeEmail: sinon.spy(),
|
||||||
|
@ -47,6 +52,8 @@ describe("loop.panel", function() {
|
||||||
};
|
};
|
||||||
|
|
||||||
document.mozL10n.initialize(navigator.mozLoop);
|
document.mozL10n.initialize(navigator.mozLoop);
|
||||||
|
// XXX prevent a race whenever mozL10n hasn't been initialized yet
|
||||||
|
setTimeout(done, 0);
|
||||||
});
|
});
|
||||||
|
|
||||||
afterEach(function() {
|
afterEach(function() {
|
||||||
|
@ -126,7 +133,7 @@ describe("loop.panel", function() {
|
||||||
});
|
});
|
||||||
|
|
||||||
describe("loop.panel.PanelView", function() {
|
describe("loop.panel.PanelView", function() {
|
||||||
var fakeClient, callUrlData, view, callTab, contactsTab;
|
var fakeClient, dispatcher, roomListStore, callUrlData;
|
||||||
|
|
||||||
beforeEach(function() {
|
beforeEach(function() {
|
||||||
callUrlData = {
|
callUrlData = {
|
||||||
|
@ -140,31 +147,94 @@ describe("loop.panel", function() {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
view = TestUtils.renderIntoDocument(loop.panel.PanelView({
|
dispatcher = new loop.Dispatcher();
|
||||||
|
roomListStore = new loop.store.RoomListStore({
|
||||||
|
dispatcher: dispatcher,
|
||||||
|
mozLoop: navigator.mozLoop
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
function createTestPanelView() {
|
||||||
|
return TestUtils.renderIntoDocument(loop.panel.PanelView({
|
||||||
notifications: notifications,
|
notifications: notifications,
|
||||||
client: fakeClient,
|
client: fakeClient,
|
||||||
showTabButtons: true,
|
showTabButtons: true,
|
||||||
|
dispatcher: dispatcher,
|
||||||
|
roomListStore: roomListStore
|
||||||
}));
|
}));
|
||||||
|
}
|
||||||
[callTab, contactsTab] =
|
|
||||||
TestUtils.scryRenderedDOMComponentsWithClass(view, "tab");
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('TabView', function() {
|
describe('TabView', function() {
|
||||||
it("should select contacts tab when clicking tab button", function() {
|
var view, callTab, roomsTab, contactsTab;
|
||||||
TestUtils.Simulate.click(
|
|
||||||
view.getDOMNode().querySelector('li[data-tab-name="contacts"]'));
|
|
||||||
|
|
||||||
expect(contactsTab.getDOMNode().classList.contains("selected"))
|
describe("loop.rooms.enabled on", function() {
|
||||||
.to.be.true;
|
beforeEach(function() {
|
||||||
|
navigator.mozLoop.getLoopBoolPref = function(pref) {
|
||||||
|
if (pref === "rooms.enabled") {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
view = createTestPanelView();
|
||||||
|
|
||||||
|
[callTab, roomsTab, contactsTab] =
|
||||||
|
TestUtils.scryRenderedDOMComponentsWithClass(view, "tab");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should select contacts tab when clicking tab button", function() {
|
||||||
|
TestUtils.Simulate.click(
|
||||||
|
view.getDOMNode().querySelector("li[data-tab-name=\"contacts\"]"));
|
||||||
|
|
||||||
|
expect(contactsTab.getDOMNode().classList.contains("selected"))
|
||||||
|
.to.be.true;
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should select rooms tab when clicking tab button", function() {
|
||||||
|
TestUtils.Simulate.click(
|
||||||
|
view.getDOMNode().querySelector("li[data-tab-name=\"rooms\"]"));
|
||||||
|
|
||||||
|
expect(roomsTab.getDOMNode().classList.contains("selected"))
|
||||||
|
.to.be.true;
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should select call tab when clicking tab button", function() {
|
||||||
|
TestUtils.Simulate.click(
|
||||||
|
view.getDOMNode().querySelector("li[data-tab-name=\"call\"]"));
|
||||||
|
|
||||||
|
expect(callTab.getDOMNode().classList.contains("selected"))
|
||||||
|
.to.be.true;
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
it("should select call tab when clicking tab button", function() {
|
describe("loop.rooms.enabled off", function() {
|
||||||
TestUtils.Simulate.click(
|
beforeEach(function() {
|
||||||
view.getDOMNode().querySelector('li[data-tab-name="call"]'));
|
navigator.mozLoop.getLoopBoolPref = function(pref) {
|
||||||
|
if (pref === "rooms.enabled") {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
expect(callTab.getDOMNode().classList.contains("selected"))
|
view = createTestPanelView();
|
||||||
.to.be.true;
|
|
||||||
|
[callTab, contactsTab] =
|
||||||
|
TestUtils.scryRenderedDOMComponentsWithClass(view, "tab");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should select contacts tab when clicking tab button", function() {
|
||||||
|
TestUtils.Simulate.click(
|
||||||
|
view.getDOMNode().querySelector("li[data-tab-name=\"contacts\"]"));
|
||||||
|
|
||||||
|
expect(contactsTab.getDOMNode().classList.contains("selected"))
|
||||||
|
.to.be.true;
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should select call tab when clicking tab button", function() {
|
||||||
|
TestUtils.Simulate.click(
|
||||||
|
view.getDOMNode().querySelector("li[data-tab-name=\"call\"]"));
|
||||||
|
|
||||||
|
expect(callTab.getDOMNode().classList.contains("selected"))
|
||||||
|
.to.be.true;
|
||||||
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -174,6 +244,8 @@ describe("loop.panel", function() {
|
||||||
navigator.mozLoop.loggedInToFxA = false;
|
navigator.mozLoop.loggedInToFxA = false;
|
||||||
navigator.mozLoop.logInToFxA = sandbox.stub();
|
navigator.mozLoop.logInToFxA = sandbox.stub();
|
||||||
|
|
||||||
|
var view = createTestPanelView();
|
||||||
|
|
||||||
TestUtils.Simulate.click(
|
TestUtils.Simulate.click(
|
||||||
view.getDOMNode().querySelector(".signin-link a"));
|
view.getDOMNode().querySelector(".signin-link a"));
|
||||||
|
|
||||||
|
@ -193,8 +265,6 @@ describe("loop.panel", function() {
|
||||||
});
|
});
|
||||||
|
|
||||||
describe("SettingsDropdown", function() {
|
describe("SettingsDropdown", function() {
|
||||||
var view;
|
|
||||||
|
|
||||||
beforeEach(function() {
|
beforeEach(function() {
|
||||||
navigator.mozLoop.logInToFxA = sandbox.stub();
|
navigator.mozLoop.logInToFxA = sandbox.stub();
|
||||||
navigator.mozLoop.logOutFromFxA = sandbox.stub();
|
navigator.mozLoop.logOutFromFxA = sandbox.stub();
|
||||||
|
@ -288,6 +358,8 @@ describe("loop.panel", function() {
|
||||||
|
|
||||||
describe("#render", function() {
|
describe("#render", function() {
|
||||||
it("should render a ToSView", function() {
|
it("should render a ToSView", function() {
|
||||||
|
var view = createTestPanelView();
|
||||||
|
|
||||||
TestUtils.findRenderedComponentWithType(view, loop.panel.ToSView);
|
TestUtils.findRenderedComponentWithType(view, loop.panel.ToSView);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@ -550,6 +622,34 @@ describe("loop.panel", function() {
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe("loop.panel.RoomList", function() {
|
||||||
|
var roomListStore, dispatcher;
|
||||||
|
|
||||||
|
beforeEach(function() {
|
||||||
|
dispatcher = new loop.Dispatcher();
|
||||||
|
roomListStore = new loop.store.RoomListStore({
|
||||||
|
dispatcher: dispatcher,
|
||||||
|
mozLoop: navigator.mozLoop
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
function createTestComponent() {
|
||||||
|
return TestUtils.renderIntoDocument(loop.panel.RoomList({
|
||||||
|
store: roomListStore,
|
||||||
|
dispatcher: dispatcher
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
it("should dispatch a GetAllRooms action on mount", function() {
|
||||||
|
var dispatch = sandbox.stub(dispatcher, "dispatch");
|
||||||
|
|
||||||
|
createTestComponent();
|
||||||
|
|
||||||
|
sinon.assert.calledOnce(dispatch);
|
||||||
|
sinon.assert.calledWithExactly(dispatch, new sharedActions.GetAllRooms());
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
describe('loop.panel.ToSView', function() {
|
describe('loop.panel.ToSView', function() {
|
||||||
|
|
||||||
it("should render when the value of loop.seenToS is not set", function() {
|
it("should render when the value of loop.seenToS is not set", function() {
|
||||||
|
|
|
@ -131,12 +131,12 @@ class Test1BrowserCall(MarionetteTestCase):
|
||||||
self.marionette.set_context("chrome")
|
self.marionette.set_context("chrome")
|
||||||
button = self.marionette.find_element(By.CLASS_NAME, "btn-hangup")
|
button = self.marionette.find_element(By.CLASS_NAME, "btn-hangup")
|
||||||
|
|
||||||
# XXX For whatever reason, the click doesn't take effect unless we
|
# XXX bug 1080095 For whatever reason, the click doesn't take effect
|
||||||
# wait for a bit (even if we wait for the element to actually be
|
# unless we wait for a bit (even if we wait for the element to
|
||||||
# displayed first, which we're not currently bothering with). It's
|
# actually be displayed first, which we're not currently bothering
|
||||||
# not entirely clear whether the click is being delivered in this case,
|
# with). It's not entirely clear whether the click is being
|
||||||
# or whether there's a Marionette bug here.
|
# delivered in this case, or whether there's a Marionette bug here.
|
||||||
sleep(2)
|
sleep(5)
|
||||||
button.click()
|
button.click()
|
||||||
|
|
||||||
# check that the feedback form is displayed
|
# check that the feedback form is displayed
|
||||||
|
|
|
@ -44,6 +44,7 @@
|
||||||
<script src="../../content/shared/js/dispatcher.js"></script>
|
<script src="../../content/shared/js/dispatcher.js"></script>
|
||||||
<script src="../../content/shared/js/otSdkDriver.js"></script>
|
<script src="../../content/shared/js/otSdkDriver.js"></script>
|
||||||
<script src="../../content/shared/js/conversationStore.js"></script>
|
<script src="../../content/shared/js/conversationStore.js"></script>
|
||||||
|
<script src="../../content/shared/js/roomListStore.js"></script>
|
||||||
|
|
||||||
<!-- Test scripts -->
|
<!-- Test scripts -->
|
||||||
<script src="models_test.js"></script>
|
<script src="models_test.js"></script>
|
||||||
|
@ -56,6 +57,7 @@
|
||||||
<script src="dispatcher_test.js"></script>
|
<script src="dispatcher_test.js"></script>
|
||||||
<script src="conversationStore_test.js"></script>
|
<script src="conversationStore_test.js"></script>
|
||||||
<script src="otSdkDriver_test.js"></script>
|
<script src="otSdkDriver_test.js"></script>
|
||||||
|
<script src="roomListStore_test.js"></script>
|
||||||
<script>
|
<script>
|
||||||
mocha.run(function () {
|
mocha.run(function () {
|
||||||
$("#mocha").append("<p id='complete'>Complete.</p>");
|
$("#mocha").append("<p id='complete'>Complete.</p>");
|
||||||
|
|
|
@ -0,0 +1,130 @@
|
||||||
|
/* Any copyright is dedicated to the Public Domain.
|
||||||
|
* http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||||
|
|
||||||
|
var expect = chai.expect;
|
||||||
|
|
||||||
|
describe("loop.store.Room", function () {
|
||||||
|
"use strict";
|
||||||
|
describe("#constructor", function() {
|
||||||
|
it("should validate room values", function() {
|
||||||
|
expect(function() {
|
||||||
|
new loop.store.Room();
|
||||||
|
}).to.Throw(Error, /missing required/);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("loop.store.RoomListStore", function () {
|
||||||
|
"use strict";
|
||||||
|
|
||||||
|
var sharedActions = loop.shared.actions;
|
||||||
|
var sandbox, dispatcher;
|
||||||
|
|
||||||
|
beforeEach(function() {
|
||||||
|
sandbox = sinon.sandbox.create();
|
||||||
|
dispatcher = new loop.Dispatcher();
|
||||||
|
});
|
||||||
|
|
||||||
|
afterEach(function() {
|
||||||
|
sandbox.restore();
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("#constructor", function() {
|
||||||
|
it("should throw an error if the dispatcher is missing", function() {
|
||||||
|
expect(function() {
|
||||||
|
new loop.store.RoomListStore({mozLoop: {}});
|
||||||
|
}).to.Throw(/dispatcher/);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should throw an error if mozLoop is missing", function() {
|
||||||
|
expect(function() {
|
||||||
|
new loop.store.RoomListStore({dispatcher: dispatcher});
|
||||||
|
}).to.Throw(/mozLoop/);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("#getAllRooms", function() {
|
||||||
|
var store, fakeMozLoop;
|
||||||
|
var fakeRoomList = [{
|
||||||
|
roomToken: "_nxD4V4FflQ",
|
||||||
|
roomUrl: "http://sample/_nxD4V4FflQ",
|
||||||
|
roomName: "First Room Name",
|
||||||
|
maxSize: 2,
|
||||||
|
currSize: 0,
|
||||||
|
ctime: 1405517546
|
||||||
|
}, {
|
||||||
|
roomToken: "QzBbvGmIZWU",
|
||||||
|
roomUrl: "http://sample/QzBbvGmIZWU",
|
||||||
|
roomName: "Second Room Name",
|
||||||
|
maxSize: 2,
|
||||||
|
currSize: 0,
|
||||||
|
ctime: 1405517418
|
||||||
|
}, {
|
||||||
|
roomToken: "3jKS_Els9IU",
|
||||||
|
roomUrl: "http://sample/3jKS_Els9IU",
|
||||||
|
roomName: "Third Room Name",
|
||||||
|
maxSize: 3,
|
||||||
|
clientMaxSize: 2,
|
||||||
|
currSize: 1,
|
||||||
|
ctime: 1405518241
|
||||||
|
}];
|
||||||
|
|
||||||
|
beforeEach(function() {
|
||||||
|
fakeMozLoop = {
|
||||||
|
rooms: {
|
||||||
|
getAll: function(cb) {
|
||||||
|
cb(null, fakeRoomList);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
store = new loop.store.RoomListStore({
|
||||||
|
dispatcher: dispatcher,
|
||||||
|
mozLoop: fakeMozLoop
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should trigger a list:changed event", function(done) {
|
||||||
|
store.on("change", function() {
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
|
||||||
|
dispatcher.dispatch(new sharedActions.GetAllRooms());
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should fetch the room list from the mozLoop API", function(done) {
|
||||||
|
store.once("change", function() {
|
||||||
|
expect(store.getStoreState().error).to.be.a.null;
|
||||||
|
expect(store.getStoreState().rooms).to.have.length.of(3);
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
|
||||||
|
dispatcher.dispatch(new sharedActions.GetAllRooms());
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should order the room list using ctime desc", function(done) {
|
||||||
|
store.once("change", function() {
|
||||||
|
var storeState = store.getStoreState();
|
||||||
|
expect(storeState.error).to.be.a.null;
|
||||||
|
expect(storeState.rooms[0].ctime).eql(1405518241);
|
||||||
|
expect(storeState.rooms[1].ctime).eql(1405517546);
|
||||||
|
expect(storeState.rooms[2].ctime).eql(1405517418);
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
|
||||||
|
dispatcher.dispatch(new sharedActions.GetAllRooms());
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should report an error", function() {
|
||||||
|
fakeMozLoop.rooms.getAll = function(cb) {
|
||||||
|
cb("fakeError");
|
||||||
|
};
|
||||||
|
|
||||||
|
store.once("change", function() {
|
||||||
|
var storeState = store.getStoreState();
|
||||||
|
expect(storeState.error).eql("fakeError");
|
||||||
|
});
|
||||||
|
|
||||||
|
dispatcher.dispatch(new sharedActions.GetAllRooms());
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
|
@ -9,7 +9,12 @@
|
||||||
navigator.mozLoop = {
|
navigator.mozLoop = {
|
||||||
ensureRegistered: function() {},
|
ensureRegistered: function() {},
|
||||||
getLoopCharPref: function() {},
|
getLoopCharPref: function() {},
|
||||||
getLoopBoolPref: function() {},
|
getLoopBoolPref: function(pref) {
|
||||||
|
// Ensure UI for rooms is displayed in the showcase.
|
||||||
|
if (pref === "rooms.enabled") {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
},
|
||||||
releaseCallData: function() {},
|
releaseCallData: function() {},
|
||||||
contacts: {
|
contacts: {
|
||||||
getAll: function(callback) {
|
getAll: function(callback) {
|
||||||
|
|
|
@ -38,7 +38,10 @@
|
||||||
<script src="../content/shared/js/mixins.js"></script>
|
<script src="../content/shared/js/mixins.js"></script>
|
||||||
<script src="../content/shared/js/views.js"></script>
|
<script src="../content/shared/js/views.js"></script>
|
||||||
<script src="../content/shared/js/websocket.js"></script>
|
<script src="../content/shared/js/websocket.js"></script>
|
||||||
|
<script src="../content/shared/js/validate.js"></script>
|
||||||
|
<script src="../content/shared/js/dispatcher.js"></script>
|
||||||
<script src="../content/shared/js/conversationStore.js"></script>
|
<script src="../content/shared/js/conversationStore.js"></script>
|
||||||
|
<script src="../content/shared/js/roomListStore.js"></script>
|
||||||
<script src="../content/js/conversationViews.js"></script>
|
<script src="../content/js/conversationViews.js"></script>
|
||||||
<script src="../content/js/client.js"></script>
|
<script src="../content/js/client.js"></script>
|
||||||
<script src="../standalone/content/js/webapp.js"></script>
|
<script src="../standalone/content/js/webapp.js"></script>
|
||||||
|
|
|
@ -69,9 +69,16 @@
|
||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
border-bottom: 1px dashed #aaa;
|
border-bottom: 1px dashed #aaa;
|
||||||
margin: 1em 0;
|
margin: 1em 0;
|
||||||
|
margin-top: -14em;
|
||||||
|
padding-top: 14em;
|
||||||
text-align: left;
|
text-align: left;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.showcase > section .example > h3 a {
|
||||||
|
text-decoration: none;
|
||||||
|
color: #555;
|
||||||
|
}
|
||||||
|
|
||||||
.showcase p.note {
|
.showcase p.note {
|
||||||
margin: 0;
|
margin: 0;
|
||||||
padding: 0;
|
padding: 0;
|
||||||
|
|
|
@ -56,6 +56,12 @@
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
|
var dispatcher = new loop.Dispatcher();
|
||||||
|
var roomListStore = new loop.store.RoomListStore({
|
||||||
|
dispatcher: dispatcher,
|
||||||
|
mozLoop: {}
|
||||||
|
});
|
||||||
|
|
||||||
// Local mocks
|
// Local mocks
|
||||||
|
|
||||||
var mockContact = {
|
var mockContact = {
|
||||||
|
@ -93,11 +99,18 @@
|
||||||
});
|
});
|
||||||
|
|
||||||
var Example = React.createClass({displayName: 'Example',
|
var Example = React.createClass({displayName: 'Example',
|
||||||
|
makeId: function(prefix) {
|
||||||
|
return (prefix || "") + this.props.summary.toLowerCase().replace(/\s/g, "-");
|
||||||
|
},
|
||||||
|
|
||||||
render: function() {
|
render: function() {
|
||||||
var cx = React.addons.classSet;
|
var cx = React.addons.classSet;
|
||||||
return (
|
return (
|
||||||
React.DOM.div({className: "example"},
|
React.DOM.div({className: "example"},
|
||||||
React.DOM.h3(null, this.props.summary),
|
React.DOM.h3({id: this.makeId()},
|
||||||
|
this.props.summary,
|
||||||
|
React.DOM.a({href: this.makeId("#")}, " ¶")
|
||||||
|
),
|
||||||
React.DOM.div({className: cx({comp: true, dashed: this.props.dashed}),
|
React.DOM.div({className: cx({comp: true, dashed: this.props.dashed}),
|
||||||
style: this.props.style || {}},
|
style: this.props.style || {}},
|
||||||
this.props.children
|
this.props.children
|
||||||
|
@ -150,26 +163,45 @@
|
||||||
),
|
),
|
||||||
Example({summary: "Call URL retrieved", dashed: "true", style: {width: "332px"}},
|
Example({summary: "Call URL retrieved", dashed: "true", style: {width: "332px"}},
|
||||||
PanelView({client: mockClient, notifications: notifications,
|
PanelView({client: mockClient, notifications: notifications,
|
||||||
callUrl: "http://invalid.example.url/"})
|
callUrl: "http://invalid.example.url/",
|
||||||
|
dispatcher: dispatcher,
|
||||||
|
roomListStore: roomListStore})
|
||||||
),
|
),
|
||||||
Example({summary: "Call URL retrieved - authenticated", dashed: "true", style: {width: "332px"}},
|
Example({summary: "Call URL retrieved - authenticated", dashed: "true", style: {width: "332px"}},
|
||||||
PanelView({client: mockClient, notifications: notifications,
|
PanelView({client: mockClient, notifications: notifications,
|
||||||
callUrl: "http://invalid.example.url/",
|
callUrl: "http://invalid.example.url/",
|
||||||
userProfile: {email: "test@example.com"}})
|
userProfile: {email: "test@example.com"},
|
||||||
|
dispatcher: dispatcher,
|
||||||
|
roomListStore: roomListStore})
|
||||||
),
|
),
|
||||||
Example({summary: "Pending call url retrieval", dashed: "true", style: {width: "332px"}},
|
Example({summary: "Pending call url retrieval", dashed: "true", style: {width: "332px"}},
|
||||||
PanelView({client: mockClient, notifications: notifications})
|
PanelView({client: mockClient, notifications: notifications,
|
||||||
|
dispatcher: dispatcher,
|
||||||
|
roomListStore: roomListStore})
|
||||||
),
|
),
|
||||||
Example({summary: "Pending call url retrieval - authenticated", dashed: "true", style: {width: "332px"}},
|
Example({summary: "Pending call url retrieval - authenticated", dashed: "true", style: {width: "332px"}},
|
||||||
PanelView({client: mockClient, notifications: notifications,
|
PanelView({client: mockClient, notifications: notifications,
|
||||||
userProfile: {email: "test@example.com"}})
|
userProfile: {email: "test@example.com"},
|
||||||
|
dispatcher: dispatcher,
|
||||||
|
roomListStore: roomListStore})
|
||||||
),
|
),
|
||||||
Example({summary: "Error Notification", dashed: "true", style: {width: "332px"}},
|
Example({summary: "Error Notification", dashed: "true", style: {width: "332px"}},
|
||||||
PanelView({client: mockClient, notifications: errNotifications})
|
PanelView({client: mockClient, notifications: errNotifications,
|
||||||
|
dispatcher: dispatcher,
|
||||||
|
roomListStore: roomListStore})
|
||||||
),
|
),
|
||||||
Example({summary: "Error Notification - authenticated", dashed: "true", style: {width: "332px"}},
|
Example({summary: "Error Notification - authenticated", dashed: "true", style: {width: "332px"}},
|
||||||
PanelView({client: mockClient, notifications: errNotifications,
|
PanelView({client: mockClient, notifications: errNotifications,
|
||||||
userProfile: {email: "test@example.com"}})
|
userProfile: {email: "test@example.com"},
|
||||||
|
dispatcher: dispatcher,
|
||||||
|
roomListStore: roomListStore})
|
||||||
|
),
|
||||||
|
Example({summary: "Room list tab", dashed: "true", style: {width: "332px"}},
|
||||||
|
PanelView({client: mockClient, notifications: notifications,
|
||||||
|
userProfile: {email: "test@example.com"},
|
||||||
|
dispatcher: dispatcher,
|
||||||
|
roomListStore: roomListStore,
|
||||||
|
selectedTab: "rooms"})
|
||||||
)
|
)
|
||||||
),
|
),
|
||||||
|
|
||||||
|
@ -247,12 +279,15 @@
|
||||||
Section({name: "PendingConversationView"},
|
Section({name: "PendingConversationView"},
|
||||||
Example({summary: "Pending conversation view (connecting)", dashed: "true"},
|
Example({summary: "Pending conversation view (connecting)", dashed: "true"},
|
||||||
React.DOM.div({className: "standalone"},
|
React.DOM.div({className: "standalone"},
|
||||||
PendingConversationView({websocket: mockWebSocket})
|
PendingConversationView({websocket: mockWebSocket,
|
||||||
|
dispatcher: dispatcher})
|
||||||
)
|
)
|
||||||
),
|
),
|
||||||
Example({summary: "Pending conversation view (ringing)", dashed: "true"},
|
Example({summary: "Pending conversation view (ringing)", dashed: "true"},
|
||||||
React.DOM.div({className: "standalone"},
|
React.DOM.div({className: "standalone"},
|
||||||
PendingConversationView({websocket: mockWebSocket, callState: "ringing"})
|
PendingConversationView({websocket: mockWebSocket,
|
||||||
|
dispatcher: dispatcher,
|
||||||
|
callState: "ringing"})
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
),
|
),
|
||||||
|
@ -262,7 +297,8 @@
|
||||||
style: {width: "260px", height: "265px"}},
|
style: {width: "260px", height: "265px"}},
|
||||||
React.DOM.div({className: "fx-embedded"},
|
React.DOM.div({className: "fx-embedded"},
|
||||||
DesktopPendingConversationView({callState: "gather",
|
DesktopPendingConversationView({callState: "gather",
|
||||||
contact: mockContact})
|
contact: mockContact,
|
||||||
|
dispatcher: dispatcher})
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
),
|
),
|
||||||
|
@ -271,7 +307,7 @@
|
||||||
Example({summary: "Call Failed", dashed: "true",
|
Example({summary: "Call Failed", dashed: "true",
|
||||||
style: {width: "260px", height: "265px"}},
|
style: {width: "260px", height: "265px"}},
|
||||||
React.DOM.div({className: "fx-embedded"},
|
React.DOM.div({className: "fx-embedded"},
|
||||||
CallFailedView(null)
|
CallFailedView({dispatcher: dispatcher})
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
),
|
),
|
||||||
|
|
|
@ -56,6 +56,12 @@
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
|
var dispatcher = new loop.Dispatcher();
|
||||||
|
var roomListStore = new loop.store.RoomListStore({
|
||||||
|
dispatcher: dispatcher,
|
||||||
|
mozLoop: {}
|
||||||
|
});
|
||||||
|
|
||||||
// Local mocks
|
// Local mocks
|
||||||
|
|
||||||
var mockContact = {
|
var mockContact = {
|
||||||
|
@ -93,11 +99,18 @@
|
||||||
});
|
});
|
||||||
|
|
||||||
var Example = React.createClass({
|
var Example = React.createClass({
|
||||||
|
makeId: function(prefix) {
|
||||||
|
return (prefix || "") + this.props.summary.toLowerCase().replace(/\s/g, "-");
|
||||||
|
},
|
||||||
|
|
||||||
render: function() {
|
render: function() {
|
||||||
var cx = React.addons.classSet;
|
var cx = React.addons.classSet;
|
||||||
return (
|
return (
|
||||||
<div className="example">
|
<div className="example">
|
||||||
<h3>{this.props.summary}</h3>
|
<h3 id={this.makeId()}>
|
||||||
|
{this.props.summary}
|
||||||
|
<a href={this.makeId("#")}> ¶</a>
|
||||||
|
</h3>
|
||||||
<div className={cx({comp: true, dashed: this.props.dashed})}
|
<div className={cx({comp: true, dashed: this.props.dashed})}
|
||||||
style={this.props.style || {}}>
|
style={this.props.style || {}}>
|
||||||
{this.props.children}
|
{this.props.children}
|
||||||
|
@ -150,26 +163,45 @@
|
||||||
</p>
|
</p>
|
||||||
<Example summary="Call URL retrieved" dashed="true" style={{width: "332px"}}>
|
<Example summary="Call URL retrieved" dashed="true" style={{width: "332px"}}>
|
||||||
<PanelView client={mockClient} notifications={notifications}
|
<PanelView client={mockClient} notifications={notifications}
|
||||||
callUrl="http://invalid.example.url/" />
|
callUrl="http://invalid.example.url/"
|
||||||
|
dispatcher={dispatcher}
|
||||||
|
roomListStore={roomListStore} />
|
||||||
</Example>
|
</Example>
|
||||||
<Example summary="Call URL retrieved - authenticated" dashed="true" style={{width: "332px"}}>
|
<Example summary="Call URL retrieved - authenticated" dashed="true" style={{width: "332px"}}>
|
||||||
<PanelView client={mockClient} notifications={notifications}
|
<PanelView client={mockClient} notifications={notifications}
|
||||||
callUrl="http://invalid.example.url/"
|
callUrl="http://invalid.example.url/"
|
||||||
userProfile={{email: "test@example.com"}} />
|
userProfile={{email: "test@example.com"}}
|
||||||
|
dispatcher={dispatcher}
|
||||||
|
roomListStore={roomListStore} />
|
||||||
</Example>
|
</Example>
|
||||||
<Example summary="Pending call url retrieval" dashed="true" style={{width: "332px"}}>
|
<Example summary="Pending call url retrieval" dashed="true" style={{width: "332px"}}>
|
||||||
<PanelView client={mockClient} notifications={notifications} />
|
<PanelView client={mockClient} notifications={notifications}
|
||||||
|
dispatcher={dispatcher}
|
||||||
|
roomListStore={roomListStore} />
|
||||||
</Example>
|
</Example>
|
||||||
<Example summary="Pending call url retrieval - authenticated" dashed="true" style={{width: "332px"}}>
|
<Example summary="Pending call url retrieval - authenticated" dashed="true" style={{width: "332px"}}>
|
||||||
<PanelView client={mockClient} notifications={notifications}
|
<PanelView client={mockClient} notifications={notifications}
|
||||||
userProfile={{email: "test@example.com"}} />
|
userProfile={{email: "test@example.com"}}
|
||||||
|
dispatcher={dispatcher}
|
||||||
|
roomListStore={roomListStore} />
|
||||||
</Example>
|
</Example>
|
||||||
<Example summary="Error Notification" dashed="true" style={{width: "332px"}}>
|
<Example summary="Error Notification" dashed="true" style={{width: "332px"}}>
|
||||||
<PanelView client={mockClient} notifications={errNotifications}/>
|
<PanelView client={mockClient} notifications={errNotifications}
|
||||||
|
dispatcher={dispatcher}
|
||||||
|
roomListStore={roomListStore} />
|
||||||
</Example>
|
</Example>
|
||||||
<Example summary="Error Notification - authenticated" dashed="true" style={{width: "332px"}}>
|
<Example summary="Error Notification - authenticated" dashed="true" style={{width: "332px"}}>
|
||||||
<PanelView client={mockClient} notifications={errNotifications}
|
<PanelView client={mockClient} notifications={errNotifications}
|
||||||
userProfile={{email: "test@example.com"}} />
|
userProfile={{email: "test@example.com"}}
|
||||||
|
dispatcher={dispatcher}
|
||||||
|
roomListStore={roomListStore} />
|
||||||
|
</Example>
|
||||||
|
<Example summary="Room list tab" dashed="true" style={{width: "332px"}}>
|
||||||
|
<PanelView client={mockClient} notifications={notifications}
|
||||||
|
userProfile={{email: "test@example.com"}}
|
||||||
|
dispatcher={dispatcher}
|
||||||
|
roomListStore={roomListStore}
|
||||||
|
selectedTab="rooms" />
|
||||||
</Example>
|
</Example>
|
||||||
</Section>
|
</Section>
|
||||||
|
|
||||||
|
@ -247,12 +279,15 @@
|
||||||
<Section name="PendingConversationView">
|
<Section name="PendingConversationView">
|
||||||
<Example summary="Pending conversation view (connecting)" dashed="true">
|
<Example summary="Pending conversation view (connecting)" dashed="true">
|
||||||
<div className="standalone">
|
<div className="standalone">
|
||||||
<PendingConversationView websocket={mockWebSocket}/>
|
<PendingConversationView websocket={mockWebSocket}
|
||||||
|
dispatcher={dispatcher} />
|
||||||
</div>
|
</div>
|
||||||
</Example>
|
</Example>
|
||||||
<Example summary="Pending conversation view (ringing)" dashed="true">
|
<Example summary="Pending conversation view (ringing)" dashed="true">
|
||||||
<div className="standalone">
|
<div className="standalone">
|
||||||
<PendingConversationView websocket={mockWebSocket} callState="ringing"/>
|
<PendingConversationView websocket={mockWebSocket}
|
||||||
|
dispatcher={dispatcher}
|
||||||
|
callState="ringing"/>
|
||||||
</div>
|
</div>
|
||||||
</Example>
|
</Example>
|
||||||
</Section>
|
</Section>
|
||||||
|
@ -262,7 +297,8 @@
|
||||||
style={{width: "260px", height: "265px"}}>
|
style={{width: "260px", height: "265px"}}>
|
||||||
<div className="fx-embedded">
|
<div className="fx-embedded">
|
||||||
<DesktopPendingConversationView callState={"gather"}
|
<DesktopPendingConversationView callState={"gather"}
|
||||||
contact={mockContact} />
|
contact={mockContact}
|
||||||
|
dispatcher={dispatcher} />
|
||||||
</div>
|
</div>
|
||||||
</Example>
|
</Example>
|
||||||
</Section>
|
</Section>
|
||||||
|
@ -271,7 +307,7 @@
|
||||||
<Example summary="Call Failed" dashed="true"
|
<Example summary="Call Failed" dashed="true"
|
||||||
style={{width: "260px", height: "265px"}}>
|
style={{width: "260px", height: "265px"}}>
|
||||||
<div className="fx-embedded">
|
<div className="fx-embedded">
|
||||||
<CallFailedView />
|
<CallFailedView dispatcher={dispatcher} />
|
||||||
</div>
|
</div>
|
||||||
</Example>
|
</Example>
|
||||||
</Section>
|
</Section>
|
||||||
|
|
|
@ -272,3 +272,8 @@ feedback_rejoin_button=Rejoin
|
||||||
## LOCALIZATION NOTE (feedback_report_user_button): Used to report a user in the case of
|
## LOCALIZATION NOTE (feedback_report_user_button): Used to report a user in the case of
|
||||||
## an abusive user.
|
## an abusive user.
|
||||||
feedback_report_user_button=Report User
|
feedback_report_user_button=Report User
|
||||||
|
|
||||||
|
## LOCALIZATION NOTE (rooms_list_current_conversations): We prefer to have no
|
||||||
|
## number in the string, but if you need it for your language please use {{num}}.
|
||||||
|
rooms_list_current_conversations=Current conversation;Current conversations
|
||||||
|
rooms_list_no_current_conversations=No current conversations
|
||||||
|
|
Загрузка…
Ссылка в новой задаче