Merge remote-tracking branch 'origin/experimental' into dbkr/fix_recovery_reminder_cancel

This commit is contained in:
David Baker 2019-01-10 09:39:57 +00:00
commit 3555b02f34
42 changed files with 914 additions and 212 deletions

View file

@ -0,0 +1,127 @@
/*
Copyright 2017 Vector Creations Ltd.
Copyright 2017, 2018 New Vector 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.
*/
import React from 'react';
import OpenRoomsStore from '../../stores/OpenRoomsStore';
import dis from '../../dispatcher';
import {_t} from '../../languageHandler';
import RoomView from './RoomView';
import classNames from 'classnames';
import MainSplit from './MainSplit';
import RightPanel from './RightPanel';
import RoomHeaderButtons from '../views/right_panel/RoomHeaderButtons';
export default class RoomGridView extends React.Component {
constructor(props) {
super(props);
this.state = {
roomStores: OpenRoomsStore.getRoomStores(),
activeRoomStore: OpenRoomsStore.getActiveRoomStore(),
};
this.onRoomsChanged = this.onRoomsChanged.bind(this);
}
componentDidUpdate(_, prevState) {
const store = this.state.activeRoomStore;
if (store) {
store.getDispatcher().dispatch({action: 'focus_composer'});
}
}
componentDidMount() {
this.componentDidUpdate();
}
componentWillMount() {
this._unmounted = false;
this._openRoomsStoreRegistration = OpenRoomsStore.addListener(this.onRoomsChanged);
}
componentWillUnmount() {
this._unmounted = true;
if (this._openRoomsStoreRegistration) {
this._openRoomsStoreRegistration.remove();
}
}
onRoomsChanged() {
if (this._unmounted) return;
this.setState({
roomStores: OpenRoomsStore.getRoomStores(),
activeRoomStore: OpenRoomsStore.getActiveRoomStore(),
});
}
_setActive(i) {
const store = OpenRoomsStore.getRoomStoreAt(i);
if (store !== this.state.activeRoomStore) {
dis.dispatch({
action: 'group_grid_set_active',
room_id: store.getRoomId(),
});
}
}
render() {
let roomStores = this.state.roomStores.slice(0, 6);
const emptyCount = 6 - roomStores.length;
if (emptyCount) {
const emptyTiles = Array.from({length: emptyCount}, () => null);
roomStores = roomStores.concat(emptyTiles);
}
const activeRoomId = this.state.activeRoomStore && this.state.activeRoomStore.getRoomId();
let rightPanel;
if (activeRoomId) {
rightPanel = (
<div className="mx_GroupGridView_rightPanel">
<div className="mx_GroupGridView_tabs"><RoomHeaderButtons /></div>
<RightPanel roomId={activeRoomId} />
</div>
);
}
return (<main className="mx_GroupGridView">
<MainSplit panel={rightPanel} collapsedRhs={this.props.collapsedRhs} >
<div className="mx_GroupGridView_rooms">
{ roomStores.map((roomStore, i) => {
if (roomStore) {
const isActive = roomStore === this.state.activeRoomStore;
const tileClasses = classNames({
"mx_GroupGridView_tile": true,
"mx_GroupGridView_activeTile": isActive,
});
return (<section
onClick={() => {this._setActive(i);}}
key={roomStore.getRoomId()}
className={tileClasses}
>
<RoomView
collapsedRhs={this.props.collapsedRhs}
isGrid={true}
roomViewStore={roomStore}
isActive={isActive}
/>
</section>);
} else {
return (<section className={"mx_GroupGridView_emptyTile"} key={`empty-${i}`}>{_t("No room in this tile yet.")}</section>);
}
}) }
</div>
</MainSplit>
</main>);
}
}

View file

