mirror of
https://github.com/thelounge/thelounge
synced 2024-11-22 12:03:11 +00:00
269 lines
6.5 KiB
Vue
269 lines
6.5 KiB
Vue
<template>
|
|
<aside id="sidebar" ref="sidebar">
|
|
<div class="scrollable-area">
|
|
<div class="logo-container">
|
|
<img
|
|
:src="`img/logo-${isPublic() ? 'horizontal-' : ''}transparent-bg.svg`"
|
|
class="logo"
|
|
alt="The Lounge"
|
|
role="presentation"
|
|
/>
|
|
<img
|
|
:src="`img/logo-${isPublic() ? 'horizontal-' : ''}transparent-bg-inverted.svg`"
|
|
class="logo-inverted"
|
|
alt="The Lounge"
|
|
role="presentation"
|
|
/>
|
|
<span
|
|
v-if="isDevelopment"
|
|
title="The Lounge has been built in development mode"
|
|
:style="{
|
|
backgroundColor: '#ff9e18',
|
|
color: '#000',
|
|
padding: '2px',
|
|
borderRadius: '4px',
|
|
fontSize: '12px',
|
|
}"
|
|
>DEVELOPER</span
|
|
>
|
|
</div>
|
|
<NetworkList />
|
|
</div>
|
|
<footer id="footer">
|
|
<span
|
|
class="tooltipped tooltipped-n tooltipped-no-touch"
|
|
aria-label="Connect to network"
|
|
><router-link
|
|
v-slot:default="{navigate, isActive}"
|
|
to="/connect"
|
|
role="tab"
|
|
aria-controls="connect"
|
|
>
|
|
<button
|
|
:class="['icon', 'connect', {active: isActive}]"
|
|
:aria-selected="isActive"
|
|
@click="navigate"
|
|
@keypress.enter="navigate"
|
|
/> </router-link
|
|
></span>
|
|
<span class="tooltipped tooltipped-n tooltipped-no-touch" aria-label="Settings"
|
|
><router-link
|
|
v-slot:default="{navigate, isActive}"
|
|
to="/settings"
|
|
role="tab"
|
|
aria-controls="settings"
|
|
>
|
|
<button
|
|
:class="['icon', 'settings', {active: isActive}]"
|
|
:aria-selected="isActive"
|
|
@click="navigate"
|
|
@keypress.enter="navigate"
|
|
></button> </router-link
|
|
></span>
|
|
<span
|
|
class="tooltipped tooltipped-n tooltipped-no-touch"
|
|
:aria-label="
|
|
store.state.serverConfiguration?.isUpdateAvailable
|
|
? 'Help\n(update available)'
|
|
: 'Help'
|
|
"
|
|
><router-link
|
|
v-slot:default="{navigate, isActive}"
|
|
to="/help"
|
|
role="tab"
|
|
aria-controls="help"
|
|
>
|
|
<button
|
|
:aria-selected="route.name === 'Help'"
|
|
:class="[
|
|
'icon',
|
|
'help',
|
|
{notified: store.state.serverConfiguration?.isUpdateAvailable},
|
|
{active: isActive},
|
|
]"
|
|
@click="navigate"
|
|
@keypress.enter="navigate"
|
|
></button> </router-link
|
|
></span>
|
|
</footer>
|
|
</aside>
|
|
</template>
|
|
|
|
<script lang="ts">
|
|
import {defineComponent, nextTick, onMounted, onUnmounted, PropType, ref} from "vue";
|
|
import {useRoute} from "vue-router";
|
|
import {useStore} from "../js/store";
|
|
import NetworkList from "./NetworkList.vue";
|
|
|
|
export default defineComponent({
|
|
name: "Sidebar",
|
|
components: {
|
|
NetworkList,
|
|
},
|
|
props: {
|
|
overlay: {type: Object as PropType<HTMLElement | null>, required: true},
|
|
},
|
|
setup(props) {
|
|
const isDevelopment = process.env.NODE_ENV !== "production";
|
|
|
|
const store = useStore();
|
|
const route = useRoute();
|
|
|
|
const touchStartPos = ref<Touch | null>();
|
|
const touchCurPos = ref<Touch | null>();
|
|
const touchStartTime = ref<number>(0);
|
|
const menuWidth = ref<number>(0);
|
|
const menuIsMoving = ref<boolean>(false);
|
|
const menuIsAbsolute = ref<boolean>(false);
|
|
|
|
const sidebar = ref<HTMLElement | null>(null);
|
|
|
|
const toggle = (state: boolean) => {
|
|
store.commit("sidebarOpen", state);
|
|
};
|
|
|
|
const onTouchMove = (e: TouchEvent) => {
|
|
const touch = (touchCurPos.value = e.touches.item(0));
|
|
|
|
if (
|
|
!touch ||
|
|
!touchStartPos.value ||
|
|
!touchStartPos.value.screenX ||
|
|
!touchStartPos.value.screenY
|
|
) {
|
|
return;
|
|
}
|
|
|
|
let distX = touch.screenX - touchStartPos.value.screenX;
|
|
const distY = touch.screenY - touchStartPos.value.screenY;
|
|
|
|
if (!menuIsMoving.value) {
|
|
// 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) {
|
|
// eslint-disable-next-line no-use-before-define
|
|
onTouchEnd();
|
|
return;
|
|
}
|
|
|
|
const devicePixelRatio = window.devicePixelRatio || 2;
|
|
|
|
if (Math.abs(distX) > devicePixelRatio) {
|
|
store.commit("sidebarDragging", true);
|
|
menuIsMoving.value = true;
|
|
}
|
|
}
|
|
|
|
// Do not animate the menu on desktop view
|
|
if (!menuIsAbsolute.value) {
|
|
return;
|
|
}
|
|
|
|
if (store.state.sidebarOpen) {
|
|
distX += menuWidth.value;
|
|
}
|
|
|
|
if (distX > menuWidth.value) {
|
|
distX = menuWidth.value;
|
|
} else if (distX < 0) {
|
|
distX = 0;
|
|
}
|
|
|
|
if (sidebar.value) {
|
|
sidebar.value.style.transform = "translate3d(" + distX.toString() + "px, 0, 0)";
|
|
}
|
|
|
|
if (props.overlay) {
|
|
props.overlay.style.opacity = `${distX / menuWidth.value}`;
|
|
}
|
|
};
|
|
|
|
const onTouchEnd = () => {
|
|
if (!touchStartPos.value?.screenX || !touchCurPos.value?.screenX) {
|
|
return;
|
|
}
|
|
|
|
const diff = touchCurPos.value.screenX - touchStartPos.value.screenX;
|
|
const absDiff = Math.abs(diff);
|
|
|
|
if (
|
|
absDiff > menuWidth.value / 2 ||
|
|
(Date.now() - touchStartTime.value < 180 && absDiff > 50)
|
|
) {
|
|
toggle(diff > 0);
|
|
}
|
|
|
|
document.body.removeEventListener("touchmove", onTouchMove);
|
|
document.body.removeEventListener("touchend", onTouchEnd);
|
|
|
|
store.commit("sidebarDragging", false);
|
|
|
|
touchStartPos.value = null;
|
|
touchCurPos.value = null;
|
|
touchStartTime.value = 0;
|
|
menuIsMoving.value = false;
|
|
|
|
void nextTick(() => {
|
|
if (sidebar.value) {
|
|
sidebar.value.style.transform = "";
|
|
}
|
|
|
|
if (props.overlay) {
|
|
props.overlay.style.opacity = "";
|
|
}
|
|
});
|
|
};
|
|
|
|
const onTouchStart = (e: TouchEvent) => {
|
|
if (!sidebar.value) {
|
|
return;
|
|
}
|
|
|
|
touchStartPos.value = touchCurPos.value = e.touches.item(0);
|
|
|
|
if (e.touches.length !== 1) {
|
|
onTouchEnd();
|
|
return;
|
|
}
|
|
|
|
const styles = window.getComputedStyle(sidebar.value);
|
|
|
|
menuWidth.value = parseFloat(styles.width);
|
|
menuIsAbsolute.value = styles.position === "absolute";
|
|
|
|
if (
|
|
!store.state.sidebarOpen ||
|
|
(touchStartPos.value?.screenX && touchStartPos.value.screenX > menuWidth.value)
|
|
) {
|
|
touchStartTime.value = Date.now();
|
|
|
|
document.body.addEventListener("touchmove", onTouchMove, {passive: true});
|
|
document.body.addEventListener("touchend", onTouchEnd, {passive: true});
|
|
}
|
|
};
|
|
|
|
onMounted(() => {
|
|
document.body.addEventListener("touchstart", onTouchStart, {passive: true});
|
|
});
|
|
|
|
onUnmounted(() => {
|
|
document.body.removeEventListener("touchstart", onTouchStart);
|
|
});
|
|
|
|
const isPublic = () => document.body.classList.contains("public");
|
|
|
|
return {
|
|
isDevelopment,
|
|
store,
|
|
route,
|
|
sidebar,
|
|
toggle,
|
|
onTouchStart,
|
|
onTouchMove,
|
|
onTouchEnd,
|
|
isPublic,
|
|
};
|
|
},
|
|
});
|
|
</script>
|