diff --git a/src/App.tsx b/src/App.tsx index 4ba8f59..fbe132f 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -41,6 +41,7 @@ const rootStackScreens: RootStackScreens = { NoteCreate: "new-note", NoteEdit: "notebook/:colUid/note/:itemUid", NoteProps: "notebook/:colUid/note/:itemUid/properties", + NoteMove: "notebook/:colUid/note/:itemUid/move", Invitations: "invitations", Settings: "settings", About: "settings/about", diff --git a/src/RootNavigator.tsx b/src/RootNavigator.tsx index 4625ad0..e00cdde 100644 --- a/src/RootNavigator.tsx +++ b/src/RootNavigator.tsx @@ -17,6 +17,7 @@ import DebugLogsScreen from "./screens/DebugLogsScreen"; import HomeScreen from "./screens/HomeScreen"; import NoteEditScreen from "./screens/NoteEditScreen"; import NotePropertiesScreen from "./screens/NotePropertiesScreen"; +import NoteMoveScreen from "./screens/NoteMoveScreen"; import CollectionEditScreen from "./screens/CollectionEditScreen"; import CollectionChangelogScreen from "./screens/CollectionChangelogScreen"; import CollectionMembersScreen from "./screens/CollectionMembersScreen"; @@ -113,6 +114,10 @@ export default React.memo(function RootNavigator() { name="NoteProps" component={NotePropertiesScreen} /> + navigation.navigate("NoteProps", { colUid, itemUid })} onDelete={() => setNoteDeleteDialogShow(true)} + onMove={() => navigation.navigate("NoteMove", { colUid, itemUid })} onShare={onShare} changed={changed} /> @@ -246,10 +247,11 @@ interface RightActionViewProps { onEdit: () => void; onSave: () => void; onDelete: () => void; + onMove: () => void; onShare: () => void; } -function RightAction({ viewMode, setViewMode, onSave, onEdit, onDelete, onShare, changed }: RightActionViewProps) { +function RightAction({ viewMode, setViewMode, onSave, onEdit, onDelete, onMove, onShare, changed }: RightActionViewProps) { const [showMenu, setShowMenu] = React.useState(false); return ( @@ -276,6 +278,12 @@ function RightAction({ viewMode, setViewMode, onSave, onEdit, onDelete, onShare, onDelete(); }} /> + { + setShowMenu(false); + onMove(); + }} + /> { diff --git a/src/screens/NoteMoveScreen.tsx b/src/screens/NoteMoveScreen.tsx new file mode 100644 index 0000000..4917847 --- /dev/null +++ b/src/screens/NoteMoveScreen.tsx @@ -0,0 +1,197 @@ +// SPDX-FileCopyrightText: © 2019 EteSync Authors +// SPDX-License-Identifier: GPL-3.0-only + +import * as React from "react"; +import { useSelector } from "react-redux"; +import { View } from "react-native"; +import { Text, HelperText, Button, TouchableRipple } from "react-native-paper"; +import { useNavigation, RouteProp } from "@react-navigation/native"; +import { StackNavigationProp } from "@react-navigation/stack"; + +import { useSyncGate } from "../SyncGate"; +import { useCredentials } from "../credentials"; +import { StoreState, useAsyncDispatch } from "../store"; +import { itemBatch, pushMessage } from "../store/actions"; +import { CachedItem } from "../store/reducers"; + +import ScrollView from "../widgets/ScrollView"; +import Container from "../widgets/Container"; +import ErrorOrLoadingDialog from "../widgets/ErrorOrLoadingDialog"; +import Select from "../widgets/Select"; +import TextInputWithIcon from "../widgets/TextInputWithIcon"; +import NotFound from "../widgets/NotFound"; + +import { useLoading } from "../helpers"; +import { RootStackParamList } from "../RootStackParamList"; + +interface FormErrors { + notebook?: string; +} + +type NavigationProp = StackNavigationProp; + +interface PropsType { + route: RouteProp; +} + +export default function NotePropertiesScreen(props: PropsType) { + const colUid = props.route.params?.colUid; + const itemUid = props.route.params?.itemUid; + const [errors, setErrors] = React.useState({} as FormErrors); + const [selectOpen, setSelectOpen] = React.useState(false); + const dispatch = useAsyncDispatch(); + const cachedCollections = useSelector((state: StoreState) => state.cache.collections); + const cachedItems = useSelector((state: StoreState) => state.cache.items); + const [cachedItem, setCachedItem] = React.useState(); + const options = Array.from( + cachedCollections.map((val, uid) => ({ ...val, uid })) + .sort((a, b) => (a.meta!.name!.toUpperCase() >= b.meta!.name!.toUpperCase()) ? 1 : -1) + .values() + ); + const [collection, setCollection] = React.useState((options.length > 0) ? options[0] : undefined); + const syncGate = useSyncGate(); + const navigation = useNavigation(); + const etebase = useCredentials()!; + const [loading, error, setPromise] = useLoading(); + + React.useEffect(() => { + if (syncGate) { + return; + } + + if (colUid) { + const cachedCollection = cachedItems.get(colUid); + + if (cachedCollection) { + setCollection(options.find((x) => x.uid === colUid) ?? options[0]); + + if (itemUid) { + const cachedItem = cachedCollection.get(itemUid); + if (cachedItem) { + setCachedItem(cachedItem); + } else { + setCachedItem(undefined); + } + } + } else { + setCollection(undefined); + } + } + + }, [syncGate, cachedItems, colUid, itemUid]); + + React.useEffect(() => { + navigation.setOptions({ + title: "Move Note", + }); + }, [colUid, itemUid]); + + if (syncGate) { + return syncGate; + } + + if (colUid && !collection) { + return ; + } + if (itemUid && !cachedItem) { + return ; + } + + function onMove() { + setPromise(async () => { + const saveErrors: FormErrors = {}; + const fieldRequired = "This field is required!"; + + if (!collection) { + saveErrors.notebook = fieldRequired; + } else if (collection.uid === colUid) { + saveErrors.notebook = "You must pick a different notebook to move the note!"; + } + + if (Object.keys(saveErrors).length > 0) { + setErrors(saveErrors); + return; + } + + const colMgr = etebase.getCollectionManager(); + + const oldCollection = cachedCollections.get(colUid!)!; + const oldCol = colMgr.cacheLoad(oldCollection.cache); + const oldItemMgr = colMgr.getItemManager(oldCol); + const oldItem = oldItemMgr.cacheLoad(cachedItem!.cache); + + const newCol = colMgr.cacheLoad(collection!.cache); + const newItemMgr = colMgr.getItemManager(newCol); + + const meta = oldItem.getMeta(); + meta.mtime = (new Date()).getTime(); + const content = await oldItem.getContent(); + const newItem = await newItemMgr.create(meta, content); + await dispatch(itemBatch(newCol, newItemMgr, [newItem])); + + oldItem.setMeta(meta); + oldItem.delete(true); + await dispatch(itemBatch(oldCol, oldItemMgr, [oldItem])); + + dispatch(pushMessage({ message: "Note moved", severity: "success" })); + navigation.navigate("NoteEdit", { colUid: collection!.uid, itemUid: newItem.uid }); + }); + } + + return ( + + + setPromise(undefined)} + /> + + +