diff --git a/src/components/markdownEditor/children/editorToolbar.tsx b/src/components/markdownEditor/children/editorToolbar.tsx index ace95001e..3f789bec2 100644 --- a/src/components/markdownEditor/children/editorToolbar.tsx +++ b/src/components/markdownEditor/children/editorToolbar.tsx @@ -182,6 +182,7 @@ export const EditorToolbar = ({ paramFiles={paramFiles} isEditing={isEditing} username={currentAccount.username} + hideToolbarExtension={_hideExtension} handleMediaInsert={handleMediaInsert} setIsUploading={setIsUploading} /> diff --git a/src/components/uploadsGalleryModal/children/uploadsGalleryContent.tsx b/src/components/uploadsGalleryModal/children/uploadsGalleryContent.tsx index be44e5cf2..e5345453a 100644 --- a/src/components/uploadsGalleryModal/children/uploadsGalleryContent.tsx +++ b/src/components/uploadsGalleryModal/children/uploadsGalleryContent.tsx @@ -1,7 +1,15 @@ import { proxifyImageSrc } from '@ecency/render-helper'; import React, { useEffect, useRef, useState } from 'react'; import { useIntl } from 'react-intl'; -import { ActivityIndicator, Alert, Keyboard, Platform, Text, TouchableOpacity, View } from 'react-native'; +import { + ActivityIndicator, + Alert, + Keyboard, + Platform, + Text, + TouchableOpacity, + View, +} from 'react-native'; import { View as AnimatedView } from 'react-native-animatable'; import Animated, { Easing } from 'react-native-reanimated'; import EStyleSheet from 'react-native-extended-stylesheet'; @@ -9,22 +17,25 @@ import FastImage from 'react-native-fast-image'; import { FlatList } from 'react-native-gesture-handler'; import { Icon, IconButton } from '../..'; import { UploadedMedia } from '../../../models'; -import styles, { COMPACT_HEIGHT, EXPANDED_HEIGHT, MAX_HORIZONTAL_THUMBS } from '../children/uploadsGalleryModalStyles'; +import styles, { + COMPACT_HEIGHT, + EXPANDED_HEIGHT, + MAX_HORIZONTAL_THUMBS, +} from './uploadsGalleryModalStyles'; +import { useMediaDeleteMutation } from '../../../providers/queries'; type Props = { - insertedMediaUrls: string[], - mediaUploads: any[], - indices: Map - isLoading: boolean, - isAddingToUploads: boolean, - getMediaUploads: () => void, - deleteMedia: (ids: string) => Promise, - insertMedia: (map: Map) => void - handleOpenGallery: (addToUploads?: boolean) => void, - handleOpenCamera: () => void, - handleOpenForUpload: () => void, -} - + insertedMediaUrls: string[]; + mediaUploads: any[]; + indices: Map; + isAddingToUploads: boolean; + getMediaUploads: () => void; + deleteMedia: (ids: string) => Promise; + insertMedia: (map: Map) => void; + handleOpenGallery: (addToUploads?: boolean) => void; + handleOpenCamera: () => void; + handleOpenForUpload: () => void; +}; const UploadsGalleryContent = ({ insertedMediaUrls, @@ -36,75 +47,66 @@ const UploadsGalleryContent = ({ handleOpenGallery, handleOpenCamera, }: Props) => { + const intl = useIntl(); - const intl = useIntl() + const deleteMediaMutation = useMediaDeleteMutation(); const [deleteIds, setDeleteIds] = useState([]); - const [isDeleting, setIsDeleting] = useState(false); const [isDeleteMode, setIsDeleteMode] = useState(false); const [isExpandedMode, setIsExpandedMode] = useState(false); const animatedHeightRef = useRef(new Animated.Value(COMPACT_HEIGHT)); + const isDeleting = deleteMediaMutation.isLoading useEffect(() => { if (isExpandedMode) { - Keyboard.dismiss() + Keyboard.dismiss(); } - }, [isExpandedMode]) - + }, [isExpandedMode]); + const _deleteMedia = async () => { - - setIsDeleting(true) - try { - for (const i in deleteIds) { - await deleteMedia(deleteIds[i]) + deleteMediaMutation.mutate(deleteIds, { + onSettled: () => { + setIsDeleteMode(false); + setDeleteIds([]); } - } catch (err) { - console.warn("Failed to delete media items") - } - getMediaUploads(); - setIsDeleting(false); - setIsDeleteMode(false); - setDeleteIds([]); - - } - + }); + }; const _onDeletePress = async () => { if (isDeleteMode && deleteIds.length > 0) { - const _onCancelPress = () => { setIsDeleteMode(false); - setDeleteIds([]) - } + setDeleteIds([]); + }; Alert.alert( intl.formatMessage({ id: 'alert.delete' }), intl.formatMessage({ id: 'uploads_modal.confirm_delete' }), - [{ - text: intl.formatMessage({ id: 'alert.cancel' }), - style: 'cancel', - onPress: _onCancelPress - }, { - text: intl.formatMessage({ id: 'alert.confirm' }), - onPress: () => _deleteMedia() - }] - ) + [ + { + text: intl.formatMessage({ id: 'alert.cancel' }), + style: 'cancel', + onPress: _onCancelPress, + }, + { + text: intl.formatMessage({ id: 'alert.confirm' }), + onPress: () => _deleteMedia(), + }, + ], + ); } else { setIsDeleteMode(!isDeleteMode); } - - } - + }; //render list item for snippet and handle actions; - const _renderItem = ({ item, index }: { item: UploadedMedia, index: number }) => { - + const _renderItem = ({ item, index }: { item: UploadedMedia; index: number }) => { const _onPress = () => { if (isDeleteMode) { - const idIndex = deleteIds.indexOf(item._id) + const idIndex = deleteIds.indexOf(item._id); if (idIndex >= 0) { deleteIds.splice(idIndex, 1); } else { @@ -112,46 +114,37 @@ const UploadsGalleryContent = ({ } setDeleteIds([...deleteIds]); } else { - insertMedia(new Map([[index, true]])) + insertMedia(new Map([[index, true]])); } - } + }; const thumbUrl = proxifyImageSrc(item.url, 600, 500, Platform.OS === 'ios' ? 'match' : 'webp'); let isInsertedTimes = 0; - insertedMediaUrls.forEach(url => isInsertedTimes += url === item.url ? 1 : 0); + insertedMediaUrls.forEach((url) => (isInsertedTimes += url === item.url ? 1 : 0)); const isToBeDeleted = deleteIds.indexOf(item._id) >= 0; const transformStyle = { - transform: isToBeDeleted ? - [{ scaleX: 0.7 }, { scaleY: 0.7 }] : [] - } + transform: isToBeDeleted ? [{ scaleX: 0.7 }, { scaleY: 0.7 }] : [], + }; - const _renderMinus = () => ( + const _renderMinus = () => isDeleteMode && ( - + - ) - ) + ); - - const _renderCounter = () => ( - isInsertedTimes > 0 && !isDeleteMode && ( - + const _renderCounter = () => + isInsertedTimes > 0 && + !isDeleteMode && ( + {isInsertedTimes} - ) - ) + ); return ( @@ -164,20 +157,22 @@ const UploadsGalleryContent = ({ {_renderMinus()} - ) + ); }; - const _renderSelectButton = (iconName: string, text: string, onPress: () => void) => { return ( - { onPress && onPress() }}> + { + onPress && onPress(); + }} + > - @@ -192,37 +187,39 @@ const UploadsGalleryContent = ({ {text} - ) - } - + ); + }; const _renderHeaderContent = () => ( - { + {_renderSelectButton('image', 'Gallery', handleOpenGallery)} {_renderSelectButton('camera', 'Camera', handleOpenCamera)} - } + { handleOpenGallery(true) }} + onPress={() => { + handleOpenGallery(true); + }} /> { setIsDeleteMode(!isDeleteMode); setDeleteIds([]) }} + onPress={() => { + setIsDeleteMode(!isDeleteMode); + setDeleteIds([]); + }} /> @@ -232,18 +229,18 @@ const UploadsGalleryContent = ({ )} - {isExpandedMode && _renderExpansionButton()} {isExpandedMode && _renderDeleteButton()} - - ) + ); //render empty list placeholder const _renderEmptyContent = () => { return ( <> - {intl.formatMessage({ id: 'uploads_modal.label_no_images' })} + + {intl.formatMessage({ id: 'uploads_modal.label_no_images' })} + ); }; @@ -259,82 +256,74 @@ const UploadsGalleryContent = ({ Animated.timing(animatedHeightRef.current, { toValue: isExpandedMode ? COMPACT_HEIGHT : EXPANDED_HEIGHT, duration: 300, - easing: Easing.inOut(Easing.cubic) + easing: Easing.inOut(Easing.cubic), }).start(() => { - setIsExpandedMode(!isExpandedMode) - }) + setIsExpandedMode(!isExpandedMode); + }); }} /> - ) - + ); const _renderDeleteButton = () => { if (deleteIds.length > 0) { - return ( - isExpandedMode ? ( - - + return isExpandedMode ? ( + 0 ? '$primaryBlack' : '$primaryBlack')} + size={32} + onPress={_onDeletePress} + isLoading={isDeleting} + /> + ) : ( + 0 ? 'slideInRight' : 'slideOutRight'} + duration={300} + style={styles.deleteButtonContainer} + > 0 ? '$primaryBlack' : '$primaryBlack')} - size={32} + name="delete-outline" + disabled={isDeleting} + size={28} onPress={_onDeletePress} isLoading={isDeleting} /> - - - ) : ( - 0 ? 'slideInRight' : 'slideOutRight' - } - duration={300} - style={styles.deleteButtonContainer} - > - - - ) - ) + + ); } return null; - } - - + }; return ( `item_${item.url}`} renderItem={_renderItem} style={{ flex: 1 }} - contentContainerStyle={isExpandedMode ? styles.gridContentContainer : styles.listContentContainer} + contentContainerStyle={ + isExpandedMode ? styles.gridContentContainer : styles.listContentContainer + } ListHeaderComponent={_renderHeaderContent} ListEmptyComponent={_renderEmptyContent} ListFooterComponent={!isExpandedMode && mediaUploads.length > 0 && _renderExpansionButton} extraData={deleteIds} horizontal={!isExpandedMode} numColumns={isExpandedMode ? 2 : 1} - keyboardShouldPersistTaps='always' + keyboardShouldPersistTaps="always" /> {!isExpandedMode && _renderDeleteButton()} - - ) -} + ); +}; -export default UploadsGalleryContent \ No newline at end of file +export default UploadsGalleryContent; diff --git a/src/components/uploadsGalleryModal/container/uploadsGalleryModal.tsx b/src/components/uploadsGalleryModal/container/uploadsGalleryModal.tsx index dca5a23ce..631f12464 100644 --- a/src/components/uploadsGalleryModal/container/uploadsGalleryModal.tsx +++ b/src/components/uploadsGalleryModal/container/uploadsGalleryModal.tsx @@ -1,17 +1,15 @@ import React, { forwardRef, useEffect, useImperativeHandle, useRef, useState } from 'react'; import { useIntl } from 'react-intl'; import { Alert } from 'react-native'; +import ImagePicker, { Image } from 'react-native-image-crop-picker'; import bugsnapInstance from '../../../config/bugsnag'; -import { addImage, deleteImage, getImages, uploadImage } from '../../../providers/ecency/ecency'; +import { getImages } from '../../../providers/ecency/ecency'; import UploadsGalleryContent from '../children/uploadsGalleryContent'; -import ImagePicker from 'react-native-image-crop-picker'; -import { signImage } from '../../../providers/hive/dhive'; + import { useAppDispatch, useAppSelector } from '../../../hooks'; import { delay, extractFilenameFromPath } from '../../../utils/editor'; -import { toastNotification } from '../../../redux/actions/uiAction'; import showLoginAlert from '../../../utils/showLoginAlert'; - - +import { useMediaQuery, useMediaUploadMutation } from '../../../providers/queries'; export interface UploadsGalleryModalRef { showModal: () => void; @@ -20,257 +18,268 @@ export interface UploadsGalleryModalRef { export enum MediaInsertStatus { UPLOADING = 'UPLOADING', READY = 'READY', - FAILED = 'FAILED' + FAILED = 'FAILED', } export interface MediaInsertData { - url: string, - filename?: string, - text: string, - status: MediaInsertStatus + url: string; + filename?: string; + text: string; + status: MediaInsertStatus; } interface UploadsGalleryModalProps { - insertedMediaUrls: string[], + insertedMediaUrls: string[]; paramFiles: any[]; username: string; isEditing: boolean; isPreviewActive: boolean; + hideToolbarExtension: () => void; handleMediaInsert: (data: Array) => void; setIsUploading: (status: boolean) => void; } -export const UploadsGalleryModal = forwardRef(({ - insertedMediaUrls, - paramFiles, - username, - isEditing, - isPreviewActive, - handleMediaInsert, - setIsUploading -}: UploadsGalleryModalProps, ref) => { - const intl = useIntl(); - const dispatch = useAppDispatch(); +export const UploadsGalleryModal = forwardRef( + ( + { + insertedMediaUrls, + paramFiles, + username, + isEditing, + isPreviewActive, + hideToolbarExtension, + handleMediaInsert, + setIsUploading, + }: UploadsGalleryModalProps, + ref, + ) => { + const intl = useIntl(); + const dispatch = useAppDispatch(); - const pendingInserts = useRef([]); + const mediaQuery = useMediaQuery(); + const mediaUploadMutation = useMediaUploadMutation(); - const [mediaUploads, setMediaUploads] = useState([]); - const [isLoading, setIsLoading] = useState(false); - const [showModal, setShowModal] = useState(false); - const [isAddingToUploads, setIsAddingToUploads] = useState(false); + const pendingInserts = useRef([]); + const [mediaUploads, setMediaUploads] = useState([]); + const [showModal, setShowModal] = useState(false); + const [isAddingToUploads, setIsAddingToUploads] = useState(false); - const isLoggedIn = useAppSelector(state => state.application.isLoggedIn); - const pinCode = useAppSelector(state => state.application.pin); - const currentAccount = useAppSelector(state => state.account.currentAccount); + const isLoggedIn = useAppSelector((state) => state.application.isLoggedIn); + const pinCode = useAppSelector((state) => state.application.pin); + const currentAccount = useAppSelector((state) => state.account.currentAccount); + useImperativeHandle(ref, () => ({ + toggleModal: (value: boolean) => { + if (!isLoggedIn) { + showLoginAlert({ intl }); + return; + } - useImperativeHandle(ref, () => ({ - toggleModal: (value:boolean) => { - if (!isLoggedIn) { - showLoginAlert({ intl }); - return; - } + if (value === showModal) { + return; + } - if(value === showModal){ - return; - } - - if (value) { - _getMediaUploads(); - } - setShowModal(value); - }, - })); + if (value) { + _getMediaUploads(); + } + setShowModal(value); + }, + })); - useEffect(() => { - if (paramFiles) { - console.log('files : ', paramFiles); + useEffect(() => { + if (paramFiles) { + console.log('files : ', paramFiles); - //delay is a workaround to let editor ready before initiating uploads on mount - delay(500).then(() => { - const _mediaItems = paramFiles.map((el) => { - if (el.filePath && el.fileName) { - const _media = { - path: el.filePath, - mime: el.mimeType, - filename: el.fileName, - }; + //delay is a workaround to let editor ready before initiating uploads on mount + delay(500).then(() => { + const _mediaItems = paramFiles.map((el) => { + if (el.filePath && el.fileName) { + const _media = { + path: el.filePath, + mime: el.mimeType, + filename: el.fileName, + }; - return _media; - } - return null + return _media; + } + return null; + }); + + _handleMediaOnSelected(_mediaItems, true); }); - - _handleMediaOnSelected(_mediaItems, true) - }) - } - - }, [paramFiles]) - - - useEffect(() => { - _getMediaUploads(); - }, []); - - useEffect(() => { - if (!isEditing && pendingInserts.current.length) { - handleMediaInsert(pendingInserts.current); - pendingInserts.current = [] - } - }, [isEditing]) - - - const _handleOpenImagePicker = (addToUploads?: boolean) => { - ImagePicker.openPicker({ - includeBase64: true, - multiple: true, - mediaType: 'photo', - smartAlbums: ['UserLibrary', 'Favorites', 'PhotoStream', 'Panoramas', 'Bursts'], - }) - .then((images) => { - if (images && !Array.isArray(images)) { - images = [images]; - } - _handleMediaOnSelected(images, !addToUploads); - - }) - .catch((e) => { - _handleMediaOnSelectFailure(e); - }); - }; - - - const _handleOpenCamera = () => { - ImagePicker.openCamera({ - includeBase64: true, - mediaType: 'photo', - }) - .then((image) => { - _handleMediaOnSelected([image], true); - }) - .catch((e) => { - _handleMediaOnSelectFailure(e); - }); - }; - - - - const _handleMediaOnSelected = async (media: any[], shouldInsert: boolean) => { - - try { - if (!media || media.length == 0) { - throw new Error("New media items returned") } + }, [paramFiles]); - if (shouldInsert) { - setShowModal(false); - media.forEach((element, index) => { - if (element) { - media[index].filename = element.filename || extractFilenameFromPath({ path: element.path, mimeType: element.mime }); - handleMediaInsert([{ - filename: element.filename, - url: '', - text: '', - status: MediaInsertStatus.UPLOADING - }]) + useEffect(() => { + if (!isEditing && pendingInserts.current.length) { + handleMediaInsert(pendingInserts.current); + pendingInserts.current = []; + } + }, [isEditing]); + + const _handleOpenImagePicker = (addToUploads?: boolean) => { + ImagePicker.openPicker({ + includeBase64: true, + multiple: true, + mediaType: 'photo', + smartAlbums: ['UserLibrary', 'Favorites', 'PhotoStream', 'Panoramas', 'Bursts'], + }) + .then((images) => { + if (images && !Array.isArray(images)) { + images = [images]; } + _handleMediaOnSelected(images, !addToUploads); }) - } + .catch((e) => { + _handleMediaOnSelectFailure(e); + }); + }; - for (let index = 0; index < media.length; index++) { - const element = media[index]; - if (element) { - await _uploadImage(element, { shouldInsert }); + const _handleOpenCamera = () => { + ImagePicker.openCamera({ + includeBase64: true, + mediaType: 'photo', + }) + .then((image) => { + _handleMediaOnSelected([image], true); + }) + .catch((e) => { + _handleMediaOnSelectFailure(e); + }); + }; + + const _handleMediaOnSelected = async (media: Image[], shouldInsert: boolean) => { + try { + if (!media || media.length == 0) { + throw new Error('New media items returned'); } - } - } catch (error) { - console.log("Failed to upload image", error); - - bugsnapInstance.notify(error); - } - - }; - - - - const _uploadImage = async (media, { shouldInsert } = { shouldInsert: false }) => { - - if (!isLoggedIn) return; - try { - setIsLoading(true); - if (setIsUploading) { - setIsUploading(true) - } - if (!shouldInsert) { - setIsAddingToUploads(true); - } - - let sign = await signImage(media, currentAccount, pinCode); - - let MAX_RETRY = 2; - - let res: any = null; - - for (var i = 0; i < MAX_RETRY; i++) { - res = await uploadImage(media, currentAccount.name, sign); - if (res && res.data) { - break; + if (shouldInsert) { + setShowModal(false); + hideToolbarExtension(); + media.forEach((element, index) => { + if (element) { + media[index].filename = + element.filename || + extractFilenameFromPath({ path: element.path, mimeType: element.mime }); + handleMediaInsert([ + { + filename: element.filename, + url: '', + text: '', + status: MediaInsertStatus.UPLOADING, + }, + ]); + } + }); } - } - if (res.data && res.data.url) { + for (let index = 0; index < media.length; index++) { + const element = media[index]; + if (element) { + await _uploadImage(element, { shouldInsert }); + } + } + } catch (error) { + console.log('Failed to upload image', error); + + bugsnapInstance.notify(error); + } + }; + + const _uploadImage = async (media, { shouldInsert } = { shouldInsert: false }) => { + if (!isLoggedIn) return; + try { + if (setIsUploading) { + setIsUploading(true); + } + if (!shouldInsert) { + setIsAddingToUploads(true); + } + + await mediaUploadMutation.mutateAsync({ + media, + addToUploads: !shouldInsert + }, { + onSuccess: (data) => { + console.log('upload successfully', data, media, shouldInsert) + if (data && data.url && shouldInsert) { + _handleMediaInsertion({ + filename: media.filename, + url: data.url, + text: '', + status: MediaInsertStatus.READY, + }); + } + }, + onSettled: () => { + if (setIsUploading) { + setIsUploading(false); + } + setIsAddingToUploads(false); + }, + onError: (err) => { throw err }, + }) + + } catch (error) { + console.log('error while uploading image : ', error); + + if (error.toString().includes('code 413')) { + Alert.alert( + intl.formatMessage({ + id: 'alert.fail', + }), + intl.formatMessage({ + id: 'alert.payloadTooLarge', + }), + ); + } else if (error.toString().includes('code 429')) { + Alert.alert( + intl.formatMessage({ + id: 'alert.fail', + }), + intl.formatMessage({ + id: 'alert.quotaExceeded', + }), + ); + } else if (error.toString().includes('code 400')) { + Alert.alert( + intl.formatMessage({ + id: 'alert.fail', + }), + intl.formatMessage({ + id: 'alert.invalidImage', + }), + ); + } else { + Alert.alert( + intl.formatMessage({ + id: 'alert.fail', + }), + error.message || error.toString(), + ); + } if (shouldInsert) { _handleMediaInsertion({ filename: media.filename, - url: res.data.url, + url: '', text: '', - status: MediaInsertStatus.READY - }) - } else { - _addUploadedImageToGallery(res.data.url) + status: MediaInsertStatus.FAILED, + }); } - - setIsLoading(false); - if (setIsUploading) { - setIsUploading(false) - } - - - } else if (res.error) { - throw res.error } + }; - } catch (error) { - console.log('error while uploading image : ', error); - - if (error.toString().includes('code 413')) { + const _handleMediaOnSelectFailure = (error) => { + if (error.code === 'E_PERMISSION_MISSING') { Alert.alert( intl.formatMessage({ - id: 'alert.fail', + id: 'alert.permission_denied', }), intl.formatMessage({ - id: 'alert.payloadTooLarge', - }), - ); - } else if (error.toString().includes('code 429')) { - Alert.alert( - intl.formatMessage({ - id: 'alert.fail', - }), - intl.formatMessage({ - id: 'alert.quotaExceeded', - }), - ); - } else if (error.toString().includes('code 400')) { - Alert.alert( - intl.formatMessage({ - id: 'alert.fail', - }), - intl.formatMessage({ - id: 'alert.invalidImage', + id: 'alert.permission_text', }), ); } else { @@ -278,141 +287,64 @@ export const UploadsGalleryModal = forwardRef(({ intl.formatMessage({ id: 'alert.fail', }), - error.message || error.toString(), + error.message || JSON.stringify(error), ); } + }; - if (shouldInsert) { - _handleMediaInsertion({ - filename: media.filename, - url: '', + const _handleMediaInsertion = (data: MediaInsertData) => { + if (isEditing) { + pendingInserts.current.push(data); + } else if (handleMediaInsert) { + handleMediaInsert([data]); + } + }; + + + + //fetch images from server + const _getMediaUploads = async () => { + try { + if (username) { + console.log('getting images for: ' + username); + const images = await getImages(); + console.log('images received', images); + setMediaUploads(images || []); + } + } catch (err) { + console.warn('Failed to get images'); + } + setIsAddingToUploads(false); + }; + + //inserts media items in post body + const _insertMedia = async (map: Map) => { + const data: MediaInsertData[] = []; + for (const index of map.keys()) { + console.log(index); + const item = mediaUploads[index]; + data.push({ + url: item.url, text: '', - status: MediaInsertStatus.FAILED - }) + status: MediaInsertStatus.READY, + }); } + handleMediaInsert(data); + }; - setIsLoading(false); - if (setIsUploading) { - setIsUploading(false) - } - setIsAddingToUploads(false); - } - - }; - - - const _handleMediaOnSelectFailure = (error) => { - - if (error.code === 'E_PERMISSION_MISSING') { - Alert.alert( - intl.formatMessage({ - id: 'alert.permission_denied', - }), - intl.formatMessage({ - id: 'alert.permission_text', - }), - ); - } else { - Alert.alert( - intl.formatMessage({ - id: 'alert.fail', - }), - error.message || JSON.stringify(error), - ); - } - }; - - - const _handleMediaInsertion = (data: MediaInsertData) => { - if (isEditing) { - pendingInserts.current.push(data); - } else if (handleMediaInsert) { - handleMediaInsert([data]); - } - } - - - //save image to user gallery - const _addUploadedImageToGallery = async (url: string) => { - try { - console.log("adding image to gallery", username, url) - setIsLoading(true); - await addImage(url); - await _getMediaUploads(); - setIsLoading(false); - } catch (err) { - console.warn("Failed to add image to gallery, could possibly a duplicate image", err) - setIsLoading(false); - setIsAddingToUploads(false); - } - - } - - - // remove image data from user's gallery - const _deleteMedia = async (id: string) => { - try { - await deleteImage(id) - return true - } catch (err) { - console.warn("failed to remove image from gallery", err) - dispatch(toastNotification(intl.formatMessage({ id: 'uploads_modal.delete_failed' }))) - return false - } - } - - - //fetch images from server - const _getMediaUploads = async () => { - try { - if (username) { - setIsLoading(true); - console.log("getting images for: " + username) - const images = await getImages() - console.log("images received", images) - setMediaUploads(images || []); - setIsLoading(false); - } - } catch (err) { - console.warn("Failed to get images") - setIsLoading(false); - } - setIsAddingToUploads(false); - } - - //inserts media items in post body - const _insertMedia = async (map: Map) => { - - const data: MediaInsertData[] = [] - for (const index of map.keys()) { - console.log(index) - const item = mediaUploads[index] - data.push({ - url: item.url, - text: "", - status: MediaInsertStatus.READY - }) - - } - handleMediaInsert(data) - } - - return ( - !isPreviewActive && showModal && ( - - ) - ); -}); - - + return ( + !isPreviewActive && + showModal && ( + + ) + ); + }, +); diff --git a/src/providers/ecency/ecency.ts b/src/providers/ecency/ecency.ts index 531903960..df3f887b6 100644 --- a/src/providers/ecency/ecency.ts +++ b/src/providers/ecency/ecency.ts @@ -15,6 +15,7 @@ import { import { CommentHistoryItem, LatestMarketPrices, + MediaItem, NotificationFilters, ReceivedVestingShare, Referral, @@ -672,7 +673,7 @@ export const addImage = async (url: string) => { try { const data = { url }; const response = await ecencyApi.post('/private-api/images-add', data); - return response.data; + return response.data as MediaItem[]; } catch (error) { console.warn('Failed to add image', error); bugsnagInstance.notify(error); @@ -708,10 +709,10 @@ export const uploadImage = async (media, username, sign, uploadProgress = null) if (!res || !res.data) { throw new Error('Returning response missing media data'); } - return res; + return res.data; } catch (error) { console.warn('Image upload failed', error); - return { error }; + throw error; } }; diff --git a/src/providers/ecency/ecency.types.ts b/src/providers/ecency/ecency.types.ts index 79ccb9e7e..8f4f5951e 100644 --- a/src/providers/ecency/ecency.types.ts +++ b/src/providers/ecency/ecency.types.ts @@ -7,6 +7,13 @@ export interface ReceivedVestingShare { timestamp: string; } +export interface MediaItem { + _id: string; + url: string; + created: string; + timestamp: number; +} + export interface Snippet { id: string; title: string; @@ -59,20 +66,20 @@ export interface CommentHistoryItem { } export enum ScheduledPostStatus { - PENDING = 1, - POSTPONED = 2, - PUBLISHED = 3, - ERROR = 4, + PENDING = 1, + POSTPONED = 2, + PUBLISHED = 3, + ERROR = 4, } export enum NotificationFilters { - ACTIVITIES = "activities", - RVOTES = "rvotes", - MENTIONS = "mentions", - FOLLOWS = "follows", - REPLIES = "replies", - REBLOGS = "reblogs", - TRANFERS = "transfers", - DELEGATIONS = "delegations", - FAVOURITES = "nfavorites" + ACTIVITIES = "activities", + RVOTES = "rvotes", + MENTIONS = "mentions", + FOLLOWS = "follows", + REPLIES = "replies", + REBLOGS = "reblogs", + TRANFERS = "transfers", + DELEGATIONS = "delegations", + FAVOURITES = "nfavorites" } diff --git a/src/providers/queries/editorQueries.ts b/src/providers/queries/editorQueries.ts index 591f44314..fca42f030 100644 --- a/src/providers/queries/editorQueries.ts +++ b/src/providers/queries/editorQueries.ts @@ -1,90 +1,212 @@ import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query'; import { useIntl } from 'react-intl'; -import { useAppDispatch } from '../../hooks'; +import { Image } from 'react-native-image-crop-picker'; +import { useAppDispatch, useAppSelector } from '../../hooks'; import { toastNotification } from '../../redux/actions/uiAction'; -import { addFragment, deleteFragment, getFragments, updateFragment } from '../ecency/ecency'; -import { Snippet } from '../ecency/ecency.types'; +import { + addFragment, + addImage, + deleteFragment, + deleteImage, + getFragments, + getImages, + updateFragment, + uploadImage, +} from '../ecency/ecency'; +import { MediaItem, Snippet } from '../ecency/ecency.types'; +import { signImage } from '../hive/dhive'; import QUERIES from './queryKeys'; interface SnippetMutationVars { - id: string | null; - title: string; - body: string; + id: string | null; + title: string; + body: string; } -export const useSnippetsQuery = () => { - const intl = useIntl(); - const dispatch = useAppDispatch(); - return useQuery([QUERIES.SNIPPETS.GET], getFragments, { - onError: () => { - dispatch(toastNotification(intl.formatMessage({ id: 'alert.fail' }))); - }, - }); +interface MediaUploadVars { + media: Image; + addToUploads: boolean; +} + + + +/** GET QUERIES **/ + +export const useMediaQuery = () => { + const intl = useIntl(); + const dispatch = useAppDispatch(); + return useQuery([QUERIES.MEDIA.GET], getImages, { + initialData: [], + onError: () => { + dispatch(toastNotification(intl.formatMessage({ id: 'alert.fail' }))); + }, + }); }; +export const useSnippetsQuery = () => { + const intl = useIntl(); + const dispatch = useAppDispatch(); + return useQuery([QUERIES.SNIPPETS.GET], getFragments, { + initialData: [], + onError: () => { + dispatch(toastNotification(intl.formatMessage({ id: 'alert.fail' }))); + }, + }); +}; + + + + +/** ADD UPDATE MUTATIONS **/ + + +export const useAddToUploadsMutation = () => { + const intl = useIntl(); + const dispatch = useAppDispatch(); + const queryClient = useQueryClient(); + + return useMutation(addImage, { + retry: 3, + onSuccess: (data) => { + queryClient.setQueryData([QUERIES.MEDIA.GET], data); + }, + onError: (error) => { + if (error.toString().includes('code 409')) { + //means image ware already preset, refresh to get updated order + queryClient.invalidateQueries([QUERIES.MEDIA.GET]); + } else { + dispatch(toastNotification(intl.formatMessage({ id: 'alert.fail' }))); + } + + } + }) +} + + +export const useMediaUploadMutation = () => { + const intl = useIntl(); + const dispatch = useAppDispatch(); + + const addToUploadsMutation = useAddToUploadsMutation(); + + const currentAccount = useAppSelector(state => state.account.currentAccount); + const pinCode = useAppSelector(state => state.application.pin); + + return useMutation( + async ({ media }) => { + console.log('uploading media', media); + let sign = await signImage(media, currentAccount, pinCode); + return await uploadImage(media, currentAccount.name, sign); + }, + { + retry: 3, + onSuccess: (response, { addToUploads }) => { + if (addToUploads && response && response.url) { + console.log('adding image to gallery', response.url); + addToUploadsMutation.mutate(response.url); + } + }, + onError: () => { + dispatch(toastNotification(intl.formatMessage({ id: 'alert.fail' }))); + }, + }, + ); +} + export const useSnippetsMutation = () => { - const intl = useIntl(); - const dispatch = useAppDispatch(); - const queryClient = useQueryClient(); + const intl = useIntl(); + const dispatch = useAppDispatch(); + const queryClient = useQueryClient(); - return useMutation( - async (vars) => { - console.log('going to add/update snippet', vars); - if (vars.id) { - const response = await updateFragment(vars.id, vars.title, vars.body); - return response; - } else { - const response = await addFragment(vars.title, vars.body); - return response; - } - }, - { - onMutate: (vars) => { - console.log('mutate snippets for add/update', vars); + return useMutation( + async (vars) => { + console.log('going to add/update snippet', vars); + if (vars.id) { + const response = await updateFragment(vars.id, vars.title, vars.body); + return response; + } else { + const response = await addFragment(vars.title, vars.body); + return response; + } + }, + { + onMutate: (vars) => { + console.log('mutate snippets for add/update', vars); - const _newItem = { - id: vars.id, - title: vars.title, - body: vars.body, - created: new Date().toDateString(), - modified: new Date().toDateString(), - } as Snippet; + const _newItem = { + id: vars.id, + title: vars.title, + body: vars.body, + created: new Date().toDateString(), + modified: new Date().toDateString(), + } as Snippet; - const data = queryClient.getQueryData([QUERIES.SNIPPETS.GET]); + const data = queryClient.getQueryData([QUERIES.SNIPPETS.GET]); - let _newData: Snippet[] = data ? [...data] : []; - if (vars.id) { - const snipIndex = _newData.findIndex((item) => vars.id === item.id); - _newData[snipIndex] = _newItem; - } else { - _newData = [_newItem, ..._newData]; - } + let _newData: Snippet[] = data ? [...data] : []; + if (vars.id) { + const snipIndex = _newData.findIndex((item) => vars.id === item.id); + _newData[snipIndex] = _newItem; + } else { + _newData = [_newItem, ..._newData]; + } - queryClient.setQueryData([QUERIES.SNIPPETS.GET], _newData); - }, - onSuccess: (data) => { - console.log('added/updated snippet', data); - queryClient.invalidateQueries([QUERIES.SNIPPETS.GET]); - }, - onError: () => { - dispatch(toastNotification(intl.formatMessage({ id: 'snippets.message_failed' }))); - }, - }, - ); + queryClient.setQueryData([QUERIES.SNIPPETS.GET], _newData); + }, + onSuccess: (data) => { + console.log('added/updated snippet', data); + queryClient.invalidateQueries([QUERIES.SNIPPETS.GET]); + }, + onError: () => { + dispatch(toastNotification(intl.formatMessage({ id: 'snippets.message_failed' }))); + }, + }, + ); }; + + +/** DELETE MUTATIONS **/ + +export const useMediaDeleteMutation = () => { + const queryClient = useQueryClient(); + const dispatch = useAppDispatch(); + const intl = useIntl(); + return useMutation(async (deleteIds) => { + for (const i in deleteIds) { + await deleteImage(deleteIds[i]); + } + return deleteIds; + }, { + retry: 3, + onSuccess: (deleteIds) => { + console.log('Success media deletion delete', deleteIds); + const data: MediaItem[] | undefined = queryClient.getQueryData([QUERIES.MEDIA.GET]); + if (data) { + const _newData = data.filter((item) => (!deleteIds.includes(item._id))) + queryClient.setQueryData([QUERIES.MEDIA.GET], _newData); + } + }, + onError: () => { + dispatch(toastNotification(intl.formatMessage({ id: 'uploads_modal.delete_failed' }))); + queryClient.invalidateQueries([QUERIES.MEDIA.GET]); + }, + }); +}; + + export const useSnippetDeleteMutation = () => { - const queryClient = useQueryClient(); - const dispatch = useAppDispatch(); - const intl = useIntl(); - return useMutation(deleteFragment, { - retry: 3, - onSuccess: (data) => { - console.log('Success scheduled post delete', data); - queryClient.setQueryData([QUERIES.SNIPPETS.GET], data); - }, - onError: () => { - dispatch(toastNotification(intl.formatMessage({ id: 'alert.fail' }))); - }, - }); + const queryClient = useQueryClient(); + const dispatch = useAppDispatch(); + const intl = useIntl(); + return useMutation(deleteFragment, { + retry: 3, + onSuccess: (data) => { + console.log('Success scheduled post delete', data); + queryClient.setQueryData([QUERIES.SNIPPETS.GET], data); + }, + onError: () => { + dispatch(toastNotification(intl.formatMessage({ id: 'alert.fail' }))); + }, + }); }; diff --git a/src/providers/queries/queryKeys.ts b/src/providers/queries/queryKeys.ts index 3241e2d49..613edb816 100644 --- a/src/providers/queries/queryKeys.ts +++ b/src/providers/queries/queryKeys.ts @@ -5,12 +5,15 @@ const QUERIES = { SCHEDULES: { GET: 'QUERY_GET_SCHEDULES', }, - NOTIFICATIONS:{ - GET: 'QERUY_GET_NOTIFICATIONS' + NOTIFICATIONS: { + GET: 'QERUY_GET_NOTIFICATIONS', }, SNIPPETS: { GET: 'QUERY_GET_SNIPPETS', - } + }, + MEDIA: { + GET: 'QUERY_GET_UPLOADS', + }, }; export default QUERIES;