Skip to content

Commit

Permalink
Change design of edit media modal in web UI (#33516)
Browse files Browse the repository at this point in the history
  • Loading branch information
Gargron authored Jan 21, 2025
1 parent 4ebdfed commit 11786f1
Show file tree
Hide file tree
Showing 42 changed files with 919 additions and 900 deletions.
Binary file removed app/javascript/images/reticle.png
Binary file not shown.
2 changes: 1 addition & 1 deletion app/javascript/mastodon/actions/compose.js
Original file line number Diff line number Diff line change
Expand Up @@ -414,7 +414,7 @@ export function initMediaEditModal(id) {

dispatch(openModal({
modalType: 'FOCAL_POINT',
modalProps: { id },
modalProps: { mediaId: id },
}));
};
}
Expand Down
70 changes: 70 additions & 0 deletions app/javascript/mastodon/actions/compose_typed.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
import type { List as ImmutableList, Map as ImmutableMap } from 'immutable';

import { apiUpdateMedia } from 'mastodon/api/compose';
import type { ApiMediaAttachmentJSON } from 'mastodon/api_types/media_attachments';
import type { MediaAttachment } from 'mastodon/models/media_attachment';
import { createDataLoadingThunk } from 'mastodon/store/typed_functions';

type SimulatedMediaAttachmentJSON = ApiMediaAttachmentJSON & {
unattached?: boolean;
};

const simulateModifiedApiResponse = (
media: MediaAttachment,
params: { description?: string; focus?: string },
): SimulatedMediaAttachmentJSON => {
const [x, y] = (params.focus ?? '').split(',');

const data = {
...media.toJS(),
...params,
meta: {
focus: {
x: parseFloat(x ?? '0'),
y: parseFloat(y ?? '0'),
},
},
} as unknown as SimulatedMediaAttachmentJSON;

return data;
};

export const changeUploadCompose = createDataLoadingThunk(
'compose/changeUpload',
async (
{
id,
...params
}: {
id: string;
description: string;
focus: string;
},
{ getState },
) => {
const media = (
(getState().compose as ImmutableMap<string, unknown>).get(
'media_attachments',
) as ImmutableList<MediaAttachment>
).find((item) => item.get('id') === id);

// Editing already-attached media is deferred to editing the post itself.
// For simplicity's sake, fake an API reply.
if (media && !media.get('unattached')) {
return new Promise<SimulatedMediaAttachmentJSON>((resolve) => {
resolve(simulateModifiedApiResponse(media, params));
});
}

return apiUpdateMedia(id, params);
},
(media: SimulatedMediaAttachmentJSON) => {
return {
media,
attached: typeof media.unattached !== 'undefined' && !media.unattached,
};
},
{
useLoadingBar: false,
},
);
1 change: 1 addition & 0 deletions app/javascript/mastodon/actions/modal.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ export type ModalType = keyof typeof MODAL_COMPONENTS;
interface OpenModalPayload {
modalType: ModalType;
modalProps: ModalProps;
previousModalProps?: ModalProps;
}
export const openModal = createAction<OpenModalPayload>('MODAL_OPEN');

Expand Down
7 changes: 7 additions & 0 deletions app/javascript/mastodon/api/compose.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import { apiRequestPut } from 'mastodon/api';
import type { ApiMediaAttachmentJSON } from 'mastodon/api_types/media_attachments';

export const apiUpdateMedia = (
id: string,
params?: { description?: string; focus?: string },
) => apiRequestPut<ApiMediaAttachmentJSON>(`v1/media/${id}`, params);
3 changes: 3 additions & 0 deletions app/javascript/mastodon/components/button.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ interface BaseProps
extends Omit<React.ButtonHTMLAttributes<HTMLButtonElement>, 'children'> {
block?: boolean;
secondary?: boolean;
compact?: boolean;
dangerous?: boolean;
}

