diff --git a/apps/web/src/components/group-header/index.js b/apps/web/src/components/group-header/index.js index 76b176118..19b237f18 100644 --- a/apps/web/src/components/group-header/index.js +++ b/apps/web/src/components/group-header/index.js @@ -1,72 +1,130 @@ import * as Icon from "../icons"; -import React, { useState } from "react"; -import { Flex, Text } from "rebass"; +import React, { useMemo } from "react"; +import { Button, Flex, Text } from "rebass"; import Animated from "../animated"; -import { usePersistentState } from "../../utils/hooks"; -import { useStore as useNoteStore } from "../../stores/note-store"; -import { useTheme } from "emotion-theming"; +import { db } from "../../common/db"; +import { useOpenContextMenu } from "../../utils/useContextMenu"; +import { useStore as useSettingsStore } from "../../stores/setting-store"; +import useMobile from "../../utils/use-mobile"; -const groups = [ - { type: undefined, title: "Default" }, - { type: "abc", title: "Alphabetic" }, - { type: "year", title: "Year" }, - { type: "week", title: "Week" }, - { type: "month", title: "Month" }, +const groupByToTitleMap = { + [undefined]: "Default", + default: "Default", + abc: "A - Z", + year: "Year", + week: "Week", + month: "Month", +}; + +const menuItems = [ + { + key: "sortDirection:asc", + title: () => "Order by ascending", + }, + { key: "sortDirection:desc", title: () => "Order by descending" }, + { key: "orderSeperator", type: "seperator" }, + { + key: "sortBy:dateCreated", + title: () => "Sort by date created", + }, + { + key: "sortBy:dateEdited", + title: () => "Sort by date edited", + }, + { + key: "sortBy:title", + title: () => "Sort by title", + }, + { key: "sortSeperator", type: "seperator" }, + { key: "groupBy:default", title: () => "Group by default" }, + { key: "groupBy:year", title: () => "Group by year" }, + { key: "groupBy:month", title: () => "Group by month" }, + { key: "groupBy:week", title: () => "Group by week" }, + { key: "groupBy:abc", title: () => "Group by A - Z" }, ]; -function getGroupTitleByType(type) { - return groups.find((v) => v.type === type).title; +function changeGroupOptions({ groupOptions, type, refresh }, { key: itemKey }) { + let [key, value] = itemKey.split(":"); + groupOptions[key] = value; + if (key === "groupBy") { + if (value === "abc") groupOptions.sortBy = "title"; + else groupOptions.sortBy = "dateEdited"; +} + db.settings.setGroupOptions(type, groupOptions); + refresh(); } +const getMenuItems = (groupOptions) => { + return menuItems.map((item) => { + if (item.type === "seperator") return item; + let [key, value] = item.key.split(":"); + + item.checked = groupOptions[key] === value; + + if (key === "sortBy") { + if (value === "title") + item.disabled = () => groupOptions.groupBy !== "abc"; + else item.disabled = () => groupOptions.groupBy === "abc"; + } + + item.onClick = changeGroupOptions; + return item; + }); +}; + function GroupHeader(props) { - const { title, groups, onJump, wasJumpedTo, index } = props; - const [selectedGroup] = usePersistentState("selectedGroup", undefined); - const [isExpanded, setIsExpanded] = useState(false); - const [menuType, setMenuType] = useState(); - const theme = useTheme(); + const { title, groups, onJump, index, type, refresh } = props; + const groupOptions = useMemo(() => db.settings.getGroupOptions(type), [type]); + const openContextMenu = useOpenContextMenu(); + if (!title) return null; return ( { + onClick={(e) => { if (groups.length <= 0) return; - setMenuType("jumpto"); - setIsExpanded((s) => !s); + e.stopPropagation(); + const items = groups.map((group) => ({ + key: group.title, + title: () => group.title, + onClick: () => onJump(group.title), + checked: group.title === title, + })); + openContextMenu(e, items, { + title: "Jump to group", + }); }} - > - 0 ? [2, "8px"] : 1} alignItems="center" justifyContent="space-between" - sx={{ borderBottom: "1px solid", borderBottomColor: "border" }} + bg="bgSecondary" + sx={{ borderRadius: "default" }} > {title} + {index === 0 && ( + - ) : ( - - ) + groupOptions.sortDirection === "asc" + ? Icon.SortAsc + : Icon.SortDesc } - onClick={() => { - setMenuType("groupby"); - setIsExpanded((s) => !s); + title={`Grouped by ${groupByToTitleMap[groupOptions.groupBy]}`} + onClick={(e) => { + const items = getMenuItems(groupOptions); + openContextMenu(e, items, { + title: "Group & sort", + groupOptions, + refresh, + type, + }); }} /> )} @@ -99,121 +157,35 @@ function GroupHeader(props) { isVisible={menuType === "jumpto"} groups={groups} /> - + )} ); } export default GroupHeader; function IconButton(props) { - const { text, icon, onClick, textStyle, sx } = props; + const { text, title, onClick } = props; + const isMobile = useMobile(); return ( - { e.stopPropagation(); onClick(e); }} - px={1} - sx={{ - borderRadius: "default", - cursor: "pointer", - ":hover": { bg: "hover" }, - ...sx, - }} - alignItems="center" > - - {text} - - {icon} - - ); -} - -function JumpToGroupMenu(props) { - const { groups, isVisible, onJump } = props; - if (!isVisible) return null; - return ( - <> - - Jump to - - - {groups.map((group) => ( - onJump(group.title)} - sx={{ bg: "shade", mr: 1, mt: 1 }} - > - ))} - - - ); -} - -function GroupByMenu(props) { - const { isVisible } = props; - - const [sortDirection, setSortDirection] = usePersistentState( - "sortDirection", - "desc" - ); - const [selectedGroup, setSelectedGroup] = usePersistentState( - "selectedGroup", - undefined - ); - const refresh = useNoteStore((store) => store.refresh); - - if (!isVisible) return null; - return ( - <> - - - Group by - - - ) : ( - - ) - } - textStyle={{ mr: 1 }} - onClick={() => { - setSortDirection(sortDirection === "desc" ? "asc" : "desc"); - refresh(); - }} - /> - - {groups.map((item) => ( - { - setSelectedGroup(item.type); - refresh(); - }} - > - {item.title} - {selectedGroup === item.type && ( - + {text && {text}} + {props.icon && ( + )} - - ))} - + ); } diff --git a/apps/web/src/components/list-container/index.js b/apps/web/src/components/list-container/index.js index cb03d341e..ecf8b67bc 100644 --- a/apps/web/src/components/list-container/index.js +++ b/apps/web/src/components/list-container/index.js @@ -24,7 +24,6 @@ function ListContainer(props) { const profile = useMemo(() => ListProfiles[type], [type]); const shouldSelectAll = useSelectionStore((store) => store.shouldSelectAll); const setSelectedItems = useSelectionStore((store) => store.setSelectedItems); - const [jumpToIndex, setJumpToIndex] = useState(-1); const listRef = useRef(); useEffect(() => { @@ -86,27 +85,21 @@ function ListContainer(props) { case "header": return ( - v.type === "header" && - v.title !== item.title - )} - wasJumpedTo={index === jumpToIndex} + groups={props.items.filter((v) => v.type === "header")} onJump={(title) => { const index = props.items.findIndex( (v) => v.title === title ); if (index < 0) return; - setJumpToIndex(index); - listRef.current.scrollToItem( - index, - "center" - ); - setTimeout(() => { - setJumpToIndex(-1); - }, 1900); + listRef.current.scrollToIndex({ + index, + align: "center", + behavior: "smooth", + }); }} /> ); diff --git a/apps/web/src/components/menu/index.js b/apps/web/src/components/menu/index.js index 5e37d8a2b..006f0a1db 100644 --- a/apps/web/src/components/menu/index.js +++ b/apps/web/src/components/menu/index.js @@ -1,4 +1,5 @@ import { useAnimation } from "framer-motion"; +import { Check } from "../icons"; import React, { useEffect, useMemo } from "react"; import { Flex, Box, Text, Button } from "rebass"; import { useIsUserPremium } from "../../hooks/use-is-user-premium"; @@ -15,91 +16,116 @@ function Menu(props) { ); return ( - + {menuItems.map( - ({ - title, - key, - onClick, - component: Component, - color, - isPro, - isNew, - disabled, - disableReason, - icon: Icon, - }) => ( - - ) + if (!Component) { + onClick(data, menuItems[index]); + } + }} + display="flex" + flexDirection="row" + alignItems="center" + justifyContent="center" + py={"0.7em"} + px={3} + sx={{ + borderRadius: 0, + color: color || "text", + cursor: "pointer", + ":hover:not(:disabled)": { + backgroundColor: "hover", + }, + }} + > + {Icon && ( + + )} + {Component ? ( + + ) : ( + + {title(data)} + + )} + {isPro && !isUserPremium && ( + + Pro + + )} + {isNew && ( + + NEW + + )} + {checked && } + + ) )} ); @@ -108,7 +134,7 @@ export default React.memo(Menu, (prev, next) => { return prev.state === next.state; }); -function MenuContainer({ id, style, sx, children }) { +function MenuContainer({ id, style, sx, title, children }) { return ( @@ -133,7 +160,7 @@ function MenuContainer({ id, style, sx, children }) { px={3} sx={{ borderBottom: "1px solid", borderBottomColor: "border" }} > - Properties + {title || "Properties"} {children} @@ -141,7 +168,7 @@ function MenuContainer({ id, style, sx, children }) { ); } -function MobileMenuContainer({ style, id, state, children }) { +function MobileMenuContainer({ style, id, state, title, children }) { const animation = useAnimation(); useEffect(() => { @@ -190,7 +217,7 @@ function MobileMenuContainer({ style, id, state, children }) { /> - Properties + {title || "Properties"} {children} diff --git a/apps/web/src/stores/note-store.js b/apps/web/src/stores/note-store.js index 6dfaa20b3..d0fd442b5 100644 --- a/apps/web/src/stores/note-store.js +++ b/apps/web/src/stores/note-store.js @@ -10,6 +10,7 @@ import Config from "../utils/config"; import { showToast } from "../utils/toast"; import { qclone } from "qclone"; import { hashNavigate } from "../navigation"; +import { groupArray } from "notes-core/utils/grouping"; class NoteStore extends BaseStore { notes = []; @@ -30,12 +31,11 @@ class NoteStore extends BaseStore { }; refresh = () => { - const notes = db.notes.group( - Config.get("selectedGroup"), - Config.get("sortDirection", "desc") - ); this.set((state) => { - state.notes = qclone(notes); + state.notes = groupArray( + db.notes.all, + db.settings.getGroupOptions("home") + ); }); this.refreshContext(); }; diff --git a/apps/web/src/stores/notebook-store.js b/apps/web/src/stores/notebook-store.js index 82d7ef196..7f2026691 100644 --- a/apps/web/src/stores/notebook-store.js +++ b/apps/web/src/stores/notebook-store.js @@ -4,6 +4,7 @@ import { store as appStore } from "./app-store"; import { store as noteStore } from "./note-store"; import BaseStore from "./index"; import { showToast } from "../utils/toast"; +import { groupArray } from "notes-core/utils/grouping"; class NotebookStore extends BaseStore { notebooks = []; @@ -12,7 +13,10 @@ class NotebookStore extends BaseStore { refresh = () => { this.set((state) => { - state.notebooks = db.notebooks.all; + state.notebooks = groupArray( + db.notebooks.all, + db.settings.getGroupOptions("notebooks") + ); }); this.setSelectedNotebook(this.get().selectedNotebookId); }; @@ -34,10 +38,13 @@ class NotebookStore extends BaseStore { }; setSelectedNotebook = (id) => { - const topics = db.notebooks.notebook(id)?.topics.all; + const topics = db.notebooks.notebook(id)?.topics?.all; if (!topics) return; this.set((state) => { - state.selectedNotebookTopics = topics; + state.selectedNotebookTopics = groupArray( + topics, + db.settings.getGroupOptions("topics") + ); state.selectedNotebookId = id; }); }; diff --git a/apps/web/src/stores/tag-store.js b/apps/web/src/stores/tag-store.js index 731bef221..591ff6487 100644 --- a/apps/web/src/stores/tag-store.js +++ b/apps/web/src/stores/tag-store.js @@ -1,12 +1,19 @@ import createStore from "../common/store"; import { db } from "../common/db"; import BaseStore from "./index"; +import { groupArray } from "notes-core/utils/grouping"; class TagStore extends BaseStore { tags = []; refresh = () => { - this.set((state) => (state.tags = db.tags.all)); + this.set( + (state) => + (state.tags = groupArray( + db.tags.all, + db.settings.getGroupOptions("tags") + )) + ); }; } diff --git a/apps/web/src/stores/trash-store.js b/apps/web/src/stores/trash-store.js index c8489a77d..8ee0e0470 100644 --- a/apps/web/src/stores/trash-store.js +++ b/apps/web/src/stores/trash-store.js @@ -3,12 +3,19 @@ import createStore from "../common/store"; import BaseStore from "./index"; import { store as appStore } from "./app-store"; import { store as notestore } from "./note-store"; +import { groupArray } from "notes-core/utils/grouping"; class TrashStore extends BaseStore { trash = []; refresh = () => { - this.set((state) => (state.trash = db.trash.all)); + this.set( + (state) => + (state.trash = groupArray( + db.trash.all, + db.settings.getGroupOptions("trash") + )) + ); }; delete = (id, commit = false) => { @@ -23,7 +30,7 @@ class TrashStore extends BaseStore { restore = (id) => { return db.trash.restore(id).then(() => { - this.set((state) => (state.trash = db.trash.all)); + this.refresh(); appStore.refreshColors(); notestore.refresh(); }); diff --git a/apps/web/src/views/home.js b/apps/web/src/views/home.js index 5fce48509..a90b1e6f0 100644 --- a/apps/web/src/views/home.js +++ b/apps/web/src/views/home.js @@ -8,6 +8,7 @@ import useNavigate from "../utils/use-navigate"; function Home() { const notes = useStore((store) => store.notes); + const refresh = useStore((store) => store.refresh); const clearContext = useStore((store) => store.clearContext); const [isLoading, setIsLoading] = useState(true); useNavigate("home", () => { @@ -39,6 +40,8 @@ function Home() { return ( diff --git a/apps/web/src/views/notebooks.js b/apps/web/src/views/notebooks.js index 6e5ca3e69..710ac71ec 100644 --- a/apps/web/src/views/notebooks.js +++ b/apps/web/src/views/notebooks.js @@ -8,11 +8,14 @@ import useNavigate from "../utils/use-navigate"; function Notebooks() { useNavigate("notebooks", () => store.refresh()); const notebooks = useStore((state) => state.notebooks); + const refresh = useStore((state) => state.refresh); return ( <> store.context); + const refreshContext = useNotesStore((store) => store.refreshContext); + const type = context?.type === "favorite" ? "favorites" : "notes"; if (!context) return null; return ( store.refresh()); const tags = useStore((store) => store.tags); + const refresh = useStore((store) => store.refresh); return ( - + ); } diff --git a/apps/web/src/views/topics.js b/apps/web/src/views/topics.js index 12bbad9cd..b0ddf78bd 100644 --- a/apps/web/src/views/topics.js +++ b/apps/web/src/views/topics.js @@ -9,11 +9,14 @@ function Topics() { (store) => store.selectedNotebookTopics ); const selectedNotebookId = useNbStore((store) => store.selectedNotebookId); + const refresh = useNbStore((store) => store.setSelectedNotebook); return ( <> refresh(selectedNotebookId)} items={selectedNotebookTopics} context={{ notebookId: selectedNotebookId }} placeholder={TopicsPlaceholder} diff --git a/apps/web/src/views/trash.js b/apps/web/src/views/trash.js index 6058cfb86..0e4165c76 100644 --- a/apps/web/src/views/trash.js +++ b/apps/web/src/views/trash.js @@ -15,11 +15,14 @@ function Trash() { store.refresh(); }); const items = useStore((store) => store.trash); + const refresh = useStore((store) => store.refresh); const clearTrash = useStore((store) => store.clear); return (