From 5253f2992898fe3359657b768f8c318f598ecc9a Mon Sep 17 00:00:00 2001 From: Travis Ralston Date: Thu, 5 Dec 2019 15:31:01 -0700 Subject: [PATCH] Build out a store for the right panel state machine This should make it easier to funnel the expected behaviour through a central block of code. --- src/components/structures/RightPanel.js | 17 +-- src/settings/Settings.js | 19 +++ .../handlers/DeviceSettingsHandler.js | 26 ++++ src/stores/RightPanelStore.js | 126 ++++++++++++++++++ 4 files changed, 175 insertions(+), 13 deletions(-) create mode 100644 src/stores/RightPanelStore.js diff --git a/src/components/structures/RightPanel.js b/src/components/structures/RightPanel.js index 895f6ae57e..fb0924848f 100644 --- a/src/components/structures/RightPanel.js +++ b/src/components/structures/RightPanel.js @@ -1,9 +1,9 @@ /* Copyright 2015, 2016 OpenMarket Ltd Copyright 2017 Vector Creations Ltd -Copyright 2017 New Vector Ltd -Copyright 2018 New Vector Ltd +Copyright 2017, 2018 New Vector Ltd Copyright 2019 Michael Telatynski <7t3chguy@gmail.com> +Copyright 2019 The Matrix.org Foundation C.I.C. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. @@ -28,6 +28,7 @@ import RateLimitedFunc from '../../ratelimitedfunc'; import { showGroupInviteDialog, showGroupAddRoomDialog } from '../../GroupAddressPicker'; import GroupStore from '../../stores/GroupStore'; import SettingsStore from "../../settings/SettingsStore"; +import {RIGHT_PANEL_PHASES} from "../../stores/RightPanelStore"; export default class RightPanel extends React.Component { static get propTypes() { @@ -44,17 +45,7 @@ export default class RightPanel extends React.Component { }; } - static Phase = Object.freeze({ - RoomMemberList: 'RoomMemberList', - GroupMemberList: 'GroupMemberList', - GroupRoomList: 'GroupRoomList', - GroupRoomInfo: 'GroupRoomInfo', - FilePanel: 'FilePanel', - NotificationPanel: 'NotificationPanel', - RoomMemberInfo: 'RoomMemberInfo', - Room3pidMemberInfo: 'Room3pidMemberInfo', - GroupMemberInfo: 'GroupMemberInfo', - }); + static Phase = RIGHT_PANEL_PHASES; constructor(props, context) { super(props, context); diff --git a/src/settings/Settings.js b/src/settings/Settings.js index b02ab82400..53a95c9c6d 100644 --- a/src/settings/Settings.js +++ b/src/settings/Settings.js @@ -1,6 +1,7 @@ /* Copyright 2017 Travis Ralston Copyright 2018, 2019 New Vector Ltd. +Copyright 2019 The Matrix.org Foundation C.I.C. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. @@ -24,6 +25,8 @@ import { import CustomStatusController from "./controllers/CustomStatusController"; import ThemeController from './controllers/ThemeController'; import ReloadOnChangeController from "./controllers/ReloadOnChangeController"; +import RightPanel from "../components/structures/RightPanel"; +import {RIGHT_PANEL_PHASES} from "../stores/RightPanelStore"; // These are just a bunch of helper arrays to avoid copy/pasting a bunch of times const LEVELS_ROOM_SETTINGS = ['device', 'room-device', 'room-account', 'account', 'config']; @@ -463,4 +466,20 @@ export const SETTINGS = { displayName: _td("Show previews/thumbnails for images"), default: true, }, + "showRightPanelInRoom": { + supportedLevels: LEVELS_DEVICE_ONLY_SETTINGS, + default: false, + }, + "showRightPanelInGroup": { + supportedLevels: LEVELS_DEVICE_ONLY_SETTINGS, + default: false, + }, + "lastRightPanelPhaseForRoom": { + supportedLevels: LEVELS_DEVICE_ONLY_SETTINGS, + default: RIGHT_PANEL_PHASES.RoomMemberInfo, + }, + "lastRightPanelPhaseForGroup": { + supportedLevels: LEVELS_DEVICE_ONLY_SETTINGS, + default: RIGHT_PANEL_PHASES.GroupMemberList, + }, }; diff --git a/src/settings/handlers/DeviceSettingsHandler.js b/src/settings/handlers/DeviceSettingsHandler.js index 76c518b97b..ed61e9f3be 100644 --- a/src/settings/handlers/DeviceSettingsHandler.js +++ b/src/settings/handlers/DeviceSettingsHandler.js @@ -1,6 +1,7 @@ /* Copyright 2017 Travis Ralston Copyright 2019 New Vector Ltd. +Copyright 2019 The Matrix.org Foundation C.I.C. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. @@ -56,6 +57,17 @@ export default class DeviceSettingsHandler extends SettingsHandler { return null; // wrong type or otherwise not set } + // Special case the right panel - see `setValue` for rationale. + if ([ + "showRightPanelInRoom", + "showRightPanelInGroup", + "lastRightPanelPhaseForRoom", + "lastRightPanelPhaseForGroup", + ].includes(settingName)) { + const val = JSON.parse(localStorage.getItem(`mx_${settingName}`) || "{}"); + return val['value']; + } + const settings = this._getSettings() || {}; return settings[settingName]; } @@ -81,6 +93,20 @@ export default class DeviceSettingsHandler extends SettingsHandler { return Promise.resolve(); } + // Special case the right panel because we want to be able to update these all + // concurrently without stomping on one another. We could use async/await, though + // that introduces just enough latency to be annoying. + if ([ + "showRightPanelInRoom", + "showRightPanelInGroup", + "lastRightPanelPhaseForRoom", + "lastRightPanelPhaseForGroup", + ].includes(settingName)) { + localStorage.setItem(`mx_${settingName}`, JSON.stringify({value: newValue})); + this._watchers.notifyUpdate(settingName, null, SettingLevel.DEVICE, newValue); + return Promise.resolve(); + } + const settings = this._getSettings() || {}; settings[settingName] = newValue; localStorage.setItem("mx_local_settings", JSON.stringify(settings)); diff --git a/src/stores/RightPanelStore.js b/src/stores/RightPanelStore.js new file mode 100644 index 0000000000..fe4be81fd6 --- /dev/null +++ b/src/stores/RightPanelStore.js @@ -0,0 +1,126 @@ +/* +Copyright 2019 The Matrix.org Foundation C.I.C. + +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 dis from '../dispatcher'; +import {Store} from 'flux/utils'; +import SettingsStore, {SettingLevel} from "../settings/SettingsStore"; + +const INITIAL_STATE = { + // Whether or not to show the right panel at all. We split out rooms and groups + // because they're different flows for the user to follow. + showRoomPanel: SettingsStore.getValue("showRightPanelInRoom"), + showGroupPanel: SettingsStore.getValue("showRightPanelInGroup"), + + // The last phase (screen) the right panel was showing + lastRoomPhase: SettingsStore.getValue("lastRightPanelPhaseForRoom"), + lastGroupPhase: SettingsStore.getValue("lastRightPanelPhaseForGroup"), +}; + +export const RIGHT_PANEL_PHASES = Object.freeze({ + // Room stuff + RoomMemberList: 'RoomMemberList', + FilePanel: 'FilePanel', + NotificationPanel: 'NotificationPanel', + RoomMemberInfo: 'RoomMemberInfo', + Room3pidMemberInfo: 'Room3pidMemberInfo', + + // Group stuff + GroupMemberList: 'GroupMemberList', + GroupRoomList: 'GroupRoomList', + GroupRoomInfo: 'GroupRoomInfo', + GroupMemberInfo: 'GroupMemberInfo', +}); + +const GROUP_PHASES = Object.keys(RIGHT_PANEL_PHASES).filter(k => k.startsWith("Group")); + +/** + * A class for tracking the state of the right panel between layouts and + * sessions. + */ +export default class RightPanelStore extends Store { + static _instance; + + constructor() { + super(dis); + + // Initialise state + this._state = INITIAL_STATE; + } + + get isOpenForRoom(): boolean { + return this._state.showRoomPanel; + } + + get isOpenForGroup(): boolean { + return this._state.showGroupPanel; + } + + get roomPanelPhase(): string { + return this._state.lastRoomPhase; + } + + get groupPanelPhase(): string { + return this._state.lastGroupPhase; + } + + _setState(newState) { + this._state = Object.assign(this._state, newState); + SettingsStore.setValue("showRightPanelInRoom", null, SettingLevel.DEVICE, this._state.showRoomPanel); + SettingsStore.setValue("showRightPanelInGroup", null, SettingLevel.DEVICE, this._state.showGroupPanel); + SettingsStore.setValue("lastRightPanelPhaseForRoom", null, SettingLevel.DEVICE, this._state.lastRoomPhase); + SettingsStore.setValue("lastRightPanelPhaseForGroup", null, SettingLevel.DEVICE, this._state.lastGroupPhase); + this.__emitChange(); + } + + __onDispatch(payload) { + 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, + }); + } + } else { + if (targetPhase === this._state.lastRoomPhase) { + this._setState({ + showRoomPanel: !this._state.showRoomPanel, + }); + } else { + this._setState({ + lastRoomPhase: targetPhase, + }); + } + } + } + + static getSharedInstance(): RightPanelStore { + if (!RightPanelStore._instance) { + RightPanelStore._instance = new RightPanelStore(); + } + return RightPanelStore._instance; + } +}