Skip to content
This repository was archived by the owner on Sep 11, 2024. It is now read-only.

Commit ed67aec

Browse files
author
Kerry
authored
Device Manager - add new labsed session manager screen (PSG-636) (#9119)
* add feature_new_device_manager labs flag * add generic settings tab container * settingstab section styles * add session manager tab to user settings * add sessions tab case to UserSettingDialog test * fussy import ordering * remove posthog tracking * i18n
1 parent d89a462 commit ed67aec

File tree

10 files changed

+174
-9
lines changed

10 files changed

+174
-9
lines changed

res/css/views/settings/tabs/_SettingsTab.pcss

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -97,3 +97,11 @@ limitations under the License.
9797
.mx_SettingsTab_toggleWithDescription {
9898
margin-top: $spacing-24;
9999
}
100+
101+
.mx_SettingsTab_sections {
102+
display: grid;
103+
grid-template-columns: 1fr;
104+
grid-gap: $spacing-32;
105+
106+
padding: 0 $spacing-16;
107+
}

src/components/views/dialogs/UserSettingsDialog.tsx

Lines changed: 24 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@ import BaseDialog from "./BaseDialog";
3535
import { IDialogProps } from "./IDialogProps";
3636
import SidebarUserSettingsTab from "../settings/tabs/user/SidebarUserSettingsTab";
3737
import KeyboardUserSettingsTab from "../settings/tabs/user/KeyboardUserSettingsTab";
38+
import SessionManagerTab from '../settings/tabs/user/SessionManagerTab';
3839
import { UserTab } from "./UserTab";
3940

4041
interface IProps extends IDialogProps {
@@ -43,32 +44,42 @@ interface IProps extends IDialogProps {
4344

4445
interface IState {
4546
mjolnirEnabled: boolean;
47+
newSessionManagerEnabled: boolean;
4648
}
4749

4850
export default class UserSettingsDialog extends React.Component<IProps, IState> {
49-
private mjolnirWatcher: string | undefined;
51+
private settingsWatchers: string[] = [];
5052

5153
constructor(props) {
5254
super(props);
5355

5456
this.state = {
5557
mjolnirEnabled: SettingsStore.getValue("feature_mjolnir"),
58+
newSessionManagerEnabled: SettingsStore.getValue("feature_new_device_manager"),
5659
};
5760
}
5861

5962
public componentDidMount(): void {
60-
this.mjolnirWatcher = SettingsStore.watchSetting("feature_mjolnir", null, this.mjolnirChanged);
63+
this.settingsWatchers = [
64+
SettingsStore.watchSetting("feature_mjolnir", null, this.mjolnirChanged),
65+
SettingsStore.watchSetting("feature_new_device_manager", null, this.sessionManagerChanged),
66+
];
6167
}
6268

6369
public componentWillUnmount(): void {
64-
this.mjolnirWatcher && SettingsStore.unwatchSetting(this.mjolnirWatcher);
70+
this.settingsWatchers.forEach(watcherRef => SettingsStore.unwatchSetting(watcherRef));
6571
}
6672

6773
private mjolnirChanged: CallbackFn = (settingName, roomId, atLevel, newValue) => {
6874
// We can cheat because we know what levels a feature is tracked at, and how it is tracked
6975
this.setState({ mjolnirEnabled: newValue });
7076
};
7177

78+
private sessionManagerChanged: CallbackFn = (settingName, roomId, atLevel, newValue) => {
79+
// We can cheat because we know what levels a feature is tracked at, and how it is tracked
80+
this.setState({ newSessionManagerEnabled: newValue });
81+
};
82+
7283
private getTabs() {
7384
const tabs: Tab[] = [];
7485

@@ -132,6 +143,16 @@ export default class UserSettingsDialog extends React.Component<IProps, IState>
132143
<SecurityUserSettingsTab closeSettingsFn={this.props.onFinished} />,
133144
"UserSettingsSecurityPrivacy",
134145
));
146+
if (this.state.newSessionManagerEnabled) {
147+
tabs.push(new Tab(
148+
UserTab.SessionManager,
149+
_td("Sessions"),
150+
"mx_UserSettingsDialog_securityIcon",
151+
<SessionManagerTab />,
152+
// don't track with posthog while under construction
153+
undefined,
154+
));
155+
}
135156
// Show the Labs tab if enabled or if there are any active betas
136157
if (SdkConfig.get("show_labs_settings")
137158
|| SettingsStore.getFeatureSettingNames().some(k => SettingsStore.getBetaInfo(k))

src/components/views/dialogs/UserTab.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,4 +26,5 @@ export enum UserTab {
2626
Labs = "USER_LABS_TAB",
2727
Mjolnir = "USER_MJOLNIR_TAB",
2828
Help = "USER_HELP_TAB",
29+
SessionManager = "USER_SESSION_MANAGER_TAB",
2930
}
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
/*
2+
Copyright 2022 The Matrix.org Foundation C.I.C.
3+
4+
Licensed under the Apache License, Version 2.0 (the "License");
5+
you may not use this file except in compliance with the License.
6+
You may obtain a copy of the License at
7+
8+
http://www.apache.org/licenses/LICENSE-2.0
9+
10+
Unless required by applicable law or agreed to in writing, software
11+
distributed under the License is distributed on an "AS IS" BASIS,
12+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
See the License for the specific language governing permissions and
14+
limitations under the License.
15+
*/
16+
import React from "react";
17+
18+
import Heading from "../../typography/Heading";
19+
20+
export interface SettingsTabProps {
21+
heading: string;
22+
children?: React.ReactNode;
23+
}
24+
25+
const SettingsTab: React.FC<SettingsTabProps> = ({ heading, children }) => (
26+
<div className="mx_SettingsTab">
27+
<Heading size='h2'>{ heading }</Heading>
28+
<div className="mx_SettingsTab_sections">
29+
{ children }
30+
</div>
31+
</div>
32+
);
33+
34+
export default SettingsTab;
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
/*
2+
Copyright 2022 The Matrix.org Foundation C.I.C.
3+
4+
Licensed under the Apache License, Version 2.0 (the "License");
5+
you may not use this file except in compliance with the License.
6+
You may obtain a copy of the License at
7+
8+
http://www.apache.org/licenses/LICENSE-2.0
9+
10+
Unless required by applicable law or agreed to in writing, software
11+
distributed under the License is distributed on an "AS IS" BASIS,
12+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
See the License for the specific language governing permissions and
14+
limitations under the License.
15+
*/
16+
17+
import React from 'react';
18+
19+
import { _t } from "../../../../../languageHandler";
20+
import SettingsTab from '../SettingsTab';
21+
22+
const SessionManagerTab: React.FC = () => {
23+
return <SettingsTab heading={_t('Sessions')} />;
24+
};
25+
26+
export default SessionManagerTab;

src/i18n/strings/en_EN.json

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -902,6 +902,7 @@
902902
"Right-click message context menu": "Right-click message context menu",
903903
"Live Location Sharing (temporary implementation: locations persist in room history)": "Live Location Sharing (temporary implementation: locations persist in room history)",
904904
"Favourite Messages (under active development)": "Favourite Messages (under active development)",
905+
"Use new session manager (under active development)": "Use new session manager (under active development)",
905906
"Font size": "Font size",
906907
"Use custom size": "Use custom size",
907908
"Enable Emoji suggestions while typing": "Enable Emoji suggestions while typing",
@@ -1561,6 +1562,7 @@
15611562
"Share anonymous data to help us identify issues. Nothing personal. No third parties.": "Share anonymous data to help us identify issues. Nothing personal. No third parties.",
15621563
"Where you're signed in": "Where you're signed in",
15631564
"Manage your signed-in devices below. A device's name is visible to people you communicate with.": "Manage your signed-in devices below. A device's name is visible to people you communicate with.",
1565+
"Sessions": "Sessions",
15641566
"Sidebar": "Sidebar",
15651567
"Spaces to show": "Spaces to show",
15661568
"Spaces are ways to group rooms and people. Alongside the spaces you're in, you can use some pre-built ones too.": "Spaces are ways to group rooms and people. Alongside the spaces you're in, you can use some pre-built ones too.",

src/settings/Settings.tsx

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -429,6 +429,13 @@ export const SETTINGS: {[setting: string]: ISetting} = {
429429
displayName: _td("Favourite Messages (under active development)"),
430430
default: false,
431431
},
432+
"feature_new_device_manager": {
433+
isFeature: true,
434+
labsGroup: LabGroup.Experimental,
435+
supportedLevels: LEVELS_FEATURE,
436+
displayName: _td("Use new session manager (under active development)"),
437+
default: false,
438+
},
432439
"baseFontSize": {
433440
displayName: _td("Font size"),
434441
supportedLevels: LEVELS_ACCOUNT_SETTINGS,

test/components/views/dialogs/UserSettingsDialog-test.tsx

Lines changed: 20 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -115,6 +115,12 @@ describe('<UserSettingsDialog />', () => {
115115
expect(getByTestId(`settings-tab-${UserTab.Voice}`)).toBeTruthy();
116116
});
117117

118+
it('renders session manager tab when enabled', () => {
119+
mockSettingsStore.getValue.mockImplementation((settingName) => settingName === "feature_new_device_manager");
120+
const { getByTestId } = render(getComponent());
121+
expect(getByTestId(`settings-tab-${UserTab.SessionManager}`)).toBeTruthy();
122+
});
123+
118124
it('renders labs tab when show_labs_settings is enabled in config', () => {
119125
mockSdkConfig.get.mockImplementation((configName) => configName === "show_labs_settings");
120126
const { getByTestId } = render(getComponent());
@@ -130,28 +136,36 @@ describe('<UserSettingsDialog />', () => {
130136
expect(getByTestId(`settings-tab-${UserTab.Labs}`)).toBeTruthy();
131137
});
132138

133-
it('watches feature_mjolnir setting', () => {
134-
let watchSettingCallback: CallbackFn = jest.fn();
139+
it('watches settings', () => {
140+
const watchSettingCallbacks: Record<string, CallbackFn> = {};
135141

136142
mockSettingsStore.watchSetting.mockImplementation((settingName, roomId, callback) => {
137-
watchSettingCallback = callback;
143+
watchSettingCallbacks[settingName] = callback;
138144
return `mock-watcher-id-${settingName}`;
139145
});
140146

141147
const { queryByTestId, unmount } = render(getComponent());
142148
expect(queryByTestId(`settings-tab-${UserTab.Mjolnir}`)).toBeFalsy();
143149

144150
expect(mockSettingsStore.watchSetting.mock.calls[0][0]).toEqual('feature_mjolnir');
151+
expect(mockSettingsStore.watchSetting.mock.calls[1][0]).toEqual('feature_new_device_manager');
145152

146153
// call the watch setting callback
147-
watchSettingCallback("feature_mjolnir", '', SettingLevel.ACCOUNT, true, true);
148-
154+
watchSettingCallbacks["feature_mjolnir"]("feature_mjolnir", '', SettingLevel.ACCOUNT, true, true);
149155
// tab is rendered now
150156
expect(queryByTestId(`settings-tab-${UserTab.Mjolnir}`)).toBeTruthy();
151157

158+
// call the watch setting callback
159+
watchSettingCallbacks["feature_new_device_manager"](
160+
"feature_new_device_manager", '', SettingLevel.ACCOUNT, true, true,
161+
);
162+
// tab is rendered now
163+
expect(queryByTestId(`settings-tab-${UserTab.SessionManager}`)).toBeTruthy();
164+
152165
unmount();
153166

154-
// unwatches mjolnir after unmount
167+
// unwatches settings on unmount
155168
expect(mockSettingsStore.unwatchSetting).toHaveBeenCalledWith('mock-watcher-id-feature_mjolnir');
169+
expect(mockSettingsStore.unwatchSetting).toHaveBeenCalledWith('mock-watcher-id-feature_new_device_manager');
156170
});
157171
});
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
/*
2+
Copyright 2022 The Matrix.org Foundation C.I.C.
3+
4+
Licensed under the Apache License, Version 2.0 (the "License");
5+
you may not use this file except in compliance with the License.
6+
You may obtain a copy of the License at
7+
8+
http://www.apache.org/licenses/LICENSE-2.0
9+
10+
Unless required by applicable law or agreed to in writing, software
11+
distributed under the License is distributed on an "AS IS" BASIS,
12+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
See the License for the specific language governing permissions and
14+
limitations under the License.
15+
*/
16+
17+
import React, { ReactElement } from 'react';
18+
import { render } from '@testing-library/react';
19+
20+
import SettingsTab, { SettingsTabProps } from '../../../../../src/components/views/settings/tabs/SettingsTab';
21+
22+
describe('<SettingsTab />', () => {
23+
const getComponent = (props: SettingsTabProps): ReactElement => (<SettingsTab {...props} />);
24+
it('renders tab', () => {
25+
const { container } = render(getComponent({ heading: 'Test Tab', children: <div>test</div> }));
26+
27+
expect(container).toMatchSnapshot();
28+
});
29+
});
30+
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
// Jest Snapshot v1, https://goo.gl/fbAQLP
2+
3+
exports[`<SettingsTab /> renders tab 1`] = `
4+
<div>
5+
<div
6+
class="mx_SettingsTab"
7+
>
8+
<h2
9+
class="mx_Heading_h2"
10+
>
11+
Test Tab
12+
</h2>
13+
<div
14+
class="mx_SettingsTab_sections"
15+
>
16+
<div>
17+
test
18+
</div>
19+
</div>
20+
</div>
21+
</div>
22+
`;

0 commit comments

Comments
 (0)