navigation: simplify nested navigation

This commit is contained in:
thecodrr
2020-04-20 11:20:42 +05:00
parent a57db4b590
commit 7c0b7d3b5e
14 changed files with 197 additions and 196 deletions

View File

@@ -9,6 +9,8 @@ import { useStore as useEditorStore } from "./stores/editor-store";
import { useStore as useUserStore } from "./stores/user-store";
import Animated from "./components/animated";
import NavigationMenu from "./components/navigationmenu";
import NavigationContainer from "./navigation/container";
import RootNavigator from "./navigation/navigators/rootnavigator";
function App() {
const [show, setShow] = usePersistentState("isContainerVisible", true);
@@ -42,7 +44,6 @@ function App() {
<Flex variant="rowFill">
<Animated.Flex
variant="columnFill"
className="RootNavigator"
initial={{ width: "30%", opacity: 1, scaleY: 1 }}
animate={{
width: show ? "30%" : "0%",
@@ -55,7 +56,12 @@ function App() {
borderRight: "1px solid",
borderColor: "border",
}}
/>
>
<NavigationContainer
navigator={RootNavigator}
variant="columnFill"
/>
</Animated.Flex>
<Flex width="100%" className="EditorNavigator" />
</Flex>
<Box id="dialogContainer" />

View File

@@ -50,7 +50,7 @@ class MarkdownShortcuts {
this.quill.formatLine(selection.index, 0, "header", size - 1);
this.quill.deleteText(selection.index - size, size);
}, 0);
}
},
},
{
name: "blockquote",
@@ -61,7 +61,7 @@ class MarkdownShortcuts {
this.quill.formatLine(selection.index, 1, "blockquote", true);
this.quill.deleteText(selection.index - 2, 2);
}, 0);
}
},
},
{
name: "code-block",
@@ -72,7 +72,7 @@ class MarkdownShortcuts {
this.quill.formatLine(selection.index, 1, "code-block", true);
this.quill.deleteText(selection.index - 4, 4);
}, 0);
}
},
},
{
name: "bolditalic",
@@ -90,11 +90,11 @@ class MarkdownShortcuts {
this.quill.deleteText(startIndex, annotatedText.length);
this.quill.insertText(startIndex, matchedText, {
bold: true,
italic: true
italic: true,
});
this.quill.format("bold", false);
}, 0);
}
},
},
{
name: "bold",
@@ -113,7 +113,7 @@ class MarkdownShortcuts {
this.quill.insertText(startIndex, matchedText, { bold: true });
this.quill.format("bold", false);
}, 0);
}
},
},
{
name: "italic",
@@ -132,7 +132,7 @@ class MarkdownShortcuts {
this.quill.insertText(startIndex, matchedText, { italic: true });
this.quill.format("italic", false);
}, 0);
}
},
},
{
name: "strikethrough",
@@ -151,7 +151,7 @@ class MarkdownShortcuts {
this.quill.insertText(startIndex, matchedText, { strike: true });
this.quill.format("strike", false);
}, 0);
}
},
},
{
name: "code",
@@ -171,7 +171,7 @@ class MarkdownShortcuts {
this.quill.format("code", false);
this.quill.insertText(this.quill.getSelection(), " ");
}, 0);
}
},
},
{
name: "hr",
@@ -190,7 +190,7 @@ class MarkdownShortcuts {
this.quill.insertText(startIndex + 2, "\n", Quill.sources.SILENT);
this.quill.setSelection(startIndex + 2, Quill.sources.SILENT);
}, 0);
}
},
},
{
name: "asterisk-ul",
@@ -199,14 +199,13 @@ class MarkdownShortcuts {
setTimeout(() => {
let index = selection.index;
this.quill.formatLine(index, 1, "list", "unordered");
console.log(text, selection, pattern);
if (text.trim() === "*") {
this.quill.deleteText(index, 1);
} else if (text.trim() === "+") {
this.quill.deleteText(index - 2, 2);
}
}, 0);
}
},
},
{
name: "image",
@@ -227,7 +226,7 @@ class MarkdownShortcuts {
);
}, 0);
}
}
},
},
{
name: "link",
@@ -249,8 +248,8 @@ class MarkdownShortcuts {
);
}, 0);
}
}
}
},
},
];
// Handler that looks for insert deltas that match specific characters

View File

