refactor: more eye candy for video player.
This commit is contained in:
parent
b438947b0d
commit
f2d7e59420
4 changed files with 130 additions and 25 deletions
|
|
@ -2,6 +2,12 @@
|
||||||
<div
|
<div
|
||||||
class="mx-auto flex min-h-0 w-full max-w-6xl flex-1 flex-col gap-4 px-3 py-4 sm:px-4 sm:py-5"
|
class="mx-auto flex min-h-0 w-full max-w-6xl flex-1 flex-col gap-4 px-3 py-4 sm:px-4 sm:py-5"
|
||||||
>
|
>
|
||||||
|
<div
|
||||||
|
class="pointer-events-none fixed inset-0 z-20 bg-black/45 backdrop-blur-[1px] transition-all duration-500 ease-out"
|
||||||
|
:class="lightsOut ? 'opacity-100' : 'opacity-0'"
|
||||||
|
aria-hidden="true"
|
||||||
|
/>
|
||||||
|
|
||||||
<div
|
<div
|
||||||
class="transition-transform duration-300"
|
class="transition-transform duration-300"
|
||||||
:class="{
|
:class="{
|
||||||
|
|
@ -615,7 +621,10 @@
|
||||||
:open="Boolean(videoItem)"
|
:open="Boolean(videoItem)"
|
||||||
title="Video"
|
title="Video"
|
||||||
:dismissible="true"
|
:dismissible="true"
|
||||||
:ui="{ content: 'w-full sm:max-w-5xl', body: 'p-0' }"
|
:ui="{
|
||||||
|
content: lightsOut ? 'w-full sm:max-w-5xl shadow-2xl' : 'w-full sm:max-w-5xl',
|
||||||
|
body: 'p-0',
|
||||||
|
}"
|
||||||
@update:open="(open) => !open && closePlayer()"
|
@update:open="(open) => !open && closePlayer()"
|
||||||
>
|
>
|
||||||
<template #body>
|
<template #body>
|
||||||
|
|
@ -628,6 +637,7 @@
|
||||||
class="w-full"
|
class="w-full"
|
||||||
@closeModel="closePlayer"
|
@closeModel="closePlayer"
|
||||||
@error="async (error: string) => await box.alert(error)"
|
@error="async (error: string) => await box.alert(error)"
|
||||||
|
@playback-state-change="(playing: boolean) => (playingNow = playing)"
|
||||||
/>
|
/>
|
||||||
</template>
|
</template>
|
||||||
</UModal>
|
</UModal>
|
||||||
|
|
@ -719,6 +729,7 @@ const {
|
||||||
|
|
||||||
const embedUrl = ref('');
|
const embedUrl = ref('');
|
||||||
const videoItem = ref<StoreItem | null>(null);
|
const videoItem = ref<StoreItem | null>(null);
|
||||||
|
const playingNow = ref(false);
|
||||||
const autoRefreshInterval = ref<ReturnType<typeof setInterval> | null>(null);
|
const autoRefreshInterval = ref<ReturnType<typeof setInterval> | null>(null);
|
||||||
|
|
||||||
const formUrl = ref('');
|
const formUrl = ref('');
|
||||||
|
|
@ -768,6 +779,7 @@ const historyEntries = computed<StoreItem[]>(() => historyItems.value);
|
||||||
const hasAnyItems = computed(() => queueItems.value.length > 0 || historyEntries.value.length > 0);
|
const hasAnyItems = computed(() => queueItems.value.length > 0 || historyEntries.value.length > 0);
|
||||||
const showSections = computed(() => hasAnyItems.value || historyIsLoading.value);
|
const showSections = computed(() => hasAnyItems.value || historyIsLoading.value);
|
||||||
const isFormDisabled = computed(() => addInProgress.value);
|
const isFormDisabled = computed(() => addInProgress.value);
|
||||||
|
const lightsOut = computed(() => Boolean(videoItem.value && playingNow.value));
|
||||||
const formContainerClass = computed(() => {
|
const formContainerClass = computed(() => {
|
||||||
if (showSections.value) {
|
if (showSections.value) {
|
||||||
return 'max-w-full';
|
return 'max-w-full';
|
||||||
|
|
@ -1027,6 +1039,7 @@ const openPlayer = (item: StoreItem): void => {
|
||||||
};
|
};
|
||||||
|
|
||||||
const closePlayer = (): void => {
|
const closePlayer = (): void => {
|
||||||
|
playingNow.value = false;
|
||||||
embedUrl.value = '';
|
embedUrl.value = '';
|
||||||
videoItem.value = null;
|
videoItem.value = null;
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -10,7 +10,7 @@
|
||||||
:class="
|
:class="
|
||||||
isFullscreen
|
isFullscreen
|
||||||
? 'h-screen w-screen max-h-screen max-w-none items-center justify-center rounded-none'
|
? 'h-screen w-screen max-h-screen max-w-none items-center justify-center rounded-none'
|
||||||
: 'max-h-[70vh] max-w-full items-center justify-center sm:max-h-[72vh]'
|
: 'min-h-72 max-h-[70vh] max-w-full items-center justify-center sm:min-h-88 sm:max-h-[72vh]'
|
||||||
"
|
"
|
||||||
>
|
>
|
||||||
<button
|
<button
|
||||||
|
|
@ -24,6 +24,7 @@
|
||||||
:src="uri(poster)"
|
:src="uri(poster)"
|
||||||
:alt="`${title || 'Untitled media'} preview`"
|
:alt="`${title || 'Untitled media'} preview`"
|
||||||
class="block h-full w-full bg-black object-contain opacity-90 transition duration-200 group-hover:opacity-100"
|
class="block h-full w-full bg-black object-contain opacity-90 transition duration-200 group-hover:opacity-100"
|
||||||
|
@error="handlePosterError"
|
||||||
/>
|
/>
|
||||||
<div
|
<div
|
||||||
v-else
|
v-else
|
||||||
|
|
@ -57,7 +58,7 @@
|
||||||
:class="
|
:class="
|
||||||
isFullscreen
|
isFullscreen
|
||||||
? 'h-full w-full max-h-screen max-w-screen'
|
? 'h-full w-full max-h-screen max-w-screen'
|
||||||
: 'h-auto w-full max-w-full max-h-[70vh] sm:max-h-[72vh]'
|
: 'h-full min-h-72 w-full max-w-full max-h-[70vh] sm:min-h-88 sm:max-h-[72vh]'
|
||||||
"
|
"
|
||||||
playsinline
|
playsinline
|
||||||
webkit-playsinline
|
webkit-playsinline
|
||||||
|
|
@ -70,8 +71,9 @@
|
||||||
@timeupdate="handleVideoTimeUpdate"
|
@timeupdate="handleVideoTimeUpdate"
|
||||||
@play="handleVideoPlay"
|
@play="handleVideoPlay"
|
||||||
@pause="handleVideoPause"
|
@pause="handleVideoPause"
|
||||||
|
@click="handleVideoClick"
|
||||||
@dblclick="handleVideoDoubleClick"
|
@dblclick="handleVideoDoubleClick"
|
||||||
@pointermove="showControls"
|
@pointermove="handlePointerMove"
|
||||||
@resize="scheduleAssLayoutRefresh"
|
@resize="scheduleAssLayoutRefresh"
|
||||||
@volumechange="handleMediaVolumeChange"
|
@volumechange="handleMediaVolumeChange"
|
||||||
@webkitbeginfullscreen="handleVideoWebkitBeginFullscreen"
|
@webkitbeginfullscreen="handleVideoWebkitBeginFullscreen"
|
||||||
|
|
@ -148,34 +150,39 @@
|
||||||
|
|
||||||
<div
|
<div
|
||||||
v-if="active"
|
v-if="active"
|
||||||
class="absolute inset-x-0 bottom-0 z-30 bg-linear-to-t from-black/95 via-black/70 to-transparent px-3 pb-3 pt-10 text-white transition-opacity duration-200"
|
class="absolute inset-x-0 bottom-0 z-30 bg-linear-to-t from-black/60 via-black/22 to-transparent px-3 pb-3 pt-10 text-white transition-opacity duration-150"
|
||||||
:class="controlsVisible ? 'opacity-100' : 'pointer-events-none opacity-0'"
|
:class="controlsVisible ? 'opacity-100' : 'pointer-events-none opacity-0'"
|
||||||
@click.self="toggleControls"
|
@click.self="toggleControls"
|
||||||
@pointermove="showControls"
|
@pointermove="showControls"
|
||||||
>
|
>
|
||||||
<div class="rounded-sm border border-white/10 bg-black/45 p-3 shadow-2xl backdrop-blur-md">
|
<div
|
||||||
<div class="space-y-3">
|
class="rounded-sm border border-white/8 bg-black/18 p-2.5 shadow-lg backdrop-blur-sm sm:p-3"
|
||||||
<input
|
>
|
||||||
:value="progress"
|
<div class="flex flex-col gap-2.5 sm:flex-row sm:items-center sm:gap-3">
|
||||||
type="range"
|
<div class="sm:min-w-0 sm:flex-1">
|
||||||
min="0"
|
<input
|
||||||
max="1000"
|
:value="progress"
|
||||||
step="1"
|
type="range"
|
||||||
class="h-1.5 w-full accent-white"
|
min="0"
|
||||||
aria-label="Seek video"
|
max="1000"
|
||||||
@input="handleSeekInput"
|
step="1"
|
||||||
/>
|
class="h-1.5 w-full accent-white opacity-70 transition-opacity hover:opacity-100"
|
||||||
<div class="flex items-center justify-between gap-2">
|
aria-label="Seek video"
|
||||||
<div class="flex items-center gap-2">
|
@input="handleSeekInput"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div class="flex items-center justify-between gap-2 sm:shrink-0 sm:justify-end">
|
||||||
|
<div class="flex min-w-0 items-center gap-2">
|
||||||
<UButton
|
<UButton
|
||||||
color="neutral"
|
color="neutral"
|
||||||
variant="soft"
|
variant="soft"
|
||||||
size="sm"
|
size="sm"
|
||||||
|
class="opacity-75 transition-opacity hover:opacity-100 focus-visible:opacity-100"
|
||||||
:icon="paused ? 'i-lucide-play' : 'i-lucide-pause'"
|
:icon="paused ? 'i-lucide-play' : 'i-lucide-pause'"
|
||||||
:aria-label="paused ? 'Play video' : 'Pause video'"
|
:aria-label="paused ? 'Play video' : 'Pause video'"
|
||||||
@click="togglePlayback"
|
@click="togglePlayback"
|
||||||
/>
|
/>
|
||||||
<div class="min-w-0 text-xs font-medium text-white/90">
|
<div class="min-w-0 whitespace-nowrap text-xs font-medium text-white/70">
|
||||||
{{ timeLabel }}
|
{{ timeLabel }}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -185,6 +192,7 @@
|
||||||
color="neutral"
|
color="neutral"
|
||||||
variant="soft"
|
variant="soft"
|
||||||
size="sm"
|
size="sm"
|
||||||
|
class="opacity-75 transition-opacity hover:opacity-100 focus-visible:opacity-100"
|
||||||
:icon="subtitleEnabled ? 'i-lucide-captions' : 'i-lucide-captions-off'"
|
:icon="subtitleEnabled ? 'i-lucide-captions' : 'i-lucide-captions-off'"
|
||||||
:aria-label="subtitleEnabled ? 'Disable subtitles' : 'Enable subtitles'"
|
:aria-label="subtitleEnabled ? 'Disable subtitles' : 'Enable subtitles'"
|
||||||
@click="subtitleEnabled = !subtitleEnabled"
|
@click="subtitleEnabled = !subtitleEnabled"
|
||||||
|
|
@ -193,6 +201,7 @@
|
||||||
color="neutral"
|
color="neutral"
|
||||||
variant="soft"
|
variant="soft"
|
||||||
size="sm"
|
size="sm"
|
||||||
|
class="opacity-75 transition-opacity hover:opacity-100 focus-visible:opacity-100"
|
||||||
:icon="effectiveVolume <= 0 ? 'i-lucide-volume-x' : 'i-lucide-volume-2'"
|
:icon="effectiveVolume <= 0 ? 'i-lucide-volume-x' : 'i-lucide-volume-2'"
|
||||||
:aria-label="effectiveVolume <= 0 ? 'Unmute video' : 'Mute video'"
|
:aria-label="effectiveVolume <= 0 ? 'Unmute video' : 'Mute video'"
|
||||||
@click="toggleMute"
|
@click="toggleMute"
|
||||||
|
|
@ -203,7 +212,7 @@
|
||||||
min="0"
|
min="0"
|
||||||
max="100"
|
max="100"
|
||||||
step="1"
|
step="1"
|
||||||
class="w-20 accent-white"
|
class="w-16 accent-white opacity-70 transition-opacity hover:opacity-100 sm:w-20"
|
||||||
aria-label="Video volume"
|
aria-label="Video volume"
|
||||||
@input="handleVolumeInput"
|
@input="handleVolumeInput"
|
||||||
/>
|
/>
|
||||||
|
|
@ -211,6 +220,7 @@
|
||||||
color="neutral"
|
color="neutral"
|
||||||
variant="soft"
|
variant="soft"
|
||||||
size="sm"
|
size="sm"
|
||||||
|
class="opacity-75 transition-opacity hover:opacity-100 focus-visible:opacity-100"
|
||||||
:icon="isFullscreen ? 'i-lucide-minimize' : 'i-lucide-maximize'"
|
:icon="isFullscreen ? 'i-lucide-minimize' : 'i-lucide-maximize'"
|
||||||
:aria-label="isFullscreen ? 'Exit fullscreen' : 'Enter fullscreen'"
|
:aria-label="isFullscreen ? 'Exit fullscreen' : 'Enter fullscreen'"
|
||||||
@click="toggleFullscreen"
|
@click="toggleFullscreen"
|
||||||
|
|
@ -369,6 +379,7 @@ const props = defineProps<{ item: StoreItem }>();
|
||||||
const emitter = defineEmits<{
|
const emitter = defineEmits<{
|
||||||
(e: 'closeModel'): void;
|
(e: 'closeModel'): void;
|
||||||
(e: 'error', message: string): void;
|
(e: 'error', message: string): void;
|
||||||
|
(e: 'playback-state-change', isPlaying: boolean): void;
|
||||||
}>();
|
}>();
|
||||||
|
|
||||||
const {
|
const {
|
||||||
|
|
@ -420,6 +431,7 @@ const helpPortal = computed<boolean | HTMLElement>(() => {
|
||||||
|
|
||||||
let assLayoutRefreshFrame = 0;
|
let assLayoutRefreshFrame = 0;
|
||||||
let controlsHideTimeout = 0;
|
let controlsHideTimeout = 0;
|
||||||
|
let pendingVideoClickTimeout = 0;
|
||||||
let gainContext: AudioContext | null = null;
|
let gainContext: AudioContext | null = null;
|
||||||
let gainSource: MediaElementAudioSourceNode | null = null;
|
let gainSource: MediaElementAudioSourceNode | null = null;
|
||||||
let gainNode: GainNode | null = null;
|
let gainNode: GainNode | null = null;
|
||||||
|
|
@ -580,15 +592,30 @@ function handleVideoPlay() {
|
||||||
void resumeGainController();
|
void resumeGainController();
|
||||||
syncVideoState();
|
syncVideoState();
|
||||||
showControls();
|
showControls();
|
||||||
|
emitter('playback-state-change', true);
|
||||||
}
|
}
|
||||||
|
|
||||||
function handleVideoPause() {
|
function handleVideoPause() {
|
||||||
syncVideoState();
|
syncVideoState();
|
||||||
clearControlsHideTimeout();
|
clearControlsHideTimeout();
|
||||||
controlsVisible.value = true;
|
controlsVisible.value = true;
|
||||||
|
emitter('playback-state-change', false);
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleVideoClick() {
|
||||||
|
if (isTouchDevice.value) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
clearPendingVideoClickTimeout();
|
||||||
|
pendingVideoClickTimeout = window.setTimeout(() => {
|
||||||
|
pendingVideoClickTimeout = 0;
|
||||||
|
toggleControls();
|
||||||
|
}, 180);
|
||||||
}
|
}
|
||||||
|
|
||||||
function handleVideoDoubleClick() {
|
function handleVideoDoubleClick() {
|
||||||
|
clearPendingVideoClickTimeout();
|
||||||
void toggleFullscreen();
|
void toggleFullscreen();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -604,6 +631,17 @@ function handleMediaError(event: Event) {
|
||||||
void src_error(event);
|
void src_error(event);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function handlePosterError(event: Event) {
|
||||||
|
const target = event.target as HTMLImageElement | null;
|
||||||
|
if (!target || poster.value === '/images/placeholder.png') {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
poster.value = '/images/placeholder.png';
|
||||||
|
hasPoster.value = false;
|
||||||
|
target.src = uri('/images/placeholder.png');
|
||||||
|
}
|
||||||
|
|
||||||
function handleMediaVolumeChange(event: Event) {
|
function handleMediaVolumeChange(event: Event) {
|
||||||
const target = event.target as HTMLMediaElement | null;
|
const target = event.target as HTMLMediaElement | null;
|
||||||
if (!target || typeof target.volume !== 'number') return;
|
if (!target || typeof target.volume !== 'number') return;
|
||||||
|
|
@ -623,6 +661,20 @@ function handleMediaVolumeChange(event: Event) {
|
||||||
updateMediaSessionPosition(target);
|
updateMediaSessionPosition(target);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function handlePointerMove(event: PointerEvent) {
|
||||||
|
if (!playerContainer.value || isTouchDevice.value) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const rect = playerContainer.value.getBoundingClientRect();
|
||||||
|
const y = event.clientY - rect.top;
|
||||||
|
const bottomZone = Math.min(Math.max(rect.height * 0.28, 96), 180);
|
||||||
|
|
||||||
|
if (y >= rect.height - bottomZone) {
|
||||||
|
showControls();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
function handleSeekInput(event: Event) {
|
function handleSeekInput(event: Event) {
|
||||||
const target = event.target as HTMLInputElement | null;
|
const target = event.target as HTMLInputElement | null;
|
||||||
if (!target || !videoElement.value || !duration.value) return;
|
if (!target || !videoElement.value || !duration.value) return;
|
||||||
|
|
@ -778,6 +830,7 @@ function syncVideoState() {
|
||||||
currentTime.value = 0;
|
currentTime.value = 0;
|
||||||
duration.value = 0;
|
duration.value = 0;
|
||||||
paused.value = true;
|
paused.value = true;
|
||||||
|
emitter('playback-state-change', false);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -788,6 +841,7 @@ function syncVideoState() {
|
||||||
duration.value = nextDuration;
|
duration.value = nextDuration;
|
||||||
currentTime.value = nextTime;
|
currentTime.value = nextTime;
|
||||||
paused.value = video.paused;
|
paused.value = video.paused;
|
||||||
|
emitter('playback-state-change', !video.paused);
|
||||||
}
|
}
|
||||||
|
|
||||||
function scheduleAssLayoutRefresh() {
|
function scheduleAssLayoutRefresh() {
|
||||||
|
|
@ -839,6 +893,13 @@ function clearControlsHideTimeout() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function clearPendingVideoClickTimeout() {
|
||||||
|
if (pendingVideoClickTimeout) {
|
||||||
|
window.clearTimeout(pendingVideoClickTimeout);
|
||||||
|
pendingVideoClickTimeout = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
function syncFullscreenState() {
|
function syncFullscreenState() {
|
||||||
const fullscreenElement = getFullscreenElement();
|
const fullscreenElement = getFullscreenElement();
|
||||||
isFullscreen.value = Boolean(
|
isFullscreen.value = Boolean(
|
||||||
|
|
@ -1076,6 +1137,9 @@ async function loadPlayerInfo() {
|
||||||
|
|
||||||
playerInfo.value = response;
|
playerInfo.value = response;
|
||||||
|
|
||||||
|
poster.value = '/images/placeholder.png';
|
||||||
|
hasPoster.value = false;
|
||||||
|
|
||||||
if (props.item.extras?.thumbnail) {
|
if (props.item.extras?.thumbnail) {
|
||||||
poster.value = `/api/thumbnail?url=${encodePath(props.item.extras.thumbnail)}`;
|
poster.value = `/api/thumbnail?url=${encodePath(props.item.extras.thumbnail)}`;
|
||||||
hasPoster.value = true;
|
hasPoster.value = true;
|
||||||
|
|
@ -1293,6 +1357,7 @@ onBeforeUnmount(() => {
|
||||||
}
|
}
|
||||||
|
|
||||||
clearControlsHideTimeout();
|
clearControlsHideTimeout();
|
||||||
|
clearPendingVideoClickTimeout();
|
||||||
|
|
||||||
if (hls) {
|
if (hls) {
|
||||||
hls.destroy();
|
hls.destroy();
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,11 @@
|
||||||
<template>
|
<template>
|
||||||
<div class="w-full min-w-0 max-w-full space-y-6">
|
<div class="w-full min-w-0 max-w-full space-y-6">
|
||||||
|
<div
|
||||||
|
class="pointer-events-none fixed inset-0 z-20 bg-black/45 backdrop-blur-[1px] transition-all duration-500 ease-out"
|
||||||
|
:class="lightsOut ? 'opacity-100' : 'opacity-0'"
|
||||||
|
aria-hidden="true"
|
||||||
|
/>
|
||||||
|
|
||||||
<div class="flex flex-col gap-4 xl:flex-row xl:items-start xl:justify-between">
|
<div class="flex flex-col gap-4 xl:flex-row xl:items-start xl:justify-between">
|
||||||
<div class="flex min-w-0 items-start gap-3">
|
<div class="flex min-w-0 items-start gap-3">
|
||||||
<span
|
<span
|
||||||
|
|
@ -502,6 +508,7 @@
|
||||||
:item="model_item"
|
:item="model_item"
|
||||||
class="w-full"
|
class="w-full"
|
||||||
@closeModel="closeModel"
|
@closeModel="closeModel"
|
||||||
|
@playback-state-change="(playing: boolean) => (playingNow = playing)"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<GetInfo
|
<GetInfo
|
||||||
|
|
@ -620,6 +627,7 @@ const initialPath = (() => {
|
||||||
const isUpdating = ref(false);
|
const isUpdating = ref(false);
|
||||||
|
|
||||||
const model_item = ref<any | null>(null);
|
const model_item = ref<any | null>(null);
|
||||||
|
const playingNow = ref(false);
|
||||||
|
|
||||||
const previewTitle = computed(() => {
|
const previewTitle = computed(() => {
|
||||||
if (!model_item.value) {
|
if (!model_item.value) {
|
||||||
|
|
@ -640,7 +648,10 @@ const previewTitle = computed(() => {
|
||||||
|
|
||||||
const previewModalUi = computed(() => {
|
const previewModalUi = computed(() => {
|
||||||
if (model_item.value?.type === 'video') {
|
if (model_item.value?.type === 'video') {
|
||||||
return { content: 'sm:max-w-5xl', body: 'p-0' };
|
return {
|
||||||
|
content: lightsOut.value ? 'sm:max-w-5xl shadow-2xl' : 'sm:max-w-5xl',
|
||||||
|
body: 'p-0',
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
if (model_item.value?.type === 'image') {
|
if (model_item.value?.type === 'image') {
|
||||||
|
|
@ -649,6 +660,7 @@ const previewModalUi = computed(() => {
|
||||||
|
|
||||||
return { content: 'sm:max-w-4xl', body: 'p-0' };
|
return { content: 'sm:max-w-4xl', body: 'p-0' };
|
||||||
});
|
});
|
||||||
|
const lightsOut = computed(() => Boolean(model_item.value?.type === 'video' && playingNow.value));
|
||||||
|
|
||||||
const buildStateUrl = (dir: string, page?: number): string => {
|
const buildStateUrl = (dir: string, page?: number): string => {
|
||||||
const params = new URLSearchParams();
|
const params = new URLSearchParams();
|
||||||
|
|
@ -730,6 +742,7 @@ watch(localSearch, async (value) => {
|
||||||
});
|
});
|
||||||
|
|
||||||
const closeModel = (): void => {
|
const closeModel = (): void => {
|
||||||
|
playingNow.value = false;
|
||||||
model_item.value = null;
|
model_item.value = null;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,11 @@
|
||||||
<template>
|
<template>
|
||||||
<main class="w-full min-w-0 max-w-full space-y-6">
|
<main class="w-full min-w-0 max-w-full space-y-6">
|
||||||
|
<div
|
||||||
|
class="pointer-events-none fixed inset-0 z-20 bg-black/45 backdrop-blur-[1px] transition-all duration-500 ease-out"
|
||||||
|
:class="lightsOut ? 'opacity-100' : 'opacity-0'"
|
||||||
|
aria-hidden="true"
|
||||||
|
/>
|
||||||
|
|
||||||
<div class="flex flex-col gap-4 xl:flex-row xl:items-start xl:justify-between">
|
<div class="flex flex-col gap-4 xl:flex-row xl:items-start xl:justify-between">
|
||||||
<div class="flex min-w-0 items-center gap-3">
|
<div class="flex min-w-0 items-center gap-3">
|
||||||
<span
|
<span
|
||||||
|
|
@ -714,8 +720,8 @@
|
||||||
:open="Boolean(video_item)"
|
:open="Boolean(video_item)"
|
||||||
:dismissible="true"
|
:dismissible="true"
|
||||||
:title="video_item?.title || 'Player'"
|
:title="video_item?.title || 'Player'"
|
||||||
:ui="{ content: 'sm:max-w-5xl', body: 'p-0' }"
|
:ui="{ content: lightsOut ? 'sm:max-w-5xl shadow-2xl' : 'sm:max-w-5xl', body: 'p-0' }"
|
||||||
@update:open="(open) => !open && (video_item = null)"
|
@update:open="(open) => !open && closeVideo()"
|
||||||
>
|
>
|
||||||
<template #body>
|
<template #body>
|
||||||
<VideoPlayer
|
<VideoPlayer
|
||||||
|
|
@ -725,8 +731,9 @@
|
||||||
:isControls="true"
|
:isControls="true"
|
||||||
:item="video_item"
|
:item="video_item"
|
||||||
class="w-full"
|
class="w-full"
|
||||||
@closeModel="video_item = null"
|
@closeModel="closeVideo()"
|
||||||
@error="async (error: string) => await box.alert(error)"
|
@error="async (error: string) => await box.alert(error)"
|
||||||
|
@playback-state-change="(playing: boolean) => (playingNow = playing)"
|
||||||
/>
|
/>
|
||||||
</template>
|
</template>
|
||||||
</UModal>
|
</UModal>
|
||||||
|
|
@ -826,12 +833,14 @@ const selectedElms = ref<string[]>([]);
|
||||||
const masterSelectAll = ref(false);
|
const masterSelectAll = ref(false);
|
||||||
const embed_url = ref('');
|
const embed_url = ref('');
|
||||||
const video_item = ref<StoreItem | null>(null);
|
const video_item = ref<StoreItem | null>(null);
|
||||||
|
const playingNow = ref(false);
|
||||||
const expandedMessages = reactive<Record<string, Set<string>>>({});
|
const expandedMessages = reactive<Record<string, Set<string>>>({});
|
||||||
|
|
||||||
const contentStyle = computed<'grid' | 'list'>(() =>
|
const contentStyle = computed<'grid' | 'list'>(() =>
|
||||||
isMobile.value ? 'grid' : display_style.value,
|
isMobile.value ? 'grid' : display_style.value,
|
||||||
);
|
);
|
||||||
const showThumbnails = computed(() => show_thumbnail.value && !hideThumbnail.value);
|
const showThumbnails = computed(() => show_thumbnail.value && !hideThumbnail.value);
|
||||||
|
const lightsOut = computed(() => Boolean(video_item.value && playingNow.value));
|
||||||
const paginationInfo = computed(() => ({
|
const paginationInfo = computed(() => ({
|
||||||
...pagination.value,
|
...pagination.value,
|
||||||
isLoading: isLoading.value,
|
isLoading: isLoading.value,
|
||||||
|
|
@ -878,6 +887,11 @@ const close_info = (): void => {
|
||||||
info_view.value.useUrl = false;
|
info_view.value.useUrl = false;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const closeVideo = (): void => {
|
||||||
|
playingNow.value = false;
|
||||||
|
video_item.value = null;
|
||||||
|
};
|
||||||
|
|
||||||
const view_info = (
|
const view_info = (
|
||||||
url: string,
|
url: string,
|
||||||
useUrl: boolean = false,
|
useUrl: boolean = false,
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue