Merge branch 'scheduled-fixes' into 'develop'

Scheduled statuses: TypeScript, fix styles

Closes #977

See merge request soapbox-pub/soapbox-fe!1442
This commit is contained in:
marcin mikołajczak 2022-05-24 15:14:59 +00:00
commit 788f6111a4
9 changed files with 183 additions and 3 deletions

View file

@ -56,6 +56,7 @@ interface IAccount {
showProfileHoverCard?: boolean,
timestamp?: string | Date,
timestampUrl?: string,
futureTimestamp?: boolean,
withDate?: boolean,
withRelationship?: boolean,
showEdit?: boolean,
@ -75,6 +76,7 @@ const Account = ({
showProfileHoverCard = true,
timestamp,
timestampUrl,
futureTimestamp = false,
withDate = false,
withRelationship = true,
showEdit = false,
@ -205,10 +207,10 @@ const Account = ({
{timestampUrl ? (
<Link to={timestampUrl} className='hover:underline'>
<RelativeTimestamp timestamp={timestamp} theme='muted' size='sm' className='whitespace-nowrap' />
<RelativeTimestamp timestamp={timestamp} theme='muted' size='sm' className='whitespace-nowrap' futureDate={futureTimestamp} />
</Link>
) : (
<RelativeTimestamp timestamp={timestamp} theme='muted' size='sm' className='whitespace-nowrap' />
<RelativeTimestamp timestamp={timestamp} theme='muted' size='sm' className='whitespace-nowrap' futureDate={futureTimestamp} />
)}
</>
) : null}

View file

@ -4,6 +4,8 @@ import React from 'react';
const justifyContentOptions = {
between: 'justify-between',
center: 'justify-center',
start: 'justify-start',
end: 'justify-end',
};
const alignItemsOptions = {
@ -29,7 +31,7 @@ interface IHStack {
/** Extra class names on the <div> element. */
className?: string,
/** Horizontal alignment of children. */
justifyContent?: 'between' | 'center',
justifyContent?: 'between' | 'center' | 'start' | 'end',
/** Size of the gap between elements. */
space?: 0.5 | 1 | 1.5 | 2 | 3 | 4 | 6,
/** Whether to let the flexbox grow. */

View file

@ -0,0 +1,66 @@
import classNames from 'classnames';
import React from 'react';
import AttachmentThumbs from 'soapbox/components/attachment-thumbs';
import StatusContent from 'soapbox/components/status_content';
import StatusReplyMentions from 'soapbox/components/status_reply_mentions';
import { HStack } from 'soapbox/components/ui';
import AccountContainer from 'soapbox/containers/account_container';
import PollPreview from 'soapbox/features/ui/components/poll_preview';
import { useAppSelector } from 'soapbox/hooks';
import { buildStatus } from '../builder';
import ScheduledStatusActionBar from './scheduled_status_action_bar';
import type { Account as AccountEntity, Status as StatusEntity } from 'soapbox/types/entities';
interface IScheduledStatus {
statusId: string,
}
const ScheduledStatus: React.FC<IScheduledStatus> = ({ statusId, ...other }) => {
const status = useAppSelector((state) => buildStatus(state, state.scheduled_statuses.get(statusId))) as StatusEntity;
if (!status) return null;
const account = status.account as AccountEntity;
return (
<div className={classNames('status__wrapper', `status__wrapper-${status.visibility}`, { 'status__wrapper-reply': !!status.in_reply_to_id })} tabIndex={0}>
<div className={classNames('status', `status-${status.visibility}`, { 'status-reply': !!status.in_reply_to_id })} data-id={status.id}>
<div className='mb-4'>
<HStack justifyContent='between' alignItems='start'>
<AccountContainer
key={account.id}
id={account.id}
timestamp={status.created_at}
futureTimestamp
/>
</HStack>
</div>
<StatusReplyMentions status={status} />
<StatusContent
status={status}
expanded
collapsable
/>
{status.media_attachments.size > 0 && (
<AttachmentThumbs
media={status.media_attachments}
sensitive={status.sensitive}
/>
)}
{status.poll && <PollPreview poll={status.poll} />}
<ScheduledStatusActionBar status={status} {...other} />
</div>
</div>
);
};
export default ScheduledStatus;

View file

@ -0,0 +1,59 @@
import React from 'react';
import { defineMessages, useIntl } from 'react-intl';
import { openModal } from 'soapbox/actions/modals';
import { cancelScheduledStatus } from 'soapbox/actions/scheduled_statuses';
import { getSettings } from 'soapbox/actions/settings';
import IconButton from 'soapbox/components/icon_button';
import { HStack } from 'soapbox/components/ui';
import { useAppDispatch } from 'soapbox/hooks';
import type { Status as StatusEntity } from 'soapbox/types/entities';
const messages = defineMessages({
cancel: { id: 'scheduled_status.cancel', defaultMessage: 'Cancel' },
deleteConfirm: { id: 'confirmations.scheduled_status_delete.confirm', defaultMessage: 'Cancel' },
deleteHeading: { id: 'confirmations.scheduled_status_delete.heading', defaultMessage: 'Cancel scheduled post' },
deleteMessage: { id: 'confirmations.scheduled_status_delete.message', defaultMessage: 'Are you sure you want to cancel this scheduled post?' },
});
interface IScheduledStatusActionBar {
status: StatusEntity,
}
const ScheduledStatusActionBar: React.FC<IScheduledStatusActionBar> = ({ status }) => {
const intl = useIntl();
const dispatch = useAppDispatch();
const handleCancelClick = () => {
dispatch((_, getState) => {
const deleteModal = getSettings(getState()).get('deleteModal');
if (!deleteModal) {
dispatch(cancelScheduledStatus(status.id));
} else {
dispatch(openModal('CONFIRM', {
icon: require('@tabler/icons/icons/calendar-stats.svg'),
heading: intl.formatMessage(messages.deleteHeading),
message: intl.formatMessage(messages.deleteMessage),
confirm: intl.formatMessage(messages.deleteConfirm),
onConfirm: () => dispatch(cancelScheduledStatus(status.id)),
}));
}
});
};
return (
<HStack justifyContent='end'>
<IconButton
title={intl.formatMessage(messages.cancel)}
text={intl.formatMessage(messages.cancel)}
src={require('@tabler/icons/icons/x.svg')}
onClick={handleCancelClick}
/>
</HStack>
);
};
export default ScheduledStatusActionBar;

View file

@ -0,0 +1,51 @@
import { debounce } from 'lodash';
import React from 'react';
import { useEffect } from 'react';
import { defineMessages, FormattedMessage, useIntl } from 'react-intl';
import { fetchScheduledStatuses, expandScheduledStatuses } from 'soapbox/actions/scheduled_statuses';
import ScrollableList from 'soapbox/components/scrollable_list';
import { useAppSelector, useAppDispatch } from 'soapbox/hooks';
import Column from '../ui/components/column';
import ScheduledStatus from './components/scheduled_status';
const messages = defineMessages({
heading: { id: 'column.scheduled_statuses', defaultMessage: 'Scheduled Posts' },
});
const handleLoadMore = debounce((dispatch) => {
dispatch(expandScheduledStatuses());
}, 300, { leading: true });
const ScheduledStatuses = () => {
const intl = useIntl();
const dispatch = useAppDispatch();
const statusIds = useAppSelector((state) => state.status_lists.getIn(['scheduled_statuses', 'items']));
const isLoading = useAppSelector((state) => state.status_lists.getIn(['scheduled_statuses', 'isLoading']));
const hasMore = useAppSelector((state) => !!state.status_lists.getIn(['scheduled_statuses', 'next']));
useEffect(() => {
dispatch(fetchScheduledStatuses());
}, []);
const emptyMessage = <FormattedMessage id='empty_column.scheduled_statuses' defaultMessage="You don't have any scheduled statuses yet. When you add one, it will show up here." />;
return (
<Column icon='calendar' label={intl.formatMessage(messages.heading)}>
<ScrollableList
scrollKey='scheduled_statuses'
hasMore={hasMore}
isLoading={isLoading}
onLoadMore={() => handleLoadMore(dispatch)}
emptyMessage={emptyMessage}
>
{statusIds.map((id: string) => <ScheduledStatus key={id} statusId={id} />)}
</ScrollableList>
</Column>
);
};
export default ScheduledStatuses;