Move vuex state to a separate file and reorganize some code

Co-Authored-By: Tim Miller-Williams <timmw@users.noreply.github.com>
This commit is contained in:
Pavel Djundik 2019-11-02 21:40:59 +02:00
parent 3c43a2bfd3
commit 2f635069e0
43 changed files with 265 additions and 248 deletions

View File

@ -1,8 +1,8 @@
<template>
<div id="viewport" :class="viewportClasses" role="tablist">
<Sidebar :networks="networks" :active-channel="activeChannel" :overlay="$refs.overlay" />
<Sidebar :overlay="$refs.overlay" />
<div id="sidebar-overlay" ref="overlay" @click="$root.setSidebar(false)" />
<article id="windows">
<article v-if="$root.initialized" id="windows">
<router-view></router-view>
</article>
<ImageViewer ref="imageViewer" />
@ -23,8 +23,6 @@ export default {
},
props: {
activeWindow: String,
activeChannel: Object,
networks: Array,
},
computed: {
viewportClasses() {

View File

@ -1,10 +1,5 @@
<template>
<ChannelWrapper
ref="wrapper"
:network="network"
:channel="channel"
:active-channel="activeChannel"
>
<ChannelWrapper ref="wrapper" :network="network" :channel="channel">
<span class="name">{{ channel.name }}</span>
<span v-if="channel.unread" :class="{highlight: channel.highlight}" class="badge">{{
channel.unread | roundBadgeNumber
@ -38,7 +33,6 @@ export default {
ChannelWrapper,
},
props: {
activeChannel: Object,
network: Object,
channel: Object,
},

View File

@ -37,13 +37,17 @@ export default {
props: {
network: Object,
channel: Object,
activeChannel: Object,
},
data() {
return {
closed: false,
};
},
computed: {
activeChannel() {
return this.$store.state.activeChannel;
},
},
methods: {
close() {
this.closed = true;

View File

@ -81,11 +81,11 @@
</div>
</div>
<div
v-if="this.$root.currentUserVisibleError"
v-if="this.$store.state.currentUserVisibleError"
id="user-visible-error"
@click="hideUserVisibleError"
>
{{ this.$root.currentUserVisibleError }}
{{ this.$store.state.currentUserVisibleError }}
</div>
<span id="upload-progressbar" />
<ChatInput :network="network" :channel="channel" />
@ -166,7 +166,7 @@ export default {
this.$root.synchronizeNotifiedState();
},
hideUserVisibleError() {
this.$root.currentUserVisibleError = null;
this.$store.commit("currentUserVisibleError", null);
},
editTopic() {
if (this.channel.type === "channel") {

View File

@ -13,7 +13,7 @@
@keypress.enter.exact.prevent="onSubmit"
/>
<span
v-if="this.$root.isFileUploadEnabled"
v-if="$store.state.isFileUploadEnabled"
id="upload-tooltip"
class="tooltipped tooltipped-w tooltipped-no-touch"
aria-label="Upload file"
@ -119,7 +119,10 @@ export default {
});
inputTrap.bind(["up", "down"], (e, key) => {
if (this.$root.isAutoCompleting || e.target.selectionStart !== e.target.selectionEnd) {
if (
this.$store.state.isAutoCompleting ||
e.target.selectionStart !== e.target.selectionEnd
) {
return;
}
@ -144,7 +147,7 @@ export default {
return false;
});
if (this.$root.isFileUploadEnabled) {
if (this.$store.state.isFileUploadEnabled) {
upload.mounted();
}
},

View File

@ -13,7 +13,7 @@ export default {
methods: {
onClick() {
const channelToFind = this.channel.toLowerCase();
const existingChannel = this.$root.activeChannel.network.channels.find(
const existingChannel = this.$store.state.activeChannel.network.channels.find(
(c) => c.name.toLowerCase() === channelToFind
);
@ -24,7 +24,7 @@ export default {
// TODO: Required here because it breaks tests
const socket = require("../js/socket");
socket.emit("input", {
target: this.$root.activeChannel.channel.id,
target: this.$store.state.activeChannel.channel.id,
text: "/join " + this.channel,
});
},

View File

@ -210,7 +210,7 @@ export default {
},
data() {
return {
config: this.$root.serverConfiguration,
config: this.$store.state.serverConfiguration,
};
},
methods: {

View File

@ -33,7 +33,6 @@
>
<NetworkLobby
:network="network"
:active-channel="activeChannel"
:is-join-channel-shown="network.isJoinChannelShown"
@toggleJoinChannel="network.isJoinChannelShown = !network.isJoinChannelShown"
/>
@ -63,7 +62,6 @@
:key="channel.id"
:channel="channel"
:network="network"
:active-channel="activeChannel"
/>
</Draggable>
</div>
@ -86,9 +84,10 @@ export default {
Channel,
Draggable,
},
props: {
activeChannel: Object,
networks: Array,
computed: {
networks() {
return this.$store.state.networks;
},
},
methods: {
isCurrentlyInTouch(e) {

View File

@ -1,5 +1,5 @@
<template>
<ChannelWrapper :network="network" :channel="channel" :active-channel="activeChannel">
<ChannelWrapper :network="network" :channel="channel">
<button
v-if="network.channels.length > 1"
:aria-controls="'network-' + network.uuid"
@ -57,7 +57,6 @@ export default {
ChannelWrapper,
},
props: {
activeChannel: Object,
network: Object,
isJoinChannelShown: Boolean,
},

View File

@ -28,7 +28,7 @@ export default {
},
methods: {
setActiveChannel() {
this.$root.activeChannel = this.activeChannel;
this.$store.commit("activeChannel", this.activeChannel);
},
},
};

View File

@ -13,7 +13,7 @@
alt="The Lounge"
/>
</div>
<NetworkList :networks="networks" :active-channel="activeChannel" />
<NetworkList />
</div>
<footer id="footer">
<span class="tooltipped tooltipped-n tooltipped-no-touch" aria-label="Sign in"
@ -84,8 +84,6 @@ export default {
NetworkList,
},
props: {
activeChannel: Object,
networks: Array,
overlay: HTMLElement,
},
mounted() {

View File

@ -30,7 +30,7 @@
<p>
<a
:href="
`https://github.com/thelounge/thelounge/releases/tag/v${$root.serverConfiguration.version}`
`https://github.com/thelounge/thelounge/releases/tag/v${$store.state.serverConfiguration.version}`
"
target="_blank"
rel="noopener"

View File

@ -1,7 +1,7 @@
<template>
<NetworkForm
:handle-submit="handleSubmit"
:defaults="$root.serverConfiguration.defaults"
:defaults="$store.state.serverConfiguration.defaults"
:disabled="disabled"
/>
</template>

View File

@ -8,7 +8,7 @@
<h2>
<small class="pull-right">
v{{ $root.serverConfiguration.version }} (<router-link
v{{ $store.state.serverConfiguration.version }} (<router-link
id="view-changelog"
to="/changelog"
>release notes</router-link
@ -20,15 +20,15 @@
<div class="about">
<VersionChecker />
<template v-if="$root.serverConfiguration.gitCommit">
<template v-if="$store.state.serverConfiguration.gitCommit">
<p>
The Lounge is running from source (<a
:href="
`https://github.com/thelounge/thelounge/tree/${$root.serverConfiguration.gitCommit}`
`https://github.com/thelounge/thelounge/tree/${$store.state.serverConfiguration.gitCommit}`
"
target="_blank"
rel="noopener"
>commit <code>{{ $root.serverConfiguration.gitCommit }}</code></a
>commit <code>{{ $store.state.serverConfiguration.gitCommit }}</code></a
>).
</p>
@ -37,11 +37,12 @@
Compare
<a
:href="
`https://github.com/thelounge/thelounge/compare/${$root.serverConfiguration.gitCommit}...master`
`https://github.com/thelounge/thelounge/compare/${$store.state.serverConfiguration.gitCommit}...master`
"
target="_blank"
rel="noopener"
>between <code>{{ $root.serverConfiguration.gitCommit }}</code> and
>between
<code>{{ $store.state.serverConfiguration.gitCommit }}</code> and
<code>master</code></a
>
to see what you are missing
@ -50,12 +51,13 @@
Compare
<a
:href="
`https://github.com/thelounge/thelounge/compare/${$root.serverConfiguration.version}...${$root.serverConfiguration.gitCommit}`
`https://github.com/thelounge/thelounge/compare/${$store.state.serverConfiguration.version}...${$store.state.serverConfiguration.gitCommit}`
"
target="_blank"
rel="noopener"
>between <code>{{ $root.serverConfiguration.version }}</code> and
<code>{{ $root.serverConfiguration.gitCommit }}</code></a
>between
<code>{{ $store.state.serverConfiguration.version }}</code> and
<code>{{ $store.state.serverConfiguration.gitCommit }}</code></a
>
to see your local changes
</li>

View File

@ -43,7 +43,7 @@ export default {
const sidebar = $("#sidebar");
// TODO: move networks to vuex and update state when the network info comes in
const network = this.$root.networks.find((n) => n.uuid === data.uuid);
const network = this.$root.findNetwork(data.uuid);
network.name = network.channels[0].name = data.name;
sidebar.find(`.network[data-uuid="${data.uuid}"] .chan.lobby .name`).click();
},

View File

@ -10,7 +10,7 @@
<div class="col-sm-6">
<label class="opt">
<input
v-model="$root.serverConfiguration.advanced"
v-model="$store.state.serverConfiguration.advanced"
type="checkbox"
name="advanced"
/>
@ -36,7 +36,10 @@
</div>
<div
v-if="!$root.serverConfiguration.public && $root.serverConfiguration.advanced"
v-if="
!$store.state.serverConfiguration.public &&
$store.state.serverConfiguration.advanced
"
class="col-sm-12"
>
<h2>
@ -158,7 +161,7 @@
Enable autocomplete
</label>
</div>
<div v-if="$root.serverConfiguration.advanced" class="col-sm-12">
<div v-if="$store.state.serverConfiguration.advanced" class="col-sm-12">
<label class="opt">
<label for="nickPostfix" class="sr-only"
>Nick autocomplete postfix (e.g. <code>, </code>)</label
@ -186,7 +189,7 @@
class="input"
>
<option
v-for="theme in $root.serverConfiguration.themes"
v-for="theme in $store.state.serverConfiguration.themes"
:key="theme.name"
:value="theme.name"
>
@ -195,7 +198,7 @@
</select>
</div>
<template v-if="$root.serverConfiguration.prefetch">
<template v-if="$store.state.serverConfiguration.prefetch">
<div class="col-sm-12">
<h2>Link previews</h2>
</div>
@ -213,7 +216,7 @@
</div>
</template>
<template v-if="!$root.serverConfiguration.public">
<template v-if="!$store.state.serverConfiguration.public">
<div class="col-sm-12">
<h2>Push Notifications</h2>
</div>
@ -222,17 +225,20 @@
id="pushNotifications"
type="button"
class="btn"
:disabled="$root.pushNotificationState !== 'supported'"
:disabled="$store.state.pushNotificationState !== 'supported'"
data-text-alternate="Unsubscribe from push notifications"
@click="onPushButtonClick"
>
Subscribe to push notifications
</button>
<div v-if="$root.pushNotificationState === 'nohttps'" class="error">
<div v-if="$store.state.pushNotificationState === 'nohttps'" class="error">
<strong>Warning</strong>: Push notifications are only supported over
HTTPS connections.
</div>
<div v-if="$root.pushNotificationState === 'unsupported'" class="error">
<div
v-if="$store.state.pushNotificationState === 'unsupported'"
class="error"
>
<strong>Warning</strong>:
<span>Push notifications are not supported by your browser.</span>
</div>
@ -251,12 +257,15 @@
name="desktopNotifications"
/>
Enable browser notifications<br />
<div v-if="$root.desktopNotificationState === 'unsupported'" class="error">
<div
v-if="$store.state.desktopNotificationState === 'unsupported'"
class="error"
>
<strong>Warning</strong>: Notifications are not supported by your
browser.
</div>
<div
v-if="$root.desktopNotificationState === 'blocked'"
v-if="$store.state.desktopNotificationState === 'blocked'"
id="warnBlockedDesktopNotifications"
class="error"
>
@ -280,7 +289,7 @@
</div>
</div>
<div v-if="$root.serverConfiguration.advanced" class="col-sm-12">
<div v-if="$store.state.serverConfiguration.advanced" class="col-sm-12">
<label class="opt">
<input
v-model="$root.settings.notifyAllMessages"
@ -291,7 +300,7 @@
</label>
</div>
<div v-if="$root.serverConfiguration.advanced" class="col-sm-12">
<div v-if="$store.state.serverConfiguration.advanced" class="col-sm-12">
<label class="opt">
<label for="highlights" class="sr-only"
>Custom highlights (comma-separated keywords)</label
@ -309,7 +318,8 @@
<div
v-if="
!$root.serverConfiguration.public && !$root.serverConfiguration.ldapEnabled
!$store.state.serverConfiguration.public &&
!$store.state.serverConfiguration.ldapEnabled
"
id="change-password"
>
@ -377,10 +387,10 @@
</div>
</div>
<div v-if="$root.serverConfiguration.advanced" class="col-sm-12">
<div v-if="$store.state.serverConfiguration.advanced" class="col-sm-12">
<h2>Custom Stylesheet</h2>
</div>
<div v-if="$root.serverConfiguration.advanced" class="col-sm-12">
<div v-if="$store.state.serverConfiguration.advanced" class="col-sm-12">
<label for="user-specified-css-input" class="sr-only"
>Custom stylesheet. You can override any style with CSS here.</label
>
@ -394,7 +404,7 @@
</div>
</div>
<div v-if="!$root.serverConfiguration.public" class="session-list">
<div v-if="!$store.state.serverConfiguration.public" class="session-list">
<h2>Sessions</h2>
<h3>Current session</h3>

View File

@ -7,6 +7,7 @@ const {Textcomplete, Textarea} = require("textcomplete");
const emojiMap = require("./libs/simplemap.json");
const constants = require("./constants");
const {vueApp} = require("./vue");
const store = require("./store").default;
let input;
let textcomplete;
@ -21,7 +22,7 @@ module.exports = {
Mousetrap(input.get(0)).unbind("tab", "keydown");
textcomplete.destroy();
enabled = false;
vueApp.isAutoCompleting = false;
store.commit("isAutoCompleting", false);
}
},
};
@ -195,7 +196,7 @@ function enableAutocomplete(inputRef) {
Mousetrap(input.get(0)).bind(
"tab",
(e) => {
if (vueApp.isAutoCompleting) {
if (store.state.isAutoCompleting) {
return;
}
@ -271,11 +272,11 @@ function enableAutocomplete(inputRef) {
});
textcomplete.on("show", () => {
vueApp.isAutoCompleting = true;
store.commit("isAutoCompleting", true);
});
textcomplete.on("hidden", () => {
vueApp.isAutoCompleting = false;
store.commit("isAutoCompleting", false);
});
$("#form").on("submit.tabcomplete", () => {
@ -292,17 +293,17 @@ function fuzzyGrep(term, array) {
}
function rawNicks() {
if (vueApp.activeChannel.channel.users.length > 0) {
const users = vueApp.activeChannel.channel.users.slice();
if (store.state.activeChannel.channel.users.length > 0) {
const users = store.state.activeChannel.channel.users.slice();
return users.sort((a, b) => b.lastMessage - a.lastMessage).map((u) => u.nick);
}
const me = vueApp.activeChannel.network.nick;
const otherUser = vueApp.activeChannel.channel.name;
const me = store.state.activeChannel.network.nick;
const otherUser = store.state.activeChannel.channel.name;
// If this is a query, add their name to autocomplete
if (me !== otherUser && vueApp.activeChannel.channel.type === "query") {
if (me !== otherUser && store.state.activeChannel.channel.type === "query") {
return [otherUser, me];
}
@ -330,7 +331,7 @@ function completeCommands(word) {
function completeChans(word) {
const words = [];
for (const channel of vueApp.activeChannel.network.channels) {
for (const channel of store.state.activeChannel.network.channels) {
if (channel.type === "channel") {
words.push(channel.name);
}

View File

@ -4,12 +4,13 @@ exports.input = function(args) {
const utils = require("../utils");
const socket = require("../socket");
const {vueApp} = require("../vue");
const store = require("../store").default;
if (args.length > 0) {
let channels = args[0];
if (channels.length > 0) {
const chanTypes = vueApp.activeChannel.network.serverOptions.CHANTYPES;
const chanTypes = store.state.activeChannel.network.serverOptions.CHANTYPES;
const channelList = args[0].split(",");
if (chanTypes && chanTypes.length > 0) {
@ -29,17 +30,17 @@ exports.input = function(args) {
} else {
socket.emit("input", {
text: `/join ${channels} ${args.length > 1 ? args[1] : ""}`,
target: vueApp.activeChannel.channel.id,
target: store.state.activeChannel.channel.id,
});
return true;
}
}
} else if (vueApp.activeChannel.channel.type === "channel") {
} else if (store.state.activeChannel.channel.type === "channel") {
// If `/join` command is used without any arguments, re-join current channel
socket.emit("input", {
target: vueApp.activeChannel.channel.id,
text: `/join ${vueApp.activeChannel.channel.name}`,
target: store.state.activeChannel.channel.id,
text: `/join ${store.state.activeChannel.channel.name}`,
});
return true;

View File

@ -4,6 +4,7 @@ const $ = require("jquery");
const Mousetrap = require("mousetrap");
const utils = require("./utils");
const {vueApp} = require("./vue");
const store = require("./store").default;
Mousetrap.bind(["alt+up", "alt+down"], function(e, keys) {
const sidebar = $("#sidebar");
@ -76,7 +77,7 @@ Mousetrap.bind(["alt+shift+up", "alt+shift+down"], function(e, keys) {
Mousetrap.bind(["alt+a"], function() {
let targetchan;
outer_loop: for (const network of vueApp.networks) {
outer_loop: for (const network of store.state.networks) {
for (const chan of network.channels) {
if (chan.highlight) {
targetchan = chan;

View File

@ -1,45 +0,0 @@
"use strict";
// vendor libraries
const $ = require("jquery");
// our libraries
const socket = require("./socket");
window.vueMounted = () => {
require("./socket-events");
require("./contextMenuFactory");
require("./webpush");
require("./keybinds");
window.addEventListener("popstate", (e) => {
const {state} = e;
if (!state) {
return;
}
let {clickTarget} = state;
if (clickTarget) {
// This will be true when click target corresponds to opening a thumbnail,
// browsing to the previous/next thumbnail, or closing the image viewer.
const imageViewerRelated = clickTarget.includes(".toggle-thumbnail");
// If the click target is not related to the image viewer but the viewer
// is currently opened, we need to close it.
if (!imageViewerRelated && $("#image-viewer").hasClass("opened")) {
clickTarget += ", #image-viewer";
}
// Emit the click to the target, while making sure it is not going to be
// added to the state again.
$(clickTarget).trigger("click", {
pushState: false,
});
}
});
// Only start opening socket.io connection after all events have been registered
socket.open();
};

View File

@ -4,6 +4,7 @@ const $ = require("jquery");
const storage = require("./localStorage");
const socket = require("./socket");
const {vueApp} = require("./vue");
const store = require("./store").default;
require("../js/autocompletion");
const $theme = $("#theme");
@ -77,9 +78,9 @@ module.exports = {
// checkbox state can not be changed).
function updateDesktopNotificationStatus() {
if (Notification.permission === "granted") {
vueApp.desktopNotificationState = "granted";
store.commit("desktopNotificationState", "granted");
} else {
vueApp.desktopNotificationState = "blocked";
store.commit("desktopNotificationState", "blocked");
}
}
@ -89,7 +90,7 @@ function applySetting(name, value) {
if ($theme.attr("href") !== themeUrl) {
$theme.attr("href", themeUrl);
const newTheme = vueApp.serverConfiguration.themes.filter(
const newTheme = store.state.serverConfiguration.themes.filter(
(theme) => theme.name === value
)[0];
let themeColor = defaultThemeColor;
@ -171,7 +172,7 @@ function initialize() {
if ("Notification" in window) {
updateDesktopNotificationStatus();
} else {
vueApp.desktopNotificationState = "unsupported";
store.commit("desktopNotificationState", "unsupported");
}
// Local init is done, let's sync

View File

@ -4,6 +4,8 @@ const Vue = require("vue").default;
const VueRouter = require("vue-router").default;
Vue.use(VueRouter);
const store = require("./store").default;
const SignIn = require("../components/Windows/SignIn.vue").default;
const Connect = require("../components/Windows/Connect.vue").default;
const Settings = require("../components/Windows/Settings.vue").default;
@ -82,13 +84,13 @@ router.afterEach((to) => {
if (!to.meta.isChat) {
// Navigating out of a chat window
router.app.$store.commit("activeWindow", to.meta.windowName);
store.commit("activeWindow", to.meta.windowName);
if (router.app.activeChannel && router.app.activeChannel.channel) {
router.app.switchOutOfChannel(router.app.activeChannel.channel);
if (store.state.activeChannel && store.state.activeChannel.channel) {
router.app.switchOutOfChannel(store.state.activeChannel.channel);
}
router.app.activeChannel = null;
store.commit("activeChannel", null);
}
});

View File

@ -4,15 +4,16 @@ const $ = require("jquery");
const socket = require("../socket");
const storage = require("../localStorage");
const utils = require("../utils");
const {vueApp, getActiveWindowComponent} = require("../vue");
const {getActiveWindowComponent} = require("../vue");
const store = require("../store").default;
socket.on("auth", function(data) {
// If we reconnected and serverHash differs, that means the server restarted
// And we will reload the page to grab the latest version
if (utils.serverHash > -1 && data.serverHash > -1 && data.serverHash !== utils.serverHash) {
socket.disconnect();
vueApp.$store.commit("isConnected", false);
vueApp.currentUserVisibleError = "Server restarted, reloading…";
store.commit("isConnected", false);
store.commit("currentUserVisibleError", "Server restarted, reloading…");
location.reload(true);
return;
}
@ -27,10 +28,10 @@ socket.on("auth", function(data) {
const user = storage.get("user");
if (!data.success) {
if (vueApp.$store.state.activeWindow !== "SignIn") {
if (store.state.activeWindow !== "SignIn") {
socket.disconnect();
vueApp.$store.commit("isConnected", false);
vueApp.currentUserVisibleError = "Authentication failed, reloading…";
store.commit("isConnected", false);
store.commit("currentUserVisibleError", "Authentication failed, reloading…");
location.reload();
return;
}
@ -42,12 +43,12 @@ socket.on("auth", function(data) {
token = storage.get("token");
if (token) {
vueApp.currentUserVisibleError = "Authorizing…";
$("#loading-page-message").text(vueApp.currentUserVisibleError);
store.commit("currentUserVisibleError", "Authorizing…");
$("#loading-page-message").text(store.state.currentUserVisibleError);
let lastMessage = -1;
for (const network of vueApp.networks) {
for (const network of store.state.networks) {
for (const chan of network.channels) {
if (chan.messages.length > 0) {
const id = chan.messages[chan.messages.length - 1].id;
@ -59,7 +60,8 @@ socket.on("auth", function(data) {
}
}
const openChannel = (vueApp.activeChannel && vueApp.activeChannel.channel.id) || null;
const openChannel =
(store.state.activeChannel && store.state.activeChannel.channel.id) || null;
socket.emit("auth", {user, token, lastMessage, openChannel});
}

View File

@ -1,11 +1,11 @@
"use strict";
const socket = require("../socket");
const {vueApp} = require("../vue");
const store = require("../store").default;
socket.on("changelog", function(data) {
vueApp.$store.commit("versionData", data);
vueApp.$store.commit("versionDataExpired", false);
store.commit("versionData", data);
store.commit("versionDataExpired", false);
let status;
@ -19,14 +19,11 @@ socket.on("changelog", function(data) {
status = "error";
}
vueApp.$store.commit("versionStatus", status);
store.commit("versionStatus", status);
// When there is a button to refresh the checker available, display it when
// data is expired. Before that, server would return same information anyway.
if (data.expiresAt) {
setTimeout(
() => vueApp.$store.commit("versionDataExpired", true),
data.expiresAt - Date.now()
);
setTimeout(() => store.commit("versionDataExpired", true), data.expiresAt - Date.now());
}
});

View File

@ -5,7 +5,7 @@ const socket = require("../socket");
const options = require("../options");
const webpush = require("../webpush");
const upload = require("../upload");
const {vueApp} = require("../vue");
const store = require("../store").default;
window.addEventListener("beforeinstallprompt", (installPromptEvent) => {
$("#webapp-install-button")
@ -22,7 +22,7 @@ window.addEventListener("beforeinstallprompt", (installPromptEvent) => {
});
socket.on("configuration", function(data) {
vueApp.isFileUploadEnabled = data.fileUpload;
store.commit("isFileUploadEnabled", data.fileUpload);
if (options.initialized) {
// Likely a reconnect, request sync for possibly missed settings.
@ -30,7 +30,7 @@ socket.on("configuration", function(data) {
return;
}
vueApp.serverConfiguration = data;
store.commit("serverConfiguration", data);
if (data.fileUpload) {
upload.initialize(data.fileUploadMaxFileSize);

View File

@ -8,16 +8,18 @@ const sidebar = $("#sidebar");
const storage = require("../localStorage");
const constants = require("../constants");
const {vueApp, initChannel} = require("../vue");
const store = require("../store").default;
socket.on("init", function(data) {
vueApp.currentUserVisibleError = "Rendering…";
$("#loading-page-message").text(vueApp.currentUserVisibleError);
store.commit("currentUserVisibleError", "Rendering…");
const previousActive = vueApp.activeChannel && vueApp.activeChannel.channel.id;
$("#loading-page-message").text(store.state.currentUserVisibleError);
vueApp.networks = mergeNetworkData(data.networks);
vueApp.$store.commit("isConnected", true);
vueApp.currentUserVisibleError = null;
const previousActive = store.state.activeChannel && store.state.activeChannel.channel.id;
store.commit("networks", mergeNetworkData(data.networks));
store.commit("isConnected", true);
store.commit("currentUserVisibleError", null);
if (!vueApp.initialized) {
vueApp.onSocketInit();
@ -106,7 +108,7 @@ function mergeNetworkData(newNetworks) {
for (let n = 0; n < newNetworks.length; n++) {
const network = newNetworks[n];
const currentNetwork = vueApp.networks.find((net) => net.uuid === network.uuid);
const currentNetwork = vueApp.findNetwork(network.uuid);
// If this network is new, set some default variables and initalize channel variables
if (!currentNetwork) {
@ -154,7 +156,7 @@ function mergeChannelData(oldChannels, newChannels) {
}
// Merge received channel object into existing currentChannel
// so the object references are exactly the same (e.g. in vueApp.activeChannel)
// so the object references are exactly the same (e.g. in store.state.activeChannel)
for (const key in channel) {
if (!Object.prototype.hasOwnProperty.call(channel, key)) {
continue;
@ -163,7 +165,10 @@ function mergeChannelData(oldChannels, newChannels) {
// Server sends an empty users array, client requests it whenever needed
if (key === "users") {
if (channel.type === "channel") {
if (vueApp.activeChannel && vueApp.activeChannel.channel === currentChannel) {
if (
store.state.activeChannel &&
store.state.activeChannel.channel === currentChannel
) {
// For currently open channel, request the user list straight away
socket.emit("names", {
target: channel.id,

View File

@ -1,14 +1,18 @@
"use strict";
const socket = require("../socket");
const {vueApp, initChannel} = require("../vue");
const {vueApp, initChannel, findNetwork} = require("../vue");
socket.on("join", function(data) {
initChannel(data.chan);
vueApp.networks
.find((n) => n.uuid === data.network)
.channels.splice(data.index || -1, 0, data.chan);
const network = findNetwork(data.network);
if (!network) {
return;
}
network.channels.splice(data.index || -1, 0, data.chan);
// Queries do not automatically focus, unless the user did a whois
if (data.chan.type === "query" && !data.shouldOpen) {

View File

@ -6,6 +6,7 @@ const options = require("../options");
const cleanIrcMessage = require("../libs/handlebars/ircmessageparser/cleanIrcMessage");
const webpush = require("../webpush");
const {vueApp, findChannel} = require("../vue");
const store = require("../store").default;
let pop;
@ -26,17 +27,18 @@ socket.on("msg", function(data) {
}
let channel = receivingChannel.channel;
const isActiveChannel = vueApp.activeChannel && vueApp.activeChannel.channel === channel;
const isActiveChannel =
store.state.activeChannel && store.state.activeChannel.channel === channel;
// Display received notices and errors in currently active channel.
// Reloading the page will put them back into the lobby window.
// We only want to put errors/notices in active channel if they arrive on the same network
if (
data.msg.showInActive &&
vueApp.activeChannel &&
vueApp.activeChannel.network === receivingChannel.network
store.state.activeChannel &&
store.state.activeChannel.network === receivingChannel.network
) {
channel = vueApp.activeChannel.channel;
channel = store.state.activeChannel.channel;
if (data.chan === channel.id) {
// If active channel is the intended channel for this message,
@ -63,7 +65,7 @@ socket.on("msg", function(data) {
if (data.msg.self) {
channel.firstUnread = data.msg.id;
} else {
notifyMessage(data.chan, channel, vueApp.activeChannel, data.msg);
notifyMessage(data.chan, channel, store.state.activeChannel, data.msg);
}
let messageLimit = 0;

View File

@ -2,6 +2,7 @@
const socket = require("../socket");
const {vueApp, initChannel, findChannel, findNetwork} = require("../vue");
const store = require("../store").default;
socket.on("network", function(data) {
const network = data.networks[0];
@ -10,16 +11,20 @@ socket.on("network", function(data) {
network.isCollapsed = false;
network.channels.forEach(initChannel);
vueApp.networks.push(network);
store.commit("networks", [...store.state.networks, network]);
vueApp.switchToChannel(network.channels[0]);
});
socket.on("network:options", function(data) {
vueApp.networks.find((n) => n.uuid === data.network).serverOptions = data.serverOptions;
const network = findNetwork(data.network);
if (network) {
network.serverOptions = data.serverOptions;
}
});
socket.on("network:status", function(data) {
const network = vueApp.networks.find((n) => n.uuid === data.network);
const network = findNetwork(data.network);
if (!network) {
return;

View File

@ -1,10 +1,10 @@
"use strict";
const socket = require("../socket");
const {vueApp} = require("../vue");
const {findNetwork} = require("../vue");
socket.on("nick", function(data) {
const network = vueApp.networks.find((n) => n.uuid === data.network);
const network = findNetwork(data.network);
if (network) {
network.nick = data.nick;

View File

@ -2,6 +2,7 @@
const socket = require("../socket");
const {vueApp, findChannel} = require("../vue");
const store = require("../store").default;
// Sync unread badge and marker when other clients open a channel
socket.on("open", function(id) {
@ -10,7 +11,7 @@ socket.on("open", function(id) {
}
// Don't do anything if the channel is active on this client
if (vueApp.activeChannel && vueApp.activeChannel.channel.id === id) {
if (store.state.activeChannel && store.state.activeChannel.channel.id === id) {
return;
}

View File

@ -2,11 +2,12 @@
const socket = require("../socket");
const {vueApp, findChannel} = require("../vue");
const store = require("../store").default;
socket.on("part", function(data) {
// When parting from the active channel/query, jump to the network's lobby
if (vueApp.activeChannel && vueApp.activeChannel.channel.id === data.chan) {
vueApp.switchToChannel(vueApp.activeChannel.network);
if (store.state.activeChannel && store.state.activeChannel.channel.id === data.chan) {
vueApp.switchToChannel(store.state.activeChannel.network);
}
const channel = findChannel(data.chan);

View File

@ -4,12 +4,10 @@ const $ = require("jquery");
const socket = require("../socket");
const sidebar = $("#sidebar");
const {vueApp} = require("../vue");
const store = require("../store").default;
socket.on("quit", function(data) {
vueApp.networks.splice(
vueApp.networks.findIndex((n) => n.uuid === data.network),
1
);
store.commit("removeNetwork", data.network);
vueApp.$nextTick(() => {
const chan = sidebar.find(".chan");

View File

@ -1,8 +1,9 @@
"use strict";
const socket = require("../socket");
const {vueApp} = require("../vue");
const store = require("../store").default;
socket.on("sessions:list", function(data) {
data.sort((a, b) => b.lastUse - a.lastUse);
vueApp.$store.commit("sessions", data);
store.commit("sessions", data);
});

View File

@ -1,19 +1,20 @@
"use strict";
const socket = require("../socket");
const {vueApp} = require("../vue");
const {findNetwork} = require("../vue");
const store = require("../store").default;
socket.on("sync_sort", function(data) {
const order = data.order;
switch (data.type) {
case "networks":
vueApp.networks.sort((a, b) => order.indexOf(a.uuid) - order.indexOf(b.uuid));
store.commit("sortNetworks", (a, b) => order.indexOf(a.uuid) - order.indexOf(b.uuid));
break;
case "channels": {
const network = vueApp.networks.find((n) => n.uuid === data.target);
const network = findNetwork(data.target);
if (!network) {
return;

View File

@ -1,10 +1,11 @@
"use strict";
const socket = require("../socket");
const {vueApp, findChannel} = require("../vue");
const {findChannel} = require("../vue");
const store = require("../store").default;
socket.on("users", function(data) {
if (vueApp.activeChannel && vueApp.activeChannel.channel.id === data.chan) {
if (store.state.activeChannel && store.state.activeChannel.channel.id === data.chan) {
return socket.emit("names", {
target: data.chan,
});

View File

@ -12,21 +12,21 @@ const socket = io({
module.exports = socket;
const {vueApp} = require("./vue");
const {requestIdleCallback} = require("./utils");
const store = require("./store").default;
socket.on("disconnect", handleDisconnect);
socket.on("connect_error", handleDisconnect);
socket.on("error", handleDisconnect);
socket.on("reconnecting", function(attempt) {
vueApp.currentUserVisibleError = `Reconnecting… (attempt ${attempt})`;
$("#loading-page-message").text(vueApp.currentUserVisibleError);
store.commit("currentUserVisibleError", `Reconnecting… (attempt ${attempt})`);
$("#loading-page-message").text(store.state.currentUserVisibleError);
});
socket.on("connecting", function() {
vueApp.currentUserVisibleError = "Connecting…";
$("#loading-page-message").text(vueApp.currentUserVisibleError);
store.commit("currentUserVisibleError", `Connecting…`);
$("#loading-page-message").text(store.state.currentUserVisibleError);
});
socket.on("connect", function() {
@ -35,21 +35,21 @@ socket.on("connect", function() {
// nothing is sent to the server that might have happened.
socket.sendBuffer = [];
vueApp.currentUserVisibleError = "Finalizing connection…";
$("#loading-page-message").text(vueApp.currentUserVisibleError);
store.commit("currentUserVisibleError", "Finalizing connection…");
$("#loading-page-message").text(store.state.currentUserVisibleError);
});
socket.on("authorized", function() {
vueApp.currentUserVisibleError = "Loading messages…";
$("#loading-page-message").text(vueApp.currentUserVisibleError);
store.commit("currentUserVisibleError", "Loading messages…");
$("#loading-page-message").text(store.state.currentUserVisibleError);
});
function handleDisconnect(data) {
const message = data.message || data;
vueApp.$store.commit("isConnected", false);
vueApp.currentUserVisibleError = `Waiting to reconnect… (${message})`;
$("#loading-page-message").text(vueApp.currentUserVisibleError);
store.commit("isConnected", false);
store.commit("currentUserVisibleError", `Waiting to reconnect… (${message})`);
$("#loading-page-message").text(store.state.currentUserVisibleError);
// If the server shuts down, socket.io skips reconnection
// and we have to manually call connect to start the process

View File

@ -4,11 +4,19 @@ const storage = require("./localStorage");
Vue.use(Vuex);
export default new Vuex.Store({
const store = new Vuex.Store({
state: {
activeChannel: null,
currentUserVisibleError: null,
desktopNotificationState: "unsupported",
isAutoCompleting: false,
isConnected: false,
isFileUploadEnabled: false,
isNotified: false,
activeWindow: null,
networks: [],
pushNotificationState: "unsupported",
serverConfiguration: {},
sessions: [],
sidebarOpen: false,
sidebarDragging: false,
@ -18,17 +26,44 @@ export default new Vuex.Store({
versionDataExpired: false,
},
mutations: {
activeChannel(state, channel) {
state.activeChannel = channel;
},
currentUserVisibleError(state, error) {
state.currentUserVisibleError = error;
},
desktopNotificationState(state, desktopNotificationState) {
state.desktopNotificationState = desktopNotificationState;
},
isAutoCompleting(state, isAutoCompleting) {
state.isAutoCompleting = isAutoCompleting;
},
isConnected(state, payload) {
state.isConnected = payload;
},
isFileUploadEnabled(state, isFileUploadEnabled) {
state.isFileUploadEnabled = isFileUploadEnabled;
},
isNotified(state, payload) {
state.isNotified = payload;
},
activeWindow(state, payload) {
state.activeWindow = payload;
},
currentNetworkConfig(state, payload) {
state.currentNetworkConfig = payload;
networks(state, networks) {
state.networks = networks;
},
removeNetwork(state, networkId) {
state.networks.splice(store.state.networks.findIndex((n) => n.uuid === networkId), 1);
},
sortNetworks(state, sortFn) {
state.networks.sort(sortFn);
},
pushNotificationState(state, pushNotificationState) {
state.pushNotificationState = pushNotificationState;
},
serverConfiguration(state, serverConfiguration) {
state.serverConfiguration = serverConfiguration;
},
sessions(state, payload) {
state.sessions = payload;
@ -57,3 +92,5 @@ export default new Vuex.Store({
otherSessions: (state) => state.sessions.filter((item) => !item.current),
},
});
export default store;

View File

@ -2,11 +2,11 @@
const socket = require("./socket");
const updateCursor = require("undate").update;
const store = require("./store").default;
class Uploader {
init(maxFileSize) {
this.maxFileSize = maxFileSize;
this.vueApp = require("./vue").vueApp;
this.xhr = null;
this.fileQueue = [];
@ -89,7 +89,7 @@ class Uploader {
return;
}
if (!this.vueApp.$store.state.isConnected) {
if (!store.state.isConnected) {
this.handleResponse({
error: `You are currently disconnected, unable to initiate upload process.`,
});
@ -177,7 +177,7 @@ class Uploader {
this.setProgress(0);
if (response.error) {
this.vueApp.currentUserVisibleError = response.error;
store.commit("currentUserVisibleError", response.error);
return;
}

View File

@ -2,7 +2,7 @@
const $ = require("jquery");
const escape = require("css.escape");
const {vueApp} = require("./vue");
const store = require("./store").default;
var serverHash = -1; // eslint-disable-line no-var
@ -11,14 +11,13 @@ module.exports = {
serverHash,
scrollIntoViewNicely,
hasRoleInChannel,
move,
requestIdleCallback,
};
function findCurrentNetworkChan(name) {
name = name.toLowerCase();
return vueApp.activeChannel.network.channels.find((c) => c.name.toLowerCase() === name);
return store.state.activeChannel.network.channels.find((c) => c.name.toLowerCase() === name);
}
// Given a channel element will determine if the lounge user or a given nick is one of the supplied roles.
@ -41,19 +40,6 @@ function scrollIntoViewNicely(el) {
el.scrollIntoView({block: "center", inline: "nearest"});
}
function move(array, old_index, new_index) {
if (new_index >= array.length) {
let k = new_index - array.length;
while (k-- + 1) {
this.push(undefined);
}
}
array.splice(new_index, 0, array.splice(old_index, 1)[0]);
return array;
}
function requestIdleCallback(callback, timeout) {
if (window.requestIdleCallback) {
// During an idle period the user agent will run idle callbacks in FIFO order

View File

@ -17,19 +17,12 @@ Vue.filter("friendlysize", friendlysize);
Vue.filter("colorClass", colorClass);
Vue.filter("roundBadgeNumber", roundBadgeNumber);
const appName = document.title;
const vueApp = new Vue({
el: "#viewport",
data: {
activeChannel: null,
appName: document.title,
currentUserVisibleError: null,
initialized: false,
isAutoCompleting: false,
isFileUploadEnabled: false,
networks: [],
pushNotificationState: "unsupported",
desktopNotificationState: "unsupported",
serverConfiguration: {},
settings: {
syncSettings: false,
advanced: false,
@ -51,8 +44,6 @@ const vueApp = new Vue({
},
router,
mounted() {
Vue.nextTick(() => window.vueMounted());
if (navigator.platform.match(/(Mac|iPhone|iPod|iPad)/i)) {
document.body.classList.add("is-apple");
}
@ -60,6 +51,20 @@ const vueApp = new Vue({
document.addEventListener("visibilitychange", this.synchronizeNotifiedState());
document.addEventListener("focus", this.synchronizeNotifiedState());
document.addEventListener("click", this.synchronizeNotifiedState());
// TODO: Hackfix because socket-events require vueApp somewhere
// and that breaks due to cyclical depenency as by this point vue.js
// does not export anything yet.
setTimeout(() => {
const socket = require("./socket");
require("./socket-events");
require("./contextMenuFactory");
require("./webpush");
require("./keybinds");
socket.open();
}, 1);
},
methods: {
onSocketInit() {
@ -92,7 +97,7 @@ const vueApp = new Vue({
this.setUserlist(!this.$store.state.userlistOpen);
},
findChannel(id) {
for (const network of this.networks) {
for (const network of this.$store.state.networks) {
for (const channel of network.channels) {
if (channel.id === id) {
return {network, channel};
@ -103,7 +108,7 @@ const vueApp = new Vue({
return null;
},
findNetwork(uuid) {
for (const network of this.networks) {
for (const network of this.$store.state.networks) {
if (network.uuid === uuid) {
return network;
}
@ -112,7 +117,10 @@ const vueApp = new Vue({
return null;
},
switchToChannel(channel) {
if (this.activeChannel && this.activeChannel.channel.id === channel.id) {
if (
this.$store.state.activeChannel &&
this.$store.state.activeChannel.channel.id === channel.id
) {
return;
}
@ -134,7 +142,7 @@ const vueApp = new Vue({
let hasAnyHighlights = false;
for (const network of this.networks) {
for (const network of this.$store.state.networks) {
for (const chan of network.channels) {
if (chan.highlight > 0) {
hasAnyHighlights = true;
@ -146,16 +154,16 @@ const vueApp = new Vue({
this.toggleNotificationMarkers(hasAnyHighlights);
},
updateTitle() {
let title = this.appName;
let title = appName;
if (this.activeChannel) {
title = `${this.activeChannel.channel.name}${title}`;
if (this.$store.state.activeChannel) {
title = `${this.$store.state.activeChannel.channel.name}${title}`;
}
// add highlight count to title
let alertEventCount = 0;
for (const network of this.networks) {
for (const network of this.$store.state.networks) {
for (const channel of network.channels) {
alertEventCount += channel.highlight;
}
@ -190,8 +198,8 @@ const vueApp = new Vue({
});
Vue.config.errorHandler = function(e) {
store.commit("currentUserVisibleError", `Vue error: ${e.message}`);
console.error(e); // eslint-disable-line
vueApp.currentUserVisibleError = `Vue error: ${e.message}. Please check devtools and report it in #thelounge`;
};
function findChannel(id) {

View File

@ -3,7 +3,7 @@
const $ = require("jquery");
const storage = require("./localStorage");
const socket = require("./socket");
const {vueApp} = require("./vue");
const store = require("./store").default;
let pushNotificationsButton;
let clientSubscribed = null;
@ -44,7 +44,7 @@ module.exports.initialize = () => {
pushNotificationsButton = $("#pushNotifications");
if (!isAllowedServiceWorkersHost()) {
vueApp.pushNotificationState = "nohttps";
store.commit("pushNotificationState", "nohttps");
return;
}
@ -59,7 +59,7 @@ module.exports.initialize = () => {
}
return registration.pushManager.getSubscription().then((subscription) => {
vueApp.pushNotificationState = "supported";
store.commit("pushNotificationState", "supported");
clientSubscribed = !!subscription;
@ -69,7 +69,7 @@ module.exports.initialize = () => {
});
})
.catch(() => {
vueApp.pushNotificationState = "unsupported";
store.commit("pushNotificationState", "unsupported");
});
}
};
@ -126,7 +126,7 @@ module.exports.onPushButton = () => {
})
)
.catch(() => {
vueApp.pushNotificationState = "unsupported";
store.commit("pushNotificationState", "unsupported");
});
return false;

View File

@ -10,7 +10,7 @@ const Helper = require("./src/helper.js");
const config = {
mode: process.env.NODE_ENV === "production" ? "production" : "development",
entry: {
"js/bundle.js": [path.resolve(__dirname, "client/js/lounge.js")],
"js/bundle.js": [path.resolve(__dirname, "client/js/vue.js")],
"css/style": path.resolve(__dirname, "client/css/style.css"),
},
devtool: "source-map",