web: replace allotment with react-resizable-panels

This commit is contained in:
Abdullah Atta
2024-04-17 14:23:19 +05:00
parent d5cf65e568
commit a65ebec7ae
12 changed files with 1269 additions and 358 deletions

File diff suppressed because it is too large Load Diff

View File

@@ -39,7 +39,6 @@
"@trpc/client": "10.38.3", "@trpc/client": "10.38.3",
"@trpc/react-query": "10.38.3", "@trpc/react-query": "10.38.3",
"@zip.js/zip.js": "^2.7.32", "@zip.js/zip.js": "^2.7.32",
"allotment": "^1.19.3",
"async-mutex": "^0.4.0", "async-mutex": "^0.4.0",
"axios": "^1.3.4", "axios": "^1.3.4",
"clipboard-polyfill": "4.0.0", "clipboard-polyfill": "4.0.0",
@@ -77,6 +76,7 @@
"react-loading-skeleton": "^3.3.1", "react-loading-skeleton": "^3.3.1",
"react-modal": "3.16.1", "react-modal": "3.16.1",
"react-qrcode-logo": "^2.9.0", "react-qrcode-logo": "^2.9.0",
"react-resizable-panels": "^2.0.17",
"react-scroll-sync": "^0.11.2", "react-scroll-sync": "^0.11.2",
"react-virtuoso": "^4.6.2", "react-virtuoso": "^4.6.2",
"timeago.js": "4.0.2", "timeago.js": "4.0.2",

View File

