UpdateDialog on InstallScreen
This commit is contained in:
parent
28adb58c69
commit
1d1b124a92
14 changed files with 443 additions and 26 deletions
|
@ -5459,6 +5459,26 @@
|
|||
"messageformat": "Update Downloaded",
|
||||
"description": "The title of update dialog when update download is completed."
|
||||
},
|
||||
"icu:InstallScreenUpdateDialog--unsupported-os__title": {
|
||||
"messageformat": "Update Required",
|
||||
"description": "The title of update dialog on install screen when user OS is unsupported"
|
||||
},
|
||||
"icu:InstallScreenUpdateDialog--auto-update__body": {
|
||||
"messageformat": "To continue using Signal, you must update to the latest version.",
|
||||
"description": "The body of update dialog on install screen when auto update is downloaded and available."
|
||||
},
|
||||
"icu:InstallScreenUpdateDialog--manual-update__action": {
|
||||
"messageformat": "Download {downloadSize}",
|
||||
"description": "The text of a confirmation button in update dialog on install screen when manual update is ready to be downloaded."
|
||||
},
|
||||
"icu:InstallScreenUpdateDialog--downloaded__body": {
|
||||
"messageformat": "Restart Signal to install the update.",
|
||||
"description": "The body of the update dialog on install screen when manual update was downloaded."
|
||||
},
|
||||
"icu:InstallScreenUpdateDialog--cannot-update__body": {
|
||||
"messageformat": "Signal Desktop failed to update, but there is a new version available. Go to {downloadUrl} and install the new version manually, then either contact support or file a bug about this problem.",
|
||||
"description": "The body of the update dialog on install screen when update cannot be installed."
|
||||
},
|
||||
"NSIS__retry-dialog--first-line": {
|
||||
"message": "Signal cannot be closed.",
|
||||
"description": "First line of the dialog displayed when Windows installer can't close application automatically and needs user intervention to complete the installation."
|
||||
|
|
39
stylesheets/components/InstallScreenUpdateDialog.scss
Normal file
39
stylesheets/components/InstallScreenUpdateDialog.scss
Normal file
|
@ -0,0 +1,39 @@
|
|||
// Copyright 2023 Signal Messenger, LLC
|
||||
// SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
.InstallScreenUpdateDialog {
|
||||
&__download-size {
|
||||
font-weight: 400;
|
||||
}
|
||||
|
||||
&__progress {
|
||||
&--container {
|
||||
@include light-theme() {
|
||||
background-color: $color-gray-15;
|
||||
}
|
||||
@include dark-theme() {
|
||||
background-color: $color-gray-65;
|
||||
}
|
||||
border-radius: 2px;
|
||||
height: 4px;
|
||||
overflow: hidden;
|
||||
width: 100%;
|
||||
margin: 16px 0;
|
||||
}
|
||||
|
||||
&--bar {
|
||||
background-color: $color-ultramarine;
|
||||
border-radius: 2px;
|
||||
display: block;
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
transform: translateX(-100%);
|
||||
transition: transform 500ms ease-out;
|
||||
}
|
||||
}
|
||||
|
||||
a {
|
||||
// Prevent breaking the text
|
||||
display: inline-block;
|
||||
}
|
||||
}
|
|
@ -88,6 +88,7 @@
|
|||
@import './components/InstallScreenLinkInProgressStep.scss';
|
||||
@import './components/InstallScreenQrCodeNotScannedStep.scss';
|
||||
@import './components/InstallScreenSignalLogo.scss';
|
||||
@import './components/InstallScreenUpdateDialog.scss';
|
||||
@import './components/LeftPaneDialog.scss';
|
||||
@import './components/LeftPaneSearchInput.scss';
|
||||
@import './components/Lightbox.scss';
|
||||
|
|
|
@ -1781,6 +1781,7 @@ export async function startApp(): Promise<void> {
|
|||
}
|
||||
|
||||
window.Whisper.events.on('setupAsNewDevice', () => {
|
||||
window.IPC.readyForUpdates();
|
||||
window.reduxActions.app.openInstaller();
|
||||
});
|
||||
|
||||
|
@ -1967,6 +1968,7 @@ export async function startApp(): Promise<void> {
|
|||
void connect();
|
||||
window.reduxActions.app.openInbox();
|
||||
} else {
|
||||
window.IPC.readyForUpdates();
|
||||
window.reduxActions.app.openInstaller();
|
||||
}
|
||||
|
||||
|
|
|
@ -13,11 +13,19 @@ import { useAnimated } from '../hooks/useAnimated';
|
|||
import { Spinner } from './Spinner';
|
||||
|
||||
export type ActionSpec = {
|
||||
text: string;
|
||||
action: () => unknown;
|
||||
style?: 'affirmative' | 'negative';
|
||||
autoClose?: boolean;
|
||||
};
|
||||
} & (
|
||||
| {
|
||||
text: string;
|
||||
id?: string;
|
||||
}
|
||||
| {
|
||||
text: string | JSX.Element;
|
||||
id: string;
|
||||
}
|
||||
);
|
||||
|
||||
export type OwnProps = Readonly<{
|
||||
actions?: Array<ActionSpec>;
|
||||
|
@ -117,7 +125,11 @@ export const ConfirmationDialog = React.memo(function ConfirmationDialogInner({
|
|||
) : null}
|
||||
{actions.map((action, i) => (
|
||||
<Button
|
||||
key={action.text}
|
||||
key={
|
||||
typeof action.text === 'string'
|
||||
? action.id ?? action.text
|
||||
: action.id
|
||||
}
|
||||
disabled={isSpinning}
|
||||
onClick={() => {
|
||||
action.action();
|
||||
|
|
|
@ -7,6 +7,7 @@ import formatFileSize from 'filesize';
|
|||
import { isBeta } from '../util/version';
|
||||
import { DialogType } from '../types/Dialogs';
|
||||
import type { LocalizerType } from '../types/Util';
|
||||
import { PRODUCTION_DOWNLOAD_URL, BETA_DOWNLOAD_URL } from '../types/support';
|
||||
import { Intl } from './Intl';
|
||||
import { LeftPaneDialog } from './LeftPaneDialog';
|
||||
import type { WidthBreakpoint } from './_util';
|
||||
|
@ -24,9 +25,6 @@ export type PropsType = {
|
|||
currentVersion: string;
|
||||
};
|
||||
|
||||
const PRODUCTION_DOWNLOAD_URL = 'https://signal.org/download/';
|
||||
const BETA_DOWNLOAD_URL = 'https://support.signal.org/beta';
|
||||
|
||||
export function DialogUpdate({
|
||||
containerWidthBreakpoint,
|
||||
dialogType,
|
||||
|
|
|
@ -6,6 +6,7 @@ import moment from 'moment';
|
|||
import type { FormatXMLElementFn } from 'intl-messageformat';
|
||||
|
||||
import type { LocalizerType } from '../types/Util';
|
||||
import { UNSUPPORTED_OS_URL } from '../types/support';
|
||||
import { missingCaseError } from '../util/missingCaseError';
|
||||
import type { WidthBreakpoint } from './_util';
|
||||
import { Intl } from './Intl';
|
||||
|
@ -20,8 +21,6 @@ export type PropsType = {
|
|||
OS: string;
|
||||
};
|
||||
|
||||
const SUPPORT_URL = 'https://support.signal.org/hc/articles/5109141421850';
|
||||
|
||||
export function UnsupportedOSDialog({
|
||||
containerWidthBreakpoint,
|
||||
expirationTimestamp,
|
||||
|
@ -30,7 +29,12 @@ export function UnsupportedOSDialog({
|
|||
OS,
|
||||
}: PropsType): JSX.Element | null {
|
||||
const learnMoreLink: FormatXMLElementFn<JSX.Element | string> = children => (
|
||||
<a key="signal-support" href={SUPPORT_URL} rel="noreferrer" target="_blank">
|
||||
<a
|
||||
key="signal-support"
|
||||
href={UNSUPPORTED_OS_URL}
|
||||
rel="noreferrer"
|
||||
target="_blank"
|
||||
>
|
||||
{children}
|
||||
</a>
|
||||
);
|
||||
|
|
|
@ -2,8 +2,10 @@
|
|||
// SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
import React, { useEffect, useState } from 'react';
|
||||
import { action } from '@storybook/addon-actions';
|
||||
|
||||
import { setupI18n } from '../../util/setupI18n';
|
||||
import { DialogType } from '../../types/Dialogs';
|
||||
import enMessages from '../../../_locales/en/messages.json';
|
||||
|
||||
import type { Loadable } from '../../util/loadable';
|
||||
|
@ -12,8 +14,24 @@ import { InstallScreenQrCodeNotScannedStep } from './InstallScreenQrCodeNotScann
|
|||
|
||||
const i18n = setupI18n('en', enMessages);
|
||||
|
||||
const LOADED_URL = {
|
||||
loadingState: LoadingState.Loaded as const,
|
||||
value:
|
||||
'sgnl://linkdevice?uuid=b33f6338-aaf1-4853-9aff-6652369f6b52&pub_key=BTpRKRtFeJGga1M3Na4PzZevMvVIWmTWQIpn0BJI3x10',
|
||||
};
|
||||
|
||||
const DEFAULT_UPDATES = {
|
||||
dialogType: DialogType.None,
|
||||
didSnooze: false,
|
||||
showEventsCount: 0,
|
||||
downloadSize: 67 * 1024 * 1024,
|
||||
downloadedSize: 15 * 1024 * 1024,
|
||||
version: 'v7.7.7',
|
||||
};
|
||||
|
||||
export default {
|
||||
title: 'Components/InstallScreen/InstallScreenQrCodeNotScannedStep',
|
||||
argTypes: {},
|
||||
};
|
||||
|
||||
function Simulation({ finalResult }: { finalResult: Loadable<string> }) {
|
||||
|
@ -34,6 +52,10 @@ function Simulation({ finalResult }: { finalResult: Loadable<string> }) {
|
|||
<InstallScreenQrCodeNotScannedStep
|
||||
i18n={i18n}
|
||||
provisioningUrl={provisioningUrl}
|
||||
updates={DEFAULT_UPDATES}
|
||||
OS="macOS"
|
||||
startUpdate={action('startUpdate')}
|
||||
currentVersion="v6.0.0"
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
@ -45,6 +67,10 @@ export function QrCodeLoading(): JSX.Element {
|
|||
provisioningUrl={{
|
||||
loadingState: LoadingState.Loading,
|
||||
}}
|
||||
updates={DEFAULT_UPDATES}
|
||||
OS="macOS"
|
||||
startUpdate={action('startUpdate')}
|
||||
currentVersion="v6.0.0"
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
@ -61,6 +87,10 @@ export function QrCodeFailedToLoad(): JSX.Element {
|
|||
loadingState: LoadingState.LoadFailed,
|
||||
error: new Error('uh oh'),
|
||||
}}
|
||||
updates={DEFAULT_UPDATES}
|
||||
OS="macOS"
|
||||
startUpdate={action('startUpdate')}
|
||||
currentVersion="v6.0.0"
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
@ -73,11 +103,11 @@ export function QrCodeLoaded(): JSX.Element {
|
|||
return (
|
||||
<InstallScreenQrCodeNotScannedStep
|
||||
i18n={i18n}
|
||||
provisioningUrl={{
|
||||
loadingState: LoadingState.Loaded,
|
||||
value:
|
||||
'https://example.com/fake-signal-link?uuid=56cdd548-e595-4962-9a27-3f1e8210a959&pub_key=SW4gdGhlIHZhc3QsIGRlZXAgZm9yZXN0IG9mIEh5cnVsZS4uLg%3D%3D',
|
||||
}}
|
||||
provisioningUrl={LOADED_URL}
|
||||
updates={DEFAULT_UPDATES}
|
||||
OS="macOS"
|
||||
startUpdate={action('startUpdate')}
|
||||
currentVersion="v6.0.0"
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
@ -87,15 +117,7 @@ QrCodeLoaded.story = {
|
|||
};
|
||||
|
||||
export function SimulatedLoading(): JSX.Element {
|
||||
return (
|
||||
<Simulation
|
||||
finalResult={{
|
||||
loadingState: LoadingState.Loaded,
|
||||
value:
|
||||
'https://example.com/fake-signal-link?uuid=56cdd548-e595-4962-9a27-3f1e8210a959&pub_key=SW4gdGhlIHZhc3QsIGRlZXAgZm9yZXN0IG9mIEh5cnVsZS4uLg%3D%3D',
|
||||
}}
|
||||
/>
|
||||
);
|
||||
return <Simulation finalResult={LOADED_URL} />;
|
||||
}
|
||||
|
||||
SimulatedLoading.story = {
|
||||
|
@ -116,3 +138,42 @@ export function SimulatedFailure(): JSX.Element {
|
|||
SimulatedFailure.story = {
|
||||
name: 'Simulated failure',
|
||||
};
|
||||
|
||||
export function WithUpdateKnobs({
|
||||
dialogType,
|
||||
currentVersion,
|
||||
}: {
|
||||
dialogType: DialogType;
|
||||
currentVersion: string;
|
||||
}): JSX.Element {
|
||||
return (
|
||||
<InstallScreenQrCodeNotScannedStep
|
||||
i18n={i18n}
|
||||
provisioningUrl={LOADED_URL}
|
||||
hasExpired
|
||||
updates={{
|
||||
...DEFAULT_UPDATES,
|
||||
dialogType,
|
||||
}}
|
||||
OS="macOS"
|
||||
startUpdate={action('startUpdate')}
|
||||
currentVersion={currentVersion}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
WithUpdateKnobs.story = {
|
||||
name: 'With Update Knobs',
|
||||
argTypes: {
|
||||
dialogType: {
|
||||
control: { type: 'select' },
|
||||
defaultValue: DialogType.AutoUpdate,
|
||||
options: Object.values(DialogType),
|
||||
},
|
||||
currentVersion: {
|
||||
control: { type: 'select' },
|
||||
defaultValue: 'v6.0.0',
|
||||
options: ['v6.0.0', 'v6.1.0-beta.1'],
|
||||
},
|
||||
},
|
||||
};
|
||||
|
|
|
@ -15,15 +15,22 @@ import { Spinner } from '../Spinner';
|
|||
import { QrCode } from '../QrCode';
|
||||
import { TitlebarDragArea } from '../TitlebarDragArea';
|
||||
import { InstallScreenSignalLogo } from './InstallScreenSignalLogo';
|
||||
import { InstallScreenUpdateDialog } from './InstallScreenUpdateDialog';
|
||||
import { getClassNamesFor } from '../../util/getClassNamesFor';
|
||||
import type { UpdatesStateType } from '../../state/ducks/updates';
|
||||
|
||||
// We can't always use destructuring assignment because of the complexity of this props
|
||||
// type.
|
||||
|
||||
type PropsType = {
|
||||
type PropsType = Readonly<{
|
||||
i18n: LocalizerType;
|
||||
provisioningUrl: Loadable<string>;
|
||||
};
|
||||
hasExpired?: boolean;
|
||||
updates: UpdatesStateType;
|
||||
currentVersion: string;
|
||||
OS: string;
|
||||
startUpdate: () => void;
|
||||
}>;
|
||||
|
||||
const QR_CODE_FAILED_LINK =
|
||||
'https://support.signal.org/hc/articles/360007320451#desktop_multiple_device';
|
||||
|
@ -35,6 +42,11 @@ const getQrCodeClassName = getClassNamesFor(
|
|||
export function InstallScreenQrCodeNotScannedStep({
|
||||
i18n,
|
||||
provisioningUrl,
|
||||
hasExpired,
|
||||
updates,
|
||||
startUpdate,
|
||||
currentVersion,
|
||||
OS,
|
||||
}: Readonly<PropsType>): ReactElement {
|
||||
return (
|
||||
<div className="module-InstallScreenQrCodeNotScannedStep">
|
||||
|
@ -42,6 +54,16 @@ export function InstallScreenQrCodeNotScannedStep({
|
|||
|
||||
<InstallScreenSignalLogo />
|
||||
|
||||
{hasExpired && (
|
||||
<InstallScreenUpdateDialog
|
||||
i18n={i18n}
|
||||
{...updates}
|
||||
startUpdate={startUpdate}
|
||||
currentVersion={currentVersion}
|
||||
OS={OS}
|
||||
/>
|
||||
)}
|
||||
|
||||
<div className="module-InstallScreenQrCodeNotScannedStep__contents">
|
||||
<InstallScreenQrCode i18n={i18n} {...provisioningUrl} />
|
||||
<div className="module-InstallScreenQrCodeNotScannedStep__instructions">
|
||||
|
|
233
ts/components/installScreen/InstallScreenUpdateDialog.tsx
Normal file
233
ts/components/installScreen/InstallScreenUpdateDialog.tsx
Normal file
|
@ -0,0 +1,233 @@
|
|||
// Copyright 2023 Signal Messenger, LLC
|
||||
// SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
import React from 'react';
|
||||
import { noop } from 'lodash';
|
||||
import type { FormatXMLElementFn } from 'intl-messageformat';
|
||||
import formatFileSize from 'filesize';
|
||||
|
||||
import { DialogType } from '../../types/Dialogs';
|
||||
import type { LocalizerType } from '../../types/Util';
|
||||
import {
|
||||
PRODUCTION_DOWNLOAD_URL,
|
||||
BETA_DOWNLOAD_URL,
|
||||
UNSUPPORTED_OS_URL,
|
||||
} from '../../types/support';
|
||||
import type { UpdatesStateType } from '../../state/ducks/updates';
|
||||
import { isBeta } from '../../util/version';
|
||||
import { ConfirmationDialog } from '../ConfirmationDialog';
|
||||
import { Modal } from '../Modal';
|
||||
import { Intl } from '../Intl';
|
||||
|
||||
export type PropsType = UpdatesStateType &
|
||||
Readonly<{
|
||||
i18n: LocalizerType;
|
||||
startUpdate: () => void;
|
||||
currentVersion: string;
|
||||
OS: string;
|
||||
}>;
|
||||
|
||||
export function InstallScreenUpdateDialog({
|
||||
i18n,
|
||||
dialogType,
|
||||
downloadSize,
|
||||
downloadedSize,
|
||||
startUpdate,
|
||||
currentVersion,
|
||||
OS,
|
||||
}: PropsType): JSX.Element | null {
|
||||
const learnMoreLink: FormatXMLElementFn<JSX.Element | string> = children => (
|
||||
<a
|
||||
key="signal-support"
|
||||
href={UNSUPPORTED_OS_URL}
|
||||
rel="noreferrer"
|
||||
target="_blank"
|
||||
>
|
||||
{children}
|
||||
</a>
|
||||
);
|
||||
|
||||
const dialogName = `InstallScreenUpdateDialog.${dialogType}`;
|
||||
|
||||
if (dialogType === DialogType.UnsupportedOS) {
|
||||
return (
|
||||
<Modal
|
||||
i18n={i18n}
|
||||
modalName={dialogName}
|
||||
noMouseClose
|
||||
title={i18n('icu:InstallScreenUpdateDialog--unsupported-os__title')}
|
||||
>
|
||||
<Intl
|
||||
id="icu:UnsupportedOSErrorDialog__body"
|
||||
i18n={i18n}
|
||||
components={{
|
||||
OS,
|
||||
learnMoreLink,
|
||||
}}
|
||||
/>
|
||||
</Modal>
|
||||
);
|
||||
}
|
||||
|
||||
if (
|
||||
dialogType === DialogType.AutoUpdate ||
|
||||
// Manual update with an action button
|
||||
dialogType === DialogType.DownloadReady ||
|
||||
dialogType === DialogType.FullDownloadReady ||
|
||||
dialogType === DialogType.DownloadedUpdate
|
||||
) {
|
||||
let title = i18n('autoUpdateNewVersionTitle');
|
||||
let actionText: string | JSX.Element = i18n('autoUpdateRestartButtonLabel');
|
||||
let bodyText = i18n('icu:InstallScreenUpdateDialog--auto-update__body');
|
||||
if (
|
||||
dialogType === DialogType.DownloadReady ||
|
||||
dialogType === DialogType.FullDownloadReady
|
||||
) {
|
||||
actionText = (
|
||||
<Intl
|
||||
id="icu:InstallScreenUpdateDialog--manual-update__action"
|
||||
i18n={i18n}
|
||||
components={{
|
||||
downloadSize: (
|
||||
<span className="InstallScreenUpdateDialog__download-size">
|
||||
({formatFileSize(downloadSize ?? 0, { round: 0 })})
|
||||
</span>
|
||||
),
|
||||
}}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
if (dialogType === DialogType.DownloadedUpdate) {
|
||||
title = i18n('icu:DialogUpdate__downloaded');
|
||||
bodyText = i18n('icu:InstallScreenUpdateDialog--downloaded__body');
|
||||
}
|
||||
|
||||
return (
|
||||
<ConfirmationDialog
|
||||
i18n={i18n}
|
||||
dialogName={dialogName}
|
||||
title={title}
|
||||
noDefaultCancelButton
|
||||
actions={[
|
||||
{
|
||||
id: 'ok',
|
||||
text: actionText,
|
||||
action: startUpdate,
|
||||
style: 'affirmative',
|
||||
autoClose: false,
|
||||
},
|
||||
]}
|
||||
onClose={noop}
|
||||
>
|
||||
{bodyText}
|
||||
</ConfirmationDialog>
|
||||
);
|
||||
}
|
||||
|
||||
if (dialogType === DialogType.Downloading) {
|
||||
// Focus trap can't be used because there are no elements that can be
|
||||
// focused within the modal.
|
||||
const width = Math.ceil(
|
||||
((downloadedSize || 1) / (downloadSize || 1)) * 100
|
||||
);
|
||||
return (
|
||||
<Modal
|
||||
i18n={i18n}
|
||||
modalName={dialogName}
|
||||
noMouseClose
|
||||
useFocusTrap={false}
|
||||
title={i18n('icu:DialogUpdate__downloading')}
|
||||
>
|
||||
<div className="InstallScreenUpdateDialog__progress--container">
|
||||
<div
|
||||
className="InstallScreenUpdateDialog__progress--bar"
|
||||
style={{ transform: `translateX(${width - 100}%)` }}
|
||||
/>
|
||||
</div>
|
||||
</Modal>
|
||||
);
|
||||
}
|
||||
|
||||
if (
|
||||
dialogType === DialogType.Cannot_Update ||
|
||||
dialogType === DialogType.Cannot_Update_Require_Manual
|
||||
) {
|
||||
const url = isBeta(currentVersion)
|
||||
? BETA_DOWNLOAD_URL
|
||||
: PRODUCTION_DOWNLOAD_URL;
|
||||
const title = i18n('cannotUpdate');
|
||||
const body = (
|
||||
<Intl
|
||||
i18n={i18n}
|
||||
id="icu:InstallScreenUpdateDialog--cannot-update__body"
|
||||
components={{
|
||||
downloadUrl: (
|
||||
<a href={url} target="_blank" rel="noreferrer">
|
||||
{url}
|
||||
</a>
|
||||
),
|
||||
}}
|
||||
/>
|
||||
);
|
||||
|
||||
if (dialogType === DialogType.Cannot_Update) {
|
||||
return (
|
||||
<ConfirmationDialog
|
||||
i18n={i18n}
|
||||
dialogName={dialogName}
|
||||
moduleClassName="InstallScreenUpdateDialog"
|
||||
title={title}
|
||||
noDefaultCancelButton
|
||||
actions={[
|
||||
{
|
||||
text: i18n('autoUpdateRetry'),
|
||||
action: startUpdate,
|
||||
style: 'affirmative',
|
||||
autoClose: false,
|
||||
},
|
||||
]}
|
||||
onClose={noop}
|
||||
>
|
||||
{body}
|
||||
</ConfirmationDialog>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<Modal
|
||||
i18n={i18n}
|
||||
modalName={dialogName}
|
||||
noMouseClose
|
||||
title={title}
|
||||
moduleClassName="InstallScreenUpdateDialog"
|
||||
>
|
||||
{body}
|
||||
</Modal>
|
||||
);
|
||||
}
|
||||
|
||||
if (dialogType === DialogType.MacOS_Read_Only) {
|
||||
// No focus trap, because there are no focusable elements.
|
||||
return (
|
||||
<Modal
|
||||
i18n={i18n}
|
||||
modalName={dialogName}
|
||||
noMouseClose
|
||||
useFocusTrap={false}
|
||||
title={i18n('cannotUpdate')}
|
||||
>
|
||||
<Intl
|
||||
components={{
|
||||
app: <strong key="app">Signal.app</strong>,
|
||||
folder: <strong key="folder">/Applications</strong>,
|
||||
}}
|
||||
i18n={i18n}
|
||||
id="readOnlyVolume"
|
||||
/>
|
||||
</Modal>
|
||||
);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
|
@ -3,6 +3,8 @@
|
|||
|
||||
import type { ThunkAction } from 'redux-thunk';
|
||||
import type { ReadonlyDeep } from 'type-fest';
|
||||
import type { BoundActionCreatorsMapObject } from '../../hooks/useBoundActions';
|
||||
import { useBoundActions } from '../../hooks/useBoundActions';
|
||||
import * as updateIpc from '../../shims/updateIpc';
|
||||
import { DialogType } from '../../types/Dialogs';
|
||||
import { DAY } from '../../util/durations';
|
||||
|
@ -140,6 +142,10 @@ export const actions = {
|
|||
startUpdate,
|
||||
};
|
||||
|
||||
export const useUpdatesActions = (): BoundActionCreatorsMapObject<
|
||||
typeof actions
|
||||
> => useBoundActions(actions);
|
||||
|
||||
// Reducer
|
||||
|
||||
export function getEmptyState(): UpdatesStateType {
|
||||
|
|
|
@ -8,7 +8,7 @@ import { DialogType } from '../../types/Dialogs';
|
|||
import type { StateType } from '../reducer';
|
||||
import type { UpdatesStateType } from '../ducks/updates';
|
||||
|
||||
const getUpdatesState = (state: Readonly<StateType>): UpdatesStateType =>
|
||||
export const getUpdatesState = (state: Readonly<StateType>): UpdatesStateType =>
|
||||
state.updates;
|
||||
|
||||
export const isUpdateDialogVisible = createSelector(
|
||||
|
|
|
@ -6,6 +6,9 @@ import React, { useCallback, useEffect, useRef, useState } from 'react';
|
|||
import { useSelector } from 'react-redux';
|
||||
|
||||
import { getIntl } from '../selectors/user';
|
||||
import { getUpdatesState } from '../selectors/updates';
|
||||
import { useUpdatesActions } from '../ducks/updates';
|
||||
import { hasExpired as hasExpiredSelector } from '../selectors/expiration';
|
||||
|
||||
import * as log from '../../logging/log';
|
||||
import type { Loadable } from '../../util/loadable';
|
||||
|
@ -23,6 +26,7 @@ import { HTTPError } from '../../textsecure/Errors';
|
|||
import { isRecord } from '../../util/isRecord';
|
||||
import * as Errors from '../../types/errors';
|
||||
import { normalizeDeviceName } from '../../util/normalizeDeviceName';
|
||||
import { getName as getOSName } from '../../OS';
|
||||
|
||||
type PropsType = ComponentProps<typeof InstallScreen>;
|
||||
|
||||
|
@ -71,6 +75,9 @@ function getInstallError(err: unknown): InstallError {
|
|||
|
||||
export function SmartInstallScreen(): ReactElement {
|
||||
const i18n = useSelector(getIntl);
|
||||
const updates = useSelector(getUpdatesState);
|
||||
const { startUpdate } = useUpdatesActions();
|
||||
const hasExpired = useSelector(hasExpiredSelector);
|
||||
|
||||
const chooseDeviceNamePromiseWrapperRef = useRef(explodePromise<string>());
|
||||
|
||||
|
@ -246,6 +253,11 @@ export function SmartInstallScreen(): ReactElement {
|
|||
screenSpecificProps: {
|
||||
i18n,
|
||||
provisioningUrl: state.provisioningUrl,
|
||||
hasExpired,
|
||||
updates,
|
||||
currentVersion: window.getVersion(),
|
||||
startUpdate,
|
||||
OS: getOSName(),
|
||||
},
|
||||
};
|
||||
break;
|
||||
|
|
7
ts/types/support.ts
Normal file
7
ts/types/support.ts
Normal file
|
@ -0,0 +1,7 @@
|
|||
// Copyright 2023 Signal Messenger, LLC
|
||||
// SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
export const PRODUCTION_DOWNLOAD_URL = 'https://signal.org/download/';
|
||||
export const BETA_DOWNLOAD_URL = 'https://support.signal.org/beta';
|
||||
export const UNSUPPORTED_OS_URL =
|
||||
'https://support.signal.org/hc/articles/5109141421850';
|
Loading…
Add table
Add a link
Reference in a new issue