2016-11-03 21:42:26 +03:00
|
|
|
/*
|
|
|
|
Copyright 2015, 2016 OpenMarket Ltd
|
2017-04-26 15:48:03 +03:00
|
|
|
Copyright 2017 Vector Creations Ltd
|
2020-04-13 16:13:27 +03:00
|
|
|
Copyright 2017, 2018, 2020 New Vector Ltd
|
2016-11-03 21:42:26 +03:00
|
|
|
|
|
|
|
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.
|
|
|
|
*/
|
|
|
|
|
2020-04-13 16:29:00 +03:00
|
|
|
import * as React from 'react';
|
|
|
|
import * as PropTypes from 'prop-types';
|
|
|
|
import { MatrixClient } from 'matrix-js-sdk/src/client';
|
|
|
|
import { MatrixEvent } from 'matrix-js-sdk/src/models/event';
|
2018-02-14 19:40:58 +03:00
|
|
|
import { DragDropContext } from 'react-beautiful-dnd';
|
2016-11-03 21:42:26 +03:00
|
|
|
|
2020-04-01 12:14:15 +03:00
|
|
|
import {Key, isOnlyCtrlOrCmdKeyEvent, isOnlyCtrlOrCmdIgnoreShiftKeyEvent} from '../../Keyboard';
|
2016-11-03 21:42:26 +03:00
|
|
|
import PageTypes from '../../PageTypes';
|
2017-04-28 20:21:22 +03:00
|
|
|
import CallMediaHandler from '../../CallMediaHandler';
|
2019-12-20 04:19:56 +03:00
|
|
|
import * as sdk from '../../index';
|
2017-01-25 17:59:18 +03:00
|
|
|
import dis from '../../dispatcher';
|
2017-05-15 16:56:05 +03:00
|
|
|
import sessionStore from '../../stores/SessionStore';
|
2020-04-13 16:29:00 +03:00
|
|
|
import {MatrixClientPeg, MatrixClientCreds} from '../../MatrixClientPeg';
|
2017-10-29 10:43:52 +03:00
|
|
|
import SettingsStore from "../../settings/SettingsStore";
|
2018-08-07 19:04:37 +03:00
|
|
|
import RoomListStore from "../../stores/RoomListStore";
|
2016-11-03 21:42:26 +03:00
|
|
|
|
2018-02-14 19:40:58 +03:00
|
|
|
import TagOrderActions from '../../actions/TagOrderActions';
|
|
|
|
import RoomListActions from '../../actions/RoomListActions';
|
2018-09-24 18:07:42 +03:00
|
|
|
import ResizeHandle from '../views/elements/ResizeHandle';
|
2019-01-21 22:32:57 +03:00
|
|
|
import {Resizer, CollapseDistributor} from '../../resizer';
|
2019-12-17 20:26:12 +03:00
|
|
|
import MatrixClientContext from "../../contexts/MatrixClientContext";
|
2020-03-18 19:40:21 +03:00
|
|
|
import * as KeyboardShortcuts from "../../accessibility/KeyboardShortcuts";
|
2020-04-07 12:48:56 +03:00
|
|
|
import HomePage from "./HomePage";
|
2020-04-13 16:29:00 +03:00
|
|
|
import ResizeNotifier from "../../utils/ResizeNotifier";
|
2020-04-11 20:57:59 +03:00
|
|
|
import PlatformPeg from "../../PlatformPeg";
|
2018-08-07 19:04:37 +03:00
|
|
|
// We need to fetch each pinned message individually (if we don't already have it)
|
|
|
|
// so each pinned message may trigger a request. Limit the number per room for sanity.
|
|
|
|
// NB. this is just for server notices rather than pinned messages in general.
|
|
|
|
const MAX_PINNED_NOTICES_PER_ROOM = 2;
|
|
|
|
|
2019-07-19 18:49:04 +03:00
|
|
|
function canElementReceiveInput(el) {
|
|
|
|
return el.tagName === "INPUT" ||
|
|
|
|
el.tagName === "TEXTAREA" ||
|
|
|
|
el.tagName === "SELECT" ||
|
|
|
|
!!el.getAttribute("contenteditable");
|
|
|
|
}
|
|
|
|
|
2020-04-13 16:29:00 +03:00
|
|
|
interface IProps {
|
|
|
|
matrixClient: MatrixClient;
|
|
|
|
onRegistered: (credentials: MatrixClientCreds) => Promise<MatrixClient>;
|
|
|
|
viaServers?: string[];
|
|
|
|
hideToSRUsers: boolean;
|
|
|
|
resizeNotifier: ResizeNotifier;
|
|
|
|
middleDisabled: boolean;
|
|
|
|
initialEventPixelOffset: number;
|
|
|
|
leftDisabled: boolean;
|
|
|
|
rightDisabled: boolean;
|
|
|
|
showCookieBar: boolean;
|
|
|
|
hasNewVersion: boolean;
|
|
|
|
userHasGeneratedPassword: boolean;
|
|
|
|
showNotifierToolbar: boolean;
|
|
|
|
page_type: string;
|
|
|
|
autoJoin: boolean;
|
|
|
|
thirdPartyInvite?: object;
|
|
|
|
roomOobData?: object;
|
|
|
|
currentRoomId: string;
|
|
|
|
ConferenceHandler?: object;
|
|
|
|
collapseLhs: boolean;
|
|
|
|
checkingForUpdate: boolean;
|
|
|
|
config: {
|
|
|
|
piwik: {
|
|
|
|
policyUrl: string;
|
|
|
|
},
|
|
|
|
[key: string]: any,
|
|
|
|
};
|
|
|
|
currentUserId?: string;
|
|
|
|
currentGroupId?: string;
|
|
|
|
currentGroupIsNew?: boolean;
|
|
|
|
version?: string;
|
|
|
|
newVersion?: string;
|
|
|
|
newVersionReleaseNotes?: string;
|
|
|
|
}
|
|
|
|
interface IState {
|
|
|
|
mouseDown?: {
|
|
|
|
x: number;
|
|
|
|
y: number;
|
|
|
|
};
|
|
|
|
syncErrorData: any;
|
|
|
|
useCompactLayout: boolean;
|
|
|
|
serverNoticeEvents: MatrixEvent[];
|
|
|
|
userHasGeneratedPassword: boolean;
|
|
|
|
}
|
|
|
|
|
2016-11-03 21:42:26 +03:00
|
|
|
/**
|
|
|
|
* This is what our MatrixChat shows when we are logged in. The precise view is
|
|
|
|
* determined by the page_type property.
|
|
|
|
*
|
|
|
|
* Currently it's very tightly coupled with MatrixChat. We should try to do
|
|
|
|
* something about that.
|
2016-11-03 21:54:30 +03:00
|
|
|
*
|
|
|
|
* Components mounted below us can access the matrix client via the react context.
|
2016-11-03 21:42:26 +03:00
|
|
|
*/
|
2020-04-13 16:29:00 +03:00
|
|
|
class LoggedInView extends React.PureComponent<IProps, IState> {
|
2020-04-13 16:13:27 +03:00
|
|
|
static displayName = 'LoggedInView';
|
2016-11-03 21:42:26 +03:00
|
|
|
|
2020-04-13 16:13:27 +03:00
|
|
|
static propTypes = {
|
2019-08-06 16:04:33 +03:00
|
|
|
matrixClient: PropTypes.instanceOf(MatrixClient).isRequired,
|
2017-12-26 04:03:18 +03:00
|
|
|
page_type: PropTypes.string.isRequired,
|
|
|
|
onRoomCreated: PropTypes.func,
|
2016-11-03 21:42:26 +03:00
|
|
|
|
2017-04-28 15:22:55 +03:00
|
|
|
// Called with the credentials of a registered user (if they were a ROU that
|
|
|
|
// transitioned to PWLU)
|
2017-12-26 04:03:18 +03:00
|
|
|
onRegistered: PropTypes.func,
|
2017-02-03 14:48:24 +03:00
|
|
|
|
2018-10-19 22:30:38 +03:00
|
|
|
// Used by the RoomView to handle joining rooms
|
|
|
|
viaServers: PropTypes.arrayOf(PropTypes.string),
|
|
|
|
|
2016-11-03 21:42:26 +03:00
|
|
|
// and lots and lots of other stuff.
|
2020-04-13 16:13:27 +03:00
|
|
|
};
|
2016-11-03 21:42:26 +03:00
|
|
|
|
2020-04-13 16:30:52 +03:00
|
|
|
protected readonly _matrixClient: MatrixClient;
|
|
|
|
protected readonly _roomView: React.RefObject<any>;
|
|
|
|
protected readonly _resizeContainer: React.RefObject<ResizeHandle>;
|
|
|
|
protected readonly _sessionStore: sessionStore;
|
|
|
|
protected readonly _sessionStoreToken: { remove: () => void };
|
|
|
|
protected resizer: Resizer;
|
2020-04-13 16:29:00 +03:00
|
|
|
|
2020-04-13 16:13:27 +03:00
|
|
|
constructor(props, context) {
|
|
|
|
super(props, context);
|
|
|
|
|
|
|
|
this.state = {
|
2020-04-13 16:29:00 +03:00
|
|
|
mouseDown: undefined,
|
|
|
|
syncErrorData: undefined,
|
|
|
|
userHasGeneratedPassword: false,
|
2017-06-01 05:00:30 +03:00
|
|
|
// use compact timeline view
|
2017-10-29 10:43:52 +03:00
|
|
|
useCompactLayout: SettingsStore.getValue('useCompactLayout'),
|
2018-08-07 19:04:37 +03:00
|
|
|
// any currently active server notice events
|
|
|
|
serverNoticeEvents: [],
|
2017-06-01 05:00:30 +03:00
|
|
|
};
|
2018-09-24 18:07:42 +03:00
|
|
|
|
2016-11-03 21:54:30 +03:00
|
|
|
// stash the MatrixClient in case we log out before we are unmounted
|
|
|
|
this._matrixClient = this.props.matrixClient;
|
|
|
|
|
2017-05-25 03:01:40 +03:00
|
|
|
CallMediaHandler.loadDevices();
|
2017-04-28 20:21:22 +03:00
|
|
|
|
2019-07-17 17:50:05 +03:00
|
|
|
document.addEventListener('keydown', this._onNativeKeyDown, false);
|
2017-05-15 16:56:05 +03:00
|
|
|
|
|
|
|
this._sessionStore = sessionStore;
|
2017-05-16 13:52:51 +03:00
|
|
|
this._sessionStoreToken = this._sessionStore.addListener(
|
2017-05-15 19:17:32 +03:00
|
|
|
this._setStateFromSessionStore,
|
2017-05-16 13:52:51 +03:00
|
|
|
);
|
2017-05-15 16:56:05 +03:00
|
|
|
this._setStateFromSessionStore();
|
2017-06-05 18:08:03 +03:00
|
|
|
|
2018-08-07 19:04:37 +03:00
|
|
|
this._updateServerNoticeEvents();
|
|
|
|
|
2017-06-01 05:00:30 +03:00
|
|
|
this._matrixClient.on("accountData", this.onAccountData);
|
2018-08-03 16:54:52 +03:00
|
|
|
this._matrixClient.on("sync", this.onSync);
|
2018-08-07 19:04:37 +03:00
|
|
|
this._matrixClient.on("RoomState.events", this.onRoomStateEvents);
|
2019-05-22 04:31:24 +03:00
|
|
|
|
2020-04-13 16:29:00 +03:00
|
|
|
this._roomView = React.createRef();
|
|
|
|
this._resizeContainer = React.createRef();
|
2020-04-13 16:13:27 +03:00
|
|
|
}
|
2016-11-03 21:42:26 +03:00
|
|
|
|
2020-04-13 16:13:27 +03:00
|
|
|
componentDidMount() {
|
|
|
|
this.resizer = this._createResizer();
|
|
|
|
this.resizer.attach();
|
|
|
|
this._loadResizerPreferences();
|
|
|
|
}
|
2016-11-03 21:42:26 +03:00
|
|
|
|
2020-04-13 16:29:00 +03:00
|
|
|
componentDidUpdate(prevProps, prevState) {
|
2019-03-12 19:29:16 +03:00
|
|
|
// attempt to guess when a banner was opened or closed
|
|
|
|
if (
|
|
|
|
(prevProps.showCookieBar !== this.props.showCookieBar) ||
|
|
|
|
(prevProps.hasNewVersion !== this.props.hasNewVersion) ||
|
2020-04-13 16:29:00 +03:00
|
|
|
(prevState.userHasGeneratedPassword !== this.state.userHasGeneratedPassword) ||
|
2019-03-12 19:29:16 +03:00
|
|
|
(prevProps.showNotifierToolbar !== this.props.showNotifierToolbar)
|
|
|
|
) {
|
|
|
|
this.props.resizeNotifier.notifyBannersChanged();
|
|
|
|
}
|
2020-04-13 16:13:27 +03:00
|
|
|
}
|
2019-03-12 19:29:16 +03:00
|
|
|
|
2020-04-13 16:13:27 +03:00
|
|
|
componentWillUnmount() {
|
2019-07-17 17:50:05 +03:00
|
|
|
document.removeEventListener('keydown', this._onNativeKeyDown, false);
|
2017-06-01 05:00:30 +03:00
|
|
|
this._matrixClient.removeListener("accountData", this.onAccountData);
|
2018-08-07 19:04:37 +03:00
|
|
|
this._matrixClient.removeListener("sync", this.onSync);
|
|
|
|
this._matrixClient.removeListener("RoomState.events", this.onRoomStateEvents);
|
2017-05-16 13:52:51 +03:00
|
|
|
if (this._sessionStoreToken) {
|
|
|
|
this._sessionStoreToken.remove();
|
2017-05-15 19:17:32 +03:00
|
|
|
}
|
2018-10-16 19:43:40 +03:00
|
|
|
this.resizer.detach();
|
2020-04-13 16:13:27 +03:00
|
|
|
}
|
2016-11-03 21:42:26 +03:00
|
|
|
|
2017-05-31 17:13:27 +03:00
|
|
|
// Child components assume that the client peg will not be null, so give them some
|
|
|
|
// sort of assurance here by only allowing a re-render if the client is truthy.
|
|
|
|
//
|
|
|
|
// This is required because `LoggedInView` maintains its own state and if this state
|
|
|
|
// updates after the client peg has been made null (during logout), then it will
|
|
|
|
// attempt to re-render and the children will throw errors.
|
2020-04-13 16:13:27 +03:00
|
|
|
shouldComponentUpdate() {
|
2017-05-31 17:13:27 +03:00
|
|
|
return Boolean(MatrixClientPeg.get());
|
2020-04-13 16:13:27 +03:00
|
|
|
}
|
2016-11-03 21:42:26 +03:00
|
|
|
|
2020-04-13 16:13:27 +03:00
|
|
|
canResetTimelineInRoom = (roomId) => {
|
2019-12-08 15:16:17 +03:00
|
|
|
if (!this._roomView.current) {
|
2017-03-22 18:06:52 +03:00
|
|
|
return true;
|
|
|
|
}
|
2019-12-08 15:16:17 +03:00
|
|
|
return this._roomView.current.canResetTimeline();
|
2020-04-13 16:13:27 +03:00
|
|
|
};
|
2017-03-22 18:06:52 +03:00
|
|
|
|
2020-04-13 16:13:27 +03:00
|
|
|
_setStateFromSessionStore = () => {
|
2017-05-15 16:56:05 +03:00
|
|
|
this.setState({
|
|
|
|
userHasGeneratedPassword: Boolean(this._sessionStore.getCachedPassword()),
|
|
|
|
});
|
2020-04-13 16:13:27 +03:00
|
|
|
};
|
2017-05-15 16:56:05 +03:00
|
|
|
|
2018-10-16 19:43:40 +03:00
|
|
|
_createResizer() {
|
|
|
|
const classNames = {
|
|
|
|
handle: "mx_ResizeHandle",
|
|
|
|
vertical: "mx_ResizeHandle_vertical",
|
2019-01-21 22:32:57 +03:00
|
|
|
reverse: "mx_ResizeHandle_reverse",
|
2018-10-16 19:43:40 +03:00
|
|
|
};
|
|
|
|
const collapseConfig = {
|
|
|
|
toggleSize: 260 - 50,
|
2018-11-26 18:45:55 +03:00
|
|
|
onCollapsed: (collapsed) => {
|
|
|
|
if (collapsed) {
|
2019-01-31 12:15:27 +03:00
|
|
|
dis.dispatch({action: "hide_left_panel"}, true);
|
2018-11-26 18:45:55 +03:00
|
|
|
window.localStorage.setItem("mx_lhs_size", '0');
|
2019-01-31 12:15:27 +03:00
|
|
|
} else {
|
|
|
|
dis.dispatch({action: "show_left_panel"}, true);
|
2018-10-16 19:43:40 +03:00
|
|
|
}
|
|
|
|
},
|
2018-11-26 18:45:55 +03:00
|
|
|
onResized: (size) => {
|
2018-10-31 14:09:53 +03:00
|
|
|
window.localStorage.setItem("mx_lhs_size", '' + size);
|
2019-03-12 18:36:12 +03:00
|
|
|
this.props.resizeNotifier.notifyLeftHandleResized();
|
2018-10-16 19:43:40 +03:00
|
|
|
},
|
|
|
|
};
|
|
|
|
const resizer = new Resizer(
|
2020-04-13 16:29:00 +03:00
|
|
|
this._resizeContainer.current,
|
2018-10-16 19:43:40 +03:00
|
|
|
CollapseDistributor,
|
|
|
|
collapseConfig);
|
|
|
|
resizer.setClassNames(classNames);
|
|
|
|
return resizer;
|
2020-04-13 16:13:27 +03:00
|
|
|
}
|
2018-10-16 19:43:40 +03:00
|
|
|
|
|
|
|
_loadResizerPreferences() {
|
2020-04-13 16:29:00 +03:00
|
|
|
let lhsSize = parseInt(window.localStorage.getItem("mx_lhs_size"), 10);
|
|
|
|
if (isNaN(lhsSize)) {
|
2019-01-22 18:40:11 +03:00
|
|
|
lhsSize = 350;
|
2018-10-16 19:43:40 +03:00
|
|
|
}
|
2019-01-22 18:40:11 +03:00
|
|
|
this.resizer.forHandleAt(0).resize(lhsSize);
|
2020-04-13 16:13:27 +03:00
|
|
|
}
|
2018-10-16 19:43:40 +03:00
|
|
|
|
2020-04-13 16:13:27 +03:00
|
|
|
onAccountData = (event) => {
|
2017-06-01 05:00:30 +03:00
|
|
|
if (event.getType() === "im.vector.web.settings") {
|
|
|
|
this.setState({
|
2017-06-05 18:08:03 +03:00
|
|
|
useCompactLayout: event.getContent().useCompactLayout,
|
2017-06-01 05:00:30 +03:00
|
|
|
});
|
|
|
|
}
|
2017-09-15 05:16:56 +03:00
|
|
|
if (event.getType() === "m.ignored_user_list") {
|
|
|
|
dis.dispatch({action: "ignore_state_changed"});
|
|
|
|
}
|
2020-04-13 16:13:27 +03:00
|
|
|
};
|
2017-06-01 05:00:30 +03:00
|
|
|
|
2020-04-13 16:13:27 +03:00
|
|
|
onSync = (syncState, oldSyncState, data) => {
|
2019-01-21 22:32:57 +03:00
|
|
|
const oldErrCode = (
|
|
|
|
this.state.syncErrorData &&
|
|
|
|
this.state.syncErrorData.error &&
|
|
|
|
this.state.syncErrorData.error.errcode
|
|
|
|
);
|
2018-08-03 20:02:09 +03:00
|
|
|
const newErrCode = data && data.error && data.error.errcode;
|
|
|
|
if (syncState === oldSyncState && oldErrCode === newErrCode) return;
|
2018-08-03 16:54:52 +03:00
|
|
|
|
|
|
|
if (syncState === 'ERROR') {
|
|
|
|
this.setState({
|
|
|
|
syncErrorData: data,
|
|
|
|
});
|
|
|
|
} else {
|
|
|
|
this.setState({
|
|
|
|
syncErrorData: null,
|
|
|
|
});
|
|
|
|
}
|
2018-08-07 19:04:37 +03:00
|
|
|
|
|
|
|
if (oldSyncState === 'PREPARED' && syncState === 'SYNCING') {
|
|
|
|
this._updateServerNoticeEvents();
|
|
|
|
}
|
2020-04-13 16:13:27 +03:00
|
|
|
};
|
2018-08-03 16:54:52 +03:00
|
|
|
|
2020-04-13 16:13:27 +03:00
|
|
|
onRoomStateEvents = (ev, state) => {
|
2018-08-07 19:04:37 +03:00
|
|
|
const roomLists = RoomListStore.getRoomLists();
|
2018-08-13 15:53:38 +03:00
|
|
|
if (roomLists['m.server_notice'] && roomLists['m.server_notice'].some(r => r.roomId === ev.getRoomId())) {
|
2018-08-07 19:04:37 +03:00
|
|
|
this._updateServerNoticeEvents();
|
|
|
|
}
|
2020-04-13 16:13:27 +03:00
|
|
|
};
|
2018-08-07 19:04:37 +03:00
|
|
|
|
2020-04-13 16:13:27 +03:00
|
|
|
_updateServerNoticeEvents = async () => {
|
2018-08-07 19:04:37 +03:00
|
|
|
const roomLists = RoomListStore.getRoomLists();
|
|
|
|
if (!roomLists['m.server_notice']) return [];
|
2018-09-24 18:07:42 +03:00
|
|
|
|
2018-08-07 19:04:37 +03:00
|
|
|
const pinnedEvents = [];
|
|
|
|
for (const room of roomLists['m.server_notice']) {
|
|
|
|
const pinStateEvent = room.currentState.getStateEvents("m.room.pinned_events", "");
|
|
|
|
|
|
|
|
if (!pinStateEvent || !pinStateEvent.getContent().pinned) continue;
|
2018-09-24 18:07:42 +03:00
|
|
|
|
2018-08-07 19:04:37 +03:00
|
|
|
const pinnedEventIds = pinStateEvent.getContent().pinned.slice(0, MAX_PINNED_NOTICES_PER_ROOM);
|
|
|
|
for (const eventId of pinnedEventIds) {
|
|
|
|
const timeline = await this._matrixClient.getEventTimeline(room.getUnfilteredTimelineSet(), eventId, 0);
|
2020-04-13 16:33:45 +03:00
|
|
|
const event = timeline.getEvents().find(ev => ev.getId() === eventId);
|
|
|
|
if (event) pinnedEvents.push(event);
|
2018-08-07 19:04:37 +03:00
|
|
|
}
|
|
|
|
}
|
|
|
|
this.setState({
|
|
|
|
serverNoticeEvents: pinnedEvents,
|
|
|
|
});
|
2020-04-13 16:13:27 +03:00
|
|
|
};
|
2018-09-24 18:07:42 +03:00
|
|
|
|
2020-04-13 16:13:27 +03:00
|
|
|
_onPaste = (ev) => {
|
2019-07-19 18:49:04 +03:00
|
|
|
let canReceiveInput = false;
|
|
|
|
let element = ev.target;
|
|
|
|
// test for all parents because the target can be a child of a contenteditable element
|
|
|
|
while (!canReceiveInput && element) {
|
|
|
|
canReceiveInput = canElementReceiveInput(element);
|
|
|
|
element = element.parentElement;
|
|
|
|
}
|
|
|
|
if (!canReceiveInput) {
|
2019-07-23 10:44:17 +03:00
|
|
|
// refocusing during a paste event will make the
|
|
|
|
// paste end up in the newly focused element,
|
|
|
|
// so dispatch synchronously before paste happens
|
2019-07-19 18:49:04 +03:00
|
|
|
dis.dispatch({action: 'focus_composer'}, true);
|
|
|
|
}
|
2020-04-13 16:13:27 +03:00
|
|
|
};
|
2019-07-19 18:49:04 +03:00
|
|
|
|
2019-07-17 17:50:05 +03:00
|
|
|
/*
|
|
|
|
SOME HACKERY BELOW:
|
|
|
|
React optimizes event handlers, by always attaching only 1 handler to the document for a given type.
|
|
|
|
It then internally determines the order in which React event handlers should be called,
|
|
|
|
emulating the capture and bubbling phases the DOM also has.
|
|
|
|
|
|
|
|
But, as the native handler for React is always attached on the document,
|
|
|
|
it will always run last for bubbling (first for capturing) handlers,
|
|
|
|
and thus React basically has its own event phases, and will always run
|
|
|
|
after (before for capturing) any native other event handlers (as they tend to be attached last).
|
|
|
|
|
|
|
|
So ideally one wouldn't mix React and native event handlers to have bubbling working as expected,
|
|
|
|
but we do need a native event handler here on the document,
|
|
|
|
to get keydown events when there is no focused element (target=body).
|
|
|
|
|
|
|
|
We also do need bubbling here to give child components a chance to call `stopPropagation()`,
|
|
|
|
for keydown events it can handle itself, and shouldn't be redirected to the composer.
|
|
|
|
|
|
|
|
So we listen with React on this component to get any events on focused elements, and get bubbling working as expected.
|
|
|
|
We also listen with a native listener on the document to get keydown events when no element is focused.
|
|
|
|
Bubbling is irrelevant here as the target is the body element.
|
|
|
|
*/
|
2020-04-13 16:13:27 +03:00
|
|
|
_onReactKeyDown = (ev) => {
|
2019-07-17 17:50:05 +03:00
|
|
|
// events caught while bubbling up on the root element
|
|
|
|
// of this component, so something must be focused.
|
|
|
|
this._onKeyDown(ev);
|
2020-04-13 16:13:27 +03:00
|
|
|
};
|
2019-07-17 17:50:05 +03:00
|
|
|
|
2020-04-13 16:13:27 +03:00
|
|
|
_onNativeKeyDown = (ev) => {
|
2019-07-17 17:50:05 +03:00
|
|
|
// only pass this if there is no focused element.
|
|
|
|
// if there is, _onKeyDown will be called by the
|
|
|
|
// react keydown handler that respects the react bubbling order.
|
|
|
|
if (ev.target === document.body) {
|
|
|
|
this._onKeyDown(ev);
|
|
|
|
}
|
2020-04-13 16:13:27 +03:00
|
|
|
};
|
2018-08-07 19:04:37 +03:00
|
|
|
|
2020-04-13 16:13:27 +03:00
|
|
|
_onKeyDown = (ev) => {
|
2016-11-03 21:42:26 +03:00
|
|
|
/*
|
|
|
|
// Remove this for now as ctrl+alt = alt-gr so this breaks keyboards which rely on alt-gr for numbers
|
|
|
|
// Will need to find a better meta key if anyone actually cares about using this.
|
|
|
|
if (ev.altKey && ev.ctrlKey && ev.keyCode > 48 && ev.keyCode < 58) {
|
|
|
|
dis.dispatch({
|
|
|
|
action: 'view_indexed_room',
|
|
|
|
roomIndex: ev.keyCode - 49,
|
|
|
|
});
|
|
|
|
ev.stopPropagation();
|
|
|
|
ev.preventDefault();
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
*/
|
|
|
|
|
2017-07-12 19:12:57 +03:00
|
|
|
let handled = false;
|
2017-12-01 13:44:00 +03:00
|
|
|
const ctrlCmdOnly = isOnlyCtrlOrCmdKeyEvent(ev);
|
2020-03-14 01:21:34 +03:00
|
|
|
const hasModifier = ev.altKey || ev.ctrlKey || ev.metaKey || ev.shiftKey;
|
|
|
|
const isModifier = ev.key === Key.ALT || ev.key === Key.CONTROL || ev.key === Key.META || ev.key === Key.SHIFT;
|
2016-11-03 21:42:26 +03:00
|
|
|
|
2019-09-30 18:04:06 +03:00
|
|
|
switch (ev.key) {
|
|
|
|
case Key.PAGE_UP:
|
|
|
|
case Key.PAGE_DOWN:
|
2020-03-14 01:21:34 +03:00
|
|
|
if (!hasModifier && !isModifier) {
|
2017-04-23 02:49:14 +03:00
|
|
|
this._onScrollKeyPressed(ev);
|
|
|
|
handled = true;
|
|
|
|
}
|
2016-11-03 21:42:26 +03:00
|
|
|
break;
|
|
|
|
|
2019-09-30 18:04:06 +03:00
|
|
|
case Key.HOME:
|
|
|
|
case Key.END:
|
2017-04-23 02:49:14 +03:00
|
|
|
if (ev.ctrlKey && !ev.shiftKey && !ev.altKey && !ev.metaKey) {
|
2016-11-03 21:42:26 +03:00
|
|
|
this._onScrollKeyPressed(ev);
|
|
|
|
handled = true;
|
|
|
|
}
|
|
|
|
break;
|
2019-09-30 18:04:06 +03:00
|
|
|
case Key.K:
|
2017-07-12 19:12:57 +03:00
|
|
|
if (ctrlCmdOnly) {
|
2017-07-12 15:51:55 +03:00
|
|
|
dis.dispatch({
|
|
|
|
action: 'focus_room_filter',
|
|
|
|
});
|
2017-07-12 19:12:57 +03:00
|
|
|
handled = true;
|
2017-07-12 15:51:55 +03:00
|
|
|
}
|
|
|
|
break;
|
2019-09-30 18:04:06 +03:00
|
|
|
case Key.BACKTICK:
|
2019-05-21 05:41:48 +03:00
|
|
|
// Ideally this would be CTRL+P for "Profile", but that's
|
|
|
|
// taken by the print dialog. CTRL+I for "Information"
|
2019-06-07 02:08:51 +03:00
|
|
|
// was previously chosen but conflicted with italics in
|
|
|
|
// composer, so CTRL+` it is
|
2019-05-21 05:41:48 +03:00
|
|
|
|
2019-05-18 00:28:12 +03:00
|
|
|
if (ctrlCmdOnly) {
|
|
|
|
dis.dispatch({
|
|
|
|
action: 'toggle_top_left_menu',
|
|
|
|
});
|
|
|
|
handled = true;
|
|
|
|
}
|
|
|
|
break;
|
2020-03-18 19:40:21 +03:00
|
|
|
|
|
|
|
case Key.SLASH:
|
2020-04-01 12:14:15 +03:00
|
|
|
if (isOnlyCtrlOrCmdIgnoreShiftKeyEvent(ev)) {
|
2020-03-18 19:40:21 +03:00
|
|
|
KeyboardShortcuts.toggleDialog();
|
|
|
|
handled = true;
|
|
|
|
}
|
|
|
|
break;
|
2020-03-20 03:18:24 +03:00
|
|
|
|
2020-03-19 22:07:33 +03:00
|
|
|
case Key.ARROW_UP:
|
|
|
|
case Key.ARROW_DOWN:
|
|
|
|
if (ev.altKey && !ev.ctrlKey && !ev.metaKey) {
|
|
|
|
dis.dispatch({
|
|
|
|
action: 'view_room_delta',
|
|
|
|
delta: ev.key === Key.ARROW_UP ? -1 : 1,
|
|
|
|
unread: ev.shiftKey,
|
|
|
|
});
|
|
|
|
handled = true;
|
|
|
|
}
|
|
|
|
break;
|
2020-03-20 19:10:27 +03:00
|
|
|
|
2020-03-20 03:18:24 +03:00
|
|
|
case Key.PERIOD:
|
|
|
|
if (ctrlCmdOnly && (this.props.page_type === "room_view" || this.props.page_type === "group_view")) {
|
|
|
|
dis.dispatch({
|
|
|
|
action: 'toggle_right_panel',
|
|
|
|
type: this.props.page_type === "room_view" ? "room" : "group",
|
|
|
|
});
|
|
|
|
handled = true;
|
|
|
|
}
|
2020-04-11 20:57:59 +03:00
|
|
|
break;
|
|
|
|
|
|
|
|
default:
|
|
|
|
// if we do not have a handler for it, pass it to the platform which might
|
|
|
|
handled = PlatformPeg.get().onKeyDown(ev);
|
2016-11-03 21:42:26 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
if (handled) {
|
|
|
|
ev.stopPropagation();
|
|
|
|
ev.preventDefault();
|
2020-03-14 01:21:34 +03:00
|
|
|
} else if (!isModifier && !ev.altKey && !ev.ctrlKey && !ev.metaKey) {
|
|
|
|
// The above condition is crafted to _allow_ characters with Shift
|
|
|
|
// already pressed (but not the Shift key down itself).
|
|
|
|
|
2019-07-17 17:53:12 +03:00
|
|
|
const isClickShortcut = ev.target !== document.body &&
|
2019-10-04 13:47:33 +03:00
|
|
|
(ev.key === Key.SPACE || ev.key === Key.ENTER);
|
2019-07-17 17:53:12 +03:00
|
|
|
|
2019-12-02 13:01:08 +03:00
|
|
|
// Do not capture the context menu key to improve keyboard accessibility
|
|
|
|
if (ev.key === Key.CONTEXT_MENU) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2019-10-10 16:13:29 +03:00
|
|
|
if (!isClickShortcut && ev.key !== Key.TAB && !canElementReceiveInput(ev.target)) {
|
2019-07-23 10:44:17 +03:00
|
|
|
// synchronous dispatch so we focus before key generates input
|
2019-07-15 19:12:45 +03:00
|
|
|
dis.dispatch({action: 'focus_composer'}, true);
|
|
|
|
ev.stopPropagation();
|
|
|
|
// we should *not* preventDefault() here as
|
|
|
|
// that would prevent typing in the now-focussed composer
|
|
|
|
}
|
2016-11-03 21:42:26 +03:00
|
|
|
}
|
2020-04-13 16:13:27 +03:00
|
|
|
};
|
2016-11-03 21:42:26 +03:00
|
|
|
|
2019-01-21 22:32:57 +03:00
|
|
|
/**
|
|
|
|
* dispatch a page-up/page-down/etc to the appropriate component
|
|
|
|
* @param {Object} ev The key event
|
|
|
|
*/
|
2020-04-13 16:13:27 +03:00
|
|
|
_onScrollKeyPressed = (ev) => {
|
2019-12-08 15:16:17 +03:00
|
|
|
if (this._roomView.current) {
|
|
|
|
this._roomView.current.handleScrollKey(ev);
|
2017-04-23 03:00:44 +03:00
|
|
|
}
|
2020-04-13 16:13:27 +03:00
|
|
|
};
|
2016-11-03 21:42:26 +03:00
|
|
|
|
2020-04-13 16:13:27 +03:00
|
|
|
_onDragEnd = (result) => {
|
2018-02-14 19:40:58 +03:00
|
|
|
// Dragged to an invalid destination, not onto a droppable
|
|
|
|
if (!result.destination) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
const dest = result.destination.droppableId;
|
|
|
|
|
|
|
|
if (dest === 'tag-panel-droppable') {
|
|
|
|
// Could be "GroupTile +groupId:domain"
|
|
|
|
const draggableId = result.draggableId.split(' ').pop();
|
|
|
|
|
|
|
|
// Dispatch synchronously so that the TagPanel receives an
|
|
|
|
// optimistic update from TagOrderStore before the previous
|
|
|
|
// state is shown.
|
|
|
|
dis.dispatch(TagOrderActions.moveTag(
|
|
|
|
this._matrixClient,
|
|
|
|
draggableId,
|
|
|
|
result.destination.index,
|
|
|
|
), true);
|
|
|
|
} else if (dest.startsWith('room-sub-list-droppable_')) {
|
|
|
|
this._onRoomTileEndDrag(result);
|
|
|
|
}
|
2020-04-13 16:13:27 +03:00
|
|
|
};
|
2018-02-14 19:40:58 +03:00
|
|
|
|
2020-04-13 16:13:27 +03:00
|
|
|
_onRoomTileEndDrag = (result) => {
|
2018-02-14 19:40:58 +03:00
|
|
|
let newTag = result.destination.droppableId.split('_')[1];
|
|
|
|
let prevTag = result.source.droppableId.split('_')[1];
|
|
|
|
if (newTag === 'undefined') newTag = undefined;
|
|
|
|
if (prevTag === 'undefined') prevTag = undefined;
|
|
|
|
|
|
|
|
const roomId = result.draggableId.split('_')[1];
|
|
|
|
|
|
|
|
const oldIndex = result.source.index;
|
|
|
|
const newIndex = result.destination.index;
|
|
|
|
|
|
|
|
dis.dispatch(RoomListActions.tagRoom(
|
|
|
|
this._matrixClient,
|
|
|
|
this._matrixClient.getRoom(roomId),
|
|
|
|
prevTag, newTag,
|
|
|
|
oldIndex, newIndex,
|
|
|
|
), true);
|
2020-04-13 16:13:27 +03:00
|
|
|
};
|
2018-02-14 19:40:58 +03:00
|
|
|
|
2020-04-13 16:13:27 +03:00
|
|
|
_onMouseDown = (ev) => {
|
2018-05-29 15:16:39 +03:00
|
|
|
// When the panels are disabled, clicking on them results in a mouse event
|
|
|
|
// which bubbles to certain elements in the tree. When this happens, close
|
|
|
|
// any settings page that is currently open (user/room/group).
|
2018-07-27 20:58:11 +03:00
|
|
|
if (this.props.leftDisabled && this.props.rightDisabled) {
|
|
|
|
const targetClasses = new Set(ev.target.className.split(' '));
|
|
|
|
if (
|
2018-07-27 20:53:36 +03:00
|
|
|
targetClasses.has('mx_MatrixChat') ||
|
|
|
|
targetClasses.has('mx_MatrixChat_middlePanel') ||
|
|
|
|
targetClasses.has('mx_RoomView')
|
2018-07-27 20:58:11 +03:00
|
|
|
) {
|
2018-09-28 01:11:57 +03:00
|
|
|
this.setState({
|
|
|
|
mouseDown: {
|
|
|
|
x: ev.pageX,
|
|
|
|
y: ev.pageY,
|
|
|
|
},
|
|
|
|
});
|
2018-07-27 20:58:11 +03:00
|
|
|
}
|
2018-05-29 15:16:39 +03:00
|
|
|
}
|
2020-04-13 16:13:27 +03:00
|
|
|
};
|
2018-05-29 15:16:39 +03:00
|
|
|
|
2020-04-13 16:13:27 +03:00
|
|
|
_onMouseUp = (ev) => {
|
2018-09-28 01:11:57 +03:00
|
|
|
if (!this.state.mouseDown) return;
|
|
|
|
|
|
|
|
const deltaX = ev.pageX - this.state.mouseDown.x;
|
|
|
|
const deltaY = ev.pageY - this.state.mouseDown.y;
|
|
|
|
const distance = Math.sqrt((deltaX * deltaX) + (deltaY + deltaY));
|
|
|
|
const maxRadius = 5; // People shouldn't be straying too far, hopefully
|
|
|
|
|
|
|
|
// Note: we track how far the user moved their mouse to help
|
|
|
|
// combat against https://github.com/vector-im/riot-web/issues/7158
|
|
|
|
|
|
|
|
if (distance < maxRadius) {
|
|
|
|
// This is probably a real click, and not a drag
|
|
|
|
dis.dispatch({ action: 'close_settings' });
|
|
|
|
}
|
|
|
|
|
|
|
|
// Always clear the mouseDown state to ensure we don't accidentally
|
|
|
|
// use stale values due to the mouseDown checks.
|
|
|
|
this.setState({mouseDown: null});
|
2020-04-13 16:13:27 +03:00
|
|
|
};
|
2018-10-19 16:22:28 +03:00
|
|
|
|
2020-04-13 16:13:27 +03:00
|
|
|
render() {
|
2017-04-26 15:48:03 +03:00
|
|
|
const LeftPanel = sdk.getComponent('structures.LeftPanel');
|
|
|
|
const RoomView = sdk.getComponent('structures.RoomView');
|
2019-02-20 14:45:55 +03:00
|
|
|
const UserView = sdk.getComponent('structures.UserView');
|
2017-06-05 18:51:50 +03:00
|
|
|
const GroupView = sdk.getComponent('structures.GroupView');
|
2017-06-28 15:56:18 +03:00
|
|
|
const MyGroups = sdk.getComponent('structures.MyGroups');
|
2019-11-20 18:18:28 +03:00
|
|
|
const ToastContainer = sdk.getComponent('structures.ToastContainer');
|
2017-04-26 15:48:03 +03:00
|
|
|
const MatrixToolbar = sdk.getComponent('globals.MatrixToolbar');
|
2018-05-15 15:15:40 +03:00
|
|
|
const CookieBar = sdk.getComponent('globals.CookieBar');
|
2017-04-26 15:48:03 +03:00
|
|
|
const NewVersionBar = sdk.getComponent('globals.NewVersionBar');
|
2017-06-03 17:10:05 +03:00
|
|
|
const UpdateCheckBar = sdk.getComponent('globals.UpdateCheckBar');
|
2017-05-05 18:31:33 +03:00
|
|
|
const PasswordNagBar = sdk.getComponent('globals.PasswordNagBar');
|
2018-08-03 16:54:52 +03:00
|
|
|
const ServerLimitBar = sdk.getComponent('globals.ServerLimitBar');
|
2017-04-26 15:48:03 +03:00
|
|
|
|
2019-01-21 22:32:57 +03:00
|
|
|
let pageElement;
|
2016-11-03 21:42:26 +03:00
|
|
|
|
|
|
|
switch (this.props.page_type) {
|
|
|
|
case PageTypes.RoomView:
|
2019-01-21 22:32:57 +03:00
|
|
|
pageElement = <RoomView
|
2019-12-08 15:16:17 +03:00
|
|
|
ref={this._roomView}
|
2016-11-03 21:42:26 +03:00
|
|
|
autoJoin={this.props.autoJoin}
|
2017-04-28 15:22:55 +03:00
|
|
|
onRegistered={this.props.onRegistered}
|
2016-11-03 21:42:26 +03:00
|
|
|
thirdPartyInvite={this.props.thirdPartyInvite}
|
|
|
|
oobData={this.props.roomOobData}
|
2018-10-19 22:30:38 +03:00
|
|
|
viaServers={this.props.viaServers}
|
2016-11-03 21:42:26 +03:00
|
|
|
eventPixelOffset={this.props.initialEventPixelOffset}
|
2017-05-24 20:04:04 +03:00
|
|
|
key={this.props.currentRoomId || 'roomview'}
|
2017-10-25 13:09:48 +03:00
|
|
|
disabled={this.props.middleDisabled}
|
2016-11-03 21:42:26 +03:00
|
|
|
ConferenceHandler={this.props.ConferenceHandler}
|
2019-03-12 18:36:12 +03:00
|
|
|
resizeNotifier={this.props.resizeNotifier}
|
2017-01-20 17:22:27 +03:00
|
|
|
/>;
|
2016-11-03 21:42:26 +03:00
|
|
|
break;
|
2019-01-17 12:29:37 +03:00
|
|
|
|
2017-06-28 15:56:18 +03:00
|
|
|
case PageTypes.MyGroups:
|
2019-01-21 22:32:57 +03:00
|
|
|
pageElement = <MyGroups />;
|
2017-06-28 15:56:18 +03:00
|
|
|
break;
|
|
|
|
|
2016-11-03 21:42:26 +03:00
|
|
|
case PageTypes.RoomDirectory:
|
2019-01-29 17:34:58 +03:00
|
|
|
// handled by MatrixChat for now
|
2016-11-03 21:42:26 +03:00
|
|
|
break;
|
2016-11-13 17:10:46 +03:00
|
|
|
|
|
|
|
case PageTypes.HomePage:
|
2020-04-07 12:48:56 +03:00
|
|
|
pageElement = <HomePage />;
|
2016-11-13 17:10:46 +03:00
|
|
|
break;
|
|
|
|
|
2016-11-03 21:42:26 +03:00
|
|
|
case PageTypes.UserView:
|
2019-02-20 14:45:55 +03:00
|
|
|
pageElement = <UserView userId={this.props.currentUserId} />;
|
2016-11-03 21:42:26 +03:00
|
|
|
break;
|
2017-06-05 18:51:50 +03:00
|
|
|
case PageTypes.GroupView:
|
2019-01-21 22:32:57 +03:00
|
|
|
pageElement = <GroupView
|
2017-06-05 18:51:50 +03:00
|
|
|
groupId={this.props.currentGroupId}
|
2017-11-06 21:02:50 +03:00
|
|
|
isNew={this.props.currentGroupIsNew}
|
2017-06-27 12:05:05 +03:00
|
|
|
/>;
|
2017-06-05 18:51:50 +03:00
|
|
|
break;
|
2016-11-03 21:42:26 +03:00
|
|
|
}
|
|
|
|
|
2018-08-16 12:35:27 +03:00
|
|
|
const usageLimitEvent = this.state.serverNoticeEvents.find((e) => {
|
2018-08-22 19:54:02 +03:00
|
|
|
return (
|
|
|
|
e && e.getType() === 'm.room.message' &&
|
|
|
|
e.getContent()['server_notice_type'] === 'm.server_notice.usage_limit_reached'
|
|
|
|
);
|
2018-08-07 19:04:37 +03:00
|
|
|
});
|
|
|
|
|
2017-06-03 17:10:05 +03:00
|
|
|
let topBar;
|
2018-08-15 19:03:54 +03:00
|
|
|
if (this.state.syncErrorData && this.state.syncErrorData.error.errcode === 'M_RESOURCE_LIMIT_EXCEEDED') {
|
|
|
|
topBar = <ServerLimitBar kind='hard'
|
|
|
|
adminContact={this.state.syncErrorData.error.data.admin_contact}
|
|
|
|
limitType={this.state.syncErrorData.error.data.limit_type}
|
|
|
|
/>;
|
2018-08-16 12:35:27 +03:00
|
|
|
} else if (usageLimitEvent) {
|
2018-08-15 19:03:54 +03:00
|
|
|
topBar = <ServerLimitBar kind='soft'
|
2018-08-16 12:35:27 +03:00
|
|
|
adminContact={usageLimitEvent.getContent().admin_contact}
|
|
|
|
limitType={usageLimitEvent.getContent().limit_type}
|
2018-08-15 19:03:54 +03:00
|
|
|
/>;
|
2018-08-03 16:54:52 +03:00
|
|
|
} else if (this.props.showCookieBar &&
|
2020-02-21 05:09:03 +03:00
|
|
|
this.props.config.piwik &&
|
2020-02-29 03:37:52 +03:00
|
|
|
navigator.doNotTrack !== "1"
|
2018-05-16 17:53:43 +03:00
|
|
|
) {
|
|
|
|
const policyUrl = this.props.config.piwik.policyUrl || null;
|
2018-05-16 17:49:23 +03:00
|
|
|
topBar = <CookieBar policyUrl={policyUrl} />;
|
2018-05-15 15:15:40 +03:00
|
|
|
} else if (this.props.hasNewVersion) {
|
2016-11-03 21:42:26 +03:00
|
|
|
topBar = <NewVersionBar version={this.props.version} newVersion={this.props.newVersion}
|
2017-06-03 17:10:05 +03:00
|
|
|
releaseNotes={this.props.newVersionReleaseNotes}
|
2016-11-03 21:42:26 +03:00
|
|
|
/>;
|
2017-06-03 17:10:05 +03:00
|
|
|
} else if (this.props.checkingForUpdate) {
|
2017-06-11 21:12:40 +03:00
|
|
|
topBar = <UpdateCheckBar {...this.props.checkingForUpdate} />;
|
2017-05-15 16:56:05 +03:00
|
|
|
} else if (this.state.userHasGeneratedPassword) {
|
2017-05-05 18:31:33 +03:00
|
|
|
topBar = <PasswordNagBar />;
|
2019-03-12 19:29:16 +03:00
|
|
|
} else if (this.props.showNotifierToolbar) {
|
2016-11-03 21:42:26 +03:00
|
|
|
topBar = <MatrixToolbar />;
|
|
|
|
}
|
|
|
|
|
2017-10-11 19:56:17 +03:00
|
|
|
let bodyClasses = 'mx_MatrixChat';
|
2016-11-03 21:42:26 +03:00
|
|
|
if (topBar) {
|
|
|
|
bodyClasses += ' mx_MatrixChat_toolbarShowing';
|
|
|
|
}
|
2017-06-01 05:00:30 +03:00
|
|
|
if (this.state.useCompactLayout) {
|
|
|
|
bodyClasses += ' mx_MatrixChat_useCompactLayout';
|
|
|
|
}
|
2016-11-03 21:42:26 +03:00
|
|
|
|
|
|
|
return (
|
2019-12-17 20:26:12 +03:00
|
|
|
<MatrixClientContext.Provider value={this._matrixClient}>
|
|
|
|
<div
|
|
|
|
onPaste={this._onPaste}
|
|
|
|
onKeyDown={this._onReactKeyDown}
|
|
|
|
className='mx_MatrixChat_wrapper'
|
|
|
|
aria-hidden={this.props.hideToSRUsers}
|
|
|
|
onMouseDown={this._onMouseDown}
|
|
|
|
onMouseUp={this._onMouseUp}
|
|
|
|
>
|
|
|
|
{ topBar }
|
|
|
|
<ToastContainer />
|
|
|
|
<DragDropContext onDragEnd={this._onDragEnd}>
|
2020-04-13 16:29:00 +03:00
|
|
|
<div ref={this._resizeContainer} className={bodyClasses}>
|
2019-12-17 20:26:12 +03:00
|
|
|
<LeftPanel
|
|
|
|
resizeNotifier={this.props.resizeNotifier}
|
|
|
|
collapsed={this.props.collapseLhs || false}
|
|
|
|
disabled={this.props.leftDisabled}
|
|
|
|
/>
|
|
|
|
<ResizeHandle />
|
|
|
|
{ pageElement }
|
|
|
|
</div>
|
|
|
|
</DragDropContext>
|
|
|
|
</div>
|
|
|
|
</MatrixClientContext.Provider>
|
2016-11-03 21:42:26 +03:00
|
|
|
);
|
2020-04-13 16:13:27 +03:00
|
|
|
}
|
|
|
|
}
|
2017-12-06 18:01:16 +03:00
|
|
|
|
2018-01-19 16:34:56 +03:00
|
|
|
export default LoggedInView;
|