Merge branch 'refactor-immutable-to-plain-object' into 'main'

refactor: remove immutable, convert to use plain javascript object

See merge request soapbox-pub/soapbox!3294
merge-requests/3305/head
Alex Gleason 2024-12-11 20:50:46 +00:00
commit 95f134785d
42 zmienionych plików z 158 dodań i 155 usunięć

Wyświetl plik

@ -1,14 +1,20 @@
import { EntityTypes, Entities } from 'soapbox/entity-store/entities.ts';
import { Entities } from 'soapbox/entity-store/entities.ts';
import { useEntities } from 'soapbox/entity-store/hooks/index.ts';
import { useApi } from 'soapbox/hooks/useApi.ts';
import { useFeatures } from 'soapbox/hooks/useFeatures.ts';
import { statusSchema } from 'soapbox/schemas/status.ts';
import { Status as StatusEntity, statusSchema } from 'soapbox/schemas/index.ts';
/**
* Get all the statuses the user has bookmarked.
* https://docs.joinmastodon.org/methods/bookmarks/#get
* GET /api/v1/bookmarks
* TODO: add 'limit'
*/
function useBookmarks() {
const api = useApi();
const features = useFeatures();
const { entities, ...result } = useEntities<EntityTypes[Entities.STATUSES]>(
const { entities, ...result } = useEntities<StatusEntity>(
[Entities.STATUSES, 'bookmarks'],
() => api.get('/api/v1/bookmarks'),
{ enabled: features.bookmarks, schema: statusSchema },

Wyświetl plik

@ -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'>

Wyświetl plik

@ -11,11 +11,10 @@ import IconButton from 'soapbox/components/ui/icon-button.tsx';
import Portal from 'soapbox/components/ui/portal.tsx';
import { useAppDispatch } from 'soapbox/hooks/useAppDispatch.ts';
import { userTouching } from 'soapbox/is-mobile.ts';
import { Status as StatusEntity } from 'soapbox/schemas/index.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?: StatusEntity;
title?: string;
}

Wyświetl plik

@ -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!] || 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}
/>
));

Wyświetl plik

