From 0aef439c088bfb3fe0d87258dce6925420e7cc96 Mon Sep 17 00:00:00 2001 From: kangfenmao Date: Mon, 13 Jan 2025 13:04:01 +0800 Subject: [PATCH] refactor: sidebar minapps --- .../src/components/DragableList/index.tsx | 29 +-- src/renderer/src/components/MinApp/index.tsx | 63 +++--- src/renderer/src/components/app/Sidebar.tsx | 209 ++++++++++-------- src/renderer/src/hooks/useMinapps.ts | 23 ++ src/renderer/src/hooks/useSettings.ts | 12 +- src/renderer/src/i18n/locales/en-us.json | 10 +- src/renderer/src/i18n/locales/ja-jp.json | 9 +- src/renderer/src/i18n/locales/ru-ru.json | 9 +- src/renderer/src/i18n/locales/zh-cn.json | 9 +- src/renderer/src/i18n/locales/zh-tw.json | 9 +- src/renderer/src/pages/apps/App.tsx | 23 +- src/renderer/src/pages/apps/AppsPage.tsx | 22 +- .../DisplaySettings/DisplaySettings.tsx | 25 +-- .../DisplaySettings/MiniAppIconsManager.tsx | 97 +++----- src/renderer/src/store/index.ts | 4 +- src/renderer/src/store/migrate.ts | 10 - src/renderer/src/store/minapps.ts | 48 ++++ src/renderer/src/store/settings.ts | 36 +-- src/renderer/src/types/index.ts | 4 + 19 files changed, 336 insertions(+), 315 deletions(-) create mode 100644 src/renderer/src/hooks/useMinapps.ts create mode 100644 src/renderer/src/store/minapps.ts diff --git a/src/renderer/src/components/DragableList/index.tsx b/src/renderer/src/components/DragableList/index.tsx index 935011016e..6be5dcd641 100644 --- a/src/renderer/src/components/DragableList/index.tsx +++ b/src/renderer/src/components/DragableList/index.tsx @@ -47,19 +47,22 @@ const DragableList: FC> = ({ {(provided) => (
- {list.map((item, index) => ( - - {(provided) => ( -
- {children(item, index)} -
- )} -
- ))} + {list.map((item, index) => { + const id = item.id || item + return ( + + {(provided) => ( +
+ {children(item, index)} +
+ )} +
+ ) + })}
)}
diff --git a/src/renderer/src/components/MinApp/index.tsx b/src/renderer/src/components/MinApp/index.tsx index 2df4042515..fe82fef73d 100644 --- a/src/renderer/src/components/MinApp/index.tsx +++ b/src/renderer/src/components/MinApp/index.tsx @@ -5,6 +5,7 @@ import { useBridge } from '@renderer/hooks/useBridge' import store from '@renderer/store' import { setMinappShow } from '@renderer/store/runtime' import { MinAppType } from '@renderer/types' +import { delay } from '@renderer/utils' import { Avatar, Drawer } from 'antd' import { WebviewTag } from 'electron' import { useEffect, useRef, useState } from 'react' @@ -28,9 +29,10 @@ const PopupContainer: React.FC = ({ app, resolve }) => { const canOpenExternalLink = app.url.startsWith('http://') || app.url.startsWith('https://') - const onClose = () => { + const onClose = async (_delay = 0.3) => { setOpen(false) - setTimeout(() => resolve({}), 300) + await delay(_delay) + resolve({}) } MinApp.onClose = onClose @@ -58,7 +60,7 @@ const PopupContainer: React.FC = ({ app, resolve }) => { )} - @@ -99,7 +101,7 @@ const PopupContainer: React.FC = ({ app, resolve }) => { } placement="bottom" - onClose={onClose} + onClose={() => onClose()} open={open} mask={true} rootClassName="minapp-drawer" @@ -202,40 +204,39 @@ const EmptyView = styled.div` export default class MinApp { static topviewId = 0 static onClose = () => {} - static isOpening = false + static app: MinAppType | null = null static async start(app: MinAppType) { - if (this.isOpening) return - this.isOpening = true - - try { - // 先关闭现有的小程序 - await this.close() - - // 确保 webview 完全卸载 - await new Promise(resolve => setTimeout(resolve, 100)) - - store.dispatch(setMinappShow(true)) - return new Promise((resolve) => { - TopView.show( - { - resolve(v) - this.close() - }} - />, - 'MinApp' - ) - }) - } finally { - this.isOpening = false + if (MinApp.app?.id === app.id) { + return } + + if (MinApp.app) { + // @ts-ignore delay params + await MinApp.onClose(0) + await delay(0) + } + + MinApp.app = app + store.dispatch(setMinappShow(true)) + + return new Promise((resolve) => { + TopView.show( + { + resolve(v) + this.close() + }} + />, + 'MinApp' + ) + }) } static close() { - if (!this.isOpening) return TopView.hide('MinApp') store.dispatch(setMinappShow(false)) + MinApp.app = null } } diff --git a/src/renderer/src/components/app/Sidebar.tsx b/src/renderer/src/components/app/Sidebar.tsx index af47548b4f..22996db73a 100644 --- a/src/renderer/src/components/app/Sidebar.tsx +++ b/src/renderer/src/components/app/Sidebar.tsx @@ -1,18 +1,21 @@ import { FileSearchOutlined, FolderOutlined, PictureOutlined, TranslationOutlined } from '@ant-design/icons' import { isMac } from '@renderer/config/constant' import { isLocalAi, UserAvatar } from '@renderer/config/env' -import { getAllMinApps } from '@renderer/config/minapps' import { useTheme } from '@renderer/context/ThemeProvider' import useAvatar from '@renderer/hooks/useAvatar' +import { useMinapps } from '@renderer/hooks/useMinapps' import { modelGenerating, useRuntime } from '@renderer/hooks/useRuntime' import { useSettings } from '@renderer/hooks/useSettings' +import type { MenuProps } from 'antd' import { Tooltip } from 'antd' import { Avatar } from 'antd' +import { Dropdown } from 'antd' import { FC } from 'react' import { useTranslation } from 'react-i18next' import { useLocation, useNavigate } from 'react-router-dom' import styled from 'styled-components' +import DragableList from '../DragableList' import MinApp from '../MinApp' import UserPopup from '../Popups/UserPopup' @@ -22,79 +25,22 @@ const Sidebar: FC = () => { const { minappShow } = useRuntime() const { t } = useTranslation() const navigate = useNavigate() - const { windowStyle, sidebarIcons, miniAppIcons } = useSettings() + const { windowStyle, sidebarIcons } = useSettings() const { theme, toggleTheme } = useTheme() - const allApps = getAllMinApps() - - const isRoute = (path: string): string => (pathname === path ? 'active' : '') - const isRoutes = (path: string): string => (pathname.startsWith(path) ? 'active' : '') + const { pinned } = useMinapps() const onEditUser = () => UserPopup.show() const macTransparentWindow = isMac && windowStyle === 'transparent' const sidebarBgColor = macTransparentWindow ? 'transparent' : 'var(--navbar-background)' + const showPinnedApps = pinned.length > 0 && sidebarIcons.visible.includes('minapp') + const to = async (path: string) => { await modelGenerating() navigate(path) } - const renderMainMenus = () => { - return sidebarIcons.visible.map((icon) => { - const iconMap = { - assistants: , - agents: , - paintings: , - translate: , - minapp: , - knowledge: , - files: - } - - const pathMap = { - assistants: '/', - agents: '/agents', - paintings: '/paintings', - translate: '/translate', - minapp: '/apps', - knowledge: '/knowledge', - files: '/files' - } - - const path = pathMap[icon] - const isActive = path === '/' ? isRoute(path) : isRoutes(path) - - return ( - - to(path)}> - {iconMap[icon]} - - - ) - }) - } - - const renderPinnedApps = () => { - if (!miniAppIcons?.pinned) return null - const pinnedApps = allApps.filter((app) => miniAppIcons.pinned.includes(app.id)) - return pinnedApps.map((app) => ( - - - MinApp.start(app)}> - - - - - )) - } - return ( { zIndex: minappShow ? 10000 : 'initial' }}> - - - - {renderMainMenus()} - {renderPinnedApps()} - - - + + + + + {showPinnedApps && ( + + + + + + + )} + toggleTheme()}> @@ -133,6 +84,89 @@ const Sidebar: FC = () => { ) } +const MainMenus: FC = () => { + const { t } = useTranslation() + const { pathname } = useLocation() + const { sidebarIcons } = useSettings() + const navigate = useNavigate() + + const isRoute = (path: string): string => (pathname === path ? 'active' : '') + const isRoutes = (path: string): string => (pathname.startsWith(path) ? 'active' : '') + + const iconMap = { + assistants: , + agents: , + paintings: , + translate: , + minapp: , + knowledge: , + files: + } + + const pathMap = { + assistants: '/', + agents: '/agents', + paintings: '/paintings', + translate: '/translate', + minapp: '/apps', + knowledge: '/knowledge', + files: '/files' + } + + return sidebarIcons.visible.map((icon) => { + const path = pathMap[icon] + const isActive = path === '/' ? isRoute(path) : isRoutes(path) + + return ( + + navigate(path)}> + {iconMap[icon]} + + + ) + }) +} + +const PinnedApps: FC = () => { + const { pinned, updatePinnedMinapps } = useMinapps() + const { t } = useTranslation() + + return ( + + {(app) => { + const menuItems: MenuProps['items'] = [ + { + key: 'togglePin', + label: t('minapp.sidebar.remove.title'), + onClick: () => { + const newPinned = pinned.filter((item) => item.id !== app.id) + updatePinnedMinapps(newPinned) + } + } + ] + return ( + + + + MinApp.start(app)}> + + + + + + ) + }} + + ) +} + const Container = styled.div` display: flex; flex-direction: column; @@ -154,9 +188,10 @@ const AvatarImg = styled(Avatar)` border: none; cursor: pointer; ` -const MainMenus = styled.div` +const MainMenusContainer = styled.div` display: flex; flex: 1; + flex-direction: column; overflow: hidden; ` @@ -164,6 +199,7 @@ const Menus = styled.div` display: flex; flex-direction: column; align-items: center; + gap: 5px; ` const Icon = styled.div` @@ -173,7 +209,6 @@ const Icon = styled.div` justify-content: center; align-items: center; border-radius: 50%; - margin-bottom: 5px; -webkit-app-region: none; border: 0.5px solid transparent; .iconfont, @@ -215,27 +250,23 @@ const AppIcon = styled.img` border-radius: 6px; ` -const ScrollContainer = styled.div` +const AppsContainer = styled.div` + display: flex; + flex: 1; + flex-direction: column; + align-items: center; overflow-y: auto; overflow-x: hidden; - height: 100%; - + margin-bottom: 10px; &::-webkit-scrollbar { - width: 0px; - } - - &:hover::-webkit-scrollbar { - width: 4px; - } - - &::-webkit-scrollbar-thumb { - background-color: var(--color-border); - border-radius: 4px; - } - - &::-webkit-scrollbar-track { - background: transparent; + display: none; } ` +const Divider = styled.div` + width: 50%; + margin: 8px 0; + border-bottom: 0.5px solid var(--color-border); +` + export default Sidebar diff --git a/src/renderer/src/hooks/useMinapps.ts b/src/renderer/src/hooks/useMinapps.ts new file mode 100644 index 0000000000..06a48da55d --- /dev/null +++ b/src/renderer/src/hooks/useMinapps.ts @@ -0,0 +1,23 @@ +import { RootState, useAppDispatch, useAppSelector } from '@renderer/store' +import { setDisabledMinApps, setMinApps, setPinnedMinApps } from '@renderer/store/minapps' +import { MinAppType } from '@renderer/types' + +export const useMinapps = () => { + const { enabled, disabled, pinned } = useAppSelector((state: RootState) => state.minapps) + const dispatch = useAppDispatch() + + return { + minapps: enabled, + disabled, + pinned, + updateMinapps: (minapps: MinAppType[]) => { + dispatch(setMinApps(minapps)) + }, + updateDisabledMinapps: (minapps: MinAppType[]) => { + dispatch(setDisabledMinApps(minapps)) + }, + updatePinnedMinapps: (minapps: MinAppType[]) => { + dispatch(setPinnedMinApps(minapps)) + } + } +} diff --git a/src/renderer/src/hooks/useSettings.ts b/src/renderer/src/hooks/useSettings.ts index 793b915334..8e0836b8e1 100644 --- a/src/renderer/src/hooks/useSettings.ts +++ b/src/renderer/src/hooks/useSettings.ts @@ -2,13 +2,14 @@ import store, { useAppDispatch, useAppSelector } from '@renderer/store' import { SendMessageShortcut, setSendMessageShortcut as _setSendMessageShortcut, + setSidebarIcons, setTheme, SettingsState, setTopicPosition, setTray, setWindowStyle } from '@renderer/store/settings' -import { ThemeMode } from '@renderer/types' +import { SidebarIcon, ThemeMode } from '@renderer/types' export function useSettings() { const settings = useAppSelector((state) => state.settings) @@ -30,6 +31,15 @@ export function useSettings() { }, setTopicPosition(topicPosition: 'left' | 'right') { dispatch(setTopicPosition(topicPosition)) + }, + updateSidebarIcons(icons: { visible: SidebarIcon[]; disabled: SidebarIcon[] }) { + dispatch(setSidebarIcons(icons)) + }, + updateSidebarVisibleIcons(icons: SidebarIcon[]) { + dispatch(setSidebarIcons({ visible: icons })) + }, + updateSidebarDisabledIcons(icons: SidebarIcon[]) { + dispatch(setSidebarIcons({ disabled: icons })) } } } diff --git a/src/renderer/src/i18n/locales/en-us.json b/src/renderer/src/i18n/locales/en-us.json index 9e50aa0e91..d63fa3bbd2 100644 --- a/src/renderer/src/i18n/locales/en-us.json +++ b/src/renderer/src/i18n/locales/en-us.json @@ -265,8 +265,8 @@ }, "minapp": { "title": "MinApp", - "sidebar.add.title": "Add minAPP to sidebar", - "sidebar.remove.title": "Remove minAPP from sidebar" + "sidebar.add.title": "Add to sidebar", + "sidebar.remove.title": "Remove from sidebar" }, "ollama": { "keep_alive_time.description": "The time in minutes to keep the connection alive, default is 5 minutes.", @@ -403,15 +403,15 @@ "display.sidebar.knowledge.icon": "Show Knowledge icon", "display.sidebar.files.icon": "Show Files icon", "display.sidebar.title": "Sidebar Settings", - "display.sidebar.visible": "Show my sidebar icons", - "display.sidebar.disabled": "Hide my sidebar icons", + "display.sidebar.visible": "Show icons", + "display.sidebar.disabled": "Hide icons", "display.sidebar.chat.hiddenMessage": "Assistants are basic functions, not supported for hiding", "display.sidebar.empty": "Drag the hidden feature from the left side here", "display.minApp.title": "MinApp Settings", "display.minApp.visible": "Visible MinApp", "display.minApp.disabled": "Hidden MinApp", "display.minApp.empty": "Drag minApp from the left to hide them here", - "display.minApp.pinnedError": "MinApp that have been added to the sidebar do not support hiding. If you want to hide them, please remove them from the sidebar first.", + "": "MinApp that have been added to the sidebar do not support hiding. If you want to hide them, please remove them from the sidebar first.", "display.topic.title": "Topic Settings", "display.custom.css": "Custom CSS", "display.custom.css.placeholder": "/* Put custom CSS here */", diff --git a/src/renderer/src/i18n/locales/ja-jp.json b/src/renderer/src/i18n/locales/ja-jp.json index 1770d8d0c4..b9031ce897 100644 --- a/src/renderer/src/i18n/locales/ja-jp.json +++ b/src/renderer/src/i18n/locales/ja-jp.json @@ -263,8 +263,8 @@ }, "minapp": { "title": "ミニアプリ", - "sidebar.add.title": "ミニプログラムをサイドバーに追加", - "sidebar.remove.title": "サイドバーからアプレットを削除する" + "sidebar.add.title": "サイドバーに追加", + "sidebar.remove.title": "サイドバーから削除" }, "ollama": { "keep_alive_time.description": "モデルがメモリに保持される時間(デフォルト:5分)", @@ -401,8 +401,8 @@ "display.sidebar.knowledge.icon": "ナレッジのアイコンを表示", "display.sidebar.files.icon": "ファイルのアイコンを表示", "display.sidebar.title": "サイドバー設定", - "display.sidebar.visible": "サイドバーのアイコンを表示する", - "display.sidebar.disabled": "サイドバーのアイコンを非表示にする", + "display.sidebar.visible": "アイコンを表示", + "display.sidebar.disabled": "アイコンを非表示", "display.sidebar.chat.hiddenMessage": "アシスタントは基本的な機能であり、非表示はサポートされていません", "display.sidebar.empty": "非表示にする機能を左側からここにドラッグ", "display.topic.title": "トピック設定", @@ -412,7 +412,6 @@ "display.minApp.visible": "表示中ミニプログラム", "display.minApp.disabled": "非表示ミニプログラム", "display.minApp.empty": "非表示にしたいアプレットを左からここまでドラッグします", - "display.minApp.pinnedError": "サイドバーに追加されたミニ プログラムは非表示をサポートしていません。非表示にしたい場合は、まずサイドバーから削除してください", "input.auto_translate_with_space": "スペースを3回押して翻訳", "messages.divider": "メッセージ間に区切り線を表示", "messages.input.paste_long_text_as_file": "長いテキストをファイルとして貼り付け", diff --git a/src/renderer/src/i18n/locales/ru-ru.json b/src/renderer/src/i18n/locales/ru-ru.json index 54dbb2f8b2..672cdfc400 100644 --- a/src/renderer/src/i18n/locales/ru-ru.json +++ b/src/renderer/src/i18n/locales/ru-ru.json @@ -265,8 +265,8 @@ }, "minapp": { "title": "Встроенные приложения", - "sidebar.add.title": "Добавить мини-программу на боковую панель", - "sidebar.remove.title": "Удалить апплет из боковой панели" + "sidebar.add.title": "Добавить в боковую панель", + "sidebar.remove.title": "Удалить из боковой панели" }, "ollama": { "keep_alive_time.description": "Время в минутах, в течение которого модель остается активной, по умолчанию 5 минут.", @@ -403,15 +403,14 @@ "display.sidebar.knowledge.icon": "Показывать иконку знаний", "display.sidebar.files.icon": "Показывать иконку файлов", "display.sidebar.title": "Настройки боковой панели", - "display.sidebar.visible": "Показать мои значки на боковой панели", - "display.sidebar.disabled": "Скрыть значок на боковой панели", + "display.sidebar.visible": "Показывать иконки", + "display.sidebar.disabled": "Скрыть иконки", "display.sidebar.chat.hiddenMessage": "Помощник является базовой функцией и не поддерживает скрытие", "display.sidebar.empty": "Перетащите скрываемую функцию с левой стороны сюда", "display.minApp.title": "Настройки отображения мини программы", "display.minApp.visible": "Отображаемый апплет", "display.minApp.disabled": "скрытый апплет", "display.minApp.empty": "Перетащите апплет, который хотите скрыть, слева сюда", - "display.minApp.pinnedError": "Мини-программы, добавленные на боковую панель, не поддерживают скрытие. Если вы хотите скрыть их, сначала удалите их с боковой панели", "display.topic.title": "Настройки топиков", "display.custom.css": "Пользовательский CSS", "display.custom.css.placeholder": "/* Здесь введите пользовательский CSS */", diff --git a/src/renderer/src/i18n/locales/zh-cn.json b/src/renderer/src/i18n/locales/zh-cn.json index 4a22f9a6de..ec51d02cdd 100644 --- a/src/renderer/src/i18n/locales/zh-cn.json +++ b/src/renderer/src/i18n/locales/zh-cn.json @@ -266,8 +266,8 @@ }, "minapp": { "title": "小程序", - "sidebar.add.title": "添加小程序到侧边栏", - "sidebar.remove.title": "从侧边栏移除小程序" + "sidebar.add.title": "添加到侧边栏", + "sidebar.remove.title": "从侧边栏移除" }, "ollama": { "keep_alive_time.description": "对话后模型在内存中保持的时间(默认:5分钟)", @@ -404,15 +404,14 @@ "display.sidebar.knowledge.icon": "显示知识图标", "display.sidebar.files.icon": "显示文件图标", "display.sidebar.title": "侧边栏设置", - "display.sidebar.visible": "显示我的侧边栏图标", - "display.sidebar.disabled": "隐藏我的侧边栏图标", + "display.sidebar.visible": "显示的图标", + "display.sidebar.disabled": "隐藏的图标", "display.sidebar.chat.hiddenMessage": "助手是基础功能,不支持隐藏", "display.sidebar.empty": "把要隐藏的功能从左侧拖拽到这里", "display.minApp.title": "小程序显示设置", "display.minApp.visible": "显示的小程序", "display.minApp.disabled": "隐藏的小程序", "display.minApp.empty": "把要隐藏的小程序从左侧拖拽到这里", - "display.minApp.pinnedError": "已经添加到侧边栏的小程序,不支持隐藏,如需隐藏请先从侧边栏移除", "display.topic.title": "话题设置", "display.custom.css": "自定义 CSS", "display.custom.css.placeholder": "/* 这里写自定义CSS */", diff --git a/src/renderer/src/i18n/locales/zh-tw.json b/src/renderer/src/i18n/locales/zh-tw.json index 62a54f1598..6f7e1596a3 100644 --- a/src/renderer/src/i18n/locales/zh-tw.json +++ b/src/renderer/src/i18n/locales/zh-tw.json @@ -265,8 +265,8 @@ }, "minapp": { "title": "小程序", - "sidebar.add.title": "新增小程式到側邊欄", - "sidebar.remove.title": "從側邊欄移除小程式" + "sidebar.add.title": "添加到側邊欄", + "sidebar.remove.title": "從側邊欄移除" }, "ollama": { "keep_alive_time.description": "對話後模型在記憶體中保持的時間(預設為 5 分鐘)。", @@ -406,13 +406,12 @@ "display.topic.title": "話題設定", "display.sidebar.chat.hiddenMessage": "助手是基礎功能,不支援隱藏", "display.sidebar.empty": "把要隱藏的功能從左側拖拽到這裡", - "display.sidebar.visible": "顯示我的側邊欄圖標", - "display.sidebar.disabled": "隱藏我的側邊欄圖標", + "display.sidebar.visible": "顯示的圖標", + "display.sidebar.disabled": "隱藏的圖標", "display.minApp.title": "小程序顯示設定", "display.minApp.visible": "顯示的小程序", "display.minApp.disabled": "隱藏的小程序", "display.minApp.empty": "把要隱藏的小程序從左側拖拽到這裡", - "display.minApp.pinnedError": "已新增至側邊欄的小程序,不支援隱藏,如需隱藏請先從側邊欄移除", "display.custom.css": "自定義 CSS", "display.custom.css.placeholder": "/* 這裡寫自定義 CSS */", "input.auto_translate_with_space": "快速敲擊3次空格翻譯", diff --git a/src/renderer/src/pages/apps/App.tsx b/src/renderer/src/pages/apps/App.tsx index 2d933b5957..8d40231e7c 100644 --- a/src/renderer/src/pages/apps/App.tsx +++ b/src/renderer/src/pages/apps/App.tsx @@ -1,6 +1,5 @@ import MinApp from '@renderer/components/MinApp' -import { useAppDispatch, useAppSelector } from '@renderer/store' -import { setMiniAppIcons } from '@renderer/store/settings' +import { useMinapps } from '@renderer/hooks/useMinapps' import { MinAppType } from '@renderer/types' import type { MenuProps } from 'antd' import { Dropdown } from 'antd' @@ -16,10 +15,9 @@ interface Props { const App: FC = ({ app, onClick, size = 60 }) => { const { t } = useTranslation() - const dispatch = useAppDispatch() - const { miniAppIcons } = useAppSelector((state) => state.settings) - const isPinned = miniAppIcons?.pinned.includes(app.id) - const isVisible = miniAppIcons?.visible.includes(app.id) + const { minapps, pinned, updatePinnedMinapps } = useMinapps() + const isPinned = pinned.some((p) => p.id === app.id) + const isVisible = minapps.some((m) => m.id === app.id) const handleClick = () => { MinApp.start(app) @@ -31,17 +29,8 @@ const App: FC = ({ app, onClick, size = 60 }) => { key: 'togglePin', label: isPinned ? t('minapp.sidebar.remove.title') : t('minapp.sidebar.add.title'), onClick: () => { - const newPinned = isPinned - ? miniAppIcons.pinned.filter((id) => id !== app.id) - : [...(miniAppIcons.pinned || []), app.id] - - dispatch( - setMiniAppIcons({ - ...miniAppIcons, - pinned: newPinned, - visible: isPinned ? miniAppIcons.visible : [...new Set([...miniAppIcons.visible, app.id])] - }) - ) + const newPinned = isPinned ? pinned.filter((item) => item.id !== app.id) : [...(pinned || []), app] + updatePinnedMinapps(newPinned) } } ] diff --git a/src/renderer/src/pages/apps/AppsPage.tsx b/src/renderer/src/pages/apps/AppsPage.tsx index 0432d287f8..107cc875a5 100644 --- a/src/renderer/src/pages/apps/AppsPage.tsx +++ b/src/renderer/src/pages/apps/AppsPage.tsx @@ -1,11 +1,10 @@ import { SearchOutlined } from '@ant-design/icons' import { Navbar, NavbarCenter } from '@renderer/components/app/Navbar' import { Center } from '@renderer/components/Layout' -import { getAllMinApps } from '@renderer/config/minapps' -import { useSettings } from '@renderer/hooks/useSettings' +import { useMinapps } from '@renderer/hooks/useMinapps' import { Empty, Input } from 'antd' import { isEmpty } from 'lodash' -import React, { FC, useMemo, useState } from 'react' +import React, { FC, useState } from 'react' import { useTranslation } from 'react-i18next' import styled from 'styled-components' @@ -14,24 +13,15 @@ import App from './App' const AppsPage: FC = () => { const { t } = useTranslation() const [search, setSearch] = useState('') - const { miniAppIcons } = useSettings() - const allApps = useMemo(() => getAllMinApps(), []) + const { minapps } = useMinapps() - // 只显示可见的小程序,但包括所有固定的小程序 - const visibleApps = useMemo(() => { - if (!miniAppIcons?.visible) return allApps - const visibleIds = new Set([ - ...miniAppIcons.visible, - ...(miniAppIcons.pinned || []) // 确保固定的小程序总是可见 - ]) - return allApps.filter((app) => visibleIds.has(app.id)) - }, [allApps, miniAppIcons?.visible, miniAppIcons?.pinned]) + console.debug('minapps', minapps) const filteredApps = search - ? visibleApps.filter( + ? minapps.filter( (app) => app.name.toLowerCase().includes(search.toLowerCase()) || app.url.includes(search.toLowerCase()) ) - : visibleApps + : minapps // Calculate the required number of lines const itemsPerRow = Math.floor(930 / 115) // Maximum width divided by the width of each item (including spacing) diff --git a/src/renderer/src/pages/settings/DisplaySettings/DisplaySettings.tsx b/src/renderer/src/pages/settings/DisplaySettings/DisplaySettings.tsx index 346c96db8a..576f18793e 100644 --- a/src/renderer/src/pages/settings/DisplaySettings/DisplaySettings.tsx +++ b/src/renderer/src/pages/settings/DisplaySettings/DisplaySettings.tsx @@ -1,13 +1,13 @@ import { isMac } from '@renderer/config/constant' +import { getAllMinApps } from '@renderer/config/minapps' import { useTheme } from '@renderer/context/ThemeProvider' +import { useMinapps } from '@renderer/hooks/useMinapps' import { useSettings } from '@renderer/hooks/useSettings' import { useAppDispatch } from '@renderer/store' import { - DEFAULT_MINIAPP_ICONS, DEFAULT_SIDEBAR_ICONS, setClickAssistantToShowTopic, setCustomCss, - setMiniAppIcons, setShowTopicTime, setSidebarIcons } from '@renderer/store/settings' @@ -32,17 +32,17 @@ const DisplaySettings: FC = () => { clickAssistantToShowTopic, showTopicTime, customCss, - sidebarIcons, - miniAppIcons + sidebarIcons } = useSettings() + const { minapps, disabled, updateMinapps, updateDisabledMinapps } = useMinapps() const { theme: themeMode } = useTheme() const { t } = useTranslation() const dispatch = useAppDispatch() const [visibleIcons, setVisibleIcons] = useState(sidebarIcons?.visible || DEFAULT_SIDEBAR_ICONS) const [disabledIcons, setDisabledIcons] = useState(sidebarIcons?.disabled || []) - const [visibleMiniApps, setVisibleMiniApps] = useState(miniAppIcons?.visible || DEFAULT_MINIAPP_ICONS) - const [disabledMiniApps, setDisabledMiniApps] = useState(miniAppIcons?.disabled || []) + const [visibleMiniApps, setVisibleMiniApps] = useState(minapps) + const [disabledMiniApps, setDisabledMiniApps] = useState(disabled || []) // 使用useCallback优化回调函数 const handleWindowStyleChange = useCallback( @@ -59,16 +59,11 @@ const DisplaySettings: FC = () => { }, [dispatch]) const handleResetMinApps = useCallback(() => { - setVisibleMiniApps(DEFAULT_MINIAPP_ICONS) + setVisibleMiniApps(getAllMinApps()) setDisabledMiniApps([]) - dispatch( - setMiniAppIcons({ - visible: DEFAULT_MINIAPP_ICONS, - disabled: [], - pinned: miniAppIcons?.pinned || [] - }) - ) - }, [dispatch, miniAppIcons?.pinned]) + updateMinapps(getAllMinApps()) + updateDisabledMinapps([]) + }, [updateDisabledMinapps, updateMinapps]) return ( diff --git a/src/renderer/src/pages/settings/DisplaySettings/MiniAppIconsManager.tsx b/src/renderer/src/pages/settings/DisplaySettings/MiniAppIconsManager.tsx index 3d3a68db5f..8fe4bdd69d 100644 --- a/src/renderer/src/pages/settings/DisplaySettings/MiniAppIconsManager.tsx +++ b/src/renderer/src/pages/settings/DisplaySettings/MiniAppIconsManager.tsx @@ -8,39 +8,33 @@ import { DropResult } from '@hello-pangea/dnd' import { getAllMinApps } from '@renderer/config/minapps' -import { useSettings } from '@renderer/hooks/useSettings' -import { useAppDispatch } from '@renderer/store' -import { MinAppIcon, setMiniAppIcons } from '@renderer/store/settings' +import { useMinapps } from '@renderer/hooks/useMinapps' +import { MinAppType } from '@renderer/types' import { FC, useCallback, useMemo } from 'react' import { useTranslation } from 'react-i18next' import styled from 'styled-components' interface MiniAppManagerProps { - visibleMiniApps: MinAppIcon[] - disabledMiniApps: MinAppIcon[] - setVisibleMiniApps: (programs: MinAppIcon[]) => void - setDisabledMiniApps: (programs: MinAppIcon[]) => void + visibleMiniApps: MinAppType[] + disabledMiniApps: MinAppType[] + setVisibleMiniApps: (programs: MinAppType[]) => void + setDisabledMiniApps: (programs: MinAppType[]) => void } -// 将可复用的类型和常量提取出来 type ListType = 'visible' | 'disabled' -interface AppInfo { - name: string - logo?: string -} // 添加 reorderLists 函数的接口定义 interface ReorderListsParams { - sourceList: MinAppIcon[] - destList: MinAppIcon[] + sourceList: MinAppType[] + destList: MinAppType[] sourceIndex: number destIndex: number isSameList: boolean } interface ReorderListsResult { - sourceList: MinAppIcon[] - destList: MinAppIcon[] + sourceList: MinAppType[] + destList: MinAppType[] } // 添加 reorderLists 函数 @@ -80,43 +74,18 @@ const MiniAppIconsManager: FC = ({ setDisabledMiniApps }) => { const { t } = useTranslation() - const dispatch = useAppDispatch() - const { miniAppIcons } = useSettings() const allApps = useMemo(() => getAllMinApps(), []) - - // 创建 app 信息的 Map 缓存 - const appInfoMap = useMemo(() => { - return allApps.reduce( - (acc, app) => { - acc[String(app.id)] = { name: app.name, logo: app.logo } - return acc - }, - {} as Record - ) - }, [allApps]) - - const getAppInfo = useCallback( - (id: MinAppIcon) => { - return appInfoMap[String(id)] || { name: id, logo: '' } - }, - [appInfoMap] - ) + const { pinned, updateMinapps, updateDisabledMinapps, updatePinnedMinapps } = useMinapps() const handleListUpdate = useCallback( - (newVisible: MinAppIcon[], newDisabled: MinAppIcon[]) => { + (newVisible: MinAppType[], newDisabled: MinAppType[]) => { setVisibleMiniApps(newVisible) setDisabledMiniApps(newDisabled) - - // 保持 pinned 状态不变 - dispatch( - setMiniAppIcons({ - visible: newVisible, - disabled: newDisabled, - pinned: miniAppIcons.pinned // 保持原有的 pinned 状态 - }) - ) + updateMinapps(newVisible) + updateDisabledMinapps(newDisabled) + updatePinnedMinapps(pinned.filter((p) => !newDisabled.some((d) => d.id === p.id))) }, - [dispatch, setVisibleMiniApps, setDisabledMiniApps, miniAppIcons.pinned] + [pinned, setDisabledMiniApps, setVisibleMiniApps, updateDisabledMinapps, updateMinapps, updatePinnedMinapps] ) const onDragEnd = useCallback( @@ -127,15 +96,7 @@ const MiniAppIconsManager: FC = ({ const sourceList = source.droppableId as ListType const destList = destination.droppableId as ListType - // 如果是 pinned 的小程序,不允许拖到 disabled - if (destList === 'disabled') { - const draggedApp = sourceList === 'visible' ? visibleMiniApps[source.index] : disabledMiniApps[source.index] - - if (miniAppIcons.pinned.includes(draggedApp)) { - window.message.error(t('settings.display.minApp.pinnedError')) - return - } - } + if (source.droppableId === destination.droppableId) return const newLists = reorderLists({ sourceList: sourceList === 'visible' ? visibleMiniApps : disabledMiniApps, @@ -150,32 +111,26 @@ const MiniAppIconsManager: FC = ({ sourceList === 'visible' ? newLists.destList : newLists.sourceList ) }, - [visibleMiniApps, disabledMiniApps, handleListUpdate, miniAppIcons.pinned, t] + [disabledMiniApps, handleListUpdate, visibleMiniApps] ) const onMoveMiniApp = useCallback( - (program: MinAppIcon, fromList: ListType) => { - // 如果是从可见列表移动到隐藏列表,且程序是 pinned 状态,则阻止移动 - if (fromList === 'visible' && miniAppIcons.pinned.includes(program)) { - window.message.error(t('settings.display.minApp.pinnedError')) - return - } - + (program: MinAppType, fromList: ListType) => { const isMovingToVisible = fromList === 'disabled' const newVisible = isMovingToVisible ? [...visibleMiniApps, program] - : visibleMiniApps.filter((p) => p !== program) + : visibleMiniApps.filter((p) => p.id !== program.id) const newDisabled = isMovingToVisible - ? disabledMiniApps.filter((p) => p !== program) + ? disabledMiniApps.filter((p) => p.id !== program.id) : [...disabledMiniApps, program] handleListUpdate(newVisible, newDisabled) }, - [visibleMiniApps, disabledMiniApps, handleListUpdate, miniAppIcons.pinned, t] + [visibleMiniApps, disabledMiniApps, handleListUpdate] ) - const renderProgramItem = (program: MinAppIcon, provided: DraggableProvided, listType: ListType) => { - const { name, logo } = getAppInfo(program) + const renderProgramItem = (program: MinAppType, provided: DraggableProvided, listType: ListType) => { + const { name, logo } = allApps.find((app) => app.id === program.id) || { name: program.name, logo: '' } return ( @@ -201,7 +156,7 @@ const MiniAppIconsManager: FC = ({ {(listType === 'visible' ? visibleMiniApps : disabledMiniApps).map((program, index) => ( - + {(provided: DraggableProvided) => renderProgramItem(program, provided, listType)} ))} @@ -230,6 +185,7 @@ const AppLogo = styled.img` const ScrollContainer = styled.div` overflow-y: auto; height: 100%; + padding-right: 5px; ` const ProgramSection = styled.div` @@ -253,6 +209,7 @@ const ProgramList = styled.div` height: 365px; min-height: 365px; padding: 10px; + padding-right: 5px; background: var(--color-background-soft); border-radius: 8px; border: 1px solid var(--color-border); diff --git a/src/renderer/src/store/index.ts b/src/renderer/src/store/index.ts index 7eb7c6929e..940515c908 100644 --- a/src/renderer/src/store/index.ts +++ b/src/renderer/src/store/index.ts @@ -8,6 +8,7 @@ import assistants from './assistants' import knowledge from './knowledge' import llm from './llm' import migrate from './migrate' +import minapps from './minapps' import paintings from './paintings' import runtime from './runtime' import settings from './settings' @@ -21,7 +22,8 @@ const rootReducer = combineReducers({ settings, runtime, shortcuts, - knowledge + knowledge, + minapps }) const persistedReducer = persistReducer( diff --git a/src/renderer/src/store/migrate.ts b/src/renderer/src/store/migrate.ts index 53de8a930a..b01ccff216 100644 --- a/src/renderer/src/store/migrate.ts +++ b/src/renderer/src/store/migrate.ts @@ -9,7 +9,6 @@ import { isEmpty } from 'lodash' import { createMigrate } from 'redux-persist' import { RootState } from '.' -import { DEFAULT_MINIAPP_ICONS, DEFAULT_SIDEBAR_ICONS } from './settings' const migrateConfig = { '2': (state: RootState) => { @@ -785,15 +784,6 @@ const migrateConfig = { system: false }) } - state.settings.sidebarIcons = { - visible: DEFAULT_SIDEBAR_ICONS, - disabled: [] - } - state.settings.miniAppIcons = { - visible: DEFAULT_MINIAPP_ICONS, - disabled: [], - pinned: [] - } return state } } diff --git a/src/renderer/src/store/minapps.ts b/src/renderer/src/store/minapps.ts new file mode 100644 index 0000000000..b3a54efb5c --- /dev/null +++ b/src/renderer/src/store/minapps.ts @@ -0,0 +1,48 @@ +import { createSlice, PayloadAction } from '@reduxjs/toolkit' +import { getAllMinApps } from '@renderer/config/minapps' +import { MinAppType, SidebarIcon } from '@renderer/types' + +export const DEFAULT_SIDEBAR_ICONS: SidebarIcon[] = [ + 'assistants', + 'agents', + 'paintings', + 'translate', + 'minapp', + 'knowledge', + 'files' +] + +export interface MinAppsState { + enabled: MinAppType[] + disabled: MinAppType[] + pinned: MinAppType[] +} + +const initialState: MinAppsState = { + enabled: getAllMinApps(), + disabled: [], + pinned: [] +} + +const minAppsSlice = createSlice({ + name: 'minApps', + initialState, + reducers: { + setMinApps: (state, action: PayloadAction) => { + state.enabled = action.payload + }, + addMinApp: (state, action: PayloadAction) => { + state.enabled.push(action.payload) + }, + setDisabledMinApps: (state, action: PayloadAction) => { + state.disabled = action.payload + }, + setPinnedMinApps: (state, action: PayloadAction) => { + state.pinned = action.payload + } + } +}) + +export const { setMinApps, addMinApp, setDisabledMinApps, setPinnedMinApps } = minAppsSlice.actions + +export default minAppsSlice.reducer diff --git a/src/renderer/src/store/settings.ts b/src/renderer/src/store/settings.ts index 6d124f3af6..de569ba6dd 100644 --- a/src/renderer/src/store/settings.ts +++ b/src/renderer/src/store/settings.ts @@ -1,5 +1,4 @@ import { createSlice, PayloadAction } from '@reduxjs/toolkit' -import { getAllMinApps } from '@renderer/config/minapps' import { TRANSLATE_PROMPT } from '@renderer/config/prompts' import { CodeStyleVarious, LanguageVarious, ThemeMode } from '@renderer/types' @@ -16,11 +15,6 @@ export const DEFAULT_SIDEBAR_ICONS: SidebarIcon[] = [ 'knowledge', 'files' ] -const [minApps] = await Promise.all([getAllMinApps()]) - -export type MinAppIcon = (typeof minApps)[number]['id'] // 假设每个小程序对象有 type 字段 - -export const DEFAULT_MINIAPP_ICONS: MinAppIcon[] = minApps.map((app) => app.id) export interface SettingsState { showAssistants: boolean @@ -67,11 +61,6 @@ export interface SettingsState { disabled: SidebarIcon[] } narrowMode: boolean - miniAppIcons: { - visible: MinAppIcon[] - disabled: MinAppIcon[] - pinned: MinAppIcon[] - } } const initialState: SettingsState = { @@ -116,12 +105,7 @@ const initialState: SettingsState = { visible: DEFAULT_SIDEBAR_ICONS, disabled: [] }, - narrowMode: false, - miniAppIcons: { - visible: DEFAULT_MINIAPP_ICONS, - disabled: [], - pinned: [] - } + narrowMode: false } const settingsSlice = createSlice({ @@ -246,17 +230,16 @@ const settingsSlice = createSlice({ setTopicNamingPrompt: (state, action: PayloadAction) => { state.topicNamingPrompt = action.payload }, - setSidebarIcons: (state, action: PayloadAction<{ visible: SidebarIcon[]; disabled: SidebarIcon[] }>) => { - state.sidebarIcons = action.payload + setSidebarIcons: (state, action: PayloadAction<{ visible?: SidebarIcon[]; disabled?: SidebarIcon[] }>) => { + if (action.payload.visible) { + state.sidebarIcons.visible = action.payload.visible + } + if (action.payload.disabled) { + state.sidebarIcons.disabled = action.payload.disabled + } }, setNarrowMode: (state, action: PayloadAction) => { state.narrowMode = action.payload - }, - setMiniAppIcons: ( - state, - action: PayloadAction<{ visible: MinAppIcon[]; disabled: MinAppIcon[]; pinned: MinAppIcon[] }> - ) => { - state.miniAppIcons = action.payload } } }) @@ -302,8 +285,7 @@ export const { setCustomCss, setTopicNamingPrompt, setSidebarIcons, - setNarrowMode, - setMiniAppIcons + setNarrowMode } = settingsSlice.actions export default settingsSlice.reducer diff --git a/src/renderer/src/types/index.ts b/src/renderer/src/types/index.ts index 307af1a590..7ffbc6b0d7 100644 --- a/src/renderer/src/types/index.ts +++ b/src/renderer/src/types/index.ts @@ -163,7 +163,9 @@ export enum ThemeMode { dark = 'dark', auto = 'auto' } + export type LanguageVarious = 'zh-CN' | 'zh-TW' | 'en-US' | 'ru-RU' | 'ja-JP' + export type CodeStyleVarious = BuiltinTheme | 'auto' export type WebDavConfig = { @@ -241,3 +243,5 @@ export type GenerateImageParams = { signal?: AbortSignal promptEnhancement?: boolean } + +export type SidebarIcon = 'assistants' | 'agents' | 'paintings' | 'translate' | 'minapp' | 'knowledge' | 'files'