Skip to content

Commit 51a4bd2

Browse files
committed
refactor: zen modal implemention and fix macos traffic light overlay
Signed-off-by: Innei <[email protected]>
1 parent eaa5699 commit 51a4bd2

File tree

22 files changed

+251
-106
lines changed

22 files changed

+251
-106
lines changed

apps/main/src/menu.ts

+13
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import { callWindowExpose } from "@follow/shared/bridge"
12
import { dispatchEventOnWindow } from "@follow/shared/event"
23
import { name } from "@pkg"
34
import type { BrowserWindow, MenuItem, MenuItemConstructorOptions } from "electron"
@@ -130,6 +131,18 @@ export const registerAppMenu = () => {
130131
{ role: "zoomIn", label: t("menu.zoomIn") },
131132
{ role: "zoomOut", label: t("menu.zoomOut") },
132133
{ type: "separator" },
134+
{
135+
type: "normal",
136+
label: t("menu.zenMode"),
137+
accelerator: "Ctrl+Shift+Z",
138+
click: () => {
139+
const mainWindow = getMainWindow()
140+
if (!mainWindow) return
141+
142+
const caller = callWindowExpose(mainWindow)
143+
caller.zenMode()
144+
},
145+
},
133146
{ role: "togglefullscreen", label: t("menu.toggleFullScreen") },
134147
],
135148
},

apps/renderer/src/atoms/settings/ui.ts

+29-8
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
import { createSettingAtom } from "@follow/atoms/helper/setting.js"
22
import type { UISettings } from "@follow/shared/interface/settings"
3-
import { atom, useAtomValue } from "jotai"
4-
5-
import { internal_feedColumnShowAtom } from "../sidebar"
3+
import { jotaiStore } from "@follow/utils/jotai"
4+
import { atom, useAtomValue, useSetAtom } from "jotai"
5+
import { useEventCallback } from "usehooks-ts"
66

