mirror of
https://github.com/streetwriters/notesnook.git
synced 2025-12-23 15:09:33 +01:00
web: more compact and unified action bar
This commit is contained in:
@@ -29,6 +29,7 @@ import {
|
|||||||
FocusMode,
|
FocusMode,
|
||||||
Fullscreen,
|
Fullscreen,
|
||||||
Lock,
|
Lock,
|
||||||
|
NewTab,
|
||||||
NormalMode,
|
NormalMode,
|
||||||
Note,
|
Note,
|
||||||
NoteRemove,
|
NoteRemove,
|
||||||
@@ -107,6 +108,12 @@ export function EditorActionBar() {
|
|||||||
const isTablet = useTablet();
|
const isTablet = useTablet();
|
||||||
|
|
||||||
const tools = [
|
const tools = [
|
||||||
|
{
|
||||||
|
title: strings.newTab(),
|
||||||
|
icon: NewTab,
|
||||||
|
enabled: true,
|
||||||
|
onClick: () => useEditorStore.getState().addTab()
|
||||||
|
},
|
||||||
{
|
{
|
||||||
title: strings.undo(),
|
title: strings.undo(),
|
||||||
icon: Undo,
|
icon: Undo,
|
||||||
@@ -244,7 +251,10 @@ export function EditorActionBar() {
|
|||||||
mr:
|
mr:
|
||||||
hasNativeWindowControls && !isMac() && !isMobile && !isTablet
|
hasNativeWindowControls && !isMac() && !isMobile && !isTablet
|
||||||
? `calc(100vw - env(titlebar-area-width))`
|
? `calc(100vw - env(titlebar-area-width))`
|
||||||
: 0
|
: 1,
|
||||||
|
pl: 1,
|
||||||
|
borderLeft: "1px solid var(--border)",
|
||||||
|
flexShrink: 0
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{tools.map((tool) => (
|
{tools.map((tool) => (
|
||||||
@@ -255,14 +265,13 @@ export function EditorActionBar() {
|
|||||||
title={tool.title}
|
title={tool.title}
|
||||||
key={tool.title}
|
key={tool.title}
|
||||||
sx={{
|
sx={{
|
||||||
height: "100%",
|
p: 1,
|
||||||
alignItems: "center",
|
alignItems: "center",
|
||||||
bg: "transparent",
|
bg: "transparent",
|
||||||
display: [
|
display: [
|
||||||
"hideOnMobile" in tool && tool.hideOnMobile ? "none" : "flex",
|
"hideOnMobile" in tool && tool.hideOnMobile ? "none" : "flex",
|
||||||
tool.hidden ? "none" : "flex"
|
tool.hidden ? "none" : "flex"
|
||||||
],
|
],
|
||||||
borderRadius: 0,
|
|
||||||
flexShrink: 0,
|
flexShrink: 0,
|
||||||
"&:hover svg path": {
|
"&:hover svg path": {
|
||||||
fill:
|
fill:
|
||||||
@@ -273,7 +282,7 @@ export function EditorActionBar() {
|
|||||||
}}
|
}}
|
||||||
onClick={tool.onClick}
|
onClick={tool.onClick}
|
||||||
>
|
>
|
||||||
<tool.icon size={18} />
|
<tool.icon size={16} />
|
||||||
</Button>
|
</Button>
|
||||||
))}
|
))}
|
||||||
</Flex>
|
</Flex>
|
||||||
@@ -288,173 +297,172 @@ function TabStrip() {
|
|||||||
const canGoForward = useEditorStore((store) => store.canGoForward);
|
const canGoForward = useEditorStore((store) => store.canGoForward);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<ScrollContainer
|
<Flex sx={{ flex: 1 }}>
|
||||||
className="tabsScroll"
|
|
||||||
suppressScrollY
|
|
||||||
style={{ flex: 1, height: TITLE_BAR_HEIGHT }}
|
|
||||||
trackStyle={() => ({
|
|
||||||
backgroundColor: "transparent",
|
|
||||||
"--ms-track-size": "6px"
|
|
||||||
})}
|
|
||||||
thumbStyle={() => ({ height: 3 })}
|
|
||||||
onWheel={(e) => {
|
|
||||||
const scrollcontainer = document.querySelector(".tabsScroll");
|
|
||||||
if (!scrollcontainer) return;
|
|
||||||
if (e.deltaY > 0) scrollcontainer.scrollLeft += 100;
|
|
||||||
else if (e.deltaY < 0) scrollcontainer.scrollLeft -= 100;
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<Flex
|
<Flex
|
||||||
sx={{
|
sx={{
|
||||||
flex: 1,
|
px: 1,
|
||||||
height: "100%"
|
borderRight: "1px solid var(--border)",
|
||||||
|
alignItems: "center",
|
||||||
|
flexShrink: 0
|
||||||
}}
|
}}
|
||||||
onDoubleClick={async (e) => {
|
onDoubleClick={(e) => e.stopPropagation()}
|
||||||
e.stopPropagation();
|
>
|
||||||
useEditorStore.getState().addTab();
|
<Button
|
||||||
|
disabled={!canGoBack}
|
||||||
|
onClick={() => useEditorStore.getState().goBack()}
|
||||||
|
variant="secondary"
|
||||||
|
sx={{ p: 1, bg: "transparent" }}
|
||||||
|
data-test-id="go-back"
|
||||||
|
>
|
||||||
|
<ArrowLeft size={16} />
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
disabled={!canGoForward}
|
||||||
|
onClick={() => useEditorStore.getState().goForward()}
|
||||||
|
variant="secondary"
|
||||||
|
sx={{ p: 1, bg: "transparent" }}
|
||||||
|
data-test-id="go-forward"
|
||||||
|
>
|
||||||
|
<ArrowRight size={16} />
|
||||||
|
</Button>
|
||||||
|
</Flex>
|
||||||
|
<ScrollContainer
|
||||||
|
className="tabsScroll"
|
||||||
|
suppressScrollY
|
||||||
|
style={{ flex: 1, height: TITLE_BAR_HEIGHT }}
|
||||||
|
trackStyle={() => ({
|
||||||
|
backgroundColor: "transparent",
|
||||||
|
"--ms-track-size": "6px"
|
||||||
|
})}
|
||||||
|
thumbStyle={() => ({ height: 3 })}
|
||||||
|
onWheel={(e) => {
|
||||||
|
const scrollcontainer = document.querySelector(".tabsScroll");
|
||||||
|
if (!scrollcontainer) return;
|
||||||
|
if (e.deltaY > 0) scrollcontainer.scrollLeft += 100;
|
||||||
|
else if (e.deltaY < 0) scrollcontainer.scrollLeft -= 100;
|
||||||
}}
|
}}
|
||||||
data-test-id="tabs"
|
|
||||||
>
|
>
|
||||||
<Flex
|
<Flex
|
||||||
sx={{
|
sx={{
|
||||||
px: 1,
|
flex: 1,
|
||||||
borderRight: "1px solid var(--border)",
|
height: "100%"
|
||||||
alignItems: "center"
|
|
||||||
}}
|
}}
|
||||||
onDoubleClick={(e) => e.stopPropagation()}
|
onDoubleClick={async (e) => {
|
||||||
|
e.stopPropagation();
|
||||||
|
useEditorStore.getState().addTab();
|
||||||
|
}}
|
||||||
|
data-test-id="tabs"
|
||||||
>
|
>
|
||||||
<Button
|
<ReorderableList
|
||||||
disabled={!canGoBack}
|
items={tabs}
|
||||||
onClick={() => useEditorStore.getState().goBack()}
|
moveItem={(from, to) => {
|
||||||
variant="secondary"
|
if (from === to) return;
|
||||||
sx={{ p: 1, bg: "transparent" }}
|
const tabs = useEditorStore.getState().tabs.slice();
|
||||||
data-test-id="go-back"
|
const isToPinned = tabs[to].pinned;
|
||||||
>
|
const [fromTab] = tabs.splice(from, 1);
|
||||||
<ArrowLeft size={15} />
|
|
||||||
</Button>
|
|
||||||
<Button
|
|
||||||
disabled={!canGoForward}
|
|
||||||
onClick={() => useEditorStore.getState().goForward()}
|
|
||||||
variant="secondary"
|
|
||||||
sx={{ p: 1, bg: "transparent" }}
|
|
||||||
data-test-id="go-forward"
|
|
||||||
>
|
|
||||||
<ArrowRight size={15} />
|
|
||||||
</Button>
|
|
||||||
</Flex>
|
|
||||||
<ReorderableList
|
|
||||||
items={tabs}
|
|
||||||
moveItem={(from, to) => {
|
|
||||||
if (from === to) return;
|
|
||||||
const tabs = useEditorStore.getState().tabs.slice();
|
|
||||||
const isToPinned = tabs[to].pinned;
|
|
||||||
const [fromTab] = tabs.splice(from, 1);
|
|
||||||
|
|
||||||
// if the tab where this tab is being dropped is pinned,
|
// if the tab where this tab is being dropped is pinned,
|
||||||
// let's pin our tab too.
|
// let's pin our tab too.
|
||||||
if (isToPinned) {
|
if (isToPinned) {
|
||||||
fromTab.pinned = true;
|
fromTab.pinned = true;
|
||||||
}
|
}
|
||||||
// unpin the tab if it is moved.
|
// unpin the tab if it is moved.
|
||||||
else if (fromTab.pinned) fromTab.pinned = false;
|
else if (fromTab.pinned) fromTab.pinned = false;
|
||||||
|
|
||||||
tabs.splice(to, 0, fromTab);
|
tabs.splice(to, 0, fromTab);
|
||||||
useEditorStore.setState({ tabs });
|
useEditorStore.setState({ tabs });
|
||||||
}}
|
}}
|
||||||
renderItem={({ item: tab, index: i }) => {
|
renderItem={({ item: tab, index: i }) => {
|
||||||
const session = useEditorStore.getState().getSession(tab.sessionId);
|
const session = useEditorStore
|
||||||
if (!session) return null;
|
.getState()
|
||||||
|
.getSession(tab.sessionId);
|
||||||
|
if (!session) return null;
|
||||||
|
|
||||||
const isUnsaved =
|
const isUnsaved =
|
||||||
session.type === "default" &&
|
session.type === "default" &&
|
||||||
session.saveState === SaveState.NotSaved;
|
session.saveState === SaveState.NotSaved;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Tab
|
<Tab
|
||||||
id={tab.sessionId}
|
id={tab.sessionId}
|
||||||
key={tab.sessionId}
|
key={tab.sessionId}
|
||||||
title={
|
title={
|
||||||
session.title ||
|
session.title ||
|
||||||
("note" in session ? session.note.title : "Untitled")
|
("note" in session
|
||||||
}
|
? session.note.title
|
||||||
isUnsaved={isUnsaved}
|
: strings.untitled())
|
||||||
isActive={tab.id === currentTab}
|
|
||||||
isPinned={!!tab.pinned}
|
|
||||||
isLocked={isLockedSession(session)}
|
|
||||||
type={session.type}
|
|
||||||
onFocus={() => {
|
|
||||||
if (tab.id !== currentTab) {
|
|
||||||
useEditorStore.getState().focusTab(tab.id);
|
|
||||||
}
|
}
|
||||||
}}
|
isUnsaved={isUnsaved}
|
||||||
onClose={() => useEditorStore.getState().closeTabs(tab.id)}
|
isActive={tab.id === currentTab}
|
||||||
onCloseAll={() =>
|
isPinned={!!tab.pinned}
|
||||||
useEditorStore
|
isLocked={isLockedSession(session)}
|
||||||
.getState()
|
type={session.type}
|
||||||
.closeTabs(
|
onFocus={() => {
|
||||||
...tabs.filter((s) => !s.pinned).map((s) => s.id)
|
if (tab.id !== currentTab) {
|
||||||
)
|
useEditorStore.getState().focusTab(tab.id);
|
||||||
}
|
}
|
||||||
onCloseOthers={() =>
|
}}
|
||||||
useEditorStore
|
onClose={() => useEditorStore.getState().closeTabs(tab.id)}
|
||||||
.getState()
|
onCloseAll={() =>
|
||||||
.closeTabs(
|
useEditorStore
|
||||||
...tabs
|
.getState()
|
||||||
.filter((s) => s.id !== tab.id && !s.pinned)
|
.closeTabs(
|
||||||
.map((s) => s.id)
|
...tabs.filter((s) => !s.pinned).map((s) => s.id)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
onCloseToTheRight={() =>
|
onCloseOthers={() =>
|
||||||
useEditorStore
|
useEditorStore
|
||||||
.getState()
|
.getState()
|
||||||
.closeTabs(
|
.closeTabs(
|
||||||
...tabs
|
...tabs
|
||||||
.filter((s, index) => index > i && !s.pinned)
|
.filter((s) => s.id !== tab.id && !s.pinned)
|
||||||
.map((s) => s.id)
|
.map((s) => s.id)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
onCloseToTheLeft={() =>
|
onCloseToTheRight={() =>
|
||||||
useEditorStore
|
useEditorStore
|
||||||
.getState()
|
.getState()
|
||||||
.closeTabs(
|
.closeTabs(
|
||||||
...tabs
|
...tabs
|
||||||
.filter((s, index) => index < i && !s.pinned)
|
.filter((s, index) => index > i && !s.pinned)
|
||||||
.map((s) => s.id)
|
.map((s) => s.id)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
onRevealInList={
|
onCloseToTheLeft={() =>
|
||||||
"note" in session
|
useEditorStore
|
||||||
? () =>
|
.getState()
|
||||||
AppEventManager.publish(
|
.closeTabs(
|
||||||
AppEvents.revealItemInList,
|
...tabs
|
||||||
session.note.id,
|
.filter((s, index) => index < i && !s.pinned)
|
||||||
true
|
.map((s) => s.id)
|
||||||
)
|
)
|
||||||
: undefined
|
}
|
||||||
}
|
onRevealInList={
|
||||||
onPin={() => {
|
"note" in session
|
||||||
useEditorStore.setState((state) => {
|
? () =>
|
||||||
// preview tabs can never be pinned.
|
AppEventManager.publish(
|
||||||
state.tabs[i].pinned = !tab.pinned;
|
AppEvents.revealItemInList,
|
||||||
state.tabs.sort((a, b) =>
|
session.note.id,
|
||||||
a.pinned === b.pinned ? 0 : a.pinned ? -1 : 1
|
true
|
||||||
);
|
)
|
||||||
});
|
: undefined
|
||||||
}}
|
}
|
||||||
/>
|
onPin={() => {
|
||||||
);
|
useEditorStore.setState((state) => {
|
||||||
}}
|
// preview tabs can never be pinned.
|
||||||
/>
|
state.tabs[i].pinned = !tab.pinned;
|
||||||
<Button
|
state.tabs.sort((a, b) =>
|
||||||
variant="secondary"
|
a.pinned === b.pinned ? 0 : a.pinned ? -1 : 1
|
||||||
sx={{ p: 1, bg: "transparent", alignSelf: "center", ml: 1 }}
|
);
|
||||||
onClick={() => useEditorStore.getState().addTab()}
|
});
|
||||||
data-test-id="new-tab"
|
}}
|
||||||
>
|
/>
|
||||||
<Plus size={18} />
|
);
|
||||||
</Button>
|
}}
|
||||||
</Flex>
|
/>
|
||||||
</ScrollContainer>
|
</Flex>
|
||||||
|
</ScrollContainer>
|
||||||
|
</Flex>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -530,8 +538,9 @@ function Tab(props: TabProps) {
|
|||||||
sx={{
|
sx={{
|
||||||
height: "100%",
|
height: "100%",
|
||||||
cursor: "pointer",
|
cursor: "pointer",
|
||||||
px: 2,
|
pl: 2,
|
||||||
borderRight: "1px solid var(--border)",
|
borderRight: "1px solid var(--border)",
|
||||||
|
":last-of-type": { borderRight: 0 },
|
||||||
|
|
||||||
transform: CSS.Transform.toString(transform),
|
transform: CSS.Transform.toString(transform),
|
||||||
transition,
|
transition,
|
||||||
@@ -543,7 +552,7 @@ function Tab(props: TabProps) {
|
|||||||
flexShrink: 0,
|
flexShrink: 0,
|
||||||
":hover": {
|
":hover": {
|
||||||
"& .closeTabButton": {
|
"& .closeTabButton": {
|
||||||
visibility: "visible"
|
opacity: 1
|
||||||
},
|
},
|
||||||
bg: isActive ? "hover-selected" : "hover"
|
bg: isActive ? "hover-selected" : "hover"
|
||||||
}
|
}
|
||||||
@@ -607,14 +616,13 @@ function Tab(props: TabProps) {
|
|||||||
{...attributes}
|
{...attributes}
|
||||||
>
|
>
|
||||||
<Flex
|
<Flex
|
||||||
mr={1}
|
|
||||||
onMouseUp={(e) => {
|
onMouseUp={(e) => {
|
||||||
if (e.button == 0) onFocus();
|
if (e.button == 0) onFocus();
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Icon
|
<Icon
|
||||||
data-test-id={`tab-icon${isUnsaved ? "-unsaved" : ""}`}
|
data-test-id={`tab-icon${isUnsaved ? "-unsaved" : ""}`}
|
||||||
size={16}
|
size={14}
|
||||||
color={
|
color={
|
||||||
isUnsaved ? "accent-error" : isActive ? "accent-selected" : "icon"
|
isUnsaved ? "accent-error" : isActive ? "accent-selected" : "icon"
|
||||||
}
|
}
|
||||||
@@ -640,7 +648,8 @@ function Tab(props: TabProps) {
|
|||||||
sx={{
|
sx={{
|
||||||
":hover": { bg: "border" },
|
":hover": { bg: "border" },
|
||||||
borderRadius: "default",
|
borderRadius: "default",
|
||||||
flexShrink: 0
|
flexShrink: 0,
|
||||||
|
ml: 1
|
||||||
}}
|
}}
|
||||||
size={14}
|
size={14}
|
||||||
onMouseUp={(e) => {
|
onMouseUp={(e) => {
|
||||||
@@ -653,10 +662,14 @@ function Tab(props: TabProps) {
|
|||||||
) : (
|
) : (
|
||||||
<Cross
|
<Cross
|
||||||
sx={{
|
sx={{
|
||||||
visibility: isActive && active?.id !== id ? "visible" : "hidden",
|
|
||||||
":hover": { bg: "border" },
|
|
||||||
borderRadius: "default",
|
borderRadius: "default",
|
||||||
flexShrink: 0
|
flexShrink: 0,
|
||||||
|
opacity: isActive || active?.id === id ? 1 : 0,
|
||||||
|
ml: "small",
|
||||||
|
mr: 1,
|
||||||
|
"&:hover": {
|
||||||
|
bg: "hover-secondary"
|
||||||
|
}
|
||||||
}}
|
}}
|
||||||
onMouseUp={(e) => {
|
onMouseUp={(e) => {
|
||||||
if (e.button == 0) {
|
if (e.button == 0) {
|
||||||
@@ -665,7 +678,7 @@ function Tab(props: TabProps) {
|
|||||||
}}
|
}}
|
||||||
className="closeTabButton"
|
className="closeTabButton"
|
||||||
data-test-id={"tab-close-button"}
|
data-test-id={"tab-close-button"}
|
||||||
size={16}
|
size={14}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
</Flex>
|
</Flex>
|
||||||
|
|||||||
@@ -219,7 +219,8 @@ import {
|
|||||||
mdiOpenInNew,
|
mdiOpenInNew,
|
||||||
mdiTagOutline,
|
mdiTagOutline,
|
||||||
mdiChatQuestionOutline,
|
mdiChatQuestionOutline,
|
||||||
mdiNoteRemoveOutline
|
mdiNoteRemoveOutline,
|
||||||
|
mdiTabPlus
|
||||||
} from "@mdi/js";
|
} from "@mdi/js";
|
||||||
import { useTheme } from "@emotion/react";
|
import { useTheme } from "@emotion/react";
|
||||||
import { Theme } from "@notesnook/theme";
|
import { Theme } from "@notesnook/theme";
|
||||||
@@ -273,7 +274,8 @@ const MDIIconWrapper = memo(
|
|||||||
(prev, next) =>
|
(prev, next) =>
|
||||||
prev.rotate === next.rotate &&
|
prev.rotate === next.rotate &&
|
||||||
prev.color === next.color &&
|
prev.color === next.color &&
|
||||||
prev.title === next.title
|
prev.title === next.title &&
|
||||||
|
prev.size === next.size
|
||||||
);
|
);
|
||||||
|
|
||||||
export type IconProps = FlexProps & Omit<MDIIconWrapperProps, "path">;
|
export type IconProps = FlexProps & Omit<MDIIconWrapperProps, "path">;
|
||||||
@@ -560,3 +562,4 @@ export const ClearCache = createIcon(mdiBroom);
|
|||||||
export const OpenInNew = createIcon(mdiOpenInNew);
|
export const OpenInNew = createIcon(mdiOpenInNew);
|
||||||
export const Coupon = createIcon(mdiTagOutline);
|
export const Coupon = createIcon(mdiTagOutline);
|
||||||
export const Support = createIcon(mdiChatQuestionOutline);
|
export const Support = createIcon(mdiChatQuestionOutline);
|
||||||
|
export const NewTab = createIcon(mdiTabPlus);
|
||||||
|
|||||||
Reference in New Issue
Block a user