support for add and update snippets

This commit is contained in:
Nouman Tahir 2022-10-05 19:56:05 +05:00
parent 6291b92bb2
commit 0f194a5670
6 changed files with 197 additions and 133 deletions

View File

@ -4,92 +4,84 @@ import { Alert, KeyboardAvoidingView, Platform, View } from 'react-native';
import { TextInput } from '..';
import { ThemeContainer } from '../../containers';
import { Snippet } from '../../models';
import { addFragment, updateFragment} from '../../providers/ecency/ecency';
import { useSnippetsMutation } from '../../providers/queries';
import { TextButton } from '../buttons';
import Modal from '../modal';
import styles from './snippetEditorModalStyles';
export interface SnippetEditorModalRef {
showNewModal:()=>void;
showEditModal:(snippet:Snippet)=>void;
showNewModal: () => void;
showEditModal: (snippet: Snippet) => void;
}
interface SnippetEditorModalProps {
onSnippetsUpdated:(snips:Array<Snippet>)=>void;
}
const SnippetEditorModal = ({onSnippetsUpdated}: SnippetEditorModalProps, ref) => {
const SnippetEditorModal = ({}, ref) => {
const intl = useIntl();
const titleInputRef = useRef(null);
const bodyInputRef = useRef(null);
const snippetsMutation = useSnippetsMutation();
const [title, setTitle] = useState('');
const [body, setBody] = useState('');
const [snippetId, setSnippetId] = useState<string|null>(null);
const [snippetId, setSnippetId] = useState<string | null>(null);
const [isNewSnippet, setIsNewSnippet] = useState(true);
const [showModal, setShowModal] = useState(false);
const [showModal, setShowModal] = useState(false);
const [titleHeight, setTitleHeight] = useState(0)
useImperativeHandle(ref, () => ({
showNewModal: () => {
setTitle('');
setBody('');
setIsNewSnippet(true);
setShowModal(true);
setTitle('');
setBody('');
setIsNewSnippet(true);
setShowModal(true);
},
showEditModal:(snippet:Snippet)=>{
showEditModal: (snippet: Snippet) => {
setSnippetId(snippet.id);
setTitle(snippet.title);
setBody(snippet.body);
setIsNewSnippet(false);
setShowModal(true);
}
}));
}));
//save snippet based on editor type
const _saveSnippet = async () => {
try{
if(!title || !body){
Alert.alert(intl.formatMessage({id:'snippets.message_incomplete'}));
if (!title || !body) {
Alert.alert(intl.formatMessage({ id: 'snippets.message_incomplete' }));
return;
}
let response = [];
if(!isNewSnippet){
console.log("Updating snippet:", snippetId, title, body)
response = await updateFragment(snippetId, title, body);
console.log("Response from add snippet: ", response)
}else{
console.log("Saving snippet:", title, body)
const res = await addFragment(title, body)
response = res && res.fragments
console.log("Response from add snippet: ", response)
}
setShowModal(false);
onSnippetsUpdated(response);
}catch(err){
Alert.alert(intl.formatMessage({id:'snippets.message_failed'}))
console.warn("Failed to save snippet", err)
}
console.log("Saving snippet:", title, body)
snippetsMutation.mutate({
id: isNewSnippet ? null : snippetId,
title,
body
})
setShowModal(false);
}
const _renderContent = (
<ThemeContainer>
{({isDarkTheme})=>(
<KeyboardAvoidingView
{({ isDarkTheme }) => (
<KeyboardAvoidingView
style={styles.container}
keyboardVerticalOffset={Platform.OS == 'ios' ? 64 : null}
behavior={Platform.OS === 'ios' ? 'padding' : null}
>
<View style={styles.inputContainer}>
<View style={{height:Math.max(35, titleHeight)}}>
<View style={{ height: Math.max(35, titleHeight) }}>
<TextInput
autoFocus={true}
innerRef={titleInputRef}
@ -97,23 +89,23 @@ const SnippetEditorModal = ({onSnippetsUpdated}: SnippetEditorModalProps, ref) =
height={Math.max(35, titleHeight)}
placeholderTextColor={isDarkTheme ? '#526d91' : '#c1c5c7'}
maxLength={250}
placeholder={intl.formatMessage({id:'snippets.placeholder_title'})}
placeholder={intl.formatMessage({ id: 'snippets.placeholder_title' })}
multiline
numberOfLines={2}
onContentSizeChange={(event) => {
setTitleHeight(event.nativeEvent.contentSize.height);
setTitleHeight(event.nativeEvent.contentSize.height);
}}
onChangeText={setTitle}
value={title}
/>
</View>
<TextInput
multiline
autoCorrect={true}
value={body}
onChangeText={setBody}
placeholder={intl.formatMessage({id:'snippets.placeholder_body'})}
placeholder={intl.formatMessage({ id: 'snippets.placeholder_body' })}
placeholderTextColor={isDarkTheme ? '#526d91' : '#c1c5c7'}
selectionColor="#357ce6"
style={styles.bodyWrapper}
@ -125,43 +117,43 @@ const SnippetEditorModal = ({onSnippetsUpdated}: SnippetEditorModalProps, ref) =
/>
</View>
<View style={styles.actionPanel}>
<TextButton
text={intl.formatMessage({id:'snippets.btn_close'})}
onPress={()=>setShowModal(false)}
style={styles.closeButton}
/>
<TextButton
text={intl.formatMessage({id:'snippets.btn_save'})}
onPress={_saveSnippet}
textStyle={styles.btnText}
style={styles.saveButton}
/>
</View>
<View style={styles.actionPanel}>
<TextButton
text={intl.formatMessage({ id: 'snippets.btn_close' })}
onPress={() => setShowModal(false)}
style={styles.closeButton}
/>
<TextButton
text={intl.formatMessage({ id: 'snippets.btn_save' })}
onPress={_saveSnippet}
textStyle={styles.btnText}
style={styles.saveButton}
/>
</View>
</KeyboardAvoidingView>
)}
</ThemeContainer>
)
</ThemeContainer>
)
return (
<Modal
isOpen={showModal}
handleOnModalClose={()=>{setShowModal(false)}}
presentationStyle="formSheet"
title={intl.formatMessage({
id:isNewSnippet
? 'snippets.title_add_snippet'
: 'snippets.title_edit_snippet'
})}
animationType="slide"
style={styles.modalStyle}
>
{_renderContent}
</Modal>
);
return (
<Modal
isOpen={showModal}
handleOnModalClose={() => { setShowModal(false) }}
presentationStyle="formSheet"
title={intl.formatMessage({
id: isNewSnippet
? 'snippets.title_add_snippet'
: 'snippets.title_edit_snippet'
})}
animationType="slide"
style={styles.modalStyle}
>
{_renderContent}
</Modal>
);
};
export default forwardRef(SnippetEditorModal);

View File

@ -4,38 +4,46 @@ import IconButton from '../iconButton';
import styles from './snippetsModalStyles';
interface SnippetItemProps {
title:string;
body:string;
index:number;
onEditPress:()=>void;
onRemovePress:()=>void;
id: string | null;
title: string;
body: string;
index: number;
onEditPress: () => void;
onRemovePress: () => void;
}
const SnippetItem = ({title, body, index, onEditPress, onRemovePress}: SnippetItemProps) => {
const SnippetItem = ({ id, title, body, index, onEditPress, onRemovePress }: SnippetItemProps) => {
return (
<View style={[styles.itemWrapper, index % 2 !== 0 && styles.itemWrapperGray]}>
<View style={styles.itemHeader}>
<Text style={styles.title} numberOfLines={1} >{`${title}`}</Text>
<IconButton
iconStyle={styles.itemIcon}
style={styles.itemIconWrapper}
iconType="MaterialCommunityIcons"
name="pencil"
onPress={onEditPress}
size={20}
/>
<IconButton
iconStyle={styles.itemIcon}
style={styles.itemIconWrapper}
iconType="MaterialCommunityIcons"
name="delete"
onPress={onRemovePress}
size={20}
/>
{
id && (
<>
<IconButton
iconStyle={styles.itemIcon}
style={styles.itemIconWrapper}
iconType="MaterialCommunityIcons"
name="pencil"
onPress={onEditPress}
size={20}
/>
<IconButton
iconStyle={styles.itemIcon}
style={styles.itemIconWrapper}
iconType="MaterialCommunityIcons"
name="delete"
onPress={onRemovePress}
size={20}
/>
</>
)
}
</View>
<Text style={styles.body} numberOfLines={2} ellipsizeMode="tail">{`${body}`}</Text>
</View>
)
};

View File

@ -69,6 +69,7 @@ const SnippetsModal = ({ handleOnSelect }: SnippetsModalProps) => {
return (
<TouchableOpacity onPress={_onPress}>
<SnippetItem
id={item.id}
title={item.title}
body={item.body}
index={index}
@ -123,7 +124,7 @@ const SnippetsModal = ({ handleOnSelect }: SnippetsModalProps) => {
ListEmptyComponent={_renderEmptyContent}
refreshControl={
<RefreshControl
refreshing={snippetsQuery.isLoading}
refreshing={snippetsQuery.isFetching}
onRefresh={snippetsQuery.refetch}
/>
}
@ -133,9 +134,6 @@ const SnippetsModal = ({ handleOnSelect }: SnippetsModalProps) => {
<SnippetEditorModal
ref={editorRef}
onSnippetsUpdated={() => {
throw new Error('not implemented');
}}
/>
</View>
);

View File

@ -18,6 +18,7 @@ import {
ReceivedVestingShare,
Referral,
ReferralStat,
Snippet,
} from './ecency.types';
/**
@ -326,7 +327,7 @@ export const deleteFavorite = async (targetUsername: string) => {
export const getFragments = async () => {
try {
const response = await ecencyApi.post('/private-api/fragments');
return response.data;
return response.data as Snippet[];
} catch (error) {
console.warn('Failed to get fragments', error);
bugsnagInstance.notify(error);

View File

@ -1,26 +1,35 @@
import { QuoteItem } from "../../redux/reducers/walletReducer";
export interface ReceivedVestingShare {
delegator:string;
delegatee:string;
vesting_shares:string;
timestamp:string;
delegator: string;
delegatee: string;
vesting_shares: string;
timestamp: string;
}
export interface Snippet {
id: string;
title: string;
body: string;
created: string;
modified: string;
}
export interface EcencyUser {
username:string;
points:string;
unclaimed_points:string;
points_by_type:{[key:string]:string};
unclaimed_points_by_type:{[key:string]:string};
username: string;
points: string;
unclaimed_points: string;
points_by_type: { [key: string]: string };
unclaimed_points_by_type: { [key: string]: string };
}
export interface Referral {
id:number;
referral:string;
rewarded:boolean;
username:string;
created:string
id: number;
referral: string;
rewarded: boolean;
username: string;
created: string
}
export interface ReferralStat {
@ -32,7 +41,7 @@ export interface UserPoint {
id: number;
type: number;
amount: string;
created:string;
created: string;
memo?: string;
receiver?: string;
sender?: string;
@ -40,14 +49,14 @@ export interface UserPoint {
}
export interface LatestQuotes {
[key:string]:QuoteItem
[key: string]: QuoteItem
}
export interface CommentHistoryItem {
body: string;
tags: [string];
title: string;
timestamp:string;
timestamp: string;
v: number;
}

View File

@ -1,16 +1,72 @@
import { useQuery } from '@tanstack/react-query';
import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query';
import { useIntl } from 'react-intl';
import { useAppDispatch } from '../../hooks';
import { toastNotification } from '../../redux/actions/uiAction';
import { getFragments } from '../ecency/ecency';
import { addFragment, getFragments, updateFragment } from '../ecency/ecency';
import { Snippet } from '../ecency/ecency.types';
import QUERIES from './queryKeys';
interface SnippetMutationVars {
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' })));
},
});
const intl = useIntl();
const dispatch = useAppDispatch();
return useQuery<Snippet[]>([QUERIES.SNIPPETS.GET], getFragments, {
onError: () => {
dispatch(toastNotification(intl.formatMessage({ id: 'alert.fail' })));
},
});
};
export const useSnippetsMutation = () => {
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)
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]);
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' })));
}
})
}