Small UI fixes for left pane dialogs
This commit is contained in:
parent
6c906d5da8
commit
f3715411c6
|
@ -2337,6 +2337,9 @@
|
|||
"autoUpdateLaterButtonLabel": {
|
||||
"message": "Later"
|
||||
},
|
||||
"autoUpdateIgnoreButtonLabel": {
|
||||
"message": "Ignore update"
|
||||
},
|
||||
"leftTheGroup": {
|
||||
"message": "$name$ left the group.",
|
||||
"description": "Shown in the conversation history when a single person leaves the group",
|
||||
|
|
|
@ -8,12 +8,17 @@
|
|||
}
|
||||
|
||||
.LeftPaneDialog {
|
||||
@include button-reset;
|
||||
|
||||
align-items: center;
|
||||
background: $color-ultramarine;
|
||||
color: $color-white;
|
||||
display: flex;
|
||||
width: 100%;
|
||||
min-height: 64px;
|
||||
padding: 12px 18px;
|
||||
user-select: none;
|
||||
cursor: inherit;
|
||||
|
||||
&__container {
|
||||
display: flex;
|
||||
|
@ -46,10 +51,11 @@
|
|||
}
|
||||
|
||||
&__icon {
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
margin-right: 18px;
|
||||
background-color: $color-white;
|
||||
-webkit-mask-size: contain;
|
||||
|
||||
&--relink {
|
||||
-webkit-mask: url('../images/icons/v2/link-broken-16.svg') no-repeat
|
||||
|
|
|
@ -5,6 +5,8 @@ import React from 'react';
|
|||
|
||||
import { LocalizerType } from '../types/Util';
|
||||
|
||||
import { LeftPaneDialog } from './LeftPaneDialog';
|
||||
|
||||
type PropsType = {
|
||||
hasExpired: boolean;
|
||||
i18n: LocalizerType;
|
||||
|
@ -19,19 +21,17 @@ export const DialogExpiredBuild = ({
|
|||
}
|
||||
|
||||
return (
|
||||
<div className="LeftPaneDialog LeftPaneDialog--error">
|
||||
<div className="LeftPaneDialog__message">
|
||||
{i18n('expiredWarning')}{' '}
|
||||
<a
|
||||
className="LeftPaneDialog__action-text"
|
||||
href="https://signal.org/download/"
|
||||
rel="noreferrer"
|
||||
tabIndex={-1}
|
||||
target="_blank"
|
||||
>
|
||||
{i18n('upgrade')}
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
<LeftPaneDialog type="error">
|
||||
{i18n('expiredWarning')}{' '}
|
||||
<a
|
||||
className="LeftPaneDialog__action-text"
|
||||
href="https://signal.org/download/"
|
||||
rel="noreferrer"
|
||||
tabIndex={-1}
|
||||
target="_blank"
|
||||
>
|
||||
{i18n('upgrade')}
|
||||
</a>
|
||||
</LeftPaneDialog>
|
||||
);
|
||||
};
|
||||
|
|
|
@ -1,8 +1,9 @@
|
|||
// Copyright 2020-2021 Signal Messenger, LLC
|
||||
// SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
import React from 'react';
|
||||
import React, { useEffect } from 'react';
|
||||
|
||||
import { LeftPaneDialog } from './LeftPaneDialog';
|
||||
import { Spinner } from './Spinner';
|
||||
import { LocalizerType } from '../types/Util';
|
||||
import { SocketStatus } from '../types/SocketStatus';
|
||||
|
@ -16,42 +17,6 @@ export type PropsType = NetworkStateType & {
|
|||
manualReconnect: () => void;
|
||||
};
|
||||
|
||||
type RenderDialogTypes = {
|
||||
isConnecting?: boolean;
|
||||
title: string;
|
||||
subtext: string;
|
||||
renderActionableButton?: () => JSX.Element;
|
||||
};
|
||||
|
||||
function renderDialog({
|
||||
isConnecting,
|
||||
title,
|
||||
subtext,
|
||||
renderActionableButton,
|
||||
}: RenderDialogTypes): JSX.Element {
|
||||
return (
|
||||
<div className="LeftPaneDialog LeftPaneDialog--warning">
|
||||
{isConnecting ? (
|
||||
<div className="LeftPaneDialog__spinner-container">
|
||||
<Spinner
|
||||
direction="on-avatar"
|
||||
moduleClassName="LeftPaneDialog__spinner"
|
||||
size="22px"
|
||||
svgSize="small"
|
||||
/>
|
||||
</div>
|
||||
) : (
|
||||
<div className="LeftPaneDialog__icon LeftPaneDialog__icon--network" />
|
||||
)}
|
||||
<div className="LeftPaneDialog__message">
|
||||
<h3>{title}</h3>
|
||||
<span>{subtext}</span>
|
||||
<div>{renderActionableButton && renderActionableButton()}</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export const DialogNetworkStatus = ({
|
||||
hasNetworkDialog,
|
||||
i18n,
|
||||
|
@ -59,8 +24,10 @@ export const DialogNetworkStatus = ({
|
|||
socketStatus,
|
||||
manualReconnect,
|
||||
}: PropsType): JSX.Element | null => {
|
||||
const [isConnecting, setIsConnecting] = React.useState<boolean>(false);
|
||||
React.useEffect(() => {
|
||||
const [isConnecting, setIsConnecting] = React.useState<boolean>(
|
||||
socketStatus === SocketStatus.CONNECTING
|
||||
);
|
||||
useEffect(() => {
|
||||
if (!hasNetworkDialog) {
|
||||
return () => null;
|
||||
}
|
||||
|
@ -80,62 +47,46 @@ export const DialogNetworkStatus = ({
|
|||
};
|
||||
}, [hasNetworkDialog, isConnecting, setIsConnecting]);
|
||||
|
||||
if (!hasNetworkDialog) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const reconnect = () => {
|
||||
setIsConnecting(true);
|
||||
manualReconnect();
|
||||
};
|
||||
|
||||
const manualReconnectButton = (): JSX.Element => (
|
||||
<button
|
||||
className="LeftPaneDialog__action-text"
|
||||
onClick={reconnect}
|
||||
type="button"
|
||||
>
|
||||
{i18n('connect')}
|
||||
</button>
|
||||
);
|
||||
if (!hasNetworkDialog) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (isConnecting) {
|
||||
return renderDialog({
|
||||
isConnecting: true,
|
||||
subtext: i18n('connectingHangOn'),
|
||||
title: i18n('connecting'),
|
||||
});
|
||||
const spinner = (
|
||||
<div className="LeftPaneDialog__spinner-container">
|
||||
<Spinner
|
||||
direction="on-avatar"
|
||||
moduleClassName="LeftPaneDialog__spinner"
|
||||
size="22px"
|
||||
svgSize="small"
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
|
||||
return (
|
||||
<LeftPaneDialog
|
||||
type="warning"
|
||||
icon={spinner}
|
||||
title={i18n('connecting')}
|
||||
subtitle={i18n('connectingHangOn')}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
if (!isOnline) {
|
||||
return renderDialog({
|
||||
renderActionableButton: manualReconnectButton,
|
||||
subtext: i18n('checkNetworkConnection'),
|
||||
title: i18n('offline'),
|
||||
});
|
||||
}
|
||||
|
||||
let subtext = '';
|
||||
let title = '';
|
||||
let renderActionableButton;
|
||||
|
||||
switch (socketStatus) {
|
||||
case SocketStatus.CONNECTING:
|
||||
subtext = i18n('connectingHangOn');
|
||||
title = i18n('connecting');
|
||||
break;
|
||||
case SocketStatus.CLOSED:
|
||||
case SocketStatus.CLOSING:
|
||||
default:
|
||||
renderActionableButton = manualReconnectButton;
|
||||
title = i18n('disconnected');
|
||||
subtext = i18n('checkNetworkConnection');
|
||||
}
|
||||
|
||||
return renderDialog({
|
||||
isConnecting: socketStatus === SocketStatus.CONNECTING,
|
||||
renderActionableButton,
|
||||
subtext,
|
||||
title,
|
||||
});
|
||||
return (
|
||||
<LeftPaneDialog
|
||||
type="warning"
|
||||
icon="network"
|
||||
title={isOnline ? i18n('disconnected') : i18n('offline')}
|
||||
subtitle={i18n('checkNetworkConnection')}
|
||||
hasAction
|
||||
clickLabel={i18n('connect')}
|
||||
onClick={reconnect}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
|
|
@ -5,6 +5,8 @@ import React from 'react';
|
|||
|
||||
import { LocalizerType } from '../types/Util';
|
||||
|
||||
import { LeftPaneDialog } from './LeftPaneDialog';
|
||||
|
||||
export type PropsType = {
|
||||
i18n: LocalizerType;
|
||||
isRegistrationDone: boolean;
|
||||
|
@ -21,20 +23,13 @@ export const DialogRelink = ({
|
|||
}
|
||||
|
||||
return (
|
||||
<div className="LeftPaneDialog LeftPaneDialog--warning">
|
||||
<div className="LeftPaneDialog__icon LeftPaneDialog__icon--relink" />
|
||||
<div className="LeftPaneDialog__message">
|
||||
<h3>{i18n('unlinked')}</h3>
|
||||
<div>
|
||||
<button
|
||||
className="LeftPaneDialog__action-text"
|
||||
onClick={relinkDevice}
|
||||
type="button"
|
||||
>
|
||||
{i18n('unlinkedWarning')}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<LeftPaneDialog
|
||||
type="warning"
|
||||
icon="relink"
|
||||
clickLabel={i18n('unlinkedWarning')}
|
||||
onClick={relinkDevice}
|
||||
title={i18n('unlinked')}
|
||||
hasAction
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
|
|
@ -5,8 +5,9 @@ import React from 'react';
|
|||
import formatFileSize from 'filesize';
|
||||
|
||||
import { DialogType } from '../types/Dialogs';
|
||||
import { Intl } from './Intl';
|
||||
import { LocalizerType } from '../types/Util';
|
||||
import { Intl } from './Intl';
|
||||
import { LeftPaneDialog } from './LeftPaneDialog';
|
||||
|
||||
export type PropsType = {
|
||||
dialogType: DialogType;
|
||||
|
@ -48,132 +49,99 @@ export const DialogUpdate = ({
|
|||
|
||||
if (dialogType === DialogType.Cannot_Update) {
|
||||
return (
|
||||
<div className="LeftPaneDialog LeftPaneDialog--warning">
|
||||
<div className="LeftPaneDialog__message">
|
||||
<h3>{i18n('cannotUpdate')}</h3>
|
||||
<span>
|
||||
<Intl
|
||||
components={[
|
||||
<a
|
||||
key="signal-download"
|
||||
href="https://signal.org/download/"
|
||||
rel="noreferrer"
|
||||
target="_blank"
|
||||
>
|
||||
https://signal.org/download/
|
||||
</a>,
|
||||
]}
|
||||
i18n={i18n}
|
||||
id="cannotUpdateDetail"
|
||||
/>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<LeftPaneDialog type="warning" title={i18n('cannotUpdate')}>
|
||||
<span>
|
||||
<Intl
|
||||
components={[
|
||||
<a
|
||||
key="signal-download"
|
||||
href="https://signal.org/download/"
|
||||
rel="noreferrer"
|
||||
target="_blank"
|
||||
>
|
||||
https://signal.org/download/
|
||||
</a>,
|
||||
]}
|
||||
i18n={i18n}
|
||||
id="cannotUpdateDetail"
|
||||
/>
|
||||
</span>
|
||||
</LeftPaneDialog>
|
||||
);
|
||||
}
|
||||
|
||||
if (dialogType === DialogType.MacOS_Read_Only) {
|
||||
return (
|
||||
<div className="LeftPaneDialog LeftPaneDialog--warning">
|
||||
<div className="LeftPaneDialog__container">
|
||||
<div className="LeftPaneDialog__message">
|
||||
<h3>{i18n('cannotUpdate')}</h3>
|
||||
<span>
|
||||
<Intl
|
||||
components={{
|
||||
app: <strong key="app">Signal.app</strong>,
|
||||
folder: <strong key="folder">/Applications</strong>,
|
||||
}}
|
||||
i18n={i18n}
|
||||
id="readOnlyVolume"
|
||||
/>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<div className="LeftPaneDialog__container-close">
|
||||
<button
|
||||
aria-label={i18n('close')}
|
||||
className="LeftPaneDialog__close-button"
|
||||
onClick={dismissDialog}
|
||||
tabIndex={0}
|
||||
type="button"
|
||||
<LeftPaneDialog
|
||||
type="warning"
|
||||
title={i18n('cannotUpdate')}
|
||||
hasXButton
|
||||
closeLabel={i18n('close')}
|
||||
onClose={dismissDialog}
|
||||
>
|
||||
<span>
|
||||
<Intl
|
||||
components={{
|
||||
app: <strong key="app">Signal.app</strong>,
|
||||
folder: <strong key="folder">/Applications</strong>,
|
||||
}}
|
||||
i18n={i18n}
|
||||
id="readOnlyVolume"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</span>
|
||||
</LeftPaneDialog>
|
||||
);
|
||||
}
|
||||
|
||||
let size: string | undefined;
|
||||
let title = i18n('autoUpdateNewVersionTitle');
|
||||
|
||||
if (
|
||||
downloadSize &&
|
||||
(dialogType === DialogType.DownloadReady ||
|
||||
dialogType === DialogType.Downloading)
|
||||
) {
|
||||
size = `(${formatFileSize(downloadSize, { round: 0 })})`;
|
||||
}
|
||||
|
||||
let updateSubText: JSX.Element;
|
||||
if (dialogType === DialogType.DownloadReady) {
|
||||
updateSubText = (
|
||||
<button
|
||||
className="LeftPaneDialog__action-text"
|
||||
onClick={startUpdate}
|
||||
type="button"
|
||||
>
|
||||
{i18n('downloadNewVersionMessage')}
|
||||
</button>
|
||||
);
|
||||
} else if (dialogType === DialogType.Downloading) {
|
||||
const width = Math.ceil(
|
||||
((downloadedSize || 1) / (downloadSize || 1)) * 100
|
||||
);
|
||||
|
||||
updateSubText = (
|
||||
<div className="LeftPaneDialog__progress--container">
|
||||
<div
|
||||
className="LeftPaneDialog__progress--bar"
|
||||
style={{ width: `${width}%` }}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
} else {
|
||||
updateSubText = (
|
||||
<button
|
||||
className="LeftPaneDialog__action-text"
|
||||
onClick={startUpdate}
|
||||
type="button"
|
||||
>
|
||||
{i18n('autoUpdateNewVersionMessage')}
|
||||
</button>
|
||||
);
|
||||
title += ` (${formatFileSize(downloadSize, { round: 0 })})`;
|
||||
}
|
||||
|
||||
const versionTitle = version
|
||||
? i18n('DialogUpdate--version-available', [version])
|
||||
: undefined;
|
||||
|
||||
return (
|
||||
<div className="LeftPaneDialog" title={versionTitle}>
|
||||
<div className="LeftPaneDialog__container">
|
||||
<div className="LeftPaneDialog__icon LeftPaneDialog__icon--update" />
|
||||
<div className="LeftPaneDialog__message">
|
||||
<h3>
|
||||
{i18n('autoUpdateNewVersionTitle')} {size}
|
||||
</h3>
|
||||
{updateSubText}
|
||||
</div>
|
||||
</div>
|
||||
<div className="LeftPaneDialog__container-close">
|
||||
{dialogType !== DialogType.Downloading && (
|
||||
<button
|
||||
aria-label={i18n('close')}
|
||||
className="LeftPaneDialog__close-button"
|
||||
onClick={snoozeUpdate}
|
||||
tabIndex={0}
|
||||
type="button"
|
||||
if (dialogType === DialogType.Downloading) {
|
||||
const width = Math.ceil(
|
||||
((downloadedSize || 1) / (downloadSize || 1)) * 100
|
||||
);
|
||||
|
||||
return (
|
||||
<LeftPaneDialog icon="update" title={title} hoverText={versionTitle}>
|
||||
<div className="LeftPaneDialog__progress--container">
|
||||
<div
|
||||
className="LeftPaneDialog__progress--bar"
|
||||
style={{ width: `${width}%` }}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</LeftPaneDialog>
|
||||
);
|
||||
}
|
||||
|
||||
let clickLabel: string;
|
||||
if (dialogType === DialogType.DownloadReady) {
|
||||
clickLabel = i18n('downloadNewVersionMessage');
|
||||
} else {
|
||||
clickLabel = i18n('autoUpdateNewVersionMessage');
|
||||
}
|
||||
|
||||
return (
|
||||
<LeftPaneDialog
|
||||
icon="update"
|
||||
title={title}
|
||||
hoverText={versionTitle}
|
||||
hasAction
|
||||
onClick={startUpdate}
|
||||
clickLabel={clickLabel}
|
||||
hasXButton
|
||||
onClose={snoozeUpdate}
|
||||
closeLabel={i18n('autoUpdateIgnoreButtonLabel')}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
|
|
@ -0,0 +1,149 @@
|
|||
// Copyright 2021 Signal Messenger, LLC
|
||||
// SPDX-License-Identifier: AGPL-3.0-only
|
||||
import React, { ReactNode } from 'react';
|
||||
import classNames from 'classnames';
|
||||
|
||||
const BASE_CLASS_NAME = 'LeftPaneDialog';
|
||||
|
||||
export type PropsType = {
|
||||
type?: 'warning' | 'error';
|
||||
icon?: 'update' | 'relink' | 'network' | ReactNode;
|
||||
title?: string;
|
||||
subtitle?: string;
|
||||
children?: ReactNode;
|
||||
hoverText?: string;
|
||||
} & (
|
||||
| {
|
||||
onClick?: undefined;
|
||||
clickLabel?: undefined;
|
||||
hasAction?: false;
|
||||
}
|
||||
| {
|
||||
onClick: () => void;
|
||||
clickLabel: string;
|
||||
hasAction: true;
|
||||
}
|
||||
) &
|
||||
(
|
||||
| {
|
||||
onClose?: undefined;
|
||||
closeLabel?: undefined;
|
||||
hasXButton?: false;
|
||||
}
|
||||
| {
|
||||
onClose: () => void;
|
||||
closeLabel: string;
|
||||
hasXButton: true;
|
||||
}
|
||||
);
|
||||
|
||||
export const LeftPaneDialog: React.FC<PropsType> = ({
|
||||
icon,
|
||||
type,
|
||||
onClick,
|
||||
clickLabel,
|
||||
title,
|
||||
subtitle,
|
||||
children,
|
||||
hoverText,
|
||||
hasAction,
|
||||
|
||||
hasXButton,
|
||||
onClose,
|
||||
closeLabel,
|
||||
}) => {
|
||||
const onClickWrap = (e: React.MouseEvent) => {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
|
||||
onClick?.();
|
||||
};
|
||||
|
||||
const onCloseWrap = (e: React.MouseEvent) => {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
|
||||
onClose?.();
|
||||
};
|
||||
|
||||
const iconClassName =
|
||||
typeof icon === 'string'
|
||||
? classNames([
|
||||
`${BASE_CLASS_NAME}__icon`,
|
||||
`${BASE_CLASS_NAME}__icon--${icon}`,
|
||||
])
|
||||
: undefined;
|
||||
|
||||
let action: ReactNode;
|
||||
if (hasAction) {
|
||||
action = (
|
||||
<button
|
||||
title={clickLabel}
|
||||
aria-label={clickLabel}
|
||||
className={`${BASE_CLASS_NAME}__action-text`}
|
||||
onClick={onClickWrap}
|
||||
tabIndex={0}
|
||||
type="button"
|
||||
>
|
||||
{clickLabel}
|
||||
</button>
|
||||
);
|
||||
}
|
||||
|
||||
let xButton: ReactNode;
|
||||
if (hasXButton) {
|
||||
xButton = (
|
||||
<div className={`${BASE_CLASS_NAME}__container-close`}>
|
||||
<button
|
||||
title={closeLabel}
|
||||
aria-label={closeLabel}
|
||||
className={`${BASE_CLASS_NAME}__close-button`}
|
||||
onClick={onCloseWrap}
|
||||
tabIndex={0}
|
||||
type="button"
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
const className = classNames([
|
||||
BASE_CLASS_NAME,
|
||||
type === undefined ? undefined : `${BASE_CLASS_NAME}--${type}`,
|
||||
]);
|
||||
|
||||
const content = (
|
||||
<>
|
||||
<div className={`${BASE_CLASS_NAME}__container`}>
|
||||
{typeof icon === 'string' ? <div className={iconClassName} /> : icon}
|
||||
<div className={`${BASE_CLASS_NAME}__message`}>
|
||||
{title === undefined ? undefined : <h3>{title}</h3>}
|
||||
{subtitle === undefined ? undefined : <div>{subtitle}</div>}
|
||||
{children}
|
||||
{action}
|
||||
</div>
|
||||
</div>
|
||||
{xButton}
|
||||
</>
|
||||
);
|
||||
|
||||
if (onClick) {
|
||||
return (
|
||||
<button
|
||||
className={className}
|
||||
type="button"
|
||||
onClick={onClickWrap}
|
||||
aria-label={clickLabel}
|
||||
title={hoverText}
|
||||
tabIndex={0}
|
||||
>
|
||||
{content}
|
||||
</button>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<div className={className} title={hoverText}>
|
||||
{content}
|
||||
</div>
|
||||
);
|
||||
};
|
Loading…
Reference in New Issue