295 lines
9.4 KiB
Vue
295 lines
9.4 KiB
Vue
<template>
|
|
<div>
|
|
<header-bar showMenu showLogo>
|
|
<title />
|
|
|
|
<action v-if="fileStore.selectedCount" icon="file_download" :label="$t('buttons.download')" @action="download"
|
|
:counter="fileStore.selectedCount" />
|
|
<button v-if="isSingleFile()" class="action copy-clipboard" :data-clipboard-text="linkSelected()"
|
|
:aria-label="$t('buttons.copyDownloadLinkToClipboard')" :data-title="$t('buttons.copyDownloadLinkToClipboard')">
|
|
<i class="material-icons">content_paste</i>
|
|
</button>
|
|
<action icon="check_circle" :label="$t('buttons.selectMultiple')" @action="toggleMultipleSelection" />
|
|
</header-bar>
|
|
|
|
<breadcrumbs :base="'/share/' + hash" />
|
|
|
|
<div v-if="layoutStore.loading">
|
|
<h2 class="message delayed">
|
|
<div class="spinner">
|
|
<div class="bounce1"></div>
|
|
<div class="bounce2"></div>
|
|
<div class="bounce3"></div>
|
|
</div>
|
|
<span>{{ $t("files.loading") }}</span>
|
|
</h2>
|
|
</div>
|
|
<div v-else-if="error">
|
|
<div v-if="error.status === 401">
|
|
<div class="card floating" id="password">
|
|
<div v-if="attemptedPasswordLogin" class="share__wrong__password">
|
|
{{ $t("login.wrongCredentials") }}
|
|
</div>
|
|
<div class="card-title">
|
|
<h2>{{ $t("login.password") }}</h2>
|
|
</div>
|
|
|
|
<div class="card-content">
|
|
<input v-focus type="password" :placeholder="$t('login.password')" v-model="password"
|
|
@keyup.enter="fetchData" />
|
|
</div>
|
|
<div class="card-action">
|
|
<button class="button button--flat" @click="fetchData" :aria-label="$t('buttons.submit')"
|
|
:data-title="$t('buttons.submit')">
|
|
{{ $t("buttons.submit") }}
|
|
</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<errors v-else :errorCode="error.status" />
|
|
</div>
|
|
<div v-else-if="req !== null">
|
|
<div class="share">
|
|
<div class="share__box share__box__info">
|
|
<div class="share__box__header">
|
|
{{
|
|
req.isDir
|
|
? $t("download.downloadFolder")
|
|
: $t("download.downloadFile")
|
|
}}
|
|
</div>
|
|
<div class="share__box__element share__box__center share__box__icon">
|
|
<i class="material-icons">{{ icon }}</i>
|
|
</div>
|
|
<div class="share__box__element">
|
|
<strong>{{ $t("prompts.displayName") }}</strong> {{ req.name }}
|
|
</div>
|
|
<div class="share__box__element" :data-title="modTime">
|
|
<strong>{{ $t("prompts.lastModified") }}:</strong> {{ humanTime }}
|
|
</div>
|
|
<div class="share__box__element">
|
|
<strong>{{ $t("prompts.size") }}:</strong> {{ humanSize }}
|
|
</div>
|
|
<div class="share__box__element share__box__center">
|
|
<a target="_blank" :href="link" class="button button--flat">
|
|
<div>
|
|
<i class="material-icons">file_download</i>{{ $t("buttons.download") }}
|
|
</div>
|
|
</a>
|
|
<a target="_blank" :href="inlineLink" class="button button--flat" v-if="!req.isDir">
|
|
<div>
|
|
<i class="material-icons">open_in_new</i>{{ $t("buttons.openFile") }}
|
|
</div>
|
|
</a>
|
|
</div>
|
|
<div class="share__box__element share__box__center">
|
|
<qrcode-vue :value="link" :size="200" level="M"></qrcode-vue>
|
|
</div>
|
|
</div>
|
|
<div v-if="req.isDir && req.items.length > 0" class="share__box share__box__items">
|
|
<div class="share__box__header" v-if="req.isDir">
|
|
{{ $t("files.files") }}
|
|
</div>
|
|
<div id="listing" class="list file-icons">
|
|
<item v-for="item in req.items.slice(0, showLimit)" :key="base64(item.name)" v-bind:index="item.index"
|
|
v-bind:name="item.name" v-bind:isDir="item.isDir" v-bind:url="item.url" v-bind:modified="item.modified"
|
|
v-bind:type="item.type" v-bind:size="item.size" readOnly>
|
|
</item>
|
|
<div v-if="req.items.length > showLimit" class="item" @click="showLimit += 100">
|
|
<div>
|
|
<p class="name">+ {{ req.items.length - showLimit }}</p>
|
|
</div>
|
|
</div>
|
|
|
|
<div :class="{ active: fileStore.multiple }" id="multiple-selection">
|
|
<p>{{ $t("files.multipleSelectionEnabled") }}</p>
|
|
<div @click="() => (fileStore.multiple = false)" tabindex="0" role="button" :data-title="$t('files.clear')"
|
|
:aria-label="$t('files.clear')" class="action">
|
|
<i class="material-icons">clear</i>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div v-else-if="req.isDir && req.items.length === 0" class="share__box share__box__items">
|
|
<h2 class="message">
|
|
<i class="material-icons">sentiment_dissatisfied</i>
|
|
<span>{{ $t("files.lonely") }}</span>
|
|
</h2>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</template>
|
|
|
|
<script setup lang="ts">
|
|
import { pub as api, files } from "@/api";
|
|
import { filesize } from "filesize";
|
|
import dayjs from "dayjs";
|
|
import { Base64 } from "js-base64";
|
|
|
|
import HeaderBar from "@/components/header/HeaderBar.vue";
|
|
import Action from "@/components/header/Action.vue";
|
|
import Breadcrumbs from "@/components/Breadcrumbs.vue";
|
|
import Errors from "@/views/Errors.vue";
|
|
import QrcodeVue from "qrcode.vue";
|
|
import Item from "@/components/files/ListingItem.vue";
|
|
import Clipboard from "clipboard";
|
|
import { useFileStore } from "@/stores/file";
|
|
import { useLayoutStore } from "@/stores/layout";
|
|
import { computed, onBeforeUnmount, onMounted, ref, watch } from "vue";
|
|
import { useRoute } from "vue-router";
|
|
|
|
const error = ref<null | any>(null)
|
|
const showLimit = ref<number>(100)
|
|
const password = ref<string>("")
|
|
const attemptedPasswordLogin = ref<boolean>(false)
|
|
const hash = ref<any>(null)
|
|
const token = ref<any>(null)
|
|
const clip = ref<any>(null)
|
|
|
|
const route = useRoute()
|
|
const fileStore = useFileStore()
|
|
const layoutStore = useLayoutStore()
|
|
|
|
watch(route, (newValue, oldValue) => {
|
|
showLimit.value = 100
|
|
fetchData();
|
|
})
|
|
|
|
const req = computed(() => fileStore.req)
|
|
|
|
// inject: ["$showSuccess"],
|
|
|
|
|
|
// Define computes
|
|
|
|
const icon = computed(() => {
|
|
if (req.value === null) return "insert_drive_file"
|
|
if (req.value.isDir) return "folder";
|
|
if (req.value.type === "image") return "insert_photo";
|
|
if (req.value.type === "audio") return "volume_up";
|
|
if (req.value.type === "video") return "movie";
|
|
return "insert_drive_file";
|
|
})
|
|
|
|
const link = computed(() => (req.value ? api.getDownloadURL(req.value) : ""))
|
|
const inlineLink = computed(() => (req.value ? api.getDownloadURL(req.value, true) : ""))
|
|
const humanSize = computed(() => {
|
|
if (req.value) {
|
|
return (req.value.isDir ? req.value.items.length : filesize(req.value.size ?? 0))
|
|
} else {
|
|
return ""
|
|
}
|
|
})
|
|
const humanTime = computed(() => dayjs(req.value?.modified).fromNow())
|
|
const modTime = computed(() => (req.value ? new Date(Date.parse(req.value.modified)).toLocaleString() : new Date()))
|
|
|
|
// Functions
|
|
const base64 = (name: any) => Base64.encodeURI(name);
|
|
const fetchData = async () => {
|
|
fileStore.reload = false;
|
|
fileStore.selected = [];
|
|
fileStore.multiple = false;
|
|
// fileStore.closeHovers();
|
|
|
|
// Set loading to true and reset the error.
|
|
layoutStore.loading = true;
|
|
error.value = null;
|
|
if (password.value !== "") {
|
|
attemptedPasswordLogin.value = true;
|
|
}
|
|
|
|
let url = route.path;
|
|
if (url === "") url = "/";
|
|
if (url[0] !== "/") url = "/" + url;
|
|
|
|
try {
|
|
let file = await api.fetch(url, password.value);
|
|
file.hash = hash.value;
|
|
|
|
|
|
token.value = file.token || "";
|
|
|
|
fileStore.updateRequest(file);
|
|
document.title = `${file.name} - ${document.title}`;
|
|
} catch (e) {
|
|
error.value = e;
|
|
} finally {
|
|
layoutStore.loading = false;
|
|
}
|
|
}
|
|
|
|
const keyEvent = (event: KeyboardEvent) => {
|
|
if (event.key === "Escape") {
|
|
// If we're on a listing, unselect all
|
|
// files and folders.
|
|
if (fileStore.selectedCount > 0) {
|
|
fileStore.selected = [];
|
|
}
|
|
}
|
|
}
|
|
|
|
const toggleMultipleSelection = () => {
|
|
// toggle
|
|
}
|
|
|
|
const isSingleFile = () => fileStore.selectedCount === 1 && !req.value?.items[fileStore.selected[0]].isDir
|
|
|
|
const download = () => {
|
|
if (isSingleFile()) {
|
|
api.download(
|
|
null,
|
|
hash.value,
|
|
token.value,
|
|
req.value?.items[fileStore.selected[0]].path
|
|
);
|
|
return;
|
|
}
|
|
layoutStore.showHover({
|
|
prompt: "download",
|
|
confirm: (format: any) => {
|
|
if (req.value === null) return false
|
|
layoutStore.closeHovers();
|
|
|
|
let files: string[] = [];
|
|
|
|
for (let i of fileStore.selected) {
|
|
files.push(req.value.items[i].path);
|
|
}
|
|
|
|
// @ts-ignore
|
|
api.download(format, hash.value, token.value, ...files);
|
|
},
|
|
});
|
|
}
|
|
|
|
const linkSelected = () => {
|
|
return isSingleFile() && req.value
|
|
// @ts-ignore
|
|
? api.getDownloadURL({
|
|
hash: hash.value,
|
|
path: req.value.items[fileStore.selected[0]].path,
|
|
})
|
|
: "";
|
|
}
|
|
|
|
|
|
onMounted(async () => {
|
|
// Created
|
|
hash.value = route.params.path[0];
|
|
await fetchData();
|
|
|
|
|
|
// window.addEventListener("keydown", this.keyEvent);
|
|
// this.clip = new Clipboard(".copy-clipboard");
|
|
// this.clip.on("success", () => {
|
|
// this.$showSuccess(this.$t("success.linkCopied"));
|
|
// });
|
|
})
|
|
|
|
onBeforeUnmount(() => {
|
|
// window.removeEventListener("keydown", this.keyEvent);
|
|
// this.clip.destroy();
|
|
})
|
|
|
|
</script> |