bug fixes

This commit is contained in:
Gene S
2016-03-04 18:12:06 -08:00
parent ac86e415e9
commit d9a1e2fd0c
2 changed files with 166 additions and 120 deletions
+6 -7
View File
@@ -380,13 +380,7 @@ ul.menu > li.separator:hover {
margin-left: auto; margin-left: auto;
width: 1.5rem; width: 1.5rem;
height: 1.5rem; height: 1.5rem;
border-radius: 50%;
background-color: transparent;
} }
.contact-box li:hover .menuTrigger {
background-color: white;
}
.contact-box li .menuTrigger a { .contact-box li .menuTrigger a {
color: transparent; color: transparent;
} }
@@ -534,7 +528,7 @@ ul.menu > li.separator:hover {
/* Topic/messages view */ /* Topic/messages view */
#topic-caption-panel { #topic-caption-panel {
display: flex; display: flex;
align-items:center;; align-items:center;
justify-content: flex-start; justify-content: flex-start;
flex: none; flex: none;
padding: 0.5rem; padding: 0.5rem;
@@ -543,6 +537,10 @@ ul.menu > li.separator:hover {
box-shadow: 0 1px 2px gray; box-shadow: 0 1px 2px gray;
z-index: 1; /* otherwise the shadow becomes invisible */ z-index: 1; /* otherwise the shadow becomes invisible */
} }
#topic-caption-panel a {
color: #757575;
text-decoration: none;
}
#hide-message-view { #hide-message-view {
display: block; display: block;
@@ -551,6 +549,7 @@ ul.menu > li.separator:hover {
#topic-users { #topic-users {
margin-left: auto; margin-left: auto;
margin-right: 1rem;
} }
/* Scrollable panel where the messages are shown */ /* Scrollable panel where the messages are shown */
+160 -113
View File
@@ -66,6 +66,99 @@
return photo ? 'data:image/' + photo.type + ';base64,' + photo.data : null; return photo ? 'data:image/' + photo.type + ';base64,' + photo.data : null;
} }
/* BEGIN Popup/dropdown menu */
var ContextMenu = React.createClass({
componentWillUnmount: function() {
this.toggle(false);
},
componentWillReceiveProps: function(nextProps) {
this.toggle(nextProps.visible);
},
toggle: function(visible) {
if (visible) {
document.addEventListener('mousedown', this.handlePageClick, false);
document.addEventListener('keyup', this.handleEscapeKey, false);
} else {
document.removeEventListener('mousedown', this.handlePageClick, false);
document.removeEventListener('keyup', this.handleEscapeKey, false);
}
},
handlePageClick: function(e) {
if (ReactDOM.findDOMNode(this).contains(e.target)) {
return;
}
e.preventDefault();
e.stopPropagation();
this.props.hide();
},
handleEscapeKey: function(e) {
if (e.keyCode === 27) {
this.props.hide();
}
},
handleClick: function(e) {
e.preventDefault();
e.stopPropagation();
this.props.hide();
this.props.onResult(this.props.items[e.currentTarget.dataset.id]);
},
render: function() {
if (!this.props.visible) {
return null;
}
var position = {};
if (this.props.position.left) {
position.left = this.props.position.left + 'px';
}
if (this.props.position.top) {
position.top = this.props.position.top + 'px';
}
if (this.props.position.right) {
position.right = this.props.position.right + 'px';
}
if (this.props.position.bottom) {
position.bottom = this.props.position.bottom + 'px';
}
var count = 0;
var menuItems = [];
var instance = this;
this.props.items.map(function(item) {
if (item === "-") {
menuItems.push(
<li className="separator" key={count} />
);
} else {
menuItems.push(
<li onClick={instance.handleClick} data-id={count} key={count}>{item}</li>
);
}
count++;
});
return (
<ul className="menu" style={position}>
{menuItems}
</ul>
);
}
});
var ContextMenuMixin = {
handleContextClick: function(e) {
e.preventDefault();
e.stopPropagation();
this.props.showContextMenu(e.pageY, e.pageX, ["Delete chat", "Mute", "Block"], this.handleMenuAction);
},
handleMenuAction: function(action) {
if (this.props.topic) {
console.log("Menu action: " + action + " for topic " + this.props.topic);
} else {
console.log("Menu action: " + action + " WITHOUT topic!");
}
}
};
/* END Popup/dropdown menu */
/* BEGIN Login: a login form */ /* BEGIN Login: a login form */
var LoginView = React.createClass({ var LoginView = React.createClass({
getInitialState: function() { getInitialState: function() {
@@ -424,6 +517,7 @@
this.props.state === 'contacts' ? this.props.state === 'contacts' ?
<ContactsView <ContactsView
data={this.props.subscriptions} data={this.props.subscriptions}
topicSelected={this.props.topicSelected}
showContextMenu={this.props.showContextMenu} showContextMenu={this.props.showContextMenu}
onTopicSelected={this.props.onTopicSelected} /> : onTopicSelected={this.props.onTopicSelected} /> :
@@ -470,20 +564,12 @@
/* BEGIN Contact list (list of topics) */ /* BEGIN Contact list (list of topics) */
/* A single topic */; /* A single topic */;
var Contact = React.createClass({ var Contact = React.createClass({
mixins: [ContextMenuMixin],
handleClick: function(e) { handleClick: function(e) {
e.preventDefault(); e.preventDefault();
e.stopPropagation(); e.stopPropagation();
this.props.onSelected(this.props.topic); this.props.onSelected(this.props.topic);
}, },
handleContextClick: function(e) {
e.preventDefault();
e.stopPropagation();
console.log("pageX=" + e.pageX + ", pageY=" + e.pageY);
this.props.showContextMenu(e.pageY, e.pageX, ["Delete chat", "Mute", "Block"], this.handleMenuAction);
},
handleMenuAction: function(action) {
console.log("Menu clicked '" + action + "' for topic " + this.props.topic);
},
render: function() { render: function() {
var title = this.props.title; var title = this.props.title;
if (!title) { if (!title) {
@@ -544,8 +630,7 @@
var ContactsView = React.createClass({ var ContactsView = React.createClass({
getInitialState: function() { getInitialState: function() {
return { return {
subscriptions: [], subscriptions: []
selected: null
}; };
}, },
componentWillMount: function() { componentWillMount: function() {
@@ -582,10 +667,6 @@
tnMeSubsUpdated: function(names) { tnMeSubsUpdated: function(names) {
this.forceUpdate(); this.forceUpdate();
}, },
handleSelectionChange: function(sel) {
this.setState({selected: sel});
this.props.onTopicSelected(sel);
},
render: function() { render: function() {
var contactNodes = []; var contactNodes = [];
Tinode.getMeTopic().contacts(function(c) { Tinode.getMeTopic().contacts(function(c) {
@@ -599,8 +680,8 @@
avatar={makeImageUrl(c.public ? c.public.photo : null)} avatar={makeImageUrl(c.public ? c.public.photo : null)}
comment={c.private} comment={c.private}
unread={unread} now={c.online} unread={unread} now={c.online}
selected={this.state.selected} selected={this.props.topicSelected}
onSelected={this.handleSelectionChange} onSelected={this.props.onTopicSelected}
showContextMenu={this.props.showContextMenu} showContextMenu={this.props.showContextMenu}
topic={c.topic} topic={c.topic}
key={c.topic} /> key={c.topic} />
@@ -754,15 +835,20 @@
}, },
render: function() { render: function() {
var contactNodes = []; var contactNodes = [];
// Don't attempt to create P2P topics which already exist. Server will reject the duplicates.
var me = Tinode.getMeTopic();
Tinode.getFndTopic().subscribers(function(s) { Tinode.getFndTopic().subscribers(function(s) {
var cont = me ? me.getContact(s.user) : null;
var key = cont ? cont.topic : s.user;
contactNodes.push( contactNodes.push(
<Contact <Contact
title={((s.public && s.public.fn) || s.private)} title={((s.public && s.public.fn) || s.private)}
avatar={makeImageUrl(s.public ? s.public.photo : null)} avatar={makeImageUrl(s.public ? s.public.photo : null)}
selected={this.state.selectedUser} selected={this.state.selectedUser}
onSelected={this.handleSelectionChange} onSelected={this.handleSelectionChange}
topic={s.user} topic={key}
key={s.user} /> key={key} />
); );
}, this); }, this);
return ( return (
@@ -865,6 +951,7 @@
}); });
var MessagesView = React.createClass({ var MessagesView = React.createClass({
mixins: [ContextMenuMixin],
getInitialState: function() { getInitialState: function() {
return { return {
messages: [], messages: [],
@@ -886,9 +973,9 @@
this.propsChange(nextProps); this.propsChange(nextProps);
}, },
propsChange: function(props) { propsChange: function(props) {
if (props.topicSelected && (props.topicSelected != this.state.topic)) { if (props.topic && (props.topic != this.state.topic)) {
var msgs = []; var msgs = [];
var topic = Tinode.getTopic(props.topicSelected); var topic = Tinode.getTopic(props.topic);
// Bind the new topic to component. // Bind the new topic to component.
topic.onData = this.handleNewMessage; topic.onData = this.handleNewMessage;
topic.onInfo = this.handleInfoReceipt; topic.onInfo = this.handleInfoReceipt;
@@ -916,7 +1003,7 @@
// the previous topic has triggered, kill it. // the previous topic has triggered, kill it.
clearTimeout(this.readTimer); clearTimeout(this.readTimer);
this.setState({messages: msgs, topic: props.topicSelected}); this.setState({messages: msgs, topic: props.topic});
} }
}, },
handleDescChange: function(desc) { handleDescChange: function(desc) {
@@ -1013,13 +1100,18 @@
<div id="topic-last-seen">{lastSeen}</div> <div id="topic-last-seen">{lastSeen}</div>
</div> </div>
<div id="topic-users">users online and menu buttons</div> <div id="topic-users">users online and menu buttons</div>
<div>
<a href="#" onClick={this.handleContextClick}>
<i className="material-icons big">more_vert</i>
</a>
</div>
</div> </div>
<div id="messages-panel" ref="messagesPanel"> <div id="messages-panel" ref="messagesPanel">
<ul className="chat-box"> <ul className="chat-box">
{messageNodes} {messageNodes}
</ul> </ul>
</div> </div>
<MessagesView.Send topic={this.props.topicSelected} sendMessage={this.props.sendMessage} /> <MessagesView.Send topic={this.props.topic} sendMessage={this.props.sendMessage} />
</div> </div>
); );
} else { } else {
@@ -1134,74 +1226,6 @@
}); });
/* End of modal dialog wrapper */ /* End of modal dialog wrapper */
/* BEGIN Popup/dropdown menu */
var ContextMenu = React.createClass({
componentWillUnmount: function() {
this.toggle(false);
},
componentWillReceiveProps: function(nextProps) {
this.toggle(nextProps.visible);
},
toggle: function(visible) {
if (visible) {
document.addEventListener('mousedown', this.handlePageClick, false);
document.addEventListener('keyup', this.handleEscapeKey, false);
} else {
document.removeEventListener('mousedown', this.handlePageClick, false);
document.removeEventListener('keyup', this.handleEscapeKey, false);
}
},
handlePageClick: function(e) {
if (ReactDOM.findDOMNode(this).contains(e.target)) {
return;
}
e.preventDefault();
e.stopPropagation();
this.props.hide();
},
handleEscapeKey: function(e) {
if (e.keyCode === 27) {
this.props.hide();
}
},
handleClick: function(e) {
e.preventDefault();
e.stopPropagation();
this.props.hide();
this.props.onResult(this.props.items[e.currentTarget.dataset.id]);
},
render: function() {
if (!this.props.visible) {
return null;
}
var position = {
left: this.props.position.left + 'px',
top: this.props.position.top + 'px'
};
var count = 0;
var menuItems = [];
var instance = this;
this.props.items.map(function(item) {
if (item === "-") {
menuItems.push(
<li className="separator" key={count} />
);
} else {
menuItems.push(
<li onClick={instance.handleClick} data-id={count} key={count}>{item}</li>
);
}
count++;
});
return (
<ul className="menu" style={position}>
{menuItems}
</ul>
);
}
});
/* END Popup/dropdown menu */
/* The top-level class to hold all fuinctionality together */ /* The top-level class to hold all fuinctionality together */
var AppView = React.createClass({ var AppView = React.createClass({
getInitialState: function() { getInitialState: function() {
@@ -1222,7 +1246,7 @@
}, },
componentDidMount: function() { componentDidMount: function() {
window.addEventListener('resize', this.handleResize); window.addEventListener('resize', this.handleResize);
Tinode.enableLogging(true); Tinode.enableLogging(true, true);
Tinode.onDisconnect = this.handleDisconnect; Tinode.onDisconnect = this.handleDisconnect;
// Setup with the default transport, usually websocket. // Setup with the default transport, usually websocket.
Tinode.setup(APP_NAME, HOST, API_KEY); Tinode.setup(APP_NAME, HOST, API_KEY);
@@ -1333,39 +1357,60 @@
// Create of a new topic. New P2P topic requires peer's name. // Create of a new topic. New P2P topic requires peer's name.
handleNewTopicRequest: function(peerName, vcard) { handleNewTopicRequest: function(peerName, vcard) {
var instance = this; var instance = this;
var topic = peerName ? Tinode.getTopic(peerName) : Tinode.newTopic();
var params = {get: {desc: {}, sub: {}, data: {}}}; // Check if user clicked on an existing connection.
params.set = peerName ? undefined : {set: {desc: {public: vcard}}}; if (Tinode.getTopicType(peerName) === "p2p") {
topic.subscribe(params).then(function() { instance.setState({
if (peerName) { errorText: '',
instance.setState({ topicSelected: peerName,
errorText: '', sidePanelSelected: 'contacts',
topicSelected: topic.name, mobilePanel: 'topic-view'
sidePanelSelected: 'contacts', });
mobilePanel: 'topic-view' } else {
}); var topic = peerName ? Tinode.newTopicWith(peerName) : Tinode.newTopic();
} else { var params = {get: {desc: {}, sub: {}, data: {}}};
console.log("new group topic created: " + topic.name); params.set = peerName ? undefined : {set: {desc: {public: vcard}}};
} topic.subscribe(params).then(function() {
}).catch(function(err) { if (peerName) {
instance.setState({errorText: err.message}); instance.setState({
}); errorText: '',
topicSelected: topic.name,
sidePanelSelected: 'contacts',
mobilePanel: 'topic-view'
});
} else {
console.log("new group topic created: " + topic.name);
}
}).catch(function(err) {
instance.setState({errorText: err.message});
});
}
}, },
handleDialogCancel: function() { handleDialogCancel: function() {
this.setState({dialogSelected: null}); this.setState({dialogSelected: null});
}, },
handleShowContextMenu: function(y, x, items, callback) { handleShowContextMenu: function(y, x, items, callback) {
console.log("SHOW ContextMenu called");
var bounds = ReactDOM.findDOMNode(this).getBoundingClientRect(); var bounds = ReactDOM.findDOMNode(this).getBoundingClientRect();
// Ensure that menu is inside the app-container.
var pos = {};
if (bounds.bottom - y < items.length * 20) {
pos.bottom = (y - bounds.top);
} else {
pos.top = (y - bounds.top);
}
if (bounds.right - x < 120) {
pos.right = (x - bounds.left);
} else {
pos.left = (x - bounds.left);
}
this.setState({ this.setState({
contextMenuVisible: true, contextMenuVisible: true,
contextMenuPosition: {top: (y - bounds.top), left: (x - bounds.left)}, contextMenuPosition: pos,
contextMenuItems: items, contextMenuItems: items,
handleContextMenuClick: callback handleContextMenuClick: callback
}); });
}, },
handleHideContextMenu: function() { handleHideContextMenu: function() {
console.log("HIDE ContextMenu called");
this.setState({ this.setState({
contextMenuVisible: false, contextMenuVisible: false,
contextMenuPosition: {top: 0, left: 0}, contextMenuPosition: {top: 0, left: 0},
@@ -1397,6 +1442,7 @@
subscriptions={this.state.subscriptions} subscriptions={this.state.subscriptions}
loginDisabled={this.state.loginDisabled} loginDisabled={this.state.loginDisabled}
errorText={this.state.errorText} errorText={this.state.errorText}
topicSelected={this.state.topicSelected}
onSignUp={this.handleNewAccount} onSignUp={this.handleNewAccount}
onSettings={this.handleSettings} onSettings={this.handleSettings}
@@ -1412,8 +1458,9 @@
onHideMessageView={this.handleHideMessageView} onHideMessageView={this.handleHideMessageView}
displayMobile={this.state.displayMobile} displayMobile={this.state.displayMobile}
hideSelf={(this.state.displayMobile && this.state.mobilePanel !== 'topic-view')} hideSelf={(this.state.displayMobile && this.state.mobilePanel !== 'topic-view')}
topicSelected={this.state.topicSelected} topic={this.state.topicSelected}
onData={this.tnData} onData={this.tnData}
showContextMenu={this.handleShowContextMenu}
sendMessage={this.handleSendMessage} /> sendMessage={this.handleSendMessage} />
</div> </div>