Lexical: Add media preview
Signed-off-by: marcin mikołajczak <git@mkljczk.pl>
This commit is contained in:
parent
b3f9edd41e
commit
41ee08cd14
3 changed files with 52 additions and 21 deletions
|
@ -1,13 +1,13 @@
|
|||
import clsx from 'clsx';
|
||||
import { List as ImmutableList } from 'immutable';
|
||||
import React, { useState } from 'react';
|
||||
import { defineMessages, useIntl, FormattedMessage } from 'react-intl';
|
||||
import { defineMessages, useIntl } from 'react-intl';
|
||||
import { spring } from 'react-motion';
|
||||
|
||||
import { openModal } from 'soapbox/actions/modals';
|
||||
import Blurhash from 'soapbox/components/blurhash';
|
||||
import Icon from 'soapbox/components/icon';
|
||||
import IconButton from 'soapbox/components/icon-button';
|
||||
import { IconButton } from 'soapbox/components/ui';
|
||||
import Motion from 'soapbox/features/ui/util/optional-motion';
|
||||
import { useAppDispatch } from 'soapbox/hooks';
|
||||
import { Attachment } from 'soapbox/types/entities';
|
||||
|
@ -57,6 +57,7 @@ export const MIMETYPE_ICONS: Record<string, string> = {
|
|||
const messages = defineMessages({
|
||||
description: { id: 'upload_form.description', defaultMessage: 'Describe for the visually impaired' },
|
||||
delete: { id: 'upload_form.undo', defaultMessage: 'Delete' },
|
||||
preview: { id: 'upload_form.preview', defaultMessage: 'Preview' },
|
||||
});
|
||||
|
||||
interface IUpload {
|
||||
|
@ -159,20 +160,24 @@ const Upload: React.FC<IUpload> = ({
|
|||
backgroundPosition: typeof x === 'number' && typeof y === 'number' ? `${x}% ${y}%` : undefined }}
|
||||
>
|
||||
<div className={clsx('compose-form__upload__actions', { active })}>
|
||||
{onDelete && (
|
||||
<IconButton
|
||||
onClick={handleUndoClick}
|
||||
src={require('@tabler/icons/x.svg')}
|
||||
text={<FormattedMessage id='upload_form.undo' defaultMessage='Delete' />}
|
||||
/>
|
||||
)}
|
||||
|
||||
{/* Only display the "Preview" button for a valid attachment with a URL */}
|
||||
{(withPreview && mediaType !== 'unknown' && Boolean(media.url)) && (
|
||||
<IconButton
|
||||
onClick={handleOpenModal}
|
||||
src={require('@tabler/icons/zoom-in.svg')}
|
||||
text={<FormattedMessage id='upload_form.preview' defaultMessage='Preview' />}
|
||||
theme='dark'
|
||||
className='hover:scale-105 hover:bg-gray-900'
|
||||
iconClassName='h-5 w-5'
|
||||
title={intl.formatMessage(messages.preview)}
|
||||
/>
|
||||
)}
|
||||
{onDelete && (
|
||||
<IconButton
|
||||
onClick={handleUndoClick}
|
||||
src={require('@tabler/icons/x.svg')}
|
||||
theme='dark'
|
||||
className='hover:scale-105 hover:bg-gray-900'
|
||||
iconClassName='h-5 w-5'
|
||||
title={intl.formatMessage(messages.delete)}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
|
|
|
@ -11,6 +11,7 @@ import { useLexicalComposerContext } from '@lexical/react/LexicalComposerContext
|
|||
import { useLexicalNodeSelection } from '@lexical/react/useLexicalNodeSelection';
|
||||
import { mergeRegister } from '@lexical/utils';
|
||||
import clsx from 'clsx';
|
||||
import { List as ImmutableList } from 'immutable';
|
||||
import {
|
||||
$getNodeByKey,
|
||||
$getSelection,
|
||||
|
@ -28,7 +29,10 @@ import {
|
|||
import * as React from 'react';
|
||||
import { Suspense, useCallback, useEffect, useRef, useState } from 'react';
|
||||
|
||||
import { IconButton } from 'soapbox/components/ui';
|
||||
import { openModal } from 'soapbox/actions/modals';
|
||||
import { HStack, IconButton } from 'soapbox/components/ui';
|
||||
import { useAppDispatch } from 'soapbox/hooks';
|
||||
import { normalizeAttachment } from 'soapbox/normalizers';
|
||||
|
||||
import { $isImageNode } from './image-node';
|
||||
|
||||
|
@ -40,6 +44,7 @@ import type {
|
|||
RangeSelection,
|
||||
} from 'lexical';
|
||||
|
||||
|
||||
const imageCache = new Set();
|
||||
|
||||
const useSuspenseImage = (src: string) => {
|
||||
|
@ -87,6 +92,8 @@ const ImageComponent = ({
|
|||
nodeKey: NodeKey
|
||||
src: string
|
||||
}): JSX.Element => {
|
||||
const dispatch = useAppDispatch();
|
||||
|
||||
const imageRef = useRef<null | HTMLImageElement>(null);
|
||||
const buttonRef = useRef<HTMLButtonElement | null>(null);
|
||||
const [isSelected, setSelected, clearSelection] =
|
||||
|
@ -109,6 +116,16 @@ const ImageComponent = ({
|
|||
[nodeKey],
|
||||
);
|
||||
|
||||
const previewImage = () => {
|
||||
const image = normalizeAttachment({
|
||||
type: 'image',
|
||||
url: src,
|
||||
altText,
|
||||
});
|
||||
|
||||
dispatch(openModal('MEDIA', { media: ImmutableList.of(image), index: 0 }));
|
||||
};
|
||||
|
||||
const onDelete = useCallback(
|
||||
(payload: KeyboardEvent) => {
|
||||
if (isSelected && $isNodeSelection($getSelection())) {
|
||||
|
@ -248,13 +265,22 @@ const ImageComponent = ({
|
|||
<Suspense fallback={null}>
|
||||
<>
|
||||
<div className='relative' draggable={draggable}>
|
||||
<IconButton
|
||||
onClick={deleteNode}
|
||||
src={require('@tabler/icons/x.svg')}
|
||||
theme='dark'
|
||||
className='absolute right-2 top-2 z-10 hover:scale-105 hover:bg-gray-900'
|
||||
iconClassName='h-5 w-5'
|
||||
/>
|
||||
<HStack className='absolute right-2 top-2 z-10' space={2}>
|
||||
<IconButton
|
||||
onClick={previewImage}
|
||||
src={require('@tabler/icons/zoom-in.svg')}
|
||||
theme='dark'
|
||||
className='!p-1.5 hover:scale-105 hover:bg-gray-900'
|
||||
iconClassName='h-5 w-5'
|
||||
/>
|
||||
<IconButton
|
||||
onClick={deleteNode}
|
||||
src={require('@tabler/icons/x.svg')}
|
||||
theme='dark'
|
||||
className='!p-1.5 hover:scale-105 hover:bg-gray-900'
|
||||
iconClassName='h-5 w-5'
|
||||
/>
|
||||
</HStack>
|
||||
<LazyImage
|
||||
className={
|
||||
clsx('cursor-default', {
|
||||
|
|
|
@ -50,7 +50,7 @@
|
|||
overflow: hidden;
|
||||
|
||||
&__actions {
|
||||
@apply bg-gradient-to-b from-gray-900/80 via-gray-900/50 to-transparent flex items-start justify-between opacity-0 transition-opacity duration-100 ease-linear;
|
||||
@apply p-2 bg-gradient-to-b from-gray-900/80 via-gray-900/50 to-transparent flex items-start gap-2 justify-end opacity-0 transition-opacity duration-100 ease-linear;
|
||||
|
||||
&.active {
|
||||
@apply opacity-100;
|
||||
|
|
Loading…
Reference in a new issue