Information page, improvements
Signed-off-by: marcin mikołajczak <git@mkljczk.pl>
This commit is contained in:
parent
309bd2c34f
commit
fe7333ddb0
28 changed files with 478 additions and 75 deletions
|
@ -11,7 +11,7 @@ import snackbar from './snackbar';
|
||||||
|
|
||||||
import type { AxiosError } from 'axios';
|
import type { AxiosError } from 'axios';
|
||||||
import type { AppDispatch, RootState } from 'soapbox/store';
|
import type { AppDispatch, RootState } from 'soapbox/store';
|
||||||
import type { APIEntity } from 'soapbox/types/entities';
|
import type { APIEntity, Status as StatusEntity } from 'soapbox/types/entities';
|
||||||
|
|
||||||
const LOCATION_SEARCH_REQUEST = 'LOCATION_SEARCH_REQUEST';
|
const LOCATION_SEARCH_REQUEST = 'LOCATION_SEARCH_REQUEST';
|
||||||
const LOCATION_SEARCH_SUCCESS = 'LOCATION_SEARCH_SUCCESS';
|
const LOCATION_SEARCH_SUCCESS = 'LOCATION_SEARCH_SUCCESS';
|
||||||
|
@ -259,7 +259,7 @@ const joinEvent = (id: string, participationMessage?: string) =>
|
||||||
return dispatch(noOp);
|
return dispatch(noOp);
|
||||||
}
|
}
|
||||||
|
|
||||||
dispatch(joinEventRequest());
|
dispatch(joinEventRequest(status));
|
||||||
|
|
||||||
return api(getState).post(`/api/v1/pleroma/events/${id}/join`, { participationMessage }).then(({ data }) => {
|
return api(getState).post(`/api/v1/pleroma/events/${id}/join`, { participationMessage }).then(({ data }) => {
|
||||||
dispatch(importFetchedStatus(data));
|
dispatch(importFetchedStatus(data));
|
||||||
|
@ -270,22 +270,24 @@ const joinEvent = (id: string, participationMessage?: string) =>
|
||||||
`/@${data.account.acct}/events/${data.id}`,
|
`/@${data.account.acct}/events/${data.id}`,
|
||||||
));
|
));
|
||||||
}).catch(function(error) {
|
}).catch(function(error) {
|
||||||
dispatch(joinEventFail(error, status?.event?.join_state || null));
|
dispatch(joinEventFail(error, status, status?.event?.join_state || null));
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
const joinEventRequest = () => ({
|
const joinEventRequest = (status: StatusEntity) => ({
|
||||||
type: EVENT_JOIN_REQUEST,
|
type: EVENT_JOIN_REQUEST,
|
||||||
|
id: status.id,
|
||||||
});
|
});
|
||||||
|
|
||||||
const joinEventSuccess = (status: APIEntity) => ({
|
const joinEventSuccess = (status: APIEntity) => ({
|
||||||
type: EVENT_JOIN_SUCCESS,
|
type: EVENT_JOIN_SUCCESS,
|
||||||
status,
|
id: status.id,
|
||||||
});
|
});
|
||||||
|
|
||||||
const joinEventFail = (error: AxiosError, previousState: string | null) => ({
|
const joinEventFail = (error: AxiosError, status: StatusEntity, previousState: string | null) => ({
|
||||||
type: EVENT_JOIN_FAIL,
|
type: EVENT_JOIN_FAIL,
|
||||||
error,
|
error,
|
||||||
|
id: status.id,
|
||||||
previousState,
|
previousState,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -297,27 +299,29 @@ const leaveEvent = (id: string) =>
|
||||||
return dispatch(noOp);
|
return dispatch(noOp);
|
||||||
}
|
}
|
||||||
|
|
||||||
dispatch(leaveEventRequest());
|
dispatch(leaveEventRequest(status));
|
||||||
|
|
||||||
return api(getState).post(`/api/v1/pleroma/events/${id}/leave`).then(({ data }) => {
|
return api(getState).post(`/api/v1/pleroma/events/${id}/leave`).then(({ data }) => {
|
||||||
dispatch(importFetchedStatus(data));
|
dispatch(importFetchedStatus(data));
|
||||||
dispatch(leaveEventSuccess(data));
|
dispatch(leaveEventSuccess(data));
|
||||||
}).catch(function(error) {
|
}).catch(function(error) {
|
||||||
dispatch(leaveEventFail(error));
|
dispatch(leaveEventFail(error, status));
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
const leaveEventRequest = () => ({
|
const leaveEventRequest = (status: StatusEntity) => ({
|
||||||
type: EVENT_LEAVE_REQUEST,
|
type: EVENT_LEAVE_REQUEST,
|
||||||
|
id: status.id,
|
||||||
});
|
});
|
||||||
|
|
||||||
const leaveEventSuccess = (status: APIEntity) => ({
|
const leaveEventSuccess = (status: APIEntity) => ({
|
||||||
type: EVENT_LEAVE_SUCCESS,
|
type: EVENT_LEAVE_SUCCESS,
|
||||||
status,
|
id: status.id,
|
||||||
});
|
});
|
||||||
|
|
||||||
const leaveEventFail = (error: AxiosError) => ({
|
const leaveEventFail = (error: AxiosError, status: StatusEntity) => ({
|
||||||
type: EVENT_LEAVE_FAIL,
|
type: EVENT_LEAVE_FAIL,
|
||||||
|
id: status.id,
|
||||||
error,
|
error,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
@ -38,7 +38,7 @@ const StatusMedia: React.FC<IStatusMedia> = ({
|
||||||
const dispatch = useAppDispatch();
|
const dispatch = useAppDispatch();
|
||||||
const [mediaWrapperWidth, setMediaWrapperWidth] = useState<number | undefined>(undefined);
|
const [mediaWrapperWidth, setMediaWrapperWidth] = useState<number | undefined>(undefined);
|
||||||
|
|
||||||
const mediaAttachments = excludeBanner ? status.media_attachments.filter(({ description }) => description !== 'Banner') : status.media_attachments;
|
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 size = mediaAttachments.size;
|
||||||
const firstAttachment = mediaAttachments.first();
|
const firstAttachment = mediaAttachments.first();
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
import classNames from 'clsx';
|
import classNames from 'clsx';
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
|
|
||||||
type SIZES = 0 | 0.5 | 1 | 1.5 | 2 | 3 | 4 | 5 | 10
|
type SIZES = 0 | 0.5 | 1 | 1.5 | 2 | 3 | 4 | 5 | 6 | 10
|
||||||
|
|
||||||
const spaces = {
|
const spaces = {
|
||||||
0: 'space-y-0',
|
0: 'space-y-0',
|
||||||
|
@ -12,6 +12,7 @@ const spaces = {
|
||||||
3: 'space-y-3',
|
3: 'space-y-3',
|
||||||
4: 'space-y-4',
|
4: 'space-y-4',
|
||||||
5: 'space-y-5',
|
5: 'space-y-5',
|
||||||
|
6: 'space-y-6',
|
||||||
10: 'space-y-10',
|
10: 'space-y-10',
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -4,7 +4,7 @@ import { defineMessages, FormattedMessage, useIntl } from 'react-intl';
|
||||||
import { joinEvent, leaveEvent } from 'soapbox/actions/events';
|
import { joinEvent, leaveEvent } from 'soapbox/actions/events';
|
||||||
import { openModal } from 'soapbox/actions/modals';
|
import { openModal } from 'soapbox/actions/modals';
|
||||||
import { Button } from 'soapbox/components/ui';
|
import { Button } from 'soapbox/components/ui';
|
||||||
import { useAppDispatch } from 'soapbox/hooks';
|
import { useAppDispatch, useAppSelector } from 'soapbox/hooks';
|
||||||
|
|
||||||
import type { Status as StatusEntity } from 'soapbox/types/entities';
|
import type { Status as StatusEntity } from 'soapbox/types/entities';
|
||||||
|
|
||||||
|
@ -21,6 +21,8 @@ const EventActionButton: React.FC<IEventAction> = ({ status }) => {
|
||||||
const intl = useIntl();
|
const intl = useIntl();
|
||||||
const dispatch = useAppDispatch();
|
const dispatch = useAppDispatch();
|
||||||
|
|
||||||
|
const me = useAppSelector((state) => state.me);
|
||||||
|
|
||||||
const event = status.event!;
|
const event = status.event!;
|
||||||
|
|
||||||
const handleJoin: React.EventHandler<React.MouseEvent> = (e) => {
|
const handleJoin: React.EventHandler<React.MouseEvent> = (e) => {
|
||||||
|
@ -49,6 +51,15 @@ const EventActionButton: React.FC<IEventAction> = ({ status }) => {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const handleOpenUnauthorizedModal: React.EventHandler<React.MouseEvent> = (e) => {
|
||||||
|
e.preventDefault();
|
||||||
|
|
||||||
|
dispatch(openModal('UNAUTHORIZED', {
|
||||||
|
action: 'JOIN',
|
||||||
|
ap_id: status.url,
|
||||||
|
}));
|
||||||
|
};
|
||||||
|
|
||||||
let buttonLabel;
|
let buttonLabel;
|
||||||
let buttonIcon;
|
let buttonIcon;
|
||||||
let buttonDisabled = false;
|
let buttonDisabled = false;
|
||||||
|
@ -69,7 +80,7 @@ const EventActionButton: React.FC<IEventAction> = ({ status }) => {
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
buttonLabel = <FormattedMessage id='event.join_state.empty' defaultMessage='Participate' />;
|
buttonLabel = <FormattedMessage id='event.join_state.empty' defaultMessage='Participate' />;
|
||||||
buttonAction = handleJoin;
|
buttonAction = me ? handleJoin : handleOpenUnauthorizedModal;
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|
|
@ -4,13 +4,15 @@ import { Link } from 'react-router-dom';
|
||||||
|
|
||||||
import { fetchEventIcs } from 'soapbox/actions/events';
|
import { fetchEventIcs } from 'soapbox/actions/events';
|
||||||
import { openModal } from 'soapbox/actions/modals';
|
import { openModal } from 'soapbox/actions/modals';
|
||||||
|
import { deleteStatusModal, toggleStatusSensitivityModal } from 'soapbox/actions/moderation';
|
||||||
import Icon from 'soapbox/components/icon';
|
import Icon from 'soapbox/components/icon';
|
||||||
import StillImage from 'soapbox/components/still_image';
|
import StillImage from 'soapbox/components/still_image';
|
||||||
import { HStack, IconButton, Menu, MenuButton, MenuDivider, MenuItem, MenuLink, MenuList, Stack, Text } from 'soapbox/components/ui';
|
import { Button, HStack, IconButton, Menu, MenuButton, MenuDivider, MenuItem, MenuLink, MenuList, Stack, Text } from 'soapbox/components/ui';
|
||||||
import SvgIcon from 'soapbox/components/ui/icon/svg-icon';
|
import SvgIcon from 'soapbox/components/ui/icon/svg-icon';
|
||||||
import VerificationBadge from 'soapbox/components/verification_badge';
|
import VerificationBadge from 'soapbox/components/verification_badge';
|
||||||
import { useAppDispatch, useAppSelector } from 'soapbox/hooks';
|
import { useAppDispatch, useOwnAccount } from 'soapbox/hooks';
|
||||||
import { download } from 'soapbox/utils/download';
|
import { download } from 'soapbox/utils/download';
|
||||||
|
import { shortNumberFormat } from 'soapbox/utils/numbers';
|
||||||
|
|
||||||
import PlaceholderEventHeader from '../../placeholder/components/placeholder_event_header';
|
import PlaceholderEventHeader from '../../placeholder/components/placeholder_event_header';
|
||||||
import EventActionButton from '../components/event-action-button';
|
import EventActionButton from '../components/event-action-button';
|
||||||
|
@ -23,6 +25,13 @@ const messages = defineMessages({
|
||||||
bannerHeader: { id: 'event.banner', defaultMessage: 'Event banner' },
|
bannerHeader: { id: 'event.banner', defaultMessage: 'Event banner' },
|
||||||
exportIcs: { id: 'event.export_ics', defaultMessage: 'Export to your calendar' },
|
exportIcs: { id: 'event.export_ics', defaultMessage: 'Export to your calendar' },
|
||||||
copy: { id: 'event.copy', defaultMessage: 'Copy link to event' },
|
copy: { id: 'event.copy', defaultMessage: 'Copy link to event' },
|
||||||
|
bookmark: { id: 'status.bookmark', defaultMessage: 'Bookmark' },
|
||||||
|
unbookmark: { id: 'status.unbookmark', defaultMessage: 'Remove bookmark' },
|
||||||
|
adminAccount: { id: 'status.admin_account', defaultMessage: 'Moderate @{name}' },
|
||||||
|
adminStatus: { id: 'status.admin_status', defaultMessage: 'Open this post in the moderation interface' },
|
||||||
|
markStatusSensitive: { id: 'admin.statuses.actions.mark_status_sensitive', defaultMessage: 'Mark post sensitive' },
|
||||||
|
markStatusNotSensitive: { id: 'admin.statuses.actions.mark_status_not_sensitive', defaultMessage: 'Mark post not sensitive' },
|
||||||
|
deleteStatus: { id: 'admin.statuses.actions.delete_status', defaultMessage: 'Delete post' },
|
||||||
});
|
});
|
||||||
|
|
||||||
interface IEventHeader {
|
interface IEventHeader {
|
||||||
|
@ -33,13 +42,15 @@ const EventHeader: React.FC<IEventHeader> = ({ status }) => {
|
||||||
const intl = useIntl();
|
const intl = useIntl();
|
||||||
const dispatch = useAppDispatch();
|
const dispatch = useAppDispatch();
|
||||||
|
|
||||||
const me = useAppSelector(state => state.me);
|
const ownAccount = useOwnAccount();
|
||||||
|
const isStaff = ownAccount ? ownAccount.staff : false;
|
||||||
|
const isAdmin = ownAccount ? ownAccount.admin : false;
|
||||||
|
|
||||||
if (!status || !status.event) {
|
if (!status || !status.event) {
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<div className='-mt-4 -mx-4'>
|
<div className='-mt-4 -mx-4'>
|
||||||
<div className='relative h-48 w-full lg:h-64 md:rounded-t-xl bg-gray-200 dark:bg-gray-900/50' />
|
<div className='relative h-32 w-full lg:h-48 md:rounded-t-xl bg-gray-200 dark:bg-gray-900/50' />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<PlaceholderEventHeader />
|
<PlaceholderEventHeader />
|
||||||
|
@ -52,17 +63,51 @@ const EventHeader: React.FC<IEventHeader> = ({ status }) => {
|
||||||
const banner = status.media_attachments?.find(({ description }) => description === 'Banner');
|
const banner = status.media_attachments?.find(({ description }) => description === 'Banner');
|
||||||
|
|
||||||
const handleHeaderClick: React.MouseEventHandler<HTMLAnchorElement> = (e) => {
|
const handleHeaderClick: React.MouseEventHandler<HTMLAnchorElement> = (e) => {
|
||||||
e.preventDefault();
|
e.stopPropagation();
|
||||||
|
|
||||||
const index = status.media_attachments!.findIndex(({ description }) => description === 'Banner');
|
const index = status.media_attachments!.findIndex(({ description }) => description === 'Banner');
|
||||||
dispatch(openModal('MEDIA', { media: status.media_attachments, index }));
|
dispatch(openModal('MEDIA', { media: status.media_attachments, index }));
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleExportClick: React.MouseEventHandler = e => {
|
const handleExportClick = () => {
|
||||||
dispatch(fetchEventIcs(status.id)).then((response) => {
|
dispatch(fetchEventIcs(status.id)).then((response) => {
|
||||||
download(response, 'calendar.ics');
|
download(response, 'calendar.ics');
|
||||||
}).catch(() => {});
|
}).catch(() => {});
|
||||||
e.preventDefault();
|
};
|
||||||
|
|
||||||
|
const handleCopy = () => {
|
||||||
|
const { uri } = status;
|
||||||
|
const textarea = document.createElement('textarea');
|
||||||
|
|
||||||
|
textarea.textContent = uri;
|
||||||
|
textarea.style.position = 'fixed';
|
||||||
|
|
||||||
|
document.body.appendChild(textarea);
|
||||||
|
|
||||||
|
try {
|
||||||
|
textarea.select();
|
||||||
|
document.execCommand('copy');
|
||||||
|
} catch {
|
||||||
|
// Do nothing
|
||||||
|
} finally {
|
||||||
|
document.body.removeChild(textarea);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleModerate = () => {
|
||||||
|
dispatch(openModal('ACCOUNT_MODERATION', { accountId: account.id }));
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleModerateStatus = () => {
|
||||||
|
window.open(`/pleroma/admin/#/statuses/${status.id}/`, '_blank');
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleToggleStatusSensitivity = () => {
|
||||||
|
dispatch(toggleStatusSensitivityModal(intl, status.id, status.sensitive));
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleDeleteStatus = () => {
|
||||||
|
dispatch(deleteStatusModal(intl, status.id));
|
||||||
};
|
};
|
||||||
|
|
||||||
const menu: MenuType = [
|
const menu: MenuType = [
|
||||||
|
@ -71,12 +116,66 @@ const EventHeader: React.FC<IEventHeader> = ({ status }) => {
|
||||||
action: handleExportClick,
|
action: handleExportClick,
|
||||||
icon: require('@tabler/icons/calendar-plus.svg'),
|
icon: require('@tabler/icons/calendar-plus.svg'),
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
text: intl.formatMessage(messages.copy),
|
||||||
|
action: handleCopy,
|
||||||
|
icon: require('@tabler/icons/link.svg'),
|
||||||
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
|
if (isStaff) {
|
||||||
|
menu.push(null);
|
||||||
|
|
||||||
|
menu.push({
|
||||||
|
text: intl.formatMessage(messages.adminAccount, { name: account.username }),
|
||||||
|
action: handleModerate,
|
||||||
|
icon: require('@tabler/icons/gavel.svg'),
|
||||||
|
});
|
||||||
|
|
||||||
|
if (isAdmin) {
|
||||||
|
menu.push({
|
||||||
|
text: intl.formatMessage(messages.adminStatus),
|
||||||
|
action: handleModerateStatus,
|
||||||
|
icon: require('@tabler/icons/pencil.svg'),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
menu.push({
|
||||||
|
text: intl.formatMessage(status.sensitive === false ? messages.markStatusSensitive : messages.markStatusNotSensitive),
|
||||||
|
action: handleToggleStatusSensitivity,
|
||||||
|
icon: require('@tabler/icons/alert-triangle.svg'),
|
||||||
|
});
|
||||||
|
|
||||||
|
if (account.id !== ownAccount?.id) {
|
||||||
|
menu.push({
|
||||||
|
text: intl.formatMessage(messages.deleteStatus),
|
||||||
|
action: handleDeleteStatus,
|
||||||
|
icon: require('@tabler/icons/trash.svg'),
|
||||||
|
destructive: true,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleManageClick: React.MouseEventHandler = e => {
|
||||||
|
e.stopPropagation();
|
||||||
|
|
||||||
|
dispatch(openModal('MANAGE_EVENT', {
|
||||||
|
statusId: status.id,
|
||||||
|
}));
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleParticipantsClick: React.MouseEventHandler = e => {
|
||||||
|
e.stopPropagation();
|
||||||
|
|
||||||
|
dispatch(openModal('EVENT_PARTICIPANTS', {
|
||||||
|
statusId: status.id,
|
||||||
|
}));
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<div className='-mt-4 -mx-4'>
|
<div className='-mt-4 -mx-4'>
|
||||||
<div className='relative h-48 w-full lg:h-64 md:rounded-t-xl bg-gray-200 dark:bg-gray-900/50'>
|
<div className='relative h-32 w-full lg:h-48 md:rounded-t-xl bg-gray-200 dark:bg-gray-900/50'>
|
||||||
{banner && (
|
{banner && (
|
||||||
<a href={banner.url} onClick={handleHeaderClick} target='_blank'>
|
<a href={banner.url} onClick={handleHeaderClick} target='_blank'>
|
||||||
<StillImage
|
<StillImage
|
||||||
|
@ -124,12 +223,21 @@ const EventHeader: React.FC<IEventHeader> = ({ status }) => {
|
||||||
})}
|
})}
|
||||||
</MenuList>
|
</MenuList>
|
||||||
</Menu>
|
</Menu>
|
||||||
{account.id !== me && <EventActionButton status={status} />}
|
{account.id === ownAccount?.id ? (
|
||||||
|
<Button
|
||||||
|
size='sm'
|
||||||
|
theme='secondary'
|
||||||
|
onClick={handleManageClick}
|
||||||
|
to={`/@${account.acct}/events/${status.id}`}
|
||||||
|
>
|
||||||
|
<FormattedMessage id='event.manage' defaultMessage='Manage' />
|
||||||
|
</Button>
|
||||||
|
) : <EventActionButton status={status} />}
|
||||||
</HStack>
|
</HStack>
|
||||||
|
|
||||||
<Stack space={1}>
|
<Stack space={1}>
|
||||||
<HStack alignItems='center' space={2}>
|
<HStack alignItems='center' space={2}>
|
||||||
<Icon src={require('@tabler/icons/user.svg')} />
|
<Icon src={require('@tabler/icons/flag-3.svg')} />
|
||||||
<span>
|
<span>
|
||||||
<FormattedMessage
|
<FormattedMessage
|
||||||
id='event.organized_by'
|
id='event.organized_by'
|
||||||
|
@ -146,6 +254,22 @@ const EventHeader: React.FC<IEventHeader> = ({ status }) => {
|
||||||
</span>
|
</span>
|
||||||
</HStack>
|
</HStack>
|
||||||
|
|
||||||
|
<HStack alignItems='center' space={2}>
|
||||||
|
<Icon src={require('@tabler/icons/users.svg')} />
|
||||||
|
<a href='#' className='hover:underline' onClick={handleParticipantsClick}>
|
||||||
|
<span>
|
||||||
|
<FormattedMessage
|
||||||
|
id='event.participants'
|
||||||
|
defaultMessage='{count} {rawCount, plural, one {person} other {people}} going'
|
||||||
|
values={{
|
||||||
|
rawCount: event.participants_count || 0,
|
||||||
|
count: shortNumberFormat(event.participants_count || 0),
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</span>
|
||||||
|
</a>
|
||||||
|
</HStack>
|
||||||
|
|
||||||
<EventDate status={status} />
|
<EventDate status={status} />
|
||||||
|
|
||||||
{event.location && (
|
{event.location && (
|
||||||
|
|
|
@ -22,8 +22,6 @@ import type { VirtuosoHandle } from 'react-virtuoso';
|
||||||
import type { RootState } from 'soapbox/store';
|
import type { RootState } from 'soapbox/store';
|
||||||
import type { Attachment as AttachmentEntity } from 'soapbox/types/entities';
|
import type { Attachment as AttachmentEntity } from 'soapbox/types/entities';
|
||||||
|
|
||||||
const getStatus = makeGetStatus();
|
|
||||||
|
|
||||||
const getDescendantsIds = createSelector([
|
const getDescendantsIds = createSelector([
|
||||||
(_: RootState, statusId: string) => statusId,
|
(_: RootState, statusId: string) => statusId,
|
||||||
(state: RootState) => state.contexts.replies,
|
(state: RootState) => state.contexts.replies,
|
||||||
|
@ -66,8 +64,11 @@ interface IEventDiscussion {
|
||||||
const EventDiscussion: React.FC<IEventDiscussion> = (props) => {
|
const EventDiscussion: React.FC<IEventDiscussion> = (props) => {
|
||||||
const dispatch = useAppDispatch();
|
const dispatch = useAppDispatch();
|
||||||
|
|
||||||
|
const getStatus = useCallback(makeGetStatus(), []);
|
||||||
const status = useAppSelector(state => getStatus(state, { id: props.params.statusId }));
|
const status = useAppSelector(state => getStatus(state, { id: props.params.statusId }));
|
||||||
|
|
||||||
|
const me = useAppSelector((state) => state.me);
|
||||||
|
|
||||||
const descendantsIds = useAppSelector(state => {
|
const descendantsIds = useAppSelector(state => {
|
||||||
let descendantsIds = ImmutableOrderedSet<string>();
|
let descendantsIds = ImmutableOrderedSet<string>();
|
||||||
|
|
||||||
|
@ -104,8 +105,8 @@ const EventDiscussion: React.FC<IEventDiscussion> = (props) => {
|
||||||
}, [props.params.statusId]);
|
}, [props.params.statusId]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (isLoaded) dispatch(eventDiscussionCompose(`reply:${props.params.statusId}`, status!));
|
if (isLoaded && me) dispatch(eventDiscussionCompose(`reply:${props.params.statusId}`, status!));
|
||||||
}, [isLoaded]);
|
}, [isLoaded, me]);
|
||||||
|
|
||||||
const handleMoveUp = (id: string) => {
|
const handleMoveUp = (id: string) => {
|
||||||
const index = ImmutableList(descendantsIds).indexOf(id);
|
const index = ImmutableList(descendantsIds).indexOf(id);
|
||||||
|
@ -208,9 +209,9 @@ const EventDiscussion: React.FC<IEventDiscussion> = (props) => {
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Stack space={2}>
|
<Stack space={2}>
|
||||||
<div className='sm:p-2 pt-0 border-b border-solid border-gray-200 dark:border-gray-800'>
|
{me && <div className='sm:p-2 pt-0 border-b border-solid border-gray-200 dark:border-gray-800'>
|
||||||
<ComposeForm id={`reply:${status.id}`} autoFocus={false} eventDiscussion />
|
<ComposeForm id={`reply:${status.id}`} autoFocus={false} eventDiscussion />
|
||||||
</div>
|
</div>}
|
||||||
<div ref={node} className='thread p-0 sm:p-2 shadow-none'>
|
<div ref={node} className='thread p-0 sm:p-2 shadow-none'>
|
||||||
<ScrollableList
|
<ScrollableList
|
||||||
id='thread'
|
id='thread'
|
||||||
|
|
|
@ -1,17 +1,17 @@
|
||||||
import React, { useEffect, useState } from 'react';
|
import React, { useCallback, useEffect, useState } from 'react';
|
||||||
|
import { FormattedDate, FormattedMessage } from 'react-intl';
|
||||||
|
|
||||||
|
import { openModal } from 'soapbox/actions/modals';
|
||||||
import { fetchStatus } from 'soapbox/actions/statuses';
|
import { fetchStatus } from 'soapbox/actions/statuses';
|
||||||
import MissingIndicator from 'soapbox/components/missing_indicator';
|
import MissingIndicator from 'soapbox/components/missing_indicator';
|
||||||
import StatusMedia from 'soapbox/components/status-media';
|
import StatusMedia from 'soapbox/components/status-media';
|
||||||
import { Stack, Text } from 'soapbox/components/ui';
|
import { HStack, Icon, Stack, Text } from 'soapbox/components/ui';
|
||||||
import { useAppDispatch, useAppSelector, useSettings } from 'soapbox/hooks';
|
import { useAppDispatch, useAppSelector, useSettings } from 'soapbox/hooks';
|
||||||
import { makeGetStatus } from 'soapbox/selectors';
|
import { makeGetStatus } from 'soapbox/selectors';
|
||||||
import { defaultMediaVisibility } from 'soapbox/utils/status';
|
import { defaultMediaVisibility } from 'soapbox/utils/status';
|
||||||
|
|
||||||
import type { Status as StatusEntity } from 'soapbox/types/entities';
|
import type { Status as StatusEntity } from 'soapbox/types/entities';
|
||||||
|
|
||||||
const getStatus = makeGetStatus();
|
|
||||||
|
|
||||||
type RouteParams = { statusId: string };
|
type RouteParams = { statusId: string };
|
||||||
|
|
||||||
interface IEventInformation {
|
interface IEventInformation {
|
||||||
|
@ -20,6 +20,8 @@ interface IEventInformation {
|
||||||
|
|
||||||
const EventInformation: React.FC<IEventInformation> = ({ params }) => {
|
const EventInformation: React.FC<IEventInformation> = ({ params }) => {
|
||||||
const dispatch = useAppDispatch();
|
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 StatusEntity;
|
||||||
|
|
||||||
const settings = useSettings();
|
const settings = useSettings();
|
||||||
|
@ -38,10 +40,96 @@ const EventInformation: React.FC<IEventInformation> = ({ params }) => {
|
||||||
setShowMedia(defaultMediaVisibility(status, displayMedia));
|
setShowMedia(defaultMediaVisibility(status, displayMedia));
|
||||||
}, [params.statusId]);
|
}, [params.statusId]);
|
||||||
|
|
||||||
const handleToggleMediaVisibility = (): void => {
|
const handleToggleMediaVisibility = () => {
|
||||||
setShowMedia(!showMedia);
|
setShowMedia(!showMedia);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const handleShowMap: React.MouseEventHandler<HTMLAnchorElement> = (e) => {
|
||||||
|
e.stopPropagation();
|
||||||
|
|
||||||
|
dispatch(openModal('EVENT_MAP', {
|
||||||
|
statusId: status.id,
|
||||||
|
}));
|
||||||
|
};
|
||||||
|
|
||||||
|
const renderEventLocation = useCallback(() => {
|
||||||
|
const event = status?.event;
|
||||||
|
|
||||||
|
return event?.location && (
|
||||||
|
<Stack space={1}>
|
||||||
|
<Text size='xl' weight='bold'>
|
||||||
|
<FormattedMessage id='event.location' defaultMessage='Location' />
|
||||||
|
</Text>
|
||||||
|
<HStack space={2} alignItems='center'>
|
||||||
|
<Icon src={require('@tabler/icons/map-pin.svg')} />
|
||||||
|
<Text>
|
||||||
|
{event.location.get('name')}
|
||||||
|
<br />
|
||||||
|
{!!event.location.get('street')?.trim() && (<>
|
||||||
|
{event.location.get('street')}
|
||||||
|
<br />
|
||||||
|
</>)}
|
||||||
|
{[event.location.get('postalCode'), event.location.get('locality'), event.location.get('country')].filter(text => text).join(', ')}
|
||||||
|
{event.location.get('latitude') && (<>
|
||||||
|
<br />
|
||||||
|
<a href='#' className='text-primary-600 dark:text-accent-blue hover:underline' onClick={handleShowMap}>
|
||||||
|
<FormattedMessage id='event.show_on_map' defaultMessage='Show on map' />
|
||||||
|
</a>
|
||||||
|
</>)}
|
||||||
|
</Text>
|
||||||
|
</HStack>
|
||||||
|
</Stack>
|
||||||
|
);
|
||||||
|
}, [status]);
|
||||||
|
|
||||||
|
const renderEventDate = useCallback(() => {
|
||||||
|
const event = status?.event;
|
||||||
|
|
||||||
|
if (!event?.start_time) return null;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Stack space={1}>
|
||||||
|
<Text size='xl' weight='bold'>
|
||||||
|
<FormattedMessage id='event.date' defaultMessage='Date' />
|
||||||
|
</Text>
|
||||||
|
<HStack space={2} alignItems='center'>
|
||||||
|
<Icon src={require('@tabler/icons/calendar.svg')} />
|
||||||
|
<Text>
|
||||||
|
<FormattedDate value={event.start_time} year='numeric' month='long' day='2-digit' weekday='long' hour='2-digit' minute='2-digit' />
|
||||||
|
{event.end_time && (<>
|
||||||
|
{' - '}
|
||||||
|
<FormattedDate value={event.end_time} year='numeric' month='long' day='2-digit' weekday='long' hour='2-digit' minute='2-digit' />
|
||||||
|
</>)}
|
||||||
|
</Text>
|
||||||
|
</HStack>
|
||||||
|
</Stack>
|
||||||
|
);
|
||||||
|
}, [status]);
|
||||||
|
|
||||||
|
const renderLinks = useCallback(() => {
|
||||||
|
const links = status?.media_attachments.filter(({ pleroma }) => pleroma.get('mime_type') === 'text/html');
|
||||||
|
|
||||||
|
if (!links?.size) return null;
|
||||||
|
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Stack space={1}>
|
||||||
|
<Text size='xl' weight='bold'>
|
||||||
|
<FormattedMessage id='event.website' defaultMessage='External links' />
|
||||||
|
</Text>
|
||||||
|
|
||||||
|
{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>
|
||||||
|
</HStack>
|
||||||
|
))}
|
||||||
|
</Stack>
|
||||||
|
);
|
||||||
|
}, [status]);
|
||||||
|
|
||||||
if (!status && isLoaded) {
|
if (!status && isLoaded) {
|
||||||
return (
|
return (
|
||||||
<MissingIndicator />
|
<MissingIndicator />
|
||||||
|
@ -50,11 +138,16 @@ const EventInformation: React.FC<IEventInformation> = ({ params }) => {
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Stack className='mt-4 sm:p-2' space={2}>
|
<Stack className='mt-4 sm:p-2' space={2}>
|
||||||
|
<Stack space={1}>
|
||||||
|
<Text size='xl' weight='bold'>
|
||||||
|
<FormattedMessage id='event.description' defaultMessage='Description' />
|
||||||
|
</Text>
|
||||||
<Text
|
<Text
|
||||||
className='break-words status__content'
|
className='break-words status__content'
|
||||||
size='sm'
|
size='sm'
|
||||||
dangerouslySetInnerHTML={{ __html: status.contentHtml }}
|
dangerouslySetInnerHTML={{ __html: status.contentHtml }}
|
||||||
/>
|
/>
|
||||||
|
</Stack>
|
||||||
|
|
||||||
<StatusMedia
|
<StatusMedia
|
||||||
status={status}
|
status={status}
|
||||||
|
@ -62,6 +155,12 @@ const EventInformation: React.FC<IEventInformation> = ({ params }) => {
|
||||||
showMedia={showMedia}
|
showMedia={showMedia}
|
||||||
onToggleVisibility={handleToggleMediaVisibility}
|
onToggleVisibility={handleToggleMediaVisibility}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
|
{renderEventLocation()}
|
||||||
|
|
||||||
|
{renderEventDate()}
|
||||||
|
|
||||||
|
{renderLinks()}
|
||||||
</Stack>
|
</Stack>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
|
@ -1,10 +1,10 @@
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { defineMessages, useIntl } from 'react-intl';
|
import { defineMessages, useIntl } from 'react-intl';
|
||||||
|
|
||||||
import IconButton from 'soapbox/components/icon_button';
|
import { Modal } from 'soapbox/components/ui';
|
||||||
|
|
||||||
const messages = defineMessages({
|
const messages = defineMessages({
|
||||||
error: { id: 'bundle_modal_error.message', defaultMessage: 'Something went wrong while loading this page.' },
|
error: { id: 'bundle_modal_error.message', defaultMessage: 'Something went wrong while loading this modal.' },
|
||||||
retry: { id: 'bundle_modal_error.retry', defaultMessage: 'Try again' },
|
retry: { id: 'bundle_modal_error.retry', defaultMessage: 'Try again' },
|
||||||
close: { id: 'bundle_modal_error.close', defaultMessage: 'Close' },
|
close: { id: 'bundle_modal_error.close', defaultMessage: 'Close' },
|
||||||
});
|
});
|
||||||
|
@ -22,23 +22,13 @@ const BundleModalError: React.FC<IBundleModalError> = ({ onRetry, onClose }) =>
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className='modal-root__modal error-modal'>
|
<Modal
|
||||||
<div className='error-modal__body'>
|
title={intl.formatMessage(messages.error)}
|
||||||
<IconButton title={intl.formatMessage(messages.retry)} icon='refresh' onClick={handleRetry} size={64} />
|
confirmationAction={onClose}
|
||||||
{intl.formatMessage(messages.error)}
|
confirmationText={intl.formatMessage(messages.close)}
|
||||||
</div>
|
secondaryAction={handleRetry}
|
||||||
|
secondaryText={intl.formatMessage(messages.retry)}
|
||||||
<div className='error-modal__footer'>
|
/>
|
||||||
<div>
|
|
||||||
<button
|
|
||||||
onClick={onClose}
|
|
||||||
className='error-modal__nav onboarding-modal__skip'
|
|
||||||
>
|
|
||||||
{intl.formatMessage(messages.close)}
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -7,7 +7,7 @@ import { Button } from 'soapbox/components/ui';
|
||||||
|
|
||||||
const ComposeButton = () => {
|
const ComposeButton = () => {
|
||||||
const dispatch = useDispatch();
|
const dispatch = useDispatch();
|
||||||
const onOpenCompose = () => dispatch(openModal('COMPOSE'));
|
const onOpenCompose = () => dispatch(openModal('CREATE_EVENT'));
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className='mt-4'>
|
<div className='mt-4'>
|
||||||
|
|
|
@ -35,6 +35,8 @@ import {
|
||||||
CreateEventModal,
|
CreateEventModal,
|
||||||
JoinEventModal,
|
JoinEventModal,
|
||||||
AccountModerationModal,
|
AccountModerationModal,
|
||||||
|
EventMapModal,
|
||||||
|
EventParticipantsModal,
|
||||||
} from 'soapbox/features/ui/util/async-components';
|
} from 'soapbox/features/ui/util/async-components';
|
||||||
|
|
||||||
import BundleContainer from '../containers/bundle_container';
|
import BundleContainer from '../containers/bundle_container';
|
||||||
|
@ -75,6 +77,8 @@ const MODAL_COMPONENTS = {
|
||||||
'CREATE_EVENT': CreateEventModal,
|
'CREATE_EVENT': CreateEventModal,
|
||||||
'JOIN_EVENT': JoinEventModal,
|
'JOIN_EVENT': JoinEventModal,
|
||||||
'ACCOUNT_MODERATION': AccountModerationModal,
|
'ACCOUNT_MODERATION': AccountModerationModal,
|
||||||
|
'EVENT_MAP': EventMapModal,
|
||||||
|
'EVENT_PARTICIPANTS': EventParticipantsModal,
|
||||||
};
|
};
|
||||||
|
|
||||||
export default class ModalRoot extends React.PureComponent {
|
export default class ModalRoot extends React.PureComponent {
|
||||||
|
|
|
@ -0,0 +1,79 @@
|
||||||
|
import L from 'leaflet';
|
||||||
|
import React, { useCallback, useEffect, useRef } from 'react';
|
||||||
|
import { defineMessages, FormattedMessage, useIntl } from 'react-intl';
|
||||||
|
|
||||||
|
import { Button, Modal, Stack } from 'soapbox/components/ui';
|
||||||
|
import { useAppSelector } from 'soapbox/hooks';
|
||||||
|
import { makeGetStatus } from 'soapbox/selectors';
|
||||||
|
|
||||||
|
import type { Status as StatusEntity } from 'soapbox/types/entities';
|
||||||
|
|
||||||
|
import 'leaflet/dist/leaflet.css';
|
||||||
|
|
||||||
|
L.Icon.Default.mergeOptions({
|
||||||
|
iconRetinaUrl: require('leaflet/dist/images/marker-icon-2x.png'),
|
||||||
|
iconUrl: require('leaflet/dist/images/marker-icon.png'),
|
||||||
|
shadowUrl: require('leaflet/dist/images/marker-shadow.png'),
|
||||||
|
});
|
||||||
|
|
||||||
|
interface IEventMapModal {
|
||||||
|
onClose: (type: string) => void,
|
||||||
|
statusId: string,
|
||||||
|
}
|
||||||
|
|
||||||
|
const messages = defineMessages({
|
||||||
|
osmAttribution: { id: 'event_map.osm_attribution', defaultMessage: '© OpenStreetMap Contributors' },
|
||||||
|
});
|
||||||
|
|
||||||
|
const EventMapModal: React.FC<IEventMapModal> = ({ onClose, statusId }) => {
|
||||||
|
const intl = useIntl();
|
||||||
|
|
||||||
|
const getStatus = useCallback(makeGetStatus(), []);
|
||||||
|
const status = useAppSelector(state => getStatus(state, { id: statusId })) as StatusEntity;
|
||||||
|
const location = status.event!.location!;
|
||||||
|
|
||||||
|
const map = useRef<L.Map>();
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const latlng: [number, number] = [+location.get('latitude'), +location.get('longitude')];
|
||||||
|
|
||||||
|
map.current = L.map('event-map').setView(latlng, 15);
|
||||||
|
|
||||||
|
L.marker(latlng, {
|
||||||
|
title: location.get('name'),
|
||||||
|
}).addTo(map.current);
|
||||||
|
|
||||||
|
L.tileLayer('https://tile.openstreetmap.org/{z}/{x}/{y}.png', {
|
||||||
|
attribution: intl.formatMessage(messages.osmAttribution),
|
||||||
|
}).addTo(map.current);
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
map.current?.remove();
|
||||||
|
};
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const onClickClose = () => {
|
||||||
|
onClose('EVENT_MAP');
|
||||||
|
};
|
||||||
|
|
||||||
|
const onClickNavigate = () => {
|
||||||
|
window.open(`https://www.openstreetmap.org/directions?from=&to=${location.get('latitude')},${location.get('longitude')}#map=14/${location.get('latitude')}/${location.get('longitude')}`, '_blank');
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Modal
|
||||||
|
title={<FormattedMessage id='column.event_map' defaultMessage='Event location' />}
|
||||||
|
onClose={onClickClose}
|
||||||
|
width='2xl'
|
||||||
|
>
|
||||||
|
<Stack alignItems='center' space={6}>
|
||||||
|
<div className='h-96 w-full' id='event-map' />
|
||||||
|
<Button onClick={onClickNavigate} icon={require('@tabler/icons/gps.svg')}>
|
||||||
|
<FormattedMessage id='event_map.navigate' defaultMessage='Navigate' />
|
||||||
|
</Button>
|
||||||
|
</Stack>
|
||||||
|
</Modal>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default EventMapModal;
|
|
@ -0,0 +1,59 @@
|
||||||
|
import React, { useEffect } from 'react';
|
||||||
|
import { FormattedMessage } from 'react-intl';
|
||||||
|
|
||||||
|
import { fetchEventParticipations } from 'soapbox/actions/events';
|
||||||
|
import { Modal, Spinner, Stack } from 'soapbox/components/ui';
|
||||||
|
import AccountContainer from 'soapbox/containers/account_container';
|
||||||
|
import { useAppDispatch, useAppSelector } from 'soapbox/hooks';
|
||||||
|
|
||||||
|
interface IEventParticipantsModal {
|
||||||
|
onClose: (type: string) => void,
|
||||||
|
statusId: string,
|
||||||
|
}
|
||||||
|
|
||||||
|
const EventParticipantsModal: React.FC<IEventParticipantsModal> = ({ onClose, statusId }) => {
|
||||||
|
const dispatch = useAppDispatch();
|
||||||
|
|
||||||
|
const accountIds = useAppSelector((state) => state.user_lists.event_participations.get(statusId)?.items);
|
||||||
|
|
||||||
|
const fetchData = () => {
|
||||||
|
dispatch(fetchEventParticipations(statusId));
|
||||||
|
};
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
fetchData();
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const onClickClose = () => {
|
||||||
|
onClose('EVENT_PARTICIPANTS');
|
||||||
|
};
|
||||||
|
|
||||||
|
let body;
|
||||||
|
|
||||||
|
if (!accountIds) {
|
||||||
|
body = <Spinner />;
|
||||||
|
} else {
|
||||||
|
body = (
|
||||||
|
<Stack space={3}>
|
||||||
|
{accountIds.size > 0 ? (
|
||||||
|
accountIds.map((id) =>
|
||||||
|
<AccountContainer key={id} id={id} />,
|
||||||
|
)
|
||||||
|
) : (
|
||||||
|
<FormattedMessage id='empty_column.event_participants' defaultMessage='No one joined this event yet. When someone does, they will show up here.' />
|
||||||
|
)}
|
||||||
|
</Stack>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Modal
|
||||||
|
title={<FormattedMessage id='column.event_participants' defaultMessage='Event participants' />}
|
||||||
|
onClose={onClickClose}
|
||||||
|
>
|
||||||
|
{body}
|
||||||
|
</Modal>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default EventParticipantsModal;
|
|
@ -15,7 +15,7 @@ const messages = defineMessages({
|
||||||
|
|
||||||
interface IUnauthorizedModal {
|
interface IUnauthorizedModal {
|
||||||
/** Unauthorized action type. */
|
/** Unauthorized action type. */
|
||||||
action: 'FOLLOW' | 'REPLY' | 'REBLOG' | 'FAVOURITE' | 'POLL_VOTE',
|
action: 'FOLLOW' | 'REPLY' | 'REBLOG' | 'FAVOURITE' | 'POLL_VOTE' | 'JOIN',
|
||||||
/** Close event handler. */
|
/** Close event handler. */
|
||||||
onClose: (modalType: string) => void,
|
onClose: (modalType: string) => void,
|
||||||
/** ActivityPub ID of the account OR status being acted upon. */
|
/** ActivityPub ID of the account OR status being acted upon. */
|
||||||
|
@ -89,6 +89,9 @@ const UnauthorizedModal: React.FC<IUnauthorizedModal> = ({ action, onClose, acco
|
||||||
} else if (action === 'POLL_VOTE') {
|
} else if (action === 'POLL_VOTE') {
|
||||||
header = <FormattedMessage id='remote_interaction.poll_vote_title' defaultMessage='Vote in a poll remotely' />;
|
header = <FormattedMessage id='remote_interaction.poll_vote_title' defaultMessage='Vote in a poll remotely' />;
|
||||||
button = <FormattedMessage id='remote_interaction.poll_vote' defaultMessage='Proceed to vote' />;
|
button = <FormattedMessage id='remote_interaction.poll_vote' defaultMessage='Proceed to vote' />;
|
||||||
|
} else if (action === 'JOIN') {
|
||||||
|
header = <FormattedMessage id='remote_interaction.event_join_title' defaultMessage='Join an event remotely' />;
|
||||||
|
button = <FormattedMessage id='remote_interaction.event_join' defaultMessage='Proceed to join' />;
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|
|
@ -525,3 +525,11 @@ export function EventInformation() {
|
||||||
export function EventDiscussion() {
|
export function EventDiscussion() {
|
||||||
return import(/* webpackChunkName: "features/event" */'../../event/event-discussion');
|
return import(/* webpackChunkName: "features/event" */'../../event/event-discussion');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function EventMapModal() {
|
||||||
|
return import(/* webpackChunkName: "modals/event-map-modal" */'../components/modals/event-map-modal');
|
||||||
|
}
|
||||||
|
|
||||||
|
export function EventParticipantsModal() {
|
||||||
|
return import(/* webpackChunkName: "modals/event-participants-modal" */'../components/modals/event-participants-modal');
|
||||||
|
}
|
||||||
|
|
|
@ -161,7 +161,7 @@
|
||||||
"bundle_column_error.retry": "Try again",
|
"bundle_column_error.retry": "Try again",
|
||||||
"bundle_column_error.title": "Network error",
|
"bundle_column_error.title": "Network error",
|
||||||
"bundle_modal_error.close": "Close",
|
"bundle_modal_error.close": "Close",
|
||||||
"bundle_modal_error.message": "Something went wrong while loading this page.",
|
"bundle_modal_error.message": "Something went wrong while loading this modal.",
|
||||||
"bundle_modal_error.retry": "Try again",
|
"bundle_modal_error.retry": "Try again",
|
||||||
"card.back.label": "Back",
|
"card.back.label": "Back",
|
||||||
"chat_box.actions.send": "Send",
|
"chat_box.actions.send": "Send",
|
||||||
|
|
|
@ -161,7 +161,7 @@
|
||||||
"bundle_column_error.retry": "Опитай отново",
|
"bundle_column_error.retry": "Опитай отново",
|
||||||
"bundle_column_error.title": "Network error",
|
"bundle_column_error.title": "Network error",
|
||||||
"bundle_modal_error.close": "Close",
|
"bundle_modal_error.close": "Close",
|
||||||
"bundle_modal_error.message": "Something went wrong while loading this page.",
|
"bundle_modal_error.message": "Something went wrong while loading this modal.",
|
||||||
"bundle_modal_error.retry": "Try again",
|
"bundle_modal_error.retry": "Try again",
|
||||||
"card.back.label": "Back",
|
"card.back.label": "Back",
|
||||||
"chat_box.actions.send": "Send",
|
"chat_box.actions.send": "Send",
|
||||||
|
|
|
@ -161,7 +161,7 @@
|
||||||
"bundle_column_error.retry": "Klask endro",
|
"bundle_column_error.retry": "Klask endro",
|
||||||
"bundle_column_error.title": "Fazi rouedad",
|
"bundle_column_error.title": "Fazi rouedad",
|
||||||
"bundle_modal_error.close": "Serriñ",
|
"bundle_modal_error.close": "Serriñ",
|
||||||
"bundle_modal_error.message": "Something went wrong while loading this page.",
|
"bundle_modal_error.message": "Something went wrong while loading this modal.",
|
||||||
"bundle_modal_error.retry": "Klask endro",
|
"bundle_modal_error.retry": "Klask endro",
|
||||||
"card.back.label": "Back",
|
"card.back.label": "Back",
|
||||||
"chat_box.actions.send": "Send",
|
"chat_box.actions.send": "Send",
|
||||||
|
|
|
@ -161,7 +161,7 @@
|
||||||
"bundle_column_error.retry": "Try again",
|
"bundle_column_error.retry": "Try again",
|
||||||
"bundle_column_error.title": "Network error",
|
"bundle_column_error.title": "Network error",
|
||||||
"bundle_modal_error.close": "Close",
|
"bundle_modal_error.close": "Close",
|
||||||
"bundle_modal_error.message": "Something went wrong while loading this page.",
|
"bundle_modal_error.message": "Something went wrong while loading this modal.",
|
||||||
"bundle_modal_error.retry": "Try again",
|
"bundle_modal_error.retry": "Try again",
|
||||||
"card.back.label": "Back",
|
"card.back.label": "Back",
|
||||||
"chat_box.actions.send": "Send",
|
"chat_box.actions.send": "Send",
|
||||||
|
|
|
@ -161,7 +161,7 @@
|
||||||
"bundle_column_error.retry": "Try again",
|
"bundle_column_error.retry": "Try again",
|
||||||
"bundle_column_error.title": "Network error",
|
"bundle_column_error.title": "Network error",
|
||||||
"bundle_modal_error.close": "Close",
|
"bundle_modal_error.close": "Close",
|
||||||
"bundle_modal_error.message": "Something went wrong while loading this page.",
|
"bundle_modal_error.message": "Something went wrong while loading this modal.",
|
||||||
"bundle_modal_error.retry": "Try again",
|
"bundle_modal_error.retry": "Try again",
|
||||||
"card.back.label": "Back",
|
"card.back.label": "Back",
|
||||||
"chat_box.actions.send": "Send",
|
"chat_box.actions.send": "Send",
|
||||||
|
|
|
@ -161,7 +161,7 @@
|
||||||
"bundle_column_error.retry": "Try again",
|
"bundle_column_error.retry": "Try again",
|
||||||
"bundle_column_error.title": "Network error",
|
"bundle_column_error.title": "Network error",
|
||||||
"bundle_modal_error.close": "Close",
|
"bundle_modal_error.close": "Close",
|
||||||
"bundle_modal_error.message": "Something went wrong while loading this page.",
|
"bundle_modal_error.message": "Something went wrong while loading this modal.",
|
||||||
"bundle_modal_error.retry": "Try again",
|
"bundle_modal_error.retry": "Try again",
|
||||||
"card.back.label": "Back",
|
"card.back.label": "Back",
|
||||||
"chat_box.actions.send": "Send",
|
"chat_box.actions.send": "Send",
|
||||||
|
|
|
@ -161,7 +161,7 @@
|
||||||
"bundle_column_error.retry": "Try again",
|
"bundle_column_error.retry": "Try again",
|
||||||
"bundle_column_error.title": "Network error",
|
"bundle_column_error.title": "Network error",
|
||||||
"bundle_modal_error.close": "Close",
|
"bundle_modal_error.close": "Close",
|
||||||
"bundle_modal_error.message": "Something went wrong while loading this page.",
|
"bundle_modal_error.message": "Something went wrong while loading this modal.",
|
||||||
"bundle_modal_error.retry": "Try again",
|
"bundle_modal_error.retry": "Try again",
|
||||||
"card.back.label": "Back",
|
"card.back.label": "Back",
|
||||||
"chat_box.actions.send": "Send",
|
"chat_box.actions.send": "Send",
|
||||||
|
|
|
@ -161,7 +161,7 @@
|
||||||
"bundle_column_error.retry": "Try again",
|
"bundle_column_error.retry": "Try again",
|
||||||
"bundle_column_error.title": "Network error",
|
"bundle_column_error.title": "Network error",
|
||||||
"bundle_modal_error.close": "Close",
|
"bundle_modal_error.close": "Close",
|
||||||
"bundle_modal_error.message": "Something went wrong while loading this page.",
|
"bundle_modal_error.message": "Something went wrong while loading this modal.",
|
||||||
"bundle_modal_error.retry": "Try again",
|
"bundle_modal_error.retry": "Try again",
|
||||||
"card.back.label": "Back",
|
"card.back.label": "Back",
|
||||||
"chat_box.actions.send": "Send",
|
"chat_box.actions.send": "Send",
|
||||||
|
|
|
@ -161,7 +161,7 @@
|
||||||
"bundle_column_error.retry": "Try again",
|
"bundle_column_error.retry": "Try again",
|
||||||
"bundle_column_error.title": "Network error",
|
"bundle_column_error.title": "Network error",
|
||||||
"bundle_modal_error.close": "Close",
|
"bundle_modal_error.close": "Close",
|
||||||
"bundle_modal_error.message": "Something went wrong while loading this page.",
|
"bundle_modal_error.message": "Something went wrong while loading this modal.",
|
||||||
"bundle_modal_error.retry": "Try again",
|
"bundle_modal_error.retry": "Try again",
|
||||||
"card.back.label": "Back",
|
"card.back.label": "Back",
|
||||||
"chat_box.actions.send": "Send",
|
"chat_box.actions.send": "Send",
|
||||||
|
|
|
@ -161,7 +161,7 @@
|
||||||
"bundle_column_error.retry": "Try again",
|
"bundle_column_error.retry": "Try again",
|
||||||
"bundle_column_error.title": "Network error",
|
"bundle_column_error.title": "Network error",
|
||||||
"bundle_modal_error.close": "Close",
|
"bundle_modal_error.close": "Close",
|
||||||
"bundle_modal_error.message": "Something went wrong while loading this page.",
|
"bundle_modal_error.message": "Something went wrong while loading this modal.",
|
||||||
"bundle_modal_error.retry": "Try again",
|
"bundle_modal_error.retry": "Try again",
|
||||||
"card.back.label": "Back",
|
"card.back.label": "Back",
|
||||||
"chat_box.actions.send": "Send",
|
"chat_box.actions.send": "Send",
|
||||||
|
|
|
@ -261,12 +261,12 @@ export default function statuses(state = initialState, action: AnyAction): State
|
||||||
case TIMELINE_DELETE:
|
case TIMELINE_DELETE:
|
||||||
return deleteStatus(state, action.id, action.references);
|
return deleteStatus(state, action.id, action.references);
|
||||||
case EVENT_JOIN_REQUEST:
|
case EVENT_JOIN_REQUEST:
|
||||||
return state.setIn([action.status.get('id'), 'event', 'join_state'], 'pending');
|
return state.setIn([action.id, 'event', 'join_state'], 'pending');
|
||||||
case EVENT_JOIN_FAIL:
|
case EVENT_JOIN_FAIL:
|
||||||
case EVENT_LEAVE_REQUEST:
|
case EVENT_LEAVE_REQUEST:
|
||||||
return state.setIn([action.status.get('id'), 'event', 'join_state'], null);
|
return state.setIn([action.id, 'event', 'join_state'], null);
|
||||||
case EVENT_LEAVE_FAIL:
|
case EVENT_LEAVE_FAIL:
|
||||||
return state.setIn([action.status.get('id'), 'event', 'join_state'], action.previousState);
|
return state.setIn([action.id, 'event', 'join_state'], action.previousState);
|
||||||
default:
|
default:
|
||||||
return state;
|
return state;
|
||||||
}
|
}
|
||||||
|
|
|
@ -75,6 +75,7 @@
|
||||||
"@types/escape-html": "^1.0.1",
|
"@types/escape-html": "^1.0.1",
|
||||||
"@types/http-link-header": "^1.0.3",
|
"@types/http-link-header": "^1.0.3",
|
||||||
"@types/jest": "^28.1.4",
|
"@types/jest": "^28.1.4",
|
||||||
|
"@types/leaflet": "^1.8.0",
|
||||||
"@types/lodash": "^4.14.180",
|
"@types/lodash": "^4.14.180",
|
||||||
"@types/object-assign": "^4.0.30",
|
"@types/object-assign": "^4.0.30",
|
||||||
"@types/object-fit-images": "^3.2.3",
|
"@types/object-fit-images": "^3.2.3",
|
||||||
|
@ -135,6 +136,7 @@
|
||||||
"intl-pluralrules": "^1.3.1",
|
"intl-pluralrules": "^1.3.1",
|
||||||
"is-nan": "^1.2.1",
|
"is-nan": "^1.2.1",
|
||||||
"jsdoc": "~3.6.7",
|
"jsdoc": "~3.6.7",
|
||||||
|
"leaflet": "^1.8.0",
|
||||||
"libphonenumber-js": "^1.10.8",
|
"libphonenumber-js": "^1.10.8",
|
||||||
"line-awesome": "^1.3.0",
|
"line-awesome": "^1.3.0",
|
||||||
"localforage": "^1.10.0",
|
"localforage": "^1.10.0",
|
||||||
|
|
|
@ -11,6 +11,7 @@ module.exports = [{
|
||||||
include: [
|
include: [
|
||||||
resolve('app', 'images'),
|
resolve('app', 'images'),
|
||||||
resolve('node_modules', 'emoji-datasource'),
|
resolve('node_modules', 'emoji-datasource'),
|
||||||
|
resolve('node_modules', 'leaflet'),
|
||||||
],
|
],
|
||||||
generator: {
|
generator: {
|
||||||
filename: 'packs/images/[name]-[contenthash:8][ext]',
|
filename: 'packs/images/[name]-[contenthash:8][ext]',
|
||||||
|
|
17
yarn.lock
17
yarn.lock
|
@ -2527,6 +2527,11 @@
|
||||||
dependencies:
|
dependencies:
|
||||||
"@types/node" "*"
|
"@types/node" "*"
|
||||||
|
|
||||||
|
"@types/geojson@*":
|
||||||
|
version "7946.0.10"
|
||||||
|
resolved "https://registry.yarnpkg.com/@types/geojson/-/geojson-7946.0.10.tgz#6dfbf5ea17142f7f9a043809f1cd4c448cb68249"
|
||||||
|
integrity sha512-Nmh0K3iWQJzniTuPRcJn5hxXkfB1T1pgB89SBig5PlJQU5yocazeu4jATJlaA0GYFKWMqDdvYemoSnF2pXgLVA==
|
||||||
|
|
||||||
"@types/graceful-fs@^4.1.3":
|
"@types/graceful-fs@^4.1.3":
|
||||||
version "4.1.5"
|
version "4.1.5"
|
||||||
resolved "https://registry.yarnpkg.com/@types/graceful-fs/-/graceful-fs-4.1.5.tgz#21ffba0d98da4350db64891f92a9e5db3cdb4e15"
|
resolved "https://registry.yarnpkg.com/@types/graceful-fs/-/graceful-fs-4.1.5.tgz#21ffba0d98da4350db64891f92a9e5db3cdb4e15"
|
||||||
|
@ -2635,6 +2640,13 @@
|
||||||
resolved "https://registry.yarnpkg.com/@types/json5/-/json5-0.0.29.tgz#ee28707ae94e11d2b827bcbe5270bcea7f3e71ee"
|
resolved "https://registry.yarnpkg.com/@types/json5/-/json5-0.0.29.tgz#ee28707ae94e11d2b827bcbe5270bcea7f3e71ee"
|
||||||
integrity sha1-7ihweulOEdK4J7y+UnC86n8+ce4=
|
integrity sha1-7ihweulOEdK4J7y+UnC86n8+ce4=
|
||||||
|
|
||||||
|
"@types/leaflet@^1.8.0":
|
||||||
|
version "1.8.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/@types/leaflet/-/leaflet-1.8.0.tgz#dc92d3e868fb6d5067b4b59fa08cd4441f84fabe"
|
||||||
|
integrity sha512-+sXFmiJTFdhaXXIGFlV5re9AdqtAODoXbGAvxx02e5SHXL3ir7ClP5J7pahO8VmzKY3dth4RUS1nf2BTT+DW1A==
|
||||||
|
dependencies:
|
||||||
|
"@types/geojson" "*"
|
||||||
|
|
||||||
"@types/lodash@^4.14.180":
|
"@types/lodash@^4.14.180":
|
||||||
version "4.14.180"
|
version "4.14.180"
|
||||||
resolved "https://registry.yarnpkg.com/@types/lodash/-/lodash-4.14.180.tgz#4ab7c9ddfc92ec4a887886483bc14c79fb380670"
|
resolved "https://registry.yarnpkg.com/@types/lodash/-/lodash-4.14.180.tgz#4ab7c9ddfc92ec4a887886483bc14c79fb380670"
|
||||||
|
@ -7823,6 +7835,11 @@ language-tags@^1.0.5:
|
||||||
dependencies:
|
dependencies:
|
||||||
language-subtag-registry "~0.3.2"
|
language-subtag-registry "~0.3.2"
|
||||||
|
|
||||||
|
leaflet@^1.8.0:
|
||||||
|
version "1.8.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/leaflet/-/leaflet-1.8.0.tgz#4615db4a22a304e8e692cae9270b983b38a2055e"
|
||||||
|
integrity sha512-gwhMjFCQiYs3x/Sf+d49f10ERXaEFCPr+nVTryhAW8DWbMGqJqt9G4XuIaHmFW08zYvhgdzqXGr8AlW8v8dQkA==
|
||||||
|
|
||||||
leven@^3.1.0:
|
leven@^3.1.0:
|
||||||
version "3.1.0"
|
version "3.1.0"
|
||||||
resolved "https://registry.yarnpkg.com/leven/-/leven-3.1.0.tgz#77891de834064cccba82ae7842bb6b14a13ed7f2"
|
resolved "https://registry.yarnpkg.com/leven/-/leven-3.1.0.tgz#77891de834064cccba82ae7842bb6b14a13ed7f2"
|
||||||
|
|
Loading…
Reference in a new issue