You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
session-desktop/ts/components/dialog/OnionStatusPathDialog.tsx

271 lines
7.7 KiB
TypeScript

import { ipcRenderer, shell } from 'electron';
import { useState } from 'react';
import { useDispatch } from 'react-redux';
import useHover from 'react-use/lib/useHover';
import styled from 'styled-components';
import { isEmpty, isTypedArray } from 'lodash';
import { CityResponse, Reader } from 'maxmind';
import useMount from 'react-use/lib/useMount';
import { onionPathModal } from '../../state/ducks/modalDialog';
import {
useFirstOnionPath,
useFirstOnionPathLength,
useIsOnline,
useOnionPathsCount,
} from '../../state/selectors/onions';
import { Flex } from '../basic/Flex';
import { Snode } from '../../data/types';
import { THEME_GLOBALS } from '../../themes/globals';
import { SessionWrapperModal } from '../SessionWrapperModal';
import { SessionIcon, SessionIconButton } from '../icon';
import { SessionSpinner } from '../loading';
import { getLocale } from '../../util/i18n/shared';
export type StatusLightType = {
glowStartDelay: number;
glowDuration: number;
color?: string;
dataTestId?: string;
};
const StyledCountry = styled.div`
margin: var(--margins-sm);
min-width: 150px;
`;
const StyledOnionNodeList = styled.div`
display: flex;
flex-direction: column;
margin: var(--margins-sm);
align-items: center;
min-width: 10vw;
position: relative;
`;
const StyledOnionDescription = styled.p`
min-width: 400px;
width: 0;
line-height: 1.3333;
`;
const StyledVerticalLine = styled.div`
background: var(--border-color);
position: absolute;
height: calc(100% - 2 * 15px);
margin: 15px calc(100% / 2 - 1px);
width: 1px;
`;
const StyledLightsContainer = styled.div`
position: relative;
`;
const StyledGrowingIcon = styled.div`
flex-grow: 1;
display: flex;
align-items: center;
`;
const OnionCountryDisplay = ({ labelText, snodeIp }: { snodeIp?: string; labelText: string }) => {
const element = (hovered: boolean) => (
<StyledCountry>{hovered && snodeIp ? snodeIp : labelText}</StyledCountry>
);
const [hoverable] = useHover(element);
return hoverable;
};
let reader: Reader<CityResponse> | null;
const OnionPathModalInner = () => {
const onionPath = useFirstOnionPath();
// eslint-disable-next-line @typescript-eslint/no-unused-vars
const [_dataLoaded, setDataLoaded] = useState(false);
const isOnline = useIsOnline();
const glowDuration = onionPath.length + 2;
useMount(() => {
ipcRenderer.once('load-maxmind-data-complete', (_event, content) => {
const asArrayBuffer = content as Uint8Array;
if (asArrayBuffer && isTypedArray(asArrayBuffer) && !isEmpty(asArrayBuffer)) {
reader = new Reader<CityResponse>(Buffer.from(asArrayBuffer.buffer));
setDataLoaded(true); // retrigger a rerender
}
});
ipcRenderer.send('load-maxmind-data');
});
if (!isOnline || !onionPath || onionPath.length === 0) {
4 years ago
return <SessionSpinner loading={true} />;
}
const nodes = [
{
label: window.i18n('you'),
},
...onionPath,
{
label: window.i18n('onionRoutingPathDestination'),
},
];
return (
<>
<StyledOnionDescription>{window.i18n('onionRoutingPathDescription')}</StyledOnionDescription>
<StyledOnionNodeList>
<Flex container={true}>
<StyledLightsContainer>
<StyledVerticalLine />
<Flex container={true} flexDirection="column" alignItems="center" height="100%">
{nodes.map((_snode: Snode | any, index: number) => {
return (
<OnionNodeStatusLight
glowDuration={glowDuration}
glowStartDelay={index}
key={`light-${index}`}
/>
);
})}
</Flex>
</StyledLightsContainer>
<Flex container={true} flexDirection="column" alignItems="flex-start">
{nodes.map((snode: Snode | any) => {
const country = reader?.get(snode.ip || '0.0.0.0')?.country;
const locale = getLocale();
// typescript complains that the [] operator cannot be used with the 'string' coming from getLocale()
const countryNamesAsAny = country?.names as any;
const countryName =
snode.label || // to take care of the "Device" case
countryNamesAsAny?.[locale] || // try to find the country name based on the user local first
// eslint-disable-next-line dot-notation
countryNamesAsAny?.['en'] || // if not found, fallback to the country in english
Merge branch 'unstable' into standardised_strings_merge # Conflicts: # .gitignore # _locales/en/messages.json # ts/components/DebugLogView.tsx # ts/components/SessionWrapperModal.tsx # ts/components/basic/SessionHTMLRenderer.tsx # ts/components/basic/SessionRadio.tsx # ts/components/buttons/MenuButton.tsx # ts/components/conversation/SessionConversation.tsx # ts/components/conversation/SubtleNotification.tsx # ts/components/conversation/TimerNotification.tsx # ts/components/conversation/composition/CompositionBox.tsx # ts/components/conversation/message/message-content/MessageText.tsx # ts/components/conversation/message/message-item/InteractionNotification.tsx # ts/components/conversation/message/reactions/ReactionPopup.tsx # ts/components/conversation/right-panel/overlay/OverlayRightPanelSettings.tsx # ts/components/conversation/right-panel/overlay/disappearing-messages/DisappearingModes.tsx # ts/components/conversation/right-panel/overlay/disappearing-messages/OverlayDisappearingMessages.tsx # ts/components/dialog/BanOrUnbanUserDialog.tsx # ts/components/dialog/DeleteAccountModal.tsx # ts/components/dialog/EditProfileDialog.tsx # ts/components/dialog/ModeratorsAddDialog.tsx # ts/components/dialog/OnionStatusPathDialog.tsx # ts/components/dialog/ReactListModal.tsx # ts/components/dialog/SessionSeedModal.tsx # ts/components/dialog/SessionSetPasswordDialog.tsx # ts/components/dialog/UserDetailsDialog.tsx # ts/components/leftpane/LeftPaneSectionHeader.tsx # ts/components/leftpane/LeftPaneSettingSection.tsx # ts/components/leftpane/conversation-list-item/InteractionItem.tsx # ts/components/leftpane/overlay/OverlayClosedGroup.tsx # ts/components/leftpane/overlay/OverlayCommunity.tsx # ts/components/leftpane/overlay/OverlayMessage.tsx # ts/components/leftpane/overlay/SessionJoinableDefaultRooms.tsx # ts/components/leftpane/overlay/choose-action/ContactsListWithBreaks.tsx # ts/components/leftpane/overlay/choose-action/OverlayChooseAction.tsx # ts/components/menu/Menu.tsx # ts/components/registration/RegistrationStages.tsx # ts/components/registration/RegistrationUserDetails.tsx # ts/components/registration/SignInTab.tsx # ts/components/registration/SignUpTab.tsx # ts/components/settings/SessionSettings.tsx # ts/components/settings/SessionSettingsHeader.tsx # ts/components/settings/ZoomingSessionSlider.tsx # ts/components/settings/section/CategoryAppearance.tsx # ts/components/settings/section/CategoryHelp.tsx # ts/components/settings/section/CategoryPermissions.tsx # ts/components/settings/section/CategoryPrivacy.tsx # ts/hooks/useParamSelector.ts # ts/mains/main_renderer.tsx # ts/models/message.ts # ts/node/menu.ts # ts/node/tray_icon.ts # ts/session/constants.ts # ts/session/disappearing_messages/timerOptions.ts # ts/session/utils/Toast.tsx # ts/state/selectors/search.ts # ts/test/session/unit/selectors/conversations_test.ts # ts/types/LocalizerKeys.ts # ts/types/Util.ts # ts/window.d.ts # yarn.lock
9 months ago
window.i18n('onionRoutingPathUnknownCountry');
return (
<OnionCountryDisplay
labelText={countryName}
snodeIp={snode.ip}
key={`country-${snode.ip}`}
/>
);
})}
</Flex>
</Flex>
</StyledOnionNodeList>
</>
);
};
export type OnionNodeStatusLightType = {
glowStartDelay: number;
glowDuration: number;
dataTestId?: string;
};
/**
* Component containing a coloured status light.
*/
export const OnionNodeStatusLight = (props: OnionNodeStatusLightType): JSX.Element => {
const { glowStartDelay, glowDuration, dataTestId } = props;
return (
<ModalStatusLight
glowDuration={glowDuration}
glowStartDelay={glowStartDelay}
color={'var(--button-path-default-color)'}
dataTestId={dataTestId}
/>
);
};
/**
* An icon with a pulsating glow emission.
*/
export const ModalStatusLight = (props: StatusLightType) => {
const { glowStartDelay, glowDuration, color } = props;
return (
<StyledGrowingIcon>
<SessionIcon
borderRadius={'50px'}
iconColor={color}
glowDuration={glowDuration}
glowStartDelay={glowStartDelay}
iconType="circle"
iconSize={'tiny'}
/>
</StyledGrowingIcon>
);
};
/**
* A status light specifically for the action panel. Color is based on aggregate node states instead of individual onion node state
*/
export const ActionPanelOnionStatusLight = (props: {
isSelected: boolean;
handleClick: () => void;
id: string;
}) => {
const { isSelected, handleClick, id } = props;
const onionPathsCount = useOnionPathsCount();
const firstPathLength = useFirstOnionPathLength();
const isOnline = useIsOnline();
const glowDuration = Number(THEME_GLOBALS['--duration-onion-status-glow']); // 10 seconds
// Set icon color based on result
const errorColor = 'var(--button-path-error-color)';
const defaultColor = 'var(--button-path-default-color)';
const connectingColor = 'var(--button-path-connecting-color)';
// start with red
let iconColor = errorColor;
// if we are not online or the first path is not valid, we keep red as color
if (isOnline && firstPathLength > 1) {
iconColor =
onionPathsCount >= 2 ? defaultColor : onionPathsCount >= 1 ? connectingColor : errorColor;
}
return (
<SessionIconButton
iconSize={'small'}
iconType="circle"
iconColor={iconColor}
onClick={handleClick}
glowDuration={glowDuration}
glowStartDelay={0}
noScale={true}
isSelected={isSelected}
dataTestId={'path-light-container'}
dataTestIdIcon={'path-light-svg'}
id={id}
/>
);
};
export const OnionPathModal = () => {
const onConfirm = () => {
void shell.openExternal('https://getsession.org/faq/#onion-routing');
};
const dispatch = useDispatch();
return (
<SessionWrapperModal
title={window.i18n('onionRoutingPath')}
confirmText={window.i18n('learnMore')}
cancelText={window.i18n('cancel')}
onConfirm={onConfirm}
onClose={() => dispatch(onionPathModal(null))}
showExitIcon={true}
>
<OnionPathModalInner />
</SessionWrapperModal>
);
};