Limit number of photos to paste in web composer

This commit is contained in:
Paul Frazee
2023-04-03 10:57:03 -05:00
parent 0d26763e11
commit ca4d8b85e0
3 changed files with 96 additions and 76 deletions
+6 -4
View File
@@ -35,6 +35,7 @@ import QuoteEmbed from '../util/post-embeds/QuoteEmbed'
import {useExternalLinkFetch} from './useExternalLinkFetch' import {useExternalLinkFetch} from './useExternalLinkFetch'
import {isDesktopWeb} from 'platform/detection' import {isDesktopWeb} from 'platform/detection'
const MAX_IMAGES = 4
const MAX_GRAPHEME_LENGTH = 300 const MAX_GRAPHEME_LENGTH = 300
export const ComposePost = observer(function ComposePost({ export const ComposePost = observer(function ComposePost({
@@ -127,12 +128,12 @@ export const ComposePost = observer(function ComposePost({
const onPhotoPasted = React.useCallback( const onPhotoPasted = React.useCallback(
async (uri: string) => { async (uri: string) => {
if (selectedPhotos.length >= 4) { if (selectedPhotos.length >= MAX_IMAGES) {
return return
} }
setSelectedPhotos(sp => { setSelectedPhotos(sp => {
if (sp.length >= 4) { if (sp.length >= MAX_IMAGES) {
return sp return sp
} }
@@ -290,6 +291,7 @@ export const ComposePost = observer(function ComposePost({
placeholder={selectTextInputPlaceholder} placeholder={selectTextInputPlaceholder}
suggestedLinks={suggestedLinks} suggestedLinks={suggestedLinks}
autocompleteView={autocompleteView} autocompleteView={autocompleteView}
numPasteableImages={MAX_IMAGES - selectedPhotos.length}
setRichText={setRichText} setRichText={setRichText}
onPhotoPasted={onPhotoPasted} onPhotoPasted={onPhotoPasted}
onSuggestedLinksChanged={setSuggestedLinks} onSuggestedLinksChanged={setSuggestedLinks}
@@ -332,12 +334,12 @@ export const ComposePost = observer(function ComposePost({
) : null} ) : null}
<View style={[pal.border, styles.bottomBar]}> <View style={[pal.border, styles.bottomBar]}>
<SelectPhotoBtn <SelectPhotoBtn
enabled={selectedPhotos.length < 4} enabled={selectedPhotos.length < MAX_IMAGES}
selectedPhotos={selectedPhotos} selectedPhotos={selectedPhotos}
onSelectPhotos={setSelectedPhotos} onSelectPhotos={setSelectedPhotos}
/> />
<OpenCameraBtn <OpenCameraBtn
enabled={selectedPhotos.length < 4} enabled={selectedPhotos.length < MAX_IMAGES}
selectedPhotos={selectedPhotos} selectedPhotos={selectedPhotos}
onSelectPhotos={setSelectedPhotos} onSelectPhotos={setSelectedPhotos}
/> />
@@ -37,6 +37,7 @@ interface TextInputProps {
placeholder: string placeholder: string
suggestedLinks: Set<string> suggestedLinks: Set<string>
autocompleteView: UserAutocompleteViewModel autocompleteView: UserAutocompleteViewModel
numPasteableImages: number
setRichText: (v: RichText) => void setRichText: (v: RichText) => void
onPhotoPasted: (uri: string) => void onPhotoPasted: (uri: string) => void
onSuggestedLinksChanged: (uris: Set<string>) => void onSuggestedLinksChanged: (uris: Set<string>) => void
@@ -55,6 +56,7 @@ export const TextInput = React.forwardRef(
placeholder, placeholder,
suggestedLinks, suggestedLinks,
autocompleteView, autocompleteView,
numPasteableImages,
setRichText, setRichText,
onPhotoPasted, onPhotoPasted,
onSuggestedLinksChanged, onSuggestedLinksChanged,
@@ -129,6 +131,9 @@ export const TextInput = React.forwardRef(
if (err) { if (err) {
return onError(cleanError(err)) return onError(cleanError(err))
} }
if (numPasteableImages <= 0) {
return
}
const uris = files.map(f => f.uri) const uris = files.map(f => f.uri)
const imgUri = uris.find(uri => /\.(jpe?g|png)$/.test(uri)) const imgUri = uris.find(uri => /\.(jpe?g|png)$/.test(uri))
if (imgUri) { if (imgUri) {
@@ -148,7 +153,7 @@ export const TextInput = React.forwardRef(
onPhotoPasted(finalImgPath) onPhotoPasted(finalImgPath)
} }
}, },
[store, onError, onPhotoPasted], [store, onError, onPhotoPasted, numPasteableImages],
) )
const onSelectionChange = React.useCallback( const onSelectionChange = React.useCallback(
@@ -11,7 +11,6 @@ import {Text} from '@tiptap/extension-text'
import isEqual from 'lodash.isequal' import isEqual from 'lodash.isequal'
import {UserAutocompleteViewModel} from 'state/models/user-autocomplete-view' import {UserAutocompleteViewModel} from 'state/models/user-autocomplete-view'
import {createSuggestion} from './web/Autocomplete' import {createSuggestion} from './web/Autocomplete'
import {Transaction} from '@tiptap/pm/state'
import {cropAndCompressFlow} from 'lib/media/picker' import {cropAndCompressFlow} from 'lib/media/picker'
import {useStores} from 'state/index' import {useStores} from 'state/index'
import { import {
@@ -31,6 +30,7 @@ interface TextInputProps {
placeholder: string placeholder: string
suggestedLinks: Set<string> suggestedLinks: Set<string>
autocompleteView: UserAutocompleteViewModel autocompleteView: UserAutocompleteViewModel
numPasteableImages: number
setRichText: (v: RichText) => void setRichText: (v: RichText) => void
onPhotoPasted: (uri: string) => void onPhotoPasted: (uri: string) => void
onSuggestedLinksChanged: (uris: Set<string>) => void onSuggestedLinksChanged: (uris: Set<string>) => void
@@ -44,6 +44,7 @@ export const TextInput = React.forwardRef(
placeholder, placeholder,
suggestedLinks, suggestedLinks,
autocompleteView, autocompleteView,
numPasteableImages,
setRichText, setRichText,
onPhotoPasted, onPhotoPasted,
onSuggestedLinksChanged, onSuggestedLinksChanged,
@@ -52,8 +53,13 @@ export const TextInput = React.forwardRef(
ref, ref,
) => { ) => {
const store = useStores() const store = useStores()
const processClipboardItemAsPhoto = async (item: DataTransferItem) => { const editorContent = React.useRef({}) // track the editor content in a ref to avoid triggering renders
const processClipboardItemAsPhoto = React.useCallback(
async (item: DataTransferItem) => {
const file = item.getAsFile() const file = item.getAsFile()
if (numPasteableImages <= 0) {
return
}
if (file && file.type && file.type.startsWith('image/')) { if (file && file.type && file.type.startsWith('image/')) {
const {uri, width, height} = await getImageInfoFromFile(file) const {uri, width, height} = await getImageInfoFromFile(file)
@@ -66,8 +72,11 @@ export const TextInput = React.forwardRef(
) )
onPhotoPasted(croppedUri) onPhotoPasted(croppedUri)
} }
} },
const editor = useEditor({ [store, onPhotoPasted, numPasteableImages],
)
const editor = useEditor(
{
extensions: [ extensions: [
Document, Document,
Link.configure({ Link.configure({
@@ -86,7 +95,8 @@ export const TextInput = React.forwardRef(
}), }),
Text, Text,
], ],
content: richtext.text.toString(), // use editorContent when possible to preserve state across re-renders (see useEditor deps below)
content: editorContent.current || richtext.text.toString(),
autofocus: true, autofocus: true,
editable: true, editable: true,
injectCSS: true, injectCSS: true,
@@ -105,7 +115,7 @@ export const TextInput = React.forwardRef(
} }
// For any pasted images, bring them through the crop and compress flow // For any pasted images, bring them through the crop and compress flow
for (const item of Array.from(items)) { for (const item of Array.from(items).slice(0, numPasteableImages)) {
processClipboardItemAsPhoto(item) processClipboardItemAsPhoto(item)
} }
@@ -115,6 +125,7 @@ export const TextInput = React.forwardRef(
}, },
onUpdate({editor: editorProp}) { onUpdate({editor: editorProp}) {
const json = editorProp.getJSON() const json = editorProp.getJSON()
editorContent.current = json
const newRt = new RichText({text: editorJsonToText(json).trim()}) const newRt = new RichText({text: editorJsonToText(json).trim()})
setRichText(newRt) setRichText(newRt)
@@ -124,7 +135,9 @@ export const TextInput = React.forwardRef(
onSuggestedLinksChanged(newSuggestedLinks) onSuggestedLinksChanged(newSuggestedLinks)
} }
}, },
}) },
[numPasteableImages],
)
React.useImperativeHandle(ref, () => ({ React.useImperativeHandle(ref, () => ({
focus: () => {}, // TODO focus: () => {}, // TODO