mirror of
https://github.com/VueFileManager/vuefilemanager.git
synced 2026-04-18 08:12:15 +00:00
grid view implementation
This commit is contained in:
@@ -1,7 +1,7 @@
|
||||
<template>
|
||||
<div
|
||||
:class="{'user-dropping-file': isDragging}"
|
||||
class="file-content md:w-full md:overflow-y-auto md:h-full md:px-0 px-4"
|
||||
:class="{'user-dropping-file': isDragging, 'grid content-start xl:grid-cols-6 xl:gap-4 lg:grid-cols-4 lg:gap-2 grid-cols-3': itemViewType === 'grid'}"
|
||||
class="md:w-full md:overflow-y-auto md:h-full md:px-0 px-4"
|
||||
@drop.stop.prevent="uploadDroppedItems($event)"
|
||||
@keydown.delete="deleteItems"
|
||||
@dragover="dragEnter"
|
||||
@@ -10,7 +10,7 @@
|
||||
tabindex="-1"
|
||||
@click.self="deselect"
|
||||
>
|
||||
<FileItemList
|
||||
<ItemHandler
|
||||
@dragstart="dragStart(item)"
|
||||
@drop.stop.native.prevent="dragFinish(item, $event)"
|
||||
@contextmenu.native.prevent="contextMenu($event, item)"
|
||||
@@ -23,18 +23,19 @@
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import FileItemList from '/resources/js/components/FilesView/FileItemList'
|
||||
import ItemHandler from '/resources/js/components/FilesView/ItemHandler'
|
||||
import {events} from '/resources/js/bus'
|
||||
import {mapGetters} from 'vuex'
|
||||
|
||||
export default {
|
||||
name: 'FileBrowser',
|
||||
components: {
|
||||
FileItemList,
|
||||
ItemHandler,
|
||||
},
|
||||
computed: {
|
||||
...mapGetters([
|
||||
'currentFolder',
|
||||
'itemViewType',
|
||||
'clipboard',
|
||||
'entries'
|
||||
]),
|
||||
|
||||
23
resources/js/components/FilesView/FileIconThumbnail.vue
Normal file
23
resources/js/components/FilesView/FileIconThumbnail.vue
Normal file
@@ -0,0 +1,23 @@
|
||||
<template>
|
||||
<div class="flex items-center justify-center pr-2">
|
||||
<span class="text-theme dark-text-theme lg:text-xs text-tiny font-semibold absolute z-10 inline-block mx-auto mt-1 w-7 overflow-ellipsis overflow-hidden text-center">
|
||||
{{ entry.data.attributes.mimetype }}
|
||||
</span>
|
||||
|
||||
<svg width="38px" height="51px" viewBox="0 0 38 51" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
|
||||
<path
|
||||
stroke-width="0"
|
||||
fill="#f4f5f6"
|
||||
d="M22.1666667,13.546875 L22.1666667,0 L2.375,0 C1.05885417,0 0,1.06582031 0,2.390625 L0,48.609375 C0,49.9341797 1.05885417,51 2.375,51 L35.625,51 C36.9411458,51 38,49.9341797 38,48.609375 L38,15.9375 L24.5416667,15.9375 C23.2354167,15.9375 22.1666667,14.8617187 22.1666667,13.546875 Z M38,12.1423828 L38,12.75 L25.3333333,12.75 L25.3333333,0 L25.9369792,0 C26.5703125,0 27.1739583,0.249023438 27.6192708,0.697265625 L37.3072917,10.4589844 C37.7526042,10.9072266 38,11.5148437 38,12.1423828 Z"></path>
|
||||
</svg>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: 'FileIconThumbnail',
|
||||
props: [
|
||||
'entry'
|
||||
]
|
||||
}
|
||||
</script>
|
||||
@@ -80,7 +80,7 @@ export default {
|
||||
},
|
||||
computed: {
|
||||
...mapGetters([
|
||||
'FilePreviewType', 'sharedDetail', 'clipboard', 'entries'
|
||||
'itemViewType', 'sharedDetail', 'clipboard', 'entries'
|
||||
]),
|
||||
folderEmojiOrColor() {
|
||||
|
||||
|
||||
@@ -27,13 +27,13 @@ export default {
|
||||
},
|
||||
computed: {
|
||||
...mapGetters([
|
||||
'FilePreviewType'
|
||||
'itemViewType'
|
||||
]),
|
||||
isGrid() {
|
||||
return this.FilePreviewType === 'grid'
|
||||
return this.itemViewType === 'grid'
|
||||
},
|
||||
isList() {
|
||||
return this.FilePreviewType === 'list'
|
||||
return this.itemViewType === 'list'
|
||||
},
|
||||
arrowForCreatedAtField() {
|
||||
if (this.filter.field !== 'created_at')
|
||||
|
||||
@@ -4,9 +4,12 @@
|
||||
<g id="team-folder">
|
||||
<path d="M48.03125,6.5 L29.790833,6.5 C28.7431613,6.5 27.7373076,6.08896217 26.9894703,5.35523504 L22.6980297,1.14476496 C21.9501924,0.41103783 20.9443387,-6.36543387e-16 19.896667,0 L4.96875,0 L4.96875,0 C2.22455078,0 0,2.18257812 0,4.875 L0,34.125 C0,36.8174219 2.22455078,39 4.96875,39 L48.03125,39 C50.7754492,39 53,36.8174219 53,34.125 L53,11.375 C53,8.68257813 50.7754492,6.5 48.03125,6.5 Z"
|
||||
class="svg-color-theme-darken"
|
||||
stroke="none"
|
||||
stroke-width="0"
|
||||
></path>
|
||||
<path d="M48.03125,12.75 C49.0609313,12.75 49.9941504,13.1577174 50.6692739,13.8201027 C51.3356976,14.4739525 51.75,15.3766531 51.75,16.375 L51.75,16.375 L51.75,34.125 C51.75,35.1233469 51.3356976,36.0260475 50.6692739,36.6798973 C49.9941504,37.3422826 49.0609313,37.75 48.03125,37.75 L48.03125,37.75 L4.96875,37.75 C3.93906868,37.75 3.00584961,37.3422826 2.33072613,36.6798973 C1.66430239,36.0260475 1.25,35.1233469 1.25,34.125 L1.25,34.125 L1.25,16.375 C1.25,15.3766531 1.66430239,14.4739525 2.33072613,13.8201027 C3.00584961,13.1577174 3.93906868,12.75 4.96875,12.75 L4.96875,12.75 Z"
|
||||
stroke-width="1" class="svg-color-theme"
|
||||
stroke-width="2"
|
||||
class="svg-color-theme"
|
||||
></path>
|
||||
<g id="Icon" transform="translate(8.000000, 20.000000)" class="stroke-current text-theme-darken" stroke-linecap="round" stroke-linejoin="round" stroke-width="1.3">
|
||||
<path d="M9.59999943,10.7999994 L9.59999943,9.59999943 C9.59999943,8.27451611 8.52548289,7.19999957 7.19999957,7.19999957 L2.39999986,7.19999957 C1.07451654,7.19999957 0,8.27451611 0,9.59999943 L0,10.7999994" class="stroke-current text-theme-darken"></path>
|
||||
|
||||
201
resources/js/components/FilesView/ItemGrid.vue
Normal file
201
resources/js/components/FilesView/ItemGrid.vue
Normal file
@@ -0,0 +1,201 @@
|
||||
<template>
|
||||
<div
|
||||
:class="{'dark:bg-dark-foreground bg-light-background': isClicked}"
|
||||
class="flex flex-wrap items-center justify-center relative text-center 2xl:h-48 lg:h-52 h-48 px-1 pt-2 rounded-lg select-none border-2 border-transparent border-dashed dark:hover:bg-dark-foreground lg:hover:bg-light-background"
|
||||
:draggable="canDrag"
|
||||
spellcheck="false"
|
||||
>
|
||||
|
||||
<!--MultiSelecting for the mobile version-->
|
||||
<CheckBox v-if="isMultiSelectMode" :is-clicked="isClicked" class="mr-5"/>
|
||||
|
||||
<div class="w-full">
|
||||
<!--Item thumbnail-->
|
||||
<div class="relative mx-auto">
|
||||
|
||||
<!--Folder Icon-->
|
||||
<FolderIcon v-if="isFolder" :item="entry" location="file-item-list" class="inline-block transform scale-150 lg:mt-2 lg:mb-8 mt-3 mb-5" />
|
||||
|
||||
<!--File Icon-->
|
||||
<div v-if="isFile || isVideo || (isImage && !entry.data.attributes.thumbnail)" class="relative">
|
||||
|
||||
<!--Member thumbnail for team folders-->
|
||||
<MemberAvatar
|
||||
v-if="user && canShowAuthor"
|
||||
:size="38"
|
||||
:is-border="true"
|
||||
:member="entry.data.relationships.user"
|
||||
class="absolute lg:right-14 lg:-bottom-7 right-6 -bottom-5 z-10 transform lg:scale-100 scale-75"
|
||||
/>
|
||||
|
||||
<FileIconThumbnail :entry="entry" class="transform lg:scale-150 scale-125 lg:mb-12 lg:mt-6 mt-5 mb-10 z-0" />
|
||||
</div>
|
||||
|
||||
<!--Image thumbnail-->
|
||||
<div v-if="isImage && entry.data.attributes.thumbnail" class="relative inline-block lg:w-36 lg:h-28 w-28 h-24 mb-4">
|
||||
|
||||
<!--Member thumbnail for team folders-->
|
||||
<MemberAvatar
|
||||
v-if="user && canShowAuthor"
|
||||
:size="38"
|
||||
:is-border="true"
|
||||
:member="entry.data.relationships.user"
|
||||
class="absolute -right-3 -bottom-2.5 transform lg:scale-100 scale-75"
|
||||
/>
|
||||
|
||||
<img class="object-cover w-full h-full rounded-lg shadow-lg" :src="entry.data.attributes.thumbnail" :alt="entry.data.attributes.name" loading="lazy" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!--Item Info-->
|
||||
<div class="text-center">
|
||||
|
||||
<!--Item Title-->
|
||||
<b class="inline-block leading-3 text-sm hover:underline w-full overflow-ellipsis overflow-hidden whitespace-nowrap md:px-6 tracking-tigh" ref="name" @input="renameItem" @keydown.delete.stop @click.stop :contenteditable="canEditName">
|
||||
{{ itemName }}
|
||||
</b>
|
||||
|
||||
<!--Item sub line-->
|
||||
<div class="flex items-center justify-center">
|
||||
|
||||
<!--Shared Icon-->
|
||||
<div v-if="$checkPermission('master') && entry.data.relationships.shared">
|
||||
<link-icon size="12" class="mr-1.5 text-theme dark-text-theme vue-feather"/>
|
||||
</div>
|
||||
|
||||
<!--File & Image sub line-->
|
||||
<small v-if="! isFolder" class="block text-xs text-gray-500">
|
||||
{{ entry.data.attributes.filesize }}<span class="lg:inline-block hidden text-xs text-gray-500">, {{ timeStamp }}</span>
|
||||
</small>
|
||||
|
||||
<!--Folder sub line-->
|
||||
<small v-if="isFolder" class="block text-xs text-gray-500">
|
||||
{{ folderItems === 0 ? $t('folder.empty') : $tc('folder.item_counts', folderItems) }}<span class="lg:inline-block hidden text-xs text-gray-500">, {{ timeStamp }}</span>
|
||||
</small>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Mobile item action button-->
|
||||
<div v-if="! isMultiSelectMode" class="inline-block py-0.5 px-2 md:hidden">
|
||||
<MoreHorizontalIcon @mousedown.stop="showItemActions" size="16" class="vue-feather text-theme dark-text-theme inline-block transform scale-110" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import FolderIcon from '/resources/js/components/FilesView/FolderIcon'
|
||||
import {LinkIcon, MoreHorizontalIcon} from 'vue-feather-icons'
|
||||
import FileIconThumbnail from "./FileIconThumbnail";
|
||||
import MemberAvatar from "./MemberAvatar";
|
||||
import CheckBox from "./CheckBox";
|
||||
import {debounce} from "lodash";
|
||||
import {mapGetters} from "vuex";
|
||||
import {events} from "../../bus";
|
||||
|
||||
export default {
|
||||
name: 'ItemList',
|
||||
components: {
|
||||
FileIconThumbnail,
|
||||
MoreHorizontalIcon,
|
||||
MemberAvatar,
|
||||
FolderIcon,
|
||||
CheckBox,
|
||||
LinkIcon,
|
||||
},
|
||||
props: [
|
||||
'entry',
|
||||
],
|
||||
data() {
|
||||
return {
|
||||
mobileMultiSelect: false,
|
||||
itemName: undefined,
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
...mapGetters([
|
||||
'isMultiSelectMode',
|
||||
'clipboard',
|
||||
'user',
|
||||
]),
|
||||
isClicked() {
|
||||
return this.clipboard.some(element => element.data.id === this.entry.data.id)
|
||||
},
|
||||
isVideo() {
|
||||
return this.entry.data.type === 'video'
|
||||
},
|
||||
isFile() {
|
||||
return this.entry.data.type === 'file'
|
||||
},
|
||||
isImage() {
|
||||
return this.entry.data.type === 'image'
|
||||
},
|
||||
isFolder() {
|
||||
return this.entry.data.type === 'folder'
|
||||
},
|
||||
timeStamp() {
|
||||
return this.entry.data.attributes.deleted_at
|
||||
? this.$t('entry_thumbnail.deleted_at', {time: this.entry.data.attributes.deleted_at})
|
||||
: this.entry.data.attributes.created_at
|
||||
},
|
||||
canEditName() {
|
||||
return !this.$isMobile()
|
||||
&& !this.$isThisRoute(this.$route, ['Trash'])
|
||||
&& !this.$checkPermission('visitor')
|
||||
&& !(this.sharedDetail && this.sharedDetail.attributes.type === 'file')
|
||||
},
|
||||
folderItems() {
|
||||
return this.entry.data.attributes.deleted_at
|
||||
? this.entry.data.attributes.trashed_items
|
||||
: this.entry.data.attributes.items
|
||||
},
|
||||
canShowAuthor() {
|
||||
return this.$isThisRoute(this.$route, ['SharedWithMe', 'TeamFolders'])
|
||||
&& !this.isFolder
|
||||
&& this.user.data.id !== this.entry.data.relationships.user.data.id
|
||||
},
|
||||
canDrag() {
|
||||
return !this.isDeleted && this.$checkPermission(['master', 'editor'])
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
showItemActions() {
|
||||
this.$store.commit('CLIPBOARD_CLEAR')
|
||||
this.$store.commit('ADD_ITEM_TO_CLIPBOARD', this.entry)
|
||||
|
||||
events.$emit('mobile-menu:show', 'file-menu')
|
||||
events.$emit('mobile-context-menu:show', this.entry)
|
||||
},
|
||||
renameItem: debounce(function (e) {
|
||||
|
||||
// Prevent submit empty string
|
||||
if (e.target.innerText.trim() === '') return
|
||||
|
||||
this.$store.dispatch('renameItem', {
|
||||
id: this.entry.data.id,
|
||||
type: this.entry.data.type,
|
||||
name: e.target.innerText
|
||||
})
|
||||
}, 300)
|
||||
},
|
||||
created() {
|
||||
|
||||
// Set item name to own component variable
|
||||
this.itemName = this.entry.data.attributes.name
|
||||
|
||||
// Change item name
|
||||
events.$on('change:name', item => {
|
||||
if (this.item.data.id === item.id) this.itemName = item.name
|
||||
})
|
||||
|
||||
// Autofocus after newly created folder
|
||||
events.$on('newFolder:focus', id => {
|
||||
|
||||
if ( !this.$isMobile() && this.entry.data.id === id) {
|
||||
this.$refs.name.focus()
|
||||
document.execCommand('selectAll')
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
</script>
|
||||
@@ -1,32 +1,49 @@
|
||||
<template>
|
||||
<ItemList
|
||||
:entry="item"
|
||||
@mouseup.stop.native="clickFilter"
|
||||
@dragstart.native="$emit('dragstart')"
|
||||
@drop.native="drop()"
|
||||
@dragleave.native="dragLeave"
|
||||
@dragover.prevent.native="dragEnter"
|
||||
:class="{'border-theme': area }"
|
||||
/>
|
||||
<div>
|
||||
<ItemList
|
||||
v-if="itemViewType === 'list'"
|
||||
:entry="item"
|
||||
@mouseup.stop.native="clickFilter"
|
||||
@dragstart.native="$emit('dragstart')"
|
||||
@drop.native="drop()"
|
||||
@dragleave.native="dragLeave"
|
||||
@dragover.prevent.native="dragEnter"
|
||||
:class="{'border-theme': area }"
|
||||
/>
|
||||
|
||||
<ItemGrid
|
||||
v-if="itemViewType === 'grid'"
|
||||
:entry="item"
|
||||
@mouseup.stop.native="clickFilter"
|
||||
@dragstart.native="$emit('dragstart')"
|
||||
@drop.native="drop()"
|
||||
@dragleave.native="dragLeave"
|
||||
@dragover.prevent.native="dragEnter"
|
||||
:class="{'border-theme': area }"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import {events} from '/resources/js/bus'
|
||||
import ItemList from './ItemList'
|
||||
import ItemGrid from './ItemGrid'
|
||||
import {mapGetters} from 'vuex'
|
||||
|
||||
export default {
|
||||
name: 'FileItemList',
|
||||
name: 'ItemHandler',
|
||||
props: [
|
||||
'disableHighlight',
|
||||
'item',
|
||||
],
|
||||
components: {
|
||||
ItemList,
|
||||
ItemGrid,
|
||||
},
|
||||
computed: {
|
||||
...mapGetters([
|
||||
'isMultiSelectMode',
|
||||
'itemViewType',
|
||||
'clipboard',
|
||||
'entries',
|
||||
'user',
|
||||
@@ -20,18 +20,7 @@
|
||||
<FolderIcon v-if="isFolder" :item="entry" location="file-item-list" />
|
||||
|
||||
<!--File Icon-->
|
||||
<div v-if="isFile || isVideo || (isImage && !entry.data.attributes.thumbnail)" class="flex items-center justify-center pr-2">
|
||||
<span class="text-theme dark-text-theme text-xs font-semibold absolute z-10 inline-block mx-auto mt-1 w-7 overflow-ellipsis overflow-hidden text-center">
|
||||
{{ entry.data.attributes.mimetype }}
|
||||
</span>
|
||||
|
||||
<svg width="38px" height="51px" viewBox="0 0 38 51" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
|
||||
<path
|
||||
stroke-width="0"
|
||||
fill="#F8F9FA"
|
||||
d="M22.1666667,13.546875 L22.1666667,0 L2.375,0 C1.05885417,0 0,1.06582031 0,2.390625 L0,48.609375 C0,49.9341797 1.05885417,51 2.375,51 L35.625,51 C36.9411458,51 38,49.9341797 38,48.609375 L38,15.9375 L24.5416667,15.9375 C23.2354167,15.9375 22.1666667,14.8617187 22.1666667,13.546875 Z M38,12.1423828 L38,12.75 L25.3333333,12.75 L25.3333333,0 L25.9369792,0 C26.5703125,0 27.1739583,0.249023438 27.6192708,0.697265625 L37.3072917,10.4589844 C37.7526042,10.9072266 38,11.5148437 38,12.1423828 Z"></path>
|
||||
</svg>
|
||||
</div>
|
||||
<FileIconThumbnail v-if="isFile || isVideo || (isImage && !entry.data.attributes.thumbnail)" :entry="entry" />
|
||||
|
||||
<!--Image thumbnail-->
|
||||
<img v-if="isImage && entry.data.attributes.thumbnail" class="w-12 h-12 rounded ml-0.5" :src="entry.data.attributes.thumbnail" :alt="entry.data.attributes.name" loading="lazy" />
|
||||
@@ -73,8 +62,9 @@
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import {LinkIcon, MoreVerticalIcon} from 'vue-feather-icons'
|
||||
import FolderIcon from '/resources/js/components/FilesView/FolderIcon'
|
||||
import {LinkIcon, MoreVerticalIcon} from 'vue-feather-icons'
|
||||
import FileIconThumbnail from "./FileIconThumbnail";
|
||||
import MemberAvatar from "./MemberAvatar";
|
||||
import CheckBox from "./CheckBox";
|
||||
import {debounce} from "lodash";
|
||||
@@ -84,6 +74,7 @@
|
||||
export default {
|
||||
name: 'ItemList',
|
||||
components: {
|
||||
FileIconThumbnail,
|
||||
MoreVerticalIcon,
|
||||
MemberAvatar,
|
||||
FolderIcon,
|
||||
|
||||
@@ -38,7 +38,7 @@
|
||||
computed: {
|
||||
...mapGetters([
|
||||
'isVisibleSidebar',
|
||||
'FilePreviewType',
|
||||
'itemViewType',
|
||||
'currentFolder',
|
||||
]),
|
||||
isLoadedFolder() {
|
||||
|
||||
Reference in New Issue
Block a user