Merge pull request #2497 from ecency/nt/media-use-query

Nt/media use query
This commit is contained in:
Feruz M 2022-10-06 21:34:37 +05:00 committed by GitHub
commit 6e203a73b1
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 631 additions and 576 deletions

View File

@ -182,6 +182,7 @@ export const EditorToolbar = ({
paramFiles={paramFiles}
isEditing={isEditing}
username={currentAccount.username}
hideToolbarExtension={_hideExtension}
handleMediaInsert={handleMediaInsert}
setIsUploading={setIsUploading} />
</View>

View File

@ -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<number, boolean>
isLoading: boolean,
isAddingToUploads: boolean,
getMediaUploads: () => void,
deleteMedia: (ids: string) => Promise<boolean>,
insertMedia: (map: Map<number, boolean>) => void
handleOpenGallery: (addToUploads?: boolean) => void,
handleOpenCamera: () => void,
handleOpenForUpload: () => void,
}
insertedMediaUrls: string[];
mediaUploads: any[];
indices: Map<number, boolean>;
isAddingToUploads: boolean;
getMediaUploads: () => void;
deleteMedia: (ids: string) => Promise<boolean>;
insertMedia: (map: Map<number, boolean>) => 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<string[]>([]);
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 && (
<AnimatedView
animation='zoomIn'
duration={300}
style={styles.minusContainer}>
<AnimatedView animation="zoomIn" duration={300} style={styles.minusContainer}>
<Icon
color={EStyleSheet.value('$pureWhite')}
iconType="MaterialCommunityIcons"
name={'minus'}
name="minus"
size={20}
/>
</AnimatedView>
)
)
);
const _renderCounter = () => (
isInsertedTimes > 0 && !isDeleteMode && (
<AnimatedView
animation='zoomIn'
duration={300}
style={styles.counterContainer}>
const _renderCounter = () =>
isInsertedTimes > 0 &&
!isDeleteMode && (
<AnimatedView animation="zoomIn" duration={300} style={styles.counterContainer}>
<Text style={styles.counterText}>{isInsertedTimes}</Text>
</AnimatedView>
)
)
);
return (
<TouchableOpacity onPress={_onPress} disabled={isDeleting}>
@ -164,20 +157,22 @@ const UploadsGalleryContent = ({
{_renderMinus()}
</View>
</TouchableOpacity>
)
);
};
const _renderSelectButton = (iconName: string, text: string, onPress: () => void) => {
return (
<TouchableOpacity onPress={() => { onPress && onPress() }}>
<TouchableOpacity
onPress={() => {
onPress && onPress();
}}
>
<View style={styles.selectButton}>
<View style={styles.selectBtnPlus}>
<Icon
color={EStyleSheet.value('$primaryBackgroundColor')}
iconType="FontAwesome5"
name={'plus-circle'}
name="plus-circle"
size={12}
/>
</View>
@ -192,37 +187,39 @@ const UploadsGalleryContent = ({
<Text style={styles.selectButtonLabel}>{text}</Text>
</View>
</TouchableOpacity>
)
}
);
};
const _renderHeaderContent = () => (
<View style={{ ...styles.buttonsContainer, paddingVertical: isExpandedMode ? 8 : 0 }}>
{<View style={styles.selectButtonsContainer} >
<View style={styles.selectButtonsContainer}>
{_renderSelectButton('image', 'Gallery', handleOpenGallery)}
{_renderSelectButton('camera', 'Camera', handleOpenCamera)}
</View>}
</View>
<View style={styles.pillBtnContainer}>
<IconButton
style={styles.uploadsActionBtn}
color={EStyleSheet.value('$primaryBlack')}
iconType="MaterialCommunityIcons"
name={'plus'}
name="plus"
size={28}
onPress={() => { handleOpenGallery(true) }}
onPress={() => {
handleOpenGallery(true);
}}
/>
<IconButton
style={{
...styles.uploadsActionBtn,
backgroundColor: isDeleteMode ?
EStyleSheet.value('$iconColor')
: 'transparent'
backgroundColor: isDeleteMode ? EStyleSheet.value('$iconColor') : 'transparent',
}}
color={EStyleSheet.value('$primaryBlack')}
iconType="MaterialCommunityIcons"
name={'minus'}
name="minus"
size={28}
onPress={() => { setIsDeleteMode(!isDeleteMode); setDeleteIds([]) }}
onPress={() => {
setIsDeleteMode(!isDeleteMode);
setDeleteIds([]);
}}
/>
</View>
@ -232,18 +229,18 @@ const UploadsGalleryContent = ({
</View>
)}
{isExpandedMode && _renderExpansionButton()}
{isExpandedMode && _renderDeleteButton()}
</View>
)
);
//render empty list placeholder
const _renderEmptyContent = () => {
return (
<>
<Text style={styles.emptyText}>{intl.formatMessage({ id: 'uploads_modal.label_no_images' })}</Text>
<Text style={styles.emptyText}>
{intl.formatMessage({ id: 'uploads_modal.label_no_images' })}
</Text>
</>
);
};
@ -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 ? (
<IconButton
style={{
...styles.pillBtnContainer,
backgroundColor: EStyleSheet.value('$primaryRed'),
}}
iconType="MaterialCommunityIcons"
name="delete-outline"
color={EStyleSheet.value(deleteIds.length > 0 ? '$primaryBlack' : '$primaryBlack')}
size={32}
onPress={_onDeletePress}
isLoading={isDeleting}
/>
) : (
<AnimatedView
animation={deleteIds.length > 0 ? 'slideInRight' : 'slideOutRight'}
duration={300}
style={styles.deleteButtonContainer}
>
<IconButton
style={{
...styles.pillBtnContainer,
backgroundColor: EStyleSheet.value('$primaryRed')
}}
style={styles.deleteButton}
color={EStyleSheet.value('$primaryBlack')}
iconType="MaterialCommunityIcons"
name={'delete-outline'}
color={EStyleSheet.value(deleteIds.length > 0 ? '$primaryBlack' : '$primaryBlack')}
size={32}
name="delete-outline"
disabled={isDeleting}
size={28}
onPress={_onDeletePress}
isLoading={isDeleting}
/>
) : (
<AnimatedView
animation={deleteIds.length > 0 ? 'slideInRight' : 'slideOutRight'
}
duration={300}
style={styles.deleteButtonContainer}
>
<IconButton
style={styles.deleteButton}
color={EStyleSheet.value('$primaryBlack')}
iconType="MaterialCommunityIcons"
name={'delete-outline'}
disabled={isDeleting}
size={28}
onPress={_onDeletePress}
isLoading={isDeleting} />
</AnimatedView >
)
)
</AnimatedView>
);
}
return null;
}
};
return (
<Animated.View style={{ ...styles.container, height: animatedHeightRef.current }}>
<FlatList
key={isExpandedMode ? 'vertical_grid' : 'horizontal_list'}
data={mediaUploads.slice(0, !isExpandedMode ? MAX_HORIZONTAL_THUMBS:undefined)}
data={mediaUploads.slice(0, !isExpandedMode ? MAX_HORIZONTAL_THUMBS : undefined)}
keyExtractor={(item) => `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()}
</Animated.View>
)
}
);
};
export default UploadsGalleryContent
export default UploadsGalleryContent;

View File

@ -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<MediaInsertData>) => 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<MediaInsertData[]>([]);
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<MediaInsertData[]>([]);
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<number, boolean>) => {
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<number, boolean>) => {
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 && (
<UploadsGalleryContent
insertedMediaUrls={insertedMediaUrls}
mediaUploads={mediaUploads}
isLoading={isLoading}
isAddingToUploads={isAddingToUploads}
getMediaUploads={_getMediaUploads}
deleteMedia={_deleteMedia}
insertMedia={_insertMedia}
handleOpenCamera={_handleOpenCamera}
handleOpenGallery={_handleOpenImagePicker}
/>
)
);
});
return (
!isPreviewActive &&
showModal && (
<UploadsGalleryContent
insertedMediaUrls={insertedMediaUrls}
mediaUploads={mediaQuery.data.slice()}
isAddingToUploads={isAddingToUploads}
getMediaUploads={_getMediaUploads}
insertMedia={_insertMedia}
handleOpenCamera={_handleOpenCamera}
handleOpenGallery={_handleOpenImagePicker}
/>
)
);
},
);

View File

@ -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;
}
};

View File

@ -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"
}

View File

@ -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<Snippet[]>([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<MediaItem[]>([QUERIES.MEDIA.GET], getImages, {
initialData: [],
onError: () => {
dispatch(toastNotification(intl.formatMessage({ id: 'alert.fail' })));
},
});
};
export const useSnippetsQuery = () => {
const intl = useIntl();
const dispatch = useAppDispatch();
return useQuery<Snippet[]>([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<any[], Error, string>(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<Image, undefined, MediaUploadVars>(
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<Snippet[], undefined, SnippetMutationVars>(
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<Snippet[], undefined, SnippetMutationVars>(
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<Snippet[]>([QUERIES.SNIPPETS.GET]);
const data = queryClient.getQueryData<Snippet[]>([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<string[], undefined, string[]>(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<Snippet[], undefined, string>(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<Snippet[], undefined, string>(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' })));
},
});
};

View File

@ -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;