diff --git a/src/Keyboard.ts b/src/Keyboard.ts index f5cf0a5492..817d0a0b97 100644 --- a/src/Keyboard.ts +++ b/src/Keyboard.ts @@ -36,6 +36,7 @@ export const Key = { CONTEXT_MENU: "ContextMenu", COMMA: ",", + PERIOD: ".", LESS_THAN: "<", GREATER_THAN: ">", BACKTICK: "`", diff --git a/src/accessibility/KeyboardShortcuts.tsx b/src/accessibility/KeyboardShortcuts.tsx index 618ed4755a..ad3da9565f 100644 --- a/src/accessibility/KeyboardShortcuts.tsx +++ b/src/accessibility/KeyboardShortcuts.tsx @@ -197,6 +197,12 @@ const shortcuts: Record = { key: Key.SPACE, }], description: _td("Activate selected button"), + }, { + keybinds: [{ + modifiers: [CMD_OR_CTRL], + key: Key.PERIOD, + }], + description: _td("Toggle right panel"), }, { keybinds: [{ modifiers: [CMD_OR_CTRL], diff --git a/src/components/structures/GroupView.js b/src/components/structures/GroupView.js index 0d10dd6d8f..524694fe95 100644 --- a/src/components/structures/GroupView.js +++ b/src/components/structures/GroupView.js @@ -424,6 +424,7 @@ export default createReactClass({ membershipBusy: false, publicityBusy: false, inviterProfile: null, + showRightPanel: RightPanelStore.getSharedInstance().isOpenForGroup, }; }, @@ -436,12 +437,18 @@ export default createReactClass({ this._initGroupStore(this.props.groupId, true); this._dispatcherRef = dis.register(this._onAction); + this._rightPanelStoreToken = RightPanelStore.getSharedInstance().addListener(this._onRightPanelStoreUpdate); }, componentWillUnmount: function() { this._unmounted = true; this._matrixClient.removeListener("Group.myMembership", this._onGroupMyMembership); dis.unregister(this._dispatcherRef); + + // Remove RightPanelStore listener + if (this._rightPanelStoreToken) { + this._rightPanelStoreToken.remove(); + } }, componentWillReceiveProps: function(newProps) { @@ -455,6 +462,12 @@ export default createReactClass({ } }, + _onRightPanelStoreUpdate: function() { + this.setState({ + showRightPanel: RightPanelStore.getSharedInstance().isOpenForGroup, + }); + }, + _onGroupMyMembership: function(group) { if (this._unmounted || group.groupId !== this.props.groupId) return; if (group.myMembership === 'leave') { @@ -577,10 +590,6 @@ export default createReactClass({ profileForm: null, }); break; - case 'after_right_panel_phase_change': - // We don't keep state on the right panel, so just re-render to update - this.forceUpdate(); - break; default: break; } @@ -1295,9 +1304,7 @@ export default createReactClass({ ); } - const rightPanel = RightPanelStore.getSharedInstance().isOpenForGroup - ? - : undefined; + const rightPanel = this.state.showRightPanel ? : undefined; const headerClasses = { "mx_GroupView_header": true, diff --git a/src/components/structures/LoggedInView.js b/src/components/structures/LoggedInView.js index 576ae2b276..734a8dde88 100644 --- a/src/components/structures/LoggedInView.js +++ b/src/components/structures/LoggedInView.js @@ -385,6 +385,15 @@ const LoggedInView = createReactClass({ handled = true; } break; + + 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; + } } if (handled) { diff --git a/src/components/structures/RoomView.js b/src/components/structures/RoomView.js index 17a496b037..e3020389da 100644 --- a/src/components/structures/RoomView.js +++ b/src/components/structures/RoomView.js @@ -131,6 +131,7 @@ export default createReactClass({ isAlone: false, isPeeking: false, showingPinned: false, + showRightPanel: RightPanelStore.getSharedInstance().isOpenForRoom, // error object, as from the matrix client/server API // If we failed to load information about the room, @@ -176,6 +177,7 @@ export default createReactClass({ MatrixClientPeg.get().on("userTrustStatusChanged", this.onUserVerificationChanged); // Start listening for RoomViewStore updates this._roomStoreToken = RoomViewStore.addListener(this._onRoomViewStoreUpdate); + this._rightPanelStoreToken = RightPanelStore.getSharedInstance().addListener(this._onRightPanelStoreUpdate); this._onRoomViewStoreUpdate(true); WidgetEchoStore.on('update', this._onWidgetEchoStoreUpdate); @@ -488,6 +490,10 @@ export default createReactClass({ if (this._roomStoreToken) { this._roomStoreToken.remove(); } + // Remove RightPanelStore listener + if (this._rightPanelStoreToken) { + this._rightPanelStoreToken.remove(); + } WidgetEchoStore.removeListener('update', this._onWidgetEchoStoreUpdate); @@ -499,6 +505,12 @@ export default createReactClass({ // Tinter.tint(); // reset colourscheme }, + _onRightPanelStoreUpdate: function() { + this.setState({ + showRightPanel: RightPanelStore.getSharedInstance().isOpenForRoom, + }); + }, + onPageUnload(event) { if (ContentMessages.sharedInstance().getCurrentUploads().length > 0) { return event.returnValue = @@ -538,10 +550,6 @@ export default createReactClass({ onAction: function(payload) { switch (payload.action) { - case 'after_right_panel_phase_change': - // We don't keep state on the right panel, so just re-render to update - this.forceUpdate(); - break; case 'message_send_failed': case 'message_sent': this._checkIfAlone(this.state.room); @@ -1997,8 +2005,7 @@ export default createReactClass({ }, ); - const showRightPanel = !forceHideRightPanel && this.state.room - && RightPanelStore.getSharedInstance().isOpenForRoom; + const showRightPanel = !forceHideRightPanel && this.state.room && this.state.showRightPanel; const rightPanel = showRightPanel ? : null; diff --git a/src/i18n/strings/en_EN.json b/src/i18n/strings/en_EN.json index c3befa2298..d9679fde13 100644 --- a/src/i18n/strings/en_EN.json +++ b/src/i18n/strings/en_EN.json @@ -2207,6 +2207,7 @@ "Toggle the top left menu": "Toggle the top left menu", "Close dialog or context menu": "Close dialog or context menu", "Activate selected button": "Activate selected button", + "Toggle right panel": "Toggle right panel", "Toggle this dialog": "Toggle this dialog", "Move autocomplete selection up/down": "Move autocomplete selection up/down", "Cancel autocomplete": "Cancel autocomplete", diff --git a/src/stores/RightPanelStore.js b/src/stores/RightPanelStore.js index ccdeb006f4..625ccc668f 100644 --- a/src/stores/RightPanelStore.js +++ b/src/stores/RightPanelStore.js @@ -35,6 +35,12 @@ const INITIAL_STATE = { const GROUP_PHASES = Object.keys(RIGHT_PANEL_PHASES).filter(k => k.startsWith("Group")); +const MEMBER_INFO_PHASES = [ + RIGHT_PANEL_PHASES.RoomMemberInfo, + RIGHT_PANEL_PHASES.Room3pidMemberInfo, + RIGHT_PANEL_PHASES.EncryptionPanel, +]; + /** * A class for tracking the state of the right panel between layouts and * sessions. @@ -114,62 +120,70 @@ export default class RightPanelStore extends Store { } __onDispatch(payload) { - if (payload.action === 'view_room' || payload.action === 'view_group') { - // Reset to the member list if we're viewing member info - const memberInfoPhases = [ - RIGHT_PANEL_PHASES.RoomMemberInfo, - RIGHT_PANEL_PHASES.Room3pidMemberInfo, - RIGHT_PANEL_PHASES.EncryptionPanel, - ]; - if (memberInfoPhases.includes(this._state.lastRoomPhase)) { - this._setState({lastRoomPhase: RIGHT_PANEL_PHASES.RoomMemberList, lastRoomPhaseParams: {}}); + switch (payload.action) { + case 'view_room': + case 'view_group': + // Reset to the member list if we're viewing member info + if (MEMBER_INFO_PHASES.includes(this._state.lastRoomPhase)) { + this._setState({lastRoomPhase: RIGHT_PANEL_PHASES.RoomMemberList, lastRoomPhaseParams: {}}); + } + + // Do the same for groups + if (this._state.lastGroupPhase === RIGHT_PANEL_PHASES.GroupMemberInfo) { + this._setState({lastGroupPhase: RIGHT_PANEL_PHASES.GroupMemberList}); + } + break; + + case 'set_right_panel_phase': { + const targetPhase = payload.phase; + if (!RIGHT_PANEL_PHASES[targetPhase]) { + console.warn(`Tried to switch right panel to unknown phase: ${targetPhase}`); + return; + } + + if (GROUP_PHASES.includes(targetPhase)) { + if (targetPhase === this._state.lastGroupPhase) { + this._setState({ + showGroupPanel: !this._state.showGroupPanel, + }); + } else { + this._setState({ + lastGroupPhase: targetPhase, + showGroupPanel: true, + }); + } + } else { + if (targetPhase === this._state.lastRoomPhase && !payload.refireParams) { + this._setState({ + showRoomPanel: !this._state.showRoomPanel, + }); + } else { + this._setState({ + lastRoomPhase: targetPhase, + showRoomPanel: true, + lastRoomPhaseParams: payload.refireParams || {}, + }); + } + } + + // Let things like the member info panel actually open to the right member. + dis.dispatch({ + action: 'after_right_panel_phase_change', + phase: targetPhase, + ...(payload.refireParams || {}), + }); + break; } - // Do the same for groups - if (this._state.lastGroupPhase === RIGHT_PANEL_PHASES.GroupMemberInfo) { - this._setState({lastGroupPhase: RIGHT_PANEL_PHASES.GroupMemberList}); - } + case 'toggle_right_panel': + if (payload.type === "room") { + this._setState({ showRoomPanel: !this._state.showRoomPanel }); + } else { // group + this._setState({ showGroupPanel: !this._state.showGroupPanel }); + } + break; + } - - if (payload.action !== 'set_right_panel_phase') return; - - const targetPhase = payload.phase; - if (!RIGHT_PANEL_PHASES[targetPhase]) { - console.warn(`Tried to switch right panel to unknown phase: ${targetPhase}`); - return; - } - - if (GROUP_PHASES.includes(targetPhase)) { - if (targetPhase === this._state.lastGroupPhase) { - this._setState({ - showGroupPanel: !this._state.showGroupPanel, - }); - } else { - this._setState({ - lastGroupPhase: targetPhase, - showGroupPanel: true, - }); - } - } else { - if (targetPhase === this._state.lastRoomPhase && !payload.refireParams) { - this._setState({ - showRoomPanel: !this._state.showRoomPanel, - }); - } else { - this._setState({ - lastRoomPhase: targetPhase, - showRoomPanel: true, - lastRoomPhaseParams: payload.refireParams || {}, - }); - } - } - - // Let things like the member info panel actually open to the right member. - dis.dispatch({ - action: 'after_right_panel_phase_change', - phase: targetPhase, - ...(payload.refireParams || {}), - }); } static getSharedInstance(): RightPanelStore {