web: more compact and unified action bar

This commit is contained in:
Abdullah Atta
2025-02-01 12:18:47 +05:00
parent 3b86e51c49
commit 3bcd288bc7
2 changed files with 184 additions and 168 deletions

View File

@@ -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>

View File

@@ -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);