@ -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) => {

Wyświetl plik

@ -7,7 +7,6 @@ import Button from 'soapbox/components/ui/button.tsx';
import HStack from 'soapbox/components/ui/hstack.tsx';
import Stack from 'soapbox/components/ui/stack.tsx';
import Text from 'soapbox/components/ui/text.tsx';
import { EntityTypes, Entities } from 'soapbox/entity-store/entities.ts';
import PureEventActionButton from 'soapbox/features/event/components/pure-event-action-button.tsx';
import PureEventDate from 'soapbox/features/event/components/pure-event-date.tsx';
import { useAppSelector } from 'soapbox/hooks/useAppSelector.ts';
@ -15,6 +14,7 @@ import { useAppSelector } from 'soapbox/hooks/useAppSelector.ts';
import Icon from './icon.tsx';
import VerificationBadge from './verification-badge.tsx';
import type { Status as StatusEntity } from 'soapbox/schemas/index.ts';
const messages = defineMessages({
eventBanner: { id: 'event.banner', defaultMessage: 'Event banner' },
@ -23,7 +23,7 @@ const messages = defineMessages({
});
interface IPureEventPreview {
status: EntityTypes[Entities.STATUSES];
status: StatusEntity;
className?: string;
hideAction?: boolean;
floatingAction?: boolean;

Wyświetl plik

@ -4,7 +4,7 @@ import { useState, useRef, useLayoutEffect, useMemo, memo } from 'react';
import { FormattedMessage } from 'react-intl';
import Icon from 'soapbox/components/icon.tsx';
import { Entities, EntityTypes } from 'soapbox/entity-store/entities.ts';
import { Status as StatusEntity } from 'soapbox/schemas/index.ts';
import { isOnlyEmoji as _isOnlyEmoji } from 'soapbox/utils/only-emoji.ts';
import { getTextDirection } from 'soapbox/utils/rtl.ts';
@ -28,7 +28,7 @@ const ReadMoreButton: React.FC<IReadMoreButton> = ({ onClick }) => (
);
interface IPureStatusContent {
status: EntityTypes[Entities.STATUSES];
status: StatusEntity;
onClick?: () => void;
collapsable?: boolean;
translatable?: boolean;

Wyświetl plik

@ -6,11 +6,11 @@ import { FormattedMessage } from 'react-intl';
import LoadGap from 'soapbox/components/load-gap.tsx';
import PureStatus from 'soapbox/components/pure-status.tsx';
import ScrollableList from 'soapbox/components/scrollable-list.tsx';
import { EntityTypes, Entities } from 'soapbox/entity-store/entities.ts';
import FeedSuggestions from 'soapbox/features/feed-suggestions/feed-suggestions.tsx';
import PlaceholderStatus from 'soapbox/features/placeholder/components/placeholder-status.tsx';
import PendingStatus from 'soapbox/features/ui/components/pending-status.tsx';
import { useSoapboxConfig } from 'soapbox/hooks/useSoapboxConfig.ts';
import { Status as StatusEntity } from 'soapbox/schemas/index.ts';
import type { VirtuosoHandle } from 'react-virtuoso';
import type { IScrollableList } from 'soapbox/components/scrollable-list.tsx';
@ -19,11 +19,11 @@ interface IPureStatusList extends Omit<IScrollableList, 'onLoadMore' | 'children
/** Unique key to preserve the scroll position when navigating back. */
scrollKey: string;
/** List of statuses to display. */
statuses: readonly EntityTypes[Entities.STATUSES][];
statuses: readonly StatusEntity[];
/** Last _unfiltered_ status ID (maxId) for pagination. */
lastStatusId?: string;
/** Pinned statuses to show at the top of the feed. */
featuredStatuses?: readonly EntityTypes[Entities.STATUSES][];
featuredStatuses?: readonly StatusEntity[];
/** Pagination callback when the end of the list is reached. */
onLoadMore?: (lastStatusId: string) => void;
/** Whether the data is currently being fetched. */
@ -124,7 +124,7 @@ const PureStatusList: React.FC<IPureStatusList> = ({
);
};
const renderStatus = (status: EntityTypes[Entities.STATUSES]) => {
const renderStatus = (status: StatusEntity) => {
return (
<PureStatus
status={status}

Wyświetl plik

@ -4,13 +4,12 @@ import { Link } from 'react-router-dom';
import { openModal } from 'soapbox/actions/modals.ts';
import HoverRefWrapper from 'soapbox/components/hover-ref-wrapper.tsx';
import HoverStatusWrapper from 'soapbox/components/hover-status-wrapper.tsx';
import { Entities, EntityTypes } from 'soapbox/entity-store/entities.ts';
import { useAppDispatch } from 'soapbox/hooks/useAppDispatch.ts';
import { Status as StatusEntity } from 'soapbox/schemas/index.ts';
import { shortenNostr } from 'soapbox/utils/nostr.ts';
interface IPureStatusReplyMentions {
status: EntityTypes[Entities.STATUSES];
status: StatusEntity;
hoverable?: boolean;
}

Wyświetl plik

@ -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';
@ -19,18 +18,18 @@ 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 { 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 as StatusEntity } from 'soapbox/schemas/index.ts';
import { Status as LegacyStatus } from 'soapbox/types/entities.ts';
import { emojifyText } from 'soapbox/utils/emojify.tsx';
import { defaultMediaVisibility, textForScreenReader, getActualStatus } from 'soapbox/utils/status.ts';
@ -49,7 +48,7 @@ const messages = defineMessages({
export interface IPureStatus {
id?: string;
avatarSize?: number;
status: EntityTypes[Entities.STATUSES];
status: StatusEntity;
onClick?: () => void;
muted?: boolean;
hidden?: boolean;
@ -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 LegacyStatus; // 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}

Wyświetl plik

@ -5,14 +5,14 @@ import { translateStatus, undoStatusTranslation } from 'soapbox/actions/statuses
import Button from 'soapbox/components/ui/button.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 { useAppDispatch } from 'soapbox/hooks/useAppDispatch.ts';
import { useAppSelector } from 'soapbox/hooks/useAppSelector.ts';
import { useFeatures } from 'soapbox/hooks/useFeatures.ts';
import { useInstance } from 'soapbox/hooks/useInstance.ts';
import { Status as StatusEntity } from 'soapbox/schemas/index.ts';
interface IPureTranslateButton {
status: EntityTypes[Entities.STATUSES];
status: StatusEntity;
}
const PureTranslateButton: React.FC<IPureTranslateButton> = ({ status }) => {

Wyświetl plik

@ -8,6 +8,7 @@ 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 { useSettings } from 'soapbox/hooks/useSettings.ts';
import { Status as StatusEntity } from 'soapbox/schemas/index.ts';
import { defaultMediaVisibility } from 'soapbox/utils/status.ts';
import EventPreview from './event-preview.tsx';
@ -17,7 +18,7 @@ import StatusContent from './status-content.tsx';
import StatusReplyMentions from './status-reply-mentions.tsx';
import SensitiveContentOverlay from './statuses/sensitive-content-overlay.tsx';
import type { Status as StatusEntity } from 'soapbox/types/entities.ts';
import type { Status as LegacyStatus } from 'soapbox/types/entities.ts';
const messages = defineMessages({
cancel: { id: 'reply_indicator.cancel', defaultMessage: 'Cancel' },
@ -25,7 +26,7 @@ const messages = defineMessages({
interface IQuotedStatus {
/** The quoted status entity. */
status?: StatusEntity;
status?: LegacyStatus;
/** Callback when cancelled (during compose). */
onCancel?: Function;
/** Whether the status is shown in the post composer. */
@ -138,7 +139,7 @@ const QuotedStatus: React.FC<IQuotedStatus> = ({ status, onCancel, compose }) =>
{status.media_attachments.size > 0 && (
<StatusMedia
status={status}
status={status.toJS() as StatusEntity}
muted={compose}
showMedia={showMedia}
onToggleVisibility={handleToggleMediaVisibility}

Wyświetl plik

@ -55,13 +55,14 @@ import { useFeatures } from 'soapbox/hooks/useFeatures.ts';
import { useOwnAccount } from 'soapbox/hooks/useOwnAccount.ts';
import { useSettings } from 'soapbox/hooks/useSettings.ts';
import { GroupRoles } from 'soapbox/schemas/group-member.ts';
import { Status as StatusEntity } from 'soapbox/schemas/index.ts';
import toast from 'soapbox/toast.tsx';
import copy from 'soapbox/utils/copy.ts';
import GroupPopover from './groups/popover/group-popover.tsx';
import type { Menu } from 'soapbox/components/dropdown-menu/index.ts';
import type { Group, Status } from 'soapbox/types/entities.ts';
import type { Group, Status as LegacyStatus } from 'soapbox/types/entities.ts';
const messages = defineMessages({
adminAccount: { id: 'status.admin_account', defaultMessage: 'Moderate @{name}' },
@ -140,7 +141,7 @@ const messages = defineMessages({
});
interface IStatusActionBar {
status: Status;
status: LegacyStatus;
expandable?: boolean;
space?: 'sm' | 'md' | 'lg';
statusActionButtonTheme?: 'default' | 'inverse';
@ -826,7 +827,7 @@ const StatusActionBar: React.FC<IStatusActionBar> = ({
/>
)}
<DropdownMenu items={menu} status={status}>
<DropdownMenu items={menu} status={status.toJS() as StatusEntity}>
<StatusActionButton
title={intl.formatMessage(messages.more)}
icon={dotsIcon}

Wyświetl plik

@ -8,13 +8,11 @@ import { GroupLinkPreview } from 'soapbox/features/groups/components/group-link-
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 { Status as StatusEntity, Attachment } from 'soapbox/schemas/index.ts';
interface IStatusMedia {
/** Status entity to render media for. */
status: Status;
status: StatusEntity;
/** Whether to display compact media. */
muted?: boolean;
/** Callback when compact media is clicked. */
@ -35,8 +33,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 +50,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 +70,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 +88,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>

Wyświetl plik

@ -20,6 +20,7 @@ import QuotedStatus from 'soapbox/features/status/containers/quoted-status-conta
import { HotKeys } from 'soapbox/features/ui/components/hotkeys.tsx';
import { useAppDispatch } from 'soapbox/hooks/useAppDispatch.ts';
import { useSettings } from 'soapbox/hooks/useSettings.ts';
import { Status as StatusEntity } from 'soapbox/schemas/index.ts';
import { emojifyText } from 'soapbox/utils/emojify.tsx';
import { defaultMediaVisibility, textForScreenReader, getActualStatus } from 'soapbox/utils/status.ts';
@ -32,7 +33,7 @@ import SensitiveContentOverlay from './statuses/sensitive-content-overlay.tsx';
import StatusInfo from './statuses/status-info.tsx';
import Tombstone from './tombstone.tsx';
import type { Status as StatusEntity } from 'soapbox/types/entities.ts';
import type { Status as LegacyStatus } from 'soapbox/types/entities.ts';
// Defined in components/scrollable-list
export type ScrollPosition = { height: number; top: number };
@ -44,7 +45,7 @@ const messages = defineMessages({
export interface IStatus {
id?: string;
avatarSize?: number;
status: StatusEntity;
status: LegacyStatus;
onClick?: () => void;
muted?: boolean;
hidden?: boolean;
@ -150,7 +151,7 @@ const Status: React.FC<IStatus> = (props) => {
if (firstAttachment.type === 'video') {
dispatch(openModal('VIDEO', { status, media: firstAttachment, time: 0 }));
} else {
dispatch(openModal('MEDIA', { status, media: status.media_attachments, index: 0 }));
dispatch(openModal('MEDIA', { status: status.toJS(), media: status.media_attachments.toJS(), index: 0 }));
}
}
};
@ -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 StatusEntity}
muted={muted}
onClick={handleClick}
showMedia={showMedia}

Wyświetl plik

@ -13,15 +13,14 @@ import DropdownMenu from 'soapbox/components/dropdown-menu/index.ts';
import Button from 'soapbox/components/ui/button.tsx';
import HStack from 'soapbox/components/ui/hstack.tsx';
import Text from 'soapbox/components/ui/text.tsx';
import { Entities, EntityTypes } from 'soapbox/entity-store/entities.ts';
import { useAppDispatch } from 'soapbox/hooks/useAppDispatch.ts';
import { useOwnAccount } from 'soapbox/hooks/useOwnAccount.ts';
import { useSettings } from 'soapbox/hooks/useSettings.ts';
import { useSoapboxConfig } from 'soapbox/hooks/useSoapboxConfig.ts';
import { Status as StatusEntity } from 'soapbox/schemas/index.ts';
import { emojifyText } from 'soapbox/utils/emojify.tsx';
import { defaultMediaVisibility } from 'soapbox/utils/status.ts';
const messages = defineMessages({
delete: { id: 'status.delete', defaultMessage: 'Delete' },
deleteConfirm: { id: 'confirmations.delete.confirm', defaultMessage: 'Delete' },
@ -37,7 +36,7 @@ const messages = defineMessages({
});
interface IPureSensitiveContentOverlay {
status: EntityTypes[Entities.STATUSES];
status: StatusEntity;
onToggleVisibility?(): void;
visible?: boolean;
}

Wyświetl plik

@ -140,7 +140,7 @@ const Upload: React.FC<IUpload> = ({
};
const handleOpenModal = () => {
dispatch(openModal('MEDIA', { media: ImmutableList.of(media), index: 0 }));
dispatch(openModal('MEDIA', { media: ImmutableList.of(media).toJS(), index: 0 }));
};
const active = hovered || focused;

Wyświetl plik

@ -74,7 +74,7 @@ const AccountGallery = () => {
const media = (attachment.status as Status).media_attachments;
const index = media.findIndex((x) => x.id === attachment.id);
dispatch(openModal('MEDIA', { media, index, status: attachment.status }));
dispatch(openModal('MEDIA', { media: media.toJS(), index, status: attachment?.status?.toJS() ?? attachment.status }));
}
};

Wyświetl plik

@ -266,7 +266,7 @@ const Header: React.FC<IHeader> = ({ account }) => {
type: 'image',
url: account.avatar,
});
dispatch(openModal('MEDIA', { media: ImmutableList.of(avatar), index: 0 }));
dispatch(openModal('MEDIA', { media: ImmutableList.of(avatar).toJS(), index: 0 }));
};
const handleAvatarClick: React.MouseEventHandler = (e) => {
@ -281,7 +281,7 @@ const Header: React.FC<IHeader> = ({ account }) => {
type: 'image',
url: account.header,
});
dispatch(openModal('MEDIA', { media: ImmutableList.of(header), index: 0 }));
dispatch(openModal('MEDIA', { media: ImmutableList.of(header).toJS(), index: 0 }));
};
const handleHeaderClick: React.MouseEventHandler = (e) => {

Wyświetl plik

@ -10,8 +10,9 @@ 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 { useAppDispatch } from 'soapbox/hooks/useAppDispatch.ts';
import { Status as StatusEntity } from 'soapbox/schemas/index.ts';
import type { AdminReport, Status } from 'soapbox/types/entities.ts';
import type { AdminReport, Status as LegacyStatus } from 'soapbox/types/entities.ts';
const messages = defineMessages({
viewStatus: { id: 'admin.reports.actions.view_status', defaultMessage: 'View post' },
@ -19,7 +20,7 @@ const messages = defineMessages({
});
interface IReportStatus {
status: Status;
status: LegacyStatus;
report?: AdminReport;
}
@ -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 StatusEntity} />
</Stack>
<div className='flex-none'>

Wyświetl plik

@ -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
/>

Wyświetl plik

@ -22,7 +22,7 @@ const ChatUpload: React.FC<IChatUpload> = ({ attachment, onDelete }) => {
const clickable = attachment.type !== 'unknown';
const handleOpenModal = () => {
dispatch(openModal('MEDIA', { media: ImmutableList.of(attachment), index: 0 }));
dispatch(openModal('MEDIA', { media: ImmutableList.of(attachment).toJS(), index: 0 }));
};
return (

Wyświetl plik

@ -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 { Status as StatusEntity } from 'soapbox/schemas/index.ts';
import { getTextDirection } from 'soapbox/utils/rtl.ts';
import type { Status } from 'soapbox/types/entities.ts';
interface IReplyIndicator {
className?: string;
status?: Status;
status?: StatusEntity;
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}

Wyświetl plik

@ -1,12 +1,14 @@
import { connect } from 'react-redux';
import { cancelReplyCompose } from 'soapbox/actions/compose.ts';
import { Status as StatusEntity } from 'soapbox/schemas/index.ts';
import { makeGetStatus } from 'soapbox/selectors/index.ts';
import ReplyIndicator from '../components/reply-indicator.tsx';
import type { AppDispatch, RootState } from 'soapbox/store.ts';
import type { Status } from 'soapbox/types/entities.ts';
import type { Status as LegacyStatus } from 'soapbox/types/entities.ts';
const makeMapStateToProps = () => {
const getStatus = makeGetStatus();
@ -16,7 +18,7 @@ const makeMapStateToProps = () => {
const editing = !!state.compose.get(composeId)?.id;
return {
status: getStatus(state, { id: statusId }) as Status,
status: (getStatus(state, { id: statusId }) as LegacyStatus)?.toJS() as StatusEntity,
hideActions: editing,
};
};

Wyświetl plik

@ -128,7 +128,7 @@ const EventHeader: React.FC<IEventHeader> = ({ status }) => {
e.preventDefault();
e.stopPropagation();
dispatch(openModal('MEDIA', { media: ImmutableList([event.banner]) }));
dispatch(openModal('MEDIA', { media: ImmutableList([event.banner]).toJS() }));
};
const handleExportClick = () => {

Wyświetl plik

@ -5,9 +5,9 @@ import { defineMessages, FormattedMessage, useIntl } from 'react-intl';
import { joinEvent, leaveEvent } from 'soapbox/actions/events.ts';
import { openModal } from 'soapbox/actions/modals.ts';
import Button from 'soapbox/components/ui/button.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 { Status as StatusEntity } from 'soapbox/schemas/index.ts';
import type { ButtonThemes } from 'soapbox/components/ui/useButtonStyles.ts';
@ -17,7 +17,7 @@ const messages = defineMessages({
});
interface IPureEventAction {
status: EntityTypes[Entities.STATUSES];
status: StatusEntity;
theme?: ButtonThemes;
}

Wyświetl plik

@ -3,11 +3,10 @@ import { FormattedDate } from 'react-intl';
import Icon from 'soapbox/components/icon.tsx';
import HStack from 'soapbox/components/ui/hstack.tsx';
import { Entities, EntityTypes } from 'soapbox/entity-store/entities.ts';
import { Status as StatusEntity } from 'soapbox/schemas/index.ts';
interface IPureEventDate {
status: EntityTypes[Entities.STATUSES];
status: StatusEntity;
}
const PureEventDate: React.FC<IPureEventDate> = ({ status }) => {

Wyświetl plik

@ -20,10 +20,11 @@ import { useAppDispatch } from 'soapbox/hooks/useAppDispatch.ts';
import { useAppSelector } from 'soapbox/hooks/useAppSelector.ts';
import { useSettings } from 'soapbox/hooks/useSettings.ts';
import { useSoapboxConfig } from 'soapbox/hooks/useSoapboxConfig.ts';
import { Status as StatusEntity } from 'soapbox/schemas/index.ts';
import { makeGetStatus } from 'soapbox/selectors/index.ts';
import { defaultMediaVisibility } from 'soapbox/utils/status.ts';
import type { Status as StatusEntity } from 'soapbox/types/entities.ts';
import type { Status as StatusLegacy } from 'soapbox/types/entities.ts';
type RouteParams = { statusId: string };
@ -35,7 +36,7 @@ const EventInformation: React.FC<IEventInformation> = ({ params }) => {
const dispatch = useAppDispatch();
const getStatus = useCallback(makeGetStatus(), []);
const status = useAppSelector(state => getStatus(state, { id: params.statusId })) as StatusEntity;
const status = useAppSelector(state => getStatus(state, { id: params.statusId })) as StatusLegacy;
const { tileServer } = useSoapboxConfig();
const { displayMedia } = useSettings();
@ -208,7 +209,7 @@ const EventInformation: React.FC<IEventInformation> = ({ params }) => {
)}
<StatusMedia
status={status}
status={status.toJS() as StatusEntity}
showMedia={showMedia}
onToggleVisibility={handleToggleMediaVisibility}
/>

Wyświetl plik

@ -63,7 +63,7 @@ const GroupHeader: React.FC<IGroupHeader> = ({ group }) => {
type: 'image',
url: group.avatar,
});
dispatch(openModal('MEDIA', { media: ImmutableList.of(avatar), index: 0 }));
dispatch(openModal('MEDIA', { media: ImmutableList.of(avatar).toJS(), index: 0 }));
};
const handleAvatarClick: React.MouseEventHandler = (e) => {
@ -78,7 +78,7 @@ const GroupHeader: React.FC<IGroupHeader> = ({ group }) => {
type: 'image',
url: group.header,
});
dispatch(openModal('MEDIA', { media: ImmutableList.of(header), index: 0 }));
dispatch(openModal('MEDIA', { media: ImmutableList.of(header).toJS(), index: 0 }));
};
const handleHeaderClick: React.MouseEventHandler = (e) => {

Wyświetl plik

@ -43,7 +43,7 @@ const GroupGallery: React.FC<IGroupGallery> = (props) => {
const media = (attachment.status as Status).media_attachments;
const index = media.findIndex((x) => x.id === attachment.id);
dispatch(openModal('MEDIA', { media, index, status: attachment.status }));
dispatch(openModal('MEDIA', { media: media.toJS(), index, status: attachment?.status?.toJS() ?? attachment.status }));
}
};

Wyświetl plik

@ -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={() => {}}

Wyświetl plik

@ -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}
/>
)}

Wyświetl plik

@ -17,17 +17,18 @@ 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 QuotedStatus from 'soapbox/features/status/containers/quoted-status-container.tsx';
import { Status as StatusEntity } from 'soapbox/schemas/index.ts';
import { getActualStatus } from 'soapbox/utils/status.ts';
import StatusInteractionBar from './status-interaction-bar.tsx';
import type { Group, Status as StatusEntity } from 'soapbox/types/entities.ts';
import type { Group, Status as LegacyStatus } from 'soapbox/types/entities.ts';
interface IDetailedStatus {
status: StatusEntity;
status: LegacyStatus;
showMedia?: boolean;
withMedia?: boolean;
onOpenCompareHistoryModal: (status: StatusEntity) => void;
onOpenCompareHistoryModal: (status: LegacyStatus) => void;
onToggleMediaVisibility: () => void;
}
@ -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 StatusEntity}
showMedia={showMedia}
onToggleVisibility={onToggleMediaVisibility}
/>

Wyświetl plik

@ -179,7 +179,7 @@ const Thread = (props: IThread) => {
if (media.size === 1 && firstAttachment.type === 'video') {
dispatch(openModal('VIDEO', { media: firstAttachment, status: status }));
} else {
dispatch(openModal('MEDIA', { media, index: 0, status: status }));
dispatch(openModal('MEDIA', { media: media.toJS(), index: 0, status: status.toJS() }));
}
}
};

Wyświetl plik

@ -36,7 +36,7 @@ const GroupMediaPanel: React.FC<IGroupMediaPanel> = ({ group }) => {
const media = attachment.getIn(['status', 'media_attachments']) as ImmutableList<Attachment>;
const index = media.findIndex(x => x.id === attachment.id);
dispatch(openModal('MEDIA', { media, index, status: attachment.status, account: attachment.account }));
dispatch(openModal('MEDIA', { media: media.toJS(), index, status: attachment?.status?.toJS() ?? attachment.status, account: attachment.account })); // NOTE: why 'account' field is here? it doesn't exist in MediaModal component
}
};

Wyświetl plik

@ -5,11 +5,11 @@ import { spring } from 'react-motion';
import HStack from 'soapbox/components/ui/hstack.tsx';
import SvgIcon from 'soapbox/components/ui/svg-icon.tsx';
import ReplyIndicator from 'soapbox/features/compose/components/reply-indicator.tsx';
import { Status as StatusEntity } from 'soapbox/schemas/index.ts';
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;

Wyświetl plik

@ -6,8 +6,9 @@ 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 ReplyIndicator from 'soapbox/features/compose/components/reply-indicator.tsx';
import { Status as StatusEntity } from 'soapbox/schemas/index.ts';
import type { Status as StatusEntity } from 'soapbox/types/entities.ts';
import type { Status as LegacyStatus } from 'soapbox/types/entities.ts';
const messages = defineMessages({
cancel_reblog: { id: 'status.cancel_reblog_private', defaultMessage: 'Un-repost' },
@ -15,8 +16,8 @@ const messages = defineMessages({
});
interface IBoostModal {
status: StatusEntity;
onReblog: (status: StatusEntity) => void;
status: LegacyStatus;
onReblog: (status: LegacyStatus) => void;
onClose: () => void;
}
@ -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 StatusEntity} hideActions />
<Text>
{/* eslint-disable-next-line formatjs/no-literal-string-in-jsx */}

Wyświetl plik

@ -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'>

Wyświetl plik

@ -24,15 +24,14 @@ import PlaceholderStatus from 'soapbox/features/placeholder/components/placehold
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 { Status as StatusEntity, Attachment } from 'soapbox/schemas/index.ts';
import { Status as LegacyStatus } 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 +54,8 @@ const containerStyle: React.CSSProperties = {
};
interface IMediaModal {
media: ImmutableList<Attachment>;
status?: Status;
media: readonly Attachment[];
status?: StatusEntity;
index: number;
time?: number;
onClose(): void;
@ -74,8 +73,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 +81,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 +105,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 +124,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 +149,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 +167,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 +191,7 @@ const MediaModal: React.FC<IMediaModal> = (props) => {
}
return null;
}).toArray();
});
const handleLoadMore = useCallback(debounce(() => {
if (next && status) {
@ -344,7 +342,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 LegacyStatus}
space='md'
statusActionButtonTheme='inverse'
/>
@ -362,7 +360,7 @@ const MediaModal: React.FC<IMediaModal> = (props) => {
}
>
<Thread
status={actualStatus}
status={normalizeStatus(actualStatus) as LegacyStatus}
withMedia={false}
useWindowScroll={false}
itemClassName='px-4'

Wyświetl plik

@ -21,6 +21,7 @@ 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';
@ -91,7 +92,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}
/>
)}

Wyświetl plik

@ -34,7 +34,7 @@ const ProfileMediaPanel: React.FC<IProfileMediaPanel> = ({ account }) => {
const media = attachment.getIn(['status', 'media_attachments']) as ImmutableList<Attachment>;
const index = media.findIndex(x => x.id === attachment.id);
dispatch(openModal('MEDIA', { media, index, status: attachment.status }));
dispatch(openModal('MEDIA', { media: media.toJS(), index, status: attachment?.status?.toJS() ?? attachment.status }));
}
};

Wyświetl plik

@ -43,6 +43,9 @@ type Tag = ReturnType<typeof TagRecord>;
type Account = SchemaAccount & LegacyMap;
/**
* @deprecated Use the Status from 'soapbox/schemas/index.ts'
*/
interface Status extends ReturnType<typeof StatusRecord> {
// HACK: same as above
quote: EmbeddedEntity<Status>;