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.codeActionsOnSave": {
"source.fixAll.eslint": "explicit",
"source.organizeImports": "explicit"
"source.fixAll.eslint": "explicit"
},
"search.exclude": {
"**/dist/**": true

View File

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

View File

@ -1,7 +1,8 @@
import '@fontsource/inter'
import store from '@renderer/store'
import store, { persistor } from '@renderer/store'
import { Provider } from 'react-redux'
import { BrowserRouter, Route, Routes } from 'react-router-dom'
import { PersistGate } from 'redux-persist/integration/react'
import Sidebar from './components/app/Sidebar'
import Statusbar from './components/app/Statusbar'
import AppsPage from './pages/apps/AppsPage'
@ -11,6 +12,7 @@ import SettingsPage from './pages/settings/SettingsPage'
function App(): JSX.Element {
return (
<Provider store={store}>
<PersistGate loading={null} persistor={persistor}>
<BrowserRouter>
<Sidebar />
<Routes>
@ -20,6 +22,7 @@ function App(): JSX.Element {
</Routes>
<Statusbar />
</BrowserRouter>
</PersistGate>
</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 useConversations from '@renderer/hooks/useConversactions'
import useThreads from '@renderer/hooks/useThreads'
import { FC, useEffect } from 'react'
import styled from 'styled-components'
import Conversations from './components/Conversations'
import Chat from './components/Chat'
import Threads from './components/Threads'
const HomePage: FC = () => {
const { conversations, activeConversation, setActiveConversation, addConversation } = useConversations()
const { threads, activeThread, setActiveThread, addThread } = useThreads()
useEffect(() => {
if (!activeConversation) {
setActiveConversation(conversations[0])
if (!activeThread) {
setActiveThread(threads[0])
}
}, [activeConversation, conversations])
}, [activeThread, threads])
const onCreateConversation = () => {
const _conversation = {
const _thread = {
// ID auto increment
id: Math.random().toString(),
name: 'New conversation',
@ -24,8 +24,8 @@ const HomePage: FC = () => {
lastMessage: 'message',
lastMessageAt: 'now'
}
addConversation(_conversation)
setActiveConversation(_conversation)
addThread(_thread)
setActiveThread(_thread)
}
return (
@ -40,12 +40,8 @@ const HomePage: FC = () => {
<NavbarRight />
</Navbar>
<ContentContainer>
<Conversations
conversations={conversations}
activeConversation={activeConversation}
onSelectConversation={setActiveConversation}
/>
<Chat activeConversation={activeConversation} />
<Threads threads={threads} activeThread={activeThread} onSelectThread={setActiveThread} />
<Chat activeThread={activeThread} />
<Settings />
</ContentContainer>
</Container>

View File

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

View File

@ -1,19 +1,35 @@
import { configureStore } from '@reduxjs/toolkit'
import { combineReducers } from '@reduxjs/toolkit'
import { useDispatch } from 'react-redux'
import { combineReducers, configureStore } from '@reduxjs/toolkit'
import { useDispatch, useSelector, useStore } from 'react-redux'
import { FLUSH, PAUSE, PERSIST, persistReducer, persistStore, PURGE, REGISTER, REHYDRATE } from 'redux-persist'
import storage from 'redux-persist/lib/storage'
import threads from './threads'
const rootReducer = combineReducers({
threads
})
const store = configureStore({
reducer: rootReducer
reducer: persistReducer(
{
key: 'cherry-ai',
storage,
version: 1
},
combineReducers({
threads
})
),
middleware: (getDefaultMiddleware) =>
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 const persistor = persistStore(store)
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

View File

@ -1,12 +1,14 @@
import { createSlice, PayloadAction } from '@reduxjs/toolkit'
import { Thread } from '@renderer/types'
interface State {
export interface ThreadsState {
threads: Thread[]
activeThread?: Thread
}
const initialState: State = {
threads: []
const initialState: ThreadsState = {
threads: [],
activeThread: undefined
}
const threadsSlice = createSlice({
@ -21,10 +23,13 @@ const threadsSlice = createSlice({
},
updateThread: (state, action: PayloadAction<Thread>) => {
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