topic creation and context menu fixes

This commit is contained in:
Gene S
2016-03-03 18:03:11 -08:00
parent 5b89abb646
commit ac86e415e9
2 changed files with 392 additions and 224 deletions
+29 -21
View File
@@ -347,35 +347,35 @@ ul.menu > li.separator:hover {
} }
/* List of contacts */ /* List of contacts */
#contacts-panel { .contacts-panel {
overflow-y: auto; overflow-y: auto;
margin-top: 1rem;: margin-top: 1rem;:
margin-bottom: auto; margin-bottom: auto;
height: 100%; height: 100%;
} }
#contact-box { .contact-box {
list-style: none; list-style: none;
} }
#contact-box > li { .contact-box > li {
display: flex; display: flex;
padding: 0.35rem 0.25rem 0.5rem 0.35rem; padding: 0.35rem 0.25rem 0.5rem 0.35rem;
min-height: 3.5rem; min-height: 3.5rem;
} }
#contact-box > li:hover { .contact-box > li:hover {
background-color: #eceff1; /* blue-gray 50 highlight */ background-color: #eceff1; /* blue-gray 50 highlight */
} }
#contact-box > li.selected { .contact-box > li.selected {
background-color: #cfd8dc; /* blue-gray 100 highlight */ background-color: #cfd8dc; /* blue-gray 100 highlight */
} }
#contact-box .contact-title { .contact-box .contact-title {
font-size: 1.05rem; font-size: 1.05rem;
} }
/* The down arrow head - trigger for context menu in contacts list */ /* The down arrow head - trigger for context menu in contacts list */
#contact-box li .menuTrigger { .contact-box li .menuTrigger {
position: relative; position: relative;
margin-left: auto; margin-left: auto;
width: 1.5rem; width: 1.5rem;
@@ -383,19 +383,19 @@ ul.menu > li.separator:hover {
border-radius: 50%; border-radius: 50%;
background-color: transparent; background-color: transparent;
} }
#contact-box li:hover .menuTrigger { .contact-box li:hover .menuTrigger {
background-color: white; background-color: white;
} }
#contact-box li .menuTrigger a { .contact-box li .menuTrigger a {
color: transparent; color: transparent;
} }
#contact-box li:hover .menuTrigger a { .contact-box li:hover .menuTrigger a {
color: #757575; color: #757575;
} }
/* Avatar and online-offline indicator */ /* Avatar and online-offline indicator */
#contact-box .avatar-box { .contact-box .avatar-box {
margin-right: 0.35rem; margin-right: 0.35rem;
position: relative; position: relative;
width: 3rem; width: 3rem;
@@ -403,25 +403,32 @@ ul.menu > li.separator:hover {
overflow: hidden; overflow: hidden;
padding: 0; padding: 0;
} }
#contact-box .avatar { .contact-box .avatar {
width: 3rem; width: 3rem;
height: auto; height: auto;
border-radius: 50%; border-radius: 50%;
} }
.contact-box .text-box {
display: flex;
flex-direction: column;
justify-content: space-around;
}
/* Online-offline indicator positioned inside avatar-box */ /* Online-offline indicator positioned inside avatar-box */
#contact-box .online, .offline { .contact-box .online, .offline {
font-size: 1.5em; display: inline-block;
line-height: 1; width: 0.75rem;
height: 0.75rem;
border-radius: 50%;
position: absolute; position: absolute;
right: -0.1rem; right: 0;
bottom: -0.1rem; bottom: 0;
} }
#contact-box .online { .contact-box .online {
color: #4caf50; background-color: #4caf50;
} }
#contact-box .offline { .contact-box .offline {
color: #888; background-color: #888;
} }
/* END of avatar box */ /* END of avatar box */
@@ -840,6 +847,7 @@ ul.menu > li.separator:hover {
border-radius: 1em; border-radius: 1em;
padding: 0.25em; padding: 0.25em;
min-width: 1.75em; min-width: 1.75em;
margin-left: 0.5rem;
text-align: center; text-align: center;
} }
+363 -203
View File
@@ -66,68 +66,6 @@
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 Menu = React.createClass({
getInitialState: function() {
return {
visible: this.props.visible
};
},
componentWillUnmount: function() {
document.removeEventListener('mousedown', this.handlePageClick, false);
},
componentWillReceiveProps: function(nextProps) {
this.setState({visible: nextProps.visible});
if (nextProps.visible) {
document.addEventListener('mousedown', this.handlePageClick, false);
} else {
document.removeEventListener('mousedown', this.handlePageClick, false);
}
},
handlePageClick: function(e) {
if (ReactDOM.findDOMNode(this).contains(e.target)) {
console.log("click in menu, exiting");
return;
}
e.stopPropagation();
document.removeEventListener('mousedown', this.handlePageClick);
console.log("click outside of menu");
this.setState({visible: false});
},
handleClick: function(e) {
e.preventDefault();
e.stopPropagation();
this.setState({visible: false});
this.props.onResult(this.props.topic, this.props.items[e.currentTarget.dataset.id]);
},
render: function() {
if (!this.state.visible) {
return null;
}
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">
{menuItems}
</ul>
);
}
});
/* 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() {
@@ -224,9 +162,9 @@
fn: this.state.name.trim(), fn: this.state.name.trim(),
}; };
if (this.state.imageDataUrl) { if (this.state.imageDataUrl) {
dataStart = this.state.imageDataUrl.indexOf(","); var dataStart = this.state.imageDataUrl.indexOf(",");
vcard.photo = { vcard.photo = {
data: this.state.imageDataUrl.substring(dataStart), data: this.state.imageDataUrl.substring(dataStart+1),
type: "jpg" type: "jpg"
}; };
} }
@@ -288,8 +226,11 @@
} }
ctx.imageSmoothingEnabled = true; ctx.imageSmoothingEnabled = true;
ctx.drawImage(this, sx, sy, size, size, 0, 0, 64, 64); ctx.drawImage(this, sx, sy, size, size, 0, 0, 64, 64);
instance.setState({dataUrl: canvas.toDataURL("image/jpg")}); var bits = canvas.toDataURL("image/jpg");
canvas = null; canvas = null;
instance.setState({dataUrl: bits});
instance.props.onImageChanged(bits);
}; };
img.src = URL.createObjectURL(e.target.files[0]); img.src = URL.createObjectURL(e.target.files[0]);
}, },
@@ -310,94 +251,6 @@
}); });
/* END Account registration */ /* END Account registration */
/* BEGIN Create new topic and invite users or send an invite */
var NewTopicView = React.createClass({
getInitialState: function() {
return {
tabSelected: "p2p",
fn: '', // full/formatted name
search: null, // search string
imageDataUrl: null
};
},
componentWillMount: function() {
var fnd = Tinode.getFndTopic();
fnd.onSubsUpdated = this.fndSubsUpdated;
fnd.subscribe()
.then(function() {
fnd.setMeta({desc: {private: ["tel:17025550005"]}});
})
.then(function() {
fnd.getMeta({sub: {}});
});
},
componentWillUnmount: function() {
var fnd = Tinode.getFndTopic();
fnd.onSubsUpdated = undefined;
},
fndSubsUpdated: function(keys) {
console.log(keys);
//this.forceUpdate();
},
handleTabClick: function(e) {
e.preventDefault();
this.setState({tabSelected: e.currentTarget.dataset.id});
},
handleFnChange: function(e) {
this.setState({fn: e.target.value});
},
handleImageChanged: function(img) {
this.setState({imageDataUrl: img});
},
handleGroupSubmit: function(e) {
e.preventDefault();
},
render: function() {
var submitClasses = "blue";
if (this.props.disabled) {
submitClasses += " disabled";
}
return (
<div>
<div className="tabbar">
<ul>
<li className={this.state.tabSelected === "p2p" ? "active" : null}>
<a href="#" data-id="p2p" onClick={this.handleTabClick}>1:1</a>
</li>
<li className={this.state.tabSelected === "grp" ? "active" : null}>
<a href="#" data-id="grp" onClick={this.handleTabClick}>group</a>
</li>
</ul>
</div>
{this.state.tabSelected === "grp" ?
<form className="panel-form" onSubmit={this.handleGroupSubmit}>
<div className="panel-form-one">
<div className="panel-form-two">
<input type="text" placeholder="Name of the topic to create"
value={this.state.fn} onChange={this.handleFnChange} autofocus required />
<div className="dialog-buttons">
<button className={submitClasses} type="submit">Create</button>
</div>
</div>
<AvatarUpload onImageChanged={this.handleImageChanged} />
</div>
</form>
: null}
<form className="panel-form" onSubmit={this.handleFindContacts}>
<div className="panel-form-one">
<input type="text" placeholder="Find people by email or phone" value={this.state.search}
onChange={this.handleSearchChange} required />
<a href="#" onClick={this.handleSearch}>
<i className="material-icons">search</i>
</a>
</div>
</form>
</div>
);
}
});
/* END Create new topic and invite users or send an invite */
/* BEGIN Tinode config panel */ /* BEGIN Tinode config panel */
var SettingsView = React.createClass({ var SettingsView = React.createClass({
getInitialState: function() { getInitialState: function() {
@@ -571,13 +424,14 @@
this.props.state === 'contacts' ? this.props.state === 'contacts' ?
<ContactsView <ContactsView
data={this.props.subscriptions} data={this.props.subscriptions}
showContextMenu={this.props.showContextMenu}
onTopicSelected={this.props.onTopicSelected} /> : onTopicSelected={this.props.onTopicSelected} /> :
this.props.state === 'new-topic' ? this.props.state === 'new-topic' ?
<NewTopicView <NewTopicView
data={this.props.subscriptions} data={this.props.subscriptions}
onContactSelected={this.props.onContactSelected} onContactSelected={this.props.onContactSelected}
onStartNewTopic={this.props.onStartNewTopic} /> : onCreateTopic={this.props.onCreateTopic} /> :
null} null}
</div> </div>
@@ -614,29 +468,27 @@
/* END EditAccount */ /* END EditAccount */
/* BEGIN Contact list (list of topics) */ /* BEGIN Contact list (list of topics) */
/* A single topic */ /* A single topic */;
var contactMenuItems = ["Delete chat", "Mute", "Block"];
var Contact = React.createClass({ var Contact = React.createClass({
getInitialState: function() { handleClick: function(e) {
return { e.preventDefault();
menuShownFor: null e.stopPropagation();
};
},
handleClick: function() {
this.props.onSelected(this.props.topic); this.props.onSelected(this.props.topic);
}, },
handleContextClick: function(e) { handleContextClick: function(e) {
e.preventDefault(); e.preventDefault();
e.stopPropagation(); e.stopPropagation();
this.setState({menuShownFor: this.props.topic}); console.log("pageX=" + e.pageX + ", pageY=" + e.pageY);
this.props.showContextMenu(e.pageY, e.pageX, ["Delete chat", "Mute", "Block"], this.handleMenuAction);
}, },
handleMenuAction: function(topic, action) { handleMenuAction: function(action) {
console.log("Menu clicked '" + action + "' for topic " + topic); console.log("Menu clicked '" + action + "' for topic " + this.props.topic);
this.setState({menuShownFor: null});
}, },
render: function() { render: function() {
var title = this.props.title; var title = this.props.title;
if (title.length > 30) { if (!title) {
title = <i>unknown</i>;
} else if (title.length > 30) {
title = title.substring(0, 28) + "..."; title = title.substring(0, 28) + "...";
} }
var selected = (this.props.selected === this.props.topic) ? "selected" : null; var selected = (this.props.selected === this.props.topic) ? "selected" : null;
@@ -649,26 +501,31 @@
<li className={selected} onClick={this.handleClick}> <li className={selected} onClick={this.handleClick}>
<div className="avatar-box"> <div className="avatar-box">
{avatar} {avatar}
<span className={this.props.now ? "online" : "offline"}>&#x25cf;</span> {this.props.now ? <span className="online"></span> : null}
</div> </div>
<div> <div className="text-box">
<span className="contact-title">{title}</span> <Contact.Unread count={this.props.unread} /> <div><span className="contact-title">{title}</span>
<p>{this.props.comment}</p> {this.props.unread > 0 ? <Contact.Unread count={this.props.unread} /> : null}</div>
{this.props.comment ? <div>{this.props.comment}</div> : null}
</div> </div>
<span className="menuTrigger"> {this.props.showContextMenu ?
<a href="#" onClick={this.handleContextClick}> <span className="menuTrigger">
<i className="material-icons">arrow_drop_down</i> <a href="#" onClick={this.handleContextClick}>
</a> <i className="material-icons">expand_more</i>
<Menu items={contactMenuItems} </a>
topic={this.props.topic} </span> : null}
onResult={this.handleMenuAction}
visible={this.state.menuShownFor ? true : false} />
</span>
</li> </li>
); );
} }
}); });
/*
<ContextMenu items={this.props.contactMenuItems}
topic={this.props.topic}
onResult={this.handleMenuAction}
visible={this.state.menuShownFor ? true : false} />
*/
/* The counter of unread messages in the topic */ /* The counter of unread messages in the topic */
Contact.Unread = React.createClass({ Contact.Unread = React.createClass({
render: function() { render: function() {
@@ -697,6 +554,12 @@
me.onData = this.tnInvite; me.onData = this.tnInvite;
me.onSubsUpdated = this.tnMeSubsUpdated; me.onSubsUpdated = this.tnMeSubsUpdated;
}, },
componentWillUnmount: function() {
var me = Tinode.getMeTopic();
me.onContactUpdate = undefined;
me.onData = undefined;
me.onSubsUpdated = undefined;
},
tnMeContactUpdate: function(what, cont) { tnMeContactUpdate: function(what, cont) {
if (what === "on" || what === "off" || what === "read") { if (what === "on" || what === "off" || what === "read") {
this.forceUpdate(); this.forceUpdate();
@@ -719,7 +582,7 @@
tnMeSubsUpdated: function(names) { tnMeSubsUpdated: function(names) {
this.forceUpdate(); this.forceUpdate();
}, },
onSelectionChange: function(sel) { handleSelectionChange: function(sel) {
this.setState({selected: sel}); this.setState({selected: sel});
this.props.onTopicSelected(sel); this.props.onTopicSelected(sel);
}, },
@@ -732,20 +595,21 @@
} }
contactNodes.push( contactNodes.push(
<Contact <Contact
title={c.public.fn} title={c.public ? c.public.fn : null}
avatar={makeImageUrl(c.public.photo)} 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.state.selected}
onSelected={this.onSelectionChange} onSelected={this.handleSelectionChange}
showContextMenu={this.props.showContextMenu}
topic={c.topic} topic={c.topic}
key={c.topic} /> key={c.topic} />
); );
}, this); }, this);
return ( return (
<div id="contacts-panel"> <div className="contacts-panel">
<ul id="contact-box"> <ul className="contact-box">
{contactNodes} {contactNodes}
</ul> </ul>
</div> </div>
@@ -754,6 +618,176 @@
}); });
/* END Contact list */ /* END Contact list */
/* BEGIN Create new topic and invite users or send an invite */
var NewTopicView = React.createClass({
getInitialState: function() {
return {
tabSelected: "p2p",
contactSelected: null
};
},
componentWillMount: function() {
var fnd = Tinode.getFndTopic();
fnd.onSubsUpdated = this.fndSubsUpdated;
fnd.subscribe()
.then(function() {
fnd.getMeta({sub: {}});
});
},
componentWillUnmount: function() {
var fnd = Tinode.getFndTopic();
fnd.onSubsUpdated = undefined;
},
fndSubsUpdated: function() {
//console.log(keys);
this.forceUpdate();
},
handleTabClick: function(e) {
e.preventDefault();
this.setState({tabSelected: e.currentTarget.dataset.id, contactSelected: null});
},
handleContactSelected: function(sel) {
if (this.state.tabSelected === "p2p") {
this.props.onCreateTopic(sel, undefined);
} else {
console.log("handle group invite " + sel);
}
},
handleGroupSubmit: function(name, dataUrl) {
//this.setState({errorCleared: false});
var vcard = null;
if (name || dataUrl) {
vcard = {
fn: name,
};
if (dataUrl) {
var dataStart = dataUrl.indexOf(",");
vcard.photo = {
data: dataUrl.substring(dataStart+1),
type: "jpg"
};
}
}
this.props.onCreateTopic(undefined, vcard);
},
render: function() {
return (
<div>
<div className="tabbar">
<ul>
<li className={this.state.tabSelected === "p2p" ? "active" : null}>
<a href="#" data-id="p2p" onClick={this.handleTabClick}>1:1</a>
</li>
<li className={this.state.tabSelected === "grp" ? "active" : null}>
<a href="#" data-id="grp" onClick={this.handleTabClick}>group</a>
</li>
</ul>
</div>
{this.state.tabSelected === "grp" ?
<NewTopicView.Group onSubmit={this.handleGroupSubmit} /> :
<NewTopicView.SelectContacts onContactSelected={this.handleContactSelected} type="p2p" />}
</div>
);
}
});
NewTopicView.Group = React.createClass({
getInitialState: function() {
return {
fn: '', // full/formatted name
imageDataUrl: null
};
},
handleFnChange: function(e) {
this.setState({fn: e.target.value});
},
handleImageChanged: function(img) {
console.log(img);
this.setState({imageDataUrl: img});
},
handleSubmit: function(e) {
e.preventDefault();
this.props.onSubmit(this.state.fn.trim(), this.state.imageDataUrl);
},
render: function() {
var submitClasses = "blue";
if (this.props.disabled) {
submitClasses += " disabled";
}
return (
<form className="panel-form" onSubmit={this.handleSubmit}>
<div className="panel-form-one">
<div className="panel-form-two">
<input type="text" placeholder="Name of the topic to create"
value={this.state.fn} onChange={this.handleFnChange} autofocus required />
<div className="dialog-buttons">
<button className={submitClasses} type="submit">Create</button>
</div>
</div>
<AvatarUpload onImageChanged={this.handleImageChanged} />
</div>
</form>
);
}
});
NewTopicView.SelectContacts = React.createClass({
getInitialState: function() {
return {
search: null,
selectedUser: null
};
},
handleSelectionChange: function(sel) {
if (this.props.type === "p2p") {
this.props.onContactSelected(sel);
} else {
console.log(sel);
}
},
handleSearchChange: function(e) {
this.setState({search: e.target.value});
},
handleSearch: function(e) {
e.preventDefault();
console.log("will search for '" + this.state.search + "'");
},
render: function() {
var contactNodes = [];
Tinode.getFndTopic().subscribers(function(s) {
contactNodes.push(
<Contact
title={((s.public && s.public.fn) || s.private)}
avatar={makeImageUrl(s.public ? s.public.photo : null)}
selected={this.state.selectedUser}
onSelected={this.handleSelectionChange}
topic={s.user}
key={s.user} />
);
}, this);
return (
<div>
<form className="panel-form" onSubmit={this.handleFindContacts}>
<div className="panel-form-one">
<input type="text" placeholder="Emails or phones to find" value={this.state.search}
onChange={this.handleSearchChange} required />
<a href="#" onClick={this.handleSearch}>
<i className="material-icons">search</i>
</a>
</div>
</form>
<div className="contacts-panel">
<ul className="contact-box">
{contactNodes}
</ul>
</div>
</div>
);
}
});
/* END Create new topic and invite users or send an invite */
/* BEGIN Conversation panel */ /* BEGIN Conversation panel */
var Received = { var Received = {
UNDEF: -2, UNDEF: -2,
@@ -886,10 +920,12 @@
} }
}, },
handleDescChange: function(desc) { handleDescChange: function(desc) {
this.setState({title: desc.public.fn}); if (desc.public) {
this.setState({title: desc.public.fn});
}
}, },
handleSubsUpdated: function(uids) { handleSubsUpdated: function(uids) {
console.log("topic subscriptions updated");
}, },
handleNewMessage: function(msg) { handleNewMessage: function(msg) {
this.setState({messages: this.state.messages.concat(msg)}); this.setState({messages: this.state.messages.concat(msg)});
@@ -1098,6 +1134,74 @@
}); });
/* 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() {
@@ -1106,6 +1210,7 @@
sidePanelTitle: null, sidePanelTitle: null,
sidePanelAvatar: null, sidePanelAvatar: null,
dialogSelected: null, dialogSelected: null,
contextMenuVisible: false,
loggedIn: false, loggedIn: false,
errorText: '', errorText: '',
topicSelected: '', topicSelected: '',
@@ -1142,7 +1247,11 @@
topicSelected: '', topicSelected: '',
loginDisabled: false, loginDisabled: false,
subscriptions: {}, subscriptions: {},
mobilePanel: 'sidepanel' mobilePanel: 'sidepanel',
contextMenuVisible: false,
contextMenuPosition: {top: 0, left: 0},
contextMenuItems: null,
handleContextMenuClick: undefined
}); });
}, },
// User clicked Login button in the side panel. // User clicked Login button in the side panel.
@@ -1178,16 +1287,6 @@
Tinode.getTopic(data.topic).noteRecv(data.seq); Tinode.getTopic(data.topic).noteRecv(data.seq);
}, RECEIVED_DELAY); }, RECEIVED_DELAY);
}, },
// Registration of a new account.
handleNewAccount: function(login_, password_, public_, private_) {
var instance = this;
Tinode.connect().then(function() {
return Tinode.createUserBasic(login_, password_,
{login: "basic", public: public_, private: private_});
}).catch(function(err) {
instance.setState({errorText: err.message});
});
},
// User clicked on a contact in the side panel. // User clicked on a contact in the side panel.
handleTopicSelected: function(topicName) { handleTopicSelected: function(topicName) {
this.setState({topicSelected: topicName, mobilePanel: 'topic-view'}); this.setState({topicSelected: topicName, mobilePanel: 'topic-view'});
@@ -1202,9 +1301,19 @@
topic.publish(msg); topic.publish(msg);
}, },
// User chose a Sign Up menu item. // User chose a Sign Up menu item.
handleSignUp: function() { handleNewAccount: function() {
this.setState({sidePanelSelected: 'register'}); this.setState({sidePanelSelected: 'register'});
}, },
// Actual registration of a new account.
handleNewAccountRequest: function(login_, password_, public_, private_) {
var instance = this;
Tinode.connect().then(function() {
return Tinode.createUserBasic(login_, password_,
{login: "basic", public: public_, private: private_});
}).catch(function(err) {
instance.setState({errorText: err.message});
});
},
// User chose Settings menu item. // User chose Settings menu item.
handleSettings: function() { handleSettings: function() {
this.setState({sidePanelSelected: this.state.loggedIn ? 'edit-account' : 'settings'}); this.setState({sidePanelSelected: this.state.loggedIn ? 'edit-account' : 'settings'});
@@ -1217,12 +1326,53 @@
this.setState({sidePanelSelected: 'login'}); this.setState({sidePanelSelected: 'login'});
} }
}, },
// User clicked a (+) menu item.
handleNewTopic: function() { handleNewTopic: function() {
this.setState({sidePanelSelected: 'new-topic'}); this.setState({sidePanelSelected: 'new-topic'});
}, },
// Create of a new topic. New P2P topic requires peer's name.
handleNewTopicRequest: function(peerName, vcard) {
var instance = this;
var topic = peerName ? Tinode.getTopic(peerName) : Tinode.newTopic();
var params = {get: {desc: {}, sub: {}, data: {}}};
params.set = peerName ? undefined : {set: {desc: {public: vcard}}};
topic.subscribe(params).then(function() {
if (peerName) {
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) {
console.log("SHOW ContextMenu called");
var bounds = ReactDOM.findDOMNode(this).getBoundingClientRect();
this.setState({
contextMenuVisible: true,
contextMenuPosition: {top: (y - bounds.top), left: (x - bounds.left)},
contextMenuItems: items,
handleContextMenuClick: callback
});
},
handleHideContextMenu: function() {
console.log("HIDE ContextMenu called");
this.setState({
contextMenuVisible: false,
contextMenuPosition: {top: 0, left: 0},
contextMenuItems: null,
handleContextMenuClick: undefined
});
},
render: function() { render: function() {
return ( return (
<div id="app-container"> <div id="app-container">
@@ -1231,6 +1381,13 @@
onOk={this.handleDialogCancel} onOk={this.handleDialogCancel}
onCancel={this.handleDialogCancel} /> onCancel={this.handleDialogCancel} />
<ContextMenu
position={this.state.contextMenuPosition}
items={this.state.contextMenuItems}
onResult={this.state.handleContextMenuClick}
visible={this.state.contextMenuVisible}
hide={this.handleHideContextMenu} />
<SidepanelView <SidepanelView
displayMobile={this.state.displayMobile} displayMobile={this.state.displayMobile}
hideSelf={(this.state.displayMobile && this.state.mobilePanel !== 'sidepanel')} hideSelf={(this.state.displayMobile && this.state.mobilePanel !== 'sidepanel')}
@@ -1240,13 +1397,16 @@
subscriptions={this.state.subscriptions} subscriptions={this.state.subscriptions}
loginDisabled={this.state.loginDisabled} loginDisabled={this.state.loginDisabled}
errorText={this.state.errorText} errorText={this.state.errorText}
onSignUp={this.handleSignUp}
onSignUp={this.handleNewAccount}
onSettings={this.handleSettings} onSettings={this.handleSettings}
onLoginRequest={this.handleLoginRequest} onLoginRequest={this.handleLoginRequest}
onCreateAccount={this.handleNewAccount} onCreateAccount={this.handleNewAccountRequest}
onTopicSelected={this.handleTopicSelected} onTopicSelected={this.handleTopicSelected}
onCreateTopic={this.handleNewTopicRequest}
onNewTopic={this.handleNewTopic} onNewTopic={this.handleNewTopic}
onCancel={this.handleSidepanelCancel} /> onCancel={this.handleSidepanelCancel}
showContextMenu={this.handleShowContextMenu} />
<MessagesView <MessagesView
onHideMessageView={this.handleHideMessageView} onHideMessageView={this.handleHideMessageView}