Expand All @@ -27,6 +28,7 @@ export const Button: React.FC<Props> = ({
disabled,
block,
secondary,
compact,
dangerous,
className,
title,
Expand All @@ -47,6 +49,7 @@ export const Button: React.FC<Props> = ({
<button
className={classNames('button', className, {
'button-secondary': secondary,
'button--compact': compact,
'button--block': block,
'button--dangerous': dangerous,
})}
Expand Down
10 changes: 8 additions & 2 deletions app/javascript/mastodon/components/follow_button.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@ import { useCallback, useEffect } from 'react';

import { useIntl, defineMessages } from 'react-intl';

import classNames from 'classnames';

import { useIdentity } from '@/mastodon/identity_context';
import { fetchRelationships, followAccount } from 'mastodon/actions/accounts';
import { openModal } from 'mastodon/actions/modal';
Expand All @@ -20,7 +22,8 @@ const messages = defineMessages({

export const FollowButton: React.FC<{
accountId?: string;
}> = ({ accountId }) => {
compact?: boolean;
}> = ({ accountId, compact }) => {
const intl = useIntl();
const dispatch = useAppDispatch();
const { signedIn } = useIdentity();
Expand Down Expand Up @@ -89,7 +92,9 @@ export const FollowButton: React.FC<{
href='/settings/profile'
target='_blank'
rel='noopener'
className='button button-secondary'
className={classNames('button button-secondary', {
'button--compact': compact,
})}
>
{label}
</a>
Expand All @@ -106,6 +111,7 @@ export const FollowButton: React.FC<{
(account?.suspended || !!account?.moved))
}
secondary={following}
compact={compact}
className={following ? 'button--destructive' : undefined}
>
{label}
Expand Down
98 changes: 49 additions & 49 deletions app/javascript/mastodon/components/gifv.tsx
Original file line number Diff line number Diff line change
@@ -1,70 +1,70 @@
import { useCallback, useState } from 'react';
import { useCallback, useState, forwardRef } from 'react';

interface Props {
src: string;
key: string;
alt?: string;
lang?: string;
width: number;
height: number;
onClick?: () => void;
width?: number;
height?: number;
onClick?: React.MouseEventHandler;
onMouseDown?: React.MouseEventHandler;
onTouchStart?: React.TouchEventHandler;
}

export const GIFV: React.FC<Props> = ({
src,
alt,
lang,
width,
height,
onClick,
}) => {
const [loading, setLoading] = useState(true);
export const GIFV = forwardRef<HTMLVideoElement, Props>(
(
{ src, alt, lang, width, height, onClick, onMouseDown, onTouchStart },
ref,
) => {
const [loading, setLoading] = useState(true);

const handleLoadedData: React.ReactEventHandler<HTMLVideoElement> =
useCallback(() => {
const handleLoadedData = useCallback(() => {
setLoading(false);
}, [setLoading]);

const handleClick: React.MouseEventHandler = useCallback(
(e) => {
if (onClick) {
const handleClick = useCallback(
(e: React.MouseEvent) => {
e.stopPropagation();
onClick();
}
},
[onClick],
);
onClick?.(e);
},
[onClick],
);

return (
<div className='gifv' style={{ position: 'relative' }}>
{loading && (
<canvas
width={width}
height={height}
return (
<div className='gifv'>
{loading && (
<canvas
role='button'
tabIndex={0}
aria-label={alt}
title={alt}
lang={lang}
onClick={handleClick}
/>
)}

<video
ref={ref}
src={src}
role='button'
tabIndex={0}
aria-label={alt}
title={alt}
lang={lang}
width={width}
height={height}
muted
loop
autoPlay
playsInline
onClick={handleClick}
onLoadedData={handleLoadedData}
onMouseDown={onMouseDown}
onTouchStart={onTouchStart}
/>
)}
</div>
);
},
);

<video
src={src}
role='button'
tabIndex={0}
aria-label={alt}
title={alt}
lang={lang}
muted
loop
autoPlay
playsInline
onClick={handleClick}
onLoadedData={handleLoadedData}
style={{ position: loading ? 'absolute' : 'static', top: 0, left: 0 }}
/>
</div>
);
};
GIFV.displayName = 'GIFV';
Loading

0 comments on commit 11786f1

Please sign in to comment.