@ -781,7 +781,7 @@ export default React.createClass({
),
button: _t("Leave"),
danger: this.state.isUserPrivileged,
onFinished: async(confirmed) => {
onFinished: async (confirmed) => {
if (!confirmed) return;
this.setState({membershipBusy: true});

View file

@ -31,6 +31,7 @@ import sessionStore from '../../stores/SessionStore';
import MatrixClientPeg from '../../MatrixClientPeg';
import SettingsStore from "../../settings/SettingsStore";
import RoomListStore from "../../stores/RoomListStore";
import OpenRoomsStore from "../../stores/OpenRoomsStore";
import TagOrderActions from '../../actions/TagOrderActions';
import RoomListActions from '../../actions/RoomListActions';
@ -416,6 +417,7 @@ const LoggedInView = React.createClass({
const RoomDirectory = sdk.getComponent('structures.RoomDirectory');
const HomePage = sdk.getComponent('structures.HomePage');
const GroupView = sdk.getComponent('structures.GroupView');
const GroupGridView = sdk.getComponent('structures.GroupGridView');
const MyGroups = sdk.getComponent('structures.MyGroups');
const MatrixToolbar = sdk.getComponent('globals.MatrixToolbar');
const CookieBar = sdk.getComponent('globals.CookieBar');
@ -428,7 +430,14 @@ const LoggedInView = React.createClass({
switch (this.props.page_type) {
case PageTypes.RoomView:
if (!OpenRoomsStore.getActiveRoomStore()) {
console.warn(`LoggedInView: getCurrentRoomStore not set!`);
}
else if (OpenRoomsStore.getActiveRoomStore().getRoomId() !== this.props.currentRoomId) {
console.warn(`LoggedInView: room id in store not the same as in props: ${OpenRoomsStore.getActiveRoomStore().getRoomId()} & ${this.props.currentRoomId}`);
}
page_element = <RoomView
roomViewStore={OpenRoomsStore.getActiveRoomStore()}
ref='roomView'
autoJoin={this.props.autoJoin}
onRegistered={this.props.onRegistered}
@ -442,7 +451,9 @@ const LoggedInView = React.createClass({
ConferenceHandler={this.props.ConferenceHandler}
/>;
break;
case PageTypes.GroupGridView:
page_element = <GroupGridView collapsedRhs={this.props.collapsedRhs} />;
break;
case PageTypes.UserSettings:
page_element = <UserSettings
onClose={this.props.onCloseAllSettings}

View file

@ -71,14 +71,13 @@ export default class MainSplit extends React.Component {
}
componentDidUpdate(prevProps) {
const wasExpanded = !this.props.collapsedRhs && prevProps.collapsedRhs;
const wasCollapsed = this.props.collapsedRhs && !prevProps.collapsedRhs;
const wasPanelSet = this.props.panel && !prevProps.panel;
const wasPanelCleared = !this.props.panel && prevProps.panel;
const shouldAllowResizing =
!this.props.collapsedRhs &&
this.props.panel;
if (wasExpanded || wasPanelSet) {
if (shouldAllowResizing && !this.resizer) {
this._createResizer();
} else if (wasCollapsed || wasPanelCleared) {
} else if (!shouldAllowResizing && this.resizer) {
this.resizer.detach();
this.resizer = null;
}

View file

@ -651,6 +651,9 @@ export default React.createClass({
case 'view_group':
this._viewGroup(payload);
break;
case 'group_grid_view':
this._viewGroupGrid(payload);
break;
case 'view_home_page':
this._viewHome();
break;
@ -862,6 +865,7 @@ export default React.createClass({
// room name and avatar from an invite email)
_viewRoom: function(roomInfo) {
this.focusComposer = true;
console.log("!!! MatrixChat._viewRoom", roomInfo);
const newState = {
currentRoomId: roomInfo.room_id || null,
@ -910,6 +914,9 @@ export default React.createClass({
if (roomInfo.event_id && roomInfo.highlighted) {
presentedId += "/" + roomInfo.event_id;
}
// TODO: only emit this when we're not in grid mode?
this.notifyNewScreen('room/' + presentedId);
newState.ready = true;
this.setState(newState);
@ -926,6 +933,11 @@ export default React.createClass({
this.notifyNewScreen('group/' + groupId);
},
_viewGroupGrid: function(payload) {
this._setPage(PageTypes.GroupGridView);
// this.notifyNewScreen('grid/' + payload.group_id);
},
_viewHome: function() {
// The home page requires the "logged in" view, so we'll set that.
this.setStateForNewView({

View file

@ -165,7 +165,7 @@ export default class RightPanel extends React.Component {
} else if (this.state.phase === RightPanel.Phase.GroupRoomList) {
panel = <GroupRoomList groupId={this.props.groupId} key={this.props.groupId} />;
} else if (this.state.phase === RightPanel.Phase.RoomMemberInfo) {
panel = <MemberInfo member={this.state.member} key={this.props.roomId || this.state.member.userId} />;
panel = <MemberInfo roomId={this.props.roomId} member={this.state.member} key={this.props.roomId || this.state.member.userId} />;
} else if (this.state.phase === RightPanel.Phase.GroupMemberInfo) {
panel = <GroupMemberInfo
groupMember={this.state.member}

View file

@ -36,7 +36,6 @@ const ContentMessages = require("../../ContentMessages");
const Modal = require("../../Modal");
const sdk = require('../../index');
const CallHandler = require('../../CallHandler');
const dis = require("../../dispatcher");
const Tinter = require("../../Tinter");
const rate_limited_func = require('../../ratelimitedfunc');
const ObjectUtils = require('../../ObjectUtils');
@ -46,7 +45,6 @@ import { KeyCode, isOnlyCtrlOrCmdKeyEvent } from '../../Keyboard';
import MainSplit from './MainSplit';
import RightPanel from './RightPanel';
import RoomViewStore from '../../stores/RoomViewStore';
import RoomScrollStateStore from '../../stores/RoomScrollStateStore';
import WidgetEchoStore from '../../stores/WidgetEchoStore';
import SettingsStore, {SettingLevel} from "../../settings/SettingsStore";
@ -94,6 +92,8 @@ module.exports = React.createClass({
// Servers the RoomView can use to try and assist joins
viaServers: PropTypes.arrayOf(PropTypes.string),
// the store for this room view
roomViewStore: PropTypes.object.isRequired,
},
getInitialState: function() {
@ -155,7 +155,7 @@ module.exports = React.createClass({
},
componentWillMount: function() {
this.dispatcherRef = dis.register(this.onAction);
this.dispatcherRef = this.props.roomViewStore.getDispatcher().register(this.onAction);
MatrixClientPeg.get().on("Room", this.onRoom);
MatrixClientPeg.get().on("Room.timeline", this.onRoomTimeline);
MatrixClientPeg.get().on("Room.name", this.onRoomName);
@ -166,7 +166,7 @@ module.exports = React.createClass({
MatrixClientPeg.get().on("crypto.keyBackupStatus", this.onKeyBackupStatus);
this._fetchMediaConfig();
// Start listening for RoomViewStore updates
this._roomStoreToken = RoomViewStore.addListener(this._onRoomViewStoreUpdate);
this._roomStoreToken = this.props.roomViewStore.addListener(this._onRoomViewStoreUpdate);
this._onRoomViewStoreUpdate(true);
WidgetEchoStore.on('update', this._onWidgetEchoStoreUpdate);
@ -197,8 +197,8 @@ module.exports = React.createClass({
if (this.unmounted) {
return;
}
if (!initial && this.state.roomId !== RoomViewStore.getRoomId()) {
const store = this.props.roomViewStore;
if (!initial && this.state.roomId !== store.getRoomId()) {
// RoomView explicitly does not support changing what room
// is being viewed: instead it should just be re-mounted when
// switching rooms. Therefore, if the room ID changes, we
@ -212,22 +212,21 @@ module.exports = React.createClass({
// it was, it means we're about to be unmounted.
return;
}
const newState = {
roomId: RoomViewStore.getRoomId(),
roomAlias: RoomViewStore.getRoomAlias(),
roomLoading: RoomViewStore.isRoomLoading(),
roomLoadError: RoomViewStore.getRoomLoadError(),
joining: RoomViewStore.isJoining(),
initialEventId: RoomViewStore.getInitialEventId(),
isInitialEventHighlighted: RoomViewStore.isInitialEventHighlighted(),
forwardingEvent: RoomViewStore.getForwardingEvent(),
shouldPeek: RoomViewStore.shouldPeek(),
showingPinned: SettingsStore.getValue("PinnedEvents.isOpen", RoomViewStore.getRoomId()),
editingRoomSettings: RoomViewStore.isEditingSettings(),
roomId: store.getRoomId(),
roomAlias: store.getRoomAlias(),
roomLoading: store.isRoomLoading(),
roomLoadError: store.getRoomLoadError(),
joining: store.isJoining(),
initialEventId: store.getInitialEventId(),
isInitialEventHighlighted: store.isInitialEventHighlighted(),
forwardingEvent: store.getForwardingEvent(),
shouldPeek: store.shouldPeek(),
showingPinned: SettingsStore.getValue("PinnedEvents.isOpen", store.getRoomId()),
editingRoomSettings: store.isEditingSettings(),
};
if (this.state.editingRoomSettings && !newState.editingRoomSettings) dis.dispatch({action: 'focus_composer'});
if (this.state.editingRoomSettings && !newState.editingRoomSettings) this.props.roomViewStore.getDispatcher().dispatch({action: 'focus_composer'});
// Temporary logging to diagnose https://github.com/vector-im/riot-web/issues/4307
console.log(
@ -389,7 +388,7 @@ module.exports = React.createClass({
// XXX: EVIL HACK to autofocus inviting on empty rooms.
// We use the setTimeout to avoid racing with focus_composer.
if (this.state.room &&
if (this.props.isActive !== false && this.state.room &&
this.state.room.getJoinedMemberCount() == 1 &&
this.state.room.getLiveTimeline() &&
this.state.room.getLiveTimeline().getEvents() &&
@ -443,7 +442,7 @@ module.exports = React.createClass({
roomView.removeEventListener('dragleave', this.onDragLeaveOrEnd);
roomView.removeEventListener('dragend', this.onDragLeaveOrEnd);
}
dis.unregister(this.dispatcherRef);
this.props.roomViewStore.getDispatcher().unregister(this.dispatcherRef);
if (MatrixClientPeg.get()) {
MatrixClientPeg.get().removeListener("Room", this.onRoom);
MatrixClientPeg.get().removeListener("Room.timeline", this.onRoomTimeline);
@ -835,7 +834,7 @@ module.exports = React.createClass({
},
onSearchResultsResize: function() {
dis.dispatch({ action: 'timeline_resize' }, true);
this.props.roomViewStore.getDispatcher().dispatch({ action: 'timeline_resize' }, true);
},
onSearchResultsFillRequest: function(backwards) {
@ -856,7 +855,7 @@ module.exports = React.createClass({
onInviteButtonClick: function() {
// call AddressPickerDialog
dis.dispatch({
this.props.roomViewStore.getDispatcher().dispatch({
action: 'view_invite',
roomId: this.state.room.roomId,
});
@ -878,7 +877,7 @@ module.exports = React.createClass({
// Join this room once the user has registered and logged in
const signUrl = this.props.thirdPartyInvite ?
this.props.thirdPartyInvite.inviteSignUrl : undefined;
dis.dispatch({
this.props.roomViewStore.getDispatcher().dispatch({
action: 'do_after_sync_prepared',
deferred_action: {
action: 'join_room',
@ -888,7 +887,7 @@ module.exports = React.createClass({
// Don't peek whilst registering otherwise getPendingEventList complains
// Do this by indicating our intention to join
dis.dispatch({
this.props.roomViewStore.getDispatcher().dispatch({
action: 'will_join',
});
@ -899,20 +898,20 @@ module.exports = React.createClass({
if (submitted) {
this.props.onRegistered(credentials);
} else {
dis.dispatch({
this.props.roomViewStore.getDispatcher().dispatch({
action: 'cancel_after_sync_prepared',
});
dis.dispatch({
this.props.roomViewStore.getDispatcher().dispatch({
action: 'cancel_join',
});
}
},
onDifferentServerClicked: (ev) => {
dis.dispatch({action: 'start_registration'});
this.props.roomViewStore.getDispatcher().dispatch({action: 'start_registration'});
close();
},
onLoginClick: (ev) => {
dis.dispatch({action: 'start_login'});
this.props.roomViewStore.getDispatcher().dispatch({action: 'start_login'});
close();
},
}).close;
@ -922,7 +921,7 @@ module.exports = React.createClass({
Promise.resolve().then(() => {
const signUrl = this.props.thirdPartyInvite ?
this.props.thirdPartyInvite.inviteSignUrl : undefined;
dis.dispatch({
this.props.roomViewStore.getDispatcher().dispatch({
action: 'join_room',
opts: { inviteSignUrl: signUrl, viaServers: this.props.viaServers },
});
@ -987,10 +986,10 @@ module.exports = React.createClass({
},
uploadFile: async function(file) {
dis.dispatch({action: 'focus_composer'});
this.props.roomViewStore.getDispatcher().dispatch({action: 'focus_composer'});
if (MatrixClientPeg.get().isGuest()) {
dis.dispatch({action: 'require_registration'});
this.props.roomViewStore.getDispatcher().dispatch({action: 'require_registration'});
return;
}
@ -1014,14 +1013,14 @@ module.exports = React.createClass({
}
// Send message_sent callback, for things like _checkIfAlone because after all a file is still a message.
dis.dispatch({
this.props.roomViewStore.getDispatcher().dispatch({
action: 'message_sent',
});
},
injectSticker: function(url, info, text) {
if (MatrixClientPeg.get().isGuest()) {
dis.dispatch({action: 'require_registration'});
this.props.roomViewStore.getDispatcher().dispatch({action: 'require_registration'});
return;
}
@ -1222,7 +1221,7 @@ module.exports = React.createClass({
},
onSettingsClick: function() {
dis.dispatch({ action: 'open_room_settings' });
this.props.roomViewStore.getDispatcher().dispatch({ action: 'open_room_settings' });
},
onSettingsSaveClick: function() {
@ -1255,31 +1254,31 @@ module.exports = React.createClass({
});
// still editing room settings
} else {
dis.dispatch({ action: 'close_settings' });
this.props.roomViewStore.getDispatcher().dispatch({ action: 'close_settings' });
}
}).finally(() => {
this.setState({
uploadingRoomSettings: false,
});
dis.dispatch({ action: 'close_settings' });
this.props.roomViewStore.getDispatcher().dispatch({ action: 'close_settings' });
}).done();
},
onCancelClick: function() {
console.log("updateTint from onCancelClick");
this.updateTint();
dis.dispatch({ action: 'close_settings' });
this.props.roomViewStore.getDispatcher().dispatch({ action: 'close_settings' });
if (this.state.forwardingEvent) {
dis.dispatch({
this.props.roomViewStore.getDispatcher().dispatch({
action: 'forward_event',
event: null,
});
}
dis.dispatch({action: 'focus_composer'});
this.props.roomViewStore.getDispatcher().dispatch({action: 'focus_composer'});
},
onLeaveClick: function() {
dis.dispatch({
this.props.roomViewStore.getDispatcher().dispatch({
action: 'leave_room',
room_id: this.state.room.roomId,
});
@ -1287,7 +1286,7 @@ module.exports = React.createClass({
onForgetClick: function() {
MatrixClientPeg.get().forget(this.state.room.roomId).done(function() {
dis.dispatch({ action: 'view_next_room' });
this.props.roomViewStore.getDispatcher().dispatch({ action: 'view_next_room' });
}, function(err) {
const errCode = err.errcode || _t("unknown error code");
const ErrorDialog = sdk.getComponent("dialogs.ErrorDialog");
@ -1304,7 +1303,7 @@ module.exports = React.createClass({
rejecting: true,
});
MatrixClientPeg.get().leave(this.state.roomId).done(function() {
dis.dispatch({ action: 'view_next_room' });
this.props.roomViewStore.getDispatcher().dispatch({ action: 'view_next_room' });
self.setState({
rejecting: false,
});
@ -1330,7 +1329,7 @@ module.exports = React.createClass({
// using /leave rather than /join. In the short term though, we
// just ignore them.
// https://github.com/vector-im/vector-web/issues/1134
dis.dispatch({
this.props.roomViewStore.getDispatcher().dispatch({
action: 'view_room_directory',
});
},
@ -1349,7 +1348,7 @@ module.exports = React.createClass({
// jump down to the bottom of this room, where new events are arriving
jumpToLiveTimeline: function() {
this.refs.messagePanel.jumpToLiveTimeline();
dis.dispatch({action: 'focus_composer'});
this.props.roomViewStore.getDispatcher().dispatch({action: 'focus_composer'});
},
// jump up to wherever our read marker is
@ -1439,7 +1438,7 @@ module.exports = React.createClass({
},
onFullscreenClick: function() {
dis.dispatch({
this.props.roomViewStore.getDispatcher().dispatch({
action: 'video_fullscreen',
fullscreen: true,
}, true);
@ -1564,6 +1563,7 @@ module.exports = React.createClass({
<RoomHeader ref="header"
room={this.state.room}
oobData={this.props.oobData}
isGrid={this.props.isGrid}
collapsedRhs={this.props.collapsedRhs}
/>
<div className="mx_RoomView_body">
@ -1610,6 +1610,7 @@ module.exports = React.createClass({
<div className="mx_RoomView">
<RoomHeader
ref="header"
isGrid={this.props.isGrid}
room={this.state.room}
collapsedRhs={this.props.collapsedRhs}
/>
@ -1751,7 +1752,9 @@ module.exports = React.createClass({
if (canSpeak) {
messageComposer =
<MessageComposer
roomViewStore={this.props.roomViewStore}
room={this.state.room}
isGrid={this.props.isGrid}
onResize={this.onChildResize}
uploadFile={this.uploadFile}
callState={this.state.callState}
@ -1878,11 +1881,14 @@ module.exports = React.createClass({
},
);
const rightPanel = this.state.room ? <RightPanel roomId={this.state.room.roomId} /> : undefined;
const rightPanel = this.state.room && !this.props.isGrid ?
<RightPanel roomId={this.state.room.roomId} /> :
undefined;
return (
<main className={"mx_RoomView" + (inCall ? " mx_RoomView_inCall" : "")} ref="roomView">
<RoomHeader ref="header" room={this.state.room} searchInfo={searchInfo}
isGrid={this.props.isGrid}
oobData={this.props.oobData}
editing={this.state.editingRoomSettings}
saving={this.state.uploadingRoomSettings}

View file

@ -835,7 +835,7 @@ module.exports = React.createClass({
SettingsStore.getLabsFeatures().forEach((featureId) => {
// TODO: this ought to be a separate component so that we don't need
// to rebind the onChange each time we render
const onChange = async(e) => {
const onChange = async (e) => {
const checked = e.target.checked;
if (featureId === "feature_lazyloading") {
const confirmed = await this._onLazyLoadChanging(checked);