/* 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 . */ import { Notebook, VirtualizedGrouping } from "@notesnook/core"; import { useThemeColors } from "@notesnook/theme"; import React, { useEffect, useRef, useState } from "react"; import { RefreshControl, View, useWindowDimensions } from "react-native"; import ActionSheet, { ActionSheetRef } from "react-native-actions-sheet"; import { FlashList } from "react-native-actions-sheet/dist/src/views/FlashList"; import Config from "react-native-config"; import Icon from "react-native-vector-icons/MaterialCommunityIcons"; import create from "zustand"; import { notesnook } from "../../../../e2e/test.ids"; import { MMKV } from "../../../common/database/mmkv"; import { useNotebook } from "../../../hooks/use-notebook"; import NotebookScreen from "../../../screens/notebook"; import { openEditor } from "../../../screens/notes/common"; import { eSendEvent, presentSheet } from "../../../services/event-manager"; import { createItemSelectionStore } from "../../../stores/item-selection-store"; import useNavigationStore from "../../../stores/use-navigation-store"; import { useSelectionStore } from "../../../stores/use-selection-store"; import { eOnNotebookUpdated } from "../../../utils/events"; import { deleteItems } from "../../../utils/functions"; import { findRootNotebookId } from "../../../utils/notebooks"; import { SIZE, normalize } from "../../../utils/size"; import { Properties } from "../../properties"; import { IconButton } from "../../ui/icon-button"; import { Pressable } from "../../ui/pressable"; import Paragraph from "../../ui/typography/paragraph"; import { AddNotebookSheet } from "../add-notebook"; import Sort from "../sort"; const useItemSelectionStore = createItemSelectionStore(true, false); type NotebookParentProp = { parent?: NotebookParentProp; item?: Notebook; }; type ConfigItem = { id: string; type: string }; class NotebookSheetConfig { static storageKey: "$$sp"; static makeId(item: ConfigItem) { return `${NotebookSheetConfig.storageKey}:${item.type}:${item.id}`; } static get(item: ConfigItem) { return MMKV.getInt(NotebookSheetConfig.makeId(item)) || 0; } static set(item: ConfigItem, index = 0) { MMKV.setInt(NotebookSheetConfig.makeId(item), index); } } 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 NotebookSheet = () => { const [collapsed, setCollapsed] = useState(false); const currentRoute = useNavigationStore((state) => state.currentRoute); const focusedRouteId = useNavigationStore((state) => state.focusedRouteId); const enabled = useItemSelectionStore((state) => state.enabled); const canShow = currentRoute === "Notebook"; const { colors } = useThemeColors("sheet"); const ref = useRef(null); const currentItem = useRef(); const { fontScale } = useWindowDimensions(); const [root, setRoot] = useState(); const { onUpdate: onRequestUpdate, notebook, nestedNotebooks: notebooks, nestedNotebookNotesCount: totalNotes, groupOptions } = useNotebook( currentRoute === "Notebook" ? root : undefined, undefined, true ); const renderNotebook = ({ index }: { item: boolean; index: number }) => ( ); useEffect(() => { if (canShow) { setImmediate(async () => { if (!focusedRouteId) return; const nextRoot = await findRootNotebookId(focusedRouteId); if (nextRoot !== currentItem.current) { console.log( "NotebookSheet.useEffect.canShow", "Root changed to", nextRoot ); useItemSelectionStore.setState({ enabled: false, selection: {} }); } currentItem.current = nextRoot; const snapPoint = NotebookSheetConfig.get({ type: "notebook", id: nextRoot as string }); if (ref.current?.isOpen()) { ref.current?.snapToIndex(snapPoint); } else { setTimeout(() => { ref.current?.show(snapPoint); }, 150); } console.log("NotebookSheet.useEffect.didShow", focusedRouteId); setRoot(nextRoot); onRequestUpdate(); }); } else { if (ref.current?.isOpen()) { useItemSelectionStore.setState({ enabled: false, selection: {} }); ref.current?.hide(); } } }, [canShow, onRequestUpdate, focusedRouteId]); return ( { setCollapsed(index === 0); NotebookSheetConfig.set( { type: "notebook", id: focusedRouteId as string }, index ); }} overlayColor={colors.primary.backdrop} closable={!canShow} elevation={10} indicatorStyle={{ width: 100, backgroundColor: colors.secondary.background }} keyboardHandlerEnabled={false} snapPoints={Config.isTesting === "true" ? [100] : [20, 100]} initialSnapIndex={1} backgroundInteractionEnabled gestureEnabled > NOTEBOOKS {enabled ? ( <> { await deleteItems( useItemSelectionStore.getState().getSelectedItemIds(), "notebook" ); useSelectionStore.getState().clearSelection(); useItemSelectionStore.setState({ enabled: false, selection: {} }); return; }} color={colors.primary.icon} tooltipText="Move to trash" tooltipPosition={1} name="delete" size={22} /> { useSelectionStore.getState().clearSelection(); useItemSelectionStore.setState({ enabled: false, selection: {} }); }} color={colors.primary.icon} tooltipText="Clear selection" tooltipPosition={1} name="close" size={22} /> ) : ( <> { presentSheet({ component: }); }} testID="group-topic-button" color={colors.primary.icon} size={22} style={{ width: 40 * fontScale, height: 40 * fontScale }} /> { if (!notebook) return; AddNotebookSheet.present(undefined, notebook, undefined); }} testID="add-notebook-button" color={colors.primary.icon} size={22} style={{ width: 40 * fontScale, height: 40 * fontScale }} /> { if (ref.current?.currentSnapIndex() !== 0) { setCollapsed(true); ref.current?.snapToIndex(0); } else { setCollapsed(false); ref.current?.snapToIndex(1); } }} color={colors.primary.icon} size={22} style={{ width: 40 * fontScale, height: 40 * fontScale }} /> )} { eSendEvent(eOnNotebookUpdated); }} colors={[colors.primary.accent]} progressBackgroundColor={colors.primary.background} /> } renderItem={renderNotebook} ListEmptyComponent={ No notebooks } /> ); }; const NotebookItem = ({ id, totalNotes, currentLevel = 0, index, parent, items }: { id: string | number; totalNotes: (id: string) => number; currentLevel?: number; index: number; parent?: NotebookParentProp; items?: VirtualizedGrouping; }) => { const { nestedNotebookNotesCount, nestedNotebooks, notebook: item } = useNotebook(id, items, true); const isFocused = useNavigationStore((state) => state.focusedRouteId === id); const { colors } = useThemeColors("sheet"); const isSelected = useItemSelectionStore((state) => item?.id ? state.selection[item.id] === "selected" : false ); const enabled = useItemSelectionStore((state) => state.enabled); const { fontScale } = useWindowDimensions(); const expanded = useNotebookExpandedStore((state) => item?.id ? state.expanded[item?.id] : undefined ); return ( 0 && currentLevel < 6 ? 15 : undefined, width: "100%" }} > { if (enabled || !item) return; useItemSelectionStore.setState({ enabled: true, selection: {} }); useItemSelectionStore .getState() .markAs(item, isSelected ? "deselected" : "selected"); }} testID={`notebook-sheet-item-${currentLevel}-${index}`} onPress={() => { if (!item) return; if (enabled) { useItemSelectionStore .getState() .markAs(item, isSelected ? "deselected" : "selected"); return; } NotebookScreen.navigate(item, true); }} style={{ justifyContent: "space-between", width: "100%", alignItems: "center", flexDirection: "row", paddingHorizontal: 12, borderRadius: 0 }} > {nestedNotebooks?.placeholders.length ? ( { 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"} /> ) : ( )} {enabled ? ( ) : null} {item?.title} {item?.id && totalNotes?.(item?.id) ? ( {totalNotes(item?.id)} ) : null} { Properties.present(item); }} left={0} right={0} bottom={0} top={0} color={colors.primary.icon} size={SIZE.xl} /> {!expanded ? null : item && nestedNotebooks?.placeholders.map((id, index) => ( ))} ); };