<script setup>
import moment from "moment";
import { DocumentIcon, VideoCameraIcon, PhotoIcon, ExclamationCircleIcon, LockOpenIcon, LockClosedIcon } from '@heroicons/vue/24/outline'
import OptionMenu from "@components/Bucket/Partials/OptionMenu.vue";
import {Dialog, DialogPanel, DialogTitle, Switch, TransitionChild, TransitionRoot} from '@headlessui/vue'
import {ref, defineEmits, watch} from "vue";
import {router} from "@inertiajs/vue3";
import route from "ziggy-js";
import DeleteModal from "@components/Bucket/Modals/DeleteModal.vue";
import FileMoveModal from "@components/Bucket/Modals/FileMoveModal.vue";
import SlideOver from "@components/UI/SlideOver.vue";
const props = defineProps({
file: {
type: Object,
default: () => {}
},
bucket: {
type: Object,
default: () => {}
},
hideMenu: {
type: Boolean,
default: false,
},
})
const emit = defineEmits(['deleted', 'moved', 'renamed'])
const openDeleteModal = ref(false)
const openFileMoveModal = ref(false)
const formatBytes = (bytes, decimals = 0) => {
if(bytes === 0) return '0 Bytes';
const k = 1024,
dm = decimals || 2,
sizes = ['Bytes', 'KB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB'],
i = Math.floor(Math.log(bytes) / Math.log(k));
return parseFloat((bytes / Math.pow(k, i)).toFixed(dm)) + ' ' + sizes[i];
}
const photoExtensions = ['jpg', 'jpeg', 'png', 'gif', 'svg', 'webp'];
const videoExtensions = ['mp4', 'avi', 'mov', 'wmv', 'flv', 'mkv', 'webm'];
const fileIcon = (extension) => {
if (photoExtensions.includes(extension)) {
return {
icon: PhotoIcon,
color: 'bg-rose-500',
}
}
if (videoExtensions.includes(extension)) {
return {
icon: VideoCameraIcon,
color: 'bg-blue-500',
}
}
return {
icon: DocumentIcon,
color: 'bg-gray-500',
}
}
const editData = ref(props.file)
const editDataOriginal = ref(props.file)
const edit = ref(false)
const cancelEditing = () => {
editData.value = editDataOriginal.value
edit.value = false
}
const update = () => {
const url = route('buckets.rename', {
bucket: props.bucket.id
})
router.post(url, {
newName: editData.value.name,
path: props.file.path,
}, {
preserveState: true,
preserveScroll: true,
onSuccess: () => {
edit.value = false
emit('renamed')
},
})
}
const viewFileDetails = ref(false)
const visibility = ref(props.file.visibility === 'public')
watch(() => visibility.value, (value) => {
const url = route('buckets.visibility', {
bucket: props.bucket.id
})
router.post(url, {
visibility: value ? 'public' : 'private',
path: props.file.name,
}, {
preserveState: true,
preserveScroll: true,
})
})
const download = () => {
const url = route('buckets.download', {
bucket: props.bucket.id,
path: props.file.path,
})
window.open(url, '_blank')
}
</script>
<template>
<!-- File Move Modal -->
<FileMoveModal
:open="openFileMoveModal"
:current-bucket="props.bucket.id"
:current-path="props.file.path"
:current-name="props.file.name"
@close="openFileMoveModal = false"
@moved="emit('moved')"
/>
<!-- File Move Modal -->
<DeleteModal
:open="openDeleteModal"
:delete-url="route('buckets.files.delete', {
bucket: props.bucket.id,
path: props.file.path,
})"
@close="openDeleteModal = false"
@deleted="emit('deleted')"
>
<div class="bg-white p-4 ">
<div class="text-center sm:text-left">
<DialogTitle
as="h3"
class="text-base font-semibold leading-6 text-gray-900"
>
{{ $t('modals.delete_title') }}
</DialogTitle>
<div class="mt-3">
<p class="text-sm text-gray-500">
{{ $t('modals.delete_file') }}
</p>
</div>
<div class="inline-flex gap-2 mt-3">
<ExclamationCircleIcon class="w-5 h-5 text-gray-500" />
<p class="text-sm text-gray-500">
{{ file.name }}
</p>
</div>
</div>
</div>
</DeleteModal>
<!-- File Information Slide -->
<SlideOver
:show="viewFileDetails"
@close="viewFileDetails = false"
>
<div class="flex h-full flex-col justify-between bg-white shadow-xl px-4 py-6 sm:px-6 ">
<div class="space-y-6">
<div>
<div class="aspect-h-7 aspect-w-10 block w-full overflow-hidden rounded-lg">
<img
v-if="photoExtensions.includes(file.extension)"
:src="file.preview_url"
:alt="file.name"
class="object-cover"
>
<video
v-else-if="videoExtensions.includes(file.extension)"
:src="file.preview_url"
class="object-cover"
controls
/>
<div
v-else
class="bg-gray-500 p-10"
>
<DocumentIcon class="object-cover text-white" />
</div>
</div>
<div class="mt-4 flex items-start justify-between">
<div>
<h2 class="text-lg font-medium text-gray-900">
<span class="sr-only">Details for </span>
{{ file.name }}
</h2>
<p class="text-sm font-medium text-gray-500">
{{ formatBytes(file.size) }}
</p>
</div>
</div>
</div>
<div>
<h3 class="font-medium text-gray-900">
{{ $t('file_manager.file.information') }}
</h3>
<dl class="mt-2 divide-y divide-gray-200 border-b border-t border-gray-200">
<div class="flex justify-between py-3 text-sm font-medium">
<dt class="text-gray-500">
{{ $t('file_manager.file.last_modified') }}
</dt>
<dd class="whitespace-nowrap text-gray-900">
{{ moment(file.modified).fromNow() }}
</dd>
</div>
<div class="flex justify-between py-3 text-sm font-medium">
<dt class="text-gray-500">
{{ $t('file_manager.file.file_visibility') }}
</dt>
<dd
class="whitespace-nowrap text-gray-900 "
>
<Switch
v-model="visibility"
:class="[visibility ? 'bg-green-500' : 'bg-rose-500', 'relative inline-flex h-6 w-11 flex-shrink-0 cursor-pointer rounded border-2 border-transparent transition-colors duration-200 ease-in-out focus:outline-none focus:ring-2 focus:ring-slate-600 focus:ring-offset-2']"
>
<span
aria-hidden="true"
:class="[visibility ? 'translate-x-5' : 'translate-x-0','pointer-events-none inline-block h-5 w-5 transform rounded bg-white shadow ring-0 transition duration-200 ease-in-out']"
>
<span
:class="[visibility ? 'opacity-0 duration-100 ease-out' : 'opacity-100 duration-200 ease-in', 'absolute inset-0 flex h-full w-full items-center justify-center transition-opacity']"
aria-hidden="true"
>
<LockClosedIcon class="h-4 w-4 text-gray-500" />
</span>
<span
:class="[visibility ? 'opacity-100 duration-200 ease-in' : 'opacity-0 duration-100 ease-out', 'absolute inset-0 flex h-full w-full items-center justify-center transition-opacity']"
aria-hidden="true"
>
<LockOpenIcon class="h-4 w-4 text-gray-500" />
</span>
</span>
</Switch>
</dd>
</div>
</dl>
</div>
</div>
<div>
<button
type="button"
class="w-full flex justify-center items-center px-4 py-2 border border-transparent rounded-md shadow-sm text-sm font-medium text-white bg-indigo-600 hover:bg-indigo-700"
@click="download"
>
{{ $t('buttons.download') }}
</button>
</div>
</div>
</SlideOver>
<div class="flex flex-col space-y-3 cursor-pointer justify-between rounded-md text-gray-500 ring-1 ring-inset ring-gray-500/10 hover:shadow-md p-3">
<div class="flex gap-3">
<div :class="['flex h-10 w-10 flex-none items-center justify-center rounded-lg', fileIcon(file.extension).color]">
<component
:is="fileIcon(file.extension).icon"
class="h-6 w-6 text-white flex-none"
aria-hidden="true"
/>
</div>
<div class="flex-grow">
<p
v-if="!edit"
class="break-all"
>
{{ file.name }}
</p>
<div v-else>
<input
v-model="editData.name"
type="text"
class="block w-full rounded-md border-0 px-2 py-1.5 text-gray-900 shadow-sm ring-1 ring-inset ring-gray-300 placeholder:text-gray-400 focus:ring-2 focus:ring-inset focus:ring-indigo-600 sm:text-sm sm:leading-6"
@keydown.esc="cancelEditing"
@keydown.enter="update"
>
</div>
</div>
<div v-if="!hideMenu">
<OptionMenu
hide-upload
@move="openFileMoveModal = true"
@delete="openDeleteModal = true"
@edit="edit = true"
@view="viewFileDetails = true"
/>
</div>
</div>
<div class="flex justify-between">
<span class="text-xs">
{{ formatBytes(file.size) }}
</span>
<span class="text-xs">
{{ moment(file.modified).fromNow() }}
</span>
</div>
</div>
</template>
<style scoped>
</style>
|