Merge pull request #1058 from matrix-org/luke/fix-event-id-state

Control currently viewed event via RoomViewStore
This commit is contained in:
Luke Barnard 2017-06-08 18:10:29 +01:00 committed by GitHub
commit ce0977373e
5 changed files with 147 additions and 89 deletions

View file

@ -223,10 +223,8 @@ export default React.createClass({
ref='roomView' ref='roomView'
autoJoin={this.props.autoJoin} autoJoin={this.props.autoJoin}
onRegistered={this.props.onRegistered} onRegistered={this.props.onRegistered}
eventId={this.props.initialEventId}
thirdPartyInvite={this.props.thirdPartyInvite} thirdPartyInvite={this.props.thirdPartyInvite}
oobData={this.props.roomOobData} oobData={this.props.roomOobData}
highlightedEventId={this.props.highlightedEventId}
eventPixelOffset={this.props.initialEventPixelOffset} eventPixelOffset={this.props.initialEventPixelOffset}
key={this.props.currentRoomId || 'roomview'} key={this.props.currentRoomId || 'roomview'}
opacity={this.props.middleOpacity} opacity={this.props.middleOpacity}

View file

@ -607,6 +607,8 @@ module.exports = React.createClass({
// @param {boolean=} roomInfo.show_settings Makes RoomView show the room settings dialog. // @param {boolean=} roomInfo.show_settings Makes RoomView show the room settings dialog.
// @param {string=} roomInfo.event_id ID of the event in this room to show: this will cause a switch to the // @param {string=} roomInfo.event_id ID of the event in this room to show: this will cause a switch to the
// context of that particular event. // context of that particular event.
// @param {boolean=} roomInfo.highlighted If true, add event_id to the hash of the URL
// and alter the EventTile to appear highlighted.
// @param {Object=} roomInfo.third_party_invite Object containing data about the third party // @param {Object=} roomInfo.third_party_invite Object containing data about the third party
// we received to join the room, if any. // we received to join the room, if any.
// @param {string=} roomInfo.third_party_invite.inviteSignUrl 3pid invite sign URL // @param {string=} roomInfo.third_party_invite.inviteSignUrl 3pid invite sign URL
@ -618,40 +620,20 @@ module.exports = React.createClass({
this.focusComposer = true; this.focusComposer = true;
const newState = { const newState = {
initialEventId: roomInfo.event_id,
highlightedEventId: roomInfo.event_id,
initialEventPixelOffset: undefined,
page_type: PageTypes.RoomView, page_type: PageTypes.RoomView,
thirdPartyInvite: roomInfo.third_party_invite, thirdPartyInvite: roomInfo.third_party_invite,
roomOobData: roomInfo.oob_data, roomOobData: roomInfo.oob_data,
currentRoomAlias: roomInfo.room_alias,
autoJoin: roomInfo.auto_join, autoJoin: roomInfo.auto_join,
}; };
if (!roomInfo.room_alias) {
newState.currentRoomId = roomInfo.room_id;
}
// if we aren't given an explicit event id, look for one in the
// scrollStateMap.
//
// TODO: do this in RoomView rather than here
if (!roomInfo.event_id && this.refs.loggedInView) {
const scrollState = this.refs.loggedInView.getScrollStateForRoom(roomInfo.room_id);
if (scrollState) {
newState.initialEventId = scrollState.focussedEvent;
newState.initialEventPixelOffset = scrollState.pixelOffset;
}
}
if (roomInfo.room_alias) { if (roomInfo.room_alias) {
console.log( console.log(
`Switching to room alias ${roomInfo.room_alias} at event ` + `Switching to room alias ${roomInfo.room_alias} at event ` +
newState.initialEventId, roomInfo.event_id,
); );
} else { } else {
console.log(`Switching to room id ${roomInfo.room_id} at event ` + console.log(`Switching to room id ${roomInfo.room_id} at event ` +
newState.initialEventId, roomInfo.event_id,
); );
} }
@ -680,7 +662,7 @@ module.exports = React.createClass({
} }
} }
if (roomInfo.event_id) { if (roomInfo.event_id && roomInfo.highlighted) {
presentedId += "/" + roomInfo.event_id; presentedId += "/" + roomInfo.event_id;
} }
this.notifyNewScreen('room/' + presentedId); this.notifyNewScreen('room/' + presentedId);
@ -1137,6 +1119,10 @@ module.exports = React.createClass({
const payload = { const payload = {
action: 'view_room', action: 'view_room',
event_id: eventId, event_id: eventId,
// If an event ID is given in the URL hash, notify RoomViewStore to mark
// it as highlighted, which will propagate to RoomView and highlight the
// associated EventTile.
highlighted: Boolean(eventId),
third_party_invite: thirdPartyInvite, third_party_invite: thirdPartyInvite,
oob_data: oobData, oob_data: oobData,
}; };

View file

@ -83,36 +83,8 @@ module.exports = React.createClass({
// * invited us tovthe room // * invited us tovthe room
oobData: React.PropTypes.object, oobData: React.PropTypes.object,
// id of an event to jump to. If not given, will go to the end of the
// live timeline.
eventId: React.PropTypes.string,
// where to position the event given by eventId, in pixels from the
// bottom of the viewport. If not given, will try to put the event
// 1/3 of the way down the viewport.
eventPixelOffset: React.PropTypes.number,
// ID of an event to highlight. If undefined, no event will be highlighted.
// Typically this will either be the same as 'eventId', or undefined.
highlightedEventId: React.PropTypes.string,
// is the RightPanel collapsed? // is the RightPanel collapsed?
collapsedRhs: React.PropTypes.bool, collapsedRhs: React.PropTypes.bool,
// a map from room id to scroll state, which will be updated on unmount.
//
// If there is no special scroll state (ie, we are following the live
// timeline), the scroll state is null. Otherwise, it is an object with
// the following properties:
//
// focussedEvent: the ID of the 'focussed' event. Typically this is
// the last event fully visible in the viewport, though if we
// have done an explicit scroll to an explicit event, it will be
// that event.
//
// pixelOffset: the number of pixels the window is scrolled down
// from the focussedEvent.
scrollStateMap: React.PropTypes.object,
}, },
getInitialState: function() { getInitialState: function() {
@ -122,6 +94,13 @@ module.exports = React.createClass({
roomLoading: true, roomLoading: true,
peekLoading: false, peekLoading: false,
// The event to be scrolled to initially
initialEventId: null,
// The offset in pixels from the event with which to scroll vertically
initialEventPixelOffset: null,
// Whether to highlight the event scrolled to
isInitialEventHighlighted: null,
forwardingEvent: null, forwardingEvent: null,
editingRoomSettings: false, editingRoomSettings: false,
uploadingRoomSettings: false, uploadingRoomSettings: false,
@ -180,13 +159,32 @@ module.exports = React.createClass({
if (this.unmounted) { if (this.unmounted) {
return; return;
} }
this.setState({ const newState = {
roomId: RoomViewStore.getRoomId(), roomId: RoomViewStore.getRoomId(),
roomAlias: RoomViewStore.getRoomAlias(), roomAlias: RoomViewStore.getRoomAlias(),
roomLoading: RoomViewStore.isRoomLoading(), roomLoading: RoomViewStore.isRoomLoading(),
roomLoadError: RoomViewStore.getRoomLoadError(), roomLoadError: RoomViewStore.getRoomLoadError(),
joining: RoomViewStore.isJoining(), joining: RoomViewStore.isJoining(),
}, () => { initialEventId: RoomViewStore.getInitialEventId(),
initialEventPixelOffset: RoomViewStore.getInitialEventPixelOffset(),
isInitialEventHighlighted: RoomViewStore.isInitialEventHighlighted(),
};
// Clear the search results when clicking a search result (which changes the
// currently scrolled to event, this.state.initialEventId).
if (this.state.initialEventId !== newState.initialEventId) {
newState.searchResults = null;
}
// Store the scroll state for the previous room so that we can return to this
// position when viewing this room in future.
if (this.state.roomId !== newState.roomId) {
this._updateScrollMap(this.state.roomId);
}
this.setState(newState, () => {
// At this point, this.state.roomId could be null (e.g. the alias might not
// have been resolved yet) so anything called here must handle this case.
this._onHaveRoom(); this._onHaveRoom();
this.onRoom(MatrixClientPeg.get().getRoom(this.state.roomId)); this.onRoom(MatrixClientPeg.get().getRoom(this.state.roomId));
}); });
@ -287,13 +285,6 @@ module.exports = React.createClass({
} }
}, },
componentWillReceiveProps: function(newProps) {
if (newProps.eventId != this.props.eventId) {
// when we change focussed event id, hide the search results.
this.setState({searchResults: null});
}
},
shouldComponentUpdate: function(nextProps, nextState) { shouldComponentUpdate: function(nextProps, nextState) {
return (!ObjectUtils.shallowEqual(this.props, nextProps) || return (!ObjectUtils.shallowEqual(this.props, nextProps) ||
!ObjectUtils.shallowEqual(this.state, nextState)); !ObjectUtils.shallowEqual(this.state, nextState));
@ -319,7 +310,7 @@ module.exports = React.createClass({
this.unmounted = true; this.unmounted = true;
// update the scroll map before we get unmounted // update the scroll map before we get unmounted
this._updateScrollMap(); this._updateScrollMap(this.state.roomId);
if (this.refs.roomView) { if (this.refs.roomView) {
// disconnect the D&D event listeners from the room view. This // disconnect the D&D event listeners from the room view. This
@ -598,6 +589,18 @@ module.exports = React.createClass({
}); });
}, },
_updateScrollMap(roomId) {
// No point updating scroll state if the room ID hasn't been resolved yet
if (!roomId) {
return;
}
dis.dispatch({
action: 'update_scroll_state',
room_id: roomId,
scroll_state: this._getScrollState(),
});
},
onRoom: function(room) { onRoom: function(room) {
if (!room || room.roomId !== this.state.roomId) { if (!room || room.roomId !== this.state.roomId) {
return; return;
@ -1240,21 +1243,6 @@ module.exports = React.createClass({
} }
}, },
// update scrollStateMap on unmount
_updateScrollMap: function() {
if (!this.state.room) {
// we were instantiated on a room alias and haven't yet joined the room.
return;
}
if (!this.props.scrollStateMap) return;
var roomId = this.state.room.roomId;
var state = this._getScrollState();
this.props.scrollStateMap[roomId] = state;
},
// get the current scroll position of the room, so that it can be // get the current scroll position of the room, so that it can be
// restored when we switch back to it. // restored when we switch back to it.
// //
@ -1677,6 +1665,14 @@ module.exports = React.createClass({
hideMessagePanel = true; hideMessagePanel = true;
} }
const shouldHighlight = this.state.isInitialEventHighlighted;
let highlightedEventId = null;
if (this.state.forwardingEvent) {
highlightedEventId = this.state.forwardingEvent.getId();
} else if (shouldHighlight) {
highlightedEventId = this.state.initialEventId;
}
// console.log("ShowUrlPreview for %s is %s", this.state.room.roomId, this.state.showUrlPreview); // console.log("ShowUrlPreview for %s is %s", this.state.room.roomId, this.state.showUrlPreview);
var messagePanel = ( var messagePanel = (
<TimelinePanel ref={this._gatherTimelinePanelRef} <TimelinePanel ref={this._gatherTimelinePanelRef}
@ -1684,9 +1680,9 @@ module.exports = React.createClass({
manageReadReceipts={!UserSettingsStore.getSyncedSetting('hideReadReceipts', false)} manageReadReceipts={!UserSettingsStore.getSyncedSetting('hideReadReceipts', false)}
manageReadMarkers={true} manageReadMarkers={true}
hidden={hideMessagePanel} hidden={hideMessagePanel}
highlightedEventId={this.state.forwardingEvent ? this.state.forwardingEvent.getId() : this.props.highlightedEventId} highlightedEventId={highlightedEventId}
eventId={this.props.eventId} eventId={this.state.initialEventId}
eventPixelOffset={this.props.eventPixelOffset} eventPixelOffset={this.state.initialEventPixelOffset}
onScroll={ this.onMessageListScroll } onScroll={ this.onMessageListScroll }
onReadMarkerUpdated={ this._updateTopUnreadMessagesBar } onReadMarkerUpdated={ this._updateTopUnreadMessagesBar }
showUrlPreview = { this.state.showUrlPreview } showUrlPreview = { this.state.showUrlPreview }

View file

@ -381,6 +381,7 @@ module.exports = WithMatrixClient(React.createClass({
dis.dispatch({ dis.dispatch({
action: 'view_room', action: 'view_room',
event_id: this.props.mxEvent.getId(), event_id: this.props.mxEvent.getId(),
highlighted: true,
room_id: this.props.mxEvent.getRoomId(), room_id: this.props.mxEvent.getRoomId(),
}); });
}, },

View file

@ -23,16 +23,38 @@ import { _t } from '../languageHandler';
const INITIAL_STATE = { const INITIAL_STATE = {
// Whether we're joining the currently viewed room // Whether we're joining the currently viewed room
joining: false, joining: false,
// Any error occurred during joining // Any error that has occurred during joining
joinError: null, joinError: null,
// The room ID of the room // The room ID of the room currently being viewed
roomId: null, roomId: null,
// The event to scroll to when the room is first viewed
initialEventId: null,
// The offset to display the initial event at (see scrollStateMap)
initialEventPixelOffset: null,
// Whether to highlight the initial event
isInitialEventHighlighted: false,
// The room alias of the room (or null if not originally specified in view_room) // The room alias of the room (or null if not originally specified in view_room)
roomAlias: null, roomAlias: null,
// Whether the current room is loading // Whether the current room is loading
roomLoading: false, roomLoading: false,
// Any error that has occurred during loading // Any error that has occurred during loading
roomLoadError: null, roomLoadError: null,
// A map from room id to scroll state.
//
// If there is no special scroll state (ie, we are following the live
// timeline), the scroll state is null. Otherwise, it is an object with
// the following properties:
//
// focussedEvent: the ID of the 'focussed' event. Typically this is
// the last event fully visible in the viewport, though if we
// have done an explicit scroll to an explicit event, it will be
// that event.
//
// pixelOffset: the number of pixels the window is scrolled down
// from the focussedEvent.
scrollStateMap: {},
}; };
/** /**
@ -58,6 +80,9 @@ class RoomViewStore extends Store {
// view_room: // view_room:
// - room_alias: '#somealias:matrix.org' // - room_alias: '#somealias:matrix.org'
// - room_id: '!roomid123:matrix.org' // - room_id: '!roomid123:matrix.org'
// - event_id: '$213456782:matrix.org'
// - event_offset: 100
// - highlighted: true
case 'view_room': case 'view_room':
this._viewRoom(payload); this._viewRoom(payload);
break; break;
@ -88,20 +113,41 @@ class RoomViewStore extends Store {
case 'on_logged_out': case 'on_logged_out':
this.reset(); this.reset();
break; break;
case 'update_scroll_state':
this._updateScrollState(payload);
break;
} }
} }
_viewRoom(payload) { _viewRoom(payload) {
// Always set the room ID if present
if (payload.room_id) { if (payload.room_id) {
this._setState({ const newState = {
roomId: payload.room_id, roomId: payload.room_id,
initialEventId: payload.event_id,
initialEventPixelOffset: undefined,
isInitialEventHighlighted: payload.highlighted,
roomLoading: false, roomLoading: false,
roomLoadError: null, roomLoadError: null,
}); };
// If an event ID wasn't specified, default to the one saved for this room
// via update_scroll_state. Assume initialEventPixelOffset should be set.
if (!newState.initialEventId) {
const roomScrollState = this._state.scrollStateMap[payload.room_id];
if (roomScrollState) {
newState.initialEventId = roomScrollState.focussedEvent;
newState.initialEventPixelOffset = roomScrollState.pixelOffset;
}
}
this._setState(newState);
} else if (payload.room_alias) { } else if (payload.room_alias) {
// Resolve the alias and then do a second dispatch with the room ID acquired
this._setState({ this._setState({
roomId: null, roomId: null,
initialEventId: null,
initialEventPixelOffset: null,
isInitialEventHighlighted: null,
roomAlias: payload.room_alias, roomAlias: payload.room_alias,
roomLoading: true, roomLoading: true,
roomLoadError: null, roomLoadError: null,
@ -111,6 +157,8 @@ class RoomViewStore extends Store {
dis.dispatch({ dis.dispatch({
action: 'view_room', action: 'view_room',
room_id: result.room_id, room_id: result.room_id,
event_id: payload.event_id,
highlighted: payload.highlighted,
room_alias: payload.room_alias, room_alias: payload.room_alias,
}); });
}, (err) => { }, (err) => {
@ -168,34 +216,63 @@ class RoomViewStore extends Store {
}); });
} }
_updateScrollState(payload) {
// Clobber existing scroll state for the given room ID
const newScrollStateMap = this._state.scrollStateMap;
newScrollStateMap[payload.room_id] = payload.scroll_state;
this._setState({
scrollStateMap: newScrollStateMap,
});
}
reset() { reset() {
this._state = Object.assign({}, INITIAL_STATE); this._state = Object.assign({}, INITIAL_STATE);
} }
// The room ID of the room currently being viewed
getRoomId() { getRoomId() {
return this._state.roomId; return this._state.roomId;
} }
// The event to scroll to when the room is first viewed
getInitialEventId() {
return this._state.initialEventId;
}
// The offset to display the initial event at (see scrollStateMap)
getInitialEventPixelOffset() {
return this._state.initialEventPixelOffset;
}
// Whether to highlight the initial event
isInitialEventHighlighted() {
return this._state.isInitialEventHighlighted;
}
// The room alias of the room (or null if not originally specified in view_room)
getRoomAlias() { getRoomAlias() {
return this._state.roomAlias; return this._state.roomAlias;
} }
// Whether the current room is loading (true whilst resolving an alias)
isRoomLoading() { isRoomLoading() {
return this._state.roomLoading; return this._state.roomLoading;
} }
// Any error that has occurred during loading
getRoomLoadError() { getRoomLoadError() {
return this._state.roomLoadError; return this._state.roomLoadError;
} }
// Whether we're joining the currently viewed room
isJoining() { isJoining() {
return this._state.joining; return this._state.joining;
} }
// Any error that has occurred during joining
getJoinError() { getJoinError() {
return this._state.joinError; return this._state.joinError;
} }
} }
let singletonRoomViewStore = null; let singletonRoomViewStore = null;