From d9a1e2fd0c141816a6bf9f75c2329e576bb4733c Mon Sep 17 00:00:00 2001 From: Gene S Date: Fri, 4 Mar 2016 18:12:06 -0800 Subject: [PATCH] bug fixes --- css/base.css | 13 ++- index.html | 273 ++++++++++++++++++++++++++++++--------------------- 2 files changed, 166 insertions(+), 120 deletions(-) diff --git a/css/base.css b/css/base.css index aee3b0a2..9f5c3198 100644 --- a/css/base.css +++ b/css/base.css @@ -380,13 +380,7 @@ ul.menu > li.separator:hover { margin-left: auto; width: 1.5rem; height: 1.5rem; - border-radius: 50%; - background-color: transparent; } -.contact-box li:hover .menuTrigger { - background-color: white; -} - .contact-box li .menuTrigger a { color: transparent; } @@ -534,7 +528,7 @@ ul.menu > li.separator:hover { /* Topic/messages view */ #topic-caption-panel { display: flex; - align-items:center;; + align-items:center; justify-content: flex-start; flex: none; padding: 0.5rem; @@ -543,6 +537,10 @@ ul.menu > li.separator:hover { box-shadow: 0 1px 2px gray; z-index: 1; /* otherwise the shadow becomes invisible */ } +#topic-caption-panel a { + color: #757575; + text-decoration: none; +} #hide-message-view { display: block; @@ -551,6 +549,7 @@ ul.menu > li.separator:hover { #topic-users { margin-left: auto; + margin-right: 1rem; } /* Scrollable panel where the messages are shown */ diff --git a/index.html b/index.html index 5cbdc6e5..4c234ba2 100644 --- a/index.html +++ b/index.html @@ -66,6 +66,99 @@ 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( +
  • + ); + } else { + menuItems.push( +
  • {item}
  • + ); + } + count++; + }); + return ( + + ); + } + }); + + 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 */ var LoginView = React.createClass({ getInitialState: function() { @@ -424,6 +517,7 @@ this.props.state === 'contacts' ? : @@ -470,20 +564,12 @@ /* BEGIN Contact list (list of topics) */ /* A single topic */; var Contact = React.createClass({ + mixins: [ContextMenuMixin], handleClick: function(e) { e.preventDefault(); e.stopPropagation(); 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() { var title = this.props.title; if (!title) { @@ -544,8 +630,7 @@ var ContactsView = React.createClass({ getInitialState: function() { return { - subscriptions: [], - selected: null + subscriptions: [] }; }, componentWillMount: function() { @@ -582,10 +667,6 @@ tnMeSubsUpdated: function(names) { this.forceUpdate(); }, - handleSelectionChange: function(sel) { - this.setState({selected: sel}); - this.props.onTopicSelected(sel); - }, render: function() { var contactNodes = []; Tinode.getMeTopic().contacts(function(c) { @@ -599,8 +680,8 @@ avatar={makeImageUrl(c.public ? c.public.photo : null)} comment={c.private} unread={unread} now={c.online} - selected={this.state.selected} - onSelected={this.handleSelectionChange} + selected={this.props.topicSelected} + onSelected={this.props.onTopicSelected} showContextMenu={this.props.showContextMenu} topic={c.topic} key={c.topic} /> @@ -754,15 +835,20 @@ }, render: function() { 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) { + var cont = me ? me.getContact(s.user) : null; + var key = cont ? cont.topic : s.user; + contactNodes.push( + topic={key} + key={key} /> ); }, this); return ( @@ -865,6 +951,7 @@ }); var MessagesView = React.createClass({ + mixins: [ContextMenuMixin], getInitialState: function() { return { messages: [], @@ -886,9 +973,9 @@ this.propsChange(nextProps); }, propsChange: function(props) { - if (props.topicSelected && (props.topicSelected != this.state.topic)) { + if (props.topic && (props.topic != this.state.topic)) { var msgs = []; - var topic = Tinode.getTopic(props.topicSelected); + var topic = Tinode.getTopic(props.topic); // Bind the new topic to component. topic.onData = this.handleNewMessage; topic.onInfo = this.handleInfoReceipt; @@ -916,7 +1003,7 @@ // the previous topic has triggered, kill it. clearTimeout(this.readTimer); - this.setState({messages: msgs, topic: props.topicSelected}); + this.setState({messages: msgs, topic: props.topic}); } }, handleDescChange: function(desc) { @@ -1013,13 +1100,18 @@
    {lastSeen}
    users online and menu buttons
    +
    + + more_vert + +
      {messageNodes}
    - + ); } else { @@ -1134,74 +1226,6 @@ }); /* 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( -
  • - ); - } else { - menuItems.push( -
  • {item}
  • - ); - } - count++; - }); - return ( -
      - {menuItems} -
    - ); - } - }); - /* END Popup/dropdown menu */ - /* The top-level class to hold all fuinctionality together */ var AppView = React.createClass({ getInitialState: function() { @@ -1222,7 +1246,7 @@ }, componentDidMount: function() { window.addEventListener('resize', this.handleResize); - Tinode.enableLogging(true); + Tinode.enableLogging(true, true); Tinode.onDisconnect = this.handleDisconnect; // Setup with the default transport, usually websocket. Tinode.setup(APP_NAME, HOST, API_KEY); @@ -1333,39 +1357,60 @@ // 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}); - }); + + // Check if user clicked on an existing connection. + if (Tinode.getTopicType(peerName) === "p2p") { + instance.setState({ + errorText: '', + topicSelected: peerName, + sidePanelSelected: 'contacts', + mobilePanel: 'topic-view' + }); + } else { + var topic = peerName ? Tinode.newTopicWith(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() { this.setState({dialogSelected: null}); }, handleShowContextMenu: function(y, x, items, callback) { - console.log("SHOW ContextMenu called"); 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({ contextMenuVisible: true, - contextMenuPosition: {top: (y - bounds.top), left: (x - bounds.left)}, + contextMenuPosition: pos, contextMenuItems: items, handleContextMenuClick: callback }); }, handleHideContextMenu: function() { - console.log("HIDE ContextMenu called"); this.setState({ contextMenuVisible: false, contextMenuPosition: {top: 0, left: 0}, @@ -1397,6 +1442,7 @@ subscriptions={this.state.subscriptions} loginDisabled={this.state.loginDisabled} errorText={this.state.errorText} + topicSelected={this.state.topicSelected} onSignUp={this.handleNewAccount} onSettings={this.handleSettings} @@ -1412,8 +1458,9 @@ onHideMessageView={this.handleHideMessageView} displayMobile={this.state.displayMobile} hideSelf={(this.state.displayMobile && this.state.mobilePanel !== 'topic-view')} - topicSelected={this.state.topicSelected} + topic={this.state.topicSelected} onData={this.tnData} + showContextMenu={this.handleShowContextMenu} sendMessage={this.handleSendMessage} />