feat: favorites
This commit is contained in:
parent
d29a98bef5
commit
c369846fa8
|
@ -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: '×',
|
||||||
|
|
16
src/client/store/effects.js
vendored
16
src/client/store/effects.js
vendored
|
@ -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) => {
|
||||||
|
|
|
@ -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',
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
|
@ -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',
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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,
|
||||||
|
|
23
src/server/models/favorite.model.js
Normal file
23
src/server/models/favorite.model.js
Normal 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;
|
|
@ -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() {
|
||||||
|
|
|
@ -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);
|
||||||
|
|
Loading…
Reference in New Issue
Block a user