12 changed files with 648 additions and 3891 deletions
File diff suppressed because it is too large
@ -33,7 +33,7 @@ |
|||||
] |
] |
||||
}, |
}, |
||||
"homepage": "https://meshtastic.org", |
"homepage": "https://meshtastic.org", |
||||
"dependencies": { |
"ddbependencies": { |
||||
"@bufbuild/protobuf": "^2.2.3", |
"@bufbuild/protobuf": "^2.2.3", |
||||
"@meshtastic/core": "npm:@jsr/[email protected]", |
"@meshtastic/core": "npm:@jsr/[email protected]", |
||||
"@meshtastic/js": "npm:@jsr/[email protected]", |
"@meshtastic/js": "npm:@jsr/[email protected]", |
||||
@ -61,6 +61,7 @@ |
|||||
"clsx": "^2.1.1", |
"clsx": "^2.1.1", |
||||
"cmdk": "^1.0.4", |
"cmdk": "^1.0.4", |
||||
"crypto-random-string": "^5.0.0", |
"crypto-random-string": "^5.0.0", |
||||
|
"idb-keyval": "^6.2.1", |
||||
"immer": "^10.1.1", |
"immer": "^10.1.1", |
||||
"js-cookie": "^3.0.5", |
"js-cookie": "^3.0.5", |
||||
"lucide-react": "^0.477.0", |
"lucide-react": "^0.477.0", |
||||
@ -100,7 +101,7 @@ |
|||||
"tar": "^7.4.3", |
"tar": "^7.4.3", |
||||
"testing-library": "^0.0.2", |
"testing-library": "^0.0.2", |
||||
"typescript": "^5.8.2", |
"typescript": "^5.8.2", |
||||
"vite": "^6.2.0", |
"vite": "^6.2.3", |
||||
"vitest": "^3.0.7", |
"vitest": "^3.0.7", |
||||
"vite-plugin-pwa": "^0.21.1" |
"vite-plugin-pwa": "^0.21.1" |
||||
} |
} |
||||
|
|||||
@ -0,0 +1,14 @@ |
|||||
|
import { StateStorage } from "zustand/middleware"; |
||||
|
import { get, set, del } from "idb-keyval"; |
||||
|
|
||||
|
export const zustandIDBStorage: StateStorage = { |
||||
|
getItem: async (name: string): Promise<string | null> => { |
||||
|
return (await get(name)) || null; |
||||
|
}, |
||||
|
setItem: async (name: string, value: string): Promise<void> => { |
||||
|
await set(name, value); |
||||
|
}, |
||||
|
removeItem: async (name: string): Promise<void> => { |
||||
|
await del(name); |
||||
|
}, |
||||
|
}; |
||||
@ -0,0 +1,110 @@ |
|||||
|
import { create } from 'zustand'; |
||||
|
import { persist, createJSONStorage } from 'zustand/middleware'; |
||||
|
import { produce } from 'immer'; |
||||
|
import { Types } from '@meshtastic/core'; |
||||
|
import { zustandIDBStorage } from "@core/services/messaging/db.ts"; |
||||
|
|
||||
|
export interface MessageWithState { |
||||
|
id: number; |
||||
|
from: number; |
||||
|
to: number; |
||||
|
channel: number; |
||||
|
content: string; |
||||
|
state: 'ack' | 'waiting' | 'failed'; |
||||
|
type: 'direct' | 'broadcast'; |
||||
|
} |
||||
|
|
||||
|
type MessageType = 'direct' | 'broadcast'; |
||||
|
|
||||
|
export interface MessageStore { |
||||
|
messages: { |
||||
|
direct: Record<number, MessageWithState[]>; |
||||
|
broadcast: Record<number, MessageWithState[]>; |
||||
|
}; |
||||
|
|
||||
|
activeChat: number; |
||||
|
chatType: MessageType; |
||||
|
|
||||
|
setActiveChat: (chat: number) => void; |
||||
|
setChatType: (type: MessageType) => void; |
||||
|
addMessage: (message: MessageWithState) => void; |
||||
|
getMessages: (type: MessageType, key: number) => MessageWithState[]; |
||||
|
setMessageState: ( |
||||
|
type: MessageType, |
||||
|
key: number, |
||||
|
messageId: number, |
||||
|
newState: MessageWithState['state'] |
||||
|
) => void; |
||||
|
clearMessages: () => void; |
||||
|
} |
||||
|
|
||||
|
export const useMessageStore = create<MessageStore>()( |
||||
|
persist( |
||||
|
(set, get) => ({ |
||||
|
messages: { |
||||
|
direct: {}, |
||||
|
broadcast: {}, |
||||
|
}, |
||||
|
|
||||
|
activeChat: Types.ChannelNumber.Primary, |
||||
|
chatType: 'broadcast', |
||||
|
|
||||
|
setActiveChat: (chat) => { |
||||
|
set(produce((state: MessageStore) => { |
||||
|
state.activeChat = chat; |
||||
|
})); |
||||
|
}, |
||||
|
|
||||
|
setChatType: (type) => { |
||||
|
set(produce((state: MessageStore) => { |
||||
|
state.chatType = type; |
||||
|
})); |
||||
|
}, |
||||
|
|
||||
|
addMessage: (message) => { |
||||
|
set(produce((state: MessageStore) => { |
||||
|
const group = message.type === 'direct' ? state.messages.direct : state.messages.broadcast; |
||||
|
const key = message.type === 'direct' ? message.from : message.channel; |
||||
|
if (!group[key]) { |
||||
|
group[key] = []; |
||||
|
} |
||||
|
group[key].push(message); |
||||
|
})); |
||||
|
}, |
||||
|
|
||||
|
getMessages: (type, key) => { |
||||
|
const group = type === 'direct' ? get().messages.direct : get().messages.broadcast; |
||||
|
return group[key] ?? []; |
||||
|
}, |
||||
|
|
||||
|
setMessageState: (type, key, messageId, newState) => { |
||||
|
set(produce((state: MessageStore) => { |
||||
|
const group = type === 'direct' ? state.messages.direct : state.messages.broadcast; |
||||
|
const messages = group[key]; |
||||
|
if (!messages) return; |
||||
|
const message = messages.find((msg) => msg.id === messageId); |
||||
|
if (message) { |
||||
|
message.state = newState; |
||||
|
} |
||||
|
})); |
||||
|
}, |
||||
|
|
||||
|
clearMessages: () => { |
||||
|
set(produce((state: MessageStore) => { |
||||
|
state.messages.direct = {}; |
||||
|
state.messages.broadcast = {}; |
||||
|
})); |
||||
|
}, |
||||
|
}), |
||||
|
{ |
||||
|
name: 'mesh-messages', |
||||
|
storage: createJSONStorage(() => zustandIDBStorage), |
||||
|
// ✅ No need for partialize magic — simple object storage
|
||||
|
partialize: (state) => ({ |
||||
|
activeChat: state.activeChat, |
||||
|
chatType: state.chatType, |
||||
|
messages: state.messages, |
||||
|
}), |
||||
|
} |
||||
|
) |
||||
|
); |
||||
Loading…
Reference in new issue