From 344185a3758eee109e9725581291bdaa78993a97 Mon Sep 17 00:00:00 2001 From: Swapnil Raj Date: Sat, 18 Jul 2020 16:34:48 +0530 Subject: [PATCH] Translate right panel stuff to ts Add actions for right panel --- .../{HeaderButton.js => HeaderButton.tsx} | 41 +++--- .../{HeaderButtons.js => HeaderButtons.tsx} | 52 +++++--- src/dispatcher/actions.ts | 15 +++ .../AfterRightPanelPhaseChangePayload.ts | 28 ++++ .../payloads/SetRightPanelPhasePayload.ts | 36 ++++++ .../payloads/ToggleRightPanelPayload.ts | 27 ++++ ...{RightPanelStore.js => RightPanelStore.ts} | 121 ++++++++++-------- ...torePhases.js => RightPanelStorePhases.ts} | 34 ++--- 8 files changed, 247 insertions(+), 107 deletions(-) rename src/components/views/right_panel/{HeaderButton.js => HeaderButton.tsx} (79%) rename src/components/views/right_panel/{HeaderButtons.js => HeaderButtons.tsx} (57%) create mode 100644 src/dispatcher/payloads/AfterRightPanelPhaseChangePayload.ts create mode 100644 src/dispatcher/payloads/SetRightPanelPhasePayload.ts create mode 100644 src/dispatcher/payloads/ToggleRightPanelPayload.ts rename src/stores/{RightPanelStore.js => RightPanelStore.ts} (59%) rename src/stores/{RightPanelStorePhases.js => RightPanelStorePhases.ts} (57%) diff --git a/src/components/views/right_panel/HeaderButton.js b/src/components/views/right_panel/HeaderButton.tsx similarity index 79% rename from src/components/views/right_panel/HeaderButton.js rename to src/components/views/right_panel/HeaderButton.tsx index 2cfc060bba..a64ed0a980 100644 --- a/src/components/views/right_panel/HeaderButton.js +++ b/src/components/views/right_panel/HeaderButton.tsx @@ -19,18 +19,33 @@ limitations under the License. */ import React from 'react'; -import PropTypes from 'prop-types'; import classNames from 'classnames'; import Analytics from '../../../Analytics'; import AccessibleTooltipButton from "../elements/AccessibleTooltipButton"; -export default class HeaderButton extends React.Component { - constructor() { - super(); +interface IProps { + // Whether this button is highlighted + isHighlighted: boolean; + // click handler + onClick: () => void; + // The badge to display above the icon + badge: React.ReactNode; + // The parameters to track the click event + analytics: string[]; + + // Button name + name: string; + // Button title + title: string; +}; + +export default class HeaderButton extends React.Component { + constructor(props: IProps) { + super(props); this.onClick = this.onClick.bind(this); } - onClick(ev) { + onClick(_ev: React.KeyboardEvent) { Analytics.trackEvent(...this.props.analytics); this.props.onClick(); } @@ -51,19 +66,3 @@ export default class HeaderButton extends React.Component { />; } } - -HeaderButton.propTypes = { - // Whether this button is highlighted - isHighlighted: PropTypes.bool.isRequired, - // click handler - onClick: PropTypes.func.isRequired, - // The badge to display above the icon - badge: PropTypes.node, - // The parameters to track the click event - analytics: PropTypes.arrayOf(PropTypes.string).isRequired, - - // Button name - name: PropTypes.string.isRequired, - // Button title - title: PropTypes.string.isRequired, -}; diff --git a/src/components/views/right_panel/HeaderButtons.js b/src/components/views/right_panel/HeaderButtons.tsx similarity index 57% rename from src/components/views/right_panel/HeaderButtons.js rename to src/components/views/right_panel/HeaderButtons.tsx index 1c66fe5828..099e785ada 100644 --- a/src/components/views/right_panel/HeaderButtons.js +++ b/src/components/views/right_panel/HeaderButtons.tsx @@ -21,42 +21,52 @@ limitations under the License. import React from 'react'; import dis from '../../../dispatcher/dispatcher'; import RightPanelStore from "../../../stores/RightPanelStore"; +import {RightPanelPhases} from "../../../stores/RightPanelStorePhases"; +import {Action} from '../../../dispatcher/actions'; -export const HEADER_KIND_ROOM = "room"; -export const HEADER_KIND_GROUP = "group"; +export enum HeaderKind { + Room = "room", + Group = "group", +} -const HEADER_KINDS = [HEADER_KIND_GROUP, HEADER_KIND_ROOM]; +interface IState { + headerKind: HeaderKind; + phase: RightPanelPhases; +} -export default class HeaderButtons extends React.Component { - constructor(props, kind) { +interface IProps {} + +export default class HeaderButtons extends React.Component { + private storeToken: ReturnType; + private dispatcherRef: string; + + constructor(props: IProps, kind: HeaderKind) { super(props); - if (!HEADER_KINDS.includes(kind)) throw new Error(`Invalid header kind: ${kind}`); - const rps = RightPanelStore.getSharedInstance(); this.state = { headerKind: kind, - phase: kind === HEADER_KIND_ROOM ? rps.visibleRoomPanelPhase : rps.visibleGroupPanelPhase, + phase: kind === HeaderKind.Room ? rps.visibleRoomPanelPhase : rps.visibleGroupPanelPhase, }; } componentDidMount() { - this._storeToken = RightPanelStore.getSharedInstance().addListener(this.onRightPanelUpdate.bind(this)); - this._dispatcherRef = dis.register(this.onAction.bind(this)); // used by subclasses + this.storeToken = RightPanelStore.getSharedInstance().addListener(this.onRightPanelUpdate.bind(this)); + this.dispatcherRef = dis.register(this.onAction.bind(this)); // used by subclasses } componentWillUnmount() { - if (this._storeToken) this._storeToken.remove(); - if (this._dispatcherRef) dis.unregister(this._dispatcherRef); + if (this.storeToken) this.storeToken.remove(); + if (this.dispatcherRef) dis.unregister(this.dispatcherRef); } onAction(payload) { // Ignore - intended to be overridden by subclasses } - setPhase(phase, extras) { + setPhase(phase: RightPanelPhases, extras) { dis.dispatch({ - action: 'set_right_panel_phase', + action: Action.SetRightPanelPhase, phase: phase, refireParams: extras, }); @@ -72,13 +82,23 @@ export default class HeaderButtons extends React.Component { onRightPanelUpdate() { const rps = RightPanelStore.getSharedInstance(); - if (this.state.headerKind === HEADER_KIND_ROOM) { + if (this.state.headerKind === HeaderKind.Room) { this.setState({phase: rps.visibleRoomPanelPhase}); - } else if (this.state.headerKind === HEADER_KIND_GROUP) { + } else if (this.state.headerKind === HeaderKind.Group) { this.setState({phase: rps.visibleGroupPanelPhase}); } } + // XXX: Make renderButtons a prop + renderButtons(): JSX.Element[] { + // Ignore - intended to be overridden by subclasses + // Return empty fragment to satisfy the type + return [ + + + ]; + } + render() { // inline style as this will be swapped around in future commits return
diff --git a/src/dispatcher/actions.ts b/src/dispatcher/actions.ts index 519a799e67..6fb71df30d 100644 --- a/src/dispatcher/actions.ts +++ b/src/dispatcher/actions.ts @@ -79,4 +79,19 @@ export enum Action { * Changes room based on room list order and payload parameters. Should be used with ViewRoomDeltaPayload. */ ViewRoomDelta = "view_room_delta", + + /** + * Sets the phase for the right panel. Should be used with SetRightPanelPhasePayload. + */ + SetRightPanelPhase = "set_right_panel_phase", + + /** + * Toggles the right panel. Should be used with ToggleRightPanelPayload. + */ + ToggleRightPanel = "toggle_right_panel", + + /** + * Trigged after the phase of the right panel is set. Should be used with AfterRightPanelPhaseChangePayload. + */ + AfterRightPanelPhaseChange = "after_right_panel_phase_change", } diff --git a/src/dispatcher/payloads/AfterRightPanelPhaseChangePayload.ts b/src/dispatcher/payloads/AfterRightPanelPhaseChangePayload.ts new file mode 100644 index 0000000000..3193f9043b --- /dev/null +++ b/src/dispatcher/payloads/AfterRightPanelPhaseChangePayload.ts @@ -0,0 +1,28 @@ +/* +Copyright 2020 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 { RightPanelPhases } from "../../stores/RightPanelStorePhases"; +import { SetRightPanelPhaseRefireParams } from "./SetRightPanelPhasePayload"; +import { ActionPayload } from "../payloads"; +import { Action } from "../actions"; + +interface AfterRightPanelPhaseChangeAction extends ActionPayload { + action: Action.AfterRightPanelPhaseChange; + phase: RightPanelPhases; +} + +export type AfterRightPanelPhaseChangePayload + = AfterRightPanelPhaseChangeAction & SetRightPanelPhaseRefireParams; diff --git a/src/dispatcher/payloads/SetRightPanelPhasePayload.ts b/src/dispatcher/payloads/SetRightPanelPhasePayload.ts new file mode 100644 index 0000000000..8d8eca762c --- /dev/null +++ b/src/dispatcher/payloads/SetRightPanelPhasePayload.ts @@ -0,0 +1,36 @@ +/* +Copyright 2020 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 {VerificationRequest} from "matrix-js-sdk/src/crypto/verification/request/VerificationRequest"; +import { RightPanelPhases } from "../../stores/RightPanelStorePhases"; +import { ActionPayload } from "../payloads"; +import { Action } from "../actions"; + +export interface SetRightPanelPhasePayload extends ActionPayload { + action: Action.SetRightPanelPhase; + + phase: RightPanelPhases; + refireParams?: SetRightPanelPhaseRefireParams; +} + +export interface SetRightPanelPhaseRefireParams { + // XXX: Fix after the types are defiend in matrix-js-sdk + // No appropriate types exist yet for the fields + members: any; + verificationRequest: typeof VerificationRequest; + groudId: string; + groupRoomId: string; +} diff --git a/src/dispatcher/payloads/ToggleRightPanelPayload.ts b/src/dispatcher/payloads/ToggleRightPanelPayload.ts new file mode 100644 index 0000000000..0635194890 --- /dev/null +++ b/src/dispatcher/payloads/ToggleRightPanelPayload.ts @@ -0,0 +1,27 @@ +/* +Copyright 2020 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 { ActionPayload } from "../payloads"; +import { Action } from "../actions"; + +export interface ToggleRightPanelPayload extends ActionPayload { + action: Action.ToggleRightPanel; + + /** + * The type of room that the panel is toggled in. + */ + type: "group" | "room"; +} diff --git a/src/stores/RightPanelStore.js b/src/stores/RightPanelStore.ts similarity index 59% rename from src/stores/RightPanelStore.js rename to src/stores/RightPanelStore.ts index a73f3befbb..6ed5d7fe9a 100644 --- a/src/stores/RightPanelStore.js +++ b/src/stores/RightPanelStore.ts @@ -15,31 +15,45 @@ limitations under the License. */ import dis from '../dispatcher/dispatcher'; +import {Action} from '../dispatcher/actions'; import {pendingVerificationRequestForUser} from '../verification'; import {Store} from 'flux/utils'; import SettingsStore, {SettingLevel} from "../settings/SettingsStore"; -import {RIGHT_PANEL_PHASES, RIGHT_PANEL_PHASES_NO_ARGS} from "./RightPanelStorePhases"; +import {RightPanelPhases, RIGHT_PANEL_PHASES_NO_ARGS} from "./RightPanelStorePhases"; -const INITIAL_STATE = { +interface RightPanelStoreState { // 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"), + showRoomPanel: boolean; + showGroupPanel: boolean; // The last phase (screen) the right panel was showing - lastRoomPhase: SettingsStore.getValue("lastRightPanelPhaseForRoom"), - lastGroupPhase: SettingsStore.getValue("lastRightPanelPhaseForGroup"), + lastRoomPhase: RightPanelPhases; + lastGroupPhase: RightPanelPhases; // Extra information about the last phase + lastRoomPhaseParams: {[key: string]: any}; +} + +const INITIAL_STATE: RightPanelStoreState = { + showRoomPanel: SettingsStore.getValue("showRightPanelInRoom"), + showGroupPanel: SettingsStore.getValue("showRightPanelInGroup"), + lastRoomPhase: SettingsStore.getValue("lastRightPanelPhaseForRoom"), + lastGroupPhase: SettingsStore.getValue("lastRightPanelPhaseForGroup"), lastRoomPhaseParams: {}, }; -const GROUP_PHASES = Object.keys(RIGHT_PANEL_PHASES).filter(k => k.startsWith("Group")); +const GROUP_PHASES = [ + RightPanelPhases.GroupMemberList, + RightPanelPhases.GroupRoomList, + RightPanelPhases.GroupRoomInfo, + RightPanelPhases.GroupMemberInfo, +]; const MEMBER_INFO_PHASES = [ - RIGHT_PANEL_PHASES.RoomMemberInfo, - RIGHT_PANEL_PHASES.Room3pidMemberInfo, - RIGHT_PANEL_PHASES.EncryptionPanel, + RightPanelPhases.RoomMemberInfo, + RightPanelPhases.Room3pidMemberInfo, + RightPanelPhases.EncryptionPanel, ]; /** @@ -47,132 +61,133 @@ const MEMBER_INFO_PHASES = [ * sessions. */ export default class RightPanelStore extends Store { - static _instance; + private static instance: RightPanelStore; + private state: RightPanelStoreState; constructor() { super(dis); // Initialise state - this._state = INITIAL_STATE; + this.state = INITIAL_STATE; } get isOpenForRoom(): boolean { - return this._state.showRoomPanel; + return this.state.showRoomPanel; } get isOpenForGroup(): boolean { - return this._state.showGroupPanel; + return this.state.showGroupPanel; } - get roomPanelPhase(): string { - return this._state.lastRoomPhase; + get roomPanelPhase(): RightPanelPhases { + return this.state.lastRoomPhase; } - get groupPanelPhase(): string { - return this._state.lastGroupPhase; + get groupPanelPhase(): RightPanelPhases { + return this.state.lastGroupPhase; } - get visibleRoomPanelPhase(): string { + get visibleRoomPanelPhase(): RightPanelPhases { return this.isOpenForRoom ? this.roomPanelPhase : null; } - get visibleGroupPanelPhase(): string { + get visibleGroupPanelPhase(): RightPanelPhases { return this.isOpenForGroup ? this.groupPanelPhase : null; } get roomPanelPhaseParams(): any { - return this._state.lastRoomPhaseParams || {}; + return this.state.lastRoomPhaseParams || {}; } - _setState(newState) { - this._state = Object.assign(this._state, newState); + private setState(newState: Partial) { + this.state = Object.assign(this.state, newState); SettingsStore.setValue( "showRightPanelInRoom", null, SettingLevel.DEVICE, - this._state.showRoomPanel, + this.state.showRoomPanel, ); SettingsStore.setValue( "showRightPanelInGroup", null, SettingLevel.DEVICE, - this._state.showGroupPanel, + this.state.showGroupPanel, ); - if (RIGHT_PANEL_PHASES_NO_ARGS.includes(this._state.lastRoomPhase)) { + if (RIGHT_PANEL_PHASES_NO_ARGS.includes(this.state.lastRoomPhase)) { SettingsStore.setValue( "lastRightPanelPhaseForRoom", null, SettingLevel.DEVICE, - this._state.lastRoomPhase, + this.state.lastRoomPhase, ); } - if (RIGHT_PANEL_PHASES_NO_ARGS.includes(this._state.lastGroupPhase)) { + if (RIGHT_PANEL_PHASES_NO_ARGS.includes(this.state.lastGroupPhase)) { SettingsStore.setValue( "lastRightPanelPhaseForGroup", null, SettingLevel.DEVICE, - this._state.lastGroupPhase, + this.state.lastGroupPhase, ); } this.__emitChange(); } - __onDispatch(payload) { + __onDispatch(payload: ActionPayload) { 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: {}}); + if (MEMBER_INFO_PHASES.includes(this.state.lastRoomPhase)) { + this.setState({lastRoomPhase: RightPanelPhases.RoomMemberList, lastRoomPhaseParams: {}}); } // Do the same for groups - if (this._state.lastGroupPhase === RIGHT_PANEL_PHASES.GroupMemberInfo) { - this._setState({lastGroupPhase: RIGHT_PANEL_PHASES.GroupMemberList}); + if (this.state.lastGroupPhase === RightPanelPhases.GroupMemberInfo) { + this.setState({lastGroupPhase: RightPanelPhases.GroupMemberList}); } break; - case 'set_right_panel_phase': { + case Action.SetRightPanelPhase: { let targetPhase = payload.phase; let refireParams = payload.refireParams; // redirect to EncryptionPanel if there is an ongoing verification request - if (targetPhase === RIGHT_PANEL_PHASES.RoomMemberInfo && payload.refireParams) { + if (targetPhase === RightPanelPhases.RoomMemberInfo && payload.refireParams) { const {member} = payload.refireParams; const pendingRequest = pendingVerificationRequestForUser(member); if (pendingRequest) { - targetPhase = RIGHT_PANEL_PHASES.EncryptionPanel; + targetPhase = RightPanelPhases.EncryptionPanel; refireParams = { verificationRequest: pendingRequest, member, }; } } - if (!RIGHT_PANEL_PHASES[targetPhase]) { + if (!RightPanelPhases[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, + if (targetPhase === this.state.lastGroupPhase) { + this.setState({ + showGroupPanel: !this.state.showGroupPanel, }); } else { - this._setState({ + this.setState({ lastGroupPhase: targetPhase, showGroupPanel: true, }); } } else { - if (targetPhase === this._state.lastRoomPhase && !refireParams) { - this._setState({ - showRoomPanel: !this._state.showRoomPanel, + if (targetPhase === this.state.lastRoomPhase && !refireParams) { + this.setState({ + showRoomPanel: !this.state.showRoomPanel, }); } else { - this._setState({ + this.setState({ lastRoomPhase: targetPhase, showRoomPanel: true, lastRoomPhaseParams: refireParams || {}, @@ -182,27 +197,27 @@ export default class RightPanelStore extends Store { // Let things like the member info panel actually open to the right member. dis.dispatch({ - action: 'after_right_panel_phase_change', + action: Action.AfterRightPanelPhaseChange, phase: targetPhase, ...(refireParams || {}), }); break; } - case 'toggle_right_panel': + case Action.ToggleRightPanel: if (payload.type === "room") { - this._setState({ showRoomPanel: !this._state.showRoomPanel }); + this.setState({ showRoomPanel: !this.state.showRoomPanel }); } else { // group - this._setState({ showGroupPanel: !this._state.showGroupPanel }); + this.setState({ showGroupPanel: !this.state.showGroupPanel }); } break; } } static getSharedInstance(): RightPanelStore { - if (!RightPanelStore._instance) { - RightPanelStore._instance = new RightPanelStore(); + if (!RightPanelStore.instance) { + RightPanelStore.instance = new RightPanelStore(); } - return RightPanelStore._instance; + return RightPanelStore.instance; } } diff --git a/src/stores/RightPanelStorePhases.js b/src/stores/RightPanelStorePhases.ts similarity index 57% rename from src/stores/RightPanelStorePhases.js rename to src/stores/RightPanelStorePhases.ts index d9af320233..4d05738425 100644 --- a/src/stores/RightPanelStorePhases.js +++ b/src/stores/RightPanelStorePhases.ts @@ -15,28 +15,28 @@ limitations under the License. */ // These are in their own file because of circular imports being a problem. -export const RIGHT_PANEL_PHASES = Object.freeze({ +export enum RightPanelPhases { // Room stuff - RoomMemberList: 'RoomMemberList', - FilePanel: 'FilePanel', - NotificationPanel: 'NotificationPanel', - RoomMemberInfo: 'RoomMemberInfo', - EncryptionPanel: 'EncryptionPanel', + RoomMemberList = 'RoomMemberList', + FilePanel = 'FilePanel', + NotificationPanel = 'NotificationPanel', + RoomMemberInfo = 'RoomMemberInfo', + EncryptionPanel = 'EncryptionPanel', - Room3pidMemberInfo: 'Room3pidMemberInfo', + Room3pidMemberInfo = 'Room3pidMemberInfo', // Group stuff - GroupMemberList: 'GroupMemberList', - GroupRoomList: 'GroupRoomList', - GroupRoomInfo: 'GroupRoomInfo', - GroupMemberInfo: 'GroupMemberInfo', -}); + GroupMemberList = 'GroupMemberList', + GroupRoomList = 'GroupRoomList', + GroupRoomInfo = 'GroupRoomInfo', + GroupMemberInfo = 'GroupMemberInfo', +}; // These are the phases that are safe to persist (the ones that don't require additional // arguments). export const RIGHT_PANEL_PHASES_NO_ARGS = [ - RIGHT_PANEL_PHASES.NotificationPanel, - RIGHT_PANEL_PHASES.FilePanel, - RIGHT_PANEL_PHASES.RoomMemberList, - RIGHT_PANEL_PHASES.GroupMemberList, - RIGHT_PANEL_PHASES.GroupRoomList, + RightPanelPhases.NotificationPanel, + RightPanelPhases.FilePanel, + RightPanelPhases.RoomMemberList, + RightPanelPhases.GroupMemberList, + RightPanelPhases.GroupRoomList, ];