feat: add redux-persist
This commit is contained in:
parent
3e055b0822
commit
f866387862
3
.vscode/settings.json
vendored
3
.vscode/settings.json
vendored
@ -1,8 +1,7 @@
|
||||
{
|
||||
"editor.formatOnSave": true,
|
||||
"editor.codeActionsOnSave": {
|
||||
"source.fixAll.eslint": "explicit",
|
||||
"source.organizeImports": "explicit"
|
||||
"source.fixAll.eslint": "explicit"
|
||||
},
|
||||
"search.exclude": {
|
||||
"**/dist/**": true
|
||||
|
||||
@ -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 () {
|
||||
|
||||
@ -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>
|
||||
)
|
||||
}
|
||||
|
||||
@ -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
|
||||
}
|
||||
}
|
||||
17
src/renderer/src/hooks/useThreads.ts
Normal file
17
src/renderer/src/hooks/useThreads.ts
Normal 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))
|
||||
}
|
||||
}
|
||||
@ -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>
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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);
|
||||
`
|
||||
@ -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({
|
||||
const store = configureStore({
|
||||
reducer: persistReducer(
|
||||
{
|
||||
key: 'cherry-ai',
|
||||
storage,
|
||||
version: 1
|
||||
},
|
||||
combineReducers({
|
||||
threads
|
||||
})
|
||||
|
||||
const store = configureStore({
|
||||
reducer: rootReducer
|
||||
),
|
||||
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
|
||||
|
||||
@ -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
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user