mirror of
https://github.com/jeffvli/feishin.git
synced 2026-05-10 04:30:25 +02:00
Add user activity
This commit is contained in:
+1
-1
@@ -53,6 +53,6 @@ const io = new socketio.Server(server, {
|
|||||||
});
|
});
|
||||||
|
|
||||||
app.set('socketio', io);
|
app.set('socketio', io);
|
||||||
io.on('connection', (socket) => sockets(socket));
|
io.on('connection', (socket) => sockets(socket, io));
|
||||||
|
|
||||||
server.listen(9321, () => console.log(`Listening on port ${PORT}`));
|
server.listen(9321, () => console.log(`Listening on port ${PORT}`));
|
||||||
|
|||||||
+45
-9
@@ -1,15 +1,51 @@
|
|||||||
import { Socket } from 'socket.io';
|
import { Socket, Server } from 'socket.io';
|
||||||
|
|
||||||
export const sockets = (socket: Socket) => {
|
export const sockets = (socket: Socket, io: Server) => {
|
||||||
socket.broadcast.emit('user:connected', {
|
socket.on('join', function (data) {
|
||||||
userID: socket.id,
|
socket.join(data.id); // We are using room of socket io
|
||||||
username: socket.handshake.query.username,
|
|
||||||
});
|
});
|
||||||
|
|
||||||
socket.on('disconnect', () => {
|
socket.broadcast.emit('user:receive:connect', {
|
||||||
socket.broadcast.emit('user:disconnected', {
|
socketId: socket.id,
|
||||||
userID: socket.id,
|
userId: socket.handshake.query.id,
|
||||||
username: socket.handshake.query.username,
|
userName: socket.handshake.query.username,
|
||||||
|
});
|
||||||
|
|
||||||
|
socket.on('disconnect', async () => {
|
||||||
|
socket.broadcast.emit('user:receive:disconnect', {
|
||||||
|
socketId: socket.id,
|
||||||
|
userId: socket.handshake.query.id,
|
||||||
|
userName: socket.handshake.query.username,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
socket.on('user:send:get_online', async (data) => {
|
||||||
|
const sockets = await io.fetchSockets();
|
||||||
|
const onlineSockets = sockets?.map((s) => s.handshake.query.id) || [];
|
||||||
|
|
||||||
|
io.sockets
|
||||||
|
.in(data?.userId)
|
||||||
|
.emit('user:receive:get_online', { online: onlineSockets });
|
||||||
|
});
|
||||||
|
|
||||||
|
socket.on('user:send:change_song', async (data) => {
|
||||||
|
socket.broadcast.emit('user:receive:change_song', {
|
||||||
|
...data,
|
||||||
|
user: { ...data.user, socketId: socket.id },
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
socket.on('user:send:status_idle', async (data) => {
|
||||||
|
socket.broadcast.emit('user:receive:status_idle', {
|
||||||
|
status: 'idle',
|
||||||
|
user: { ...data.user, socketId: socket.id },
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
socket.on('user:send:status_playing', async (data) => {
|
||||||
|
socket.broadcast.emit('user:receive:status_playing', {
|
||||||
|
status: 'playing',
|
||||||
|
user: { ...data.user, socketId: socket.id },
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -14,11 +14,13 @@ import {
|
|||||||
RiMusicLine,
|
RiMusicLine,
|
||||||
RiPlayListLine,
|
RiPlayListLine,
|
||||||
RiSearchLine,
|
RiSearchLine,
|
||||||
|
RiUser3Line,
|
||||||
RiUserVoiceLine,
|
RiUserVoiceLine,
|
||||||
} from 'react-icons/ri';
|
} from 'react-icons/ri';
|
||||||
import { useNavigate, Link } from 'react-router-dom';
|
import { useNavigate, Link } from 'react-router-dom';
|
||||||
import styled from 'styled-components';
|
import styled from 'styled-components';
|
||||||
import { Button, TextInput } from '@/renderer/components';
|
import { Button, TextInput } from '@/renderer/components';
|
||||||
|
import { UserActivity } from '@/renderer/features/users';
|
||||||
import { AppRoute } from '@/renderer/router/routes';
|
import { AppRoute } from '@/renderer/router/routes';
|
||||||
import { useAppStore, usePlayerStore } from '@/renderer/store';
|
import { useAppStore, usePlayerStore } from '@/renderer/store';
|
||||||
import { fadeIn } from '@/renderer/styles';
|
import { fadeIn } from '@/renderer/styles';
|
||||||
@@ -119,8 +121,10 @@ export const Sidebar = () => {
|
|||||||
</SidebarItem.Link>
|
</SidebarItem.Link>
|
||||||
</SidebarItem>
|
</SidebarItem>
|
||||||
<Accordion
|
<Accordion
|
||||||
disableChevronRotation
|
|
||||||
multiple
|
multiple
|
||||||
|
styles={{
|
||||||
|
item: { borderBottom: 'none' },
|
||||||
|
}}
|
||||||
value={sidebar.expanded}
|
value={sidebar.expanded}
|
||||||
onChange={(e) => setSidebar({ expanded: e })}
|
onChange={(e) => setSidebar({ expanded: e })}
|
||||||
>
|
>
|
||||||
@@ -161,7 +165,7 @@ export const Sidebar = () => {
|
|||||||
<Accordion.Item value="collections">
|
<Accordion.Item value="collections">
|
||||||
<Accordion.Control disabled p="1rem">
|
<Accordion.Control disabled p="1rem">
|
||||||
<Group>
|
<Group>
|
||||||
<RiPlayListLine size={20} />
|
<RiPlayListLine size={15} />
|
||||||
Collections
|
Collections
|
||||||
</Group>
|
</Group>
|
||||||
</Accordion.Control>
|
</Accordion.Control>
|
||||||
@@ -170,12 +174,25 @@ export const Sidebar = () => {
|
|||||||
<Accordion.Item value="playlists">
|
<Accordion.Item value="playlists">
|
||||||
<Accordion.Control disabled p="1rem">
|
<Accordion.Control disabled p="1rem">
|
||||||
<Group>
|
<Group>
|
||||||
<RiPlayListLine size={20} />
|
<RiPlayListLine size={15} />
|
||||||
Playlists
|
Playlists
|
||||||
</Group>
|
</Group>
|
||||||
</Accordion.Control>
|
</Accordion.Control>
|
||||||
<Accordion.Panel />
|
<Accordion.Panel />
|
||||||
</Accordion.Item>
|
</Accordion.Item>
|
||||||
|
<Accordion.Item value="activity">
|
||||||
|
<Accordion.Control p="1rem">
|
||||||
|
<Group>
|
||||||
|
<RiUser3Line size={15} />
|
||||||
|
User Activity
|
||||||
|
</Group>
|
||||||
|
</Accordion.Control>
|
||||||
|
<Accordion.Panel>
|
||||||
|
<div>
|
||||||
|
<UserActivity />
|
||||||
|
</div>
|
||||||
|
</Accordion.Panel>
|
||||||
|
</Accordion.Item>
|
||||||
</Accordion>
|
</Accordion>
|
||||||
</Stack>
|
</Stack>
|
||||||
</Stack>
|
</Stack>
|
||||||
|
|||||||
@@ -0,0 +1,178 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import { Avatar, Group, Indicator, Stack } from '@mantine/core';
|
||||||
|
import { useDisclosure } from '@mantine/hooks';
|
||||||
|
import { motion } from 'framer-motion';
|
||||||
|
import { IoIosPause } from 'react-icons/io';
|
||||||
|
import { RiPlayFill, RiServerLine, RiUserLine } from 'react-icons/ri';
|
||||||
|
import { generatePath } from 'react-router';
|
||||||
|
import { Link } from 'react-router-dom';
|
||||||
|
import styled from 'styled-components';
|
||||||
|
import { User } from '@/renderer/api/types';
|
||||||
|
import { Popover, Text } from '@/renderer/components';
|
||||||
|
import { useServerMap } from '@/renderer/features/servers';
|
||||||
|
import { AppRoute } from '@/renderer/router/routes';
|
||||||
|
import { titleCase } from '../../../utils/title-case';
|
||||||
|
|
||||||
|
export type Activity = {
|
||||||
|
socketId?: string;
|
||||||
|
song?: {
|
||||||
|
album?: string;
|
||||||
|
albumArtists: { id: string; name: string }[];
|
||||||
|
albumId?: string;
|
||||||
|
id: string;
|
||||||
|
name: string;
|
||||||
|
serverId: string;
|
||||||
|
};
|
||||||
|
status?: 'playing' | 'idle' | 'offline';
|
||||||
|
};
|
||||||
|
|
||||||
|
export type UserWithActivity = User & {
|
||||||
|
activity?: Activity;
|
||||||
|
avatarUrl?: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
interface UserActivityItemProps {
|
||||||
|
user: UserWithActivity;
|
||||||
|
}
|
||||||
|
|
||||||
|
const ActivityContainer = styled(motion.div)`
|
||||||
|
padding: 0.5rem;
|
||||||
|
`;
|
||||||
|
|
||||||
|
const ItemGrid = styled.div`
|
||||||
|
display: grid;
|
||||||
|
grid-auto-columns: 1fr;
|
||||||
|
grid-template-areas: 'image info';
|
||||||
|
grid-template-rows: 1fr;
|
||||||
|
grid-template-columns: 50px minmax(0, 1fr);
|
||||||
|
`;
|
||||||
|
|
||||||
|
const ItemImageContainer = styled.div`
|
||||||
|
display: flex;
|
||||||
|
grid-area: image;
|
||||||
|
align-items: center;
|
||||||
|
`;
|
||||||
|
|
||||||
|
const ItemInfoContainer = styled.div`
|
||||||
|
grid-area: info;
|
||||||
|
`;
|
||||||
|
|
||||||
|
export const UserActivityItem = ({ user }: UserActivityItemProps) => {
|
||||||
|
const { data: serverMap } = useServerMap();
|
||||||
|
const [opened, { close, open }] = useDisclosure(false);
|
||||||
|
|
||||||
|
const displayedName = user?.displayName
|
||||||
|
? `${user.displayName} (${user.username})`
|
||||||
|
: user.username;
|
||||||
|
const songName = user?.activity?.song?.name;
|
||||||
|
const songId = user?.activity?.song?.id;
|
||||||
|
const albumId = user?.activity?.song?.albumId;
|
||||||
|
const albumName = user?.activity?.song?.album;
|
||||||
|
const serverId = user?.activity?.song?.serverId;
|
||||||
|
const status = user?.activity?.status;
|
||||||
|
const albumArtists = user?.activity?.song?.albumArtists;
|
||||||
|
|
||||||
|
console.log('serverMap', serverMap);
|
||||||
|
|
||||||
|
console.log('serverId', serverId);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<ActivityContainer>
|
||||||
|
<ItemGrid>
|
||||||
|
<ItemImageContainer>
|
||||||
|
<Popover opened={opened} position="top-start">
|
||||||
|
<Popover.Target>
|
||||||
|
<Indicator
|
||||||
|
color="green"
|
||||||
|
offset={5}
|
||||||
|
position="bottom-end"
|
||||||
|
onMouseEnter={open}
|
||||||
|
onMouseLeave={close}
|
||||||
|
>
|
||||||
|
<Avatar radius="xl" size={40} src={user?.avatarUrl} />
|
||||||
|
</Indicator>
|
||||||
|
</Popover.Target>
|
||||||
|
<Popover.Dropdown>
|
||||||
|
<Stack spacing={5}>
|
||||||
|
<Group />
|
||||||
|
{serverId && <Group />}
|
||||||
|
</Stack>
|
||||||
|
<Stack spacing={5}>
|
||||||
|
<Group>
|
||||||
|
<RiUserLine /> {displayedName}
|
||||||
|
</Group>
|
||||||
|
{serverId && (
|
||||||
|
<Group>
|
||||||
|
<RiServerLine /> {serverMap?.data[serverId]?.name} (
|
||||||
|
{titleCase(serverMap?.data[serverId]?.type || '')})
|
||||||
|
</Group>
|
||||||
|
)}
|
||||||
|
</Stack>
|
||||||
|
</Popover.Dropdown>
|
||||||
|
</Popover>
|
||||||
|
</ItemImageContainer>
|
||||||
|
<ItemInfoContainer>
|
||||||
|
<Group noWrap position="apart">
|
||||||
|
<Stack spacing={0} sx={{ lineHeight: 1, maxWidth: '80%' }}>
|
||||||
|
<Text overflow="hidden" size="sm">
|
||||||
|
{songId ? songName : 'Idle...'}
|
||||||
|
</Text>
|
||||||
|
<Text $secondary overflow="hidden" size="xs">
|
||||||
|
{albumArtists?.length ? (
|
||||||
|
albumArtists.map((artist, index) => (
|
||||||
|
<React.Fragment
|
||||||
|
key={`activity-${user.id}-artist-${artist.id}`}
|
||||||
|
>
|
||||||
|
{index > 0 ? ', ' : null}
|
||||||
|
<Text
|
||||||
|
$link
|
||||||
|
$secondary
|
||||||
|
component={Link}
|
||||||
|
overflow="hidden"
|
||||||
|
size="xs"
|
||||||
|
sx={{ width: 'fit-content' }}
|
||||||
|
to={generatePath(AppRoute.LIBRARY_ALBUMARTISTS_DETAIL, {
|
||||||
|
albumArtistId: artist.id,
|
||||||
|
})}
|
||||||
|
>
|
||||||
|
{artist.name}
|
||||||
|
</Text>
|
||||||
|
</React.Fragment>
|
||||||
|
))
|
||||||
|
) : (
|
||||||
|
<Text $secondary>—</Text>
|
||||||
|
)}
|
||||||
|
</Text>
|
||||||
|
{albumId ? (
|
||||||
|
<Text
|
||||||
|
$link
|
||||||
|
$secondary
|
||||||
|
component={Link}
|
||||||
|
overflow="hidden"
|
||||||
|
size="xs"
|
||||||
|
sx={{ width: 'fit-content' }}
|
||||||
|
to={generatePath(AppRoute.LIBRARY_ALBUMS_DETAIL, {
|
||||||
|
albumId,
|
||||||
|
})}
|
||||||
|
>
|
||||||
|
{albumName}
|
||||||
|
</Text>
|
||||||
|
) : (
|
||||||
|
<Text $secondary overflow="hidden" size="xs">
|
||||||
|
—
|
||||||
|
</Text>
|
||||||
|
)}
|
||||||
|
</Stack>
|
||||||
|
<Group>
|
||||||
|
{status === 'playing' ? (
|
||||||
|
<RiPlayFill color="var(--main-fg)" size={15} />
|
||||||
|
) : (
|
||||||
|
<IoIosPause color="var(--main-fg)" size={15} />
|
||||||
|
)}
|
||||||
|
</Group>
|
||||||
|
</Group>
|
||||||
|
</ItemInfoContainer>
|
||||||
|
</ItemGrid>
|
||||||
|
</ActivityContainer>
|
||||||
|
);
|
||||||
|
};
|
||||||
@@ -0,0 +1,268 @@
|
|||||||
|
import { useCallback, useEffect, useMemo, useState } from 'react';
|
||||||
|
import { motion } from 'framer-motion';
|
||||||
|
import merge from 'lodash/merge';
|
||||||
|
import sortBy from 'lodash/sortBy';
|
||||||
|
import styled from 'styled-components';
|
||||||
|
import { socket } from '@/renderer/api';
|
||||||
|
import { UserListResponse } from '@/renderer/api/users.api';
|
||||||
|
import {
|
||||||
|
Activity,
|
||||||
|
UserActivityItem,
|
||||||
|
UserWithActivity,
|
||||||
|
} from '@/renderer/features/users/components/user-activity-item';
|
||||||
|
import { useUserList } from '@/renderer/features/users/queries/get-user-list';
|
||||||
|
import { useAuthStore, usePlayerStore } from '@/renderer/store';
|
||||||
|
import { PlayerStatus } from '@/renderer/types';
|
||||||
|
|
||||||
|
const UserActivityContainer = styled(motion.div)`
|
||||||
|
min-height: 10rem;
|
||||||
|
overflow-x: hidden;
|
||||||
|
`;
|
||||||
|
|
||||||
|
type UserConnectionEvent = {
|
||||||
|
online?: string[];
|
||||||
|
socketId: string;
|
||||||
|
userId: string;
|
||||||
|
userName: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
type SongChangeEvent = {
|
||||||
|
song: Activity['song'];
|
||||||
|
user: UserConnectionEvent;
|
||||||
|
};
|
||||||
|
|
||||||
|
type CheckOnlineEvent = {
|
||||||
|
online: string[];
|
||||||
|
};
|
||||||
|
|
||||||
|
type PlayStatusChangeEvent = {
|
||||||
|
status: Activity['status'];
|
||||||
|
user: UserConnectionEvent;
|
||||||
|
};
|
||||||
|
|
||||||
|
const sortByName = (users: UserWithActivity[]) => {
|
||||||
|
return sortBy(users, [
|
||||||
|
(user) => user.displayName?.toLowerCase(),
|
||||||
|
(user) => user.username.toLowerCase(),
|
||||||
|
]);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const UserActivity = () => {
|
||||||
|
const currentSong = usePlayerStore((state) => state.current.song);
|
||||||
|
const currentUser = useAuthStore((state) => state.permissions);
|
||||||
|
const playStatus = usePlayerStore((state) => state.current.status);
|
||||||
|
const [activityList, setActivityList] = useState<UserWithActivity[]>([]);
|
||||||
|
|
||||||
|
const userDetails = useMemo(
|
||||||
|
() => ({ userId: currentUser?.id, userName: currentUser?.username }),
|
||||||
|
[currentUser?.id, currentUser?.username]
|
||||||
|
);
|
||||||
|
|
||||||
|
useUserList({
|
||||||
|
onSuccess: async (data: UserListResponse) => {
|
||||||
|
const userList = data.data.filter((user) => user.id !== currentUser?.id);
|
||||||
|
setActivityList((prev) => {
|
||||||
|
const newList = userList.map((user) => {
|
||||||
|
const existingUser = prev.find((u) => u.id === user.id);
|
||||||
|
return merge({}, existingUser, user);
|
||||||
|
});
|
||||||
|
return sortByName(newList);
|
||||||
|
});
|
||||||
|
|
||||||
|
if (userDetails) {
|
||||||
|
socket.emit('user:send:get_online', userDetails);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
staleTime: 0,
|
||||||
|
});
|
||||||
|
|
||||||
|
const handleGetOnlineUsers = useCallback((data: CheckOnlineEvent) => {
|
||||||
|
setActivityList((prev) => {
|
||||||
|
const updatedUsers = prev.map((user) => {
|
||||||
|
if (data.online.includes(user.id)) {
|
||||||
|
return {
|
||||||
|
...user,
|
||||||
|
activity: {
|
||||||
|
...user.activity,
|
||||||
|
status: 'idle' as Activity['status'],
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
...user,
|
||||||
|
activity: {
|
||||||
|
...user.activity,
|
||||||
|
status: 'offline' as Activity['status'],
|
||||||
|
},
|
||||||
|
};
|
||||||
|
});
|
||||||
|
return sortByName(updatedUsers);
|
||||||
|
});
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const handleUserConnect = useCallback((data: UserConnectionEvent) => {
|
||||||
|
setActivityList((prev) => {
|
||||||
|
const user = prev.find((user) => user.id === data.userId);
|
||||||
|
if (!user) return prev;
|
||||||
|
|
||||||
|
return sortByName([
|
||||||
|
...prev.filter((user) => user.id !== data.userId),
|
||||||
|
{
|
||||||
|
...user,
|
||||||
|
activity: {
|
||||||
|
...user?.activity,
|
||||||
|
socketId: data.socketId,
|
||||||
|
status: 'idle',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const handleUserDisconnect = useCallback((data: UserConnectionEvent) => {
|
||||||
|
setActivityList((prev) => {
|
||||||
|
const user = prev.find((user) => user.id === data.userId);
|
||||||
|
if (!user) return prev;
|
||||||
|
|
||||||
|
return sortByName([
|
||||||
|
...prev.filter((user) => user.id !== data.userId),
|
||||||
|
{
|
||||||
|
...user,
|
||||||
|
activity: {
|
||||||
|
...user?.activity,
|
||||||
|
socketId: undefined,
|
||||||
|
song: undefined,
|
||||||
|
status: 'offline',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const handleUserSongChange = useCallback((data: SongChangeEvent) => {
|
||||||
|
setActivityList((prev) => {
|
||||||
|
const user = prev.find((user) => user.id === data.user.userId);
|
||||||
|
if (!user) return prev;
|
||||||
|
|
||||||
|
const shouldUpdateStatus =
|
||||||
|
!user?.activity?.status || user?.activity?.status === 'offline';
|
||||||
|
|
||||||
|
return sortByName([
|
||||||
|
...prev.filter((user) => user.id !== data.user.userId),
|
||||||
|
{
|
||||||
|
...user,
|
||||||
|
activity: {
|
||||||
|
...user.activity,
|
||||||
|
socketId: data.user.socketId,
|
||||||
|
song: data.song,
|
||||||
|
status: shouldUpdateStatus ? 'playing' : user?.activity?.status,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const handleUserStatusChange = useCallback((data: PlayStatusChangeEvent) => {
|
||||||
|
console.log('data', data);
|
||||||
|
setActivityList((prev) => {
|
||||||
|
const user = prev.find((user) => user.id === data.user.userId);
|
||||||
|
if (!user) return prev;
|
||||||
|
|
||||||
|
console.log('data.status', data.status);
|
||||||
|
|
||||||
|
return sortByName([
|
||||||
|
...prev.filter((user) => user.id !== data.user.userId),
|
||||||
|
{
|
||||||
|
...user,
|
||||||
|
activity: {
|
||||||
|
...user.activity,
|
||||||
|
socketId: data.user.socketId,
|
||||||
|
status: data.status,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (currentSong) {
|
||||||
|
const currentSongDetails: Activity['song'] = {
|
||||||
|
album: currentSong?.album?.name,
|
||||||
|
albumArtists: currentSong?.album?.albumArtists.map((artist) => ({
|
||||||
|
id: artist.id,
|
||||||
|
name: artist.name,
|
||||||
|
})),
|
||||||
|
albumId: currentSong?.album?.id,
|
||||||
|
id: currentSong?.id,
|
||||||
|
name: currentSong?.name,
|
||||||
|
serverId: currentSong?.serverId,
|
||||||
|
};
|
||||||
|
|
||||||
|
socket.emit('user:send:change_song', {
|
||||||
|
song: currentSongDetails,
|
||||||
|
user: userDetails,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}, [currentSong, playStatus, userDetails]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (playStatus === PlayerStatus.PAUSED) {
|
||||||
|
socket.emit('user:send:status_idle', { user: userDetails });
|
||||||
|
} else {
|
||||||
|
socket.emit('user:send:status_playing', { user: userDetails });
|
||||||
|
}
|
||||||
|
}, [playStatus, userDetails]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
socket.on('user:receive:connect', (data: UserConnectionEvent) => {
|
||||||
|
handleUserConnect(data);
|
||||||
|
});
|
||||||
|
|
||||||
|
socket.on('user:receive:disconnect', (data: UserConnectionEvent) => {
|
||||||
|
handleUserDisconnect(data);
|
||||||
|
});
|
||||||
|
|
||||||
|
socket.on('user:receive:change_song', (data: SongChangeEvent) => {
|
||||||
|
handleUserSongChange(data);
|
||||||
|
});
|
||||||
|
|
||||||
|
socket.on('user:receive:status_idle', (data: PlayStatusChangeEvent) => {
|
||||||
|
handleUserStatusChange(data);
|
||||||
|
});
|
||||||
|
|
||||||
|
socket.on('user:receive:status_playing', (data: PlayStatusChangeEvent) => {
|
||||||
|
handleUserStatusChange(data);
|
||||||
|
});
|
||||||
|
|
||||||
|
socket.on('user:receive:get_online', (data: CheckOnlineEvent) => {
|
||||||
|
handleGetOnlineUsers(data);
|
||||||
|
});
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
socket.off('user:receive:connect');
|
||||||
|
socket.off('user:recieve:disconnect');
|
||||||
|
socket.off('user:receive:change_song');
|
||||||
|
socket.off('user:receive:status_idle');
|
||||||
|
socket.off('user:receive:status_playing');
|
||||||
|
socket.off('user:receive:get_online');
|
||||||
|
};
|
||||||
|
}, [
|
||||||
|
handleGetOnlineUsers,
|
||||||
|
handleUserConnect,
|
||||||
|
handleUserDisconnect,
|
||||||
|
handleUserSongChange,
|
||||||
|
handleUserStatusChange,
|
||||||
|
]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<UserActivityContainer>
|
||||||
|
{activityList
|
||||||
|
.filter(
|
||||||
|
(user) => user.activity?.status && user.activity?.status !== 'offline'
|
||||||
|
)
|
||||||
|
.map((user) => (
|
||||||
|
<UserActivityItem key={`activity-${user.id}`} user={user} />
|
||||||
|
))}
|
||||||
|
</UserActivityContainer>
|
||||||
|
);
|
||||||
|
};
|
||||||
@@ -3,5 +3,7 @@ export * from './mutations/delete-user';
|
|||||||
export * from './mutations/update-user';
|
export * from './mutations/update-user';
|
||||||
export * from './components/add-user-form';
|
export * from './components/add-user-form';
|
||||||
export * from './components/user-list';
|
export * from './components/user-list';
|
||||||
|
export * from './components/user-activity';
|
||||||
|
export * from './components/user-activity-item';
|
||||||
export * from './queries/get-user-detail';
|
export * from './queries/get-user-detail';
|
||||||
export * from './queries/get-user-list';
|
export * from './queries/get-user-list';
|
||||||
|
|||||||
Reference in New Issue
Block a user