fix: the number of online users is inaccurate
This commit is contained in:
parent
d325be4bec
commit
c6301a826e
12 changed files with 232 additions and 100 deletions
|
@ -62,19 +62,31 @@ export default function App() {
|
||||||
}, [danmakuIsEnabled])
|
}, [danmakuIsEnabled])
|
||||||
|
|
||||||
return (
|
return (
|
||||||
appStatusLoadIsFinished && (
|
<div id="app" className={cn('contents', userInfo?.themeMode)}>
|
||||||
<div id="app" className={cn('contents', userInfo?.themeMode)}>
|
{appStatusLoadIsFinished && (
|
||||||
<AppMain>
|
<>
|
||||||
<Header />
|
<AppMain>
|
||||||
<Main />
|
<Header />
|
||||||
<Footer />
|
<Main />
|
||||||
{notUserInfo && <Setup></Setup>}
|
<Footer />
|
||||||
<Toaster richColors offset="70px" visibleToasts={1} position="top-center"></Toaster>
|
{notUserInfo && <Setup></Setup>}
|
||||||
</AppMain>
|
<Toaster
|
||||||
<AppButton></AppButton>
|
richColors
|
||||||
|
theme={userInfo?.themeMode}
|
||||||
<DanmakuContainer ref={danmakuContainerRef} />
|
offset="70px"
|
||||||
</div>
|
visibleToasts={1}
|
||||||
)
|
toastOptions={{
|
||||||
|
classNames: {
|
||||||
|
toast: 'dark:bg-slate-950 border dark:border-slate-600'
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
position="top-center"
|
||||||
|
></Toaster>
|
||||||
|
</AppMain>
|
||||||
|
<AppButton></AppButton>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
<DanmakuContainer ref={danmakuContainerRef} />
|
||||||
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
@ -13,10 +13,10 @@ import { NotificationImpl } from '@/domain/impls/Notification'
|
||||||
import { ToastImpl } from '@/domain/impls/Toast'
|
import { ToastImpl } from '@/domain/impls/Toast'
|
||||||
// import { PeerRoomImpl } from '@/domain/impls/PeerRoom'
|
// import { PeerRoomImpl } from '@/domain/impls/PeerRoom'
|
||||||
import { PeerRoomImpl } from '@/domain/impls/PeerRoom2'
|
import { PeerRoomImpl } from '@/domain/impls/PeerRoom2'
|
||||||
import '@/assets/styles/tailwind.css'
|
|
||||||
// Remove import after merging: https://github.com/emilkowalski/sonner/pull/508
|
// Remove import after merging: https://github.com/emilkowalski/sonner/pull/508
|
||||||
import '@/assets/styles/sonner.css'
|
import '@/assets/styles/sonner.css'
|
||||||
import '@/assets/styles/overlay.css'
|
import '@/assets/styles/overlay.css'
|
||||||
|
import '@/assets/styles/tailwind.css'
|
||||||
import NotificationDomain from '@/domain/Notification'
|
import NotificationDomain from '@/domain/Notification'
|
||||||
import { createElement } from '@/utils'
|
import { createElement } from '@/utils'
|
||||||
|
|
||||||
|
|
|
@ -107,7 +107,7 @@ const AppButton: FC<AppButtonProps> = ({ className }) => {
|
||||||
<Button
|
<Button
|
||||||
onClick={handleSwitchTheme}
|
onClick={handleSwitchTheme}
|
||||||
variant="outline"
|
variant="outline"
|
||||||
className="relative size-10 overflow-hidden rounded-full p-0 shadow"
|
className="relative size-10 overflow-hidden rounded-full p-0 shadow dark:border-slate-600"
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
className={cn(
|
className={cn(
|
||||||
|
@ -121,10 +121,18 @@ const AppButton: FC<AppButtonProps> = ({ className }) => {
|
||||||
</div>
|
</div>
|
||||||
</Button>
|
</Button>
|
||||||
|
|
||||||
<Button onClick={handleOpenOptionsPage} variant="outline" className="size-10 rounded-full p-0 shadow">
|
<Button
|
||||||
|
onClick={handleOpenOptionsPage}
|
||||||
|
variant="outline"
|
||||||
|
className="size-10 rounded-full p-0 shadow dark:border-slate-600"
|
||||||
|
>
|
||||||
<SettingsIcon size={20} />
|
<SettingsIcon size={20} />
|
||||||
</Button>
|
</Button>
|
||||||
<Button ref={appButtonRef} variant="outline" className="size-10 cursor-grab rounded-full p-0 shadow">
|
<Button
|
||||||
|
ref={appButtonRef}
|
||||||
|
variant="outline"
|
||||||
|
className="size-10 cursor-grab rounded-full p-0 shadow dark:border-slate-600"
|
||||||
|
>
|
||||||
<HandIcon size={20} />
|
<HandIcon size={20} />
|
||||||
</Button>
|
</Button>
|
||||||
</motion.div>
|
</motion.div>
|
||||||
|
|
|
@ -40,7 +40,7 @@ const AppStatusDomain = Remesh.domain({
|
||||||
})
|
})
|
||||||
|
|
||||||
const StatusState = domain.state<AppStatus>({
|
const StatusState = domain.state<AppStatus>({
|
||||||
name: 'AppStatus.OpenState',
|
name: 'AppStatus.StatusState',
|
||||||
default: defaultStatusState
|
default: defaultStatusState
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
|
@ -58,7 +58,7 @@ export interface TextMessage extends MessageUser {
|
||||||
|
|
||||||
export type RoomMessage = SyncUserMessage | SyncHistoryMessage | LikeMessage | HateMessage | TextMessage
|
export type RoomMessage = SyncUserMessage | SyncHistoryMessage | LikeMessage | HateMessage | TextMessage
|
||||||
|
|
||||||
export type RoomUser = MessageUser & { peerId: string; joinTime: number }
|
export type RoomUser = MessageUser & { peerIds: string[]; joinTime: number }
|
||||||
|
|
||||||
const MessageUserSchema = {
|
const MessageUserSchema = {
|
||||||
userId: v.string(),
|
userId: v.string(),
|
||||||
|
@ -165,7 +165,7 @@ const RoomDomain = Remesh.domain({
|
||||||
const SelfUserQuery = domain.query({
|
const SelfUserQuery = domain.query({
|
||||||
name: 'Room.SelfUserQuery',
|
name: 'Room.SelfUserQuery',
|
||||||
impl: ({ get }) => {
|
impl: ({ get }) => {
|
||||||
return get(UserListQuery()).find((user) => user.peerId === get(PeerIdQuery()))!
|
return get(UserListQuery()).find((user) => user.peerIds.includes(peerRoom.peerId))!
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
@ -185,9 +185,7 @@ const RoomDomain = Remesh.domain({
|
||||||
const JoinRoomCommand = domain.command({
|
const JoinRoomCommand = domain.command({
|
||||||
name: 'Room.JoinRoomCommand',
|
name: 'Room.JoinRoomCommand',
|
||||||
impl: ({ get }) => {
|
impl: ({ get }) => {
|
||||||
peerRoom.joinRoom()
|
|
||||||
const { id: userId, name: username, avatar: userAvatar } = get(userInfoDomain.query.UserInfoQuery())!
|
const { id: userId, name: username, avatar: userAvatar } = get(userInfoDomain.query.UserInfoQuery())!
|
||||||
|
|
||||||
return [
|
return [
|
||||||
UpdateUserListCommand({
|
UpdateUserListCommand({
|
||||||
type: 'create',
|
type: 'create',
|
||||||
|
@ -204,15 +202,20 @@ const RoomDomain = Remesh.domain({
|
||||||
receiveTime: Date.now()
|
receiveTime: Date.now()
|
||||||
}),
|
}),
|
||||||
JoinStatusModule.command.SetFinishedCommand(),
|
JoinStatusModule.command.SetFinishedCommand(),
|
||||||
JoinRoomEvent(peerRoom.roomId)
|
JoinRoomEvent(peerRoom.roomId),
|
||||||
|
SelfJoinRoomEvent(peerRoom.roomId)
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
JoinRoomCommand.after(() => {
|
||||||
|
peerRoom.joinRoom()
|
||||||
|
return null
|
||||||
|
})
|
||||||
|
|
||||||
const LeaveRoomCommand = domain.command({
|
const LeaveRoomCommand = domain.command({
|
||||||
name: 'Room.LeaveRoomCommand',
|
name: 'Room.LeaveRoomCommand',
|
||||||
impl: ({ get }) => {
|
impl: ({ get }) => {
|
||||||
peerRoom.leaveRoom()
|
|
||||||
const { id: userId, name: username, avatar: userAvatar } = get(userInfoDomain.query.UserInfoQuery())!
|
const { id: userId, name: username, avatar: userAvatar } = get(userInfoDomain.query.UserInfoQuery())!
|
||||||
return [
|
return [
|
||||||
messageListDomain.command.CreateItemCommand({
|
messageListDomain.command.CreateItemCommand({
|
||||||
|
@ -230,11 +233,17 @@ const RoomDomain = Remesh.domain({
|
||||||
user: { peerId: peerRoom.peerId, joinTime: Date.now(), userId, username, userAvatar }
|
user: { peerId: peerRoom.peerId, joinTime: Date.now(), userId, username, userAvatar }
|
||||||
}),
|
}),
|
||||||
JoinStatusModule.command.SetInitialCommand(),
|
JoinStatusModule.command.SetInitialCommand(),
|
||||||
LeaveRoomEvent(peerRoom.roomId)
|
LeaveRoomEvent(peerRoom.roomId),
|
||||||
|
SelfLeaveRoomEvent(peerRoom.roomId)
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
LeaveRoomCommand.after(() => {
|
||||||
|
peerRoom.leaveRoom()
|
||||||
|
return null
|
||||||
|
})
|
||||||
|
|
||||||
const SendTextMessageCommand = domain.command({
|
const SendTextMessageCommand = domain.command({
|
||||||
name: 'Room.SendTextMessageCommand',
|
name: 'Room.SendTextMessageCommand',
|
||||||
impl: ({ get }, message: string | { body: string; atUsers: AtUser[] }) => {
|
impl: ({ get }, message: string | { body: string; atUsers: AtUser[] }) => {
|
||||||
|
@ -314,6 +323,7 @@ const RoomDomain = Remesh.domain({
|
||||||
const syncUserMessage: SyncUserMessage = {
|
const syncUserMessage: SyncUserMessage = {
|
||||||
...self,
|
...self,
|
||||||
id: nanoid(),
|
id: nanoid(),
|
||||||
|
peerId: peerRoom.peerId,
|
||||||
sendTime: Date.now(),
|
sendTime: Date.now(),
|
||||||
lastMessageTime,
|
lastMessageTime,
|
||||||
type: SendType.SyncUser
|
type: SendType.SyncUser
|
||||||
|
@ -393,12 +403,32 @@ const RoomDomain = Remesh.domain({
|
||||||
|
|
||||||
const UpdateUserListCommand = domain.command({
|
const UpdateUserListCommand = domain.command({
|
||||||
name: 'Room.UpdateUserListCommand',
|
name: 'Room.UpdateUserListCommand',
|
||||||
impl: ({ get }, action: { type: 'create' | 'delete'; user: RoomUser }) => {
|
impl: ({ get }, action: { type: 'create' | 'delete'; user: Omit<RoomUser, 'peerIds'> & { peerId: string } }) => {
|
||||||
const userList = get(UserListState())
|
const userList = get(UserListState())
|
||||||
|
const existUser = userList.find((user) => user.userId === action.user.userId)
|
||||||
if (action.type === 'create') {
|
if (action.type === 'create') {
|
||||||
return [UserListState().new(upsert(userList, action.user, 'userId'))]
|
return [
|
||||||
|
UserListState().new(
|
||||||
|
upsert(
|
||||||
|
userList,
|
||||||
|
{ ...action.user, peerIds: [...(existUser?.peerIds || []), action.user.peerId] },
|
||||||
|
'userId'
|
||||||
|
)
|
||||||
|
)
|
||||||
|
]
|
||||||
} else {
|
} else {
|
||||||
return [UserListState().new(userList.filter(({ userId }) => userId !== action.user.userId))]
|
return [
|
||||||
|
UserListState().new(
|
||||||
|
upsert(
|
||||||
|
userList,
|
||||||
|
{
|
||||||
|
...action.user,
|
||||||
|
peerIds: existUser?.peerIds?.filter((peerId) => peerId !== action.user.peerId) || []
|
||||||
|
},
|
||||||
|
'userId'
|
||||||
|
).filter((user) => user.peerIds.length)
|
||||||
|
)
|
||||||
|
]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
@ -443,10 +473,18 @@ const RoomDomain = Remesh.domain({
|
||||||
name: 'Room.OnJoinRoomEvent'
|
name: 'Room.OnJoinRoomEvent'
|
||||||
})
|
})
|
||||||
|
|
||||||
|
const SelfJoinRoomEvent = domain.event<string>({
|
||||||
|
name: 'Room.SelfJoinRoomEvent'
|
||||||
|
})
|
||||||
|
|
||||||
const OnLeaveRoomEvent = domain.event<string>({
|
const OnLeaveRoomEvent = domain.event<string>({
|
||||||
name: 'Room.OnLeaveRoomEvent'
|
name: 'Room.OnLeaveRoomEvent'
|
||||||
})
|
})
|
||||||
|
|
||||||
|
const SelfLeaveRoomEvent = domain.event<string>({
|
||||||
|
name: 'Room.SelfLeaveRoomEvent'
|
||||||
|
})
|
||||||
|
|
||||||
const OnErrorEvent = domain.event<Error>({
|
const OnErrorEvent = domain.event<Error>({
|
||||||
name: 'Room.OnErrorEvent'
|
name: 'Room.OnErrorEvent'
|
||||||
})
|
})
|
||||||
|
@ -486,37 +524,33 @@ const RoomDomain = Remesh.domain({
|
||||||
const messageCommand$ = (() => {
|
const messageCommand$ = (() => {
|
||||||
switch (message.type) {
|
switch (message.type) {
|
||||||
case SendType.SyncUser: {
|
case SendType.SyncUser: {
|
||||||
const userList = get(UserListQuery())
|
|
||||||
const selfUser = get(SelfUserQuery())
|
const selfUser = get(SelfUserQuery())
|
||||||
// If the browser has multiple tabs open, it can cause the same user to join multiple times with the same peerId but different userId
|
|
||||||
const isRepeatJoin = userList.some((user) => user.userId === message.userId)
|
// If a new user joins after the current user has entered the room, a join log message needs to be created.
|
||||||
// When a new user joins, it triggers join events for all users, i.e., newUser join event and oldUser join event
|
const existUser = get(UserListQuery()).find((user) => user.userId === message.userId)
|
||||||
// Use joinTime to determine if it's a new user
|
const isNewJoinUser = !existUser && message.joinTime > selfUser.joinTime
|
||||||
const isNewJoinEvent = selfUser.joinTime < message.joinTime
|
|
||||||
|
|
||||||
const lastMessageTime = get(LastMessageTimeQuery())
|
const lastMessageTime = get(LastMessageTimeQuery())
|
||||||
const needSyncHistory = lastMessageTime > message.lastMessageTime
|
const needSyncHistory = lastMessageTime > message.lastMessageTime
|
||||||
|
|
||||||
return isRepeatJoin
|
return of(
|
||||||
? EMPTY
|
UpdateUserListCommand({ type: 'create', user: message }),
|
||||||
: of(
|
isNewJoinUser
|
||||||
UpdateUserListCommand({ type: 'create', user: message }),
|
? messageListDomain.command.CreateItemCommand({
|
||||||
isNewJoinEvent
|
...message,
|
||||||
? messageListDomain.command.CreateItemCommand({
|
id: nanoid(),
|
||||||
...message,
|
body: `"${message.username}" joined the chat`,
|
||||||
id: nanoid(),
|
type: MessageType.Prompt,
|
||||||
body: `"${message.username}" joined the chat`,
|
receiveTime: Date.now()
|
||||||
type: MessageType.Prompt,
|
})
|
||||||
receiveTime: Date.now()
|
: null,
|
||||||
})
|
needSyncHistory
|
||||||
: null,
|
? SendSyncHistoryMessageCommand({
|
||||||
needSyncHistory
|
peerId: message.peerId,
|
||||||
? SendSyncHistoryMessageCommand({
|
lastMessageTime: message.lastMessageTime
|
||||||
peerId: message.peerId,
|
})
|
||||||
lastMessageTime: message.lastMessageTime
|
: null
|
||||||
})
|
)
|
||||||
: null
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
case SendType.SyncHistory: {
|
case SendType.SyncHistory: {
|
||||||
|
@ -574,20 +608,26 @@ const RoomDomain = Remesh.domain({
|
||||||
impl: ({ get }) => {
|
impl: ({ get }) => {
|
||||||
const onLeaveRoom$ = fromEventPattern<string>(peerRoom.onLeaveRoom).pipe(
|
const onLeaveRoom$ = fromEventPattern<string>(peerRoom.onLeaveRoom).pipe(
|
||||||
map((peerId) => {
|
map((peerId) => {
|
||||||
|
if (get(JoinStatusModule.query.IsInitialQuery())) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
// console.log('onLeaveRoom', peerId)
|
// console.log('onLeaveRoom', peerId)
|
||||||
const user = get(UserListQuery()).find((user) => user.peerId === peerId)
|
|
||||||
|
|
||||||
if (user) {
|
const existUser = get(UserListQuery()).find((user) => user.peerIds.includes(peerId))
|
||||||
|
|
||||||
|
if (existUser) {
|
||||||
return [
|
return [
|
||||||
UpdateUserListCommand({ type: 'delete', user }),
|
UpdateUserListCommand({ type: 'delete', user: { ...existUser, peerId } }),
|
||||||
messageListDomain.command.CreateItemCommand({
|
existUser.peerIds.length === 1
|
||||||
...user,
|
? messageListDomain.command.CreateItemCommand({
|
||||||
id: nanoid(),
|
...existUser,
|
||||||
body: `"${user.username}" left the chat`,
|
id: nanoid(),
|
||||||
type: MessageType.Prompt,
|
body: `"${existUser.username}" left the chat`,
|
||||||
sendTime: Date.now(),
|
type: MessageType.Prompt,
|
||||||
receiveTime: Date.now()
|
sendTime: Date.now(),
|
||||||
}),
|
receiveTime: Date.now()
|
||||||
|
})
|
||||||
|
: null,
|
||||||
OnLeaveRoomEvent(peerId)
|
OnLeaveRoomEvent(peerId)
|
||||||
]
|
]
|
||||||
} else {
|
} else {
|
||||||
|
@ -612,7 +652,6 @@ const RoomDomain = Remesh.domain({
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
// TODO: Move this to a service worker in the future, so we don't need to send a leave room message every time the page refreshes
|
|
||||||
domain.effect({
|
domain.effect({
|
||||||
name: 'Room.OnUnloadEffect',
|
name: 'Room.OnUnloadEffect',
|
||||||
impl: ({ get }) => {
|
impl: ({ get }) => {
|
||||||
|
@ -647,7 +686,9 @@ const RoomDomain = Remesh.domain({
|
||||||
SendSyncUserMessageEvent,
|
SendSyncUserMessageEvent,
|
||||||
SendSyncHistoryMessageEvent,
|
SendSyncHistoryMessageEvent,
|
||||||
JoinRoomEvent,
|
JoinRoomEvent,
|
||||||
|
SelfJoinRoomEvent,
|
||||||
LeaveRoomEvent,
|
LeaveRoomEvent,
|
||||||
|
SelfLeaveRoomEvent,
|
||||||
OnMessageEvent,
|
OnMessageEvent,
|
||||||
OnTextMessageEvent,
|
OnTextMessageEvent,
|
||||||
OnJoinRoomEvent,
|
OnJoinRoomEvent,
|
||||||
|
|
|
@ -8,6 +8,18 @@ const ToastDomain = Remesh.domain({
|
||||||
impl: (domain) => {
|
impl: (domain) => {
|
||||||
const roomDomain = domain.getDomain(RoomDomain())
|
const roomDomain = domain.getDomain(RoomDomain())
|
||||||
const toastModule = ToastModule(domain)
|
const toastModule = ToastModule(domain)
|
||||||
|
|
||||||
|
domain.effect({
|
||||||
|
name: 'Toast.OnRoomSelfJoinRoomEffect',
|
||||||
|
impl: ({ fromEvent }) => {
|
||||||
|
const onRoomJoin$ = fromEvent(roomDomain.event.SelfJoinRoomEvent).pipe(
|
||||||
|
map(() => toastModule.command.LoadingCommand('Connected to the chat.'))
|
||||||
|
)
|
||||||
|
|
||||||
|
return onRoomJoin$
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
domain.effect({
|
domain.effect({
|
||||||
name: 'Toast.OnRoomErrorEffect',
|
name: 'Toast.OnRoomErrorEffect',
|
||||||
impl: ({ fromEvent }) => {
|
impl: ({ fromEvent }) => {
|
||||||
|
|
|
@ -5,7 +5,7 @@ export interface PeerRoom {
|
||||||
readonly peerId: string
|
readonly peerId: string
|
||||||
readonly roomId: string
|
readonly roomId: string
|
||||||
joinRoom: () => PeerRoom
|
joinRoom: () => PeerRoom
|
||||||
sendMessage: (message: RoomMessage, id?: string) => PeerRoom
|
sendMessage: (message: RoomMessage, id?: string | string[]) => PeerRoom
|
||||||
onMessage: (callback: (message: RoomMessage) => void) => PeerRoom
|
onMessage: (callback: (message: RoomMessage) => void) => PeerRoom
|
||||||
leaveRoom: () => PeerRoom
|
leaveRoom: () => PeerRoom
|
||||||
onJoinRoom: (callback: (id: string) => void) => PeerRoom
|
onJoinRoom: (callback: (id: string) => void) => PeerRoom
|
||||||
|
|
|
@ -1,10 +1,12 @@
|
||||||
import { Remesh } from 'remesh'
|
import { Remesh } from 'remesh'
|
||||||
|
|
||||||
export interface Toast {
|
export interface Toast {
|
||||||
success: (message: string) => void
|
success: (message: string, duration?: number) => number | string
|
||||||
error: (message: string) => void
|
error: (message: string, duration?: number) => number | string
|
||||||
info: (message: string) => void
|
info: (message: string, duration?: number) => number | string
|
||||||
warning: (message: string) => void
|
warning: (message: string, duration?: number) => number | string
|
||||||
|
loading: (message: string, duration?: number) => number | string
|
||||||
|
cancel: (id: number | string) => number | string
|
||||||
}
|
}
|
||||||
|
|
||||||
export const ToastExtern = Remesh.extern<Toast>({
|
export const ToastExtern = Remesh.extern<Toast>({
|
||||||
|
@ -20,6 +22,12 @@ export const ToastExtern = Remesh.extern<Toast>({
|
||||||
},
|
},
|
||||||
warning: () => {
|
warning: () => {
|
||||||
throw new Error('"warning" not implemented.')
|
throw new Error('"warning" not implemented.')
|
||||||
|
},
|
||||||
|
loading: () => {
|
||||||
|
throw new Error('"loading" not implemented.')
|
||||||
|
},
|
||||||
|
cancel: () => {
|
||||||
|
throw new Error('"cancel" not implemented.')
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
|
@ -45,7 +45,7 @@ class PeerRoom extends EventHub {
|
||||||
return this
|
return this
|
||||||
}
|
}
|
||||||
|
|
||||||
sendMessage(message: RoomMessage, id?: string) {
|
sendMessage(message: RoomMessage, id?: string | string[]) {
|
||||||
if (!this.room) {
|
if (!this.room) {
|
||||||
this.once('action', () => {
|
this.once('action', () => {
|
||||||
if (!this.room) {
|
if (!this.room) {
|
||||||
|
|
|
@ -46,7 +46,7 @@ class PeerRoom extends EventHub {
|
||||||
return this
|
return this
|
||||||
}
|
}
|
||||||
|
|
||||||
sendMessage(message: RoomMessage, id?: string) {
|
sendMessage(message: RoomMessage, id?: string | string[]) {
|
||||||
if (!this.room) {
|
if (!this.room) {
|
||||||
this.once('action', () => {
|
this.once('action', () => {
|
||||||
if (!this.room) {
|
if (!this.room) {
|
||||||
|
|
|
@ -2,16 +2,24 @@ import { toast } from 'sonner'
|
||||||
import { ToastExtern } from '@/domain/externs/Toast'
|
import { ToastExtern } from '@/domain/externs/Toast'
|
||||||
|
|
||||||
export const ToastImpl = ToastExtern.impl({
|
export const ToastImpl = ToastExtern.impl({
|
||||||
success: (message: string) => {
|
success: (message: string, duration: number = 4000) => {
|
||||||
toast.success(message)
|
return toast.success(message, { duration })
|
||||||
},
|
},
|
||||||
error: (message: string) => {
|
error: (message: string, duration: number = 4000) => {
|
||||||
toast.error(message)
|
return toast.error(message, { duration })
|
||||||
},
|
},
|
||||||
info: (message: string) => {
|
info: (message: string, duration: number = 4000) => {
|
||||||
toast.info(message)
|
return toast.info(message, { duration })
|
||||||
},
|
},
|
||||||
warning: (message: string) => {
|
warning: (message: string, duration: number = 4000) => {
|
||||||
toast.warning(message)
|
return toast.warning(message, { duration })
|
||||||
|
},
|
||||||
|
loading: (message: string, duration: number = 4000) => {
|
||||||
|
const id = toast.loading(message, { duration })
|
||||||
|
setTimeout(() => toast.dismiss(id), duration)
|
||||||
|
return id
|
||||||
|
},
|
||||||
|
cancel: (id: number | string) => {
|
||||||
|
return toast.dismiss(id)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
|
@ -8,51 +8,90 @@ export interface ToastOptions {
|
||||||
const ToastModule = (domain: RemeshDomainContext, options: ToastOptions = { name: 'MessageToastModule' }) => {
|
const ToastModule = (domain: RemeshDomainContext, options: ToastOptions = { name: 'MessageToastModule' }) => {
|
||||||
const toast = domain.getExtern(ToastExtern)
|
const toast = domain.getExtern(ToastExtern)
|
||||||
|
|
||||||
const SuccessEvent = domain.event({
|
const SuccessEvent = domain.event<number | string>({
|
||||||
name: `${options.name}.SuccessEvent`
|
name: `${options.name}.SuccessEvent`
|
||||||
})
|
})
|
||||||
|
|
||||||
const SuccessCommand = domain.command({
|
const SuccessCommand = domain.command({
|
||||||
name: `${options.name}.SuccessCommand`,
|
name: `${options.name}.SuccessCommand`,
|
||||||
impl: (_, message: string) => {
|
impl: (_, message: string | { message: string; duration?: number }) => {
|
||||||
toast.success(message)
|
const id = toast.success(
|
||||||
return [SuccessEvent()]
|
typeof message === 'string' ? message : message.message,
|
||||||
|
typeof message === 'string' ? undefined : message.duration
|
||||||
|
)
|
||||||
|
return [SuccessEvent(id)]
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
const ErrorEvent = domain.event({
|
const ErrorEvent = domain.event<number | string>({
|
||||||
name: `${options.name}.ErrorEvent`
|
name: `${options.name}.ErrorEvent`
|
||||||
})
|
})
|
||||||
|
|
||||||
const ErrorCommand = domain.command({
|
const ErrorCommand = domain.command({
|
||||||
name: `${options.name}.ErrorCommand`,
|
name: `${options.name}.ErrorCommand`,
|
||||||
impl: (_, message: string) => {
|
impl: (_, message: string | { message: string; duration?: number }) => {
|
||||||
toast.error(message)
|
const id = toast.error(
|
||||||
return [ErrorEvent()]
|
typeof message === 'string' ? message : message.message,
|
||||||
|
typeof message === 'string' ? undefined : message.duration
|
||||||
|
)
|
||||||
|
return [ErrorEvent(id)]
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
const InfoEvent = domain.event({
|
const InfoEvent = domain.event<number | string>({
|
||||||
name: `${options.name}.InfoEvent`
|
name: `${options.name}.InfoEvent`
|
||||||
})
|
})
|
||||||
|
|
||||||
const InfoCommand = domain.command({
|
const InfoCommand = domain.command({
|
||||||
name: `${options.name}.InfoCommand`,
|
name: `${options.name}.InfoCommand`,
|
||||||
impl: (_, message: string) => {
|
impl: (_, message: string | { message: string; duration?: number }) => {
|
||||||
toast.info(message)
|
const id = toast.info(
|
||||||
return [InfoEvent()]
|
typeof message === 'string' ? message : message.message,
|
||||||
|
typeof message === 'string' ? undefined : message.duration
|
||||||
|
)
|
||||||
|
return [InfoEvent(id)]
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
const WarningEvent = domain.event({
|
const WarningEvent = domain.event<number | string>({
|
||||||
name: `${options.name}.WarningEvent`
|
name: `${options.name}.WarningEvent`
|
||||||
})
|
})
|
||||||
|
|
||||||
const WarningCommand = domain.command({
|
const WarningCommand = domain.command({
|
||||||
name: `${options.name}.WarningCommand`,
|
name: `${options.name}.WarningCommand`,
|
||||||
impl: (_, message: string) => {
|
impl: (_, message: string | { message: string; duration?: number }) => {
|
||||||
toast.warning(message)
|
const id = toast.warning(
|
||||||
return [WarningEvent()]
|
typeof message === 'string' ? message : message.message,
|
||||||
|
typeof message === 'string' ? undefined : message.duration
|
||||||
|
)
|
||||||
|
return [WarningEvent(id)]
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
const LoadingEvent = domain.event<number | string>({
|
||||||
|
name: `${options.name}.LoadingEvent`
|
||||||
|
})
|
||||||
|
|
||||||
|
const LoadingCommand = domain.command({
|
||||||
|
name: `${options.name}.LoadingCommand`,
|
||||||
|
impl: (_, message: string | { message: string; duration?: number }) => {
|
||||||
|
const id = toast.loading(
|
||||||
|
typeof message === 'string' ? message : message.message,
|
||||||
|
typeof message === 'string' ? undefined : message.duration
|
||||||
|
)
|
||||||
|
return [LoadingEvent(id)]
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
const CancelEvent = domain.event<number | string>({
|
||||||
|
name: `${options.name}.CancelEvent`
|
||||||
|
})
|
||||||
|
|
||||||
|
const CancelCommand = domain.command({
|
||||||
|
name: `${options.name}.CancelCommand`,
|
||||||
|
impl: (_, id: number | string) => {
|
||||||
|
toast.cancel(id)
|
||||||
|
return [CancelEvent(id)]
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
@ -61,13 +100,17 @@ const ToastModule = (domain: RemeshDomainContext, options: ToastOptions = { name
|
||||||
SuccessEvent,
|
SuccessEvent,
|
||||||
ErrorEvent,
|
ErrorEvent,
|
||||||
InfoEvent,
|
InfoEvent,
|
||||||
WarningEvent
|
WarningEvent,
|
||||||
|
LoadingEvent,
|
||||||
|
CancelEvent
|
||||||
},
|
},
|
||||||
command: {
|
command: {
|
||||||
SuccessCommand,
|
SuccessCommand,
|
||||||
ErrorCommand,
|
ErrorCommand,
|
||||||
InfoCommand,
|
InfoCommand,
|
||||||
WarningCommand
|
WarningCommand,
|
||||||
|
LoadingCommand,
|
||||||
|
CancelCommand
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue