You've already forked filebrowser
mirror of
https://github.com/filebrowser/filebrowser.git
synced 2025-11-26 14:25:26 +08:00
feat: migrate to vue 3 (#2689)
--------- Co-authored-by: Joep <jcbuhre@gmail.com> Co-authored-by: Omar Hussein <omarmohammad1951@gmail.com> Co-authored-by: Oleg Lobanov <oleg@lobanov.me>
This commit is contained in:
@@ -1,46 +1,46 @@
|
||||
<template>
|
||||
<div
|
||||
id="previewer"
|
||||
@touchmove.prevent.stop
|
||||
@touchmove.prevent.stop
|
||||
@wheel.prevent.stop
|
||||
@mousemove="toggleNavigation"
|
||||
@touchstart="toggleNavigation"
|
||||
>
|
||||
<header-bar v-if="showNav">
|
||||
<header-bar v-if="showNav">
|
||||
<action icon="close" :label="$t('buttons.close')" @action="close()" />
|
||||
<title>{{ name }}</title>
|
||||
<action
|
||||
:disabled="loading"
|
||||
v-if="isResizeEnabled && req.type === 'image'"
|
||||
:disabled="layoutStore.loading"
|
||||
v-if="isResizeEnabled && fileStore.req?.type === 'image'"
|
||||
:icon="fullSize ? 'photo_size_select_large' : 'hd'"
|
||||
@action="toggleSize"
|
||||
/>
|
||||
|
||||
<template #actions>
|
||||
<action
|
||||
:disabled="loading"
|
||||
v-if="user.perm.rename"
|
||||
:disabled="layoutStore.loading"
|
||||
v-if="authStore.user?.perm.rename"
|
||||
icon="mode_edit"
|
||||
:label="$t('buttons.rename')"
|
||||
show="rename"
|
||||
/>
|
||||
<action
|
||||
:disabled="loading"
|
||||
v-if="user.perm.delete"
|
||||
:disabled="layoutStore.loading"
|
||||
v-if="authStore.user?.perm.delete"
|
||||
icon="delete"
|
||||
:label="$t('buttons.delete')"
|
||||
@action="deleteFile"
|
||||
id="delete-button"
|
||||
/>
|
||||
<action
|
||||
:disabled="loading"
|
||||
v-if="user.perm.download"
|
||||
:disabled="layoutStore.loading"
|
||||
v-if="authStore.user?.perm.download"
|
||||
icon="file_download"
|
||||
:label="$t('buttons.download')"
|
||||
@action="download"
|
||||
/>
|
||||
<action
|
||||
:disabled="loading"
|
||||
:disabled="layoutStore.loading"
|
||||
icon="info"
|
||||
:label="$t('buttons.info')"
|
||||
show="info"
|
||||
@@ -48,7 +48,7 @@
|
||||
</template>
|
||||
</header-bar>
|
||||
|
||||
<div class="loading delayed" v-if="loading">
|
||||
<div class="loading delayed" v-if="layoutStore.loading">
|
||||
<div class="spinner">
|
||||
<div class="bounce1"></div>
|
||||
<div class="bounce2"></div>
|
||||
@@ -57,41 +57,29 @@
|
||||
</div>
|
||||
<template v-else>
|
||||
<div class="preview">
|
||||
<ExtendedImage v-if="req.type == 'image'" :src="raw"></ExtendedImage>
|
||||
<ExtendedImage v-if="fileStore.req?.type == 'image'" :src="raw" />
|
||||
<audio
|
||||
v-else-if="req.type == 'audio'"
|
||||
v-else-if="fileStore.req?.type == 'audio'"
|
||||
ref="player"
|
||||
:src="raw"
|
||||
controls
|
||||
:autoplay="autoPlay"
|
||||
@play="autoPlay = true"
|
||||
></audio>
|
||||
<video
|
||||
v-else-if="req.type == 'video'"
|
||||
<VideoPlayer
|
||||
v-else-if="fileStore.req?.type == 'video'"
|
||||
ref="player"
|
||||
:src="raw"
|
||||
controls
|
||||
:autoplay="autoPlay"
|
||||
@play="autoPlay = true"
|
||||
:source="raw"
|
||||
:subtitles="subtitles"
|
||||
:options="videoOptions"
|
||||
>
|
||||
<track
|
||||
kind="captions"
|
||||
v-for="(sub, index) in subtitles"
|
||||
:key="index"
|
||||
:src="sub"
|
||||
:label="'Subtitle ' + index"
|
||||
:default="index === 0"
|
||||
/>
|
||||
Sorry, your browser doesn't support embedded videos, but don't worry,
|
||||
you can <a :href="downloadUrl">download it</a>
|
||||
and watch it with your favorite video player!
|
||||
</video>
|
||||
</VideoPlayer>
|
||||
<object
|
||||
v-else-if="req.extension.toLowerCase() == '.pdf'"
|
||||
v-else-if="fileStore.req?.extension.toLowerCase() == '.pdf'"
|
||||
class="pdf"
|
||||
:data="raw"
|
||||
></object>
|
||||
<div v-else-if="req.type == 'blob'" class="info">
|
||||
<div v-else-if="fileStore.req?.type == 'blob'" class="info">
|
||||
<div class="title">
|
||||
<i class="material-icons">feedback</i>
|
||||
{{ $t("files.noPreview") }}
|
||||
@@ -107,7 +95,7 @@
|
||||
target="_blank"
|
||||
:href="raw"
|
||||
class="button button--flat"
|
||||
v-if="!req.isDir"
|
||||
v-if="!fileStore.req?.isDir"
|
||||
>
|
||||
<div>
|
||||
<i class="material-icons">open_in_new</i
|
||||
@@ -144,214 +132,215 @@
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { mapGetters, mapState } from "vuex";
|
||||
<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 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 = ["image", "video", "audio", "blob"];
|
||||
const mediaTypes: ResourceType[] = ["image", "video", "audio", "blob"];
|
||||
|
||||
export default {
|
||||
name: "preview",
|
||||
components: {
|
||||
HeaderBar,
|
||||
Action,
|
||||
ExtendedImage,
|
||||
},
|
||||
data: function () {
|
||||
return {
|
||||
previousLink: "",
|
||||
nextLink: "",
|
||||
listing: null,
|
||||
name: "",
|
||||
fullSize: false,
|
||||
showNav: true,
|
||||
navTimeout: null,
|
||||
hoverNav: false,
|
||||
autoPlay: false,
|
||||
previousRaw: "",
|
||||
nextRaw: "",
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
...mapState(["req", "user", "oldReq", "jwt", "loading"]),
|
||||
...mapGetters(["currentPrompt"]),
|
||||
hasPrevious() {
|
||||
return this.previousLink !== "";
|
||||
},
|
||||
hasNext() {
|
||||
return this.nextLink !== "";
|
||||
},
|
||||
downloadUrl() {
|
||||
return api.getDownloadURL(this.req);
|
||||
},
|
||||
raw() {
|
||||
if (this.req.type === "image" && !this.fullSize) {
|
||||
return api.getPreviewURL(this.req, "big");
|
||||
}
|
||||
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>("");
|
||||
|
||||
return api.getDownloadURL(this.req, true);
|
||||
},
|
||||
showMore() {
|
||||
return this.currentPrompt?.prompt === "more";
|
||||
},
|
||||
isResizeEnabled() {
|
||||
return resizePreview;
|
||||
},
|
||||
subtitles() {
|
||||
if (this.req.subtitles) {
|
||||
return api.getSubtitlesURL(this.req);
|
||||
}
|
||||
return [];
|
||||
},
|
||||
},
|
||||
watch: {
|
||||
$route: function () {
|
||||
this.updatePreview();
|
||||
this.toggleNavigation();
|
||||
},
|
||||
},
|
||||
async mounted() {
|
||||
window.addEventListener("keydown", this.key);
|
||||
this.listing = this.oldReq.items;
|
||||
this.updatePreview();
|
||||
},
|
||||
beforeDestroy() {
|
||||
window.removeEventListener("keydown", this.key);
|
||||
},
|
||||
methods: {
|
||||
deleteFile() {
|
||||
this.$store.commit("showHover", {
|
||||
prompt: "delete",
|
||||
confirm: () => {
|
||||
this.listing = this.listing.filter((item) => item.name !== this.name);
|
||||
const player = ref<HTMLVideoElement | HTMLAudioElement | null>(null);
|
||||
|
||||
if (this.hasNext) {
|
||||
this.next();
|
||||
} else if (!this.hasPrevious && !this.hasNext) {
|
||||
this.close();
|
||||
} else {
|
||||
this.prev();
|
||||
}
|
||||
},
|
||||
});
|
||||
},
|
||||
prev() {
|
||||
this.hoverNav = false;
|
||||
this.$router.replace({ path: this.previousLink });
|
||||
},
|
||||
next() {
|
||||
this.hoverNav = false;
|
||||
this.$router.replace({ path: this.nextLink });
|
||||
},
|
||||
key(event) {
|
||||
if (this.currentPrompt !== 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 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 (event.which === 13 || event.which === 39) {
|
||||
// right arrow
|
||||
if (this.hasNext) this.next();
|
||||
} else if (event.which === 37) {
|
||||
// left arrow
|
||||
if (this.hasPrevious) this.prev();
|
||||
} else if (event.which === 27) {
|
||||
// esc
|
||||
this.close();
|
||||
if (hasNext.value) {
|
||||
next();
|
||||
} else if (!hasPrevious.value && !hasNext.value) {
|
||||
close();
|
||||
} else {
|
||||
prev();
|
||||
}
|
||||
},
|
||||
async updatePreview() {
|
||||
if (
|
||||
this.$refs.player &&
|
||||
this.$refs.player.paused &&
|
||||
!this.$refs.player.ended
|
||||
) {
|
||||
this.autoPlay = false;
|
||||
}
|
||||
|
||||
let dirs = this.$route.fullPath.split("/");
|
||||
this.name = decodeURIComponent(dirs[dirs.length - 1]);
|
||||
|
||||
if (!this.listing) {
|
||||
try {
|
||||
const path = url.removeLastDir(this.$route.path);
|
||||
const res = await api.fetch(path);
|
||||
this.listing = res.items;
|
||||
} catch (e) {
|
||||
this.$showError(e);
|
||||
}
|
||||
}
|
||||
|
||||
this.previousLink = "";
|
||||
this.nextLink = "";
|
||||
|
||||
for (let i = 0; i < this.listing.length; i++) {
|
||||
if (this.listing[i].name !== this.name) {
|
||||
continue;
|
||||
}
|
||||
|
||||
for (let j = i - 1; j >= 0; j--) {
|
||||
if (mediaTypes.includes(this.listing[j].type)) {
|
||||
this.previousLink = this.listing[j].url;
|
||||
this.previousRaw = this.prefetchUrl(this.listing[j]);
|
||||
break;
|
||||
}
|
||||
}
|
||||
for (let j = i + 1; j < this.listing.length; j++) {
|
||||
if (mediaTypes.includes(this.listing[j].type)) {
|
||||
this.nextLink = this.listing[j].url;
|
||||
this.nextRaw = this.prefetchUrl(this.listing[j]);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
},
|
||||
prefetchUrl(item) {
|
||||
if (item.type !== "image") {
|
||||
return "";
|
||||
}
|
||||
|
||||
return this.fullSize
|
||||
? api.getDownloadURL(item, true)
|
||||
: api.getPreviewURL(item, "big");
|
||||
},
|
||||
openMore() {
|
||||
this.$store.commit("showHover", "more");
|
||||
},
|
||||
resetPrompts() {
|
||||
this.$store.commit("closeHovers");
|
||||
},
|
||||
toggleSize() {
|
||||
this.fullSize = !this.fullSize;
|
||||
},
|
||||
toggleNavigation: throttle(function () {
|
||||
this.showNav = true;
|
||||
|
||||
if (this.navTimeout) {
|
||||
clearTimeout(this.navTimeout);
|
||||
}
|
||||
|
||||
this.navTimeout = setTimeout(() => {
|
||||
this.showNav = false || this.hoverNav;
|
||||
this.navTimeout = null;
|
||||
}, 1500);
|
||||
}, 500),
|
||||
close() {
|
||||
this.$store.commit("updateRequest", {});
|
||||
|
||||
let uri = url.removeLastDir(this.$route.path) + "/";
|
||||
this.$router.push({ path: uri });
|
||||
},
|
||||
download() {
|
||||
window.open(this.downloadUrl);
|
||||
},
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
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>
|
||||
|
||||
Reference in New Issue
Block a user