Show info for permanently undownloadable visual attachments
This commit is contained in:
parent
6451ff0cf1
commit
1cc26d5cc7
23 changed files with 314 additions and 15 deletions
|
@ -1526,6 +1526,14 @@
|
|||
"messageformat": "{count, plural, one {# item} other {# items}}",
|
||||
"description": "Describes a button shown on a grid of attachments to start of them downloading"
|
||||
},
|
||||
"icu:mediaNoLongerAvailable": {
|
||||
"messageformat": "This media is no longer available.",
|
||||
"description": "Shown in info toast for messages with old image and video attachments which are no longer available for download. Also used for accessibility label for the download attachment button."
|
||||
},
|
||||
"icu:attachmentNoLongerAvailable__learnMore": {
|
||||
"messageformat": "Learn more",
|
||||
"description": "Link in message placeholder and info toast for messages with old attachments which are no longer available for download."
|
||||
},
|
||||
"icu:save": {
|
||||
"messageformat": "Save",
|
||||
"description": "Used on save buttons"
|
||||
|
|
5
images/icons/v3/photo/photo-slash-compact.svg
Normal file
5
images/icons/v3/photo/photo-slash-compact.svg
Normal file
|
@ -0,0 +1,5 @@
|
|||
<svg width="20" height="20" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M2.58705 1.55585C2.30229 1.2711 1.84061 1.2711 1.55585 1.55585C1.2711 1.84061 1.2711 2.30229 1.55585 2.58705L17.3892 18.4204C17.6739 18.7051 18.1356 18.7051 18.4204 18.4204C18.7051 18.1356 18.7051 17.6739 18.4204 17.3892L2.58705 1.55585Z" fill="#000"/>
|
||||
<path d="M1.75197 4.59525C1.75695 4.58548 1.76197 4.57573 1.76704 4.566L2.90669 5.70565C2.88314 5.82821 2.86432 5.97187 2.85024 6.14414C2.8135 6.59392 2.81293 7.17165 2.81293 8.00043L2.81293 12.0004C2.81293 12.4902 2.81313 12.8923 2.82087 13.2321L4.97298 11.08C5.78224 10.2708 7.02639 10.1614 7.95312 10.7521L9.94988 12.7488L9.47436 13.2244C9.1896 13.5091 8.72792 13.5091 8.44316 13.2244L7.33001 12.1112C6.96389 11.7451 6.3703 11.7451 6.00418 12.1112L3.16888 14.9465C3.37706 15.2653 3.66568 15.5254 4.00732 15.6995C4.19935 15.7973 4.45288 15.8646 4.89414 15.9006C5.34392 15.9374 5.92165 15.9379 6.75043 15.9379H13.139L14.5871 17.3861C14.2061 17.3963 13.7735 17.3963 13.2817 17.3963H6.7192C5.92894 17.3963 5.29155 17.3963 4.77539 17.3541C4.24394 17.3107 3.77713 17.2189 3.34525 16.9989C2.65925 16.6494 2.1015 16.0916 1.75197 15.4056C1.53191 14.9737 1.44017 14.5069 1.39675 13.9755C1.35458 13.4593 1.35459 12.8219 1.35459 12.0317V7.9692C1.35459 7.17894 1.35458 6.54155 1.39675 6.02539C1.44017 5.49394 1.53191 5.02713 1.75197 4.59525Z" fill="#000"/>
|
||||
<path d="M15.0279 8.99667C14.1184 8.08718 12.6596 8.06177 11.7194 8.92044L12.7534 9.95442C13.1213 9.66366 13.6569 9.68814 13.9967 10.0279L17.1804 13.2116C17.1751 13.4554 17.1661 13.6674 17.1506 13.8567C17.1365 14.029 17.1177 14.1726 17.0942 14.2952L18.2338 15.4349C18.2389 15.4251 18.2439 15.4154 18.2489 15.4056C18.4689 14.9737 18.5607 14.5069 18.6041 13.9755C18.6463 13.4593 18.6463 12.8219 18.6463 12.0317V7.96922C18.6463 7.17895 18.6463 6.54155 18.6041 6.02539C18.5607 5.49394 18.4689 5.02713 18.2489 4.59525C17.8994 3.90925 17.3416 3.35151 16.6556 3.00197C16.2237 2.78191 15.7569 2.69018 15.2255 2.64675C14.7093 2.60458 14.0719 2.60459 13.2817 2.60459H6.71921C6.22739 2.60459 5.79475 2.60459 5.41371 2.61475L6.86189 4.06293L13.2504 4.06293C14.0792 4.06293 14.6569 4.0635 15.1067 4.10024C15.548 4.1363 15.8015 4.20351 15.9935 4.30135C16.4051 4.51107 16.7398 4.84572 16.9495 5.25732C17.0473 5.44935 17.1146 5.70288 17.1506 6.14415C17.1874 6.59392 17.1879 7.17165 17.1879 8.00043V11.1567L15.0279 8.99667Z" fill="#000"/>
|
||||
</svg>
|
After Width: | Height: | Size: 2.4 KiB |
|
@ -2864,6 +2864,7 @@ button.module-image__border-overlay:focus {
|
|||
bottom: 0;
|
||||
z-index: variables.$z-index-base;
|
||||
inset-inline: 0;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.module-image__overlay-circle {
|
||||
|
@ -2877,6 +2878,10 @@ button.module-image__border-overlay:focus {
|
|||
}
|
||||
}
|
||||
|
||||
.module-image__overlay-circle--undownloadable {
|
||||
background-color: variables.$color-black-alpha-40;
|
||||
}
|
||||
|
||||
.module-image__play-icon {
|
||||
@include mixins.position-absolute-center;
|
||||
|
||||
|
@ -2908,6 +2913,16 @@ button.module-image__border-overlay:focus {
|
|||
variables.$color-white
|
||||
);
|
||||
}
|
||||
.module-image__undownloadable-icon {
|
||||
@include mixins.position-absolute-center;
|
||||
|
||||
height: 24px;
|
||||
width: 24px;
|
||||
@include mixins.color-svg(
|
||||
'../images/icons/v3/photo/photo-slash-compact.svg',
|
||||
variables.$color-white
|
||||
);
|
||||
}
|
||||
|
||||
.module-image__text-container {
|
||||
position: absolute;
|
||||
|
|
|
@ -65,6 +65,7 @@ const MESSAGE_DEFAULT_PROPS = {
|
|||
showExpiredIncomingTapToViewToast: shouldNeverBeCalled,
|
||||
showExpiredOutgoingTapToViewToast: shouldNeverBeCalled,
|
||||
showLightboxForViewOnceMedia: shouldNeverBeCalled,
|
||||
showMediaNoLongerAvailableToast: shouldNeverBeCalled,
|
||||
startConversation: shouldNeverBeCalled,
|
||||
textDirection: TextDirection.Default,
|
||||
viewStory: shouldNeverBeCalled,
|
||||
|
|
|
@ -73,6 +73,7 @@ const MESSAGE_DEFAULT_PROPS = {
|
|||
showExpiredOutgoingTapToViewToast: shouldNeverBeCalled,
|
||||
showLightbox: shouldNeverBeCalled,
|
||||
showLightboxForViewOnceMedia: shouldNeverBeCalled,
|
||||
showMediaNoLongerAvailableToast: shouldNeverBeCalled,
|
||||
startConversation: shouldNeverBeCalled,
|
||||
theme: ThemeType.dark,
|
||||
viewStory: shouldNeverBeCalled,
|
||||
|
|
|
@ -129,6 +129,8 @@ function getToast(toastType: ToastType): AnyToast {
|
|||
return { toastType: ToastType.LoadingFullLogs };
|
||||
case ToastType.MaxAttachments:
|
||||
return { toastType: ToastType.MaxAttachments };
|
||||
case ToastType.MediaNoLongerAvailable:
|
||||
return { toastType: ToastType.MediaNoLongerAvailable };
|
||||
case ToastType.MessageBodyTooLong:
|
||||
return { toastType: ToastType.MessageBodyTooLong };
|
||||
case ToastType.MessageLoop:
|
||||
|
|
|
@ -16,6 +16,8 @@ import type { AnyToast } from '../types/Toast';
|
|||
import { ToastType } from '../types/Toast';
|
||||
import type { AnyActionableMegaphone } from '../types/Megaphone';
|
||||
import { MegaphoneType } from '../types/Megaphone';
|
||||
import { openLinkInWebBrowser } from '../util/openLinkInWebBrowser';
|
||||
import { LINKED_DEVICES_URL } from '../types/support';
|
||||
|
||||
export type PropsType = {
|
||||
hideToast: () => unknown;
|
||||
|
@ -407,6 +409,20 @@ export function renderToast({
|
|||
return <Toast onClose={hideToast}>{i18n('icu:maximumAttachments')}</Toast>;
|
||||
}
|
||||
|
||||
if (toastType === ToastType.MediaNoLongerAvailable) {
|
||||
return (
|
||||
<Toast
|
||||
onClose={hideToast}
|
||||
toastAction={{
|
||||
label: i18n('icu:attachmentNoLongerAvailable__learnMore'),
|
||||
onClick: () => openLinkInWebBrowser(LINKED_DEVICES_URL),
|
||||
}}
|
||||
>
|
||||
{i18n('icu:mediaNoLongerAvailable')}
|
||||
</Toast>
|
||||
);
|
||||
}
|
||||
|
||||
if (toastType === ToastType.MessageBodyTooLong) {
|
||||
return <Toast onClose={hideToast}>{i18n('icu:messageBodyTooLong')}</Toast>;
|
||||
}
|
||||
|
|
|
@ -12,12 +12,14 @@ import {
|
|||
hasNotResolved,
|
||||
getImageDimensions,
|
||||
defaultBlurHash,
|
||||
isDownloadable,
|
||||
} from '../../types/Attachment';
|
||||
import * as Errors from '../../types/errors';
|
||||
import * as log from '../../logging/log';
|
||||
import { useReducedMotion } from '../../hooks/useReducedMotion';
|
||||
import { AttachmentDetailPill } from './AttachmentDetailPill';
|
||||
import { getSpinner } from './Image';
|
||||
import { useUndownloadableMediaHandler } from '../../hooks/useUndownloadableMediaHandler';
|
||||
|
||||
const MAX_GIF_REPEAT = 4;
|
||||
const MAX_GIF_TIME = 8;
|
||||
|
@ -33,6 +35,7 @@ export type Props = {
|
|||
readonly theme?: ThemeType;
|
||||
|
||||
onError(): void;
|
||||
showMediaNoLongerAvailableToast?: () => void;
|
||||
showVisualAttachment(): void;
|
||||
startDownload(): void;
|
||||
cancelDownload(): void;
|
||||
|
@ -51,6 +54,7 @@ export function GIF(props: Props): JSX.Element {
|
|||
theme,
|
||||
|
||||
onError,
|
||||
showMediaNoLongerAvailableToast,
|
||||
showVisualAttachment,
|
||||
startDownload,
|
||||
cancelDownload,
|
||||
|
@ -123,6 +127,10 @@ export function GIF(props: Props): JSX.Element {
|
|||
setIsPlaying(isFocused && !isTapToPlayPaused);
|
||||
}, [isFocused, playTime, currentTime, repeatCount, tapToPlay]);
|
||||
|
||||
const undownloadableClick = useUndownloadableMediaHandler(
|
||||
showMediaNoLongerAvailableToast
|
||||
);
|
||||
|
||||
const onTimeUpdate = async (event: MediaEvent): Promise<void> => {
|
||||
const { currentTime: reportedTime } = event.currentTarget;
|
||||
if (!Number.isNaN(reportedTime)) {
|
||||
|
@ -175,9 +183,10 @@ export function GIF(props: Props): JSX.Element {
|
|||
|
||||
const isPending = Boolean(attachment.pending);
|
||||
const isNotResolved = hasNotResolved(attachment) && !isPending;
|
||||
const isMediaDownloadable = isDownloadable(attachment);
|
||||
|
||||
let gif: JSX.Element | undefined;
|
||||
if (isNotResolved || isPending) {
|
||||
if (isNotResolved || isPending || !isMediaDownloadable) {
|
||||
gif = (
|
||||
<Blurhash
|
||||
hash={attachment.blurHash || defaultBlurHash(theme)}
|
||||
|
@ -241,7 +250,7 @@ export function GIF(props: Props): JSX.Element {
|
|||
});
|
||||
|
||||
let overlay: JSX.Element | undefined;
|
||||
if ((tapToPlay && !isPlaying) || isNotResolved) {
|
||||
if ((tapToPlay && !isPlaying) || (isNotResolved && isMediaDownloadable)) {
|
||||
const className = classNames([
|
||||
'module-image__border-overlay',
|
||||
'module-image__border-overlay--with-click-handler',
|
||||
|
@ -262,9 +271,21 @@ export function GIF(props: Props): JSX.Element {
|
|||
<span />
|
||||
</button>
|
||||
);
|
||||
} else if (!isMediaDownloadable) {
|
||||
overlay = (
|
||||
<button
|
||||
type="button"
|
||||
className="module-image__overlay-circle module-image__overlay-circle--undownloadable"
|
||||
aria-label={i18n('icu:mediaNoLongerAvailable')}
|
||||
onClick={undownloadableClick}
|
||||
tabIndex={tabIndex}
|
||||
>
|
||||
<div className="module-image__undownloadable-icon" />
|
||||
</button>
|
||||
);
|
||||
}
|
||||
|
||||
const detailPill = (
|
||||
const detailPill = isDownloadable(attachment) ? (
|
||||
<AttachmentDetailPill
|
||||
attachments={[attachment]}
|
||||
cancelDownload={cancelDownload}
|
||||
|
@ -272,7 +293,7 @@ export function GIF(props: Props): JSX.Element {
|
|||
isGif
|
||||
startDownload={startDownload}
|
||||
/>
|
||||
);
|
||||
) : null;
|
||||
|
||||
return (
|
||||
<div className="module-image module-image--gif">
|
||||
|
|
|
@ -14,10 +14,12 @@ import type {
|
|||
} from '../../types/Attachment';
|
||||
import {
|
||||
defaultBlurHash,
|
||||
isDownloadable,
|
||||
isIncremental,
|
||||
isReadyToView,
|
||||
} from '../../types/Attachment';
|
||||
import { ProgressCircle } from '../ProgressCircle';
|
||||
import { useUndownloadableMediaHandler } from '../../hooks/useUndownloadableMediaHandler';
|
||||
|
||||
export enum CurveType {
|
||||
None = 0,
|
||||
|
@ -55,6 +57,7 @@ export type Props = {
|
|||
|
||||
i18n: LocalizerType;
|
||||
theme?: ThemeType;
|
||||
showMediaNoLongerAvailableToast?: () => void;
|
||||
showVisualAttachment?: (attachment: AttachmentType) => void;
|
||||
cancelDownload?: () => void;
|
||||
startDownload?: () => void;
|
||||
|
@ -78,6 +81,7 @@ export function Image({
|
|||
i18n,
|
||||
noBackground,
|
||||
noBorder,
|
||||
showMediaNoLongerAvailableToast,
|
||||
showVisualAttachment,
|
||||
startDownload,
|
||||
cancelDownload,
|
||||
|
@ -164,6 +168,9 @@ export function Image({
|
|||
},
|
||||
[startDownload]
|
||||
);
|
||||
const undownloadableClick = useUndownloadableMediaHandler(
|
||||
showMediaNoLongerAvailableToast
|
||||
);
|
||||
|
||||
const imageOrBlurHash = url ? (
|
||||
<img
|
||||
|
@ -211,6 +218,8 @@ export function Image({
|
|||
tabIndex,
|
||||
});
|
||||
|
||||
const isMediaDownloadable = isDownloadable(attachment);
|
||||
|
||||
return (
|
||||
<div
|
||||
className={classNames(
|
||||
|
@ -226,7 +235,19 @@ export function Image({
|
|||
}}
|
||||
>
|
||||
{imageOrBlurHash}
|
||||
{startDownloadButton}
|
||||
{isMediaDownloadable ? (
|
||||
startDownloadButton
|
||||
) : (
|
||||
<button
|
||||
type="button"
|
||||
className="module-image__overlay-circle module-image__overlay-circle--undownloadable"
|
||||
aria-label={i18n('icu:mediaNoLongerAvailable')}
|
||||
onClick={undownloadableClick}
|
||||
tabIndex={tabIndex}
|
||||
>
|
||||
<div className="module-image__undownloadable-icon" />
|
||||
</button>
|
||||
)}
|
||||
{spinner}
|
||||
|
||||
{attachment.caption ? (
|
||||
|
@ -245,7 +266,9 @@ export function Image({
|
|||
}}
|
||||
/>
|
||||
) : null}
|
||||
{(attachment.path || isIncremental(attachment)) && playIconOverlay ? (
|
||||
{(attachment.path || isIncremental(attachment)) &&
|
||||
isMediaDownloadable &&
|
||||
playIconOverlay ? (
|
||||
<div className="module-image__overlay-circle">
|
||||
<div className="module-image__play-icon" />
|
||||
</div>
|
||||
|
@ -267,7 +290,9 @@ export function Image({
|
|||
style={curveStyles}
|
||||
/>
|
||||
) : null}
|
||||
{showVisualAttachment && isReadyToView(attachment) ? (
|
||||
{showVisualAttachment &&
|
||||
isReadyToView(attachment) &&
|
||||
isMediaDownloadable ? (
|
||||
<button
|
||||
type="button"
|
||||
className={classNames('module-image__border-overlay', {
|
||||
|
|
|
@ -14,6 +14,7 @@ import {
|
|||
getImageDimensions,
|
||||
getThumbnailUrl,
|
||||
getUrl,
|
||||
isDownloadable,
|
||||
isIncremental,
|
||||
isVideoAttachment,
|
||||
} from '../../types/Attachment';
|
||||
|
@ -42,6 +43,7 @@ export type Props = {
|
|||
|
||||
onError: () => void;
|
||||
showVisualAttachment: (attachment: AttachmentType) => void;
|
||||
showMediaNoLongerAvailableToast: () => void;
|
||||
cancelDownload: () => void;
|
||||
startDownload: () => void;
|
||||
};
|
||||
|
@ -112,6 +114,7 @@ export function ImageGrid({
|
|||
isSticker,
|
||||
stickerSize,
|
||||
onError,
|
||||
showMediaNoLongerAvailableToast,
|
||||
showVisualAttachment,
|
||||
cancelDownload,
|
||||
startDownload,
|
||||
|
@ -158,9 +161,13 @@ export function ImageGrid({
|
|||
return null;
|
||||
}
|
||||
|
||||
const downloadableAttachments = attachments.filter(attachment =>
|
||||
isDownloadable(attachment)
|
||||
);
|
||||
|
||||
const detailPill = (
|
||||
<AttachmentDetailPill
|
||||
attachments={attachments}
|
||||
attachments={downloadableAttachments}
|
||||
i18n={i18n}
|
||||
startDownload={startDownload}
|
||||
cancelDownload={cancelDownload}
|
||||
|
@ -207,6 +214,7 @@ export function ImageGrid({
|
|||
getUrl(attachments[0]) ?? attachments[0].thumbnailFromBackup?.url
|
||||
}
|
||||
tabIndex={tabIndex}
|
||||
showMediaNoLongerAvailableToast={showMediaNoLongerAvailableToast}
|
||||
showVisualAttachment={showVisualAttachment}
|
||||
cancelDownload={cancelDownload}
|
||||
startDownload={startDownload}
|
||||
|
@ -235,6 +243,7 @@ export function ImageGrid({
|
|||
width={150}
|
||||
cropWidth={GAP}
|
||||
url={getThumbnailUrl(attachments[0])}
|
||||
showMediaNoLongerAvailableToast={showMediaNoLongerAvailableToast}
|
||||
showVisualAttachment={showVisualAttachment}
|
||||
cancelDownload={cancelDownload}
|
||||
startDownload={downloadPill ? undefined : startDownload}
|
||||
|
@ -254,6 +263,7 @@ export function ImageGrid({
|
|||
width={150}
|
||||
attachment={attachments[1]}
|
||||
url={getThumbnailUrl(attachments[1])}
|
||||
showMediaNoLongerAvailableToast={showMediaNoLongerAvailableToast}
|
||||
showVisualAttachment={showVisualAttachment}
|
||||
cancelDownload={cancelDownload}
|
||||
startDownload={downloadPill ? undefined : startDownload}
|
||||
|
@ -283,6 +293,7 @@ export function ImageGrid({
|
|||
width={200}
|
||||
cropWidth={GAP}
|
||||
url={getUrl(attachments[0])}
|
||||
showMediaNoLongerAvailableToast={showMediaNoLongerAvailableToast}
|
||||
showVisualAttachment={showVisualAttachment}
|
||||
cancelDownload={cancelDownload}
|
||||
startDownload={downloadPill ? undefined : startDownload}
|
||||
|
@ -301,6 +312,7 @@ export function ImageGrid({
|
|||
attachment={attachments[1]}
|
||||
playIconOverlay={isVideoAttachment(attachments[1])}
|
||||
url={getThumbnailUrl(attachments[1])}
|
||||
showMediaNoLongerAvailableToast={showMediaNoLongerAvailableToast}
|
||||
showVisualAttachment={showVisualAttachment}
|
||||
cancelDownload={cancelDownload}
|
||||
startDownload={downloadPill ? undefined : startDownload}
|
||||
|
@ -319,6 +331,7 @@ export function ImageGrid({
|
|||
attachment={attachments[2]}
|
||||
playIconOverlay={isVideoAttachment(attachments[2])}
|
||||
url={getThumbnailUrl(attachments[2])}
|
||||
showMediaNoLongerAvailableToast={showMediaNoLongerAvailableToast}
|
||||
showVisualAttachment={showVisualAttachment}
|
||||
cancelDownload={cancelDownload}
|
||||
startDownload={downloadPill ? undefined : startDownload}
|
||||
|
@ -350,6 +363,7 @@ export function ImageGrid({
|
|||
cropHeight={GAP}
|
||||
cropWidth={GAP}
|
||||
url={getThumbnailUrl(attachments[0])}
|
||||
showMediaNoLongerAvailableToast={showMediaNoLongerAvailableToast}
|
||||
showVisualAttachment={showVisualAttachment}
|
||||
cancelDownload={cancelDownload}
|
||||
startDownload={downloadPill ? undefined : startDownload}
|
||||
|
@ -368,6 +382,7 @@ export function ImageGrid({
|
|||
cropHeight={GAP}
|
||||
attachment={attachments[1]}
|
||||
url={getThumbnailUrl(attachments[1])}
|
||||
showMediaNoLongerAvailableToast={showMediaNoLongerAvailableToast}
|
||||
showVisualAttachment={showVisualAttachment}
|
||||
cancelDownload={cancelDownload}
|
||||
startDownload={downloadPill ? undefined : startDownload}
|
||||
|
@ -389,6 +404,7 @@ export function ImageGrid({
|
|||
cropWidth={GAP}
|
||||
attachment={attachments[2]}
|
||||
url={getThumbnailUrl(attachments[2])}
|
||||
showMediaNoLongerAvailableToast={showMediaNoLongerAvailableToast}
|
||||
showVisualAttachment={showVisualAttachment}
|
||||
cancelDownload={cancelDownload}
|
||||
startDownload={downloadPill ? undefined : startDownload}
|
||||
|
@ -407,6 +423,7 @@ export function ImageGrid({
|
|||
width={150}
|
||||
attachment={attachments[3]}
|
||||
url={getThumbnailUrl(attachments[3])}
|
||||
showMediaNoLongerAvailableToast={showMediaNoLongerAvailableToast}
|
||||
showVisualAttachment={showVisualAttachment}
|
||||
cancelDownload={cancelDownload}
|
||||
startDownload={downloadPill ? undefined : startDownload}
|
||||
|
@ -441,6 +458,7 @@ export function ImageGrid({
|
|||
width={150}
|
||||
cropWidth={GAP}
|
||||
url={getThumbnailUrl(attachments[0])}
|
||||
showMediaNoLongerAvailableToast={showMediaNoLongerAvailableToast}
|
||||
showVisualAttachment={showVisualAttachment}
|
||||
cancelDownload={cancelDownload}
|
||||
startDownload={downloadPill ? undefined : startDownload}
|
||||
|
@ -457,6 +475,7 @@ export function ImageGrid({
|
|||
width={150}
|
||||
attachment={attachments[1]}
|
||||
url={getThumbnailUrl(attachments[1])}
|
||||
showMediaNoLongerAvailableToast={showMediaNoLongerAvailableToast}
|
||||
showVisualAttachment={showVisualAttachment}
|
||||
cancelDownload={cancelDownload}
|
||||
startDownload={downloadPill ? undefined : startDownload}
|
||||
|
@ -478,6 +497,7 @@ export function ImageGrid({
|
|||
cropWidth={GAP}
|
||||
attachment={attachments[2]}
|
||||
url={getThumbnailUrl(attachments[2])}
|
||||
showMediaNoLongerAvailableToast={showMediaNoLongerAvailableToast}
|
||||
showVisualAttachment={showVisualAttachment}
|
||||
cancelDownload={cancelDownload}
|
||||
startDownload={downloadPill ? undefined : startDownload}
|
||||
|
@ -496,6 +516,7 @@ export function ImageGrid({
|
|||
cropWidth={GAP}
|
||||
attachment={attachments[3]}
|
||||
url={getThumbnailUrl(attachments[3])}
|
||||
showMediaNoLongerAvailableToast={showMediaNoLongerAvailableToast}
|
||||
showVisualAttachment={showVisualAttachment}
|
||||
cancelDownload={cancelDownload}
|
||||
startDownload={downloadPill ? undefined : startDownload}
|
||||
|
@ -516,6 +537,7 @@ export function ImageGrid({
|
|||
overlayText={moreMessagesOverlayText}
|
||||
attachment={attachments[4]}
|
||||
url={getThumbnailUrl(attachments[4])}
|
||||
showMediaNoLongerAvailableToast={showMediaNoLongerAvailableToast}
|
||||
showVisualAttachment={showVisualAttachment}
|
||||
cancelDownload={undefined}
|
||||
startDownload={undefined}
|
||||
|
@ -548,6 +570,13 @@ function renderDownloadPill({
|
|||
return null;
|
||||
}
|
||||
|
||||
const noneDownloadable = !attachments.some(attachment =>
|
||||
isDownloadable(attachment)
|
||||
);
|
||||
if (noneDownloadable) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<button
|
||||
type="button"
|
||||
|
|
|
@ -70,6 +70,7 @@ import {
|
|||
isVideo,
|
||||
isGIF,
|
||||
isPlayed,
|
||||
isDownloadable,
|
||||
} from '../../types/Attachment';
|
||||
import type { EmbeddedContactType } from '../../types/EmbeddedContact';
|
||||
|
||||
|
@ -375,6 +376,7 @@ export type PropsActions = {
|
|||
showAttachmentDownloadStillInProgressToast: (count: number) => unknown;
|
||||
showExpiredIncomingTapToViewToast: () => unknown;
|
||||
showExpiredOutgoingTapToViewToast: () => unknown;
|
||||
showMediaNoLongerAvailableToast: () => unknown;
|
||||
viewStory: ViewStoryActionCreatorType;
|
||||
|
||||
onToggleSelect: (selected: boolean, shift: boolean) => void;
|
||||
|
@ -945,6 +947,7 @@ export class Message extends React.PureComponent<Props, State> {
|
|||
shouldCollapseAbove,
|
||||
shouldCollapseBelow,
|
||||
showLightbox,
|
||||
showMediaNoLongerAvailableToast,
|
||||
status,
|
||||
text,
|
||||
textAttachment,
|
||||
|
@ -1008,6 +1011,7 @@ export class Message extends React.PureComponent<Props, State> {
|
|||
messageId: id,
|
||||
});
|
||||
}}
|
||||
showMediaNoLongerAvailableToast={showMediaNoLongerAvailableToast}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
|
@ -1043,11 +1047,13 @@ export class Message extends React.PureComponent<Props, State> {
|
|||
cancelDownload={() => {
|
||||
cancelAttachmentDownload({ messageId: id });
|
||||
}}
|
||||
showMediaNoLongerAvailableToast={showMediaNoLongerAvailableToast}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
if (isAudio(attachments)) {
|
||||
const played = isPlayed(direction, status, readStatus);
|
||||
|
||||
|
@ -1165,6 +1171,7 @@ export class Message extends React.PureComponent<Props, State> {
|
|||
id,
|
||||
kickOffAttachmentDownload,
|
||||
cancelAttachmentDownload,
|
||||
showMediaNoLongerAvailableToast,
|
||||
previews,
|
||||
quote,
|
||||
shouldCollapseAbove,
|
||||
|
@ -1237,6 +1244,7 @@ export class Message extends React.PureComponent<Props, State> {
|
|||
cancelDownload={() => {
|
||||
cancelAttachmentDownload({ messageId: id });
|
||||
}}
|
||||
showMediaNoLongerAvailableToast={showMediaNoLongerAvailableToast}
|
||||
/>
|
||||
) : null}
|
||||
<div dir="auto" className="module-message__link-preview__content">
|
||||
|
@ -1264,6 +1272,9 @@ export class Message extends React.PureComponent<Props, State> {
|
|||
blurHash={first.image.blurHash}
|
||||
onError={this.handleImageError}
|
||||
i18n={i18n}
|
||||
showMediaNoLongerAvailableToast={
|
||||
showMediaNoLongerAvailableToast
|
||||
}
|
||||
showVisualAttachment={() => {
|
||||
openLinkInWebBrowser(first.url);
|
||||
}}
|
||||
|
@ -2599,7 +2610,8 @@ export class Message extends React.PureComponent<Props, State> {
|
|||
attachments &&
|
||||
attachments.length > 0 &&
|
||||
!isAttachmentPending &&
|
||||
!isDownloaded(attachments[0])
|
||||
!isDownloaded(attachments[0]) &&
|
||||
isDownloadable(attachments[0])
|
||||
) {
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
|
|
|
@ -105,6 +105,7 @@ export type PropsReduxActions = Pick<
|
|||
| 'showExpiredOutgoingTapToViewToast'
|
||||
| 'showLightbox'
|
||||
| 'showLightboxForViewOnceMedia'
|
||||
| 'showMediaNoLongerAvailableToast'
|
||||
| 'showSpoiler'
|
||||
| 'startConversation'
|
||||
| 'viewStory'
|
||||
|
@ -152,6 +153,7 @@ export function MessageDetail({
|
|||
showExpiredOutgoingTapToViewToast,
|
||||
showLightbox,
|
||||
showLightboxForViewOnceMedia,
|
||||
showMediaNoLongerAvailableToast,
|
||||
showSpoiler,
|
||||
startConversation,
|
||||
theme,
|
||||
|
@ -375,6 +377,7 @@ export function MessageDetail({
|
|||
showExpiredOutgoingTapToViewToast
|
||||
}
|
||||
showLightbox={showLightbox}
|
||||
showMediaNoLongerAvailableToast={showMediaNoLongerAvailableToast}
|
||||
startConversation={startConversation}
|
||||
theme={theme}
|
||||
viewStory={viewStory}
|
||||
|
|
|
@ -144,6 +144,7 @@ const defaultMessageProps: TimelineMessagesProps = {
|
|||
showExpiredOutgoingTapToViewToast: action(
|
||||
'showExpiredOutgoingTapToViewToast'
|
||||
),
|
||||
showMediaNoLongerAvailableToast: action('showMediaNoLongerAvailableToast'),
|
||||
toggleDeleteMessagesModal: action('default--toggleDeleteMessagesModal'),
|
||||
toggleForwardMessagesModal: action('default--toggleForwardMessagesModal'),
|
||||
showLightbox: action('default--showLightbox'),
|
||||
|
|
|
@ -316,6 +316,7 @@ const actions = () => ({
|
|||
showExpiredOutgoingTapToViewToast: action(
|
||||
'showExpiredOutgoingTapToViewToast'
|
||||
),
|
||||
showMediaNoLongerAvailableToast: action('showMediaNoLongerAvailableToast'),
|
||||
toggleDeleteMessagesModal: action('toggleDeleteMessagesModal'),
|
||||
toggleForwardMessagesModal: action('toggleForwardMessagesModal'),
|
||||
|
||||
|
|
|
@ -107,6 +107,7 @@ const getDefaultProps = () => ({
|
|||
showExpiredOutgoingTapToViewToast: action(
|
||||
'showExpiredIncomingTapToViewToast'
|
||||
),
|
||||
showMediaNoLongerAvailableToast: action('showMediaNoLongerAvailableToast'),
|
||||
scrollToQuotedMessage: action('scrollToQuotedMessage'),
|
||||
showSpoiler: action('showSpoiler'),
|
||||
startConversation: action('startConversation'),
|
||||
|
|
|
@ -351,6 +351,7 @@ const createProps = (overrideProps: Partial<Props> = {}): Props => ({
|
|||
showExpiredOutgoingTapToViewToast: action(
|
||||
'showExpiredOutgoingTapToViewToast'
|
||||
),
|
||||
showMediaNoLongerAvailableToast: action('showMediaNoLongerAvailableToast'),
|
||||
toggleDeleteMessagesModal: action('toggleDeleteMessagesModal'),
|
||||
toggleForwardMessagesModal: action('toggleForwardMessagesModal'),
|
||||
showLightbox: action('showLightbox'),
|
||||
|
@ -2198,3 +2199,129 @@ export function MultiSelect(): JSX.Element {
|
|||
MultiSelect.args = {
|
||||
name: 'Multi Select',
|
||||
};
|
||||
|
||||
export function PermanentlyUndownloadableAttachments(): JSX.Element {
|
||||
const imageProps = createProps({
|
||||
attachments: [
|
||||
fakeAttachment({
|
||||
contentType: IMAGE_JPEG,
|
||||
fileName: 'bird.jpg',
|
||||
blurHash: 'LDA,FDBnm+I=p{tkIUI;~UkpELV]',
|
||||
width: 296,
|
||||
height: 394,
|
||||
path: undefined,
|
||||
key: undefined,
|
||||
id: undefined,
|
||||
}),
|
||||
],
|
||||
status: 'sent',
|
||||
});
|
||||
const multipleImagesProps = createProps({
|
||||
attachments: [
|
||||
fakeAttachment({
|
||||
contentType: IMAGE_JPEG,
|
||||
fileName: 'bird.jpg',
|
||||
blurHash: 'LDA,FDBnm+I=p{tkIUI;~UkpELV]',
|
||||
width: 296,
|
||||
height: 394,
|
||||
path: undefined,
|
||||
key: undefined,
|
||||
id: undefined,
|
||||
}),
|
||||
fakeAttachment({
|
||||
contentType: IMAGE_JPEG,
|
||||
fileName: 'bird.jpg',
|
||||
blurHash: 'LDA,FDBnm+I=p{tkIUI;~UkpELV]',
|
||||
width: 296,
|
||||
height: 394,
|
||||
path: undefined,
|
||||
key: undefined,
|
||||
id: undefined,
|
||||
}),
|
||||
],
|
||||
status: 'sent',
|
||||
});
|
||||
const gifProps = createProps({
|
||||
attachments: [
|
||||
fakeAttachment({
|
||||
contentType: VIDEO_MP4,
|
||||
flags: SignalService.AttachmentPointer.Flags.GIF,
|
||||
fileName: 'bird.gif',
|
||||
blurHash: 'LDA,FDBnm+I=p{tkIUI;~UkpELV]',
|
||||
width: 296,
|
||||
height: 394,
|
||||
path: undefined,
|
||||
key: undefined,
|
||||
id: undefined,
|
||||
}),
|
||||
],
|
||||
status: 'sent',
|
||||
text: 'cool gif',
|
||||
});
|
||||
const videoProps = createProps({
|
||||
attachments: [
|
||||
fakeAttachment({
|
||||
contentType: VIDEO_MP4,
|
||||
fileName: 'bird.mp4',
|
||||
width: 720,
|
||||
height: 480,
|
||||
path: undefined,
|
||||
key: undefined,
|
||||
id: undefined,
|
||||
}),
|
||||
],
|
||||
status: 'sent',
|
||||
});
|
||||
|
||||
const outgoingAuthor = {
|
||||
...imageProps.author,
|
||||
id: getDefaultConversation().id,
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<TimelineMessage {...imageProps} shouldCollapseAbove />
|
||||
<TimelineMessage {...gifProps} />
|
||||
<TimelineMessage {...videoProps} />
|
||||
<TimelineMessage {...multipleImagesProps} shouldCollapseBelow />
|
||||
<TimelineMessage
|
||||
{...imageProps}
|
||||
author={outgoingAuthor}
|
||||
direction="outgoing"
|
||||
shouldCollapseAbove
|
||||
/>
|
||||
<TimelineMessage
|
||||
{...gifProps}
|
||||
author={outgoingAuthor}
|
||||
direction="outgoing"
|
||||
/>
|
||||
<TimelineMessage
|
||||
{...videoProps}
|
||||
author={outgoingAuthor}
|
||||
direction="outgoing"
|
||||
/>
|
||||
<TimelineMessage
|
||||
{...multipleImagesProps}
|
||||
author={outgoingAuthor}
|
||||
direction="outgoing"
|
||||
shouldCollapseBelow
|
||||
/>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
export const AttachmentWithError = Template.bind({});
|
||||
AttachmentWithError.args = {
|
||||
attachments: [
|
||||
fakeAttachment({
|
||||
contentType: IMAGE_PNG,
|
||||
fileName: 'test.png',
|
||||
blurHash: 'LDA,FDBnm+I=p{tkIUI;~UkpELV]',
|
||||
width: 296,
|
||||
height: 394,
|
||||
path: undefined,
|
||||
error: true,
|
||||
}),
|
||||
],
|
||||
status: 'sent',
|
||||
};
|
||||
|
|
19
ts/hooks/useUndownloadableMediaHandler.tsx
Normal file
19
ts/hooks/useUndownloadableMediaHandler.tsx
Normal file
|
@ -0,0 +1,19 @@
|
|||
// Copyright 2025 Signal Messenger, LLC
|
||||
// SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
import { useCallback } from 'react';
|
||||
|
||||
export function useUndownloadableMediaHandler(
|
||||
showMediaNoLongerAvailableToast: (() => void) | undefined
|
||||
): (event: React.MouseEvent) => void {
|
||||
return useCallback(
|
||||
(event: React.MouseEvent) => {
|
||||
if (showMediaNoLongerAvailableToast) {
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
showMediaNoLongerAvailableToast();
|
||||
}
|
||||
},
|
||||
[showMediaNoLongerAvailableToast]
|
||||
);
|
||||
}
|
|
@ -1209,6 +1209,7 @@ export const actions = {
|
|||
showFindByUsername,
|
||||
showFindByPhoneNumber,
|
||||
showInbox,
|
||||
showMediaNoLongerAvailableToast,
|
||||
startComposing,
|
||||
startConversation,
|
||||
startSettingGroupMetadata,
|
||||
|
@ -4563,6 +4564,14 @@ function showInbox(): ShowInboxActionType {
|
|||
payload: null,
|
||||
};
|
||||
}
|
||||
function showMediaNoLongerAvailableToast(): ShowToastActionType {
|
||||
return {
|
||||
type: SHOW_TOAST,
|
||||
payload: {
|
||||
toastType: ToastType.MediaNoLongerAvailable,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
type ShowConversationArgsType = ReadonlyDeep<{
|
||||
conversationId?: string;
|
||||
|
|
|
@ -66,11 +66,7 @@ import type {
|
|||
AttachmentForUIType,
|
||||
AttachmentType,
|
||||
} from '../../types/Attachment';
|
||||
import {
|
||||
isVoiceMessage,
|
||||
canBeDownloaded,
|
||||
defaultBlurHash,
|
||||
} from '../../types/Attachment';
|
||||
import { isVoiceMessage, defaultBlurHash } from '../../types/Attachment';
|
||||
import { type DefaultConversationColorType } from '../../types/Colors';
|
||||
import { ReadStatus } from '../../messages/MessageReadStatus';
|
||||
|
||||
|
@ -325,7 +321,6 @@ export const getAttachmentsForMessage = ({
|
|||
}
|
||||
return (
|
||||
attachments
|
||||
.filter(attachment => !attachment.error || canBeDownloaded(attachment))
|
||||
// Long message attachments are removed from message.attachments quickly,
|
||||
// but in case they are still around, let's make sure not to show them
|
||||
.filter(attachment => attachment.contentType !== LONG_MESSAGE)
|
||||
|
|
|
@ -55,6 +55,7 @@ export const SmartMessageDetail = memo(
|
|||
showConversation,
|
||||
showExpiredIncomingTapToViewToast,
|
||||
showExpiredOutgoingTapToViewToast,
|
||||
showMediaNoLongerAvailableToast,
|
||||
showSpoiler,
|
||||
startConversation,
|
||||
} = useConversationsActions();
|
||||
|
@ -115,6 +116,7 @@ export const SmartMessageDetail = memo(
|
|||
showExpiredOutgoingTapToViewToast={showExpiredOutgoingTapToViewToast}
|
||||
showLightbox={showLightbox}
|
||||
showLightboxForViewOnceMedia={showLightboxForViewOnceMedia}
|
||||
showMediaNoLongerAvailableToast={showMediaNoLongerAvailableToast}
|
||||
showSpoiler={showSpoiler}
|
||||
startConversation={startConversation}
|
||||
theme={theme}
|
||||
|
|
|
@ -133,6 +133,7 @@ export const SmartTimelineItem = memo(function SmartTimelineItem(
|
|||
showConversation,
|
||||
showExpiredIncomingTapToViewToast,
|
||||
showExpiredOutgoingTapToViewToast,
|
||||
showMediaNoLongerAvailableToast,
|
||||
showSpoiler,
|
||||
startConversation,
|
||||
targetMessage,
|
||||
|
@ -236,6 +237,7 @@ export const SmartTimelineItem = memo(function SmartTimelineItem(
|
|||
showExpiredOutgoingTapToViewToast={showExpiredOutgoingTapToViewToast}
|
||||
showLightbox={showLightbox}
|
||||
showLightboxForViewOnceMedia={showLightboxForViewOnceMedia}
|
||||
showMediaNoLongerAvailableToast={showMediaNoLongerAvailableToast}
|
||||
showSpoiler={showSpoiler}
|
||||
startConversation={startConversation}
|
||||
toggleDeleteMessagesModal={toggleDeleteMessagesModal}
|
||||
|
|
|
@ -44,6 +44,7 @@ export enum ToastType {
|
|||
LinkCopied = 'LinkCopied',
|
||||
LoadingFullLogs = 'LoadingFullLogs',
|
||||
MaxAttachments = 'MaxAttachments',
|
||||
MediaNoLongerAvailable = 'MediaNoLongerAvailable',
|
||||
MessageBodyTooLong = 'MessageBodyTooLong',
|
||||
MessageLoop = 'MessageLoop',
|
||||
OriginalMessageNotFound = 'OriginalMessageNotFound',
|
||||
|
@ -136,6 +137,7 @@ export type AnyToast =
|
|||
| { toastType: ToastType.LinkCopied }
|
||||
| { toastType: ToastType.LoadingFullLogs }
|
||||
| { toastType: ToastType.MaxAttachments }
|
||||
| { toastType: ToastType.MediaNoLongerAvailable }
|
||||
| { toastType: ToastType.MessageBodyTooLong }
|
||||
| { toastType: ToastType.MessageLoop }
|
||||
| { toastType: ToastType.OriginalMessageNotFound }
|
||||
|
|
|
@ -5,6 +5,8 @@ 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';
|
||||
export const LINKED_DEVICES_URL =
|
||||
'https://support.signal.org/hc/en-us/articles/360007320551-Linked-Devices';
|
||||
export const LINK_SIGNAL_DESKTOP =
|
||||
'https://support.signal.org/hc/articles/360007320451#desktop_multiple_device';
|
||||
export const SAFETY_NUMBER_URL =
|
||||
|
|
Loading…
Add table
Reference in a new issue