From e6842eab94b7dc6abbae94b45f3bf7cdb19b496d Mon Sep 17 00:00:00 2001 From: Matthew Hodgson Date: Thu, 31 Mar 2016 18:38:01 +0100 Subject: [PATCH 01/18] WIP url previewing --- src/component-index.js | 13 ++-- src/components/views/messages/TextualBody.js | 45 +++++++++++--- .../views/rooms/LinkPreviewWidget.js | 60 +++++++++++++++++++ 3 files changed, 103 insertions(+), 15 deletions(-) create mode 100644 src/components/views/rooms/LinkPreviewWidget.js diff --git a/src/component-index.js b/src/component-index.js index 8a4035811a..b5f5dd0a53 100644 --- a/src/component-index.js +++ b/src/component-index.js @@ -26,10 +26,6 @@ limitations under the License. module.exports.components = {}; module.exports.components['structures.CreateRoom'] = require('./components/structures/CreateRoom'); -module.exports.components['structures.login.ForgotPassword'] = require('./components/structures/login/ForgotPassword'); -module.exports.components['structures.login.Login'] = require('./components/structures/login/Login'); -module.exports.components['structures.login.PostRegistration'] = require('./components/structures/login/PostRegistration'); -module.exports.components['structures.login.Registration'] = require('./components/structures/login/Registration'); module.exports.components['structures.MatrixChat'] = require('./components/structures/MatrixChat'); module.exports.components['structures.MessagePanel'] = require('./components/structures/MessagePanel'); module.exports.components['structures.RoomStatusBar'] = require('./components/structures/RoomStatusBar'); @@ -38,6 +34,10 @@ module.exports.components['structures.ScrollPanel'] = require('./components/stru module.exports.components['structures.TimelinePanel'] = require('./components/structures/TimelinePanel'); module.exports.components['structures.UploadBar'] = require('./components/structures/UploadBar'); module.exports.components['structures.UserSettings'] = require('./components/structures/UserSettings'); +module.exports.components['structures.login.ForgotPassword'] = require('./components/structures/login/ForgotPassword'); +module.exports.components['structures.login.Login'] = require('./components/structures/login/Login'); +module.exports.components['structures.login.PostRegistration'] = require('./components/structures/login/PostRegistration'); +module.exports.components['structures.login.Registration'] = require('./components/structures/login/Registration'); module.exports.components['views.avatars.BaseAvatar'] = require('./components/views/avatars/BaseAvatar'); module.exports.components['views.avatars.MemberAvatar'] = require('./components/views/avatars/MemberAvatar'); module.exports.components['views.avatars.RoomAvatar'] = require('./components/views/avatars/RoomAvatar'); @@ -64,10 +64,10 @@ module.exports.components['views.login.LoginHeader'] = require('./components/vie module.exports.components['views.login.PasswordLogin'] = require('./components/views/login/PasswordLogin'); module.exports.components['views.login.RegistrationForm'] = require('./components/views/login/RegistrationForm'); module.exports.components['views.login.ServerConfig'] = require('./components/views/login/ServerConfig'); -module.exports.components['views.messages.MessageEvent'] = require('./components/views/messages/MessageEvent'); module.exports.components['views.messages.MFileBody'] = require('./components/views/messages/MFileBody'); module.exports.components['views.messages.MImageBody'] = require('./components/views/messages/MImageBody'); module.exports.components['views.messages.MVideoBody'] = require('./components/views/messages/MVideoBody'); +module.exports.components['views.messages.MessageEvent'] = require('./components/views/messages/MessageEvent'); module.exports.components['views.messages.TextualBody'] = require('./components/views/messages/TextualBody'); module.exports.components['views.messages.TextualEvent'] = require('./components/views/messages/TextualEvent'); module.exports.components['views.messages.UnknownBody'] = require('./components/views/messages/UnknownBody'); @@ -77,6 +77,7 @@ module.exports.components['views.rooms.AuxPanel'] = require('./components/views/ module.exports.components['views.rooms.EntityTile'] = require('./components/views/rooms/EntityTile'); module.exports.components['views.rooms.EventTile'] = require('./components/views/rooms/EventTile'); module.exports.components['views.rooms.InviteMemberList'] = require('./components/views/rooms/InviteMemberList'); +module.exports.components['views.rooms.LinkPreviewWidget'] = require('./components/views/rooms/LinkPreviewWidget'); module.exports.components['views.rooms.MemberInfo'] = require('./components/views/rooms/MemberInfo'); module.exports.components['views.rooms.MemberList'] = require('./components/views/rooms/MemberList'); module.exports.components['views.rooms.MemberTile'] = require('./components/views/rooms/MemberTile'); @@ -90,8 +91,8 @@ module.exports.components['views.rooms.RoomPreviewBar'] = require('./components/ module.exports.components['views.rooms.RoomSettings'] = require('./components/views/rooms/RoomSettings'); module.exports.components['views.rooms.RoomTile'] = require('./components/views/rooms/RoomTile'); module.exports.components['views.rooms.RoomTopicEditor'] = require('./components/views/rooms/RoomTopicEditor'); -module.exports.components['views.rooms.SearchableEntityList'] = require('./components/views/rooms/SearchableEntityList'); module.exports.components['views.rooms.SearchResultTile'] = require('./components/views/rooms/SearchResultTile'); +module.exports.components['views.rooms.SearchableEntityList'] = require('./components/views/rooms/SearchableEntityList'); module.exports.components['views.rooms.SimpleRoomHeader'] = require('./components/views/rooms/SimpleRoomHeader'); module.exports.components['views.rooms.TabCompleteBar'] = require('./components/views/rooms/TabCompleteBar'); module.exports.components['views.rooms.TopUnreadMessagesBar'] = require('./components/views/rooms/TopUnreadMessagesBar'); diff --git a/src/components/views/messages/TextualBody.js b/src/components/views/messages/TextualBody.js index 92447dd1da..d93b4bac9b 100644 --- a/src/components/views/messages/TextualBody.js +++ b/src/components/views/messages/TextualBody.js @@ -22,6 +22,7 @@ var HtmlUtils = require('../../../HtmlUtils'); var linkify = require('linkifyjs'); var linkifyElement = require('linkifyjs/element'); var linkifyMatrix = require('../../../linkify-matrix'); +var sdk = require('../../../index'); linkifyMatrix(linkify); @@ -39,26 +40,42 @@ module.exports = React.createClass({ highlightLink: React.PropTypes.string, }, + getInitialState: function() { + return { + link: null, + }; + }, + componentDidMount: function() { linkifyElement(this.refs.content, linkifyMatrix.options); - if (this.props.mxEvent.getContent().format === "org.matrix.custom.html") - HtmlUtils.highlightDom(ReactDOM.findDOMNode(this)); - }, - - componentDidUpdate: function() { - // XXX: why don't we linkify here? - // XXX: why do we bother doing this on update at all, given events are immutable? + var link = this.findLink(this.refs.content.children); + if (link) { + this.setState({ link: link.getAttribute("href") }); + } if (this.props.mxEvent.getContent().format === "org.matrix.custom.html") HtmlUtils.highlightDom(ReactDOM.findDOMNode(this)); }, - shouldComponentUpdate: function(nextProps) { + findLink: function(nodes) { + for (var i = 0; i < nodes.length; i++) { + var node = nodes[i]; + if (node.tagName === "A" && node.getAttribute("href")) { + return node; + } + else if (node.children && node.children.length) { + return this.findLink(node.children) + } + } + }, + + shouldComponentUpdate: function(nextProps, nextState) { // exploit that events are immutable :) return (nextProps.mxEvent.getId() !== this.props.mxEvent.getId() || nextProps.highlights !== this.props.highlights || - nextProps.highlightLink !== this.props.highlightLink); + nextProps.highlightLink !== this.props.highlightLink || + nextState.link !== this.state.link); }, render: function() { @@ -67,24 +84,34 @@ module.exports = React.createClass({ var body = HtmlUtils.bodyToHtml(content, this.props.highlights, {highlightLink: this.props.highlightLink}); + + var widget; + if (this.state.link) { + var LinkPreviewWidget = sdk.getComponent('rooms.LinkPreviewWidget'); + widget = ; + } + switch (content.msgtype) { case "m.emote": var name = mxEvent.sender ? mxEvent.sender.name : mxEvent.getSender(); return ( * { name } { body } + { widget } ); case "m.notice": return ( { body } + { widget } ); default: // including "m.text" return ( { body } + { widget } ); } diff --git a/src/components/views/rooms/LinkPreviewWidget.js b/src/components/views/rooms/LinkPreviewWidget.js new file mode 100644 index 0000000000..f474776713 --- /dev/null +++ b/src/components/views/rooms/LinkPreviewWidget.js @@ -0,0 +1,60 @@ +/* +Copyright 2016 OpenMarket Ltd + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +'use strict'; + +var React = require('react'); + +var MatrixClientPeg = require('../../../MatrixClientPeg'); + +module.exports = React.createClass({ + displayName: 'LinkPreviewWidget', + + propTypes: { + link: React.PropTypes.string.isRequired + }, + + getInitialState: function() { + return { + preview: {} + }; + }, + + componentWillMount: function() { + MatrixClientPeg.get().getUrlPreview(this.props.link).then((res)=>{ + this.setState({ preview: res }); + }, (error)=>{ + console.error("Failed to get preview for URL: " + error); + }); + }, + + render: function() { + var p = this.state.preview; + return ( +
+
{ p["og:title"] }
+
{ p["og:site_name"] ? (" &emdash; " + p["og:site_name"]) : null }
+
+ +
+
+ { p["og:description"] } +
+ +
+ ); + } +}); From 4d959fc33b9cf863368320fb9a789e17efa86511 Mon Sep 17 00:00:00 2001 From: Matthew Hodgson Date: Fri, 1 Apr 2016 02:16:11 +0100 Subject: [PATCH 02/18] improve layout and make thumbnails work --- .../views/rooms/LinkPreviewWidget.js | 20 +++++++++++-------- 1 file changed, 12 insertions(+), 8 deletions(-) diff --git a/src/components/views/rooms/LinkPreviewWidget.js b/src/components/views/rooms/LinkPreviewWidget.js index f474776713..e44717034a 100644 --- a/src/components/views/rooms/LinkPreviewWidget.js +++ b/src/components/views/rooms/LinkPreviewWidget.js @@ -29,7 +29,7 @@ module.exports = React.createClass({ getInitialState: function() { return { - preview: {} + preview: null; }; }, @@ -37,23 +37,27 @@ module.exports = React.createClass({ MatrixClientPeg.get().getUrlPreview(this.props.link).then((res)=>{ this.setState({ preview: res }); }, (error)=>{ - console.error("Failed to get preview for URL: " + error); + console.error("Failed to get preview for " + this.props.link + " " + error); }); }, render: function() { var p = this.state.preview; + if (!p) return
; + var img = p["og:image"] + if (img && img.startsWith("mxc://")) img = MatrixClientPeg.get().mxcUrlToHttp(img, 100, 100) return (
-
{ p["og:title"] }
-
{ p["og:site_name"] ? (" &emdash; " + p["og:site_name"]) : null }
- +
-
- { p["og:description"] } +
+ +
{ p["og:site_name"] ? (" - " + p["og:site_name"]) : null }
+
+ { p["og:description"] } +
-
); } From 62d04c38ef3b3d7a107d24904a238feab800a769 Mon Sep 17 00:00:00 2001 From: Matthew Hodgson Date: Fri, 1 Apr 2016 02:23:29 +0100 Subject: [PATCH 03/18] fix typo and add linkify descriptions --- .../views/rooms/LinkPreviewWidget.js | 19 +++++++++++++++++-- 1 file changed, 17 insertions(+), 2 deletions(-) diff --git a/src/components/views/rooms/LinkPreviewWidget.js b/src/components/views/rooms/LinkPreviewWidget.js index e44717034a..d55d2e9a9d 100644 --- a/src/components/views/rooms/LinkPreviewWidget.js +++ b/src/components/views/rooms/LinkPreviewWidget.js @@ -20,6 +20,11 @@ var React = require('react'); var MatrixClientPeg = require('../../../MatrixClientPeg'); +var linkify = require('linkifyjs'); +var linkifyElement = require('linkifyjs/element'); +var linkifyMatrix = require('../../../linkify-matrix'); +linkifyMatrix(linkify); + module.exports = React.createClass({ displayName: 'LinkPreviewWidget', @@ -29,7 +34,7 @@ module.exports = React.createClass({ getInitialState: function() { return { - preview: null; + preview: null }; }, @@ -41,6 +46,16 @@ module.exports = React.createClass({ }); }, + componentDidMount: function() { + if (this.refs.description) + linkifyElement(this.refs.description, linkifyMatrix.options); + }, + + componentDidUpdate: function() { + if (this.refs.description) + linkifyElement(this.refs.description, linkifyMatrix.options); + }, + render: function() { var p = this.state.preview; if (!p) return
; @@ -54,7 +69,7 @@ module.exports = React.createClass({
{ p["og:site_name"] ? (" - " + p["og:site_name"]) : null }
-
+
{ p["og:description"] }
From 4388334e30dd2505165676effeb924c8caacdd39 Mon Sep 17 00:00:00 2001 From: Matthew Hodgson Date: Sat, 2 Apr 2016 00:36:19 +0100 Subject: [PATCH 04/18] fix up scroll behaviour when loading widgets --- src/components/structures/MessagePanel.js | 10 ++++++ src/components/structures/RoomView.js | 6 ++-- src/components/views/messages/MImageBody.js | 32 ++++--------------- src/components/views/messages/MessageEvent.js | 4 +-- src/components/views/messages/TextualBody.js | 5 ++- src/components/views/rooms/EventTile.js | 6 ++-- .../views/rooms/LinkPreviewWidget.js | 32 +++++++++++++++---- .../views/rooms/SearchResultTile.js | 4 +-- 8 files changed, 55 insertions(+), 44 deletions(-) diff --git a/src/components/structures/MessagePanel.js b/src/components/structures/MessagePanel.js index aaa00934cb..dff3e9e514 100644 --- a/src/components/structures/MessagePanel.js +++ b/src/components/structures/MessagePanel.js @@ -302,6 +302,7 @@ module.exports = React.createClass({ ref={this._collectEventNode.bind(this, eventId)} data-scroll-token={scrollToken}> ); @@ -368,6 +369,15 @@ module.exports = React.createClass({ this.eventNodes[eventId] = node; }, + // once dynamic content in the events load, make the scrollPanel check the + // scroll offsets. + _onWidgetLoad: function() { + var scrollPanel = this.refs.scrollPanel; + if (scrollPanel) { + scrollPanel.checkScroll(); + } + }, + onResize: function() { dis.dispatch({ action: 'timeline_resize' }, true); }, diff --git a/src/components/structures/RoomView.js b/src/components/structures/RoomView.js index b5c34de20d..7fdb9f437a 100644 --- a/src/components/structures/RoomView.js +++ b/src/components/structures/RoomView.js @@ -798,9 +798,9 @@ module.exports = React.createClass({ } } - // once images in the search results load, make the scrollPanel check + // once dynamic content in the search results load, make the scrollPanel check // the scroll offsets. - var onImageLoad = () => { + var onWidgetLoad = () => { var scrollPanel = this.refs.searchResultsPanel; if (scrollPanel) { scrollPanel.checkScroll(); @@ -844,7 +844,7 @@ module.exports = React.createClass({ searchResult={result} searchHighlights={this.state.searchHighlights} resultLink={resultLink} - onImageLoad={onImageLoad}/>); + onWidgetLoad={onWidgetLoad}/>); } return ret; }, diff --git a/src/components/views/messages/MImageBody.js b/src/components/views/messages/MImageBody.js index ff05cf8609..01cd7cc637 100644 --- a/src/components/views/messages/MImageBody.js +++ b/src/components/views/messages/MImageBody.js @@ -20,6 +20,7 @@ var React = require('react'); var filesize = require('filesize'); var MatrixClientPeg = require('../../../MatrixClientPeg'); +var ImageUtils = require('../../../ImageUtils'); var Modal = require('../../../Modal'); var sdk = require('../../../index'); var dis = require("../../../dispatcher"); @@ -32,29 +33,7 @@ module.exports = React.createClass({ mxEvent: React.PropTypes.object.isRequired, /* callback called when images in events are loaded */ - onImageLoad: React.PropTypes.func, - }, - - thumbHeight: function(fullWidth, fullHeight, thumbWidth, thumbHeight) { - if (!fullWidth || !fullHeight) { - // Cannot calculate thumbnail height for image: missing w/h in metadata. We can't even - // log this because it's spammy - return undefined; - } - if (fullWidth < thumbWidth && fullHeight < thumbHeight) { - // no scaling needs to be applied - return fullHeight; - } - var widthMulti = thumbWidth / fullWidth; - var heightMulti = thumbHeight / fullHeight; - if (widthMulti < heightMulti) { - // width is the dominant dimension so scaling will be fixed on that - return Math.floor(widthMulti * fullHeight); - } - else { - // height is the dominant dimension so scaling will be fixed on that - return Math.floor(heightMulti * fullHeight); - } + onWidgetLoad: React.PropTypes.func, }, onClick: function onClick(ev) { @@ -134,7 +113,9 @@ module.exports = React.createClass({ // the alternative here would be 600*timelineWidth/800; to scale them down to fit inside a 4:3 bounding box //console.log("trying to fit image into timelineWidth of " + this.refs.body.offsetWidth + " or " + this.refs.body.clientWidth); - if (content.info) thumbHeight = this.thumbHeight(content.info.w, content.info.h, timelineWidth, maxHeight); + if (content.info) { + thumbHeight = ImageUtils.thumbHeight(content.info.w, content.info.h, timelineWidth, maxHeight); + } this.refs.image.style.height = thumbHeight + "px"; // console.log("Image height now", thumbHeight); }, @@ -152,8 +133,7 @@ module.exports = React.createClass({ {content.body} + onMouseLeave={this.onImageLeave} />
diff --git a/src/components/views/messages/MessageEvent.js b/src/components/views/messages/MessageEvent.js index 34d6d53924..48512dad22 100644 --- a/src/components/views/messages/MessageEvent.js +++ b/src/components/views/messages/MessageEvent.js @@ -39,7 +39,7 @@ module.exports = React.createClass({ highlightLink: React.PropTypes.string, /* callback called when images in events are loaded */ - onImageLoad: React.PropTypes.func, + onWidgetLoad: React.PropTypes.func, }, @@ -64,6 +64,6 @@ module.exports = React.createClass({ return ; + onWidgetLoad={this.props.onWidgetLoad} />; }, }); diff --git a/src/components/views/messages/TextualBody.js b/src/components/views/messages/TextualBody.js index d93b4bac9b..5a98119dac 100644 --- a/src/components/views/messages/TextualBody.js +++ b/src/components/views/messages/TextualBody.js @@ -38,6 +38,9 @@ module.exports = React.createClass({ /* link URL for the highlights */ highlightLink: React.PropTypes.string, + + /* callback for when our widget has loaded */ + onWidgetLoad: React.PropTypes.func, }, getInitialState: function() { @@ -88,7 +91,7 @@ module.exports = React.createClass({ var widget; if (this.state.link) { var LinkPreviewWidget = sdk.getComponent('rooms.LinkPreviewWidget'); - widget = ; + widget = ; } switch (content.msgtype) { diff --git a/src/components/views/rooms/EventTile.js b/src/components/views/rooms/EventTile.js index 8771afac36..08cefda67a 100644 --- a/src/components/views/rooms/EventTile.js +++ b/src/components/views/rooms/EventTile.js @@ -105,8 +105,8 @@ module.exports = React.createClass({ /* is this the focused event */ isSelectedEvent: React.PropTypes.bool, - /* callback called when images in events are loaded */ - onImageLoad: React.PropTypes.func, + /* callback called when dynamic content in events are loaded */ + onWidgetLoad: React.PropTypes.func, }, getInitialState: function() { @@ -345,7 +345,7 @@ module.exports = React.createClass({
+ onWidgetLoad={this.props.onWidgetLoad} />
); diff --git a/src/components/views/rooms/LinkPreviewWidget.js b/src/components/views/rooms/LinkPreviewWidget.js index d55d2e9a9d..3391745a9a 100644 --- a/src/components/views/rooms/LinkPreviewWidget.js +++ b/src/components/views/rooms/LinkPreviewWidget.js @@ -19,6 +19,7 @@ limitations under the License. var React = require('react'); var MatrixClientPeg = require('../../../MatrixClientPeg'); +var ImageUtils = require('../../../ImageUtils'); var linkify = require('linkifyjs'); var linkifyElement = require('linkifyjs/element'); @@ -29,7 +30,8 @@ module.exports = React.createClass({ displayName: 'LinkPreviewWidget', propTypes: { - link: React.PropTypes.string.isRequired + link: React.PropTypes.string.isRequired, + onWidgetLoad: React.PropTypes.func, }, getInitialState: function() { @@ -41,6 +43,7 @@ module.exports = React.createClass({ componentWillMount: function() { MatrixClientPeg.get().getUrlPreview(this.props.link).then((res)=>{ this.setState({ preview: res }); + this.props.onWidgetLoad(); }, (error)=>{ console.error("Failed to get preview for " + this.props.link + " " + error); }); @@ -59,13 +62,28 @@ module.exports = React.createClass({ render: function() { var p = this.state.preview; if (!p) return
; - var img = p["og:image"] - if (img && img.startsWith("mxc://")) img = MatrixClientPeg.get().mxcUrlToHttp(img, 100, 100) + + var image = p["og:image"]; + var imageMaxWidth = 100, imageMaxHeight = 100; + if (image && image.startsWith("mxc://")) { + image = MatrixClientPeg.get().mxcUrlToHttp(image, imageMaxWidth, imageMaxHeight); + } + + var thumbHeight = imageMaxHeight; + if (p["og:image:width"] && p["og:image:height"]) { + thumbHeight = ImageUtils.thumbHeight(p["og:image:width"], p["og:image:height"], imageMaxWidth, imageMaxHeight); + } + + var img; + if (image) { + img =
+ +
+ } + return ( -
-
- -
+
+ { img }
{ p["og:site_name"] ? (" - " + p["og:site_name"]) : null }
diff --git a/src/components/views/rooms/SearchResultTile.js b/src/components/views/rooms/SearchResultTile.js index 1fc0384433..7fac244481 100644 --- a/src/components/views/rooms/SearchResultTile.js +++ b/src/components/views/rooms/SearchResultTile.js @@ -32,7 +32,7 @@ module.exports = React.createClass({ // href for the highlights in this result resultLink: React.PropTypes.string, - onImageLoad: React.PropTypes.func, + onWidgetLoad: React.PropTypes.func, }, render: function() { @@ -56,7 +56,7 @@ module.exports = React.createClass({ if (EventTile.haveTileForEvent(ev)) { ret.push(); + onWidgetLoad={this.props.onWidgetLoad} />); } } return ( From a6b6be7f79cf1923a97834cfcccb8222e2c27aa2 Mon Sep 17 00:00:00 2001 From: Matthew Hodgson Date: Sat, 2 Apr 2016 00:36:33 +0100 Subject: [PATCH 05/18] add ImageUtils --- src/ImageUtils.js | 42 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 42 insertions(+) create mode 100644 src/ImageUtils.js diff --git a/src/ImageUtils.js b/src/ImageUtils.js new file mode 100644 index 0000000000..a53f4fe31c --- /dev/null +++ b/src/ImageUtils.js @@ -0,0 +1,42 @@ +/* +Copyright 2015, 2016 OpenMarket Ltd + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +'use strict'; + +module.exports = { + thumbHeight: function(fullWidth, fullHeight, thumbWidth, thumbHeight) { + if (!fullWidth || !fullHeight) { + // Cannot calculate thumbnail height for image: missing w/h in metadata. We can't even + // log this because it's spammy + return undefined; + } + if (fullWidth < thumbWidth && fullHeight < thumbHeight) { + // no scaling needs to be applied + return fullHeight; + } + var widthMulti = thumbWidth / fullWidth; + var heightMulti = thumbHeight / fullHeight; + if (widthMulti < heightMulti) { + // width is the dominant dimension so scaling will be fixed on that + return Math.floor(widthMulti * fullHeight); + } + else { + // height is the dominant dimension so scaling will be fixed on that + return Math.floor(heightMulti * fullHeight); + } + }, +} + From bffb482133598863aba5d704e613c2779ea636d7 Mon Sep 17 00:00:00 2001 From: Matthew Hodgson Date: Sat, 2 Apr 2016 02:46:19 +0100 Subject: [PATCH 06/18] add FIXME --- src/components/views/rooms/LinkPreviewWidget.js | 1 + 1 file changed, 1 insertion(+) diff --git a/src/components/views/rooms/LinkPreviewWidget.js b/src/components/views/rooms/LinkPreviewWidget.js index 3391745a9a..bfefb293c9 100644 --- a/src/components/views/rooms/LinkPreviewWidget.js +++ b/src/components/views/rooms/LinkPreviewWidget.js @@ -63,6 +63,7 @@ module.exports = React.createClass({ var p = this.state.preview; if (!p) return
; + // FIXME: do we want to factor out all image displaying between this and MImageBody - especially for lightboxing? var image = p["og:image"]; var imageMaxWidth = 100, imageMaxHeight = 100; if (image && image.startsWith("mxc://")) { From f9c914c40e1c8234e0b0165f870976006de7b158 Mon Sep 17 00:00:00 2001 From: Matthew Hodgson Date: Sun, 3 Apr 2016 01:21:56 +0100 Subject: [PATCH 07/18] specify timestamps for historical previews --- src/components/views/messages/TextualBody.js | 2 +- src/components/views/rooms/LinkPreviewWidget.js | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/src/components/views/messages/TextualBody.js b/src/components/views/messages/TextualBody.js index 5a98119dac..d13111bcc6 100644 --- a/src/components/views/messages/TextualBody.js +++ b/src/components/views/messages/TextualBody.js @@ -91,7 +91,7 @@ module.exports = React.createClass({ var widget; if (this.state.link) { var LinkPreviewWidget = sdk.getComponent('rooms.LinkPreviewWidget'); - widget = ; + widget = ; } switch (content.msgtype) { diff --git a/src/components/views/rooms/LinkPreviewWidget.js b/src/components/views/rooms/LinkPreviewWidget.js index bfefb293c9..80e03522a4 100644 --- a/src/components/views/rooms/LinkPreviewWidget.js +++ b/src/components/views/rooms/LinkPreviewWidget.js @@ -31,6 +31,7 @@ module.exports = React.createClass({ propTypes: { link: React.PropTypes.string.isRequired, + ts: React.PropTypes.number, onWidgetLoad: React.PropTypes.func, }, @@ -41,7 +42,7 @@ module.exports = React.createClass({ }, componentWillMount: function() { - MatrixClientPeg.get().getUrlPreview(this.props.link).then((res)=>{ + MatrixClientPeg.get().getUrlPreview(this.props.link, this.props.ts).then((res)=>{ this.setState({ preview: res }); this.props.onWidgetLoad(); }, (error)=>{ From e61c99f7f3c2c17f0516bad5efc35e8b958c53e2 Mon Sep 17 00:00:00 2001 From: Matthew Hodgson Date: Sun, 3 Apr 2016 02:50:36 +0100 Subject: [PATCH 08/18] support cancelling previews --- src/components/views/messages/TextualBody.js | 2 +- .../views/rooms/LinkPreviewWidget.js | 21 +++++++++++++++++-- 2 files changed, 20 insertions(+), 3 deletions(-) diff --git a/src/components/views/messages/TextualBody.js b/src/components/views/messages/TextualBody.js index d13111bcc6..dff6772af2 100644 --- a/src/components/views/messages/TextualBody.js +++ b/src/components/views/messages/TextualBody.js @@ -91,7 +91,7 @@ module.exports = React.createClass({ var widget; if (this.state.link) { var LinkPreviewWidget = sdk.getComponent('rooms.LinkPreviewWidget'); - widget = ; + widget = ; } switch (content.msgtype) { diff --git a/src/components/views/rooms/LinkPreviewWidget.js b/src/components/views/rooms/LinkPreviewWidget.js index 80e03522a4..266ec36ecf 100644 --- a/src/components/views/rooms/LinkPreviewWidget.js +++ b/src/components/views/rooms/LinkPreviewWidget.js @@ -31,7 +31,7 @@ module.exports = React.createClass({ propTypes: { link: React.PropTypes.string.isRequired, - ts: React.PropTypes.number, + mxEvent: React.PropTypes.object.isRequired, onWidgetLoad: React.PropTypes.func, }, @@ -42,7 +42,13 @@ module.exports = React.createClass({ }, componentWillMount: function() { - MatrixClientPeg.get().getUrlPreview(this.props.link, this.props.ts).then((res)=>{ + if (global.localStorage) { + if (global.localStorage.getItem("hide_preview_" + this.props.mxEvent.getId()) === "1") { + return; + } + } + + MatrixClientPeg.get().getUrlPreview(this.props.link, this.props.mxEvent.getTs()).then((res)=>{ this.setState({ preview: res }); this.props.onWidgetLoad(); }, (error)=>{ @@ -60,6 +66,15 @@ module.exports = React.createClass({ linkifyElement(this.refs.description, linkifyMatrix.options); }, + onCancelClick: function(event) { + this.setState({ preview: null }); + // FIXME: persist this somewhere smarter than local storage + // FIXME: add to event contextual menu ability to unhide hidden previews + if (global.localStorage) { + global.localStorage.setItem("hide_preview_" + this.props.mxEvent.getId(), "1"); + } + }, + render: function() { var p = this.state.preview; if (!p) return
; @@ -93,6 +108,8 @@ module.exports = React.createClass({ { p["og:description"] }
+
); } From 0eb7b627fc9e4f960d97357d598095b5258be37b Mon Sep 17 00:00:00 2001 From: Matthew Hodgson Date: Sun, 3 Apr 2016 23:30:48 +0100 Subject: [PATCH 09/18] ugly impl to track whether to hide the widget or not --- src/components/views/messages/TextualBody.js | 39 +++++++++++++++++-- .../views/rooms/LinkPreviewWidget.js | 18 +-------- 2 files changed, 38 insertions(+), 19 deletions(-) diff --git a/src/components/views/messages/TextualBody.js b/src/components/views/messages/TextualBody.js index dff6772af2..771bddf86e 100644 --- a/src/components/views/messages/TextualBody.js +++ b/src/components/views/messages/TextualBody.js @@ -46,6 +46,13 @@ module.exports = React.createClass({ getInitialState: function() { return { link: null, + + // track whether the preview widget is hidden + // we can't directly use mxEvent's widgetHidden property + // as shouldComponentUpdate needs to be able to do before & after + // comparisons of the property (and we don't pass it in as a top + // level prop to avoid bloating the number of props flying around) + widgetHidden: false, }; }, @@ -55,6 +62,13 @@ module.exports = React.createClass({ var link = this.findLink(this.refs.content.children); if (link) { this.setState({ link: link.getAttribute("href") }); + + // lazy-load the hidden state of the preview widget from localstorage + if (global.localStorage) { + var hidden = global.localStorage.getItem("hide_preview_" + this.props.mxEvent.getId()); + this.props.mxEvent.widgetHidden = hidden; + this.setState({ widgetHidden: hidden }); + } } if (this.props.mxEvent.getContent().format === "org.matrix.custom.html") @@ -78,7 +92,22 @@ module.exports = React.createClass({ return (nextProps.mxEvent.getId() !== this.props.mxEvent.getId() || nextProps.highlights !== this.props.highlights || nextProps.highlightLink !== this.props.highlightLink || - nextState.link !== this.state.link); + nextState.link !== this.state.link || + nextProps.mxEvent.widgetHidden !== this.state.widgetHidden); + }, + + componentWillUpdate: function(nextProps, nextState) { + this.setState({ widgetHidden: nextProps.mxEvent.widgetHidden }); + }, + + onCancelClick: function(event) { + this.props.mxEvent.widgetHidden = true; + this.setState({ widgetHidden: true }); + // FIXME: persist this somewhere smarter than local storage + if (global.localStorage) { + global.localStorage.setItem("hide_preview_" + this.props.mxEvent.getId(), "1"); + } + this.forceUpdate(); }, render: function() { @@ -89,9 +118,13 @@ module.exports = React.createClass({ var widget; - if (this.state.link) { + if (this.state.link && !this.state.widgetHidden) { var LinkPreviewWidget = sdk.getComponent('rooms.LinkPreviewWidget'); - widget = ; + widget = ; } switch (content.msgtype) { diff --git a/src/components/views/rooms/LinkPreviewWidget.js b/src/components/views/rooms/LinkPreviewWidget.js index 266ec36ecf..e802390cbb 100644 --- a/src/components/views/rooms/LinkPreviewWidget.js +++ b/src/components/views/rooms/LinkPreviewWidget.js @@ -32,6 +32,7 @@ module.exports = React.createClass({ propTypes: { link: React.PropTypes.string.isRequired, mxEvent: React.PropTypes.object.isRequired, + onCancelClick: React.PropTypes.func, onWidgetLoad: React.PropTypes.func, }, @@ -42,12 +43,6 @@ module.exports = React.createClass({ }, componentWillMount: function() { - if (global.localStorage) { - if (global.localStorage.getItem("hide_preview_" + this.props.mxEvent.getId()) === "1") { - return; - } - } - MatrixClientPeg.get().getUrlPreview(this.props.link, this.props.mxEvent.getTs()).then((res)=>{ this.setState({ preview: res }); this.props.onWidgetLoad(); @@ -66,15 +61,6 @@ module.exports = React.createClass({ linkifyElement(this.refs.description, linkifyMatrix.options); }, - onCancelClick: function(event) { - this.setState({ preview: null }); - // FIXME: persist this somewhere smarter than local storage - // FIXME: add to event contextual menu ability to unhide hidden previews - if (global.localStorage) { - global.localStorage.setItem("hide_preview_" + this.props.mxEvent.getId(), "1"); - } - }, - render: function() { var p = this.state.preview; if (!p) return
; @@ -109,7 +95,7 @@ module.exports = React.createClass({
+ onClick={ this.props.onCancelClick }/>
); } From 96b0f42db2978affa6d068145ed9e7f5b0520f39 Mon Sep 17 00:00:00 2001 From: Matthew Hodgson Date: Mon, 4 Apr 2016 00:18:18 +0100 Subject: [PATCH 10/18] support lightboxes for image previews --- src/components/views/messages/MImageBody.js | 1 + .../views/rooms/LinkPreviewWidget.js | 28 ++++++++++++++++++- src/components/views/rooms/MemberInfo.js | 2 +- 3 files changed, 29 insertions(+), 2 deletions(-) diff --git a/src/components/views/messages/MImageBody.js b/src/components/views/messages/MImageBody.js index 01cd7cc637..2ec2e8ec8f 100644 --- a/src/components/views/messages/MImageBody.js +++ b/src/components/views/messages/MImageBody.js @@ -50,6 +50,7 @@ module.exports = React.createClass({ if (content.info) { params.width = content.info.w; params.height = content.info.h; + params.size = content.info.size; } Modal.createDialog(ImageView, params, "mx_Dialog_lightbox"); diff --git a/src/components/views/rooms/LinkPreviewWidget.js b/src/components/views/rooms/LinkPreviewWidget.js index e802390cbb..85bb7eb8ee 100644 --- a/src/components/views/rooms/LinkPreviewWidget.js +++ b/src/components/views/rooms/LinkPreviewWidget.js @@ -18,8 +18,10 @@ limitations under the License. var React = require('react'); +var sdk = require('../../../index'); var MatrixClientPeg = require('../../../MatrixClientPeg'); var ImageUtils = require('../../../ImageUtils'); +var Modal = require('../../../Modal'); var linkify = require('linkifyjs'); var linkifyElement = require('linkifyjs/element'); @@ -61,6 +63,30 @@ module.exports = React.createClass({ linkifyElement(this.refs.description, linkifyMatrix.options); }, + onImageClick: function(ev) { + var p = this.state.preview; + if (ev.button == 0 && !ev.metaKey) { + ev.preventDefault(); + var ImageView = sdk.getComponent("elements.ImageView"); + + var src = p["og:image"]; + if (src && src.startsWith("mxc://")) { + src = MatrixClientPeg.get().mxcUrlToHttp(src); + } + + var params = { + src: src, + width: p["og:image:width"], + height: p["og:image:height"], + name: p["og:title"] || p["og:description"], + size: p["matrix:image:size"], + link: this.props.link, + }; + + Modal.createDialog(ImageView, params, "mx_Dialog_lightbox"); + } + }, + render: function() { var p = this.state.preview; if (!p) return
; @@ -80,7 +106,7 @@ module.exports = React.createClass({ var img; if (image) { img =
- +
} diff --git a/src/components/views/rooms/MemberInfo.js b/src/components/views/rooms/MemberInfo.js index 52313430d4..43896e3e83 100644 --- a/src/components/views/rooms/MemberInfo.js +++ b/src/components/views/rooms/MemberInfo.js @@ -451,7 +451,7 @@ module.exports = React.createClass({ onMemberAvatarClick: function () { var avatarUrl = this.props.member.user.avatarUrl; if(!avatarUrl) return; - + var httpUrl = MatrixClientPeg.get().mxcUrlToHttp(avatarUrl); var ImageView = sdk.getComponent("elements.ImageView"); var params = { From 1de4e0d2ddd57b60e2f41533020b6cbad2e98fd2 Mon Sep 17 00:00:00 2001 From: Matthew Hodgson Date: Mon, 4 Apr 2016 01:06:54 +0100 Subject: [PATCH 11/18] label previews with the target URL if all else fails --- src/components/views/rooms/LinkPreviewWidget.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/views/rooms/LinkPreviewWidget.js b/src/components/views/rooms/LinkPreviewWidget.js index 85bb7eb8ee..adfdc1b3d2 100644 --- a/src/components/views/rooms/LinkPreviewWidget.js +++ b/src/components/views/rooms/LinkPreviewWidget.js @@ -78,7 +78,7 @@ module.exports = React.createClass({ src: src, width: p["og:image:width"], height: p["og:image:height"], - name: p["og:title"] || p["og:description"], + name: p["og:title"] || p["og:description"] || this.props.link, size: p["matrix:image:size"], link: this.props.link, }; From 1125c62505d63d99a09ec9c20243cdaf02f4bd94 Mon Sep 17 00:00:00 2001 From: Matthew Hodgson Date: Thu, 7 Apr 2016 18:10:35 +0100 Subject: [PATCH 12/18] add comments for thumbHeight --- src/ImageUtils.js | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/src/ImageUtils.js b/src/ImageUtils.js index a53f4fe31c..fdb12c7608 100644 --- a/src/ImageUtils.js +++ b/src/ImageUtils.js @@ -17,6 +17,21 @@ limitations under the License. 'use strict'; module.exports = { + + /** + * Returns the actual height that an image of dimensions (fullWidth, fullHeight) + * will occupy if resized to fit inside a thumbnail bounding box of size + * (thumbWidth, thumbHeight). + * + * If the aspect ratio of the source image is taller than the aspect ratio of + * the thumbnail bounding box, then we return the thumbHeight parameter unchanged. + * Otherwise we return the thumbHeight parameter scaled down appropriately to + * reflect the actual height the scaled thumbnail occupies. + * + * This is very useful for calculating how much height a thumbnail will actually + * consume in the timeline, when performing scroll offset calcuations + * (e.g. scroll locking) + */ thumbHeight: function(fullWidth, fullHeight, thumbWidth, thumbHeight) { if (!fullWidth || !fullHeight) { // Cannot calculate thumbnail height for image: missing w/h in metadata. We can't even From 1d8b08040e886e972a4cbc9d577552b313eeb4b3 Mon Sep 17 00:00:00 2001 From: Matthew Hodgson Date: Thu, 7 Apr 2016 18:58:50 +0100 Subject: [PATCH 13/18] incorporate PR feedback --- src/components/structures/MessagePanel.js | 1 + src/components/views/messages/MImageBody.js | 5 +- src/components/views/messages/MessageEvent.js | 2 +- src/components/views/messages/TextualBody.js | 26 +++++----- .../views/rooms/LinkPreviewWidget.js | 49 ++++++++++--------- 5 files changed, 42 insertions(+), 41 deletions(-) diff --git a/src/components/structures/MessagePanel.js b/src/components/structures/MessagePanel.js index 69f622e0d4..773c223331 100644 --- a/src/components/structures/MessagePanel.js +++ b/src/components/structures/MessagePanel.js @@ -405,6 +405,7 @@ module.exports = React.createClass({ var scrollPanel = this.refs.scrollPanel; if (scrollPanel) { scrollPanel.checkScroll(); + scrollPanel.refs.geminiPanel.forceUpdate(); } }, diff --git a/src/components/views/messages/MImageBody.js b/src/components/views/messages/MImageBody.js index 2ec2e8ec8f..13f9cf4c19 100644 --- a/src/components/views/messages/MImageBody.js +++ b/src/components/views/messages/MImageBody.js @@ -31,9 +31,6 @@ module.exports = React.createClass({ propTypes: { /* the MatrixEvent to show */ mxEvent: React.PropTypes.object.isRequired, - - /* callback called when images in events are loaded */ - onWidgetLoad: React.PropTypes.func, }, onClick: function onClick(ev) { @@ -50,7 +47,7 @@ module.exports = React.createClass({ if (content.info) { params.width = content.info.w; params.height = content.info.h; - params.size = content.info.size; + params.fileSize = content.info.size; } Modal.createDialog(ImageView, params, "mx_Dialog_lightbox"); diff --git a/src/components/views/messages/MessageEvent.js b/src/components/views/messages/MessageEvent.js index 48512dad22..38a25d4904 100644 --- a/src/components/views/messages/MessageEvent.js +++ b/src/components/views/messages/MessageEvent.js @@ -38,7 +38,7 @@ module.exports = React.createClass({ /* link URL for the highlights */ highlightLink: React.PropTypes.string, - /* callback called when images in events are loaded */ + /* callback called when dynamic content in events are loaded */ onWidgetLoad: React.PropTypes.func, }, diff --git a/src/components/views/messages/TextualBody.js b/src/components/views/messages/TextualBody.js index 771bddf86e..36120a3b81 100644 --- a/src/components/views/messages/TextualBody.js +++ b/src/components/views/messages/TextualBody.js @@ -66,6 +66,7 @@ module.exports = React.createClass({ // lazy-load the hidden state of the preview widget from localstorage if (global.localStorage) { var hidden = global.localStorage.getItem("hide_preview_" + this.props.mxEvent.getId()); + // XXX: we're gutwrenching mxEvent here by setting our own custom property on it this.props.mxEvent.widgetHidden = hidden; this.setState({ widgetHidden: hidden }); } @@ -75,18 +76,6 @@ module.exports = React.createClass({ HtmlUtils.highlightDom(ReactDOM.findDOMNode(this)); }, - findLink: function(nodes) { - for (var i = 0; i < nodes.length; i++) { - var node = nodes[i]; - if (node.tagName === "A" && node.getAttribute("href")) { - return node; - } - else if (node.children && node.children.length) { - return this.findLink(node.children) - } - } - }, - shouldComponentUpdate: function(nextProps, nextState) { // exploit that events are immutable :) return (nextProps.mxEvent.getId() !== this.props.mxEvent.getId() || @@ -100,7 +89,20 @@ module.exports = React.createClass({ this.setState({ widgetHidden: nextProps.mxEvent.widgetHidden }); }, + findLink: function(nodes) { + for (var i = 0; i < nodes.length; i++) { + var node = nodes[i]; + if (node.tagName === "A" && node.getAttribute("href")) { + return node; + } + else if (node.children && node.children.length) { + return this.findLink(node.children) + } + } + }, + onCancelClick: function(event) { + // XXX: we're gutwrenching mxEvent here by setting our own custom property on it this.props.mxEvent.widgetHidden = true; this.setState({ widgetHidden: true }); // FIXME: persist this somewhere smarter than local storage diff --git a/src/components/views/rooms/LinkPreviewWidget.js b/src/components/views/rooms/LinkPreviewWidget.js index adfdc1b3d2..302e0f1e75 100644 --- a/src/components/views/rooms/LinkPreviewWidget.js +++ b/src/components/views/rooms/LinkPreviewWidget.js @@ -32,10 +32,10 @@ module.exports = React.createClass({ displayName: 'LinkPreviewWidget', propTypes: { - link: React.PropTypes.string.isRequired, - mxEvent: React.PropTypes.object.isRequired, - onCancelClick: React.PropTypes.func, - onWidgetLoad: React.PropTypes.func, + link: React.PropTypes.string.isRequired, // the URL being previewed + mxEvent: React.PropTypes.object.isRequired, // the Event associated with the preview + onCancelClick: React.PropTypes.func, // called when the preview's cancel ('hide') button is clicked + onWidgetLoad: React.PropTypes.func, // called when the preview's contents has loaded }, getInitialState: function() { @@ -46,8 +46,10 @@ module.exports = React.createClass({ componentWillMount: function() { MatrixClientPeg.get().getUrlPreview(this.props.link, this.props.mxEvent.getTs()).then((res)=>{ - this.setState({ preview: res }); - this.props.onWidgetLoad(); + this.setState( + { preview: res }, + this.props.onWidgetLoad + ); }, (error)=>{ console.error("Failed to get preview for " + this.props.link + " " + error); }); @@ -65,26 +67,25 @@ module.exports = React.createClass({ onImageClick: function(ev) { var p = this.state.preview; - if (ev.button == 0 && !ev.metaKey) { - ev.preventDefault(); - var ImageView = sdk.getComponent("elements.ImageView"); + if (ev.button != 0 || ev.metaKey) return; + ev.preventDefault(); + var ImageView = sdk.getComponent("elements.ImageView"); - var src = p["og:image"]; - if (src && src.startsWith("mxc://")) { - src = MatrixClientPeg.get().mxcUrlToHttp(src); - } - - var params = { - src: src, - width: p["og:image:width"], - height: p["og:image:height"], - name: p["og:title"] || p["og:description"] || this.props.link, - size: p["matrix:image:size"], - link: this.props.link, - }; - - Modal.createDialog(ImageView, params, "mx_Dialog_lightbox"); + var src = p["og:image"]; + if (src && src.startsWith("mxc://")) { + src = MatrixClientPeg.get().mxcUrlToHttp(src); } + + var params = { + src: src, + width: p["og:image:width"], + height: p["og:image:height"], + name: p["og:title"] || p["og:description"] || this.props.link, + fileSize: p["matrix:image:size"], + link: this.props.link, + }; + + Modal.createDialog(ImageView, params, "mx_Dialog_lightbox"); }, render: function() { From 4abc5d0d36737fd36b944d4e38fc772dd68f9b51 Mon Sep 17 00:00:00 2001 From: Matthew Hodgson Date: Fri, 8 Apr 2016 20:21:12 +0100 Subject: [PATCH 14/18] add comment --- src/components/views/messages/TextualBody.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/components/views/messages/TextualBody.js b/src/components/views/messages/TextualBody.js index 36120a3b81..77550019ab 100644 --- a/src/components/views/messages/TextualBody.js +++ b/src/components/views/messages/TextualBody.js @@ -45,6 +45,8 @@ module.exports = React.createClass({ getInitialState: function() { return { + // the URL (if any) to be previewed with a LinkPreviewWidget + // inside this TextualBody. link: null, // track whether the preview widget is hidden From 23d6edbf63dafe9e75adfc74afecdcb38f8b2cf2 Mon Sep 17 00:00:00 2001 From: Matthew Hodgson Date: Fri, 8 Apr 2016 20:21:27 +0100 Subject: [PATCH 15/18] forceupdate the whole scrollPanel rather than the geminiPanel at vdh's PR review request --- src/components/structures/MessagePanel.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/structures/MessagePanel.js b/src/components/structures/MessagePanel.js index 773c223331..a779317f8f 100644 --- a/src/components/structures/MessagePanel.js +++ b/src/components/structures/MessagePanel.js @@ -405,7 +405,7 @@ module.exports = React.createClass({ var scrollPanel = this.refs.scrollPanel; if (scrollPanel) { scrollPanel.checkScroll(); - scrollPanel.refs.geminiPanel.forceUpdate(); + scrollPanel.forceUpdate(); } }, From 6c372d37f754f3ca8938074d4fff1dd6b89d9987 Mon Sep 17 00:00:00 2001 From: Matthew Hodgson Date: Fri, 8 Apr 2016 21:42:29 +0100 Subject: [PATCH 16/18] add the concept of eventTileOps for managing widget visibility based on vdh's PR feedback --- src/components/views/messages/MessageEvent.js | 15 ++++++----- src/components/views/messages/TextualBody.js | 27 ++++++++++--------- src/components/views/rooms/EventTile.js | 5 ++-- 3 files changed, 26 insertions(+), 21 deletions(-) diff --git a/src/components/views/messages/MessageEvent.js b/src/components/views/messages/MessageEvent.js index 38a25d4904..1313ce6b00 100644 --- a/src/components/views/messages/MessageEvent.js +++ b/src/components/views/messages/MessageEvent.js @@ -42,11 +42,14 @@ module.exports = React.createClass({ onWidgetLoad: React.PropTypes.func, }, + getEventTileOps: function() { + return this.refs.body ? this.refs.body.getEventTileOps() : null; + }, render: function() { - var UnknownMessageTile = sdk.getComponent('messages.UnknownBody'); + var UnknownBody = sdk.getComponent('messages.UnknownBody'); - var tileTypes = { + var bodyTypes = { 'm.text': sdk.getComponent('messages.TextualBody'), 'm.notice': sdk.getComponent('messages.TextualBody'), 'm.emote': sdk.getComponent('messages.TextualBody'), @@ -57,12 +60,12 @@ module.exports = React.createClass({ var content = this.props.mxEvent.getContent(); var msgtype = content.msgtype; - var TileType = UnknownMessageTile; - if (msgtype && tileTypes[msgtype]) { - TileType = tileTypes[msgtype]; + var BodyType = UnknownBody; + if (msgtype && bodyTypes[msgtype]) { + BodyType = bodyTypes[msgtype]; } - return ; }, diff --git a/src/components/views/messages/TextualBody.js b/src/components/views/messages/TextualBody.js index 77550019ab..75fa02e35c 100644 --- a/src/components/views/messages/TextualBody.js +++ b/src/components/views/messages/TextualBody.js @@ -50,10 +50,6 @@ module.exports = React.createClass({ link: null, // track whether the preview widget is hidden - // we can't directly use mxEvent's widgetHidden property - // as shouldComponentUpdate needs to be able to do before & after - // comparisons of the property (and we don't pass it in as a top - // level prop to avoid bloating the number of props flying around) widgetHidden: false, }; }, @@ -68,8 +64,6 @@ module.exports = React.createClass({ // lazy-load the hidden state of the preview widget from localstorage if (global.localStorage) { var hidden = global.localStorage.getItem("hide_preview_" + this.props.mxEvent.getId()); - // XXX: we're gutwrenching mxEvent here by setting our own custom property on it - this.props.mxEvent.widgetHidden = hidden; this.setState({ widgetHidden: hidden }); } } @@ -84,11 +78,7 @@ module.exports = React.createClass({ nextProps.highlights !== this.props.highlights || nextProps.highlightLink !== this.props.highlightLink || nextState.link !== this.state.link || - nextProps.mxEvent.widgetHidden !== this.state.widgetHidden); - }, - - componentWillUpdate: function(nextProps, nextState) { - this.setState({ widgetHidden: nextProps.mxEvent.widgetHidden }); + nextState.widgetHidden !== this.state.widgetHidden); }, findLink: function(nodes) { @@ -104,8 +94,6 @@ module.exports = React.createClass({ }, onCancelClick: function(event) { - // XXX: we're gutwrenching mxEvent here by setting our own custom property on it - this.props.mxEvent.widgetHidden = true; this.setState({ widgetHidden: true }); // FIXME: persist this somewhere smarter than local storage if (global.localStorage) { @@ -114,6 +102,19 @@ module.exports = React.createClass({ this.forceUpdate(); }, + getEventTileOps: function() { + var self = this; + return { + isWidgetHidden: function() { + return self.state.widgetHidden; + }, + + unhideWidget: function() { + self.setState({ widgetHidden: false }); + }, + } + }, + render: function() { var mxEvent = this.props.mxEvent; var content = mxEvent.getContent(); diff --git a/src/components/views/rooms/EventTile.js b/src/components/views/rooms/EventTile.js index 08cefda67a..1ef3b6e6c0 100644 --- a/src/components/views/rooms/EventTile.js +++ b/src/components/views/rooms/EventTile.js @@ -123,7 +123,7 @@ module.exports = React.createClass({ { return false; } - + return actions.tweaks.highlight; }, @@ -137,6 +137,7 @@ module.exports = React.createClass({ mxEvent: this.props.mxEvent, left: x, top: y, + eventTileOps: this.refs.tile ? this.refs.tile.getEventTileOps() : undefined, onFinished: function() { self.setState({menu: false}); } @@ -343,7 +344,7 @@ module.exports = React.createClass({ { avatar } { sender }
-
From 8e48bed34673246c7ddebf9fcae6b484bdd7f3a7 Mon Sep 17 00:00:00 2001 From: Matthew Hodgson Date: Mon, 11 Apr 2016 17:32:10 +0100 Subject: [PATCH 17/18] apparently we don't need this. i'm lost now --- src/components/structures/MessagePanel.js | 1 - 1 file changed, 1 deletion(-) diff --git a/src/components/structures/MessagePanel.js b/src/components/structures/MessagePanel.js index a779317f8f..8f5ffd9e56 100644 --- a/src/components/structures/MessagePanel.js +++ b/src/components/structures/MessagePanel.js @@ -404,7 +404,6 @@ module.exports = React.createClass({ _onWidgetLoad: function() { var scrollPanel = this.refs.scrollPanel; if (scrollPanel) { - scrollPanel.checkScroll(); scrollPanel.forceUpdate(); } }, From 9845e5ef0e7b32a4caa6b410ed40a8abb3e870f2 Mon Sep 17 00:00:00 2001 From: Matthew Hodgson Date: Mon, 11 Apr 2016 23:53:36 +0100 Subject: [PATCH 18/18] move all the localstorage crap to TextualBody --- src/components/views/messages/TextualBody.js | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/components/views/messages/TextualBody.js b/src/components/views/messages/TextualBody.js index 75fa02e35c..ce33a60872 100644 --- a/src/components/views/messages/TextualBody.js +++ b/src/components/views/messages/TextualBody.js @@ -111,6 +111,9 @@ module.exports = React.createClass({ unhideWidget: function() { self.setState({ widgetHidden: false }); + if (global.localStorage) { + global.localStorage.removeItem("hide_preview_" + self.props.mxEvent.getId()); + } }, } },