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
This commit is contained in:
parent
d89a46289d
commit
ed67aec334
10 changed files with 174 additions and 9 deletions
|
@ -97,3 +97,11 @@ limitations under the License.
|
|||
.mx_SettingsTab_toggleWithDescription {
|
||||
margin-top: $spacing-24;
|
||||
}
|
||||
|
||||
.mx_SettingsTab_sections {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr;
|
||||
grid-gap: $spacing-32;
|
||||
|
||||
padding: 0 $spacing-16;
|
||||
}
|
||||
|
|
|
@ -35,6 +35,7 @@ import BaseDialog from "./BaseDialog";
|
|||
import { IDialogProps } from "./IDialogProps";
|
||||
import SidebarUserSettingsTab from "../settings/tabs/user/SidebarUserSettingsTab";
|
||||
import KeyboardUserSettingsTab from "../settings/tabs/user/KeyboardUserSettingsTab";
|
||||
import SessionManagerTab from '../settings/tabs/user/SessionManagerTab';
|
||||
import { UserTab } from "./UserTab";
|
||||
|
||||
interface IProps extends IDialogProps {
|
||||
|
@ -43,25 +44,30 @@ interface IProps extends IDialogProps {
|
|||
|
||||
interface IState {
|
||||
mjolnirEnabled: boolean;
|
||||
newSessionManagerEnabled: boolean;
|
||||
}
|
||||
|
||||
export default class UserSettingsDialog extends React.Component<IProps, IState> {
|
||||
private mjolnirWatcher: string | undefined;
|
||||
private settingsWatchers: string[] = [];
|
||||
|
||||
constructor(props) {
|
||||
super(props);
|
||||
|
||||
this.state = {
|
||||
mjolnirEnabled: SettingsStore.getValue("feature_mjolnir"),
|
||||
newSessionManagerEnabled: SettingsStore.getValue("feature_new_device_manager"),
|
||||
};
|
||||
}
|
||||
|
||||
public componentDidMount(): void {
|
||||
this.mjolnirWatcher = SettingsStore.watchSetting("feature_mjolnir", null, this.mjolnirChanged);
|
||||
this.settingsWatchers = [
|
||||
SettingsStore.watchSetting("feature_mjolnir", null, this.mjolnirChanged),
|
||||
SettingsStore.watchSetting("feature_new_device_manager", null, this.sessionManagerChanged),
|
||||
];
|
||||
}
|
||||
|
||||
public componentWillUnmount(): void {
|
||||
this.mjolnirWatcher && SettingsStore.unwatchSetting(this.mjolnirWatcher);
|
||||
this.settingsWatchers.forEach(watcherRef => SettingsStore.unwatchSetting(watcherRef));
|
||||
}
|
||||
|
||||
private mjolnirChanged: CallbackFn = (settingName, roomId, atLevel, newValue) => {
|
||||
|
@ -69,6 +75,11 @@ export default class UserSettingsDialog extends React.Component<IProps, IState>
|
|||
this.setState({ mjolnirEnabled: newValue });
|
||||
};
|
||||
|
||||
private sessionManagerChanged: CallbackFn = (settingName, roomId, atLevel, newValue) => {
|
||||
// We can cheat because we know what levels a feature is tracked at, and how it is tracked
|
||||
this.setState({ newSessionManagerEnabled: newValue });
|
||||
};
|
||||
|
||||
private getTabs() {
|
||||
const tabs: Tab[] = [];
|
||||
|
||||
|
@ -132,6 +143,16 @@ export default class UserSettingsDialog extends React.Component<IProps, IState>
|
|||
<SecurityUserSettingsTab closeSettingsFn={this.props.onFinished} />,
|
||||
"UserSettingsSecurityPrivacy",
|
||||
));
|
||||
if (this.state.newSessionManagerEnabled) {
|
||||
tabs.push(new Tab(
|
||||
UserTab.SessionManager,
|
||||
_td("Sessions"),
|
||||
"mx_UserSettingsDialog_securityIcon",
|
||||
<SessionManagerTab />,
|
||||
// don't track with posthog while under construction
|
||||
undefined,
|
||||
));
|
||||
}
|
||||
// Show the Labs tab if enabled or if there are any active betas
|
||||
if (SdkConfig.get("show_labs_settings")
|
||||
|| SettingsStore.getFeatureSettingNames().some(k => SettingsStore.getBetaInfo(k))
|
||||
|
|
|
@ -26,4 +26,5 @@ export enum UserTab {
|
|||
Labs = "USER_LABS_TAB",
|
||||
Mjolnir = "USER_MJOLNIR_TAB",
|
||||
Help = "USER_HELP_TAB",
|
||||
SessionManager = "USER_SESSION_MANAGER_TAB",
|
||||
}
|
||||
|
|
34
src/components/views/settings/tabs/SettingsTab.tsx
Normal file
34
src/components/views/settings/tabs/SettingsTab.tsx
Normal file
|
@ -0,0 +1,34 @@
|
|||
/*
|
||||
Copyright 2022 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 React from "react";
|
||||
|
||||
import Heading from "../../typography/Heading";
|
||||
|
||||
export interface SettingsTabProps {
|
||||
heading: string;
|
||||
children?: React.ReactNode;
|
||||
}
|
||||
|
||||
const SettingsTab: React.FC<SettingsTabProps> = ({ heading, children }) => (
|
||||
<div className="mx_SettingsTab">
|
||||
<Heading size='h2'>{ heading }</Heading>
|
||||
<div className="mx_SettingsTab_sections">
|
||||
{ children }
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
export default SettingsTab;
|
|
@ -0,0 +1,26 @@
|
|||
/*
|
||||
Copyright 2022 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 React from 'react';
|
||||
|
||||
import { _t } from "../../../../../languageHandler";
|
||||
import SettingsTab from '../SettingsTab';
|
||||
|
||||
const SessionManagerTab: React.FC = () => {
|
||||
return <SettingsTab heading={_t('Sessions')} />;
|
||||
};
|
||||
|
||||
export default SessionManagerTab;
|
|
@ -902,6 +902,7 @@
|
|||
"Right-click message context menu": "Right-click message context menu",
|
||||
"Live Location Sharing (temporary implementation: locations persist in room history)": "Live Location Sharing (temporary implementation: locations persist in room history)",
|
||||
"Favourite Messages (under active development)": "Favourite Messages (under active development)",
|
||||
"Use new session manager (under active development)": "Use new session manager (under active development)",
|
||||
"Font size": "Font size",
|
||||
"Use custom size": "Use custom size",
|
||||
"Enable Emoji suggestions while typing": "Enable Emoji suggestions while typing",
|
||||
|
@ -1561,6 +1562,7 @@
|
|||
"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.",
|
||||
"Where you're signed in": "Where you're signed in",
|
||||
"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.",
|
||||
"Sessions": "Sessions",
|
||||
"Sidebar": "Sidebar",
|
||||
"Spaces to show": "Spaces to show",
|
||||
"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.",
|
||||
|
|
|
@ -429,6 +429,13 @@ export const SETTINGS: {[setting: string]: ISetting} = {
|
|||
displayName: _td("Favourite Messages (under active development)"),
|
||||
default: false,
|
||||
},
|
||||
"feature_new_device_manager": {
|
||||
isFeature: true,
|
||||
labsGroup: LabGroup.Experimental,
|
||||
supportedLevels: LEVELS_FEATURE,
|
||||
displayName: _td("Use new session manager (under active development)"),
|
||||
default: false,
|
||||
},
|
||||
"baseFontSize": {
|
||||
displayName: _td("Font size"),
|
||||
supportedLevels: LEVELS_ACCOUNT_SETTINGS,
|
||||
|
|
|
@ -115,6 +115,12 @@ describe('<UserSettingsDialog />', () => {
|
|||
expect(getByTestId(`settings-tab-${UserTab.Voice}`)).toBeTruthy();
|
||||
});
|
||||
|
||||
it('renders session manager tab when enabled', () => {
|
||||
mockSettingsStore.getValue.mockImplementation((settingName) => settingName === "feature_new_device_manager");
|
||||
const { getByTestId } = render(getComponent());
|
||||
expect(getByTestId(`settings-tab-${UserTab.SessionManager}`)).toBeTruthy();
|
||||
});
|
||||
|
||||
it('renders labs tab when show_labs_settings is enabled in config', () => {
|
||||
mockSdkConfig.get.mockImplementation((configName) => configName === "show_labs_settings");
|
||||
const { getByTestId } = render(getComponent());
|
||||
|
@ -130,11 +136,11 @@ describe('<UserSettingsDialog />', () => {
|
|||
expect(getByTestId(`settings-tab-${UserTab.Labs}`)).toBeTruthy();
|
||||
});
|
||||
|
||||
it('watches feature_mjolnir setting', () => {
|
||||
let watchSettingCallback: CallbackFn = jest.fn();
|
||||
it('watches settings', () => {
|
||||
const watchSettingCallbacks: Record<string, CallbackFn> = {};
|
||||
|
||||
mockSettingsStore.watchSetting.mockImplementation((settingName, roomId, callback) => {
|
||||
watchSettingCallback = callback;
|
||||
watchSettingCallbacks[settingName] = callback;
|
||||
return `mock-watcher-id-${settingName}`;
|
||||
});
|
||||
|
||||
|
@ -142,16 +148,24 @@ describe('<UserSettingsDialog />', () => {
|
|||
expect(queryByTestId(`settings-tab-${UserTab.Mjolnir}`)).toBeFalsy();
|
||||
|
||||
expect(mockSettingsStore.watchSetting.mock.calls[0][0]).toEqual('feature_mjolnir');
|
||||
expect(mockSettingsStore.watchSetting.mock.calls[1][0]).toEqual('feature_new_device_manager');
|
||||
|
||||
// call the watch setting callback
|
||||
watchSettingCallback("feature_mjolnir", '', SettingLevel.ACCOUNT, true, true);
|
||||
|
||||
watchSettingCallbacks["feature_mjolnir"]("feature_mjolnir", '', SettingLevel.ACCOUNT, true, true);
|
||||
// tab is rendered now
|
||||
expect(queryByTestId(`settings-tab-${UserTab.Mjolnir}`)).toBeTruthy();
|
||||
|
||||
// call the watch setting callback
|
||||
watchSettingCallbacks["feature_new_device_manager"](
|
||||
"feature_new_device_manager", '', SettingLevel.ACCOUNT, true, true,
|
||||
);
|
||||
// tab is rendered now
|
||||
expect(queryByTestId(`settings-tab-${UserTab.SessionManager}`)).toBeTruthy();
|
||||
|
||||
unmount();
|
||||
|
||||
// unwatches mjolnir after unmount
|
||||
// unwatches settings on unmount
|
||||
expect(mockSettingsStore.unwatchSetting).toHaveBeenCalledWith('mock-watcher-id-feature_mjolnir');
|
||||
expect(mockSettingsStore.unwatchSetting).toHaveBeenCalledWith('mock-watcher-id-feature_new_device_manager');
|
||||
});
|
||||
});
|
||||
|
|
30
test/components/views/settings/tabs/SettingsTab-test.tsx
Normal file
30
test/components/views/settings/tabs/SettingsTab-test.tsx
Normal file
|
@ -0,0 +1,30 @@
|
|||
/*
|
||||
Copyright 2022 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 React, { ReactElement } from 'react';
|
||||
import { render } from '@testing-library/react';
|
||||
|
||||
import SettingsTab, { SettingsTabProps } from '../../../../../src/components/views/settings/tabs/SettingsTab';
|
||||
|
||||
describe('<SettingsTab />', () => {
|
||||
const getComponent = (props: SettingsTabProps): ReactElement => (<SettingsTab {...props} />);
|
||||
it('renders tab', () => {
|
||||
const { container } = render(getComponent({ heading: 'Test Tab', children: <div>test</div> }));
|
||||
|
||||
expect(container).toMatchSnapshot();
|
||||
});
|
||||
});
|
||||
|
|
@ -0,0 +1,22 @@
|
|||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`<SettingsTab /> renders tab 1`] = `
|
||||
<div>
|
||||
<div
|
||||
class="mx_SettingsTab"
|
||||
>
|
||||
<h2
|
||||
class="mx_Heading_h2"
|
||||
>
|
||||
Test Tab
|
||||
</h2>
|
||||
<div
|
||||
class="mx_SettingsTab_sections"
|
||||
>
|
||||
<div>
|
||||
test
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
Loading…
Reference in a new issue