Move most side bar and user list interactions to vue.

This commit is contained in:
Richard Lewis 2019-04-13 23:44:04 +03:00 committed by Pavel Djundik
parent e73bf1e9a7
commit 467ebab31f
14 changed files with 205 additions and 168 deletions

View file

@ -2,7 +2,7 @@
<!-- TODO: move all class toggling to vue, since vue clears existing classes when changing the notified class -->
<div
id="viewport"
:class="{notified: $store.state.isNotified}"
:class="viewportClasses"
role="tablist"
>
<Sidebar
@ -54,6 +54,15 @@ export default {
activeChannel: Object,
networks: Array,
},
computed: {
viewportClasses() {
return {
"notified": this.$store.state.isNotified,
"menu-open": this.$store.state.sidebarOpen,
"userlist-open": this.$store.state.userlistOpen,
};
},
},
mounted() {
// Make a single throttled resize listener available to all components
this.debouncedResize = throttle(() => {

View file

@ -1,4 +1,5 @@
<template>
<!-- TODO: move closed style to it's own class -->
<div
v-if="
!network.isCollapsed ||

View file

@ -18,7 +18,7 @@
role="tabpanel"
>
<div class="header">
<button class="lt" aria-label="Toggle channel list" />
<SidebarToggle />
<span class="title">{{ channel.name }}</span>
<div v-if="channel.editTopic === true" class="topic-container">
<input
@ -44,7 +44,11 @@
class="rt-tooltip tooltipped tooltipped-w"
aria-label="Toggle user list"
>
<button class="rt" aria-label="Toggle user list" />
<button
class="rt"
aria-label="Toggle user list"
@click="$root.toggleUserlist"
/>
</span>
</div>
<div v-if="channel.type === 'special'" class="chat-content">
@ -94,6 +98,7 @@ import ParsedMessage from "./ParsedMessage.vue";
import MessageList from "./MessageList.vue";
import ChatInput from "./ChatInput.vue";
import ChatUserList from "./ChatUserList.vue";
import SidebarToggle from "./SidebarToggle.vue";
import ListBans from "./Special/ListBans.vue";
import ListInvites from "./Special/ListInvites.vue";
import ListChannels from "./Special/ListChannels.vue";
@ -106,6 +111,7 @@ export default {
MessageList,
ChatInput,
ChatUserList,
SidebarToggle,
},
props: {
network: Object,

View file

@ -6,10 +6,7 @@
aria-label="Connect"
>
<div class="header">
<button
class="lt"
aria-label="Toggle channel list"
/>
<SidebarToggle />
</div>
<form
class="container"
@ -217,11 +214,13 @@
<script>
import RevealPassword from "./RevealPassword.vue";
import SidebarToggle from "./SidebarToggle.vue";
export default {
name: "NetworkForm",
components: {
RevealPassword,
SidebarToggle,
},
props: {
handleSubmit: Function,

View file

@ -66,7 +66,10 @@
/></span>
</footer>
</aside>
<div id="sidebar-overlay" />
<div
id="sidebar-overlay"
@click="$root.setSidebar(false)"
/>
</div>
</template>

View file

@ -0,0 +1,13 @@
<template>
<button
class="lt"
aria-label="Toggle channel list"
@click="$root.toggleSidebar"
/>
</template>
<script>
export default {
name: "SidebarToggle",
};
</script>

View file

@ -5,10 +5,7 @@
aria-label="Changelog"
>
<div class="header">
<button
class="lt"
aria-label="Toggle channel list"
/>
<SidebarToggle />
</div>
<div class="container">
<a
@ -42,8 +39,13 @@
</template>
<script>
import SidebarToggle from "../SidebarToggle.vue";
export default {
name: "Changelog",
components: {
SidebarToggle,
},
mounted() {
},

View file

@ -6,10 +6,7 @@
aria-label="Help"
>
<div class="header">
<button
class="lt"
aria-label="Toggle channel list"
/>
<SidebarToggle />
</div>
<div class="container">
<h1 class="title">Help</h1>
@ -659,7 +656,12 @@
</template>
<script>
import SidebarToggle from "../SidebarToggle.vue";
export default {
name: "Help",
components: {
SidebarToggle,
},
};
</script>

View file

@ -6,10 +6,7 @@
aria-label="Settings"
>
<div class="header">
<button
class="lt"
aria-label="Toggle channel list"
/>
<SidebarToggle />
</div>
<form
ref="settingsForm"
@ -505,12 +502,14 @@
import socket from "../../js/socket";
import RevealPassword from "../RevealPassword.vue";
import Session from "../Session.vue";
import SidebarToggle from "../SidebarToggle.vue";
export default {
name: "Settings",
components: {
RevealPassword,
Session,
SidebarToggle,
},
data() {
return {

View file

@ -10,9 +10,7 @@ const {vueApp, findChannel} = require("./vue");
window.vueMounted = () => {
require("./socket-events");
const slideoutMenu = require("./slideout");
const contextMenuFactory = require("./contextMenuFactory");
const storage = require("./localStorage");
const utils = require("./utils");
require("./webpush");
require("./keybinds");
@ -20,40 +18,6 @@ window.vueMounted = () => {
const sidebar = $("#sidebar, #footer");
const viewport = $("#viewport");
function storeSidebarVisibility(name, state) {
storage.set(name, state);
vueApp.$emit("resize");
}
// If sidebar overlay is visible and it is clicked, close the sidebar
$("#sidebar-overlay").on("click", () => {
slideoutMenu.toggle(false);
if ($(window).outerWidth() > utils.mobileViewportPixels) {
storeSidebarVisibility("thelounge.state.sidebar", false);
}
});
$("#windows").on("click", "button.lt", () => {
const isOpen = !slideoutMenu.isOpen();
slideoutMenu.toggle(isOpen);
if ($(window).outerWidth() > utils.mobileViewportPixels) {
storeSidebarVisibility("thelounge.state.sidebar", isOpen);
}
});
viewport.on("click", ".rt", function() {
const isOpen = !viewport.hasClass("userlist-open");
viewport.toggleClass("userlist-open", isOpen);
storeSidebarVisibility("thelounge.state.userlist", isOpen);
return false;
});
viewport.on("contextmenu", ".network .chan", function(e) {
return contextMenuFactory.createContextMenu($(this), e).show();
});
@ -138,8 +102,8 @@ window.vueMounted = () => {
socket.emit("open", channel ? channel.channel.id : null);
if ($(window).outerWidth() <= utils.mobileViewportPixels) {
slideoutMenu.toggle(false);
if (!keepSidebarOpen && $(window).outerWidth() <= utils.mobileViewportPixels) {
vueApp.setSidebar(false);
}
} else {
vueApp.activeChannel = null;

View file

@ -1,112 +1,112 @@
"use strict";
const viewport = document.getElementById("viewport");
const menu = document.getElementById("sidebar");
const sidebarOverlay = document.getElementById("sidebar-overlay");
let touchStartPos = null;
let touchCurPos = null;
let touchStartTime = 0;
let menuWidth = 0;
let menuIsOpen = false;
let menuIsMoving = false;
let menuIsAbsolute = false;
class SlideoutMenu {
static enable() {
document.body.addEventListener("touchstart", onTouchStart, {passive: true});
enable() {
this.viewport = document.getElementById("viewport");
this.menu = document.getElementById("sidebar");
this.sidebarOverlay = document.getElementById("sidebar-overlay");
this.touchStartPos = null;
this.touchCurPos = null;
this.touchStartTime = 0;
this.menuWidth = 0;
this.menuIsOpen = false;
this.menuIsMoving = false;
this.menuIsAbsolute = false;
this.onTouchStart = (e) => {
this.touchStartPos = this.touchCurPos = e.touches.item(0);
if (e.touches.length !== 1) {
this.onTouchEnd();
return;
}
const styles = window.getComputedStyle(this.menu);
this.menuWidth = parseFloat(styles.width);
this.menuIsAbsolute = styles.position === "absolute";
if (!this.menuIsOpen || this.touchStartPos.screenX > this.menuWidth) {
this.touchStartTime = Date.now();
document.body.addEventListener("touchmove", this.onTouchMove, {passive: true});
document.body.addEventListener("touchend", this.onTouchEnd, {passive: true});
}
};
this.onTouchMove = (e) => {
const touch = this.touchCurPos = e.touches.item(0);
let distX = touch.screenX - this.touchStartPos.screenX;
const distY = touch.screenY - this.touchStartPos.screenY;
if (!this.menuIsMoving) {
// tan(45°) is 1. Gestures in 0°-45° (< 1) are considered horizontal, so
// menu must be open; gestures in 45°-90° (>1) are considered vertical, so
// chat windows must be scrolled.
if (Math.abs(distY / distX) >= 1) {
this.onTouchEnd();
return;
}
const devicePixelRatio = window.devicePixelRatio || 2;
if (Math.abs(distX) > devicePixelRatio) {
this.viewport.classList.toggle("menu-dragging", true);
this.menuIsMoving = true;
}
}
// Do not animate the menu on desktop view
if (!this.menuIsAbsolute) {
return;
}
if (this.menuIsOpen) {
distX += this.menuWidth;
}
if (distX > this.menuWidth) {
distX = this.menuWidth;
} else if (distX < 0) {
distX = 0;
}
this.menu.style.transform = "translate3d(" + distX + "px, 0, 0)";
this.sidebarOverlay.style.opacity = distX / this.menuWidth;
};
this.onTouchEnd = () => {
const diff = this.touchCurPos.screenX - this.touchStartPos.screenX;
const absDiff = Math.abs(diff);
if (absDiff > this.menuWidth / 2 || Date.now() - this.touchStartTime < 180 && absDiff > 50) {
this.toggle(diff > 0);
}
document.body.removeEventListener("touchmove", this.onTouchMove);
document.body.removeEventListener("touchend", this.onTouchEnd);
this.viewport.classList.toggle("menu-dragging", false);
this.menu.style.transform = null;
this.sidebarOverlay.style.opacity = null;
this.touchStartPos = null;
this.touchCurPos = null;
this.touchStartTime = 0;
this.menuIsMoving = false;
};
document.body.addEventListener("touchstart", this.onTouchStart, {passive: true});
}
static toggle(state) {
menuIsOpen = state;
viewport.classList.toggle("menu-open", state);
toggle(state) {
this.menuIsOpen = state;
this.viewport.classList.toggle("menu-open", state);
}
static isOpen() {
return menuIsOpen;
isOpen() {
return this.menuIsOpen;
}
}
function onTouchStart(e) {
touchStartPos = touchCurPos = e.touches.item(0);
if (e.touches.length !== 1) {
onTouchEnd();
return;
}
const styles = window.getComputedStyle(menu);
menuWidth = parseFloat(styles.width);
menuIsAbsolute = styles.position === "absolute";
if (!menuIsOpen || touchStartPos.screenX > menuWidth) {
touchStartTime = Date.now();
document.body.addEventListener("touchmove", onTouchMove, {passive: true});
document.body.addEventListener("touchend", onTouchEnd, {passive: true});
}
}
function onTouchMove(e) {
const touch = (touchCurPos = e.touches.item(0));
let distX = touch.screenX - touchStartPos.screenX;
const distY = touch.screenY - touchStartPos.screenY;
if (!menuIsMoving) {
// tan(45°) is 1. Gestures in 0°-45° (< 1) are considered horizontal, so
// menu must be open; gestures in 45°-90° (>1) are considered vertical, so
// chat windows must be scrolled.
if (Math.abs(distY / distX) >= 1) {
onTouchEnd();
return;
}
const devicePixelRatio = window.devicePixelRatio || 2;
if (Math.abs(distX) > devicePixelRatio) {
viewport.classList.toggle("menu-dragging", true);
menuIsMoving = true;
}
}
// Do not animate the menu on desktop view
if (!menuIsAbsolute) {
return;
}
if (menuIsOpen) {
distX += menuWidth;
}
if (distX > menuWidth) {
distX = menuWidth;
} else if (distX < 0) {
distX = 0;
}
menu.style.transform = "translate3d(" + distX + "px, 0, 0)";
sidebarOverlay.style.opacity = distX / menuWidth;
}
function onTouchEnd() {
const diff = touchCurPos.screenX - touchStartPos.screenX;
const absDiff = Math.abs(diff);
if (absDiff > menuWidth / 2 || (Date.now() - touchStartTime < 180 && absDiff > 50)) {
SlideoutMenu.toggle(diff > 0);
}
document.body.removeEventListener("touchmove", onTouchMove);
document.body.removeEventListener("touchend", onTouchEnd);
viewport.classList.toggle("menu-dragging", false);
menu.style.transform = null;
sidebarOverlay.style.opacity = null;
touchStartPos = null;
touchCurPos = null;
touchStartTime = 0;
menuIsMoving = false;
}
module.exports = SlideoutMenu;
module.exports = (new SlideoutMenu);

View file

@ -4,7 +4,6 @@ const $ = require("jquery");
const escape = require("css.escape");
const socket = require("../socket");
const webpush = require("../webpush");
const slideoutMenu = require("../slideout");
const sidebar = $("#sidebar");
const storage = require("../localStorage");
const utils = require("../utils");
@ -21,7 +20,7 @@ socket.on("init", function(data) {
vueApp.currentUserVisibleError = null;
if (!vueApp.initialized) {
vueApp.initialized = true;
vueApp.onSocketInit();
if (data.token) {
storage.set("token", data.token);
@ -29,14 +28,11 @@ socket.on("init", function(data) {
webpush.configurePushNotifications(data.pushSubscription, data.applicationServerKey);
slideoutMenu.enable();
const viewport = $("#viewport");
const viewportWidth = $(window).outerWidth();
const viewportWidth = window.outerWidth;
let isUserlistOpen = storage.get("thelounge.state.userlist");
if (viewportWidth > utils.mobileViewportPixels) {
slideoutMenu.toggle(storage.get("thelounge.state.sidebar") !== "false");
vueApp.setSidebar(storage.get("thelounge.state.sidebar") !== "false");
}
// If The Lounge is opened on a small screen (less than 1024px), and we don't have stored
@ -45,7 +41,7 @@ socket.on("init", function(data) {
isUserlistOpen = "true";
}
viewport.toggleClass("userlist-open", isUserlistOpen === "true");
vueApp.setUserlist(isUserlistOpen === "true");
$(document.body).removeClass("signed-out");
$("#loading").remove();

View file

@ -1,5 +1,6 @@
import Vue from "vue";
import Vuex from "vuex";
const storage = require("./localStorage");
Vue.use(Vuex);
@ -9,6 +10,8 @@ export default new Vuex.Store({
isNotified: false,
activeWindow: null,
sessions: [],
sidebarOpen: false,
userlistOpen: storage.get("thelounge.state.userlist") !== "false",
},
mutations: {
isConnected(state, payload) {
@ -26,6 +29,12 @@ export default new Vuex.Store({
sessions(state, payload) {
state.sessions = payload;
},
sidebarOpen(state, payload) {
state.sidebarOpen = payload;
},
userlistOpen(state, payload) {
state.userlistOpen = payload;
},
},
getters: {
currentSession: (state) => state.sessions.find((item) => item.current),

View file

@ -7,6 +7,8 @@ const roundBadgeNumber = require("./libs/handlebars/roundBadgeNumber");
const localetime = require("./libs/handlebars/localetime");
const friendlysize = require("./libs/handlebars/friendlysize");
const colorClass = require("./libs/handlebars/colorClass");
const slideoutMenu = require("../js/slideout");
const storage = require("./localStorage");
Vue.filter("localetime", localetime);
Vue.filter("friendlysize", friendlysize);
@ -48,6 +50,38 @@ const vueApp = new Vue({
mounted() {
Vue.nextTick(() => window.vueMounted());
},
methods: {
onSocketInit() {
this.initialized = true;
this.$store.commit("isConnected", true);
// TODO: handle slideut in vue
slideoutMenu.enable();
},
setSidebar(state) {
const utils = require("./utils");
this.$store.commit("sidebarOpen", state);
slideoutMenu.toggle(false);
if (window.outerWidth > utils.mobileViewportPixels) {
storage.set("thelounge.state.sidebar", state);
}
this.$emit("resize");
},
toggleSidebar() {
this.setSidebar(!this.$store.state.sidebarOpen);
},
setUserlist(state) {
storage.set("thelounge.state.userlist", state);
this.$store.commit("userlistOpen", state);
this.$emit("resize");
},
toggleUserlist() {
this.setUserlist(!this.$store.state.userlistOpen);
},
},
render(createElement) {
return createElement(App, {
ref: "app",