Merge branch 'develop'
This commit is contained in:
commit
7a9fcad960
9 changed files with 238 additions and 56 deletions
|
@ -8,22 +8,11 @@ import RoomDomain from '@/domain/Room'
|
||||||
import UserInfoDomain from '@/domain/UserInfo'
|
import UserInfoDomain from '@/domain/UserInfo'
|
||||||
import Setup from '@/app/content/views/Setup'
|
import Setup from '@/app/content/views/Setup'
|
||||||
import MessageListDomain from '@/domain/MessageList'
|
import MessageListDomain from '@/domain/MessageList'
|
||||||
import { useEffect, useRef, useState } from 'react'
|
import { useEffect, useRef } from 'react'
|
||||||
import { Toaster } from 'sonner'
|
import { Toaster } from 'sonner'
|
||||||
import { browserSyncStorage, indexDBStorage } from '@/domain/impls/Storage'
|
|
||||||
import { APP_OPEN_STATUS_STORAGE_KEY } from '@/constants/config'
|
|
||||||
import LogoIcon0 from '@/assets/images/logo-0.svg'
|
|
||||||
import LogoIcon1 from '@/assets/images/logo-1.svg'
|
|
||||||
import LogoIcon2 from '@/assets/images/logo-2.svg'
|
|
||||||
import LogoIcon3 from '@/assets/images/logo-3.svg'
|
|
||||||
import LogoIcon4 from '@/assets/images/logo-4.svg'
|
|
||||||
import LogoIcon5 from '@/assets/images/logo-5.svg'
|
|
||||||
import LogoIcon6 from '@/assets/images/logo-6.svg'
|
|
||||||
|
|
||||||
import { getDay } from 'date-fns'
|
|
||||||
import DanmakuContainer from './components/DanmakuContainer'
|
import DanmakuContainer from './components/DanmakuContainer'
|
||||||
import DanmakuDomain from '@/domain/Danmaku'
|
import DanmakuDomain from '@/domain/Danmaku'
|
||||||
import { browser } from 'wxt/browser'
|
|
||||||
|
|
||||||
export default function App() {
|
export default function App() {
|
||||||
const send = useRemeshSend()
|
const send = useRemeshSend()
|
||||||
|
@ -32,13 +21,11 @@ export default function App() {
|
||||||
const messageListDomain = useRemeshDomain(MessageListDomain())
|
const messageListDomain = useRemeshDomain(MessageListDomain())
|
||||||
const danmakuDomain = useRemeshDomain(DanmakuDomain())
|
const danmakuDomain = useRemeshDomain(DanmakuDomain())
|
||||||
const danmakuIsEnabled = useRemeshQuery(danmakuDomain.query.IsEnabledQuery())
|
const danmakuIsEnabled = useRemeshQuery(danmakuDomain.query.IsEnabledQuery())
|
||||||
const userInfo = useRemeshQuery(userInfoDomain.query.UserInfoQuery())
|
|
||||||
const userInfoSetFinished = useRemeshQuery(userInfoDomain.query.UserInfoSetIsFinishedQuery())
|
const userInfoSetFinished = useRemeshQuery(userInfoDomain.query.UserInfoSetIsFinishedQuery())
|
||||||
const userInfoLoadFinished = useRemeshQuery(userInfoDomain.query.UserInfoLoadIsFinishedQuery())
|
const userInfoLoadFinished = useRemeshQuery(userInfoDomain.query.UserInfoLoadIsFinishedQuery())
|
||||||
const messageListLoadFinished = useRemeshQuery(messageListDomain.query.LoadIsFinishedQuery())
|
const messageListLoadFinished = useRemeshQuery(messageListDomain.query.LoadIsFinishedQuery())
|
||||||
const notUserInfo = userInfoLoadFinished && !userInfoSetFinished
|
|
||||||
|
|
||||||
const DayLogo = [LogoIcon0, LogoIcon1, LogoIcon2, LogoIcon3, LogoIcon4, LogoIcon5, LogoIcon6][getDay(Date())]
|
const notUserInfo = userInfoLoadFinished && !userInfoSetFinished
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (messageListLoadFinished) {
|
if (messageListLoadFinished) {
|
||||||
|
@ -51,23 +38,6 @@ export default function App() {
|
||||||
}
|
}
|
||||||
}, [userInfoSetFinished, messageListLoadFinished])
|
}, [userInfoSetFinished, messageListLoadFinished])
|
||||||
|
|
||||||
const [appOpen, setAppOpen] = useState(false)
|
|
||||||
|
|
||||||
const handleToggleApp = async () => {
|
|
||||||
const value = !appOpen
|
|
||||||
setAppOpen(value)
|
|
||||||
await indexDBStorage.setItem<boolean>(APP_OPEN_STATUS_STORAGE_KEY, value)
|
|
||||||
}
|
|
||||||
|
|
||||||
const getAppOpenStatus = async () => {
|
|
||||||
const value = await indexDBStorage.getItem<boolean>(APP_OPEN_STATUS_STORAGE_KEY)
|
|
||||||
setAppOpen(!!value)
|
|
||||||
}
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
getAppOpenStatus()
|
|
||||||
}, [])
|
|
||||||
|
|
||||||
const danmakuContainerRef = useRef<HTMLDivElement>(null)
|
const danmakuContainerRef = useRef<HTMLDivElement>(null)
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
@ -76,19 +46,18 @@ export default function App() {
|
||||||
danmakuIsEnabled && send(danmakuDomain.command.DestroyCommand())
|
danmakuIsEnabled && send(danmakuDomain.command.DestroyCommand())
|
||||||
}
|
}
|
||||||
}, [danmakuIsEnabled])
|
}, [danmakuIsEnabled])
|
||||||
|
console.log(1)
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<AppContainer open={appOpen}>
|
<AppContainer>
|
||||||
<Header />
|
<Header />
|
||||||
<Main />
|
<Main />
|
||||||
<Footer />
|
<Footer />
|
||||||
{notUserInfo && <Setup />}
|
{notUserInfo && <Setup />}
|
||||||
<Toaster richColors offset="70px" visibleToasts={1} position="top-center"></Toaster>
|
<Toaster richColors offset="70px" visibleToasts={1} position="top-center"></Toaster>
|
||||||
</AppContainer>
|
</AppContainer>
|
||||||
<AppButton onClick={handleToggleApp}>
|
<AppButton></AppButton>
|
||||||
<DayLogo className="max-h-full max-w-full"></DayLogo>
|
|
||||||
</AppButton>
|
|
||||||
<DanmakuContainer ref={danmakuContainerRef} />
|
<DanmakuContainer ref={danmakuContainerRef} />
|
||||||
</>
|
</>
|
||||||
)
|
)
|
||||||
|
|
|
@ -7,7 +7,7 @@ import { defineContentScript } from 'wxt/sandbox'
|
||||||
import { createShadowRootUi } from 'wxt/client'
|
import { createShadowRootUi } from 'wxt/client'
|
||||||
|
|
||||||
import App from './App'
|
import App from './App'
|
||||||
import { IndexDBStorageImpl, BrowserSyncStorageImpl, indexDBStorage } from '@/domain/impls/Storage'
|
import { LocalStorageImpl, IndexDBStorageImpl, BrowserSyncStorageImpl } from '@/domain/impls/Storage'
|
||||||
import { PeerRoomImpl } from '@/domain/impls/PeerRoom'
|
import { PeerRoomImpl } from '@/domain/impls/PeerRoom'
|
||||||
import { DanmakuImpl } from '@/domain/impls/Danmaku'
|
import { DanmakuImpl } from '@/domain/impls/Danmaku'
|
||||||
// import { PeerRoomImpl } from '@/domain/impls/PeerRoom2'
|
// import { PeerRoomImpl } from '@/domain/impls/PeerRoom2'
|
||||||
|
@ -23,7 +23,7 @@ export default defineContentScript({
|
||||||
excludeMatches: ['*://localhost/*', '*://127.0.0.1/*'],
|
excludeMatches: ['*://localhost/*', '*://127.0.0.1/*'],
|
||||||
async main(ctx) {
|
async main(ctx) {
|
||||||
const store = Remesh.store({
|
const store = Remesh.store({
|
||||||
externs: [IndexDBStorageImpl, BrowserSyncStorageImpl, PeerRoomImpl, ToastImpl, DanmakuImpl]
|
externs: [LocalStorageImpl, IndexDBStorageImpl, BrowserSyncStorageImpl, PeerRoomImpl, ToastImpl, DanmakuImpl]
|
||||||
// inspectors: __DEV__ ? [RemeshLogger()] : []
|
// inspectors: __DEV__ ? [RemeshLogger()] : []
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
import { type ReactNode, type FC, useState, type MouseEvent, useRef } from 'react'
|
import { type FC, useState, type MouseEvent, useRef } from 'react'
|
||||||
import { SettingsIcon, MoonIcon, SunIcon } from 'lucide-react'
|
import { SettingsIcon, MoonIcon, SunIcon } from 'lucide-react'
|
||||||
import { motion, AnimatePresence } from 'framer-motion'
|
import { motion, AnimatePresence } from 'framer-motion'
|
||||||
|
|
||||||
|
@ -10,17 +10,25 @@ import UserInfoDomain from '@/domain/UserInfo'
|
||||||
import useClickAway from '@/hooks/useClickAway'
|
import useClickAway from '@/hooks/useClickAway'
|
||||||
import { checkSystemDarkMode, cn } from '@/utils'
|
import { checkSystemDarkMode, cn } from '@/utils'
|
||||||
import ToastDomain from '@/domain/Toast'
|
import ToastDomain from '@/domain/Toast'
|
||||||
|
import LogoIcon0 from '@/assets/images/logo-0.svg'
|
||||||
|
import LogoIcon1 from '@/assets/images/logo-1.svg'
|
||||||
|
import LogoIcon2 from '@/assets/images/logo-2.svg'
|
||||||
|
import LogoIcon3 from '@/assets/images/logo-3.svg'
|
||||||
|
import LogoIcon4 from '@/assets/images/logo-4.svg'
|
||||||
|
import LogoIcon5 from '@/assets/images/logo-5.svg'
|
||||||
|
import LogoIcon6 from '@/assets/images/logo-6.svg'
|
||||||
|
import AppStatusDomain from '@/domain/AppStatus'
|
||||||
|
import { getDay } from 'date-fns'
|
||||||
|
|
||||||
export interface AppButtonProps {
|
const AppButton: FC = () => {
|
||||||
children?: ReactNode
|
|
||||||
onClick?: (e: MouseEvent<HTMLButtonElement>) => void
|
|
||||||
}
|
|
||||||
|
|
||||||
const AppButton: FC<AppButtonProps> = ({ children, onClick }) => {
|
|
||||||
const send = useRemeshSend()
|
const send = useRemeshSend()
|
||||||
|
const appStatusDomain = useRemeshDomain(AppStatusDomain())
|
||||||
|
const appOpenStatus = useRemeshQuery(appStatusDomain.query.OpenQuery())
|
||||||
|
const hasUnreadQuery = useRemeshQuery(appStatusDomain.query.HasUnreadQuery())
|
||||||
const userInfoDomain = useRemeshDomain(UserInfoDomain())
|
const userInfoDomain = useRemeshDomain(UserInfoDomain())
|
||||||
const userInfo = useRemeshQuery(userInfoDomain.query.UserInfoQuery())
|
const userInfo = useRemeshQuery(userInfoDomain.query.UserInfoQuery())
|
||||||
const toastDomain = useRemeshDomain(ToastDomain())
|
const toastDomain = useRemeshDomain(ToastDomain())
|
||||||
|
const DayLogo = [LogoIcon0, LogoIcon1, LogoIcon2, LogoIcon3, LogoIcon4, LogoIcon5, LogoIcon6][getDay(Date())]
|
||||||
|
|
||||||
const isDarkMode =
|
const isDarkMode =
|
||||||
userInfo?.themeMode === 'dark' ? true : userInfo?.themeMode === 'light' ? false : checkSystemDarkMode()
|
userInfo?.themeMode === 'dark' ? true : userInfo?.themeMode === 'light' ? false : checkSystemDarkMode()
|
||||||
|
@ -51,12 +59,16 @@ const AppButton: FC<AppButtonProps> = ({ children, onClick }) => {
|
||||||
browser.runtime.sendMessage(EVENT.OPEN_OPTIONS_PAGE)
|
browser.runtime.sendMessage(EVENT.OPEN_OPTIONS_PAGE)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const handleToggleApp = () => {
|
||||||
|
send(appStatusDomain.command.UpdateOpenCommand(!appOpenStatus))
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div ref={menuRef} className="fixed bottom-5 right-5 z-infinity grid select-none justify-center gap-y-3">
|
<div ref={menuRef} className="fixed bottom-5 right-5 z-infinity grid select-none justify-center gap-y-3">
|
||||||
<AnimatePresence>
|
<AnimatePresence>
|
||||||
{menuOpen && (
|
{menuOpen && (
|
||||||
<motion.div
|
<motion.div
|
||||||
className="z-infinity grid gap-y-3"
|
className="z-10 grid gap-y-3"
|
||||||
initial={{ opacity: 0, y: 12 }}
|
initial={{ opacity: 0, y: 12 }}
|
||||||
animate={{ opacity: 1, y: 0 }}
|
animate={{ opacity: 1, y: 0 }}
|
||||||
exit={{ opacity: 0, y: 12 }}
|
exit={{ opacity: 0, y: 12 }}
|
||||||
|
@ -90,11 +102,27 @@ const AppButton: FC<AppButtonProps> = ({ children, onClick }) => {
|
||||||
)}
|
)}
|
||||||
</AnimatePresence>
|
</AnimatePresence>
|
||||||
<Button
|
<Button
|
||||||
onClick={onClick}
|
onClick={handleToggleApp}
|
||||||
onContextMenu={handleToggleMenu}
|
onContextMenu={handleToggleMenu}
|
||||||
className="relative z-10 size-11 overflow-hidden rounded-full p-0 text-xs shadow-lg shadow-slate-500/50"
|
className="relative z-20 size-11 rounded-full p-0 text-xs shadow-lg shadow-slate-500/50"
|
||||||
>
|
>
|
||||||
{children}
|
<AnimatePresence>
|
||||||
|
{hasUnreadQuery && (
|
||||||
|
<motion.div
|
||||||
|
initial={{ opacity: 0 }}
|
||||||
|
animate={{ opacity: 1 }}
|
||||||
|
exit={{ opacity: 0 }}
|
||||||
|
transition={{ duration: 0.1 }}
|
||||||
|
className="absolute -right-1 -top-1 flex size-5 items-center justify-center"
|
||||||
|
>
|
||||||
|
<span
|
||||||
|
className={cn('absolute inline-flex size-full animate-ping rounded-full opacity-75', 'bg-orange-400')}
|
||||||
|
></span>
|
||||||
|
<span className={cn('relative inline-flex size-3 rounded-full', 'bg-orange-500')}></span>
|
||||||
|
</motion.div>
|
||||||
|
)}
|
||||||
|
</AnimatePresence>
|
||||||
|
<DayLogo className="max-h-full max-w-full"></DayLogo>
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
|
|
|
@ -1,13 +1,17 @@
|
||||||
import { type ReactNode, type FC } from 'react'
|
import { type ReactNode, type FC } from 'react'
|
||||||
import useResizable from '@/hooks/useResizable'
|
import useResizable from '@/hooks/useResizable'
|
||||||
import { motion, AnimatePresence } from 'framer-motion'
|
import { motion, AnimatePresence } from 'framer-motion'
|
||||||
|
import AppStatusDomain from '@/domain/AppStatus'
|
||||||
|
import { useRemeshDomain, useRemeshQuery } from 'remesh-react'
|
||||||
|
|
||||||
export interface AppContainerProps {
|
export interface AppContainerProps {
|
||||||
children?: ReactNode
|
children?: ReactNode
|
||||||
open?: boolean
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const AppContainer: FC<AppContainerProps> = ({ children, open }) => {
|
const AppContainer: FC<AppContainerProps> = ({ children }) => {
|
||||||
|
const appStatusDomain = useRemeshDomain(AppStatusDomain())
|
||||||
|
const appOpenStatus = useRemeshQuery(appStatusDomain.query.OpenQuery())
|
||||||
|
|
||||||
const { size, ref } = useResizable({
|
const { size, ref } = useResizable({
|
||||||
initSize: Math.max(375, window.innerWidth / 6),
|
initSize: Math.max(375, window.innerWidth / 6),
|
||||||
maxSize: Math.min(750, window.innerWidth / 3),
|
maxSize: Math.min(750, window.innerWidth / 3),
|
||||||
|
@ -17,7 +21,7 @@ const AppContainer: FC<AppContainerProps> = ({ children, open }) => {
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<AnimatePresence>
|
<AnimatePresence>
|
||||||
{open && (
|
{appOpenStatus && (
|
||||||
<motion.div
|
<motion.div
|
||||||
initial={{ opacity: 0, y: 10, x: 10 }}
|
initial={{ opacity: 0, y: 10, x: 10 }}
|
||||||
animate={{ opacity: 1, y: 0, x: 0 }}
|
animate={{ opacity: 1, y: 0, x: 0 }}
|
||||||
|
|
|
@ -11,7 +11,6 @@ const Header: FC = () => {
|
||||||
const siteInfo = getSiteInfo()
|
const siteInfo = getSiteInfo()
|
||||||
const roomDomain = useRemeshDomain(RoomDomain())
|
const roomDomain = useRemeshDomain(RoomDomain())
|
||||||
const userList = useRemeshQuery(roomDomain.query.UserListQuery())
|
const userList = useRemeshQuery(roomDomain.query.UserListQuery())
|
||||||
const peerId = useRemeshQuery(roomDomain.query.PeerIdQuery())
|
|
||||||
const onlineCount = userList.length
|
const onlineCount = userList.length
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
@ -27,7 +26,6 @@ const Header: FC = () => {
|
||||||
<Button className="overflow-hidden" variant="link">
|
<Button className="overflow-hidden" variant="link">
|
||||||
<span className="truncate text-lg font-semibold text-slate-600">
|
<span className="truncate text-lg font-semibold text-slate-600">
|
||||||
{siteInfo.hostname.replace(/^www\./i, '')}
|
{siteInfo.hostname.replace(/^www\./i, '')}
|
||||||
{/* {peerId} */}
|
|
||||||
</span>
|
</span>
|
||||||
</Button>
|
</Button>
|
||||||
</HoverCardTrigger>
|
</HoverCardTrigger>
|
||||||
|
|
|
@ -191,7 +191,7 @@ export const MESSAGE_LIST_STORAGE_KEY = 'WEB_CHAT_MESSAGE_LIST' as const
|
||||||
|
|
||||||
export const USER_INFO_STORAGE_KEY = 'WEB_CHAT_USER_INFO' as const
|
export const USER_INFO_STORAGE_KEY = 'WEB_CHAT_USER_INFO' as const
|
||||||
|
|
||||||
export const APP_OPEN_STATUS_STORAGE_KEY = 'WEB_CHAT_APP_OPEN_STATUS' as const
|
export const APP_STATUS_STORAGE_KEY = 'WEB_CHAT_APP_OPEN_STATUS' as const
|
||||||
/**
|
/**
|
||||||
* In chrome storage.sync, each key-value pair supports a maximum storage of 8kb
|
* In chrome storage.sync, each key-value pair supports a maximum storage of 8kb
|
||||||
* Image is encoded as base64, and the size is increased by about 33%.
|
* Image is encoded as base64, and the size is increased by about 33%.
|
||||||
|
|
144
src/domain/AppStatus.ts
Normal file
144
src/domain/AppStatus.ts
Normal file
|
@ -0,0 +1,144 @@
|
||||||
|
import { Remesh } from 'remesh'
|
||||||
|
import StatusModule from './modules/Status'
|
||||||
|
import { LocalStorageExtern } from './externs/Storage'
|
||||||
|
import { APP_STATUS_STORAGE_KEY } from '@/constants/config'
|
||||||
|
import StorageEffect from './modules/StorageEffect'
|
||||||
|
import RoomDomain, { SendType } from './Room'
|
||||||
|
import { map } from 'rxjs'
|
||||||
|
|
||||||
|
export interface AppStatus {
|
||||||
|
open: boolean
|
||||||
|
unread: number
|
||||||
|
}
|
||||||
|
|
||||||
|
export const defaultStatusState = {
|
||||||
|
open: false,
|
||||||
|
unread: 0
|
||||||
|
}
|
||||||
|
|
||||||
|
const AppStatusDomain = Remesh.domain({
|
||||||
|
name: 'AppStatusDomain',
|
||||||
|
impl: (domain) => {
|
||||||
|
const storageEffect = new StorageEffect({
|
||||||
|
domain,
|
||||||
|
extern: LocalStorageExtern,
|
||||||
|
key: APP_STATUS_STORAGE_KEY
|
||||||
|
})
|
||||||
|
const roomDomain = domain.getDomain(RoomDomain())
|
||||||
|
|
||||||
|
const StatusLoadModule = StatusModule(domain, {
|
||||||
|
name: 'AppStatus.LoadStatusModule'
|
||||||
|
})
|
||||||
|
|
||||||
|
const StatusLoadIsFinishedQuery = domain.query({
|
||||||
|
name: 'AppStatus.StatusLoadIsFinishedQuery',
|
||||||
|
impl: () => {
|
||||||
|
return StatusLoadModule.query.IsFinishedQuery()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
const StatusState = domain.state<AppStatus>({
|
||||||
|
name: 'AppStatus.OpenState',
|
||||||
|
default: defaultStatusState
|
||||||
|
})
|
||||||
|
|
||||||
|
const OpenQuery = domain.query({
|
||||||
|
name: 'AppStatus.IsOpenQuery',
|
||||||
|
impl: ({ get }) => {
|
||||||
|
return get(StatusState()).open
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
const UnreadQuery = domain.query({
|
||||||
|
name: 'AppStatus.UnreadQuery',
|
||||||
|
impl: ({ get }) => {
|
||||||
|
return get(StatusState()).unread
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
const HasUnreadQuery = domain.query({
|
||||||
|
name: 'AppStatus.HasUnreadQuery',
|
||||||
|
impl: ({ get }) => {
|
||||||
|
return get(StatusState()).unread > 0
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
const UpdateOpenCommand = domain.command({
|
||||||
|
name: 'AppStatus.UpdateOpenCommand',
|
||||||
|
impl: ({ get }, value: boolean) => {
|
||||||
|
const status = get(StatusState())
|
||||||
|
return UpdateStatusCommand({
|
||||||
|
unread: value ? 0 : status.unread,
|
||||||
|
open: value
|
||||||
|
})
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
const UpdateUnreadCommand = domain.command({
|
||||||
|
name: 'AppStatus.UpdateUnreadCommand',
|
||||||
|
impl: ({ get }, value: number) => {
|
||||||
|
const status = get(StatusState())
|
||||||
|
return UpdateStatusCommand({
|
||||||
|
...status,
|
||||||
|
unread: value
|
||||||
|
})
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
const UpdateStatusCommand = domain.command({
|
||||||
|
name: 'AppStatus.UpdateStatusCommand',
|
||||||
|
impl: (_, value: AppStatus) => {
|
||||||
|
return [StatusState().new(value), SyncToStorageEvent()]
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
const SyncToStorageEvent = domain.event({
|
||||||
|
name: 'UserInfo.SyncToStorageEvent',
|
||||||
|
impl: ({ get }) => {
|
||||||
|
return get(StatusState())
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
storageEffect
|
||||||
|
.set(SyncToStorageEvent)
|
||||||
|
.get<AppStatus>((value) => [
|
||||||
|
UpdateStatusCommand(value ?? defaultStatusState),
|
||||||
|
StatusLoadModule.command.SetFinishedCommand()
|
||||||
|
])
|
||||||
|
.watch<AppStatus>((value) => [UpdateStatusCommand(value ?? defaultStatusState)])
|
||||||
|
|
||||||
|
domain.effect({
|
||||||
|
name: 'OnMessageEffect',
|
||||||
|
impl: ({ fromEvent, get }) => {
|
||||||
|
const onMessage$ = fromEvent(roomDomain.event.OnMessageEvent).pipe(
|
||||||
|
map((message) => {
|
||||||
|
const status = get(StatusState())
|
||||||
|
if (!status.open && message.type === SendType.Text) {
|
||||||
|
return UpdateUnreadCommand(status.unread + 1)
|
||||||
|
}
|
||||||
|
return null
|
||||||
|
})
|
||||||
|
)
|
||||||
|
return onMessage$
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
return {
|
||||||
|
query: {
|
||||||
|
OpenQuery,
|
||||||
|
UnreadQuery,
|
||||||
|
HasUnreadQuery,
|
||||||
|
StatusLoadIsFinishedQuery
|
||||||
|
},
|
||||||
|
command: {
|
||||||
|
UpdateOpenCommand,
|
||||||
|
UpdateUnreadCommand
|
||||||
|
},
|
||||||
|
event: {
|
||||||
|
SyncToStorageEvent
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
export default AppStatusDomain
|
|
@ -15,6 +15,30 @@ export interface Storage {
|
||||||
unwatch: Unwatch
|
unwatch: Unwatch
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export const LocalStorageExtern = Remesh.extern<Storage>({
|
||||||
|
default: {
|
||||||
|
name: 'STORAGE',
|
||||||
|
get: async () => {
|
||||||
|
throw new Error('"get" not implemented.')
|
||||||
|
},
|
||||||
|
set: async () => {
|
||||||
|
throw new Error('"set" not implemented.')
|
||||||
|
},
|
||||||
|
remove: async () => {
|
||||||
|
throw new Error('"remove" not implemented.')
|
||||||
|
},
|
||||||
|
clear: async () => {
|
||||||
|
throw new Error('"clear" not implemented.')
|
||||||
|
},
|
||||||
|
watch: async () => {
|
||||||
|
throw new Error('"watch" not implemented.')
|
||||||
|
},
|
||||||
|
unwatch: async () => {
|
||||||
|
throw new Error('"unwatch" not implemented.')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
export const IndexDBStorageExtern = Remesh.extern<Storage>({
|
export const IndexDBStorageExtern = Remesh.extern<Storage>({
|
||||||
default: {
|
default: {
|
||||||
name: 'STORAGE',
|
name: 'STORAGE',
|
||||||
|
|
|
@ -1,11 +1,16 @@
|
||||||
import { createStorage } from 'unstorage'
|
import { createStorage } from 'unstorage'
|
||||||
import indexedDbDriver from 'unstorage/drivers/indexedb'
|
import indexedDbDriver from 'unstorage/drivers/indexedb'
|
||||||
import { IndexDBStorageExtern, BrowserSyncStorageExtern } from '@/domain/externs/Storage'
|
import localStorageDriver from 'unstorage/drivers/localstorage'
|
||||||
|
import { LocalStorageExtern, IndexDBStorageExtern, BrowserSyncStorageExtern } from '@/domain/externs/Storage'
|
||||||
import { STORAGE_NAME } from '@/constants/config'
|
import { STORAGE_NAME } from '@/constants/config'
|
||||||
import { webExtensionDriver } from '@/utils/webExtensionDriver'
|
import { webExtensionDriver } from '@/utils/webExtensionDriver'
|
||||||
import { browser } from 'wxt/browser'
|
import { browser } from 'wxt/browser'
|
||||||
import { Storage } from '@/domain/externs/Storage'
|
import { Storage } from '@/domain/externs/Storage'
|
||||||
|
|
||||||
|
export const localStorage = createStorage({
|
||||||
|
driver: localStorageDriver({ base: `${STORAGE_NAME}:` })
|
||||||
|
})
|
||||||
|
|
||||||
export const indexDBStorage = createStorage({
|
export const indexDBStorage = createStorage({
|
||||||
driver: indexedDbDriver({ base: `${STORAGE_NAME}:` })
|
driver: indexedDbDriver({ base: `${STORAGE_NAME}:` })
|
||||||
})
|
})
|
||||||
|
@ -14,6 +19,16 @@ export const browserSyncStorage = createStorage({
|
||||||
driver: webExtensionDriver({ storageArea: 'sync' })
|
driver: webExtensionDriver({ storageArea: 'sync' })
|
||||||
})
|
})
|
||||||
|
|
||||||
|
export const LocalStorageImpl = LocalStorageExtern.impl({
|
||||||
|
name: STORAGE_NAME,
|
||||||
|
get: localStorage.getItem,
|
||||||
|
set: localStorage.setItem,
|
||||||
|
remove: localStorage.removeItem,
|
||||||
|
clear: localStorage.clear,
|
||||||
|
watch: localStorage.watch as Storage['watch'],
|
||||||
|
unwatch: localStorage.unwatch
|
||||||
|
})
|
||||||
|
|
||||||
export const IndexDBStorageImpl = IndexDBStorageExtern.impl({
|
export const IndexDBStorageImpl = IndexDBStorageExtern.impl({
|
||||||
name: STORAGE_NAME,
|
name: STORAGE_NAME,
|
||||||
get: indexDBStorage.getItem,
|
get: indexDBStorage.getItem,
|
||||||
|
|
Loading…
Reference in a new issue