diff --git a/apps/web/src/dialogs/move-note-dialog.tsx b/apps/web/src/dialogs/move-note-dialog.tsx index 20a89933d..94e30dc4a 100644 --- a/apps/web/src/dialogs/move-note-dialog.tsx +++ b/apps/web/src/dialogs/move-note-dialog.tsx @@ -17,41 +17,42 @@ You should have received a copy of the GNU General Public License along with this program. If not, see . */ -import { useCallback, useEffect, useState } from "react"; -import { Box, Button, Flex, Input, Text } from "@theme-ui/components"; +import { useCallback, useEffect, useRef } from "react"; +import { Button, Flex, Text } from "@theme-ui/components"; import { Plus, ChevronDown, - ChevronUp, Circle, CheckCircleOutline, CheckIntermediate, CheckRemove, - CircleEmpty + CircleEmpty, + ChevronRight } from "../components/icons"; import { db } from "../common/db"; import Dialog from "../components/dialog"; -import { useStore, store } from "../stores/notebook-store"; -import { store as notestore } from "../stores/note-store"; -import { Perform } from "../common/dialog-controller"; +import { useStore } from "../stores/notebook-store"; +import { store as noteStore } from "../stores/note-store"; +import { store as notebookStore } from "../stores/notebook-store"; +import { Perform, showAddNotebookDialog } from "../common/dialog-controller"; import { showToast } from "../utils/toast"; import { isMac } from "../utils/platform"; import { create } from "zustand"; -import { FilteredList } from "../components/filtered-list"; +import { Notebook, isGroupHeader } from "@notesnook/core/dist/types"; import { - Notebook, - GroupHeader, - isGroupHeader -} from "@notesnook/core/dist/types"; + UncontrolledTreeEnvironment, + Tree, + TreeItemIndex +} from "react-complex-tree"; +import { FlexScrollContainer } from "../components/scroll-container"; +import { pluralize } from "@notesnook/common"; type MoveDialogProps = { onClose: Perform; noteIds: string[] }; type NotebookReference = { id: string; - //topic?: string; new: boolean; op: "add" | "remove"; }; -type Item = Notebook | GroupHeader; interface ISelectionStore { selected: NotebookReference[]; @@ -70,17 +71,17 @@ function MoveDialog({ onClose, noteIds }: MoveDialogProps) { const setSelected = useSelectionStore((store) => store.setSelected); const setIsMultiselect = useSelectionStore((store) => store.setIsMultiselect); const isMultiselect = useSelectionStore((store) => store.isMultiselect); - const refreshNotebooks = useStore((store) => store.refresh); const notebooks = useStore((store) => store.notebooks); - const getAllNotebooks = useCallback(async () => { - await refreshNotebooks(); - return (store.get().notebooks?.ids.filter((a) => !isGroupHeader(a)) || - []) as string[]; - }, [refreshNotebooks]); + const reloadItem = useRef<(changedItemIds: TreeItemIndex[]) => void>(); useEffect(() => { - if (!notebooks) return; + if (!notebooks) { + (async function () { + await refreshNotebooks(); + })(); + return; + } // for (const notebook of notebooks.ids) { // if (isGroupHeader(notebook)) continue; @@ -138,7 +139,7 @@ function MoveDialog({ onClose, noteIds }: MoveDialogProps) { // new: false // }); // } - }, [noteIds, notebooks, setSelected, setIsMultiselect]); + }, [noteIds, notebooks, refreshNotebooks, setSelected, setIsMultiselect]); const _onClose = useCallback( (result: boolean) => { @@ -175,15 +176,16 @@ function MoveDialog({ onClose, noteIds }: MoveDialogProps) { } } - notestore.refresh(); + await noteStore.refresh(); + await notebookStore.refresh(); - // const stringified = stringifySelected(selected); - // if (stringified) { - // showToast( - // "success", - // `${pluralize(noteIds.length, "note")} ${stringified}` - // ); - // } + const stringified = stringifySelected(selected); + if (stringified) { + showToast( + "success", + `${pluralize(noteIds.length, "note")} ${stringified}` + ); + } _onClose(true); } @@ -204,202 +206,226 @@ function MoveDialog({ onClose, noteIds }: MoveDialogProps) { setSelected(originalSelection); setIsMultiselect(originalSelection.length > 1); }} - sx={{ textDecoration: "none", mt: 1 }} + sx={{ textDecoration: "none", mb: 2 }} > Reset selection )} - - [] + {notebooks && ( + + !isGroupHeader(t) + ) as string[] + }; + } - //db.lookup.notebooks(notebooks, query) || [] - } - onCreateNewItem={async (title) => { - await db.notebooks.add({ - title - }); - }} - renderItem={(notebook, _index, refresh, isSearching) => ( - notebooks?.item(id)} - isSearching={isSearching} - onCreateItem={async (title) => { - // await db.notebooks.topics(notebook).add({ title }); - // refresh(); - }} - /> - )} - /> - + const notebook = (await db.notebooks.notebook( + itemId as string + ))!; + const children = await db.relations + .from({ type: "notebook", id: itemId as string }, "notebook") + .get(); + return { + index: itemId, + data: notebook, + children: children.map((i) => i.toId), + isFolder: children.length > 0 + }; + }, + async getTreeItems(itemIds) { + const records = await db.notebooks.all.records( + itemIds as string[], + db.settings.getGroupOptions("notebooks") + ); + const children = await db.relations + .from( + { type: "notebook", ids: itemIds as string[] }, + "notebook" + ) + .get(); + console.log(itemIds, notebooks?.ids); + return itemIds.filter(Boolean).map((id) => { + if (id === "root") { + return { + data: { title: "Root" }, + index: id, + isFolder: true, + canMove: false, + canRename: false, + children: notebooks.ids.filter( + (t) => !isGroupHeader(t) + ) as string[] + }; + } + + const notebook = records[id]; + const subNotebooks = children + .filter((r) => r.fromId === id) + .map((r) => r.toId); + // const totalNotes = allChildren.filter( + // (r) => r.fromId === id && r.toType === "note" + // ).length; + return { + index: id, + data: notebook, + children: subNotebooks, + isFolder: subNotebooks.length > 0 + }; + }); + } + }} + renderItem={(props) => ( + <> + reloadItem.current?.([props.item.index])} + /> + + {props.children} + + )} + getItemTitle={(item) => item.data.title} + viewState={{}} + > + + + + )} ); } - +function calculateIndentation( + expandable: boolean, + depth: number, + base: number +) { + if (expandable && depth > 0) return depth * 7 + base; + else if (depth === 0) return 0; + else return depth * 12 + base; +} function NotebookItem(props: { - id: string; - isSearching: boolean; - resolve: (id: string) => Promise | undefined; - onCreateItem: (title: string) => void; + notebook: Notebook; + isExpanded: boolean; + isExpandable: boolean; + toggle: () => void; + depth: number; + onCreateItem: () => void; }) { - const { id, resolve, isSearching, onCreateItem } = props; - - const [isCreatingNew, setIsCreatingNew] = useState(false); - const [notebook, setNotebook] = useState(); + const { notebook, isExpanded, toggle, depth, isExpandable, onCreateItem } = + props; const setIsMultiselect = useSelectionStore((store) => store.setIsMultiselect); const setSelected = useSelectionStore((store) => store.setSelected); const isMultiselect = useSelectionStore((store) => store.isMultiselect); - useEffect(() => { - (async function () { - setNotebook(await resolve(id)); - })(); - }, [id, resolve]); + const check: React.MouseEventHandler = useCallback( + (e) => { + e.stopPropagation(); + e.preventDefault(); + + const { selected } = useSelectionStore.getState(); + + const isCtrlPressed = e.ctrlKey || e.metaKey; + if (isCtrlPressed) setIsMultiselect(true); + + if (isMultiselect || isCtrlPressed) { + setSelected(selectMultiple(notebook, selected)); + } else { + setSelected(selectSingle(notebook, selected)); + } + }, + [isMultiselect, notebook, setIsMultiselect, setSelected] + ); - if (!notebook) return null; return ( - - - { + if (!isExpandable) { + check(e); + return; + } + e.stopPropagation(); + e.preventDefault(); + toggle(); + }} + > + + {isExpandable ? ( + isExpanded ? ( + + ) : ( + + ) + ) : null} + + - { - e.preventDefault(); - e.stopPropagation(); - const { selected } = useSelectionStore.getState(); - - const isCtrlPressed = e.ctrlKey || e.metaKey; - if (isCtrlPressed) setIsMultiselect(true); - - if (isMultiselect || isCtrlPressed) { - setSelected(selectMultiple(notebook, selected)); - } else { - setSelected(selectSingle(notebook, selected)); - } - }} - > - - - {notebook.title} - {/* + {notebook.title} + {/* {" "} ({pluralize(notebook.topics.length, "topic")}) */} - - - - - - - - - - + + + + + + ); } @@ -412,54 +438,16 @@ function TopicSelectionIndicator({ notebook }: { notebook: Notebook }) { return ; } -// function TopicItem(props: { topic: Topic }) { -// const { topic } = props; - -// const setSelected = useSelectionStore((store) => store.setSelected); -// const setIsMultiselect = useSelectionStore((store) => store.setIsMultiselect); -// const isMultiselect = useSelectionStore((store) => store.isMultiselect); - -// return ( -// { -// const { selected } = useSelectionStore.getState(); - -// const isCtrlPressed = e.ctrlKey || e.metaKey; -// if (isCtrlPressed) setIsMultiselect(true); - -// if (isMultiselect || isCtrlPressed) { -// setSelected(selectMultiple(topic, selected)); -// } else { -// setSelected(selectSingle(topic, selected)); -// } -// }} -// > -// -// -// {topic.title} -// -// -// ); -// } - export default MoveDialog; function SelectedCheck({ item, - size = 20 + size = 20, + onClick }: { item?: Notebook; size?: number; + onClick?: React.MouseEventHandler; }) { const selectedItems = useSelectionStore((store) => store.selected); @@ -469,17 +457,28 @@ function SelectedCheck({ selectedItem?.op === "remove" ? "remove" : selectedItem?.op === "add"; return selected === true ? ( - + ) : selected === null ? ( ) : selected === "remove" ? ( - + ) : ( - + ); } @@ -537,31 +536,29 @@ function selectSingle(topic: Notebook, array: NotebookReference[]) { return selected; } -// function stringifySelected(suggestion: NotebookReference[]) { -// const added = suggestion -// .filter((a) => a.new && a.op === "add") -// .map(resolveReference) -// .filter(Boolean); -// const removed = suggestion -// .filter((a) => a.op === "remove") -// .map(resolveReference) -// .filter(Boolean); -// if (!added.length && !removed.length) return; +function stringifySelected(suggestion: NotebookReference[]) { + const added = suggestion.filter((a) => a.new && a.op === "add"); + // .map(resolveReference) + // .filter(Boolean); + const removed = suggestion.filter((a) => a.op === "remove"); + // .map(resolveReference) + // .filter(Boolean); + if (!added.length && !removed.length) return; -// const parts = []; -// if (added.length > 0) parts.push("added to"); -// if (added.length >= 1) parts.push(added[0]); -// if (added.length > 1) parts.push(`and ${added.length - 1} others`); + const parts = []; + if (added.length > 0) + parts.push(`added to ${pluralize(added.length, "notebook")}`); + // if (added.length >= 1) parts.push(added[0]); + // if (added.length > 1) parts.push(`and ${added.length - 1} others`); -// if (removed.length >= 1) { -// if (parts.length > 0) parts.push("&"); -// parts.push("removed from"); -// parts.push(removed[0]); -// } -// if (removed.length > 1) parts.push(`and ${removed.length - 1} others`); + if (removed.length >= 1) { + if (parts.length > 0) parts.push("&"); + parts.push(`removed from ${pluralize(added.length, "notebook")}`); + } + // if (removed.length > 1) parts.push(`and ${removed.length - 1} others`); -// return parts.join(" ") + "."; -// } + return parts.join(" ") + "."; +} // function resolveReference(ref: NotebookReference): string | undefined { // const notebook = db.notebooks.notebook(ref.id);