Files
notesnook/packages/editor-mobile/src/components/header.tsx
2024-10-10 12:35:58 +05:00

499 lines
15 KiB
TypeScript

/*
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 <http://www.gnu.org/licenses/>.
*/
import { ControlledMenu, MenuItem as MenuItemInner } from "@szhsin/react-menu";
import ArrowBackIcon from "mdi-react/ArrowBackIcon";
import ArrowULeftTopIcon from "mdi-react/ArrowULeftTopIcon";
import ArrowURightTopIcon from "mdi-react/ArrowURightTopIcon";
import DotsHorizontalIcon from "mdi-react/DotsHorizontalIcon";
import DotsVerticalIcon from "mdi-react/DotsVerticalIcon";
import FullscreenIcon from "mdi-react/FullscreenIcon";
import MagnifyIcon from "mdi-react/MagnifyIcon";
import PencilLockIcon from "mdi-react/PencilLockIcon";
import TableOfContentsIcon from "mdi-react/TableOfContentsIcon";
import React, { useRef, useState } from "react";
import { useSafeArea } from "../hooks/useSafeArea";
import { useTabContext, useTabStore } from "../hooks/useTabStore";
import { EventTypes, Settings } from "../utils";
import styles from "./styles.module.css";
import { strings } from "@notesnook/intl";
const menuClassName = ({ state }: any) =>
state === "opening"
? styles.menuOpening
: state === "closing"
? styles.menuClosing
: styles.menu;
const menuItemClassName = ({ hover, disabled }: any) =>
disabled
? styles.menuItemDisabled
: hover
? styles.menuItemHover
: styles.menuItem;
const MenuItem = (props: any) => (
<MenuItemInner {...props} className={menuItemClassName} />
);
const Button = ({
onPress,
children,
style,
preventDefault = true,
fwdRef,
onClick
}: {
onPress?: () => void;
onClick?: (event: any) => void;
children: React.ReactNode;
style: React.CSSProperties;
preventDefault?: boolean;
fwdRef?: any;
}) => {
return (
<button
ref={fwdRef}
className={styles.btn_header}
style={style}
onMouseDown={(e) => {
if (preventDefault) e.preventDefault();
onPress?.();
onClick?.(e);
}}
>
{children}
</button>
);
};
function Header({
noHeader,
settings,
hasUndo,
hasRedo
}: {
noHeader: boolean;
settings: Settings;
hasUndo: boolean;
hasRedo: boolean;
}): JSX.Element {
const tab = useTabContext();
const editor = editors[tab.id];
const insets = useSafeArea();
const openedTabsCount = useTabStore((state) => state.tabs.length);
const [isOpen, setOpen] = useState(false);
const btnRef = useRef(null);
return (
<div
style={{
display: "flex",
alignItems: "center",
height: noHeader ? `${insets.top}px` : `${50 + insets.top}px`,
backgroundColor: "var(--nn_primary_background)",
position: "sticky",
width: "100vw",
zIndex: 99999
}}
>
{noHeader ? null : (
<div
style={{
display: "flex",
width: "100%",
justifyContent: "space-between",
flexDirection: "row",
paddingTop: insets.top,
height: 50,
alignItems: "center"
}}
id="header"
>
{settings.deviceMode !== "mobile" && !settings.fullscreen ? (
<div />
) : (
<Button
onPress={() => {
post(EventTypes.back, undefined, tab.id, tab.noteId);
}}
preventDefault={false}
style={{
borderWidth: 0,
borderRadius: 100,
color: "var(--nn_primary_icon)",
marginLeft: 6,
width: 40,
height: 40,
display: "flex",
justifyContent: "center",
alignItems: "center",
flexDirection: "column",
position: "relative"
}}
>
<ArrowBackIcon
size={28 * settings.fontScale}
style={{
position: "absolute"
}}
color="var(--nn_primary_icon)"
/>
</Button>
)}
<div
style={{
display: "flex",
alignItems: "center",
flexDirection: "row"
}}
>
{tab.locked ? null : (
<>
<Button
onPress={() => {
editor?.commands.undo();
}}
style={{
borderWidth: 0,
borderRadius: 100,
color: "var(--nn_primary_icon)",
marginRight: 10,
width: 39,
height: 39,
display: "flex",
justifyContent: "center",
alignItems: "center",
position: "relative"
}}
>
<ArrowULeftTopIcon
color={
!hasUndo
? "var(--nn_secondary_border)"
: "var(--nn_primary_icon)"
}
size={25 * settings.fontScale}
style={{
position: "absolute"
}}
/>
</Button>
<Button
onPress={() => {
if (tab.locked) return;
editor?.commands.redo();
}}
style={{
borderWidth: 0,
borderRadius: 100,
color: "var(--nn_primary_icon)",
marginRight: 10,
width: 39,
height: 39,
display: "flex",
justifyContent: "center",
alignItems: "center",
position: "relative"
}}
>
<ArrowURightTopIcon
color={
!hasRedo
? "var(--nn_secondary_border)"
: "var(--nn_primary_icon)"
}
size={25 * settings.fontScale}
style={{
position: "absolute"
}}
/>
</Button>
</>
)}
{settings.deviceMode !== "mobile" && !settings.fullscreen ? (
<Button
onPress={() => {
post(EventTypes.fullscreen, undefined, tab.id, tab.noteId);
}}
preventDefault={false}
style={{
borderWidth: 0,
borderRadius: 100,
color: "var(--nn_primary_icon)",
marginRight: 10,
width: 39,
height: 39,
display: "flex",
justifyContent: "center",
alignItems: "center",
position: "relative"
}}
>
<FullscreenIcon
size={25 * settings.fontScale}
style={{
position: "absolute"
}}
color="var(--nn_primary_icon)"
/>
</Button>
) : null}
{tab.readonly ? (
<Button
onPress={() => {
post(
"editor-events:disable-readonly-mode",
useTabStore
.getState()
.getNoteIdForTab(useTabStore.getState().currentTab)
);
}}
fwdRef={btnRef}
preventDefault={false}
style={{
borderWidth: 0,
borderRadius: 100,
color: "var(--nn_primary_accent)",
marginRight: 12,
width: 39,
height: 39,
display: "flex",
justifyContent: "center",
alignItems: "center",
position: "relative"
}}
>
<PencilLockIcon
size={25 * settings.fontScale}
style={{
position: "absolute"
}}
color="var(--nn_primary_accent)"
/>
</Button>
) : null}
<Button
onPress={() => {
post(EventTypes.showTabs, undefined, tab.id, tab.noteId);
}}
preventDefault={false}
style={{
borderWidth: 0,
borderRadius: 100,
color: "var(--nn_primary_icon)",
marginRight: 12,
width: 39,
height: 39,
display: "flex",
justifyContent: "center",
alignItems: "center",
position: "relative"
}}
>
<div
style={{
border: "2px solid var(--nn_primary_icon)",
width: 19 * settings.fontScale,
height: 19 * settings.fontScale,
minWidth: 19 * settings.fontScale,
borderRadius: 5,
display: "flex",
justifyContent: "center",
alignItems: "center"
}}
>
<p
style={{
fontSize:
openedTabsCount > 100
? 10 * settings.fontScale
: 12 * settings.fontScale
}}
>
{openedTabsCount}
</p>
</div>
</Button>
<Button
fwdRef={btnRef}
onPress={() => {
if (tab.locked) {
post(EventTypes.properties, undefined, tab.id, tab.noteId);
} else {
setOpen(!isOpen);
}
}}
preventDefault={false}
style={{
borderWidth: 0,
borderRadius: 100,
color: "var(--nn_primary_icon)",
marginRight: 12,
width: 39,
height: 39,
display: "flex",
justifyContent: "center",
alignItems: "center",
position: "relative"
}}
>
{tab.locked ? (
<DotsHorizontalIcon
size={25 * settings.fontScale}
style={{
position: "absolute"
}}
color="var(--nn_primary_icon)"
/>
) : (
<DotsVerticalIcon
size={25 * settings.fontScale}
style={{
position: "absolute"
}}
color="var(--nn_primary_icon)"
/>
)}
</Button>
<ControlledMenu
align="end"
anchorPoint={{
x: window.innerWidth - 10,
y: 70
}}
state={isOpen ? "open" : "closed"}
menuClassName={menuClassName}
onClose={() => {
setOpen(false);
}}
autoFocus={false}
onItemClick={(e) => {
switch (e.value) {
case "toc":
post(
EventTypes.toc,
editorControllers[tab.id]?.getTableOfContents(),
tab.id,
tab.noteId
);
break;
case "search":
editor?.commands.startSearch();
break;
case "properties":
logger("info", "post properties...");
post(EventTypes.properties, undefined, tab.id, tab.noteId);
break;
default:
break;
}
}}
>
<MenuItem
value="search"
style={{
display: "flex",
gap: 10,
alignItems: "center"
}}
>
<MagnifyIcon
size={22 * settings.fontScale}
color="var(--nn_primary_icon)"
/>
<span
style={{
color: "var(--nn_primary_paragraph)"
}}
>
{strings.search()}
</span>
</MenuItem>
<MenuItem
value="toc"
style={{
display: "flex",
gap: 10,
alignItems: "center"
}}
>
<TableOfContentsIcon
size={22 * settings.fontScale}
color="var(--nn_primary_icon)"
/>
<span
style={{
color: "var(--nn_primary_paragraph)"
}}
>
{strings.toc()}
</span>
</MenuItem>
<MenuItem
value="properties"
style={{
display: "flex",
gap: 10,
alignItems: "center"
}}
>
<DotsHorizontalIcon
size={22 * settings.fontScale}
color="var(--nn_primary_icon)"
/>
<span
style={{
color: "var(--nn_primary_paragraph)"
}}
>
{strings.properties()}
</span>
</MenuItem>
</ControlledMenu>
</div>
</div>
)}
</div>
);
}
export default React.memo(Header, (prev, next) => {
if (
prev.settings.deviceMode !== next.settings.deviceMode ||
prev.settings.fullscreen !== next.settings.fullscreen ||
prev.settings.premium !== next.settings.premium ||
prev.settings.fontScale !== next.settings.fontScale ||
prev.noHeader !== next.noHeader ||
prev.hasRedo !== next.hasRedo ||
prev.hasUndo !== next.hasUndo
)
return false;
return true;
});