feat: favorites

This commit is contained in:
cha0s 2020-07-18 22:48:11 -05:00
parent d29a98bef5
commit c369846fa8
10 changed files with 114 additions and 40 deletions

View File

@ -3,7 +3,13 @@ import './chat--leftRooms.scss';
import React from 'react'; import React from 'react';
import {useDispatch, useSelector} from 'react-redux'; import {useDispatch, useSelector} from 'react-redux';
import {favoriteChannelsSelector, recentSelector, removeRecent} from '~/common/state/user'; import {
favoriteChannelsSelector,
recentSelector,
removeRecent,
submitAddFavorite,
submitRemoveFavorite,
} from '~/common/state/user';
import Channel from './channel'; import Channel from './channel';
@ -12,13 +18,6 @@ export default function ChatLeftRooms() {
const favorites = useSelector(favoriteChannelsSelector); const favorites = useSelector(favoriteChannelsSelector);
const recent = useSelector(recentSelector) const recent = useSelector(recentSelector)
.filter((channel) => -1 === favorites.indexOf(channel)); .filter((channel) => -1 === favorites.indexOf(channel));
const favoritesActions = [
{
icon: '💔',
label: 'Unfavorite',
onClick: () => {},
},
];
return ( return (
<div <div
className="chat--leftRooms" className="chat--leftRooms"
@ -35,18 +34,27 @@ export default function ChatLeftRooms() {
<> <>
<h2 className="channels__chatsTitle">Favorites</h2> <h2 className="channels__chatsTitle">Favorites</h2>
<ul className="channels__chatsList"> <ul className="channels__chatsList">
{favorites.map((channel) => ( {favorites.map((channel) => {
<li const favoritesActions = [
className="channels__item" {
> icon: '💔',
<Channel label: 'Unfavorite',
actions={favoritesActions} onClick: () => dispatch(submitRemoveFavorite(`/r/${channel}`)),
href={`/chat/r/${channel}`} },
name={channel} ];
prefix="/r/" return (
/> <li
</li> className="channels__item"
))} >
<Channel
actions={favoritesActions}
href={`/chat/r/${channel}`}
name={channel}
prefix="/r/"
/>
</li>
);
})}
</ul> </ul>
</> </>
)} )}
@ -59,7 +67,7 @@ export default function ChatLeftRooms() {
{ {
icon: '❤️', icon: '❤️',
label: 'Favorite', label: 'Favorite',
onClick: () => {}, onClick: () => dispatch(submitAddFavorite(`/r/${channel}`)),
}, },
{ {
icon: '×', icon: '×',

View File

@ -12,6 +12,14 @@ import {
submitMessage, submitMessage,
} from '~/common/state/chat'; } from '~/common/state/chat';
import AddFavorite from '../../common/packets/add-favorite.packet';
import RemoveFavorite from '../../common/packets/remove-favorite.packet';
import {
addToFavorites,
removeFromFavorites,
submitAddFavorite,
submitRemoveFavorite,
} from '../../common/state/user';
import { import {
fetchUsernames, fetchUsernames,
} from '~/common/state/usernames'; } from '~/common/state/usernames';
@ -44,6 +52,14 @@ const effects = {
dispatch(confirmMessage({current, previous: payload.uuid, timestamp})); dispatch(confirmMessage({current, previous: payload.uuid, timestamp}));
}); });
}, },
[submitAddFavorite]: ({dispatch}, {payload}) => {
dispatch(addToFavorites(payload));
socket.send(new AddFavorite(payload));
},
[submitRemoveFavorite]: ({dispatch}, {payload}) => {
dispatch(removeFromFavorites(payload));
socket.send(new RemoveFavorite(payload));
},
}; };
export const middleware = (store) => (next) => (action) => { export const middleware = (store) => (next) => (action) => {

View File

@ -1,11 +1,11 @@
import {Packet} from '@avocado/net'; import {Packet} from '@avocado/net';
export default class Blur extends Packet { export default class AddFavorite extends Packet {
static get schema() { static get schema() {
return { return {
...super.schema, ...super.schema,
data: {}, data: 'string',
}; };
} }

View File

@ -1,11 +1,11 @@
import {Packet} from '@avocado/net'; import {Packet} from '@avocado/net';
export default class Focus extends Packet { export default class RemoveFavorite extends Packet {
static get schema() { static get schema() {
return { return {
...super.schema, ...super.schema,
data: {}, data: 'string',
}; };
} }

View File

@ -89,7 +89,7 @@ const slice = createSlice({
addFriendship: (state, {payload: {friendship}}) => { addFriendship: (state, {payload: {friendship}}) => {
state.friendship.push(friendship); state.friendship.push(friendship);
}, },
addToFavorites: (state, {payload: {favorite}}) => { addToFavorites: (state, {payload: favorite}) => {
state.favorites.push(favorite); state.favorites.push(favorite);
}, },
blockUser: (state, {payload: {blocked}}) => { blockUser: (state, {payload: {blocked}}) => {
@ -101,14 +101,14 @@ const slice = createSlice({
removeFriendship: (state, {payload: {id}}) => { removeFriendship: (state, {payload: {id}}) => {
state.friendship.splice(state.friendship.findIndex((friendship) => friendship.id === id), 1); state.friendship.splice(state.friendship.findIndex((friendship) => friendship.id === id), 1);
}, },
removeFromFavorites: (state, {payload: {type, name}}) => { removeFromFavorites: (state, {payload: favorite}) => {
state.favorites.splice(state.favorites.findIndex( state.favorites.splice(state.favorites.indexOf(favorite), 1);
(favorite) => favorite.name === name && favorite.type === type,
), 1);
}, },
removeRecent: ({recent}, {payload: channel}) => { removeRecent: ({recent}, {payload: channel}) => {
recent.splice(recent.indexOf(channel), 1); recent.splice(recent.indexOf(channel), 1);
}, },
submitAddFavorite: () => {},
submitRemoveFavorite: () => {},
updateFriendshipStatus: (state, {payload: {id, status}}) => { updateFriendshipStatus: (state, {payload: {id, status}}) => {
state.friendship.find((friendship) => friendship.id === id).status = status; state.friendship.find((friendship) => friendship.id === id).status = status;
}, },
@ -135,10 +135,14 @@ const slice = createSlice({
export const { export const {
addFriend, addFriend,
addToFavorites,
blockUser, blockUser,
focus, focus,
removeFriend, removeFriend,
removeFromFavorites,
removeRecent, removeRecent,
submitAddFavorite,
submitRemoveFavorite,
} = slice.actions; } = slice.actions;
export default slice.reducer; export default slice.reducer;

View File

@ -1,7 +1,7 @@
/* eslint-disable import/no-extraneous-dependencies */ /* eslint-disable import/no-extraneous-dependencies */
import {promisify} from 'util'; import {promisify} from 'util';
import {joinChannel} from '~/common/channel'; import {joinChannel, parseChannel} from '~/common/channel';
import {allModels} from './models/registrar'; import {allModels} from './models/registrar';
import createRedisClient, {keys} from './redis'; import createRedisClient, {keys} from './redis';
@ -49,16 +49,22 @@ export const channelState = async (req, channel) => {
}; };
}; };
export const channelsToHydrate = async (req) => ( export const channelsToHydrate = async (req) => {
(req.channel ? [req.channel] : []).concat(req.user ? await req.user.favorites() : []) const channels = await Promise.all(
); (req.channel ? [req.channel] : [])
.concat(req.user ? await req.user.favorites() : []),
);
const channelStrings = channels.map(joinChannel);
const uniqueChannels = (new Set(channelStrings)).values();
return Array.from(uniqueChannels).map((channel) => parseChannel(`/chat${channel}`));
};
const userState = async (req) => { const userState = async (req) => {
const {channel, user} = req; const {channel, user} = req;
const toHydrate = await channelsToHydrate(req); const toHydrate = await channelsToHydrate(req);
return user return user
? { ? {
favorites: [], favorites: (await user.favorites()).map(joinChannel),
focus: channel ? joinChannel(channel) : '', focus: channel ? joinChannel(channel) : '',
friends: await user.friends(), friends: await user.friends(),
id: user.id, id: user.id,

View File

@ -0,0 +1,23 @@
import {DataTypes as Types} from 'sequelize';
import BaseModel from './base';
class Favorite extends BaseModel {
static get attributes() {
return {
channel: Types.STRING,
};
}
static associate({User}) {
User.hasMany(this);
this.belongsTo(User);
}
static get name() {
return 'Favorite';
}
}
export default Favorite;

View File

@ -5,6 +5,8 @@ import bcrypt from 'bcrypt';
import {registerHooks} from 'scwp'; import {registerHooks} from 'scwp';
import {DataTypes as Types, Op} from 'sequelize'; import {DataTypes as Types, Op} from 'sequelize';
import {parseChannel} from '~/common/channel';
import BaseModel from './base'; import BaseModel from './base';
import {allModels} from './registrar'; import {allModels} from './registrar';
@ -35,7 +37,9 @@ class User extends BaseModel {
} }
async favorites() { async favorites() {
return []; return (await this.getFavorites())
.map(({channel}) => channel)
.map((channel) => parseChannel(`/chat${channel}`));
} }
async friends() { async friends() {

View File

@ -1,16 +1,17 @@
/* eslint-disable import/no-extraneous-dependencies */ /* eslint-disable import/no-extraneous-dependencies */
import {promisify} from 'util'; import {promisify} from 'util';
import {ServerSocket, SocketServer} from '@avocado/net/server/socket';
import socketSession from 'express-socket.io-session';
import redisAdapter from 'socket.io-redis'; import redisAdapter from 'socket.io-redis';
import {v4 as uuidv4} from 'uuid'; import {v4 as uuidv4} from 'uuid';
import {ServerSocket, SocketServer} from '@avocado/net/server/socket';
import socketSession from 'express-socket.io-session';
import {joinChannel, parseChannel} from '~/common/channel'; import {joinChannel, parseChannel} from '~/common/channel';
import AddFavorite from '~/common/packets/add-favorite.packet';
import Join from '~/common/packets/join.packet'; import Join from '~/common/packets/join.packet';
import Leave from '~/common/packets/leave.packet'; import Leave from '~/common/packets/leave.packet';
import Message from '~/common/packets/message.packet'; import Message from '~/common/packets/message.packet';
import RemoveFavorite from '~/common/packets/remove-favorite.packet';
import Usernames from '~/common/packets/usernames.packet'; import Usernames from '~/common/packets/usernames.packet';
import { import {
@ -30,7 +31,7 @@ const subClient = createRedisClient();
const adapter = redisAdapter({pubClient, subClient}); const adapter = redisAdapter({pubClient, subClient});
export function createSocketServer(httpServer) { export function createSocketServer(httpServer) {
const {User} = allModels(); const {Favorite, User} = allModels();
const socketServer = new SocketServer(httpServer, { const socketServer = new SocketServer(httpServer, {
adapter, adapter,
}); });
@ -76,6 +77,18 @@ export function createSocketServer(httpServer) {
socketServer.on('connect', (socket) => { socketServer.on('connect', (socket) => {
const {req} = socket; const {req} = socket;
socket.on('packet', async (packet, fn) => { socket.on('packet', async (packet, fn) => {
if (packet instanceof AddFavorite) {
await req.user.createFavorite({channel: packet.data});
}
if (packet instanceof RemoveFavorite) {
const favorites = await req.user.getFavorites();
const toRemove = favorites.find(({channel}) => channel === packet.data);
await Favorite.destroy({
where: {
id: toRemove.id,
},
});
}
if (packet instanceof Join) { if (packet instanceof Join) {
const {channel} = packet.data; const {channel} = packet.data;
await userJoin(channel, socket.socket); await userJoin(channel, socket.socket);