@@ -1,4 +1,4 @@
import React, { useEffect } from "react";
import React from "react";
import { Box } from "rebass";
import RootNavigator, {
bottomRoutes,
@@ -21,11 +21,6 @@ function NavigationMenu(props) {
const colors = useStore((store) => store.colors);
const isSideMenuOpen = useStore((store) => store.isSideMenuOpen);
useEffect(() => {
RootNavigator.navigate(selectedRoute);
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
return (
<Animated.Flex
flexDirection="column"

View File

@@ -35,7 +35,6 @@ class Notebook extends React.Component {
render() {
const { item, index, onClick, onTopicClick } = this.props;
const notebook = item;
console.log("rendering notebook", notebook.id);
return (
<ListItem
selectable

View File

@@ -0,0 +1,10 @@
import React, { useEffect } from "react";
import { Flex } from "rebass";
function NavigationContainer(props) {
useEffect(() => {
props.navigator.onLoad();
}, [props.navigator]);
return <Flex className={props.navigator.root} variant="columnFill" />;
}
export default NavigationContainer;

View File

@@ -1,12 +1,10 @@
import React from "react";
import ReactDOM from "react-dom";
import { Box, Flex, Heading, Text } from "rebass";
import Animated from "../components/animated";
import { AnimatePresence } from "framer-motion";
import * as Icon from "../components/icons";
import ThemeProvider from "../components/theme-provider";
import { useStore } from "../stores/app-store";
import { store as selectionStore } from "../stores/selection-store";
import Route from "./route";
import Config from "../utils/config";
class Navigator {
constructor(root, routes, options = {}) {
@@ -17,10 +15,24 @@ class Navigator {
this.lastRoute = undefined;
}
onLoad = () => {
const route = Config.get(this.root, this.getRoute(this.options.default));
this.navigate(route.key, route.params);
};
getRoute(key) {
return this.routes[key];
}
setLastRoute(route) {
this.lastRoute = route;
// cache the route in localStorage
// NOTE: we delete the navigator key if any so it's always new across refreshes
const copy = { ...route, params: { ...route.params } };
if (copy.params.navigator) delete copy.params.navigator;
Config.set(this.root, copy);
}
getRoot() {
return document.querySelector(`.${this.root}`);
}
@@ -37,7 +49,7 @@ class Navigator {
if (this.lastRoute) {
this.history.push(this.lastRoute);
}
this.lastRoute = route;
this.setLastRoute(route);
return this.renderRoute(route);
}
@@ -59,7 +71,7 @@ class Navigator {
flexDirection="column"
flex="1 1 auto"
>
<NavigationContainer
<Route
navigator={this}
route={route}
params={route.params}
@@ -80,7 +92,7 @@ class Navigator {
if (!route) {
return false;
}
this.lastRoute = route;
this.setLastRoute(route);
return this.renderRoute(this._mergeParams(route, params));
}
@@ -99,98 +111,3 @@ class Navigator {
}
}
export default Navigator;
function NavigationContainer(props) {
const toggleSideMenu = useStore((store) => store.toggleSideMenu);
const isSelectionMode = useStore((store) => store.isSelectionMode);
const exitSelectionMode = useStore((store) => store.exitSelectionMode);
const selectAll = useStore((store) => store.selectAll);
return (
<ThemeProvider>
<Flex flexDirection="column" px={2}>
{(props.route.title || props.route.params.title) && (
<>
<Flex alignItems="center" justifyContent="space-between">
<Flex alignItems="center" py={2}>
{props.canGoBack && (
<Box
onClick={props.backAction}
ml={-2}
height={38}
width={38}
>
<Icon.ChevronLeft size={38} color="fontPrimary" />
</Box>
)}
<Box
onClick={toggleSideMenu}
height={38}
ml={-5}
sx={{
display: ["block", "none", "none"],
}}
>
<Icon.Menu size={38} />
</Box>
<Heading
fontSize="heading"
color={props.route.titleColor || "text"}
>
{props.route.title || props.route.params.title}
</Heading>
</Flex>
{props.route.options && isSelectionMode && (
<Flex>
{props.route.options.map((option) => (
<Box
key={option.icon.name}
onClick={option.onClick}
mx={2}
sx={{ cursor: "pointer" }}
>
<option.icon />
</Box>
))}
</Flex>
)}
</Flex>
{props.route.params.subtitle && (
<Text
variant="title"
color="primary"
sx={{
marginBottom: 2,
cursor: isSelectionMode ? "pointer" : "normal",
}}
>
{props.route.params.subtitle}
</Text>
)}
{isSelectionMode && (
<Flex alignItems="center" mb={2} sx={{ cursor: "pointer" }}>
<Text
variant="title"
color="primary"
onClick={() => selectAll()}
>
Select all
</Text>
<Text
ml={2}
variant="title"
color="primary"
onClick={() => exitSelectionMode()}
>
Unselect
</Text>
</Flex>
)}
</>
)}
</Flex>
{props.route.component && (
<props.route.component navigator={props.navigator} {...props.params} />
)}
</ThemeProvider>
);
}

View File

@@ -6,16 +6,18 @@ import SelectionModeOptions from "../../common/selectionoptions";
const routes = {
...createRoute("notebooks", Notebooks, {
title: "Notebooks",
options: SelectionModeOptions.NotebooksOptions
options: SelectionModeOptions.NotebooksOptions,
}),
...createRoute("topics", Topics, {
options: SelectionModeOptions.TopicOptions
options: SelectionModeOptions.TopicOptions,
}),
...createRoute("notes", Notes, {
options: SelectionModeOptions.NotesOptions,
}),
...createRoute("notes", Notes, { options: SelectionModeOptions.NotesOptions })
};
const NotebookNavigator = new Navigator("NotebookNavigator", routes, {
backButtonEnabled: true
backButtonEnabled: true,
default: "notebooks",
});
export default NotebookNavigator;

View File

@@ -1,20 +1,18 @@
import {
Home,
SettingsContainer,
Trash,
NotebooksContainer,
TagsContainer,
Notes,
Account,
} from "../../views";
import { Home, Trash, Notes, Account } from "../../views";
import * as Icon from "../../components/icons";
import { createRoute, createNormalRoute, createDeadRoute } from "../routes";
import {
createRoute,
createNavigatorRoute,
createNormalRoute,
createDeadRoute,
} from "../routes";
import Navigator from "../index";
import SelectionModeOptions from "../../common/selectionoptions";
import Search from "../../views/Search";
import { store as userStore } from "../../stores/user-store";
import { store as themeStore } from "../../stores/theme-store";
import { showLogInDialog } from "../../components/dialogs/logindialog";
import { NotebookNavigator, TagNavigator, SettingsNavigator } from "./index";
export const bottomRoutes = {
...createDeadRoute("nightmode", Icon.Theme, {
@@ -32,9 +30,7 @@ export const bottomRoutes = {
} else return RootNavigator.navigate("account");
},
}),
...createRoute("settings", SettingsContainer, {
icon: Icon.Settings,
}),
...createNavigatorRoute("settings", Icon.Settings, SettingsNavigator),
};
export const routes = {
@@ -42,9 +38,7 @@ export const routes = {
title: "Home",
options: SelectionModeOptions.NotesOptions,
}),
...createRoute("notebooks", NotebooksContainer, {
icon: Icon.Notebook,
}),
...createNavigatorRoute("notebooks", Icon.Notebook, NotebookNavigator),
...createRoute(
"favorites",
Notes,
@@ -61,9 +55,7 @@ export const routes = {
title: "Trash",
options: SelectionModeOptions.TrashOptions,
}),
...createRoute("tags", TagsContainer, {
icon: Icon.Tag,
}),
...createNavigatorRoute("tags", Icon.Tag, TagNavigator),
};
const invisibleRoutes = {
@@ -79,6 +71,7 @@ const RootNavigator = new Navigator(
{ ...routes, ...bottomRoutes, ...invisibleRoutes },
{
backButtonEnabled: false,
default: "home",
}
);

View File

@@ -0,0 +1,102 @@
import React from "react";
import { Box, Flex, Heading, Text } from "rebass";
import * as Icon from "../components/icons";
import ThemeProvider from "../components/theme-provider";
import { useStore } from "../stores/app-store";
function Route(props) {
const toggleSideMenu = useStore((store) => store.toggleSideMenu);
const isSelectionMode = useStore((store) => store.isSelectionMode);
const exitSelectionMode = useStore((store) => store.exitSelectionMode);
const selectAll = useStore((store) => store.selectAll);
const navigator = props.params.navigator || props.navigator;
return (
<ThemeProvider>
<Flex flexDirection="column" px={2}>
{(props.route.title || props.route.params.title) && (
<>
<Flex alignItems="center" justifyContent="space-between">
<Flex alignItems="center" py={2}>
{props.canGoBack && (
<Box
onClick={props.backAction}
ml={-2}
height={38}
width={38}
>
<Icon.ChevronLeft size={38} color="fontPrimary" />
</Box>
)}
<Box
onClick={toggleSideMenu}
height={38}
ml={-5}
sx={{
display: ["block", "none", "none"],
}}
>
<Icon.Menu size={38} />
</Box>
<Heading
fontSize="heading"
color={props.route.titleColor || "text"}
>
{props.route.title || props.route.params.title}
</Heading>
</Flex>
{props.route.options && isSelectionMode && (
<Flex>
{props.route.options.map((option) => (
<Box
key={option.icon.name}
onClick={option.onClick}
mx={2}
sx={{ cursor: "pointer" }}
>
<option.icon />
</Box>
))}
</Flex>
)}
</Flex>
{props.route.params.subtitle && (
<Text
variant="title"
color="primary"
sx={{
marginBottom: 2,
cursor: isSelectionMode ? "pointer" : "normal",
}}
>
{props.route.params.subtitle}
</Text>
)}
{isSelectionMode && (
<Flex alignItems="center" mb={2} sx={{ cursor: "pointer" }}>
<Text
variant="title"
color="primary"
onClick={() => selectAll()}
>
Select all
</Text>
<Text
ml={2}
variant="title"
color="primary"
onClick={() => exitSelectionMode()}
>
Unselect
</Text>
</Flex>
)}
</>
)}
</Flex>
{props.route.component && (
<props.route.component navigator={navigator} {...props.params} />
)}
</ThemeProvider>
);
}
export default Route;

View File

@@ -1,14 +1,20 @@
import NavigationContainer from "./container";
export function createRoute(key, component, props = {}, params = {}) {
return {
[key]: {
key,
component,
...props,
params
}
params,
},
};
}
export function createNavigatorRoute(key, icon, navigator) {
return createRoute(key, NavigationContainer, { icon }, { navigator });
}
export function createNormalRoute(key, component, icon, props = {}) {
return createRoute(key, component, { title: component.name, icon, ...props });
}

View File

@@ -1,5 +1,4 @@
import React, { useState, useEffect } from "react";
import { Flex } from "rebass";
import { db } from "../common";
import Notebook from "../components/notebook";
import AddNotebookDialog from "../components/dialogs/addnotebookdialog";
@@ -64,15 +63,13 @@ function Notebooks(props) {
);
}
function NotebooksContainer() {
useEffect(() => {
const NotebookNavigator = require("../navigation/navigators/nbnavigator")
.default;
if (!NotebookNavigator.restore()) {
NotebookNavigator.navigate("notebooks");
}
}, []);
return <Flex variant="columnFill" className="NotebookNavigator" />;
}
/* function NotebooksContainer() {
return (
<NavigationContainer
variant="columnFill"
navigator={require("../navigation/navigators/nbnavigator").default}
/>
);
} */
export { NotebooksContainer, Notebooks };
export default Notebooks;

View File

@@ -1,4 +1,4 @@
import React, { useEffect } from "react";
import React from "react";
import { Box, Button, Flex, Text } from "rebass";
import * as Icon from "../components/icons";
import { useStore as useUserStore } from "../stores/user-store";
@@ -107,15 +107,4 @@ function Settings(props) {
);
}
function SettingsContainer() {
useEffect(() => {
const SettingsNavigator = require("../navigation/navigators/settingnavigator")
.default;
if (!SettingsNavigator.restore()) {
SettingsNavigator.navigate("settings");
}
}, []);
return <Flex variant="columnFill" className="SettingsNavigator" />;
}
export { Settings, SettingsContainer };
export default Settings;

View File

@@ -1,5 +1,5 @@
import React, { useEffect } from "react";
import { Flex, Text } from "rebass";
import { Text } from "rebass";
import ListContainer from "../components/list-container";
import ListItem from "../components/list-item";
import { useStore, store } from "../stores/tag-store";
@@ -48,15 +48,4 @@ function Tags(props) {
);
}
function TagsContainer() {
useEffect(() => {
const TagNavigator = require("../navigation/navigators/tagnavigator")
.default;
if (!TagNavigator.restore()) {
TagNavigator.navigate("tags");
}
}, []);
return <Flex variant="columnFill" className="TagNavigator" />;
}
export { Tags, TagsContainer };
export default Tags;

View File

@@ -1,12 +1,9 @@
export const Home = require("./Home").default;
export const NotebooksContainer = require("./Notebooks").NotebooksContainer;
export const Notebooks = require("./Notebooks").Notebooks;
export const Notebooks = require("./Notebooks").default;
export const Notes = require("./Notes").default;
export const Topics = require("./Topics").default;
export const Settings = require("./Settings").Settings;
export const Settings = require("./Settings").default;
export const Trash = require("./Trash").default;
export const Account = require("./Account").default;
export const SettingsContainer = require("./Settings").SettingsContainer;
export const Tags = require("./Tags").Tags;
export const TagsContainer = require("./Tags").TagsContainer;
export const Tags = require("./Tags").default;
export const Search = require("./Search").default;