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