feat: add swipe functionality for sidebar in mobile
This commit is contained in:
parent
00ce7efb32
commit
fe7c18e6c6
1 changed files with 143 additions and 1 deletions
|
|
@ -64,6 +64,7 @@
|
||||||
:ui="{ base: 'relative flex min-h-full overflow-visible' }"
|
:ui="{ base: 'relative flex min-h-full overflow-visible' }"
|
||||||
>
|
>
|
||||||
<UDashboardSidebar
|
<UDashboardSidebar
|
||||||
|
v-model:open="sidebarOpen"
|
||||||
side="left"
|
side="left"
|
||||||
collapsible
|
collapsible
|
||||||
resizable
|
resizable
|
||||||
|
|
@ -485,9 +486,10 @@
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import type { NavigationMenuItem } from '@nuxt/ui';
|
import type { NavigationMenuItem } from '@nuxt/ui';
|
||||||
import { ref, onMounted, readonly } from 'vue';
|
import { ref, onBeforeUnmount, onMounted, readonly } from 'vue';
|
||||||
import { useStorage } from '@vueuse/core';
|
import { useStorage } from '@vueuse/core';
|
||||||
import moment from 'moment';
|
import moment from 'moment';
|
||||||
|
import { useMediaQuery } from '~/composables/useMediaQuery';
|
||||||
import type { toastPosition } from '~/composables/useNotification';
|
import type { toastPosition } from '~/composables/useNotification';
|
||||||
import type { YTDLPOption } from '~/types/ytdlp';
|
import type { YTDLPOption } from '~/types/ytdlp';
|
||||||
import { useDialog } from '~/composables/useDialog';
|
import { useDialog } from '~/composables/useDialog';
|
||||||
|
|
@ -510,6 +512,10 @@ type SidebarSection = {
|
||||||
};
|
};
|
||||||
|
|
||||||
type ColorModePreference = 'system' | 'light' | 'dark';
|
type ColorModePreference = 'system' | 'light' | 'dark';
|
||||||
|
type SwipeMode = 'open' | 'close';
|
||||||
|
|
||||||
|
const MOBILE_SIDEBAR_EDGE_WIDTH = 32;
|
||||||
|
const MOBILE_SIDEBAR_MIN_SWIPE_DISTANCE = 64;
|
||||||
|
|
||||||
const socket = useSocketStore();
|
const socket = useSocketStore();
|
||||||
const config = useConfigStore();
|
const config = useConfigStore();
|
||||||
|
|
@ -525,7 +531,18 @@ const show_settings = ref(false);
|
||||||
const checkingUpdates = ref(false);
|
const checkingUpdates = ref(false);
|
||||||
const updateCheckMessage = ref('Up to date - Click to check');
|
const updateCheckMessage = ref('Up to date - Click to check');
|
||||||
const showRouteSearch = ref(false);
|
const showRouteSearch = ref(false);
|
||||||
|
const sidebarOpen = ref(false);
|
||||||
const { alertDialog, confirmDialog } = useDialog();
|
const { alertDialog, confirmDialog } = useDialog();
|
||||||
|
const isMobile = useMediaQuery({ query: '(max-width: 1023px)' });
|
||||||
|
|
||||||
|
const SwipeState = {
|
||||||
|
mode: null as SwipeMode | null,
|
||||||
|
tracking: false,
|
||||||
|
startX: 0,
|
||||||
|
startY: 0,
|
||||||
|
endX: 0,
|
||||||
|
endY: 0,
|
||||||
|
};
|
||||||
|
|
||||||
const colorModePreferences: Array<ColorModePreference> = ['system', 'light', 'dark'];
|
const colorModePreferences: Array<ColorModePreference> = ['system', 'light', 'dark'];
|
||||||
|
|
||||||
|
|
@ -578,6 +595,100 @@ const cycleColorMode = (): void => {
|
||||||
colorMode.preference = nextColorModePreference.value;
|
colorMode.preference = nextColorModePreference.value;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const resetSwipe = (): void => {
|
||||||
|
SwipeState.mode = null;
|
||||||
|
SwipeState.tracking = false;
|
||||||
|
SwipeState.startX = 0;
|
||||||
|
SwipeState.startY = 0;
|
||||||
|
SwipeState.endX = 0;
|
||||||
|
SwipeState.endY = 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
const updateSwipePosition = (touch?: Touch): void => {
|
||||||
|
if (!touch) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
SwipeState.endX = touch.clientX;
|
||||||
|
SwipeState.endY = touch.clientY;
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleSwipeStart = (event: TouchEvent): void => {
|
||||||
|
if (!isMobile.value || event.touches.length !== 1) {
|
||||||
|
resetSwipe();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const touch = event.touches[0];
|
||||||
|
|
||||||
|
if (!touch) {
|
||||||
|
resetSwipe();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const swipeMode: SwipeMode | null = sidebarOpen.value
|
||||||
|
? 'close'
|
||||||
|
: touch.clientX <= MOBILE_SIDEBAR_EDGE_WIDTH
|
||||||
|
? 'open'
|
||||||
|
: null;
|
||||||
|
|
||||||
|
if (!swipeMode) {
|
||||||
|
resetSwipe();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
SwipeState.mode = swipeMode;
|
||||||
|
SwipeState.tracking = true;
|
||||||
|
SwipeState.startX = touch.clientX;
|
||||||
|
SwipeState.startY = touch.clientY;
|
||||||
|
updateSwipePosition(touch);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleSwipeMove = (event: TouchEvent): void => {
|
||||||
|
if (!SwipeState.tracking || event.touches.length !== 1) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
updateSwipePosition(event.touches[0]);
|
||||||
|
};
|
||||||
|
|
||||||
|
const completeSwipe = (): void => {
|
||||||
|
if (!SwipeState.tracking) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const swipeMode = SwipeState.mode;
|
||||||
|
const deltaX = SwipeState.endX - SwipeState.startX;
|
||||||
|
const deltaY = SwipeState.endY - SwipeState.startY;
|
||||||
|
const isHorizontalOpenSwipe =
|
||||||
|
swipeMode === 'open' &&
|
||||||
|
deltaX >= MOBILE_SIDEBAR_MIN_SWIPE_DISTANCE &&
|
||||||
|
deltaX > Math.abs(deltaY);
|
||||||
|
const isHorizontalCloseSwipe =
|
||||||
|
swipeMode === 'close' &&
|
||||||
|
deltaX <= -MOBILE_SIDEBAR_MIN_SWIPE_DISTANCE &&
|
||||||
|
Math.abs(deltaX) > Math.abs(deltaY);
|
||||||
|
|
||||||
|
resetSwipe();
|
||||||
|
|
||||||
|
if (isHorizontalOpenSwipe) {
|
||||||
|
sidebarOpen.value = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isHorizontalCloseSwipe) {
|
||||||
|
sidebarOpen.value = false;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleSwipeEnd = (event: TouchEvent): void => {
|
||||||
|
updateSwipePosition(event.changedTouches[0]);
|
||||||
|
completeSwipe();
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleSwipeCancel = (): void => {
|
||||||
|
resetSwipe();
|
||||||
|
};
|
||||||
|
|
||||||
const dashboardSidebarUi = {
|
const dashboardSidebarUi = {
|
||||||
root: 'border-r border-default bg-default/95 backdrop-blur-sm',
|
root: 'border-r border-default bg-default/95 backdrop-blur-sm',
|
||||||
header: 'border-b border-default px-2.5 py-3',
|
header: 'border-b border-default px-2.5 py-3',
|
||||||
|
|
@ -819,6 +930,22 @@ const checkForUpdates = async () => {
|
||||||
};
|
};
|
||||||
|
|
||||||
onMounted(async () => {
|
onMounted(async () => {
|
||||||
|
document.addEventListener('touchstart', handleSwipeStart, {
|
||||||
|
passive: true,
|
||||||
|
capture: true,
|
||||||
|
});
|
||||||
|
document.addEventListener('touchmove', handleSwipeMove, {
|
||||||
|
passive: true,
|
||||||
|
capture: true,
|
||||||
|
});
|
||||||
|
document.addEventListener('touchend', handleSwipeEnd, {
|
||||||
|
passive: true,
|
||||||
|
capture: true,
|
||||||
|
});
|
||||||
|
document.addEventListener('touchcancel', handleSwipeCancel, {
|
||||||
|
passive: true,
|
||||||
|
capture: true,
|
||||||
|
});
|
||||||
syncShellModeClass();
|
syncShellModeClass();
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
|
@ -841,8 +968,23 @@ onMounted(async () => {
|
||||||
} catch {}
|
} catch {}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
onBeforeUnmount(() => {
|
||||||
|
document.removeEventListener('touchstart', handleSwipeStart, true);
|
||||||
|
document.removeEventListener('touchmove', handleSwipeMove, true);
|
||||||
|
document.removeEventListener('touchend', handleSwipeEnd, true);
|
||||||
|
document.removeEventListener('touchcancel', handleSwipeCancel, true);
|
||||||
|
});
|
||||||
|
|
||||||
watch(bg_enable, async (v) => await handleImage(v));
|
watch(bg_enable, async (v) => await handleImage(v));
|
||||||
watch(simpleMode, () => syncShellModeClass());
|
watch(simpleMode, () => syncShellModeClass());
|
||||||
|
watch(isMobile, (v) => {
|
||||||
|
if (v) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
sidebarOpen.value = false;
|
||||||
|
resetSwipe();
|
||||||
|
});
|
||||||
watch(bg_opacity, () => {
|
watch(bg_opacity, () => {
|
||||||
if (false === bg_enable.value) {
|
if (false === bg_enable.value) {
|
||||||
return;
|
return;
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue