Files
notesnook/apps/mobile/app/components/sheets/add-to/index.js

475 lines
15 KiB
JavaScript
Raw Normal View History

/*
This file is part of the Notesnook project (https://notesnook.com/)
Copyright (C) 2022 Streetwriters (Private) Limited
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
2022-08-30 16:13:11 +05:00
2022-08-29 16:19:17 +05:00
import React, { createRef, useEffect, useState } from "react";
import { Keyboard, TouchableOpacity, View } from "react-native";
import { FlatList } from "react-native-gesture-handler";
import { notesnook } from "../../../../e2e/test.ids";
2022-08-29 16:19:17 +05:00
import { db } from "../../../common/database";
import {
eSubscribeEvent,
eUnSubscribeEvent,
ToastEvent
} from "../../../services/event-manager";
import Navigation from "../../../services/navigation";
import SearchService from "../../../services/search";
import { useNotebookStore } from "../../../stores/use-notebook-store";
2022-08-29 16:19:17 +05:00
import { useSelectionStore } from "../../../stores/use-selection-store";
import { useThemeStore } from "../../../stores/use-theme-store";
import { getTotalNotes } from "../../../utils";
import { eOpenMoveNoteDialog } from "../../../utils/events";
import layoutmanager from "../../../utils/layout-manager";
import { SIZE } from "../../../utils/size";
import { Dialog } from "../../dialog";
import DialogHeader from "../../dialog/dialog-header";
import { presentDialog } from "../../dialog/functions";
import { Button } from "../../ui/button";
import { IconButton } from "../../ui/icon-button";
import Input from "../../ui/input";
import { PressableButton } from "../../ui/pressable";
import SheetWrapper from "../../ui/sheet";
import Heading from "../../ui/typography/heading";
import Paragraph from "../../ui/typography/paragraph";
2022-08-30 18:27:09 +05:00
import { useCallback } from "react";
let newNotebookTitle = null;
const notebookInput = createRef();
2020-12-16 11:02:40 +05:00
const actionSheetRef = createRef();
2022-02-28 15:32:55 +05:00
const AddToNotebookSheet = () => {
2020-09-14 17:10:02 +05:00
const [visible, setVisible] = useState(false);
const [note, setNote] = useState(null);
2021-02-15 11:09:01 +05:00
function open(note) {
setNote(note);
2020-09-14 16:45:41 +05:00
setVisible(true);
2020-12-29 11:24:34 +05:00
actionSheetRef.current?.setModalVisible(true);
2020-09-14 16:45:41 +05:00
}
useEffect(() => {
eSubscribeEvent(eOpenMoveNoteDialog, open);
return () => {
eUnSubscribeEvent(eOpenMoveNoteDialog, open);
};
}, []);
const _onClose = () => {
2020-09-14 16:45:41 +05:00
setVisible(false);
newNotebookTitle = null;
setNote(null);
2022-04-24 05:59:14 +05:00
Navigation.queueRoutesForUpdate(
"Notes",
"Favorites",
"ColoredNotes",
"TaggedNotes",
"TopicNotes",
"Notebooks",
"Notebook"
2022-04-24 05:59:14 +05:00
);
2020-09-14 16:45:41 +05:00
};
2020-01-18 18:13:34 +05:00
return !visible ? null : (
2021-12-25 11:16:33 +05:00
<SheetWrapper fwdRef={actionSheetRef} onClose={_onClose}>
<MoveNoteComponent note={note} />
2021-12-25 11:16:33 +05:00
</SheetWrapper>
);
};
2022-02-28 15:32:55 +05:00
export default AddToNotebookSheet;
const MoveNoteComponent = ({ note }) => {
const colors = useThemeStore((state) => state.colors);
2021-06-05 21:10:20 +05:00
const notebooks = useNotebookStore((state) =>
state.notebooks.filter((n) => n?.type === "notebook")
);
2021-12-31 15:01:43 +05:00
const selectedItemsList = useSelectionStore(
(state) => state.selectedItemsList
);
const setNotebooks = useNotebookStore((state) => state.setNotebooks);
const [expanded, setExpanded] = useState("");
const [notebookInputFocused, setNotebookInputFocused] = useState(false);
const [noteExists, setNoteExists] = useState([]);
2020-09-14 16:45:41 +05:00
const addNewNotebook = async () => {
if (!newNotebookTitle || newNotebookTitle.trim().length === 0)
2021-02-20 15:03:02 +05:00
return ToastEvent.show({
heading: "Notebook title is required",
type: "error",
context: "local"
2021-02-20 15:03:02 +05:00
});
2020-01-18 18:13:34 +05:00
2021-12-27 18:09:00 +05:00
let id = await db.notebooks.add({
2020-09-14 16:45:41 +05:00
title: newNotebookTitle,
description: null,
2020-09-14 16:45:41 +05:00
topics: [],
2021-12-27 18:09:00 +05:00
id: null
2020-01-18 18:13:34 +05:00
});
2022-08-31 13:03:22 +05:00
2021-12-27 18:09:00 +05:00
setExpanded(id);
openAddTopicDialog(db.notebooks.notebook(id).data);
notebookInput.current?.clear();
notebookInput.current?.blur();
2021-06-05 21:10:20 +05:00
setNotebooks();
2021-12-27 18:09:00 +05:00
updateNoteExists();
2020-09-14 16:45:41 +05:00
};
2020-01-18 18:13:34 +05:00
2021-12-31 09:05:44 +05:00
const addNewTopic = async (value, item) => {
2021-12-27 18:09:00 +05:00
if (!value || value.trim().length === 0) {
ToastEvent.show({
heading: "Topic title is required",
type: "error",
context: "local"
2021-02-20 15:03:02 +05:00
});
2021-12-27 18:09:00 +05:00
return false;
}
2022-08-31 13:03:22 +05:00
2021-12-27 18:09:00 +05:00
await db.notebooks.notebook(item.id).topics.add(value);
2021-06-05 21:10:20 +05:00
setNotebooks();
2021-12-27 18:09:00 +05:00
updateNoteExists();
return true;
2020-09-14 16:45:41 +05:00
};
const handlePress = async (item) => {
let noteIds =
selectedItemsList.length > 0
? selectedItemsList.map((n) => n.id)
: [note?.id];
2020-12-29 17:19:32 +05:00
if (getCount(item)) {
await db.notebooks
.notebook(item.notebookId)
.topics.topic(item.id)
.delete(...noteIds);
} else {
await db.notes.move(
{
topic: item.id,
id: item.notebookId
},
...noteIds
);
}
2022-04-24 05:59:14 +05:00
Navigation.queueRoutesForUpdate(
"Notes",
"Favorites",
"ColoredNotes",
"TaggedNotes",
"TopicNotes"
2022-04-24 05:59:14 +05:00
);
2021-06-05 21:10:20 +05:00
setNotebooks();
2021-12-27 18:09:00 +05:00
updateNoteExists();
2022-03-23 12:23:51 +05:00
SearchService.updateAndSearch();
};
useEffect(() => {
2021-12-27 18:09:00 +05:00
updateNoteExists();
2022-08-30 18:27:09 +05:00
}, [updateNoteExists]);
2021-12-27 18:09:00 +05:00
2022-08-30 18:27:09 +05:00
const updateNoteExists = useCallback(() => {
if (!note?.id && selectedItemsList?.length === 0) return;
let notes =
selectedItemsList.length > 0
? selectedItemsList.map((n) => n.id)
: [note?.id];
let ids = [];
2021-12-27 18:09:00 +05:00
let notebooks = db.notebooks.all;
for (let i = 0; i < notebooks.length; i++) {
2021-07-17 20:40:09 +05:00
if (notebooks[i].topics) {
for (let t = 0; t < notebooks[i].topics.length; t++) {
let topic = notebooks[i].topics[t];
if (topic.type !== "topic") continue;
for (let id of notes) {
if (topic.notes.indexOf(id) > -1) {
if (ids.indexOf(notebooks[i].id) === -1) {
ids.push(notebooks[i].id);
}
if (ids.indexOf(topic.id) === -1) ids.push(topic.id);
}
2021-07-17 20:40:09 +05:00
}
}
}
}
2022-08-31 13:03:22 +05:00
setNoteExists(ids);
2022-08-30 18:27:09 +05:00
}, [note?.id, selectedItemsList]);
2021-12-27 18:09:00 +05:00
const openAddTopicDialog = (item) => {
2021-12-27 18:09:00 +05:00
presentDialog({
context: "move_note",
2021-12-27 18:09:00 +05:00
input: true,
inputPlaceholder: "Enter title",
title: "New topic",
paragraph: "Add a new topic in " + item.title,
positiveText: "Add",
positivePress: (value) => {
2021-12-31 09:05:44 +05:00
return addNewTopic(value, item);
2021-12-27 18:09:00 +05:00
}
});
};
const getCount = (topic) => {
if (!topic) return;
let notes =
selectedItemsList.length > 0
? selectedItemsList.map((n) => n.id)
: [note?.id];
let count = 0;
for (let id of notes) {
if (topic.notes.indexOf(id) > -1) {
count++;
}
}
return count > 0 && notes.length > 0
? `${count} of ${notes.length} selected notes exist in this topic. (Tap to remove)`
: null;
};
return (
<>
2021-12-27 18:09:00 +05:00
<Dialog context="move_note" />
<View>
<TouchableOpacity
2020-01-18 18:13:34 +05:00
style={{
width: "100%",
height: "100%",
position: "absolute"
}}
onPress={() => {
Keyboard.dismiss();
}}
/>
<View
style={{
paddingHorizontal: 12,
flexDirection: "row",
justifyContent: "space-between"
2022-01-22 12:57:05 +05:00
}}
>
<DialogHeader
title="Add to notebook"
paragraph={"Add your notes to notebooks to find them easily."}
2020-11-23 15:57:31 +05:00
/>
</View>
2020-11-23 15:57:31 +05:00
2020-12-29 11:24:34 +05:00
<FlatList
nestedScrollEnabled={true}
2020-12-16 11:02:40 +05:00
onMomentumScrollEnd={() => {
2020-12-29 17:19:32 +05:00
actionSheetRef.current?.handleChildScrollEnd();
2020-12-16 11:02:40 +05:00
}}
2020-12-29 11:24:34 +05:00
keyboardShouldPersistTaps="always"
keyboardDismissMode="none"
2021-02-22 09:59:34 +05:00
data={notebooks}
2020-12-29 11:24:34 +05:00
ListFooterComponent={
<View
style={{
height: 200
2020-12-29 11:24:34 +05:00
}}
/>
}
ListHeaderComponent={
2020-11-23 15:57:31 +05:00
<View
2020-09-14 16:45:41 +05:00
style={{
width: "100%",
2021-12-27 18:09:00 +05:00
marginTop: 10
2022-01-22 12:57:05 +05:00
}}
>
2021-12-27 18:09:00 +05:00
<Input
fwdRef={notebookInput}
onChangeText={(value) => {
newNotebookTitle = value;
}}
testID={notesnook.ids.dialogs.addTo.addNotebook}
blurOnSubmit={false}
2021-12-27 18:09:00 +05:00
onFocusInput={() => {
setNotebookInputFocused(true);
}}
2021-12-27 18:09:00 +05:00
onBlurInput={() => {
setNotebookInputFocused(false);
}}
2021-12-27 18:09:00 +05:00
button={{
icon: "check",
color: notebookInputFocused ? colors.accent : colors.icon,
2022-01-22 12:57:05 +05:00
onPress: addNewNotebook
2021-12-27 18:09:00 +05:00
}}
onSubmit={addNewNotebook}
placeholder="Create a new notebook"
2020-11-23 15:57:31 +05:00
/>
</View>
2020-12-29 11:24:34 +05:00
}
2021-12-27 18:09:00 +05:00
style={{
paddingHorizontal: 12
}}
renderItem={({ item }) => (
2020-12-29 11:24:34 +05:00
<View
style={{
2021-12-27 18:09:00 +05:00
borderWidth: 1,
borderColor: expanded ? colors.nav : "transparent",
2021-12-27 18:09:00 +05:00
borderRadius: 6,
overflow: "hidden",
2021-12-27 18:09:00 +05:00
marginBottom: 10
2022-01-22 12:57:05 +05:00
}}
>
<PressableButton
onPress={() => {
2021-12-27 18:09:00 +05:00
if (!item.topics || item.topics.length === 0) {
setExpanded(item.id);
openAddTopicDialog(item);
return;
}
2021-12-31 09:05:44 +05:00
layoutmanager.withAnimation(200);
setExpanded(item.id === expanded ? null : item.id);
setNotebookInputFocused(false);
}}
2021-12-27 18:09:00 +05:00
type="grayBg"
customStyle={{
height: 50,
width: "100%",
2021-12-27 18:09:00 +05:00
borderRadius: 5,
alignItems: "flex-start"
2022-01-22 12:57:05 +05:00
}}
>
2020-09-14 16:45:41 +05:00
<View
style={{
width: "100%",
height: 50,
justifyContent: "space-between",
flexDirection: "row",
alignItems: "center",
2021-12-27 18:09:00 +05:00
paddingHorizontal: 12
2022-01-22 12:57:05 +05:00
}}
>
<View>
<Heading
color={
noteExists.indexOf(item.id) > -1 ? colors.accent : null
}
2022-01-22 12:57:05 +05:00
size={SIZE.md}
>
{item.title}
</Heading>
2021-07-17 20:40:09 +05:00
{item.topics?.length > 0 ? (
<Paragraph size={SIZE.xs} color={colors.icon}>
{getTotalNotes(item) + " notes" + " & "}
2021-07-17 20:40:09 +05:00
{item.topics.length === 1
? item.topics.length + " topic"
: item.topics.length + " topics"}
2021-07-17 20:40:09 +05:00
</Paragraph>
) : null}
2020-09-14 16:45:41 +05:00
</View>
2022-02-28 13:48:59 +05:00
<IconButton
name={expanded === item.id ? "plus" : "chevron-down"}
2021-12-27 18:09:00 +05:00
color={expanded === item.id ? colors.accent : colors.pri}
size={SIZE.xl}
onPress={() => {
if (expanded !== item.id) {
setExpanded(item.id);
return;
}
2021-12-31 09:05:44 +05:00
layoutmanager.withAnimation(200);
2021-12-27 18:09:00 +05:00
setExpanded(item.id);
openAddTopicDialog(item);
}}
/>
2020-11-23 15:57:31 +05:00
</View>
</PressableButton>
2020-09-14 16:45:41 +05:00
{expanded === item.id ? (
<FlatList
2020-12-29 11:24:34 +05:00
nestedScrollEnabled
data={item.topics?.filter((t) => t.type === "topic")}
keyboardShouldPersistTaps="always"
keyboardDismissMode="none"
2020-12-29 11:24:34 +05:00
onMomentumScrollEnd={() => {
2020-12-29 17:19:32 +05:00
actionSheetRef.current?.handleChildScrollEnd();
2020-12-29 11:24:34 +05:00
}}
2020-12-20 18:24:33 +05:00
style={{
width: "100%",
alignSelf: "flex-end",
2021-12-27 18:09:00 +05:00
maxHeight: 500
2020-12-20 18:24:33 +05:00
}}
2022-01-22 12:57:05 +05:00
renderItem={({ item, index }) => (
<PressableButton
2020-12-19 13:15:34 +05:00
onPress={() => handlePress(item, index)}
type="gray"
customStyle={{
minHeight: 50,
borderTopWidth: index === 0 ? 0 : 1,
2021-12-27 18:09:00 +05:00
borderTopColor: index === 0 ? null : colors.nav,
width: "100%",
borderRadius: 0,
alignItems: "center",
flexDirection: "row",
paddingHorizontal: 12,
justifyContent: "space-between",
paddingVertical: 12
2022-01-22 12:57:05 +05:00
}}
>
<View>
<Paragraph color={colors.heading}>
{item.title}
</Paragraph>
2021-01-14 17:41:27 +05:00
<Paragraph color={colors.icon} size={SIZE.xs}>
{item.notes.length + " notes"}
2021-02-15 11:09:01 +05:00
</Paragraph>
{getCount(item) ? (
<View
style={{
backgroundColor: colors.nav,
borderRadius: 5,
paddingHorizontal: 5,
paddingVertical: 2,
marginTop: 5
}}
>
<Paragraph color={colors.icon} size={SIZE.xs}>
{getCount(item)}
</Paragraph>
</View>
) : null}
</View>
{noteExists.indexOf(item.id) > -1 ? (
2021-01-14 15:45:07 +05:00
<Button
onPress={() => handlePress(item, index)}
2021-12-27 18:09:00 +05:00
icon="check"
2022-04-03 01:48:44 +05:00
testID="icon-check"
2021-12-27 18:09:00 +05:00
iconColor={colors.accent}
iconSize={SIZE.lg}
height={35}
width={35}
2021-01-14 15:45:07 +05:00
style={{
borderRadius: 100,
2021-12-27 18:09:00 +05:00
paddingHorizontal: 0
2021-01-14 15:45:07 +05:00
}}
/>
) : null}
</PressableButton>
)}
/>
) : null}
2020-12-01 11:19:29 +05:00
</View>
2020-12-29 11:24:34 +05:00
)}
/>
</View>
</>
2020-09-14 16:45:41 +05:00
);
};