mirror of
https://github.com/filebrowser/filebrowser.git
synced 2025-06-07 09:52:57 +00:00
345 lines
9.1 KiB
Vue
345 lines
9.1 KiB
Vue
<template>
|
|
<div
|
|
id="previewer"
|
|
@touchmove.prevent.stop
|
|
@wheel.prevent.stop
|
|
@mousemove="toggleNavigation"
|
|
@touchstart="toggleNavigation"
|
|
>
|
|
<header-bar v-if="isPdf || showNav">
|
|
<action icon="close" :label="$t('buttons.close')" @action="close()" />
|
|
<title>{{ name }}</title>
|
|
<action
|
|
:disabled="layoutStore.loading"
|
|
v-if="isResizeEnabled && fileStore.req?.type === 'image'"
|
|
:icon="fullSize ? 'photo_size_select_large' : 'hd'"
|
|
@action="toggleSize"
|
|
/>
|
|
|
|
<template #actions>
|
|
<action
|
|
:disabled="layoutStore.loading"
|
|
v-if="authStore.user?.perm.rename"
|
|
icon="mode_edit"
|
|
:label="$t('buttons.rename')"
|
|
show="rename"
|
|
/>
|
|
<action
|
|
:disabled="layoutStore.loading"
|
|
v-if="authStore.user?.perm.delete"
|
|
icon="delete"
|
|
:label="$t('buttons.delete')"
|
|
@action="deleteFile"
|
|
id="delete-button"
|
|
/>
|
|
<action
|
|
:disabled="layoutStore.loading"
|
|
v-if="authStore.user?.perm.download"
|
|
icon="file_download"
|
|
:label="$t('buttons.download')"
|
|
@action="download"
|
|
/>
|
|
<action
|
|
:disabled="layoutStore.loading"
|
|
icon="info"
|
|
:label="$t('buttons.info')"
|
|
show="info"
|
|
/>
|
|
</template>
|
|
</header-bar>
|
|
|
|
<div class="loading delayed" v-if="layoutStore.loading">
|
|
<div class="spinner">
|
|
<div class="bounce1"></div>
|
|
<div class="bounce2"></div>
|
|
<div class="bounce3"></div>
|
|
</div>
|
|
</div>
|
|
<template v-else>
|
|
<div class="preview">
|
|
<ExtendedImage v-if="fileStore.req?.type == 'image'" :src="raw" />
|
|
<audio
|
|
v-else-if="fileStore.req?.type == 'audio'"
|
|
ref="player"
|
|
:src="raw"
|
|
controls
|
|
:autoplay="autoPlay"
|
|
@play="autoPlay = true"
|
|
></audio>
|
|
<VideoPlayer
|
|
v-else-if="fileStore.req?.type == 'video'"
|
|
ref="player"
|
|
:source="raw"
|
|
:subtitles="subtitles"
|
|
:options="videoOptions"
|
|
>
|
|
</VideoPlayer>
|
|
<object v-else-if="isPdf" class="pdf" :data="raw"></object>
|
|
<div v-else-if="fileStore.req?.type == 'blob'" class="info">
|
|
<div class="title">
|
|
<i class="material-icons">feedback</i>
|
|
{{ $t("files.noPreview") }}
|
|
</div>
|
|
<div>
|
|
<a target="_blank" :href="downloadUrl" class="button button--flat">
|
|
<div>
|
|
<i class="material-icons">file_download</i
|
|
>{{ $t("buttons.download") }}
|
|
</div>
|
|
</a>
|
|
<a
|
|
target="_blank"
|
|
:href="raw"
|
|
class="button button--flat"
|
|
v-if="!fileStore.req?.isDir"
|
|
>
|
|
<div>
|
|
<i class="material-icons">open_in_new</i
|
|
>{{ $t("buttons.openFile") }}
|
|
</div>
|
|
</a>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</template>
|
|
|
|
<button
|
|
@click="prev"
|
|
@mouseover="hoverNav = true"
|
|
@mouseleave="hoverNav = false"
|
|
:class="{ hidden: !hasPrevious || !showNav }"
|
|
:aria-label="$t('buttons.previous')"
|
|
:title="$t('buttons.previous')"
|
|
>
|
|
<i class="material-icons">chevron_left</i>
|
|
</button>
|
|
<button
|
|
@click="next"
|
|
@mouseover="hoverNav = true"
|
|
@mouseleave="hoverNav = false"
|
|
:class="{ hidden: !hasNext || !showNav }"
|
|
:aria-label="$t('buttons.next')"
|
|
:title="$t('buttons.next')"
|
|
>
|
|
<i class="material-icons">chevron_right</i>
|
|
</button>
|
|
<link rel="prefetch" :href="previousRaw" />
|
|
<link rel="prefetch" :href="nextRaw" />
|
|
</div>
|
|
</template>
|
|
|
|
<script setup lang="ts">
|
|
import { useAuthStore } from "@/stores/auth";
|
|
import { useFileStore } from "@/stores/file";
|
|
import { useLayoutStore } from "@/stores/layout";
|
|
|
|
import { files as api } from "@/api";
|
|
import { resizePreview } from "@/utils/constants";
|
|
import url from "@/utils/url";
|
|
import throttle from "lodash/throttle";
|
|
import HeaderBar from "@/components/header/HeaderBar.vue";
|
|
import Action from "@/components/header/Action.vue";
|
|
import ExtendedImage from "@/components/files/ExtendedImage.vue";
|
|
import { computed, inject, onBeforeUnmount, onMounted, ref, watch } from "vue";
|
|
import { useRoute, useRouter } from "vue-router";
|
|
import VideoPlayer from "@/components/files/VideoPlayer.vue";
|
|
|
|
const mediaTypes: ResourceType[] = ["image", "video", "audio", "blob"];
|
|
|
|
const previousLink = ref<string>("");
|
|
const nextLink = ref<string>("");
|
|
const listing = ref<ResourceItem[] | null>(null);
|
|
const name = ref<string>("");
|
|
const fullSize = ref<boolean>(false);
|
|
const showNav = ref<boolean>(true);
|
|
const navTimeout = ref<null | number>(null);
|
|
const hoverNav = ref<boolean>(false);
|
|
const autoPlay = ref<boolean>(false);
|
|
const previousRaw = ref<string>("");
|
|
const nextRaw = ref<string>("");
|
|
|
|
const player = ref<HTMLVideoElement | HTMLAudioElement | null>(null);
|
|
|
|
const $showError = inject<IToastError>("$showError")!;
|
|
|
|
const authStore = useAuthStore();
|
|
const fileStore = useFileStore();
|
|
const layoutStore = useLayoutStore();
|
|
|
|
const route = useRoute();
|
|
const router = useRouter();
|
|
|
|
const hasPrevious = computed(() => previousLink.value !== "");
|
|
|
|
const hasNext = computed(() => nextLink.value !== "");
|
|
|
|
const downloadUrl = computed(() =>
|
|
fileStore.req ? api.getDownloadURL(fileStore.req, true) : ""
|
|
);
|
|
|
|
const raw = computed(() => {
|
|
if (fileStore.req?.type === "image" && !fullSize.value) {
|
|
return api.getPreviewURL(fileStore.req, "big");
|
|
}
|
|
|
|
return downloadUrl.value;
|
|
});
|
|
|
|
const isPdf = computed(() => fileStore.req?.extension.toLowerCase() == ".pdf");
|
|
|
|
const isResizeEnabled = computed(() => resizePreview);
|
|
|
|
const subtitles = computed(() => {
|
|
if (fileStore.req?.subtitles) {
|
|
return api.getSubtitlesURL(fileStore.req);
|
|
}
|
|
return [];
|
|
});
|
|
|
|
const videoOptions = computed(() => {
|
|
return { autoplay: autoPlay.value };
|
|
});
|
|
|
|
watch(route, () => {
|
|
updatePreview();
|
|
toggleNavigation();
|
|
});
|
|
|
|
// Specify hooks
|
|
onMounted(async () => {
|
|
window.addEventListener("keydown", key);
|
|
if (fileStore.oldReq) {
|
|
listing.value = fileStore.oldReq.items;
|
|
updatePreview();
|
|
}
|
|
});
|
|
|
|
onBeforeUnmount(() => window.removeEventListener("keydown", key));
|
|
|
|
// Specify methods
|
|
const deleteFile = () => {
|
|
layoutStore.showHover({
|
|
prompt: "delete",
|
|
confirm: () => {
|
|
if (listing.value === null) {
|
|
return;
|
|
}
|
|
listing.value = listing.value.filter((item) => item.name !== name.value);
|
|
|
|
if (hasNext.value) {
|
|
next();
|
|
} else if (!hasPrevious.value && !hasNext.value) {
|
|
close();
|
|
} else {
|
|
prev();
|
|
}
|
|
},
|
|
});
|
|
};
|
|
|
|
const prev = () => {
|
|
hoverNav.value = false;
|
|
router.replace({ path: previousLink.value });
|
|
};
|
|
|
|
const next = () => {
|
|
hoverNav.value = false;
|
|
router.replace({ path: nextLink.value });
|
|
};
|
|
|
|
const key = (event: KeyboardEvent) => {
|
|
if (layoutStore.currentPrompt !== null) {
|
|
return;
|
|
}
|
|
if (event.which === 13 || event.which === 39) {
|
|
// right arrow
|
|
if (hasNext.value) next();
|
|
} else if (event.which === 37) {
|
|
// left arrow
|
|
if (hasPrevious.value) prev();
|
|
} else if (event.which === 27) {
|
|
// esc
|
|
close();
|
|
}
|
|
};
|
|
const updatePreview = async () => {
|
|
if (player.value && player.value.paused && !player.value.ended) {
|
|
autoPlay.value = false;
|
|
}
|
|
|
|
let dirs = route.fullPath.split("/");
|
|
name.value = decodeURIComponent(dirs[dirs.length - 1]);
|
|
|
|
if (!listing.value) {
|
|
try {
|
|
const path = url.removeLastDir(route.path);
|
|
const res = await api.fetch(path);
|
|
listing.value = res.items;
|
|
} catch (e: any) {
|
|
$showError(e);
|
|
}
|
|
}
|
|
|
|
previousLink.value = "";
|
|
nextLink.value = "";
|
|
if (listing.value) {
|
|
for (let i = 0; i < listing.value.length; i++) {
|
|
if (listing.value[i].name !== name.value) {
|
|
continue;
|
|
}
|
|
|
|
for (let j = i - 1; j >= 0; j--) {
|
|
if (mediaTypes.includes(listing.value[j].type)) {
|
|
previousLink.value = listing.value[j].url;
|
|
previousRaw.value = prefetchUrl(listing.value[j]);
|
|
break;
|
|
}
|
|
}
|
|
for (let j = i + 1; j < listing.value.length; j++) {
|
|
if (mediaTypes.includes(listing.value[j].type)) {
|
|
nextLink.value = listing.value[j].url;
|
|
nextRaw.value = prefetchUrl(listing.value[j]);
|
|
break;
|
|
}
|
|
}
|
|
|
|
return;
|
|
}
|
|
}
|
|
};
|
|
|
|
const prefetchUrl = (item: ResourceItem) => {
|
|
if (item.type !== "image") {
|
|
return "";
|
|
}
|
|
|
|
return fullSize.value
|
|
? api.getDownloadURL(item, true)
|
|
: api.getPreviewURL(item, "big");
|
|
};
|
|
|
|
const toggleSize = () => (fullSize.value = !fullSize.value);
|
|
|
|
const toggleNavigation = throttle(function () {
|
|
showNav.value = true;
|
|
|
|
if (navTimeout.value) {
|
|
clearTimeout(navTimeout.value);
|
|
}
|
|
|
|
navTimeout.value = setTimeout(() => {
|
|
showNav.value = false || hoverNav.value;
|
|
navTimeout.value = null;
|
|
}, 1500);
|
|
}, 500);
|
|
|
|
const close = () => {
|
|
fileStore.updateRequest(null);
|
|
|
|
let uri = url.removeLastDir(route.path) + "/";
|
|
router.push({ path: uri });
|
|
};
|
|
|
|
const download = () => window.open(downloadUrl.value);
|
|
</script>
|