Signed-off-by: marcin mikołajczak <git@mkljczk.pl>
This commit is contained in:
marcin mikołajczak 2022-11-27 23:29:45 +01:00
parent c61dcddd81
commit 6465423ccb
10 changed files with 81 additions and 56 deletions

View File

@ -19,12 +19,13 @@ const messages = defineMessages({
});
interface IEventPreview {
status: StatusEntity,
className?: string,
hideAction?: boolean;
status: StatusEntity
className?: string
hideAction?: boolean
floatingAction?: boolean
}
const EventPreview: React.FC<IEventPreview> = ({ status, className, hideAction }) => {
const EventPreview: React.FC<IEventPreview> = ({ status, className, hideAction, floatingAction = true }) => {
const intl = useIntl();
const me = useAppSelector((state) => state.me);
@ -32,26 +33,37 @@ const EventPreview: React.FC<IEventPreview> = ({ status, className, hideAction }
const account = status.account as AccountEntity;
const event = status.event!;
const banner = status.media_attachments?.find(({ description }) => description === 'Banner');
const banner = event.banner;
const action = !hideAction && (account.id === me ? (
<Button
size='sm'
theme={floatingAction ? 'secondary' : 'primary'}
to={`/@${account.acct}/events/${status.id}`}
>
<FormattedMessage id='event.manage' defaultMessage='Manage' />
</Button>
) : (
<EventActionButton
status={status}
theme={floatingAction ? 'secondary' : 'primary'}
/>
));
return (
<div className={classNames('w-full rounded-lg bg-gray-100 dark:bg-primary-800 relative overflow-hidden', className)}>
<div className='absolute top-28 right-3'>
{!hideAction && (account.id === me ? (
<Button
size='sm'
theme='secondary'
to={`/@${account.acct}/events/${status.id}`}
>
<FormattedMessage id='event.manage' defaultMessage='Manage' />
</Button>
) : <EventActionButton status={status} />)}
{floatingAction && action}
</div>
<div className='bg-primary-200 dark:bg-gray-600 h-40'>
{banner && <img className='h-full w-full object-cover' src={banner.url} alt={intl.formatMessage(messages.bannerHeader)} />}
</div>
<Stack className='p-2.5' space={2}>
<Text weight='semibold'>{event.name}</Text>
<HStack space={2} alignItems='center' justifyContent='between'>
<Text weight='semibold' truncate>{event.name}</Text>
{!floatingAction && action}
</HStack>
<div className='flex gap-y-1 gap-x-2 flex-wrap text-gray-700 dark:text-gray-600'>
<HStack alignItems='center' space={2}>

View File

@ -24,8 +24,6 @@ interface IStatusMedia {
showMedia?: boolean,
/** Callback when visibility is toggled (eg clicked through NSFW). */
onToggleVisibility?: () => void,
/** Whether or not to hide image describer as 'Banner' */
excludeBanner?: boolean,
}
/** Render media attachments for a status. */
@ -35,7 +33,6 @@ const StatusMedia: React.FC<IStatusMedia> = ({
onClick,
showMedia = true,
onToggleVisibility = () => { },
excludeBanner = false,
}) => {
const dispatch = useAppDispatch();
const settings = useSettings();
@ -43,10 +40,8 @@ const StatusMedia: React.FC<IStatusMedia> = ({
const [mediaWrapperWidth, setMediaWrapperWidth] = useState<number | undefined>(undefined);
const mediaAttachments = excludeBanner ? status.media_attachments.filter(({ description, pleroma }) => description !== 'Banner' && pleroma.get('mime_type') !== 'text/html') : status.media_attachments;
const size = mediaAttachments.size;
const firstAttachment = mediaAttachments.first();
const size = status.media_attachments.size;
const firstAttachment = status.media_attachments.first();
let media: JSX.Element | null = null;
@ -76,7 +71,7 @@ const StatusMedia: React.FC<IStatusMedia> = ({
if (muted) {
media = (
<AttachmentThumbs
media={mediaAttachments}
media={status.media_attachments}
onClick={onClick}
sensitive={status.sensitive}
/>
@ -147,7 +142,7 @@ const StatusMedia: React.FC<IStatusMedia> = ({
<Bundle fetchComponent={MediaGallery} loading={renderLoadingMediaGallery}>
{(Component: any) => (
<Component
media={mediaAttachments}
media={status.media_attachments}
sensitive={status.sensitive}
height={285}
onOpenMedia={openMedia}

View File

@ -6,6 +6,7 @@ import { openModal } from 'soapbox/actions/modals';
import { Button } from 'soapbox/components/ui';
import { useAppDispatch, useAppSelector } from 'soapbox/hooks';
import type { ButtonThemes } from 'soapbox/components/ui/button/useButtonStyles';
import type { Status as StatusEntity } from 'soapbox/types/entities';
const messages = defineMessages({
@ -14,10 +15,11 @@ const messages = defineMessages({
});
interface IEventAction {
status: StatusEntity,
status: StatusEntity
theme?: ButtonThemes
}
const EventActionButton: React.FC<IEventAction> = ({ status }) => {
const EventActionButton: React.FC<IEventAction> = ({ status, theme = 'secondary' }) => {
const intl = useIntl();
const dispatch = useAppDispatch();
@ -86,7 +88,7 @@ const EventActionButton: React.FC<IEventAction> = ({ status }) => {
return (
<Button
size='sm'
theme='secondary'
theme={theme}
icon={buttonIcon}
onClick={buttonAction}
disabled={buttonDisabled}

View File

@ -1,3 +1,4 @@
import { List as ImmutableList } from 'immutable';
import React from 'react';
import { defineMessages, FormattedMessage, useIntl } from 'react-intl';
import { Link, useHistory } from 'react-router-dom';
@ -86,15 +87,14 @@ const EventHeader: React.FC<IEventHeader> = ({ status }) => {
const account = status.account as AccountEntity;
const event = status.event;
const banner = status.media_attachments?.find(({ description }) => description === 'Banner');
const banner = event.banner;
const username = account.username;
const handleHeaderClick: React.MouseEventHandler<HTMLAnchorElement> = (e) => {
e.stopPropagation();
const index = status.media_attachments!.findIndex(({ description }) => description === 'Banner');
dispatch(openModal('MEDIA', { media: status.media_attachments, index }));
dispatch(openModal('MEDIA', { media: ImmutableList([event.banner]) }));
};
const handleExportClick = () => {

View File

@ -4,8 +4,11 @@ import { FormattedDate, FormattedMessage } from 'react-intl';
import { openModal } from 'soapbox/actions/modals';
import { fetchStatus } from 'soapbox/actions/statuses';
import MissingIndicator from 'soapbox/components/missing-indicator';
import StatusContent from 'soapbox/components/status-content';
import StatusMedia from 'soapbox/components/status-media';
import TranslateButton from 'soapbox/components/translate-button';
import { HStack, Icon, Stack, Text } from 'soapbox/components/ui';
import QuotedStatus from 'soapbox/features/status/containers/quoted-status-container';
import { useAppDispatch, useAppSelector, useSettings } from 'soapbox/hooks';
import { makeGetStatus } from 'soapbox/selectors';
import { defaultMediaVisibility } from 'soapbox/utils/status';
@ -107,10 +110,7 @@ const EventInformation: React.FC<IEventInformation> = ({ params }) => {
}, [status]);
const renderLinks = useCallback(() => {
const links = status?.media_attachments.filter(({ pleroma }) => pleroma.get('mime_type') === 'text/html');
if (!links?.size) return null;
if (!status.event?.links.size) return null;
return (
<Stack space={1}>
@ -118,11 +118,11 @@ const EventInformation: React.FC<IEventInformation> = ({ params }) => {
<FormattedMessage id='event.website' defaultMessage='External links' />
</Text>
{links.map(link => (
{status.event.links.map(link => (
<HStack space={2} alignItems='center'>
<Icon src={require('@tabler/icons/link.svg')} />
<a href={link.remote_url || link.url} className='text-primary-600 dark:text-accent-blue hover:underline' target='_blank'>
{(link.remote_url || link.url).replace(/^https?:\/\//, '')}
<a href={link.url} className='text-primary-600 dark:text-accent-blue hover:underline' target='_blank'>
{link.url.replace(/^https?:\/\//, '')}
</a>
</HStack>
))}
@ -143,21 +143,23 @@ const EventInformation: React.FC<IEventInformation> = ({ params }) => {
<Text size='xl' weight='bold'>
<FormattedMessage id='event.description' defaultMessage='Description' />
</Text>
<Text
className='break-words status__content'
size='sm'
dangerouslySetInnerHTML={{ __html: status.contentHtml }}
/>
<StatusContent status={status} collapsable={false} translatable />
<TranslateButton status={status} />
</Stack>
)}
<StatusMedia
status={status}
excludeBanner
showMedia={showMedia}
onToggleVisibility={handleToggleMediaVisibility}
/>
{status.quote && status.pleroma.get('quote_visible', true) && (
<QuotedStatus statusId={status.quote as string} />
)}
{renderEventLocation()}
{renderEventDate()}

View File

@ -22,7 +22,7 @@ const Event = ({ id }: { id: string }) => {
className='w-full px-1'
to={`/@${status.getIn(['account', 'acct'])}/events/${status.id}`}
>
<EventPreview status={status} />
<EventPreview status={status} floatingAction={false} />
</Link>
);
};
@ -56,7 +56,6 @@ const EventCarousel: React.FC<IEventCarousel> = ({ statusIds, isLoading, emptyMe
{index !== 0 && (
<div className='z-10 absolute left-3 top-1/2 -mt-4'>
<button
data-testid='prev-page'
onClick={() => handleChangeIndex(index - 1)}
className='bg-white/50 dark:bg-gray-900/50 backdrop-blur rounded-full h-8 w-8 flex items-center justify-center'
>
@ -70,7 +69,6 @@ const EventCarousel: React.FC<IEventCarousel> = ({ statusIds, isLoading, emptyMe
{index !== statusIds.size - 1 && (
<div className='z-10 absolute right-3 top-1/2 -mt-4'>
<button
data-testid='next-page'
onClick={() => handleChangeIndex(index + 1)}
className='bg-white/50 dark:bg-gray-900/50 backdrop-blur rounded-full h-8 w-8 flex items-center justify-center'
>

View File

@ -1,4 +1,4 @@
import React, { useEffect, useState } from 'react';
import React, { useEffect } from 'react';
import { defineMessages, FormattedMessage, useIntl } from 'react-intl';
import { fetchJoinedEvents, fetchRecentEvents } from 'soapbox/actions/events';

View File

@ -10,9 +10,7 @@ const PlaceholderEventPreview = () => {
return (
<div className='w-full rounded-lg bg-gray-100 dark:bg-primary-800 relative overflow-hidden animate-pulse text-primary-50 dark:text-primary-800'>
<div className='bg-primary-200 dark:bg-gray-600 h-40'>
{/* <img className='h-full w-full object-cover' src={banner.url} alt={intl.formatMessage(messages.bannerHeader)} />} */}
</div>
<div className='bg-primary-200 dark:bg-gray-600 h-40' />
<Stack className='p-2.5' space={2}>
<Text weight='semibold'>{generateText(eventNameLength)}</Text>

View File

@ -32,6 +32,8 @@ export const EventRecord = ImmutableRecord({
participants_count: 0,
location: null as ImmutableMap<string, any> | null,
join_state: null as EventJoinState | null,
banner: null as Attachment | null,
links: ImmutableList<Attachment>(),
});
// https://docs.joinmastodon.org/entities/status/
@ -174,9 +176,27 @@ const fixSensitivity = (status: ImmutableMap<string, any>) => {
// Normalize event
const normalizeEvent = (status: ImmutableMap<string, any>) => {
if (status.getIn(['pleroma', 'event'])) {
return status.set('event', EventRecord(status.getIn(['pleroma', 'event']) as ImmutableMap<string, any>));
} else {
return status.set('event', null);
const firstAttachment = status.get('media_attachments').first();
let banner = null;
let mediaAttachments = status.get('media_attachments');
if (firstAttachment && firstAttachment.description === 'Banner' && firstAttachment.type === 'image') {
banner = normalizeAttachment(firstAttachment);
mediaAttachments = mediaAttachments.shift();
}
const links = mediaAttachments.filter((attachment: Attachment) => attachment.pleroma.get('mime_type') === 'text/html');
mediaAttachments = mediaAttachments.filter((attachment: Attachment) => attachment.pleroma.get('mime_type') !== 'text/html');
const event = EventRecord(
(status.getIn(['pleroma', 'event']) as ImmutableMap<string, any>)
.set('banner', banner)
.set('links', links),
);
status
.set('event', event)
.set('media_attachments', mediaAttachments);
}
};

View File

@ -25,7 +25,6 @@ import { normalizeAttachment, normalizeLocation } from 'soapbox/normalizers';
import type {
Attachment as AttachmentEntity,
Location as LocationEntity,
Status as StatusEntity,
} from 'soapbox/types/entities';
export const ReducerRecord = ImmutableRecord({
@ -90,11 +89,10 @@ export default function compose_event(state = ReducerRecord(), action: AnyAction
return ReducerRecord({
name: action.status.event.name,
status: action.text,
// location: null as LocationEntity | null,
start_time: new Date(action.status.event.start_time),
end_time: action.status.event.end_time ? new Date(action.status.event.end_time) : null,
approval_required: action.status.event.join_mode !== 'free',
banner: (action.status as StatusEntity).media_attachments.find(({ description }) => description === 'Banner') || null,
banner: action.status.event.banner || null,
location: action.location ? normalizeLocation(action.location) : null,
progress: 0,
is_uploading: false,