@@ -1,36 +0,0 @@
diff --git a/node_modules/allotment/dist/modern.mjs b/node_modules/allotment/dist/modern.mjs
index d5dbdc6..e2d9d2e 100644
--- a/node_modules/allotment/dist/modern.mjs
+++ b/node_modules/allotment/dist/modern.mjs
@@ -1156,11 +1156,11 @@ class Ne extends W {
return e < 0 || e >= this.viewItems.length ? -1 : this.viewItems[e].size;
}
isViewVisible(e) {
- if (e < 0 || e >= this.viewItems.length) throw new Error("Index out of bounds");
+ if (e < 0 || e >= this.viewItems.length) return false;
return this.viewItems[e].visible;
}
setViewVisible(e, t) {
- if (e < 0 || e >= this.viewItems.length) throw new Error("Index out of bounds");
+ if (e < 0 || e >= this.viewItems.length) return;
this.viewItems[e].setVisible(t), this.distributeEmptySpace(e), this.layoutViews(), this.saveProportions();
}
distributeViewSizes() {
diff --git a/node_modules/allotment/dist/module.js b/node_modules/allotment/dist/module.js
index 0de655f..f296aff 100644
--- a/node_modules/allotment/dist/module.js
+++ b/node_modules/allotment/dist/module.js
@@ -1168,11 +1168,11 @@ class Ne extends W {
return e < 0 || e >= this.viewItems.length ? -1 : this.viewItems[e].size;
}
isViewVisible(e) {
- if (e < 0 || e >= this.viewItems.length) throw new Error("Index out of bounds");
+ if (e < 0 || e >= this.viewItems.length) return false;
return this.viewItems[e].visible;
}
setViewVisible(e, t) {
- if (e < 0 || e >= this.viewItems.length) throw new Error("Index out of bounds");
+ if (e < 0 || e >= this.viewItems.length) return;
this.viewItems[e].setVisible(t), this.distributeEmptySpace(e), this.layoutViews(), this.saveProportions();
}
distributeViewSizes() {

View File

@@ -14,16 +14,31 @@
-webkit-app-region: no-drag; -webkit-app-region: no-drag;
} }
.sash-module_sash__K-9lB.sash-module_hover__80W6I:before, .panel-resize-handle {
.sash-module_sash__K-9lB.sash-module_active__bJspD:before { position: relative;
background: var(--accent);
} }
.allotment-module_splitView__L-yRc.allotment-module_separatorBorder__x-rDS .panel-resize-handle::before {
> .allotment-module_splitViewContainer__rQnVa content: " ";
> .allotment-module_splitViewView__MGZ6O:not(:first-child)::before { position: absolute;
background-color: transparent; transition: background-color 300ms ease-out;
z-index: 999;
} }
.panel-resize-handle[data-panel-group-direction="horizontal"]::before {
width: 5px;
height: 100%;
}
.panel-resize-handle[data-panel-group-direction="vertical"]::before {
width: 100%;
height: 5px;
}
.panel-resize-handle[data-resize-handle-state="hover"]::before {
background-color: var(--accent);
}
/* open-sans-regular - vietnamese_latin-ext_latin_hebrew_greek-ext_greek_cyrillic-ext_cyrillic */ /* open-sans-regular - vietnamese_latin-ext_latin_hebrew_greek-ext_greek_cyrillic-ext_cyrillic */
@font-face { @font-face {
font-family: "Open Sans"; font-family: "Open Sans";

View File

@@ -17,13 +17,12 @@ You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>. along with this program. If not, see <http://www.gnu.org/licenses/>.
*/ */
import React, { useState, Suspense, useRef } from "react"; import React, { useState, Suspense, useRef, useEffect } from "react";
import { Box, Flex } from "@theme-ui/components"; import { Box, Flex } from "@theme-ui/components";
import { ScopedThemeProvider } from "./components/theme-provider"; import { ScopedThemeProvider } from "./components/theme-provider";
import useMobile from "./hooks/use-mobile"; import useMobile from "./hooks/use-mobile";
import useTablet from "./hooks/use-tablet"; import useTablet from "./hooks/use-tablet";
import useDatabase from "./hooks/use-database"; import useDatabase from "./hooks/use-database";
import { Allotment, AllotmentHandle, LayoutPriority } from "allotment";
import { useStore } from "./stores/app-store"; import { useStore } from "./stores/app-store";
import { Toaster } from "react-hot-toast"; import { Toaster } from "react-hot-toast";
import { ViewLoader } from "./components/loaders/view-loader"; import { ViewLoader } from "./components/loaders/view-loader";
@@ -33,7 +32,12 @@ import { EditorLoader } from "./components/loaders/editor-loader";
import { FlexScrollContainer } from "./components/scroll-container"; import { FlexScrollContainer } from "./components/scroll-container";
import CachedRouter from "./components/cached-router"; import CachedRouter from "./components/cached-router";
import { WebExtensionRelay } from "./utils/web-extension-relay"; import { WebExtensionRelay } from "./utils/web-extension-relay";
import { usePersistentState } from "./hooks/use-persistent-state"; import {
PanelGroup,
Panel,
PanelResizeHandle,
ImperativePanelHandle
} from "react-resizable-panels";
new WebExtensionRelay(); new WebExtensionRelay();
@@ -128,12 +132,19 @@ function DesktopAppContents({
}: DesktopAppContentsProps) { }: DesktopAppContentsProps) {
const isFocusMode = useStore((store) => store.isFocusMode); const isFocusMode = useStore((store) => store.isFocusMode);
const isTablet = useTablet(); const isTablet = useTablet();
const [paneSizes, setPaneSizes] = usePersistentState("paneSizes", [ const [isNarrow, setIsNarrow] = useState(isTablet || false);
isTablet ? 60 : 180, const navPane = useRef<ImperativePanelHandle>(null);
isTablet ? 240 : 380 const middlePane = useRef<ImperativePanelHandle>(null);
]);
const panesRef = useRef<AllotmentHandle>(null); useEffect(() => {
const [isNarrow, setIsNarrow] = useState(paneSizes[0] <= 55); if (show) middlePane.current?.expand();
else middlePane.current?.collapse();
}, [show]);
useEffect(() => {
if (isFocusMode) navPane.current?.collapse();
else navPane.current?.expand();
}, [isFocusMode]);
return ( return (
<> <>
@@ -143,20 +154,14 @@ function DesktopAppContents({
overflow: "hidden" overflow: "hidden"
}} }}
> >
<Allotment <PanelGroup autoSaveId="global-panel-group" direction="horizontal">
ref={panesRef} <Panel
proportionalLayout={false} ref={navPane}
onDragEnd={(sizes) => { className="nav-pane"
setPaneSizes(sizes); defaultSize={10}
setIsNarrow(sizes[0] <= 55); minSize={3}
}} onResize={(size) => setIsNarrow(size <= 3)}
> collapsible
<Allotment.Pane
className="pane nav-pane"
minSize={50}
preferredSize={isTablet ? 50 : paneSizes[0]}
visible={!isFocusMode}
priority={LayoutPriority.Low}
> >
<NavigationMenu <NavigationMenu
toggleNavigationContainer={(state) => { toggleNavigationContainer={(state) => {
@@ -164,13 +169,13 @@ function DesktopAppContents({
}} }}
isTablet={isNarrow} isTablet={isNarrow}
/> />
</Allotment.Pane> </Panel>
<Allotment.Pane <PanelResizeHandle className="panel-resize-handle" />
className="pane middle-pane" <Panel
minSize={2} ref={middlePane}
preferredSize={paneSizes[1]} className="middle-pane"
visible={show} collapsible
priority={LayoutPriority.Normal} defaultSize={20}
> >
<ScopedThemeProvider <ScopedThemeProvider
className="listMenu" className="listMenu"
@@ -185,11 +190,9 @@ function DesktopAppContents({
> >
{isAppLoaded && <CachedRouter />} {isAppLoaded && <CachedRouter />}
</ScopedThemeProvider> </ScopedThemeProvider>
</Allotment.Pane> </Panel>
<Allotment.Pane <PanelResizeHandle className="panel-resize-handle" />
className="pane editor-pane" <Panel className="editor-pane">
priority={LayoutPriority.High}
>
<Flex <Flex
sx={{ sx={{
display: "flex", display: "flex",
@@ -201,10 +204,9 @@ function DesktopAppContents({
> >
{isAppLoaded && <HashRouter />} {isAppLoaded && <HashRouter />}
</Flex> </Flex>
</Allotment.Pane> </Panel>
</Allotment> </PanelGroup>
</Flex> </Flex>
<StatusBar /> <StatusBar />
</> </>
); );

View File

@@ -67,6 +67,7 @@ export async function downloadAttachment<
type: TType, type: TType,
groupId?: string groupId?: string
): Promise<TOutputType | undefined> { ): Promise<TOutputType | undefined> {
console.log("DOWNLOADING FILE", hash);
const response = await download(hash, groupId); const response = await download(hash, groupId);
if (!response) return; if (!response) return;
const { attachment, key } = response; const { attachment, key } = response;

View File

@@ -56,7 +56,6 @@ import Titlebox from "./title-box";
import Config from "../../utils/config"; import Config from "../../utils/config";
import { ScopedThemeProvider } from "../theme-provider"; import { ScopedThemeProvider } from "../theme-provider";
import { Lightbox } from "../lightbox"; import { Lightbox } from "../lightbox";
import { Allotment } from "allotment";
import { showToast } from "../../utils/toast"; import { showToast } from "../../utils/toast";
import { Item, MaybeDeletedItem, isDeleted } from "@notesnook/core/dist/types"; import { Item, MaybeDeletedItem, isDeleted } from "@notesnook/core/dist/types";
import { debounce, debounceWithId } from "@notesnook/common"; import { debounce, debounceWithId } from "@notesnook/common";
@@ -69,6 +68,7 @@ import { scrollIntoViewById } from "@notesnook/editor";
import { IEditor } from "./types"; import { IEditor } from "./types";
import { EditorActionBar } from "./action-bar"; import { EditorActionBar } from "./action-bar";
import { logger } from "../../utils/logger"; import { logger } from "../../utils/logger";
import { PanelGroup, Panel, PanelResizeHandle } from "react-resizable-panels";
const PDFPreview = React.lazy(() => import("../pdf-preview")); const PDFPreview = React.lazy(() => import("../pdf-preview"));
@@ -120,13 +120,8 @@ export default function TabsView() {
flexDirection: "column" flexDirection: "column"
}} }}
> >
<Allotment <PanelGroup direction="horizontal" autoSaveId={"editor-panels"}>
proportionalLayout={true} <Panel id="editor-panel" className="editor-pane" order={1}>
onDragEnd={(sizes) => {
Config.set("editor:panesize", sizes[1]);
}}
>
<Allotment.Pane className="editor-pane">
{sessions.map((session) => ( {sessions.map((session) => (
<Freeze <Freeze
key={session.id} key={session.id}
@@ -164,12 +159,16 @@ export default function TabsView() {
)} )}
</Freeze> </Freeze>
))} ))}
</Allotment.Pane> </Panel>
{documentPreview && ( {documentPreview && (
<Allotment.Pane <>
minSize={450} <PanelResizeHandle className="panel-resize-handle" />
preferredSize={Config.get("editor:panesize", 500)} <Panel
id="pdf-preview-panel"
order={2}
minSize={35}
defaultSize={35}
> >
<ScopedThemeProvider <ScopedThemeProvider
scope="editorSidebar" scope="editorSidebar"
@@ -186,14 +185,18 @@ export default function TabsView() {
{documentPreview.url ? ( {documentPreview.url ? (
<Suspense <Suspense
fallback={ fallback={
<DownloadAttachmentProgress hash={documentPreview.hash} /> <DownloadAttachmentProgress
hash={documentPreview.hash}
/>
} }
> >
<PDFPreview <PDFPreview
fileUrl={documentPreview.url} fileUrl={documentPreview.url}
hash={documentPreview.hash} hash={documentPreview.hash}
onClose={() => onClose={() =>
useEditorStore.setState({ documentPreview: undefined }) useEditorStore.setState({
documentPreview: undefined
})
} }
/> />
</Suspense> </Suspense>
@@ -201,9 +204,10 @@ export default function TabsView() {
<DownloadAttachmentProgress hash={documentPreview.hash} /> <DownloadAttachmentProgress hash={documentPreview.hash} />
)} )}
</ScopedThemeProvider> </ScopedThemeProvider>
</Allotment.Pane> </Panel>
</>
)} )}
</Allotment> </PanelGroup>
<DropZone overlayRef={overlayRef} /> <DropZone overlayRef={overlayRef} />
{arePropertiesVisible && activeSessionId && ( {arePropertiesVisible && activeSessionId && (
<Properties sessionId={activeSessionId} /> <Properties sessionId={activeSessionId} />

View File

@@ -20,7 +20,6 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
import { useEffect, useState } from "react"; import { useEffect, useState } from "react";
import { initializeDatabase } from "../common/db"; import { initializeDatabase } from "../common/db";
import { useErrorBoundary } from "react-error-boundary"; import { useErrorBoundary } from "react-error-boundary";
import "allotment/dist/style.css";
import "../utils/analytics"; import "../utils/analytics";
import "../app.css"; import "../app.css";

View File

@@ -458,6 +458,7 @@ async function downloadFile(
requestOptions: RequestOptionsWithSignal requestOptions: RequestOptionsWithSignal
) { ) {
try { try {
console.log("DOWNLOADING FILE", filename);
const { url, headers, chunkSize, signal } = requestOptions; const { url, headers, chunkSize, signal } = requestOptions;
const handle = await streamablefs.readFile(filename); const handle = await streamablefs.readFile(filename);

View File

@@ -65,6 +65,7 @@ const routes = defineRoutes({
return defineRoute({ return defineRoute({
key: "notebook", key: "notebook",
type: "notes", type: "notes",
noCache: true,
component: Notebook, component: Notebook,
props: { props: {
rootId, rootId,

View File

@@ -29,6 +29,7 @@ export type RouteResult = {
| React.MemoExoticComponent<React.FunctionComponent>; | React.MemoExoticComponent<React.FunctionComponent>;
props?: any; props?: any;
buttons?: RouteContainerButtons; buttons?: RouteContainerButtons;
noCache?: boolean;
}; };
export function isRouteResult(obj: any): obj is RouteResult { export function isRouteResult(obj: any): obj is RouteResult {

View File

@@ -37,7 +37,6 @@ import {
ShortcutLink ShortcutLink
} from "../components/icons"; } from "../components/icons";
import { pluralize } from "@notesnook/common"; import { pluralize } from "@notesnook/common";
import { Allotment, AllotmentHandle } from "allotment";
import { Plus } from "../components/icons"; import { Plus } from "../components/icons";
import { useStore as useNotesStore } from "../stores/note-store"; import { useStore as useNotesStore } from "../stores/note-store";
import { useStore as useNotebookStore } from "../stores/notebook-store"; import { useStore as useNotebookStore } from "../stores/notebook-store";
@@ -58,6 +57,12 @@ import { FlexScrollContainer } from "../components/scroll-container";
import { Menu } from "../hooks/use-menu"; import { Menu } from "../hooks/use-menu";
import Config from "../utils/config"; import Config from "../utils/config";
import Notes from "./notes"; import Notes from "./notes";
import {
PanelGroup,
Panel,
PanelResizeHandle,
ImperativePanelHandle
} from "react-resizable-panels";
type NotebookProps = { type NotebookProps = {
rootId: string; rootId: string;
@@ -67,8 +72,7 @@ function Notebook(props: NotebookProps) {
const { rootId, notebookId } = props; const { rootId, notebookId } = props;
const [isCollapsed, setIsCollapsed] = useState(false); const [isCollapsed, setIsCollapsed] = useState(false);
const paneRef = useRef<AllotmentHandle>(null); const subNotebooksPane = useRef<ImperativePanelHandle>(null);
const sizes = useRef<number[]>([]);
const context = useNotesStore((store) => store.context); const context = useNotesStore((store) => store.context);
const notes = useNotesStore((store) => store.contextNotes); const notes = useNotesStore((store) => store.contextNotes);
@@ -96,40 +100,13 @@ function Notebook(props: NotebookProps) {
}); });
}, [rootId, notebookId]); }, [rootId, notebookId]);
const toggleCollapse = useCallback((isCollapsed: boolean) => {
if (!paneRef.current || !sizes.current) return;
if (!isCollapsed) {
if (sizes.current[1] < 60) {
paneRef.current.reset();
} else {
paneRef.current.resize(sizes.current);
}
}
}, []);
useEffect(() => {
toggleCollapse(isCollapsed);
}, [isCollapsed, toggleCollapse]);
if (!context || !notes || context.type !== "notebook") return null; if (!context || !notes || context.type !== "notebook") return null;
return ( return (
<> <PanelGroup
<Allotment direction="vertical"
ref={paneRef} autoSaveId={`notebook-panel-sizes:${rootId}`}
vertical
onChange={(paneSizes) => {
const [_, topicsPane] = paneSizes;
if (topicsPane > 30 && !isCollapsed) sizes.current = paneSizes;
}}
onDragEnd={([_, topicsPane]) => {
if (topicsPane < 35 && !isCollapsed) {
setIsCollapsed(true);
}
}}
> >
<Allotment.Pane> <Panel>
<Flex variant="columnFill" sx={{ height: "100%" }}>
<Notes <Notes
header={ header={
<NotebookHeader <NotebookHeader
@@ -139,12 +116,15 @@ function Notebook(props: NotebookProps) {
/> />
} }
/> />
</Flex> </Panel>
</Allotment.Pane> <PanelResizeHandle className="panel-resize-handle" />
<Allotment.Pane <Panel
preferredSize={250} ref={subNotebooksPane}
visible defaultSize={25}
maxSize={isCollapsed ? 30 : Infinity} collapsedSize={7}
collapsible
minSize={7}
onResize={(size) => setIsCollapsed(size <= 7)}
> >
<SubNotebooks <SubNotebooks
key={rootId} key={rootId}
@@ -152,12 +132,12 @@ function Notebook(props: NotebookProps) {
isCollapsed={isCollapsed} isCollapsed={isCollapsed}
rootId={rootId} rootId={rootId}
onClick={() => { onClick={() => {
setIsCollapsed((isCollapsed) => !isCollapsed); if (isCollapsed) subNotebooksPane.current?.expand();
else subNotebooksPane.current?.collapse();
}} }}
/> />
</Allotment.Pane> </Panel>
</Allotment> </PanelGroup>
</>
); );
} }
export default Notebook; export default Notebook;