From b13b3a6a38ac3bbb8016f07ac3b5d9ca2aade85d Mon Sep 17 00:00:00 2001 From: ragsav Date: Sun, 30 Apr 2023 15:03:59 +0530 Subject: [PATCH] added undo and redo --- js/hooks/useUndo.js | 114 +++++++++++++++++++++++++++++++++++++++ js/screens/TaskScreen.js | 56 +++++++++++++++---- 2 files changed, 161 insertions(+), 9 deletions(-) create mode 100644 js/hooks/useUndo.js diff --git a/js/hooks/useUndo.js b/js/hooks/useUndo.js new file mode 100644 index 0000000..b29daac --- /dev/null +++ b/js/hooks/useUndo.js @@ -0,0 +1,114 @@ +import {useReducer, useCallback} from 'react'; + +const ActionType = { + Undo: 'UNDO', + Redo: 'REDO', + Set: 'SET', + Reset: 'RESET', +}; + +const initialState = { + past: [], + present: null, + future: [], +}; + +export const useUndo = (initialPresent, opts = {}) => { + const {useCheckpoints} = { + useCheckpoints: false, + ...opts, + }; + + const reducer = (state, action) => { + const {past, present, future} = state; + + switch (action.type) { + case ActionType.Undo: { + if (past.length === 0) { + return state; + } + + const previous = past[past.length - 1]; + const newPast = past.slice(0, past.length - 1); + + return { + past: newPast, + present: previous, + future: [present, ...future], + }; + } + + case ActionType.Redo: { + if (future.length === 0) { + return state; + } + const next = future[0]; + const newFuture = future.slice(1); + + return { + past: [...past, present], + present: next, + future: newFuture, + }; + } + + case ActionType.Set: { + const isNewCheckpoint = useCheckpoints + ? !!action.historyCheckpoint + : true; + const {newPresent} = action; + + if (newPresent === present) { + return state; + } + + return { + past: isNewCheckpoint === false ? past : [...past, present], + present: newPresent, + future: [], + }; + } + + case ActionType.Reset: { + const {newPresent} = action; + + return { + past: [], + present: newPresent, + future: [], + }; + } + } + }; + + const [state, dispatch] = useReducer(reducer, { + ...initialState, + present: initialPresent, + }); + + const canUndo = state.past.length !== 0; + const canRedo = state.future.length !== 0; + const undo = useCallback(() => { + if (canUndo) { + dispatch({type: ActionType.Undo}); + } + }, [canUndo]); + const redo = useCallback(() => { + if (canRedo) { + dispatch({type: ActionType.Redo}); + } + }, [canRedo]); + const set = useCallback((newPresent, checkpoint = false) => { + dispatch({ + type: ActionType.Set, + newPresent, + historyCheckpoint: checkpoint, + }); + }, []); + const reset = useCallback( + newPresent => dispatch({type: ActionType.Reset, newPresent}), + [], + ); + + return [state, {set, reset, undo, redo, canUndo, canRedo}]; +}; diff --git a/js/screens/TaskScreen.js b/js/screens/TaskScreen.js index ee08597..cea71f9 100644 --- a/js/screens/TaskScreen.js +++ b/js/screens/TaskScreen.js @@ -55,6 +55,7 @@ import { import {CONSTANTS} from '../../constants'; import {NotePasswordInputScreen} from './NotePasswordInputScreen'; import {Q} from '@nozbe/watermelondb'; +import {useUndo} from '../hooks/useUndo'; /** * @@ -72,6 +73,8 @@ const TaskScreen = ({ route, }) => { // ref + const titleRef = useRef(); + const descriptionRef = useRef(); // variables const theme = useTheme(); @@ -85,21 +88,29 @@ const TaskScreen = ({ error: null, }); const [title, setTitle] = useState(''); - const [description, setDescription] = useState(''); - const titleRef = useRef(); - const descriptionRef = useRef(); + + const [ + descriptionUndoRedoState, + { + set: setDescription, + reset: resetDescription, + undo: undoDescription, + redo: redoDescription, + canUndo, + canRedo, + }, + ] = useUndo(''); + const {present: presentDescription} = descriptionUndoRedoState; const [error, setError] = useState(null); const [isDeleteDialogOpen, setIsDeleteDialogOpen] = useState(false); const [dueDateString, setDueDateString] = useState('date'); const [isMenuOpen, setIsMenuOpen] = useState(false); const [isDueDateTimePickerVisible, setIsDueDateTimePickerVisible] = useState(false); - const [reminderDateString, setReminderDateString] = useState('time'); const [isReminderDateTimePickerVisible, setIsReminderDateTimePickerVisible] = useState(false); const [urls, setURLs] = useState([]); - const [isImagePickerOpen, setIsImagePickerOpen] = useState(false); const [imageToView, setImageToView] = useState({ images: [], @@ -166,9 +177,9 @@ const TaskScreen = ({ useEffect(() => { if (descriptionRef) { - descriptionRef.current = description; + descriptionRef.current = presentDescription; } - }, [description, descriptionRef, descriptionRef.current]); + }, [presentDescription, descriptionRef, descriptionRef.current]); useEffect(() => { if (titleRef) { @@ -339,7 +350,7 @@ const TaskScreen = ({ try { const result = await Share.share({ title: title, - message: description, + message: presentDescription, }); if (result.action === Share.sharedAction) { if (result.activityType) { @@ -595,12 +606,13 @@ const TaskScreen = ({ textAlignVertical: 'top', padding: 12, paddingHorizontal: 18, + paddingBottom: 30, }} onBlur={_handleDescriptionSave} onChangeText={_handleDescriptionChange} placeholder="Add some here..." placeholderTextColor={theme?.colors.onSurfaceDisabled} - value={description} + value={presentDescription} multiline={true} /> @@ -623,6 +635,32 @@ const TaskScreen = ({ presentationStyle="fullScreen" onRequestClose={() => setImageToView({...imageToView, visible: false})} /> + + + + + + ) : (