dispatch/client/js/state/channels.js

328 lines
7.1 KiB
JavaScript
Raw Normal View History

import { createSelector } from 'reselect';
2018-04-25 03:36:27 +00:00
import get from 'lodash/get';
import sortBy from 'lodash/sortBy';
import createReducer from 'utils/createReducer';
2020-06-19 01:54:19 +00:00
import { trimPrefixChar, find, findIndex } from 'utils';
import { getSelectedTab, updateSelection } from './tab';
import * as actions from './actions';
2015-12-28 23:34:32 +00:00
const modePrefixes = [
{ mode: 'q', prefix: '~' }, // Owner
{ mode: 'a', prefix: '&' }, // Admin
{ mode: 'o', prefix: '@' }, // Op
{ mode: 'h', prefix: '%' }, // Halfop
2018-03-25 00:34:41 +00:00
{ mode: 'v', prefix: '+' } // Voice
];
2015-12-28 23:34:32 +00:00
2018-04-25 03:36:27 +00:00
function getRenderName(user) {
for (let i = 0; i < modePrefixes.length; i++) {
if (user.mode.indexOf(modePrefixes[i].mode) !== -1) {
2018-04-25 03:36:27 +00:00
return `${modePrefixes[i].prefix}${user.nick}`;
}
2015-12-28 23:34:32 +00:00
}
2018-04-25 03:36:27 +00:00
return user.nick;
2015-12-28 23:34:32 +00:00
}
function createUser(nick, mode) {
2018-04-25 03:36:27 +00:00
const user = {
nick,
mode: mode || ''
};
user.renderName = getRenderName(user);
return user;
2015-12-28 23:34:32 +00:00
}
function loadUser(nick) {
let mode;
for (let i = 0; i < modePrefixes.length; i++) {
if (nick[0] === modePrefixes[i].prefix) {
2018-03-25 00:34:41 +00:00
({ mode } = modePrefixes[i]);
}
2015-12-28 23:34:32 +00:00
}
if (mode) {
return createUser(nick.slice(1), mode);
}
return createUser(nick);
2015-12-28 23:34:32 +00:00
}
2018-04-25 03:36:27 +00:00
function removeUser(users, nick) {
const i = findIndex(users, u => u.nick === nick);
if (i !== -1) {
users.splice(i, 1);
}
}
2020-06-15 08:58:51 +00:00
function init(state, network, channel) {
if (!state[network]) {
state[network] = {};
2018-04-25 03:36:27 +00:00
}
2020-06-15 08:58:51 +00:00
if (channel && !state[network][channel]) {
state[network][channel] = {
name: channel,
users: [],
joined: false
};
2018-04-25 03:36:27 +00:00
}
2020-06-15 08:58:51 +00:00
return state[network][channel];
2018-04-25 03:36:27 +00:00
}
export function compareUsers(a, b) {
2015-12-28 23:34:32 +00:00
a = a.renderName.toLowerCase();
b = b.renderName.toLowerCase();
for (let i = 0; i < modePrefixes.length; i++) {
2018-03-25 00:34:41 +00:00
const { prefix } = modePrefixes[i];
if (a[0] === prefix && b[0] !== prefix) {
return -1;
}
if (b[0] === prefix && a[0] !== prefix) {
return 1;
}
2015-12-28 23:34:32 +00:00
}
2015-12-28 23:34:32 +00:00
if (a < b) {
return -1;
}
if (a > b) {
return 1;
}
return 0;
}
export const getChannels = state => state.channels;
2020-04-30 05:54:30 +00:00
export const getSortedChannels = createSelector(getChannels, channels =>
sortBy(
2020-06-15 08:58:51 +00:00
Object.keys(channels).map(network => ({
address: network,
2020-06-19 01:54:19 +00:00
channels: sortBy(channels[network], channel =>
trimPrefixChar(channel.name, '#').toLowerCase()
)
2020-04-30 05:54:30 +00:00
})),
2020-06-15 08:58:51 +00:00
network => network.address.toLowerCase()
2020-04-30 05:54:30 +00:00
)
2017-06-29 04:56:05 +00:00
);
export const getSelectedChannel = createSelector(
getSelectedTab,
getChannels,
2020-06-15 08:58:51 +00:00
(tab, channels) => get(channels, [tab.network, tab.name])
);
export const getSelectedChannelUsers = createSelector(
getSelectedChannel,
2018-04-25 03:36:27 +00:00
channel => {
if (channel) {
return channel.users.concat().sort(compareUsers);
}
return [];
}
);
2018-04-25 03:36:27 +00:00
export default createReducer(
{},
{
2020-06-15 08:58:51 +00:00
[actions.JOIN](state, { network, channels }) {
channels.forEach(channel => init(state, network, channel));
2018-12-06 12:27:53 +00:00
},
2020-06-15 08:58:51 +00:00
[actions.PART](state, { network, channels }) {
channels.forEach(channel => delete state[network][channel]);
2018-04-25 03:36:27 +00:00
},
2020-06-15 08:58:51 +00:00
[actions.socket.JOIN](state, { network, channels, user }) {
2018-04-25 03:36:27 +00:00
const channel = channels[0];
2020-06-15 08:58:51 +00:00
const chan = init(state, network, channel);
chan.name = channel;
chan.joined = true;
chan.users.push(createUser(user));
2018-04-25 03:36:27 +00:00
},
[actions.socket.CHANNEL_FORWARD](state, action) {
2020-06-15 08:58:51 +00:00
init(state, action.network, action.new);
delete state[action.network][action.old];
},
2020-06-15 08:58:51 +00:00
[actions.socket.PART](state, { network, channel, user }) {
if (state[network][channel]) {
removeUser(state[network][channel].users, user);
}
2018-04-25 03:36:27 +00:00
},
2020-06-15 08:58:51 +00:00
[actions.socket.QUIT](state, { network, user }) {
Object.keys(state[network]).forEach(channel => {
removeUser(state[network][channel].users, user);
2017-07-06 04:46:53 +00:00
});
2018-04-25 03:36:27 +00:00
},
2015-12-28 23:34:32 +00:00
2020-06-15 08:58:51 +00:00
[actions.KICKED](state, { network, channel, user, self }) {
const chan = state[network][channel];
if (self) {
chan.joined = false;
chan.users = [];
} else {
removeUser(chan.users, user);
}
},
[actions.socket.NICK](state, { network, oldNick, newNick }) {
Object.keys(state[network]).forEach(channel => {
2018-04-25 03:36:27 +00:00
const user = find(
2020-06-15 08:58:51 +00:00
state[network][channel].users,
2018-04-25 03:36:27 +00:00
u => u.nick === oldNick
2018-04-05 23:46:22 +00:00
);
2018-04-25 03:36:27 +00:00
if (user) {
user.nick = newNick;
user.renderName = getRenderName(user);
}
2015-12-28 23:34:32 +00:00
});
2018-04-25 03:36:27 +00:00
},
2015-12-28 23:34:32 +00:00
2020-06-15 08:58:51 +00:00
[actions.socket.USERS](state, { network, channel, users }) {
state[network][channel].users = users.map(nick => loadUser(nick));
2018-04-25 03:36:27 +00:00
},
2015-12-28 23:34:32 +00:00
2020-06-15 08:58:51 +00:00
[actions.socket.TOPIC](state, { network, channel, topic }) {
state[network][channel].topic = topic;
2018-04-25 03:36:27 +00:00
},
2020-06-15 08:58:51 +00:00
[actions.socket.MODE](state, { network, channel, user, remove, add }) {
const u = find(state[network][channel].users, v => v.nick === user);
2018-04-25 03:36:27 +00:00
if (u) {
2018-05-25 21:54:36 +00:00
if (remove) {
let j = remove.length;
while (j--) {
u.mode = u.mode.replace(remove[j], '');
}
}
if (add) {
u.mode += add;
2015-12-28 23:34:32 +00:00
}
2018-04-25 03:36:27 +00:00
u.renderName = getRenderName(u);
}
},
2016-01-14 04:56:53 +00:00
2018-04-25 03:36:27 +00:00
[actions.socket.CHANNELS](state, { data }) {
if (data) {
2020-06-15 08:58:51 +00:00
data.forEach(({ network, name, topic }) => {
const chan = init(state, network, name);
chan.joined = true;
chan.topic = topic;
2018-04-25 03:36:27 +00:00
});
}
},
2015-12-28 23:34:32 +00:00
2020-06-15 08:58:51 +00:00
[actions.socket.NETWORKS](state, { data }) {
2018-04-25 03:36:27 +00:00
if (data) {
data.forEach(({ host }) => init(state, host));
}
},
[actions.CONNECT](state, { host }) {
init(state, host);
},
2020-06-15 08:58:51 +00:00
[actions.DISCONNECT](state, { network }) {
delete state[network];
2018-04-25 03:36:27 +00:00
}
2015-12-28 23:34:32 +00:00
}
2018-04-25 03:36:27 +00:00
);
2020-06-15 08:58:51 +00:00
export function join(channels, network) {
return {
type: actions.JOIN,
channels,
2020-06-15 08:58:51 +00:00
network,
socket: {
type: 'join',
2020-06-15 08:58:51 +00:00
data: { channels, network }
}
};
}
2020-06-15 08:58:51 +00:00
export function part(channels, network) {
return (dispatch, getState) => {
const action = {
type: actions.PART,
channels,
2020-06-15 08:58:51 +00:00
network
};
2020-06-15 08:58:51 +00:00
const state = getState().channels[network];
const joined = channels.filter(c => state[c] && state[c].joined);
if (joined.length > 0) {
action.socket = {
type: 'part',
data: {
channels: joined,
2020-06-15 08:58:51 +00:00
network
}
};
}
dispatch(action);
dispatch(updateSelection());
};
}
2020-06-15 08:58:51 +00:00
export function invite(user, channel, network) {
return {
type: actions.INVITE,
user,
channel,
2020-06-15 08:58:51 +00:00
network,
socket: {
type: 'invite',
2020-06-15 08:58:51 +00:00
data: { user, channel, network }
}
};
}
2020-06-15 08:58:51 +00:00
export function kick(user, channel, network) {
return {
type: actions.KICK,
user,
channel,
2020-06-15 08:58:51 +00:00
network,
socket: {
type: 'kick',
2020-06-15 08:58:51 +00:00
data: { user, channel, network }
}
};
}
2020-06-15 08:58:51 +00:00
export function kicked(network, channel, user) {
return (dispatch, getState) => {
const nick = getState().networks[network]?.nick;
dispatch({
type: actions.KICKED,
network,
channel,
user,
self: nick === user
});
};
}
export function setTopic(topic, channel, network) {
return {
type: actions.SET_TOPIC,
topic,
channel,
2020-06-15 08:58:51 +00:00
network,
socket: {
type: 'topic',
2020-06-15 08:58:51 +00:00
data: { topic, channel, network }
}
};
}