Translate right panel stuff to ts

Add actions for right panel
This commit is contained in:
Swapnil Raj 2020-07-18 16:34:48 +05:30
parent 6bb9be56cd
commit 344185a375
8 changed files with 247 additions and 107 deletions

View file

@ -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<IProps> {
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,
};

View file

@ -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<IProps, IState> {
private storeToken: ReturnType<RightPanelStore["addListener"]>;
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 [
<React.Fragment>
</React.Fragment>
];
}
render() {
// inline style as this will be swapped around in future commits
return <div className="mx_HeaderButtons" role="tablist">

View file

@ -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",
}

View file

@ -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;

View file

@ -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;
}

View file

@ -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";
}

View file

@ -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<RightPanelStoreState>) {
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;
}
}

View file

@ -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,
];