Add manifest.json, icons and install button, flatten client/src
This commit is contained in:
parent
a219e689c1
commit
474afda9c2
105 changed files with 338 additions and 283 deletions
23
client/js/modules/documentTitle.js
Normal file
23
client/js/modules/documentTitle.js
Normal file
|
@ -0,0 +1,23 @@
|
|||
import capitalize from 'lodash/capitalize';
|
||||
import { getRouter } from 'state';
|
||||
import { getCurrentServerName } from 'state/servers';
|
||||
import { observe } from 'utils/observe';
|
||||
|
||||
export default function documentTitle({ store }) {
|
||||
observe(store, [getRouter, getCurrentServerName], (router, serverName) => {
|
||||
let title;
|
||||
|
||||
if (router.route === 'chat') {
|
||||
const { name } = router.params;
|
||||
if (name) {
|
||||
title = `${name} @ ${serverName}`;
|
||||
} else {
|
||||
title = serverName;
|
||||
}
|
||||
} else {
|
||||
title = capitalize(router.route);
|
||||
}
|
||||
|
||||
document.title = `${title} | Dispatch`;
|
||||
});
|
||||
}
|
22
client/js/modules/fonts.js
Normal file
22
client/js/modules/fonts.js
Normal file
|
@ -0,0 +1,22 @@
|
|||
import FontFaceObserver from 'fontfaceobserver';
|
||||
import { setCharWidth } from 'state/app';
|
||||
import { stringWidth } from 'utils';
|
||||
|
||||
export default function fonts({ store }) {
|
||||
let { charWidth } = localStorage;
|
||||
if (charWidth) {
|
||||
store.dispatch(setCharWidth(parseFloat(charWidth)));
|
||||
}
|
||||
|
||||
new FontFaceObserver('Roboto Mono').load().then(() => {
|
||||
if (!charWidth) {
|
||||
charWidth = stringWidth(' ', '16px Roboto Mono');
|
||||
store.dispatch(setCharWidth(charWidth));
|
||||
localStorage.charWidth = charWidth;
|
||||
}
|
||||
});
|
||||
|
||||
new FontFaceObserver('Montserrat').load();
|
||||
new FontFaceObserver('Montserrat', { weight: 700 }).load();
|
||||
new FontFaceObserver('Roboto Mono', { weight: 700 }).load();
|
||||
}
|
16
client/js/modules/index.js
Normal file
16
client/js/modules/index.js
Normal file
|
@ -0,0 +1,16 @@
|
|||
import documentTitle from './documentTitle';
|
||||
import fonts from './fonts';
|
||||
import initialState from './initialState';
|
||||
import socket from './socket';
|
||||
import storage from './storage';
|
||||
import widthUpdates from './widthUpdates';
|
||||
|
||||
export default function runModules(ctx) {
|
||||
fonts(ctx);
|
||||
initialState(ctx);
|
||||
|
||||
documentTitle(ctx);
|
||||
socket(ctx);
|
||||
storage(ctx);
|
||||
widthUpdates(ctx);
|
||||
}
|
80
client/js/modules/initialState.js
Normal file
80
client/js/modules/initialState.js
Normal file
|
@ -0,0 +1,80 @@
|
|||
/* eslint-disable no-underscore-dangle */
|
||||
import Cookie from 'js-cookie';
|
||||
import { socket as socketActions } from 'state/actions';
|
||||
import { getWrapWidth, setConnectDefaults, appSet } from 'state/app';
|
||||
import { addMessages } from 'state/messages';
|
||||
import { setSettings } from 'state/settings';
|
||||
import { select, updateSelection } from 'state/tab';
|
||||
import { find } from 'utils';
|
||||
import { when } from 'utils/observe';
|
||||
import { replace } from 'utils/router';
|
||||
|
||||
function loadState({ store }, env) {
|
||||
store.dispatch(setConnectDefaults(env.defaults));
|
||||
store.dispatch(appSet('hexIP', env.hexIP));
|
||||
store.dispatch(setSettings(env.settings, true));
|
||||
|
||||
if (env.servers) {
|
||||
store.dispatch({
|
||||
type: socketActions.SERVERS,
|
||||
data: env.servers
|
||||
});
|
||||
|
||||
if (!store.getState().router.route) {
|
||||
const tab = Cookie.get('tab');
|
||||
if (tab) {
|
||||
const [server, name = null] = tab.split(/;(.+)/);
|
||||
|
||||
if (
|
||||
name &&
|
||||
find(
|
||||
env.channels,
|
||||
chan => chan.server === server && chan.name === name
|
||||
)
|
||||
) {
|
||||
store.dispatch(select(server, name, true));
|
||||
} else if (find(env.servers, srv => srv.host === server)) {
|
||||
store.dispatch(select(server, null, true));
|
||||
} else {
|
||||
store.dispatch(updateSelection());
|
||||
}
|
||||
} else {
|
||||
store.dispatch(updateSelection());
|
||||
}
|
||||
}
|
||||
} else {
|
||||
store.dispatch(replace('/connect'));
|
||||
}
|
||||
|
||||
if (env.channels) {
|
||||
store.dispatch({
|
||||
type: socketActions.CHANNELS,
|
||||
data: env.channels
|
||||
});
|
||||
}
|
||||
|
||||
if (env.users) {
|
||||
store.dispatch({
|
||||
type: socketActions.USERS,
|
||||
...env.users
|
||||
});
|
||||
}
|
||||
|
||||
// Wait until wrapWidth gets initialized so that height calculations
|
||||
// only happen once for these messages
|
||||
when(store, getWrapWidth, () => {
|
||||
if (env.messages) {
|
||||
const { messages, server, to, next } = env.messages;
|
||||
store.dispatch(addMessages(messages, server, to, false, next));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
export default function initialState(ctx) {
|
||||
if (window.__env__) {
|
||||
window.__env__.then(env => loadState(ctx, env));
|
||||
} else {
|
||||
const env = JSON.parse(document.getElementById('env').innerHTML);
|
||||
loadState(ctx, env);
|
||||
}
|
||||
}
|
161
client/js/modules/socket.js
Normal file
161
client/js/modules/socket.js
Normal file
|
@ -0,0 +1,161 @@
|
|||
import { socketAction } from 'state/actions';
|
||||
import { setConnected } from 'state/app';
|
||||
import {
|
||||
broadcast,
|
||||
inform,
|
||||
print,
|
||||
addMessage,
|
||||
addMessages
|
||||
} from 'state/messages';
|
||||
import { reconnect } from 'state/servers';
|
||||
import { select } from 'state/tab';
|
||||
import { find, normalizeChannel } from 'utils';
|
||||
|
||||
function withReason(message, reason) {
|
||||
return message + (reason ? ` (${reason})` : '');
|
||||
}
|
||||
|
||||
function findChannels(state, server, user) {
|
||||
const channels = [];
|
||||
|
||||
Object.keys(state.channels[server]).forEach(channel => {
|
||||
if (find(state.channels[server][channel].users, u => u.nick === user)) {
|
||||
channels.push(channel);
|
||||
}
|
||||
});
|
||||
|
||||
return channels;
|
||||
}
|
||||
|
||||
export default function handleSocket({
|
||||
socket,
|
||||
store: { dispatch, getState }
|
||||
}) {
|
||||
const handlers = {
|
||||
message(message) {
|
||||
dispatch(addMessage(message, message.server, message.to));
|
||||
},
|
||||
|
||||
pm(message) {
|
||||
dispatch(addMessage(message, message.server, message.from));
|
||||
},
|
||||
|
||||
messages({ messages, server, to, prepend, next }) {
|
||||
dispatch(addMessages(messages, server, to, prepend, next));
|
||||
},
|
||||
|
||||
join({ user, server, channels }) {
|
||||
const state = getState();
|
||||
const tab = state.tab.selected;
|
||||
const [joinedChannel] = channels;
|
||||
if (tab.server && tab.name) {
|
||||
const { nick } = state.servers[tab.server];
|
||||
if (
|
||||
tab.server === server &&
|
||||
nick === user &&
|
||||
tab.name !== joinedChannel &&
|
||||
normalizeChannel(tab.name) === normalizeChannel(joinedChannel)
|
||||
) {
|
||||
dispatch(select(server, joinedChannel));
|
||||
}
|
||||
}
|
||||
|
||||
dispatch(inform(`${user} joined the channel`, server, joinedChannel));
|
||||
},
|
||||
|
||||
part({ user, server, channel, reason }) {
|
||||
dispatch(
|
||||
inform(withReason(`${user} left the channel`, reason), server, channel)
|
||||
);
|
||||
},
|
||||
|
||||
quit({ user, server, reason }) {
|
||||
const channels = findChannels(getState(), server, user);
|
||||
dispatch(broadcast(withReason(`${user} quit`, reason), server, channels));
|
||||
},
|
||||
|
||||
nick({ server, oldNick, newNick }) {
|
||||
const channels = findChannels(getState(), server, oldNick);
|
||||
dispatch(
|
||||
broadcast(`${oldNick} changed nick to ${newNick}`, server, channels)
|
||||
);
|
||||
},
|
||||
|
||||
topic({ server, channel, topic, nick }) {
|
||||
if (nick) {
|
||||
if (topic) {
|
||||
dispatch(inform(`${nick} changed the topic to:`, server, channel));
|
||||
dispatch(print(topic, server, channel));
|
||||
} else {
|
||||
dispatch(inform(`${nick} cleared the topic`, server, channel));
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
motd({ content, server }) {
|
||||
dispatch(addMessages(content.map(line => ({ content: line })), server));
|
||||
},
|
||||
|
||||
whois(data) {
|
||||
const tab = getState().tab.selected;
|
||||
|
||||
dispatch(
|
||||
print(
|
||||
[
|
||||
`Nick: ${data.nick}`,
|
||||
`Username: ${data.username}`,
|
||||
`Realname: ${data.realname}`,
|
||||
`Host: ${data.host}`,
|
||||
`Server: ${data.server}`,
|
||||
`Channels: ${data.channels}`
|
||||
],
|
||||
tab.server,
|
||||
tab.name
|
||||
)
|
||||
);
|
||||
},
|
||||
|
||||
print(message) {
|
||||
const tab = getState().tab.selected;
|
||||
dispatch(addMessage(message, tab.server, tab.name));
|
||||
},
|
||||
|
||||
connection_update({ server, errorType }) {
|
||||
if (
|
||||
errorType === 'verify' &&
|
||||
window.confirm(
|
||||
'The server is using a self-signed certificate, continue anyway?'
|
||||
)
|
||||
) {
|
||||
dispatch(
|
||||
reconnect(server, {
|
||||
skipVerify: true
|
||||
})
|
||||
);
|
||||
}
|
||||
},
|
||||
|
||||
_connected(connected) {
|
||||
dispatch(setConnected(connected));
|
||||
}
|
||||
};
|
||||
|
||||
socket.onMessage((type, data) => {
|
||||
let action;
|
||||
if (Array.isArray(data)) {
|
||||
action = { type: socketAction(type), data: [...data] };
|
||||
} else {
|
||||
action = { ...data, type: socketAction(type) };
|
||||
}
|
||||
|
||||
if (type in handlers) {
|
||||
handlers[type](data);
|
||||
}
|
||||
|
||||
if (type.charAt(0) === '_') {
|
||||
return;
|
||||
}
|
||||
|
||||
dispatch(action);
|
||||
});
|
||||
}
|
18
client/js/modules/storage.js
Normal file
18
client/js/modules/storage.js
Normal file
|
@ -0,0 +1,18 @@
|
|||
import Cookie from 'js-cookie';
|
||||
import debounce from 'lodash/debounce';
|
||||
import { getSelectedTab } from 'state/tab';
|
||||
import { isChannel, stringifyTab } from 'utils';
|
||||
import { observe } from 'utils/observe';
|
||||
|
||||
const saveTab = debounce(
|
||||
tab => Cookie.set('tab', stringifyTab(tab), { expires: 30 }),
|
||||
1000
|
||||
);
|
||||
|
||||
export default function storage({ store }) {
|
||||
observe(store, getSelectedTab, tab => {
|
||||
if (isChannel(tab) || (tab.server && !tab.name)) {
|
||||
saveTab(tab);
|
||||
}
|
||||
});
|
||||
}
|
31
client/js/modules/widthUpdates.js
Normal file
31
client/js/modules/widthUpdates.js
Normal file
|
@ -0,0 +1,31 @@
|
|||
import { getCharWidth } from 'state/app';
|
||||
import { updateMessageHeight } from 'state/messages';
|
||||
import { when } from 'utils/observe';
|
||||
import { measureScrollBarWidth } from 'utils';
|
||||
import { addResizeListener } from 'utils/size';
|
||||
|
||||
const menuWidth = 200;
|
||||
const messagePadding = 30;
|
||||
const smallScreen = 600;
|
||||
|
||||
export default function widthUpdates({ store }) {
|
||||
when(store, getCharWidth, charWidth => {
|
||||
window.messageIndent = 6 * charWidth;
|
||||
const scrollBarWidth = measureScrollBarWidth();
|
||||
let prevWrapWidth;
|
||||
|
||||
function updateWidth(windowWidth) {
|
||||
let wrapWidth = windowWidth - scrollBarWidth - messagePadding;
|
||||
if (windowWidth > smallScreen) {
|
||||
wrapWidth -= menuWidth;
|
||||
}
|
||||
|
||||
if (wrapWidth !== prevWrapWidth) {
|
||||
prevWrapWidth = wrapWidth;
|
||||
store.dispatch(updateMessageHeight(wrapWidth, charWidth, windowWidth));
|
||||
}
|
||||
}
|
||||
|
||||
addResizeListener(updateWidth, true);
|
||||
});
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue