@lobehub/lobehub 2.0.0-next.240 → 2.0.0-next.242
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.github/workflows/test.yml +13 -5
- package/CHANGELOG.md +50 -0
- package/apps/desktop/build/Icon-beta.Assets.car +0 -0
- package/apps/desktop/build/Icon-beta.icns +0 -0
- package/apps/desktop/build/icon-beta.ico +0 -0
- package/apps/desktop/build/icon-beta.png +0 -0
- package/apps/desktop/resources/locales/ar/menu.json +5 -1
- package/apps/desktop/resources/locales/bg-BG/menu.json +5 -1
- package/apps/desktop/resources/locales/de-DE/menu.json +5 -1
- package/apps/desktop/resources/locales/es-ES/menu.json +5 -1
- package/apps/desktop/resources/locales/fa-IR/menu.json +5 -1
- package/apps/desktop/resources/locales/fr-FR/menu.json +5 -1
- package/apps/desktop/resources/locales/it-IT/menu.json +5 -1
- package/apps/desktop/resources/locales/ja-JP/menu.json +5 -1
- package/apps/desktop/resources/locales/ko-KR/menu.json +5 -1
- package/apps/desktop/resources/locales/nl-NL/menu.json +5 -1
- package/apps/desktop/resources/locales/pl-PL/menu.json +5 -1
- package/apps/desktop/resources/locales/pt-BR/menu.json +5 -1
- package/apps/desktop/resources/locales/ru-RU/menu.json +5 -1
- package/apps/desktop/resources/locales/tr-TR/menu.json +5 -1
- package/apps/desktop/resources/locales/vi-VN/menu.json +5 -1
- package/apps/desktop/resources/locales/zh-CN/menu.json +5 -1
- package/apps/desktop/resources/locales/zh-TW/menu.json +5 -1
- package/apps/desktop/src/main/locales/default/menu.ts +5 -1
- package/apps/desktop/src/main/menus/impls/linux.ts +30 -0
- package/apps/desktop/src/main/menus/impls/macOS.test.ts +17 -0
- package/apps/desktop/src/main/menus/impls/macOS.ts +33 -0
- package/apps/desktop/src/main/menus/impls/windows.ts +30 -0
- package/changelog/v1.json +10 -0
- package/locales/ar/electron.json +24 -0
- package/locales/ar/models.json +48 -7
- package/locales/ar/plugin.json +9 -0
- package/locales/ar/providers.json +1 -0
- package/locales/bg-BG/electron.json +24 -0
- package/locales/bg-BG/models.json +35 -7
- package/locales/bg-BG/plugin.json +9 -0
- package/locales/bg-BG/providers.json +1 -0
- package/locales/de-DE/electron.json +24 -0
- package/locales/de-DE/models.json +26 -6
- package/locales/de-DE/plugin.json +9 -0
- package/locales/de-DE/providers.json +1 -0
- package/locales/en-US/electron.json +24 -0
- package/locales/en-US/models.json +10 -10
- package/locales/en-US/oauth.json +0 -1
- package/locales/en-US/providers.json +1 -0
- package/locales/en-US/subscription.json +2 -2
- package/locales/es-ES/electron.json +24 -0
- package/locales/es-ES/models.json +42 -7
- package/locales/es-ES/plugin.json +9 -0
- package/locales/es-ES/providers.json +1 -0
- package/locales/fa-IR/electron.json +24 -0
- package/locales/fa-IR/models.json +52 -10
- package/locales/fa-IR/plugin.json +9 -0
- package/locales/fa-IR/providers.json +1 -0
- package/locales/fr-FR/electron.json +24 -0
- package/locales/fr-FR/models.json +36 -7
- package/locales/fr-FR/plugin.json +9 -0
- package/locales/fr-FR/providers.json +1 -0
- package/locales/it-IT/electron.json +24 -0
- package/locales/it-IT/models.json +42 -7
- package/locales/it-IT/plugin.json +9 -0
- package/locales/it-IT/providers.json +1 -0
- package/locales/ja-JP/electron.json +24 -0
- package/locales/ja-JP/models.json +35 -6
- package/locales/ja-JP/plugin.json +9 -0
- package/locales/ja-JP/providers.json +1 -0
- package/locales/ko-KR/electron.json +24 -0
- package/locales/ko-KR/models.json +28 -7
- package/locales/ko-KR/plugin.json +9 -0
- package/locales/ko-KR/providers.json +1 -0
- package/locales/nl-NL/electron.json +24 -0
- package/locales/nl-NL/models.json +35 -6
- package/locales/nl-NL/plugin.json +9 -0
- package/locales/nl-NL/providers.json +1 -0
- package/locales/pl-PL/electron.json +24 -0
- package/locales/pl-PL/models.json +36 -7
- package/locales/pl-PL/plugin.json +9 -0
- package/locales/pl-PL/providers.json +1 -0
- package/locales/pt-BR/electron.json +24 -0
- package/locales/pt-BR/models.json +35 -6
- package/locales/pt-BR/plugin.json +9 -0
- package/locales/pt-BR/providers.json +1 -0
- package/locales/ru-RU/electron.json +24 -0
- package/locales/ru-RU/models.json +35 -7
- package/locales/ru-RU/plugin.json +9 -0
- package/locales/ru-RU/providers.json +1 -0
- package/locales/tr-TR/electron.json +24 -0
- package/locales/tr-TR/models.json +5 -7
- package/locales/tr-TR/plugin.json +9 -0
- package/locales/tr-TR/providers.json +1 -0
- package/locales/vi-VN/electron.json +24 -0
- package/locales/vi-VN/models.json +5 -5
- package/locales/vi-VN/plugin.json +9 -0
- package/locales/vi-VN/providers.json +1 -0
- package/locales/zh-CN/electron.json +24 -0
- package/locales/zh-CN/models.json +48 -6
- package/locales/zh-CN/oauth.json +0 -1
- package/locales/zh-CN/providers.json +1 -0
- package/locales/zh-CN/subscription.json +1 -1
- package/locales/zh-TW/electron.json +24 -0
- package/locales/zh-TW/models.json +10 -10
- package/locales/zh-TW/plugin.json +9 -0
- package/locales/zh-TW/providers.json +1 -0
- package/package.json +1 -1
- package/packages/electron-client-ipc/src/events/navigation.ts +12 -0
- package/src/components/PageTitle/index.tsx +11 -1
- package/src/features/ElectronTitlebar/NavigationBar/RecentlyViewed.tsx +137 -0
- package/src/features/ElectronTitlebar/NavigationBar/index.tsx +86 -0
- package/src/features/ElectronTitlebar/helpers/routeMetadata.ts +214 -0
- package/src/features/ElectronTitlebar/hooks/useNavigationHistory.ts +152 -0
- package/src/features/ElectronTitlebar/index.tsx +13 -5
- package/src/features/NavHeader/index.tsx +4 -2
- package/src/features/NavPanel/components/NavPanelDraggable.tsx +174 -0
- package/src/features/NavPanel/hooks/useNavPanel.ts +11 -35
- package/src/features/NavPanel/index.tsx +2 -126
- package/src/hooks/useTypeScriptHappyCallback.ts +7 -0
- package/src/locales/default/electron.ts +24 -0
- package/src/locales/default/subscription.ts +2 -3
- package/src/server/services/memory/userMemory/extract.ts +46 -6
- package/src/store/electron/actions/navigationHistory.ts +247 -0
- package/src/store/electron/initialState.ts +7 -1
- package/src/store/electron/store.ts +9 -2
- package/src/store/global/selectors/systemStatus.ts +4 -1
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
import { useWatchBroadcast } from '@lobechat/electron-client-ipc';
|
|
4
|
+
import { useCallback, useEffect, useRef } from 'react';
|
|
5
|
+
import { useTranslation } from 'react-i18next';
|
|
6
|
+
import { useLocation, useNavigate } from 'react-router-dom';
|
|
7
|
+
|
|
8
|
+
import { useElectronStore } from '@/store/electron';
|
|
9
|
+
|
|
10
|
+
import { getRouteMetadata } from '../helpers/routeMetadata';
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Hook to manage navigation history in Electron desktop app
|
|
14
|
+
* Provides browser-like back/forward functionality
|
|
15
|
+
*/
|
|
16
|
+
export const useNavigationHistory = () => {
|
|
17
|
+
const { t } = useTranslation('electron');
|
|
18
|
+
const navigate = useNavigate();
|
|
19
|
+
const location = useLocation();
|
|
20
|
+
|
|
21
|
+
// Get store state and actions
|
|
22
|
+
const isNavigatingHistory = useElectronStore((s) => s.isNavigatingHistory);
|
|
23
|
+
const historyCurrentIndex = useElectronStore((s) => s.historyCurrentIndex);
|
|
24
|
+
const historyEntries = useElectronStore((s) => s.historyEntries);
|
|
25
|
+
const currentPageTitle = useElectronStore((s) => s.currentPageTitle);
|
|
26
|
+
const pushHistory = useElectronStore((s) => s.pushHistory);
|
|
27
|
+
const replaceHistory = useElectronStore((s) => s.replaceHistory);
|
|
28
|
+
const setIsNavigatingHistory = useElectronStore((s) => s.setIsNavigatingHistory);
|
|
29
|
+
const storeGoBack = useElectronStore((s) => s.goBack);
|
|
30
|
+
const storeGoForward = useElectronStore((s) => s.goForward);
|
|
31
|
+
const canGoBackFn = useElectronStore((s) => s.canGoBack);
|
|
32
|
+
const canGoForwardFn = useElectronStore((s) => s.canGoForward);
|
|
33
|
+
const getCurrentEntry = useElectronStore((s) => s.getCurrentEntry);
|
|
34
|
+
|
|
35
|
+
// Track previous location to avoid duplicate entries
|
|
36
|
+
const prevLocationRef = useRef<string | null>(null);
|
|
37
|
+
|
|
38
|
+
// Calculate can go back/forward
|
|
39
|
+
const canGoBack = historyCurrentIndex > 0;
|
|
40
|
+
const canGoForward = historyCurrentIndex < historyEntries.length - 1;
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Go back in history
|
|
44
|
+
*/
|
|
45
|
+
const goBack = useCallback(() => {
|
|
46
|
+
if (!canGoBackFn()) return;
|
|
47
|
+
|
|
48
|
+
const targetEntry = storeGoBack();
|
|
49
|
+
if (targetEntry) {
|
|
50
|
+
navigate(targetEntry.url);
|
|
51
|
+
}
|
|
52
|
+
}, [canGoBackFn, storeGoBack, navigate]);
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* Go forward in history
|
|
56
|
+
*/
|
|
57
|
+
const goForward = useCallback(() => {
|
|
58
|
+
if (!canGoForwardFn()) return;
|
|
59
|
+
|
|
60
|
+
const targetEntry = storeGoForward();
|
|
61
|
+
if (targetEntry) {
|
|
62
|
+
navigate(targetEntry.url);
|
|
63
|
+
}
|
|
64
|
+
}, [canGoForwardFn, storeGoForward, navigate]);
|
|
65
|
+
|
|
66
|
+
// Listen to route changes and push history
|
|
67
|
+
useEffect(() => {
|
|
68
|
+
const currentUrl = location.pathname + location.search;
|
|
69
|
+
|
|
70
|
+
// Skip if this is a back/forward navigation
|
|
71
|
+
if (isNavigatingHistory) {
|
|
72
|
+
setIsNavigatingHistory(false);
|
|
73
|
+
prevLocationRef.current = currentUrl;
|
|
74
|
+
return;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
// Skip if same as previous location
|
|
78
|
+
if (prevLocationRef.current === currentUrl) {
|
|
79
|
+
return;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
// Skip if same as current entry
|
|
83
|
+
const currentEntry = getCurrentEntry();
|
|
84
|
+
if (currentEntry?.url === currentUrl) {
|
|
85
|
+
prevLocationRef.current = currentUrl;
|
|
86
|
+
return;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
// Get metadata for this route
|
|
90
|
+
const metadata = getRouteMetadata(location.pathname);
|
|
91
|
+
const presetTitle = t(metadata.titleKey as any) as string;
|
|
92
|
+
|
|
93
|
+
// Push history with preset title (will be updated by PageTitle if useDynamicTitle)
|
|
94
|
+
pushHistory({
|
|
95
|
+
metadata: {
|
|
96
|
+
timestamp: Date.now(),
|
|
97
|
+
},
|
|
98
|
+
title: presetTitle,
|
|
99
|
+
url: currentUrl,
|
|
100
|
+
});
|
|
101
|
+
|
|
102
|
+
prevLocationRef.current = currentUrl;
|
|
103
|
+
}, [
|
|
104
|
+
location.pathname,
|
|
105
|
+
location.search,
|
|
106
|
+
isNavigatingHistory,
|
|
107
|
+
setIsNavigatingHistory,
|
|
108
|
+
getCurrentEntry,
|
|
109
|
+
pushHistory,
|
|
110
|
+
t,
|
|
111
|
+
]);
|
|
112
|
+
|
|
113
|
+
// Update current history entry title when PageTitle component updates
|
|
114
|
+
useEffect(() => {
|
|
115
|
+
if (!currentPageTitle) return;
|
|
116
|
+
|
|
117
|
+
const currentEntry = getCurrentEntry();
|
|
118
|
+
if (!currentEntry) return;
|
|
119
|
+
|
|
120
|
+
// Check if current route supports dynamic title
|
|
121
|
+
const metadata = getRouteMetadata(location.pathname);
|
|
122
|
+
if (!metadata.useDynamicTitle) return;
|
|
123
|
+
|
|
124
|
+
// Skip if title is already the same
|
|
125
|
+
if (currentEntry.title === currentPageTitle) return;
|
|
126
|
+
|
|
127
|
+
// Update the current history entry with the dynamic title
|
|
128
|
+
replaceHistory({
|
|
129
|
+
...currentEntry,
|
|
130
|
+
title: currentPageTitle,
|
|
131
|
+
});
|
|
132
|
+
}, [currentPageTitle, getCurrentEntry, replaceHistory, location.pathname]);
|
|
133
|
+
|
|
134
|
+
// Listen to broadcast events from main process (Electron menu)
|
|
135
|
+
useWatchBroadcast('historyGoBack', () => {
|
|
136
|
+
goBack();
|
|
137
|
+
});
|
|
138
|
+
|
|
139
|
+
useWatchBroadcast('historyGoForward', () => {
|
|
140
|
+
goForward();
|
|
141
|
+
});
|
|
142
|
+
|
|
143
|
+
return {
|
|
144
|
+
canGoBack,
|
|
145
|
+
canGoForward,
|
|
146
|
+
currentEntry: getCurrentEntry(),
|
|
147
|
+
goBack,
|
|
148
|
+
goForward,
|
|
149
|
+
historyEntries,
|
|
150
|
+
historyIndex: historyCurrentIndex,
|
|
151
|
+
};
|
|
152
|
+
};
|
|
@@ -1,12 +1,13 @@
|
|
|
1
1
|
import { Flexbox } from '@lobehub/ui';
|
|
2
2
|
import { Divider } from 'antd';
|
|
3
|
-
import { memo } from 'react';
|
|
3
|
+
import { memo, useMemo } from 'react';
|
|
4
4
|
|
|
5
5
|
import { useElectronStore } from '@/store/electron';
|
|
6
6
|
import { electronStylish } from '@/styles/electron';
|
|
7
7
|
import { isMacOS } from '@/utils/platform';
|
|
8
8
|
|
|
9
9
|
import Connection from './Connection';
|
|
10
|
+
import NavigationBar from './NavigationBar';
|
|
10
11
|
import { UpdateModal } from './UpdateModal';
|
|
11
12
|
import { UpdateNotification } from './UpdateNotification';
|
|
12
13
|
import WinControl from './WinControl';
|
|
@@ -25,6 +26,15 @@ const TitleBar = memo(() => {
|
|
|
25
26
|
useWatchThemeUpdate();
|
|
26
27
|
|
|
27
28
|
const showWinControl = isAppStateInit && !isMac;
|
|
29
|
+
|
|
30
|
+
const padding = useMemo(() => {
|
|
31
|
+
if (showWinControl) {
|
|
32
|
+
return '0 12px 0 0';
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
return '0 12px';
|
|
36
|
+
}, [showWinControl, isMac]);
|
|
37
|
+
|
|
28
38
|
return (
|
|
29
39
|
<Flexbox
|
|
30
40
|
align={'center'}
|
|
@@ -32,12 +42,10 @@ const TitleBar = memo(() => {
|
|
|
32
42
|
height={TITLE_BAR_HEIGHT}
|
|
33
43
|
horizontal
|
|
34
44
|
justify={'space-between'}
|
|
35
|
-
|
|
36
|
-
style={{ minHeight: TITLE_BAR_HEIGHT }}
|
|
45
|
+
style={{ minHeight: TITLE_BAR_HEIGHT, padding }}
|
|
37
46
|
width={'100%'}
|
|
38
47
|
>
|
|
39
|
-
<
|
|
40
|
-
<div>{/* TODO */}</div>
|
|
48
|
+
<NavigationBar />
|
|
41
49
|
|
|
42
50
|
<Flexbox align={'center'} gap={4} horizontal>
|
|
43
51
|
<Flexbox className={electronStylish.nodrag} gap={8} horizontal>
|
|
@@ -2,7 +2,8 @@ import { Flexbox, type FlexboxProps, TooltipGroup } from '@lobehub/ui';
|
|
|
2
2
|
import { type CSSProperties, type ReactNode, memo } from 'react';
|
|
3
3
|
|
|
4
4
|
import ToggleLeftPanelButton from '@/features/NavPanel/ToggleLeftPanelButton';
|
|
5
|
-
import {
|
|
5
|
+
import { useGlobalStore } from '@/store/global';
|
|
6
|
+
import { systemStatusSelectors } from '@/store/global/selectors';
|
|
6
7
|
|
|
7
8
|
export interface NavHeaderProps extends Omit<FlexboxProps, 'children'> {
|
|
8
9
|
children?: ReactNode;
|
|
@@ -18,7 +19,8 @@ export interface NavHeaderProps extends Omit<FlexboxProps, 'children'> {
|
|
|
18
19
|
|
|
19
20
|
const NavHeader = memo<NavHeaderProps>(
|
|
20
21
|
({ showTogglePanelButton = true, style, children, left, right, styles, ...rest }) => {
|
|
21
|
-
const
|
|
22
|
+
const expand = useGlobalStore(systemStatusSelectors.showLeftPanel);
|
|
23
|
+
|
|
22
24
|
const noContent = !left && !right;
|
|
23
25
|
|
|
24
26
|
if (noContent && expand) return;
|
|
@@ -0,0 +1,174 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
import { DraggablePanel } from '@lobehub/ui';
|
|
4
|
+
import { createStaticStyles, cssVar } from 'antd-style';
|
|
5
|
+
import { AnimatePresence, motion } from 'motion/react';
|
|
6
|
+
import { type ReactNode, memo, useMemo, useRef } from 'react';
|
|
7
|
+
|
|
8
|
+
import { USER_DROPDOWN_ICON_ID } from '@/app/[variants]/(main)/home/_layout/Header/components/User';
|
|
9
|
+
import { isDesktop } from '@/const/version';
|
|
10
|
+
import { TOGGLE_BUTTON_ID } from '@/features/NavPanel/ToggleLeftPanelButton';
|
|
11
|
+
import { useGlobalStore } from '@/store/global';
|
|
12
|
+
import { systemStatusSelectors } from '@/store/global/selectors';
|
|
13
|
+
import { isMacOS } from '@/utils/platform';
|
|
14
|
+
|
|
15
|
+
import { useNavPanelSizeChangeHandler } from '../hooks/useNavPanel';
|
|
16
|
+
import { BACK_BUTTON_ID } from './BackButton';
|
|
17
|
+
|
|
18
|
+
const motionVariants = {
|
|
19
|
+
animate: { opacity: 1, x: 0 },
|
|
20
|
+
exit: {
|
|
21
|
+
opacity: 0,
|
|
22
|
+
x: '-20%',
|
|
23
|
+
},
|
|
24
|
+
initial: {
|
|
25
|
+
opacity: 0,
|
|
26
|
+
x: 0,
|
|
27
|
+
},
|
|
28
|
+
transition: {
|
|
29
|
+
duration: 0.4,
|
|
30
|
+
ease: [0.4, 0, 0.2, 1],
|
|
31
|
+
},
|
|
32
|
+
} as const;
|
|
33
|
+
|
|
34
|
+
const draggableStyles = createStaticStyles(({ css, cssVar }) => ({
|
|
35
|
+
content: css`
|
|
36
|
+
position: relative;
|
|
37
|
+
|
|
38
|
+
overflow: hidden;
|
|
39
|
+
display: flex;
|
|
40
|
+
|
|
41
|
+
height: 100%;
|
|
42
|
+
min-height: 100%;
|
|
43
|
+
max-height: 100%;
|
|
44
|
+
`,
|
|
45
|
+
inner: css`
|
|
46
|
+
position: relative;
|
|
47
|
+
inset: 0;
|
|
48
|
+
|
|
49
|
+
overflow: hidden;
|
|
50
|
+
flex: 1;
|
|
51
|
+
flex-direction: column;
|
|
52
|
+
|
|
53
|
+
min-width: 240px;
|
|
54
|
+
`,
|
|
55
|
+
panel: css`
|
|
56
|
+
user-select: none;
|
|
57
|
+
height: 100%;
|
|
58
|
+
color: ${cssVar.colorTextSecondary};
|
|
59
|
+
background: ${isDesktop && isMacOS() ? 'transparent' : cssVar.colorBgLayout};
|
|
60
|
+
|
|
61
|
+
* {
|
|
62
|
+
user-select: none;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
#${TOGGLE_BUTTON_ID} {
|
|
66
|
+
width: 0 !important;
|
|
67
|
+
opacity: 0;
|
|
68
|
+
transition:
|
|
69
|
+
opacity,
|
|
70
|
+
width 0.2s ${cssVar.motionEaseOut};
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
#${USER_DROPDOWN_ICON_ID} {
|
|
74
|
+
width: 0 !important;
|
|
75
|
+
opacity: 0;
|
|
76
|
+
transition:
|
|
77
|
+
opacity,
|
|
78
|
+
width 0.2s ${cssVar.motionEaseOut};
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
#${BACK_BUTTON_ID} {
|
|
82
|
+
width: 0 !important;
|
|
83
|
+
opacity: 0;
|
|
84
|
+
transition: all 0.2s ${cssVar.motionEaseOut};
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
&:hover {
|
|
88
|
+
#${TOGGLE_BUTTON_ID} {
|
|
89
|
+
width: 32px !important;
|
|
90
|
+
opacity: 1;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
#${USER_DROPDOWN_ICON_ID} {
|
|
94
|
+
width: 14px !important;
|
|
95
|
+
opacity: 1;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
&:hover {
|
|
99
|
+
#${BACK_BUTTON_ID} {
|
|
100
|
+
width: 24px !important;
|
|
101
|
+
opacity: 1;
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
`,
|
|
106
|
+
}));
|
|
107
|
+
|
|
108
|
+
interface NavPanelDraggableProps {
|
|
109
|
+
activeContent: {
|
|
110
|
+
key: string;
|
|
111
|
+
node: ReactNode;
|
|
112
|
+
};
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
const classNames = {
|
|
116
|
+
content: draggableStyles.content,
|
|
117
|
+
};
|
|
118
|
+
|
|
119
|
+
export const NavPanelDraggable = memo<NavPanelDraggableProps>(({ activeContent }) => {
|
|
120
|
+
const [expand, togglePanel] = useGlobalStore((s) => [
|
|
121
|
+
systemStatusSelectors.showLeftPanel(s),
|
|
122
|
+
s.toggleLeftPanel,
|
|
123
|
+
]);
|
|
124
|
+
const handleSizeChange = useNavPanelSizeChangeHandler();
|
|
125
|
+
|
|
126
|
+
const defaultWidthRef = useRef(0);
|
|
127
|
+
if (defaultWidthRef.current === 0) {
|
|
128
|
+
defaultWidthRef.current = systemStatusSelectors.leftPanelWidth(useGlobalStore.getState());
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
const defaultSize = useMemo(
|
|
132
|
+
() => ({
|
|
133
|
+
height: '100%',
|
|
134
|
+
width: defaultWidthRef.current,
|
|
135
|
+
}),
|
|
136
|
+
[defaultWidthRef.current],
|
|
137
|
+
);
|
|
138
|
+
const styles = useMemo(
|
|
139
|
+
() => ({
|
|
140
|
+
background: isDesktop && isMacOS() ? 'transparent' : cssVar.colorBgLayout,
|
|
141
|
+
zIndex: 11,
|
|
142
|
+
}),
|
|
143
|
+
[isDesktop, isMacOS()],
|
|
144
|
+
);
|
|
145
|
+
return (
|
|
146
|
+
<DraggablePanel
|
|
147
|
+
className={draggableStyles.panel}
|
|
148
|
+
classNames={classNames}
|
|
149
|
+
defaultSize={defaultSize}
|
|
150
|
+
expand={expand}
|
|
151
|
+
expandable={false}
|
|
152
|
+
maxWidth={400}
|
|
153
|
+
minWidth={240}
|
|
154
|
+
onExpandChange={togglePanel}
|
|
155
|
+
onSizeDragging={handleSizeChange}
|
|
156
|
+
placement="left"
|
|
157
|
+
showBorder={false}
|
|
158
|
+
style={styles}
|
|
159
|
+
>
|
|
160
|
+
<AnimatePresence initial={false} mode="popLayout">
|
|
161
|
+
<motion.div
|
|
162
|
+
animate={motionVariants.animate}
|
|
163
|
+
className={draggableStyles.inner}
|
|
164
|
+
exit={motionVariants.exit}
|
|
165
|
+
initial={motionVariants.initial}
|
|
166
|
+
key={activeContent.key}
|
|
167
|
+
transition={motionVariants.transition}
|
|
168
|
+
>
|
|
169
|
+
{activeContent.node}
|
|
170
|
+
</motion.div>
|
|
171
|
+
</AnimatePresence>
|
|
172
|
+
</DraggablePanel>
|
|
173
|
+
);
|
|
174
|
+
});
|
|
@@ -2,49 +2,25 @@
|
|
|
2
2
|
|
|
3
3
|
import { type DraggablePanelProps } from '@lobehub/ui';
|
|
4
4
|
import isEqual from 'fast-deep-equal';
|
|
5
|
-
import { useCallback, useState } from 'react';
|
|
6
5
|
|
|
6
|
+
import { useTypeScriptHappyCallback } from '@/hooks/useTypeScriptHappyCallback';
|
|
7
7
|
import { useGlobalStore } from '@/store/global';
|
|
8
8
|
import { systemStatusSelectors } from '@/store/global/selectors';
|
|
9
9
|
|
|
10
|
-
export const
|
|
11
|
-
const [
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
s.toggleLeftPanel,
|
|
15
|
-
s.updateSystemStatus,
|
|
16
|
-
]);
|
|
17
|
-
|
|
18
|
-
const [tmpWidth, setWidth] = useState(leftPanelWidth);
|
|
19
|
-
|
|
20
|
-
if (tmpWidth !== leftPanelWidth) setWidth(leftPanelWidth);
|
|
21
|
-
|
|
22
|
-
const handleSizeChange: DraggablePanelProps['onSizeChange'] = useCallback(
|
|
23
|
-
(_: any, size: any) => {
|
|
24
|
-
const width = size?.width;
|
|
10
|
+
export const useNavPanelSizeChangeHandler = (onChange?: (width: number) => void) => {
|
|
11
|
+
const handleSizeChange: DraggablePanelProps['onSizeChange'] = useTypeScriptHappyCallback(
|
|
12
|
+
(_, size) => {
|
|
13
|
+
const width = typeof size?.width === 'string' ? Number.parseInt(size.width) : size?.width;
|
|
25
14
|
if (!width || width < 64) return;
|
|
15
|
+
const s = useGlobalStore.getState();
|
|
16
|
+
const leftPanelWidth = systemStatusSelectors.leftPanelWidth(s);
|
|
17
|
+
const updatePreference = s.updateSystemStatus;
|
|
26
18
|
if (isEqual(width, leftPanelWidth)) return;
|
|
27
|
-
|
|
19
|
+
onChange?.(width);
|
|
28
20
|
updatePreference({ leftPanelWidth: width });
|
|
29
21
|
},
|
|
30
|
-
[
|
|
22
|
+
[],
|
|
31
23
|
);
|
|
32
24
|
|
|
33
|
-
|
|
34
|
-
togglePanel(true);
|
|
35
|
-
}, [togglePanel]);
|
|
36
|
-
|
|
37
|
-
const closePanel = useCallback(() => {
|
|
38
|
-
togglePanel(false);
|
|
39
|
-
}, [togglePanel]);
|
|
40
|
-
|
|
41
|
-
return {
|
|
42
|
-
closePanel,
|
|
43
|
-
defaultWidth: tmpWidth,
|
|
44
|
-
expand: sessionExpandable,
|
|
45
|
-
handleSizeChange,
|
|
46
|
-
openPanel,
|
|
47
|
-
togglePanel,
|
|
48
|
-
width: leftPanelWidth,
|
|
49
|
-
};
|
|
25
|
+
return handleSizeChange;
|
|
50
26
|
};
|
|
@@ -1,8 +1,5 @@
|
|
|
1
1
|
'use client';
|
|
2
2
|
|
|
3
|
-
import { DraggablePanel } from '@lobehub/ui';
|
|
4
|
-
import { createStaticStyles, cssVar } from 'antd-style';
|
|
5
|
-
import { AnimatePresence, motion } from 'motion/react';
|
|
6
3
|
import {
|
|
7
4
|
type PropsWithChildren,
|
|
8
5
|
type ReactNode,
|
|
@@ -11,14 +8,8 @@ import {
|
|
|
11
8
|
useSyncExternalStore,
|
|
12
9
|
} from 'react';
|
|
13
10
|
|
|
14
|
-
import { USER_DROPDOWN_ICON_ID } from '@/app/[variants]/(main)/home/_layout/Header/components/User';
|
|
15
|
-
import { isDesktop } from '@/const/version';
|
|
16
|
-
import { TOGGLE_BUTTON_ID } from '@/features/NavPanel/ToggleLeftPanelButton';
|
|
17
|
-
import { isMacOS } from '@/utils/platform';
|
|
18
|
-
|
|
19
11
|
import Sidebar from '../../app/[variants]/(main)/home/_layout/Sidebar';
|
|
20
|
-
import {
|
|
21
|
-
import { useNavPanel } from './hooks/useNavPanel';
|
|
12
|
+
import { NavPanelDraggable } from './components/NavPanelDraggable';
|
|
22
13
|
|
|
23
14
|
export const NAV_PANEL_RIGHT_DRAWER_ID = 'nav-panel-drawer';
|
|
24
15
|
|
|
@@ -41,82 +32,7 @@ const setNavPanelSnapshot = (snapshot: NavPanelSnapshot) => {
|
|
|
41
32
|
listeners.forEach((listener) => listener());
|
|
42
33
|
};
|
|
43
34
|
|
|
44
|
-
export const styles = createStaticStyles(({ css, cssVar }) => ({
|
|
45
|
-
content: css`
|
|
46
|
-
position: relative;
|
|
47
|
-
|
|
48
|
-
overflow: hidden;
|
|
49
|
-
display: flex;
|
|
50
|
-
|
|
51
|
-
height: 100%;
|
|
52
|
-
min-height: 100%;
|
|
53
|
-
max-height: 100%;
|
|
54
|
-
`,
|
|
55
|
-
inner: css`
|
|
56
|
-
position: relative;
|
|
57
|
-
inset: 0;
|
|
58
|
-
|
|
59
|
-
overflow: hidden;
|
|
60
|
-
flex: 1;
|
|
61
|
-
flex-direction: column;
|
|
62
|
-
|
|
63
|
-
min-width: 240px;
|
|
64
|
-
`,
|
|
65
|
-
panel: css`
|
|
66
|
-
user-select: none;
|
|
67
|
-
height: 100%;
|
|
68
|
-
color: ${cssVar.colorTextSecondary};
|
|
69
|
-
background: ${isDesktop && isMacOS() ? 'transparent' : cssVar.colorBgLayout};
|
|
70
|
-
|
|
71
|
-
* {
|
|
72
|
-
user-select: none;
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
#${TOGGLE_BUTTON_ID} {
|
|
76
|
-
width: 0 !important;
|
|
77
|
-
opacity: 0;
|
|
78
|
-
transition:
|
|
79
|
-
opacity,
|
|
80
|
-
width 0.2s ${cssVar.motionEaseOut};
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
#${USER_DROPDOWN_ICON_ID} {
|
|
84
|
-
width: 0 !important;
|
|
85
|
-
opacity: 0;
|
|
86
|
-
transition:
|
|
87
|
-
opacity,
|
|
88
|
-
width 0.2s ${cssVar.motionEaseOut};
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
#${BACK_BUTTON_ID} {
|
|
92
|
-
width: 0 !important;
|
|
93
|
-
opacity: 0;
|
|
94
|
-
transition: all 0.2s ${cssVar.motionEaseOut};
|
|
95
|
-
}
|
|
96
|
-
|
|
97
|
-
&:hover {
|
|
98
|
-
#${TOGGLE_BUTTON_ID} {
|
|
99
|
-
width: 32px !important;
|
|
100
|
-
opacity: 1;
|
|
101
|
-
}
|
|
102
|
-
|
|
103
|
-
#${USER_DROPDOWN_ICON_ID} {
|
|
104
|
-
width: 14px !important;
|
|
105
|
-
opacity: 1;
|
|
106
|
-
}
|
|
107
|
-
|
|
108
|
-
&:hover {
|
|
109
|
-
#${BACK_BUTTON_ID} {
|
|
110
|
-
width: 24px !important;
|
|
111
|
-
opacity: 1;
|
|
112
|
-
}
|
|
113
|
-
}
|
|
114
|
-
}
|
|
115
|
-
`,
|
|
116
|
-
}));
|
|
117
|
-
|
|
118
35
|
const NavPanel = memo(() => {
|
|
119
|
-
const { expand, handleSizeChange, width, togglePanel } = useNavPanel();
|
|
120
36
|
const panelContent = useSyncExternalStore(
|
|
121
37
|
subscribeNavPanel,
|
|
122
38
|
getNavPanelSnapshot,
|
|
@@ -128,47 +44,7 @@ const NavPanel = memo(() => {
|
|
|
128
44
|
|
|
129
45
|
return (
|
|
130
46
|
<>
|
|
131
|
-
<
|
|
132
|
-
className={styles.panel}
|
|
133
|
-
classNames={{
|
|
134
|
-
content: styles.content,
|
|
135
|
-
}}
|
|
136
|
-
defaultSize={{ height: '100%', width }}
|
|
137
|
-
expand={expand}
|
|
138
|
-
expandable={false}
|
|
139
|
-
maxWidth={400}
|
|
140
|
-
minWidth={240}
|
|
141
|
-
onExpandChange={(expand) => togglePanel(expand)}
|
|
142
|
-
onSizeChange={handleSizeChange}
|
|
143
|
-
placement="left"
|
|
144
|
-
showBorder={false}
|
|
145
|
-
style={{
|
|
146
|
-
background: isDesktop && isMacOS() ? 'transparent' : cssVar.colorBgLayout,
|
|
147
|
-
zIndex: 11,
|
|
148
|
-
}}
|
|
149
|
-
>
|
|
150
|
-
<AnimatePresence initial={false} mode="popLayout">
|
|
151
|
-
<motion.div
|
|
152
|
-
animate={{ opacity: 1, x: 0 }}
|
|
153
|
-
className={styles.inner}
|
|
154
|
-
exit={{
|
|
155
|
-
opacity: 0,
|
|
156
|
-
x: '-20%',
|
|
157
|
-
}}
|
|
158
|
-
initial={{
|
|
159
|
-
opacity: 0,
|
|
160
|
-
x: 0,
|
|
161
|
-
}}
|
|
162
|
-
key={activeContent.key}
|
|
163
|
-
transition={{
|
|
164
|
-
duration: 0.4,
|
|
165
|
-
ease: [0.4, 0, 0.2, 1],
|
|
166
|
-
}}
|
|
167
|
-
>
|
|
168
|
-
{activeContent.node}
|
|
169
|
-
</motion.div>
|
|
170
|
-
</AnimatePresence>
|
|
171
|
-
</DraggablePanel>
|
|
47
|
+
<NavPanelDraggable activeContent={activeContent} />
|
|
172
48
|
<div
|
|
173
49
|
id={NAV_PANEL_RIGHT_DRAWER_ID}
|
|
174
50
|
style={{
|
|
@@ -1,4 +1,28 @@
|
|
|
1
1
|
export default {
|
|
2
|
+
'navigation.chat': 'Chat',
|
|
3
|
+
'navigation.discover': 'Discover',
|
|
4
|
+
'navigation.discoverAssistants': 'Discover Assistants',
|
|
5
|
+
'navigation.discoverMcp': 'Discover MCP',
|
|
6
|
+
'navigation.discoverModels': 'Discover Models',
|
|
7
|
+
'navigation.discoverProviders': 'Discover Providers',
|
|
8
|
+
'navigation.group': 'Group',
|
|
9
|
+
'navigation.groupChat': 'Group Chat',
|
|
10
|
+
'navigation.home': 'Home',
|
|
11
|
+
'navigation.image': 'Image',
|
|
12
|
+
'navigation.knowledgeBase': 'Knowledge Base',
|
|
13
|
+
'navigation.lobehub': 'LobeHub',
|
|
14
|
+
'navigation.memory': 'Memory',
|
|
15
|
+
'navigation.memoryContexts': 'Memory - Contexts',
|
|
16
|
+
'navigation.memoryExperiences': 'Memory - Experiences',
|
|
17
|
+
'navigation.memoryIdentities': 'Memory - Identities',
|
|
18
|
+
'navigation.memoryPreferences': 'Memory - Preferences',
|
|
19
|
+
'navigation.onboarding': 'Onboarding',
|
|
20
|
+
'navigation.page': 'Page',
|
|
21
|
+
'navigation.pages': 'Pages',
|
|
22
|
+
'navigation.provider': 'Provider',
|
|
23
|
+
'navigation.recentView': 'Recent pages',
|
|
24
|
+
'navigation.resources': 'Resources',
|
|
25
|
+
'navigation.settings': 'Settings',
|
|
2
26
|
'notification.finishChatGeneration': 'AI message generation completed',
|
|
3
27
|
'proxy.auth': 'Authentication Required',
|
|
4
28
|
'proxy.authDesc': 'If the proxy server requires a username and password',
|
|
@@ -132,10 +132,9 @@ export default {
|
|
|
132
132
|
'Your {{plan}} computing credits have been exhausted. Upgrade now to get more credits.',
|
|
133
133
|
'limitation.limited.descUltimate':
|
|
134
134
|
'Your {{plan}} computing credits have been exhausted. Please top up credits to continue.',
|
|
135
|
-
'limitation.limited.referralTip':
|
|
136
|
-
'Invite new users to register, and you and your friend will each receive {{reward}}M credits',
|
|
135
|
+
'limitation.limited.referralTip': 'Invite friends, both get {{reward}}M',
|
|
137
136
|
'limitation.limited.title': 'Computing Credits Exhausted',
|
|
138
|
-
'limitation.limited.topup': 'Top
|
|
137
|
+
'limitation.limited.topup': 'Top-Up Credits',
|
|
139
138
|
'limitation.limited.upgrade': 'Upgrade to Higher Plan',
|
|
140
139
|
'limitation.providers.lock.addNew': 'Subscribe now to create custom AI providers',
|
|
141
140
|
'limitation.providers.lock.enableProvider': 'Subscribe now to enable this AI provider',
|