kopia lustrzana https://gitlab.com/soapbox-pub/soapbox
refactor: remove immutable, convert to use plain javascript object
all changes were made to the StatusMedia component and its components, including all subcomponents recursively. other components that dispatch openModal() of 'MEDIA' still need to be updatedfinish-refactor-pure-status-component
rodzic
77e45dea2c
commit
10ff18e76f
|
@ -3,12 +3,10 @@ import { Suspense } from 'react';
|
|||
import { openModal } from 'soapbox/actions/modals.ts';
|
||||
import { MediaGallery } from 'soapbox/features/ui/util/async-components.ts';
|
||||
import { useAppDispatch } from 'soapbox/hooks/useAppDispatch.ts';
|
||||
|
||||
import type { List as ImmutableList } from 'immutable';
|
||||
import type { Attachment } from 'soapbox/types/entities.ts';
|
||||
import { Attachment } from 'soapbox/schemas/index.ts';
|
||||
|
||||
interface IAttachmentThumbs {
|
||||
media: ImmutableList<Attachment>;
|
||||
media: readonly Attachment[];
|
||||
onClick?(): void;
|
||||
sensitive?: boolean;
|
||||
}
|
||||
|
@ -18,7 +16,7 @@ const AttachmentThumbs = (props: IAttachmentThumbs) => {
|
|||
const dispatch = useAppDispatch();
|
||||
|
||||
const fallback = <div className='!h-[50px] bg-transparent' />;
|
||||
const onOpenMedia = (media: ImmutableList<Attachment>, index: number) => dispatch(openModal('MEDIA', { media, index }));
|
||||
const onOpenMedia = (media: readonly Attachment[], index: number) => dispatch(openModal('MEDIA', { media, index }));
|
||||
|
||||
return (
|
||||
<div className='relative'>
|
||||
|
|
|
@ -9,13 +9,12 @@ import { closeDropdownMenu as closeDropdownMenuRedux, openDropdownMenu } from 's
|
|||
import { closeModal, openModal } from 'soapbox/actions/modals.ts';
|
||||
import IconButton from 'soapbox/components/ui/icon-button.tsx';
|
||||
import Portal from 'soapbox/components/ui/portal.tsx';
|
||||
import { Entities, EntityTypes } from 'soapbox/entity-store/entities.ts';
|
||||
import { useAppDispatch } from 'soapbox/hooks/useAppDispatch.ts';
|
||||
import { userTouching } from 'soapbox/is-mobile.ts';
|
||||
|
||||
import DropdownMenuItem, { MenuItem } from './dropdown-menu-item.tsx';
|
||||
|
||||
import type { Status } from 'soapbox/types/entities.ts';
|
||||
|
||||
export type Menu = Array<MenuItem | null>;
|
||||
|
||||
interface IDropdownMenu {
|
||||
|
@ -28,7 +27,7 @@ interface IDropdownMenu {
|
|||
onShiftClick?: React.EventHandler<React.MouseEvent | React.KeyboardEvent>;
|
||||
placement?: Placement;
|
||||
src?: string;
|
||||
status?: Status;
|
||||
status?: EntityTypes[Entities.STATUSES];
|
||||
title?: string;
|
||||
}
|
||||
|
||||
|
|
|
@ -8,7 +8,7 @@ import StillImage from 'soapbox/components/still-image.tsx';
|
|||
import { MIMETYPE_ICONS } from 'soapbox/components/upload.tsx';
|
||||
import { useSettings } from 'soapbox/hooks/useSettings.ts';
|
||||
import { useSoapboxConfig } from 'soapbox/hooks/useSoapboxConfig.ts';
|
||||
import { Attachment } from 'soapbox/types/entities.ts';
|
||||
import { Attachment } from 'soapbox/schemas/index.ts';
|
||||
import { truncateFilename } from 'soapbox/utils/media.ts';
|
||||
|
||||
import { isIOS } from '../is-mobile.ts';
|
||||
|
@ -16,8 +16,6 @@ import { isPanoramic, isPortrait, isNonConformingRatio, minimumAspectRatio, maxi
|
|||
|
||||
import SvgIcon from './ui/svg-icon.tsx';
|
||||
|
||||
import type { List as ImmutableList } from 'immutable';
|
||||
|
||||
// const Gameboy = lazy(() => import('./gameboy'));
|
||||
|
||||
const ATTACHMENT_LIMIT = 4;
|
||||
|
@ -46,7 +44,8 @@ const withinLimits = (aspectRatio: number) => {
|
|||
};
|
||||
|
||||
const shouldLetterbox = (attachment: Attachment): boolean => {
|
||||
const aspectRatio = attachment.getIn(['meta', 'original', 'aspect']) as number | undefined;
|
||||
const aspectRatio = 'meta' in attachment && 'original' in attachment.meta && (attachment)?.meta?.original?.aspect;
|
||||
|
||||
if (!aspectRatio) return true;
|
||||
|
||||
return !withinLimits(aspectRatio);
|
||||
|
@ -159,7 +158,7 @@ const Item: React.FC<IItem> = ({
|
|||
const attachmentIcon = (
|
||||
<SvgIcon
|
||||
className={clsx('size-16 text-gray-800 dark:text-gray-200', { 'size-8': compact })}
|
||||
src={MIMETYPE_ICONS[attachment.getIn(['pleroma', 'mime_type']) as string] || paperclipIcon}
|
||||
src={MIMETYPE_ICONS[attachment?.pleroma?.mime_type as string] || paperclipIcon}
|
||||
/>
|
||||
);
|
||||
|
||||
|
@ -291,9 +290,9 @@ const Item: React.FC<IItem> = ({
|
|||
|
||||
export interface IMediaGallery {
|
||||
sensitive?: boolean;
|
||||
media: ImmutableList<Attachment>;
|
||||
media: readonly Attachment[];
|
||||
height?: number;
|
||||
onOpenMedia: (media: ImmutableList<Attachment>, index: number) => void;
|
||||
onOpenMedia: (media: readonly Attachment[], index: number) => void;
|
||||
defaultWidth?: number;
|
||||
cacheWidth?: (width: number) => void;
|
||||
visible?: boolean;
|
||||
|
@ -323,7 +322,7 @@ const MediaGallery: React.FC<IMediaGallery> = (props) => {
|
|||
|
||||
const getSizeDataSingle = (): SizeData => {
|
||||
const w = width || defaultWidth;
|
||||
const aspectRatio = media.getIn([0, 'meta', 'original', 'aspect']) as number | undefined;
|
||||
const aspectRatio = 'meta' in media[0] && 'original' in media[0].meta && (media[0])?.meta?.original?.aspect;
|
||||
|
||||
const getHeight = () => {
|
||||
if (!aspectRatio) return w * 9 / 16;
|
||||
|
@ -349,7 +348,9 @@ const MediaGallery: React.FC<IMediaGallery> = (props) => {
|
|||
let itemsDimensions: Dimensions[] = [];
|
||||
|
||||
const ratios = Array(size).fill(null).map((_, i) =>
|
||||
media.getIn([i, 'meta', 'original', 'aspect']) as number,
|
||||
'meta' in media[i] && 'original' in media[i].meta && typeof media[i].meta?.original?.aspect === 'number'
|
||||
? media[i].meta.original.aspect
|
||||
: undefined as unknown as number, // NOTE: the old logic returned undefined anyways, and the implementation of the functions below call 'isNaN', such as the 'isPortrait' function
|
||||
);
|
||||
|
||||
const [ar1, ar2, ar3, ar4] = ratios;
|
||||
|
@ -547,9 +548,9 @@ const MediaGallery: React.FC<IMediaGallery> = (props) => {
|
|||
};
|
||||
};
|
||||
|
||||
const sizeData: SizeData = getSizeData(media.size);
|
||||
const sizeData: SizeData = getSizeData(media.length);
|
||||
|
||||
const children = media.take(ATTACHMENT_LIMIT).map((attachment, i) => (
|
||||
const children = media.slice(0, ATTACHMENT_LIMIT).map((attachment, i) => (
|
||||
<Item
|
||||
key={attachment.id}
|
||||
onClick={handleClick}
|
||||
|
@ -560,7 +561,7 @@ const MediaGallery: React.FC<IMediaGallery> = (props) => {
|
|||
visible={!!props.visible}
|
||||
dimensions={sizeData.itemsDimensions[i]}
|
||||
last={i === ATTACHMENT_LIMIT - 1}
|
||||
total={media.size}
|
||||
total={media.length}
|
||||
compact={compact}
|
||||
/>
|
||||
));
|
||||
|
|
|
@ -3,7 +3,6 @@ import linkIcon from '@tabler/icons/outline/link.svg';
|
|||
import playerPlayIcon from '@tabler/icons/outline/player-play.svg';
|
||||
import zoomInIcon from '@tabler/icons/outline/zoom-in.svg';
|
||||
import clsx from 'clsx';
|
||||
import { List as ImmutableList } from 'immutable';
|
||||
import { useState, useEffect, useRef } from 'react';
|
||||
|
||||
import Blurhash from 'soapbox/components/blurhash.tsx';
|
||||
|
@ -12,17 +11,18 @@ import Stack from 'soapbox/components/ui/stack.tsx';
|
|||
import SvgIcon from 'soapbox/components/ui/svg-icon.tsx';
|
||||
import Text from 'soapbox/components/ui/text.tsx';
|
||||
import { normalizeAttachment } from 'soapbox/normalizers/index.ts';
|
||||
import { Attachment } from 'soapbox/schemas/index.ts';
|
||||
import { addAutoPlay } from 'soapbox/utils/media.ts';
|
||||
import { getTextDirection } from 'soapbox/utils/rtl.ts';
|
||||
|
||||
import type { Card as CardEntity, Attachment } from 'soapbox/types/entities.ts';
|
||||
import type { Card as CardEntity } from 'soapbox/types/entities.ts';
|
||||
|
||||
/** Props for `PreviewCard`. */
|
||||
interface IPreviewCard {
|
||||
card: CardEntity;
|
||||
maxTitle?: number;
|
||||
maxDescription?: number;
|
||||
onOpenMedia: (attachments: ImmutableList<Attachment>, index: number) => void;
|
||||
onOpenMedia: (attachments: readonly Attachment[], index: number) => void;
|
||||
compact?: boolean;
|
||||
defaultWidth?: number;
|
||||
cacheWidth?: (width: number) => void;
|
||||
|
@ -73,9 +73,9 @@ const PreviewCard: React.FC<IPreviewCard> = ({
|
|||
height: card.height,
|
||||
},
|
||||
},
|
||||
});
|
||||
}).toJS();
|
||||
|
||||
onOpenMedia(ImmutableList([attachment]), 0);
|
||||
onOpenMedia([{ ...attachment, blurhash: attachment.blurhash === undefined ? null : attachment.blurhash } as Attachment], 0);
|
||||
};
|
||||
|
||||
const handleEmbedClick: React.MouseEventHandler = (e) => {
|
||||
|
|
|
@ -2,11 +2,10 @@ import circlesIcon from '@tabler/icons/outline/circles.svg';
|
|||
import pinnedIcon from '@tabler/icons/outline/pinned.svg';
|
||||
import repeatIcon from '@tabler/icons/outline/repeat.svg';
|
||||
import clsx from 'clsx';
|
||||
import { useCallback, useEffect, useRef, useState } from 'react';
|
||||
import { useEffect, useRef, useState } from 'react';
|
||||
import { useIntl, FormattedMessage, defineMessages } from 'react-intl';
|
||||
import { Link, useHistory } from 'react-router-dom';
|
||||
|
||||
import { importFetchedStatuses } from 'soapbox/actions/importer/index.ts';
|
||||
import { openModal } from 'soapbox/actions/modals.ts';
|
||||
import { unfilterStatus } from 'soapbox/actions/statuses.ts';
|
||||
import PureEventPreview from 'soapbox/components/pure-event-preview.tsx';
|
||||
|
@ -23,14 +22,14 @@ import { EntityTypes, Entities } from 'soapbox/entity-store/entities.ts';
|
|||
import QuotedStatus from 'soapbox/features/status/containers/quoted-status-container.tsx';
|
||||
import { HotKeys } from 'soapbox/features/ui/components/hotkeys.tsx';
|
||||
import { useAppDispatch } from 'soapbox/hooks/useAppDispatch.ts';
|
||||
import { useAppSelector } from 'soapbox/hooks/useAppSelector.ts';
|
||||
import { useFavourite } from 'soapbox/hooks/useFavourite.ts';
|
||||
import { useMentionCompose } from 'soapbox/hooks/useMentionCompose.ts';
|
||||
import { useReblog } from 'soapbox/hooks/useReblog.ts';
|
||||
import { useReplyCompose } from 'soapbox/hooks/useReplyCompose.ts';
|
||||
import { useSettings } from 'soapbox/hooks/useSettings.ts';
|
||||
import { useStatusHidden } from 'soapbox/hooks/useStatusHidden.ts';
|
||||
import { makeGetStatus } from 'soapbox/selectors/index.ts';
|
||||
import { normalizeStatus } from 'soapbox/normalizers/index.ts';
|
||||
import { Status } from 'soapbox/types/entities.ts';
|
||||
import { emojifyText } from 'soapbox/utils/emojify.tsx';
|
||||
import { defaultMediaVisibility, textForScreenReader, getActualStatus } from 'soapbox/utils/status.ts';
|
||||
|
||||
|
@ -127,16 +126,7 @@ const PureStatus: React.FC<IPureStatus> = (props) => {
|
|||
}
|
||||
}, [overlay.current]);
|
||||
|
||||
// TODO: remove this code, it will be removed once all components in this file are pure.
|
||||
useEffect(() => {
|
||||
dispatch(importFetchedStatuses([status]));
|
||||
}, []);
|
||||
const getStatus = useCallback(makeGetStatus(), []);
|
||||
const statusImmutable = useAppSelector(state => getStatus(state, { id: status.id }));
|
||||
if (!statusImmutable) {
|
||||
return null;
|
||||
}
|
||||
// END TODO
|
||||
const statusImmutable = normalizeStatus(status) as Status; // TODO: remove this line, it will be removed once all components in this file are pure.
|
||||
|
||||
const handleToggleMediaVisibility = (): void => {
|
||||
setShowMedia(!showMedia);
|
||||
|
@ -486,7 +476,7 @@ const PureStatus: React.FC<IPureStatus> = (props) => {
|
|||
{(quote || actualStatus.card || actualStatus.media_attachments.length > 0) && (
|
||||
<Stack space={4}>
|
||||
<StatusMedia
|
||||
status={statusImmutable} // FIXME: stop using 'statusImmutable' and use 'status' variable directly, for that create a new component called 'PureStatusMedia'
|
||||
status={status}
|
||||
muted={muted}
|
||||
onClick={handleClick}
|
||||
showMedia={showMedia}
|
||||
|
|
|
@ -7,6 +7,7 @@ import { useHistory } from 'react-router-dom';
|
|||
import StatusMedia from 'soapbox/components/status-media.tsx';
|
||||
import Stack from 'soapbox/components/ui/stack.tsx';
|
||||
import AccountContainer from 'soapbox/containers/account-container.tsx';
|
||||
import { Entities, EntityTypes } from 'soapbox/entity-store/entities.ts';
|
||||
import { useSettings } from 'soapbox/hooks/useSettings.ts';
|
||||
import { defaultMediaVisibility } from 'soapbox/utils/status.ts';
|
||||
|
||||
|
@ -138,7 +139,7 @@ const QuotedStatus: React.FC<IQuotedStatus> = ({ status, onCancel, compose }) =>
|
|||
|
||||
{status.media_attachments.size > 0 && (
|
||||
<StatusMedia
|
||||
status={status}
|
||||
status={status.toJS() as EntityTypes[Entities.STATUSES]}
|
||||
muted={compose}
|
||||
showMedia={showMedia}
|
||||
onToggleVisibility={handleToggleMediaVisibility}
|
||||
|
|
|
@ -49,6 +49,7 @@ import DropdownMenu from 'soapbox/components/dropdown-menu/index.ts';
|
|||
import StatusActionButton from 'soapbox/components/status-action-button.tsx';
|
||||
import StatusReactionWrapper from 'soapbox/components/status-reaction-wrapper.tsx';
|
||||
import HStack from 'soapbox/components/ui/hstack.tsx';
|
||||
import { Entities, EntityTypes } from 'soapbox/entity-store/entities.ts';
|
||||
import { useAppDispatch } from 'soapbox/hooks/useAppDispatch.ts';
|
||||
import { useAppSelector } from 'soapbox/hooks/useAppSelector.ts';
|
||||
import { useFeatures } from 'soapbox/hooks/useFeatures.ts';
|
||||
|
@ -826,7 +827,7 @@ const StatusActionBar: React.FC<IStatusActionBar> = ({
|
|||
/>
|
||||
)}
|
||||
|
||||
<DropdownMenu items={menu} status={status}>
|
||||
<DropdownMenu items={menu} status={status.toJS() as EntityTypes[Entities.STATUSES]}>
|
||||
<StatusActionButton
|
||||
title={intl.formatMessage(messages.more)}
|
||||
icon={dotsIcon}
|
||||
|
|
|
@ -4,17 +4,16 @@ import { Suspense } from 'react';
|
|||
import { openModal } from 'soapbox/actions/modals.ts';
|
||||
import AttachmentThumbs from 'soapbox/components/attachment-thumbs.tsx';
|
||||
import PreviewCard from 'soapbox/components/preview-card.tsx';
|
||||
import { Entities, EntityTypes } from 'soapbox/entity-store/entities.ts';
|
||||
import { GroupLinkPreview } from 'soapbox/features/groups/components/group-link-preview.tsx';
|
||||
import PlaceholderCard from 'soapbox/features/placeholder/components/placeholder-card.tsx';
|
||||
import { MediaGallery, Video, Audio } from 'soapbox/features/ui/util/async-components.ts';
|
||||
import { useAppDispatch } from 'soapbox/hooks/useAppDispatch.ts';
|
||||
|
||||
import type { List as ImmutableList } from 'immutable';
|
||||
import type { Status, Attachment } from 'soapbox/types/entities.ts';
|
||||
import { Attachment } from 'soapbox/schemas/index.ts';
|
||||
|
||||
interface IStatusMedia {
|
||||
/** Status entity to render media for. */
|
||||
status: Status;
|
||||
status: EntityTypes[Entities.STATUSES];
|
||||
/** Whether to display compact media. */
|
||||
muted?: boolean;
|
||||
/** Callback when compact media is clicked. */
|
||||
|
@ -35,8 +34,8 @@ const StatusMedia: React.FC<IStatusMedia> = ({
|
|||
}) => {
|
||||
const dispatch = useAppDispatch();
|
||||
|
||||
const size = status.media_attachments.size;
|
||||
const firstAttachment = status.media_attachments.first();
|
||||
const size = status.media_attachments.length;
|
||||
const firstAttachment = status.media_attachments[0];
|
||||
|
||||
let media: JSX.Element | null = null;
|
||||
|
||||
|
@ -52,7 +51,7 @@ const StatusMedia: React.FC<IStatusMedia> = ({
|
|||
return <div className='relative mt-2 block cursor-pointer border-0 bg-cover bg-center bg-no-repeat' style={{ height: '285px' }} />;
|
||||
};
|
||||
|
||||
const openMedia = (media: ImmutableList<Attachment>, index: number) => {
|
||||
const openMedia = (media: readonly Attachment[], index: number) => {
|
||||
dispatch(openModal('MEDIA', { media, status, index }));
|
||||
};
|
||||
|
||||
|
@ -72,10 +71,10 @@ const StatusMedia: React.FC<IStatusMedia> = ({
|
|||
<Suspense fallback={renderLoadingVideoPlayer()}>
|
||||
<Video
|
||||
preview={video.preview_url}
|
||||
blurhash={video.blurhash}
|
||||
blurhash={video.blurhash ?? undefined}
|
||||
src={video.url}
|
||||
alt={video.description}
|
||||
aspectRatio={Number(video.meta.getIn(['original', 'aspect']))}
|
||||
aspectRatio={Number(video.meta?.original?.aspect)}
|
||||
height={285}
|
||||
visible={showMedia}
|
||||
inline
|
||||
|
@ -90,11 +89,11 @@ const StatusMedia: React.FC<IStatusMedia> = ({
|
|||
<Audio
|
||||
src={attachment.url}
|
||||
alt={attachment.description}
|
||||
poster={attachment.preview_url !== attachment.url ? attachment.preview_url : status.getIn(['account', 'avatar_static']) as string | undefined}
|
||||
backgroundColor={attachment.meta.getIn(['colors', 'background']) as string | undefined}
|
||||
foregroundColor={attachment.meta.getIn(['colors', 'foreground']) as string | undefined}
|
||||
accentColor={attachment.meta.getIn(['colors', 'accent']) as string | undefined}
|
||||
duration={attachment.meta.getIn(['original', 'duration'], 0) as number | undefined}
|
||||
poster={attachment.preview_url !== attachment.url ? attachment.preview_url : status.account.avatar_static}
|
||||
backgroundColor={attachment.meta?.colors?.background}
|
||||
foregroundColor={attachment.meta?.colors?.foreground}
|
||||
accentColor={attachment.meta?.colors?.accent}
|
||||
duration={attachment.meta?.duration ?? 0}
|
||||
height={263}
|
||||
/>
|
||||
</Suspense>
|
||||
|
|
|
@ -16,6 +16,7 @@ import Icon from 'soapbox/components/ui/icon.tsx';
|
|||
import Stack from 'soapbox/components/ui/stack.tsx';
|
||||
import Text from 'soapbox/components/ui/text.tsx';
|
||||
import AccountContainer from 'soapbox/containers/account-container.tsx';
|
||||
import { Entities, EntityTypes } from 'soapbox/entity-store/entities.ts';
|
||||
import QuotedStatus from 'soapbox/features/status/containers/quoted-status-container.tsx';
|
||||
import { HotKeys } from 'soapbox/features/ui/components/hotkeys.tsx';
|
||||
import { useAppDispatch } from 'soapbox/hooks/useAppDispatch.ts';
|
||||
|
@ -465,7 +466,7 @@ const Status: React.FC<IStatus> = (props) => {
|
|||
{(quote || actualStatus.card || actualStatus.media_attachments.size > 0) && (
|
||||
<Stack space={4}>
|
||||
<StatusMedia
|
||||
status={actualStatus}
|
||||
status={actualStatus.toJS() as EntityTypes[Entities.STATUSES]}
|
||||
muted={muted}
|
||||
onClick={handleClick}
|
||||
showMedia={showMedia}
|
||||
|
|
|
@ -9,6 +9,7 @@ import StatusContent from 'soapbox/components/status-content.tsx';
|
|||
import StatusMedia from 'soapbox/components/status-media.tsx';
|
||||
import HStack from 'soapbox/components/ui/hstack.tsx';
|
||||
import Stack from 'soapbox/components/ui/stack.tsx';
|
||||
import { Entities, EntityTypes } from 'soapbox/entity-store/entities.ts';
|
||||
import { useAppDispatch } from 'soapbox/hooks/useAppDispatch.ts';
|
||||
|
||||
import type { AdminReport, Status } from 'soapbox/types/entities.ts';
|
||||
|
@ -52,7 +53,7 @@ const ReportStatus: React.FC<IReportStatus> = ({ status }) => {
|
|||
<HStack space={2} alignItems='start'>
|
||||
<Stack space={2} className='overflow-hidden' grow>
|
||||
<StatusContent status={status} />
|
||||
<StatusMedia status={status} />
|
||||
<StatusMedia status={status.toJS() as EntityTypes[Entities.STATUSES]} />
|
||||
</Stack>
|
||||
|
||||
<div className='flex-none'>
|
||||
|
|
|
@ -23,6 +23,7 @@ import { useAppSelector } from 'soapbox/hooks/useAppSelector.ts';
|
|||
import { useFeatures } from 'soapbox/hooks/useFeatures.ts';
|
||||
import { ChatKeys, IChat, useChatActions } from 'soapbox/queries/chats.ts';
|
||||
import { queryClient } from 'soapbox/queries/client.ts';
|
||||
import { Attachment } from 'soapbox/schemas/index.ts';
|
||||
import { htmlToPlaintext } from 'soapbox/utils/html.ts';
|
||||
import { isOnlyEmoji as _isOnlyEmoji } from 'soapbox/utils/only-emoji.ts';
|
||||
|
||||
|
@ -112,7 +113,7 @@ const ChatMessage = (props: IChatMessage) => {
|
|||
'rounded-br-sm': isMyMessage && content,
|
||||
'rounded-bl-sm': !isMyMessage && content,
|
||||
})}
|
||||
media={chatMessage.media_attachments}
|
||||
media={chatMessage.media_attachments.toJS() as unknown as Attachment[]}
|
||||
onOpenMedia={onOpenMedia}
|
||||
visible
|
||||
/>
|
||||
|
|
|
@ -5,13 +5,12 @@ import AttachmentThumbs from 'soapbox/components/attachment-thumbs.tsx';
|
|||
import Markup from 'soapbox/components/markup.tsx';
|
||||
import Stack from 'soapbox/components/ui/stack.tsx';
|
||||
import AccountContainer from 'soapbox/containers/account-container.tsx';
|
||||
import { Entities, EntityTypes } from 'soapbox/entity-store/entities.ts';
|
||||
import { getTextDirection } from 'soapbox/utils/rtl.ts';
|
||||
|
||||
import type { Status } from 'soapbox/types/entities.ts';
|
||||
|
||||
interface IReplyIndicator {
|
||||
className?: string;
|
||||
status?: Status;
|
||||
status?: EntityTypes[Entities.STATUSES];
|
||||
onCancel?: () => void;
|
||||
hideActions: boolean;
|
||||
}
|
||||
|
@ -50,12 +49,12 @@ const ReplyIndicator: React.FC<IReplyIndicator> = ({ className, status, hideActi
|
|||
className='break-words'
|
||||
size='sm'
|
||||
direction={getTextDirection(status.search_index)}
|
||||
emojis={status?.emojis?.toJS() ?? status.emojis} // Use toJS() if status.emojis is immutable; otherwise, fallback to plain status.emojis
|
||||
mentions={status.mentions.toJS()}
|
||||
emojis={status.emojis}
|
||||
mentions={status.mentions}
|
||||
html={{ __html: status.content }}
|
||||
/>
|
||||
|
||||
{status.media_attachments.size > 0 && (
|
||||
{status.media_attachments.length > 0 && (
|
||||
<AttachmentThumbs
|
||||
media={status.media_attachments}
|
||||
sensitive={status.sensitive}
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
import { connect } from 'react-redux';
|
||||
|
||||
import { cancelReplyCompose } from 'soapbox/actions/compose.ts';
|
||||
import { Entities, EntityTypes } from 'soapbox/entity-store/entities.ts';
|
||||
import { makeGetStatus } from 'soapbox/selectors/index.ts';
|
||||
|
||||
import ReplyIndicator from '../components/reply-indicator.tsx';
|
||||
|
@ -16,7 +17,7 @@ const makeMapStateToProps = () => {
|
|||
const editing = !!state.compose.get(composeId)?.id;
|
||||
|
||||
return {
|
||||
status: getStatus(state, { id: statusId }) as Status,
|
||||
status: (getStatus(state, { id: statusId }) as Status)?.toJS() as EntityTypes[Entities.STATUSES],
|
||||
hideActions: editing,
|
||||
};
|
||||
};
|
||||
|
|
|
@ -15,6 +15,7 @@ import HStack from 'soapbox/components/ui/hstack.tsx';
|
|||
import Icon from 'soapbox/components/ui/icon.tsx';
|
||||
import Stack from 'soapbox/components/ui/stack.tsx';
|
||||
import Text from 'soapbox/components/ui/text.tsx';
|
||||
import { Entities, EntityTypes } from 'soapbox/entity-store/entities.ts';
|
||||
import QuotedStatus from 'soapbox/features/status/containers/quoted-status-container.tsx';
|
||||
import { useAppDispatch } from 'soapbox/hooks/useAppDispatch.ts';
|
||||
import { useAppSelector } from 'soapbox/hooks/useAppSelector.ts';
|
||||
|
@ -208,7 +209,7 @@ const EventInformation: React.FC<IEventInformation> = ({ params }) => {
|
|||
)}
|
||||
|
||||
<StatusMedia
|
||||
status={status}
|
||||
status={status.toJS() as EntityTypes[Entities.STATUSES]}
|
||||
showMedia={showMedia}
|
||||
onToggleVisibility={handleToggleMediaVisibility}
|
||||
/>
|
||||
|
|
|
@ -7,6 +7,7 @@ import Toggle from 'soapbox/components/ui/toggle.tsx';
|
|||
import { MediaGallery, Video, Audio } from 'soapbox/features/ui/util/async-components.ts';
|
||||
import { useAppDispatch } from 'soapbox/hooks/useAppDispatch.ts';
|
||||
import { useAppSelector } from 'soapbox/hooks/useAppSelector.ts';
|
||||
import { Attachment } from 'soapbox/schemas/index.ts';
|
||||
|
||||
interface IStatusCheckBox {
|
||||
id: string;
|
||||
|
@ -62,7 +63,7 @@ const StatusCheckBox: React.FC<IStatusCheckBox> = ({ id, disabled }) => {
|
|||
} else {
|
||||
media = (
|
||||
<MediaGallery
|
||||
media={status.media_attachments}
|
||||
media={status.media_attachments.toJS() as unknown as Attachment[]}
|
||||
sensitive={status.sensitive}
|
||||
height={110}
|
||||
onOpenMedia={() => {}}
|
||||
|
|
|
@ -8,6 +8,7 @@ import HStack from 'soapbox/components/ui/hstack.tsx';
|
|||
import Stack from 'soapbox/components/ui/stack.tsx';
|
||||
import PollPreview from 'soapbox/features/ui/components/poll-preview.tsx';
|
||||
import { useAppSelector } from 'soapbox/hooks/useAppSelector.ts';
|
||||
import { Attachment } from 'soapbox/schemas/index.ts';
|
||||
|
||||
import { buildStatus } from '../builder.tsx';
|
||||
|
||||
|
@ -55,7 +56,7 @@ const ScheduledStatus: React.FC<IScheduledStatus> = ({ statusId, ...other }) =>
|
|||
|
||||
{status.media_attachments.size > 0 && (
|
||||
<AttachmentThumbs
|
||||
media={status.media_attachments}
|
||||
media={status.media_attachments.toJS() as unknown as Attachment[]}
|
||||
sensitive={status.sensitive}
|
||||
/>
|
||||
)}
|
||||
|
|
|
@ -16,6 +16,7 @@ import HStack from 'soapbox/components/ui/hstack.tsx';
|
|||
import Icon from 'soapbox/components/ui/icon.tsx';
|
||||
import Stack from 'soapbox/components/ui/stack.tsx';
|
||||
import Text from 'soapbox/components/ui/text.tsx';
|
||||
import { Entities, EntityTypes } from 'soapbox/entity-store/entities.ts';
|
||||
import QuotedStatus from 'soapbox/features/status/containers/quoted-status-container.tsx';
|
||||
import { getActualStatus } from 'soapbox/utils/status.ts';
|
||||
|
||||
|
@ -162,7 +163,7 @@ const DetailedStatus: React.FC<IDetailedStatus> = ({
|
|||
{(withMedia && (quote || actualStatus.card || actualStatus.media_attachments.size > 0)) && (
|
||||
<Stack space={4}>
|
||||
<StatusMedia
|
||||
status={actualStatus}
|
||||
status={actualStatus.toJS() as EntityTypes[Entities.STATUSES]}
|
||||
showMedia={showMedia}
|
||||
onToggleVisibility={onToggleMediaVisibility}
|
||||
/>
|
||||
|
|
|
@ -4,15 +4,15 @@ import { spring } from 'react-motion';
|
|||
|
||||
import HStack from 'soapbox/components/ui/hstack.tsx';
|
||||
import SvgIcon from 'soapbox/components/ui/svg-icon.tsx';
|
||||
import { Entities, EntityTypes } from 'soapbox/entity-store/entities.ts';
|
||||
import ReplyIndicator from 'soapbox/features/compose/components/reply-indicator.tsx';
|
||||
|
||||
import Motion from '../../util/optional-motion.tsx';
|
||||
|
||||
import type { Menu, MenuItem } from 'soapbox/components/dropdown-menu/index.ts';
|
||||
import type { Status as StatusEntity } from 'soapbox/types/entities.ts';
|
||||
|
||||
interface IActionsModal {
|
||||
status: StatusEntity;
|
||||
status: EntityTypes[Entities.STATUSES];
|
||||
actions: Menu;
|
||||
onClick: () => void;
|
||||
onClose: () => void;
|
||||
|
|
|
@ -5,6 +5,7 @@ import Icon from 'soapbox/components/icon.tsx';
|
|||
import Modal from 'soapbox/components/ui/modal.tsx';
|
||||
import Stack from 'soapbox/components/ui/stack.tsx';
|
||||
import Text from 'soapbox/components/ui/text.tsx';
|
||||
import { Entities, EntityTypes } from 'soapbox/entity-store/entities.ts';
|
||||
import ReplyIndicator from 'soapbox/features/compose/components/reply-indicator.tsx';
|
||||
|
||||
import type { Status as StatusEntity } from 'soapbox/types/entities.ts';
|
||||
|
@ -37,7 +38,7 @@ const BoostModal: React.FC<IBoostModal> = ({ status, onReblog, onClose }) => {
|
|||
confirmationText={intl.formatMessage(buttonText)}
|
||||
>
|
||||
<Stack space={4}>
|
||||
<ReplyIndicator status={status} hideActions />
|
||||
<ReplyIndicator status={status.toJS() as EntityTypes[Entities.STATUSES]} hideActions />
|
||||
|
||||
<Text>
|
||||
{/* eslint-disable-next-line formatjs/no-literal-string-in-jsx */}
|
||||
|
|
|
@ -12,6 +12,7 @@ import Stack from 'soapbox/components/ui/stack.tsx';
|
|||
import Text from 'soapbox/components/ui/text.tsx';
|
||||
import { useAppDispatch } from 'soapbox/hooks/useAppDispatch.ts';
|
||||
import { useAppSelector } from 'soapbox/hooks/useAppSelector.ts';
|
||||
import { Attachment } from 'soapbox/schemas/index.ts';
|
||||
import { emojifyText } from 'soapbox/utils/emojify.tsx';
|
||||
|
||||
import type { StatusEdit as StatusEditEntity } from 'soapbox/types/entities.ts';
|
||||
|
@ -81,7 +82,7 @@ const CompareHistoryModal: React.FC<ICompareHistoryModal> = ({ onClose, statusId
|
|||
)}
|
||||
|
||||
{version.media_attachments.size > 0 && (
|
||||
<AttachmentThumbs media={version.media_attachments} />
|
||||
<AttachmentThumbs media={version.media_attachments.toJS() as unknown as Attachment[]} />
|
||||
)}
|
||||
|
||||
<Text align='right' tag='span' theme='muted' size='sm'>
|
||||
|
|
|
@ -19,20 +19,20 @@ import HStack from 'soapbox/components/ui/hstack.tsx';
|
|||
import IconButton from 'soapbox/components/ui/icon-button.tsx';
|
||||
import Icon from 'soapbox/components/ui/icon.tsx';
|
||||
import Stack from 'soapbox/components/ui/stack.tsx';
|
||||
import { Entities, EntityTypes } from 'soapbox/entity-store/entities.ts';
|
||||
import Audio from 'soapbox/features/audio/index.tsx';
|
||||
import PlaceholderStatus from 'soapbox/features/placeholder/components/placeholder-status.tsx';
|
||||
import Thread from 'soapbox/features/status/components/thread.tsx';
|
||||
import Video from 'soapbox/features/video/index.tsx';
|
||||
import { useAppDispatch } from 'soapbox/hooks/useAppDispatch.ts';
|
||||
import { useAppSelector } from 'soapbox/hooks/useAppSelector.ts';
|
||||
import { userTouching } from 'soapbox/is-mobile.ts';
|
||||
import { makeGetStatus } from 'soapbox/selectors/index.ts';
|
||||
import { normalizeStatus } from 'soapbox/normalizers/index.ts';
|
||||
import { Attachment } from 'soapbox/schemas/index.ts';
|
||||
import { Status } from 'soapbox/types/entities.ts';
|
||||
import { getActualStatus } from 'soapbox/utils/status.ts';
|
||||
|
||||
import ImageLoader from '../image-loader.tsx';
|
||||
|
||||
import type { List as ImmutableList } from 'immutable';
|
||||
import type { Attachment, Status } from 'soapbox/types/entities.ts';
|
||||
|
||||
const messages = defineMessages({
|
||||
close: { id: 'lightbox.close', defaultMessage: 'Close' },
|
||||
expand: { id: 'lightbox.expand', defaultMessage: 'Expand' },
|
||||
|
@ -55,8 +55,8 @@ const containerStyle: React.CSSProperties = {
|
|||
};
|
||||
|
||||
interface IMediaModal {
|
||||
media: ImmutableList<Attachment>;
|
||||
status?: Status;
|
||||
media: readonly Attachment[];
|
||||
status?: EntityTypes[Entities.STATUSES];
|
||||
index: number;
|
||||
time?: number;
|
||||
onClose(): void;
|
||||
|
@ -74,8 +74,7 @@ const MediaModal: React.FC<IMediaModal> = (props) => {
|
|||
const history = useHistory();
|
||||
const intl = useIntl();
|
||||
|
||||
const getStatus = useCallback(makeGetStatus(), []);
|
||||
const actualStatus = useAppSelector((state) => getStatus(state, { id: status?.id as string }));
|
||||
const actualStatus = status ? getActualStatus(status) : undefined;
|
||||
|
||||
const [isLoaded, setIsLoaded] = useState<boolean>(!!status);
|
||||
const [next, setNext] = useState<string>();
|
||||
|
@ -83,11 +82,11 @@ const MediaModal: React.FC<IMediaModal> = (props) => {
|
|||
const [navigationHidden, setNavigationHidden] = useState(false);
|
||||
const [isFullScreen, setIsFullScreen] = useState(!status);
|
||||
|
||||
const hasMultipleImages = media.size > 1;
|
||||
const hasMultipleImages = media.length > 1;
|
||||
|
||||
const handleSwipe = (index: number) => setIndex(index % media.size);
|
||||
const handleNextClick = () => setIndex((getIndex() + 1) % media.size);
|
||||
const handlePrevClick = () => setIndex((media.size + getIndex() - 1) % media.size);
|
||||
const handleSwipe = (index: number) => setIndex(index % media.length);
|
||||
const handleNextClick = () => setIndex((getIndex() + 1) % media.length);
|
||||
const handlePrevClick = () => setIndex((media.length + getIndex() - 1) % media.length);
|
||||
|
||||
const navigationHiddenClassName = navigationHidden ? 'pointer-events-none opacity-0' : '';
|
||||
|
||||
|
@ -107,7 +106,7 @@ const MediaModal: React.FC<IMediaModal> = (props) => {
|
|||
};
|
||||
|
||||
const handleDownload = () => {
|
||||
const mediaItem = hasMultipleImages ? media.get(index as number) : media.get(0);
|
||||
const mediaItem = hasMultipleImages ? media[index as number] : media[0];
|
||||
window.open(mediaItem?.url);
|
||||
};
|
||||
|
||||
|
@ -126,8 +125,8 @@ const MediaModal: React.FC<IMediaModal> = (props) => {
|
|||
};
|
||||
|
||||
const content = media.map((attachment, i) => {
|
||||
const width = (attachment.meta.getIn(['original', 'width']) || undefined) as number | undefined;
|
||||
const height = (attachment.meta.getIn(['original', 'height']) || undefined) as number | undefined;
|
||||
const width = 'meta' in attachment && 'original' in attachment.meta ? (attachment)?.meta?.original?.width : undefined;
|
||||
const height = 'meta' in attachment && 'original' in attachment.meta ? (attachment)?.meta?.original?.height : undefined;
|
||||
|
||||
const link = (status && (
|
||||
<a href={status.url} onClick={handleStatusClick}>
|
||||
|
@ -151,7 +150,7 @@ const MediaModal: React.FC<IMediaModal> = (props) => {
|
|||
return (
|
||||
<Video
|
||||
preview={attachment.preview_url}
|
||||
blurhash={attachment.blurhash}
|
||||
blurhash={attachment.blurhash ?? undefined}
|
||||
src={attachment.url}
|
||||
width={width}
|
||||
height={height}
|
||||
|
@ -169,11 +168,11 @@ const MediaModal: React.FC<IMediaModal> = (props) => {
|
|||
<Audio
|
||||
src={attachment.url}
|
||||
alt={attachment.description}
|
||||
poster={attachment.preview_url !== attachment.url ? attachment.preview_url : (status?.getIn(['account', 'avatar_static'])) as string | undefined}
|
||||
backgroundColor={attachment.meta.getIn(['colors', 'background']) as string | undefined}
|
||||
foregroundColor={attachment.meta.getIn(['colors', 'foreground']) as string | undefined}
|
||||
accentColor={attachment.meta.getIn(['colors', 'accent']) as string | undefined}
|
||||
duration={attachment.meta.getIn(['original', 'duration'], 0) as number | undefined}
|
||||
poster={attachment.preview_url !== attachment.url ? attachment.preview_url : status?.account.avatar_static}
|
||||
backgroundColor={attachment.meta?.colors?.background}
|
||||
foregroundColor={attachment.meta?.colors?.foreground}
|
||||
accentColor={attachment.meta?.colors?.accent}
|
||||
duration={attachment?.meta?.duration ?? 0}
|
||||
key={attachment.url}
|
||||
/>
|
||||
);
|
||||
|
@ -193,7 +192,7 @@ const MediaModal: React.FC<IMediaModal> = (props) => {
|
|||
}
|
||||
|
||||
return null;
|
||||
}).toArray();
|
||||
});
|
||||
|
||||
const handleLoadMore = useCallback(debounce(() => {
|
||||
if (next && status) {
|
||||
|
@ -344,7 +343,7 @@ const MediaModal: React.FC<IMediaModal> = (props) => {
|
|||
className={clsx('absolute bottom-2 flex w-full transition-opacity', navigationHiddenClassName)}
|
||||
>
|
||||
<StatusActionBar
|
||||
status={actualStatus}
|
||||
status={normalizeStatus(actualStatus) as Status}
|
||||
space='md'
|
||||
statusActionButtonTheme='inverse'
|
||||
/>
|
||||
|
@ -362,7 +361,7 @@ const MediaModal: React.FC<IMediaModal> = (props) => {
|
|||
}
|
||||
>
|
||||
<Thread
|
||||
status={actualStatus}
|
||||
status={normalizeStatus(actualStatus) as Status}
|
||||
withMedia={false}
|
||||
useWindowScroll={false}
|
||||
itemClassName='px-4'
|
||||
|
|
|
@ -21,11 +21,13 @@ import AccountContainer from 'soapbox/containers/account-container.tsx';
|
|||
import { useAppDispatch } from 'soapbox/hooks/useAppDispatch.ts';
|
||||
import { useAppSelector } from 'soapbox/hooks/useAppSelector.ts';
|
||||
import { useInstance } from 'soapbox/hooks/useInstance.ts';
|
||||
import { Attachment } from 'soapbox/schemas/index.ts';
|
||||
|
||||
import ConfirmationStep from './steps/confirmation-step.tsx';
|
||||
import OtherActionsStep from './steps/other-actions-step.tsx';
|
||||
import ReasonStep from './steps/reason-step.tsx';
|
||||
|
||||
|
||||
const messages = defineMessages({
|
||||
blankslate: { id: 'report.reason.blankslate', defaultMessage: 'You have removed all statuses from being selected.' },
|
||||
done: { id: 'report.done', defaultMessage: 'Done' },
|
||||
|
@ -91,7 +93,7 @@ const SelectedStatus = ({ statusId }: { statusId: string }) => {
|
|||
|
||||
{status.media_attachments.size > 0 && (
|
||||
<AttachmentThumbs
|
||||
media={status.media_attachments}
|
||||
media={status.media_attachments.toJS() as unknown as Attachment[]}
|
||||
sensitive={status.sensitive}
|
||||
/>
|
||||
)}
|
||||
|
|
Ładowanie…
Reference in New Issue