Unsupported OS Dialog
This commit is contained in:
parent
c6e184016b
commit
ac50af52d2
44 changed files with 776 additions and 224 deletions
|
@ -43,6 +43,7 @@ type PropsType = {
|
|||
onUndoArchive: (conversationId: string) => unknown;
|
||||
openFileInFolder: (target: string) => unknown;
|
||||
hasCustomTitleBar: boolean;
|
||||
OS: string;
|
||||
osClassName: string;
|
||||
hideMenuBar: boolean;
|
||||
|
||||
|
@ -76,6 +77,7 @@ export function App({
|
|||
onUndoArchive,
|
||||
openFileInFolder,
|
||||
openInbox,
|
||||
OS,
|
||||
osClassName,
|
||||
registerSingleDevice,
|
||||
renderCallManager,
|
||||
|
@ -173,6 +175,7 @@ export function App({
|
|||
})}
|
||||
>
|
||||
<ToastManager
|
||||
OS={OS}
|
||||
hideToast={hideToast}
|
||||
i18n={i18n}
|
||||
onUndoArchive={onUndoArchive}
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
// SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
import * as React from 'react';
|
||||
import { boolean, select } from '@storybook/addon-knobs';
|
||||
import { select } from '@storybook/addon-knobs';
|
||||
|
||||
import { DialogExpiredBuild } from './DialogExpiredBuild';
|
||||
import { setupI18n } from '../util/setupI18n';
|
||||
|
@ -22,13 +22,11 @@ export const _DialogExpiredBuild = (): JSX.Element => {
|
|||
WidthBreakpoint,
|
||||
WidthBreakpoint.Wide
|
||||
);
|
||||
const hasExpired = boolean('hasExpired', true);
|
||||
|
||||
return (
|
||||
<FakeLeftPaneContainer containerWidthBreakpoint={containerWidthBreakpoint}>
|
||||
<DialogExpiredBuild
|
||||
containerWidthBreakpoint={containerWidthBreakpoint}
|
||||
hasExpired={hasExpired}
|
||||
i18n={i18n}
|
||||
/>
|
||||
</FakeLeftPaneContainer>
|
||||
|
|
|
@ -9,21 +9,15 @@ import type { WidthBreakpoint } from './_util';
|
|||
import { LeftPaneDialog } from './LeftPaneDialog';
|
||||
import { openLinkInWebBrowser } from '../util/openLinkInWebBrowser';
|
||||
|
||||
type PropsType = {
|
||||
export type PropsType = {
|
||||
containerWidthBreakpoint: WidthBreakpoint;
|
||||
hasExpired: boolean;
|
||||
i18n: LocalizerType;
|
||||
};
|
||||
|
||||
export function DialogExpiredBuild({
|
||||
containerWidthBreakpoint,
|
||||
hasExpired,
|
||||
i18n,
|
||||
}: PropsType): JSX.Element | null {
|
||||
if (!hasExpired) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<LeftPaneDialog
|
||||
containerWidthBreakpoint={containerWidthBreakpoint}
|
||||
|
|
|
@ -13,16 +13,14 @@ import { clearTimeoutIfNecessary } from '../util/clearTimeoutIfNecessary';
|
|||
|
||||
const FIVE_SECONDS = 5 * 1000;
|
||||
|
||||
export type PropsType = NetworkStateType & {
|
||||
export type PropsType = Pick<NetworkStateType, 'isOnline' | 'socketStatus'> & {
|
||||
containerWidthBreakpoint: WidthBreakpoint;
|
||||
hasNetworkDialog: boolean;
|
||||
i18n: LocalizerType;
|
||||
manualReconnect: () => void;
|
||||
};
|
||||
|
||||
export function DialogNetworkStatus({
|
||||
containerWidthBreakpoint,
|
||||
hasNetworkDialog,
|
||||
i18n,
|
||||
isOnline,
|
||||
socketStatus,
|
||||
|
@ -32,10 +30,6 @@ export function DialogNetworkStatus({
|
|||
socketStatus === SocketStatus.CONNECTING
|
||||
);
|
||||
useEffect(() => {
|
||||
if (!hasNetworkDialog) {
|
||||
return () => null;
|
||||
}
|
||||
|
||||
let timeout: NodeJS.Timeout;
|
||||
|
||||
if (isConnecting) {
|
||||
|
@ -47,17 +41,13 @@ export function DialogNetworkStatus({
|
|||
return () => {
|
||||
clearTimeoutIfNecessary(timeout);
|
||||
};
|
||||
}, [hasNetworkDialog, isConnecting, setIsConnecting]);
|
||||
}, [isConnecting, setIsConnecting]);
|
||||
|
||||
const reconnect = () => {
|
||||
setIsConnecting(true);
|
||||
manualReconnect();
|
||||
};
|
||||
|
||||
if (!hasNetworkDialog) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (isConnecting) {
|
||||
const spinner = (
|
||||
<div className="LeftPaneDialog__spinner-container">
|
||||
|
|
|
@ -2,7 +2,6 @@
|
|||
// SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
import * as React from 'react';
|
||||
import { boolean } from '@storybook/addon-knobs';
|
||||
import { action } from '@storybook/addon-actions';
|
||||
|
||||
import { DialogRelink } from './DialogRelink';
|
||||
|
@ -16,7 +15,6 @@ const i18n = setupI18n('en', enMessages);
|
|||
const defaultProps = {
|
||||
containerWidthBreakpoint: WidthBreakpoint.Wide,
|
||||
i18n,
|
||||
isRegistrationDone: true,
|
||||
relinkDevice: action('relink-device'),
|
||||
};
|
||||
|
||||
|
@ -25,14 +23,12 @@ const permutations = [
|
|||
title: 'Unlinked (wide container)',
|
||||
props: {
|
||||
containerWidthBreakpoint: WidthBreakpoint.Wide,
|
||||
isRegistrationDone: false,
|
||||
},
|
||||
},
|
||||
{
|
||||
title: 'Unlinked (narrow container)',
|
||||
props: {
|
||||
containerWidthBreakpoint: WidthBreakpoint.Narrow,
|
||||
isRegistrationDone: false,
|
||||
},
|
||||
},
|
||||
];
|
||||
|
@ -41,14 +37,6 @@ export default {
|
|||
title: 'Components/DialogRelink',
|
||||
};
|
||||
|
||||
export function KnobsPlayground(): JSX.Element {
|
||||
const isRegistrationDone = boolean('isRegistrationDone', false);
|
||||
|
||||
return (
|
||||
<DialogRelink {...defaultProps} isRegistrationDone={isRegistrationDone} />
|
||||
);
|
||||
}
|
||||
|
||||
export function Iterations(): JSX.Element {
|
||||
return (
|
||||
<>
|
||||
|
|
|
@ -11,20 +11,14 @@ import { LeftPaneDialog } from './LeftPaneDialog';
|
|||
export type PropsType = {
|
||||
containerWidthBreakpoint: WidthBreakpoint;
|
||||
i18n: LocalizerType;
|
||||
isRegistrationDone: boolean;
|
||||
relinkDevice: () => void;
|
||||
};
|
||||
|
||||
export function DialogRelink({
|
||||
containerWidthBreakpoint,
|
||||
i18n,
|
||||
isRegistrationDone,
|
||||
relinkDevice,
|
||||
}: PropsType): JSX.Element | null {
|
||||
if (isRegistrationDone) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<LeftPaneDialog
|
||||
containerWidthBreakpoint={containerWidthBreakpoint}
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
// SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
import * as React from 'react';
|
||||
import { boolean, select } from '@storybook/addon-knobs';
|
||||
import { select } from '@storybook/addon-knobs';
|
||||
import { action } from '@storybook/addon-actions';
|
||||
import { DialogUpdate } from './DialogUpdate';
|
||||
import { DialogType } from '../types/Dialogs';
|
||||
|
@ -20,9 +20,7 @@ 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'),
|
||||
|
@ -40,8 +38,6 @@ export function KnobsPlayground(): JSX.Element {
|
|||
WidthBreakpoint.Wide
|
||||
);
|
||||
const dialogType = select('dialogType', DialogType, DialogType.Update);
|
||||
const hasNetworkDialog = boolean('hasNetworkDialog', false);
|
||||
const didSnooze = boolean('didSnooze', false);
|
||||
|
||||
return (
|
||||
<FakeLeftPaneContainer containerWidthBreakpoint={containerWidthBreakpoint}>
|
||||
|
@ -49,8 +45,6 @@ export function KnobsPlayground(): JSX.Element {
|
|||
{...defaultProps}
|
||||
containerWidthBreakpoint={containerWidthBreakpoint}
|
||||
dialogType={dialogType}
|
||||
didSnooze={didSnooze}
|
||||
hasNetworkDialog={hasNetworkDialog}
|
||||
currentVersion="5.24.0"
|
||||
/>
|
||||
</FakeLeftPaneContainer>
|
||||
|
@ -231,6 +225,23 @@ MacOSReadOnlyWide.story = {
|
|||
name: 'MacOS_Read_Only (Wide)',
|
||||
};
|
||||
|
||||
export function UnsupportedOSWide(): JSX.Element {
|
||||
return (
|
||||
<FakeLeftPaneContainer containerWidthBreakpoint={WidthBreakpoint.Wide}>
|
||||
<DialogUpdate
|
||||
{...defaultProps}
|
||||
containerWidthBreakpoint={WidthBreakpoint.Wide}
|
||||
currentVersion="5.24.0"
|
||||
dialogType={DialogType.UnsupportedOS}
|
||||
/>
|
||||
</FakeLeftPaneContainer>
|
||||
);
|
||||
}
|
||||
|
||||
UnsupportedOSWide.story = {
|
||||
name: 'UnsupportedOS (Wide)',
|
||||
};
|
||||
|
||||
export function UpdateNarrow(): JSX.Element {
|
||||
return (
|
||||
<FakeLeftPaneContainer containerWidthBreakpoint={WidthBreakpoint.Narrow}>
|
||||
|
@ -385,3 +396,20 @@ export function MacOSReadOnlyNarrow(): JSX.Element {
|
|||
MacOSReadOnlyNarrow.story = {
|
||||
name: 'MacOS_Read_Only (Narrow)',
|
||||
};
|
||||
|
||||
export function UnsupportedOSNarrow(): JSX.Element {
|
||||
return (
|
||||
<FakeLeftPaneContainer containerWidthBreakpoint={WidthBreakpoint.Narrow}>
|
||||
<DialogUpdate
|
||||
{...defaultProps}
|
||||
containerWidthBreakpoint={WidthBreakpoint.Narrow}
|
||||
currentVersion="5.24.0"
|
||||
dialogType={DialogType.UnsupportedOS}
|
||||
/>
|
||||
</FakeLeftPaneContainer>
|
||||
);
|
||||
}
|
||||
|
||||
UnsupportedOSNarrow.story = {
|
||||
name: 'UnsupportedOS (Narrow)',
|
||||
};
|
||||
|
|
|
@ -14,13 +14,10 @@ import type { WidthBreakpoint } from './_util';
|
|||
export type PropsType = {
|
||||
containerWidthBreakpoint: WidthBreakpoint;
|
||||
dialogType: DialogType;
|
||||
didSnooze: boolean;
|
||||
dismissDialog: () => void;
|
||||
downloadSize?: number;
|
||||
downloadedSize?: number;
|
||||
hasNetworkDialog: boolean;
|
||||
i18n: LocalizerType;
|
||||
showEventsCount: number;
|
||||
snoozeUpdate: () => void;
|
||||
startUpdate: () => void;
|
||||
version?: string;
|
||||
|
@ -33,29 +30,15 @@ const BETA_DOWNLOAD_URL = 'https://support.signal.org/beta';
|
|||
export function DialogUpdate({
|
||||
containerWidthBreakpoint,
|
||||
dialogType,
|
||||
didSnooze,
|
||||
dismissDialog,
|
||||
downloadSize,
|
||||
downloadedSize,
|
||||
hasNetworkDialog,
|
||||
i18n,
|
||||
snoozeUpdate,
|
||||
startUpdate,
|
||||
version,
|
||||
currentVersion,
|
||||
}: PropsType): JSX.Element | null {
|
||||
if (hasNetworkDialog) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (dialogType === DialogType.None) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (didSnooze) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (dialogType === DialogType.Cannot_Update) {
|
||||
const url = isBeta(currentVersion)
|
||||
? BETA_DOWNLOAD_URL
|
||||
|
@ -174,6 +157,11 @@ export function DialogUpdate({
|
|||
);
|
||||
}
|
||||
|
||||
if (dialogType === DialogType.UnsupportedOS) {
|
||||
// Displayed as UnsupportedOSDialog in LeftPane
|
||||
return null;
|
||||
}
|
||||
|
||||
let title = i18n('autoUpdateNewVersionTitle');
|
||||
|
||||
if (
|
||||
|
|
|
@ -4,18 +4,27 @@
|
|||
import * as React from 'react';
|
||||
|
||||
import { action } from '@storybook/addon-actions';
|
||||
import { select } from '@storybook/addon-knobs';
|
||||
import { boolean, select } from '@storybook/addon-knobs';
|
||||
|
||||
import type { PropsType } from './LeftPane';
|
||||
import { LeftPane, LeftPaneMode } from './LeftPane';
|
||||
import { CaptchaDialog } from './CaptchaDialog';
|
||||
import { CrashReportDialog } from './CrashReportDialog';
|
||||
import type { PropsType as DialogNetworkStatusPropsType } from './DialogNetworkStatus';
|
||||
import { DialogExpiredBuild } from './DialogExpiredBuild';
|
||||
import { DialogNetworkStatus } from './DialogNetworkStatus';
|
||||
import { DialogRelink } from './DialogRelink';
|
||||
import type { PropsType as DialogUpdatePropsType } from './DialogUpdate';
|
||||
import { DialogUpdate } from './DialogUpdate';
|
||||
import { UnsupportedOSDialog } from './UnsupportedOSDialog';
|
||||
import type { ConversationType } from '../state/ducks/conversations';
|
||||
import { MessageSearchResult } from './conversationList/MessageSearchResult';
|
||||
import { setupI18n } from '../util/setupI18n';
|
||||
import { DurationInSeconds } from '../util/durations';
|
||||
import { DurationInSeconds, DAY } from '../util/durations';
|
||||
import enMessages from '../../_locales/en/messages.json';
|
||||
import { ThemeType } from '../types/Util';
|
||||
import { DialogType } from '../types/Dialogs';
|
||||
import { SocketStatus } from '../types/SocketStatus';
|
||||
import { getDefaultConversation } from '../test-both/helpers/getDefaultConversation';
|
||||
import { StorybookThemeContext } from '../../.storybook/StorybookThemeContext';
|
||||
import {
|
||||
|
@ -25,6 +34,11 @@ import {
|
|||
|
||||
const i18n = setupI18n('en', enMessages);
|
||||
|
||||
type OverridePropsType = Partial<PropsType> & {
|
||||
dialogNetworkStatus?: Partial<DialogNetworkStatusPropsType>;
|
||||
dialogUpdate?: Partial<DialogUpdatePropsType>;
|
||||
};
|
||||
|
||||
export default {
|
||||
title: 'Components/LeftPane',
|
||||
};
|
||||
|
@ -95,7 +109,7 @@ const defaultModeSpecificProps = {
|
|||
|
||||
const emptySearchResultsGroup = { isLoading: false, results: [] };
|
||||
|
||||
const useProps = (overrideProps: Partial<PropsType> = {}): PropsType => {
|
||||
const useProps = (overrideProps: OverridePropsType = {}): PropsType => {
|
||||
let modeSpecificProps =
|
||||
overrideProps.modeSpecificProps ?? defaultModeSpecificProps;
|
||||
|
||||
|
@ -112,6 +126,8 @@ const useProps = (overrideProps: Partial<PropsType> = {}): PropsType => {
|
|||
};
|
||||
}
|
||||
|
||||
const isUpdateDownloaded = boolean('isUpdateDownloaded', false);
|
||||
|
||||
return {
|
||||
clearConversationSearch: action('clearConversationSearch'),
|
||||
clearGroupCreationError: action('clearGroupCreationError'),
|
||||
|
@ -124,6 +140,7 @@ const useProps = (overrideProps: Partial<PropsType> = {}): PropsType => {
|
|||
createGroup: action('createGroup'),
|
||||
getPreferredBadge: () => undefined,
|
||||
i18n,
|
||||
isMacOS: boolean('isMacOS', false),
|
||||
preferredWidthFromStorage: 320,
|
||||
regionCode: 'US',
|
||||
challengeStatus: select(
|
||||
|
@ -132,12 +149,23 @@ const useProps = (overrideProps: Partial<PropsType> = {}): PropsType => {
|
|||
'idle'
|
||||
),
|
||||
crashReportCount: select('challengeReportCount', [0, 1], 0),
|
||||
|
||||
hasNetworkDialog: boolean('hasNetworkDialog', false),
|
||||
hasExpiredDialog: boolean('hasExpiredDialog', false),
|
||||
hasRelinkDialog: boolean('hasRelinkDialog', false),
|
||||
hasUpdateDialog: boolean('hasUpdateDialog', false),
|
||||
unsupportedOSDialogType: select(
|
||||
'unsupportedOSDialogType',
|
||||
['error', 'warning', undefined],
|
||||
undefined
|
||||
),
|
||||
isUpdateDownloaded,
|
||||
|
||||
setChallengeStatus: action('setChallengeStatus'),
|
||||
lookupConversationWithoutUuid: makeFakeLookupConversationWithoutUuid(),
|
||||
showUserNotFoundModal: action('showUserNotFoundModal'),
|
||||
setIsFetchingUUID,
|
||||
showConversation: action('showConversation'),
|
||||
renderExpiredBuildDialog: () => <div />,
|
||||
renderMainHeader: () => <div />,
|
||||
renderMessageSearchResult: (id: string) => (
|
||||
<MessageSearchResult
|
||||
|
@ -155,9 +183,39 @@ const useProps = (overrideProps: Partial<PropsType> = {}): PropsType => {
|
|||
to={defaultConversations[1]}
|
||||
/>
|
||||
),
|
||||
renderNetworkStatus: () => <div />,
|
||||
renderRelinkDialog: () => <div />,
|
||||
renderUpdateDialog: () => <div />,
|
||||
|
||||
renderNetworkStatus: props => (
|
||||
<DialogNetworkStatus
|
||||
i18n={i18n}
|
||||
socketStatus={SocketStatus.CLOSED}
|
||||
isOnline={false}
|
||||
manualReconnect={action('manualReconnect')}
|
||||
{...overrideProps.dialogNetworkStatus}
|
||||
{...props}
|
||||
/>
|
||||
),
|
||||
renderRelinkDialog: props => (
|
||||
<DialogRelink
|
||||
i18n={i18n}
|
||||
relinkDevice={action('relinkDevice')}
|
||||
{...props}
|
||||
/>
|
||||
),
|
||||
renderUpdateDialog: props => (
|
||||
<DialogUpdate
|
||||
i18n={i18n}
|
||||
dialogType={
|
||||
isUpdateDownloaded ? DialogType.Update : DialogType.DownloadReady
|
||||
}
|
||||
dismissDialog={action('dismissUpdate')}
|
||||
snoozeUpdate={action('snoozeUpdate')}
|
||||
startUpdate={action('startUpdate')}
|
||||
currentVersion="1.0.0"
|
||||
{...overrideProps.dialogUpdate}
|
||||
{...props}
|
||||
/>
|
||||
),
|
||||
|
||||
renderCaptchaDialog: () => (
|
||||
<CaptchaDialog
|
||||
i18n={i18n}
|
||||
|
@ -174,6 +232,15 @@ const useProps = (overrideProps: Partial<PropsType> = {}): PropsType => {
|
|||
eraseCrashReports={action('eraseCrashReports')}
|
||||
/>
|
||||
),
|
||||
renderExpiredBuildDialog: props => <DialogExpiredBuild {...props} />,
|
||||
renderUnsupportedOSDialog: props => (
|
||||
<UnsupportedOSDialog
|
||||
i18n={i18n}
|
||||
OS="macOS"
|
||||
expirationTimestamp={Date.now() + 5 * DAY}
|
||||
{...props}
|
||||
/>
|
||||
),
|
||||
selectedConversationId: undefined,
|
||||
selectedMessageId: undefined,
|
||||
savePreferredLeftPaneWidth: action('savePreferredLeftPaneWidth'),
|
||||
|
|
|
@ -22,7 +22,6 @@ import { LeftPaneChooseGroupMembersHelper } from './leftPane/LeftPaneChooseGroup
|
|||
import type { LeftPaneSetGroupMetadataPropsType } from './leftPane/LeftPaneSetGroupMetadataHelper';
|
||||
import { LeftPaneSetGroupMetadataHelper } from './leftPane/LeftPaneSetGroupMetadataHelper';
|
||||
|
||||
import * as OS from '../OS';
|
||||
import type { LocalizerType, ThemeType } from '../types/Util';
|
||||
import { ScrollBehavior } from '../types/Util';
|
||||
import type { PreferredBadgeSelectorType } from '../state/selectors/badges';
|
||||
|
@ -41,9 +40,11 @@ import {
|
|||
} from '../util/leftPaneWidth';
|
||||
import type { LookupConversationWithoutUuidActionsType } from '../util/lookupConversationWithoutUuid';
|
||||
import type { ShowConversationType } from '../state/ducks/conversations';
|
||||
import type { PropsType as UnsupportedOSDialogPropsType } from '../state/smart/UnsupportedOSDialog';
|
||||
|
||||
import { ConversationList } from './ConversationList';
|
||||
import { ContactCheckboxDisabledReason } from './conversationList/ContactCheckbox';
|
||||
import type { PropsType as DialogExpiredBuildPropsType } from './DialogExpiredBuild';
|
||||
|
||||
import type {
|
||||
DeleteAvatarFromDiskActionType,
|
||||
|
@ -61,6 +62,13 @@ export enum LeftPaneMode {
|
|||
}
|
||||
|
||||
export type PropsType = {
|
||||
hasExpiredDialog: boolean;
|
||||
hasNetworkDialog: boolean;
|
||||
hasRelinkDialog: boolean;
|
||||
hasUpdateDialog: boolean;
|
||||
isUpdateDownloaded: boolean;
|
||||
unsupportedOSDialogType: 'error' | 'warning' | undefined;
|
||||
|
||||
// These help prevent invalid states. For example, we don't need the list of pinned
|
||||
// conversations if we're trying to start a new conversation. Ideally these would be
|
||||
// at the top level, but this is not supported by react-redux + TypeScript.
|
||||
|
@ -85,6 +93,7 @@ export type PropsType = {
|
|||
} & LeftPaneSetGroupMetadataPropsType);
|
||||
getPreferredBadge: PreferredBadgeSelectorType;
|
||||
i18n: LocalizerType;
|
||||
isMacOS: boolean;
|
||||
preferredWidthFromStorage: number;
|
||||
selectedConversationId: undefined | string;
|
||||
selectedMessageId: undefined | string;
|
||||
|
@ -122,14 +131,14 @@ export type PropsType = {
|
|||
updateSearchTerm: (_: string) => void;
|
||||
|
||||
// Render Props
|
||||
renderExpiredBuildDialog: (
|
||||
_: Readonly<{ containerWidthBreakpoint: WidthBreakpoint }>
|
||||
) => JSX.Element;
|
||||
renderMainHeader: () => JSX.Element;
|
||||
renderMessageSearchResult: (id: string) => JSX.Element;
|
||||
renderNetworkStatus: (
|
||||
_: Readonly<{ containerWidthBreakpoint: WidthBreakpoint }>
|
||||
) => JSX.Element;
|
||||
renderUnsupportedOSDialog: (
|
||||
_: Readonly<UnsupportedOSDialogPropsType>
|
||||
) => JSX.Element;
|
||||
renderRelinkDialog: (
|
||||
_: Readonly<{ containerWidthBreakpoint: WidthBreakpoint }>
|
||||
) => JSX.Element;
|
||||
|
@ -138,6 +147,7 @@ export type PropsType = {
|
|||
) => JSX.Element;
|
||||
renderCaptchaDialog: (props: { onSkip(): void }) => JSX.Element;
|
||||
renderCrashReportDialog: () => JSX.Element;
|
||||
renderExpiredBuildDialog: (_: DialogExpiredBuildPropsType) => JSX.Element;
|
||||
} & LookupConversationWithoutUuidActionsType;
|
||||
|
||||
export function LeftPane({
|
||||
|
@ -153,8 +163,14 @@ export function LeftPane({
|
|||
crashReportCount,
|
||||
createGroup,
|
||||
getPreferredBadge,
|
||||
hasExpiredDialog,
|
||||
hasNetworkDialog,
|
||||
hasRelinkDialog,
|
||||
hasUpdateDialog,
|
||||
i18n,
|
||||
lookupConversationWithoutUuid,
|
||||
isMacOS,
|
||||
isUpdateDownloaded,
|
||||
modeSpecificProps,
|
||||
preferredWidthFromStorage,
|
||||
renderCaptchaDialog,
|
||||
|
@ -163,6 +179,7 @@ export function LeftPane({
|
|||
renderMainHeader,
|
||||
renderMessageSearchResult,
|
||||
renderNetworkStatus,
|
||||
renderUnsupportedOSDialog,
|
||||
renderRelinkDialog,
|
||||
renderUpdateDialog,
|
||||
savePreferredLeftPaneWidth,
|
||||
|
@ -186,6 +203,7 @@ export function LeftPane({
|
|||
theme,
|
||||
toggleComposeEditingAvatar,
|
||||
toggleConversationInChooseMembers,
|
||||
unsupportedOSDialogType,
|
||||
updateSearchTerm,
|
||||
}: PropsType): JSX.Element {
|
||||
const [preferredWidth, setPreferredWidth] = useState(
|
||||
|
@ -289,7 +307,7 @@ export function LeftPane({
|
|||
useEffect(() => {
|
||||
const onKeyDown = (event: KeyboardEvent) => {
|
||||
const { ctrlKey, shiftKey, altKey, metaKey } = event;
|
||||
const commandOrCtrl = OS.isMacOS() ? metaKey : ctrlKey;
|
||||
const commandOrCtrl = isMacOS ? metaKey : ctrlKey;
|
||||
const key = KeyboardLayout.lookup(event);
|
||||
|
||||
if (key === 'Escape') {
|
||||
|
@ -383,6 +401,7 @@ export function LeftPane({
|
|||
}, [
|
||||
clearSearch,
|
||||
helper,
|
||||
isMacOS,
|
||||
searchInConversation,
|
||||
selectedConversationId,
|
||||
selectedMessageId,
|
||||
|
@ -527,6 +546,60 @@ export function LeftPane({
|
|||
|
||||
const widthBreakpoint = getConversationListWidthBreakpoint(width);
|
||||
|
||||
const commonDialogProps = {
|
||||
i18n,
|
||||
containerWidthBreakpoint: widthBreakpoint,
|
||||
};
|
||||
|
||||
// Yellow dialogs
|
||||
let maybeYellowDialog: JSX.Element | undefined;
|
||||
|
||||
if (unsupportedOSDialogType === 'warning') {
|
||||
maybeYellowDialog = renderUnsupportedOSDialog({
|
||||
type: 'warning',
|
||||
...commonDialogProps,
|
||||
});
|
||||
} else if (hasNetworkDialog) {
|
||||
maybeYellowDialog = renderNetworkStatus(commonDialogProps);
|
||||
} else if (hasRelinkDialog) {
|
||||
maybeYellowDialog = renderRelinkDialog(commonDialogProps);
|
||||
}
|
||||
|
||||
// Update dialog
|
||||
let maybeUpdateDialog: JSX.Element | undefined;
|
||||
if (hasUpdateDialog && (!hasNetworkDialog || isUpdateDownloaded)) {
|
||||
maybeUpdateDialog = renderUpdateDialog(commonDialogProps);
|
||||
}
|
||||
|
||||
// Red dialogs
|
||||
let maybeRedDialog: JSX.Element | undefined;
|
||||
if (unsupportedOSDialogType === 'error') {
|
||||
maybeRedDialog = renderUnsupportedOSDialog({
|
||||
type: 'error',
|
||||
...commonDialogProps,
|
||||
});
|
||||
} else if (hasExpiredDialog) {
|
||||
maybeRedDialog = renderExpiredBuildDialog(commonDialogProps);
|
||||
}
|
||||
|
||||
const dialogs = new Array<{ key: string; dialog: JSX.Element }>();
|
||||
|
||||
if (maybeRedDialog) {
|
||||
dialogs.push({ key: 'red', dialog: maybeRedDialog });
|
||||
if (maybeUpdateDialog) {
|
||||
dialogs.push({ key: 'update', dialog: maybeUpdateDialog });
|
||||
} else if (maybeYellowDialog) {
|
||||
dialogs.push({ key: 'yellow', dialog: maybeYellowDialog });
|
||||
}
|
||||
} else {
|
||||
if (maybeUpdateDialog) {
|
||||
dialogs.push({ key: 'update', dialog: maybeUpdateDialog });
|
||||
}
|
||||
if (maybeYellowDialog) {
|
||||
dialogs.push({ key: 'yellow', dialog: maybeYellowDialog });
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<div
|
||||
className={classNames(
|
||||
|
@ -556,12 +629,9 @@ export function LeftPane({
|
|||
showConversation,
|
||||
})}
|
||||
<div className="module-left-pane__dialogs">
|
||||
{renderExpiredBuildDialog({
|
||||
containerWidthBreakpoint: widthBreakpoint,
|
||||
})}
|
||||
{renderRelinkDialog({ containerWidthBreakpoint: widthBreakpoint })}
|
||||
{renderNetworkStatus({ containerWidthBreakpoint: widthBreakpoint })}
|
||||
{renderUpdateDialog({ containerWidthBreakpoint: widthBreakpoint })}
|
||||
{dialogs.map(({ key, dialog }) => (
|
||||
<React.Fragment key={key}>{dialog}</React.Fragment>
|
||||
))}
|
||||
</div>
|
||||
{preRowsNode && <React.Fragment key={0}>{preRowsNode}</React.Fragment>}
|
||||
<Measure bounds>
|
||||
|
|
|
@ -25,6 +25,9 @@ export default {
|
|||
toast: {
|
||||
defaultValue: undefined,
|
||||
},
|
||||
OS: {
|
||||
defaultValue: 'macOS',
|
||||
},
|
||||
},
|
||||
} as Meta;
|
||||
|
||||
|
@ -327,6 +330,13 @@ UnsupportedMultiAttachment.args = {
|
|||
},
|
||||
};
|
||||
|
||||
export const UnsupportedOS = Template.bind({});
|
||||
UnsupportedOS.args = {
|
||||
toast: {
|
||||
toastType: ToastType.UnsupportedOS,
|
||||
},
|
||||
};
|
||||
|
||||
export const UserAddedToGroup = Template.bind({});
|
||||
UserAddedToGroup.args = {
|
||||
toast: {
|
||||
|
|
|
@ -12,6 +12,7 @@ export type PropsType = {
|
|||
hideToast: () => unknown;
|
||||
i18n: LocalizerType;
|
||||
openFileInFolder: (target: string) => unknown;
|
||||
OS: string;
|
||||
onUndoArchive: (conversaetionId: string) => unknown;
|
||||
toast?: {
|
||||
toastType: ToastType;
|
||||
|
@ -26,6 +27,7 @@ export function ToastManager({
|
|||
i18n,
|
||||
openFileInFolder,
|
||||
onUndoArchive,
|
||||
OS,
|
||||
toast,
|
||||
}: PropsType): JSX.Element | null {
|
||||
if (toast === undefined) {
|
||||
|
@ -320,6 +322,14 @@ export function ToastManager({
|
|||
);
|
||||
}
|
||||
|
||||
if (toastType === ToastType.UnsupportedOS) {
|
||||
return (
|
||||
<Toast onClose={hideToast}>
|
||||
{i18n('icu:UnsupportedOSErrorToast', { OS })}
|
||||
</Toast>
|
||||
);
|
||||
}
|
||||
|
||||
if (toastType === ToastType.UserAddedToGroup) {
|
||||
return (
|
||||
<Toast onClose={hideToast}>
|
||||
|
|
79
ts/components/UnsupportedOSDialog.stories.tsx
Normal file
79
ts/components/UnsupportedOSDialog.stories.tsx
Normal file
|
@ -0,0 +1,79 @@
|
|||
// Copyright 2023 Signal Messenger, LLC
|
||||
// SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
import * as React from 'react';
|
||||
|
||||
import { UnsupportedOSDialog } from './UnsupportedOSDialog';
|
||||
import type { PropsType } from './UnsupportedOSDialog';
|
||||
import { setupI18n } from '../util/setupI18n';
|
||||
import { DAY } from '../util/durations';
|
||||
import enMessages from '../../_locales/en/messages.json';
|
||||
import { WidthBreakpoint } from './_util';
|
||||
import { FakeLeftPaneContainer } from '../test-both/helpers/FakeLeftPaneContainer';
|
||||
|
||||
const i18n = setupI18n('en', enMessages);
|
||||
|
||||
const defaultProps: PropsType = {
|
||||
containerWidthBreakpoint: WidthBreakpoint.Wide,
|
||||
OS: 'macOS',
|
||||
expirationTimestamp: Date.now() + 5 * DAY,
|
||||
i18n,
|
||||
type: 'warning',
|
||||
};
|
||||
|
||||
const permutations: ReadonlyArray<{
|
||||
title: string;
|
||||
props: Partial<PropsType>;
|
||||
}> = [
|
||||
{
|
||||
title: 'Warning (wide container)',
|
||||
props: {
|
||||
containerWidthBreakpoint: WidthBreakpoint.Wide,
|
||||
type: 'warning',
|
||||
},
|
||||
},
|
||||
{
|
||||
title: 'Warning (narrow container)',
|
||||
props: {
|
||||
containerWidthBreakpoint: WidthBreakpoint.Narrow,
|
||||
type: 'warning',
|
||||
},
|
||||
},
|
||||
{
|
||||
title: 'Error (wide container)',
|
||||
props: {
|
||||
containerWidthBreakpoint: WidthBreakpoint.Wide,
|
||||
type: 'error',
|
||||
},
|
||||
},
|
||||
{
|
||||
title: 'Error (narrow container)',
|
||||
props: {
|
||||
containerWidthBreakpoint: WidthBreakpoint.Narrow,
|
||||
type: 'error',
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
export default {
|
||||
title: 'Components/UnsupportedOSDialog',
|
||||
};
|
||||
|
||||
export function Iterations(): JSX.Element {
|
||||
return (
|
||||
<>
|
||||
{permutations.map(({ props, title }) => (
|
||||
<>
|
||||
<h3>{title}</h3>
|
||||
<FakeLeftPaneContainer
|
||||
containerWidthBreakpoint={
|
||||
props.containerWidthBreakpoint ?? WidthBreakpoint.Wide
|
||||
}
|
||||
>
|
||||
<UnsupportedOSDialog {...defaultProps} {...props} />
|
||||
</FakeLeftPaneContainer>
|
||||
</>
|
||||
))}
|
||||
</>
|
||||
);
|
||||
}
|
74
ts/components/UnsupportedOSDialog.tsx
Normal file
74
ts/components/UnsupportedOSDialog.tsx
Normal file
|
@ -0,0 +1,74 @@
|
|||
// Copyright 2023 Signal Messenger, LLC
|
||||
// SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
import React from 'react';
|
||||
import moment from 'moment';
|
||||
import type { FormatXMLElementFn } from 'intl-messageformat';
|
||||
|
||||
import type { LocalizerType } from '../types/Util';
|
||||
import { missingCaseError } from '../util/missingCaseError';
|
||||
import type { WidthBreakpoint } from './_util';
|
||||
import { Intl } from './Intl';
|
||||
|
||||
import { LeftPaneDialog } from './LeftPaneDialog';
|
||||
|
||||
export type PropsType = {
|
||||
containerWidthBreakpoint: WidthBreakpoint;
|
||||
expirationTimestamp: number;
|
||||
i18n: LocalizerType;
|
||||
type: 'warning' | 'error';
|
||||
OS: string;
|
||||
};
|
||||
|
||||
const SUPPORT_URL = 'https://support.signal.org/hc/articles/5109141421850';
|
||||
|
||||
export function UnsupportedOSDialog({
|
||||
containerWidthBreakpoint,
|
||||
expirationTimestamp,
|
||||
i18n,
|
||||
type,
|
||||
OS,
|
||||
}: PropsType): JSX.Element | null {
|
||||
const learnMoreLink: FormatXMLElementFn<JSX.Element | string> = children => (
|
||||
<a key="signal-support" href={SUPPORT_URL} rel="noreferrer" target="_blank">
|
||||
{children}
|
||||
</a>
|
||||
);
|
||||
|
||||
let body: JSX.Element;
|
||||
if (type === 'error') {
|
||||
body = (
|
||||
<Intl
|
||||
id="icu:UnsupportedOSErrorDialog__body"
|
||||
i18n={i18n}
|
||||
components={{
|
||||
OS,
|
||||
learnMoreLink,
|
||||
}}
|
||||
/>
|
||||
);
|
||||
} else if (type === 'warning') {
|
||||
body = (
|
||||
<Intl
|
||||
id="icu:UnsupportedOSWarningDialog__body"
|
||||
i18n={i18n}
|
||||
components={{
|
||||
OS,
|
||||
expirationDate: moment(expirationTimestamp).format('ll'),
|
||||
learnMoreLink,
|
||||
}}
|
||||
/>
|
||||
);
|
||||
} else {
|
||||
throw missingCaseError(type);
|
||||
}
|
||||
|
||||
return (
|
||||
<LeftPaneDialog
|
||||
containerWidthBreakpoint={containerWidthBreakpoint}
|
||||
type={type}
|
||||
>
|
||||
<span>{body}</span>
|
||||
</LeftPaneDialog>
|
||||
);
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue