Add ability to reorder uploaded media before posting in web UI

Based on Gargron's changes in Mastodon: https://github.com/mastodon/mastodon/pull/28456

Signed-off-by: marcin mikołajczak <git@mkljczk.pl>
environments/review-feature-me-qhzrkd/deployments/4531
marcin mikołajczak 5 months ago
parent 35620a7330
commit 3a27967846

@ -89,6 +89,8 @@ const COMPOSE_SET_STATUS = 'COMPOSE_SET_STATUS' as const;
const COMPOSE_EDITOR_STATE_SET = 'COMPOSE_EDITOR_STATE_SET' as const;
const COMPOSE_CHANGE_MEDIA_ORDER = 'COMPOSE_CHANGE_MEDIA_ORDER' as const;
const messages = defineMessages({
scheduleError: { id: 'compose.invalid_schedule', defaultMessage: 'You must schedule a post at least 5 minutes out.' },
success: { id: 'compose.submit_success', defaultMessage: 'Your post was sent!' },
@ -851,6 +853,13 @@ const setEditorState = (composeId: string, editorState: EditorState | string | n
editorState: editorState,
});
const changeMediaOrder = (composeId: string, a: string, b: string) => ({
type: COMPOSE_CHANGE_MEDIA_ORDER,
id: composeId,
a,
b,
});
type ComposeAction =
ComposeSetStatusAction
| ReturnType<typeof changeCompose>
@ -897,6 +906,7 @@ type ComposeAction =
| ComposeRemoveFromMentionsAction
| ComposeEventReplyAction
| ReturnType<typeof setEditorState>
| ReturnType<typeof changeMediaOrder>
export {
COMPOSE_CHANGE,
@ -945,6 +955,7 @@ export {
COMPOSE_SET_STATUS,
COMPOSE_EDITOR_STATE_SET,
COMPOSE_SET_GROUP_TIMELINE_VISIBLE,
COMPOSE_CHANGE_MEDIA_ORDER,
setComposeToStatus,
changeCompose,
replyCompose,
@ -1000,5 +1011,6 @@ export {
removeFromMentions,
eventDiscussionCompose,
setEditorState,
changeMediaOrder,
type ComposeAction,
};

@ -61,7 +61,7 @@ const messages = defineMessages({
descriptionMissingTitle: { id: 'upload_form.description_missing.title', defaultMessage: 'This attachment doesn\'t have a description' },
});
interface IUpload {
interface IUpload extends Pick<React.HTMLAttributes<HTMLDivElement>, 'onDragStart' | 'onDragEnter' | 'onDragEnd'> {
media: Attachment;
onSubmit?(): void;
onDelete?(): void;
@ -75,6 +75,9 @@ const Upload: React.FC<IUpload> = ({
onSubmit,
onDelete,
onDescriptionChange,
onDragStart,
onDragEnter,
onDragEnd,
descriptionLimit,
withPreview = true,
}) => {
@ -151,7 +154,18 @@ const Upload: React.FC<IUpload> = ({
);
return (
<div className='compose-form__upload' tabIndex={0} onMouseEnter={handleMouseEnter} onMouseLeave={handleMouseLeave} onClick={handleClick} role='button'>
<div
className='compose-form__upload'
tabIndex={0}
onMouseEnter={handleMouseEnter}
onMouseLeave={handleMouseLeave}
onClick={handleClick}
role='button'
draggable
onDragStart={onDragStart}
onDragEnter={onDragEnter}
onDragEnd={onDragEnd}
>
<Blurhash hash={media.blurhash} className='media-gallery__preview' />
<Motion defaultStyle={{ scale: 0.8 }} style={{ scale: spring(1, { stiffness: 180, damping: 12 }) }}>
{({ scale }) => (

@ -1,8 +1,9 @@
import clsx from 'clsx';
import React from 'react';
import React, { useCallback, useRef } from 'react';
import { changeMediaOrder } from 'soapbox/actions/compose';
import { HStack } from 'soapbox/components/ui';
import { useCompose } from 'soapbox/hooks';
import { useAppDispatch, useCompose } from 'soapbox/hooks';
import Upload from './upload';
import UploadProgress from './upload-progress';
@ -15,15 +16,42 @@ interface IUploadForm {
}
const UploadForm: React.FC<IUploadForm> = ({ composeId, onSubmit }) => {
const dispatch = useAppDispatch();
const mediaIds = useCompose(composeId).media_attachments.map((item: AttachmentEntity) => item.id);
const dragItem = useRef<string | null>();
const dragOverItem = useRef<string | null>();
const handleDragStart = useCallback((id: string) => {
dragItem.current = id;
}, [dragItem]);
const handleDragEnter = useCallback((id: string) => {
dragOverItem.current = id;
}, [dragOverItem]);
const handleDragEnd = useCallback(() => {
dispatch(changeMediaOrder(composeId, dragItem.current!, dragOverItem.current!));
dragItem.current = null;
dragOverItem.current = null;
}, [dragItem, dragOverItem]);
return (
<div className='overflow-hidden'>
<UploadProgress composeId={composeId} />
<HStack wrap className={clsx('overflow-hidden', mediaIds.size !== 0 && 'p-1')}>
{mediaIds.map((id: string) => (
<Upload id={id} key={id} composeId={composeId} onSubmit={onSubmit} />
<Upload
id={id}
key={id}
composeId={composeId}
onSubmit={onSubmit}
onDragStart={handleDragStart}
onDragEnter={handleDragEnter}
onDragEnd={handleDragEnd}
/>
))}
</HStack>
</div>

@ -1,4 +1,4 @@
import React from 'react';
import React, { useCallback } from 'react';
import { undoUploadCompose, changeUploadCompose } from 'soapbox/actions/compose';
import Upload from 'soapbox/components/upload';
@ -8,9 +8,12 @@ interface IUploadCompose {
id: string;
composeId: string;
onSubmit?(): void;
onDragStart: (id: string) => void;
onDragEnter: (id: string) => void;
onDragEnd: () => void;
}
const UploadCompose: React.FC<IUploadCompose> = ({ composeId, id, onSubmit }) => {
const UploadCompose: React.FC<IUploadCompose> = ({ composeId, id, onSubmit, onDragStart, onDragEnter, onDragEnd }) => {
const dispatch = useAppDispatch();
const { pleroma: { metadata: { description_limit: descriptionLimit } } } = useInstance();
@ -24,12 +27,23 @@ const UploadCompose: React.FC<IUploadCompose> = ({ composeId, id, onSubmit }) =>
dispatch(undoUploadCompose(composeId, media.id));
};
const handleDragStart = useCallback(() => {
onDragStart(id);
}, [onDragStart, id]);
const handleDragEnter = useCallback(() => {
onDragEnter(id);
}, [onDragEnter, id]);
return (
<Upload
media={media}
onDelete={handleDelete}
onDescriptionChange={handleDescriptionChange}
onSubmit={onSubmit}
onDragStart={handleDragStart}
onDragEnter={handleDragEnter}
onDragEnd={onDragEnd}
descriptionLimit={descriptionLimit}
withPreview
/>

@ -54,6 +54,7 @@ import {
COMPOSE_EDITOR_STATE_SET,
COMPOSE_SET_GROUP_TIMELINE_VISIBLE,
ComposeAction,
COMPOSE_CHANGE_MEDIA_ORDER,
} from '../actions/compose';
import { EVENT_COMPOSE_CANCEL, EVENT_FORM_SET, type EventsAction } from '../actions/events';
import { ME_FETCH_SUCCESS, ME_PATCH_SUCCESS, MeAction } from '../actions/me';
@ -520,6 +521,14 @@ export default function compose(state = initialState, action: ComposeAction | Ev
return updateCompose(state, 'event-compose-modal', compose => compose.set('text', ''));
case EVENT_FORM_SET:
return updateCompose(state, 'event-compose-modal', compose => compose.set('text', action.text));
case COMPOSE_CHANGE_MEDIA_ORDER:
return updateCompose(state, action.id, compose => compose.update('media_attachments', list => {
const indexA = list.findIndex(x => x.get('id') === action.a);
const moveItem = list.get(indexA)!;
const indexB = list.findIndex(x => x.get('id') === action.b);
return list.splice(indexA, 1).splice(indexB, 0, moveItem);
}));
default:
return state;
}

Loading…
Cancel
Save