Skip to content

Commit 4a627cb

Browse files
authored
feat: add compact mode for window (#947)
* feat: add compact mode for window * docs: update changelog * feat: add i18n * refactor: update * refactor: update
1 parent 3029303 commit 4a627cb

13 files changed

Lines changed: 167 additions & 34 deletions

File tree

docs/content.en/docs/release-notes/_index.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ feat(View Extension): page field now accepts HTTP(s) links #925
2222
feat: return sub-exts when extension type exts themselves are matched #928
2323
feat: open quick ai with modifier key + enter #939
2424
feat: allow navigate back when cursor is at the beginning #940
25+
feat: add compact mode for window #947
2526

2627
### 🐛 Bug fix
2728

src/components/Search/InputBox.tsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -310,6 +310,7 @@ export default function ChatInput({
310310
<div className={`w-full relative`}>
311311
<div
312312
ref={containerRef}
313+
id="search-bar"
313314
className={`flex items-center dark:text-[#D8D8D8] rounded-md transition-all relative overflow-hidden`}
314315
>
315316
{lineCount === 1 && renderSearchIcon()}

src/components/Search/InputControls.tsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -165,6 +165,7 @@ const InputControls = ({
165165

166166
return (
167167
<div
168+
id="filter-bar"
168169
data-tauri-drag-region
169170
className="flex justify-between items-center pt-2"
170171
>

src/components/SearchChat/index.tsx

Lines changed: 50 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ import {
99
useMemo,
1010
} from "react";
1111
import clsx from "clsx";
12-
import { useMount } from "ahooks";
12+
import { useMount, useMutationObserver } from "ahooks";
1313

1414
import Search from "@/components/Search/Search";
1515
import InputBox from "@/components/Search/InputBox";
@@ -28,10 +28,12 @@ import { useConnectStore } from "@/stores/connectStore";
2828
import { useAppearanceStore } from "@/stores/appearanceStore";
2929
import type { StartPage } from "@/types/chat";
3030
import {
31+
canNavigateBack,
3132
hasUploadingAttachment,
3233
visibleFilterBar,
3334
visibleSearchBar,
3435
} from "@/utils";
36+
import { useTauriFocus } from "@/hooks/useTauriFocus";
3537

3638
interface SearchChatProps {
3739
isTauri?: boolean;
@@ -82,6 +84,7 @@ function SearchChat({
8284
);
8385

8486
const [state, dispatch] = useReducer(appReducer, customInitialState);
87+
8588
const {
8689
isChatMode,
8790
input,
@@ -91,6 +94,52 @@ function SearchChat({
9194
isMCPActive,
9295
isTyping,
9396
} = state;
97+
98+
const inputRef = useRef<string>();
99+
const isChatModeRef = useRef(false);
100+
101+
const setWindowSize = useCallback(() => {
102+
const width = 680;
103+
let height = 590;
104+
105+
const updateAppDialog = document.querySelector("#update-app-dialog");
106+
107+
if (!updateAppDialog && !canNavigateBack() && !inputRef.current) {
108+
const { windowMode } = useAppearanceStore.getState();
109+
110+
if (windowMode === "compact") {
111+
const searchBar = document.querySelector("#search-bar");
112+
const filterBar = document.querySelector("#filter-bar");
113+
114+
if (searchBar && filterBar) {
115+
height = searchBar.clientHeight + filterBar.clientHeight + 16;
116+
} else {
117+
height = 82;
118+
}
119+
120+
height = Math.min(height, 88);
121+
}
122+
}
123+
124+
platformAdapter.setWindowSize(width, height);
125+
}, []);
126+
127+
useMutationObserver(setWindowSize, document.body, {
128+
subtree: true,
129+
childList: true,
130+
});
131+
132+
useEffect(() => {
133+
inputRef.current = input;
134+
isChatModeRef.current = isChatMode;
135+
136+
setWindowSize();
137+
}, [input, isChatMode]);
138+
139+
useTauriFocus({
140+
onFocus: setWindowSize,
141+
});
142+
94143
useEffect(() => {
95144
dispatch({
96145
type: "SET_SEARCH_ACTIVE",
@@ -114,11 +163,6 @@ function SearchChat({
114163
const setTheme = useThemeStore((state) => state.setTheme);
115164
const setIsDark = useThemeStore((state) => state.setIsDark);
116165

117-
const isChatModeRef = useRef(false);
118-
useEffect(() => {
119-
isChatModeRef.current = isChatMode;
120-
}, [isChatMode]);
121-
122166
useMount(async () => {
123167
const isWin10 = await platformAdapter.isWindows10();
124168

src/components/Settings/Advanced/components/Appearance/index.tsx

Lines changed: 0 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,24 +1,14 @@
11
import SettingsInput from "@/components/Settings/SettingsInput";
22
import SettingsItem from "@/components/Settings/SettingsItem";
33
import { useAppearanceStore } from "@/stores/appearanceStore";
4-
import platformAdapter from "@/utils/platformAdapter";
54
import { AppWindowMac } from "lucide-react";
6-
import { useEffect } from "react";
75
import { useTranslation } from "react-i18next";
86

97
const Appearance = () => {
108
const { t } = useTranslation();
119
const opacity = useAppearanceStore((state) => state.opacity);
1210
const setOpacity = useAppearanceStore((state) => state.setOpacity);
1311

14-
useEffect(() => {
15-
const unlisten = useAppearanceStore.subscribe((state) => {
16-
platformAdapter.emitEvent("change-appearance-store", state);
17-
});
18-
19-
return unlisten;
20-
}, []);
21-
2212
return (
2313
<>
2414
<h2 className="text-lg font-semibold text-gray-900 dark:text-white mb-4">

src/components/Settings/GeneralSettings.tsx

Lines changed: 66 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { useState, useEffect } from "react";
1+
import { useState, useEffect, cloneElement, ReactElement } from "react";
22
import {
33
Command,
44
Monitor,
@@ -9,6 +9,9 @@ import {
99
Tags,
1010
// Trash2,
1111
Globe,
12+
PictureInPicture2,
13+
PanelTop,
14+
RectangleHorizontal,
1215
} from "lucide-react";
1316
import { useTranslation } from "react-i18next";
1417
import { isTauri } from "@tauri-apps/api/core";
@@ -31,6 +34,8 @@ import {
3134
unregister_shortcut,
3235
} from "@/commands";
3336
import platformAdapter from "@/utils/platformAdapter";
37+
import clsx from "clsx";
38+
import { useAppearanceStore, WindowMode } from "@/stores/appearanceStore";
3439

3540
export function ThemeOption({
3641
icon: Icon,
@@ -76,6 +81,7 @@ export default function GeneralSettings() {
7681
const [launchAtLogin, setLaunchAtLogin] = useState(true);
7782

7883
const { showTooltip, setShowTooltip, language, setLanguage } = useAppStore();
84+
const { windowMode, setWindowMode } = useAppearanceStore();
7985

8086
const fetchAutoStartStatus = async () => {
8187
if (isTauri()) {
@@ -176,6 +182,20 @@ export default function GeneralSettings() {
176182

177183
const currentLanguage = language || i18n.language;
178184

185+
const windowModes: Array<{
186+
icon: ReactElement;
187+
value: WindowMode;
188+
}> = [
189+
{
190+
icon: <PanelTop />,
191+
value: "default",
192+
},
193+
{
194+
icon: <RectangleHorizontal />,
195+
value: "compact",
196+
},
197+
];
198+
179199
return (
180200
<div className="space-y-8">
181201
<div>
@@ -239,6 +259,51 @@ export default function GeneralSettings() {
239259
/>
240260
</div>
241261

262+
<SettingsItem
263+
icon={PictureInPicture2}
264+
title={t("settings.windowMode.title")}
265+
description={t("settings.windowMode.description")}
266+
/>
267+
<div className="grid grid-cols-3 gap-4">
268+
{windowModes.map((item) => {
269+
const { icon, value } = item;
270+
271+
const label = t(`settings.windowMode.${value}`);
272+
273+
let isSelected = value === windowMode;
274+
275+
return (
276+
<button
277+
onClick={() => {
278+
setWindowMode(value);
279+
}}
280+
className={clsx(
281+
"p-4 rounded-lg border-2 border-gray-200 dark:border-gray-700 hover:border-gray-300 dark:hover:border-gray-600 flex flex-col items-center justify-center space-y-2 transition-all",
282+
{
283+
"!border-blue-500 bg-blue-50 dark:bg-blue-900/20":
284+
isSelected,
285+
}
286+
)}
287+
title={label}
288+
>
289+
{cloneElement(icon, {
290+
className: clsx({
291+
"text-blue-500": isSelected,
292+
}),
293+
})}
294+
295+
<span
296+
className={clsx(`text-sm font-medium`, {
297+
"text-blue-500": isSelected,
298+
})}
299+
>
300+
{label}
301+
</span>
302+
</button>
303+
);
304+
})}
305+
</div>
306+
242307
<SettingsItem
243308
icon={Globe}
244309
title={t("settings.language.title")}

src/components/Settings/SettingsItem.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ interface SettingsItemProps {
44
icon: LucideIcon;
55
title: string;
66
description: string;
7-
children: React.ReactNode;
7+
children?: React.ReactNode;
88
}
99

1010
export default function SettingsItem({

src/components/UpdateApp/index.tsx

Lines changed: 11 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -131,9 +131,8 @@ const UpdateApp = ({ isCheckPage }: UpdateAppProps) => {
131131

132132
const { skipVersions, updateInfo } = useUpdateStore.getState();
133133

134-
if(updateInfo?.version){
135-
setSkipVersions([...skipVersions, updateInfo.version]);
136-
134+
if (updateInfo?.version) {
135+
setSkipVersions([...skipVersions, updateInfo.version]);
137136
}
138137

139138
isCheckPage ? hide_check() : setVisible(false);
@@ -143,6 +142,7 @@ const UpdateApp = ({ isCheckPage }: UpdateAppProps) => {
143142
<Dialog
144143
open={isCheckPage ? true : visible}
145144
as="div"
145+
id="update-app-dialog"
146146
className="relative z-10 focus:outline-none"
147147
onClose={noop}
148148
>
@@ -154,18 +154,21 @@ const UpdateApp = ({ isCheckPage }: UpdateAppProps) => {
154154
}`}
155155
>
156156
<div
157+
data-tauri-drag-region
157158
className={clsx(
158159
"flex min-h-full items-center justify-center",
159160
!isCheckPage && "p-4"
160161
)}
161162
>
162163
<DialogPanel
163164
transition
164-
className={`relative w-[340px] py-8 flex flex-col items-center ${
165-
isCheckPage
166-
? ""
167-
: "rounded-lg bg-white dark:bg-[#333] border border-[#EDEDED] dark:border-black/20 shadow-md"
168-
}`}
165+
className={clsx(
166+
"relative w-[340px] py-8 flex flex-col items-center",
167+
{
168+
"rounded-lg bg-white dark:bg-[#333] border border-[#EDEDED] dark:border-black/20 shadow-md":
169+
!isCheckPage,
170+
}
171+
)}
169172
>
170173
{!isCheckPage && isOptional && (
171174
<X

src/hooks/useSyncStore.ts

Lines changed: 9 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -117,8 +117,11 @@ export const useSyncStore = () => {
117117
const setShowTooltip = useAppStore((state) => state.setShowTooltip);
118118
const setEndpoint = useAppStore((state) => state.setEndpoint);
119119
const setLanguage = useAppStore((state) => state.setLanguage);
120-
121-
const setServerListSilently = useConnectStore((state) => state.setServerListSilently);
120+
const { setWindowMode } = useAppearanceStore();
121+
122+
const setServerListSilently = useConnectStore(
123+
(state) => state.setServerListSilently
124+
);
122125

123126
useEffect(() => {
124127
if (!resetFixedWindow) {
@@ -182,11 +185,8 @@ export const useSyncStore = () => {
182185
}),
183186

184187
platformAdapter.listenEvent("change-connect-store", ({ payload }) => {
185-
const {
186-
connectionTimeout,
187-
querySourceTimeout,
188-
allowSelfSignature,
189-
} = payload;
188+
const { connectionTimeout, querySourceTimeout, allowSelfSignature } =
189+
payload;
190190
if (isNumber(connectionTimeout)) {
191191
setConnectionTimeout(connectionTimeout);
192192
}
@@ -197,12 +197,13 @@ export const useSyncStore = () => {
197197
}),
198198

199199
platformAdapter.listenEvent("change-appearance-store", ({ payload }) => {
200-
const { opacity, snapshotUpdate } = payload;
200+
const { opacity, snapshotUpdate, windowMode } = payload;
201201

202202
if (isNumber(opacity)) {
203203
setOpacity(opacity);
204204
}
205205
setSnapshotUpdate(snapshotUpdate);
206+
setWindowMode(windowMode);
206207
}),
207208

208209
platformAdapter.listenEvent("change-extensions-store", ({ payload }) => {

src/locales/en/translation.json

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,12 @@
1717
"dark": "Dark",
1818
"auto": "Auto"
1919
},
20+
"windowMode": {
21+
"title": "Window Mode",
22+
"description": "Set how the window appears when opened.",
23+
"default": "Default",
24+
"compact": "Compact"
25+
},
2026
"language": {
2127
"title": "Language",
2228
"description": "Choose your preferred language",

0 commit comments

Comments
 (0)