From fbf2970bae1030849a39410f026e030d2bf39786 Mon Sep 17 00:00:00 2001 From: Mary Date: Sat, 20 Jan 2024 10:39:28 +0700 Subject: feat: web composer image drag and drop --- src/view/com/composer/text-input/TextInput.web.tsx | 63 ++++++++++++++++++---- 1 file changed, 54 insertions(+), 9 deletions(-) (limited to 'src/view/com/composer/text-input/TextInput.web.tsx') diff --git a/src/view/com/composer/text-input/TextInput.web.tsx b/src/view/com/composer/text-input/TextInput.web.tsx index ec3a042a3..351ca1d0c 100644 --- a/src/view/com/composer/text-input/TextInput.web.tsx +++ b/src/view/com/composer/text-input/TextInput.web.tsx @@ -18,6 +18,7 @@ import {Emoji} from './web/EmojiPicker.web' import {LinkDecorator} from './web/LinkDecorator' import {generateJSON} from '@tiptap/html' import {useActorAutocompleteFn} from '#/state/queries/actor-autocomplete' +import {usePalette} from '#/lib/hooks/usePalette' export interface TextInputRef { focus: () => void @@ -53,7 +54,11 @@ export const TextInput = React.forwardRef(function TextInputImpl( ) { const autocomplete = useActorAutocompleteFn() + const pal = usePalette('default') const modeClass = useColorSchemeStyle('ProseMirror-light', 'ProseMirror-dark') + + const [isDropping, setIsDropping] = React.useState(false) + const extensions = React.useMemo( () => [ Document, @@ -112,6 +117,32 @@ export const TextInput = React.forwardRef(function TextInputImpl( return true } }, + handleDOMEvents: { + dragover: (_, event) => { + const transfer = event.dataTransfer + if (transfer && transfer.types.includes('Files')) { + setIsDropping(true) + } + }, + dragleave: (_, _event) => { + setIsDropping(false) + }, + drop: (_, event) => { + const transfer = event.dataTransfer + if (transfer) { + const items = transfer.items + + if (items.length > 0) { + event.preventDefault() + getImageFromUri(items, (uri: string) => { + textInputWebEmitter.emit('photo-pasted', uri) + }) + } + } + + setIsDropping(false) + }, + }, }, content: generateJSON(richtext.text.toString(), extensions), autofocus: 'end', @@ -179,6 +210,10 @@ export const TextInput = React.forwardRef(function TextInputImpl( return ( + + {isDropping && ( + + )} ) }) @@ -210,6 +245,17 @@ const styles = StyleSheet.create({ marginLeft: 8, marginBottom: 10, }, + dropContainer: { + pointerEvents: 'none', + position: 'absolute', + borderWidth: 4, + borderRadius: 8, + borderStyle: 'dashed', + top: 0, + bottom: 0, + left: 0, + right: 0, + }, }) function getImageFromUri( @@ -218,25 +264,24 @@ function getImageFromUri( ) { for (let index = 0; index < items.length; index++) { const item = items[index] - const {kind, type} = item + const type = item.type if (type === 'text/plain') { item.getAsString(async itemString => { if (isUriImage(itemString)) { const response = await fetch(itemString) const blob = await response.blob() - blobToDataUri(blob).then(callback, err => console.error(err)) + + if (blob.type.startsWith('image/')) { + blobToDataUri(blob).then(callback, err => console.error(err)) + } } }) - } - - if (kind === 'file') { + } else if (type.startsWith('image/')) { const file = item.getAsFile() - if (file instanceof Blob) { - blobToDataUri(new Blob([file], {type: item.type})).then(callback, err => - console.error(err), - ) + if (file) { + blobToDataUri(file).then(callback, err => console.error(err)) } } } -- cgit 1.4.1 From 90b6cfbb917154c50568e6422ef4dd6bfc77c02a Mon Sep 17 00:00:00 2001 From: Mary Date: Sat, 20 Jan 2024 21:08:11 +0700 Subject: feat: new design --- src/view/com/composer/text-input/TextInput.web.tsx | 116 ++++++++++++++------- 1 file changed, 81 insertions(+), 35 deletions(-) (limited to 'src/view/com/composer/text-input/TextInput.web.tsx') diff --git a/src/view/com/composer/text-input/TextInput.web.tsx b/src/view/com/composer/text-input/TextInput.web.tsx index 351ca1d0c..b0bc78c15 100644 --- a/src/view/com/composer/text-input/TextInput.web.tsx +++ b/src/view/com/composer/text-input/TextInput.web.tsx @@ -9,7 +9,7 @@ import Hardbreak from '@tiptap/extension-hard-break' import {Mention} from '@tiptap/extension-mention' import {Paragraph} from '@tiptap/extension-paragraph' import {Placeholder} from '@tiptap/extension-placeholder' -import {Text} from '@tiptap/extension-text' +import {Text as TiptapText} from '@tiptap/extension-text' import isEqual from 'lodash.isequal' import {createSuggestion} from './web/Autocomplete' import {useColorSchemeStyle} from 'lib/hooks/useColorSchemeStyle' @@ -19,6 +19,10 @@ import {LinkDecorator} from './web/LinkDecorator' import {generateJSON} from '@tiptap/html' import {useActorAutocompleteFn} from '#/state/queries/actor-autocomplete' import {usePalette} from '#/lib/hooks/usePalette' +import {Portal} from '#/components/Portal' +import {Text} from '../../util/text/Text' +import {Trans} from '@lingui/macro' +import Animated, {FadeIn, FadeOut} from 'react-native-reanimated' export interface TextInputRef { focus: () => void @@ -73,7 +77,7 @@ export const TextInput = React.forwardRef(function TextInputImpl( Placeholder.configure({ placeholder, }), - Text, + TiptapText, History, Hardbreak, ], @@ -93,6 +97,41 @@ export const TextInput = React.forwardRef(function TextInputImpl( } }, [onPhotoPasted]) + React.useEffect(() => { + const handleDrop = (event: DragEvent) => { + const transfer = event.dataTransfer + if (transfer) { + const items = transfer.items + + getImageFromUri(items, (uri: string) => { + textInputWebEmitter.emit('photo-pasted', uri) + }) + } + + event.preventDefault() + setIsDropping(false) + } + const handleDragOver = (event: DragEvent) => { + const transfer = event.dataTransfer + if (transfer && transfer.types.includes('Files')) { + setIsDropping(true) + } + } + const handleDragLeave = (_event: DragEvent) => { + setIsDropping(false) + } + + document.body.addEventListener('drop', handleDrop) + document.body.addEventListener('dragover', handleDragOver) + document.body.addEventListener('dragleave', handleDragLeave) + + return () => { + document.body.removeEventListener('drop', handleDrop) + document.body.removeEventListener('dragover', handleDragOver) + document.body.removeEventListener('dragleave', handleDragLeave) + } + }, [setIsDropping]) + const editor = useEditor( { extensions, @@ -117,32 +156,6 @@ export const TextInput = React.forwardRef(function TextInputImpl( return true } }, - handleDOMEvents: { - dragover: (_, event) => { - const transfer = event.dataTransfer - if (transfer && transfer.types.includes('Files')) { - setIsDropping(true) - } - }, - dragleave: (_, _event) => { - setIsDropping(false) - }, - drop: (_, event) => { - const transfer = event.dataTransfer - if (transfer) { - const items = transfer.items - - if (items.length > 0) { - event.preventDefault() - getImageFromUri(items, (uri: string) => { - textInputWebEmitter.emit('photo-pasted', uri) - }) - } - } - - setIsDropping(false) - }, - }, }, content: generateJSON(richtext.text.toString(), extensions), autofocus: 'end', @@ -208,13 +221,32 @@ export const TextInput = React.forwardRef(function TextInputImpl( })) return ( - - + <> + + + {isDropping && ( - + + + + + Drop to add images + + + + )} - + ) }) @@ -246,16 +278,30 @@ const styles = StyleSheet.create({ marginBottom: 10, }, dropContainer: { + backgroundColor: '#0007', pointerEvents: 'none', + alignItems: 'center', + justifyContent: 'center', position: 'absolute', - borderWidth: 4, - borderRadius: 8, - borderStyle: 'dashed', + padding: 16, top: 0, bottom: 0, left: 0, right: 0, }, + dropModal: { + // @ts-ignore web only + boxShadow: 'rgba(0, 0, 0, 0.3) 0px 5px 20px', + padding: 8, + borderWidth: 1, + borderRadius: 8, + }, + dropText: { + padding: 32, + borderStyle: 'dashed', + borderRadius: 8, + borderWidth: 2, + }, }) function getImageFromUri( -- cgit 1.4.1 From 854718c5551139109a0eef60126b9a39117f833f Mon Sep 17 00:00:00 2001 From: Mary Date: Sat, 20 Jan 2024 21:20:07 +0700 Subject: refactor: use right borderDark --- src/view/com/composer/text-input/TextInput.web.tsx | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) (limited to 'src/view/com/composer/text-input/TextInput.web.tsx') diff --git a/src/view/com/composer/text-input/TextInput.web.tsx b/src/view/com/composer/text-input/TextInput.web.tsx index b0bc78c15..3ffe3baaf 100644 --- a/src/view/com/composer/text-input/TextInput.web.tsx +++ b/src/view/com/composer/text-input/TextInput.web.tsx @@ -235,11 +235,7 @@ export const TextInput = React.forwardRef(function TextInputImpl( + style={[pal.text, pal.borderDark, styles.dropText]}> Drop to add images -- cgit 1.4.1 From 88f2a73ad6eb0f3f4b9b5a10aaf3d3be090bc5e5 Mon Sep 17 00:00:00 2001 From: Mary Date: Sun, 21 Jan 2024 04:24:58 +0700 Subject: fix: always call preventDefault --- src/view/com/composer/text-input/TextInput.web.tsx | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) (limited to 'src/view/com/composer/text-input/TextInput.web.tsx') diff --git a/src/view/com/composer/text-input/TextInput.web.tsx b/src/view/com/composer/text-input/TextInput.web.tsx index 3ffe3baaf..a2344ad3f 100644 --- a/src/view/com/composer/text-input/TextInput.web.tsx +++ b/src/view/com/composer/text-input/TextInput.web.tsx @@ -111,24 +111,32 @@ export const TextInput = React.forwardRef(function TextInputImpl( event.preventDefault() setIsDropping(false) } - const handleDragOver = (event: DragEvent) => { + const handleDragEnter = (event: DragEvent) => { const transfer = event.dataTransfer + + event.preventDefault() if (transfer && transfer.types.includes('Files')) { setIsDropping(true) } } - const handleDragLeave = (_event: DragEvent) => { + const handleDragLeave = (event: DragEvent) => { + event.preventDefault() setIsDropping(false) } + const handleDragOver = (event: DragEvent) => { + event.preventDefault() + } document.body.addEventListener('drop', handleDrop) - document.body.addEventListener('dragover', handleDragOver) + document.body.addEventListener('dragenter', handleDragEnter) document.body.addEventListener('dragleave', handleDragLeave) + document.body.addEventListener('dragover', handleDragOver) return () => { document.body.removeEventListener('drop', handleDrop) - document.body.removeEventListener('dragover', handleDragOver) + document.body.removeEventListener('dragenter', handleDragEnter) document.body.removeEventListener('dragleave', handleDragLeave) + document.body.removeEventListener('dragover', handleDragOver) } }, [setIsDropping]) -- cgit 1.4.1 From f5356c3d479115103982079a5e76541d3ce4e11c Mon Sep 17 00:00:00 2001 From: Mary Date: Sun, 21 Jan 2024 04:25:16 +0700 Subject: fix: design adjustments --- src/view/com/composer/text-input/TextInput.web.tsx | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) (limited to 'src/view/com/composer/text-input/TextInput.web.tsx') diff --git a/src/view/com/composer/text-input/TextInput.web.tsx b/src/view/com/composer/text-input/TextInput.web.tsx index a2344ad3f..db1b6b6f4 100644 --- a/src/view/com/composer/text-input/TextInput.web.tsx +++ b/src/view/com/composer/text-input/TextInput.web.tsx @@ -298,10 +298,11 @@ const styles = StyleSheet.create({ boxShadow: 'rgba(0, 0, 0, 0.3) 0px 5px 20px', padding: 8, borderWidth: 1, - borderRadius: 8, + borderRadius: 16, }, dropText: { - padding: 32, + paddingVertical: 44, + paddingHorizontal: 36, borderStyle: 'dashed', borderRadius: 8, borderWidth: 2, -- cgit 1.4.1 From 526411ab25c66f787e9e702ef26240a165c45a61 Mon Sep 17 00:00:00 2001 From: Mary Date: Sun, 21 Jan 2024 04:34:29 +0700 Subject: fix: firefox edge-case --- src/view/com/composer/text-input/TextInput.web.tsx | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) (limited to 'src/view/com/composer/text-input/TextInput.web.tsx') diff --git a/src/view/com/composer/text-input/TextInput.web.tsx b/src/view/com/composer/text-input/TextInput.web.tsx index db1b6b6f4..8898ab977 100644 --- a/src/view/com/composer/text-input/TextInput.web.tsx +++ b/src/view/com/composer/text-input/TextInput.web.tsx @@ -123,20 +123,17 @@ export const TextInput = React.forwardRef(function TextInputImpl( event.preventDefault() setIsDropping(false) } - const handleDragOver = (event: DragEvent) => { - event.preventDefault() - } document.body.addEventListener('drop', handleDrop) document.body.addEventListener('dragenter', handleDragEnter) + document.body.addEventListener('dragover', handleDragEnter) document.body.addEventListener('dragleave', handleDragLeave) - document.body.addEventListener('dragover', handleDragOver) return () => { document.body.removeEventListener('drop', handleDrop) document.body.removeEventListener('dragenter', handleDragEnter) + document.body.removeEventListener('dragover', handleDragEnter) document.body.removeEventListener('dragleave', handleDragLeave) - document.body.removeEventListener('dragover', handleDragOver) } }, [setIsDropping]) -- cgit 1.4.1