feat: add redux-persist

This commit is contained in:
kangfenmao 2024-05-31 15:53:55 +08:00
parent 3e055b0822
commit f866387862
10 changed files with 102 additions and 119 deletions

View File

@ -1,8 +1,7 @@
{ {
"editor.formatOnSave": true, "editor.formatOnSave": true,
"editor.codeActionsOnSave": { "editor.codeActionsOnSave": {
"source.fixAll.eslint": "explicit", "source.fixAll.eslint": "explicit"
"source.organizeImports": "explicit"
}, },
"search.exclude": { "search.exclude": {
"**/dist/**": true "**/dist/**": true

View File

@ -1,8 +1,7 @@
import { app, BrowserWindow, ipcMain, shell } from 'electron'
import { electronApp, is, optimizer } from '@electron-toolkit/utils' import { electronApp, is, optimizer } from '@electron-toolkit/utils'
import { app, BrowserWindow, ipcMain, shell } from 'electron'
import windowStateKeeper from 'electron-window-state' import windowStateKeeper from 'electron-window-state'
import { join } from 'path' import { join } from 'path'
import { initStore } from './store'
import icon from '../../resources/icon.png?asset' import icon from '../../resources/icon.png?asset'
function createWindow(): void { function createWindow(): void {
@ -68,8 +67,6 @@ app.whenReady().then(() => {
// IPC test // IPC test
ipcMain.on('ping', () => console.log('pong')) ipcMain.on('ping', () => console.log('pong'))
initStore()
createWindow() createWindow()
app.on('activate', function () { app.on('activate', function () {

View File

@ -1,7 +1,8 @@
import '@fontsource/inter' import '@fontsource/inter'
import store from '@renderer/store' import store, { persistor } from '@renderer/store'
import { Provider } from 'react-redux' import { Provider } from 'react-redux'
import { BrowserRouter, Route, Routes } from 'react-router-dom' import { BrowserRouter, Route, Routes } from 'react-router-dom'
import { PersistGate } from 'redux-persist/integration/react'
import Sidebar from './components/app/Sidebar' import Sidebar from './components/app/Sidebar'
import Statusbar from './components/app/Statusbar' import Statusbar from './components/app/Statusbar'
import AppsPage from './pages/apps/AppsPage' import AppsPage from './pages/apps/AppsPage'
@ -11,6 +12,7 @@ import SettingsPage from './pages/settings/SettingsPage'
function App(): JSX.Element { function App(): JSX.Element {
return ( return (
<Provider store={store}> <Provider store={store}>
<PersistGate loading={null} persistor={persistor}>
<BrowserRouter> <BrowserRouter>
<Sidebar /> <Sidebar />
<Routes> <Routes>
@ -20,6 +22,7 @@ function App(): JSX.Element {
</Routes> </Routes>
<Statusbar /> <Statusbar />
</BrowserRouter> </BrowserRouter>
</PersistGate>
</Provider> </Provider>
) )
} }

View File

@ -1,51 +0,0 @@
import { runAsyncFunction } from '@renderer/utils'
import localforage from 'localforage'
import { useEffect, useState } from 'react'
export type Conversation = {
id: string
name: string
avatar: string
lastMessage: string
lastMessageAt: string
}
export default function useConversations() {
const [conversations, setConversations] = useState<Conversation[]>([])
const [activeConversation, setActiveConversation] = useState<Conversation>()
// Use localforage to initialize conversations
useEffect(() => {
runAsyncFunction(async () => {
const conversations = await localforage.getItem<Conversation[]>('conversations')
conversations && setConversations(conversations)
})
}, [])
// Update localforage
useEffect(() => {
localforage.setItem('conversations', conversations)
}, [conversations])
const addConversation = (conversation) => {
setConversations([...conversations, conversation])
}
const removeConversation = (conversationId) => {
setConversations(conversations.filter((c) => c.id !== conversationId))
}
const updateConversation = (conversation) => {
setConversations(conversations.map((c) => (c.id === conversation.id ? conversation : c)))
}
return {
conversations,
activeConversation,
setConversations,
addConversation,
removeConversation,
updateConversation,
setActiveConversation
}
}

View File

@ -0,0 +1,17 @@
import { useAppDispatch, useAppSelector } from '@renderer/store'
import { addThread, removeThread, setActiveThread, updateThread } from '@renderer/store/threads'
import { Thread } from '@renderer/types'
export default function useThreads() {
const { threads, activeThread } = useAppSelector((state) => state.threads)
const dispatch = useAppDispatch()
return {
threads,
activeThread,
setActiveThread: (thread: Thread) => dispatch(setActiveThread(thread)),
addThread: (thread: Thread) => dispatch(addThread(thread)),
removeThread: (id: string) => dispatch(removeThread({ id })),
updateThread: (thread: Thread) => dispatch(updateThread(thread))
}
}

View File

@ -1,21 +1,21 @@
import { Navbar, NavbarCenter, NavbarLeft, NavbarRight } from '@renderer/components/app/Navbar' import { Navbar, NavbarCenter, NavbarLeft, NavbarRight } from '@renderer/components/app/Navbar'
import useConversations from '@renderer/hooks/useConversactions' import useThreads from '@renderer/hooks/useThreads'
import { FC, useEffect } from 'react' import { FC, useEffect } from 'react'
import styled from 'styled-components' import styled from 'styled-components'
import Conversations from './components/Conversations'
import Chat from './components/Chat' import Chat from './components/Chat'
import Threads from './components/Threads'
const HomePage: FC = () => { const HomePage: FC = () => {
const { conversations, activeConversation, setActiveConversation, addConversation } = useConversations() const { threads, activeThread, setActiveThread, addThread } = useThreads()
useEffect(() => { useEffect(() => {
if (!activeConversation) { if (!activeThread) {
setActiveConversation(conversations[0]) setActiveThread(threads[0])
} }
}, [activeConversation, conversations]) }, [activeThread, threads])
const onCreateConversation = () => { const onCreateConversation = () => {
const _conversation = { const _thread = {
// ID auto increment // ID auto increment
id: Math.random().toString(), id: Math.random().toString(),
name: 'New conversation', name: 'New conversation',
@ -24,8 +24,8 @@ const HomePage: FC = () => {
lastMessage: 'message', lastMessage: 'message',
lastMessageAt: 'now' lastMessageAt: 'now'
} }
addConversation(_conversation) addThread(_thread)
setActiveConversation(_conversation) setActiveThread(_thread)
} }
return ( return (
@ -40,12 +40,8 @@ const HomePage: FC = () => {
<NavbarRight /> <NavbarRight />
</Navbar> </Navbar>
<ContentContainer> <ContentContainer>
<Conversations <Threads threads={threads} activeThread={activeThread} onSelectThread={setActiveThread} />
conversations={conversations} <Chat activeThread={activeThread} />
activeConversation={activeConversation}
onSelectConversation={setActiveConversation}
/>
<Chat activeConversation={activeConversation} />
<Settings /> <Settings />
</ContentContainer> </ContentContainer>
</Container> </Container>

View File

@ -1,13 +1,13 @@
import { Conversation } from '@renderer/hooks/useConversactions' import { Thread } from '@renderer/types'
import { FC } from 'react' import { FC } from 'react'
import styled from 'styled-components' import styled from 'styled-components'
interface Props { interface Props {
activeConversation?: Conversation activeThread?: Thread
} }
const Chat: FC<Props> = ({ activeConversation }) => { const Chat: FC<Props> = ({ activeThread }) => {
return <Container>{activeConversation?.lastMessage}</Container> return <Container>{activeThread?.lastMessage}</Container>
} }
const Container = styled.div` const Container = styled.div`
@ -15,6 +15,7 @@ const Container = styled.div`
height: 100%; height: 100%;
flex: 1; flex: 1;
border-right: 1px solid #ffffff20; border-right: 1px solid #ffffff20;
padding: 15px;
` `
export default Chat export default Chat

View File

@ -1,25 +1,25 @@
import type { Conversation } from '@renderer/hooks/useConversactions' import type { Thread } from '@renderer/types'
import { FC } from 'react' import { FC } from 'react'
import styled from 'styled-components' import styled from 'styled-components'
interface Props { interface Props {
conversations: Conversation[] threads: Thread[]
activeConversation?: Conversation activeThread?: Thread
onSelectConversation: (conversation: Conversation) => void onSelectThread: (conversation: Thread) => void
} }
const Conversations: FC<Props> = ({ conversations, activeConversation, onSelectConversation }) => { const Conversations: FC<Props> = ({ threads, activeThread, onSelectThread }) => {
return ( return (
<Container> <Container>
{conversations.map((conversation) => ( {threads.map((thread) => (
<Conversation <ThreadItem
key={conversation.id} key={thread.id}
onClick={() => onSelectConversation(conversation)} onClick={() => onSelectThread(thread)}
className={conversation.id === activeConversation?.id ? 'active' : ''}> className={thread.id === activeThread?.id ? 'active' : ''}>
<ConversationTime>{conversation.lastMessageAt}</ConversationTime> <ThreadTime>{thread.lastMessageAt}</ThreadTime>
<ConversationName>{conversation.name}</ConversationName> <ThreadName>{thread.name}</ThreadName>
<ConversationLastMessage>{conversation.lastMessage}</ConversationLastMessage> <ThreadLastMessage>{thread.lastMessage}</ThreadLastMessage>
</Conversation> </ThreadItem>
))} ))}
</Container> </Container>
) )
@ -38,7 +38,7 @@ const Container = styled.div`
} }
` `
const Conversation = styled.div` const ThreadItem = styled.div`
display: flex; display: flex;
flex-direction: column; flex-direction: column;
padding: 10px; padding: 10px;
@ -54,18 +54,18 @@ const Conversation = styled.div`
margin-bottom: 10px; margin-bottom: 10px;
` `
const ConversationTime = styled.div` const ThreadTime = styled.div`
font-size: 12px; font-size: 12px;
color: var(--color-text-2); color: var(--color-text-2);
` `
const ConversationName = styled.div` const ThreadName = styled.div`
font-size: 14px; font-size: 14px;
color: var(--color-text-1); color: var(--color-text-1);
font-weight: bold; font-weight: bold;
` `
const ConversationLastMessage = styled.div` const ThreadLastMessage = styled.div`
font-size: 12px; font-size: 12px;
color: var(--color-text-2); color: var(--color-text-2);
` `

View File

@ -1,19 +1,35 @@
import { configureStore } from '@reduxjs/toolkit' import { combineReducers, configureStore } from '@reduxjs/toolkit'
import { useDispatch, useSelector, useStore } from 'react-redux'
import { combineReducers } from '@reduxjs/toolkit' import { FLUSH, PAUSE, PERSIST, persistReducer, persistStore, PURGE, REGISTER, REHYDRATE } from 'redux-persist'
import { useDispatch } from 'react-redux' import storage from 'redux-persist/lib/storage'
import threads from './threads' import threads from './threads'
const rootReducer = combineReducers({ const store = configureStore({
reducer: persistReducer(
{
key: 'cherry-ai',
storage,
version: 1
},
combineReducers({
threads threads
}) })
),
const store = configureStore({ middleware: (getDefaultMiddleware) =>
reducer: rootReducer getDefaultMiddleware({
serializableCheck: {
ignoredActions: [FLUSH, REHYDRATE, PAUSE, PERSIST, PURGE, REGISTER]
}
})
}) })
export type RootState = ReturnType<typeof rootReducer> export type RootState = ReturnType<typeof store.getState>
export type AppDispatch = typeof store.dispatch export type AppDispatch = typeof store.dispatch
export const persistor = persistStore(store)
export const useAppDispatch = useDispatch.withTypes<AppDispatch>() export const useAppDispatch = useDispatch.withTypes<AppDispatch>()
export const useAppSelector = useSelector.withTypes<RootState>()
export const useAppStore = useStore.withTypes<typeof store>()
// export const dispatch: AppDispatch = useDispatch()
export default store export default store

View File

@ -1,12 +1,14 @@
import { createSlice, PayloadAction } from '@reduxjs/toolkit' import { createSlice, PayloadAction } from '@reduxjs/toolkit'
import { Thread } from '@renderer/types' import { Thread } from '@renderer/types'
interface State { export interface ThreadsState {
threads: Thread[] threads: Thread[]
activeThread?: Thread
} }
const initialState: State = { const initialState: ThreadsState = {
threads: [] threads: [],
activeThread: undefined
} }
const threadsSlice = createSlice({ const threadsSlice = createSlice({
@ -21,10 +23,13 @@ const threadsSlice = createSlice({
}, },
updateThread: (state, action: PayloadAction<Thread>) => { updateThread: (state, action: PayloadAction<Thread>) => {
state.threads = state.threads.map((c) => (c.id === action.payload.id ? action.payload : c)) state.threads = state.threads.map((c) => (c.id === action.payload.id ? action.payload : c))
},
setActiveThread: (state, action: PayloadAction<Thread>) => {
state.activeThread = action.payload
} }
} }
}) })
export const { addThread, removeThread, updateThread } = threadsSlice.actions export const { addThread, removeThread, updateThread, setActiveThread } = threadsSlice.actions
export default threadsSlice export default threadsSlice.reducer