77
export const createDefaultSettings = (): UISettings => ({
88
// Sidebar
@@ -42,6 +42,8 @@ export const createDefaultSettings = (): UISettings => ({
4242
wideMode: false,
4343
})
4444

45+
const zenModeAtom = atom(false)
46+
4547
export const {
4648
useSettingKey: useUISettingKey,
4749
useSettingSelector: useUISettingSelector,
@@ -61,9 +63,28 @@ export const uiServerSyncWhiteListKeys: (keyof UISettings)[] = [
6163
"customCSS",
6264
]
6365

64-
const isZenModeAtom = atom((get) => {
65-
const ui = get(__uiSettingAtom)
66-
return ui.wideMode && !get(internal_feedColumnShowAtom)
67-
})
66+
export const useIsZenMode = () => useAtomValue(zenModeAtom)
67+
export const getIsZenMode = () => jotaiStore.get(zenModeAtom)
68+
69+
export const useSetZenMode = () => {
70+
const setZenMode = useSetAtom(zenModeAtom)
71+
return useEventCallback((checked: boolean) => {
72+
setZenMode(checked)
73+
})
74+
}
75+
76+
export const useToggleZenMode = () => {
77+
const setZenMode = useSetZenMode()
78+
const isZenMode = useIsZenMode()
79+
return useEventCallback(() => {
80+
const newIsZenMode = !isZenMode
81+
document.documentElement.dataset.zenMode = newIsZenMode.toString()
82+
setZenMode(newIsZenMode)
83+
})
84+
}
6885

69-
export const useIsZenMode = () => useAtomValue(isZenModeAtom)
86+
export const useRealInWideMode = () => {
87+
const wideMode = useUISettingKey("wideMode")
88+
const isZenMode = useIsZenMode()
89+
return wideMode || isZenMode
90+
}

apps/renderer/src/atoms/sidebar.ts

+16-8
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@ import { atom } from "jotai"
44
import { getRouteParams } from "~/hooks/biz/useRouteParams"
55
import { createAtomHooks } from "~/lib/jotai"
66

7+
import { getIsZenMode, useIsZenMode } from "./settings/ui"
8+
79
const defaultFeedView = FeedViewType.Articles
810
const viewAtom = atom<FeedViewType | -1>(-1)
911
const [
@@ -48,14 +50,20 @@ viewAtom.onMount = () => {
4850
setSidebarActiveView(view)
4951
}
5052
}
51-
export const [
52-
internal_feedColumnShowAtom,
53-
,
54-
useFeedColumnShow,
55-
,
56-
getFeedColumnShow,
57-
setFeedColumnShow,
58-
] = createAtomHooks(atom(true))
53+
const [, , internal_useFeedColumnShow, , internal_getFeedColumnShow, setFeedColumnShow] =
54+
createAtomHooks(atom(true))
55+
56+
export const useFeedColumnShow = () => {
57+
const isZenMode = useIsZenMode()
58+
return internal_useFeedColumnShow() && !isZenMode
59+
}
60+
61+
export const getFeedColumnShow = () => {
62+
const isZenMode = getIsZenMode()
63+
return internal_getFeedColumnShow() && !isZenMode
64+
}
65+
66+
export { setFeedColumnShow }
5967

6068
export const [, , useFeedColumnTempShow, , getFeedColumnTempShow, setFeedColumnTempShow] =
6169
createAtomHooks(atom(false))

apps/renderer/src/components/ui/markdown/components/Toc.tsx

+2-2
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ import {
1818
} from "react"
1919
import { useEventCallback } from "usehooks-ts"
2020

21-
import { useUISettingKey } from "~/atoms/settings/ui"
21+
import { useRealInWideMode } from "~/atoms/settings/ui"
2222
import {
2323
useGetWrappedElementPosition,
2424
useWrappedElementPosition,
@@ -58,7 +58,7 @@ export const Toc: Component<TocProps> = ({ className, onItemClick }) => {
5858

5959
const renderContentElementPosition = useWrappedElementPosition()
6060
const renderContentElementSize = useWrappedElementSize()
61-
const entryContentInWideMode = useUISettingKey("wideMode")
61+
const entryContentInWideMode = useRealInWideMode()
6262
const shouldShowTitle = useViewport((v) => {
6363
if (!entryContentInWideMode) return false
6464
const { w } = v

apps/renderer/src/constants/shortcuts.ts

+4
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,10 @@ export const shortcuts: Shortcuts = {
3232
name: "keys.layout.toggleWideMode",
3333
key: "Meta+[",
3434
},
35+
zenMode: {
36+
name: "keys.layout.zenMode",
37+
key: "Control+Shift+Z",
38+
},
3539
},
3640
entries: {
3741
refetch: {

apps/renderer/src/main.tsx

+14-2
Original file line numberDiff line numberDiff line change
@@ -20,8 +20,20 @@ initializeApp().finally(() => {
2020

2121
const $container = document.querySelector("#root") as HTMLElement
2222

23-
if (IN_ELECTRON && getOS() === "Windows") {
24-
document.body.style.cssText += `--fo-window-padding-top: ${ElECTRON_CUSTOM_TITLEBAR_HEIGHT}px;`
23+
if (IN_ELECTRON) {
24+
const os = getOS()
25+
26+
switch (os) {
27+
case "Windows": {
28+
document.body.style.cssText += `--fo-window-padding-top: ${ElECTRON_CUSTOM_TITLEBAR_HEIGHT}px;`
29+
break
30+
}
31+
case "macOS": {
32+
document.body.style.cssText += `--fo-macos-traffic-light-width: 100px; --fo-macos-traffic-light-height: 30px;`
33+
break
34+
}
35+
}
36+
document.documentElement.dataset.os = getOS()
2537
}
2638

2739
ReactDOM.createRoot($container).render(

apps/renderer/src/modules/entry-column/layouts/EntryListHeader.tsx

+30-11
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import { MdiMeditation } from "@follow/components/icons/Meditation.jsx"
12
import { ActionButton } from "@follow/components/ui/button/index.js"
23
import { DividerVertical } from "@follow/components/ui/divider/index.js"
34
import { RotatingRefreshIcon } from "@follow/components/ui/loading/index.jsx"
@@ -12,7 +13,13 @@ import * as React from "react"
1213
import { useTranslation } from "react-i18next"
1314

1415
import { setGeneralSetting, useGeneralSettingKey } from "~/atoms/settings/general"
15-
import { setUISetting, useUISettingKey } from "~/atoms/settings/ui"
16+
import {
17+
setUISetting,
18+
useIsZenMode,
19+
useRealInWideMode,
20+
useSetZenMode,
21+
useUISettingKey,
22+
} from "~/atoms/settings/ui"
1623
import { useWhoami } from "~/atoms/user"
1724
import { ImpressionView } from "~/components/common/ImpressionTracker"
1825
import { FEED_COLLECTION_LIST, ROUTE_ENTRY_PENDING, ROUTE_FEED_IN_LIST } from "~/constants"
@@ -234,9 +241,11 @@ const SwitchToMasonryButton = () => {
234241
}
235242

236243
const WideModeButton = () => {
237-
const isWideMode = useUISettingKey("wideMode")
244+
const isWideMode = useRealInWideMode()
245+
const isZenMode = useIsZenMode()
238246
const { t } = useTranslation()
239247

248+
const setIsZenMode = useSetZenMode()
240249
return (
241250
<ImpressionView
242251
event="Switch to Wide Mode"
@@ -247,23 +256,33 @@ const WideModeButton = () => {
247256
<ActionButton
248257
shortcut={shortcuts.layout.toggleWideMode.key}
249258
onClick={() => {
250-
setUISetting("wideMode", !isWideMode)
251-
// TODO: Remove this after useMeasure can get bounds in time
252-
window.dispatchEvent(new Event("resize"))
259+
if (isZenMode) {
260+
setIsZenMode(false)
261+
} else {
262+
setUISetting("wideMode", !isWideMode)
263+
// TODO: Remove this after useMeasure can get bounds in time
264+
window.dispatchEvent(new Event("resize"))
265+
}
253266
window.analytics?.capture("Switch to Wide Mode", {
254267
wideMode: !isWideMode ? 1 : 0,
255268
click: 1,
256269
})
257270
}}
258271
tooltip={
259-
!isWideMode
260-
? t("entry_list_header.switch_to_widemode")
261-
: t("entry_list_header.switch_to_normalmode")
272+
isZenMode
273+
? t("zen.exit")
274+
: !isWideMode
275+
? t("entry_list_header.switch_to_widemode")
276+
: t("entry_list_header.switch_to_normalmode")
262277
}
263278
>
264-
<i
265-
className={cn(isWideMode ? "i-mgc-align-justify-cute-re" : "i-mgc-align-left-cute-re")}
266-
/>
279+
{isZenMode ? (
280+
<MdiMeditation />
281+
) : (
282+
<i
283+
className={cn(isWideMode ? "i-mgc-align-justify-cute-re" : "i-mgc-align-left-cute-re")}
284+
/>
285+
)}
267286
</ActionButton>
268287
</ImpressionView>
269288
)

apps/renderer/src/modules/entry-column/templates/list-item-template.tsx

+3-2
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import { cn, isSafari } from "@follow/utils/utils"
33
import { useDebounceCallback } from "usehooks-ts"
44

55
import { AudioPlayer, useAudioPlayerAtomSelector } from "~/atoms/player"
6-
import { useUISettingKeys } from "~/atoms/settings/ui"
6+
import { useRealInWideMode, useUISettingKey } from "~/atoms/settings/ui"
77
import { RelativeTime } from "~/components/ui/datetime"
88
import { Media } from "~/components/ui/media"
99
import { FEED_COLLECTION_LIST } from "~/constants"
@@ -62,7 +62,8 @@ export function ListItem({
6262
{ leading: false },
6363
)
6464

65-
const [settingWideMode, thumbnailRatio] = useUISettingKeys(["wideMode", "thumbnailRatio"])
65+
const settingWideMode = useRealInWideMode()
66+
const thumbnailRatio = useUISettingKey("thumbnailRatio")
6667
const rid = `list-item-${entryId}`
6768

6869
// NOTE: prevent 0 height element, react virtuoso will not stop render any more

apps/renderer/src/modules/entry-content/header.tsx

+1-1
Original file line numberDiff line numberDiff line change
@@ -70,7 +70,7 @@ function EntryHeaderImpl({
7070
{!hideRecentReader && (
7171
<div
7272
className={cn(
73-
"absolute left-5 top-0 flex h-full items-center gap-2 text-[13px] leading-none text-zinc-500",
73+
"absolute left-5 top-0 flex h-full items-center gap-2 text-[13px] leading-none text-zinc-500 zen-mode-macos:left-12",
7474
"visible z-[11]",
7575
views[view].wideMode && "static",
7676
shouldShowMeta && "hidden",

apps/renderer/src/modules/feed-column/header.tsx

+24-11
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import { Logo } from "@follow/components/icons/logo.jsx"
2+
import { MdiMeditation } from "@follow/components/icons/Meditation.js"
23
import { ActionButton } from "@follow/components/ui/button/index.js"
34
import { Popover, PopoverContent, PopoverTrigger } from "@follow/components/ui/popover/index.jsx"
45
import { stopPropagation } from "@follow/utils/dom"
@@ -13,6 +14,7 @@ import { toast } from "sonner"
1314

1415
import { setAppSearchOpen } from "~/atoms/app"
1516
import { useGeneralSettingKey } from "~/atoms/settings/general"
17+
import { useIsZenMode, useSetZenMode } from "~/atoms/settings/ui"
1618
import { setFeedColumnShow, useFeedColumnShow, useSidebarActiveView } from "~/atoms/sidebar"
1719
import { useNavigateEntry } from "~/hooks/biz/useNavigateEntry"
1820
import { useI18n } from "~/hooks/common"
@@ -81,7 +83,8 @@ const LayoutActionButton = () => {
8183
const [animation, setAnimation] = useState({
8284
width: !feedColumnShow ? "auto" : 0,
8385
})
84-
86+
const isZenMode = useIsZenMode()
87+
const setIsZenMode = useSetZenMode()
8588
useEffect(() => {
8689
setAnimation({
8790
width: !feedColumnShow ? "auto" : 0,
@@ -95,18 +98,28 @@ const LayoutActionButton = () => {
9598
return (
9699
<m.div initial={animation} animate={animation} className="overflow-hidden">
97100
<ActionButton
98-
tooltip={t("app.toggle_sidebar")}
101+
tooltip={isZenMode ? t("zen.exit") : t("app.toggle_sidebar")}
99102
icon={
100-
<i
101-
className={cn(
102-
!feedColumnShow
103-
? "i-mgc-layout-leftbar-open-cute-re "
104-
: "i-mgc-layout-leftbar-close-cute-re",
105-
"text-theme-vibrancyFg",
106-
)}
107-
/>
103+
isZenMode ? (
104+
<MdiMeditation />
105+
) : (
106+
<i
107+
className={cn(
108+
!feedColumnShow
109+
? "i-mgc-layout-leftbar-open-cute-re "
110+
: "i-mgc-layout-leftbar-close-cute-re",
111+
"text-theme-vibrancyFg",
112+
)}
113+
/>
114+
)
108115
}
109-
onClick={() => setFeedColumnShow(!feedColumnShow)}
116+
onClick={() => {
117+
if (isZenMode) {
118+
setIsZenMode(false)
119+
} else {
120+
setFeedColumnShow(!feedColumnShow)
121+
}
122+
}}
110123
/>
111124
</m.div>
112125
)

apps/renderer/src/modules/settings/tabs/apperance.tsx

+6-13
Original file line numberDiff line numberDiff line change
@@ -18,11 +18,12 @@ import { bundledThemesInfo } from "shiki/themes"
1818
import {
1919
getUISettings,
2020
setUISetting,
21+
useIsZenMode,
22+
useToggleZenMode,
2123
useUISettingKey,
2224
useUISettingSelector,
2325
useUISettingValue,
2426
} from "~/atoms/settings/ui"
25-
import { setFeedColumnShow, useFeedColumnShow } from "~/atoms/sidebar"
2627
import { useCurrentModal, useModalStack } from "~/components/ui/modal/stacked/hooks"
2728
import { isElectronBuild } from "~/constants"
2829
import { useSetTheme } from "~/hooks/common"
@@ -246,22 +247,14 @@ export const AppThemeSegment = () => {
246247

247248
const ZenMode = () => {
248249
const { t } = useTranslation("settings")
249-
const feedColumnShow = useFeedColumnShow()
250-
const isWideMode = useUISettingKey("wideMode")
250+
const isZenMode = useIsZenMode()
251+
const toggleZenMode = useToggleZenMode()
251252
return (
252253
<SettingItemGroup>
253254
<SettingSwitch
254-
checked={!feedColumnShow && isWideMode}
255+
checked={isZenMode}
255256
className="mt-4"
256-
onCheckedChange={(checked) => {
257-
if (checked) {
258-
setFeedColumnShow(false)
259-
setUISetting("wideMode", true)
260-
} else {
261-
setFeedColumnShow(true)
262-
setUISetting("wideMode", false)
263-
}
264-
}}
257+
onCheckedChange={toggleZenMode}
265258
label={t("appearance.zen_mode.label")}
266259
/>
267260
<SettingDescription>{t("appearance.zen_mode.description")}</SettingDescription>

0 commit comments

Comments
 (0)