mirror of
https://github.com/streetwriters/notesnook.git
synced 2025-12-24 07:29:30 +01:00
mobile: refactor
This commit is contained in:
committed by
Abdullah Atta
parent
d33f3c4769
commit
905080e743
@@ -64,7 +64,8 @@ export const SectionHeader = React.memo<
|
||||
<View
|
||||
style={{
|
||||
width: "100%",
|
||||
paddingHorizontal: DefaultAppStyles.GAP
|
||||
paddingHorizontal: DefaultAppStyles.GAP,
|
||||
marginBottom: DefaultAppStyles.GAP_VERTICAL
|
||||
}}
|
||||
>
|
||||
<View
|
||||
|
||||
@@ -17,44 +17,41 @@ You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import { Note } from "@notesnook/core";
|
||||
import { Note, Notebook } from "@notesnook/core";
|
||||
import { strings } from "@notesnook/intl";
|
||||
import { useThemeColors } from "@notesnook/theme";
|
||||
import React, {
|
||||
RefObject,
|
||||
useCallback,
|
||||
useEffect,
|
||||
useRef,
|
||||
useState
|
||||
} from "react";
|
||||
import {
|
||||
ActivityIndicator,
|
||||
Keyboard,
|
||||
TouchableOpacity,
|
||||
View
|
||||
} from "react-native";
|
||||
import { ActionSheetRef } from "react-native-actions-sheet";
|
||||
import { FlashList } from "react-native-actions-sheet/dist/src/views/FlashList";
|
||||
import React, { RefObject, useCallback, useEffect, useRef } from "react";
|
||||
import { ActivityIndicator, View } from "react-native";
|
||||
import { ActionSheetRef, FlatList } from "react-native-actions-sheet";
|
||||
import { db } from "../../../common/database";
|
||||
import { presentSheet } from "../../../services/event-manager";
|
||||
import Navigation from "../../../services/navigation";
|
||||
import {
|
||||
createNotebookTreeStores,
|
||||
TreeItem
|
||||
} from "../../../stores/create-notebook-tree-stores";
|
||||
import { ItemSelection } from "../../../stores/item-selection-store";
|
||||
import { useNotebooks } from "../../../stores/use-notebook-store";
|
||||
import { useRelationStore } from "../../../stores/use-relation-store";
|
||||
import { useSelectionStore } from "../../../stores/use-selection-store";
|
||||
import { useSettingStore } from "../../../stores/use-setting-store";
|
||||
import { updateNotebook } from "../../../utils/notebooks";
|
||||
import { AppFontSize } from "../../../utils/size";
|
||||
import { DefaultAppStyles } from "../../../utils/styles";
|
||||
import { Dialog } from "../../dialog";
|
||||
import DialogHeader from "../../dialog/dialog-header";
|
||||
import SheetProvider from "../../sheet-provider";
|
||||
import { NotebookItem } from "../../side-menu/notebook-item";
|
||||
import { Button } from "../../ui/button";
|
||||
import { IconButton } from "../../ui/icon-button";
|
||||
import Input from "../../ui/input";
|
||||
import Paragraph from "../../ui/typography/paragraph";
|
||||
import { AddNotebookSheet } from "../add-notebook";
|
||||
import { NotebookItem } from "./notebook-item";
|
||||
import { useNotebookItemSelectionStore } from "./store";
|
||||
|
||||
const {
|
||||
useNotebookExpandedStore,
|
||||
useNotebookSelectionStore,
|
||||
useNotebookTreeStore
|
||||
} = createNotebookTreeStores(true, true, "link-notebooks-expanded-store");
|
||||
|
||||
async function updateInitialSelectionState(items: string[]) {
|
||||
const relations = await db.relations
|
||||
@@ -86,7 +83,7 @@ async function updateInitialSelectionState(items: string[]) {
|
||||
initialSelectionState[id] = "intermediate";
|
||||
}
|
||||
}
|
||||
useNotebookItemSelectionStore.setState({
|
||||
useNotebookSelectionStore.setState({
|
||||
initialState: initialSelectionState,
|
||||
selection: { ...initialSelectionState },
|
||||
multiSelect: relations.length > 1
|
||||
@@ -101,31 +98,58 @@ const MoveNoteSheet = ({
|
||||
actionSheetRef: RefObject<ActionSheetRef>;
|
||||
}) => {
|
||||
const { colors } = useThemeColors();
|
||||
const [rootNotebooks, loading] = useNotebooks();
|
||||
const [notebooks, loading] = useNotebooks();
|
||||
const tree = useNotebookTreeStore((state) => state.tree);
|
||||
const searchQuery = useRef("");
|
||||
const searchTimer = useRef<NodeJS.Timeout>();
|
||||
const [notebooks, setNotebooks] = useState(rootNotebooks);
|
||||
const [filteredNotebooks, setFilteredNotebooks] = React.useState(notebooks);
|
||||
const loadRootNotebooks = React.useCallback(async () => {
|
||||
if (!filteredNotebooks) return;
|
||||
const _notebooks: Notebook[] = [];
|
||||
for (let i = 0; i < filteredNotebooks.placeholders.length; i++) {
|
||||
_notebooks[i] = (await filteredNotebooks?.item(i))?.item as Notebook;
|
||||
}
|
||||
const items = await useNotebookTreeStore
|
||||
.getState()
|
||||
.addNotebooks("root", _notebooks, 0);
|
||||
useNotebookTreeStore.getState().setTree(items);
|
||||
}, [filteredNotebooks]);
|
||||
|
||||
const updateNotebooks = React.useCallback(() => {
|
||||
if (searchQuery.current) {
|
||||
db.lookup
|
||||
.notebooks(searchQuery.current)
|
||||
.sorted()
|
||||
.then((filtered) => {
|
||||
setFilteredNotebooks(filtered);
|
||||
});
|
||||
} else {
|
||||
setFilteredNotebooks(notebooks);
|
||||
}
|
||||
}, [notebooks]);
|
||||
|
||||
useEffect(() => {
|
||||
updateNotebooks();
|
||||
}, [updateNotebooks]);
|
||||
|
||||
useEffect(() => {
|
||||
(async () => {
|
||||
if (!loading) {
|
||||
loadRootNotebooks();
|
||||
}
|
||||
})();
|
||||
}, [loadRootNotebooks, loading]);
|
||||
|
||||
const dimensions = useSettingStore((state) => state.dimensions);
|
||||
const selectedItemsList = useSelectionStore(
|
||||
(state) => state.selectedItemsList
|
||||
);
|
||||
|
||||
const multiSelect = useNotebookItemSelectionStore(
|
||||
(state) => state.multiSelect
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
if (!loading) {
|
||||
setNotebooks(rootNotebooks);
|
||||
}
|
||||
}, [loading, rootNotebooks]);
|
||||
const multiSelect = useNotebookSelectionStore((state) => state.multiSelect);
|
||||
|
||||
useEffect(() => {
|
||||
const items = note ? [note.id] : selectedItemsList;
|
||||
updateInitialSelectionState(items);
|
||||
return () => {
|
||||
useNotebookItemSelectionStore.setState({
|
||||
useNotebookSelectionStore.setState({
|
||||
initialState: {},
|
||||
selection: {},
|
||||
multiSelect: false,
|
||||
@@ -137,7 +161,7 @@ const MoveNoteSheet = ({
|
||||
const onSave = async () => {
|
||||
const noteIds = note ? [note.id] : selectedItemsList;
|
||||
|
||||
const changedNotebooks = useNotebookItemSelectionStore.getState().selection;
|
||||
const changedNotebooks = useNotebookSelectionStore.getState().selection;
|
||||
|
||||
for (const id in changedNotebooks) {
|
||||
const item = await db.notebooks.notebook(id);
|
||||
@@ -161,43 +185,39 @@ const MoveNoteSheet = ({
|
||||
};
|
||||
|
||||
const hasSelected = () => {
|
||||
const selection = useNotebookItemSelectionStore.getState().selection;
|
||||
const selection = useNotebookSelectionStore.getState().selection;
|
||||
return Object.keys(selection).some((key) => selection[key] === "selected");
|
||||
};
|
||||
|
||||
const renderNotebook = useCallback(
|
||||
({ index }: { item: boolean; index: number }) => (
|
||||
<NotebookItem items={notebooks} id={index} index={index} />
|
||||
({ item, index }: { item: TreeItem; index: number }) => (
|
||||
<NotebookItemWrapper item={item} index={index} />
|
||||
),
|
||||
[notebooks]
|
||||
[]
|
||||
);
|
||||
|
||||
return (
|
||||
<>
|
||||
<Dialog context="move_note" />
|
||||
<SheetProvider context="link-notebooks" />
|
||||
<View>
|
||||
<TouchableOpacity
|
||||
style={{
|
||||
width: "100%",
|
||||
height: "100%",
|
||||
position: "absolute"
|
||||
}}
|
||||
onPress={() => {
|
||||
Keyboard.dismiss();
|
||||
}}
|
||||
/>
|
||||
<View
|
||||
style={{
|
||||
gap: DefaultAppStyles.GAP_VERTICAL
|
||||
}}
|
||||
>
|
||||
<View
|
||||
style={{
|
||||
paddingHorizontal: 12,
|
||||
paddingHorizontal: DefaultAppStyles.GAP,
|
||||
justifyContent: "space-between",
|
||||
flexDirection: "row",
|
||||
alignItems: "flex-start"
|
||||
alignItems: "center",
|
||||
borderBottomWidth: 1,
|
||||
borderBottomColor: colors.primary.border,
|
||||
paddingBottom: DefaultAppStyles.GAP_VERTICAL
|
||||
}}
|
||||
>
|
||||
<DialogHeader
|
||||
style={{
|
||||
minHeight: 10,
|
||||
flexShrink: 1
|
||||
}}
|
||||
title={strings.selectNotebooks()}
|
||||
@@ -211,7 +231,7 @@ const MoveNoteSheet = ({
|
||||
<View
|
||||
style={{
|
||||
flexDirection: "row",
|
||||
columnGap: 10
|
||||
columnGap: DefaultAppStyles.GAP_SMALL
|
||||
}}
|
||||
>
|
||||
{hasSelected() ? (
|
||||
@@ -232,112 +252,199 @@ const MoveNoteSheet = ({
|
||||
<Button
|
||||
height={40}
|
||||
style={{
|
||||
borderRadius: 100,
|
||||
paddingHorizontal: 24,
|
||||
alignSelf: "flex-start"
|
||||
}}
|
||||
title={strings.save()}
|
||||
type={"accent"}
|
||||
type="accent"
|
||||
onPress={onSave}
|
||||
/>
|
||||
</View>
|
||||
</View>
|
||||
|
||||
<View
|
||||
<FlatList
|
||||
data={tree}
|
||||
windowSize={3}
|
||||
style={{
|
||||
paddingHorizontal: 0,
|
||||
maxHeight: dimensions.height * 0.85,
|
||||
height: dimensions.height * 0.85,
|
||||
paddingTop: 6
|
||||
width: "100%"
|
||||
}}
|
||||
>
|
||||
<FlashList
|
||||
data={notebooks?.placeholders}
|
||||
style={{
|
||||
width: "100%"
|
||||
}}
|
||||
keyboardShouldPersistTaps="handled"
|
||||
ListHeaderComponent={
|
||||
<View
|
||||
style={{
|
||||
paddingHorizontal: 12,
|
||||
width: "100%",
|
||||
paddingTop: 12
|
||||
keyboardShouldPersistTaps="handled"
|
||||
ListHeaderComponent={
|
||||
<View
|
||||
style={{
|
||||
paddingHorizontal: DefaultAppStyles.GAP,
|
||||
width: "100%",
|
||||
paddingTop: DefaultAppStyles.GAP_VERTICAL
|
||||
}}
|
||||
>
|
||||
<Input
|
||||
placeholder={strings.searchNotebooks()}
|
||||
button={{
|
||||
icon: "plus",
|
||||
onPress: () => {
|
||||
AddNotebookSheet.present(
|
||||
undefined,
|
||||
undefined,
|
||||
"link-notebooks",
|
||||
undefined,
|
||||
false,
|
||||
searchQuery.current
|
||||
);
|
||||
},
|
||||
color: colors.primary.icon
|
||||
}}
|
||||
>
|
||||
<Input
|
||||
placeholder={strings.searchNotebooks()}
|
||||
button={{
|
||||
icon: "plus",
|
||||
onPress: () => {
|
||||
AddNotebookSheet.present(
|
||||
undefined,
|
||||
undefined,
|
||||
"link-notebooks",
|
||||
undefined,
|
||||
false,
|
||||
searchQuery.current
|
||||
);
|
||||
},
|
||||
color: colors.primary.icon
|
||||
}}
|
||||
onChangeText={(value) => {
|
||||
searchQuery.current = value;
|
||||
if (!searchQuery.current) {
|
||||
setNotebooks(rootNotebooks);
|
||||
return;
|
||||
}
|
||||
searchTimer.current = setTimeout(() => {
|
||||
db.lookup
|
||||
.notebooks(searchQuery.current)
|
||||
.sorted()
|
||||
.then((result) => {
|
||||
if (searchQuery.current === value) {
|
||||
setNotebooks(result);
|
||||
}
|
||||
});
|
||||
}, 300);
|
||||
}}
|
||||
onChangeText={(value) => {
|
||||
searchQuery.current = value;
|
||||
searchTimer.current = setTimeout(() => {
|
||||
updateNotebooks();
|
||||
}, 300);
|
||||
}}
|
||||
/>
|
||||
</View>
|
||||
}
|
||||
renderItem={renderNotebook}
|
||||
ListEmptyComponent={
|
||||
<View
|
||||
style={{
|
||||
flex: 1,
|
||||
justifyContent: "center",
|
||||
alignItems: "center",
|
||||
height: 200
|
||||
}}
|
||||
>
|
||||
{loading ? (
|
||||
<ActivityIndicator
|
||||
size={AppFontSize.lg}
|
||||
color={colors.primary.accent}
|
||||
/>
|
||||
</View>
|
||||
}
|
||||
estimatedItemSize={50}
|
||||
renderItem={renderNotebook}
|
||||
ListEmptyComponent={
|
||||
<View
|
||||
style={{
|
||||
flex: 1,
|
||||
justifyContent: "center",
|
||||
alignItems: "center",
|
||||
height: 200
|
||||
}}
|
||||
>
|
||||
{loading ? (
|
||||
<ActivityIndicator
|
||||
size={AppFontSize.lg}
|
||||
color={colors.primary.accent}
|
||||
/>
|
||||
) : (
|
||||
<Paragraph color={colors.primary.icon}>
|
||||
{strings.emptyPlaceholders("notebook")}
|
||||
</Paragraph>
|
||||
)}
|
||||
</View>
|
||||
}
|
||||
ListFooterComponent={<View style={{ height: 50 }} />}
|
||||
/>
|
||||
</View>
|
||||
) : (
|
||||
<Paragraph color={colors.primary.icon}>
|
||||
{strings.emptyPlaceholders("notebook")}
|
||||
</Paragraph>
|
||||
)}
|
||||
</View>
|
||||
}
|
||||
/>
|
||||
</View>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
const NotebookItemWrapper = React.memo(
|
||||
({
|
||||
item,
|
||||
index,
|
||||
onPress
|
||||
}: {
|
||||
item: TreeItem;
|
||||
index: number;
|
||||
onPress?: () => void;
|
||||
}) => {
|
||||
const expanded = useNotebookExpandedStore(
|
||||
(state) => state.expanded[item.notebook.id]
|
||||
);
|
||||
const selectionEnabled = useNotebookSelectionStore(
|
||||
(state) => state.enabled
|
||||
);
|
||||
const selected = useNotebookSelectionStore(
|
||||
(state) => state.selection[item.notebook.id] === "selected"
|
||||
);
|
||||
|
||||
const onItemUpdate = React.useCallback(async () => {
|
||||
const notebook = await db.notebooks.notebook(item.notebook.id);
|
||||
if (notebook) {
|
||||
useNotebookTreeStore.getState().updateItem(item.notebook.id, notebook);
|
||||
if (expanded) {
|
||||
useNotebookTreeStore
|
||||
.getState()
|
||||
.setTree(
|
||||
await useNotebookTreeStore
|
||||
.getState()
|
||||
.fetchAndAdd(item.notebook.id, item.depth + 1)
|
||||
);
|
||||
}
|
||||
} else {
|
||||
useNotebookTreeStore.getState().removeItem(item.notebook.id);
|
||||
}
|
||||
}, [expanded, item.depth, item.notebook.id]);
|
||||
|
||||
return (
|
||||
<View
|
||||
style={{
|
||||
paddingHorizontal: DefaultAppStyles.GAP
|
||||
}}
|
||||
>
|
||||
<NotebookItem
|
||||
item={item}
|
||||
index={index}
|
||||
expanded={expanded}
|
||||
onToggleExpanded={async () => {
|
||||
useNotebookExpandedStore.getState().setExpanded(item.notebook.id);
|
||||
if (!expanded) {
|
||||
useNotebookTreeStore
|
||||
.getState()
|
||||
.setTree(
|
||||
await useNotebookTreeStore
|
||||
.getState()
|
||||
.fetchAndAdd(item.notebook.id, item.depth + 1)
|
||||
);
|
||||
} else {
|
||||
useNotebookTreeStore.getState().removeChildren(item.notebook.id);
|
||||
}
|
||||
}}
|
||||
onLongPress={() => {
|
||||
useNotebookSelectionStore.setState({
|
||||
multiSelect: !useNotebookSelectionStore.getState().multiSelect
|
||||
});
|
||||
|
||||
const state = useNotebookSelectionStore.getState();
|
||||
useNotebookSelectionStore
|
||||
.getState()
|
||||
.markAs(
|
||||
item.notebook,
|
||||
!selected
|
||||
? "selected"
|
||||
: !state.initialState[item.notebook.id]
|
||||
? undefined
|
||||
: "deselected"
|
||||
);
|
||||
}}
|
||||
canDisableSelectionMode={false}
|
||||
selected={selected}
|
||||
selectionEnabled={selectionEnabled}
|
||||
selectionStore={useNotebookSelectionStore}
|
||||
onItemUpdate={onItemUpdate}
|
||||
focused={false}
|
||||
onPress={onPress}
|
||||
onAddNotebook={() => {
|
||||
AddNotebookSheet.present(
|
||||
undefined,
|
||||
item.notebook,
|
||||
"link-notebooks",
|
||||
undefined,
|
||||
false
|
||||
);
|
||||
}}
|
||||
/>
|
||||
</View>
|
||||
);
|
||||
},
|
||||
(prev, next) => {
|
||||
return (
|
||||
prev.item.notebook.id === next.item.notebook.id &&
|
||||
prev.item.notebook.dateModified === next.item.notebook.dateModified &&
|
||||
prev.item.notebook.dateEdited === next.item.notebook.dateEdited &&
|
||||
prev.item.hasChildren === next.item.hasChildren &&
|
||||
prev.index === next.index &&
|
||||
prev.item.parentId === next.item.parentId
|
||||
);
|
||||
}
|
||||
);
|
||||
NotebookItemWrapper.displayName = "NotebookItemWrapper";
|
||||
|
||||
MoveNoteSheet.present = (note?: Note) => {
|
||||
presentSheet({
|
||||
component: (ref) => <MoveNoteSheet actionSheetRef={ref} note={note} />,
|
||||
enableGesturesInScrollView: false,
|
||||
noBottomPadding: true,
|
||||
keyboardHandlerDisabled: true
|
||||
component: (ref) => <MoveNoteSheet actionSheetRef={ref} note={note} />
|
||||
});
|
||||
};
|
||||
export default MoveNoteSheet;
|
||||
|
||||
@@ -1,263 +0,0 @@
|
||||
/*
|
||||
This file is part of the Notesnook project (https://notesnook.com/)
|
||||
|
||||
Copyright (C) 2023 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/>.
|
||||
*/
|
||||
import { Notebook, VirtualizedGrouping } from "@notesnook/core";
|
||||
import { useThemeColors } from "@notesnook/theme";
|
||||
import React, { useEffect } from "react";
|
||||
import { View, useWindowDimensions } from "react-native";
|
||||
import { notesnook } from "../../../../e2e/test.ids";
|
||||
import { useTotalNotes } from "../../../hooks/use-db-item";
|
||||
import { useNotebook } from "../../../hooks/use-notebook";
|
||||
import useNavigationStore from "../../../stores/use-navigation-store";
|
||||
import { AppFontSize } from "../../../utils/size";
|
||||
import { IconButton } from "../../ui/icon-button";
|
||||
import { Pressable } from "../../ui/pressable";
|
||||
import Paragraph from "../../ui/typography/paragraph";
|
||||
import { AddNotebookSheet } from "../add-notebook";
|
||||
import {
|
||||
useNotebookExpandedStore,
|
||||
useNotebookItemSelectionStore
|
||||
} from "./store";
|
||||
|
||||
type NotebookParentProp = {
|
||||
parent?: NotebookParentProp;
|
||||
item?: Notebook;
|
||||
};
|
||||
|
||||
export const NotebookItem = ({
|
||||
id,
|
||||
currentLevel = 0,
|
||||
index,
|
||||
parent,
|
||||
items
|
||||
}: {
|
||||
id: string | number;
|
||||
currentLevel?: number;
|
||||
index: number;
|
||||
parent?: NotebookParentProp;
|
||||
items?: VirtualizedGrouping<Notebook>;
|
||||
}) => {
|
||||
const { nestedNotebooks, notebook: item } = useNotebook(id, items, true);
|
||||
const expanded = useNotebookExpandedStore((state) =>
|
||||
item?.id ? state.expanded[item?.id] : false
|
||||
);
|
||||
const { totalNotes: totalNotes, getTotalNotes } = useTotalNotes("notebook");
|
||||
const focusedRouteId = useNavigationStore((state) => state.focusedRouteId);
|
||||
const { colors } = useThemeColors("sheet");
|
||||
const selection = useNotebookItemSelectionStore((state) =>
|
||||
item?.id ? state.selection[item?.id] : undefined
|
||||
);
|
||||
const isSelected = selection === "selected";
|
||||
const isFocused = focusedRouteId === id;
|
||||
const { fontScale } = useWindowDimensions();
|
||||
|
||||
useEffect(() => {
|
||||
if (item?.id) {
|
||||
getTotalNotes([item?.id]);
|
||||
}
|
||||
}, [getTotalNotes, item?.id]);
|
||||
|
||||
const onPress = () => {
|
||||
if (!item) return;
|
||||
const state = useNotebookItemSelectionStore.getState();
|
||||
|
||||
if (isSelected) {
|
||||
state.markAs(
|
||||
item,
|
||||
!state.initialState[item?.id] ? undefined : "deselected"
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!state.multiSelect) {
|
||||
const keys = Object.keys(state.selection);
|
||||
const nextState: any = {};
|
||||
for (const key in keys) {
|
||||
nextState[key] = !state.initialState[key] ? undefined : "deselected";
|
||||
}
|
||||
|
||||
state.setSelection({
|
||||
[item.id]: "selected",
|
||||
...nextState
|
||||
});
|
||||
} else {
|
||||
state.markAs(item, "selected");
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<View
|
||||
style={{
|
||||
paddingLeft: currentLevel > 0 && currentLevel < 6 ? 15 : undefined,
|
||||
width: "100%"
|
||||
}}
|
||||
>
|
||||
<Pressable
|
||||
type={"transparent"}
|
||||
onLongPress={() => {
|
||||
if (!item) return;
|
||||
useNotebookItemSelectionStore.setState({
|
||||
multiSelect: true
|
||||
});
|
||||
const state = useNotebookItemSelectionStore.getState();
|
||||
useNotebookItemSelectionStore
|
||||
.getState()
|
||||
.markAs(
|
||||
item,
|
||||
!isSelected
|
||||
? "selected"
|
||||
: !state.initialState[item?.id]
|
||||
? undefined
|
||||
: "deselected"
|
||||
);
|
||||
}}
|
||||
testID={`add-to-notebook-item-${currentLevel}-${index}`}
|
||||
onPress={onPress}
|
||||
style={{
|
||||
justifyContent: "space-between",
|
||||
width: "100%",
|
||||
alignItems: "center",
|
||||
flexDirection: "row",
|
||||
paddingLeft: 12,
|
||||
paddingRight: 12,
|
||||
borderRadius: 0,
|
||||
height: 45
|
||||
}}
|
||||
>
|
||||
<View
|
||||
style={{
|
||||
flexDirection: "row",
|
||||
alignItems: "center"
|
||||
}}
|
||||
>
|
||||
{nestedNotebooks?.placeholders.length ? (
|
||||
<IconButton
|
||||
size={AppFontSize.xl}
|
||||
color={isSelected ? colors.selected.icon : colors.primary.icon}
|
||||
onPress={() => {
|
||||
if (!item?.id) return;
|
||||
useNotebookExpandedStore.getState().setExpanded(item?.id);
|
||||
}}
|
||||
top={0}
|
||||
left={0}
|
||||
bottom={0}
|
||||
right={0}
|
||||
style={{
|
||||
width: 35,
|
||||
height: 35
|
||||
}}
|
||||
name={expanded ? "chevron-down" : "chevron-right"}
|
||||
/>
|
||||
) : null}
|
||||
|
||||
<IconButton
|
||||
size={AppFontSize.xl}
|
||||
color={
|
||||
isSelected
|
||||
? colors.selected.icon
|
||||
: selection === "deselected"
|
||||
? colors.error.accent
|
||||
: colors.primary.icon
|
||||
}
|
||||
onPress={onPress}
|
||||
top={0}
|
||||
left={0}
|
||||
bottom={0}
|
||||
right={0}
|
||||
style={{
|
||||
width: 40,
|
||||
height: 40
|
||||
}}
|
||||
name={
|
||||
selection === "deselected"
|
||||
? "close-circle-outline"
|
||||
: isSelected
|
||||
? "check-circle-outline"
|
||||
: selection === "intermediate"
|
||||
? "minus-circle-outline"
|
||||
: "checkbox-blank-circle-outline"
|
||||
}
|
||||
/>
|
||||
|
||||
<Paragraph
|
||||
color={
|
||||
isFocused ? colors.selected.paragraph : colors.secondary.paragraph
|
||||
}
|
||||
size={AppFontSize.sm}
|
||||
>
|
||||
{item?.title}
|
||||
</Paragraph>
|
||||
</View>
|
||||
|
||||
<View
|
||||
style={{
|
||||
flexDirection: "row",
|
||||
columnGap: 10,
|
||||
alignItems: "center"
|
||||
}}
|
||||
>
|
||||
{item?.id && totalNotes?.(item?.id) ? (
|
||||
<Paragraph size={AppFontSize.sm} color={colors.secondary.paragraph}>
|
||||
{totalNotes(item?.id)}
|
||||
</Paragraph>
|
||||
) : null}
|
||||
<IconButton
|
||||
name="plus"
|
||||
style={{
|
||||
width: 40 * fontScale,
|
||||
height: 40 * fontScale
|
||||
}}
|
||||
testID={notesnook.ids.notebook.menu}
|
||||
onPress={() => {
|
||||
if (!item) return;
|
||||
AddNotebookSheet.present(
|
||||
undefined,
|
||||
item,
|
||||
"link-notebooks",
|
||||
undefined,
|
||||
false
|
||||
);
|
||||
}}
|
||||
left={0}
|
||||
right={0}
|
||||
bottom={0}
|
||||
top={0}
|
||||
color={colors.primary.icon}
|
||||
size={AppFontSize.xl}
|
||||
/>
|
||||
</View>
|
||||
</Pressable>
|
||||
|
||||
{!expanded
|
||||
? null
|
||||
: nestedNotebooks?.placeholders.map((id, index) => (
|
||||
<NotebookItem
|
||||
key={item?.id + "_" + index}
|
||||
id={index}
|
||||
index={index}
|
||||
currentLevel={currentLevel + 1}
|
||||
items={nestedNotebooks}
|
||||
parent={{
|
||||
parent: parent,
|
||||
item: item
|
||||
}}
|
||||
/>
|
||||
))}
|
||||
</View>
|
||||
);
|
||||
};
|
||||
@@ -1,39 +0,0 @@
|
||||
/*
|
||||
This file is part of the Notesnook project (https://notesnook.com/)
|
||||
|
||||
Copyright (C) 2023 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/>.
|
||||
*/
|
||||
import create from "zustand";
|
||||
import { createItemSelectionStore } from "../../../stores/item-selection-store";
|
||||
|
||||
export const useNotebookExpandedStore = create<{
|
||||
expanded: {
|
||||
[id: string]: boolean;
|
||||
};
|
||||
setExpanded: (id: string) => void;
|
||||
}>((set, get) => ({
|
||||
expanded: {},
|
||||
setExpanded(id: string) {
|
||||
set({
|
||||
expanded: {
|
||||
...get().expanded,
|
||||
[id]: !get().expanded[id]
|
||||
}
|
||||
});
|
||||
}
|
||||
}));
|
||||
|
||||
export const useNotebookItemSelectionStore = createItemSelectionStore(true);
|
||||
@@ -63,9 +63,9 @@ export const MoveNotebookSheet = ({
|
||||
selectedNotebooks: Notebook[];
|
||||
close?: () => void;
|
||||
}) => {
|
||||
const tree = useNotebookTreeStore((state) => state.tree);
|
||||
const [notebooks, loading] = useNotebooks();
|
||||
const { colors } = useThemeColors();
|
||||
const tree = useNotebookTreeStore((state) => state.tree);
|
||||
const lastQuery = React.useRef<string>();
|
||||
const [filteredNotebooks, setFilteredNotebooks] = React.useState(notebooks);
|
||||
const [moveToTopEnabled, setMoveToTopEnabled] = useState(false);
|
||||
|
||||
@@ -48,7 +48,8 @@ export const NotebookItem = ({
|
||||
onItemUpdate,
|
||||
onPress,
|
||||
onLongPress,
|
||||
onAddNotebook
|
||||
onAddNotebook,
|
||||
canDisableSelectionMode
|
||||
}: {
|
||||
index: number;
|
||||
item: TreeItem;
|
||||
@@ -62,6 +63,7 @@ export const NotebookItem = ({
|
||||
onPress?: () => void;
|
||||
onLongPress?: () => void;
|
||||
onAddNotebook?: () => void;
|
||||
canDisableSelectionMode?: boolean;
|
||||
}) => {
|
||||
const notebook = item.notebook;
|
||||
const isFocused = focused;
|
||||
@@ -101,10 +103,34 @@ export const NotebookItem = ({
|
||||
testID={`notebook-item-${item.depth}-${index}`}
|
||||
onPress={async () => {
|
||||
if (selectionEnabled) {
|
||||
selectionStore
|
||||
.getState()
|
||||
.markAs(item.notebook, selected ? "deselected" : "selected");
|
||||
if (selectionStore.getState().getSelectedItemIds().length === 0) {
|
||||
const state = selectionStore.getState();
|
||||
|
||||
if (selected) {
|
||||
state.markAs(item.notebook, "deselected");
|
||||
return;
|
||||
}
|
||||
|
||||
if (!state.multiSelect) {
|
||||
const keys = Object.keys(state.selection);
|
||||
const nextState: any = {};
|
||||
for (const key in keys) {
|
||||
nextState[key] = !state.initialState[key]
|
||||
? undefined
|
||||
: "deselected";
|
||||
}
|
||||
|
||||
state.setSelection({
|
||||
[item.notebook.id]: "selected",
|
||||
...nextState
|
||||
});
|
||||
} else {
|
||||
state.markAs(item.notebook, "selected");
|
||||
}
|
||||
|
||||
if (
|
||||
selectionStore.getState().getSelectedItemIds().length === 0 &&
|
||||
canDisableSelectionMode
|
||||
) {
|
||||
selectionStore.setState({
|
||||
enabled: false
|
||||
});
|
||||
@@ -167,48 +193,59 @@ export const NotebookItem = ({
|
||||
</Paragraph>
|
||||
</View>
|
||||
|
||||
{selectionEnabled ? (
|
||||
<View
|
||||
style={{
|
||||
width: 25,
|
||||
height: 25,
|
||||
justifyContent: "center",
|
||||
alignItems: "center"
|
||||
}}
|
||||
>
|
||||
<AppIcon
|
||||
name={selected ? "checkbox-outline" : "checkbox-blank-outline"}
|
||||
size={AppFontSize.md}
|
||||
color={selected ? colors.selected.icon : colors.primary.icon}
|
||||
/>
|
||||
</View>
|
||||
) : onAddNotebook ? (
|
||||
<IconButton
|
||||
name="plus"
|
||||
size={AppFontSize.md}
|
||||
top={0}
|
||||
left={50}
|
||||
bottom={0}
|
||||
right={40}
|
||||
style={{
|
||||
width: 32,
|
||||
height: 32,
|
||||
borderRadius: defaultBorderRadius
|
||||
}}
|
||||
onPress={() => {
|
||||
onAddNotebook();
|
||||
}}
|
||||
/>
|
||||
) : (
|
||||
<>
|
||||
<Paragraph
|
||||
size={AppFontSize.xxs}
|
||||
color={colors.secondary.paragraph}
|
||||
<View
|
||||
style={{
|
||||
gap: DefaultAppStyles.GAP_SMALL,
|
||||
flexDirection: "row",
|
||||
alignItems: "center",
|
||||
justifyContent: "center"
|
||||
}}
|
||||
>
|
||||
{selectionEnabled ? (
|
||||
<View
|
||||
style={{
|
||||
width: 25,
|
||||
height: 25,
|
||||
justifyContent: "center",
|
||||
alignItems: "center"
|
||||
}}
|
||||
>
|
||||
{totalNotes?.(notebook?.id) || 0}
|
||||
</Paragraph>
|
||||
</>
|
||||
)}
|
||||
<AppIcon
|
||||
name={selected ? "checkbox-outline" : "checkbox-blank-outline"}
|
||||
size={AppFontSize.md}
|
||||
color={selected ? colors.selected.icon : colors.primary.icon}
|
||||
/>
|
||||
</View>
|
||||
) : (
|
||||
<>
|
||||
<Paragraph
|
||||
size={AppFontSize.xxs}
|
||||
color={colors.secondary.paragraph}
|
||||
>
|
||||
{totalNotes?.(notebook?.id) || 0}
|
||||
</Paragraph>
|
||||
</>
|
||||
)}
|
||||
|
||||
{onAddNotebook ? (
|
||||
<IconButton
|
||||
name="plus"
|
||||
size={AppFontSize.md}
|
||||
top={0}
|
||||
left={50}
|
||||
bottom={0}
|
||||
right={40}
|
||||
style={{
|
||||
width: 32,
|
||||
height: 32,
|
||||
borderRadius: defaultBorderRadius
|
||||
}}
|
||||
onPress={() => {
|
||||
onAddNotebook();
|
||||
}}
|
||||
/>
|
||||
) : null}
|
||||
</View>
|
||||
</Pressable>
|
||||
</View>
|
||||
);
|
||||
|
||||
@@ -39,7 +39,9 @@ import {
|
||||
useSideMenuNotebookSelectionStore,
|
||||
useSideMenuNotebookTreeStore
|
||||
} from "./stores";
|
||||
|
||||
useSideMenuNotebookSelectionStore.setState({
|
||||
multiSelect: true
|
||||
});
|
||||
export const SideMenuNotebooks = () => {
|
||||
const tree = useSideMenuNotebookTreeStore((state) => state.tree);
|
||||
const [notebooks, loading] = useNotebooks();
|
||||
|
||||
Reference in New Issue
Block a user