New option for control over update downloads

This commit is contained in:
Josh Perez 2021-08-19 18:56:29 -04:00 committed by GitHub
parent 80c1ad6ee3
commit e9308bbafb
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
49 changed files with 1230 additions and 803 deletions

View file

@ -36,6 +36,7 @@ const createProps = (overrideProps: Partial<Props> = {}): Props => ({
conversationTypeMap,
overrideProps.conversationType || 'direct'
),
hasPendingUpdate: Boolean(overrideProps.hasPendingUpdate),
i18n,
isMe: true,
name: text('name', overrideProps.name || ''),
@ -47,6 +48,7 @@ const createProps = (overrideProps: Partial<Props> = {}): Props => ({
profileName: text('profileName', overrideProps.profileName || ''),
sharedGroupNames: [],
size: 80,
startUpdate: action('startUpdate'),
style: {},
title: text('title', overrideProps.title || ''),
});
@ -83,3 +85,11 @@ stories.add('Phone Number', () => {
return <AvatarPopup {...props} />;
});
stories.add('Update Available', () => {
const props = createProps({
hasPendingUpdate: true,
});
return <AvatarPopup {...props} />;
});

View file

@ -12,6 +12,9 @@ import { LocalizerType } from '../types/Util';
export type Props = {
readonly i18n: LocalizerType;
hasPendingUpdate: boolean;
startUpdate: () => unknown;
onEditProfile: () => unknown;
onViewPreferences: () => unknown;
onViewArchive: () => unknown;
@ -23,15 +26,17 @@ export type Props = {
export const AvatarPopup = (props: Props): JSX.Element => {
const {
hasPendingUpdate,
i18n,
name,
profileName,
phoneNumber,
title,
onEditProfile,
onViewPreferences,
onViewArchive,
onViewPreferences,
phoneNumber,
profileName,
startUpdate,
style,
title,
} = props;
const shouldShowNumber = Boolean(name || profileName);
@ -92,6 +97,24 @@ export const AvatarPopup = (props: Props): JSX.Element => {
{i18n('avatarMenuViewArchive')}
</div>
</button>
{hasPendingUpdate && (
<button
type="button"
className="module-avatar-popup__item"
onClick={startUpdate}
>
<div
className={classNames(
'module-avatar-popup__item__icon',
'module-avatar-popup__item__icon--update'
)}
/>
<div className="module-avatar-popup__item__text">
{i18n('avatarMenuUpdateAvailable')}
</div>
<div className="module-avatar-popup__item--badge" />
</button>
)}
</div>
);
};

View file

@ -5,17 +5,17 @@ import * as React from 'react';
import { storiesOf } from '@storybook/react';
import { boolean } from '@storybook/addon-knobs';
import { ExpiredBuildDialog } from './ExpiredBuildDialog';
import { DialogExpiredBuild } from './DialogExpiredBuild';
import { setup as setupI18n } from '../../js/modules/i18n';
import enMessages from '../../_locales/en/messages.json';
const i18n = setupI18n('en', enMessages);
storiesOf('Components/ExpiredBuildDialog', module).add(
'ExpiredBuildDialog',
storiesOf('Components/DialogExpiredBuild', module).add(
'DialogExpiredBuild',
() => {
const hasExpired = boolean('hasExpired', true);
return <ExpiredBuildDialog hasExpired={hasExpired} i18n={i18n} />;
return <DialogExpiredBuild hasExpired={hasExpired} i18n={i18n} />;
}
);

View file

@ -10,7 +10,7 @@ type PropsType = {
i18n: LocalizerType;
};
export const ExpiredBuildDialog = ({
export const DialogExpiredBuild = ({
hasExpired,
i18n,
}: PropsType): JSX.Element | null => {
@ -19,19 +19,17 @@ export const ExpiredBuildDialog = ({
}
return (
<div className="module-left-pane-dialog module-left-pane-dialog--error">
{i18n('expiredWarning')}
<div className="module-left-pane-dialog__actions">
<div className="LeftPaneDialog LeftPaneDialog--error">
<div className="LeftPaneDialog__message">
{i18n('expiredWarning')}{' '}
<a
className="module-left-pane-dialog__link"
className="LeftPaneDialog__action-text"
href="https://signal.org/download/"
rel="noreferrer"
tabIndex={-1}
target="_blank"
>
<button type="button" className="upgrade">
{i18n('upgrade')}
</button>
{i18n('upgrade')}
</a>
</div>
</div>

View file

@ -0,0 +1,69 @@
// Copyright 2020 Signal Messenger, LLC
// SPDX-License-Identifier: AGPL-3.0-only
import * as React from 'react';
import { storiesOf } from '@storybook/react';
import { boolean, select } from '@storybook/addon-knobs';
import { action } from '@storybook/addon-actions';
import { DialogNetworkStatus } from './DialogNetworkStatus';
import { SocketStatus } from '../types/SocketStatus';
import { setup as setupI18n } from '../../js/modules/i18n';
import enMessages from '../../_locales/en/messages.json';
const i18n = setupI18n('en', enMessages);
const defaultProps = {
hasNetworkDialog: true,
i18n,
isOnline: true,
socketStatus: SocketStatus.CONNECTING,
manualReconnect: action('manual-reconnect'),
withinConnectingGracePeriod: false,
challengeStatus: 'idle' as const,
};
const story = storiesOf('Components/DialogNetworkStatus', module);
story.add('Knobs Playground', () => {
const hasNetworkDialog = boolean('hasNetworkDialog', true);
const isOnline = boolean('isOnline', true);
const socketStatus = select(
'socketStatus',
{
CONNECTING: SocketStatus.CONNECTING,
OPEN: SocketStatus.OPEN,
CLOSING: SocketStatus.CLOSING,
CLOSED: SocketStatus.CLOSED,
},
SocketStatus.CONNECTING
);
return (
<DialogNetworkStatus
{...defaultProps}
hasNetworkDialog={hasNetworkDialog}
isOnline={isOnline}
socketStatus={socketStatus}
/>
);
});
story.add('Connecting', () => (
<DialogNetworkStatus
{...defaultProps}
socketStatus={SocketStatus.CONNECTING}
/>
));
story.add('Closing', () => (
<DialogNetworkStatus {...defaultProps} socketStatus={SocketStatus.CLOSING} />
));
story.add('Closed', () => (
<DialogNetworkStatus {...defaultProps} socketStatus={SocketStatus.CLOSED} />
));
story.add('Offline', () => (
<DialogNetworkStatus {...defaultProps} isOnline={false} />
));

View file

@ -3,6 +3,7 @@
import React from 'react';
import { Spinner } from './Spinner';
import { LocalizerType } from '../types/Util';
import { SocketStatus } from '../types/SocketStatus';
import { NetworkStateType } from '../state/ducks/network';
@ -16,28 +17,42 @@ export type PropsType = NetworkStateType & {
};
type RenderDialogTypes = {
isConnecting?: boolean;
title: string;
subtext: string;
renderActionableButton?: () => JSX.Element;
};
function renderDialog({
isConnecting,
title,
subtext,
renderActionableButton,
}: RenderDialogTypes): JSX.Element {
return (
<div className="module-left-pane-dialog module-left-pane-dialog--warning">
<div className="module-left-pane-dialog__message">
<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>
{renderActionableButton && renderActionableButton()}
</div>
);
}
export const NetworkStatus = ({
export const DialogNetworkStatus = ({
hasNetworkDialog,
i18n,
isOnline,
@ -75,19 +90,23 @@ export const NetworkStatus = ({
};
const manualReconnectButton = (): JSX.Element => (
<div className="module-left-pane-dialog__actions">
<button onClick={reconnect} type="button">
{i18n('connect')}
</button>
</div>
<button
className="LeftPaneDialog__action-text"
onClick={reconnect}
type="button"
>
{i18n('connect')}
</button>
);
if (isConnecting) {
return renderDialog({
isConnecting: true,
subtext: i18n('connectingHangOn'),
title: i18n('connecting'),
});
}
if (!isOnline) {
return renderDialog({
renderActionableButton: manualReconnectButton,
@ -114,6 +133,7 @@ export const NetworkStatus = ({
}
return renderDialog({
isConnecting: socketStatus === SocketStatus.CONNECTING,
renderActionableButton,
subtext,
title,

View file

@ -0,0 +1,64 @@
// Copyright 2020 Signal Messenger, LLC
// SPDX-License-Identifier: AGPL-3.0-only
import * as React from 'react';
import { storiesOf } from '@storybook/react';
import { boolean, select } from '@storybook/addon-knobs';
import { action } from '@storybook/addon-actions';
import { DialogUpdate } from './DialogUpdate';
import { DialogType } from '../types/Dialogs';
import { setup as setupI18n } from '../../js/modules/i18n';
import enMessages from '../../_locales/en/messages.json';
const i18n = setupI18n('en', enMessages);
const defaultProps = {
dismissDialog: action('dismiss-dialog'),
downloadSize: 116504357,
downloadedSize: 61003110,
hasNetworkDialog: false,
i18n,
didSnooze: false,
showEventsCount: 0,
snoozeUpdate: action('snooze-update'),
startUpdate: action('start-update'),
version: 'v7.7.7',
};
const story = storiesOf('Components/DialogUpdate', module);
story.add('Knobs Playground', () => {
const dialogType = select('dialogType', DialogType, DialogType.Update);
const hasNetworkDialog = boolean('hasNetworkDialog', false);
const didSnooze = boolean('didSnooze', false);
return (
<DialogUpdate
{...defaultProps}
dialogType={dialogType}
didSnooze={didSnooze}
hasNetworkDialog={hasNetworkDialog}
/>
);
});
story.add('Update', () => (
<DialogUpdate {...defaultProps} dialogType={DialogType.Update} />
));
story.add('Download Ready', () => (
<DialogUpdate {...defaultProps} dialogType={DialogType.DownloadReady} />
));
story.add('Downloading', () => (
<DialogUpdate {...defaultProps} dialogType={DialogType.Downloading} />
));
story.add('Cannot Update', () => (
<DialogUpdate {...defaultProps} dialogType={DialogType.Cannot_Update} />
));
story.add('macOS RO Error', () => (
<DialogUpdate {...defaultProps} dialogType={DialogType.MacOS_Read_Only} />
));

View file

@ -0,0 +1,179 @@
// Copyright 2020-2021 Signal Messenger, LLC
// SPDX-License-Identifier: AGPL-3.0-only
import React from 'react';
import formatFileSize from 'filesize';
import { DialogType } from '../types/Dialogs';
import { Intl } from './Intl';
import { LocalizerType } from '../types/Util';
export type PropsType = {
dialogType: DialogType;
didSnooze: boolean;
dismissDialog: () => void;
downloadSize?: number;
downloadedSize?: number;
hasNetworkDialog: boolean;
i18n: LocalizerType;
showEventsCount: number;
snoozeUpdate: () => void;
startUpdate: () => void;
version?: string;
};
export const DialogUpdate = ({
dialogType,
didSnooze,
dismissDialog,
downloadSize,
downloadedSize,
hasNetworkDialog,
i18n,
snoozeUpdate,
startUpdate,
version,
}: PropsType): JSX.Element | null => {
if (hasNetworkDialog) {
return null;
}
if (dialogType === DialogType.None) {
return null;
}
if (didSnooze) {
return null;
}
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>
);
}
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"
/>
</div>
</div>
);
}
let size: string | undefined;
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>
);
}
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"
/>
)}
</div>
</div>
);
};

View file

@ -45,6 +45,7 @@ const createProps = (overrideProps: Partial<PropsType> = {}): PropsType => ({
title: requiredText('title', overrideProps.title),
name: optionalText('name', overrideProps.name),
avatarPath: optionalText('avatarPath', overrideProps.avatarPath),
hasPendingUpdate: Boolean(overrideProps.hasPendingUpdate),
i18n,
@ -55,6 +56,7 @@ const createProps = (overrideProps: Partial<PropsType> = {}): PropsType => ({
searchInConversation: action('searchInConversation'),
clearConversationSearch: action('clearConversationSearch'),
clearSearch: action('clearSearch'),
startUpdate: action('startUpdate'),
showArchivedConversations: action('showArchivedConversations'),
startComposing: action('startComposing'),
@ -115,3 +117,9 @@ story.add('Searching Conversation with Term', () => {
return <MainHeader {...props} />;
});
story.add('Update Available', () => {
const props = createProps({ hasPendingUpdate: true });
return <MainHeader {...props} />;
});

View file

@ -37,6 +37,7 @@ export type PropsType = {
profileName?: string;
title: string;
avatarPath?: string;
hasPendingUpdate: boolean;
i18n: LocalizerType;
@ -59,6 +60,7 @@ export type PropsType = {
noteToSelf: string;
}
) => void;
startUpdate: () => unknown;
clearConversationSearch: () => void;
clearSearch: () => void;
@ -342,16 +344,18 @@ export class MainHeader extends React.Component<PropsType, StateType> {
avatarPath,
color,
disabled,
hasPendingUpdate,
i18n,
name,
startComposing,
phoneNumber,
profileName,
title,
searchConversationId,
searchConversationName,
searchTerm,
showArchivedConversations,
startComposing,
startUpdate,
title,
toggleProfileEditor,
} = this.props;
const { showingAvatarPopup, popperRoot } = this.state;
@ -369,25 +373,30 @@ export class MainHeader extends React.Component<PropsType, StateType> {
<Manager>
<Reference>
{({ ref }) => (
<Avatar
acceptedMessageRequest
avatarPath={avatarPath}
className="module-main-header__avatar"
color={color}
conversationType="direct"
i18n={i18n}
isMe
name={name}
phoneNumber={phoneNumber}
profileName={profileName}
title={title}
// `sharedGroupNames` makes no sense for yourself, but `<Avatar>` needs it
// to determine blurring.
sharedGroupNames={[]}
size={28}
innerRef={ref}
onClick={this.showAvatarPopup}
/>
<div className="module-main-header__avatar--container">
<Avatar
acceptedMessageRequest
avatarPath={avatarPath}
className="module-main-header__avatar"
color={color}
conversationType="direct"
i18n={i18n}
isMe
name={name}
phoneNumber={phoneNumber}
profileName={profileName}
title={title}
// `sharedGroupNames` makes no sense for yourself, but
// `<Avatar>` needs it to determine blurring.
sharedGroupNames={[]}
size={28}
innerRef={ref}
onClick={this.showAvatarPopup}
/>
{hasPendingUpdate && (
<div className="module-main-header__avatar--badged" />
)}
</div>
)}
</Reference>
{showingAvatarPopup && popperRoot
@ -408,6 +417,8 @@ export class MainHeader extends React.Component<PropsType, StateType> {
title={title}
avatarPath={avatarPath}
size={28}
hasPendingUpdate={hasPendingUpdate}
startUpdate={startUpdate}
// See the comment above about `sharedGroupNames`.
sharedGroupNames={[]}
onEditProfile={() => {

View file

@ -1,84 +0,0 @@
// Copyright 2020 Signal Messenger, LLC
// SPDX-License-Identifier: AGPL-3.0-only
import * as React from 'react';
import { storiesOf } from '@storybook/react';
import { boolean, select } from '@storybook/addon-knobs';
import { action } from '@storybook/addon-actions';
import { NetworkStatus } from './NetworkStatus';
import { SocketStatus } from '../types/SocketStatus';
import { setup as setupI18n } from '../../js/modules/i18n';
import enMessages from '../../_locales/en/messages.json';
const i18n = setupI18n('en', enMessages);
const defaultProps = {
hasNetworkDialog: true,
i18n,
isOnline: true,
socketStatus: SocketStatus.CONNECTING,
manualReconnect: action('manual-reconnect'),
withinConnectingGracePeriod: false,
challengeStatus: 'idle' as const,
};
const permutations = [
{
title: 'Connecting',
props: {
socketStatus: SocketStatus.CONNECTING,
},
},
{
title: 'Closing (online)',
props: {
socketStatus: SocketStatus.CLOSING,
},
},
{
title: 'Closed (online)',
props: {
socketStatus: SocketStatus.CLOSED,
},
},
{
title: 'Offline',
props: {
isOnline: false,
},
},
];
storiesOf('Components/NetworkStatus', module)
.add('Knobs Playground', () => {
const hasNetworkDialog = boolean('hasNetworkDialog', true);
const isOnline = boolean('isOnline', true);
const socketStatus = select(
'socketStatus',
{
CONNECTING: SocketStatus.CONNECTING,
OPEN: SocketStatus.OPEN,
CLOSING: SocketStatus.CLOSING,
CLOSED: SocketStatus.CLOSED,
},
SocketStatus.CONNECTING
);
return (
<NetworkStatus
{...defaultProps}
hasNetworkDialog={hasNetworkDialog}
isOnline={isOnline}
socketStatus={socketStatus}
/>
);
})
.add('Iterations', () => {
return permutations.map(({ props, title }) => (
<>
<h3>{title}</h3>
<NetworkStatus {...defaultProps} {...props} />
</>
));
});

View file

@ -69,6 +69,7 @@ const createProps = (): PropsType => ({
defaultConversationColor: DEFAULT_CONVERSATION_COLOR,
deviceName: 'Work Windows ME',
hasAudioNotifications: true,
hasAutoDownloadUpdate: true,
hasAutoLaunch: true,
hasCallNotifications: true,
hasCallRingtoneNotification: false,
@ -125,6 +126,7 @@ const createProps = (): PropsType => ({
isSystemTraySupported: true,
onAudioNotificationsChange: action('onAudioNotificationsChange'),
onAutoDownloadUpdateChange: action('onAutoDownloadUpdateChange'),
onAutoLaunchChange: action('onAutoLaunchChange'),
onCallNotificationsChange: action('onCallNotificationsChange'),
onCallRingtoneNotificationChange: action('onCallRingtoneNotificationChange'),

View file

@ -43,6 +43,7 @@ export type PropsType = {
defaultConversationColor: DefaultConversationColorType;
deviceName?: string;
hasAudioNotifications?: boolean;
hasAutoDownloadUpdate: boolean;
hasAutoLaunch: boolean;
hasCallNotifications: boolean;
hasCallRingtoneNotification: boolean;
@ -104,6 +105,7 @@ export type PropsType = {
// Change handlers
onAudioNotificationsChange: CheckboxChangeHandlerType;
onAutoDownloadUpdateChange: CheckboxChangeHandlerType;
onAutoLaunchChange: CheckboxChangeHandlerType;
onCallNotificationsChange: CheckboxChangeHandlerType;
onCallRingtoneNotificationChange: CheckboxChangeHandlerType;
@ -161,6 +163,7 @@ export const Preferences = ({
editCustomColor,
getConversationsWithCustomColor,
hasAudioNotifications,
hasAutoDownloadUpdate,
hasAutoLaunch,
hasCallNotifications,
hasCallRingtoneNotification,
@ -191,6 +194,7 @@ export const Preferences = ({
makeSyncRequest,
notificationContent,
onAudioNotificationsChange,
onAutoDownloadUpdateChange,
onAutoLaunchChange,
onCallNotificationsChange,
onCallRingtoneNotificationChange,
@ -340,6 +344,15 @@ export const Preferences = ({
onChange={onMediaCameraPermissionsChange}
/>
</SettingsRow>
<SettingsRow title={i18n('Preferences--updates')}>
<Checkbox
checked={hasAutoDownloadUpdate}
label={i18n('Preferences__download-update')}
moduleClassName="Preferences__checkbox"
name="autoDownloadUpdate"
onChange={onAutoDownloadUpdateChange}
/>
</SettingsRow>
</>
);
} else if (page === Page.Appearance) {

View file

@ -21,12 +21,12 @@ export const RelinkDialog = ({
}
return (
<div className="module-left-pane-dialog module-left-pane-dialog--warning">
<div className="module-left-pane-dialog__message">
<div className="LeftPaneDialog LeftPaneDialog--warning">
<div className="LeftPaneDialog__message">
<h3>{i18n('unlinked')}</h3>
<span>{i18n('unlinkedWarning')}</span>
</div>
<div className="module-left-pane-dialog__actions">
<div className="LeftPaneDialog__actions">
<button onClick={relinkDevice} type="button">
{i18n('relink')}
</button>

View file

@ -1,85 +0,0 @@
// Copyright 2020 Signal Messenger, LLC
// SPDX-License-Identifier: AGPL-3.0-only
import * as React from 'react';
import { storiesOf } from '@storybook/react';
import { boolean, select } from '@storybook/addon-knobs';
import { action } from '@storybook/addon-actions';
import { UpdateDialog } from './UpdateDialog';
import { setup as setupI18n } from '../../js/modules/i18n';
import enMessages from '../../_locales/en/messages.json';
const i18n = setupI18n('en', enMessages);
const defaultProps = {
ackRender: action('ack-render'),
dismissDialog: action('dismiss-dialog'),
hasNetworkDialog: false,
i18n,
didSnooze: false,
showEventsCount: 0,
snoozeUpdate: action('snooze-update'),
startUpdate: action('start-update'),
};
const permutations = [
{
title: 'Update',
props: {
dialogType: 1,
},
},
{
title: 'Update (didSnooze=true)',
props: {
dialogType: 1,
didSnooze: true,
},
},
{
title: 'Cannot Update',
props: {
dialogType: 2,
},
},
{
title: 'MacOS Read Only Error',
props: {
dialogType: 3,
},
},
];
storiesOf('Components/UpdateDialog', module)
.add('Knobs Playground', () => {
const dialogType = select(
'dialogType',
{
None: 0,
Update: 1,
Cannot_Update: 2,
MacOS_Read_Only: 3,
},
1
);
const hasNetworkDialog = boolean('hasNetworkDialog', false);
const didSnooze = boolean('didSnooze', false);
return (
<UpdateDialog
{...defaultProps}
dialogType={dialogType}
didSnooze={didSnooze}
hasNetworkDialog={hasNetworkDialog}
/>
);
})
.add('Iterations', () => {
return permutations.map(({ props, title }) => (
<>
<h3>{title}</h3>
<UpdateDialog {...defaultProps} {...props} />
</>
));
});

View file

@ -1,117 +0,0 @@
// Copyright 2020-2021 Signal Messenger, LLC
// SPDX-License-Identifier: AGPL-3.0-only
import React from 'react';
import { Dialogs } from '../types/Dialogs';
import { Intl } from './Intl';
import { LocalizerType } from '../types/Util';
export type PropsType = {
ackRender: () => void;
dialogType: Dialogs;
didSnooze: boolean;
dismissDialog: () => void;
hasNetworkDialog: boolean;
i18n: LocalizerType;
showEventsCount: number;
snoozeUpdate: () => void;
startUpdate: () => void;
};
export const UpdateDialog = ({
ackRender,
dialogType,
didSnooze,
dismissDialog,
hasNetworkDialog,
i18n,
snoozeUpdate,
startUpdate,
}: PropsType): JSX.Element | null => {
React.useEffect(() => {
ackRender();
});
if (hasNetworkDialog) {
return null;
}
if (dialogType === Dialogs.None) {
return null;
}
if (dialogType === Dialogs.Cannot_Update) {
return (
<div className="module-left-pane-dialog module-left-pane-dialog--warning">
<div className="module-left-pane-dialog__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>
);
}
if (dialogType === Dialogs.MacOS_Read_Only) {
return (
<div className="module-left-pane-dialog module-left-pane-dialog--warning">
<div className="module-left-pane-dialog__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 className="module-left-pane-dialog__actions">
<button type="button" onClick={dismissDialog}>
{i18n('ok')}
</button>
</div>
</div>
);
}
return (
<div className="module-left-pane-dialog">
<div className="module-left-pane-dialog__message">
<h3>{i18n('autoUpdateNewVersionTitle')}</h3>
<span>{i18n('autoUpdateNewVersionMessage')}</span>
</div>
<div className="module-left-pane-dialog__actions">
{!didSnooze && (
<button
type="button"
className="module-left-pane-dialog__button--no-border"
onClick={snoozeUpdate}
>
{i18n('autoUpdateLaterButtonLabel')}
</button>
)}
<button type="button" onClick={startUpdate}>
{i18n('autoUpdateRestartButtonLabel')}
</button>
</div>
</div>
);
};

View file

@ -0,0 +1,75 @@
// Copyright 2021 Signal Messenger, LLC
// SPDX-License-Identifier: AGPL-3.0-only
import React, { useState } from 'react';
import moment from 'moment';
import { Modal } from './Modal';
import { Intl } from './Intl';
import { LocalizerType } from '../types/Util';
export type PropsType = {
i18n: LocalizerType;
};
type ReleaseNotesType = {
date: Date;
version: string;
features: Array<string>;
};
export const WhatsNew = ({ i18n }: PropsType): JSX.Element => {
const [releaseNotes, setReleaseNotes] = useState<
ReleaseNotesType | undefined
>();
const viewReleaseNotes = () => {
setReleaseNotes({
date: new Date('08/17/2021'),
version: window.getVersion(),
features: [
'WhatsNew__v5.15--1',
'WhatsNew__v5.15--2',
'WhatsNew__v5.15--3',
'WhatsNew__v5.15--4',
'WhatsNew__v5.15--5',
],
});
};
return (
<>
{releaseNotes && (
<Modal
hasXButton
i18n={i18n}
onClose={() => setReleaseNotes(undefined)}
title={i18n('WhatsNew__modal-title')}
>
<>
<span>
{moment(releaseNotes.date).format('LL')} &middot;{' '}
{releaseNotes.version}
</span>
<ul>
{releaseNotes.features.map(featureKey => (
<li key={featureKey}>
<Intl i18n={i18n} id={featureKey} />
</li>
))}
</ul>
</>
</Modal>
)}
<Intl
i18n={i18n}
id="whatsNew"
components={[
<button className="WhatsNew" type="button" onClick={viewReleaseNotes}>
{i18n('viewReleaseNotes')}
</button>,
]}
/>
</>
);
};