Add manifest.json, icons and install button, flatten client/src

This commit is contained in:
Ken-Håvard Lieng 2018-11-10 12:18:45 +01:00
parent a219e689c1
commit 474afda9c2
105 changed files with 338 additions and 283 deletions

View 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`;
});
}

View 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();
}

View 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);
}

View 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
View 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);
});
}

View 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);
}
});
}

View 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);
});
}