Device manager - expandable session details in device list (PSG-644) (#9188)

* add expandable device details to session list

* test device expansion in filtered list

* test expanded device id management from sessionmanager tab

* i18n

* update snapshot

* update snapshots

* use css instead of br
This commit is contained in:
Kerry 2022-08-17 11:58:34 +02:00 committed by GitHub
parent fecc03289d
commit e5fedfcd74
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
13 changed files with 155 additions and 12 deletions

View file

@ -49,7 +49,7 @@ const DeviceDetails: React.FC<Props> = ({ device }) => {
],
},
];
return <div className='mx_DeviceDetails'>
return <div className='mx_DeviceDetails' data-testid={`device-detail-${device.device_id}`}>
<section className='mx_DeviceDetails_section'>
<Heading size='h3'>{ device.display_name ?? device.device_id }</Heading>
<DeviceVerificationStatusCard device={device} />

View file

@ -18,6 +18,7 @@ import classNames from 'classnames';
import React from 'react';
import { Icon as CaretIcon } from '../../../../../res/img/feather-customised/dropdown-arrow.svg';
import { _t } from '../../../../languageHandler';
import AccessibleButton from '../../elements/AccessibleButton';
interface Props {
@ -28,6 +29,7 @@ interface Props {
const DeviceExpandDetailsButton: React.FC<Props> = ({ isExpanded, onClick, ...rest }) => {
return <AccessibleButton
{...rest}
aria-label={_t('Toggle device details')}
kind='icon'
className={classNames('mx_DeviceExpandDetailsButton', {
mx_DeviceExpandDetailsButton_expanded: isExpanded,

View file

@ -19,6 +19,8 @@ import React from 'react';
import { _t } from '../../../../languageHandler';
import AccessibleButton from '../../elements/AccessibleButton';
import Dropdown from '../../elements/Dropdown';
import DeviceDetails from './DeviceDetails';
import DeviceExpandDetailsButton from './DeviceExpandDetailsButton';
import DeviceSecurityCard from './DeviceSecurityCard';
import DeviceTile from './DeviceTile';
import {
@ -33,8 +35,10 @@ import {
interface Props {
devices: DevicesDictionary;
expandedDeviceIds: DeviceWithVerification['device_id'][];
filter?: DeviceSecurityVariation;
onFilterChange: (filter: DeviceSecurityVariation | undefined) => void;
onDeviceExpandToggle: (deviceId: DeviceWithVerification['device_id']) => void;
}
// devices without timestamp metadata should be sorted last
@ -123,11 +127,35 @@ const NoResults: React.FC<NoResultsProps> = ({ filter, clearFilter }) =>
}
</div>;
const DeviceListItem: React.FC<{
device: DeviceWithVerification;
isExpanded: boolean;
onDeviceExpandToggle: () => void;
}> = ({
device, isExpanded, onDeviceExpandToggle,
}) => <li className='mx_FilteredDeviceList_listItem'>
<DeviceTile
device={device}
>
<DeviceExpandDetailsButton
isExpanded={isExpanded}
onClick={onDeviceExpandToggle}
/>
</DeviceTile>
{ isExpanded && <DeviceDetails device={device} /> }
</li>;
/**
* Filtered list of devices
* Sorted by latest activity descending
*/
const FilteredDeviceList: React.FC<Props> = ({ devices, filter, onFilterChange }) => {
const FilteredDeviceList: React.FC<Props> = ({
devices,
filter,
expandedDeviceIds,
onFilterChange,
onDeviceExpandToggle,
}) => {
const sortedDevices = getFilteredSortedDevices(devices, filter);
const options = [
@ -177,13 +205,12 @@ const FilteredDeviceList: React.FC<Props> = ({ devices, filter, onFilterChange }
: <NoResults filter={filter} clearFilter={() => onFilterChange(undefined)} />
}
<ol className='mx_FilteredDeviceList_list'>
{ sortedDevices.map((device) =>
<li key={device.device_id}>
<DeviceTile
device={device}
/>
</li>,
{ sortedDevices.map((device) => <DeviceListItem
key={device.device_id}
device={device}
isExpanded={expandedDeviceIds.includes(device.device_id)}
onDeviceExpandToggle={() => onDeviceExpandToggle(device.device_id)}
/>,
) }
</ol>
</div>

View file

@ -22,12 +22,21 @@ import SettingsSubsection from '../../shared/SettingsSubsection';
import FilteredDeviceList from '../../devices/FilteredDeviceList';
import CurrentDeviceSection from '../../devices/CurrentDeviceSection';
import SecurityRecommendations from '../../devices/SecurityRecommendations';
import { DeviceSecurityVariation } from '../../devices/types';
import { DeviceSecurityVariation, DeviceWithVerification } from '../../devices/types';
import SettingsTab from '../SettingsTab';
const SessionManagerTab: React.FC = () => {
const { devices, currentDeviceId, isLoading } = useOwnDevices();
const [filter, setFilter] = useState<DeviceSecurityVariation>();
const [expandedDeviceIds, setExpandedDeviceIds] = useState([]);
const onDeviceExpandToggle = (deviceId: DeviceWithVerification['device_id']): void => {
if (expandedDeviceIds.includes(deviceId)) {
setExpandedDeviceIds(expandedDeviceIds.filter(id => id !== deviceId));
} else {
setExpandedDeviceIds([...expandedDeviceIds, deviceId]);
}
};
const { [currentDeviceId]: currentDevice, ...otherDevices } = devices;
const shouldShowOtherSessions = Object.keys(otherDevices).length > 0;
@ -51,7 +60,9 @@ const SessionManagerTab: React.FC = () => {
<FilteredDeviceList
devices={otherDevices}
filter={filter}
expandedDeviceIds={expandedDeviceIds}
onFilterChange={setFilter}
onDeviceExpandToggle={onDeviceExpandToggle}
/>
</SettingsSubsection>
}

View file

@ -1701,6 +1701,7 @@
"Device": "Device",
"IP address": "IP address",
"Session details": "Session details",
"Toggle device details": "Toggle device details",
"Inactive for %(inactiveAgeDays)s+ days": "Inactive for %(inactiveAgeDays)s+ days",
"Verified": "Verified",
"Unverified": "Unverified",