2022-12-10 02:02:22 +00:00
|
|
|
// Copyright 2022 Signal Messenger, LLC
|
|
|
|
// SPDX-License-Identifier: AGPL-3.0-only
|
|
|
|
|
|
|
|
import type { ThunkAction } from 'redux-thunk';
|
|
|
|
|
2023-01-13 20:07:26 +00:00
|
|
|
import type { ReadonlyDeep } from 'type-fest';
|
2022-12-10 02:02:22 +00:00
|
|
|
import type { AttachmentType } from '../../types/Attachment';
|
|
|
|
import type { BoundActionCreatorsMapObject } from '../../hooks/useBoundActions';
|
|
|
|
import type { MediaItemType } from '../../types/MediaItem';
|
2023-01-05 22:16:56 +00:00
|
|
|
import type {
|
|
|
|
MessageChangedActionType,
|
|
|
|
MessageDeletedActionType,
|
|
|
|
MessageExpiredActionType,
|
|
|
|
} from './conversations';
|
2022-12-10 02:02:22 +00:00
|
|
|
import type { ShowStickerPackPreviewActionType } from './globalModals';
|
|
|
|
import type { ShowToastActionType } from './toast';
|
2022-12-20 17:50:23 +00:00
|
|
|
import type { StateType as RootStateType } from '../reducer';
|
2022-12-10 02:02:22 +00:00
|
|
|
|
|
|
|
import * as log from '../../logging/log';
|
2023-10-04 00:12:57 +00:00
|
|
|
import { __DEPRECATED$getMessageById } from '../../messages/getMessageById';
|
2023-03-04 03:03:15 +00:00
|
|
|
import type { MessageAttributesType } from '../../model-types.d';
|
2022-12-10 02:02:22 +00:00
|
|
|
import { isGIF } from '../../types/Attachment';
|
|
|
|
import {
|
|
|
|
isImageTypeSupported,
|
|
|
|
isVideoTypeSupported,
|
|
|
|
} from '../../util/GoogleChrome';
|
|
|
|
import { isTapToView } from '../selectors/message';
|
2022-12-15 00:48:36 +00:00
|
|
|
import { SHOW_TOAST } from './toast';
|
|
|
|
import { ToastType } from '../../types/Toast';
|
2023-01-05 22:16:56 +00:00
|
|
|
import {
|
|
|
|
MESSAGE_CHANGED,
|
|
|
|
MESSAGE_DELETED,
|
|
|
|
MESSAGE_EXPIRED,
|
|
|
|
saveAttachmentFromMessage,
|
|
|
|
} from './conversations';
|
2022-12-10 02:02:22 +00:00
|
|
|
import { showStickerPackPreview } from './globalModals';
|
|
|
|
import { useBoundActions } from '../../hooks/useBoundActions';
|
2023-03-04 03:03:15 +00:00
|
|
|
import dataInterface from '../../sql/Client';
|
2022-12-10 02:02:22 +00:00
|
|
|
|
2023-01-13 20:07:26 +00:00
|
|
|
// eslint-disable-next-line local-rules/type-alias-readonlydeep
|
2022-12-10 02:02:22 +00:00
|
|
|
export type LightboxStateType =
|
|
|
|
| {
|
|
|
|
isShowingLightbox: false;
|
|
|
|
}
|
|
|
|
| {
|
|
|
|
isShowingLightbox: true;
|
|
|
|
isViewOnce: boolean;
|
2023-01-13 20:07:26 +00:00
|
|
|
media: ReadonlyArray<ReadonlyDeep<MediaItemType>>;
|
2023-03-04 03:03:15 +00:00
|
|
|
hasPrevMessage: boolean;
|
|
|
|
hasNextMessage: boolean;
|
2023-09-26 15:38:21 +00:00
|
|
|
selectedIndex: number | undefined;
|
2024-02-02 23:39:32 +00:00
|
|
|
playbackDisabled: boolean;
|
2022-12-10 02:02:22 +00:00
|
|
|
};
|
|
|
|
|
|
|
|
const CLOSE_LIGHTBOX = 'lightbox/CLOSE';
|
|
|
|
const SHOW_LIGHTBOX = 'lightbox/SHOW';
|
2023-09-26 15:38:21 +00:00
|
|
|
const SET_SELECTED_LIGHTBOX_INDEX = 'lightbox/SET_SELECTED_LIGHTBOX_INDEX';
|
2024-02-02 23:39:32 +00:00
|
|
|
const SET_LIGHTBOX_PLAYBACK_DISABLED =
|
|
|
|
'lightbox/SET_LIGHTBOX_PLAYBACK_DISABLED';
|
2022-12-10 02:02:22 +00:00
|
|
|
|
2023-01-13 20:07:26 +00:00
|
|
|
type CloseLightboxActionType = ReadonlyDeep<{
|
2022-12-10 02:02:22 +00:00
|
|
|
type: typeof CLOSE_LIGHTBOX;
|
2023-01-13 20:07:26 +00:00
|
|
|
}>;
|
2022-12-10 02:02:22 +00:00
|
|
|
|
2023-01-13 20:07:26 +00:00
|
|
|
// eslint-disable-next-line local-rules/type-alias-readonlydeep
|
2022-12-10 02:02:22 +00:00
|
|
|
type ShowLightboxActionType = {
|
|
|
|
type: typeof SHOW_LIGHTBOX;
|
|
|
|
payload: {
|
|
|
|
isViewOnce: boolean;
|
2023-01-13 20:07:26 +00:00
|
|
|
media: ReadonlyArray<ReadonlyDeep<MediaItemType>>;
|
2023-03-04 03:03:15 +00:00
|
|
|
hasPrevMessage: boolean;
|
|
|
|
hasNextMessage: boolean;
|
2023-09-26 15:38:21 +00:00
|
|
|
selectedIndex: number | undefined;
|
2022-12-10 02:02:22 +00:00
|
|
|
};
|
|
|
|
};
|
|
|
|
|
2024-02-02 23:39:32 +00:00
|
|
|
type SetLightboxPlaybackDisabledActionType = ReadonlyDeep<{
|
|
|
|
type: typeof SET_LIGHTBOX_PLAYBACK_DISABLED;
|
|
|
|
payload: boolean;
|
|
|
|
}>;
|
|
|
|
|
2023-09-26 15:38:21 +00:00
|
|
|
type SetSelectedLightboxIndexActionType = ReadonlyDeep<{
|
|
|
|
type: typeof SET_SELECTED_LIGHTBOX_INDEX;
|
|
|
|
payload: number;
|
2023-03-04 03:03:15 +00:00
|
|
|
}>;
|
|
|
|
|
2023-01-13 20:07:26 +00:00
|
|
|
// eslint-disable-next-line local-rules/type-alias-readonlydeep
|
2022-12-20 17:50:23 +00:00
|
|
|
type LightboxActionType =
|
|
|
|
| CloseLightboxActionType
|
2023-01-05 22:16:56 +00:00
|
|
|
| MessageChangedActionType
|
|
|
|
| MessageDeletedActionType
|
2022-12-20 17:50:23 +00:00
|
|
|
| MessageExpiredActionType
|
2023-03-04 03:03:15 +00:00
|
|
|
| ShowLightboxActionType
|
2024-02-02 23:39:32 +00:00
|
|
|
| SetSelectedLightboxIndexActionType
|
|
|
|
| SetLightboxPlaybackDisabledActionType;
|
2022-12-10 02:02:22 +00:00
|
|
|
|
|
|
|
function closeLightbox(): ThunkAction<
|
|
|
|
void,
|
|
|
|
RootStateType,
|
|
|
|
unknown,
|
|
|
|
CloseLightboxActionType
|
|
|
|
> {
|
|
|
|
return (dispatch, getState) => {
|
|
|
|
const { lightbox } = getState();
|
|
|
|
|
|
|
|
if (!lightbox.isShowingLightbox) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
const { isViewOnce, media } = lightbox;
|
|
|
|
|
|
|
|
if (isViewOnce) {
|
|
|
|
media.forEach(item => {
|
|
|
|
if (!item.attachment.path) {
|
|
|
|
return;
|
|
|
|
}
|
2022-12-21 18:41:48 +00:00
|
|
|
void window.Signal.Migrations.deleteTempFile(item.attachment.path);
|
2022-12-10 02:02:22 +00:00
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
dispatch({
|
|
|
|
type: CLOSE_LIGHTBOX,
|
|
|
|
});
|
|
|
|
};
|
|
|
|
}
|
|
|
|
|
2024-02-02 23:39:32 +00:00
|
|
|
function setPlaybackDisabled(
|
|
|
|
playbackDisabled: boolean
|
|
|
|
): ThunkAction<
|
|
|
|
void,
|
|
|
|
RootStateType,
|
|
|
|
unknown,
|
|
|
|
SetLightboxPlaybackDisabledActionType
|
|
|
|
> {
|
|
|
|
return (dispatch, getState) => {
|
|
|
|
const { lightbox } = getState();
|
|
|
|
|
|
|
|
if (!lightbox.isShowingLightbox) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
dispatch({
|
|
|
|
type: SET_LIGHTBOX_PLAYBACK_DISABLED,
|
|
|
|
payload: playbackDisabled,
|
|
|
|
});
|
|
|
|
};
|
|
|
|
}
|
|
|
|
|
2022-12-10 02:02:22 +00:00
|
|
|
function showLightboxWithMedia(
|
2023-09-26 15:38:21 +00:00
|
|
|
selectedIndex: number | undefined,
|
2023-01-13 20:07:26 +00:00
|
|
|
media: ReadonlyArray<ReadonlyDeep<MediaItemType>>
|
2022-12-10 02:02:22 +00:00
|
|
|
): ShowLightboxActionType {
|
|
|
|
return {
|
|
|
|
type: SHOW_LIGHTBOX,
|
|
|
|
payload: {
|
|
|
|
isViewOnce: false,
|
|
|
|
media,
|
2023-09-26 15:38:21 +00:00
|
|
|
selectedIndex,
|
2023-03-04 03:03:15 +00:00
|
|
|
hasPrevMessage: false,
|
|
|
|
hasNextMessage: false,
|
2022-12-10 02:02:22 +00:00
|
|
|
},
|
|
|
|
};
|
|
|
|
}
|
|
|
|
|
|
|
|
function showLightboxForViewOnceMedia(
|
|
|
|
messageId: string
|
|
|
|
): ThunkAction<void, RootStateType, unknown, ShowLightboxActionType> {
|
|
|
|
return async dispatch => {
|
|
|
|
log.info('showLightboxForViewOnceMedia: attempting to display message');
|
|
|
|
|
2023-10-04 00:12:57 +00:00
|
|
|
const message = await __DEPRECATED$getMessageById(messageId);
|
2022-12-10 02:02:22 +00:00
|
|
|
if (!message) {
|
|
|
|
throw new Error(
|
|
|
|
`showLightboxForViewOnceMedia: Message ${messageId} missing!`
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!isTapToView(message.attributes)) {
|
|
|
|
throw new Error(
|
|
|
|
`showLightboxForViewOnceMedia: Message ${message.idForLogging()} is not a tap to view message`
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (message.isErased()) {
|
|
|
|
throw new Error(
|
|
|
|
`showLightboxForViewOnceMedia: Message ${message.idForLogging()} is already erased`
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
const firstAttachment = (message.get('attachments') || [])[0];
|
|
|
|
if (!firstAttachment || !firstAttachment.path) {
|
|
|
|
throw new Error(
|
|
|
|
`showLightboxForViewOnceMedia: Message ${message.idForLogging()} had no first attachment with path`
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
const {
|
|
|
|
copyIntoTempDirectory,
|
|
|
|
getAbsoluteAttachmentPath,
|
|
|
|
getAbsoluteTempPath,
|
|
|
|
} = window.Signal.Migrations;
|
|
|
|
|
|
|
|
const absolutePath = getAbsoluteAttachmentPath(firstAttachment.path);
|
|
|
|
const { path: tempPath } = await copyIntoTempDirectory(absolutePath);
|
|
|
|
const tempAttachment = {
|
|
|
|
...firstAttachment,
|
|
|
|
path: tempPath,
|
|
|
|
};
|
|
|
|
|
|
|
|
await message.markViewOnceMessageViewed();
|
|
|
|
|
|
|
|
const { path, contentType } = tempAttachment;
|
|
|
|
|
|
|
|
const media = [
|
|
|
|
{
|
|
|
|
attachment: tempAttachment,
|
|
|
|
objectURL: getAbsoluteTempPath(path),
|
|
|
|
contentType,
|
|
|
|
index: 0,
|
|
|
|
message: {
|
|
|
|
attachments: message.get('attachments') || [],
|
|
|
|
id: message.get('id'),
|
|
|
|
conversationId: message.get('conversationId'),
|
|
|
|
received_at: message.get('received_at'),
|
|
|
|
received_at_ms: Number(message.get('received_at_ms')),
|
|
|
|
sent_at: message.get('sent_at'),
|
|
|
|
},
|
|
|
|
},
|
|
|
|
];
|
|
|
|
|
|
|
|
dispatch({
|
|
|
|
type: SHOW_LIGHTBOX,
|
|
|
|
payload: {
|
|
|
|
isViewOnce: true,
|
|
|
|
media,
|
2023-09-26 15:38:21 +00:00
|
|
|
selectedIndex: undefined,
|
2023-03-04 03:03:15 +00:00
|
|
|
hasPrevMessage: false,
|
|
|
|
hasNextMessage: false,
|
2022-12-10 02:02:22 +00:00
|
|
|
},
|
|
|
|
});
|
|
|
|
};
|
|
|
|
}
|
|
|
|
|
2023-03-04 03:03:15 +00:00
|
|
|
function filterValidAttachments(
|
|
|
|
attributes: MessageAttributesType
|
|
|
|
): Array<AttachmentType> {
|
|
|
|
return (attributes.attachments ?? []).filter(
|
|
|
|
item => item.thumbnail && !item.pending && !item.error
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
2022-12-10 02:02:22 +00:00
|
|
|
function showLightbox(opts: {
|
|
|
|
attachment: AttachmentType;
|
|
|
|
messageId: string;
|
|
|
|
}): ThunkAction<
|
|
|
|
void,
|
|
|
|
RootStateType,
|
|
|
|
unknown,
|
|
|
|
| ShowLightboxActionType
|
|
|
|
| ShowStickerPackPreviewActionType
|
|
|
|
| ShowToastActionType
|
|
|
|
> {
|
2022-12-14 18:12:04 +00:00
|
|
|
return async (dispatch, getState) => {
|
2022-12-10 02:02:22 +00:00
|
|
|
const { attachment, messageId } = opts;
|
|
|
|
|
2023-10-04 00:12:57 +00:00
|
|
|
const message = await __DEPRECATED$getMessageById(messageId);
|
2022-12-10 02:02:22 +00:00
|
|
|
if (!message) {
|
|
|
|
throw new Error(`showLightbox: Message ${messageId} missing!`);
|
|
|
|
}
|
|
|
|
const sticker = message.get('sticker');
|
|
|
|
if (sticker) {
|
|
|
|
const { packId, packKey } = sticker;
|
|
|
|
dispatch(showStickerPackPreview(packId, packKey));
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
const { contentType } = attachment;
|
|
|
|
|
|
|
|
if (
|
|
|
|
!isImageTypeSupported(contentType) &&
|
|
|
|
!isVideoTypeSupported(contentType)
|
|
|
|
) {
|
2022-12-14 18:12:04 +00:00
|
|
|
saveAttachmentFromMessage(messageId, attachment)(
|
|
|
|
dispatch,
|
|
|
|
getState,
|
|
|
|
null
|
|
|
|
);
|
2022-12-10 02:02:22 +00:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2023-03-04 03:03:15 +00:00
|
|
|
const attachments = filterValidAttachments(message.attributes);
|
2022-12-10 02:02:22 +00:00
|
|
|
const loop = isGIF(attachments);
|
|
|
|
|
|
|
|
const { getAbsoluteAttachmentPath } = window.Signal.Migrations;
|
|
|
|
|
2023-03-04 03:03:15 +00:00
|
|
|
const authorId =
|
|
|
|
window.ConversationController.lookupOrCreate({
|
2023-08-16 20:54:39 +00:00
|
|
|
serviceId: message.get('sourceServiceId'),
|
2023-03-04 03:03:15 +00:00
|
|
|
e164: message.get('source'),
|
|
|
|
reason: 'conversation_view.showLightBox',
|
|
|
|
})?.id || message.get('conversationId');
|
|
|
|
const receivedAt = message.get('received_at');
|
|
|
|
const sentAt = message.get('sent_at');
|
|
|
|
|
|
|
|
const media = attachments.map((item, index) => ({
|
|
|
|
objectURL: getAbsoluteAttachmentPath(item.path ?? ''),
|
|
|
|
path: item.path,
|
|
|
|
contentType: item.contentType,
|
|
|
|
loop,
|
|
|
|
index,
|
|
|
|
message: {
|
|
|
|
attachments: message.get('attachments') || [],
|
|
|
|
id: messageId,
|
|
|
|
conversationId: authorId,
|
|
|
|
received_at: receivedAt,
|
|
|
|
received_at_ms: Number(message.get('received_at_ms')),
|
|
|
|
sent_at: sentAt,
|
|
|
|
},
|
|
|
|
attachment: item,
|
|
|
|
thumbnailObjectUrl:
|
|
|
|
item.thumbnail?.objectUrl ||
|
|
|
|
getAbsoluteAttachmentPath(item.thumbnail?.path ?? ''),
|
|
|
|
}));
|
2022-12-10 02:02:22 +00:00
|
|
|
|
|
|
|
if (!media.length) {
|
|
|
|
log.error(
|
|
|
|
'showLightbox: unable to load attachment',
|
2023-03-04 03:03:15 +00:00
|
|
|
sentAt,
|
|
|
|
message.get('attachments')?.map(x => ({
|
|
|
|
thumbnail: !!x.thumbnail,
|
2022-12-10 02:02:22 +00:00
|
|
|
contentType: x.contentType,
|
2023-03-04 03:03:15 +00:00
|
|
|
pending: x.pending,
|
2022-12-10 02:02:22 +00:00
|
|
|
error: x.error,
|
|
|
|
flags: x.flags,
|
|
|
|
path: x.path,
|
|
|
|
size: x.size,
|
|
|
|
}))
|
|
|
|
);
|
|
|
|
|
|
|
|
dispatch({
|
|
|
|
type: SHOW_TOAST,
|
|
|
|
payload: {
|
|
|
|
toastType: ToastType.UnableToLoadAttachment,
|
|
|
|
},
|
|
|
|
});
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2023-03-04 03:03:15 +00:00
|
|
|
const { older, newer } =
|
|
|
|
await dataInterface.getConversationRangeCenteredOnMessage({
|
|
|
|
conversationId: message.get('conversationId'),
|
|
|
|
messageId,
|
|
|
|
receivedAt,
|
|
|
|
sentAt,
|
|
|
|
limit: 1,
|
|
|
|
storyId: undefined,
|
|
|
|
includeStoryReplies: false,
|
|
|
|
|
|
|
|
// This is the critical option since we only want messages with visual
|
|
|
|
// attachments.
|
|
|
|
requireVisualMediaAttachments: true,
|
|
|
|
});
|
|
|
|
|
2022-12-10 02:02:22 +00:00
|
|
|
dispatch({
|
|
|
|
type: SHOW_LIGHTBOX,
|
|
|
|
payload: {
|
|
|
|
isViewOnce: false,
|
|
|
|
media,
|
2023-09-26 15:38:21 +00:00
|
|
|
selectedIndex: media.findIndex(({ path }) => path === attachment.path),
|
2023-03-04 03:03:15 +00:00
|
|
|
hasPrevMessage:
|
|
|
|
older.length > 0 && filterValidAttachments(older[0]).length > 0,
|
|
|
|
hasNextMessage:
|
|
|
|
newer.length > 0 && filterValidAttachments(newer[0]).length > 0,
|
2024-02-02 23:39:32 +00:00
|
|
|
playbackDisabled: false,
|
2022-12-10 02:02:22 +00:00
|
|
|
},
|
|
|
|
});
|
|
|
|
};
|
|
|
|
}
|
|
|
|
|
2023-03-04 03:03:15 +00:00
|
|
|
enum AdjacentMessageDirection {
|
|
|
|
Previous = 'Previous',
|
|
|
|
Next = 'Next',
|
|
|
|
}
|
|
|
|
|
|
|
|
function showLightboxForAdjacentMessage(
|
|
|
|
direction: AdjacentMessageDirection
|
|
|
|
): ThunkAction<
|
|
|
|
void,
|
|
|
|
RootStateType,
|
|
|
|
unknown,
|
|
|
|
ShowLightboxActionType | ShowToastActionType
|
|
|
|
> {
|
|
|
|
return async (dispatch, getState) => {
|
|
|
|
const { lightbox } = getState();
|
|
|
|
|
|
|
|
if (!lightbox.isShowingLightbox || lightbox.media.length === 0) {
|
|
|
|
log.warn('showLightboxForAdjacentMessage: empty lightbox');
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
const [media] = lightbox.media;
|
|
|
|
const {
|
|
|
|
id: messageId,
|
|
|
|
received_at: receivedAt,
|
|
|
|
sent_at: sentAt,
|
|
|
|
} = media.message;
|
|
|
|
|
2023-10-04 00:12:57 +00:00
|
|
|
const message = await __DEPRECATED$getMessageById(messageId);
|
2023-03-04 03:03:15 +00:00
|
|
|
if (!message) {
|
|
|
|
log.warn('showLightboxForAdjacentMessage: original message is gone');
|
|
|
|
dispatch({
|
|
|
|
type: SHOW_TOAST,
|
|
|
|
payload: {
|
|
|
|
toastType: ToastType.UnableToLoadAttachment,
|
|
|
|
},
|
|
|
|
});
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
const conversationId = message.get('conversationId');
|
|
|
|
|
|
|
|
const options = {
|
|
|
|
conversationId,
|
|
|
|
messageId,
|
|
|
|
receivedAt,
|
|
|
|
sentAt,
|
|
|
|
limit: 1,
|
|
|
|
storyId: undefined,
|
|
|
|
includeStoryReplies: false,
|
|
|
|
|
|
|
|
// This is the critical option since we only want messages with visual
|
|
|
|
// attachments.
|
|
|
|
requireVisualMediaAttachments: true,
|
|
|
|
};
|
|
|
|
|
|
|
|
const [adjacent] =
|
|
|
|
direction === AdjacentMessageDirection.Previous
|
|
|
|
? await dataInterface.getOlderMessagesByConversation(options)
|
|
|
|
: await dataInterface.getNewerMessagesByConversation(options);
|
|
|
|
|
|
|
|
if (!adjacent) {
|
|
|
|
log.warn(
|
|
|
|
`showLightboxForAdjacentMessage(${direction}, ${messageId}, ` +
|
|
|
|
`${sentAt}): no ${direction} message found`
|
|
|
|
);
|
|
|
|
dispatch({
|
|
|
|
type: SHOW_TOAST,
|
|
|
|
payload: {
|
|
|
|
toastType: ToastType.UnableToLoadAttachment,
|
|
|
|
},
|
|
|
|
});
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
const attachments = filterValidAttachments(adjacent);
|
|
|
|
if (!attachments.length) {
|
|
|
|
log.warn(
|
|
|
|
`showLightboxForAdjacentMessage(${direction}, ${messageId}, ` +
|
|
|
|
`${sentAt}): no valid attachments found`
|
|
|
|
);
|
|
|
|
dispatch({
|
|
|
|
type: SHOW_TOAST,
|
|
|
|
payload: {
|
|
|
|
toastType: ToastType.UnableToLoadAttachment,
|
|
|
|
},
|
|
|
|
});
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
dispatch(
|
|
|
|
showLightbox({
|
|
|
|
attachment:
|
|
|
|
direction === AdjacentMessageDirection.Previous
|
|
|
|
? attachments[attachments.length - 1]
|
|
|
|
: attachments[0],
|
|
|
|
messageId: adjacent.id,
|
|
|
|
})
|
|
|
|
);
|
|
|
|
};
|
|
|
|
}
|
|
|
|
|
|
|
|
function showLightboxForNextMessage(): ThunkAction<
|
|
|
|
void,
|
|
|
|
RootStateType,
|
|
|
|
unknown,
|
|
|
|
ShowLightboxActionType
|
|
|
|
> {
|
|
|
|
return showLightboxForAdjacentMessage(AdjacentMessageDirection.Next);
|
|
|
|
}
|
|
|
|
|
|
|
|
function showLightboxForPrevMessage(): ThunkAction<
|
|
|
|
void,
|
|
|
|
RootStateType,
|
|
|
|
unknown,
|
|
|
|
ShowLightboxActionType
|
|
|
|
> {
|
|
|
|
return showLightboxForAdjacentMessage(AdjacentMessageDirection.Previous);
|
|
|
|
}
|
|
|
|
|
2023-09-26 15:38:21 +00:00
|
|
|
function setSelectedLightboxIndex(
|
|
|
|
index: number
|
|
|
|
): SetSelectedLightboxIndexActionType {
|
2023-03-04 03:03:15 +00:00
|
|
|
return {
|
2023-09-26 15:38:21 +00:00
|
|
|
type: SET_SELECTED_LIGHTBOX_INDEX,
|
|
|
|
payload: index,
|
2023-03-04 03:03:15 +00:00
|
|
|
};
|
|
|
|
}
|
|
|
|
|
2022-12-10 02:02:22 +00:00
|
|
|
export const actions = {
|
|
|
|
closeLightbox,
|
|
|
|
showLightbox,
|
|
|
|
showLightboxForViewOnceMedia,
|
|
|
|
showLightboxWithMedia,
|
2023-03-04 03:03:15 +00:00
|
|
|
showLightboxForPrevMessage,
|
|
|
|
showLightboxForNextMessage,
|
2023-09-26 15:38:21 +00:00
|
|
|
setSelectedLightboxIndex,
|
2024-02-02 23:39:32 +00:00
|
|
|
setPlaybackDisabled,
|
2022-12-10 02:02:22 +00:00
|
|
|
};
|
|
|
|
|
|
|
|
export const useLightboxActions = (): BoundActionCreatorsMapObject<
|
|
|
|
typeof actions
|
|
|
|
> => useBoundActions(actions);
|
|
|
|
|
|
|
|
export function getEmptyState(): LightboxStateType {
|
|
|
|
return {
|
|
|
|
isShowingLightbox: false,
|
|
|
|
};
|
|
|
|
}
|
|
|
|
|
|
|
|
export function reducer(
|
|
|
|
state: Readonly<LightboxStateType> = getEmptyState(),
|
|
|
|
action: Readonly<LightboxActionType>
|
|
|
|
): LightboxStateType {
|
|
|
|
if (action.type === CLOSE_LIGHTBOX) {
|
|
|
|
return getEmptyState();
|
|
|
|
}
|
|
|
|
|
|
|
|
if (action.type === SHOW_LIGHTBOX) {
|
|
|
|
return {
|
|
|
|
...action.payload,
|
|
|
|
isShowingLightbox: true,
|
2024-02-02 23:39:32 +00:00
|
|
|
playbackDisabled: false,
|
2022-12-10 02:02:22 +00:00
|
|
|
};
|
|
|
|
}
|
|
|
|
|
2023-09-26 15:38:21 +00:00
|
|
|
if (action.type === SET_SELECTED_LIGHTBOX_INDEX) {
|
2023-03-04 03:03:15 +00:00
|
|
|
if (!state.isShowingLightbox) {
|
|
|
|
return state;
|
|
|
|
}
|
|
|
|
|
|
|
|
return {
|
|
|
|
...state,
|
2023-09-26 15:38:21 +00:00
|
|
|
selectedIndex: Math.max(
|
|
|
|
0,
|
|
|
|
Math.min(state.media.length - 1, action.payload)
|
|
|
|
),
|
2023-03-04 03:03:15 +00:00
|
|
|
};
|
|
|
|
}
|
|
|
|
|
2024-02-02 23:39:32 +00:00
|
|
|
if (action.type === SET_LIGHTBOX_PLAYBACK_DISABLED) {
|
|
|
|
if (!state.isShowingLightbox) {
|
|
|
|
return state;
|
|
|
|
}
|
|
|
|
|
|
|
|
return {
|
|
|
|
...state,
|
|
|
|
playbackDisabled: action.payload,
|
|
|
|
};
|
|
|
|
}
|
|
|
|
|
2023-01-05 22:16:56 +00:00
|
|
|
if (
|
|
|
|
action.type === MESSAGE_CHANGED ||
|
|
|
|
action.type === MESSAGE_DELETED ||
|
|
|
|
action.type === MESSAGE_EXPIRED
|
|
|
|
) {
|
2022-12-20 17:50:23 +00:00
|
|
|
if (!state.isShowingLightbox) {
|
|
|
|
return state;
|
|
|
|
}
|
|
|
|
|
2023-01-05 22:16:56 +00:00
|
|
|
if (action.type === MESSAGE_EXPIRED && !state.isViewOnce) {
|
|
|
|
return state;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (
|
|
|
|
action.type === MESSAGE_CHANGED &&
|
|
|
|
!action.payload.data.deletedForEveryone
|
|
|
|
) {
|
2022-12-20 17:50:23 +00:00
|
|
|
return state;
|
|
|
|
}
|
|
|
|
|
2023-01-05 22:16:56 +00:00
|
|
|
const nextMedia = state.media.filter(
|
|
|
|
item => item.message.id !== action.payload.id
|
2022-12-20 17:50:23 +00:00
|
|
|
);
|
|
|
|
|
2023-01-05 22:16:56 +00:00
|
|
|
if (nextMedia.length === state.media.length) {
|
2022-12-20 17:50:23 +00:00
|
|
|
return state;
|
|
|
|
}
|
|
|
|
|
2023-01-05 22:16:56 +00:00
|
|
|
if (!nextMedia.length) {
|
|
|
|
return getEmptyState();
|
|
|
|
}
|
|
|
|
|
|
|
|
return {
|
|
|
|
...state,
|
|
|
|
media: nextMedia,
|
|
|
|
};
|
2022-12-20 17:50:23 +00:00
|
|
|
}
|
|
|
|
|
2022-12-10 02:02:22 +00:00
|
|
|
return state;
|
|
|
|
}
|