diff --git a/apps/mobile/app/components/list/reorderable-list.tsx b/apps/mobile/app/components/list/reorderable-list.tsx
new file mode 100644
index 000000000..3ecc57d0d
--- /dev/null
+++ b/apps/mobile/app/components/list/reorderable-list.tsx
@@ -0,0 +1,186 @@
+/*
+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 { useThemeColors } from "@notesnook/theme";
+import React, { useEffect, useRef, useState } from "react";
+import { FlatList, StyleSheet, View } from "react-native";
+import {
+ DraxList,
+ DraxListProps,
+ DraxListRenderItemContent,
+ DraxProvider
+} from "react-native-drax";
+import { tabBarRef } from "../../utils/global-refs";
+import { SIZE } from "../../utils/size";
+import { IconButton } from "../ui/icon-button";
+import Paragraph from "../ui/typography/paragraph";
+import { useSideBarDraggingStore } from "../side-menu/dragging-store";
+
+interface ReorderableListProps
+ extends Omit, "renderItem" | "data" | "renderItemContent"> {
+ onListOrderChanged: (data: string[]) => void;
+ renderDraggableItem: DraxListRenderItemContent;
+ data: T[];
+ itemOrder: string[];
+ hiddenItems: string[];
+ onHiddenItemsChanged: (data: string[]) => void;
+}
+
+function ReorderableList({
+ renderDraggableItem,
+ data,
+ onListOrderChanged,
+ hiddenItems = [],
+ itemOrder = [],
+ onHiddenItemsChanged,
+ ...restProps
+}: ReorderableListProps) {
+ const { colors } = useThemeColors();
+ const [itemOrderState, setItemsOrder] = useState(itemOrder);
+ const [hiddenItemsState, setHiddenItems] = useState(hiddenItems);
+ const dragging = useSideBarDraggingStore((state) => state.dragging);
+ const listRef = useRef(null);
+
+ if (dragging) {
+ tabBarRef.current?.lock();
+ } else {
+ tabBarRef.current?.unlock();
+ }
+
+ useEffect(() => {
+ setItemsOrder(itemOrder);
+ setHiddenItems(hiddenItems);
+ }, [itemOrder, hiddenItems]);
+
+ const renderItemContent: DraxListRenderItemContent = React.useCallback(
+ (info, props) => {
+ const isHidden = hiddenItemsState.indexOf(info?.item?.id) > -1;
+ return isHidden && !dragging ? null : (
+
+
+ {renderDraggableItem(info, props)}
+
+ {dragging ? (
+ {
+ const _hiddenItems = hiddenItemsState.slice();
+ const index = _hiddenItems.indexOf(info.item.id);
+ if (index === -1) {
+ _hiddenItems.push(info.item?.id);
+ } else {
+ _hiddenItems.splice(index, 1);
+ }
+ onHiddenItemsChanged(_hiddenItems);
+ setHiddenItems(_hiddenItems);
+ }}
+ />
+ ) : null}
+
+ );
+ },
+ [
+ colors.primary.icon,
+ dragging,
+ hiddenItemsState,
+ onHiddenItemsChanged,
+ renderDraggableItem
+ ]
+ );
+
+ function getOrderedItems() {
+ const items: T[] = [];
+ data.forEach((item) => {
+ const index = itemOrderState.indexOf(item?.id);
+ if (index === -1) {
+ items.push(item);
+ } else {
+ items.splice(index, 0, item);
+ }
+ });
+ console.log(items.map((item) => item.id));
+ return items;
+ }
+
+ return (
+
+
+
+ useSideBarDraggingStore.setState({
+ dragging: true
+ })
+ }
+ lockItemDragsToMainAxis
+ onItemReorder={({ fromIndex, fromItem, toIndex, toItem }) => {
+ const newOrder = getOrderedItems().map((item) => item.id);
+ const element = newOrder.splice(fromIndex, 1)[0];
+ if (toIndex === 0) {
+ newOrder.unshift(element);
+ } else {
+ newOrder.splice(toIndex, 0, element);
+ }
+ console.log(newOrder);
+ setItemsOrder(newOrder);
+ onListOrderChanged?.(newOrder);
+ }}
+ keyExtractor={(item) => (item as any).id}
+ />
+
+
+ );
+}
+
+const styles = StyleSheet.create({
+ container: {
+ flex: 1
+ }
+});
+
+export default ReorderableList;
diff --git a/apps/mobile/app/components/side-menu/color-section.tsx b/apps/mobile/app/components/side-menu/color-section.tsx
index 1c96cda5d..7a9a3dd39 100644
--- a/apps/mobile/app/components/side-menu/color-section.tsx
+++ b/apps/mobile/app/components/side-menu/color-section.tsx
@@ -32,6 +32,7 @@ import { PressableButton } from "../ui/pressable";
import Heading from "../ui/typography/heading";
import Paragraph from "../ui/typography/paragraph";
import { Color } from "@notesnook/core";
+import ReorderableList from "../list/reorderable-list";
export const ColorSection = React.memo(
function ColorSection() {
@@ -45,9 +46,27 @@ export const ColorSection = React.memo(
}
}, [loading, setColorNotes]);
- return colorNotes.map((item) => {
- return ;
- });
+ return (
+ {
+ db.settings.setSideBarOrder("colors", data);
+ }}
+ onHiddenItemsChanged={(data) => {
+ db.settings.setSideBarHiddenItems("colors", data);
+ }}
+ itemOrder={db.settings.getSideBarOrder("colors")}
+ hiddenItems={db.settings.getSideBarHiddenItems("colors")}
+ alwaysBounceVertical={false}
+ data={colorNotes}
+ style={{
+ width: "100%"
+ }}
+ showsVerticalScrollIndicator={false}
+ renderDraggableItem={({ item }) => {
+ return ;
+ }}
+ />
+ );
},
() => true
);
@@ -66,7 +85,7 @@ const ColorItem = React.memo(
const onHeaderStateChange = useCallback(
(state: any) => {
setTimeout(() => {
- let id = state.focusedRouteId;
+ const id = state.focusedRouteId;
if (id === item.id) {
setHeaderTextState({ id: state.currentScreen.id });
} else {
diff --git a/apps/mobile/app/components/side-menu/dragging-store.ts b/apps/mobile/app/components/side-menu/dragging-store.ts
new file mode 100644
index 000000000..121b14e93
--- /dev/null
+++ b/apps/mobile/app/components/side-menu/dragging-store.ts
@@ -0,0 +1,23 @@
+/*
+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 create from "zustand";
+
+export const useSideBarDraggingStore = create((set, get) => ({
+ dragging: false
+}));
diff --git a/apps/mobile/app/components/side-menu/index.js b/apps/mobile/app/components/side-menu/index.js
index bdf212e93..780036e7f 100644
--- a/apps/mobile/app/components/side-menu/index.js
+++ b/apps/mobile/app/components/side-menu/index.js
@@ -33,9 +33,15 @@ import { SUBSCRIPTION_STATUS } from "../../utils/constants";
import { eOpenPremiumDialog } from "../../utils/events";
import { ColorSection } from "./color-section";
import { MenuItem } from "./menu-item";
-import { TagsSection } from "./pinned-section";
+import { PinnedSection } from "./pinned-section";
import { UserStatus } from "./user-status";
import { useThemeStore } from "../../stores/use-theme-store";
+import ReorderableList from "../list/reorderable-list";
+import { db } from "../../common/database";
+import { useSideBarDraggingStore } from "./dragging-store";
+import Paragraph from "../ui/typography/paragraph";
+import { IconButton } from "../ui/icon-button";
+import { SIZE } from "../../utils/size";
export const SideMenu = React.memo(
function SideMenu() {
@@ -45,6 +51,8 @@ export const SideMenu = React.memo(
(state) => state.user?.subscription?.type
);
const loading = useNoteStore((state) => state.loading);
+ const dragging = useSideBarDraggingStore((state) => state.dragging);
+
const introCompleted = useSettingStore(
(state) => state.settings.introCompleted
);
@@ -77,20 +85,38 @@ export const SideMenu = React.memo(
eSendEvent(eOpenPremiumDialog);
}
};
-
const renderItem = useCallback(
() => (
<>
- {MenuItemsList.map((item, index) => (
-
- ))}
+ {
+ console.log(data);
+ db.settings.setSideBarOrder("menu", data);
+ }}
+ onHiddenItemsChanged={(data) => {
+ db.settings.setSideBarHiddenItems("menu", data);
+ }}
+ itemOrder={db.settings.getSideBarOrder("menu")}
+ hiddenItems={db.settings.getSideBarHiddenItems("menu")}
+ alwaysBounceVertical={false}
+ data={MenuItemsList}
+ style={{
+ width: "100%"
+ }}
+ showsVerticalScrollIndicator={false}
+ renderDraggableItem={({ item, index }) => {
+ return (
+
+ );
+ }}
+ />
-
+
>
),
[noTextMode]
@@ -109,22 +135,45 @@ export const SideMenu = React.memo(
height: "100%",
width: "100%",
backgroundColor: colors.primary.background,
- paddingTop: insets.top,
- borderRadius: 10,
- borderTopLeftRadius: 0,
- borderBottomLeftRadius: 0
+ paddingTop: insets.top
}}
>
+ {dragging ? (
+
+ REORDER SIDEBAR
+
+ {
+ useSideBarDraggingStore.setState({
+ dragging: false
+ });
+ }}
+ customStyle={{
+ width: 35,
+ height: 35
+ }}
+ />
+
+ ) : null}
+
"mainMenuView"}
diff --git a/apps/mobile/app/components/side-menu/pinned-section.tsx b/apps/mobile/app/components/side-menu/pinned-section.tsx
index 6b2df225f..f41c05e03 100644
--- a/apps/mobile/app/components/side-menu/pinned-section.tsx
+++ b/apps/mobile/app/components/side-menu/pinned-section.tsx
@@ -20,7 +20,7 @@ along with this program. If not, see .
import { Notebook, Tag } from "@notesnook/core";
import { useThemeColors } from "@notesnook/theme";
import React, { useCallback, useEffect, useRef, useState } from "react";
-import { FlatList, View } from "react-native";
+import { View } from "react-native";
import Icon from "react-native-vector-icons/MaterialCommunityIcons";
import { db } from "../../common/database";
import NotebookScreen from "../../screens/notebook";
@@ -30,6 +30,7 @@ import { useMenuStore } from "../../stores/use-menu-store";
import useNavigationStore from "../../stores/use-navigation-store";
import { useNoteStore } from "../../stores/use-notes-store";
import { SIZE, normalize } from "../../utils/size";
+import ReorderableList from "../list/reorderable-list";
import { Properties } from "../properties";
import { Button } from "../ui/button";
import { Notice } from "../ui/notice";
@@ -39,8 +40,8 @@ import SheetWrapper from "../ui/sheet";
import Heading from "../ui/typography/heading";
import Paragraph from "../ui/typography/paragraph";
-export const TagsSection = React.memo(
- function TagsSection() {
+export const PinnedSection = React.memo(
+ function PinnedSection() {
const menuPins = useMenuStore((state) => state.menuPins);
const loading = useNoteStore((state) => state.loading);
const setMenuPins = useMenuStore((state) => state.setMenuPins);
@@ -71,11 +72,27 @@ export const TagsSection = React.memo(
flexGrow: 1
}}
>
- {
+ db.settings.setSideBarOrder("pinned", data);
+ }}
+ onHiddenItemsChanged={(data) => {
+ db.settings.setSideBarHiddenItems("pinned", data);
+ }}
+ itemOrder={db.settings.getSideBarOrder("pinned")}
+ hiddenItems={db.settings.getSideBarHiddenItems("pinned")}
+ alwaysBounceVertical={false}
data={menuPins}
style={{
+ flexGrow: 1,
+ width: "100%",
+ paddingHorizontal: 12
+ }}
+ contentContainerStyle={{
flexGrow: 1
}}
+ showsVerticalScrollIndicator={false}
+ renderDraggableItem={renderItem}
ListEmptyComponent={
}
- contentContainerStyle={{
- flexGrow: 1
- }}
- keyExtractor={(item) => item.id}
- renderItem={renderItem}
/>
);
diff --git a/apps/mobile/app/utils/menu-items.ts b/apps/mobile/app/utils/menu-items.ts
index 1ace6d653..efd9c7f3a 100644
--- a/apps/mobile/app/utils/menu-items.ts
+++ b/apps/mobile/app/utils/menu-items.ts
@@ -20,32 +20,38 @@ along with this program. If not, see .
import { Monographs } from "../screens/notes/monographs";
export const MenuItemsList = [
{
+ id: "notes",
name: "Notes",
icon: "home-variant-outline",
close: true
},
{
+ id: "notebooks",
name: "Notebooks",
icon: "book-outline",
close: true
},
{
+ id: "favorites",
name: "Favorites",
icon: "star-outline",
close: true
},
{
+ id: "tags",
name: "Tags",
icon: "pound",
close: true
},
{
+ id: "reminders",
name: "Reminders",
icon: "bell",
close: true,
isBeta: true
},
{
+ id: "monographs",
name: "Monographs",
icon: "text-box-multiple-outline",
close: true,
@@ -54,6 +60,7 @@ export const MenuItemsList = [
}
},
{
+ id: "trash",
name: "Trash",
icon: "delete-outline",
close: true
diff --git a/packages/core/src/types.ts b/packages/core/src/types.ts
index 1b2543a41..d5d0e21cb 100644
--- a/packages/core/src/types.ts
+++ b/packages/core/src/types.ts
@@ -33,6 +33,8 @@ export type SortOptions = {
sortDirection: "desc" | "asc";
};
+export type SideBarSectionKey = "menu" | "colors" | "pinned";
+
export type GroupOptions = SortOptions & {
groupBy: "none" | "abc" | "year" | "month" | "week" | "default";
};
@@ -422,7 +424,9 @@ export type SettingItemMap = {
dateFormat: string;
defaultNotebook: string | undefined;
} & Record<`groupOptions:${GroupingKey}`, GroupOptions> &
- Record<`toolbarConfig:${ToolbarConfigPlatforms}`, ToolbarConfig | undefined>;
+ Record<`toolbarConfig:${ToolbarConfigPlatforms}`, ToolbarConfig | undefined> &
+ Record<`sideBarOrder:${SideBarSectionKey}`, string[]> &
+ Record<`sideBarHiddenItems:${SideBarSectionKey}`, string[]>;
export interface SettingItem<
TKey extends keyof SettingItemMap = keyof SettingItemMap