diff --git a/app/javascript/mastodon/actions/compose_typed.ts b/app/javascript/mastodon/actions/compose_typed.ts index 9ff15f4b6c2..b86d5892362 100644 --- a/app/javascript/mastodon/actions/compose_typed.ts +++ b/app/javascript/mastodon/actions/compose_typed.ts @@ -147,6 +147,10 @@ export const changeUploadCompose = createDataLoadingThunk( }, ); +export const rearrangeComposeAttachments = createAction( + 'compose/rearrangeAttachments', +); + export const quoteCompose = createAppThunk( 'compose/quoteComposeStatus', (status: Status, { dispatch }) => { diff --git a/app/javascript/mastodon/components/sortable_list/hooks.ts b/app/javascript/mastodon/components/sortable_list/hooks.ts index 60de075da04..3b88d0a0511 100644 --- a/app/javascript/mastodon/components/sortable_list/hooks.ts +++ b/app/javascript/mastodon/components/sortable_list/hooks.ts @@ -6,28 +6,24 @@ import { useState, } from 'react'; -import type { UniqueIdentifier } from '@dnd-kit/core'; +import type { DragStartEvent, UniqueIdentifier } from '@dnd-kit/core'; import { useSortable } from '@dnd-kit/sortable'; import { normalizeKey } from '../hotkeys/utils'; -interface UseSortableListArgs { - ids: Id[]; - onSort: (ids: Id[]) => void; +interface UseSortableListArgs { onCancel: () => void; } -export function useSortableList({ - onCancel, -}: UseSortableListArgs) { - const [isDragging, setIsDragging] = useState(false); +export function useSortableList({ onCancel }: UseSortableListArgs) { + const [activeId, setActiveId] = useState(null); - const onDragStart = useCallback(() => { - setIsDragging(true); + const onDragStart = useCallback((event: DragStartEvent) => { + setActiveId(event.active.id); }, []); const onDragEnd = useCallback(() => { - setIsDragging(false); + setActiveId(null); }, []); // Combines the Escape shortcut for closing the modal and for cancelling the drag, depending on the current state. @@ -40,21 +36,20 @@ export function useSortableList({ event.stopPropagation(); // Trigger the drag cancel here, since onDragCancel triggers before this handler. - if (isDragging) { - setIsDragging(false); + if (activeId) { + setActiveId(null); } else { onCancel(); } } }, - [isDragging, onCancel], + [activeId, onCancel], ); return { - isDragging, + activeId, onDragStart, onDragEnd, - onCancel, onModalExit, }; } diff --git a/app/javascript/mastodon/components/sortable_list/item.tsx b/app/javascript/mastodon/components/sortable_list/item.tsx index 5070bdc7369..986d58009dc 100644 --- a/app/javascript/mastodon/components/sortable_list/item.tsx +++ b/app/javascript/mastodon/components/sortable_list/item.tsx @@ -16,6 +16,7 @@ interface SortableListItemOwnProps { id: UniqueIdentifier; draggingClassName?: string; overClassName?: string; + noTransition?: boolean; } export type SortableListItemProps = @@ -33,6 +34,7 @@ export const SortableListItem = ({ draggingClassName, overClassName, style: componentStyle, + noTransition, ...props }: SortableListItemProps) => { const ItemComponent = AsComp ?? 'li'; @@ -56,7 +58,8 @@ export const SortableListItem = ({ const style = { ...componentStyle, transform: CSS.Translate.toString(transform), - transition, + transition: noTransition ? undefined : transition, + ['--transition']: transition, }; return ( @@ -74,6 +77,8 @@ export const SortableListItem = ({ isDragging && draggingClassName, isOver && overClassName, )} + data-dragging={isDragging} + data-over={isOver} > {children} diff --git a/app/javascript/mastodon/components/sortable_list/list.tsx b/app/javascript/mastodon/components/sortable_list/list.tsx index 27e2c8a6661..22505decd74 100644 --- a/app/javascript/mastodon/components/sortable_list/list.tsx +++ b/app/javascript/mastodon/components/sortable_list/list.tsx @@ -5,14 +5,18 @@ import type { MessageDescriptor } from 'react-intl'; import { useIntl } from 'react-intl'; import type { + Active, Announcements, DragEndEvent, DragStartEvent, + DropAnimation, + Over, ScreenReaderInstructions, UniqueIdentifier, } from '@dnd-kit/core'; import { DndContext, + DragOverlay, KeyboardSensor, PointerSensor, useSensor, @@ -62,10 +66,13 @@ interface SortableListOwnProps< ids: Id[]; renderItem?: (id: Id) => React.ReactNode; messages?: SortableListMessages; + messageLabelCb?: (item: Active | Over) => Id; as?: As; onSort?: (ids: Id[]) => void; onDragStart?: (event: DragStartEvent) => void; onDragEnd?: (event: DragEndEvent) => void; + overlay?: React.ReactNode; + dropAnimation?: DropAnimation | null; } export type SortableListProps< @@ -86,6 +93,9 @@ export const SortableList = < onDragEnd: onDragEndParent, messages, children, + overlay, + dropAnimation, + messageLabelCb, ...props }: SortableListProps) => { const sensors = useSensors( @@ -122,6 +132,9 @@ export const SortableList = < if (!messages) { return undefined; } + + const itemRender = messageLabelCb ?? (({ id }) => String(id)); + return { screenReaderInstructions: { draggable: intl.formatMessage(messages.screenReaderInstructions), @@ -133,15 +146,15 @@ export const SortableList = < return undefined; } return intl.formatMessage(messages.onDragStart, { - item: active.id, + item: itemRender(active), }); }, onDragOver({ active, over }) { if (over && active.id !== over.id && messages.onDragMoveOver) { return intl.formatMessage(messages.onDragMoveOver, { - item: active.id, - over: over.id, + item: itemRender(active), + over: itemRender(over), }); } @@ -150,30 +163,32 @@ export const SortableList = < } return intl.formatMessage(messages.onDragMove, { - item: active.id, + item: itemRender(active), }); }, - onDragEnd({ active }) { + onDragEnd({ active, over }) { if (!messages.onDragEnd) { return undefined; } return intl.formatMessage(messages.onDragEnd, { - item: active.id, + item: itemRender(active), + over: over ? itemRender(over) : undefined, }); }, - onDragCancel({ active }) { + onDragCancel({ active, over }) { if (!messages.onDragCancel) { return undefined; } return intl.formatMessage(messages.onDragCancel, { - item: active.id, + item: itemRender(active), + over: over ? itemRender(over) : undefined, }); }, } satisfies Announcements, }; - }, [intl, messages]); + }, [intl, messageLabelCb, messages]); const ListComponent = AsComp ?? 'ol'; @@ -196,6 +211,10 @@ export const SortableList = < )))} + + {overlay && ( + {overlay} + )} ); }; diff --git a/app/javascript/mastodon/features/audio/index.tsx b/app/javascript/mastodon/features/audio/index.tsx index e9c680f0864..8194da82553 100644 --- a/app/javascript/mastodon/features/audio/index.tsx +++ b/app/javascript/mastodon/features/audio/index.tsx @@ -553,7 +553,7 @@ export const Audio: React.FC<{ /> )} -