mirror of
https://github.com/infinilabs/coco-app.git
synced 2025-12-16 19:47:43 +01:00
feat: add compact mode for window (#947)
* feat: add compact mode for window * docs: update changelog * feat: add i18n * refactor: update * refactor: update
This commit is contained in:
@@ -22,6 +22,7 @@ feat(View Extension): page field now accepts HTTP(s) links #925
|
||||
feat: return sub-exts when extension type exts themselves are matched #928
|
||||
feat: open quick ai with modifier key + enter #939
|
||||
feat: allow navigate back when cursor is at the beginning #940
|
||||
feat: add compact mode for window #947
|
||||
|
||||
### 🐛 Bug fix
|
||||
|
||||
|
||||
@@ -310,6 +310,7 @@ export default function ChatInput({
|
||||
<div className={`w-full relative`}>
|
||||
<div
|
||||
ref={containerRef}
|
||||
id="search-bar"
|
||||
className={`flex items-center dark:text-[#D8D8D8] rounded-md transition-all relative overflow-hidden`}
|
||||
>
|
||||
{lineCount === 1 && renderSearchIcon()}
|
||||
|
||||
@@ -165,6 +165,7 @@ const InputControls = ({
|
||||
|
||||
return (
|
||||
<div
|
||||
id="filter-bar"
|
||||
data-tauri-drag-region
|
||||
className="flex justify-between items-center pt-2"
|
||||
>
|
||||
|
||||
@@ -9,7 +9,7 @@ import {
|
||||
useMemo,
|
||||
} from "react";
|
||||
import clsx from "clsx";
|
||||
import { useMount } from "ahooks";
|
||||
import { useMount, useMutationObserver } from "ahooks";
|
||||
|
||||
import Search from "@/components/Search/Search";
|
||||
import InputBox from "@/components/Search/InputBox";
|
||||
@@ -28,10 +28,12 @@ import { useConnectStore } from "@/stores/connectStore";
|
||||
import { useAppearanceStore } from "@/stores/appearanceStore";
|
||||
import type { StartPage } from "@/types/chat";
|
||||
import {
|
||||
canNavigateBack,
|
||||
hasUploadingAttachment,
|
||||
visibleFilterBar,
|
||||
visibleSearchBar,
|
||||
} from "@/utils";
|
||||
import { useTauriFocus } from "@/hooks/useTauriFocus";
|
||||
|
||||
interface SearchChatProps {
|
||||
isTauri?: boolean;
|
||||
@@ -82,6 +84,7 @@ function SearchChat({
|
||||
);
|
||||
|
||||
const [state, dispatch] = useReducer(appReducer, customInitialState);
|
||||
|
||||
const {
|
||||
isChatMode,
|
||||
input,
|
||||
@@ -91,6 +94,52 @@ function SearchChat({
|
||||
isMCPActive,
|
||||
isTyping,
|
||||
} = state;
|
||||
|
||||
const inputRef = useRef<string>();
|
||||
const isChatModeRef = useRef(false);
|
||||
|
||||
const setWindowSize = useCallback(() => {
|
||||
const width = 680;
|
||||
let height = 590;
|
||||
|
||||
const updateAppDialog = document.querySelector("#update-app-dialog");
|
||||
|
||||
if (!updateAppDialog && !canNavigateBack() && !inputRef.current) {
|
||||
const { windowMode } = useAppearanceStore.getState();
|
||||
|
||||
if (windowMode === "compact") {
|
||||
const searchBar = document.querySelector("#search-bar");
|
||||
const filterBar = document.querySelector("#filter-bar");
|
||||
|
||||
if (searchBar && filterBar) {
|
||||
height = searchBar.clientHeight + filterBar.clientHeight + 16;
|
||||
} else {
|
||||
height = 82;
|
||||
}
|
||||
|
||||
height = Math.min(height, 88);
|
||||
}
|
||||
}
|
||||
|
||||
platformAdapter.setWindowSize(width, height);
|
||||
}, []);
|
||||
|
||||
useMutationObserver(setWindowSize, document.body, {
|
||||
subtree: true,
|
||||
childList: true,
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
inputRef.current = input;
|
||||
isChatModeRef.current = isChatMode;
|
||||
|
||||
setWindowSize();
|
||||
}, [input, isChatMode]);
|
||||
|
||||
useTauriFocus({
|
||||
onFocus: setWindowSize,
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
dispatch({
|
||||
type: "SET_SEARCH_ACTIVE",
|
||||
@@ -114,11 +163,6 @@ function SearchChat({
|
||||
const setTheme = useThemeStore((state) => state.setTheme);
|
||||
const setIsDark = useThemeStore((state) => state.setIsDark);
|
||||
|
||||
const isChatModeRef = useRef(false);
|
||||
useEffect(() => {
|
||||
isChatModeRef.current = isChatMode;
|
||||
}, [isChatMode]);
|
||||
|
||||
useMount(async () => {
|
||||
const isWin10 = await platformAdapter.isWindows10();
|
||||
|
||||
|
||||
@@ -1,9 +1,7 @@
|
||||
import SettingsInput from "@/components/Settings/SettingsInput";
|
||||
import SettingsItem from "@/components/Settings/SettingsItem";
|
||||
import { useAppearanceStore } from "@/stores/appearanceStore";
|
||||
import platformAdapter from "@/utils/platformAdapter";
|
||||
import { AppWindowMac } from "lucide-react";
|
||||
import { useEffect } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
|
||||
const Appearance = () => {
|
||||
@@ -11,14 +9,6 @@ const Appearance = () => {
|
||||
const opacity = useAppearanceStore((state) => state.opacity);
|
||||
const setOpacity = useAppearanceStore((state) => state.setOpacity);
|
||||
|
||||
useEffect(() => {
|
||||
const unlisten = useAppearanceStore.subscribe((state) => {
|
||||
platformAdapter.emitEvent("change-appearance-store", state);
|
||||
});
|
||||
|
||||
return unlisten;
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<>
|
||||
<h2 className="text-lg font-semibold text-gray-900 dark:text-white mb-4">
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { useState, useEffect } from "react";
|
||||
import { useState, useEffect, cloneElement, ReactElement } from "react";
|
||||
import {
|
||||
Command,
|
||||
Monitor,
|
||||
@@ -9,6 +9,9 @@ import {
|
||||
Tags,
|
||||
// Trash2,
|
||||
Globe,
|
||||
PictureInPicture2,
|
||||
PanelTop,
|
||||
RectangleHorizontal,
|
||||
} from "lucide-react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { isTauri } from "@tauri-apps/api/core";
|
||||
@@ -31,6 +34,8 @@ import {
|
||||
unregister_shortcut,
|
||||
} from "@/commands";
|
||||
import platformAdapter from "@/utils/platformAdapter";
|
||||
import clsx from "clsx";
|
||||
import { useAppearanceStore, WindowMode } from "@/stores/appearanceStore";
|
||||
|
||||
export function ThemeOption({
|
||||
icon: Icon,
|
||||
@@ -76,6 +81,7 @@ export default function GeneralSettings() {
|
||||
const [launchAtLogin, setLaunchAtLogin] = useState(true);
|
||||
|
||||
const { showTooltip, setShowTooltip, language, setLanguage } = useAppStore();
|
||||
const { windowMode, setWindowMode } = useAppearanceStore();
|
||||
|
||||
const fetchAutoStartStatus = async () => {
|
||||
if (isTauri()) {
|
||||
@@ -176,6 +182,20 @@ export default function GeneralSettings() {
|
||||
|
||||
const currentLanguage = language || i18n.language;
|
||||
|
||||
const windowModes: Array<{
|
||||
icon: ReactElement;
|
||||
value: WindowMode;
|
||||
}> = [
|
||||
{
|
||||
icon: <PanelTop />,
|
||||
value: "default",
|
||||
},
|
||||
{
|
||||
icon: <RectangleHorizontal />,
|
||||
value: "compact",
|
||||
},
|
||||
];
|
||||
|
||||
return (
|
||||
<div className="space-y-8">
|
||||
<div>
|
||||
@@ -239,6 +259,51 @@ export default function GeneralSettings() {
|
||||
/>
|
||||
</div>
|
||||
|
||||
<SettingsItem
|
||||
icon={PictureInPicture2}
|
||||
title={t("settings.windowMode.title")}
|
||||
description={t("settings.windowMode.description")}
|
||||
/>
|
||||
<div className="grid grid-cols-3 gap-4">
|
||||
{windowModes.map((item) => {
|
||||
const { icon, value } = item;
|
||||
|
||||
const label = t(`settings.windowMode.${value}`);
|
||||
|
||||
let isSelected = value === windowMode;
|
||||
|
||||
return (
|
||||
<button
|
||||
onClick={() => {
|
||||
setWindowMode(value);
|
||||
}}
|
||||
className={clsx(
|
||||
"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",
|
||||
{
|
||||
"!border-blue-500 bg-blue-50 dark:bg-blue-900/20":
|
||||
isSelected,
|
||||
}
|
||||
)}
|
||||
title={label}
|
||||
>
|
||||
{cloneElement(icon, {
|
||||
className: clsx({
|
||||
"text-blue-500": isSelected,
|
||||
}),
|
||||
})}
|
||||
|
||||
<span
|
||||
className={clsx(`text-sm font-medium`, {
|
||||
"text-blue-500": isSelected,
|
||||
})}
|
||||
>
|
||||
{label}
|
||||
</span>
|
||||
</button>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
|
||||
<SettingsItem
|
||||
icon={Globe}
|
||||
title={t("settings.language.title")}
|
||||
|
||||
@@ -4,7 +4,7 @@ interface SettingsItemProps {
|
||||
icon: LucideIcon;
|
||||
title: string;
|
||||
description: string;
|
||||
children: React.ReactNode;
|
||||
children?: React.ReactNode;
|
||||
}
|
||||
|
||||
export default function SettingsItem({
|
||||
|
||||
@@ -131,9 +131,8 @@ const UpdateApp = ({ isCheckPage }: UpdateAppProps) => {
|
||||
|
||||
const { skipVersions, updateInfo } = useUpdateStore.getState();
|
||||
|
||||
if(updateInfo?.version){
|
||||
if (updateInfo?.version) {
|
||||
setSkipVersions([...skipVersions, updateInfo.version]);
|
||||
|
||||
}
|
||||
|
||||
isCheckPage ? hide_check() : setVisible(false);
|
||||
@@ -143,6 +142,7 @@ const UpdateApp = ({ isCheckPage }: UpdateAppProps) => {
|
||||
<Dialog
|
||||
open={isCheckPage ? true : visible}
|
||||
as="div"
|
||||
id="update-app-dialog"
|
||||
className="relative z-10 focus:outline-none"
|
||||
onClose={noop}
|
||||
>
|
||||
@@ -154,6 +154,7 @@ const UpdateApp = ({ isCheckPage }: UpdateAppProps) => {
|
||||
}`}
|
||||
>
|
||||
<div
|
||||
data-tauri-drag-region
|
||||
className={clsx(
|
||||
"flex min-h-full items-center justify-center",
|
||||
!isCheckPage && "p-4"
|
||||
@@ -161,11 +162,13 @@ const UpdateApp = ({ isCheckPage }: UpdateAppProps) => {
|
||||
>
|
||||
<DialogPanel
|
||||
transition
|
||||
className={`relative w-[340px] py-8 flex flex-col items-center ${
|
||||
isCheckPage
|
||||
? ""
|
||||
: "rounded-lg bg-white dark:bg-[#333] border border-[#EDEDED] dark:border-black/20 shadow-md"
|
||||
}`}
|
||||
className={clsx(
|
||||
"relative w-[340px] py-8 flex flex-col items-center",
|
||||
{
|
||||
"rounded-lg bg-white dark:bg-[#333] border border-[#EDEDED] dark:border-black/20 shadow-md":
|
||||
!isCheckPage,
|
||||
}
|
||||
)}
|
||||
>
|
||||
{!isCheckPage && isOptional && (
|
||||
<X
|
||||
|
||||
@@ -117,8 +117,11 @@ export const useSyncStore = () => {
|
||||
const setShowTooltip = useAppStore((state) => state.setShowTooltip);
|
||||
const setEndpoint = useAppStore((state) => state.setEndpoint);
|
||||
const setLanguage = useAppStore((state) => state.setLanguage);
|
||||
const { setWindowMode } = useAppearanceStore();
|
||||
|
||||
const setServerListSilently = useConnectStore((state) => state.setServerListSilently);
|
||||
const setServerListSilently = useConnectStore(
|
||||
(state) => state.setServerListSilently
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
if (!resetFixedWindow) {
|
||||
@@ -182,11 +185,8 @@ export const useSyncStore = () => {
|
||||
}),
|
||||
|
||||
platformAdapter.listenEvent("change-connect-store", ({ payload }) => {
|
||||
const {
|
||||
connectionTimeout,
|
||||
querySourceTimeout,
|
||||
allowSelfSignature,
|
||||
} = payload;
|
||||
const { connectionTimeout, querySourceTimeout, allowSelfSignature } =
|
||||
payload;
|
||||
if (isNumber(connectionTimeout)) {
|
||||
setConnectionTimeout(connectionTimeout);
|
||||
}
|
||||
@@ -197,12 +197,13 @@ export const useSyncStore = () => {
|
||||
}),
|
||||
|
||||
platformAdapter.listenEvent("change-appearance-store", ({ payload }) => {
|
||||
const { opacity, snapshotUpdate } = payload;
|
||||
const { opacity, snapshotUpdate, windowMode } = payload;
|
||||
|
||||
if (isNumber(opacity)) {
|
||||
setOpacity(opacity);
|
||||
}
|
||||
setSnapshotUpdate(snapshotUpdate);
|
||||
setWindowMode(windowMode);
|
||||
}),
|
||||
|
||||
platformAdapter.listenEvent("change-extensions-store", ({ payload }) => {
|
||||
|
||||
@@ -17,6 +17,12 @@
|
||||
"dark": "Dark",
|
||||
"auto": "Auto"
|
||||
},
|
||||
"windowMode": {
|
||||
"title": "Window Mode",
|
||||
"description": "Set how the window appears when opened.",
|
||||
"default": "Default",
|
||||
"compact": "Compact"
|
||||
},
|
||||
"language": {
|
||||
"title": "Language",
|
||||
"description": "Choose your preferred language",
|
||||
|
||||
@@ -17,6 +17,12 @@
|
||||
"dark": "深色",
|
||||
"auto": "自动"
|
||||
},
|
||||
"windowMode": {
|
||||
"title": "窗口模式",
|
||||
"description": "设置窗口打开时的显示方式。",
|
||||
"default": "默认",
|
||||
"compact": "紧凑"
|
||||
},
|
||||
"language": {
|
||||
"title": "语言",
|
||||
"description": "选择您的首选语言",
|
||||
|
||||
@@ -16,6 +16,7 @@ import { useConnectStore } from "@/stores/connectStore";
|
||||
import platformAdapter from "@/utils/platformAdapter";
|
||||
import { useAppStore } from "@/stores/appStore";
|
||||
import { useExtensionsStore } from "@/stores/extensionsStore";
|
||||
import { useAppearanceStore } from "@/stores/appearanceStore";
|
||||
|
||||
const tabIndexMap: { [key: string]: number } = {
|
||||
general: 0,
|
||||
@@ -58,6 +59,10 @@ function SettingsPage() {
|
||||
platformAdapter.emitEvent("change-app-store", state);
|
||||
});
|
||||
|
||||
const unsubscribeAppearanceStore = useAppearanceStore.subscribe((state) => {
|
||||
platformAdapter.emitEvent("change-appearance-store", state);
|
||||
});
|
||||
|
||||
const unlisten2 = platformAdapter.listenEvent(
|
||||
"config-extension",
|
||||
({ payload }) => {
|
||||
@@ -70,6 +75,7 @@ function SettingsPage() {
|
||||
return () => {
|
||||
unsubscribeConnect();
|
||||
unsubscribeAppStore();
|
||||
unsubscribeAppearanceStore();
|
||||
unlisten.then((fn) => fn());
|
||||
unlisten2.then((fn) => fn());
|
||||
};
|
||||
|
||||
@@ -1,11 +1,15 @@
|
||||
import { create } from "zustand";
|
||||
import { persist, subscribeWithSelector } from "zustand/middleware";
|
||||
|
||||
export type WindowMode = "default" | "compact";
|
||||
|
||||
export type IAppearanceStore = {
|
||||
opacity: number;
|
||||
setOpacity: (opacity?: number) => void;
|
||||
snapshotUpdate: boolean;
|
||||
setSnapshotUpdate: (snapshotUpdate: boolean) => void;
|
||||
windowMode: WindowMode;
|
||||
setWindowMode: (windowMode: WindowMode) => void;
|
||||
};
|
||||
|
||||
export const useAppearanceStore = create<IAppearanceStore>()(
|
||||
@@ -20,12 +24,17 @@ export const useAppearanceStore = create<IAppearanceStore>()(
|
||||
setSnapshotUpdate: (snapshotUpdate) => {
|
||||
return set({ snapshotUpdate });
|
||||
},
|
||||
windowMode: "default",
|
||||
setWindowMode(windowMode) {
|
||||
return set({ windowMode });
|
||||
},
|
||||
}),
|
||||
{
|
||||
name: "startup-store",
|
||||
partialize: (state) => ({
|
||||
opacity: state.opacity,
|
||||
snapshotUpdate: state.snapshotUpdate,
|
||||
windowMode: state.windowMode,
|
||||
}),
|
||||
}
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user