filebrowser/frontend/src/components/files/ListingItem.vue

330 lines
7.8 KiB
Vue

<template>
<div
:class="{ activebutton: this.isMaximized && this.isSelected }"
class="item"
role="button"
tabindex="0"
:draggable="isDraggable"
@dragstart="dragStart"
@dragover="dragOver"
@drop="drop"
@click="itemClick"
:data-dir="isDir"
:data-type="type"
:aria-label="name"
:aria-selected="isSelected"
>
<div
@click="toggleClick"
:class="{ activetitle: this.isMaximized && this.isSelected }"
>
<img
v-if="readOnly === undefined && type === 'image' && isThumbsEnabled && isInView"
v-lazy="thumbnailUrl"
:class="{ activeimg: this.isMaximized && this.isSelected }"
ref="thumbnail"
/>
<i
:class="{ iconActive: this.isMaximized && this.isSelected }"
v-else
class="material-icons"
></i>
</div>
<div class="text" :class="{ activecontent: this.isMaximized && this.isSelected }">
<p class="name">{{ name }}</p>
<p v-if="isDir" class="size" data-order="-1">&mdash;</p>
<p v-else class="size" :data-order="humanSize()">{{ humanSize() }}</p>
<p class="modified">
<time :datetime="modified">{{ humanTime() }}</time>
</p>
</div>
</div>
</template>
<style>
.activebutton {
height: 10em;
}
.activecontent {
height: 5em !important;
display: grid !important;
}
.activeimg {
width: 8em !important;
height: 8em !important;
}
.iconActive {
font-size: 6em !important;
}
.activetitle {
width: 9em !important;
margin-right: 1em !important;
}
</style>
<script>
import { enableThumbs } from "@/utils/constants";
import { getHumanReadableFilesize } from "@/utils/filesizes";
import { fromNow } from "@/utils/moment";
import { files as api } from "@/api";
import * as upload from "@/utils/upload";
import { state, getters, mutations } from "@/store"; // Import your custom store
export default {
name: "item",
data: function () {
return {
isThumbnailInView: false,
isMaximized: false,
touches: 0,
};
},
props: [
"name",
"isDir",
"url",
"type",
"size",
"modified",
"index",
"readOnly",
"path",
],
computed: {
user() {
return state.user;
},
selected() {
return state.selected;
},
req() {
return state.req;
},
jwt() {
return state.jwt;
},
selectedCount() {
return getters.selectedCount();
},
isClicked() {
if (state.user.singleClick || !this.allowedView) {
return false;
}
return !this.isMaximized;
},
allowedView() {
return state.user.viewMode != "gallery" && state.user.viewMode != "normal";
},
singleClick() {
return this.readOnly == undefined && state.user.singleClick;
},
isSelected() {
return this.selected.indexOf(this.index) !== -1;
},
isDraggable() {
return this.readOnly == undefined && state.user.perm?.rename;
},
canDrop() {
if (!this.isDir || this.readOnly !== undefined) return false;
for (let i of this.selected) {
if (state.req.items[i].url === this.url) {
return false;
}
}
return true;
},
thumbnailUrl() {
let path = state.req.path;
if (state.req.path == "/") {
path = "";
}
const file = {
path: path + "/" + this.name,
modified: this.modified,
};
return api.getPreviewURL(file, "thumb");
},
isThumbsEnabled() {
return enableThumbs;
},
isInView() {
return enableThumbs;
},
},
mounted() {
const observer = new IntersectionObserver(this.handleIntersect, {
root: null,
rootMargin: "0px",
threshold: 0.5, // Adjust threshold as needed
});
// Get the thumbnail element and start observing
const thumbnailElement = this.$refs.thumbnail; // Add ref="thumbnail" to the <img> tag
if (thumbnailElement) {
observer.observe(thumbnailElement);
}
},
methods: {
handleIntersect(entries, observer) {
entries.forEach((entry) => {
if (entry.isIntersecting) {
this.isThumbnailInView = true;
// Stop observing once thumbnail is in view
observer.unobserve(entry.target);
}
});
},
toggleClick() {
this.isMaximized = this.isClicked;
},
humanSize: function () {
return this.type == "invalid_link"
? "invalid link"
: getHumanReadableFilesize(this.size);
},
humanTime: function () {
if (this.readOnly == undefined && state.user.dateFormat) {
return fromNow(this.modified, state.user.locale).format("L LT");
}
return fromNow(this.modified, state.user.locale);
},
dragStart: function () {
if (getters.selectedCount() === 0) {
mutations.addSelected(this.index);
return;
}
if (!this.isSelected) {
mutations.resetSelected();
mutations.addSelected(this.index);
}
},
dragOver: function (event) {
if (!this.canDrop) return;
event.preventDefault();
let el = event.target;
for (let i = 0; i < 5; i++) {
if (!el.classList.contains("item")) {
el = el.parentElement;
}
}
el.style.opacity = 1;
},
drop: async function (event) {
if (!this.canDrop) return;
event.preventDefault();
if (getters.selectedCount() === 0) return;
let el = event.target;
for (let i = 0; i < 5; i++) {
if (el !== null && !el.classList.contains("item")) {
el = el.parentElement;
}
}
let items = [];
for (let i of this.selected) {
items.push({
from: state.req.items[i].url,
to: this.url + encodeURIComponent(state.req.items[i].name),
name: state.req.items[i].name,
});
}
// Get url from ListingItem instance
let path = el.__vue__.url;
let baseItems = (await api.fetch(path)).items;
let action = (overwrite, rename) => {
api
.move(items, overwrite, rename)
.then(() => {
mutations.setReload(true);
})
.catch(showError);
};
let conflict = upload.checkConflict(items, baseItems);
let overwrite = false;
let rename = false;
if (conflict) {
mutations.showHover({
name: "replace-rename",
confirm: (event, option) => {
overwrite = option == "overwrite";
rename = option == "rename";
event.preventDefault();
mutations.closeHovers();
action(overwrite, rename);
},
});
return;
}
action(overwrite, rename);
},
itemClick: function (event) {
if (this.singleClick && !state.multiple) this.open();
else this.click(event);
},
click: function (event) {
if (!this.singleClick && getters.selectedCount() !== 0) event.preventDefault();
setTimeout(() => {
this.touches = 0;
}, 300);
this.touches++;
if (this.touches > 1) {
this.open();
}
if (state.selected.indexOf(this.index) !== -1) {
mutations.removeSelected(this.index);
return;
}
if (event.shiftKey && this.selected.length > 0) {
let fi = 0;
let la = 0;
if (this.index > this.selected[0]) {
fi = this.selected[0] + 1;
la = this.index;
} else {
fi = this.index;
la = this.selected[0] - 1;
}
for (; fi <= la; fi++) {
if (state.selected.indexOf(fi) == -1) {
mutations.addSelected(fi);
}
}
return;
}
if (!this.singleClick && !event.ctrlKey && !event.metaKey && !state.multiple)
mutations.resetSelected();
mutations.addSelected(this.index);
},
open: function () {
this.$router.push({ path: this.url });
},
},
};
</script>