mirror of
https://github.com/CherryHQ/cherry-studio.git
synced 2025-12-30 15:59:09 +08:00
refactor(App): replace TabsContainer with AppLayout for improved layout structure; integrate ChatProvider for chat context management; update routing logic in NavigationHandler to utilize SettingsPopup; adjust main height variable in styles for consistency; enhance FilesPage layout by repositioning SideNav; implement new SettingsPopup component for settings management.
This commit is contained in:
parent
bedea8aaaa
commit
db1c03f9fa
@ -5,7 +5,7 @@ import { Provider } from 'react-redux'
|
||||
import { HashRouter } from 'react-router-dom'
|
||||
import { PersistGate } from 'redux-persist/integration/react'
|
||||
|
||||
import TabsContainer from './components/Tabs/TabsContainer'
|
||||
import AppLayout from './components/Layout/AppLayout'
|
||||
import TopViewContainer from './components/TopView'
|
||||
import AntdProvider from './context/AntdProvider'
|
||||
import { CodeStyleProvider } from './context/CodeStyleProvider'
|
||||
@ -13,6 +13,7 @@ import { NotificationProvider } from './context/NotificationProvider'
|
||||
import StyleSheetManager from './context/StyleSheetManager'
|
||||
import { ThemeProvider } from './context/ThemeProvider'
|
||||
import NavigationHandler from './handler/NavigationHandler'
|
||||
import { ChatProvider } from './hooks/useChat'
|
||||
import Routes from './Routes'
|
||||
|
||||
function App(): React.ReactElement {
|
||||
@ -24,14 +25,16 @@ function App(): React.ReactElement {
|
||||
<NotificationProvider>
|
||||
<CodeStyleProvider>
|
||||
<PersistGate loading={null} persistor={persistor}>
|
||||
<TopViewContainer>
|
||||
<HashRouter>
|
||||
<HashRouter>
|
||||
<TopViewContainer>
|
||||
<NavigationHandler />
|
||||
<TabsContainer>
|
||||
<Routes />
|
||||
</TabsContainer>
|
||||
</HashRouter>
|
||||
</TopViewContainer>
|
||||
<ChatProvider>
|
||||
<AppLayout>
|
||||
<Routes />
|
||||
</AppLayout>
|
||||
</ChatProvider>
|
||||
</TopViewContainer>
|
||||
</HashRouter>
|
||||
</PersistGate>
|
||||
</CodeStyleProvider>
|
||||
</NotificationProvider>
|
||||
|
||||
@ -5,10 +5,8 @@ import AppsPage from './pages/apps/AppsPage'
|
||||
import FilesPage from './pages/files/FilesPage'
|
||||
import HomePage from './pages/home/HomePage'
|
||||
import KnowledgePage from './pages/knowledge/KnowledgePage'
|
||||
import LaunchpadPage from './pages/launchpad/LaunchpadPage'
|
||||
import McpServersPage from './pages/mcp-servers'
|
||||
import PaintingsRoutePage from './pages/paintings/PaintingsRoutePage'
|
||||
import SettingsPage from './pages/settings/SettingsPage'
|
||||
import TranslatePage from './pages/translate/TranslatePage'
|
||||
|
||||
const RouteContainer = () => {
|
||||
@ -22,8 +20,8 @@ const RouteContainer = () => {
|
||||
<Route path="/knowledge" element={<KnowledgePage />} />
|
||||
<Route path="/apps" element={<AppsPage />} />
|
||||
<Route path="/mcp-servers/*" element={<McpServersPage />} />
|
||||
<Route path="/settings/*" element={<SettingsPage />} />
|
||||
<Route path="/launchpad" element={<LaunchpadPage />} />
|
||||
{/* <Route path="/settings/*" element={<SettingsPage />} />
|
||||
<Route path="/launchpad" element={<LaunchpadPage />} /> */}
|
||||
</Routes>
|
||||
)
|
||||
}
|
||||
|
||||
@ -12,7 +12,7 @@
|
||||
--list-item-border-radius: 15px;
|
||||
--border-width: 0.5px;
|
||||
|
||||
--main-height: calc(100vh - var(--navbar-height) - 6px);
|
||||
--main-height: 100vh;
|
||||
|
||||
--border-width: 0.5px;
|
||||
}
|
||||
|
||||
27
src/renderer/src/components/Layout/AppLayout.tsx
Normal file
27
src/renderer/src/components/Layout/AppLayout.tsx
Normal file
@ -0,0 +1,27 @@
|
||||
import { HStack } from '@renderer/components/Layout'
|
||||
import MainSidebar from '@renderer/pages/home/MainSidebar/MainSidebar'
|
||||
import { FC } from 'react'
|
||||
import styled from 'styled-components'
|
||||
|
||||
interface AppLayoutProps {
|
||||
children: React.ReactNode
|
||||
}
|
||||
|
||||
const AppLayout: FC<AppLayoutProps> = ({ children }) => {
|
||||
return (
|
||||
<HStack style={{ display: 'flex', flex: 1 }} id="app-layout">
|
||||
<MainSidebar />
|
||||
<ContentArea>{children}</ContentArea>
|
||||
</HStack>
|
||||
)
|
||||
}
|
||||
|
||||
const ContentArea = styled.div`
|
||||
min-width: 0;
|
||||
display: flex;
|
||||
flex: 1;
|
||||
flex-direction: column;
|
||||
height: 100vh;
|
||||
`
|
||||
|
||||
export default AppLayout
|
||||
230
src/renderer/src/components/Popups/SettingsPopup.tsx
Normal file
230
src/renderer/src/components/Popups/SettingsPopup.tsx
Normal file
@ -0,0 +1,230 @@
|
||||
import AboutSettings from '@renderer/pages/settings/AboutSettings'
|
||||
import DataSettings from '@renderer/pages/settings/DataSettings/DataSettings'
|
||||
import DisplaySettings from '@renderer/pages/settings/DisplaySettings/DisplaySettings'
|
||||
import GeneralSettings from '@renderer/pages/settings/GeneralSettings'
|
||||
import ModelSettings from '@renderer/pages/settings/ModelSettings/ModelSettings'
|
||||
import ProvidersList from '@renderer/pages/settings/ProviderSettings'
|
||||
import QuickAssistantSettings from '@renderer/pages/settings/QuickAssistantSettings'
|
||||
import QuickPhraseSettings from '@renderer/pages/settings/QuickPhraseSettings'
|
||||
import SelectionAssistantSettings from '@renderer/pages/settings/SelectionAssistantSettings/SelectionAssistantSettings'
|
||||
import ShortcutSettings from '@renderer/pages/settings/ShortcutSettings'
|
||||
import WebSearchSettings from '@renderer/pages/settings/WebSearchSettings'
|
||||
import { Modal, Spin } from 'antd'
|
||||
import {
|
||||
Cloud,
|
||||
Command,
|
||||
Globe,
|
||||
HardDrive,
|
||||
Info,
|
||||
MonitorCog,
|
||||
Package,
|
||||
Rocket,
|
||||
Settings2,
|
||||
TextCursorInput,
|
||||
Zap
|
||||
} from 'lucide-react'
|
||||
import React, { Suspense, useState } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import styled from 'styled-components'
|
||||
|
||||
import { TopView } from '../TopView'
|
||||
|
||||
type SettingsTab =
|
||||
| 'provider'
|
||||
| 'model'
|
||||
| 'web-search'
|
||||
| 'general'
|
||||
| 'display'
|
||||
| 'shortcut'
|
||||
| 'quickAssistant'
|
||||
| 'selectionAssistant'
|
||||
| 'data'
|
||||
| 'about'
|
||||
| 'quickPhrase'
|
||||
|
||||
interface SettingsPopupShowParams {
|
||||
defaultTab?: SettingsTab
|
||||
}
|
||||
|
||||
interface Props extends SettingsPopupShowParams {
|
||||
resolve?: (value: any) => void
|
||||
}
|
||||
|
||||
const SettingsPopupContainer: React.FC<Props> = ({ defaultTab = 'provider', resolve }) => {
|
||||
const { t } = useTranslation()
|
||||
const [activeTab, setActiveTab] = useState<SettingsTab>(defaultTab)
|
||||
const [open, setOpen] = useState(true)
|
||||
|
||||
const menuItems = [
|
||||
{ key: 'provider', icon: <Cloud size={18} />, label: t('settings.provider.title') },
|
||||
{ key: 'model', icon: <Package size={18} />, label: t('settings.model') },
|
||||
{ key: 'web-search', icon: <Globe size={18} />, label: t('settings.websearch.title') },
|
||||
{ key: 'general', icon: <Settings2 size={18} />, label: t('settings.general') },
|
||||
{ key: 'display', icon: <MonitorCog size={18} />, label: t('settings.display.title') },
|
||||
{ key: 'shortcut', icon: <Command size={18} />, label: t('settings.shortcuts.title') },
|
||||
{ key: 'quickAssistant', icon: <Rocket size={18} />, label: t('settings.quickAssistant.title') },
|
||||
{ key: 'selectionAssistant', icon: <TextCursorInput size={18} />, label: t('selection.name') },
|
||||
{ key: 'quickPhrase', icon: <Zap size={18} />, label: t('settings.quickPhrase.title') },
|
||||
{ key: 'data', icon: <HardDrive size={18} />, label: t('settings.data.title') },
|
||||
{ key: 'about', icon: <Info size={18} />, label: t('settings.about') }
|
||||
] as const
|
||||
|
||||
const renderContent = () => {
|
||||
switch (activeTab) {
|
||||
case 'provider':
|
||||
return (
|
||||
<Suspense fallback={<Spin />}>
|
||||
<ProvidersList />
|
||||
</Suspense>
|
||||
)
|
||||
case 'model':
|
||||
return <ModelSettings />
|
||||
case 'web-search':
|
||||
return <WebSearchSettings />
|
||||
case 'general':
|
||||
return <GeneralSettings />
|
||||
case 'display':
|
||||
return <DisplaySettings />
|
||||
case 'shortcut':
|
||||
return <ShortcutSettings />
|
||||
case 'quickAssistant':
|
||||
return <QuickAssistantSettings />
|
||||
case 'selectionAssistant':
|
||||
return <SelectionAssistantSettings />
|
||||
case 'data':
|
||||
return <DataSettings />
|
||||
case 'about':
|
||||
return <AboutSettings />
|
||||
case 'quickPhrase':
|
||||
return <QuickPhraseSettings />
|
||||
default:
|
||||
return <ProvidersList />
|
||||
}
|
||||
}
|
||||
|
||||
const onCancel = () => {
|
||||
setOpen(false)
|
||||
}
|
||||
|
||||
const onAfterClose = () => {
|
||||
resolve && resolve(null)
|
||||
TopView.hide(TopViewKey)
|
||||
}
|
||||
|
||||
// 设置全局隐藏方法
|
||||
SettingsPopup.hide = onCancel
|
||||
|
||||
return (
|
||||
<StyledModal
|
||||
title={t('settings.title')}
|
||||
open={open}
|
||||
onCancel={onCancel}
|
||||
afterClose={onAfterClose}
|
||||
footer={null}
|
||||
width={1000}
|
||||
centered
|
||||
destroyOnClose>
|
||||
<ContentContainer>
|
||||
<SettingMenus>
|
||||
{menuItems.map((item) => (
|
||||
<MenuItem
|
||||
key={item.key}
|
||||
className={activeTab === item.key ? 'active' : ''}
|
||||
onClick={() => setActiveTab(item.key as SettingsTab)}>
|
||||
{item.icon}
|
||||
{item.label}
|
||||
</MenuItem>
|
||||
))}
|
||||
</SettingMenus>
|
||||
<SettingContent>{renderContent()}</SettingContent>
|
||||
</ContentContainer>
|
||||
</StyledModal>
|
||||
)
|
||||
}
|
||||
|
||||
const TopViewKey = 'SettingsPopup'
|
||||
|
||||
export default class SettingsPopup {
|
||||
static hide() {
|
||||
TopView.hide(TopViewKey)
|
||||
}
|
||||
|
||||
static show(props: SettingsPopupShowParams = {}) {
|
||||
return new Promise<any>((resolve) => {
|
||||
TopView.show(<SettingsPopupContainer {...props} resolve={resolve} />, TopViewKey)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
const StyledModal = styled(Modal)`
|
||||
.ant-modal-content {
|
||||
height: 80vh;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.ant-modal-body {
|
||||
flex: 1;
|
||||
padding: 0;
|
||||
overflow: hidden;
|
||||
}
|
||||
`
|
||||
|
||||
const ContentContainer = styled.div`
|
||||
display: flex;
|
||||
flex: 1;
|
||||
height: 100%;
|
||||
`
|
||||
|
||||
const SettingMenus = styled.div`
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
min-width: var(--settings-width);
|
||||
border-right: 0.5px solid var(--color-border);
|
||||
padding: 10px;
|
||||
user-select: none;
|
||||
background: var(--color-background);
|
||||
`
|
||||
|
||||
const MenuItem = styled.div`
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
padding: 6px 10px;
|
||||
width: 100%;
|
||||
cursor: pointer;
|
||||
border-radius: var(--list-item-border-radius);
|
||||
font-weight: 500;
|
||||
transition: all 0.2s ease-in-out;
|
||||
border: 0.5px solid transparent;
|
||||
margin-bottom: 5px;
|
||||
|
||||
.anticon {
|
||||
font-size: 16px;
|
||||
opacity: 0.8;
|
||||
}
|
||||
|
||||
.iconfont {
|
||||
font-size: 18px;
|
||||
line-height: 18px;
|
||||
opacity: 0.7;
|
||||
margin-left: -1px;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
background: var(--color-background-soft);
|
||||
}
|
||||
|
||||
&.active {
|
||||
background: var(--color-background-soft);
|
||||
border: 0.5px solid var(--color-border);
|
||||
}
|
||||
`
|
||||
|
||||
const SettingContent = styled.div`
|
||||
display: flex;
|
||||
height: 100%;
|
||||
flex: 1;
|
||||
overflow: auto;
|
||||
`
|
||||
@ -1,15 +1,12 @@
|
||||
import SettingsPopup from '@renderer/components/Popups/SettingsPopup'
|
||||
import NavigationService from '@renderer/services/NavigationService'
|
||||
import { useAppDispatch, useAppSelector } from '@renderer/store'
|
||||
import { addTab, Tab } from '@renderer/store/tabs'
|
||||
import { useAppSelector } from '@renderer/store'
|
||||
import { useEffect } from 'react'
|
||||
import { useHotkeys } from 'react-hotkeys-hook'
|
||||
import { useLocation, useNavigate } from 'react-router-dom'
|
||||
import { useNavigate } from 'react-router-dom'
|
||||
|
||||
const NavigationHandler: React.FC = () => {
|
||||
const dispatch = useAppDispatch()
|
||||
const location = useLocation()
|
||||
const navigate = useNavigate()
|
||||
const tabs = useAppSelector((state) => state.tabs.tabs)
|
||||
|
||||
const showSettingsShortcutEnabled = useAppSelector(
|
||||
(state) => state.shortcuts.shortcuts.find((s) => s.key === 'show_settings')?.enabled
|
||||
@ -22,10 +19,7 @@ const NavigationHandler: React.FC = () => {
|
||||
useHotkeys(
|
||||
'meta+, ! ctrl+,',
|
||||
function () {
|
||||
if (location.pathname.startsWith('/settings')) {
|
||||
return
|
||||
}
|
||||
navigate('/settings/provider')
|
||||
SettingsPopup.show({ defaultTab: 'provider' })
|
||||
},
|
||||
{
|
||||
splitKey: '!',
|
||||
@ -35,20 +29,6 @@ const NavigationHandler: React.FC = () => {
|
||||
}
|
||||
)
|
||||
|
||||
// 初始化 home tab
|
||||
useEffect(() => {
|
||||
if (tabs.length === 0) {
|
||||
const homeTab: Tab = {
|
||||
id: 'home',
|
||||
titleKey: 'title.home',
|
||||
title: '',
|
||||
path: '/',
|
||||
iconType: 'home'
|
||||
}
|
||||
dispatch(addTab(homeTab))
|
||||
}
|
||||
}, [dispatch, tabs.length])
|
||||
|
||||
return null
|
||||
}
|
||||
|
||||
|
||||
@ -1,10 +1,9 @@
|
||||
import { EVENT_NAMES, EventEmitter } from '@renderer/services/EventService'
|
||||
import { useAppDispatch, useAppSelector } from '@renderer/store'
|
||||
import { loadTopicMessagesThunk } from '@renderer/store/thunk/messageThunk'
|
||||
import { Assistant } from '@renderer/types'
|
||||
import { Topic } from '@renderer/types'
|
||||
import { use, useEffect, useMemo, useState } from 'react'
|
||||
import { createContext } from 'react'
|
||||
import { Assistant, Topic } from '@renderer/types'
|
||||
import { createContext, use, useCallback, useEffect, useMemo, useState } from 'react'
|
||||
import { useLocation, useNavigate } from 'react-router-dom'
|
||||
|
||||
import { useTopicsForAssistant } from './useAssistant'
|
||||
import { useSettings } from './useSettings'
|
||||
@ -20,14 +19,25 @@ const ChatContext = createContext<ChatContextType | null>(null)
|
||||
|
||||
export const ChatProvider = ({ children }) => {
|
||||
const assistants = useAppSelector((state) => state.assistants.assistants)
|
||||
const [activeAssistant, setActiveAssistant] = useState<Assistant>(assistants[0])
|
||||
const [activeAssistant, setActiveAssistantBase] = useState<Assistant>(assistants[0])
|
||||
const topics = useTopicsForAssistant(activeAssistant.id)
|
||||
const [activeTopic, setActiveTopic] = useState<Topic>(topics[0])
|
||||
const { clickAssistantToShowTopic } = useSettings()
|
||||
const dispatch = useAppDispatch()
|
||||
const navigate = useNavigate()
|
||||
const location = useLocation()
|
||||
|
||||
console.log('activeAssistant', activeAssistant)
|
||||
console.log('activeTopic', activeTopic)
|
||||
// 包装setActiveAssistant以添加导航逻辑
|
||||
const setActiveAssistant = useCallback(
|
||||
(assistant: Assistant) => {
|
||||
setActiveAssistantBase(assistant)
|
||||
// 如果当前不在聊天页面,导航到聊天页面
|
||||
if (location.pathname !== '/') {
|
||||
navigate('/')
|
||||
}
|
||||
},
|
||||
[setActiveAssistantBase, location.pathname, navigate]
|
||||
)
|
||||
|
||||
// 当 topics 变化时,如果当前 activeTopic 不在 topics 中,设置第一个 topic
|
||||
useEffect(() => {
|
||||
@ -58,7 +68,7 @@ export const ChatProvider = ({ children }) => {
|
||||
EventEmitter.on(EVENT_NAMES.SET_TOPIC, setActiveTopic)
|
||||
]
|
||||
return () => subscriptions.forEach((subscription) => subscription())
|
||||
}, [])
|
||||
}, [setActiveAssistant])
|
||||
|
||||
const value = useMemo(
|
||||
() => ({
|
||||
@ -67,7 +77,7 @@ export const ChatProvider = ({ children }) => {
|
||||
setActiveAssistant,
|
||||
setActiveTopic
|
||||
}),
|
||||
[activeAssistant, activeTopic]
|
||||
[activeAssistant, activeTopic, setActiveAssistant]
|
||||
)
|
||||
|
||||
return <ChatContext value={value}>{children}</ChatContext>
|
||||
|
||||
@ -134,17 +134,6 @@ const FilesPage: FC = () => {
|
||||
<NavbarCenter style={{ borderRight: 'none' }}>{t('files.title')}</NavbarCenter>
|
||||
</NavbarMain>
|
||||
<ContentContainer id="content-container">
|
||||
<SideNav>
|
||||
{menuItems.map((item) => (
|
||||
<ListItem
|
||||
key={item.key}
|
||||
icon={item.icon}
|
||||
title={item.label}
|
||||
active={fileType === item.key}
|
||||
onClick={() => setFileType(item.key as FileTypes)}
|
||||
/>
|
||||
))}
|
||||
</SideNav>
|
||||
<MainContent>
|
||||
<SortContainer>
|
||||
<Flex gap={8} align="center">
|
||||
@ -207,6 +196,17 @@ const FilesPage: FC = () => {
|
||||
<Empty image={Empty.PRESENTED_IMAGE_SIMPLE} />
|
||||
)}
|
||||
</MainContent>
|
||||
<SideNav>
|
||||
{menuItems.map((item) => (
|
||||
<ListItem
|
||||
key={item.key}
|
||||
icon={item.icon}
|
||||
title={item.label}
|
||||
active={fileType === item.key}
|
||||
onClick={() => setFileType(item.key as FileTypes)}
|
||||
/>
|
||||
))}
|
||||
</SideNav>
|
||||
</ContentContainer>
|
||||
</Container>
|
||||
)
|
||||
|
||||
@ -122,7 +122,7 @@ const Chat: FC = () => {
|
||||
}
|
||||
|
||||
const Main = styled(Flex)`
|
||||
height: calc(100vh - var(--navbar-height) - 50px);
|
||||
height: calc(100vh - var(--navbar-height));
|
||||
transform: translateZ(0);
|
||||
position: relative;
|
||||
`
|
||||
|
||||
@ -1,12 +1,9 @@
|
||||
import { HStack } from '@renderer/components/Layout'
|
||||
import { ChatProvider } from '@renderer/hooks/useChat'
|
||||
import { useSettings } from '@renderer/hooks/useSettings'
|
||||
import { FC, useEffect } from 'react'
|
||||
import styled from 'styled-components'
|
||||
|
||||
import Chat from './Chat'
|
||||
import ChatNavbar from './ChatNavbar'
|
||||
import MainSidebar from './MainSidebar/MainSidebar'
|
||||
|
||||
const HomePage: FC<{ style?: React.CSSProperties }> = ({ style }) => {
|
||||
const { showAssistants, showTopics, topicPosition } = useSettings()
|
||||
@ -20,17 +17,12 @@ const HomePage: FC<{ style?: React.CSSProperties }> = ({ style }) => {
|
||||
}, [showAssistants, showTopics, topicPosition])
|
||||
|
||||
return (
|
||||
<ChatProvider>
|
||||
<HStack style={{ display: 'flex', flex: 1 }} id="home-page">
|
||||
<MainSidebar />
|
||||
<Container style={style}>
|
||||
<ChatNavbar />
|
||||
<ContentContainer id="content-container">
|
||||
<Chat />
|
||||
</ContentContainer>
|
||||
</Container>
|
||||
</HStack>
|
||||
</ChatProvider>
|
||||
<Container style={style}>
|
||||
<ChatNavbar />
|
||||
<ContentContainer id="content-container">
|
||||
<Chat />
|
||||
</ContentContainer>
|
||||
</Container>
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@ -56,6 +56,7 @@ export const Container = styled.div<{ transparent?: boolean }>`
|
||||
min-height: var(--main-height);
|
||||
background: var(--color-background);
|
||||
padding-top: 10px;
|
||||
margin-top: 50px;
|
||||
`
|
||||
|
||||
export const MainMenu = styled.div`
|
||||
|
||||
@ -96,6 +96,13 @@ const KnowledgePage: FC = () => {
|
||||
<NavbarCenter style={{ borderRight: 'none' }}>{t('knowledge.title')}</NavbarCenter>
|
||||
</NavbarMain>
|
||||
<ContentContainer id="content-container">
|
||||
{bases.length === 0 ? (
|
||||
<MainContent>
|
||||
<Empty description={t('knowledge.empty')} image={Empty.PRESENTED_IMAGE_SIMPLE} />
|
||||
</MainContent>
|
||||
) : selectedBase ? (
|
||||
<KnowledgeContent selectedBase={selectedBase} />
|
||||
) : null}
|
||||
<SideNav>
|
||||
<ScrollContainer>
|
||||
<DragableList
|
||||
@ -128,13 +135,6 @@ const KnowledgePage: FC = () => {
|
||||
<div style={{ minHeight: '10px' }}></div>
|
||||
</ScrollContainer>
|
||||
</SideNav>
|
||||
{bases.length === 0 ? (
|
||||
<MainContent>
|
||||
<Empty description={t('knowledge.empty')} image={Empty.PRESENTED_IMAGE_SIMPLE} />
|
||||
</MainContent>
|
||||
) : selectedBase ? (
|
||||
<KnowledgeContent selectedBase={selectedBase} />
|
||||
) : null}
|
||||
</ContentContainer>
|
||||
</Container>
|
||||
)
|
||||
|
||||
Loading…
Reference in New Issue
Block a user