Merge remote-tracking branch 'origin/master' into oasis

# Conflicts:
#	public/chunks/admin-account.js
#	public/chunks/admin.js
#	public/chunks/app-appearance~chunks/app-billings~chunks/app-email~chunks/app-index~chunks/app-others~chunks~8cc7d96f.js
#	public/chunks/app-language.js
#	public/chunks/app-settings.js
#	public/chunks/app-setup.js
#	public/chunks/billings-detail.js
#	public/chunks/dashboard.js
#	public/chunks/database.js
#	public/chunks/environment-setup.js
#	public/chunks/files.js
#	public/chunks/files~chunks/shared-files~chunks/shared/file-browser.js
#	public/chunks/files~chunks/shared-files~chunks/shared/file-browser~chunks/shared/single-file.js
#	public/chunks/homepage.js
#	public/chunks/installation-disclaimer.js
#	public/chunks/invoices.js
#	public/chunks/page-edit.js
#	public/chunks/pages.js
#	public/chunks/plan-create.js
#	public/chunks/plan-settings.js
#	public/chunks/plan-subscribers.js
#	public/chunks/plan.js
#	public/chunks/plans.js
#	public/chunks/platform.js
#	public/chunks/profile.js
#	public/chunks/profile~chunks/settings-password.js
#	public/chunks/purchase-code.js
#	public/chunks/settings-create-payment-methods.js
#	public/chunks/settings-invoices.js
#	public/chunks/settings-payment-methods.js
#	public/chunks/settings-storage.js
#	public/chunks/settings-subscription.js
#	public/chunks/settings.js
#	public/chunks/shared-files.js
#	public/chunks/shared.js
#	public/chunks/shared/file-browser.js
#	public/chunks/stripe-credentials.js
#	public/chunks/subscription-plans.js
#	public/chunks/subscription-service.js
#	public/chunks/upgrade-billing.js
#	public/chunks/upgrade-plan.js
#	public/chunks/user-create.js
#	public/chunks/user-delete.js
#	public/chunks/user-detail.js
#	public/chunks/user-invoices.js
#	public/chunks/user-storage.js
#	public/chunks/user-subscription.js
#	public/chunks/user.js
#	public/chunks/users.js
#	public/js/main.js
#	public/mix-manifest.json
#	public/vendors~chunks/platform~chunks/shared.js
This commit is contained in:
Peter Papp
2021-04-20 08:15:34 +02:00
154 changed files with 5332 additions and 4857 deletions
+24 -20
View File
@@ -5,13 +5,14 @@
<Alert />
<ToastrWrapper />
<CookieDisclaimer />
<Vignette />
<!--Show spinner before translations is loaded-->
<Spinner v-if="! isLoadedTranslations"/>
<Spinner v-if="! isLoaded"/>
<!--App view-->
<router-view v-if="isLoadedTranslations" />
<router-view v-if="isLoaded" />
<Vignette />
</div>
</template>
@@ -34,7 +35,7 @@ export default {
},
data() {
return {
isLoadedTranslations: false
isLoaded: false
}
},
methods: {
@@ -44,25 +45,12 @@ export default {
},
beforeMount() {
// Get language translations
this.$store.dispatch('getLanguageTranslations', this.$root.$data.config.language)
.then(() => {
this.isLoadedTranslations = true
// Store config to vuex
this.$store.commit('INIT', {
config: this.$root.$data.config,
rootDirectory: {
name: this.$t('locations.home'),
location: 'base',
id: undefined
}
})
})
// Get installation state
let installation = this.$root.$data.config.installation
if (['setup-disclaimer', 'setup-database'].includes(installation))
this.isLoaded = true
// Redirect to database verify code
if (installation === 'setup-database')
this.$router.push({name: 'PurchaseCode'})
@@ -70,6 +58,22 @@ export default {
// Redirect to starting installation process
if (installation === 'setup-disclaimer')
this.$router.push({name: 'InstallationDisclaimer'})
if (installation === 'setup-done')
this.$store.dispatch('getLanguageTranslations', this.$root.$data.config.language)
.then(() => {
this.isLoaded = true
// Store config to vuex
this.$store.commit('INIT', {
config: this.$root.$data.config,
rootDirectory: {
name: this.$t('locations.home'),
location: 'base',
id: undefined
}
})
})
},
mounted() {
this.$checkOS()
@@ -0,0 +1,51 @@
<template>
<div v-if="isVisible" class="popover-item">
<slot></slot>
</div>
</template>
<script>
import {events} from '@/bus'
export default {
name: 'PopoverItem',
props: [
'name'
],
data() {
return {
isVisible: false,
}
},
mounted() {
events.$on('popover:open', name => {
if (this.name === name) this.isVisible = !this.isVisible
})
events.$on('unClick', () => this.isVisible = false)
}
}
</script>
<style scoped lang="scss">
@import "@assets/vuefilemanager/_variables";
@import "@assets/vuefilemanager/_mixins";
.popover-item {
min-width: 250px;
position: absolute;
z-index: 9;
box-shadow: $shadow;
background: white;
border-radius: 8px;
overflow: hidden;
right: 0;
top: 50px;
}
@media (prefers-color-scheme: dark) {
.popover-item {
background: $dark_mode_foreground;
}
}
</style>
@@ -0,0 +1,18 @@
<template>
<div class="popover-wrapper">
<slot></slot>
</div>
</template>
<script>
export default {
name: 'PopoverWrapper',
}
</script>
<style scoped lang="scss">
.popover-wrapper {
position: relative;
}
</style>
@@ -0,0 +1,28 @@
<template>
<div class="toolbar-button-wrapper">
<slot></slot>
</div>
</template>
<script>
export default {
name: 'ToolbarWrapper',
}
</script>
<style scoped lang="scss">
.toolbar-button-wrapper {
margin-left: 28px;
display: flex;
align-items: center;
}
@media only screen and (max-width: 1024px) {
.toolbar-button-wrapper {
margin-left: 25px;
}
}
</style>
@@ -0,0 +1,21 @@
<template>
<div class="toolbar-tools">
<slot></slot>
</div>
</template>
<script>
export default {
name: 'ToolbarWrapper',
}
</script>
<style scoped lang="scss">
@import "@assets/vuefilemanager/_variables";
@import "@assets/vuefilemanager/_mixins";
.toolbar-tools {
text-align: right;
display: flex;;
}
</style>
@@ -193,21 +193,21 @@ export default {
Option
},
computed: {
...mapGetters(['user', 'fileInfoDetail']),
...mapGetters(['user', 'clipboard']),
hasFolder() {
return this.fileInfoDetail.find(item => item.type === 'folder')
return this.clipboard.find(item => item.type === 'folder')
},
hasFile() {
return this.fileInfoDetail.find(item => item.type !== 'folder')
return this.clipboard.find(item => item.type !== 'folder')
},
isMultiSelectContextMenu() {
// If is context Menu open on multi selected items open just options for the multi selected items
if (this.fileInfoDetail.length > 1 && this.fileInfoDetail.includes(this.item))
if (this.clipboard.length > 1 && this.clipboard.includes(this.item))
return false
// If is context Menu open for the non selected item open options for the single item
if (this.fileInfoDetail.length < 2 || !this.fileInfoDetail.includes(this.item))
if (this.clipboard.length < 2 || !this.clipboard.includes(this.item))
return true
},
favourites() {
@@ -250,11 +250,11 @@ export default {
restoreItem() {
// If is item not in selected items restore just this single item
if (!this.fileInfoDetail.includes(this.item))
if (!this.clipboard.includes(this.item))
this.$store.dispatch('restoreItem', this.item)
// If is item in selected items restore all items from fileInfoDetail
if (this.fileInfoDetail.includes(this.item))
// If is item in selected items restore all items from clipboard
if (this.clipboard.includes(this.item))
this.$store.dispatch('restoreItem', null)
},
shareCancel() {
@@ -267,13 +267,14 @@ export default {
events.$emit('popup:open', {name: 'move', item: [this.item]})
},
shareItem() {
if (this.item.shared) {
// Open edit share popup
events.$emit('popup:open', {name: 'share-edit', item: this.item})
} else {
// Open create share popup
events.$emit('popup:open', {name: 'share-create', item: this.item})
}
let event = this.item.shared
? 'share-edit'
: 'share-create'
events.$emit('popup:open', {
name: event,
item: this.item
})
},
addToFavourites() {
// Check if folder is in favourites and then add/remove from favourites
@@ -282,12 +283,12 @@ export default {
!this.favourites.find(el => el.id === this.item.id)
) {
// Add to favourite folder that is not selected
if (!this.fileInfoDetail.includes(this.item)) {
if (!this.clipboard.includes(this.item)) {
this.$store.dispatch('addToFavourites', this.item)
}
// Add to favourites all selected folders
if (this.fileInfoDetail.includes(this.item)) {
if (this.clipboard.includes(this.item)) {
this.$store.dispatch('addToFavourites', null)
}
} else {
@@ -295,7 +296,7 @@ export default {
}
},
downloadItem() {
if (this.fileInfoDetail.length > 1)
if (this.clipboard.length > 1)
this.$store.dispatch('downloadFiles')
else {
this.$downloadFile(this.item.file_url, this.item.name + '.' + this.item.mimetype)
@@ -303,18 +304,18 @@ export default {
},
ItemDetail() {
// Dispatch load file info detail
this.$store.commit('GET_FILEINFO_DETAIL', this.item)
this.$store.commit('ADD_ITEM_TO_CLIPBOARD', this.item)
// Show panel if is not open
this.$store.dispatch('fileInfoToggle', true)
},
deleteItem() {
// If is context menu open on non selected item delete this single item
if (!this.fileInfoDetail.includes(this.item)) {
if (!this.clipboard.includes(this.item)) {
this.$store.dispatch('deleteItem', this.item)
}
// If is context menu open to multi selected items dele this selected items
if (this.fileInfoDetail.includes(this.item)) {
if (this.clipboard.includes(this.item)) {
this.$store.dispatch('deleteItem')
}
},
@@ -385,12 +386,6 @@ export default {
}
}
},
mounted() {
events.$on('actualShowingImage:ContextMenu', (item) => {
this.item = item
})
},
created() {
events.$on('showContextMenuPreview:show', (item) => {
if (!this.showFromPreview) {
@@ -1,59 +0,0 @@
<template>
<div v-if="isVisible" class="sorting-preview">
<SortingAndPreviewMenu />
</div>
</template>
<script>
import SortingAndPreviewMenu from '@/components/FilesView/SortingAndPreviewMenu'
import { events } from '@/bus'
export default {
name: 'DesktopSortingAndPreview',
components: {SortingAndPreviewMenu},
data () {
return {
isVisible: false
}
},
mounted () {
events.$on('sortingAndPreview', (state) => {
this.isVisible = state
})
events.$on('unClick', () => {
this.isVisible = false
})
}
}
</script>
<style scoped lang="scss">
@import "@assets/vuefilemanager/_variables";
@import "@assets/vuefilemanager/_mixins";
.sorting-preview {
min-width: 250px;
position: absolute;
z-index: 99;
box-shadow: $shadow;
background: white;
border-radius: 8px;
overflow: hidden;
right: 66px;
top: 63px;
&.showed {
display: block;
}
}
@media (prefers-color-scheme: dark) {
.sorting-preview {
background: $dark_mode_foreground;
}
}
</style>
@@ -1,399 +1,334 @@
<template>
<div id="desktop-toolbar">
<div class="toolbar-wrapper">
<!-- Go back-->
<div class="toolbar-go-back" v-if="homeDirectory">
<div @click="goBack" class="go-back-button">
<chevron-left-icon size="17" :class="{ 'is-active': browseHistory.length > 1 }" class="icon-back"></chevron-left-icon>
<span class="back-directory-title">
{{ directoryName }}
</span>
<div v-if="homeDirectory" @click="goBack" class="location">
<chevron-left-icon :class="{'is-active': browseHistory.length > 1 }" class="icon-back" size="17" />
<span @click.stop="folderActions" v-if="browseHistory.length > 1 && $isThisLocation(['base', 'public'])" class="folder-options group" id="folder-actions">
<more-horizontal-icon size="14" class="icon-more group-hover-text-theme" />
</span>
</div>
</div>
<span class="location-title">
{{ directoryName }}
</span>
<!-- Tools-->
<div class="toolbar-tools">
<!--Search bar-->
<div class="toolbar-button-wrapper">
<SearchBar/>
</div>
<span @click.stop="folderActions" v-if="browseHistory.length > 1 && $isThisLocation(['base', 'public'])" class="location-more group" id="folder-actions">
<more-horizontal-icon size="14" class="icon-more group-hover-text-theme" />
</span>
</div>
<!--Creating controls-->
<div class="toolbar-button-wrapper" v-if="$checkPermission(['master', 'editor'])">
<ToolbarButtonUpload :class="{ 'is-inactive': canUploadInView || !hasCapacity }" :action="$t('actions.upload')"/>
<ToolbarButton :class="{ 'is-inactive': canCreateFolderInView }" @click.native="createFolder" source="folder-plus" :action="$t('actions.create_folder')"/>
</div>
<ToolbarWrapper>
<!--File Controls-->
<div class="toolbar-button-wrapper" v-if="$checkPermission(['master', 'editor']) && ! $isMobile()">
<ToolbarButton source="move" :class="{ 'is-inactive': canMoveInView }" :action="$t('actions.move')" @click.native="moveItem"/>
<ToolbarButton v-if="!$isThisLocation(['public'])" source="share" :class="{ 'is-inactive': canShareInView }" :action="$t('actions.share')" @click.native="shareItem"/>
<ToolbarButton source="trash" :class="{ 'is-inactive': canDeleteInView }" :action="$t('actions.delete')" @click.native="deleteItem"/>
</div>
<!--Search bar-->
<ToolbarGroup style="margin-left: 0">
<SearchBar v-model="query" @reset-query="query = ''" :placeholder="$t('inputs.placeholder_search_files')" />
</ToolbarGroup>
<!--View Controls-->
<div class="toolbar-button-wrapper">
<ToolbarButton source="preview-sorting" class="preview-sorting" :action="$t('actions.sorting_view')" :class="{ active: sortingAndPreview }" @click.stop.native="sortingAndPreview = !sortingAndPreview"/>
<ToolbarButton :action="$t('actions.info_panel')" :class="{ active: fileInfoVisible }" @click.native="$store.dispatch('fileInfoToggle')" source="info"/>
</div>
</div>
<!--Creating controls-->
<ToolbarGroup v-if="$checkPermission(['master', 'editor'])">
<ToolbarButtonUpload :class="{'is-inactive': canUploadInView || !hasCapacity }" :action="$t('actions.upload')" />
<ToolbarButton @click.native="createFolder" :class="{'is-inactive': canCreateFolderInView }" source="folder-plus" :action="$t('actions.create_folder')" />
</ToolbarGroup>
<!--File Controls-->
<ToolbarGroup v-if="$checkPermission(['master', 'editor']) && ! $isMobile()">
<ToolbarButton @click.native="moveItem" :class="{'is-inactive': canMoveInView }" source="move" :action="$t('actions.move')" />
<ToolbarButton @click.native="shareItem" v-if="!$isThisLocation(['public'])" :class="{'is-inactive': canShareInView }" source="share" :action="$t('actions.share')" />
<ToolbarButton @click.native="deleteItem" :class="{'is-inactive': canDeleteInView }" source="trash" :action="$t('actions.delete')" />
</ToolbarGroup>
<!--View Controls-->
<ToolbarGroup>
<PopoverWrapper>
<ToolbarButton @click.stop.native="showSortingMenu" source="preview-sorting" :action="$t('actions.sorting_view')" />
<PopoverItem name="desktop-sorting">
<FileSortingOptions />
</PopoverItem>
</PopoverWrapper>
<ToolbarButton @click.native="$store.dispatch('fileInfoToggle')" :class="{'active': isVisibleSidebar }" :action="$t('actions.info_panel')" source="info" />
</ToolbarGroup>
</ToolbarWrapper>
</div>
<UploadProgress/>
<UploadProgress />
</div>
</template>
<script>
import ToolbarButtonUpload from '@/components/FilesView/ToolbarButtonUpload'
import { ChevronLeftIcon, MoreHorizontalIcon } from 'vue-feather-icons'
import UploadProgress from '@/components/FilesView/UploadProgress'
import ToolbarButton from '@/components/FilesView/ToolbarButton'
import SearchBar from '@/components/FilesView/SearchBar'
import { mapGetters } from 'vuex'
import { events } from '@/bus'
import { last } from 'lodash'
import ToolbarButtonUpload from '@/components/FilesView/ToolbarButtonUpload'
import FileSortingOptions from '@/components/FilesView/FileSortingOptions'
import {ChevronLeftIcon, MoreHorizontalIcon} from 'vue-feather-icons'
import UploadProgress from '@/components/FilesView/UploadProgress'
import PopoverWrapper from '@/components/Desktop/PopoverWrapper'
import ToolbarWrapper from '@/components/Desktop/ToolbarWrapper'
import ToolbarButton from '@/components/FilesView/ToolbarButton'
import ToolbarGroup from '@/components/Desktop/ToolbarGroup'
import PopoverItem from '@/components/Desktop/PopoverItem'
import SearchBar from '@/components/FilesView/SearchBar'
import {debounce, last} from 'lodash'
import {mapGetters} from 'vuex'
import {events} from '@/bus'
export default {
name: 'ToolBar',
components: {
ToolbarButtonUpload,
MoreHorizontalIcon,
ChevronLeftIcon,
UploadProgress,
ToolbarButton,
SearchBar
},
computed: {
...mapGetters([
'FilePreviewType',
'fileInfoVisible',
'fileInfoDetail',
'currentFolder',
'browseHistory',
'homeDirectory'
]),
hasCapacity() {
// Check if set storage limitation
if (!this.$store.getters.config.storageLimit) return true
export default {
name: 'ToolBar',
components: {
ToolbarButtonUpload,
FileSortingOptions,
MoreHorizontalIcon,
ChevronLeftIcon,
ToolbarWrapper,
UploadProgress,
PopoverWrapper,
ToolbarButton,
ToolbarGroup,
PopoverItem,
SearchBar,
},
computed: {
...mapGetters([
'isVisibleSidebar',
'FilePreviewType',
'currentFolder',
'browseHistory',
'homeDirectory',
'clipboard',
]),
hasCapacity() {
// Check if storage limitation is set
if (!this.$store.getters.config.storageLimit) return true
// Check if is loaded user
if (!this.$store.getters.user) return true
// Check if user is loaded
if (!this.$store.getters.user) return true
// Check if user has storage
return this.$store.getters.user.data.attributes.storage.used <= 100
},
directoryName() {
return this.currentFolder
? this.currentFolder.name
: this.homeDirectory.name
},
preview() {
return this.FilePreviewType === 'list' ? 'th' : 'th-list'
},
canCreateFolderInView() {
return !this.$isThisLocation(['base', 'public'])
},
canDeleteInView() {
let locations = [
'trash',
'trash-root',
'base',
'participant_uploads',
'latest',
'shared',
'public'
]
return !this.$isThisLocation(locations) || this.fileInfoDetail.length === 0
},
canUploadInView() {
return !this.$isThisLocation(['base', 'public'])
},
canMoveInView() {
let locations = [
'base',
'participant_uploads',
'latest',
'shared',
'public'
]
return !this.$isThisLocation(locations) || this.fileInfoDetail.length === 0
// Check if user has storage
return this.$store.getters.user.data.attributes.storage.used <= 100
},
directoryName() {
return this.currentFolder
? this.currentFolder.name
: this.homeDirectory.name
},
preview() {
return this.FilePreviewType === 'list'
? 'th'
: 'th-list'
},
canCreateFolderInView() {
return !this.$isThisLocation(['base', 'public'])
},
canDeleteInView() {
let locations = [
'participant_uploads',
'trash-root',
'latest',
'shared',
'public',
'trash',
'base',
]
return !this.$isThisLocation(locations) || this.clipboard.length === 0
},
canUploadInView() {
return !this.$isThisLocation(['base', 'public'])
},
canMoveInView() {
let locations = [
'participant_uploads',
'latest',
'shared',
'public',
'base',
]
return !this.$isThisLocation(locations) || this.clipboard.length === 0
},
canShareInView() {
let locations = [
'participant_uploads',
'latest',
'shared',
'public',
'base',
]
return !this.$isThisLocation(locations) || this.clipboard.length > 1 || this.clipboard.length === 0
}
},
data() {
return {
query: '',
}
},
watch: {
query(val) {
this.$searchFiles(val)
}
},
methods: {
showSortingMenu() {
events.$emit('popover:open', 'desktop-sorting')
},
goBack() {
let previousFolder = last(this.browseHistory)
},
canShareInView() {
let locations = [
'base',
'participant_uploads',
'latest',
'shared',
'public'
]
if (!previousFolder) return
return !this.$isThisLocation(locations) || this.fileInfoDetail.length > 1 || this.fileInfoDetail.length === 0
}
},
data() {
return {
sortingAndPreview: false
}
},
watch: {
sortingAndPreview() {
if (this.sortingAndPreview) {
events.$emit('sortingAndPreview', true)
}
if (previousFolder.location === 'trash-root') {
this.$store.dispatch('getTrash')
if (!this.sortingAndPreview) {
events.$emit('unClick')
}
}
},
methods: {
goBack() {
// Get previous folder
let previousFolder = last(this.browseHistory)
} else if (previousFolder.location === 'shared') {
this.$store.dispatch('getShared')
if (!previousFolder) return
} else {
if (this.$isThisLocation('public')) {
this.$store.dispatch('browseShared', [
{folder: previousFolder, back: true, init: false}
])
} else {
this.$store.dispatch('getFolder', [
{folder: previousFolder, back: true, init: false}
])
}
}
},
folderActions() {
events.$emit('folder:actions', this.currentFolder)
},
deleteItem() {
if (this.clipboard.length > 0)
this.$store.dispatch('deleteItem')
},
createFolder() {
this.$store.dispatch('createFolder', {name: this.$t('popup_create_folder.folder_default_name')})
},
moveItem() {
if (this.clipboard.length > 0)
events.$emit('popup:open', {name: 'move', item: this.clipboard})
},
shareItem() {
let event = this.clipboard[0].shared
? 'share-edit'
: 'share-create'
if (previousFolder.location === 'trash-root') {
this.$store.dispatch('getTrash')
} else if (previousFolder.location === 'shared') {
this.$store.dispatch('getShared')
} else {
if (this.$isThisLocation('public')) {
this.$store.dispatch('browseShared', [
{ folder: previousFolder, back: true, init: false }
])
} else {
this.$store.dispatch('getFolder', [
{ folder: previousFolder, back: true, init: false }
])
}
}
},
folderActions() {
events.$emit('folder:actions', this.currentFolder)
},
deleteItem() {
if (this.fileInfoDetail.length > 0)
this.$store.dispatch('deleteItem')
},
createFolder() {
this.$store.dispatch('createFolder', {name: this.$t('popup_create_folder.folder_default_name')})
},
moveItem() {
if (this.fileInfoDetail.length > 0)
events.$emit('popup:open', { name: 'move', item: this.fileInfoDetail })
},
shareItem() {
if (this.fileInfoDetail[0]) {
//ADD BY M
if (this.fileInfoDetail[0].shared) {
events.$emit('popup:open', {
name: 'share-edit',
item: this.fileInfoDetail[0]
})
} else {
events.$emit('popup:open', {
name: 'share-create',
item: this.fileInfoDetail[0]
})
}
}
}
},
mounted() {
// events.$on('sortingAndPreview', (state) => {
// this.sortingAndPreview = state
// })
events.$on('unClick', () => {
this.sortingAndPreview = false
})
}
}
events.$emit('popup:open', {
name: event,
item: this.clipboard[0]
})
}
},
}
</script>
<style scoped lang="scss">
@import "@assets/vuefilemanager/_variables";
@import "@assets/vuefilemanager/_mixins";
.preview-sorting {
/deep/ .label {
color: $text !important;
}
.is-inactive {
opacity: 0.25;
pointer-events: none;
}
.toolbar-wrapper {
padding-top: 10px;
padding-bottom: 10px;
display: flex;
position: relative;
z-index: 2;
> div {
flex-grow: 1;
align-self: center;
white-space: nowrap;
}
padding-top: 10px;
padding-bottom: 10px;
display: flex;
justify-content: space-between;
align-items: center;
position: relative;
z-index: 2;
}
.directory-name {
vertical-align: middle;
@include font-size(17);
color: $text;
font-weight: 700;
max-width: 220px;
overflow: hidden;
text-overflow: ellipsis;
display: inline-block;
}
.location {
align-items: center;
cursor: pointer;
display: flex;
.icon-back {
vertical-align: middle;
cursor: pointer;
margin-right: 6px;
opacity: 0.15;
pointer-events: none;
@include transition(150ms);
.icon-back {
@include transition(150ms);
pointer-events: none;
margin-right: 6px;
flex-shrink: 0;
opacity: 0.15;
&.is-active {
opacity: 1;
pointer-events: initial;
}
}
&.is-active {
opacity: 1;
pointer-events: initial;
}
}
.toolbar-go-back {
cursor: pointer;
.location-title {
@include font-size(15);
line-height: 1;
font-weight: 700;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
color: $text;
}
.folder-options {
vertical-align: middle;
margin-left: 6px;
padding: 1px 4px;
line-height: 0;
border-radius: 3px;
@include transition(150ms);
.location-more {
margin-left: 6px;
padding: 1px 4px;
line-height: 0;
border-radius: 3px;
@include transition(150ms);
svg circle {
@include transition(150ms);
}
svg circle {
@include transition(150ms);
}
&:hover {
background: $light_background;
&:hover {
background: $light_background;
svg circle {
color: inherit;
}
}
.icon-more {
vertical-align: middle;
}
}
.back-directory-title {
@include font-size(15);
line-height: 1;
font-weight: 700;
overflow: hidden;
text-overflow: ellipsis;
display: inline-block;
vertical-align: middle;
color: $text;
}
svg circle {
color: inherit;
}
}
}
}
.toolbar-position {
text-align: center;
text-align: center;
span {
@include font-size(17);
font-weight: 600;
}
}
.toolbar-tools {
text-align: right;
.toolbar-button-wrapper {
margin-left: 28px;
display: inline-block;
vertical-align: middle;
&:first-child {
margin-left: 0 !important;
}
}
.button {
margin-left: 5px;
&.active {
&.preview-sorting {
background: $light_background;
}
}
&.is-inactive {
opacity: 0.25;
pointer-events: none;
}
&:first-child {
margin-left: 0;
}
}
span {
@include font-size(17);
font-weight: 600;
}
}
@media only screen and (max-width: 1024px) {
.toolbar-go-back .back-directory-title {
max-width: 120px;
}
.location {
.toolbar-tools {
.button {
margin-left: 0;
height: 40px;
width: 40px;
}
.location-title {
max-width: 120px;
}
}
.toolbar-button-wrapper {
margin-left: 25px;
}
}
.toolbar-tools {
.button {
margin-left: 0;
height: 40px;
width: 40px;
}
}
}
@media only screen and (max-width: 960px) {
#desktop-toolbar {
display: none;
}
#desktop-toolbar {
display: none;
}
}
@media (prefers-color-scheme: dark) {
.toolbar .directory-name {
color: $dark_mode_text_primary;
}
.toolbar .directory-name {
color: $dark_mode_text_primary;
}
.toolbar-go-back {
.back-directory-title {
color: $dark_mode_text_primary;
}
.toolbar-go-back {
.location-title {
color: $dark_mode_text_primary;
}
.folder-options {
&:hover {
background: $dark_mode_foreground;
}
}
}
.active {
&.preview-sorting {
background: $dark_mode_foreground !important;
}
}
.location-more {
&:hover {
background: $dark_mode_foreground;
}
}
}
}
</style>
+21 -14
View File
@@ -1,20 +1,30 @@
<template>
<MultiSelected :title="title" :subtitle="subtitle" id="multi-select-ui" v-show="isVisible"/>
<TitlePreview
icon="check-square"
:title="title"
:subtitle="subtitle"
id="drag-ui"
v-show="isVisible"
/>
</template>
<script>
import MultiSelected from '@/components/FilesView/MultiSelected'
import TitlePreview from '@/components/FilesView/TitlePreview'
import { mapGetters } from 'vuex'
import { events } from '@/bus'
export default {
name: 'DragUI',
components: { MultiSelected },
components: {
TitlePreview
},
computed: {
...mapGetters(['fileInfoDetail']),
...mapGetters([
'clipboard'
]),
title() {
let filesLength = this.fileInfoDetail.length,
hasDraggedItem = this.fileInfoDetail.includes(this.draggedItem)
let filesLength = this.clipboard.length,
hasDraggedItem = this.clipboard.includes(this.draggedItem)
// Title for multiple selected items
if (filesLength > 1 && hasDraggedItem) {
@@ -27,8 +37,8 @@ export default {
}
},
subtitle() {
let filesLength = this.fileInfoDetail.length,
hasDraggedItem = this.fileInfoDetail.includes(this.draggedItem)
let filesLength = this.clipboard.length,
hasDraggedItem = this.clipboard.includes(this.draggedItem)
// Subtitle for multiple selected items
if (filesLength > 1 && hasDraggedItem) {
@@ -56,7 +66,6 @@ export default {
}
},
created() {
// Handle Drag & Drop Ghost show
events.$on('dragstart', data => {
this.draggedItem = data
@@ -65,9 +74,7 @@ export default {
}, 100)
})
events.$on('drop', () => {
this.isVisible = false
})
events.$on('drop', () => this.isVisible = false)
}
}
</script>
@@ -76,7 +83,7 @@ export default {
@import '@assets/vuefilemanager/_variables';
@import '@assets/vuefilemanager/_mixins';
#multi-select-ui {
#drag-ui {
max-width: 300px;
min-width: 250px;
position: fixed;
@@ -89,7 +96,7 @@ export default {
}
@media (prefers-color-scheme: dark) {
#multi-select-ui {
#drag-ui {
background: $dark_mode_foreground;
}
}
@@ -0,0 +1,120 @@
<template>
<div class="empty-page" v-if="isLoading || isEmpty">
<div class="empty-state">
<!--Shared empty message-->
<div class="text-content" v-if="$isThisLocation(['shared']) && ! isLoading">
<h1 class="title">{{ $t('shared.empty_shared') }}</h1>
</div>
<!--Trash empty message-->
<div class="text-content" v-if="$isThisLocation(['trash', 'trash-root']) && ! isLoading">
<h1 class="title">{{ $t('empty_page.title') }}</h1>
</div>
<!--Trash empty message-->
<div class="text-content" v-if="$isThisLocation(['participant_uploads']) && ! isLoading">
<h1 class="title">{{ $t('messages.nothing_from_participants') }}</h1>
</div>
<!--Base file browser empty message-->
<div class="text-content" v-if="$isThisLocation(['base', 'public', 'latest']) && !isLoading">
<h1 class="title">{{ $t('empty_page.title') }}</h1>
<p v-if="$checkPermission(['master', 'editor'])" class="description">{{ $t('empty_page.description') }}</p>
<ButtonUpload
v-if="$checkPermission(['master', 'editor'])"
button-style="theme"
>
{{ $t('empty_page.call_to_action') }}
</ButtonUpload>
</div>
<!--Spinner-->
<div class="text-content" v-if="isLoading">
<Spinner />
</div>
</div>
</div>
</template>
<script>
import ButtonUpload from '@/components/FilesView/ButtonUpload'
import Spinner from '@/components/FilesView/Spinner'
import {mapGetters} from 'vuex'
export default {
name: 'EmptyFilePage',
props: [
'title',
'description'
],
components: {
ButtonUpload,
Spinner,
},
computed: {
...mapGetters([
'currentFolder',
'isLoading',
'entries',
]),
isEmpty() {
return this.entries && this.entries.length == 0
}
}
}
</script>
<style scoped lang="scss">
@import '@assets/vuefilemanager/_variables';
@import '@assets/vuefilemanager/_mixins';
.empty-page {
position: absolute;
left: 0;
right: 0;
bottom: 0;
top: 0;
margin-top: 85px;
display: flex;
align-items: center;
.empty-state {
margin: 0 auto;
padding-left: 15px;
padding-right: 15px;
}
}
.text-content {
text-align: center;
margin: 30px 0;
.title {
@include font-size(20);
color: $text;
font-weight: 700;
margin: 0;
}
.description {
@include font-size(13);
color: $text-muted;
margin-bottom: 20px;
display: block;
}
}
@media (prefers-color-scheme: dark) {
.text-content {
.title {
color: $dark_mode_text_primary;
}
.description {
color: $dark_mode_text_secondary;
}
}
}
</style>
@@ -1,113 +0,0 @@
<template>
<div class="empty-page" v-if="isLoading || isEmpty">
<div class="empty-state">
<!--Shared empty message-->
<div class="text-content" v-if="$isThisLocation(['shared']) && ! isLoading">
<h1 class="title">{{ $t('shared.empty_shared') }}</h1>
</div>
<!--Trash empty message-->
<div class="text-content" v-if="$isThisLocation(['trash', 'trash-root']) && ! isLoading">
<h1 class="title">{{ $t('empty_page.title') }}</h1>
</div>
<!--Trash empty message-->
<div class="text-content" v-if="$isThisLocation(['participant_uploads']) && ! isLoading">
<h1 class="title">{{ $t('messages.nothing_from_participants') }}</h1>
</div>
<!--Base file browser empty message-->
<div class="text-content" v-if="$isThisLocation(['base', 'public', 'latest']) && !isLoading">
<h1 class="title">{{ $t('empty_page.title') }}</h1>
<p v-if="$checkPermission(['master', 'editor'])" class="description">{{ $t('empty_page.description') }}</p>
<ButtonUpload
v-if="$checkPermission(['master', 'editor'])"
button-style="theme"
>
{{ $t('empty_page.call_to_action') }}
</ButtonUpload>
</div>
<!--Spinner-->
<div class="text-content" v-if="isLoading">
<Spinner/>
</div>
</div>
</div>
</template>
<script>
import ButtonUpload from '@/components/FilesView/ButtonUpload'
import Spinner from '@/components/FilesView/Spinner'
import {mapGetters} from 'vuex'
export default {
name: 'EmptyPage',
props: ['title', 'description'],
components: {
ButtonUpload,
Spinner
},
computed: {
...mapGetters(['data', 'isLoading', 'currentFolder']),
isEmpty() {
return this.data && this.data.length == 0
}
}
}
</script>
<style scoped lang="scss">
@import '@assets/vuefilemanager/_variables';
@import '@assets/vuefilemanager/_mixins';
.empty-page {
position: absolute;
left: 0;
right: 0;
bottom: 0;
top: 0;
margin-top: 85px;
display: flex;
align-items: center;
.empty-state {
margin: 0 auto;
padding-left: 15px;
padding-right: 15px;
}
}
.text-content {
text-align: center;
margin: 30px 0;
.title {
@include font-size(20);
color: $text;
font-weight: 700;
margin: 0;
}
.description {
@include font-size(13);
color: $text-muted;
margin-bottom: 20px;
display: block;
}
}
@media (prefers-color-scheme: dark) {
.text-content {
.title {
color: $dark_mode_text_primary;
}
.description {
color: $dark_mode_text_secondary;
}
}
}
</style>
@@ -0,0 +1,236 @@
<template>
<div id="mobile-actions-wrapper">
<!--Base location-->
<div v-if="$isThisLocation(['base']) && $checkPermission(['master', 'editor']) && ! isSelectMode" class="mobile-actions">
<MobileActionButton @click.native="showLocations" icon="filter">
{{ filterLocationTitle }}
</MobileActionButton>
<MobileActionButton @click.native="createFolder" icon="folder-plus">
{{ $t('context_menu.add_folder') }}
</MobileActionButton>
<MobileActionButtonUpload>
{{ $t('context_menu.upload') }}
</MobileActionButtonUpload>
<MobileActionButton @click.native="enableMultiSelectMode" icon="check-square">
{{ $t('context_menu.select') }}
</MobileActionButton>
<MobileActionButton @click.native="showViewOptions" icon="preview-sorting">
{{ $t('preview_sorting.preview_sorting_button') }}
</MobileActionButton>
</div>
<!--Base location editor-->
<div v-if="$isThisLocation('public') && $checkPermission('editor') && ! isSelectMode" class="mobile-actions">
<MobileActionButton @click.native="createFolder" icon="folder-plus">
{{ $t('context_menu.add_folder') }}
</MobileActionButton>
<MobileActionButtonUpload>
{{ $t('context_menu.upload') }}
</MobileActionButtonUpload>
<MobileActionButton @click.native="enableMultiSelectMode" icon="check-square">
{{ $t('context_menu.select') }}
</MobileActionButton>
<MobileActionButton @click.native="showViewOptions" icon="preview-sorting">
{{ $t('preview_sorting.preview_sorting_button') }}
</MobileActionButton>
</div>
<!--Base location visitor-->
<div v-if="$isThisLocation('public') && $checkPermission('visitor') && ! isSelectMode" class="mobile-actions">
<MobileActionButton @click.native="enableMultiSelectMode" icon="check-square">
{{ $t('context_menu.select') }}
</MobileActionButton>
<MobileActionButton @click.native="showViewOptions" icon="preview-sorting">
{{ $t('preview_sorting.preview_sorting_button') }}
</MobileActionButton>
</div>
<!--Recent uploads location-->
<div v-if="$isThisLocation('latest') && ! isSelectMode" class="mobile-actions">
<MobileActionButton @click.native="showLocations" icon="filter">
{{ filterLocationTitle }}
</MobileActionButton>
<MobileActionButtonUpload>
{{ $t('context_menu.upload') }}
</MobileActionButtonUpload>
<MobileActionButton @click.native="enableMultiSelectMode" icon="check-square">
{{ $t('context_menu.select') }}
</MobileActionButton>
<MobileActionButton @click.native="showViewOptions" icon="preview-sorting">
{{ $t('preview_sorting.preview_sorting_button') }}
</MobileActionButton>
</div>
<!--Trash location--->
<div v-if="$isThisLocation(['trash', 'trash-root']) && ! isSelectMode" class="mobile-actions">
<MobileActionButton @click.native="showLocations" icon="filter">
{{ filterLocationTitle }}
</MobileActionButton>
<MobileActionButton @click.native="$store.dispatch('emptyTrash')" icon="trash">
{{ $t('context_menu.empty_trash') }}
</MobileActionButton>
<MobileActionButton @click.native="enableMultiSelectMode" icon="check-square">
{{ $t('context_menu.select') }}
</MobileActionButton>
<MobileActionButton @click.native="showViewOptions" icon="preview-sorting">
{{ $t('preview_sorting.preview_sorting_button') }}
</MobileActionButton>
</div>
<!--Shared location--->
<div v-if="$isThisLocation(['shared', 'participant_uploads']) && ! isSelectMode" class="mobile-actions">
<MobileActionButton @click.native="showLocations" icon="filter">
{{ filterLocationTitle }}
</MobileActionButton>
<MobileActionButton @click.native="enableMultiSelectMode" icon="check-square">
{{ $t('context_menu.select') }}
</MobileActionButton>
<MobileActionButton @click.native="showViewOptions" icon="preview-sorting">
{{ $t('preview_sorting.preview_sorting_button') }}
</MobileActionButton>
</div>
<!-- Multi select mode -->
<div v-if="isSelectMode" class="mobile-actions">
<MobileActionButton @click.native="selectAll" icon="check-square">
{{ $t('mobile_selecting.select_all') }}
</MobileActionButton>
<MobileActionButton @click.native="deselectAll" icon="x-square">
{{ $t('mobile_selecting.deselect_all') }}
</MobileActionButton>
<MobileActionButton @click.native="disableMultiSelectMode" icon="check">
{{ $t('mobile_selecting.done') }}
</MobileActionButton>
</div>
<!--Upload Progressbar-->
<UploadProgress />
</div>
</template>
<script>
import MobileActionButtonUpload from '@/components/FilesView/MobileActionButtonUpload'
import MobileActionButton from '@/components/FilesView/MobileActionButton'
import UploadProgress from '@/components/FilesView/UploadProgress'
import {mapGetters} from 'vuex'
import {events} from '@/bus'
import store from "../../store";
export default {
name: 'FileActionsMobile',
components: {
MobileActionButtonUpload,
MobileActionButton,
UploadProgress,
},
computed: {
...mapGetters([
'FilePreviewType'
]),
previewIcon() {
return this.FilePreviewType === 'list'
? 'th'
: 'th-list'
},
filterLocationTitle() {
return {
'base': 'Files',
'public': 'Files',
'shared': 'Shared',
'latest': 'Latest',
'trash': 'Trash',
'trash-root': 'Trash',
'participant_uploads': 'Participants',
}[this.$store.getters.currentFolder.location]
}
},
data() {
return {
isSelectMode: false,
}
},
methods: {
showLocations() {
events.$emit('mobile-menu:show', 'file-filter')
},
selectAll() {
this.$store.commit('ADD_ALL_ITEMS_TO_CLIPBOARD')
},
deselectAll() {
this.$store.commit('CLIPBOARD_CLEAR')
},
enableMultiSelectMode() {
this.isSelectMode = true
events.$emit('mobileSelecting:start')
},
disableMultiSelectMode() {
this.isSelectMode = false
events.$emit('mobileSelecting:stop')
},
showViewOptions() {
events.$emit('mobile-menu:show', 'file-sorting')
},
createFolder() {
events.$emit('popup:open', {name: 'create-folder'})
},
},
mounted() {
events.$on('mobileSelecting:stop', () => this.isSelectMode = false)
}
}
</script>
<style scoped lang="scss">
@import '@assets/vuefilemanager/_variables';
@import '@assets/vuefilemanager/_mixins';
.button-enter-active,
.button-leave-active {
transition: all 250ms;
}
.button-enter {
opacity: 0;
transform: translateY(-50%);
}
.button-leave-to {
opacity: 0;
transform: translateY(50%);
}
.button-leave-active {
position: absolute;
}
#mobile-actions-wrapper {
display: none;
background: white;
position: sticky;
top: 35px;
z-index: 3;
}
.mobile-actions {
white-space: nowrap;
overflow-x: auto;
margin: 0 -15px;
padding: 10px 0 10px 15px;
}
@media only screen and (max-width: 960px) {
#mobile-actions-wrapper {
display: block;
}
}
@media (prefers-color-scheme: dark) {
#mobile-actions-wrapper {
background: $dark_mode_background;
}
}
</style>
+351 -354
View File
@@ -1,442 +1,439 @@
<template>
<div class="file-content" id="file-content-id" :class="{ 'is-offset': filesInQueueTotal > 0, 'is-dragging': isDragging }"
@dragover.prevent
@drop.stop.prevent="dropUpload($event)"
@dragover="dragEnter"
@dragleave="dragLeave"
@keydown.delete="deleteItems"
tabindex="-1"
>
<div
:class="{'is-offset': filesInQueueTotal > 0, 'is-dragging': isDragging }"
class="file-content"
id="file-content-id"
@drop.stop.prevent="dropUpload($event)"
@keydown.delete="deleteItems"
@dragover="dragEnter"
@dragleave="dragLeave"
@dragover.prevent
tabindex="-1"
>
<div
class="files-container"
ref="fileContainer"
:class="{'is-fileinfo-visible': fileInfoVisible && !$isMinimalScale() , 'mobile-multi-select' : mobileMultiSelect}"
@click.self="filesContainerClick"
>
<!--MobileToolbar-->
:class="{'is-visible': isVisibleSidebar, 'mobile-multi-select': isMultiSelect}"
@click.self="filesContainerClick"
class="files-container"
ref="fileContainer"
>
<MobileToolbar />
<!--Searchbar-->
<SearchBar class="mobile-search" />
<SearchBar v-model="query" @reset-query="query = ''" class="mobile-search" :placeholder="$t('inputs.placeholder_search_files')" />
<!--Mobile Actions-->
<MobileActions />
<!--Mobile Actions-->
<FileActionsMobile />
<!--Item previews list-->
<!--Item previews list-->
<div v-if="isList" class="file-list-wrapper">
<transition-group
name="file"
tag="section"
class="file-list"
:class="FilePreviewType"
>
name="file"
tag="section"
class="file-list"
:class="FilePreviewType"
>
<FileItemList
@dragstart="dragStart(item)"
@drop.stop.native.prevent="dragFinish(item, $event)"
@contextmenu.native.prevent="contextMenu($event, item)"
:item="item"
v-for="item in data"
:key="item.id"
class="file-item"
:class="draggedItems.includes(item) ? 'dragged' : '' "
/>
@dragstart="dragStart(item)"
@drop.stop.native.prevent="dragFinish(item, $event)"
@contextmenu.native.prevent="contextMenu($event, item)"
:item="item"
v-for="item in entries"
:key="item.id"
class="file-item"
:class="draggedItems.includes(item) ? 'dragged' : '' "
/>
</transition-group>
</div>
<!--Item previews grid-->
<!--Item previews grid-->
<div v-if="isGrid" class="file-grid-wrapper">
<transition-group
name="file"
tag="section"
class="file-list"
:class="FilePreviewType"
>
name="file"
tag="section"
class="file-list"
:class="FilePreviewType"
>
<FileItemGrid
@dragstart="dragStart(item)"
@drop.native.prevent="dragFinish(item, $event)"
@contextmenu.native.prevent="contextMenu($event, item)"
:item="item"
v-for="item in data"
:key="item.id"
class="file-item"
:class="draggedItems.includes(item) ? 'dragged' : '' "
/>
@dragstart="dragStart(item)"
@drop.native.prevent="dragFinish(item, $event)"
@contextmenu.native.prevent="contextMenu($event, item)"
:item="item"
v-for="item in entries"
:key="item.id"
class="file-item"
:class="draggedItems.includes(item) ? 'dragged' : '' "
/>
</transition-group>
</div>
<!--Show empty page if folder is empty-->
<EmptyPage v-if="! isSearching" />
<!--Show empty page if folder is empty-->
<EmptyFilePage v-if="! isSearching" />
<!--Show empty page if no search results-->
<!--Show empty page if no search results-->
<EmptyMessage
v-if="isSearching && isEmpty"
:message="$t('messages.nothing_was_found')"
icon="eye-slash"
/>
v-if="isSearching && isEmpty"
:message="$t('messages.nothing_was_found')"
icon="eye-slash"
/>
</div>
<!--File Info Panel-->
<div v-if="! $isMinimalScale()" class="file-info-container" :class="{ 'is-fileinfo-visible': fileInfoVisible }">
<!--File info panel-->
<FileInfoPanel v-if="fileInfoDetail.length === 1" />
<MultiSelected v-if="fileInfoDetail.length > 1"
:title="$t('file_detail.selected_multiple')"
:subtitle="this.fileInfoDetail.length + ' ' + $tc('file_detail.items', this.fileInfoDetail.length)"
/>
<!--If file info panel empty show message-->
<EmptyMessage v-if="fileInfoDetail.length === 0" :message="$t('messages.nothing_to_preview')" icon="eye-off" />
<!--File Info Panel-->
<div :class="{'is-visible': isVisibleSidebar }" class="file-info-container">
<InfoSidebar />
</div>
</div>
</template>
<script>
import MobileToolbar from '@/components/FilesView/MobileToolbar'
import MobileActions from '@/components/FilesView/MobileActions'
import MultiSelected from '@/components/FilesView/MultiSelected'
import FileInfoPanel from '@/components/FilesView/FileInfoPanel'
import FileItemList from '@/components/FilesView/FileItemList'
import FileItemGrid from '@/components/FilesView/FileItemGrid'
import EmptyMessage from '@/components/FilesView/EmptyMessage'
import EmptyPage from '@/components/FilesView/EmptyPage'
import SearchBar from '@/components/FilesView/SearchBar'
import {mapGetters} from 'vuex'
import {events} from '@/bus'
import FileActionsMobile from '@/components/FilesView/FileActionsMobile'
import MobileToolbar from '@/components/FilesView/MobileToolbar'
import EmptyFilePage from '@/components/FilesView/EmptyFilePage'
import EmptyMessage from '@/components/FilesView/EmptyMessage'
import FileItemList from '@/components/FilesView/FileItemList'
import FileItemGrid from '@/components/FilesView/FileItemGrid'
import InfoSidebar from '@/components/FilesView/InfoSidebar'
import SearchBar from '@/components/FilesView/SearchBar'
import {mapGetters} from 'vuex'
import {events} from '@/bus'
import {debounce} from "lodash";
export default {
name: 'FilesContainer',
components: {
MobileToolbar,
MobileActions,
MultiSelected,
FileInfoPanel,
FileItemList,
FileItemGrid,
EmptyMessage,
SearchBar,
EmptyPage
},
computed: {
...mapGetters([
'filesInQueueTotal',
'fileInfoVisible',
'fileInfoDetail',
'currentFolder',
'FilePreviewType',
'isSearching',
'isLoading',
'data'
]),
isGrid() {
return this.FilePreviewType === 'grid'
},
isList() {
return this.FilePreviewType === 'list'
},
isEmpty() {
return this.data.length == 0
},
draggedItems() {
//Set opacity for dragged items
export default {
name: 'FilesContainer',
components: {
FileActionsMobile,
EmptyFilePage,
MobileToolbar,
FileItemList,
FileItemGrid,
EmptyMessage,
InfoSidebar,
SearchBar,
},
computed: {
...mapGetters([
'filesInQueueTotal',
'isVisibleSidebar',
'FilePreviewType',
'currentFolder',
'isSearching',
'clipboard',
'isLoading',
'entries'
]),
isGrid() {
return this.FilePreviewType === 'grid'
},
isList() {
return this.FilePreviewType === 'list'
},
isEmpty() {
return this.entries.length == 0
},
draggedItems() {
//Set opacity for dragged items
if (!this.fileInfoDetail.includes(this.draggingId)) {
return [this.draggingId]
}
if (!this.clipboard.includes(this.draggingId)) {
return [this.draggingId]
}
if (this.fileInfoDetail.includes(this.draggingId)) {
return this.fileInfoDetail
}
}
},
data() {
return {
draggingId: undefined,
isDragging: false,
mobileMultiSelect: false,
}
},
methods: {
deleteItems() {
if (this.fileInfoDetail.length > 0 && this.$checkPermission('master') || this.$checkPermission('editor')) {
this.$store.dispatch('deleteItem')
}
},
dropUpload(event) {
// Upload external file
this.$uploadExternalFiles(event, this.currentFolder.id)
if (this.clipboard.includes(this.draggingId)) {
return this.clipboard
}
}
},
watch: {
query(val) {
this.$searchFiles(val)
}
},
data() {
return {
draggingId: undefined,
isDragging: false,
isMultiSelect: false,
query: '',
}
},
methods: {
deleteItems() {
if (this.clipboard.length > 0 && this.$checkPermission('master') || this.$checkPermission('editor')) {
this.$store.dispatch('deleteItem')
}
},
dropUpload(event) {
// Upload external file
this.$uploadExternalFiles(event, this.currentFolder.id)
this.isDragging = false
},
dragEnter() {
this.isDragging = true
},
dragLeave() {
this.isDragging = false
},
dragStart(data) {
let img = document.createElement('img')
img.src = "data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7"
event.dataTransfer.setDragImage(img, 0, 0)
this.isDragging = false
},
dragEnter() {
this.isDragging = true
},
dragLeave() {
this.isDragging = false
},
dragStart(data) {
let img = document.createElement('img')
img.src = "data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7"
event.dataTransfer.setDragImage(img, 0, 0)
events.$emit('dragstart', data)
events.$emit('dragstart', data)
// Store dragged folder
this.draggingId = data
},
dragFinish(data, event) {
// Store dragged folder
this.draggingId = data
},
dragFinish(data, event) {
if (event.dataTransfer.items.length == 0) {
// Prevent to drop on file or image
if (data.type !== 'folder' || this.draggingId === data) return
if (event.dataTransfer.items.length == 0) {
// Prevent to drop on file or image
if (data.type !== 'folder' || this.draggingId === data) return
//Prevent move selected folder to folder if in beteewn selected folders
if (this.fileInfoDetail.find(item => item === data && this.fileInfoDetail.length > 1)) return
//Prevent move selected folder to folder if in beteewn selected folders
if (this.clipboard.find(item => item === data && this.clipboard.length > 1)) return
// Move folder to new parent
// Move folder to new parent
//Move item if is not included in selected items
if (!this.fileInfoDetail.includes(this.draggingId)) {
this.$store.dispatch('moveItem', {to_item: data, noSelectedItem: this.draggingId})
}
//Move item if is not included in selected items
if (!this.clipboard.includes(this.draggingId)) {
this.$store.dispatch('moveItem', {to_item: data, noSelectedItem: this.draggingId})
}
//Move selected items to folder
if (this.fileInfoDetail.length > 0 && this.fileInfoDetail.includes(this.draggingId)) {
this.$store.dispatch('moveItem', {to_item: data, noSelectedItem: null})
}
//Move selected items to folder
if (this.clipboard.length > 0 && this.clipboard.includes(this.draggingId)) {
this.$store.dispatch('moveItem', {to_item: data, noSelectedItem: null})
}
} else {
} else {
// Get id of current folder
const id = data.type !== 'folder' ? this.currentFolder.id : data.id
// Get id of current folder
const id = data.type !== 'folder' ? this.currentFolder.id : data.id
// Upload external file
this.$uploadExternalFiles(event, id)
}
// Upload external file
this.$uploadExternalFiles(event, id)
}
this.isDragging = false
},
contextMenu(event, item) {
events.$emit('contextMenu:show', event, item)
},
filesContainerClick() {
this.isDragging = false
},
contextMenu(event, item) {
events.$emit('contextMenu:show', event, item)
},
filesContainerClick() {
// Deselect items clicked by outside
this.$store.commit('CLEAR_FILEINFO_DETAIL')
}
},
created() {
events.$on('mobileSelecting:start', () => {
this.mobileMultiSelect = true
})
// Deselect items clicked by outside
this.$store.commit('CLIPBOARD_CLEAR')
}
},
created() {
events.$on('mobileSelecting:start', () => {
this.isMultiSelect = true
})
events.$on('mobileSelecting:stop', () => {
this.mobileMultiSelect = false
})
events.$on('mobileSelecting:stop', () => {
this.isMultiSelect = false
})
events.$on('drop', () => {
this.isDragging = false
events.$on('drop', () => {
this.isDragging = false
setTimeout(() => {
this.draggingId = undefined
}, 10)
})
setTimeout(() => {
this.draggingId = undefined
}, 10)
})
events.$on('fileItem:deselect', () => {
this.$store.commit('CLEAR_FILEINFO_DETAIL')
})
events.$on('fileItem:deselect', () => {
this.$store.commit('CLIPBOARD_CLEAR')
})
events.$on('scrollTop', () => {
events.$on('scrollTop', () => {
// Scroll top
var container = document.getElementsByClassName(
'files-container'
)[0]
// Scroll top
var container = document.getElementsByClassName(
'files-container'
)[0]
if (container)
container.scrollTop = 0
})
}
}
if (container)
container.scrollTop = 0
})
}
}
</script>
<style scoped lang="scss">
@import '@assets/vuefilemanager/_variables';
@import '@assets/vuefilemanager/_mixins';
@import '@assets/vuefilemanager/_mixins';
.file-list {
.dragged {
/deep/ .is-dragenter {
border: 2px solid transparent;
}
}
}
.file-list {
.dragged {
/deep/ .is-dragenter {
border: 2px solid transparent;
}
}
}
.dragged {
opacity: 0.5;
}
.dragged {
opacity: 0.5;
}
#multi-selected {
position: fixed;
pointer-events: none;
z-index: 100;
#multi-selected {
position: fixed;
pointer-events: none;
z-index: 100;
}
}
.mobile-multi-select {
bottom: 50px !important;
top: 0px;
}
.mobile-multi-select {
bottom: 50px !important;
top: 0px;
}
.button-upload {
display: block;
text-align: center;
margin: 20px 0;
}
.button-upload {
display: block;
text-align: center;
margin: 20px 0;
}
.mobile-search {
display: none;
margin-bottom: 10px;
margin-top: 10px;
}
.mobile-search {
display: none;
margin-bottom: 10px;
margin-top: 10px;
}
.file-content {
display: flex;
.file-content {
display: flex;
&.is-dragging {
@include transform(scale(0.99));
}
}
&.is-dragging {
@include transform(scale(0.99));
}
}
.files-container {
overflow-x: hidden;
overflow-y: auto;
flex: 0 0 100%;
@include transition(150ms);
position: relative;
scroll-behavior: smooth;
.files-container {
overflow-x: hidden;
overflow-y: auto;
flex: 0 0 100%;
@include transition(150ms);
position: relative;
scroll-behavior: smooth;
&.is-fileinfo-visible {
flex: 0 1 100%;
}
&.is-visible {
flex: 0 1 100%;
}
.file-list {
.file-list {
&.grid {
display: grid;
grid-template-columns: repeat(auto-fill, 180px);
justify-content: space-evenly;
}
}
}
&.grid {
display: grid;
grid-template-columns: repeat(auto-fill, 180px);
justify-content: space-evenly;
}
}
}
.file-info-container {
flex: 0 0 300px;
padding-left: 20px;
overflow: auto;
}
.file-info-container {
flex: 0 0 300px;
padding-left: 20px;
overflow: auto;
}
// Transition
.file-move {
transition: transform 0.6s;
}
// Transition
.file-move {
transition: transform 0.6s;
}
.file-enter-active {
transition: all 300ms ease;
}
.file-enter-active {
transition: all 300ms ease;
}
.file-leave-active {
transition: all 0ms;
}
.file-leave-active {
transition: all 0ms;
}
.file-enter, .file-leave-to /* .list-leave-active below version 2.1.8 */
{
opacity: 0;
transform: translateX(-20px);
}
.file-enter, .file-leave-to /* .list-leave-active below version 2.1.8 */
{
opacity: 0;
transform: translateX(-20px);
}
@media only screen and (min-width: 960px) {
@media only screen and (min-width: 960px) {
.file-content {
position: absolute;
top: 72px;
left: 15px;
right: 15px;
bottom: 0;
@include transition;
overflow-y: auto;
overflow-x: hidden;
.file-content {
position: absolute;
top: 72px;
left: 15px;
right: 15px;
bottom: 0;
@include transition;
overflow-y: auto;
overflow-x: hidden;
&.is-offset {
margin-top: 50px;
}
}
}
&.is-offset {
margin-top: 50px;
}
}
}
@media only screen and (max-width: 960px) {
@media only screen and (max-width: 960px) {
.file-info-container {
display: none;
}
.file-info-container {
display: none;
}
.mobile-search {
display: block;
}
.file-content {
position: absolute;
top: 0px;
left: 15px;
right: 15px;
bottom: 0;
@include transition;
overflow-y: auto;
overflow-x: hidden;
.mobile-search {
display: block;
}
.file-content {
position: absolute;
top: 0px;
left: 15px;
right: 15px;
bottom: 0;
@include transition;
overflow-y: auto;
overflow-x: hidden;
&.is-offset {
margin-top: 50px;
}
}
}
&.is-offset {
margin-top: 50px;
}
}
}
@media only screen and (max-width: 690px) {
@media only screen and (max-width: 690px) {
.files-container {
padding-left: 15px;
padding-right: 15px;
top: 0;
left: 0;
right: 0;
bottom: 0;
position: fixed;
overflow-y: auto;
.files-container {
padding-left: 15px;
padding-right: 15px;
top: 0;
left: 0;
right: 0;
bottom: 0;
position: fixed;
overflow-y: auto;
.file-list {
.file-list {
&.grid {
grid-template-columns: repeat(auto-fill, 120px);
}
}
}
&.grid {
grid-template-columns: repeat(auto-fill, 120px);
}
}
}
.file-content {
position: absolute;
top: 0;
left: 0px;
right: 0px;
bottom: 0;
@include transition;
.file-content {
position: absolute;
top: 0;
left: 0px;
right: 0px;
bottom: 0;
@include transition;
&.is-offset {
margin-top: 50px;
}
}
&.is-offset {
margin-top: 50px;
}
}
.mobile-search {
margin-bottom: 0;
}
.mobile-search {
margin-bottom: 0;
}
.file-info-container {
display: none;
}
}
.file-info-container {
display: none;
}
}
</style>
@@ -0,0 +1,63 @@
<template>
<MenuMobile name="file-filter">
<MenuMobileGroup>
<OptionGroup>
<Option @click.native="goToFiles" :title="$t('menu.files')" icon="hard-drive" :is-active="$isThisLocation('base')" is-hover-disabled="true" />
<Option @click.native="goToLatest" :title="$t('menu.latest')" icon="upload-cloud" :is-active="$isThisLocation('latest')" is-hover-disabled="true" />
<Option @click.native="goToTrash" :title="$t('menu.trash')" icon="trash" :is-active="$isThisLocation(['trash', 'trash-root'])" is-hover-disabled="true" />
</OptionGroup>
<OptionGroup>
<Option @click.native="goToShared" :title="$t('sidebar.my_shared')" icon="share" :is-active="$isThisLocation('shared')" is-hover-disabled="true" />
<Option @click.native="goToParticipantUploads" :title="$t('sidebar.participant_uploads')" icon="users" :is-active="$isThisLocation('participant_uploads')" is-hover-disabled="true" />
</OptionGroup>
</MenuMobileGroup>
</MenuMobile>
</template>
<script>
import MenuMobileGroup from '@/components/Mobile/MenuMobileGroup'
import OptionGroup from '@/components/FilesView/OptionGroup'
import MenuMobile from '@/components/Mobile/MenuMobile'
import Option from '@/components/FilesView/Option'
import {mapGetters} from 'vuex'
export default {
name: 'FileMenuMobile',
components: {
MenuMobileGroup,
OptionGroup,
MenuMobile,
Option,
},
computed: {
...mapGetters([
'homeDirectory'
]),
},
methods: {
flushBrowseHistory() {
this.$store.commit('FLUSH_FOLDER_HISTORY')
},
goToFiles() {
this.$store.dispatch('getFolder', [{folder: this.homeDirectory, back: false, init: true}])
this.flushBrowseHistory()
},
goToLatest() {
this.$store.dispatch('getLatest')
this.flushBrowseHistory()
},
goToTrash() {
this.$store.dispatch('getTrash')
this.flushBrowseHistory()
},
goToShared() {
this.$store.dispatch('getShared', [{back: false, init: false}])
this.flushBrowseHistory()
},
goToParticipantUploads() {
this.$store.dispatch('getParticipantUploads')
this.flushBrowseHistory()
}
}
}
</script>
@@ -1,96 +0,0 @@
<template>
<div
v-if="showFullPreview"
class="file-full-preview-wrapper"
id="fileFullPreview"
ref="filePreview"
tabindex="-1"
@click="closeContextMenu"
@keydown.esc=";(showFullPreview = false), hideContextMenu()"
@keydown.right="next"
@keydown.left="prev"
>
<FilePreviewNavigationPanel />
<MediaFullPreview />
<FilePreviewActions />
</div>
</template>
<script>
import { events } from '@/bus'
import { mapGetters } from 'vuex'
import MediaFullPreview from '@/components/FilesView/MediaFullPreview'
import FilePreviewActions from '@/components/FilesView/FilePreviewActions'
import FilePreviewNavigationPanel from '@/components/FilesView/FilePreviewNavigationPanel'
export default {
name: 'FileFullPreview',
components: {
MediaFullPreview,
FilePreviewNavigationPanel,
FilePreviewActions
},
computed: {
...mapGetters(['fileInfoDetail', 'data'])
},
data() {
return {
showFullPreview: false
}
},
methods: {
closeContextMenu(event) {
if ((event.target.parentElement.id || event.target.id) === 'fast-preview-menu') {
return
} else {
events.$emit('showContextMenuPreview:hide')
}
},
next: function() {
events.$emit('filePreviewAction:next')
},
prev: function() {
events.$emit('filePreviewAction:prev')
},
hideContextMenu() {
events.$emit('showContextMenuPreview:hide')
}
},
updated() {
//Focus file preview for key binding
if (this.showFullPreview) {
this.$refs.filePreview.focus()
}
},
mounted() {
events.$on('fileFullPreview:show', () => {
this.showFullPreview = true
})
events.$on('fileFullPreview:hide', () => {
this.showFullPreview = false
events.$emit('hide:mobile-navigation')
})
}
}
</script>
<style lang="scss" scoped>
@import '@assets/vuefilemanager/_variables';
.file-full-preview-wrapper {
width: 100%;
height: 100%;
position: absolute;
z-index: 7;
background-color: white;
}
@media (prefers-color-scheme: dark) {
.file-full-preview-wrapper {
background-color: $dark_mode_background;
}
}
</style>
@@ -1,218 +0,0 @@
<template>
<div class="file-info-content" v-if="fileInfoDetail.length === 1">
<div class="file-headline" spellcheck="false">
<FilePreview/>
<!--File info-->
<div class="flex">
<div class="icon">
<div class="icon-preview">
<image-icon v-if="fileType === 'image'" size="21"></image-icon>
<video-icon v-if="fileType === 'video'" size="21"></video-icon>
<folder-icon v-if="fileType === 'folder'" size="21"></folder-icon>
<file-icon v-if="fileType === 'file'" size="21"></file-icon>
</div>
</div>
<div class="file-info">
<span ref="name" class="name">{{ fileInfoDetail[0].name }}</span>
<span class="mimetype text-theme" v-if="fileInfoDetail[0].mimetype">.{{ fileInfoDetail[0].mimetype }}</span>
</div>
</div>
</div>
<!--Info list-->
<ListInfo>
<ListInfoItem v-if="fileInfoDetail[0].filesize"
:title="$t('file_detail.size')"
:content="fileInfoDetail[0].filesize">
</ListInfoItem>
<ListInfoItem v-if="$checkPermission(['master']) && fileInfoDetail[0].author !== 'user'"
:title="$t('file_detail.author')"
:content="$t('file_detail.author_participant')">
</ListInfoItem>
<ListInfoItem
:title="$t('file_detail.created_at')"
:content="fileInfoDetail[0].created_at">
</ListInfoItem>
<ListInfoItem v-if="$checkPermission(['master'])"
:title="$t('file_detail.where')">
<div class="action-button" @click="moveItem">
<span>{{ fileInfoDetail[0].parent ? fileInfoDetail[0].parent.name : $t('locations.home') }}</span>
<edit-2-icon size="10" class="edit-icon"></edit-2-icon>
</div>
</ListInfoItem>
<ListInfoItem v-if="$checkPermission('master') && fileInfoDetail[0].shared"
:title="$t('file_detail.shared')">
<div class="action-button" @click="shareItemOptions">
<span>{{ sharedInfo }}</span>
<edit-2-icon size="10" class="edit-icon"></edit-2-icon>
</div>
<div class="sharelink">
<lock-icon v-if="isLocked" @click="shareItemOptions" class="lock-icon" size="17"></lock-icon>
<unlock-icon v-if="! isLocked" @click="shareItemOptions" class="lock-icon" size="17"></unlock-icon>
<CopyInput class="copy-sharelink" size="small" :item="fileInfoDetail[0]"/>
</div>
</ListInfoItem>
<ListInfoItem v-if="canShowMetaData" :title="$t('file_detail_meta.meta_data')">
<ImageMetaData />
</ListInfoItem>
</ListInfo>
</div>
</template>
<script>
import {Edit2Icon, LockIcon, UnlockIcon, ImageIcon, VideoIcon, FolderIcon, FileIcon} from 'vue-feather-icons'
import ImageMetaData from '@/components/FilesView/ImageMetaData'
import FilePreview from '@/components/FilesView/FilePreview'
import CopyInput from '@/components/Others/Forms/CopyInput'
import ListInfoItem from '@/components/Others/ListInfoItem'
import ListInfo from '@/components/Others/ListInfo'
import {mapGetters} from 'vuex'
import {events} from "@/bus"
export default {
name: 'FileInfoPanel',
components: {
ImageMetaData,
ListInfoItem,
ListInfo,
FilePreview,
FolderIcon,
UnlockIcon,
VideoIcon,
CopyInput,
ImageIcon,
FileIcon,
Edit2Icon,
LockIcon,
},
computed: {
...mapGetters(['fileInfoDetail', 'permissionOptions']),
fileType() {
return this.fileInfoDetail[0].type
},
canShowMetaData() {
return this.fileInfoDetail[0].metadata && this.fileInfoDetail[0].metadata.ExifImageWidth
},
sharedInfo() {
// Get permission title
let title = this.permissionOptions.find(option => {
return option.value === this.fileInfoDetail[0].shared.permission
})
return title ? this.$t(title.label) : this.$t('shared.can_download')
},
sharedIcon() {
switch (this.fileInfoDetail[0].shared.permission) {
case 'editor':
return 'user-edit'
break
case 'visitor':
return 'user'
break
default:
return 'download'
}
},
isLocked() {
return this.fileInfoDetail[0].shared.is_protected
}
},
methods: {
shareItemOptions() {
// Open share item popup
events.$emit('popup:open', {name: 'share-edit', item: this.fileInfoDetail[0]})
},
moveItem() {
// Move item fire popup
events.$emit("popup:open", { name: "move", item: this.fileInfoDetail});
}
}
}
</script>
<style scoped lang="scss">
@import '@assets/vuefilemanager/_variables';
@import '@assets/vuefilemanager/_mixins';
.file-info-content {
padding-bottom: 20px;
}
.file-headline {
margin-bottom: 20px;
border-radius: 8px;
.flex {
display: flex;
align-items: flex-start;
}
.icon-preview {
display: inline-flex;
align-items: center;
justify-content: center;
padding: 0;
text-align: center;
cursor: pointer;
white-space: nowrap;
outline: none;
border: none;
}
.file-info {
padding-left: 10px;
width: 100%;
word-break: break-all;
.name {
@include font-size(14);
font-weight: 700;
line-height: 1.4;
display: block;
color: $text;
}
.mimetype {
@include font-size(12);
font-weight: 600;
display: block;
}
}
}
.sharelink {
display: flex;
width: 100%;
align-items: center;
margin-top: 10px;
.lock-icon {
display: inline-block;
width: 15px;
margin-right: 9px;
cursor: pointer;
}
.copy-sharelink {
width: 100%;
}
}
@media (prefers-color-scheme: dark) {
.file-headline {
.file-info {
.name {
color: $dark_mode_text_primary;
}
}
}
}
</style>
@@ -1,6 +1,5 @@
<template>
<div class="file-wrapper" @click.stop="clickedItem" @dblclick="goToItem" spellcheck="false">
<!--Grid preview-->
<div class="file-wrapper" @mouseup.stop="clickedItem" @dblclick="goToItem" spellcheck="false">
<div :draggable="canDrag" @dragstart="$emit('dragstart')" @drop="
drop()
area = false" @dragleave="dragLeave" @dragover.prevent="dragEnter" class="file-item" :class="{'is-clicked' : isClicked , 'no-clicked' : !isClicked && this.$isMobile(), 'is-dragenter': area }">
@@ -8,7 +7,7 @@
<div class="icon-item">
<!-- MultiSelecting for the mobile version -->
<div :class="{'check-select-folder' : this.item.type === 'folder', 'check-select' : this.item.type !== 'folder'}" v-if="multiSelectMode">
<div :class="{'check-select-folder' : this.item.type === 'folder', 'check-select' : this.item.type !== 'folder'}" v-if="mobileMultiSelect">
<div class="select-box" :class="{'select-box-active' : isClicked } ">
<CheckIcon v-if="isClicked" class="icon" size="17" />
</div>
@@ -58,8 +57,8 @@
</div>
</div>
<span @click.stop="showItemActions" class="show-actions" v-if="$isMobile() && ! multiSelectMode && canShowMobileOptions">
<MoreHorizontalIcon icon="ellipsis-h" size="16" class="icon-action text-theme"/>
<span @mousedown.stop="showItemActions" class="show-actions" v-if="$isMobile() && ! mobileMultiSelect && canShowMobileOptions">
<MoreHorizontalIcon icon="ellipsis-h" size="16" class="icon-action text-theme" />
</span>
</div>
</div>
@@ -84,7 +83,7 @@ export default {
},
computed: {
...mapGetters([
'FilePreviewType', 'sharedDetail', 'fileInfoDetail', 'data'
'FilePreviewType', 'sharedDetail', 'clipboard', 'entries'
]),
folderEmojiOrColor() {
@@ -102,7 +101,7 @@ export default {
},
isClicked() {
return this.fileInfoDetail.some(element => element.id === this.item.id)
return this.clipboard.some(element => element.id === this.item.id)
},
isFolder() {
return this.item.type === 'folder'
@@ -149,7 +148,7 @@ export default {
return {
area: false,
itemName: undefined,
multiSelectMode: false
mobileMultiSelect: false
}
},
methods: {
@@ -158,10 +157,10 @@ export default {
},
showItemActions() {
// Load file info detail
this.$store.commit('CLEAR_FILEINFO_DETAIL')
this.$store.commit('GET_FILEINFO_DETAIL', this.item)
this.$store.commit('CLIPBOARD_CLEAR')
this.$store.commit('ADD_ITEM_TO_CLIPBOARD', this.item)
events.$emit('mobileMenu:show')
events.$emit('mobile-menu:show', 'file-menu')
},
dragEnter() {
if (this.item.type !== 'folder') return
@@ -181,77 +180,79 @@ export default {
if (e.ctrlKey || e.metaKey && !e.shiftKey) {
// Click + Ctrl
if (this.fileInfoDetail.some(item => item.id === this.item.id)) {
this.$store.commit('REMOVE_ITEM_FILEINFO_DETAIL', this.item)
if (this.clipboard.some(item => item.id === this.item.id)) {
this.$store.commit('REMOVE_ITEM_FROM_CLIPBOARD', this.item)
} else {
this.$store.commit('GET_FILEINFO_DETAIL', this.item)
this.$store.commit('ADD_ITEM_TO_CLIPBOARD', this.item)
}
} else if (e.shiftKey) {
// Click + Shift
let lastItem = this.data.indexOf(this.fileInfoDetail[this.fileInfoDetail.length - 1])
let clickedItem = this.data.indexOf(this.item)
let lastItem = this.entries.indexOf(this.clipboard[this.clipboard.length - 1])
let clickedItem = this.entries.indexOf(this.item)
// If Click + Shift + Ctrl dont remove already selected items
if (!e.ctrlKey && !e.metaKey) {
this.$store.commit('CLEAR_FILEINFO_DETAIL')
this.$store.commit('CLIPBOARD_CLEAR')
}
//Shift selecting from top to bottom
if (lastItem < clickedItem) {
for (let i = lastItem; i <= clickedItem; i++) {
this.$store.commit('GET_FILEINFO_DETAIL', this.data[i])
this.$store.commit('ADD_ITEM_TO_CLIPBOARD', this.entries[i])
}
//Shift selecting from bottom to top
} else {
for (let i = lastItem; i >= clickedItem; i--) {
this.$store.commit('GET_FILEINFO_DETAIL', this.data[i])
this.$store.commit('ADD_ITEM_TO_CLIPBOARD', this.entries[i])
}
}
} else {
// Click
this.$store.commit('CLEAR_FILEINFO_DETAIL')
this.$store.commit('GET_FILEINFO_DETAIL', this.item)
this.$store.commit('CLIPBOARD_CLEAR')
this.$store.commit('ADD_ITEM_TO_CLIPBOARD', this.item)
}
}
if (!this.multiSelectMode && this.$isMobile()) {
// Open in mobile version on first click
if (this.$isMobile() && this.isFolder) {
// Go to folder
if (!this.mobileMultiSelect && this.$isMobile()) {
if (this.isFolder) {
if (this.$isThisLocation('public')) {
this.$store.dispatch('browseShared', [{folder: this.item, back: false, init: false}])
} else {
this.$store.dispatch('getFolder', [{folder: this.item, back: false, init: false}])
}
}
} else {
if (this.$isMobile()) {
if (this.isImage || this.isVideo || this.isAudio) {
this.$store.commit('GET_FILEINFO_DETAIL', this.item)
events.$emit('fileFullPreview:show')
if (this.isImage || this.isVideo || this.isAudio || this.isPdf) {
this.$store.commit('CLIPBOARD_CLEAR')
this.$store.commit('ADD_ITEM_TO_CLIPBOARD', this.item)
events.$emit('file-preview:show')
}
}
}
if (this.multiSelectMode && this.$isMobile()) {
if (this.fileInfoDetail.some(item => item.id === this.item.id)) {
this.$store.commit('REMOVE_ITEM_FILEINFO_DETAIL', this.item)
if (this.mobileMultiSelect && this.$isMobile()) {
if (this.clipboard.some(item => item.id === this.item.id)) {
this.$store.commit('REMOVE_ITEM_FROM_CLIPBOARD', this.item)
} else {
this.$store.commit('GET_FILEINFO_DETAIL', this.item)
this.$store.commit('ADD_ITEM_TO_CLIPBOARD', this.item)
}
}
},
goToItem() {
if (this.isImage || this.isVideo || this.isAudio) {
events.$emit('fileFullPreview:show')
if (this.isImage || this.isVideo || this.isAudio || this.isPdf) {
events.$emit('file-preview:show')
} else if (this.isFile || !this.isFolder && !this.isPdf && !this.isVideo && !this.isAudio && !this.isImage) {
} else if (this.isFile || !this.isFolder && !this.isVideo && !this.isAudio && !this.isImage) {
this.$downloadFile(this.item.file_url, this.item.name + '.' + this.item.mimetype)
} else if (this.isFolder) {
//Clear selected data after open another folder
this.$store.commit('CLEAR_FILEINFO_DETAIL')
this.$store.commit('CLIPBOARD_CLEAR')
if (this.$isThisLocation('public')) {
this.$store.dispatch('browseShared', [{folder: this.item, back: false, init: false}])
@@ -284,13 +285,13 @@ export default {
})
events.$on('mobileSelecting:start', () => {
this.multiSelectMode = true
this.$store.commit('CLEAR_FILEINFO_DETAIL')
this.mobileMultiSelect = true
this.$store.commit('CLIPBOARD_CLEAR')
})
events.$on('mobileSelecting:stop', () => {
this.multiSelectMode = false
this.$store.commit('CLEAR_FILEINFO_DETAIL')
this.mobileMultiSelect = false
this.$store.commit('CLIPBOARD_CLEAR')
})
// Change item name
events.$on('change:name', (item) => {
@@ -1,6 +1,5 @@
<template>
<div class="file-wrapper" @click.stop="clickedItem" @dblclick="goToItem" spellcheck="false">
<!--List preview-->
<div class="file-wrapper" @mouseup.stop="clickedItem" @dblclick="goToItem" spellcheck="false">
<div
:draggable="canDrag"
@dragstart="$emit('dragstart')"
@@ -13,7 +12,7 @@
<transition name="slide-from-left">
<div class="check-select" v-if="mobileMultiSelect">
<div class="select-box" :class="{'select-box-active' : isClicked } ">
<CheckIcon v-if="isClicked" class="icon" size="17"/>
<CheckIcon v-if="isClicked" class="icon" size="17" />
</div>
</div>
</transition>
@@ -26,12 +25,12 @@
</span>
<!--Folder thumbnail-->
<FontAwesomeIcon v-if="isFile || (isImage && !item.thumbnail)" class="file-icon" icon="file"/>
<FontAwesomeIcon v-if="isFile || (isImage && !item.thumbnail)" class="file-icon" icon="file" />
<!--Image thumbnail-->
<img loading="lazy" v-if="isImage && item.thumbnail" class="image" :src="item.thumbnail" :alt="item.name"/>
<img loading="lazy" v-if="isImage && item.thumbnail" class="image" :src="item.thumbnail" :alt="item.name" />
<!--Else show only folder icon-->
<!--Else show only folder icon-->
<FolderIcon v-if="isFolder" :item="item" location="file-item-list" class="folder svg-color-theme" />
</div>
@@ -63,7 +62,7 @@
<!--Show item actions-->
<transition name="slide-from-right">
<div class="actions" v-if="$isMobile() && ! mobileMultiSelect">
<span @click.stop="showItemActions" class="show-actions">
<span @mousedown.stop="showItemActions" class="show-actions">
<MoreVerticalIcon size="16" class="icon-action text-theme" />
</span>
</div>
@@ -73,26 +72,32 @@
</template>
<script>
import { LinkIcon, UserPlusIcon, CheckIcon, MoreVerticalIcon } from 'vue-feather-icons'
import {LinkIcon, UserPlusIcon, CheckIcon, MoreVerticalIcon} from 'vue-feather-icons'
import FolderIcon from '@/components/FilesView/FolderIcon'
import { debounce } from 'lodash'
import { mapGetters } from 'vuex'
import { events } from '@/bus'
import {debounce} from 'lodash'
import {mapGetters} from 'vuex'
import {events} from '@/bus'
export default {
name: 'FileItemList',
props: ['item'],
props: [
'item'
],
components: {
MoreVerticalIcon,
UserPlusIcon,
LinkIcon,
FolderIcon,
CheckIcon,
LinkIcon,
},
computed: {
...mapGetters(['FilePreviewType', 'fileInfoDetail', 'data']),
...mapGetters([
'FilePreviewType',
'clipboard',
'entries'
]),
isClicked() {
return this.fileInfoDetail.some(element => element.id === this.item.id)
return this.clipboard.some(element => element.id === this.item.id)
},
isFolder() {
return this.item.type === 'folder'
@@ -120,7 +125,7 @@ export default {
return !this.isDeleted && this.$checkPermission(['master', 'editor'])
},
timeStamp() {
return this.item.deleted_at ? this.$t('item_thumbnail.deleted_at', { time: this.item.deleted_at }) : this.item.created_at
return this.item.deleted_at ? this.$t('item_thumbnail.deleted_at', {time: this.item.deleted_at}) : this.item.created_at
},
folderItems() {
return this.item.deleted_at ? this.item.trashed_items : this.item.items
@@ -151,11 +156,10 @@ export default {
events.$emit('drop')
},
showItemActions() {
// Load file info detail
this.$store.commit('CLEAR_FILEINFO_DETAIL')
this.$store.commit('GET_FILEINFO_DETAIL', this.item)
this.$store.commit('CLIPBOARD_CLEAR')
this.$store.commit('ADD_ITEM_TO_CLIPBOARD', this.item)
events.$emit('mobileMenu:show')
events.$emit('mobile-menu:show', 'file-menu')
},
dragEnter() {
if (this.item.type !== 'folder') return
@@ -174,89 +178,92 @@ export default {
document.getSelection().removeAllRanges();
if ((e.ctrlKey || e.metaKey) && !e.shiftKey) {
// Click + Ctrl
if (this.fileInfoDetail.some(item => item.id === this.item.id)) {
this.$store.commit('REMOVE_ITEM_FILEINFO_DETAIL', this.item)
// Click + Ctrl
if (this.clipboard.some(item => item.id === this.item.id)) {
this.$store.commit('REMOVE_ITEM_FROM_CLIPBOARD', this.item)
} else {
this.$store.commit('GET_FILEINFO_DETAIL', this.item)
this.$store.commit('ADD_ITEM_TO_CLIPBOARD', this.item)
}
} else if (e.shiftKey) {
// Click + Shift
let lastItem = this.data.indexOf(this.fileInfoDetail[this.fileInfoDetail.length - 1])
let clickedItem = this.data.indexOf(this.item)
// Click + Shift
let lastItem = this.entries.indexOf(this.clipboard[this.clipboard.length - 1])
let clickedItem = this.entries.indexOf(this.item)
// If Click + Shift + Ctrl dont remove already selected items
if (!e.ctrlKey && !e.metaKey) {
this.$store.commit('CLEAR_FILEINFO_DETAIL')
this.$store.commit('CLIPBOARD_CLEAR')
}
//Shift selecting from top to bottom
if (lastItem < clickedItem) {
for (let i = lastItem; i <= clickedItem; i++) {
this.$store.commit('GET_FILEINFO_DETAIL', this.data[i])
this.$store.commit('ADD_ITEM_TO_CLIPBOARD', this.entries[i])
}
//Shift selecting from bottom to top
} else {
for (let i = lastItem; i >= clickedItem; i--) {
this.$store.commit('GET_FILEINFO_DETAIL', this.data[i])
this.$store.commit('ADD_ITEM_TO_CLIPBOARD', this.entries[i])
}
}
} else {
// Click
this.$store.commit('CLEAR_FILEINFO_DETAIL')
this.$store.commit('GET_FILEINFO_DETAIL', this.item)
// Click
this.$store.commit('CLIPBOARD_CLEAR')
this.$store.commit('ADD_ITEM_TO_CLIPBOARD', this.item)
}
}
if (!this.mobileMultiSelect && this.$isMobile()) {
// Open in mobile version on first click
if (this.$isMobile() && this.isFolder) {
// Go to folder
if (this.$isThisLocation('public')) {
this.$store.dispatch('browseShared', [{ folder: this.item, back: false, init: false }])
} else {
this.$store.dispatch('getFolder', [{ folder: this.item, back: false, init: false }])
}
}
if (this.isFolder) {
if (this.$isMobile()) {
if (this.isImage || this.isVideo || this.isAudio) {
this.$store.commit('GET_FILEINFO_DETAIL', this.item)
events.$emit('fileFullPreview:show')
if (this.$isThisLocation('public')) {
this.$store.dispatch('browseShared', [{folder: this.item, back: false, init: false}])
} else {
this.$store.dispatch('getFolder', [{folder: this.item, back: false, init: false}])
}
} else {
if (this.isImage || this.isVideo || this.isAudio || this.isPdf) {
this.$store.commit('CLIPBOARD_CLEAR')
this.$store.commit('ADD_ITEM_TO_CLIPBOARD', this.item)
events.$emit('file-preview:show')
}
}
}
if (this.mobileMultiSelect && this.$isMobile()) {
if (this.fileInfoDetail.some(item => item.id === this.item.id)) {
this.$store.commit('REMOVE_ITEM_FILEINFO_DETAIL', this.item)
if (this.clipboard.some(item => item.id === this.item.id)) {
this.$store.commit('REMOVE_ITEM_FROM_CLIPBOARD', this.item)
} else {
this.$store.commit('GET_FILEINFO_DETAIL', this.item)
this.$store.commit('ADD_ITEM_TO_CLIPBOARD', this.item)
}
}
},
goToItem() {
if (this.isImage || this.isVideo || this.isAudio) {
events.$emit('fileFullPreview:show')
if (this.isImage || this.isVideo || this.isAudio || this.isPdf) {
events.$emit('file-preview:show')
} else if (this.isFile || !this.isFolder && !this.isPdf && !this.isVideo && !this.isAudio && !this.isImage) {
} else if (this.isFile || !this.isFolder && !this.isVideo && !this.isAudio && !this.isImage) {
this.$downloadFile(this.item.file_url, this.item.name + '.' + this.item.mimetype)
} else if (this.isFolder) {
// Clear selected items after open another folder
this.$store.commit('CLEAR_FILEINFO_DETAIL')
this.$store.commit('CLIPBOARD_CLEAR')
if (this.$isThisLocation('public')) {
this.$store.dispatch('browseShared', [{ folder: this.item, back: false, init: false }])
this.$store.dispatch('browseShared', [{folder: this.item, back: false, init: false}])
} else {
this.$store.dispatch('getFolder', [{ folder: this.item, back: false, init: false }])
this.$store.dispatch('getFolder', [{folder: this.item, back: false, init: false}])
}
}
},
renameItem: debounce(function(e) {
renameItem: debounce(function (e) {
// Prevent submit empty string
if (e.target.innerText.trim() === '') return
@@ -273,7 +280,7 @@ export default {
events.$on('newFolder:focus', (id) => {
if(this.item.id === id && !this.$isMobile()) {
if (this.item.id === id && !this.$isMobile()) {
this.$refs[id].focus()
document.execCommand('selectAll')
}
@@ -281,12 +288,12 @@ export default {
events.$on('mobileSelecting:start', () => {
this.mobileMultiSelect = true
this.$store.commit('CLEAR_FILEINFO_DETAIL')
this.$store.commit('CLIPBOARD_CLEAR')
})
events.$on('mobileSelecting:stop', () => {
this.mobileMultiSelect = false
this.$store.commit('CLEAR_FILEINFO_DETAIL')
this.$store.commit('CLIPBOARD_CLEAR')
})
// Change item name
@@ -454,7 +461,7 @@ export default {
flex: 0 0 50px;
line-height: 0;
margin-right: 20px;
.folder {
width: 52px;
height: 52px;
@@ -0,0 +1,179 @@
<template>
<MenuMobile name="file-menu">
<ThumbnailItem class="item-thumbnail" :item="clipboard[0]" info="metadata" />
<!--Trash location-->
<MenuMobileGroup v-if="$isThisLocation(['trash', 'trash-root']) && $checkPermission('master')">
<OptionGroup v-if="clipboard[0]">
<Option @click.native="restoreItem" :title="$t('context_menu.restore')" icon="restore" />
<Option @click.native="deleteItem" :title="$t('context_menu.delete')" icon="delete" />
</OptionGroup>
<OptionGroup>
<Option v-if="!isFolder" @click.native="downloadItem" :title="$t('context_menu.download')" icon="download" />
<Option v-if="isFolder" @click.native="downloadFolder" :title="$t('context_menu.zip_folder')" icon="zip-folder" />
</OptionGroup>
</MenuMobileGroup>
<!--Shared location-->
<MenuMobileGroup v-if="$isThisLocation(['shared']) && $checkPermission('master')">
<OptionGroup v-if="clipboard[0] && isFolder">
<Option @click.native="addToFavourites" :title="favouritesTitle" icon="star" />
</OptionGroup>
<OptionGroup v-if="clipboard[0]">
<Option @click.native="renameItem" :title="$t('context_menu.rename')" icon="rename" />
<Option @click.native="shareItem" :title="clipboard[0].shared ? $t('context_menu.share_edit') : $t('context_menu.share')" icon="share" />
<Option @click.native="deleteItem" :title="$t('context_menu.delete')" icon="trash" />
</OptionGroup>
<OptionGroup>
<Option v-if="!isFolder" @click.native="downloadItem" :title="$t('context_menu.download')" icon="download" />
<Option v-if="isFolder" @click.native="downloadFolder" :title="$t('context_menu.zip_folder')" icon="zip-folder" />
</OptionGroup>
</MenuMobileGroup>
<!--Base location for user-->
<MenuMobileGroup v-if="$isThisLocation(['base', 'participant_uploads', 'latest']) && $checkPermission('master')">
<OptionGroup v-if="clipboard[0] && isFolder">
<Option @click.native="addToFavourites" :title="favouritesTitle" icon="star" />
</OptionGroup>
<OptionGroup v-if="clipboard[0]">
<Option @click.native="renameItem" :title="$t('context_menu.rename')" icon="rename" />
<Option @click.native="moveItem" :title="$t('context_menu.move')" icon="move-item" />
<Option @click.native="shareItem" :title="clipboard[0].shared ? $t('context_menu.share_edit') : $t('context_menu.share')" icon="share" />
<Option @click.native="deleteItem" :title="$t('context_menu.delete')" icon="trash" />
</OptionGroup>
<OptionGroup>
<Option v-if="!isFolder" @click.native="downloadItem" :title="$t('context_menu.download')" icon="download" />
<Option v-if="isFolder" @click.native="downloadFolder" :title="$t('context_menu.zip_folder')" icon="zip-folder" />
</OptionGroup>
</MenuMobileGroup>
<!--Base location for guest-->
<MenuMobileGroup v-if="$isThisLocation(['base', 'public']) && $checkPermission('editor')">
<OptionGroup>
<Option v-if="clipboard[0]" @click.native="renameItem" :title="$t('context_menu.rename')" icon="rename" />
<Option v-if="clipboard[0]" @click.native="moveItem" :title="$t('context_menu.move')" icon="move-item" />
<Option @click.native="deleteItem" :title="$t('context_menu.delete')" icon="trash" />
</OptionGroup>
<OptionGroup>
<Option v-if="!isFolder" @click.native="downloadItem" :title="$t('context_menu.download')" icon="download" />
<Option v-if="isFolder" @click.native="downloadFolder" :title="$t('context_menu.zip_folder')" icon="zip-folder" />
</OptionGroup>
</MenuMobileGroup>
<!--Base location for guest with visit permission-->
<MenuMobileGroup v-if="$isThisLocation(['base', 'public']) && $checkPermission('visitor')">
<OptionGroup>
<Option v-if="!isFolder" @click.native="downloadItem" :title="$t('context_menu.download')" icon="download" />
<Option v-if="isFolder" @click.native="downloadFolder" :title="$t('context_menu.zip_folder')" icon="zip-folder" />
</OptionGroup>
</MenuMobileGroup>
</MenuMobile>
</template>
<script>
import MenuMobileGroup from '@/components/Mobile/MenuMobileGroup'
import MenuMobile from '@/components/Mobile/MenuMobile'
import ThumbnailItem from '@/components/Others/ThumbnailItem'
import OptionGroup from '@/components/FilesView/OptionGroup'
import Option from '@/components/FilesView/Option'
import {mapGetters} from 'vuex'
import {events} from '@/bus'
export default {
name: 'FileMenuMobile',
components: {
MenuMobileGroup,
MenuMobile,
ThumbnailItem,
OptionGroup,
Option,
},
computed: {
...mapGetters([
'clipboard',
'user',
]),
favourites() {
return this.user.data.relationships.favourites.data.attributes.folders
},
favouritesTitle() {
return this.isInFavourites
? this.$t('context_menu.remove_from_favourites')
: this.$t('context_menu.add_to_favourites')
},
isInFavourites() {
return this.favourites.find(el => el.id === this.clipboard[0].id)
},
isFile() {
return !this.isImage && !this.isFolder
},
isImage() {
return this.clipboard[0] && this.clipboard[0].type === 'image'
},
isFolder() {
return this.clipboard[0] && this.clipboard[0].type === 'folder'
}
},
data() {
return {
isVisible: false,
}
},
methods: {
downloadFolder() {
this.$store.dispatch('downloadFolder', this.clipboard[0])
},
moveItem() {
events.$emit('popup:open', {name: 'move', item: [this.clipboard[0]]})
},
shareItem() {
let event = this.clipboard[0].shared
? 'share-edit'
: 'share-create'
events.$emit('popup:open', {
name: event,
item: this.clipboard[0]
})
},
addToFavourites() {
if (this.favourites && !this.favourites.find(el => el.id === this.clipboard[0].id)) {
this.$store.dispatch('addToFavourites', this.clipboard[0])
} else {
this.$store.dispatch('removeFromFavourites', this.clipboard[0])
}
},
downloadItem() {
this.$downloadFile(
this.clipboard[0].file_url,
this.clipboard[0].name + '.' + this.clipboard[0].mimetype
)
},
deleteItem() {
this.$store.dispatch('deleteItem')
},
restoreItem() {
this.$store.dispatch('restoreItem', this.clipboard[0])
},
renameItem() {
events.$emit('popup:open', {name: 'rename-item', item: this.clipboard[0]})
},
}
}
</script>
<style scoped lang="scss">
@import "@assets/vuefilemanager/_variables";
@import "@assets/vuefilemanager/_mixins";
.item-thumbnail {
padding: 20px 20px 10px;
margin-bottom: 0;
}
</style>
@@ -1,61 +1,77 @@
<template>
<div v-if="canBePreview" class="preview">
<img v-if="fileInfoDetail[0].type == 'image' && fileInfoDetail[0].thumbnail" :src="fileInfoDetail[0].thumbnail" :alt="fileInfoDetail[0].name" />
<audio v-else-if="fileInfoDetail[0].type == 'audio'" :src="fileInfoDetail[0].file_url" controlsList="nodownload" controls></audio>
<video v-else-if="fileInfoDetail[0].type == 'video'" controlsList="nodownload" disablePictureInPicture playsinline controls>
<source :src="fileInfoDetail[0].file_url" type="video/mp4">
</video>
</div>
<div
v-if="isFullPreview"
class="file-preview"
ref="filePreview"
tabindex="-1"
@keydown.esc="closeFilePreview"
@keydown.right="next"
@keydown.left="prev"
>
<FilePreviewToolbar />
<FilePreviewMedia />
</div>
</template>
<script>
import { mapGetters } from 'vuex'
import { includes } from 'lodash'
import FilePreviewToolbar from '@/components/FilesView/FilePreviewToolbar'
import FilePreviewMedia from '@/components/FilesView/FilePreviewMedia'
import {events} from '@/bus'
export default {
name: 'FilePreview',
computed: {
...mapGetters(['fileInfoDetail']),
canBePreview() {
return this.fileInfoDetail[0] && ! includes([
'folder', 'file'
], this.fileInfoDetail[0].type)
components: {
FilePreviewToolbar,
FilePreviewMedia,
},
data() {
return {
isFullPreview: false
}
},
methods: {
closeFilePreview() {
this.isFullPreview = false
events.$emit('showContextMenuPreview:hide')
},
next() {
events.$emit('file-preview:next')
},
prev() {
events.$emit('file-preview:prev')
}
},
updated() {
if (this.isFullPreview) {
this.$refs.filePreview.focus()
}
},
mounted() {
events.$on('file-preview:show', () => {
this.isFullPreview = true
})
events.$on('file-preview:hide', () => {
this.isFullPreview = false
})
}
}
</script>
<style scoped lang="scss">
<style lang="scss" scoped>
@import '@assets/vuefilemanager/_variables';
@import '@assets/vuefilemanager/_mixins';
.preview {
.file-preview {
width: 100%;
display: block;
margin-bottom: 7px;
height: 100%;
position: absolute;
z-index: 7;
background-color: white;
}
img {
border-radius: 4px;
overflow: hidden;
width: 100%;
object-fit: cover;
}
audio {
width: 100%;
&::-webkit-media-controls-panel {
background-color: $light_background;
}
&::-webkit-media-controls-play-button {
color: $theme;
}
}
video {
width: 100%;
height: auto;
border-radius: 3px;
@media (prefers-color-scheme: dark) {
.file-preview {
background-color: $dark_mode_background;
}
}
</style>
@@ -1,78 +0,0 @@
<template>
<div v-if="filteredFiles.length > 1">
<div @click.prevent="prev" class="prev">
<chevron-left-icon size="17"></chevron-left-icon>
</div>
<div @click.prevent="next" class="next">
<chevron-right-icon size="17"></chevron-right-icon>
</div>
</div>
</template>
<script>
import { events } from '@/bus'
import { mapGetters } from 'vuex'
import { ChevronLeftIcon, ChevronRightIcon } from 'vue-feather-icons'
export default {
name: 'FilePreviewActions',
components: {
ChevronLeftIcon,
ChevronRightIcon
},
computed: {
...mapGetters(['fileInfoDetail', 'data']),
filteredFiles() {
let filteredData = []
this.data.filter((element) => {
if (element.type == this.fileInfoDetail[0].type) {
filteredData.push(element)
}
})
return filteredData
}
},
methods: {
next: function() {
events.$emit('filePreviewAction:next')
},
prev: function() {
events.$emit('filePreviewAction:prev')
}
}
}
</script>
<style lang="scss" scoped>
@import '@assets/vuefilemanager/_variables';
.prev,
.next {
cursor: pointer;
position: absolute;
top: 53.5%;
display: flex;
justify-content: center;
color: $text;
border-radius: 50%;
text-decoration: none;
user-select: none;
filter: drop-shadow(0px 1px 0 rgba(255, 255, 255, 1));
padding: 10px;
}
.next {
right: 0;
}
.prev {
left: 0;
}
@media (prefers-color-scheme: dark) {
.prev,
.next {
color: $light-text;
filter: drop-shadow(0px 1px 0 rgba(17, 19, 20, 1));
}
}
</style>
@@ -0,0 +1,368 @@
<template>
<div class="media-full-preview" id="mediaPreview" v-if="clipboard[0]">
<!--Arrow navigation-->
<div v-if="files.length > 1" class="navigation-arrows">
<div @click.prevent="prev" class="prev">
<chevron-left-icon size="17" />
</div>
<div @click.prevent="next" class="next">
<chevron-right-icon size="17" />
</div>
</div>
<!--File preview-->
<div class="file-wrapper-preview">
<!--Show PDF-->
<div v-if="isPDF" id="pdf-wrapper" :style="{width: documentSize + '%'}">
<pdf :src="pdfdata" v-for="i in numPages" :key="i" :resize="true" :page="i" scale="page-width" style="width:100%; margin:20px auto;" id="printable-file">
<template slot="loading">
<h1>loading content...</h1>
</template>
</pdf>
</div>
<!--Show Audio, Video and Image-->
<div v-if="!isPDF" class="file-wrapper">
<audio
v-if="isAudio"
:class="{ 'file-shadow': !$isMobile() }"
class="file audio"
:src="currentFile.file_url"
controls>
</audio>
<img
id="printable-file"
v-if="isImage"
class="file"
:class="{'file-shadow': !$isMobile() }"
:src="currentFile.file_url"
/>
<div class="video-wrapper" v-if="isVideo">
<video
:src="currentFile.file_url"
class="video"
:class="{'file-shadow': !$isMobile() }"
controlsList="nodownload"
disablePictureInPicture
playsinline
controls
autoplay
/>
</div>
</div>
</div>
</div>
</template>
<script>
import {ChevronLeftIcon, ChevronRightIcon} from 'vue-feather-icons'
import ToolbarButton from '@/components/FilesView/ToolbarButton'
import Spinner from '@/components/FilesView/Spinner'
import {mapGetters} from 'vuex'
import {events} from '@/bus'
import pdf from 'pdfvuer'
export default {
name: 'MediaFullPreview',
components: {
ChevronRightIcon,
ChevronLeftIcon,
ToolbarButton,
Spinner,
pdf,
},
computed: {
...mapGetters([
'clipboard',
'entries',
]),
currentFile() {
return this.files[Math.abs(this.currentIndex) % this.files.length]
},
isPDF() {
return this.clipboard[0].mimetype === 'pdf'
},
isVideo() {
return this.clipboard[0].type === 'video'
},
isAudio() {
return this.clipboard[0].type === 'audio'
},
isImage() {
return this.clipboard[0].type === 'image'
}
},
data() {
return {
pdfdata: undefined,
numPages: 0,
currentIndex: 0,
files: [],
documentSize: 50,
}
},
watch: {
files() {
if (this.files.length === 0)
events.$emit('file-preview:hide')
},
currentFile() {
if (this.clipboard[0]) {
this.$store.commit('CLIPBOARD_CLEAR')
this.$store.commit('ADD_ITEM_TO_CLIPBOARD', this.currentFile)
// Init pdf instance
if (this.clipboard[0].mimetype === 'pdf') {
this.getPdf()
}
}
},
clipboard() {
if (!this.clipboard[0]) {
this.currentIndex -= 1
this.$store.commit('ADD_ITEM_TO_CLIPBOARD', this.currentFile)
this.files = []
}
},
data(newValue, oldValue) {
if (newValue !== oldValue) {
this.files = []
}
},
},
methods: {
next() {
if (!this.files.length > 1) return
this.pdfdata = undefined
this.currentIndex += 1
if (this.currentIndex > this.files.length - 1) {
this.currentIndex = 0
}
},
prev() {
if (!this.files.length > 1) return
this.pdfdata = undefined
this.currentIndex -= 1
if (this.currentIndex < 0) {
this.currentIndex = this.files.length - 1
}
},
getPdf() {
this.pdfdata = undefined
this.numPages = 0
let self = this;
self.pdfdata = pdf.createLoadingTask(this.currentFile.file_url);
self.pdfdata.then(pdf => self.numPages = pdf.numPages);
},
getFilesForView() {
let requestedFile = this.clipboard[0]
this.entries.map(element => {
if (requestedFile.mimetype === 'pdf') {
if (element.mimetype === 'pdf')
this.files.push(element)
} else {
if (element.type === requestedFile.type)
this.files.push(element)
}
})
this.files.forEach((element, index) => {
if (element.id === this.clipboard[0].id) {
this.currentIndex = index
}
})
},
},
created() {
// Set zoom size
this.documentSize = window.innerWidth < 960 ? 100 : 50
events.$on('file-preview:next', () => this.next())
events.$on('file-preview:prev', () => this.prev())
events.$on('document-zoom:in', () => {
if (this.documentSize < 100)
this.documentSize += 10
})
events.$on('document-zoom:out', () => {
if (this.documentSize > 40)
this.documentSize -= 10
})
this.getFilesForView()
}
}
</script>
<style src="pdfvuer/dist/pdfvuer.css" lang="css"></style>
<style lang="scss">
@import '@assets/vuefilemanager/_variables';
@import '@assets/vuefilemanager/_mixins';
.navigation-arrows {
.prev, .next {
cursor: pointer;
position: absolute;
top: 45%;
display: flex;
justify-content: center;
color: $text;
border-radius: 50%;
text-decoration: none;
user-select: none;
filter: drop-shadow(0px 1px 0 rgba(255, 255, 255, 1));
padding: 10px;
z-index: 2;
}
.next {
right: 0;
}
.prev {
left: 0;
}
}
#pdf-wrapper {
overflow-y: scroll;
margin: 0 auto;
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
z-index: 1;
}
.media-full-preview {
height: calc(100% - 72px);
top: 72px;
position: relative;
background-color: white;
}
.file-wrapper-preview {
width: 100%;
height: 100%;
padding: 30px 0px;
display: flex;
overflow: hidden;
justify-content: center;
align-items: center;
background-color: white;
.file-wrapper {
width: 100%;
height: 100%;
display: flex;
justify-content: center;
align-items: center;
.file-shadow {
box-shadow: 0 8px 40px rgba(17, 26, 52, 0.05);
}
.file {
max-width: 100%;
max-height: 100%;
align-self: center;
}
.audio {
border-radius: 28px;
}
img {
border-radius: 4px;
}
.video-wrapper {
max-width: 1080px;
max-height: 100%;
@media (min-width: 1200px) {
& {
max-width: 800px;
}
}
@media (min-width: 1920px) and (max-width: 2560px) {
& {
max-width: 1080px;
}
}
@media (min-width: 2560px) and (max-width: 3840px) {
& {
max-width: 1440px;
}
}
@media (min-width: 3840px) {
& {
max-width: 2160px;
}
}
.video {
max-width: 100%;
max-height: 100%;
align-self: center;
}
}
}
}
@media only screen and (max-width: 960px) {
.media-full-preview {
top: 53px;
}
}
@media (prefers-color-scheme: dark) {
.navigation-arrows {
.prev, .next {
color: $light-text;
filter: drop-shadow(0px 1px 0 rgba(17, 19, 20, 1));
}
}
.file-wrapper-preview {
background-color: $dark_mode_background;
.file-wrapper {
.file-shadow {
box-shadow: 0 8px 40px rgba(0, 0, 0, 0.1);
}
}
}
}
</style>
@@ -1,319 +0,0 @@
<template>
<div class="navigation-panel" v-if="fileInfoDetail[0]">
<div class="name-wrapper">
<x-icon @click="closeFullPreview" size="22" class="icon-close hover-text-theme" />
<div class="name-count-wrapper">
<p class="title">{{ fileInfoDetail[0].name }}</p>
<span class="file-count"> ({{ showingImageIndex + ' ' + $t('pronouns.of') + ' ' + filteredFiles.length }}) </span>
</div>
<span id="fast-preview-menu" class="fast-menu-icon group" @click.stop="menuOpen" v-if="$checkPermission(['master', 'editor', 'visitor'])">
<more-horizontal-icon class="more-icon group-hover-text-theme" size="14" />
</span>
</div>
<div class="created-at-wrapper">
<p>{{ fileInfoDetail[0].filesize }}, {{ fileInfoDetail[0].created_at }}</p>
</div>
<div class="navigation-icons">
<div class="navigation-tool-wrapper">
<ToolbarButton source="download" class="mobile-hide" @click.native="downloadItem" :action="$t('actions.download')" />
<ToolbarButton v-if="canShowShareView" :class="{ 'is-inactive': canShareInView }" @click.native="shareItem" source="share" class="mobile-hide" :action="$t('actions.share')" />
<ToolbarButton v-if="this.fileInfoDetail[0].type === 'image'" source="print" :action="$t('actions.print')" @click.native="printMethod()" />
</div>
</div>
</div>
</template>
<script>
import { events } from '@/bus'
import { mapGetters } from 'vuex'
import { XIcon, MoreHorizontalIcon } from 'vue-feather-icons'
import ToolbarButton from '@/components/FilesView/ToolbarButton'
export default {
name: 'FilePreviewNavigationPanel',
components: { ToolbarButton, XIcon, MoreHorizontalIcon },
computed: {
...mapGetters(['fileInfoDetail', 'data']),
filteredFiles() {
let files = []
this.data.filter((element) => {
if (element.type == this.fileInfoDetail[0].type) {
files.push(element)
}
})
return files
},
showingImageIndex() {
let activeIndex = ''
this.filteredFiles.filter((element, index) => {
if (element.id === this.fileInfoDetail[0].id) {
activeIndex = index + 1
}
})
return activeIndex
},
canShowShareView() {
return this.$isThisLocation(['base', 'participant_uploads', 'latest', 'shared'])
},
canShareInView() {
return ! this.$isThisLocation(['base', 'participant_uploads', 'latest', 'shared'])
}
},
data() {
return {
showContextMenu: false
}
},
methods: {
printMethod() {
var tab = document.getElementById('image')
var win = window.open('', '', 'height=700,width=700')
win.document.write(tab.outerHTML)
win.document.close()
win.print()
},
downloadItem() {
// Download file
this.$downloadFile(this.fileInfoDetail[0].file_url, this.fileInfoDetail[0].name + '.' + this.fileInfoDetail[0].mimetype)
},
shareItem() {
if (this.fileInfoDetail[0].shared) {
events.$emit('popup:open', {
name: 'share-edit',
item: this.fileInfoDetail[0]
})
} else {
events.$emit('popup:open', {
name: 'share-create',
item: this.fileInfoDetail[0]
})
}
},
menuOpen() {
if (this.$isMobile()) {
events.$emit('mobileMenu:show', 'showFromMediaPreview')
} else {
events.$emit('showContextMenuPreview:show', this.fileInfoDetail[0])
}
},
closeFullPreview() {
events.$emit('fileFullPreview:hide')
events.$emit('showContextMenuPreview:hide')
}
}
}
</script>
<style lang="scss" scoped>
@import '@assets/vuefilemanager/_variables';
@import '@assets/vuefilemanager/_mixins';
.name-wrapper {
width: 33%;
height: 22px;
display: flex;
position: relative;
align-items: center;
flex-grow: 1;
align-self: center;
white-space: nowrap;
.name-count-wrapper {
margin-left: 6px;
margin-right: 6px;
.file-count {
@include font-size(15);
line-height: 1;
font-weight: 700;
overflow: hidden;
text-overflow: ellipsis;
display: inline-block;
vertical-align: middle;
align-self: center;
color: $text;
}
.title {
@include font-size(15);
max-width: 250px;
line-height: 1;
font-weight: 700;
overflow: hidden;
text-overflow: ellipsis;
display: inline-block;
vertical-align: middle;
color: $text;
}
@media (max-width: 570px) {
.title{
max-width: 180px;
@include font-size(17);
}
.file-count {
@include font-size(17);
}
}
}
.icon-close {
min-width: 22px;
padding: 1px 4px;
border-radius: 6px;
vertical-align: middle;
cursor: pointer;
@include transition(150ms);
&:hover {
background: $light_background;
line {
color: inherit;
}
}
}
.fast-menu-icon {
height: 24px;
display: flex;
align-items: center;
vertical-align: middle;
padding: 1px 4px;
line-height: 0;
border-radius: 3px;
cursor: pointer;
@include transition(150ms);
svg circle {
@include transition(150ms);
}
&:hover {
background: $light_background;
svg circle {
color: inherit;
}
}
.more-icon {
vertical-align: middle;
cursor: pointer;
}
}
}
.context-menu {
min-width: 250px;
position: absolute;
z-index: 99;
box-shadow: $shadow;
background: white;
border-radius: 8px;
overflow: hidden;
top: 29px;
&.showed {
display: block;
}
}
.created-at-wrapper {
width: 33%;
display: flex;
text-align: center;
justify-content: center;
p {
display: flex;
align-items: center;
@include font-size(11);
}
}
.navigation-icons {
width: 33%;
text-align: right;
.navigation-tool-wrapper {
margin-left: 28px;
display: inline-block;
vertical-align: middle;
}
.button {
margin-left: 5px;
&:hover {
background: $light_background;
}
}
}
.navigation-panel {
height: 63px;
width: 100%;
padding: 10px 15px;
display: flex;
position: absolute;
z-index: 8;
align-items: center;
background-color: white;
color: $text;
}
@media (max-width: 960px) {
.context-menu {
.name-wrapper {
width: 67%;
}
}
.navigation-icons {
display: none;
}
.navigation-panel {
height: 53px;
padding: 15px;
}
.created-at-wrapper {
display: none;
}
.name-wrapper {
justify-content: space-between;
flex-direction: row-reverse;
width: 100%;
}
}
@media (prefers-color-scheme: dark) {
.navigation-panel {
background-color: $dark_mode_background;
color: $dark_mode_text_primary;
.icon-close {
color: $dark_mode_text_primary;
&:hover {
background-color: $dark_mode_background;
}
}
.fast-menu-icon:hover {
background: $dark_mode_background;
}
}
.name-wrapper {
.title,
.file-count {
color: $dark_mode_text_primary !important;
}
}
.navigation-icons {
.button:hover {
background: $dark_mode_background;
}
}
}
</style>
@@ -0,0 +1,357 @@
<template>
<div class="navigation-panel" v-if="clipboard[0]">
<div class="name-wrapper">
<x-icon @click="closeFullPreview" size="22" class="icon-close hover-text-theme" />
<div class="name-count-wrapper">
<p class="title">{{ clipboard[0].name }}</p>
<span class="file-count"> ({{ showingImageIndex + ' ' + $t('pronouns.of') + ' ' + files.length }}) </span>
</div>
<span @click.stop="menuOpen" id="fast-preview-menu" class="fast-menu-icon group">
<more-horizontal-icon class="more-icon group-hover-text-theme" size="14" />
</span>
</div>
<div class="created-at-wrapper">
<p>{{ clipboard[0].filesize }}, {{ clipboard[0].created_at }}</p>
</div>
<div class="navigation-icons">
<div v-if="isPdf" class="navigation-tool-wrapper">
<ToolbarButton @click.native="increaseSizeOfPDF" source="zoom-in" :action="$t('pdf_zoom_in')" />
<ToolbarButton @click.native="decreaseSizeOfPDF" source="zoom-out" :action="$t('pdf_zoom_out')" />
</div>
<div class="navigation-tool-wrapper">
<ToolbarButton @click.native="downloadItem" class="mobile-hide" source="download" :action="$t('actions.download')" />
<ToolbarButton v-if="canShareItem" @click.native="shareItem" class="mobile-hide" :class="{ 'is-inactive': !canShareItem }" source="share" :action="$t('actions.share')" />
<ToolbarButton v-if="isImage" @click.native="printMethod()" source="print" :action="$t('actions.print')" />
</div>
</div>
</div>
</template>
<script>
import ToolbarButton from '@/components/FilesView/ToolbarButton'
import {XIcon, MoreHorizontalIcon} from 'vue-feather-icons'
import {mapGetters} from 'vuex'
import {events} from '@/bus'
export default {
name: 'FilePreviewToolbar',
components: {
MoreHorizontalIcon,
ToolbarButton,
XIcon,
},
computed: {
...mapGetters([
'clipboard',
'entries'
]),
isImage() {
return this.clipboard[0].type === 'image'
},
isPdf() {
return this.clipboard[0].mimetype === 'pdf'
},
files() {
let files = []
this.entries.map(element => {
if (this.clipboard[0].mimetype === 'pdf') {
if (element.mimetype === 'pdf')
files.push(element)
} else {
if (element.type === this.clipboard[0].type)
files.push(element)
}
})
return files
},
showingImageIndex() {
let activeIndex = undefined
this.files.forEach((element, index) => {
if (element.id === this.clipboard[0].id) {
activeIndex = index + 1
}
})
return activeIndex
},
canShareItem() {
return this.$isThisLocation([
'base', 'participant_uploads', 'latest', 'shared'
])
},
},
methods: {
increaseSizeOfPDF() {
events.$emit('document-zoom:in')
},
decreaseSizeOfPDF() {
events.$emit('document-zoom:out')
},
printMethod() {
let tab = document.getElementById('printable-file')
let win = window.open('', '', 'height=700,width=700')
win.document.write(tab.outerHTML)
win.document.close()
win.print()
},
downloadItem() {
this.$downloadFile(
this.clipboard[0].file_url,
this.clipboard[0].name + '.' + this.clipboard[0].mimetype
)
},
shareItem() {
let event = this.clipboard[0].shared
? 'share-edit'
: 'share-create'
events.$emit('popup:open', {
name: event,
item: this.clipboard[0]
})
},
menuOpen() {
if (this.$isMobile()) {
events.$emit('mobile-menu:show', 'file-menu')
} else {
events.$emit('showContextMenuPreview:show', this.clipboard[0])
}
},
closeFullPreview() {
events.$emit('file-preview:hide')
events.$emit('showContextMenuPreview:hide')
}
}
}
</script>
<style lang="scss" scoped>
@import '@assets/vuefilemanager/_variables';
@import '@assets/vuefilemanager/_mixins';
.name-wrapper {
width: 33%;
height: 22px;
display: flex;
position: relative;
align-items: center;
flex-grow: 1;
align-self: center;
white-space: nowrap;
.name-count-wrapper {
margin-left: 6px;
margin-right: 6px;
.file-count {
@include font-size(15);
line-height: 1;
font-weight: 700;
overflow: hidden;
text-overflow: ellipsis;
display: inline-block;
vertical-align: middle;
align-self: center;
color: $text;
}
.title {
@include font-size(15);
max-width: 250px;
line-height: 1;
font-weight: 700;
overflow: hidden;
text-overflow: ellipsis;
display: inline-block;
vertical-align: middle;
color: $text;
}
@media (max-width: 570px) {
.title {
max-width: 180px;
@include font-size(17);
}
.file-count {
@include font-size(17);
}
}
}
.icon-close {
min-width: 22px;
padding: 1px 4px;
border-radius: 6px;
vertical-align: middle;
cursor: pointer;
@include transition(150ms);
&:hover {
background: $light_background;
line {
color: inherit;
}
}
}
.fast-menu-icon {
height: 24px;
display: flex;
align-items: center;
vertical-align: middle;
padding: 1px 4px;
line-height: 0;
border-radius: 3px;
cursor: pointer;
@include transition(150ms);
svg circle {
@include transition(150ms);
}
&:hover {
background: $light_background;
svg circle {
color: inherit;
}
}
.more-icon {
vertical-align: middle;
cursor: pointer;
}
}
}
.context-menu {
min-width: 250px;
position: absolute;
z-index: 99;
box-shadow: $shadow;
background: white;
border-radius: 8px;
overflow: hidden;
top: 29px;
&.showed {
display: block;
}
}
.created-at-wrapper {
width: 33%;
display: flex;
text-align: center;
justify-content: center;
p {
display: flex;
align-items: center;
@include font-size(11);
}
}
.navigation-icons {
width: 33%;
text-align: right;
.navigation-tool-wrapper {
margin-left: 28px;
display: inline-block;
vertical-align: middle;
}
.button {
margin-left: 5px;
&:hover {
background: $light_background;
}
}
}
.navigation-panel {
height: 63px;
width: 100%;
padding: 10px 15px;
display: flex;
position: absolute;
z-index: 8;
align-items: center;
background-color: white;
color: $text;
}
@media (max-width: 960px) {
.context-menu {
.name-wrapper {
width: 67%;
}
}
.navigation-icons {
display: none;
}
.navigation-panel {
height: 53px;
padding: 15px;
}
.created-at-wrapper {
display: none;
}
.name-wrapper {
justify-content: space-between;
flex-direction: row-reverse;
width: 100%;
}
}
@media (prefers-color-scheme: dark) {
.navigation-panel {
background-color: $dark_mode_background;
color: $dark_mode_text_primary;
.icon-close {
color: $dark_mode_text_primary;
&:hover {
background-color: $dark_mode_background;
}
}
.fast-menu-icon:hover {
background: $dark_mode_background;
}
}
.name-wrapper {
.title,
.file-count {
color: $dark_mode_text_primary !important;
}
}
.navigation-icons {
.button:hover {
background: $dark_mode_background;
}
}
}
</style>
@@ -0,0 +1,22 @@
<template>
<MenuMobile name="file-sorting">
<MenuMobileGroup>
<FileSortingOptions />
</MenuMobileGroup>
</MenuMobile>
</template>
<script>
import FileSortingOptions from '@/components/FilesView/FileSortingOptions'
import MenuMobileGroup from '@/components/Mobile/MenuMobileGroup'
import MenuMobile from '@/components/Mobile/MenuMobile'
export default {
name: 'FilterSortingMobile',
components: {
FileSortingOptions,
MenuMobileGroup,
MenuMobile,
},
}
</script>
@@ -1,10 +1,10 @@
<template>
<div class="menu-options" id="menu-list">
<OptionGroup class="menu-option-group">
<div>
<OptionGroup>
<Option v-if="isList" @click.native="changePreview('grid')" :title="$t('preview_sorting.grid_view')" icon="grid" />
<Option v-if="isGrid" @click.native="changePreview('list')" :title="$t('preview_sorting.list_view')" icon="list" />
</OptionGroup>
<OptionGroup class="menu-option-group">
<OptionGroup>
<Option @click.native.stop="sort('created_at')" :title="$t('preview_sorting.sort_date')" icon="calendar" />
<Option @click.native.stop="sort('name')" :title="$t('preview_sorting.sort_alphabet')" icon="alphabet" />
</OptionGroup>
@@ -19,25 +19,20 @@
import OptionGroup from '@/components/FilesView/OptionGroup'
import Option from '@/components/FilesView/Option'
import { CalendarIcon, ListIcon, GridIcon, ArrowUpIcon, CheckIcon } from 'vue-feather-icons'
import AlphabetIcon from '@/components/FilesView/Icons/AlphabetIcon'
import { ArrowUpIcon } from 'vue-feather-icons'
import { mapGetters } from 'vuex'
import { events } from '@/bus'
export default {
name: 'SortingAndPreviewMenu',
name: 'FileSortingOptions',
components: {
OptionGroup,
Option,
CalendarIcon,
AlphabetIcon,
ArrowUpIcon,
CheckIcon,
ListIcon,
GridIcon
Option,
},
computed: {
...mapGetters(['FilePreviewType']),
...mapGetters([
'FilePreviewType'
]),
isGrid() {
return this.FilePreviewType === 'grid'
},
@@ -55,7 +50,6 @@ export default {
},
methods: {
sort(field) {
this.filter.field = field
// Set sorting direction
@@ -74,16 +68,10 @@ export default {
this.$getDataByLocation()
},
changePreview(previewType) {
this.$store.dispatch('changePreviewType', previewType)
if (this.$isMobile())
events.$emit('mobileSortingAndPreview', false)
events.$emit('mobileSortingAndPreviewVignette', this.mobileSortingAndPreview)
}
},
mounted() {
let sorting = JSON.parse(localStorage.getItem('sorting'))
// Set default sorting if in not setup in LocalStorage
@@ -92,89 +80,3 @@ export default {
}
}
</script>
<style scoped lang="scss">
@import "@assets/vuefilemanager/_variables";
@import "@assets/vuefilemanager/_mixins";
.show-icon {
margin-left: auto;
max-height: 19px;
.arrow-down {
@include transform(rotate(180deg));
}
}
.menu-option {
display: flex;
.icon {
margin-right: 20px;
line-height: 0;
}
.text-label {
@include font-size(16);
}
}
.sorting-preview {
min-width: 250px;
position: absolute;
z-index: 99;
box-shadow: $shadow;
background: white;
border-radius: 8px;
overflow: hidden;
right: 66px;
top: 63px;
&.showed {
display: block;
}
}
.menu-options {
list-style: none;
width: 100%;
margin: 0;
padding: 0;
.menu-option-group {
padding: 5px 0;
border-bottom: 1px solid $light_mode_border;
&:first-child {
padding-top: 0;
}
&:last-child {
padding-bottom: 0;
border-bottom: none;
}
}
.menu-option {
white-space: nowrap;
font-weight: 700;
@include font-size(14);
padding: 15px 20px;
cursor: pointer;
width: 100%;
color: $text;
}
}
@media (prefers-color-scheme: dark) {
.menu-options {
.menu-option-group {
border-color: $dark_mode_border_color;
}
.menu-option {
color: $dark_mode_text_primary;
}
}
}
</style>
@@ -1,5 +1,5 @@
<template>
<svg class="preview-list-icon" fill="none" stroke="currentColor" stroke-width="1.5" fill-rule="evenodd" stroke-linecap="round" stroke-linejoin="round" width="15px" height="15px" viewBox="0 0 20 18" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<svg class="preview-list-icon" fill="none" stroke="currentColor" stroke-width="1.5" fill-rule="evenodd" stroke-linecap="round" stroke-linejoin="round" width="20px" height="20px" viewBox="0 0 20 16" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<rect x="9.77777778" y="0" width="6.22222222" height="6.22222222"></rect>
<rect x="9.77777778" y="9.77777778" width="6.22222222" height="6.22222222"></rect>
<line x1="0" y1="2" x2="6" y2="2"></line>
@@ -1,108 +1,109 @@
<template>
<div>
<ul class="meta-data-list">
<li v-if="fileInfoDetail.metadata.DateTimeOriginal">
<li v-if="clipboard.metadata.DateTimeOriginal">
<span>{{ $t('file_detail_meta.time_data') }}</span>
<b>{{ fileInfoDetail.metadata.DateTimeOriginal }}</b>
<b>{{ clipboard.metadata.DateTimeOriginal }}</b>
</li>
<li v-if="fileInfoDetail.metadata.Artist">
<li v-if="clipboard.metadata.Artist">
<span>{{ $t('file_detail_meta.author') }}</span>
<b>{{ fileInfoDetail.metadata.Artist }}</b>
<b>{{ clipboard.metadata.Artist }}</b>
</li>
<li v-if="fileInfoDetail.metadata.ExifImageWidth && fileInfoDetail.metadata.ExifImageLength">
<li v-if="clipboard.metadata.ExifImageWidth && clipboard.metadata.ExifImageLength">
<span>{{ $t('file_detail_meta.dimension') }}</span>
<b>{{ fileInfoDetail.metadata.ExifImageWidth }}x{{ fileInfoDetail.metadata.ExifImageLength }}</b>
<b>{{ clipboard.metadata.ExifImageWidth }}x{{ clipboard.metadata.ExifImageLength }}</b>
</li>
<li v-if="fileInfoDetail.metadata.XResolution && fileInfoDetail.metadata.YResolution">
<li v-if="clipboard.metadata.XResolution && clipboard.metadata.YResolution">
<span>{{ $t('file_detail_meta.resolution') }}</span>
<b>{{ fileInfoDetail.metadata.XResolution }}x{{ fileInfoDetail.metadata.YResolution }}</b>
<b>{{ clipboard.metadata.XResolution }}x{{ clipboard.metadata.YResolution }}</b>
</li>
<li v-if="fileInfoDetail.metadata.ColorSpace">
<li v-if="clipboard.metadata.ColorSpace">
<span> {{ $t('file_detail_meta.color_space') }}</span>
<b>{{ fileInfoDetail.metadata.ColorSpace}}</b>
<b>{{ clipboard.metadata.ColorSpace }}</b>
</li>
<!--TODO: Colour profile:sRGB IEC61966-2.1-->
<!--TODO: Colour profile:sRGB IEC61966-2.1-->
<li v-if="fileInfoDetail.metadata.Make">
<li v-if="clipboard.metadata.Make">
<span>{{ $t('file_detail_meta.make') }}</span>
<b>{{ fileInfoDetail.metadata.Make }}</b>
<b>{{ clipboard.metadata.Make }}</b>
</li>
<li v-if="fileInfoDetail.metadata.Model">
<li v-if="clipboard.metadata.Model">
<span>{{ $t('file_detail_meta.model') }}</span>
<b>{{ fileInfoDetail.metadata.Model }}</b>
<b>{{ clipboard.metadata.Model }}</b>
</li>
<li v-if="fileInfoDetail.metadata.ApertureValue">
<li v-if="clipboard.metadata.ApertureValue">
<span>{{ $t('file_detail_meta.aperture_value') }}</span>
<b v-html="parseInt(fileInfoDetail.metadata.ApertureValue) / 100"></b>
<b v-html="parseInt(clipboard.metadata.ApertureValue) / 100"></b>
</li>
<li v-if="fileInfoDetail.metadata.ExposureTime">
<li v-if="clipboard.metadata.ExposureTime">
<span>{{ $t('file_detail_meta.exposure') }}</span>
<b>{{ fileInfoDetail.metadata.ExposureTime }}</b>
<b>{{ clipboard.metadata.ExposureTime }}</b>
</li>
<li v-if="fileInfoDetail.metadata.FocalLength">
<li v-if="clipboard.metadata.FocalLength">
<span>{{ $t('file_detail_meta.focal') }}</span>
<b>{{ fileInfoDetail.metadata.FocalLength }}</b>
<b>{{ clipboard.metadata.FocalLength }}</b>
</li>
<li v-if="fileInfoDetail.metadata.ISOSpeedRatings">
<li v-if="clipboard.metadata.ISOSpeedRatings">
<span>{{ $t('file_detail_meta.iso') }}</span>
<b>{{ fileInfoDetail.metadata.ISOSpeedRatings }}</b>
<b>{{ clipboard.metadata.ISOSpeedRatings }}</b>
</li>
<li v-if="fileInfoDetail.metadata.COMPUTED.ApertureFNumber">
<li v-if="clipboard.metadata.COMPUTED.ApertureFNumber">
<span>{{ $t('file_detail_meta.aperature') }}</span>
<b>{{ fileInfoDetail.metadata.COMPUTED.ApertureFNumber }}</b>
<b>{{ clipboard.metadata.COMPUTED.ApertureFNumber }}</b>
</li>
<li v-if="fileInfoDetail.metadata.COMPUTED.CCDWidth">
<li v-if="clipboard.metadata.COMPUTED.CCDWidth">
<span>{{ $t('file_detail_meta.camera_lens') }}</span>
<b>{{ fileInfoDetail.metadata.COMPUTED.CCDWidth }}</b>
<b>{{ clipboard.metadata.COMPUTED.CCDWidth }}</b>
</li>
<li v-if="fileInfoDetail.metadata.GPSLongitude">
<li v-if="clipboard.metadata.GPSLongitude">
<span>{{ $t('file_detail_meta.longitude') }}</span>
<b>{{ formatGps(fileInfoDetail.metadata.GPSLongitude,fileInfoDetail.metadata.GPSLongitudeRef) }}</b>
</li>
<li v-if="fileInfoDetail.metadata.GPSLatitude">
<span>{{ $t('file_detail_meta.latitude') }}</span>
<b>{{ formatGps(fileInfoDetail.metadata.GPSLatitude, fileInfoDetail.metadata.GPSLatitudeRef) }}</b>
<b>{{ formatGps(clipboard.metadata.GPSLongitude, clipboard.metadata.GPSLongitudeRef) }}</b>
</li>
<li v-if="clipboard.metadata.GPSLatitude">
<span>{{ $t('file_detail_meta.latitude') }}</span>
<b>{{ formatGps(clipboard.metadata.GPSLatitude, clipboard.metadata.GPSLatitudeRef) }}</b>
</li>
</ul>
</div>
</template>
<script>
import { mapGetters } from 'vuex'
import {mapGetters} from 'vuex'
import {split} from 'lodash'
export default {
name: 'ImageMetaData',
computed: {
fileInfoDetail() {
return this.$store.getters.fileInfoDetail[0]
},
},
methods: {
formatGps(location, ref) {
let data = []
location.forEach(location => {
data.push(split(location , '/' , 2)[0])
})
return `${data[0]}° ${data[1]}' ${data[2].substr(0,4) / 100}" ${ref} `
}
},
name: 'ImageMetaData',
computed: {
clipboard() {
return this.$store.getters.clipboard[0]
},
},
methods: {
formatGps(location, ref) {
let data = []
location.forEach(location => {
data.push(split(location, '/', 2)[0])
})
return `${data[0]}° ${data[1]}' ${data[2].substr(0, 4) / 100}" ${ref} `
}
},
}
</script>
@@ -111,33 +112,33 @@ export default {
@import '@assets/vuefilemanager/_mixins';
.meta-data-list {
list-style: none;
padding: 0px;
margin: 0px;
list-style: none;
padding: 0px;
margin: 0px;
li {
display: flex;
justify-content: space-between;
padding: 9px 0;
border-bottom: 1px solid $light_mode_border;
li {
display: flex;
justify-content: space-between;
padding: 9px 0;
border-bottom: 1px solid $light_mode_border;
b, span {
@include font-size(14);
color: $text;
}
}
b, span {
@include font-size(14);
color: $text;
}
}
}
@media (prefers-color-scheme: dark) {
.meta-data-list {
li {
border-color: $dark_mode_border_color;
.meta-data-list {
li {
border-color: $dark_mode_border_color;
b, span {
color: $dark_mode_text_primary !important;
}
}
}
b, span {
color: $dark_mode_text_primary !important;
}
}
}
}
</style>
@@ -0,0 +1,186 @@
<template>
<div class="info-wrapper">
<!--Is empty clipboard-->
<EmptyMessage
v-if="isEmpty"
:message="$t('messages.nothing_to_preview')"
icon="eye-off"
/>
<!--Multiple item selection-->
<div v-if="! isSingleFile && !isEmpty" class="info-headline">
<TitlePreview
icon="check-square"
:title="$t('file_detail.selected_multiple')"
:subtitle="this.clipboard.length + ' ' + $tc('file_detail.items', this.clipboard.length)"
/>
</div>
<!--Single file preview-->
<div v-if="isSingleFile && !isEmpty" class="info-headline">
<FilePreviewDetail />
<TitlePreview
:icon="clipboard[0].type"
:title="clipboard[0].name"
:subtitle="clipboard[0].mimetype"
/>
</div>
<!--File info-->
<ListInfo v-if="isSingleFile && !isEmpty">
<!--Filesize-->
<ListInfoItem
v-if="singleFile.filesize"
:title="$t('file_detail.size')"
:content="singleFile.filesize"
/>
<!--Participant-->
<ListInfoItem
v-if="$checkPermission(['master']) && singleFile.author !== 'user'"
:title="$t('file_detail.author')"
:content="$t('file_detail.author_participant')"
/>
<!--Created At-->
<ListInfoItem
:title="$t('file_detail.created_at')"
:content="singleFile.created_at"
/>
<!--Location-->
<ListInfoItem
v-if="$checkPermission(['master'])"
:title="$t('file_detail.where')"
>
<div class="action-button" @click="openMoveOptions">
<span>{{ singleFile.parent ? singleFile.parent.name : $t('locations.home') }}</span>
<edit-2-icon size="10" class="edit-icon" />
</div>
</ListInfoItem>
<!--Shared-->
<ListInfoItem
v-if="$checkPermission('master') && singleFile.shared"
:title="$t('file_detail.shared')"
>
<div @click="openShareOptions" class="action-button">
<span>{{ sharedInfo }}</span>
<edit-2-icon size="10" class="edit-icon" />
</div>
<div class="share-link">
<lock-icon v-if="isLocked" @click="openShareOptions" class="lock-icon" size="17" />
<unlock-icon v-if="! isLocked" @click="openShareOptions" class="lock-icon" size="17" />
<CopyInput :item="singleFile" class="copy-share-link" size="small" />
</div>
</ListInfoItem>
<!--Metadata-->
<ListInfoItem
v-if="canShowMetaData"
:title="$t('file_detail_meta.meta_data')"
>
<ImageMetaData />
</ListInfoItem>
</ListInfo>
</div>
</template>
<script>
import FilePreviewDetail from '@/components/Others/FilePreviewDetail'
import {Edit2Icon, LockIcon, UnlockIcon} from 'vue-feather-icons'
import ImageMetaData from '@/components/FilesView/ImageMetaData'
import EmptyMessage from '@/components/FilesView/EmptyMessage'
import TitlePreview from '@/components/FilesView/TitlePreview'
import CopyInput from '@/components/Others/Forms/CopyInput'
import ListInfoItem from '@/components/Others/ListInfoItem'
import ListInfo from '@/components/Others/ListInfo'
import {mapGetters} from 'vuex'
import {events} from "@/bus"
export default {
name: 'InfoSidebar',
components: {
FilePreviewDetail,
ImageMetaData,
EmptyMessage,
TitlePreview,
ListInfoItem,
UnlockIcon,
CopyInput,
Edit2Icon,
LockIcon,
ListInfo,
},
computed: {
...mapGetters([
'permissionOptions',
'clipboard',
]),
isEmpty() {
return this.clipboard.length === 0
},
isSingleFile() {
return this.clipboard.length === 1
},
singleFile() {
return this.clipboard[0]
},
canShowMetaData() {
return this.clipboard[0].metadata && this.clipboard[0].metadata.ExifImageWidth
},
isLocked() {
return this.clipboard[0].shared.is_protected
},
sharedInfo() {
let title = this.permissionOptions.find(option => {
return option.value === this.clipboard[0].shared.permission
})
return title ? this.$t(title.label) : this.$t('shared.can_download')
},
},
methods: {
openShareOptions() {
events.$emit('popup:open', {name: 'share-edit', item: this.clipboard[0]})
},
openMoveOptions() {
events.$emit("popup:open", {name: "move", item: this.clipboard});
}
}
}
</script>
<style scoped lang="scss">
.info-wrapper {
padding-bottom: 20px;
height: 100%;
}
.info-headline {
margin-bottom: 20px;
border-radius: 8px;
}
.share-link {
display: flex;
width: 100%;
align-items: center;
margin-top: 10px;
.lock-icon {
display: inline-block;
width: 15px;
margin-right: 9px;
cursor: pointer;
}
.copy-share-link {
width: 100%;
}
}
</style>
@@ -1,236 +0,0 @@
<template>
<div class="media-full-preview" id="mediaPreview" v-if="this.isMedia && fileInfoDetail[0]">
<div class="file-wrapper-preview" v-for="i in [currentIndex]" :key="i">
<div class="file-wrapper">
<audio class="file audio" :class="{ 'file-shadow': !isMobileDevice }" v-if="fileInfoDetail[0].type == 'audio'" :src="currentFile.file_url" controlsList="nodownload" controls></audio>
<img v-if="fileInfoDetail[0].type === 'image' && currentFile.thumbnail" class="file" :class="{ 'file-shadow': !isMobileDevice }" id="image" :src="currentFile.file_url" />
<div class="video-wrapper" v-if="fileInfoDetail[0].type === 'video' && currentFile.file_url">
<video :src="currentFile.file_url" class="video" :class="{ 'file-shadow': !isMobileDevice }" controlsList="nodownload" disablePictureInPicture playsinline controls autoplay />
</div>
</div>
</div>
</div>
</template>
<script>
import { events } from '@/bus'
import { mapGetters } from 'vuex'
import ToolbarButton from '@/components/FilesView/ToolbarButton'
import Spinner from '@/components/FilesView/Spinner'
export default {
name: 'MediaFullPreview',
components: { ToolbarButton, Spinner },
computed: {
...mapGetters(['fileInfoDetail', 'data']),
isMobileDevice() {
return this.$isMobile()
},
currentFile: function() {
return this.sliderFile[Math.abs(this.currentIndex) % this.sliderFile.length]
},
isMedia() {
return this.fileInfoDetail[0] === 'image' || 'video' || 'audio'
},
canShareInView() {
return !this.$isThisLocation(['base', 'participant_uploads', 'latest', 'shared', 'public'])
}
},
data() {
return {
currentIndex: 1,
sliderFile: []
// loaded: false
}
},
watch: {
sliderFile() {
//Close file preview after delete all items
if (this.sliderFile.length == 0) {
events.$emit('fileFullPreview:hide')
}
},
currentFile() {
//Handle actual view image in fileInfoDetail
if (this.fileInfoDetail[0]) {
this.$store.commit('CLEAR_FILEINFO_DETAIL')
this.$store.commit('GET_FILEINFO_DETAIL', this.currentFile)
events.$emit('actualShowingImage:ContextMenu', this.currentFile)
// this.loaded = false
}
},
fileInfoDetail() {
//File delete handling - show next image after delete one
if (!this.fileInfoDetail[0]) {
this.currentIndex = this.currentIndex - 1
this.$store.commit('GET_FILEINFO_DETAIL', this.currentFile)
this.sliderFile = []
this.filteredFiles()
}
},
data(newValue, oldValue) {
//Move item handling
if (newValue != oldValue) {
this.sliderFile = []
this.filteredFiles()
}
}
},
methods: {
filteredFiles() {
this.data.filter((element) => {
if (element.type == this.fileInfoDetail[0].type) {
this.sliderFile.push(element)
}
})
this.choseActiveFile()
},
choseActiveFile() {
this.sliderFile.forEach((element, index) => {
if (element.id == this.fileInfoDetail[0].id) {
this.currentIndex = index
}
})
}
},
mounted() {
if (this.sliderFile.length > 1) {
events.$on('filePreviewAction:next', () => {
this.currentIndex += 1
this.slideType = 'next'
if (this.currentIndex > this.sliderFile.length - 1) {
this.currentIndex = 0
}
})
events.$on('filePreviewAction:prev', () => {
this.slideType = 'prev'
this.currentIndex -= 1
if (this.currentIndex < 0) {
this.currentIndex = this.sliderFile.length - 1
}
})
}
},
created() {
this.filteredFiles()
}
}
</script>
<style lang="scss" scoped>
@import '@assets/vuefilemanager/_variables';
@import '@assets/vuefilemanager/_mixins';
.media-full-preview {
height: calc(100% - 72px);
top: 72px;
position: relative;
background-color: white;
}
.navigation-panel {
width: 100%;
height: 7%;
display: flex;
align-items: center;
padding: 20px;
justify-content: space-between;
background-color: $light-background;
color: $text;
.icon-close {
color: $text;
@include font-size(21);
&:hover {
color: $theme;
}
}
}
.loading-spinner {
position: relative;
}
.file-wrapper-preview {
width: 100%;
height: 100%;
padding: 30px 0px;
display: flex;
overflow: hidden;
justify-content: center;
align-items: center;
background-color: white;
.file-wrapper {
width: 100%;
height: 100%;
display: flex;
justify-content: center;
align-items: center;
.file-shadow {
box-shadow: 0 8px 40px rgba(17, 26, 52, 0.05);
}
.file {
max-width: 100%;
max-height: 100%;
align-self: center;
}
.audio {
border-radius: 28px;
}
img {
border-radius: 4px;
}
.video-wrapper {
max-width: 1080px;
max-height: 100%;
@media (min-width: 1200px) {
& {
max-width: 800px;
}
}
@media (min-width: 1920px) and (max-width: 2560px) {
& {
max-width: 1080px;
}
}
@media (min-width: 2560px) and (max-width: 3840px) {
& {
max-width: 1440px;
}
}
@media (min-width: 3840px) {
& {
max-width: 2160px;
}
}
.video {
max-width: 100%;
max-height: 100%;
align-self: center;
}
}
}
}
@media (prefers-color-scheme: dark) {
.file-wrapper-preview {
background-color: $dark_mode_background;
.file-wrapper {
.file-shadow {
box-shadow: 0 8px 40px rgba(0, 0, 0, 0.1);
}
}
}
}
</style>
@@ -1,6 +1,7 @@
<template>
<button class="mobile-action-button">
<div class="flex">
<filter-icon v-if="icon === 'filter'" size="15" class="icon dark-text-theme" />
<credit-card-icon v-if="icon === 'credit-card'" size="15" class="icon dark-text-theme" />
<folder-plus-icon v-if="icon === 'folder-plus'" size="15" class="icon dark-text-theme" />
<list-icon v-if="icon === 'th-list'" size="15" class="icon dark-text-theme" />
@@ -12,7 +13,7 @@
<x-square-icon v-if="icon === 'x-square'" size="15" class="icon dark-text-theme" />
<check-icon v-if="icon === 'check'" size="15" class="icon dark-text-theme" />
<dollar-sign-icon v-if="icon === 'dollar-sign'" size="15" class="icon dark-text-theme" />
<sorting-and-preview-icon v-if="icon === 'preview-sorting'" size="15" class="icon dark-text-theme preview-sorting" />
<sorting-icon v-if="icon === 'preview-sorting'" class="icon dark-text-theme preview-sorting" />
<span class="label">
<slot></slot>
</span>
@@ -21,8 +22,8 @@
</template>
<script>
import { DollarSignIcon, CheckIcon, XSquareIcon, CheckSquareIcon, FolderPlusIcon, ListIcon, GridIcon, TrashIcon, UserPlusIcon, PlusIcon, CreditCardIcon } from 'vue-feather-icons'
import SortingAndPreviewIcon from '@/components/FilesView/Icons/SortingAndPreviewIcon'
import { FilterIcon, DollarSignIcon, CheckIcon, XSquareIcon, CheckSquareIcon, FolderPlusIcon, ListIcon, GridIcon, TrashIcon, UserPlusIcon, PlusIcon, CreditCardIcon } from 'vue-feather-icons'
import SortingIcon from '@/components/FilesView/Icons/SortingIcon'
export default {
name: 'MobileActionButton',
@@ -30,13 +31,14 @@
'icon'
],
components: {
SortingAndPreviewIcon,
SortingIcon,
CheckSquareIcon,
DollarSignIcon,
CreditCardIcon,
FolderPlusIcon,
UserPlusIcon,
XSquareIcon,
FilterIcon,
CheckIcon,
TrashIcon,
PlusIcon,
@@ -52,9 +54,9 @@
.mobile-action-button {
background: $light_background;
margin-right: 15px;
margin-right: 8px;
border-radius: 8px;
padding: 7px 10px;
padding: 7px 14px;
cursor: pointer;
border: none;
@include transition(150ms);
@@ -68,7 +70,7 @@
margin-right: 10px;
@include font-size(14);
path, line, polyline, rect, circle {
path, line, polyline, rect, circle, polygon {
@include transition(150ms);
}
}
@@ -89,7 +91,7 @@
.mobile-action-button {
background: $dark_mode_foreground;
path, line, polyline, rect, circle {
path, line, polyline, rect, circle, polygon {
color: inherit;
}
@@ -39,9 +39,9 @@
.mobile-action-button {
background: $light_background;
margin-right: 15px;
margin-right: 8px;
border-radius: 8px;
padding: 7px 10px;
padding: 7px 14px;
cursor: pointer;
border: none;
@@ -1,192 +0,0 @@
<template>
<div id="mobile-actions-wrapper">
<!--Actions for trash location with MASTER permission--->
<div v-if="trashLocationMenu && ! multiSelectMode" class="mobile-actions">
<MobileActionButton @click.native="$store.dispatch('emptyTrash')" icon="trash">
{{ $t('context_menu.empty_trash') }}
</MobileActionButton>
<MobileActionButton @click.native="enableMultiSelectMode" icon="check-square">
{{ $t('context_menu.select') }}
</MobileActionButton>
<MobileActionButton class="preview-sorting" @click.native="showViewOptions" icon="preview-sorting">
{{$t('preview_sorting.preview_sorting_button')}}
</MobileActionButton>
</div>
<!--ContextMenu for Base location with MASTER permission-->
<transition name="button">
<div v-if="baseLocationMasterMenu && ! multiSelectMode" class="mobile-actions">
<MobileActionButton @click.native="createFolder" icon="folder-plus" :class="{'is-inactive' : multiSelectMode}">
{{ $t('context_menu.add_folder') }}
</MobileActionButton>
<MobileActionButtonUpload :class="{'is-inactive' : multiSelectMode}">
{{ $t('context_menu.upload') }}
</MobileActionButtonUpload>
<MobileActionButton @click.native="enableMultiSelectMode" icon="check-square">
{{ $t('context_menu.select') }}
</MobileActionButton>
<MobileActionButton class="preview-sorting" @click.native="showViewOptions" icon="preview-sorting">
{{$t('preview_sorting.preview_sorting_button')}}
</MobileActionButton>
</div>
</transition>
<!-- Selecting buttons -->
<transition name="button">
<div v-if="multiSelectMode" class="mobile-actions">
<MobileActionButton @click.native="selectAll" icon="check-square">
{{$t('mobile_selecting.select_all')}}
</MobileActionButton>
<MobileActionButton @click.native="deselectAll" icon="x-square">
{{$t('mobile_selecting.deselect_all')}}
</MobileActionButton>
<MobileActionButton @click.native="disableMultiSelectMode" icon="check">
{{$t('mobile_selecting.done')}}
</MobileActionButton>
</div>
</transition>
<!--ContextMenu for Base location with VISITOR permission-->
<div v-if="baseLocationVisitorMenu && ! multiSelectMode" class="mobile-actions">
<MobileActionButton @click.native="enableMultiSelectMode" icon="check-square">
{{ $t('context_menu.select') }}
</MobileActionButton>
<MobileActionButton class="preview-sorting" @click.native="showViewOptions" icon="preview-sorting">
{{$t('preview_sorting.preview_sorting_button')}}
</MobileActionButton>
</div>
<!--Upload Progressbar-->
<UploadProgress />
</div>
</template>
<script>
import MobileActionButtonUpload from '@/components/FilesView/MobileActionButtonUpload'
import MobileActionButton from '@/components/FilesView/MobileActionButton'
import UploadProgress from '@/components/FilesView/UploadProgress'
import {mapGetters} from 'vuex'
import {events} from '@/bus'
export default {
name: 'MobileActions',
components: {
MobileActionButtonUpload,
MobileActionButton,
UploadProgress,
},
computed: {
...mapGetters(['FilePreviewType']),
previewIcon() {
return this.FilePreviewType === 'list' ? 'th' : 'th-list'
},
trashLocationMenu() {
return this.$isThisLocation(['trash', 'trash-root']) && this.$checkPermission('master')
},
baseLocationMasterMenu() {
return this.$isThisLocation(['base', 'public']) && this.$checkPermission(['master', 'editor'])
},
baseLocationVisitorMenu() {
return (this.$isThisLocation(['base', 'shared', 'public']) && this.$checkPermission('visitor')) || (this.$isThisLocation(['latest', 'shared']) && this.$checkPermission('master'))
},
},
data () {
return {
multiSelectMode: false,
mobileSortingAndPreview: false,
}
},
methods: {
selectAll() {
this.$store.commit('SELECT_ALL_FILES')
},
deselectAll() {
this.$store.commit('CLEAR_FILEINFO_DETAIL')
},
enableMultiSelectMode() {
this.multiSelectMode = true
events.$emit('mobileSelecting:start')
},
disableMultiSelectMode() {
this.multiSelectMode = false
events.$emit('mobileSelecting:stop')
},
showViewOptions() {
this.mobileSortingAndPreview = ! this.mobileSortingAndPreview
// Toggle mobile sorting
events.$emit('mobileSortingAndPreview', this.mobileSortingAndPreview)
events.$emit('mobileSortingAndPreviewVignette', this.mobileSortingAndPreview)
},
createFolder() {
events.$emit('popup:open', {name: 'create-folder'})
},
},
mounted () {
events.$on('mobileSelecting:stop', () => this.multiSelectMode = false)
events.$on('mobileSortingAndPreview', state => this.mobileSortingAndPreview = state)
}
}
</script>
<style scoped lang="scss">
@import '@assets/vuefilemanager/_variables';
@import '@assets/vuefilemanager/_mixins';
.button-enter-active,
.button-leave-active {
transition: all 250ms;
}
.button-enter {
opacity: 0;
transform: translateY(-50%);
}
.button-leave-to {
opacity: 0;
transform: translateY(50%);
}
.button-leave-active {
position: absolute;
}
#mobile-actions-wrapper {
display: none;
background: white;
position: sticky;
top: 35px;
z-index: 3;
}
.mobile-action-button {
&.is-inactive {
opacity: 0.25;
pointer-events: none;
}
}
.mobile-actions {
white-space: nowrap;
overflow-x: auto;
margin: 0 -15px;
padding: 10px 0 10px 15px;
}
@media only screen and (max-width: 960px) {
#mobile-actions-wrapper {
display: block;
}
}
@media (prefers-color-scheme: dark) {
#mobile-actions-wrapper {
background: $dark_mode_background;
}
}
</style>
@@ -1,560 +0,0 @@
<template>
<div class="options-wrapper">
<transition name="context-menu">
<div v-if="isVisible" ref="contextmenu" class="options" @click="closeAndResetContextMenu">
<div class="menu-wrapper">
<!--Item Thumbnail-->
<ThumbnailItem class="item-thumbnail" :item="fileInfoDetail[0]" info="metadata"/>
<!--Mobile for trash location-->
<div v-if="$isThisLocation(['trash', 'trash-root']) && $checkPermission('master')" class="menu-options">
<ul class="menu-option-group">
<li class="menu-option" @click="$store.dispatch('restoreItem', fileInfoDetail[0])" v-if="fileInfoDetail[0]">
<div class="icon">
<life-buoy-icon size="17"></life-buoy-icon>
</div>
<div class="text-label">
{{ $t('context_menu.restore') }}
</div>
</li>
<li class="menu-option delete" @click="deleteItem" v-if="fileInfoDetail[0]">
<div class="icon">
<trash-2-icon size="17"></trash-2-icon>
</div>
<div class="text-label">
{{ $t('context_menu.delete') }}
</div>
</li>
</ul>
<ul class="menu-option-group" >
<li class="menu-option" @click="downloadItem" v-if="!isFolder">
<div class="icon">
<download-cloud-icon size="17"></download-cloud-icon>
</div>
<div class="text-label">
{{ $t('context_menu.download') }}
</div>
</li>
<li class="menu-option" @click="downloadFolder" v-if="isFolder">
<div class="icon">
<paperclip-icon size="17"></paperclip-icon>
</div>
<div class="text-label">
{{ $t('context_menu.zip_folder') }}
</div>
</li>
</ul>
</div>
<!--Mobile for Base location-->
<div v-if="$isThisLocation(['shared']) && $checkPermission('master')" class="menu-options">
<ul class="menu-option-group">
<li class="menu-option" @click="addToFavourites" v-if="fileInfoDetail[0] && isFolder">
<div class="icon">
<star-icon size="17"></star-icon>
</div>
<div class="text-label">
{{
isInFavourites
? $t('context_menu.remove_from_favourites')
: $t('context_menu.add_to_favourites')
}}
</div>
</li>
</ul>
<ul class="menu-option-group">
<li class="menu-option" @click="renameItem" v-if="fileInfoDetail[0]">
<div class="icon">
<edit-2-icon size="17"></edit-2-icon>
</div>
<div class="text-label">
{{ $t('context_menu.rename') }}
</div>
</li>
<li class="menu-option" @click="shareItem" v-if="fileInfoDetail[0]">
<div class="icon">
<link-icon size="17"></link-icon>
</div>
<div class="text-label">
{{
fileInfoDetail[0].shared
? $t('context_menu.share_edit')
: $t('context_menu.share')
}}
</div>
</li>
<li class="menu-option delete" @click="deleteItem" v-if="fileInfoDetail[0]">
<div class="icon">
<trash-2-icon size="17"></trash-2-icon>
</div>
<div class="text-label">
{{ $t('context_menu.delete') }}
</div>
</li>
</ul>
<ul class="menu-option-group">
<li class="menu-option" @click="downloadItem" v-if="!isFolder">
<div class="icon">
<download-cloud-icon size="17"></download-cloud-icon>
</div>
<div class="text-label">
{{ $t('context_menu.download') }}
</div>
</li>
<li class="menu-option" @click="downloadFolder" v-if="isFolder">
<div class="icon">
<paperclip-icon size="17"></paperclip-icon>
</div>
<div class="text-label">
{{ $t('context_menu.zip_folder') }}
</div>
</li>
</ul>
</div>
<!--Mobile for Base location-->
<div v-if="$isThisLocation(['base', 'participant_uploads', 'latest']) && $checkPermission('master')" class="menu-options">
<ul class="menu-option-group" v-if="fileInfoDetail[0] && isFolder">
<li class="menu-option" @click="addToFavourites">
<div class="icon">
<star-icon size="17"></star-icon>
</div>
<div class="text-label">
{{
isInFavourites
? $t('context_menu.remove_from_favourites')
: $t('context_menu.add_to_favourites')
}}
</div>
</li>
</ul>
<ul class="menu-option-group">
<li class="menu-option" @click="renameItem" v-if="fileInfoDetail[0]">
<div class="icon">
<edit-2-icon size="17"></edit-2-icon>
</div>
<div class="text-label">
{{ $t('context_menu.rename') }}
</div>
</li>
<li class="menu-option" @click="moveItem" v-if="fileInfoDetail[0]">
<div class="icon">
<corner-down-right-icon size="17"></corner-down-right-icon>
</div>
<div class="text-label">
{{ $t('context_menu.move') }}
</div>
</li>
<li class="menu-option" @click="shareItem" v-if="fileInfoDetail[0]">
<div class="icon">
<link-icon size="17"></link-icon>
</div>
<div class="text-label">
{{
fileInfoDetail[0].shared
? $t('context_menu.share_edit')
: $t('context_menu.share')
}}
</div>
</li>
<li class="menu-option delete" @click="deleteItem" v-if="fileInfoDetail[0]">
<div class="icon">
<trash-2-icon size="17"></trash-2-icon>
</div>
<div class="text-label">
{{ $t('context_menu.delete') }}
</div>
</li>
</ul>
<ul class="menu-option-group">
<li class="menu-option" @click="downloadItem" v-if="!isFolder">
<div class="icon">
<download-cloud-icon size="17"></download-cloud-icon>
</div>
<div class="text-label">
{{ $t('context_menu.download') }}
</div>
</li>
<li class="menu-option" @click="downloadFolder" v-if="isFolder">
<div class="icon">
<paperclip-icon size="17"></paperclip-icon>
</div>
<div class="text-label">
{{ $t('context_menu.zip_folder') }}
</div>
</li>
</ul>
</div>
<!--Mobile for Base location with EDITOR permission-->
<div v-if="$isThisLocation(['base', 'public']) && $checkPermission('editor')" class="menu-options">
<ul class="menu-option-group">
<li class="menu-option" @click="renameItem" v-if="fileInfoDetail[0]">
<div class="icon">
<edit-2-icon size="17"></edit-2-icon>
</div>
<div class="text-label">
{{ $t('context_menu.rename') }}
</div>
</li>
<li class="menu-option" @click="moveItem" v-if="fileInfoDetail[0]">
<div class="icon">
<corner-down-right-icon size="17"></corner-down-right-icon>
</div>
<div class="text-label">
{{ $t('context_menu.move') }}
</div>
</li>
<li class="menu-option" @click="deleteItem">
<div class="icon">
<trash-2-icon size="17"></trash-2-icon>
</div>
<div class="text-label">
{{ $t('context_menu.delete') }}
</div>
</li>
</ul>
<ul class="menu-option-group">
<li class="menu-option" @click="downloadItem" v-if="!isFolder">
<div class="icon">
<download-cloud-icon size="17"></download-cloud-icon>
</div>
<div class="text-label">
{{ $t('context_menu.download') }}
</div>
</li>
<li class="menu-option" @click="downloadFolder" v-if="isFolder">
<div class="icon">
<paperclip-icon size="17"></paperclip-icon>
</div>
<div class="text-label">
{{ $t('context_menu.zip_folder') }}
</div>
</li>
</ul>
</div>
<!--Mobile for Base location with VISITOR permission-->
<div v-if="$isThisLocation(['base', 'public']) && $checkPermission('visitor')" class="menu-options">
<ul class="menu-option-group">
<li class="menu-option" @click="downloadItem" v-if="!isFolder">
<div class="icon">
<download-cloud-icon size="17"></download-cloud-icon>
</div>
<div class="text-label">
{{ $t('context_menu.download') }}
</div>
</li>
<li class="menu-option" @click="downloadFolder" v-if="isFolder">
<div class="icon">
<paperclip-icon size="17"></paperclip-icon>
</div>
<div class="text-label">
{{ $t('context_menu.zip_folder') }}
</div>
</li>
</ul>
</div>
</div>
</div>
</transition>
<transition name="fade">
<div v-show="isVisible" class="vignette" @click="closeAndResetContextMenu"></div>
</transition>
</div>
</template>
<script>
import ThumbnailItem from '@/components/Others/ThumbnailItem'
import {
CornerDownRightIcon,
DownloadCloudIcon,
FolderPlusIcon,
PaperclipIcon,
LifeBuoyIcon,
Trash2Icon,
Edit2Icon,
TrashIcon,
StarIcon,
LinkIcon,
EyeIcon
} from 'vue-feather-icons'
import { events } from '@/bus'
import { mapGetters } from 'vuex'
export default {
name: 'MobileMenu',
components: {
CornerDownRightIcon,
DownloadCloudIcon,
FolderPlusIcon,
PaperclipIcon,
ThumbnailItem,
LifeBuoyIcon,
Trash2Icon,
Edit2Icon,
TrashIcon,
LinkIcon,
StarIcon,
EyeIcon
},
computed: {
...mapGetters(['fileInfoDetail', 'user']),
favourites() {
return this.user.data.relationships.favourites.data.attributes.folders
},
isInFavourites() {
return this.favourites.find(
(el) => el.id == this.fileInfoDetail[0].id
)
},
isFile() {
return (
this.fileInfoDetail[0] &&
this.fileInfoDetail[0].type !== 'folder' &&
this.fileInfoDetail[0] &&
this.fileInfoDetail[0].type !== 'image'
)
},
isImage() {
return this.fileInfoDetail[0] && this.fileInfoDetail[0].type === 'image'
},
isFolder() {
return this.fileInfoDetail[0] && this.fileInfoDetail[0].type === 'folder'
}
},
data() {
return {
isVisible: false,
showFromMediaPreview: false
}
},
methods: {
downloadFolder(){
this.$store.dispatch( 'downloadFolder' , this.fileInfoDetail[0] )
},
moveItem() {
events.$emit('popup:open', { name: 'move', item: [this.fileInfoDetail[0]] })
},
shareItem() {
if (this.fileInfoDetail[0].shared) {
// Open share item popup
events.$emit('popup:open', {
name: 'share-edit',
item: this.fileInfoDetail[0]
})
} else {
// Open share item popup
events.$emit('popup:open', {
name: 'share-create',
item: this.fileInfoDetail[0]
})
}
},
addToFavourites() {
if (
this.favourites &&
!this.favourites.find(
(el) => el.id == this.fileInfoDetail[0].id
)
) {
this.$store.dispatch('addToFavourites', this.fileInfoDetail[0])
} else {
this.$store.dispatch('removeFromFavourites', this.fileInfoDetail[0])
}
},
downloadItem() {
this.$downloadFile(
this.fileInfoDetail[0].file_url,
this.fileInfoDetail[0].name + '.' + this.fileInfoDetail[0].mimetype
)
},
deleteItem() {
this.$store.dispatch('deleteItem')
},
renameItem() {
events.$emit('popup:open', { name: 'rename-item', item: this.fileInfoDetail[0] })
},
closeAndResetContextMenu() {
//If emit to show menu coming from MediaFullPreview dont reset data
this.isVisible = false
this.showFromMediaPreview = false
events.$emit('hide:mobile-navigation')
}
},
created() {
events.$on('mobileMenu:show', showFromMedia => {
// If emit come from MediaFullPreview
if (showFromMedia) {
this.isVisible = true
this.showFromMediaPreview = true
} else {
this.isVisible = !this.isVisible
}
})
// Hide mobile menu
events.$on('mobileMenu:hide', () => {
this.isVisible = false
})
}
}
</script>
<style scoped lang="scss">
@import "@assets/vuefilemanager/_variables";
@import "@assets/vuefilemanager/_mixins";
.mobile-selected-menu {
display: flex;
margin-left: 15px;
margin-right: 15px;
.close-icon {
margin-left: auto !important;
}
}
.menu-option {
display: flex;
align-items: center;
.icon {
margin-right: 20px;
line-height: 0;
}
.text-label {
@include font-size(16);
}
}
.vignette {
background: rgba(0, 0, 0, 0.35);
position: absolute;
top: 0;
right: 0;
bottom: 0;
left: 0;
z-index: 9;
cursor: pointer;
opacity: 1;
}
.options {
position: absolute;
bottom: 0;
left: 0;
right: 0;
z-index: 99;
overflow: hidden;
background: white;
border-top-left-radius: 12px;
border-top-right-radius: 12px;
&.showed {
display: block;
}
.item-thumbnail {
padding: 20px 20px 10px;
margin-bottom: 0px;
}
.menu-options {
margin-top: 10px;
list-style: none;
width: 100%;
.menu-option-group {
padding: 5px 0;
border-bottom: 1px solid $light_mode_border;
&:first-child {
padding-top: 0;
}
&:last-child {
padding-bottom: 0;
border-bottom: none;
}
}
.menu-option {
font-weight: 700;
letter-spacing: 0.15px;
@include font-size(14);
cursor: pointer;
width: 100%;
padding: 17px 20px;
text-align: center;
&:last-child {
border: none;
}
}
}
}
@media (prefers-color-scheme: dark) {
.vignette {
background: $dark_mode_vignette;
}
.options {
background: $dark_mode_foreground;
.menu-options {
background: $dark_mode_foreground;
.menu-option-group {
border-color: $dark_mode_border_color;
}
.menu-option {
color: $dark_mode_text_primary;
}
}
}
}
// Transition
.context-menu-enter-active,
.fade-enter-active {
transition: all 200ms;
}
.context-menu-leave-active,
.fade-leave-active {
transition: all 200ms;
}
.fade-enter,
.fade-leave-to {
opacity: 0;
}
.context-menu-enter,
.context-menu-leave-to {
opacity: 0;
transform: translateY(100%);
}
.context-menu-leave-active {
position: absolute;
}
</style>
@@ -1,76 +0,0 @@
<template>
<transition v-if="isVisible" name="preview-menu" >
<SortingAndPreviewMenu class="options"/>
</transition>
</template>
<script>
import SortingAndPreviewMenu from '@/components/FilesView/SortingAndPreviewMenu'
import { events } from '@/bus'
export default {
name: 'MobileSortingAndPreview',
components: {SortingAndPreviewMenu},
data () {
return {
isVisible: false
}
},
mounted () {
events.$on('mobileSortingAndPreview', (state) => {
this.isVisible = state
})
}
}
</script>
<style scoped lang="scss">
@import "@assets/vuefilemanager/_variables";
@import "@assets/vuefilemanager/_mixins";
.options {
position: absolute;
bottom: 0;
left: 0;
right: 0;
z-index: 99;
overflow: hidden;
background: white;
border-top-left-radius: 12px;
border-top-right-radius: 12px;
}
@media (prefers-color-scheme: dark) {
.options {
background: $dark_mode_foreground;
}
}
// Transition
.preview-menu-enter-active,
.fade-enter-active {
transition: all 200ms;
}
.preview-menu-leave-active,
.fade-leave-active {
transition: all 200ms;
}
.fade-enter,
.fade-leave-to {
opacity: 0;
}
.preview-menu-enter,
.preview-menu-leave-to {
opacity: 0;
transform: translateY(100%);
}
.preview-menu-leave-active {
position: absolute;
}
</style>
@@ -3,16 +3,18 @@
<!-- Go back-->
<div @click="goBack" class="go-back-button">
<chevron-left-icon size="17" :class="{'is-visible': browseHistory.length > 1}" class="icon-back"></chevron-left-icon>
<chevron-left-icon :class="{'is-visible': browseHistory.length > 1}" size="17" class="icon-back" />
</div>
<!--Folder Title-->
<div class="directory-name">{{ directoryName }}</div>
<div class="directory-name">
{{ directoryName }}
</div>
<!--More Actions-->
<div class="more-actions-button">
<div class="tap-area" @click="showMobileNavigation" v-if="$checkPermission('master')">
<menu-icon size="17"></menu-icon>
<div v-if="$checkPermission('master')" @click="showMobileNavigation" class="tap-area">
<menu-icon size="17" />
</div>
</div>
</div>
@@ -38,9 +40,8 @@
},
computed: {
...mapGetters([
'fileInfoVisible',
'isVisibleSidebar',
'FilePreviewType',
'fileInfoDetail',
'currentFolder',
'browseHistory',
'homeDirectory',
@@ -51,11 +52,10 @@
},
methods: {
showMobileNavigation() {
events.$emit('show:mobile-navigation')
events.$emit('mobile-menu:show', 'user-navigation')
events.$emit('mobileSelecting:stop')
},
goBack() {
let previousFolder = last(this.browseHistory)
if (previousFolder.location === 'trash-root') {
@@ -1,31 +1,31 @@
<template>
<transition name="context-menu">
<div class="multiselect-actions" v-if="mobileMultiSelect">
<ToolbarButton class="action-btn" v-if="!$isThisLocation(['trash', 'trash-root' , 'shared', 'latest']) && $checkPermission('master') || $checkPermission('editor')" source="move" :action="$t('actions.move')" :class="{'is-inactive' : fileInfoDetail.length < 1}" @click.native="moveItem"/>
<ToolbarButton class="action-btn" v-if="!$isThisLocation(['trash', 'trash-root' , 'shared', 'latest']) && $checkPermission('master') || $checkPermission('editor')" source="move" :action="$t('actions.move')" :class="{'is-inactive' : clipboard.length < 1}" @click.native="moveItem" />
<ToolbarButton class="action-btn" v-if="!$isThisLocation(['shared']) && $checkPermission('master') || $checkPermission('editor')" source="trash" :class="{'is-inactive' : fileInfoDetail.length < 1}" :action="$t('actions.delete')" @click.native="deleteItem"/>
<ToolbarButton class="action-btn" v-if="!$isThisLocation(['shared']) && $checkPermission('master') || $checkPermission('editor')" source="trash" :class="{'is-inactive' : clipboard.length < 1}" :action="$t('actions.delete')" @click.native="deleteItem" />
<ToolbarButton class="action-btn" v-if="!$isThisLocation(['shared'])" source="download" :class="{'is-inactive': canDownloadItems}" :action="$t('actions.delete')" @click.native="downloadItem"/>
<ToolbarButton class="action-btn" v-if="!$isThisLocation(['shared'])" source="download" :class="{'is-inactive': canDownloadItems}" :action="$t('actions.delete')" @click.native="downloadItem" />
<ToolbarButton class="action-btn" source="shared-off" @click.native="shareCancel" v-if="$isThisLocation(['shared'])"/>
<ToolbarButton class="action-btn" source="shared-off" @click.native="shareCancel" v-if="$isThisLocation(['shared'])" />
<ToolbarButton class="action-btn close-icon" source="close" :action="$t('actions.close')" @click.native="closeSelecting"/>
<ToolbarButton class="action-btn close-icon" source="close" :action="$t('actions.close')" @click.native="closeSelecting" />
</div>
</transition>
</template>
<script>
import ToolbarButton from '@/components/FilesView/ToolbarButton'
import { events } from '@/bus'
import { mapGetters } from 'vuex'
import {events} from '@/bus'
import {mapGetters} from 'vuex'
export default {
name: 'MobileMultiSelectMenu',
components: { ToolbarButton },
name: 'MultiSelectToolbarMobile',
components: {ToolbarButton},
computed: {
...mapGetters(['fileInfoDetail']),
...mapGetters(['clipboard']),
canDownloadItems() {
return this.fileInfoDetail.filter(item => item.type === 'folder').length !== 0
return this.clipboard.filter(item => item.type === 'folder').length !== 0
}
},
data() {
@@ -34,7 +34,7 @@ export default {
}
},
methods: {
shareCancel() {
shareCancel() {
this.$store.dispatch('shareCancel')
this.closeSelecting()
},
@@ -42,16 +42,16 @@ export default {
events.$emit('mobileSelecting:stop')
},
downloadItem() {
if (this.fileInfoDetail.length > 1)
if (this.clipboard.length > 1)
this.$store.dispatch('downloadFiles')
else {
this.$downloadFile(this.fileInfoDetail[0].file_url, this.fileInfoDetail[0].name + '.' + this.fileInfoDetail[0].mimetype)
this.$downloadFile(this.clipboard[0].file_url, this.clipboard[0].name + '.' + this.clipboard[0].mimetype)
}
this.closeSelecting()
},
moveItem() {
// Open move item popup
events.$emit('popup:open', { name: 'move', item: [this.fileInfoDetail[0]] })
events.$emit('popup:open', {name: 'move', item: [this.clipboard[0]]})
},
deleteItem() {
//Delete items
@@ -120,37 +120,37 @@ export default {
}
}
@media (prefers-color-scheme: dark) {
@media (prefers-color-scheme: dark) {
.multiselect-actions {
background: $dark_mode_foreground;
}
.multiselect-actions {
background: $dark_mode_foreground;
}
}
// Transition
.context-menu-enter-active,
.fade-enter-active {
transition: all 200ms;
}
// Transition
.context-menu-enter-active,
.fade-enter-active {
transition: all 200ms;
}
.context-menu-leave-active,
.fade-leave-active {
transition: all 200ms;
}
.context-menu-leave-active,
.fade-leave-active {
transition: all 200ms;
}
.fade-enter,
.fade-leave-to {
opacity: 0;
}
.fade-enter,
.fade-leave-to {
opacity: 0;
}
.context-menu-enter,
.context-menu-leave-to {
opacity: 0;
transform: translateY(100%);
}
.context-menu-enter,
.context-menu-leave-to {
opacity: 0;
transform: translateY(100%);
}
.context-menu-leave-active {
position: absolute;
}
.context-menu-leave-active {
position: absolute;
}
</style>
+94 -25
View File
@@ -1,32 +1,64 @@
<template>
<li class="menu-option group">
<div class="icon group-hover-text-theme">
<calendar-icon v-if="icon === 'calendar'" size="17" class="group-hover-text-theme"/>
<grid-icon v-if="icon === 'grid'" size="17" class="group-hover-text-theme"/>
<list-icon v-if="icon === 'list'" size="17" class="group-hover-text-theme"/>
<trash-2-icon v-if="icon === 'trash'" size="17" class="group-hover-text-theme"/>
<life-buoy-icon v-if="icon === 'restore'" size="17" class="group-hover-text-theme"/>
<trash-icon v-if="icon === 'empty-trash'" size="17" class="group-hover-text-theme"/>
<eye-icon v-if="icon ==='detail'" size="17" class="group-hover-text-theme"/>
<download-cloud-icon v-if="icon === 'download'" size="17" class="group-hover-text-theme"/>
<edit2-icon v-if="icon === 'rename'" size="17" class="group-hover-text-theme"/>
<corner-down-right-icon v-if="icon === 'move-item'" size="17" class="group-hover-text-theme"/>
<link-icon v-if="icon === 'share'" size="17" class="group-hover-text-theme"/>
<star-icon v-if="icon === 'favourites'" size="17" class="group-hover-text-theme"/>
<folder-plus-icon v-if="icon === 'create-folder'" size="17" class="group-hover-text-theme"/>
<smile-icon v-if="icon === 'no-options'" size="17" class="group-hover-text-theme"/>
<paperclip-icon v-if="icon === 'zip-folder'" size="17" class="group-hover-text-theme"/>
<alphabet-icon v-if="icon === 'alphabet'" size="17" class="group-hover-text-theme"/>
<li class="menu-option group" :class="{'hover-disabled': isHoverDisabled}">
<div class="icon-left group-hover-text-theme">
<calendar-icon v-if="icon === 'calendar'" size="17" class="group-hover-text-theme" :class="{'text-theme': isActive}"/>
<grid-icon v-if="icon === 'grid'" size="17" class="group-hover-text-theme" :class="{'text-theme': isActive}"/>
<list-icon v-if="icon === 'list'" size="17" class="group-hover-text-theme" :class="{'text-theme': isActive}"/>
<trash-2-icon v-if="icon === 'trash'" size="17" class="group-hover-text-theme" :class="{'text-theme': isActive}"/>
<life-buoy-icon v-if="icon === 'restore'" size="17" class="group-hover-text-theme" :class="{'text-theme': isActive}"/>
<trash-icon v-if="icon === 'empty-trash'" size="17" class="group-hover-text-theme" :class="{'text-theme': isActive}"/>
<eye-icon v-if="icon ==='detail'" size="17" class="group-hover-text-theme" :class="{'text-theme': isActive}"/>
<download-cloud-icon v-if="icon === 'download'" size="17" class="group-hover-text-theme" :class="{'text-theme': isActive}"/>
<edit2-icon v-if="icon === 'rename'" size="17" class="group-hover-text-theme" :class="{'text-theme': isActive}"/>
<corner-down-right-icon v-if="icon === 'move-item'" size="17" class="group-hover-text-theme" :class="{'text-theme': isActive}"/>
<link-icon v-if="icon === 'share'" size="17" class="group-hover-text-theme" :class="{'text-theme': isActive}"/>
<star-icon v-if="icon === 'favourites'" size="17" class="group-hover-text-theme" :class="{'text-theme': isActive}"/>
<folder-plus-icon v-if="icon === 'create-folder'" size="17" class="group-hover-text-theme" :class="{'text-theme': isActive}"/>
<smile-icon v-if="icon === 'no-options'" size="17" class="group-hover-text-theme" :class="{'text-theme': isActive}"/>
<paperclip-icon v-if="icon === 'zip-folder'" size="17" class="group-hover-text-theme" :class="{'text-theme': isActive}"/>
<alphabet-icon v-if="icon === 'alphabet'" size="17" class="group-hover-text-theme" :class="{'text-theme': isActive}"/>
<star-icon v-if="icon === 'star'" size="17" class="group-hover-text-theme" :class="{'text-theme': isActive}"/>
<hard-drive-icon v-if="icon === 'hard-drive'" size="17" class="group-hover-text-theme" :class="{'text-theme': isActive}"/>
<upload-cloud-icon v-if="icon === 'upload-cloud'" size="17" class="group-hover-text-theme" :class="{'text-theme': isActive}"/>
<users-icon v-if="icon === 'users'" size="17" class="group-hover-text-theme" :class="{'text-theme': isActive}"/>
<user-icon v-if="icon === 'user'" size="17" class="group-hover-text-theme" :class="{'text-theme': isActive}"/>
<settings-icon v-if="icon === 'settings'" size="17" class="group-hover-text-theme" :class="{'text-theme': isActive}"/>
<power-icon v-if="icon === 'power'" size="17" class="group-hover-text-theme" :class="{'text-theme': isActive}"/>
<lock-icon v-if="icon === 'lock'" size="17" class="group-hover-text-theme" :class="{'text-theme': isActive}"/>
<cloud-icon v-if="icon === 'cloud'" size="17" class="group-hover-text-theme" :class="{'text-theme': isActive}"/>
<credit-card-icon v-if="icon === 'credit-card'" size="17" class="group-hover-text-theme" :class="{'text-theme': isActive}"/>
<file-text-icon v-if="icon === 'file-text'" size="17" class="group-hover-text-theme" :class="{'text-theme': isActive}"/>
<database-icon v-if="icon === 'database'" size="17" class="group-hover-text-theme" :class="{'text-theme': isActive}"/>
<globe-icon v-if="icon === 'globe'" size="17" class="group-hover-text-theme" :class="{'text-theme': isActive}"/>
<monitor-icon v-if="icon === 'monitor'" size="17" class="group-hover-text-theme" :class="{'text-theme': isActive}"/>
<box-icon v-if="icon === 'box'" size="17" class="group-hover-text-theme" :class="{'text-theme': isActive}"/>
</div>
<div class="text-label group-hover-text-theme">
<div class="text-label group-hover-text-theme" :class="{'text-theme': isActive}">
{{ title }}
</div>
<div v-if="isArrowRight" class="icon-right group-hover-text-theme">
<chevron-right-icon size="17" class="group-hover-text-theme" :class="{'text-theme': isActive}"/>
</div>
</li>
</template>
<script>
import AlphabetIcon from '@/components/FilesView/Icons/AlphabetIcon'
import {
ChevronRightIcon,
BoxIcon,
MonitorIcon,
GlobeIcon,
FileTextIcon,
CreditCardIcon,
CloudIcon,
LockIcon,
PowerIcon,
SettingsIcon,
UsersIcon,
UserIcon,
UploadCloudIcon,
HardDriveIcon,
CornerDownRightIcon,
DownloadCloudIcon,
FolderPlusIcon,
@@ -42,28 +74,50 @@ import {
GridIcon,
ListIcon,
CalendarIcon,
DatabaseIcon,
} from 'vue-feather-icons'
export default {
name: 'Option',
props:['title' , 'icon'],
props:[
'isHoverDisabled',
'isArrowRight',
'isActive',
'title',
'icon'
],
components: {
BoxIcon,
MonitorIcon,
GlobeIcon,
DatabaseIcon,
ChevronRightIcon,
FileTextIcon,
CreditCardIcon,
CloudIcon,
LockIcon,
CornerDownRightIcon,
DownloadCloudIcon,
UploadCloudIcon,
FolderPlusIcon,
HardDriveIcon,
PaperclipIcon,
SettingsIcon,
LifeBuoyIcon,
CalendarIcon,
AlphabetIcon,
Trash2Icon,
SmileIcon,
PowerIcon,
UsersIcon,
Edit2Icon,
TrashIcon,
LinkIcon,
StarIcon,
EyeIcon,
GridIcon,
ListIcon,
CalendarIcon,
UserIcon,
EyeIcon,
}
}
</script>
@@ -83,7 +137,21 @@ import {
display: flex;
align-items: center;
.icon {
.icon-right {
vertical-align: middle;
text-align: right;
width: 100%;
svg {
@include transform(translateY(3px));
}
polyline {
color: inherit;
}
}
.icon-left {
margin-right: 20px;
line-height: 0;
@@ -101,16 +169,17 @@ import {
@include font-size(16);
}
&:hover {
&:hover:not(.hover-disabled) {
background: $light_background;
}
}
@media (prefers-color-scheme: dark) {
.menu-option {
color: $dark_mode_text_primary;
&:hover {
&:hover:not(.hover-disabled) {
background: lighten($dark_mode_foreground, 2%);
}
}
+13 -35
View File
@@ -7,29 +7,29 @@
<x-icon class="pointer" size="19" />
</div>
<input
v-model="query"
class="query focus-border-theme"
type="text"
name="query"
:placeholder="$t('inputs.placeholder_search_files')"
v-model="query"
@input="$emit('input', query)"
class="query focus-border-theme"
type="text"
:placeholder="placeholder"
/>
</div>
</template>
<script>
import { SearchIcon, XIcon } from 'vue-feather-icons'
import {mapGetters} from 'vuex'
import {debounce} from 'lodash'
import {SearchIcon, XIcon} from 'vue-feather-icons'
import {events} from '@/bus'
export default {
name: 'SearchBar',
name: 'DesktopSearchBar',
props: [
'placeholder'
],
components: {
SearchIcon,
XIcon,
},
computed: {
...mapGetters(['currentFolder']),
isQuery() {
return this.query !== '' && typeof this.query !== 'undefined'
}
@@ -39,36 +39,14 @@
query: ''
}
},
watch: {
query(val) {
return this.getResult(val)
}
},
methods: {
resetQuery() {
this.query = ''
},
getResult: debounce(function (value) {
if (this.isQuery) {
// Get search result if query is not empty
this.$store.dispatch('getSearchResult', value)
} else if (typeof value !== 'undefined') {
if (this.currentFolder) {
// Get back after delete query to previosly folder
if ( this.$isThisLocation('public') ) {
this.$store.dispatch('browseShared', [{folder: this.currentFolder, back: true, init: false}])
} else {
this.$store.dispatch('getFolder', [{folder: this.currentFolder, back: true, init: false}])
}
}
this.$store.commit('CHANGE_SEARCHING_STATE', false)
}
}, 300)
this.$emit('reset-query')
}
},
created() {
events.$on('clear-query', () => (this.query = undefined))
events.$on('clear-query', () => this.query = undefined)
}
}
</script>
@@ -1,26 +1,41 @@
<template>
<div class="wrapper">
<div class="icon-wrapper">
<CheckSquareIcon class="icon text-theme" size="21" />
<div class="icon-wrapper">
<CheckSquareIcon v-if="icon === 'check-square'" class="icon text-theme" size="21" />
<image-icon v-if="icon === 'image'" class="icon text-theme" size="21" />
<video-icon v-if="icon === 'video'" class="icon text-theme" size="21" />
<folder-icon v-if="icon === 'folder'" class="icon text-theme" size="21" />
<file-icon v-if="icon === 'file'" class="icon text-theme" size="21" />
</div>
<div class="text">
<span class="title">{{ title }}</span>
<span class="count">{{ subtitle }}</span>
<span class="subtitle">{{ subtitle }}</span>
</div>
</div>
</template>
<script>
import {CheckSquareIcon} from "vue-feather-icons"
import {
CheckSquareIcon,
FolderIcon,
ImageIcon,
VideoIcon,
FileIcon,
} from "vue-feather-icons"
export default {
name: 'MultiSelected',
name: 'TitlePreview',
props: [
'subtitle',
'title',
'subtitle'
'icon',
],
components: {
CheckSquareIcon
CheckSquareIcon,
FolderIcon,
ImageIcon,
VideoIcon,
FileIcon,
},
}
</script>
@@ -31,7 +46,7 @@ export default {
.wrapper {
display: flex;
justify-content: center;
align-items: flex-start;
.text {
padding-left: 10px;
@@ -46,7 +61,7 @@ export default {
color: $text;
}
.count {
.subtitle {
@include font-size(12);
font-weight: 600;
color: $text-muted;
@@ -55,17 +70,8 @@ export default {
}
.icon-wrapper {
display: inline-flex;
align-items: center;
justify-content: center;
padding: 0;
text-align: center;
cursor: pointer;
white-space: nowrap;
outline: none;
border: none;
polyline, path {
polyline, path, rect, circle, polyline {
color: inherit;
}
}
@@ -78,7 +84,7 @@ export default {
color: $dark_mode_text_primary;
}
.count {
.subtitle {
color: $dark_mode_text_secondary;
}
}
@@ -1,20 +1,10 @@
<template>
<button class="button hover-text-theme hover-svg-stroke-theme" :title="action">
<corner-down-right-icon
v-if="source === 'move'"
size="19"
class="hover-text-theme"
/>
<download-cloud-icon
v-if="source === 'download'"
size="19"
class="hover-text-theme"
/>
<folder-plus-icon
v-if="source === 'folder-plus'"
size="19"
class="hover-text-theme"
/>
<corner-down-right-icon v-if="source === 'move'" size="19" class="hover-text-theme" />
<download-cloud-icon v-if="source === 'download'" size="19" class="hover-text-theme" />
<folder-plus-icon v-if="source === 'folder-plus'" size="19" class="hover-text-theme" />
<zoom-in-icon v-if="source === 'zoom-in'" size="19" />
<zoom-out-icon v-if="source === 'zoom-out'" size="19" />
<edit-2-icon v-if="source === 'rename'" size="19" />
<printer-icon v-if="source === 'print'" size="19" />
<trash-2-icon v-if="source === 'trash'" size="19" />
@@ -24,46 +14,53 @@
<link-icon v-if="source === 'share'" size="19" />
<x-icon v-if="source === 'close'" size="19" />
<cloud-off-icon v-if="source === 'shared-off'" size="19" />
<sorting-and-preview-icon v-if="source === 'preview-sorting'" size="19" class="preview-sorting"/>
<sorting-icon v-if="source === 'preview-sorting'" class="preview-sorting"/>
</button>
</template>
<script>
import {
FolderPlusIcon,
Trash2Icon,
GridIcon,
ListIcon,
Edit2Icon,
InfoIcon,
CornerDownRightIcon,
LinkIcon,
DownloadCloudIcon,
XIcon,
PrinterIcon,
CloudOffIcon,
} from "vue-feather-icons";
import SortingAndPreviewIcon from '@/components/FilesView/Icons/SortingAndPreviewIcon'
export default {
name: "ToolbarButton",
props: ["source", "action"],
components: {
SortingAndPreviewIcon,
import SortingIcon from '@/components/FilesView/Icons/SortingIcon'
import {
CornerDownRightIcon,
DownloadCloudIcon,
FolderPlusIcon,
CloudOffIcon,
PrinterIcon,
ZoomOutIcon,
ZoomInIcon,
Trash2Icon,
Edit2Icon,
ListIcon,
XIcon,
GridIcon,
ListIcon,
InfoIcon,
LinkIcon,
},
};
XIcon,
} from "vue-feather-icons";
export default {
name: "ToolbarButton",
props: [
'source',
'action'
],
components: {
SortingIcon,
CornerDownRightIcon,
DownloadCloudIcon,
FolderPlusIcon,
CloudOffIcon,
PrinterIcon,
ZoomOutIcon,
ZoomInIcon,
Trash2Icon,
Edit2Icon,
ListIcon,
GridIcon,
InfoIcon,
LinkIcon,
XIcon,
},
};
</script>
<style scoped lang="scss">
@@ -1,145 +0,0 @@
<template>
<ul class="link-group">
<router-link :to="{name: link.routeName}" v-for="(link, i) in navigation" :key="i" v-if="link.isVisible" :class="link.icon" class="link-item" @click.native="$emit('menu', link.icon)">
<div class="menu-icon">
<hard-drive-icon v-if="link.icon === 'hard-drive'" size="17"></hard-drive-icon>
<share-icon v-if="link.icon === 'share'" size="17"></share-icon>
<trash2-icon v-if="link.icon === 'trash'" size="17"></trash2-icon>
<power-icon v-if="link.icon === 'power'" size="17"></power-icon>
<settings-icon v-if="link.icon === 'settings'" size="17"></settings-icon>
<upload-cloud-icon v-if="link.icon === 'latest'" size="17"></upload-cloud-icon>
<user-icon v-if="link.icon === 'user'" size="17"></user-icon>
<users-icon v-if="link.icon === 'users'" size="17"></users-icon>
<lock-icon v-if="link.icon === 'lock'" size="17"></lock-icon>
<file-text-icon v-if="link.icon === 'file-text'" size="17"></file-text-icon>
<database-icon v-if="link.icon === 'database'" size="17"></database-icon>
<credit-card-icon v-if="link.icon === 'credit-card'" size="17"></credit-card-icon>
<cloud-icon v-if="link.icon === 'cloud'" size="17"></cloud-icon>
<monitor-icon v-if="link.icon === 'monitor'" size="17"></monitor-icon>
<box-icon v-if="link.icon === 'box'" size="17"></box-icon>
<globe-icon v-if="link.icon === 'language'" size="17"></globe-icon>
</div>
<b class="menu-link">
<span>{{ link.title }}</span>
<chevron-right-icon size="15" class="arrow-right"></chevron-right-icon>
</b>
</router-link>
</ul>
</template>
<script>
import {
ChevronRightIcon,
UploadCloudIcon,
CreditCardIcon,
HardDriveIcon,
FileTextIcon,
SettingsIcon,
DatabaseIcon,
MonitorIcon,
Trash2Icon,
CloudIcon,
PowerIcon,
GlobeIcon,
ShareIcon,
UsersIcon,
UserIcon,
LockIcon,
BoxIcon,
} from 'vue-feather-icons'
export default {
name: 'MenuBar',
components: {
BoxIcon,
MonitorIcon,
ChevronRightIcon,
UploadCloudIcon,
CreditCardIcon,
HardDriveIcon,
DatabaseIcon,
FileTextIcon,
SettingsIcon,
Trash2Icon,
CloudIcon,
PowerIcon,
GlobeIcon,
UsersIcon,
ShareIcon,
LockIcon,
UserIcon,
},
props: [
'navigation'
],
}
</script>
<style scoped lang="scss">
@import '@assets/vuefilemanager/_variables';
@import '@assets/vuefilemanager/_mixins';
.link-item {
display: flex;
text-decoration: none;
padding: 17px 0;
width: 100%;
&.power {
.menu-icon {
path, line, polyline, rect, circle, ellipse {
stroke: $red;
}
}
.menu-link {
color: $red;
}
}
.menu-icon {
display: block;
margin-right: 20px;
svg {
margin-top: -1px;
vertical-align: middle;
}
path, line, polyline, rect, circle, ellipse {
stroke: $text;
}
}
.menu-link {
width: 100%;
display: flex;
justify-content: space-between;
align-items: center;
color: $text;
span {
@include font-size(14);
}
}
}
@media (prefers-color-scheme: dark) {
.link-item {
.menu-icon {
path, line, polyline, rect, circle, ellipse {
stroke: $dark_mode_text_primary;
}
}
.menu-link {
color: $dark_mode_text_primary;
}
}
}
</style>
@@ -0,0 +1,93 @@
<template>
<transition name="context-menu">
<div v-if="isVisible" @click="closeMenu" class="options">
<slot></slot>
</div>
</transition>
</template>
<script>
import {events} from '@/bus'
export default {
name: 'MenuMobile',
props: [
'name'
],
data() {
return {
isVisible: false,
}
},
methods: {
closeMenu() {
this.isVisible = false
events.$emit('mobile-menu:hide')
}
},
created() {
events.$on('mobile-menu:show', name => {
if (name === this.name)
this.isVisible = !this.isVisible
})
events.$on('mobile-menu:hide', () => this.isVisible = false)
}
}
</script>
<style scoped lang="scss">
@import "@assets/vuefilemanager/_variables";
@import "@assets/vuefilemanager/_mixins";
.options {
position: absolute;
padding-bottom: 12px;
bottom: 0;
left: 0;
right: 0;
z-index: 99;
overflow: hidden;
background: white;
border-top-left-radius: 12px;
border-top-right-radius: 12px;
&.showed {
display: block;
}
}
// Transition
.context-menu-enter-active,
.fade-enter-active {
transition: all 300ms;
}
.context-menu-leave-active,
.fade-leave-active {
transition: all 300ms;
}
.fade-enter,
.fade-leave-to {
opacity: 0;
}
.context-menu-enter,
.context-menu-leave-to {
opacity: 0;
transform: translateY(100%);
}
.context-menu-leave-active {
position: absolute;
}
@media (prefers-color-scheme: dark) {
.options {
background: $dark_mode_foreground;
}
}
</style>
@@ -0,0 +1,35 @@
<template>
<div class="menu-options">
<slot></slot>
</div>
</template>
<script>
import {mapGetters} from 'vuex'
export default {
name: 'MenuMobileGroup',
components: {
},
computed: {
...mapGetters(['config']),
},
data() {
return {
}
}
}
</script>
<style scoped lang="scss">
@import "@assets/vuefilemanager/_variables";
@import "@assets/vuefilemanager/_mixins";
.menu-options {
margin-top: 10px;
list-style: none;
width: 100%;
}
</style>
@@ -3,7 +3,7 @@
<!-- Go back-->
<div @click="goBack" class="go-back">
<chevron-left-icon size="17" class="icon"></chevron-left-icon>
<chevron-left-icon size="17" class="icon" />
</div>
<!--Folder Title-->
@@ -25,7 +25,7 @@
} from 'vue-feather-icons'
export default {
name: 'MenuBar',
name: 'MobileHeader',
props: [
'title'
],
@@ -35,7 +35,7 @@
},
methods: {
showMobileNavigation() {
events.$emit('show:mobile-navigation')
events.$emit('mobile-menu:show', 'user-navigation')
},
goBack() {
this.$router.back();
@@ -1,32 +1,24 @@
<template>
<div @contextmenu.prevent.capture="contextMenu($event, undefined)"
id="files-view">
<ContextMenu/>
<DesktopSortingAndPreview/>
<DesktopToolbar/>
<div @contextmenu.prevent.capture="contextMenu($event, undefined)" id="files-view">
<DesktopToolbar/>
<ContextMenu />
<FileBrowser/>
</div>
</template>
<script>
import DesktopSortingAndPreview from '@/components/FilesView/DesktopSortingAndPreview'
import DesktopToolbar from '@/components/FilesView/DesktopToolbar'
import FileBrowser from '@/components/FilesView/FileBrowser'
import ContextMenu from '@/components/FilesView/ContextMenu'
import {mapGetters} from 'vuex'
import ContextMenu from '@/components/FilesView/ContextMenu'
import {events} from '@/bus'
export default {
name: 'FilesView',
components: {
DesktopSortingAndPreview,
DesktopToolbar,
FileBrowser,
ContextMenu,
},
computed: {
...mapGetters(['config']),
},
methods: {
contextMenu(event, item) {
events.$emit('contextMenu:show', event, item)
@@ -36,8 +28,6 @@
</script>
<style lang="scss">
@import '@assets/vuefilemanager/_variables';
@import '@assets/vuefilemanager/_mixins';
#files-view {
font-family: 'Nunito', sans-serif;
@@ -55,7 +55,7 @@
import {events} from '@/bus'
export default {
name: 'CreateFolder',
name: 'CreateFolderPopup',
components: {
ValidationProvider,
ValidationObserver,
@@ -0,0 +1,61 @@
<template>
<div v-if="canBePreview" class="preview">
<img v-if="clipboard[0].type == 'image' && clipboard[0].thumbnail" :src="clipboard[0].thumbnail" :alt="clipboard[0].name" />
<audio v-else-if="clipboard[0].type == 'audio'" :src="clipboard[0].file_url" controlsList="nodownload" controls></audio>
<video v-else-if="clipboard[0].type == 'video'" controlsList="nodownload" disablePictureInPicture playsinline controls>
<source :src="clipboard[0].file_url" type="video/mp4">
</video>
</div>
</template>
<script>
import { mapGetters } from 'vuex'
import { includes } from 'lodash'
export default {
name: 'FilePreview',
computed: {
...mapGetters(['clipboard']),
canBePreview() {
return this.clipboard[0] && ! includes([
'folder', 'file'
], this.clipboard[0].type)
}
},
}
</script>
<style scoped lang="scss">
@import '@assets/vuefilemanager/_variables';
@import '@assets/vuefilemanager/_mixins';
.preview {
width: 100%;
display: block;
margin-bottom: 7px;
img {
border-radius: 4px;
overflow: hidden;
width: 100%;
object-fit: cover;
}
audio {
width: 100%;
&::-webkit-media-controls-panel {
background-color: $light_background;
}
&::-webkit-media-controls-play-button {
color: $theme;
}
}
video {
width: 100%;
height: auto;
border-radius: 3px;
}
}
</style>
@@ -175,12 +175,12 @@ export default {
@media (prefers-color-scheme: dark) {
.input-wrapper {
background: $dark_mode_foreground;
background: lighten($dark_mode_foreground, 3%);
.email-list {
.email-input {
background: $dark_mode_foreground;
background: lighten($dark_mode_foreground, 3%);
color: $dark_mode_text_primary;
&::placeholder {
@@ -190,6 +190,4 @@ export default {
}
}
}
</style>
@@ -85,7 +85,7 @@
.box-item {
border-color: $dark_mode_border_color;
background: $dark_mode_foreground;
background: lighten($dark_mode_foreground, 3%);
}
}
}
@@ -7,8 +7,8 @@
<!--If is selected-->
<div class="selected" v-if="selected">
<div class="option-icon" v-if="selected.icon">
<user-icon v-if="selected.icon === 'user'" size="14"></user-icon>
<edit2-icon v-if="selected.icon === 'user-edit'" size="14"></edit2-icon>
<user-icon v-if="selected.icon === 'user'" size="14" />
<edit2-icon v-if="selected.icon === 'user-edit'" size="14" />
</div>
<span class="option-value">{{ selected.label }}</span>
</div>
@@ -23,34 +23,64 @@
<!--Options-->
<transition name="slide-in">
<ul class="input-options" v-if="isOpen">
<li class="option-item" @click="selectOption(option)" v-for="(option, i) in options" :key="i">
<div class="option-icon" v-if="option.icon">
<user-icon v-if="option.icon === 'user'" size="14"></user-icon>
<edit2-icon v-if="option.icon === 'user-edit'" size="14"></edit2-icon>
</div>
<span class="option-value">{{ option.label }}</span>
</li>
</ul>
<div class="input-options" v-if="isOpen">
<div v-if="options.length > 5" class="select-search">
<input v-model="query" ref="search" type="text" :placeholder="$t('select_search_placeholder')" class="search-input focus-border-theme">
</div>
<ul class="option-list">
<li class="option-item" @click="selectOption(option)" v-for="(option, i) in optionList" :key="i">
<div class="option-icon" v-if="option.icon">
<user-icon v-if="option.icon === 'user'" size="14" />
<edit2-icon v-if="option.icon === 'user-edit'" size="14" />
</div>
<span class="option-value">{{ option.label }}</span>
</li>
</ul>
</div>
</transition>
</div>
</template>
<script>
import { ChevronDownIcon, Edit2Icon, UserIcon } from 'vue-feather-icons'
import {debounce, omitBy} from "lodash"
export default {
name:'SelectInput',
props: ['options', 'isError', 'default', 'placeholder'],
props: [
'placeholder',
'options',
'isError',
'default',
],
components: {
Edit2Icon,
UserIcon,
ChevronDownIcon
},
watch: {
query: debounce(function (val) {
this.searchedResults = omitBy(this.options, string => {
return !string.label.toLowerCase().includes(val.toLowerCase())
})
}, 200),
},
computed: {
isSearching() {
return this.searchedResults && this.query !== ''
},
optionList() {
return this.isSearching
? this.searchedResults
: this.options
}
},
data() {
return {
searchedResults: undefined,
selected: undefined,
isOpen: false,
query: '',
}
},
methods: {
@@ -67,10 +97,13 @@
},
openMenu() {
this.isOpen = ! this.isOpen
if (this.isOpen) {
this.$nextTick(() => this.$refs.search.focus());
}
},
},
created() {
if (this.default)
this.selected = this.options.find(option => option.value === this.default)
}
@@ -87,6 +120,26 @@
width: 100%;
}
.select-search {
background: white;
position: sticky;
top: 0;
padding: 13px;
.search-input {
border: 1px solid transparent;
background: $light_background;
@include transition(150ms);
@include font-size(14);
border-radius: 8px;
padding: 13px 20px;
appearance: none;
font-weight: 700;
outline: 0;
width: 100%;
}
}
.input-options {
box-shadow: 0 5px 15px rgba(0, 0, 0, 0.12);
background: white;
@@ -184,6 +237,14 @@
@media (prefers-color-scheme: dark) {
.select-search {
background: $dark_mode_foreground;
.search-input {
background: $dark_mode_background;
}
}
.input-area {
background: $dark_mode_foreground;
border-color: $dark_mode_foreground;
@@ -1,199 +1,141 @@
<template>
<div class="mobile-main-navigation" v-if="user">
<transition name="context-menu">
<nav v-if="isVisible" class="mobile-navigation">
<MenuMobile name="user-navigation">
<!--User Info-->
<div class="user-info">
<UserAvatar size="large"/>
<UserHeadline/>
</div>
<!--User avatar-->
<UserHeadline v-if="!clickedSubmenu" class="user-info" />
<!--Navigation-->
<MenuItemList :navigation="navigation" @menu="action"/>
</nav>
</transition>
<transition name="fade">
<div v-show="isVisible" class="vignette" @click="closeAndResetContextMenu"></div>
</transition>
</div>
<!--Go back button-->
<div v-if="clickedSubmenu" @click.stop="showSubmenu(undefined)" class="go-back">
<chevron-left-icon size="19" class="text-theme" />
<span class="title text-theme">{{ backTitle }}</span>
</div>
<!--Menu links-->
<MenuMobileGroup>
<!--Main navigation-->
<OptionGroup v-if="!clickedSubmenu">
<Option @click.native="goToFiles" :title="$t('menu.files')" icon="hard-drive" is-hover-disabled="true"/>
<Option @click.native.stop="showSubmenu('settings')" :title="$t('menu.settings')" icon="user" :is-arrow-right="true" is-hover-disabled="true"/>
<Option v-if="isAdmin" @click.native.stop="showSubmenu('admin')" :title="$t('menu.admin')" icon="settings" :is-arrow-right="true" is-hover-disabled="true"/>
</OptionGroup>
<OptionGroup v-if="!clickedSubmenu">
<Option @click.native="logOut" :title="$t('menu.logout')" icon="power" is-hover-disabled="true" />
</OptionGroup>
<!--Submenu: User settings-->
<OptionGroup v-if="clickedSubmenu === 'settings'">
<Option @click.native="goToRoute('Profile')" :title="$t('menu.profile')" icon="user" is-hover-disabled="true" />
<Option @click.native="goToRoute('Storage')" :title="$t('menu.storage')" icon="hard-drive" is-hover-disabled="true" />
<Option @click.native="goToRoute('Password')" :title="$t('menu.password')" icon="lock" is-hover-disabled="true" />
</OptionGroup>
<OptionGroup v-if="clickedSubmenu === 'settings' && config.isSaaS">
<Option v-if="" @click.native="goToRoute('Subscription')" :title="$t('menu.subscription')" icon="cloud" is-hover-disabled="true" />
<Option @click.native="goToRoute('PaymentMethods')" :title="$t('menu.payment_cards')" icon="credit-card" is-hover-disabled="true" />
<Option @click.native="goToRoute('Invoice')" :title="$t('menu.invoices')" icon="file-text" is-hover-disabled="true" />
</OptionGroup>
<!--Submenu: Admin settings-->
<OptionGroup v-if="clickedSubmenu === 'admin'">
<Option @click.native="goToRoute('Dashboard')" :title="$t('admin_menu.dashboard')" icon="box" is-hover-disabled="true" />
<Option @click.native="goToRoute('Users')" :title="$t('admin_menu.users')" icon="users" is-hover-disabled="true" />
<Option @click.native="goToRoute('AppOthers')" :title="$t('admin_menu.settings')" icon="settings" is-hover-disabled="true" />
<Option @click.native="goToRoute('Pages')" :title="$t('admin_menu.pages')" icon="monitor" is-hover-disabled="true" />
<Option @click.native="goToRoute('Language')" :title="$t('languages')" icon="globe" is-hover-disabled="true" />
</OptionGroup>
<OptionGroup v-if="clickedSubmenu === 'admin' && config.isSaaS">
<Option v-if="" @click.native="goToRoute('Plans')" :title="$t('admin_menu.plans')" icon="database" is-hover-disabled="true" />
<Option @click.native="goToRoute('Invoices')" :title="$t('admin_menu.invoices')" icon="file-text" is-hover-disabled="true" />
</OptionGroup>
</MenuMobileGroup>
</MenuMobile>
</template>
<script>
import MenuMobileGroup from '@/components/Mobile/MenuMobileGroup'
import OptionGroup from '@/components/FilesView/OptionGroup'
import UserHeadline from '@/components/Sidebar/UserHeadline'
import MenuItemList from '@/components/Mobile/MenuItemList'
import UserAvatar from '@/components/Others/UserAvatar'
import MenuMobile from '@/components/Mobile/MenuMobile'
import Option from '@/components/FilesView/Option'
import {ChevronLeftIcon} from 'vue-feather-icons'
import {mapGetters} from 'vuex'
import {events} from '@/bus'
export default {
name: 'UserMobileNavigation',
name: 'MobileNavigation',
components: {
MenuItemList,
ChevronLeftIcon,
MenuMobileGroup,
UserHeadline,
UserAvatar,
OptionGroup,
MenuMobile,
Option,
},
computed: {
...mapGetters(['user', 'homeDirectory']),
navigation() {
return [
{
icon: 'hard-drive',
title: this.$t('menu.files'),
routeName: 'Files',
isVisible: true,
},
{
icon: 'latest',
title: this.$t('menu.latest'),
routeName: 'Files',
isVisible: true,
},
{
icon: 'share',
title: this.$t('menu.shared'),
routeName: 'SharedFiles',
isVisible: true,
},
{
icon: 'trash',
title: this.$t('menu.trash'),
routeName: 'Files',
isVisible: true,
},
{
icon: 'user',
title: this.$t('menu.settings'),
routeName: 'UserProfileMobileMenu',
isVisible: true,
},
{
icon: 'settings',
title: this.$t('menu.admin'),
routeName: 'AdminMobileMenu',
isVisible: this.user.data.attributes.role === 'admin',
},
{
icon: 'power',
title: this.$t('menu.logout'),
routeName: 'LogOut',
isVisible: true,
},
]
...mapGetters([
'homeDirectory',
'config',
'user',
]),
isAdmin() {
return this.user && this.user.data.attributes.role === 'admin'
},
backTitle() {
let location = {
'settings': this.$t('menu.settings'),
'admin': this.$t('menu.admin')
}
return 'Go back from ' + location[this.clickedSubmenu]
}
},
data() {
return {
isVisible: false,
clickedSubmenu: undefined,
}
},
methods: {
action(name) {
if (name === 'latest') {
this.$store.dispatch('getLatest')
}
if (name === 'trash') {
this.$store.dispatch('getTrash')
}
if (name === 'hard-drive') {
this.$store.dispatch('getFolder', [{folder: this.homeDirectory, back: false, init: true}])
}
if (name === 'power') {
this.$store.dispatch('logOut')
}
this.closeAndResetContextMenu()
goToRoute(route) {
this.$router.push({name: route})
this.clickedSubmenu = undefined
},
closeAndResetContextMenu() {
this.isVisible = false
showSubmenu(name) {
this.clickedSubmenu = name
},
goToFiles() {
if (this.$route.name !== 'Files')
this.$router.push({name: 'Files'})
events.$emit('hide:mobile-navigation')
}
},
created() {
events.$on('show:mobile-navigation', () => {
this.isVisible = true
})
this.$store.dispatch('getFolder', [{folder: this.homeDirectory, back: false, init: true}])
},
logOut() {
this.$store.dispatch('logOut')
},
}
}
</script>
<style scoped lang="scss">
@import '@assets/vuefilemanager/_variables';
@import '@assets/vuefilemanager/_mixins';
.mobile-navigation {
padding: 20px;
width: 100%;
position: absolute;
bottom: 0;
left: 0;
right: 0;
z-index: 99;
background: white;
border-top-left-radius: 12px;
border-top-right-radius: 12px;
min-height: 440px;
max-height: 80%;
overflow-y: auto;
}
.vignette {
background: rgba(0, 0, 0, 0.35);
position: absolute;
top: 0;
right: 0;
bottom: 0;
left: 0;
z-index: 9;
cursor: pointer;
opacity: 1;
}
@import "@assets/vuefilemanager/_variables";
@import "@assets/vuefilemanager/_mixins";
.user-info {
padding: 20px 20px 10px;
}
.go-back {
display: flex;
align-items: center;
margin-bottom: 10px;
}
padding: 30px 20px 10px;
cursor: pointer;
@media only screen and (max-width: 690px) {
.title {
@include font-size(14);
font-weight: 700;
margin-left: 10px;
}
}
@media (prefers-color-scheme: dark) {
.mobile-navigation {
background: $dark_mode_background;
polyline {
color: inherit;
}
}
// Transition
.context-menu-enter-active,
.fade-enter-active {
transition: all 200ms;
}
.context-menu-leave-active,
.fade-leave-active {
transition: all 200ms;
}
.fade-enter,
.fade-leave-to {
opacity: 0;
}
.context-menu-enter,
.context-menu-leave-to {
opacity: 0;
transform: translateY(100%);
}
.context-menu-leave-active {
position: absolute;
}
</style>
@@ -11,12 +11,12 @@
<!--Folder tree-->
<div v-if="! isLoadingTree && navigation">
<ThumbnailItem v-if="fileInfoDetail.length < 2 || isSelectedItem" class="item-thumbnail" :item="pickedItem" info="location" />
<ThumbnailItem v-if="clipboard.length < 2 || isSelectedItem" class="item-thumbnail" :item="pickedItem" info="location" />
<MultiSelected class="multiple-selected"
<TitlePreview class="multiple-selected"
:title="$t('file_detail.selected_multiple')"
:subtitle="this.fileInfoDetail.length + ' ' + $tc('file_detail.items', this.fileInfoDetail.length)"
v-if="fileInfoDetail.length > 1 && !isSelectedItem" />
:subtitle="this.clipboard.length + ' ' + $tc('file_detail.items', this.clipboard.length)"
v-if="clipboard.length > 1 && !isSelectedItem" />
<TreeMenu :disabled-by-id="pickedItem" :depth="1" :nodes="items" v-for="items in navigation" :key="items.id" />
</div>
@@ -43,22 +43,23 @@
<script>
import PopupWrapper from '@/components/Others/Popup/PopupWrapper'
import PopupActions from '@/components/Others/Popup/PopupActions'
import MultiSelected from '@/components/FilesView/MultiSelected'
import TitlePreview from '@/components/FilesView/TitlePreview'
import PopupContent from '@/components/Others/Popup/PopupContent'
import PopupHeader from '@/components/Others/Popup/PopupHeader'
import ThumbnailItem from '@/components/Others/ThumbnailItem'
import ButtonBase from '@/components/FilesView/ButtonBase'
import Spinner from '@/components/FilesView/Spinner'
import TreeMenu from '@/components/Others/TreeMenu'
import {isArray, isNull} from 'lodash'
import {mapGetters} from 'vuex'
import {events} from '@/bus'
export default {
name: 'MoveItem',
name: 'MoveItemPopup',
components: {
ThumbnailItem,
TitlePreview,
PopupWrapper,
MultiSelected,
PopupActions,
PopupContent,
PopupHeader,
@@ -67,7 +68,10 @@
Spinner,
},
computed: {
...mapGetters(['navigation', 'fileInfoDetail']),
...mapGetters([
'clipboard',
'navigation',
]),
},
data() {
return {
@@ -83,7 +87,7 @@
if (!this.selectedFolder) return
// Prevent to move items to the same parent
if (this.fileInfoDetail.find(item => item.parent_id === this.selectedFolder.id)) return
if ( isArray(this.selectedFolder) && this.clipboard.find(item => item.parent_id === this.selectedFolder.id)) return
// Move item
if (!this.isSelectedItem) {
@@ -94,9 +98,6 @@
this.$store.dispatch('moveItem', {to_item: this.selectedFolder, isSelectedItem: this.pickedItem})
}
console.log('to item:', this.selectedFolder);
console.log('isSelectedItem:', this.pickedItem);
// Close popup
events.$emit('popup:close')
@@ -106,12 +107,14 @@
},
},
mounted() {
// Select folder in tree
events.$on('pick-folder', folder => {
if (folder.id === this.pickedItem.id) {
this.selectedFolder = undefined
} else if ( ! folder.id && folder.location === 'base') {
this.selectedFolder = 'base'
} else {
this.selectedFolder = folder
}
@@ -131,13 +134,13 @@
})
// Store picked item
if (!this.fileInfoDetail.includes(args.item[0])) {
if (!this.clipboard.includes(args.item[0])) {
this.pickedItem = args.item[0]
this.isSelectedItem = true
}
if (this.fileInfoDetail.includes(args.item[0])) {
this.pickedItem = this.fileInfoDetail[0]
if (this.clipboard.includes(args.item[0])) {
this.pickedItem = this.clipboard[0]
this.isSelectedItem = false
}
})
@@ -165,35 +168,5 @@
.multiple-selected {
padding: 0 20px;;
margin-bottom: 20px;
/deep/ .text {
.title {
color: $text;
}
.count {
color: $text-muted;
}
}
/deep/ .icon-wrapper {
.icon {
stroke: $theme;
}
}
}
@media (prefers-color-scheme: dark) {
.multiple-selected {
/deep/ .text {
.title {
color: $dark_mode_text_primary;
}
.count {
color: $dark_mode_text_secondary;
}
}
}
}
</style>
@@ -1,6 +1,5 @@
<template>
<PopupWrapper>
<div class="popup-image">
<span class="emoji">{{ emoji }}</span>
</div>
@@ -59,7 +59,7 @@ import {required} from 'vee-validate/dist/rules'
import {events} from '@/bus'
export default {
name: 'RenameItem',
name: 'RenameItemPopup',
components: {
ValidationProvider,
ValidationObserver,
@@ -112,7 +112,7 @@ import {events} from '@/bus'
import axios from 'axios'
export default {
name: 'ShareCreate',
name: 'ShareCreatePopup',
components: {
ValidationProvider,
ValidationObserver,
@@ -115,7 +115,7 @@
import axios from 'axios'
export default {
name: 'ShareEdit',
name: 'ShareEditPopup',
components: {
ValidationProvider,
ValidationObserver,
@@ -121,7 +121,6 @@
<script>
import {ChevronUpIcon, ChevronLeftIcon, ChevronRightIcon} from 'vue-feather-icons'
import DatatableCell from '@/components/Others/Tables/DatatableCell'
import {chunk, sortBy} from 'lodash'
import axios from "axios";
export default {
+3 -3
View File
@@ -31,14 +31,14 @@
TreeMenu,
},
computed: {
...mapGetters(['fileInfoDetail']),
...mapGetters(['clipboard']),
indent() {
return { paddingLeft: this.depth * 20 + 'px' }
},
disableId() {
let canBeShow = true
if(this.fileInfoDetail.includes(this.disabledById)){
this.fileInfoDetail.map(item => {
if(this.clipboard.includes(this.disabledById)){
this.clipboard.map(item => {
if(item.id === this.nodes.id) {
canBeShow = false
}
@@ -37,7 +37,7 @@
FolderIcon,
},
computed: {
...mapGetters(['fileInfoDetail']),
...mapGetters(['clipboard']),
disabledFolder() {
let disableFolder = false
@@ -84,12 +84,12 @@
methods: {
dragFinish() {
// Move no selected item
if(!this.fileInfoDetail.includes(this.draggedItem[0])) {
if(!this.clipboard.includes(this.draggedItem[0])) {
this.$store.dispatch('moveItem', {to_item: this.nodes ,noSelectedItem:this.draggedItem[0]})
}
// Move all selected items
if(this.fileInfoDetail.includes(this.draggedItem[0])) {
if(this.clipboard.includes(this.draggedItem[0])) {
this.$store.dispatch('moveItem', {to_item: this.nodes ,noSelectedItem:null})
}
@@ -127,12 +127,12 @@
//Get dragged item
events.$on('dragstart' , (data) => {
//If is dragged item not selected
if(!this.fileInfoDetail.includes(data)) {
if(!this.clipboard.includes(data)) {
this.draggedItem = [data]
}
//If are the dragged items selected
if(this.fileInfoDetail.includes(data)) {
this.draggedItem = this.fileInfoDetail
if(this.clipboard.includes(data)) {
this.draggedItem = this.clipboard
}
})
+5 -7
View File
@@ -26,22 +26,20 @@
methods: {
closePopup() {
events.$emit('popup:close')
events.$emit('mobileMenu:hide')
events.$emit('mobileSortingAndPreview', false)
events.$emit('mobile-menu:hide')
}
},
created() {
// Hide vignette
events.$on('popup:close', () => this.isVisibleVignette = false)
// Show vignette
events.$on('popup:open', () => this.isVisibleVignette = true)
events.$on('mobile-menu:show', () => this.isVisibleVignette = true)
events.$on('alert:open', () => this.isVisibleVignette = true)
events.$on('success:open', () => this.isVisibleVignette = true)
events.$on('confirm:open', () => this.isVisibleVignette = true)
events.$on('mobileSortingAndPreviewVignette', (state) => this.isVisibleVignette = state)
// Hide vignette
events.$on('mobile-menu:hide', () => this.isVisibleVignette = false)
events.$on('popup:close', () => this.isVisibleVignette = false)
}
}
</script>
@@ -57,7 +57,7 @@
} from 'vue-feather-icons'
export default {
name: 'MenuBar',
name: 'SidebarNavigation',
components: {
HardDriveIcon,
SettingsIcon,
@@ -1,18 +1,26 @@
<template>
<div class="user-meta" v-if="user">
<b class="name">{{ user.data.relationships.settings.data.attributes.name }}</b>
<span class="email text-theme">{{ user.data.attributes.email }}</span>
<div class="user-headline">
<UserAvatar size="large"/>
<div class="user-meta">
<b class="name">{{ user.data.relationships.settings.data.attributes.name }}</b>
<span class="email text-theme">{{ user.data.attributes.email }}</span>
</div>
</div>
</template>
<script>
import UserAvatar from '@/components/Others/UserAvatar'
import { mapGetters } from 'vuex'
import {events} from '@/bus'
export default {
name: 'UserHeadline',
components: {
UserAvatar,
},
computed: {
...mapGetters(['user']),
...mapGetters([
'user'
]),
},
}
</script>
@@ -21,6 +29,11 @@
@import '@assets/vuefilemanager/_variables';
@import '@assets/vuefilemanager/_mixins';
.user-headline {
display: flex;
align-items: center;
}
.user-meta {
padding-left: 20px;
@@ -35,12 +48,4 @@
font-weight: 600;
}
}
@media only screen and (max-width: 690px) {
}
@media (prefers-color-scheme: dark) {
}
</style>
+23 -20
View File
@@ -7,6 +7,28 @@ import axios from 'axios'
const Helpers = {
install(Vue) {
Vue.prototype.$searchFiles = debounce(function (value) {
if (value !== '' && typeof value !== 'undefined') {
this.$store.dispatch('getSearchResult', value)
} else if (typeof value !== 'undefined') {
if (this.$store.getters.currentFolder) {
// Get back after delete query to previously folder
if (this.$isThisLocation('public')) {
this.$store.dispatch('browseShared', [{folder: this.$store.getters.currentFolder, back: true, init: false}])
} else {
this.$store.dispatch('getFolder', [{folder: this.$store.getters.currentFolder, back: true, init: false}])
}
}
this.$store.commit('CHANGE_SEARCHING_STATE', false)
}
}, 300)
Vue.prototype.$updateText = debounce(function (route, name, value) {
let enableEmptyInput = ['mimetypes_blacklist', 'google_analytics', 'upload_limit', 'description']
@@ -253,24 +275,6 @@ const Helpers = {
})
}
Vue.prototype.$isMinimalScale = function () {
let sizeType = store.getters.filesViewWidth
return sizeType === 'minimal-scale'
}
Vue.prototype.$isCompactScale = function () {
let sizeType = store.getters.filesViewWidth
return sizeType === 'compact-scale'
}
Vue.prototype.$isFullScale = function () {
let sizeType = store.getters.filesViewWidth
return sizeType === 'full-scale'
}
Vue.prototype.$isSomethingWrong = function () {
events.$emit('alert:open', {
title: i18n.t('popup_error.title'),
@@ -346,8 +350,7 @@ const Helpers = {
// Detect windows
Vue.prototype.$checkOS = function () {
if (navigator.userAgent.indexOf('Windows') != -1) {
let body = document.body
body.classList.add('windows')
document.body.classList.add('windows')
}
}
+1 -1
View File
@@ -91,7 +91,7 @@ Vue.config.productionTip = false;
// Handle position of Drag & Drop Ghost
document.addEventListener('drag', event => {
let multiSelect = document.getElementById('multi-select-ui')
let multiSelect = document.getElementById('drag-ui')
multiSelect.style.top = event.clientY + 20 + 'px'
multiSelect.style.left = event.clientX + 'px'
-22
View File
@@ -1,11 +1,7 @@
import Vue from 'vue'
import Router from 'vue-router'
import i18n from '@/i18n/index'
import store from './store/index'
import AdminMobileMenu from './views/Mobile/AdminMobileMenu'
import UserProfileMobileMenu from './views/Mobile/UserProfileMobileMenu'
Vue.use(Router)
const routesOasis = [
@@ -417,24 +413,6 @@ const routesAdmin = [
}
]
},
{
name: 'AdminMobileMenu',
path: '/admin-menu',
component: AdminMobileMenu,
meta: {
requiresAuth: true,
title: 'routes_title.settings_mobile'
},
},
{
name: 'UserProfileMobileMenu',
path: '/user-menu',
component: UserProfileMobileMenu,
meta: {
requiresAuth: true,
title: 'routes_title.profile_settings'
},
},
]
const routesShared = [
{
+4 -4
View File
@@ -3,7 +3,7 @@ import axios from "axios";
import Vue from "vue";
const defaultState = {
fileInfoPanelVisible: localStorage.getItem('file_info_visibility') === 'true' || false,
isVisibleSidebar: localStorage.getItem('file_info_visibility') === 'true' || false,
FilePreviewType: localStorage.getItem('preview_type') || 'list',
config: undefined,
index: undefined,
@@ -982,7 +982,7 @@ const actions = {
},
fileInfoToggle: (context, visibility = undefined) => {
if (!visibility) {
if (context.state.fileInfoPanelVisible) {
if (context.state.isVisibleSidebar) {
context.commit('FILE_INFO_TOGGLE', false)
} else {
context.commit('FILE_INFO_TOGGLE', true)
@@ -1025,7 +1025,7 @@ const mutations = {
state.config.stripe_public_key = data
},
FILE_INFO_TOGGLE(state, isVisible) {
state.fileInfoPanelVisible = isVisible
state.isVisibleSidebar = isVisible
localStorage.setItem('file_info_visibility', isVisible)
},
@@ -1044,7 +1044,7 @@ const mutations = {
}
const getters = {
fileInfoVisible: state => state.fileInfoPanelVisible,
isVisibleSidebar: state => state.isVisibleSidebar,
FilePreviewType: state => state.FilePreviewType,
expirationList: state => state.expirationList,
homeDirectory: state => state.homeDirectory,
+41 -42
View File
@@ -5,13 +5,15 @@ import router from '@/router'
import i18n from '@/i18n/index'
const defaultState = {
fileInfoDetail: [],
currentFolder: undefined,
navigation: undefined,
isSearching: false,
browseHistory: [],
isLoading: true,
data: [],
browseHistory: [],
clipboard: [],
entries: [],
}
const actions = {
@@ -30,7 +32,7 @@ const actions = {
// Set folder location
payload.folder.location = payload.folder.deleted_at || payload.folder.location === 'trash' ? 'trash' : 'base'
if (! payload.back && !payload.sorting)
if (!payload.back && !payload.sorting)
commit('STORE_PREVIOUS_FOLDER', getters.currentFolder)
let url = payload.folder.location === 'trash'
@@ -77,7 +79,7 @@ const actions = {
})
axios
.get(getters.api + '/browse/latest' )
.get(getters.api + '/browse/latest')
.then(response => {
commit('LOADING_STATE', {loading: false, data: response.data})
events.$emit('scrollTop')
@@ -175,7 +177,6 @@ const actions = {
.catch(() => Vue.prototype.$isSomethingWrong())
},
getFolderTree: ({commit, getters}) => {
return new Promise((resolve, reject) => {
// Get route
@@ -204,19 +205,19 @@ const actions = {
}
const mutations = {
LOADING_STATE(state, payload) {
state.clipboard = []
state.entries = payload.data
state.isLoading = payload.loading
},
UPDATE_FOLDER_TREE(state, tree) {
state.navigation = tree
},
LOADING_STATE(state, payload) {
state.fileInfoDetail= []
state.data = payload.data
state.isLoading = payload.loading
},
FLUSH_FOLDER_HISTORY(state) {
state.browseHistory = []
},
FLUSH_SHARED(state, id) {
state.data.find(item => {
state.entries.find(item => {
if (item.id === id) item.shared = undefined
})
},
@@ -227,13 +228,14 @@ const mutations = {
state.browseHistory.pop()
},
CHANGE_ITEM_NAME(state, updatedFile) {
// Rename filename in file info detail
if (state.fileInfoDetail && state.fileInfoDetail.id === updatedFile.id) {
state.fileInfoDetail = updatedFile
// Rename filename in clipboard
if (state.clipboard && state.clipboard.id === updatedFile.id) {
state.clipboard = updatedFile
}
// Rename item name in data view
state.data.find(item => {
state.entries.find(item => {
if (item.id === updatedFile.id) {
item.name = updatedFile.name
item.color = updatedFile.color ? updatedFile.color : null
@@ -241,60 +243,57 @@ const mutations = {
}
})
},
REMOVE_ITEM_FILEINFO_DETAIL(state,item) {
state.fileInfoDetail = state.fileInfoDetail.filter(element => element.id !== item.id)
},
CLEAR_FILEINFO_DETAIL(state) {
state.fileInfoDetail = []
},
LOAD_FILEINFO_DETAIL(state, item) {
state.fileInfoDetail = []
state.fileInfoDetail.push(item)
},
GET_FILEINFO_DETAIL(state, item) {
let checkData = state.data.find(el => el.id === item.id)
if(state.fileInfoDetail.includes(checkData)) return
state.fileInfoDetail.push(checkData ? checkData : state.currentFolder)
},
SELECT_ALL_FILES(state){
state.fileInfoDetail = state.data
},
CHANGE_SEARCHING_STATE(state, searchState) {
state.isSearching = searchState
},
UPDATE_SHARED_ITEM(state, data) {
state.data.find(item => {
state.entries.find(item => {
if (item.id === data.item_id) item.shared = data
})
},
ADD_NEW_FOLDER(state, folder) {
state.data.unshift(folder)
state.entries.unshift(folder)
},
ADD_NEW_ITEMS(state, items) {
state.data = state.data.concat(items)
state.entries = state.entries.concat(items)
},
REMOVE_ITEM(state, id) {
state.data = state.data.filter(el => el.id !== id)
state.entries = state.entries.filter(el => el.id !== id)
},
INCREASE_FOLDER_ITEM(state, id) {
state.data.map(el => {
state.entries.map(el => {
if (el.id && el.id === id) el.items++
})
},
STORE_CURRENT_FOLDER(state, folder) {
state.currentFolder = folder
},
REMOVE_ITEM_FROM_CLIPBOARD(state, item) {
state.clipboard = state.clipboard.filter(element => element.id !== item.id)
},
ADD_ALL_ITEMS_TO_CLIPBOARD(state) {
state.clipboard = state.entries
},
ADD_ITEM_TO_CLIPBOARD(state, item) {
let selectedItem = state.entries.find(el => el.id === item.id)
if (state.clipboard.includes(selectedItem)) return
state.clipboard.push(selectedItem ? selectedItem : state.currentFolder)
},
CLIPBOARD_CLEAR(state) {
state.clipboard = []
},
}
const getters = {
fileInfoDetail: state => state.fileInfoDetail,
clipboard: state => state.clipboard,
currentFolder: state => state.currentFolder,
browseHistory: state => state.browseHistory,
isSearching: state => state.isSearching,
navigation: state => state.navigation,
isLoading: state => state.isLoading,
data: state => state.data,
entries: state => state.entries,
}
export default {
+11 -13
View File
@@ -41,7 +41,7 @@ const actions = {
let files = []
// get ids of selected files
getters.fileInfoDetail.forEach(file => files.push(file.id))
getters.clipboard.forEach(file => files.push(file.id))
// Get route
let route = getters.sharedDetail
@@ -71,9 +71,9 @@ const actions = {
let itemsToMove = []
let items = [noSelectedItem]
// If coming no selected item dont get items to move from fileInfoDetail
// If coming no selected item dont get items to move from clipboard
if (!noSelectedItem)
items = getters.fileInfoDetail
items = getters.clipboard
items.forEach(data => itemsToMove.push({
'id': data.id,
@@ -82,15 +82,13 @@ const actions = {
// Remove file preview
if (!noSelectedItem)
commit('CLEAR_FILEINFO_DETAIL')
commit('CLIPBOARD_CLEAR')
// Get route
let route = getters.sharedDetail
? `/api/editor/move/${router.currentRoute.params.token}`
: '/api/move'
console.log(to_item);
axios
.post(route, {
to_id: to_item.id ? to_item.id : null,
@@ -269,9 +267,9 @@ const actions = {
let items = [item]
let restoreToHome = false
// If coming no selected item dont get items to restore from fileInfoDetail
// If coming no selected item dont get items to restore from clipboard
if (!item)
items = getters.fileInfoDetail
items = getters.clipboard
// Check if file can be restored to home directory
if (getters.currentFolder.location === 'trash')
@@ -283,7 +281,7 @@ const actions = {
}))
// Remove file preview
commit('CLEAR_FILEINFO_DETAIL')
commit('CLIPBOARD_CLEAR')
axios
.post(getters.api + '/trash/restore', {
@@ -301,9 +299,9 @@ const actions = {
let itemsToDelete = []
let items = [noSelectedItem]
// If coming no selected item dont get items to move from fileInfoDetail
// If coming no selected item dont get items to move from clipboard
if (!noSelectedItem)
items = getters.fileInfoDetail
items = getters.clipboard
items.forEach(data => {
itemsToDelete.push({
@@ -335,7 +333,7 @@ const actions = {
// Remove file preview
if (!noSelectedItem) {
commit('CLEAR_FILEINFO_DETAIL')
commit('CLIPBOARD_CLEAR')
}
// Get route
@@ -387,7 +385,7 @@ const actions = {
commit('LOADING_STATE', {loading: false, data: []})
events.$emit('scrollTop')
commit('CLEAR_FILEINFO_DETAIL')
commit('CLIPBOARD_CLEAR')
})
.catch(() => Vue.prototype.$isSomethingWrong())
}
+2 -2
View File
@@ -95,7 +95,7 @@ const actions = {
let items = [singleItem]
if(!singleItem) {
items = getters.fileInfoDetail
items = getters.clipboard
}
items.forEach(data => {
@@ -119,7 +119,7 @@ const actions = {
// Flush shared data
commit('FLUSH_SHARED', item.id)
commit('CLEAR_FILEINFO_DETAIL')
commit('CLIPBOARD_CLEAR')
})
resolve(true)
+4 -4
View File
@@ -56,9 +56,9 @@ const actions = {
let addFavourites = []
let items = [folder]
// If dont coming single folder get folders to add to favourites from fileInfoDetail
// If dont coming single folder get folders to add to favourites from clipboard
if (!folder)
items = context.getters.fileInfoDetail
items = context.getters.clipboard
items.forEach((data) => {
if (data.type === 'folder') {
@@ -71,9 +71,9 @@ const actions = {
}
})
// If dont coming single folder clear the selected folders in fileInfoDetail
// If dont coming single folder clear the selected folders in clipboard
if (!folder) {
context.commit('CLEAR_FILEINFO_DETAIL')
context.commit('CLIPBOARD_CLEAR')
}
let pushToFavorites = []
+7 -7
View File
@@ -4,14 +4,14 @@
<!--Mobile Navigation-->
<MobileNavigation />
<!--Confirm Popup-->
<Confirm />
<!--ConfirmPopup Popup-->
<ConfirmPopup />
<!-- Create language popup -->
<CreateLanguage/>
<!--Navigation Sidebar-->
<MenuBar/>
<SidebarNavigation/>
<ContentSidebar>
@@ -90,12 +90,12 @@
<script>
import { UsersIcon, SettingsIcon, FileTextIcon, CreditCardIcon, DatabaseIcon, BoxIcon, MonitorIcon, GlobeIcon } from 'vue-feather-icons'
import SidebarNavigation from '@/components/Sidebar/SidebarNavigation'
import MobileNavigation from '@/components/Others/MobileNavigation'
import ContentSidebar from '@/components/Sidebar/ContentSidebar'
import CreateLanguage from '@/components/Others/CreateLanguage'
import ContentGroup from '@/components/Sidebar/ContentGroup'
import Confirm from '@/components/Others/Popup/Confirm'
import MenuBar from '@/components/Sidebar/MenuBar'
import ConfirmPopup from '@/components/Others/Popup/ConfirmPopup'
import { mapGetters } from 'vuex'
export default {
@@ -104,6 +104,7 @@
...mapGetters(['config']),
},
components: {
SidebarNavigation,
MobileNavigation,
CreateLanguage,
ContentSidebar,
@@ -115,8 +116,7 @@
MonitorIcon,
UsersIcon,
GlobeIcon,
Confirm,
MenuBar,
ConfirmPopup,
BoxIcon,
},
}
@@ -321,8 +321,6 @@ export default {
get_started_description: response.data.get_started_description,
footer_content: response.data.footer_content
}
console.log(this.app);
})
.finally(() => {
this.isLoading = false
@@ -386,7 +386,7 @@
}
.languages-wrapper {
margin-top: 0px;
margin-top: 0;
}
}
}
@@ -430,7 +430,7 @@
@media only screen and (max-width: 1024px) {
.search-bar-wrapper {
top: 15px;
top: 58px;
z-index: 7;
}
}
@@ -6,7 +6,7 @@
<p>{{ $t('user_box_delete.description') }}</p>
</InfoBox>
<ValidationObserver ref="deleteUser" @submit.prevent="deleteUser" v-slot="{ invalid }" tag="form" class="form block-form">
<ValidationProvider tag="div" class="block-wrapper" v-slot="{ errors }" mode="passive" name="User name" :rules="'required|is:' + user.data.relationships.settings.data.attributes.name">
<ValidationProvider tag="div" class="block-wrapper" v-slot="{ errors }" mode="passive" name="User name" rules="required">
<label>{{ $t('admin_page_user.label_delete_user', {user: user.data.relationships.settings.data.attributes.name}) }}:</label>
<div class="single-line-form">
<input v-model="userName"
@@ -71,6 +71,15 @@
if (!isValid) return;
if (this.userName !== this.user.data.relationships.settings.data.attributes.name) {
this.$refs.deleteUser.setErrors({
'User name': 'The user name is not the same.'
});
return
}
this.isSendingRequest = true
axios
+8 -8
View File
@@ -43,7 +43,7 @@
<span class="empty-note navigator" v-if="tree.length == 0">
{{ $t('sidebar.folders_empty') }}
</span>
<TreeMenuNavigator class="folder-tree" :depth="0" :nodes="items" v-for="items in tree" :key="items.id"/>
<TreeMenuNavigator class="folder-tree" :depth="0" :nodes="folder" v-for="folder in tree" :key="folder.id"/>
</ContentGroup>
<!--Favourites-->
@@ -74,7 +74,7 @@
<script>
import UpgradeSidebarBanner from '@/components/Others/UpgradeSidebarBanner'
import TreeMenuNavigator from '@/components/Others/TreeMenuNavigator'
import MultiSelected from '@/components/FilesView/MultiSelected'
import TitlePreview from '@/components/FilesView/TitlePreview'
import ContentFileView from '@/components/Others/ContentFileView'
import ContentSidebar from '@/components/Sidebar/ContentSidebar'
import ContentGroup from '@/components/Sidebar/ContentGroup'
@@ -94,7 +94,7 @@ export default {
UpgradeSidebarBanner,
TreeMenuNavigator,
ContentFileView,
MultiSelected,
TitlePreview,
ContentSidebar,
UploadCloudIcon,
ContentGroup,
@@ -104,7 +104,7 @@ export default {
XIcon
},
computed: {
...mapGetters(['user', 'homeDirectory', 'currentFolder', 'config', 'fileInfoDetail']),
...mapGetters(['user', 'homeDirectory', 'currentFolder', 'config', 'clipboard']),
favourites() {
return this.user.data.relationships.favourites.data.attributes.folders
},
@@ -137,7 +137,7 @@ export default {
dragEnter() {
if (this.draggedItem && this.draggedItem.type !== 'folder') return
if (this.fileInfoDetail.length > 0 && this.fileInfoDetail.find(item => item.type !== 'folder')) return
if (this.clipboard.length > 0 && this.clipboard.find(item => item.type !== 'folder')) return
this.area = true
},
@@ -156,17 +156,17 @@ export default {
if (this.favourites.find(folder => folder.id == this.draggedItem.id)) return
// Prevent to move folders to self
if (this.fileInfoDetail.length > 0 && this.fileInfoDetail.find(item => item.type !== 'folder')) return
if (this.clipboard.length > 0 && this.clipboard.find(item => item.type !== 'folder')) return
// Store favourites folder
//Add to favourites non selected folder
if (!this.fileInfoDetail.includes(this.draggedItem)) {
if (!this.clipboard.includes(this.draggedItem)) {
this.$store.dispatch('addToFavourites', this.draggedItem)
}
//Add to favourites selected folders
if (this.fileInfoDetail.includes(this.draggedItem)) {
if (this.clipboard.includes(this.draggedItem)) {
this.$store.dispatch('addToFavourites', null)
}
+2 -2
View File
@@ -6,7 +6,7 @@
<!--Navigator-->
<ContentGroup :title="$t('sidebar.locations_title')">
<div class="menu-list-wrapper vertical">
<li class="menu-list-item link" :class="{'is-active': $isThisLocation(['shared'])}" @click="getShared()">
<li class="menu-list-item link" :class="{'is-active': $isThisLocation(['shared'])}" @click="getShared">
<div class="icon text-theme">
<link-icon size="17"></link-icon>
</div>
@@ -14,7 +14,7 @@
{{ $t('sidebar.my_shared') }}
</div>
</li>
<li class="menu-list-item link" :class="{'is-active': $isThisLocation(['participant_uploads'])}" @click="getParticipantUploads()">
<li class="menu-list-item link" :class="{'is-active': $isThisLocation(['participant_uploads'])}" @click="getParticipantUploads">
<div class="icon text-theme">
<users-icon size="17"></users-icon>
</div>
@@ -1,125 +0,0 @@
<template>
<div id="single-page">
<div id="page-content">
<!--Header-->
<MobileHeader :title="$t($router.currentRoute.meta.title)"/>
<!--Content-->
<div class="content-page">
<nav class="mobile-navigation">
<!--Admin menu-->
<b class="mobile-menu-label">{{ $t('global.admin') }}</b>
<MenuItemList :navigation="AdminNavigation" />
<!--SaaS menu-->
<b v-if="config.isSaaS" class="mobile-menu-label">{{ $t('global.saas') }}</b>
<MenuItemList v-if="config.isSaaS" :navigation="SassNavigation" />
</nav>
</div>
</div>
</div>
</template>
<script>
import MenuItemList from '@/components/Mobile/MenuItemList'
import MobileHeader from '@/components/Mobile/MobileHeader'
import { mapGetters } from 'vuex'
export default {
name: 'AdminMobileMenu',
components: {
MenuItemList,
MobileHeader,
},
computed: {
...mapGetters(['config']),
},
data() {
return {
AdminNavigation: [
{
icon: 'box',
title: this.$t('admin_menu.dashboard'),
routeName: 'Dashboard',
isVisible: true,
},
{
icon: 'users',
title: this.$t('admin_menu.users'),
routeName: 'Users',
isVisible: true,
},
{
icon: 'settings',
title: this.$t('admin_menu.settings'),
routeName: 'AppOthers',
isVisible: true,
},
{
icon: 'monitor',
title: this.$t('admin_menu.pages'),
routeName: 'Pages',
isVisible: true,
},
{
icon: 'language',
title: this.$t('languages'),
routeName: 'Language',
isVisible: true,
}
],
SassNavigation: [
{
icon: 'database',
title: this.$t('admin_menu.plans'),
routeName: 'Plans',
isVisible: true,
},
{
icon: 'file-text',
title: this.$t('admin_menu.invoices'),
routeName: 'Invoices',
isVisible: true,
},
]
}
},
}
</script>
<style scoped lang="scss">
@import '@assets/vuefilemanager/_variables';
@import '@assets/vuefilemanager/_mixins';
.mobile-navigation {
width: 100%;
bottom: 0;
left: 0;
right: 0;
z-index: 99;
.mobile-menu-label {
margin-top: 30px;
margin-bottom: 5px;
@include font-size(11);
color: $text-muted;
display: block;
&:first-child {
margin-top: 0;
}
}
}
@media (prefers-color-scheme: dark) {
.mobile-navigation {
.mobile-menu-label {
color: $dark_mode_text_secondary;
}
}
}
</style>
@@ -1,114 +0,0 @@
<template>
<div id="single-page">
<div id="page-content">
<!--Header-->
<MobileHeader :title="$t($router.currentRoute.meta.title)"/>
<!--Content-->
<div class="content-page">
<nav class="mobile-navigation">
<!--Admin menu-->
<b class="mobile-menu-label">{{ $t('global.menu') }}</b>
<MenuItemList :navigation="ProfileNavigation" />
<!--SaaS menu-->
<b class="mobile-menu-label">{{ $t('global.subscription') }}</b>
<MenuItemList :navigation="SubscriptionNavigation" />
</nav>
</div>
</div>
</div>
</template>
<script>
import MenuItemList from '@/components/Mobile/MenuItemList'
import MobileHeader from '@/components/Mobile/MobileHeader'
export default {
name: 'UserProfileMobileMenu',
components: {
MenuItemList,
MobileHeader,
},
data() {
return {
ProfileNavigation: [
{
icon: 'user',
title: this.$t('menu.profile'),
routeName: 'Profile',
isVisible: true,
},
{
icon: 'hard-drive',
title: this.$t('menu.storage'),
routeName: 'Storage',
isVisible: true,
},
{
icon: 'lock',
title: this.$t('menu.password'),
routeName: 'Password',
isVisible: true,
},
],
SubscriptionNavigation: [
{
icon: 'cloud',
title: this.$t('menu.subscription'),
routeName: 'Subscription',
isVisible: true,
},
{
icon: 'credit-card',
title: this.$t('menu.payment_cards'),
routeName: 'PaymentMethods',
isVisible: true,
},
{
icon: 'file-text',
title: this.$t('menu.invoices'),
routeName: 'Invoice',
isVisible: true,
},
]
}
},
}
</script>
<style scoped lang="scss">
@import '@assets/vuefilemanager/_variables';
@import '@assets/vuefilemanager/_mixins';
.mobile-navigation {
width: 100%;
bottom: 0;
left: 0;
right: 0;
z-index: 99;
.mobile-menu-label {
margin-top: 30px;
margin-bottom: 5px;
@include font-size(11);
color: $text-muted;
display: block;
&:first-child {
margin-top: 0;
}
}
}
@media (prefers-color-scheme: dark) {
.mobile-navigation {
.mobile-menu-label {
color: $dark_mode_text_secondary;
}
}
}
</style>
+75 -90
View File
@@ -1,118 +1,103 @@
<template>
<div id="application-wrapper">
<!--Full File Preview-->
<FileFullPreview />
<!--File preview window-->
<FilePreview />
<!--Mobile Navigation-->
<MobileNavigation />
<!--Processing popup-->
<!--Popups-->
<ProcessingPopup />
<ConfirmPopup />
<!--Confirm Popup-->
<Confirm />
<ShareCreatePopup />
<ShareEditPopup />
<!--Share Item popup-->
<ShareCreate />
<ShareEdit />
<CreateFolderPopup />
<RenameItemPopup />
<!--Rename folder/file item-->
<RenameItem />
<MoveItemPopup />
<!--Create folder mobile UI-->
<CreateFolder />
<!--Mobile components-->
<FileSortingMobile />
<FileFilterMobile />
<FileMenuMobile />
<!--Move item popup-->
<MoveItem />
<MultiSelectToolbarMobile />
<!-- Mobile Menu for Multi selected items -->
<MobileMultiSelectMenu />
<!--Navigations-->
<MobileNavigation />
<SidebarNavigation />
<!--Drag UI-->
<!--Others-->
<DragUI />
<!--Mobile menu for selecting view and sorting-->
<MobileSortingAndPreview />
<!--Mobile Menu-->
<MobileMenu />
<!--Navigation Sidebar-->
<MenuBar />
<!--File page-->
<keep-alive :include="['Admin', 'Users']">
<router-view :class="{'is-scaled-down': isScaledDown}" />
</keep-alive>
<router-view :class="{'is-scaled-down': isScaledDown}" />
</div>
</template>
<script>
import MobileSortingAndPreview from '@/components/FilesView/MobileSortingAndPreview'
import MobileMultiSelectMenu from '@/components/FilesView/MobileMultiSelectMenu'
import ProcessingPopup from '@/components/FilesView/ProcessingPopup'
import FileFullPreview from '@/components/FilesView/FileFullPreview'
import MobileNavigation from '@/components/Others/MobileNavigation'
import CreateFolder from '@/components/Others/CreateFolder'
import MobileMenu from '@/components/FilesView/MobileMenu'
import ShareCreate from '@/components/Others/ShareCreate'
import Confirm from '@/components/Others/Popup/Confirm'
import RenameItem from '@/components/Others/RenameItem'
import ShareEdit from '@/components/Others/ShareEdit'
import MoveItem from '@/components/Others/MoveItem'
import DragUI from '@/components/FilesView/DragUI'
import MenuBar from '@/components/Sidebar/MenuBar'
import {includes} from 'lodash'
import {mapGetters} from 'vuex'
import {events} from '@/bus'
import MultiSelectToolbarMobile from '@/components/FilesView/MultiSelectToolbarMobile'
import FileSortingMobile from '@/components/FilesView/FileSortingMobile'
import SidebarNavigation from '@/components/Sidebar/SidebarNavigation'
import FileFilterMobile from '@/components/FilesView/FileFilterMobile'
import CreateFolderPopup from '@/components/Others/CreateFolderPopup'
import ProcessingPopup from '@/components/FilesView/ProcessingPopup'
import MobileNavigation from '@/components/Others/MobileNavigation'
import ShareCreatePopup from '@/components/Others/ShareCreatePopup'
import FileMenuMobile from '@/components/FilesView/FileMenuMobile'
import ConfirmPopup from '@/components/Others/Popup/ConfirmPopup'
import RenameItemPopup from '@/components/Others/RenameItemPopup'
import ShareEditPopup from '@/components/Others/ShareEditPopup'
import MoveItemPopup from '@/components/Others/MoveItemPopup'
import FilePreview from '@/components/FilesView/FilePreview'
import DragUI from '@/components/FilesView/DragUI'
import {mapGetters} from 'vuex'
import {events} from '@/bus'
export default {
name: 'Platform',
components: {
MobileSortingAndPreview,
MobileMultiSelectMenu,
MobileNavigation,
FileFullPreview,
ProcessingPopup,
CreateFolder,
ShareCreate,
MobileMenu,
RenameItem,
ShareEdit,
MoveItem,
Confirm,
MenuBar,
DragUI,
},
computed: {
...mapGetters([
'config'
]),
},
data() {
return {
isScaledDown: false
export default {
name: 'Platform',
components: {
MultiSelectToolbarMobile,
CreateFolderPopup,
FileSortingMobile,
SidebarNavigation,
FileFilterMobile,
MobileNavigation,
ShareCreatePopup,
ProcessingPopup,
RenameItemPopup,
ShareEditPopup,
FileMenuMobile,
MoveItemPopup,
ConfirmPopup,
FilePreview,
DragUI,
},
computed: {
...mapGetters([
'config'
]),
},
data() {
return {
isScaledDown: false
}
},
mounted() {
events.$on('mobile-menu:show', () => this.isScaledDown = true)
events.$on('fileItem:deselect', () => this.isScaledDown = false)
events.$on('mobile-menu:hide', () => this.isScaledDown = false)
}
},
mounted() {
events.$on('show:mobile-navigation', () => this.isScaledDown = true)
events.$on('hide:mobile-navigation', () => this.isScaledDown = false)
events.$on('mobileMenu:show', () => this.isScaledDown = true)
events.$on('fileItem:deselect', () => this.isScaledDown = false)
events.$on('mobileSortingAndPreview', state => this.isScaledDown = state)
}
}
</script>
<style lang="scss">
@import '@assets/vuefilemanager/_variables';
@import '@assets/vuefilemanager/_mixins';
@import '@assets/vuefilemanager/_mixins';
@media only screen and (max-width: 690px) {
@media only screen and (max-width: 690px) {
.is-scaled-down {
@include transform(scale(0.95));
.is-scaled-down {
@include transform(scale(0.95));
}
}
}
</style>
+89 -85
View File
@@ -1,108 +1,112 @@
<template>
<div id="application-wrapper">
<!--Full File Preview-->
<FileFullPreview />
<!--Loading Spinner-->
<Spinner v-if="isLoading" />
<!--Move item popup-->
<MoveItem />
<!--File preview window-->
<FilePreview />
<!-- Processing popup for zip -->
<!--Popups-->
<ProcessingPopup />
<!-- Mobile Menu for Multi selected items -->
<MobileMultiSelectMenu />
<CreateFolderPopup />
<RenameItemPopup />
<!--Rename folder/file item-->
<RenameItem />
<MoveItemPopup />
<!--Create folder mobile UI-->
<CreateFolder />
<!-- Mobile components -->
<FileSortingMobile />
<FileMenuMobile />
<!--Drag UI-->
<MultiSelectToolbarMobile />
<!--Others-->
<Vignette />
<DragUI />
<!--Mobile Menu-->
<MobileMenu />
<!--Mobile menu for selecting view and sorting-->
<MobileSortingAndPreview />
<!--System alerts-->
<Alert />
<!--Background vignette-->
<Vignette />
<!--Pages-->
<router-view />
<router-view :class="{'is-scaled-down': isScaledDown}" />
</div>
</template>
<script>
import MobileSortingAndPreview from '@/components/FilesView/MobileSortingAndPreview'
import MobileMultiSelectMenu from '@/components/FilesView/MobileMultiSelectMenu'
import ProcessingPopup from '@/components/FilesView/ProcessingPopup'
import FileFullPreview from '@/components/FilesView/FileFullPreview'
import CreateFolder from '@/components/Others/CreateFolder'
import MobileMenu from '@/components/FilesView/MobileMenu'
import RenameItem from '@/components/Others/RenameItem'
import Spinner from '@/components/FilesView/Spinner'
import MoveItem from '@/components/Others/MoveItem'
import Vignette from '@/components/Others/Vignette'
import DragUI from '@/components/FilesView/DragUI'
import Alert from '@/components/FilesView/Alert'
import {mapGetters} from 'vuex'
import MultiSelectToolbarMobile from '@/components/FilesView/MultiSelectToolbarMobile'
import FileSortingMobile from '@/components/FilesView/FileSortingMobile'
import CreateFolderPopup from '@/components/Others/CreateFolderPopup'
import ProcessingPopup from '@/components/FilesView/ProcessingPopup'
import FileMenuMobile from '@/components/FilesView/FileMenuMobile'
import RenameItemPopup from '@/components/Others/RenameItemPopup'
import MoveItemPopup from '@/components/Others/MoveItemPopup'
import FilePreview from '@/components/FilesView/FilePreview'
import Spinner from '@/components/FilesView/Spinner'
import Vignette from '@/components/Others/Vignette'
import DragUI from '@/components/FilesView/DragUI'
import Alert from '@/components/FilesView/Alert'
import {events} from '@/bus'
import {mapGetters} from 'vuex'
export default {
name: 'Platform',
components: {
MobileSortingAndPreview,
MobileMultiSelectMenu,
FileFullPreview,
ProcessingPopup,
CreateFolder,
MobileMenu,
RenameItem,
MoveItem,
Vignette,
Spinner,
DragUI,
Alert,
},
computed: {
...mapGetters([
'config'
]),
},
data() {
return {
isLoading: true,
export default {
name: 'Platform',
components: {
MultiSelectToolbarMobile,
CreateFolderPopup,
FileSortingMobile,
ProcessingPopup,
RenameItemPopup,
FileMenuMobile,
MoveItemPopup,
FilePreview,
Vignette,
Spinner,
DragUI,
Alert,
},
computed: {
...mapGetters([
'config'
]),
},
data() {
return {
isLoading: true,
isScaledDown: false
}
},
mounted() {
events.$on('mobile-menu:show', () => this.isScaledDown = true)
events.$on('fileItem:deselect', () => this.isScaledDown = false)
this.$store.dispatch('getShareDetail', this.$route.params.token)
.then(response => {
this.isLoading = false
// Show public file browser
if (response.data.data.attributes.type === 'folder' && !response.data.data.attributes.is_protected && this.$router.currentRoute.name !== 'SharedFileBrowser') {
this.$router.push({name: 'SharedFileBrowser'})
}
// Show public single file
if (response.data.data.attributes.type !== 'folder' && !response.data.data.attributes.is_protected && this.$router.currentRoute.name !== 'SharedSingleFile') {
this.$router.push({name: 'SharedSingleFile'})
}
// Show authentication page
if (response.data.data.attributes.is_protected && this.$router.currentRoute.name !== 'SharedAuthentication') {
this.$router.push({name: 'SharedAuthentication'})
}
})
}
},
mounted() {
this.$store.dispatch('getShareDetail', this.$route.params.token)
.then(response => {
this.isLoading = false
// Show public file browser
if (response.data.data.attributes.type === 'folder' && !response.data.data.attributes.is_protected && this.$router.currentRoute.name !== 'SharedFileBrowser') {
this.$router.push({name: 'SharedFileBrowser'})
}
// Show public single file
if (response.data.data.attributes.type !== 'folder' && !response.data.data.attributes.is_protected && this.$router.currentRoute.name !== 'SharedSingleFile') {
this.$router.push({name: 'SharedSingleFile'})
}
// Show authentication page
if (response.data.data.attributes.is_protected && this.$router.currentRoute.name !== 'SharedAuthentication') {
this.$router.push({name: 'SharedAuthentication'})
}
})
}
}
</script>
<style lang="scss">
@import '@assets/vuefilemanager/_mixins';
@media only screen and (max-width: 690px) {
.is-scaled-down {
@include transform(scale(0.95));
}
}
</style>
@@ -7,7 +7,7 @@
<div class="menu-list-wrapper vertical">
<a class="menu-list-item link" @click="goHome">
<div class="icon">
<home-icon size="17"></home-icon>
<home-icon size="17"/>
</div>
<div class="label">
{{ $t('sidebar.home') }}
@@ -18,7 +18,7 @@
<!--Navigator-->
<ContentGroup :title="$t('sidebar.navigator_title')" class="navigator">
<TreeMenuNavigator class="folder-tree" :depth="0" :nodes="items" v-for="items in navigationTree" :key="items.id" />
<TreeMenuNavigator class="folder-tree" :depth="0" :nodes="folder" v-for="folder in navigationTree" :key="folder.id" />
</ContentGroup>
</ContentSidebar>
@@ -28,14 +28,11 @@
<DesktopToolbar />
<FileBrowser />
<DesktopSortingAndPreview />
</div>
</div>
</template>
<script>
import DesktopSortingAndPreview from '@/components/FilesView/DesktopSortingAndPreview'
import TreeMenuNavigator from '@/components/Others/TreeMenuNavigator'
import DesktopToolbar from '@/components/FilesView/DesktopToolbar'
import ContentSidebar from '@/components/Sidebar/ContentSidebar'
@@ -49,7 +46,6 @@
export default {
name: 'SharedFileBrowser',
components: {
DesktopSortingAndPreview,
TreeMenuNavigator,
ContentSidebar,
DesktopToolbar,
@@ -5,7 +5,7 @@
@endphp
{{-- Group options --}}
.group:hover .group-hover-text-theme {color: {{ $color }} !important;}
.group:hover:not(.hover-disabled) .group-hover-text-theme {color: {{ $color }} !important;}
{{-- Single option --}}
@@ -50,7 +50,7 @@
{{-- Emoji Picker --}}
.active-menu.focus-border-theme {border-color: {{ $color }} !important;}
{{-- Menubar --}}
{{-- Sidebar Navigation --}}
.router-link-active.home .button-icon {background: {{ $color }}10}
{{-- Content Panel --}}