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 af3081866..d13a2e8ff 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 { presentDialog } from "../dialog/functions";
import { PressableButton } from "../ui/pressable";
import Heading from "../ui/typography/heading";
import Paragraph from "../ui/typography/paragraph";
+import ReorderableList from "../list/reorderable-list";
export const ColorSection = React.memo(
function ColorSection() {
@@ -48,11 +49,27 @@ export const ColorSection = React.memo(
}
}, [loading, setColorNotes]);
- return !loadingColors
- ? colorNotes.map((item) => {
+ 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 ;
- })
- : null;
+ }}
+ />
+ );
},
() => true
);
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 a5a600ef0..428d5d332 100644
--- a/apps/mobile/app/components/side-menu/index.js
+++ b/apps/mobile/app/components/side-menu/index.js
@@ -33,8 +33,14 @@ import { eOpenPremiumDialog } from "../../utils/events";
import { MenuItemsList } from "../../utils/menu-items";
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 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() {
@@ -44,6 +50,7 @@ export const SideMenu = React.memo(
(state) => state.user?.subscription?.type
);
const isAppLoading = useSettingStore((state) => state.isAppLoading);
+ const dragging = useSideBarDraggingStore((state) => state.dragging);
const introCompleted = useSettingStore(
(state) => state.settings.introCompleted
@@ -77,20 +84,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 +134,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 bf4ce38ab..95ef77ed3 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, { 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 { useSettingStore } from "../../stores/use-setting-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,9 +40,9 @@ import SheetWrapper from "../ui/sheet";
import Heading from "../ui/typography/heading";
import Paragraph from "../ui/typography/paragraph";
-export const TagsSection = React.memo(
- function TagsSection() {
- const [menuPins] = useMenuStore((state) => [state.menuPins]);
+export const PinnedSection = React.memo(
+ function PinnedSection() {
+ const menuPins = useMenuStore((state) => state.menuPins);
const loading = useSettingStore((state) => state.isAppLoading);
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 5ac177165..a2d0a55ce 100644
--- a/packages/core/src/types.ts
+++ b/packages/core/src/types.ts
@@ -34,6 +34,8 @@ export type SortOptions = {
sortDirection: "desc" | "asc";
};
+export type SideBarSectionKey = "menu" | "colors" | "pinned";
+
export type GroupOptions = SortOptions & {
groupBy: "none" | "abc" | "year" | "month" | "week" | "default";
};
@@ -431,7 +433,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