v1.5-alpha.1

This commit is contained in:
carodej
2020-05-15 17:31:25 +02:00
parent cfecf542ca
commit 41656235fc
97 changed files with 4108 additions and 2118 deletions
+2 -1
View File
@@ -28,7 +28,8 @@
</script>
<style scoped lang="scss">
@import "@assets/app.scss";
@import '@assets/vue-file-manager/_variables';
@import '@assets/vue-file-manager/_mixins';
.button {
cursor: pointer;
@@ -19,8 +19,3 @@
}
}
</script>
<style scoped lang="scss">
</style>
@@ -11,7 +11,6 @@
</script>
<style scoped lang="scss">
@import "@assets/app.scss";
#auth {
height: 100%;
+2 -1
View File
@@ -88,7 +88,8 @@
</script>
<style scoped lang="scss">
@import "@assets/app.scss";
@import '@assets/vue-file-manager/_variables';
@import '@assets/vue-file-manager/_mixins';
.popup {
position: absolute;
@@ -15,10 +15,11 @@
</script>
<style scoped lang="scss">
@import "@assets/app.scss";
@import '@assets/vue-file-manager/_variables';
@import '@assets/vue-file-manager/_mixins';
.button-base {
@include font-size(16);
@include font-size(15);
font-weight: 700;
cursor: pointer;
transition: 0.15s all ease;
@@ -31,10 +31,11 @@
</script>
<style scoped lang="scss">
@import "@assets/app.scss";
@import '@assets/vue-file-manager/_variables';
@import '@assets/vue-file-manager/_mixins';
.button-base {
@include font-size(16);
@include font-size(15);
font-weight: 700;
cursor: pointer;
transition: 0.15s all ease;
+334 -101
View File
@@ -7,105 +7,270 @@
ref="contextmenu"
>
<!--ContextMenu for trash location-->
<ul v-if="$isThisLocation(['trash', 'trash-root']) && $checkPermission('master')" class="menu-options" ref="list">
<li class="menu-option" @click="deleteItem" v-if="item">
{{ $t('context_menu.delete') }}
</li>
<li class="menu-option" @click="$store.dispatch('restoreItem', item)" v-if="item">
{{ $t('context_menu.restore') }}
</li>
<li class="menu-option" @click="$store.dispatch('emptyTrash')">
{{ $t('context_menu.empty_trash') }}
</li>
<li class="menu-option" @click="ItemDetail" v-if="item">
{{ $t('context_menu.detail') }}
</li>
<li class="menu-option" @click="downloadItem" v-if="! isFolder && item">
{{ $t('context_menu.download') }}
</li>
</ul>
<div v-if="$isThisLocation(['trash', 'trash-root']) && $checkPermission('master')" id="menu-list" class="menu-options">
<ul class="menu-option-group">
<li class="menu-option" @click="$store.dispatch('restoreItem', item)" v-if="item">
<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" @click="$store.dispatch('emptyTrash')">
<div class="icon">
<trash-icon size="17"></trash-icon>
</div>
<div class="text-label">
{{ $t('context_menu.empty_trash') }}
</div>
</li>
<li class="menu-option" @click="deleteItem" v-if="item">
<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="ItemDetail" v-if="item">
<div class="icon">
<eye-icon size="17"></eye-icon>
</div>
<div class="text-label">
{{ $t('context_menu.detail') }}
</div>
</li>
<li class="menu-option" @click="downloadItem" v-if="! isFolder && item">
<div class="icon">
<download-cloud-icon size="17"></download-cloud-icon>
</div>
<div class="text-label">
{{ $t('context_menu.download') }}
</div>
</li>
</ul>
</div>
<!--ContextMenu for Base location with MASTER permission-->
<ul v-if="$isThisLocation(['shared']) && $checkPermission('master')" class="menu-options" ref="list">
<li class="menu-option" @click="addToFavourites" v-if="item && isFolder">
{{ isInFavourites ? $t('context_menu.remove_from_favourites') : $t('context_menu.add_to_favourites') }}
</li>
<li class="menu-option" @click="deleteItem" v-if="item">
{{ $t('context_menu.delete') }}
</li>
<li class="menu-option" @click="shareItem" v-if="item">
{{ item.shared ? $t('context_menu.share_edit') : $t('context_menu.share') }}
</li>
<li class="menu-option" @click="ItemDetail" v-if="item">
{{ $t('context_menu.detail') }}
</li>
<li class="menu-option" @click="downloadItem" v-if="! isFolder && item">
{{ $t('context_menu.download') }}
</li>
</ul>
<div v-if="$isThisLocation(['shared']) && $checkPermission('master')" id="menu-list" class="menu-options">
<ul class="menu-option-group">
<li class="menu-option" @click="addToFavourites" v-if="item && 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="deleteItem" v-if="item">
<div class="icon">
<trash-2-icon size="17"></trash-2-icon>
</div>
<div class="text-label">
{{ $t('context_menu.delete') }}
</div>
</li>
<li class="menu-option" @click="shareItem" v-if="item">
<div class="icon">
<link-icon size="17"></link-icon>
</div>
<div class="text-label">
{{ item.shared ? $t('context_menu.share_edit') : $t('context_menu.share') }}
</div>
</li>
</ul>
<ul class="menu-option-group">
<li class="menu-option" @click="ItemDetail" v-if="item">
<div class="icon">
<eye-icon size="17"></eye-icon>
</div>
<div class="text-label">
{{ $t('context_menu.detail') }}
</div>
</li>
<li class="menu-option" @click="downloadItem" v-if="! isFolder && item">
<div class="icon">
<download-cloud-icon size="17"></download-cloud-icon>
</div>
<div class="text-label">
{{ $t('context_menu.download') }}
</div>
</li>
</ul>
</div>
<!--ContextMenu for Base location with MASTER permission-->
<ul v-if="$isThisLocation(['base']) && $checkPermission('master')" class="menu-options" ref="list">
<li class="menu-option" @click="addToFavourites" v-if="item && isFolder">
{{ isInFavourites ? $t('context_menu.remove_from_favourites') : $t('context_menu.add_to_favourites') }}
</li>
<li class="menu-option" @click="createFolder">
{{ $t('context_menu.create_folder') }}
</li>
<li class="menu-option" @click="deleteItem" v-if="item">
{{ $t('context_menu.delete') }}
</li>
<li class="menu-option" @click="moveItem" v-if="item">
{{ $t('context_menu.move') }}
</li>
<li class="menu-option" @click="shareItem" v-if="item">
{{ item.shared ? $t('context_menu.share_edit') : $t('context_menu.share') }}
</li>
<li class="menu-option" @click="ItemDetail" v-if="item">
{{ $t('context_menu.detail') }}
</li>
<li class="menu-option" @click="downloadItem" v-if="! isFolder && item">
{{ $t('context_menu.download') }}
</li>
</ul>
<div v-if="$isThisLocation(['base', 'participant_uploads', 'latest']) && $checkPermission('master')" id="menu-list" class="menu-options">
<ul class="menu-option-group" v-if="! $isThisLocation(['participant_uploads', 'latest'])">
<li class="menu-option" @click="addToFavourites" v-if="item && 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>
<li class="menu-option" @click="createFolder">
<div class="icon">
<folder-plus-icon size="17"></folder-plus-icon>
</div>
<div class="text-label">
{{ $t('context_menu.create_folder') }}
</div>
</li>
</ul>
<ul class="menu-option-group">
<li class="menu-option" @click="moveItem" v-if="item">
<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="item">
<div class="icon">
<link-icon size="17"></link-icon>
</div>
<div class="text-label">
{{ item.shared ? $t('context_menu.share_edit') : $t('context_menu.share') }}
</div>
</li>
<li class="menu-option" @click="deleteItem" v-if="item">
<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="ItemDetail" v-if="item">
<div class="icon">
<eye-icon size="17"></eye-icon>
</div>
<div class="text-label">
{{ $t('context_menu.detail') }}
</div>
</li>
<li class="menu-option" @click="downloadItem" v-if="! isFolder && item">
<div class="icon">
<download-cloud-icon size="17"></download-cloud-icon>
</div>
<div class="text-label">
{{ $t('context_menu.download') }}
</div>
</li>
</ul>
</div>
<!--ContextMenu for Base location with EDITOR permission-->
<ul v-if="$isThisLocation(['base', 'public']) && $checkPermission('editor')" class="menu-options" ref="list">
<li class="menu-option" @click="createFolder">
{{ $t('context_menu.create_folder') }}
</li>
<li class="menu-option" @click="deleteItem" v-if="item">
{{ $t('context_menu.delete') }}
</li>
<li class="menu-option" @click="moveItem" v-if="item">
{{ $t('context_menu.move') }}
</li>
<li class="menu-option" @click="ItemDetail" v-if="item">
{{ $t('context_menu.detail') }}
</li>
<li class="menu-option" @click="downloadItem" v-if="! isFolder && item">
{{ $t('context_menu.download') }}
</li>
</ul>
<div v-if="$isThisLocation(['base', 'public']) && $checkPermission('editor')" id="menu-list" class="menu-options">
<ul class="menu-option-group">
<li class="menu-option" @click="createFolder">
<div class="icon">
<folder-plus-icon size="17"></folder-plus-icon>
</div>
<div class="text-label">
{{ $t('context_menu.create_folder') }}
</div>
</li>
</ul>
<ul class="menu-option-group" v-if="item && isFolder">
<li class="menu-option" @click="moveItem" v-if="item">
<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" v-if="item">
<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" v-if="item && isFolder">
<li class="menu-option" @click="ItemDetail" v-if="item">
<div class="icon">
<eye-icon size="17"></eye-icon>
</div>
<div class="text-label">
{{ $t('context_menu.detail') }}
</div>
</li>
<li class="menu-option" @click="downloadItem" v-if="! isFolder && item">
<div class="icon">
<download-cloud-icon size="17"></download-cloud-icon>
</div>
<div class="text-label">
{{ $t('context_menu.download') }}
</div>
</li>
</ul>
</div>
<!--ContextMenu for Base location with VISITOR permission-->
<ul v-if="$isThisLocation(['base', 'public']) && $checkPermission('visitor')" class="menu-options" ref="list">
<div v-if="$isThisLocation(['base', 'public']) && $checkPermission('visitor')" id="menu-list" class="menu-options">
<li class="menu-option" @click="ItemDetail" v-if="item">
{{ $t('context_menu.detail') }}
<div class="icon">
<eye-icon size="17"></eye-icon>
</div>
<div class="text-label">
{{ $t('context_menu.detail') }}
</div>
</li>
<li class="menu-option" @click="downloadItem" v-if="! isFolder && item">
{{ $t('context_menu.download') }}
<div class="icon">
<download-cloud-icon size="17"></download-cloud-icon>
</div>
<div class="text-label">
{{ $t('context_menu.download') }}
</div>
</li>
</ul>
</div>
</div>
</template>
<script>
import {
CornerDownRightIcon,
DownloadCloudIcon,
FolderPlusIcon,
LifeBuoyIcon,
Trash2Icon,
TrashIcon,
StarIcon,
LinkIcon,
EyeIcon,
} from 'vue-feather-icons'
import {mapGetters} from 'vuex'
import {events} from '@/bus'
export default {
name: 'ContextMenu',
components: {
CornerDownRightIcon,
DownloadCloudIcon,
FolderPlusIcon,
LifeBuoyIcon,
Trash2Icon,
TrashIcon,
LinkIcon,
StarIcon,
EyeIcon,
},
computed: {
...mapGetters(['app']),
isFolder() {
@@ -145,7 +310,7 @@
},
addToFavourites() {
// Check if folder is in favourites and then add/remove from favourites
if (this.app.favourites && ! this.app.favourites.find(el => el.unique_id == this.item.unique_id)) {
if (this.app.favourites && !this.app.favourites.find(el => el.unique_id == this.item.unique_id)) {
this.$store.dispatch('addToFavourites', this.item)
} else {
this.$store.dispatch('removeFromFavourites', this.item)
@@ -159,6 +324,7 @@
)
},
ItemDetail() {
// Dispatch load file info detail
this.$store.commit('GET_FILEINFO_DETAIL', this.item)
@@ -180,8 +346,20 @@
// Reset item container
this.item = undefined
},
showContextMenu(event, item) {
let VerticalOffsetArea = item && this.$refs.list.children ? this.$refs.list.children.length * 50 : 50
showFolderActionsMenu() {
let container = document.getElementById('folder-actions')
this.positionX = container.offsetLeft + 16
this.positionY = container.offsetTop + 30
// Show context menu
this.isVisible = true
},
showContextMenu(event) {
let parent = document.getElementById('menu-list')
let nodesSameClass = parent.getElementsByClassName("menu-option")
let VerticalOffsetArea = nodesSameClass.length * 50
let HorizontalOffsetArea = 190
let container = document.getElementById('files-view')
@@ -220,15 +398,42 @@
})
events.$on('contextMenu:hide', () => (this.closeAndResetContextMenu()))
events.$on('folder:actions', folder => {
// Store item
this.item = folder
if (this.isVisible)
this.isVisible = false
else
this.showFolderActionsMenu()
})
}
}
</script>
<style scoped lang="scss">
@import "@assets/app.scss";
@import '@assets/vue-file-manager/_variables';
@import '@assets/vue-file-manager/_mixins';
.menu-option {
display: flex;
align-items: center;
.icon {
margin-right: 20px;
line-height: 0;
}
.text-label {
@include font-size(16);
}
}
.contextmenu {
min-width: 190px;
min-width: 250px;
position: absolute;
z-index: 99;
box-shadow: $shadow;
@@ -239,26 +444,47 @@
&.showed {
display: block;
}
}
.menu-options {
list-style: none;
.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%;
margin: 0;
padding: 0;
color: $text;
.menu-option {
white-space: nowrap;
font-weight: 700;
@include font-size(15);
padding: 15px 30px;
cursor: pointer;
width: 100%;
color: $text;
&:hover {
background: $light_background;
&:hover {
background: $light_background;
.text-label {
color: $theme;
}
path, line, polyline, rect, circle, polygon {
stroke: $theme;
}
}
}
}
@@ -268,11 +494,18 @@
.contextmenu {
background: $dark_mode_foreground;
.menu-options .menu-option {
color: $dark_mode_text_primary;
.menu-options {
&:hover {
background: $dark_mode_background;
.menu-option-group {
border-color: $dark_mode_border_color;
}
.menu-option {
color: $dark_mode_text_primary;
&:hover {
background: rgba($theme, 0.1);
}
}
}
}
@@ -1,41 +1,57 @@
<template>
<div id="desktop-toolbar" v-if="! $isMinimalScale()">
<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">
<FontAwesomeIcon
v-if="browseHistory.length > 0"
class="icon-back"
icon="chevron-left"
></FontAwesomeIcon>
<chevron-left-icon size="17" :class="{'is-active': browseHistory.length > 1}" class="icon-back"></chevron-left-icon>
<span class="back-directory-title">
{{ directoryName }}
</span>
<span @click.stop="folderActions" v-if="browseHistory.length > 1 && $isThisLocation(['base', 'public'])" class="folder-options" id="folder-actions">
<more-horizontal-icon size="14" class="icon-more"></more-horizontal-icon>
</span>
</div>
</div>
<!-- Tools-->
<div class="toolbar-tools">
<!--Search bar-->
<div class="toolbar-button-wrapper">
<SearchBar/>
</div>
<!--Files controlls-->
<div class="toolbar-button-wrapper" v-if="$checkPermission(['master', 'editor'])">
<div class="toolbar-button-wrapper"
v-if="$checkPermission(['master', 'editor'])">
<ToolbarButtonUpload
source="upload"
:class="{'is-inactive': canUploadInView}"
:action="$t('actions.upload')"
/>
<ToolbarButton
source="trash-alt"
:action="$t('actions.delete')"
@click.native="deleteItems"
source="move"
:class="{'is-inactive': canMoveInView}"
action="Move"
@click.native="moveItem"
/>
<ToolbarButton
v-if="! $isThisLocation(['public'])"
source="share"
:class="{'is-inactive': canShareInView}"
action="Share"
@click.native="shareItem"
/>
<ToolbarButton
source="trash"
:class="{'is-inactive': canDeleteInView}"
:action="$t('actions.delete')"
@click.native="deleteItem"
/>
<ToolbarButton
:class="{'is-inactive': canCreateFolderInView}"
@click.native="createFolder"
source="folder-plus"
:action="$t('actions.create_folder')"
@@ -63,89 +79,108 @@
<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'
export default {
name: 'ToolBar',
components: {
ToolbarButtonUpload,
MoreHorizontalIcon,
ChevronLeftIcon,
UploadProgress,
ToolbarButton,
SearchBar
},
computed: {
...mapGetters([
'FilePreviewType',
'fileInfoVisible',
'fileInfoDetail',
'currentFolder',
'browseHistory',
'homeDirectory',
'FilePreviewType',
]),
directoryName() {
return this.currentFolder ? this.currentFolder.name : this.homeDirectory.name
},
previousFolder() {
const length = this.browseHistory.length - 2
return this.browseHistory[length] ? this.browseHistory[length] : this.homeDirectory
},
preview() {
return this.FilePreviewType === 'list' ? 'th' : 'th-list'
},
},
data() {
return {
isSidebarMenu: false,
canCreateFolderInView() {
return ! this.$isThisLocation(['base', 'public'])
},
canDeleteInView() {
return ! this.$isThisLocation(['trash', 'trash-root', 'base', 'participant_uploads', 'latest', 'shared', 'public'])
},
canUploadInView() {
return ! this.$isThisLocation(['base', 'public'])
},
canMoveInView() {
return ! this.$isThisLocation(['base', 'participant_uploads', 'latest', 'shared', 'public'])
},
canShareInView() {
return ! this.$isThisLocation(['base', 'participant_uploads', 'latest', 'shared', 'public'])
}
},
methods: {
showSidebarMenu() {
this.isSidebarMenu = ! this.isSidebarMenu
events.$emit('show:sidebar')
},
goBack() {
// Get previous folder
let previousFolder = last(this.browseHistory)
if (this.previousFolder.location === 'trash-root') {
if (! previousFolder)
return
if (previousFolder.location === 'trash-root') {
this.$store.dispatch('getTrash')
this.$store.commit('FLUSH_BROWSER_HISTORY')
} else if (previousFolder.location === 'shared') {
this.$store.dispatch('getShared')
} else {
if ( this.$isThisLocation('public') ) {
this.$store.dispatch('browseShared', [this.previousFolder, true])
this.$store.dispatch('browseShared', [{folder: previousFolder, back: true, init: false}])
} else {
this.$store.dispatch('getFolder', [this.previousFolder, true])
this.$store.dispatch('getFolder', [{folder: previousFolder, back: true, init: false}])
}
}
},
deleteItems() {
folderActions() {
events.$emit('folder:actions', this.currentFolder)
},
deleteItem() {
events.$emit('items:delete')
},
createFolder() {
if (! this.$isThisLocation(['trash', 'trash-root']))
this.$createFolder()
this.$createFolder()
},
moveItem() {
events.$emit('popup:open', {name: 'move', item: this.fileInfoDetail})
},
shareItem() {
if (this.fileInfoDetail.shared) {
events.$emit('popup:open', {name: 'share-edit', item: this.fileInfoDetail})
} else {
events.$emit('popup:open', {name: 'share-create', item: this.fileInfoDetail})
}
}
},
created() {
// Listen for hide sidebar
events.$on('show:content', () => {
if (this.isSidebarMenu) this.isSidebarMenu = false
})
}
}
</script>
<style scoped lang="scss">
@import "@assets/app.scss";
@import '@assets/vue-file-manager/_variables';
@import '@assets/vue-file-manager/_mixins';
.toolbar-wrapper {
padding-top: 15px;
padding-bottom: 15px;
padding-top: 10px;
padding-bottom: 10px;
display: flex;
position: relative;
z-index: 2;
@@ -171,13 +206,48 @@
.icon-back {
vertical-align: middle;
cursor: pointer;
margin-right: 12px;
margin-right: 6px;
opacity: 0.15;
pointer-events: none;
@include transition(150ms);
&.is-active {
opacity: 1;
pointer-events: initial;
}
}
.toolbar-go-back {
cursor: pointer;
.folder-options {
vertical-align: middle;
margin-left: 6px;
padding: 1px 4px;
line-height: 0;
border-radius: 3px;
@include transition(150ms);
svg circle {
@include transition(150ms);
}
&:hover {
background: $light_background;
svg circle {
stroke: $theme;
}
}
.icon-more {
vertical-align: middle;
}
}
.back-directory-title {
@include font-size(15);
line-height: 1;
font-weight: 700;
overflow: hidden;
@@ -201,7 +271,7 @@
text-align: right;
.toolbar-button-wrapper {
margin-left: 75px;
margin-left: 28px;
display: inline-block;
vertical-align: middle;
@@ -210,8 +280,13 @@
}
}
button {
margin-left: 20px;
.button {
margin-left: 5px;
&.is-inactive {
opacity: 0.25;
pointer-events: none;
}
&:first-child {
margin-left: 0;
@@ -219,6 +294,33 @@
}
}
@media only screen and (max-width: 1024px) {
.toolbar-go-back .back-directory-title {
max-width: 120px;
}
.toolbar-tools {
.button {
margin-left: 0;
height: 40px;
width: 40px;
}
.toolbar-button-wrapper {
margin-left: 25px;
}
}
}
@media only screen and (max-width: 960px) {
#desktop-toolbar {
display: none;
}
}
@media (prefers-color-scheme: dark) {
.toolbar .directory-name {
color: $dark_mode_text_primary;
@@ -229,6 +331,13 @@
.back-directory-title {
color: $dark_mode_text_primary;
}
.folder-options {
&:hover {
background: $dark_mode_foreground;
}
}
}
}
</style>
@@ -1,21 +1,27 @@
<template>
<div class="empty-message">
<div class="message">
<FontAwesomeIcon class="icon" :icon="icon"/>
<eye-off-icon v-if="icon === 'eye-off'" size="36" class="icon"></eye-off-icon>
<p>{{ message }}</p>
</div>
</div>
</template>
<script>
import { EyeOffIcon } from 'vue-feather-icons'
export default {
name: 'EmptyMessage',
props: ['icon', 'message']
props: ['icon', 'message'],
components: {
EyeOffIcon
}
}
</script>
<style scoped lang="scss">
@import "@assets/app.scss";
@import '@assets/vue-file-manager/_variables';
@import '@assets/vue-file-manager/_mixins';
.empty-message {
text-align: center;
@@ -35,11 +41,8 @@
}
.icon {
@include font-size(36);
color: $text;
path {
fill: $text;
path, line, polyline, rect, circle {
stroke: $text;
}
}
}
@@ -47,8 +50,9 @@
@media (prefers-color-scheme: dark) {
.empty-message .message .icon {
path {
fill: $dark_mode_text_secondary;
path, line, polyline, rect, circle {
stroke: $dark_mode_text_secondary;
}
}
}
@@ -18,7 +18,6 @@
<p v-if="$checkPermission(['master', 'editor'])" class="description">{{ $t('empty_page.description') }}</p>
<ButtonUpload
v-if="$checkPermission(['master', 'editor'])"
@input.native="$uploadFiles"
button-style="theme"
>
{{ $t('empty_page.call_to_action') }}
@@ -55,7 +54,8 @@
</script>
<style scoped lang="scss">
@import "@assets/app.scss";
@import '@assets/vue-file-manager/_variables';
@import '@assets/vue-file-manager/_mixins';
.empty-page {
position: absolute;
@@ -15,7 +15,7 @@
<MobileToolbar />
<!--Searchbar-->
<SearchBar v-if="$isMinimalScale()" class="mobile-search"/>
<SearchBar class="mobile-search" />
<!--Mobile Actions-->
<MobileActions />
@@ -77,7 +77,7 @@
<FileInfoPanel v-if="fileInfoDetail"/>
<!--If file info panel empty show message-->
<EmptyMessage v-if="!fileInfoDetail" :message="$t('messages.nothing_to_preview')" icon="eye-slash"/>
<EmptyMessage v-if="!fileInfoDetail" :message="$t('messages.nothing_to_preview')" icon="eye-off"/>
</div>
</div>
</template>
@@ -209,7 +209,8 @@
</script>
<style scoped lang="scss">
@import "@assets/app.scss";
@import '@assets/vue-file-manager/_variables';
@import '@assets/vue-file-manager/_mixins';
.button-upload {
display: block;
@@ -218,6 +219,7 @@
}
.mobile-search {
display: none;
margin-bottom: 10px;
margin-top: 10px;
}
@@ -276,11 +278,70 @@
transform: translateX(-20px);
}
.file-leave-active {
position: absolute;
@media only screen and (min-width: 960px) {
.file-content {
position: absolute;
top: 72px;
left: 15px;
right: 15px;
bottom: 0;
@include transition;
&.is-offset {
margin-top: 50px;
}
}
}
@media only screen and (max-width: 660px) {
@media only screen and (max-width: 960px) {
.file-info-container {
display: none;
}
.mobile-search {
display: block;
}
}
@media only screen and (max-width: 690px) {
.file-list {
&.grid {
grid-template-columns: repeat(auto-fill, 120px);
}
}
.files-container {
padding-left: 15px;
padding-right: 15px;
top: 0;
left: 0;
right: 0;
bottom: 0;
position: absolute;
overflow-y: auto;
}
.file-content {
position: absolute;
top: 0;
left: 0px;
right: 0px;
bottom: 0;
@include transition;
&.is-offset {
margin-top: 50px;
}
}
.mobile-search {
margin-bottom: 0;
}
.file-info-container {
display: none;
}
@@ -7,12 +7,15 @@
<div class="flex">
<div class="icon">
<div class="icon-preview">
<FontAwesomeIcon :icon="filePreviewIcon"></FontAwesomeIcon>
<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.name }}</span>
<span class="mimetype" v-if="fileInfoDetail.mimetype">{{ fileInfoDetail.mimetype }}</span>
<span class="mimetype" v-if="fileInfoDetail.mimetype">.{{ fileInfoDetail.mimetype }}</span>
</div>
</div>
</div>
@@ -42,8 +45,8 @@
<li v-if="$checkPermission(['master'])" class="list-info-item">
<b>{{ $t('file_detail.where') }}</b>
<div class="action-button" @click="moveItem">
<FontAwesomeIcon class="icon" icon="pencil-alt" />
<span>{{ fileInfoDetail.parent ? fileInfoDetail.parent.name : $t('locations.home') }}</span>
<edit-2-icon size="10" class="edit-icon"></edit-2-icon>
</div>
</li>
@@ -51,11 +54,13 @@
<li v-if="$checkPermission('master') && fileInfoDetail.shared" class="list-info-item">
<b>{{ $t('file_detail.shared') }}</b>
<div class="action-button" @click="shareItemOptions">
<FontAwesomeIcon class="icon" :icon="sharedIcon" />
<span>{{ sharedInfo }}</span>
<edit-2-icon size="10" class="edit-icon"></edit-2-icon>
</div>
<div class="sharelink">
<FontAwesomeIcon class="lock-icon" :icon="lockIcon" @click="shareItemOptions" />
<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" :value="fileInfoDetail.shared.link" />
</div>
</li>
@@ -64,6 +69,7 @@
</template>
<script>
import { Edit2Icon, LockIcon, UnlockIcon, ImageIcon, VideoIcon, FolderIcon, FileIcon } from 'vue-feather-icons'
import FilePreview from '@/components/FilesView/FilePreview'
import CopyInput from '@/components/Others/Forms/CopyInput'
import {mapGetters} from 'vuex'
@@ -73,12 +79,20 @@
name: 'FileInfoPanel',
components: {
FilePreview,
FolderIcon,
UnlockIcon,
VideoIcon,
CopyInput,
ImageIcon,
FileIcon,
Edit2Icon,
LockIcon,
},
computed: {
...mapGetters(['fileInfoDetail', 'permissionOptions']),
filePreviewIcon() {
switch (this.fileInfoDetail.type) {
fileType() {
return this.fileInfoDetail.type
/* switch () {
case 'folder':
return 'folder'
break;
@@ -94,7 +108,7 @@
case 'file':
return 'file-audio'
break;
}
}*/
},
sharedInfo() {
@@ -117,8 +131,8 @@
return 'download'
}
},
lockIcon() {
return this.fileInfoDetail.shared.protected ? 'lock' : 'lock-open'
isLocked() {
return this.fileInfoDetail.shared.protected
}
},
methods: {
@@ -135,15 +149,14 @@
</script>
<style scoped lang="scss">
@import "@assets/app.scss";
@import '@assets/vue-file-manager/_variables';
@import '@assets/vue-file-manager/_mixins';
.file-info-content {
padding-bottom: 20px;
}
.file-headline {
background: $light_background;
padding: 12px;
margin-bottom: 20px;
border-radius: 8px;
@@ -153,37 +166,19 @@
}
.icon-preview {
height: 42px;
width: 42px;
border-radius: 8px;
display: inline-flex;
align-items: center;
justify-content: center;
padding: 0;
background: white;
text-align: center;
cursor: pointer;
white-space: nowrap;
outline: none;
border: none;
/deep/ svg {
@include font-size(22);
path {
fill: $theme;
}
}
&:hover {
.icon path {
fill: $theme;
}
}
}
.file-info {
padding-left: 12px;
padding-left: 10px;
width: 100%;
word-break: break-all;
@@ -196,7 +191,7 @@
}
.mimetype {
@include font-size(14);
@include font-size(12);
font-weight: 600;
color: $theme;
display: block;
@@ -205,11 +200,10 @@
}
.list-info {
padding-left: 12px;
.list-info-item {
display: block;
padding-top: 15px;
padding-top: 20px;
&:first-child {
padding-top: 0;
@@ -218,14 +212,9 @@
.action-button {
cursor: pointer;
.icon {
@include font-size(10);
.edit-icon {
display: inline-block;
margin-right: 2px;
path {
fill: $theme;
}
margin-left: 3px;
}
}
@@ -252,22 +241,10 @@
margin-top: 10px;
.lock-icon {
@include font-size(10);
display: inline-block;
width: 10px;
width: 15px;
margin-right: 9px;
cursor: pointer;
path {
fill: $text;
}
&:hover {
path {
fill: $theme;
}
}
}
.copy-sharelink {
@@ -278,11 +255,6 @@
@media (prefers-color-scheme: dark) {
.file-headline {
background: $dark_mode_foreground;
.icon-preview {
background: $dark_mode_background;
}
.file-info {
@@ -313,14 +285,10 @@
.lock-icon {
path {
fill: $dark_mode_text_primary;
}
&:hover {
path {
fill: $theme;
path, rect {
stroke: $theme;
}
}
}
@@ -52,12 +52,12 @@
<!--Shared Icon-->
<div v-if="$checkPermission('master') && data.shared" class="item-shared">
<FontAwesomeIcon class="shared-icon" icon="share"/>
<link-icon size="12" class="shared-icon"></link-icon>
</div>
<!--Participant owner Icon-->
<div v-if="$checkPermission('master') && data.user_scope !== 'master'" class="item-shared">
<FontAwesomeIcon class="shared-icon" icon="user-edit"/>
<user-plus-icon size="12" class="shared-icon"></user-plus-icon>
</div>
<!--Filesize-->
@@ -79,6 +79,7 @@
</template>
<script>
import { LinkIcon, UserPlusIcon } from 'vue-feather-icons'
import {debounce} from 'lodash'
import {mapGetters} from 'vuex'
import {events} from '@/bus'
@@ -86,6 +87,10 @@
export default {
name: 'FileItemGrid',
props: ['data'],
components: {
UserPlusIcon,
LinkIcon,
},
computed: {
...mapGetters([
'FilePreviewType', 'sharedDetail'
@@ -155,9 +160,9 @@
// Go to folder
if (this.$isThisLocation('public')) {
this.$store.dispatch('browseShared', [this.data, false])
this.$store.dispatch('browseShared', [{folder: this.data, back: false, init: false}])
} else {
this.$store.dispatch('getFolder', [this.data, false])
this.$store.dispatch('getFolder', [{folder: this.data, back: false, init: false}])
}
}
@@ -187,11 +192,11 @@
}
if (this.isFolder) {
// Go to folder
if (this.$isThisLocation('public')) {
this.$store.dispatch('browseShared', [this.data, false])
this.$store.dispatch('browseShared', [{folder: this.data, back: false, init: false}])
} else {
this.$store.dispatch('getFolder', [this.data, false])
this.$store.dispatch('getFolder', [{folder: this.data, back: false, init: false}])
}
}
},
@@ -224,7 +229,8 @@
</script>
<style scoped lang="scss">
@import "@assets/app.scss";
@import '@assets/vue-file-manager/_variables';
@import '@assets/vue-file-manager/_mixins';
.show-actions {
cursor: pointer;
@@ -240,6 +246,7 @@
}
.file-wrapper {
user-select: none;
position: relative;
text-align: center;
display: inline-block;
@@ -254,9 +261,9 @@
.item-size,
.item-length {
@include font-size(12);
@include font-size(11);
font-weight: 400;
color: $text-muted;
color: rgba($text, 0.7);
display: inline-block;
}
@@ -275,10 +282,10 @@
}
.shared-icon {
@include font-size(9);
vertical-align: middle;
path {
fill: $theme;
path, circle, line {
stroke: $theme;
}
}
}
@@ -404,6 +411,55 @@
}
}
@media only screen and (max-width: 960px) {
.file-wrapper {
.icon-item {
margin-bottom: 15px;
}
}
}
@media only screen and (max-width: 690px) {
.file-wrapper {
.file-item {
width: 120px;
}
.icon-item {
margin-bottom: 10px;
height: 90px;
.file-icon {
@include font-size(75);
}
.file-icon-text {
@include font-size(12);
}
.folder-icon {
@include font-size(75);
margin-top: 0;
margin-bottom: 0;
}
.image {
width: 90px;
height: 90px;
}
}
.item-name .name {
@include font-size(13);
line-height: .9;
max-height: 30px;
}
}
}
@media (prefers-color-scheme: dark) {
.file-wrapper {
@@ -51,12 +51,12 @@
<!--Shared Icon-->
<div v-if="$checkPermission('master') && data.shared" class="item-shared">
<FontAwesomeIcon class="shared-icon" icon="share"/>
<link-icon size="12" class="shared-icon"></link-icon>
</div>
<!--Participant owner Icon-->
<div v-if="$checkPermission('master') && data.user_scope !== 'master'" class="item-shared">
<FontAwesomeIcon class="shared-icon" icon="user-edit"/>
<user-plus-icon size="12" class="shared-icon"></user-plus-icon>
</div>
<!--Filesize and timestamp-->
@@ -80,6 +80,7 @@
</template>
<script>
import { LinkIcon, UserPlusIcon } from 'vue-feather-icons'
import {debounce} from 'lodash'
import {mapGetters} from 'vuex'
import {events} from '@/bus'
@@ -87,6 +88,10 @@
export default {
name: 'FileItemList',
props: ['data'],
components: {
UserPlusIcon,
LinkIcon,
},
computed: {
...mapGetters(['FilePreviewType']),
isFolder() {
@@ -164,9 +169,9 @@
// Go to folder
if (this.$isThisLocation('public')) {
this.$store.dispatch('browseShared', [this.data, false])
this.$store.dispatch('browseShared', [{folder: this.data, back: false, init: false}])
} else {
this.$store.dispatch('getFolder', [this.data, false])
this.$store.dispatch('getFolder', [{folder: this.data, back: false, init: false}])
}
}
@@ -194,9 +199,9 @@
if (this.isFolder) {
if (this.$isThisLocation('public')) {
this.$store.dispatch('browseShared', [this.data, false])
this.$store.dispatch('browseShared', [{folder: this.data, back: false, init: false}])
} else {
this.$store.dispatch('getFolder', [this.data, false])
this.$store.dispatch('getFolder', [{folder: this.data, back: false, init: false}])
}
}
},
@@ -229,9 +234,11 @@
</script>
<style scoped lang="scss">
@import "@assets/app.scss";
@import '@assets/vue-file-manager/_variables';
@import '@assets/vue-file-manager/_mixins';
.file-wrapper {
user-select: none;
position: relative;
&:hover {
@@ -278,19 +285,19 @@
}
.shared-icon {
@include font-size(9);
vertical-align: middle;
path {
fill: $theme;
path, circle, line {
stroke: $theme;
}
}
}
.item-size,
.item-length {
@include font-size(12);
@include font-size(11);
font-weight: 400;
color: $text-muted;
color: rgba($text, 0.7);
}
.name {
@@ -412,6 +419,7 @@
}
@media (prefers-color-scheme: dark) {
.file-wrapper {
.icon-item .file-icon {
@@ -437,8 +445,16 @@
}
}
.item-name .name {
color: $dark_mode_text_primary;
.item-name {
.name {
color: $dark_mode_text_primary;
}
.item-size,
.item-length {
color: $dark_mode_text_secondary;
}
}
}
}
@@ -26,7 +26,8 @@
</script>
<style scoped lang="scss">
@import "@assets/app.scss";
@import '@assets/vue-file-manager/_variables';
@import '@assets/vue-file-manager/_mixins';
.preview {
width: 100%;
@@ -1,24 +1,37 @@
<template>
<button class="mobile-action-button">
<FontAwesomeIcon class="icon" :icon="icon"></FontAwesomeIcon>
<span class="label">
<slot></slot>
</span>
<div class="flex">
<folder-plus-icon v-if="icon === 'folder-plus'" size="15" class="icon"></folder-plus-icon>
<list-icon v-if="icon === 'th-list'" size="15" class="icon"></list-icon>
<trash-icon v-if="icon === 'trash'" size="15" class="icon"></trash-icon>
<grid-icon v-if="icon === 'th'" size="15" class="icon"></grid-icon>
<span class="label">
<slot></slot>
</span>
</div>
</button>
</template>
<script>
import { FolderPlusIcon, ListIcon, GridIcon, TrashIcon } from 'vue-feather-icons'
export default {
name: 'MobileActionButton',
props: [
'icon'
],
components: {
FolderPlusIcon,
TrashIcon,
ListIcon,
GridIcon,
}
}
</script>
<style scoped lang="scss">
@import "@assets/app.scss";
@import '@assets/vue-file-manager/_variables';
@import '@assets/vue-file-manager/_mixins';
.mobile-action-button {
background: $light_background;
@@ -28,6 +41,11 @@
cursor: pointer;
border: none;
.flex {
display: flex;
align-items: center;
}
.icon {
margin-right: 10px;
@include font-size(14);
@@ -44,8 +62,8 @@
.mobile-action-button {
background: $dark_mode_foreground;
.icon path {
fill: $theme;
path, line, polyline, rect, circle {
stroke: $theme;
}
.label {
@@ -1,31 +1,29 @@
<template>
<button class="mobile-action-button">
<FontAwesomeIcon class="icon" :icon="icon"></FontAwesomeIcon>
<label label="file" class="label button file-input button-base">
<slot></slot>
<input
accept="*"
v-show="false"
@change="emmitFiles"
id="file"
type="file"
name="files[]"
multiple
/>
</label>
<div class="flex">
<upload-cloud-icon class="icon" size="15"></upload-cloud-icon>
<label label="file" class="label button file-input button-base">
<slot></slot>
<input
@change="emmitFiles"
v-show="false"
id="file"
type="file"
name="files[]"
multiple
/>
</label>
</div>
</button>
</template>
<script>
import { UploadCloudIcon } from 'vue-feather-icons'
export default {
name: 'MobileActionButtonUpload',
props: [
'icon'
],
data() {
return {
files: undefined
}
components: {
UploadCloudIcon,
},
methods: {
emmitFiles(e) {
@@ -36,8 +34,8 @@
</script>
<style scoped lang="scss">
@import "@assets/app.scss";
@import '@assets/vue-file-manager/_variables';
@import '@assets/vue-file-manager/_mixins';
.mobile-action-button {
background: $light_background;
@@ -47,12 +45,19 @@
cursor: pointer;
border: none;
.flex {
display: flex;
align-items: center;
}
.icon {
margin-right: 8px;
vertical-align: middle;
margin-right: 10px;
@include font-size(14);
}
.label {
vertical-align: middle;
@include font-size(14);
font-weight: 700;
color: $text;
@@ -63,8 +68,8 @@
.mobile-action-button {
background: $dark_mode_foreground;
.icon path {
fill: $theme;
path, line, polyline, rect, circle {
stroke: $theme;
}
.label {
@@ -1,22 +1,22 @@
<template>
<div id="mobile-actions-wrapper" v-if="$isMinimalScale()">
<div id="mobile-actions-wrapper">
<!--Actions for trash location with MASTER permission--->
<div v-if="$isThisLocation(['trash', 'trash-root']) && $checkPermission('master')" class="mobile-actions">
<MobileActionButton @click.native="switchPreview" :icon="previewIcon">
{{ previewText }}
</MobileActionButton>
<MobileActionButton @click.native="$store.dispatch('emptyTrash')" icon="trash-alt">
<MobileActionButton @click.native="$store.dispatch('emptyTrash')" icon="trash">
{{ $t('context_menu.empty_trash') }}
</MobileActionButton>
</div>
<!--ContextMenu for Base location with MASTER permission-->
<div v-if="$isThisLocation(['base', 'shared', 'public']) && $checkPermission(['master', 'editor'])" class="mobile-actions">
<div v-if="$isThisLocation(['base', 'public']) && $checkPermission(['master', 'editor'])" class="mobile-actions">
<MobileActionButton @click.native="createFolder" icon="folder-plus">
{{ $t('context_menu.add_folder') }}
</MobileActionButton>
<MobileActionButtonUpload @input.native="$uploadFiles" icon="upload">
<MobileActionButtonUpload>
{{ $t('context_menu.upload') }}
</MobileActionButtonUpload>
<MobileActionButton @click.native="switchPreview" :icon="previewIcon">
@@ -25,7 +25,7 @@
</div>
<!--ContextMenu for Base location with VISITOR permission-->
<div v-if="$isThisLocation(['base', 'shared', 'public']) && $checkPermission('visitor')" class="mobile-actions">
<div v-if="($isThisLocation(['base', 'shared', 'public']) && $checkPermission('visitor')) || ($isThisLocation(['latest', 'shared']) && $checkPermission('master'))" class="mobile-actions">
<MobileActionButton @click.native="switchPreview" :icon="previewIcon">
{{ previewText }}
</MobileActionButton>
@@ -81,10 +81,11 @@
</script>
<style scoped lang="scss">
@import "@assets/app.scss";
@import '@assets/vue-file-manager/_variables';
@import '@assets/vue-file-manager/_mixins';
#mobile-actions-wrapper {
display: none;
background: white;
position: sticky;
top: 35px;
@@ -98,6 +99,13 @@
overflow-x: auto;
}
@media only screen and (max-width: 960px) {
#mobile-actions-wrapper {
display: block;
}
}
@media (prefers-color-scheme: dark) {
#mobile-actions-wrapper {
background: $dark_mode_background;
+262 -74
View File
@@ -9,79 +9,205 @@
>
<div class="menu-wrapper">
<!--Item Thumbnail-->
<ThumbnailItem class="item-thumbnail" :item="fileInfoDetail" info="metadata"/>
<!--Mobile for trash location-->
<ul v-if="$isThisLocation(['trash', 'trash-root']) && $checkPermission('master')" class="menu-options">
<li class="menu-option" @click="$store.dispatch('restoreItem', fileInfoDetail)" v-if="fileInfoDetail">
{{ $t('context_menu.restore') }}
</li>
<li class="menu-option" @click="downloadItem" v-if="! isFolder">
{{ $t('context_menu.download') }}
</li>
<li class="menu-option delete" @click="deleteItem" v-if="fileInfoDetail">
{{ $t('context_menu.delete') }}
</li>
</ul>
<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)" v-if="fileInfoDetail">
<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">
<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" v-if="! isFolder">
<li class="menu-option" @click="downloadItem">
<div class="icon">
<download-cloud-icon size="17"></download-cloud-icon>
</div>
<div class="text-label">
{{ $t('context_menu.download') }}
</div>
</li>
</ul>
</div>
<!--Mobile for Base location-->
<ul v-if="$isThisLocation(['shared']) && $checkPermission('master')" class="menu-options">
<li class="menu-option" @click="addToFavourites" v-if="fileInfoDetail && isFolder">
{{ isInFavourites ? $t('context_menu.remove_from_favourites') : $t('context_menu.add_to_favourites') }}
</li>
<li class="menu-option" @click="renameItem" v-if="fileInfoDetail">
{{ $t('context_menu.rename') }}
</li>
<li class="menu-option" @click="shareItem" v-if="fileInfoDetail">
{{ fileInfoDetail.shared ? $t('context_menu.share_edit') : $t('context_menu.share') }}
</li>
<li class="menu-option" @click="downloadItem" v-if="! isFolder">
{{ $t('context_menu.download') }}
</li>
<li class="menu-option delete" @click="deleteItem" v-if="fileInfoDetail">
{{ $t('context_menu.delete') }}
</li>
</ul>
<div v-if="$isThisLocation(['shared']) && $checkPermission('master')" class="menu-options">
<ul class="menu-option-group">
<li class="menu-option" @click="addToFavourites" v-if="fileInfoDetail && 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">
<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">
<div class="icon">
<link-icon size="17"></link-icon>
</div>
<div class="text-label">
{{ fileInfoDetail.shared ? $t('context_menu.share_edit') : $t('context_menu.share') }}
</div>
</li>
<li class="menu-option delete" @click="deleteItem" v-if="fileInfoDetail">
<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>
</ul>
</div>
<!--Mobile for Base location-->
<ul v-if="$isThisLocation(['base']) && $checkPermission('master')" class="menu-options">
<li class="menu-option" @click="addToFavourites" v-if="fileInfoDetail && isFolder">
{{ isInFavourites ? $t('context_menu.remove_from_favourites') : $t('context_menu.add_to_favourites') }}
</li>
<li class="menu-option" @click="renameItem" v-if="fileInfoDetail">
{{ $t('context_menu.rename') }}
</li>
<li class="menu-option" @click="moveItem" v-if="fileInfoDetail">
{{ $t('context_menu.move') }}
</li>
<li class="menu-option" @click="shareItem" v-if="fileInfoDetail">
{{ fileInfoDetail.shared ? $t('context_menu.share_edit') : $t('context_menu.share') }}
</li>
<li class="menu-option" @click="downloadItem" v-if="! isFolder">
{{ $t('context_menu.download') }}
</li>
<li class="menu-option delete" @click="deleteItem" v-if="fileInfoDetail">
{{ $t('context_menu.delete') }}
</li>
</ul>
<div v-if="$isThisLocation(['base', 'latest']) && $checkPermission('master')" class="menu-options">
<ul class="menu-option-group" v-if="fileInfoDetail && 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">
<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">
<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">
<div class="icon">
<link-icon size="17"></link-icon>
</div>
<div class="text-label">
{{ fileInfoDetail.shared ? $t('context_menu.share_edit') : $t('context_menu.share') }}
</div>
</li>
<li class="menu-option delete" @click="deleteItem" v-if="fileInfoDetail">
<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>
</ul>
</div>
<!--Mobile for Base location with EDITOR permission-->
<ul v-if="$isThisLocation(['base', 'public']) && $checkPermission('editor')" class="menu-options">
<li class="menu-option" @click="renameItem" v-if="fileInfoDetail">
{{ $t('context_menu.rename') }}
</li>
<li class="menu-option" @click="moveItem" v-if="fileInfoDetail">
{{ $t('context_menu.move') }}
</li>
<li class="menu-option" @click="downloadItem" v-if="! isFolder">
{{ $t('context_menu.download') }}
</li>
</ul>
<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">
<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">
<div class="icon">
<corner-down-right-icon size="17"></corner-down-right-icon>
</div>
<div class="text-label">
{{ $t('context_menu.move') }}
</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>
</ul>
</div>
<!--Mobile for Base location with VISITOR permission-->
<ul v-if="$isThisLocation(['base', 'public']) && $checkPermission('visitor')" class="menu-options">
<li class="menu-option" @click="downloadItem" v-if="! isFolder">
{{ $t('context_menu.download') }}
</li>
</ul>
<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>
</ul>
</div>
</div>
</div>
</transition>
@@ -92,11 +218,37 @@
</template>
<script>
import ThumbnailItem from '@/components/Others/ThumbnailItem'
import {
CornerDownRightIcon,
DownloadCloudIcon,
FolderPlusIcon,
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,
ThumbnailItem,
LifeBuoyIcon,
Trash2Icon,
Edit2Icon,
TrashIcon,
LinkIcon,
StarIcon,
EyeIcon,
},
computed: {
...mapGetters(['fileInfoDetail', 'app']),
isInFavourites() {
@@ -194,10 +346,25 @@
</script>
<style scoped lang="scss">
@import "@assets/app.scss";
@import '@assets/vue-file-manager/_variables';
@import '@assets/vue-file-manager/_mixins';
.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.15);
background: rgba(0, 0, 0, 0.35);
position: absolute;
top: 0;
right: 0;
@@ -215,29 +382,46 @@
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;
box-shadow: $shadow;
background: white;
border-top-left-radius: 8px;
border-top-right-radius: 8px;
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(15);
@include font-size(14);
cursor: pointer;
width: 100%;
padding: 20px 10px;
padding: 17px 20px;
text-align: center;
border-bottom: 1px solid $light_mode_border;
&:last-child {
border: none;
@@ -253,12 +437,16 @@
}
.options {
background: $dark_mode_background;
.menu-options {
background: $dark_mode_background;
.menu-option {
.menu-option-group {
border-color: $dark_mode_border_color;
}
.menu-option {
color: $dark_mode_text_primary;
}
}
@@ -1,13 +1,9 @@
<template>
<div class="mobile-toolbar" v-if="$isMinimalScale()">
<div class="mobile-toolbar">
<!-- Go back-->
<div @click="goBack" class="go-back-button">
<FontAwesomeIcon
:class="{'is-visible': browseHistory.length > 0}"
class="icon-back"
icon="chevron-left"
></FontAwesomeIcon>
<chevron-left-icon size="17" :class="{'is-visible': browseHistory.length > 1}" class="icon-back"></chevron-left-icon>
</div>
<!--Folder Title-->
@@ -15,8 +11,8 @@
<!--More Actions-->
<div class="more-actions-button">
<div class="tap-area" @click="showSidebarMenu" v-if="$checkPermission('master')">
<FontAwesomeIcon icon="bars" v-if="isSmallAppSize"></FontAwesomeIcon>
<div class="tap-area" @click="showMobileNavigation" v-if="$checkPermission('master')">
<menu-icon size="17"></menu-icon>
</div>
</div>
</div>
@@ -26,15 +22,19 @@
import ToolbarButtonUpload from '@/components/FilesView/ToolbarButtonUpload'
import ToolbarButton from '@/components/FilesView/ToolbarButton'
import SearchBar from '@/components/FilesView/SearchBar'
import { MenuIcon, ChevronLeftIcon } from 'vue-feather-icons'
import {mapGetters} from 'vuex'
import {events} from '@/bus'
import {last} from 'lodash'
export default {
name: 'MobileToolBar',
components: {
ToolbarButtonUpload,
ChevronLeftIcon,
ToolbarButton,
SearchBar
SearchBar,
MenuIcon,
},
computed: {
...mapGetters([
@@ -49,36 +49,30 @@
directoryName() {
return this.currentFolder ? this.currentFolder.name : this.homeDirectory.name
},
previousFolder() {
const length = this.browseHistory.length - 2
return this.browseHistory[length] ? this.browseHistory[length] : this.homeDirectory
},
isSmallAppSize() {
return this.appSize === 'small'
}
},
data() {
return {
isSidebarMenu: false,
}
},
methods: {
showSidebarMenu() {
this.isSidebarMenu = ! this.isSidebarMenu
events.$emit('show:sidebar')
showMobileNavigation() {
events.$emit('show:mobile-navigation')
},
goBack() {
if (this.previousFolder.location === 'trash-root') {
let previousFolder = last(this.browseHistory)
if (previousFolder.location === 'trash-root') {
this.$store.dispatch('getTrash')
this.$store.commit('FLUSH_BROWSER_HISTORY')
} else if (previousFolder.location === 'shared') {
this.$store.dispatch('getShared')
} else {
if ( this.$isThisLocation('public') ) {
this.$store.dispatch('browseShared', [this.previousFolder, true])
this.$store.dispatch('browseShared', [{folder: previousFolder, back: true, init: false}])
} else {
this.$store.dispatch('getFolder', [this.previousFolder, true])
this.$store.dispatch('getFolder', [{folder: previousFolder, back: true, init: false}])
}
}
},
@@ -93,13 +87,13 @@
</script>
<style scoped lang="scss">
@import "@assets/app.scss";
@import '@assets/vue-file-manager/_variables';
@import '@assets/vue-file-manager/_mixins';
.mobile-toolbar {
background: white;
text-align: center;
display: flex;
display: none;
padding: 10px 0;
position: sticky;
top: 0;
@@ -121,6 +115,7 @@
cursor: pointer;
opacity: 0;
visibility: hidden;
margin-top: -2px;
&.is-visible {
opacity: 1;
@@ -154,10 +149,21 @@
position: absolute;
right: -10px;
top: -20px;
path, line, polyline, rect, circle {
stroke: $text;
}
}
}
}
@media only screen and (max-width: 960px) {
.mobile-toolbar {
display: flex;
}
}
@media (prefers-color-scheme: dark) {
.mobile-toolbar {
@@ -167,8 +173,11 @@
color: $dark_mode_text_primary;
}
.more-actions-button svg path {
fill: $dark_mode_text_primary;
.more-actions-button .tap-area {
path, line, polyline, rect, circle {
stroke: $dark_mode_text_primary;
}
}
}
}
@@ -12,7 +12,8 @@ export default {
</script>
<style scoped lang="scss">
@import "@assets/app.scss";
@import '@assets/vue-file-manager/_variables';
@import '@assets/vue-file-manager/_mixins';
.progress-bar {
width: 100%;
+54 -19
View File
@@ -1,5 +1,11 @@
<template>
<div class="search-bar">
<div class="icon" v-if="!isQuery">
<search-icon size="19"></search-icon>
</div>
<div class="icon" v-if="isQuery" @click="resetQuery">
<x-icon class="pointer" size="19"></x-icon>
</div>
<input
v-model="query"
class="query"
@@ -7,22 +13,21 @@
name="query"
:placeholder="$t('inputs.placeholder_search_files')"
/>
<div class="icon" v-if="!isQuery">
<FontAwesomeIcon icon="search"></FontAwesomeIcon>
</div>
<div class="icon" v-if="isQuery" @click="resetQuery">
<FontAwesomeIcon icon="times" class="pointer"></FontAwesomeIcon>
</div>
</div>
</template>
<script>
import { SearchIcon, XIcon } from 'vue-feather-icons'
import {mapGetters} from 'vuex'
import {debounce} from 'lodash'
import {events} from '@/bus'
export default {
name: 'SearchBar',
components: {
SearchIcon,
XIcon,
},
computed: {
...mapGetters(['currentFolder']),
isQuery() {
@@ -52,9 +57,9 @@
// Get back after delete query to previosly folder
if ( this.$isThisLocation('public') ) {
this.$store.dispatch('browseShared', [this.currentFolder, true])
this.$store.dispatch('browseShared', [{folder: this.currentFolder, back: true, init: false}])
} else {
this.$store.dispatch('getFolder', [this.currentFolder, true])
this.$store.dispatch('getFolder', [{folder: this.currentFolder, back: true, init: false}])
}
}
@@ -69,20 +74,20 @@
</script>
<style scoped lang="scss">
@import "@assets/app.scss";
@import '@assets/vue-file-manager/_variables';
@import '@assets/vue-file-manager/_mixins';
.search-bar {
position: relative;
input {
//width: 100%;
background: $light_background;
background: transparent;
border-radius: 8px;
outline: 0;
padding: 9px 20px;
font-weight: 100;
padding: 9px 20px 9px 43px;
font-weight: 400;
@include font-size(16);
min-width: 380px;
min-width: 175px;
transition: 0.15s all ease;
border: 1px solid white;
-webkit-appearance: none;
@@ -90,7 +95,7 @@
&::placeholder {
color: $text;
@include font-size(14);
font-weight: 400;
font-weight: 500;
}
&:focus {
@@ -108,8 +113,8 @@
.icon {
position: absolute;
top: 0;
right: 0;
padding: 10px 15px;
left: 0;
padding: 11px 15px;
.pointer {
cursor: pointer;
@@ -117,11 +122,41 @@
}
}
@media only screen and (max-width: 1024px) {
.search-bar input {
max-width: 190px;
padding-right: 0;
}
}
@media only screen and (max-width: 690px) {
.search-bar {
input {
min-width: initial;
width: 100%;
padding: 9px 20px 9px 30px;
&:focus {
border: 1px solid transparent;
box-shadow: none;
}
}
.icon {
padding: 11px 15px 11px 0;
}
}
}
@media (prefers-color-scheme: dark) {
.search-bar {
input {
background: $dark_mode_foreground;
border-color: $dark_mode_foreground;
border-color: transparent;
color: $dark_mode_text_primary;
&::placeholder {
color: $dark_mode_text_secondary;
@@ -11,7 +11,8 @@
</script>
<style scoped lang="scss">
@import "@assets/app.scss";
@import '@assets/vue-file-manager/_variables';
@import '@assets/vue-file-manager/_mixins';
#loading-bar-spinner.spinner {
left: 50%;
@@ -1,18 +1,36 @@
<template>
<button class="button" :title="action">
<FontAwesomeIcon class="icon" :icon="source"></FontAwesomeIcon>
<corner-down-right-icon v-if="source === 'move'" size="19"></corner-down-right-icon>
<folder-plus-icon v-if="source === 'folder-plus'" size="19"></folder-plus-icon>
<trash-2-icon v-if="source === 'trash'" size="19"></trash-2-icon>
<list-icon v-if="source === 'th-list'" size="19"></list-icon>
<info-icon v-if="source === 'info'" size="19"></info-icon>
<grid-icon v-if="source === 'th'" size="19"></grid-icon>
<link-icon v-if="source === 'share'" size="19"></link-icon>
</button>
</template>
<script>
import {FolderPlusIcon, Trash2Icon, GridIcon, ListIcon, InfoIcon, CornerDownRightIcon, LinkIcon} from 'vue-feather-icons'
export default {
name: 'ToolbarButton',
props: ['source', 'action']
props: ['source', 'action'],
components: {
CornerDownRightIcon,
FolderPlusIcon,
Trash2Icon,
ListIcon,
GridIcon,
InfoIcon,
LinkIcon,
},
}
</script>
<style scoped lang="scss">
@import "@assets/app.scss";
@import '@assets/vue-file-manager/_variables';
@import '@assets/vue-file-manager/_mixins';
.button {
height: 42px;
@@ -22,42 +40,36 @@
align-items: center;
justify-content: center;
padding: 0;
background: $light_background;
text-align: center;
cursor: pointer;
white-space: nowrap;
outline: none;
border: none;
.icon {
@include font-size(16);
}
@include transition(150ms);
background: transparent;
&:hover {
background: rgba($theme, .1);
background: $light_background;
/deep/ svg path {
@include transition;
fill: $theme;
}
}
&.active {
background: rgba($theme, .1);
/deep/ svg path {
fill: $theme;
path, line, polyline, rect, circle {
@include transition(150ms);
stroke: $theme;
}
}
}
@media (prefers-color-scheme: dark) {
.button {
background: $dark_mode_foreground;
}
.icon path {
fill: $dark_mode_text_primary;
.button {
background: transparent;
&:hover {
background: $dark_mode_foreground;
}
path, line, polyline, rect, circle {
stroke: $dark_mode_text_primary;
}
}
}
</style>
@@ -1,6 +1,6 @@
<template>
<label label="file" class="button file-input">
<FontAwesomeIcon class="icon" :icon="source"></FontAwesomeIcon>
<upload-cloud-icon size="17"></upload-cloud-icon>
<input
@change="emmitFiles"
v-show="false"
@@ -8,15 +8,19 @@
type="file"
name="files[]"
multiple
:disabled="$isThisLocation(['trash', 'trash-root'])"
/>
</label>
</template>
<script>
import { UploadCloudIcon } from 'vue-feather-icons'
export default {
name: 'ToolbarButtonUpload',
props: ['source', 'action'],
props: ['action'],
components: {
UploadCloudIcon,
},
methods: {
emmitFiles(e) {
this.$uploadFiles(e.target.files)
@@ -26,7 +30,8 @@
</script>
<style scoped lang="scss">
@import "@assets/app.scss";
@import '@assets/vue-file-manager/_variables';
@import '@assets/vue-file-manager/_mixins';
.button {
height: 42px;
@@ -36,7 +41,6 @@
align-items: center;
justify-content: center;
padding: 0;
background: $light_background;
text-align: center;
cursor: pointer;
white-space: nowrap;
@@ -44,26 +48,27 @@
border: none;
&:hover {
background: rgba($theme, .1);
background: $light_background;
/deep/ svg path {
@include transition;
fill: $theme;
path, line, polyline, rect, circle {
@include transition(150ms);
stroke: $theme;
}
}
.icon {
@include font-size(16);
}
}
@media (prefers-color-scheme: dark) {
.button {
background: $dark_mode_foreground;
}
.icon path {
fill: $dark_mode_text_primary;
.button {
background: transparent;
&:hover {
background: $dark_mode_foreground;
}
path, line, polyline, rect, circle {
stroke: $dark_mode_text_primary;
}
}
}
</style>
@@ -25,7 +25,8 @@
</script>
<style scoped lang="scss">
@import "@assets/app.scss";
@import '@assets/vue-file-manager/_variables';
@import '@assets/vue-file-manager/_mixins';
.info-panel-enter-active,
.info-panel-leave-active {
@@ -0,0 +1,115 @@
<template>
<ul class="link-group">
<router-link :to="{name: link.routeName}" v-for="(link, i) in navigation" :key="i" :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>
</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,
HardDriveIcon,
SettingsIcon,
Trash2Icon,
PowerIcon,
ShareIcon,
} from 'vue-feather-icons'
export default {
name: 'MenuBar',
components: {
ChevronRightIcon,
UploadCloudIcon,
HardDriveIcon,
SettingsIcon,
Trash2Icon,
PowerIcon,
ShareIcon,
},
props: [
'navigation'
],
}
</script>
<style scoped lang="scss">
@import '@assets/vue-file-manager/_variables';
@import '@assets/vue-file-manager/_mixins';
.link-item {
display: flex;
text-decoration: none;
padding: 17px 0;
width: 100%;
&.power {
.menu-icon {
path, line, polyline, rect, circle {
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 {
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 {
stroke: $dark_mode_text_primary;
}
}
.menu-link {
color: $dark_mode_text_primary;
}
}
}
</style>
@@ -0,0 +1,108 @@
<template>
<header class="mobile-header">
<!-- Go back-->
<div @click="goBack" class="go-back">
<chevron-left-icon size="17" class="icon"></chevron-left-icon>
</div>
<!--Folder Title-->
<div class="location-name">{{ $router.currentRoute.meta.title }}</div>
<!--More Actions-->
<div @click="showMobileNavigation" class="mobile-menu">
<menu-icon size="17" class="icon"></menu-icon>
</div>
</header>
</template>
<script>
import {events} from '@/bus'
import {
ChevronLeftIcon,
MenuIcon,
} from 'vue-feather-icons'
export default {
name: 'MenuBar',
components: {
ChevronLeftIcon,
MenuIcon,
},
methods: {
showMobileNavigation() {
events.$emit('show:mobile-navigation')
},
goBack() {
this.$router.back();
}
}
}
</script>
<style scoped lang="scss">
@import '@assets/vue-file-manager/_variables';
@import '@assets/vue-file-manager/_mixins';
.mobile-header {
padding: 10px 15px;
text-align: center;
background: white;
position: sticky;
display: none;
z-index: 6;
top: 0;
> div {
flex-grow: 1;
align-self: center;
white-space: nowrap;
}
.go-back {
text-align: left;
}
.location-name {
line-height: 1;
text-align: center;
width: 100%;
vertical-align: middle;
@include font-size(15);
color: $text;
font-weight: 700;
max-width: 220px;
overflow: hidden;
text-overflow: ellipsis;
display: inline-block;
}
.mobile-menu {
text-align: right;
}
.icon {
vertical-align: middle;
margin-top: -4px;
}
}
@media only screen and (max-width: 690px) {
.mobile-header {
display: flex;
margin-bottom: 25px;
}
}
@media (prefers-color-scheme: dark) {
.mobile-header {
background: $dark_mode_background;
.location-name {
color: $dark_mode_text_primary;
}
}
}
</style>
@@ -15,7 +15,8 @@
</script>
<style lang="scss" scoped>
@import "@assets/app.scss";
@import '@assets/vue-file-manager/_variables';
@import '@assets/vue-file-manager/_mixins';
.action-button {
cursor: pointer;
@@ -0,0 +1,63 @@
<template>
<div @click="fileViewClick"
@contextmenu.prevent.capture="contextMenu($event, undefined)"
id="files-view">
<ContextMenu/>
<DesktopToolbar/>
<FileBrowser/>
</div>
</template>
<script>
import DesktopToolbar from '@/components/FilesView/DesktopToolbar'
import FileBrowser from '@/components/FilesView/FileBrowser'
import ContextMenu from '@/components/FilesView/ContextMenu'
import {ResizeSensor} from 'css-element-queries'
import {mapGetters} from 'vuex'
import {events} from '@/bus'
export default {
name: 'FilesView',
components: {
DesktopToolbar,
FileBrowser,
ContextMenu,
},
computed: {
...mapGetters(['config']),
},
methods: {
fileViewClick() {
events.$emit('contextMenu:hide')
},
contextMenu(event, item) {
events.$emit('contextMenu:show', event, item)
},
},
}
</script>
<style lang="scss">
@import '@assets/vue-file-manager/_variables';
@import '@assets/vue-file-manager/_mixins';
#files-view {
font-family: 'Nunito', sans-serif;
font-size: 16px;
width: 100%;
height: 100%;
position: relative;
min-width: 320px;
overflow-x: hidden;
padding-left: 15px;
padding-right: 15px;
}
@media only screen and (max-width: 690px) {
#files-view {
padding-left: 0;
padding-right: 0;
}
}
</style>
@@ -2,15 +2,22 @@
<div class="inline-wrapper icon-append copy-input" :class="size" @click="copyUrl">
<input ref="sel" :value="value" id="link-input" type="text" class="input-text" readonly>
<div class="icon">
<FontAwesomeIcon :icon="isCopiedLink ? 'check' : 'link'"/>
<link-icon v-if="! isCopiedLink" size="14"></link-icon>
<check-icon v-if="isCopiedLink" size="14"></check-icon>
</div>
</div>
</template>
<script>
import { LinkIcon, CheckIcon } from 'vue-feather-icons'
export default {
name: 'CopyInput',
props: ['size', 'value'],
components: {
CheckIcon,
LinkIcon,
},
data() {
return {
isCopiedLink: false,
@@ -40,8 +47,10 @@
</script>
<style lang="scss" scoped>
@import "@assets/app.scss";
@import '@assets/vue-file-manager/_variables';
@import '@assets/vue-file-manager/_mixins';
@import "@assets/vue-file-manager/_inapp-forms.scss";
@import "@assets/vue-file-manager/_forms.scss";
// Single page
.copy-input {
@@ -52,7 +61,6 @@
.icon {
padding: 8px 10px;
@include font-size(11);
}
}
@@ -81,9 +89,6 @@
.copy-input {
input {
color: $dark_mode_text_primary;
&:disabled {
}
}
}
}
@@ -7,7 +7,8 @@
<!--If is selected-->
<div class="selected" v-if="selected">
<div class="option-icon" v-if="selected.icon">
<FontAwesomeIcon :icon="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>
</div>
<span class="option-value">{{ selected.label }}</span>
</div>
@@ -17,7 +18,7 @@
<span class="option-value placehoder">{{ placeholder }}</span>
</div>
<FontAwesomeIcon icon="chevron-down" class="chevron"/>
<chevron-down-icon size="19" class="chevron"></chevron-down-icon>
</div>
<!--Options-->
@@ -25,7 +26,8 @@
<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">
<FontAwesomeIcon :icon="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>
@@ -35,9 +37,16 @@
</template>
<script>
import { ChevronDownIcon, Edit2Icon, UserIcon } from 'vue-feather-icons'
export default {
name:'SelectInput',
props: ['options', 'isError', 'default', 'placeholder'],
components: {
Edit2Icon,
UserIcon,
ChevronDownIcon
},
data() {
return {
selected: undefined,
@@ -69,7 +78,8 @@
</script>
<style lang="scss" scoped>
@import "@assets/app.scss";
@import '@assets/vue-file-manager/_variables';
@import '@assets/vue-file-manager/_mixins';
.select {
position: relative;
@@ -77,7 +87,7 @@
}
.input-options {
background: $light_background;
background: $light_mode_input_background;
border-radius: 8px;
position: absolute;
overflow: hidden;
@@ -105,7 +115,7 @@
.input-area {
justify-content: space-between;
background: $light_background;
background: $light_mode_input_background;
border: 1px solid transparent;
@include transition(150ms);
align-items: center;
@@ -138,16 +148,22 @@
.option-icon {
width: 20px;
display: inline-block;
@include font-size(12);
@include font-size(10);
svg {
margin-top: -4px;
vertical-align: middle;
}
}
.option-value {
@include font-size(15);
@include font-size(14);
font-weight: 700;
width: 100%;
vertical-align: middle;
&.placehoder {
color: $light_text;
color: rgba($text, 0.5);
}
}
@@ -39,7 +39,8 @@
</script>
<style lang="scss" scoped>
@import "@assets/app.scss";
@import '@assets/vue-file-manager/_variables';
@import '@assets/vue-file-manager/_mixins';
.input-wrapper {
display: flex;
@@ -0,0 +1,177 @@
<template>
<div class="mobile-main-navigation">
<transition name="context-menu">
<nav v-if="isVisible" class="mobile-navigation">
<!--User Info-->
<div class="user-info">
<UserAvatar size="large" />
<UserHeadline/>
</div>
<!--Navigation-->
<MenuItemList :navigation="navigation" @menu="action" />
</nav>
</transition>
<transition name="fade">
<div v-show="isVisible" class="vignette" @click="closeAndResetContextMenu"></div>
</transition>
</div>
</template>
<script>
import UserHeadline from '@/components/Sidebar/UserHeadline'
import MenuItemList from '@/components/Mobile/MenuItemList'
import UserAvatar from '@/components/Others/UserAvatar'
import {mapGetters} from 'vuex'
import {events} from '@/bus'
export default {
name: 'MenuBar',
components: {
MenuItemList,
UserHeadline,
UserAvatar,
},
computed: {
...mapGetters(['app', 'homeDirectory']),
},
data() {
return {
isVisible: false,
navigation: [
{
icon: 'hard-drive',
title: 'Files',
routeName: 'Files',
},
{
icon: 'latest',
title: 'Recent Uploads',
routeName: 'Files',
},
{
icon: 'share',
title: 'Shared Files',
routeName: 'SharedFiles',
},
{
icon: 'trash',
title: 'Trash',
routeName: 'Trash',
},
{
icon: 'settings',
title: 'Settings',
routeName: 'MobileSettings',
},
{
icon: 'power',
title: 'Log Out',
routeName: 'LogOut',
},
]
}
},
methods: {
action(name) {
if (name === 'latest') {
this.$store.dispatch('getLatest')
}
if (name === 'hard-drive') {
this.$store.dispatch('getFolder', [{folder: this.homeDirectory, back: false, init: true}])
}
this.closeAndResetContextMenu()
},
closeAndResetContextMenu() {
this.isVisible = false
events.$emit('hide:mobile-navigation')
}
},
created() {
events.$on('show:mobile-navigation', () => {
this.isVisible = true
})
}
}
</script>
<style scoped lang="scss">
@import '@assets/vue-file-manager/_variables';
@import '@assets/vue-file-manager/_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;
}
.user-info {
display: flex;
align-items: center;
margin-bottom: 10px;
}
@media only screen and (max-width: 690px) {
}
@media (prefers-color-scheme: dark) {
.mobile-navigation {
background: $dark_mode_background;
}
}
// 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 -2
View File
@@ -1,7 +1,7 @@
<template>
<PopupWrapper name="move">
<!--Title-->
<PopupHeader :title="$t('popup_move_item.title')" />
<PopupHeader :title="$t('popup_move_item.title')" icon="move" />
<!--Content-->
<PopupContent type="height-limited" v-if="pickedItem">
@@ -123,7 +123,6 @@
</script>
<style scoped lang="scss">
@import "@assets/app.scss";
.item-thumbnail {
margin-bottom: 20px;
+13 -5
View File
@@ -36,19 +36,20 @@
</script>
<style lang="scss" scoped>
@import "@assets/app.scss";
@import '@assets/vue-file-manager/_variables';
@import '@assets/vue-file-manager/_mixins';
.page-header {
display: flex;
align-items: center;
padding: 20px 30px;
background: white;
position: sticky;
top: 0;
z-index: 9;
max-width: 700px;
width: 100%;
margin: 20px auto 30px;
.title {
@include font-size(22);
@include font-size(18);
font-weight: 700;
color: $text;
}
@@ -64,6 +65,7 @@
.page-header {
padding: 20px 15px;
margin: 0;
.title {
@include font-size(18);
@@ -71,6 +73,12 @@
}
}
@media only screen and (max-width: 690px) {
.page-header {
display: none;
}
}
@media (prefers-color-scheme: dark) {
.page-header {
@@ -11,7 +11,8 @@
</script>
<style lang="scss" scoped>
@import "@assets/app.scss";
@import '@assets/vue-file-manager/_variables';
@import '@assets/vue-file-manager/_mixins';
.actions {
padding: 20px;
@@ -14,7 +14,8 @@
</script>
<style lang="scss" scoped>
@import "@assets/app.scss";
@import '@assets/vue-file-manager/_variables';
@import '@assets/vue-file-manager/_mixins';
.popup-content {
@@ -1,26 +1,58 @@
<template>
<div class="popup-header">
<h1 class="title">{{ title }}</h1>
<div class="icon">
<corner-down-right-icon v-if="icon === 'move'" size="15" class="title-icon"></corner-down-right-icon>
<link-icon v-if="icon === 'share'" size="17" class="title-icon"></link-icon>
</div>
<div class="label">
<h1 class="title">{{ title }}</h1>
<x-icon @click="closePopup" size="22" class="close-icon"></x-icon>
</div>
</div>
</template>
<script>
import { CornerDownRightIcon, LinkIcon, XIcon } from 'vue-feather-icons'
import {events} from '@/bus'
export default {
name: 'PopupHeader',
props: [
'title'
]
'title', 'icon'
],
components: {
CornerDownRightIcon,
LinkIcon,
XIcon,
},
methods: {
closePopup() {
events.$emit('popup:close')
}
}
}
</script>
<style lang="scss" scoped>
@import "@assets/app.scss";
@import '@assets/vue-file-manager/_variables';
@import '@assets/vue-file-manager/_mixins';
.popup-header {
padding: 20px;
display: flex;
align-items: center;
.icon {
margin-right: 10px;
line-height: 0;
path, line, polyline, rect, circle {
stroke: $theme;
}
}
.title {
@include font-size(18);
@include font-size(17);
font-weight: 700;
color: $text;
}
@@ -30,6 +62,27 @@
color: #8b8f9a;
margin-top: 5px;
}
.label {
display: flex;
justify-content: space-between;
width: 100%;
align-items: center;
.close-icon {
padding: 1px 4px;
border-radius: 6px;
&:hover {
background: $light_background;
line {
stroke: $theme;
}
}
cursor: pointer;
}
}
}
.small {
@@ -38,6 +91,10 @@
}
}
@media only screen and (max-width: 690px) {
}
@media (prefers-color-scheme: dark) {
.popup-header {
.title {
@@ -1,6 +1,6 @@
<template>
<transition name="popup">
<div class="popup" @click.self="closePopup" v-show="isVisibleWrapper">
<div class="popup" @click.self="closePopup" v-if="isVisibleWrapper">
<div class="popup-wrapper">
<slot></slot>
</div>
@@ -46,7 +46,8 @@
</script>
<style lang="scss" scoped>
@import "@assets/app.scss";
@import '@assets/vue-file-manager/_variables';
@import '@assets/vue-file-manager/_mixins';
.popup {
position: absolute;
@@ -129,6 +130,15 @@
}
}
@media only screen and (max-width: 690px) {
.popup-wrapper {
left: 15px;
right: 15px;
padding: 25px 15px;
}
}
@media (prefers-color-scheme: dark) {
.popup-wrapper {
background: $dark_mode_background;
@@ -1,7 +1,7 @@
<template>
<PopupWrapper name="share-create">
<!--Title-->
<PopupHeader :title="$t('popup_share_create.title', {item: itemTypeTitle})" />
<PopupHeader :title="$t('popup_share_create.title', {item: itemTypeTitle})" icon="share" />
<!--Content-->
<PopupContent>
@@ -181,8 +181,6 @@
// Restore data
setTimeout(() => {
this.isGeneratedShared = false
this.shareLink = undefined
this.shareOptions = {
permission: undefined,
password: undefined,
@@ -190,6 +188,8 @@
type: undefined,
unique_id: undefined,
}
this.isGeneratedShared = false
this.shareLink = undefined
}, 150)
})
}
@@ -197,8 +197,8 @@
</script>
<style scoped lang="scss">
@import "@assets/app.scss";
@import "@assets/vue-file-manager/_inapp-forms.scss";
@import '@assets/vue-file-manager/_forms';
.input-wrapper {
+2 -2
View File
@@ -1,7 +1,7 @@
<template>
<PopupWrapper name="share-edit">
<!--Title-->
<PopupHeader :title="$t('popup_share_edit.title')" />
<PopupHeader :title="$t('popup_share_edit.title')" icon="share" />
<!--Content-->
<PopupContent v-if="pickedItem && pickedItem.shared">
@@ -243,8 +243,8 @@
</script>
<style scoped lang="scss">
@import "@assets/app.scss";
@import "@assets/vue-file-manager/_inapp-forms.scss";
@import '@assets/vue-file-manager/_forms';
.input-wrapper {
+12 -5
View File
@@ -11,17 +11,24 @@
</script>
<style lang="scss" scoped>
@import "@assets/app.scss";
@import '@assets/vue-file-manager/_variables';
@import '@assets/vue-file-manager/_mixins';
.text-label {
@include font-size(10);
color: $theme;
text-transform: uppercase;
font-weight: 900;
padding-left: 25px;
@include font-size(12);
color: #AFAFAF;
font-weight: 700;
display: block;
margin-bottom: 5px;
}
@media only screen and (max-width: 1024px) {
.text-label {
padding-left: 20px;
}
}
@media (prefers-color-scheme: dark) {
.text-label {
color: $theme;
@@ -11,14 +11,14 @@
</script>
<style lang="scss" scoped>
@import "@assets/app.scss";
@import '@assets/vue-file-manager/_variables';
@import '@assets/vue-file-manager/_mixins';
.theme-label {
@include font-size(14);
color: $theme;
text-transform: uppercase;
font-weight: 900;
font-weight: 600;
display: block;
margin-bottom: 5px;
margin-bottom: 20px;
}
</style>
@@ -61,7 +61,8 @@
</script>
<style scoped lang="scss">
@import "@assets/app.scss";
@import '@assets/vue-file-manager/_variables';
@import '@assets/vue-file-manager/_mixins';
.file-item {
display: flex;
@@ -104,7 +105,7 @@
.icon-item {
position: relative;
min-width: 40px;
min-width: 52px;
text-align: center;
line-height: 0;
@@ -150,8 +151,8 @@
user-select: none;
max-width: 100%;
border-radius: 5px;
width: 36px;
height: 36px;
width: 52px;
height: 52px;
}
}
}
@@ -159,7 +160,7 @@
.small {
.file-item {
padding: 0 15px;
margin-bottom: 25px;
margin-bottom: 20px;
}
}
+38 -51
View File
@@ -2,9 +2,10 @@
<!--Folder Icon-->
<div class="folder-item-wrapper">
<div class="folder-item" :class="{'is-selected': isSelected}" @click="showTree" :style="indent">
<FontAwesomeIcon class="icon-chevron" :class="{'is-opened': isVisible, 'is-visible': nodes.folders.length !== 0}" icon="chevron-right"/>
<FontAwesomeIcon class="icon" :icon="directoryIcon"/>
<div class="folder-item" :class="{'is-selected': isSelected}" @click="getFolder" :style="indent">
<chevron-right-icon @click.stop="showTree" size="17" class="icon-arrow" :class="{'is-opened': isVisible, 'is-visible': nodes.folders.length !== 0}"></chevron-right-icon>
<hard-drive-icon v-if="nodes.location === 'base'" size="17" class="icon"></hard-drive-icon>
<folder-icon v-if="nodes.location !== 'base'" size="17" class="icon"></folder-icon>
<span class="label">{{ nodes.name }}</span>
</div>
@@ -14,6 +15,7 @@
<script>
import TreeMenu from '@/components/Others/TreeMenu'
import {FolderIcon, ChevronRightIcon, HardDriveIcon} from 'vue-feather-icons'
import {events} from "@/bus"
export default {
@@ -22,20 +24,15 @@
'nodes', 'depth'
],
components: {
ChevronRightIcon,
HardDriveIcon,
FolderIcon,
TreeMenu,
},
computed: {
indent() {
return { paddingLeft: this.depth * 25 + 'px' }
return { paddingLeft: this.depth * 20 + 'px' }
},
directoryIcon() {
if (this.nodes.location === 'base') {
return 'hdd'
} else {
return 'folder'
}
}
},
data() {
return {
@@ -44,10 +41,12 @@
}
},
methods: {
getFolder() {
events.$emit('show-folder-item', this.nodes)
events.$emit('pick-folder', this.nodes)
},
showTree() {
this.isVisible = ! this.isVisible
events.$emit('pick-folder', this.nodes)
}
},
created() {
@@ -61,36 +60,46 @@
if (this.nodes.unique_id == node.unique_id) this.isSelected = true
})
// Select clicked folder
events.$on('show-folder-item', node => {
this.isSelected = false
if (this.nodes.unique_id == node.unique_id)
this.isSelected = true
})
}
}
</script>
<style lang="scss" scoped>
@import "@assets/app.scss";
@import '@assets/vue-file-manager/_variables';
@import '@assets/vue-file-manager/_mixins';
.folder-item {
user-select: none;
display: block;
padding: 15px 20px;
padding: 8px 23px;
@include transition(150ms);
cursor: pointer;
position: relative;
white-space: nowrap;
border-bottom: 1px solid $light_mode_border;
.icon {
@include font-size(18);
line-height: 0;
width: 15px;
margin-right: 9px;
vertical-align: middle;
margin-top: -1px;
path {
fill: $text;
path, line, polyline, rect, circle {
@include transition(150ms);
}
}
.icon-chevron {
.icon-arrow {
@include transition(300ms);
@include font-size(13);
margin-right: 9px;
margin-right: 4px;
vertical-align: middle;
opacity: 0;
@@ -101,14 +110,11 @@
&.is-opened {
@include transform(rotate(90deg));
}
path {
fill: rgba($text, 0.25);
}
}
.label {
@include font-size(15);
@include transition(150ms);
@include font-size(13);
font-weight: 700;
vertical-align: middle;
white-space: nowrap;
@@ -118,16 +124,12 @@
color: $text;
}
&:hover {
background: $light_background;
}
&:hover,
&.is-selected {
background: rgba($theme, .1);
.icon {
path {
fill: $theme;
path, line, polyline, rect, circle {
stroke: $theme;
}
}
@@ -141,33 +143,18 @@
@media (prefers-color-scheme: dark) {
.folder-item {
border-bottom: 1px solid $dark_mode_border_color;
.label {
color: $dark_mode_text_primary;
}
&:hover {
background: $dark_mode_foreground;
background: rgba($theme, .1);
}
&.is-selected {
background: rgba($theme, .1);
}
.icon {
path {
fill: lighten($dark_mode_foreground, 10%);
}
}
.icon-chevron {
path {
fill: $theme;
}
}
}
&.is-selected {
@@ -0,0 +1,182 @@
<template>
<transition name="folder">
<div class="folder-item-wrapper">
<div class="folder-item" :class="{'is-selected': isSelected}" :style="indent" @click="getFolder">
<chevron-right-icon @click.stop="showTree" size="17" class="icon-arrow"
:class="{'is-opened': isVisible, 'is-visible': nodes.folders.length !== 0}"></chevron-right-icon>
<folder-icon size="17" class="icon"></folder-icon>
<span class="label">{{ nodes.name }}</span>
</div>
<TreeMenuNavigator :depth="depth + 1" v-if="isVisible" :nodes="item" v-for="item in nodes.folders"
:key="item.unique_id"/>
</div>
</transition>
</template>
<script>
import TreeMenuNavigator from '@/components/Others/TreeMenuNavigator'
import {FolderIcon, ChevronRightIcon} from 'vue-feather-icons'
import {events} from "@/bus"
export default {
name: 'TreeMenuNavigator',
props: [
'nodes', 'depth'
],
components: {
TreeMenuNavigator,
ChevronRightIcon,
FolderIcon,
},
computed: {
indent() {
let offset = window.innerWidth <= 1024 ? 17 : 22;
let value = this.depth == 0 ? offset : offset + (this.depth * 20);
return {paddingLeft: value + 'px'}
},
},
data() {
return {
isVisible: false,
isSelected: false,
}
},
methods: {
getFolder() {
events.$emit('show-folder', this.nodes)
// Get folder content
this.$store.dispatch('getFolder', [{folder: this.nodes, back: false, init: false}])
},
showTree() {
this.isVisible = !this.isVisible
}
},
created() {
// Select clicked folder
events.$on('show-folder', node => {
this.isSelected = false
if (this.nodes.unique_id == node.unique_id)
this.isSelected = true
})
}
}
</script>
<style lang="scss" scoped>
@import '@assets/vue-file-manager/_variables';
@import '@assets/vue-file-manager/_mixins';
.folder-item {
display: block;
padding: 8px 0;
@include transition(150ms);
cursor: pointer;
position: relative;
white-space: nowrap;
width: 100%;
.icon {
line-height: 0;
width: 15px;
margin-right: 9px;
vertical-align: middle;
margin-top: -1px;
path, line, polyline, rect, circle {
@include transition(150ms);
}
}
.icon-arrow {
@include transition(300ms);
margin-right: 4px;
vertical-align: middle;
opacity: 0;
&.is-visible {
opacity: 1;
}
&.is-opened {
@include transform(rotate(90deg));
}
}
.label {
@include transition(150ms);
@include font-size(13);
font-weight: 700;
vertical-align: middle;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
display: inline-block;
color: $text;
max-width: 130px;
}
&:hover,
&.is-selected {
.icon {
path, line, polyline, rect, circle {
stroke: $theme;
}
}
.label {
color: $theme;
}
}
}
@media only screen and (max-width: 1024px) {
.folder-item {
padding: 8px 0;
}
}
// Dark mode
@media (prefers-color-scheme: dark) {
.folder-item {
.label {
color: $dark_mode_text_primary;
}
&:hover {
background: rgba($theme, .1);
}
&.is-selected {
background: rgba($theme, .1);
}
}
&.is-selected {
background: rgba($theme, .1);
}
}
@media (prefers-color-scheme: dark) and (max-width: 690px) {
.folder-item {
&:hover,
&.is-selected {
background: rgba($theme, .1);
}
}
}
</style>
@@ -0,0 +1,47 @@
<template>
<div class="user-avatar" :class="size">
<img :src="app.user.avatar" :alt="app.user.name">
</div>
</template>
<script>
import {mapGetters} from 'vuex'
export default {
name: 'UserAvatar',
props: [
'size'
],
computed: {
...mapGetters(['app']),
},
}
</script>
<style lang="scss" scoped>
@import '@assets/vue-file-manager/_variables';
@import '@assets/vue-file-manager/_mixins';
.user-avatar {
line-height: 0;
img {
border-radius: 6px;
width: 40px;
height: 40px;
}
&.large {
img {
border-radius: 9px;
width: 52px;
height: 52px;
}
}
}
@media (prefers-color-scheme: dark) {
}
</style>
@@ -60,7 +60,8 @@
</script>
<style lang="scss" scoped>
@import "@assets/app.scss";
@import '@assets/vue-file-manager/_variables';
@import '@assets/vue-file-manager/_mixins';
.dropzone {
position: relative;
@@ -79,12 +80,10 @@
}
.image-preview {
width: 75px;
height: 75px;
width: 56px;
height: 56px;
object-fit: cover;
border-radius: 8px;
border: 1px dashed $theme;
padding: 2px;
}
}
</style>
+2 -1
View File
@@ -34,7 +34,8 @@
</script>
<style lang="scss" scoped>
@import "@assets/app.scss";
@import '@assets/vue-file-manager/_variables';
@import '@assets/vue-file-manager/_mixins';
.vignette {
position: absolute;
@@ -0,0 +1,25 @@
<template>
<div class="content-group">
<TextLabel>{{ title }}</TextLabel>
<slot></slot>
</div>
</template>
<script>
import TextLabel from '@/components/Others/TextLabel'
export default {
name: 'ContentGroup',
props: ['title'],
components: {
TextLabel,
}
}
</script>
<style scoped lang="scss">
.content-group {
margin-bottom: 30px;
}
</style>
@@ -0,0 +1,43 @@
<template>
<section class="content-sidebar">
<slot></slot>
</section>
</template>
<script>
export default {
name: 'ContentSidebar',
}
</script>
<style scoped lang="scss">
@import '@assets/vue-file-manager/_variables';
@import '@assets/vue-file-manager/_mixins';
.content-sidebar {
background: linear-gradient(0deg, rgba(246, 245, 241, 0.4) 0%, rgba(243, 244, 246, 0.4) 100%);
user-select: none;
padding-top: 25px;
overflow-y: auto;
flex: 0 0 225px;
}
@media only screen and (max-width: 1024px) {
.content-sidebar {
flex: 0 0 205px;
}
}
@media only screen and (max-width: 690px) {
.content-sidebar {
display: none;
}
}
@media (prefers-color-scheme: dark) {
.content-sidebar {
background: rgba($dark_mode_foreground, 0.2);
}
}
</style>
@@ -1,183 +0,0 @@
<template>
<div class="file-item">
<!--Thumbnail for item-->
<div class="icon-item">
<!--If is file or image, then link item-->
<span v-if="isFile" class="file-icon-text">{{ file.mimetype }}</span>
<!--Folder thumbnail-->
<FontAwesomeIcon v-if="isFile" class="file-icon" icon="file" />
<!--Image thumbnail-->
<img v-if="isImage" class="image" :src="file.thumbnail" :alt="file.name" />
<!--Else show only folder icon-->
<FontAwesomeIcon v-if="isFolder" class="folder-icon" icon="folder" />
</div>
<!--Name-->
<div class="item-name">
<!--Name-->
<span class="name" >{{ file.name }}</span>
<!--Other attributes-->
<span v-if="! isFolder" class="item-size">{{ file.filesize }}</span>
<span v-if="isFolder" class="item-length">{{ file.items == 0 ? $t('folder.empty') : $tc('folder.item_counts', folderItems) }}, {{ file.created_at }}</span>
</div>
</div>
</template>
<script>
export default {
name: 'FileListItemThumbnail',
props: ['file'],
computed: {
isFolder() {
return this.file.type === 'folder'
},
isFile() {
return this.file.type !== 'folder' && this.file.type !== 'image'
},
isImage() {
return this.file.type === 'image'
}
},
}
</script>
<style scoped lang="scss">
@import "@assets/app.scss";
.file-item {
display: flex;
align-items: center;
padding: 10px 15px;
@include transition(150ms);
cursor: pointer;
&:hover {
background: rgba($theme, .1);
.item-name .name {
color: $theme;
}
}
.item-name {
display: block;
margin-left: 10px;
width: 100%;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
.item-size,
.item-length {
@include font-size(11);
font-weight: 400;
color: $text-muted;
display: block;
}
.name {
white-space: nowrap;
}
.name {
color: $text;
@include font-size(14);
font-weight: 700;
max-height: 40px;
overflow: hidden;
text-overflow: ellipsis;
}
}
.icon-item {
position: relative;
min-width: 40px;
text-align: center;
line-height: 0;
.file-icon {
@include font-size(35);
path {
fill: #fafafc;
stroke: #dfe0e8;
stroke-width: 1;
}
}
.file-icon-text {
top: 40%;
@include font-size(9);
line-height: 1;
margin: 0 auto;
position: absolute;
text-align: center;
left: 0;
right: 0;
color: $theme;
font-weight: 600;
user-select: none;
max-width: 20px;
max-height: 20px;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.image {
object-fit: cover;
user-select: none;
max-width: 100%;
border-radius: 5px;
width: 36px;
height: 36px;
}
}
}
@media (prefers-color-scheme: dark) {
.file-item {
.icon-item .file-icon {
path {
fill: $dark_mode_background;
stroke: #2F3C54;
}
}
&:hover,
&.is-clicked {
background: rgba($theme, .1);
}
.item-name .name {
color: $dark_mode_text_primary;
}
}
}
@media (max-width: 690px) and (prefers-color-scheme: dark){
.file-item {
.icon-item .file-icon {
path {
fill: $dark_mode_foreground;
}
}
}
}
</style>
+216
View File
@@ -0,0 +1,216 @@
<template>
<nav class="menu-bar" v-if="app">
<!--Navigation Icons-->
<div class="icon-navigation menu">
<router-link :to="{name: 'Profile'}" class="icon-navigation-item user">
<UserAvatar />
</router-link>
<router-link :to="{name: 'Files'}" :title="$t('locations.home')" class="icon-navigation-item home">
<div class="button-icon">
<hard-drive-icon size="19"></hard-drive-icon>
</div>
</router-link>
<router-link :to="{name: 'SharedFiles'}" :title="$t('locations.shared')" class="icon-navigation-item shared">
<div class="button-icon">
<share-icon size="19"></share-icon>
</div>
</router-link>
<router-link :to="{name: 'Trash'}" :title="$t('locations.trash')" class="icon-navigation-item trash">
<div class="button-icon">
<trash-2-icon size="19"></trash-2-icon>
</div>
</router-link>
<router-link :to="{name: 'Profile'}" :class="{'is-active': $isThisRoute($route, ['Password'])}" class="icon-navigation-item settings">
<div class="button-icon">
<settings-icon size="19"></settings-icon>
</div>
</router-link>
</div>
<!--User avatar & Logout-->
<ul class="icon-navigation logout">
<li @click="$store.dispatch('logOut')" class="icon-navigation-item">
<div class="button-icon">
<power-icon size="19"></power-icon>
</div>
</li>
</ul>
</nav>
</template>
<script>
import UserAvatar from '@/components/Others/UserAvatar'
import {mapGetters} from 'vuex'
import {
HardDriveIcon,
SettingsIcon,
Trash2Icon,
PowerIcon,
ShareIcon,
} from 'vue-feather-icons'
export default {
name: 'MenuBar',
components: {
HardDriveIcon,
SettingsIcon,
UserAvatar,
Trash2Icon,
PowerIcon,
ShareIcon,
},
computed: {
...mapGetters(['app']),
},
mounted() {
this.$store.dispatch('getAppData')
}
}
</script>
<style scoped lang="scss">
@import '@assets/vue-file-manager/_variables';
@import '@assets/vue-file-manager/_mixins';
.menu-bar {
background: linear-gradient(180deg, rgba(246, 245, 241, 0.8) 0%, rgba(243, 244, 246, 0.8) 100%);
user-select: none;
padding-top: 25px;
display: grid;
width: 72px;
}
.icon-navigation {
text-align: center;
&.menu {
margin-bottom: auto;
}
&.logout {
margin-top: auto;
}
.icon-navigation-item {
display: block;
margin-bottom: 10px;
&.user {
margin-bottom: 20px;
display: block;
}
}
.button-icon {
cursor: pointer;
border-radius: 4px;
padding: 12px;
display: inline-block;
line-height: 0;
@include transition(150ms);
&:hover {
background: darken($light_background, 5%);
}
path, line, polyline, rect, circle {
@include transition(150ms);
stroke: black;
}
}
.router-link-active,
.is-active {
&.home {
.button-icon {
background: rgba($theme, 0.1);
path, line, polyline, rect, circle {
stroke: $theme;
}
}
}
&.shared {
.button-icon {
background: rgba($yellow, 0.1);
path, line, polyline, rect, circle {
stroke: $yellow;
}
}
}
&.trash {
.button-icon {
background: rgba($red, 0.1);
path, line, polyline, rect, circle {
stroke: $red;
}
}
}
&.settings {
.button-icon {
background: rgba($purple, 0.1);
path, line, polyline, rect, circle {
stroke: $purple;
}
}
}
}
}
@media only screen and (max-width: 1024px) {
.menu-bar {
width: 60px;
}
.icon-navigation {
.icon-navigation-item {
margin-bottom: 15px;
}
.button-icon {
padding: 8px;
}
}
}
@media only screen and (max-width: 690px) {
.menu-bar {
display: none;
}
}
@media (prefers-color-scheme: dark) {
.icon-navigation {
.button-icon {
&:hover {
background: $dark_mode_background;
}
path, line, polyline, rect, circle {
stroke: $dark_mode_text_primary;
}
}
}
.menu-bar {
background: $dark_mode_foreground;
}
}
</style>
-460
View File
@@ -1,460 +0,0 @@
<template>
<transition name="sidebar">
<div id="sidebar" v-if="app && (isVisibleSidebar || ! isSmallAppSize)">
<!--User Headline-->
<UserHeadline/>
<!--Content-->
<div class="content-scroller">
<!--Locations-->
<div class="menu-list-wrapper">
<TextLabel>{{ $t('sidebar.locations') }}</TextLabel>
<ul class="menu-list">
<li class="menu-list-item" :class="{'is-active': isBaseLocation}" @click="goHome">
<FontAwesomeIcon class="icon" icon="hdd"/>
<span class="label">{{ $t('locations.home') }}</span>
</li>
<li class="menu-list-item" :class="{'is-active': isSharedLocation}" @click="getShared">
<FontAwesomeIcon class="icon" icon="share"/>
<span class="label">{{ $t('locations.shared') }}</span>
</li>
<li class="menu-list-item" :class="{'is-active': isTrashLocation}" @click="getTrash">
<FontAwesomeIcon class="icon" icon="trash-alt"/>
<span class="label">{{ $t('locations.trash') }}</span>
</li>
</ul>
</div>
<!--Favourites-->
<div class="menu-list-wrapper favourites"
@dragover.prevent="dragEnter"
@dragleave="dragLeave"
@drop="dragFinish($event)"
:class="{ 'is-dragenter': area }"
>
<TextLabel>{{ $t('sidebar.favourites') }}</TextLabel>
<transition-group tag="ul" class="menu-list" name="folder-item">
<li class="empty-list" v-if="app.favourites.length == 0" :key="0">
{{ $t('sidebar.favourites_empty') }}
</li>
<li @click.stop="openFolder(folder)" class="menu-list-item" v-for="folder in app.favourites"
:key="folder.unique_id">
<div>
<FontAwesomeIcon class="icon" icon="folder"/>
<span class="label">{{ folder.name }}</span>
</div>
<FontAwesomeIcon @click.stop="removeFavourite(folder)" v-if="! $isMobile()" class="delete-icon" icon="times"/>
</li>
</transition-group>
</div>
<!--Last Uploads-->
<div class="menu-list-wrapper">
<TextLabel>{{ $t('sidebar.latest') }}</TextLabel>
<p class="empty-list" v-if="app.latest_uploads.length == 0">{{ $t('sidebar.latest_empty') }}</p>
<FileListItemThumbnail @dblclick.native="downloadFile(item)" @click.native="showFileDetail(item)" :file="item" v-for="item in app.latest_uploads" :key="item.unique_id"/>
</div>
</div>
<!--Storage Size Info-->
<StorageSize v-if="config.storageLimit"/>
<!--Mobile logout button-->
<div v-if="isSmallAppSize" class="log-out-button">
<ButtonBase @click.native="$store.dispatch('logOut')" button-style="danger">{{ $t('context_menu.log_out') }}</ButtonBase>
</div>
</div>
</transition>
</template>
<script>
import FileListItemThumbnail from '@/components/Sidebar/FileListItemThumbnail'
import UserHeadline from '@/components/Sidebar/UserHeadline'
import ButtonBase from '@/components/FilesView/ButtonBase'
import StorageSize from '@/components/Sidebar/StorageSize'
import TextLabel from '@/components/Others/TextLabel'
import {mapGetters} from 'vuex'
import {events} from '@/bus'
export default {
name: 'Sidebar',
components: {
FileListItemThumbnail,
UserHeadline,
StorageSize,
ButtonBase,
TextLabel,
},
computed: {
...mapGetters(['homeDirectory', 'app', 'appSize', 'config', 'currentFolder']),
isTrashLocation() {
return this.currentFolder && this.currentFolder.location === 'trash-root' || this.currentFolder && this.currentFolder.location === 'trash'
},
isBaseLocation() {
return this.currentFolder && this.currentFolder.location === 'base'
},
isSharedLocation() {
return this.currentFolder && this.currentFolder.location === 'shared'
},
isSmallAppSize() {
return this.appSize === 'small'
}
},
data() {
return {
area: false,
draggedItem: undefined,
isVisibleSidebar: false,
}
},
methods: {
getShared() {
this.$store.dispatch('getShared')
},
getTrash() {
this.$store.dispatch('getTrash')
},
goHome() {
this.$store.commit('FLUSH_BROWSER_HISTORY')
this.$store.dispatch('getFolder', [this.homeDirectory, true])
},
openFolder(folder) {
// Go to folder
this.$store.dispatch('getFolder', [folder, false])
},
downloadFile(file) {
this.$downloadFile(file.file_url, file.name + '.' + file.mimetype)
},
showFileDetail(file) {
// Dispatch load file info detail
this.$store.dispatch('getFileDetail', file)
// Show panel if is not open
this.$store.dispatch('fileInfoToggle', true)
},
dragEnter() {
if (this.draggedItem && this.draggedItem.type !== 'folder') return
this.area = true
},
dragLeave() {
this.area = false
},
dragFinish() {
this.area = false
// Check if draged item is folder
if (this.draggedItem && this.draggedItem.type !== 'folder') return
// Check if folder exist in favourites
if (this.app.favourites.find(folder => folder.unique_id == this.draggedItem.unique_id)) return
// Store favourites folder
this.$store.dispatch('addToFavourites', this.draggedItem)
},
removeFavourite(folder) {
// Remove favourites folder
this.$store.dispatch('removeFromFavourites', folder)
}
},
mounted() {
this.$store.dispatch('getAppData')
// Listen for dragstart folder items
events.$on('dragstart', (item) => this.draggedItem = item)
// Listen for show sidebar
events.$on('show:sidebar', () => {
this.isVisibleSidebar = !this.isVisibleSidebar
})
// Listen for hide sidebar
events.$on('show:content', () => {
if (this.isVisibleSidebar) this.isVisibleSidebar = false
})
}
}
</script>
<style scoped lang="scss">
@import "@assets/app.scss";
#sidebar {
position: relative;
flex: 0 0 265px;
background: $light_background;
}
.content-scroller {
bottom: 50px;
position: absolute;
top: 75px;
left: 0;
right: 0;
overflow-x: auto;
.menu-list-wrapper:first-of-type {
margin-top: 20px;
}
.text-label {
margin-left: 15px;
}
}
.menu-list-wrapper {
margin-bottom: 20px;
.menu-list {
.menu-list-item {
display: block;
padding: 8px 15px 8px 25px;
@include transition(150ms);
cursor: pointer;
position: relative;
white-space: nowrap;
.icon {
@include font-size(13);
width: 15px;
margin-right: 9px;
vertical-align: middle;
path {
fill: $text;
}
}
.delete-icon {
display: none;
position: absolute;
right: 15px;
top: 50%;
@include transform(translateY(-50%));
@include font-size(12);
path {
fill: $text-muted;
}
}
.label {
@include font-size(14);
font-weight: 700;
vertical-align: middle;
white-space: nowrap;
max-width: 210px;
overflow: hidden;
text-overflow: ellipsis;
display: inline-block;
}
&:hover,
&.is-active {
background: rgba($theme, .1);
.icon {
path {
fill: $theme;
}
}
.label {
color: $theme;
}
.delete-icon {
display: block;
}
}
}
}
&.favourites {
&.is-dragenter {
.menu-list {
border: 2px dashed $theme;
border-radius: 8px;
}
}
.menu-list {
border: 2px dashed transparent;
.menu-list-item {
padding: 8px 23px;
.icon {
margin-right: 5px;
@include font-size(14);
width: 20px;
path {
fill: $theme;
}
}
&:hover {
background: rgba($theme, .1);
.icon {
path {
fill: $theme;
}
}
}
}
}
}
.empty-list {
@include font-size(12);
color: $text-muted;
display: block;
padding: 15px;
}
}
.log-out-button {
margin-top: 15px;
padding: 15px;
button {
width: 100%;
}
}
@media only screen and (max-width: 1024px) {
#sidebar {
flex: 0 0 265px;
}
.menu-list-wrapper .menu-list .menu-list-item .label {
max-width: 190px;
}
}
@media only screen and (max-width: 690px) {
.content-scroller {
bottom: initial;
position: relative;
top: initial;
overflow-x: initial;
}
#sidebar {
position: absolute;
overflow-y: auto;
top: 0;
left: 0;
right: 0;
z-index: 99;
bottom: 0;
background: white;
opacity: 1;
}
.menu-list-wrapper {
&.favourites {
.menu-list .menu-list-item {
padding: 10px 26px;
}
}
.menu-list .menu-list-item {
padding: 10px 26px;
.label {
@include font-size(14);
}
}
}
.log-out-button {
margin-top: 0;
}
}
@media (prefers-color-scheme: dark) {
#sidebar {
background: $dark_mode_background;
}
.menu-list-wrapper {
.menu-list .menu-list-item {
.label {
color: $dark_mode_text_primary;
}
.icon {
path {
fill: lighten($dark_mode_foreground, 10%);
}
}
&:hover {
background: rgba($theme, .1);
}
}
}
}
@media (prefers-color-scheme: dark) and (max-width: 690px) {
#sidebar {
background: $dark_mode_background;
}
}
.sidebar-enter-active {
transition: all 300ms ease;
}
.sidebar-enter /* .list-leave-active below version 2.1.8 */
{
opacity: 0;
transform: translateX(-100%);
}
// Transition
.folder-item-move {
transition: transform 300s ease;
}
.folder-item-enter-active {
transition: all 300ms ease;
}
.folder-item-leave-active {
transition: all 300ms;
}
.folder-item-enter, .folder-item-leave-to /* .list-leave-active below version 2.1.8 */
{
opacity: 0;
transform: translateX(30px);
}
.folder-item-leave-active {
position: absolute;
}
</style>
@@ -24,7 +24,8 @@
</script>
<style scoped lang="scss">
@import "@assets/app.scss";
@import '@assets/vue-file-manager/_variables';
@import '@assets/vue-file-manager/_mixins';
.storage-size {
position: absolute;
+11 -192
View File
@@ -1,26 +1,7 @@
<template>
<div class="user-headline-wrapper" v-if="app">
<div class="user-headline" @click="openMenu">
<div class="user-avatar">
<img :src="app.user.avatar" :alt="app.user.name">
</div>
<div class="user-name">
<b class="name">{{ app.user.name }}</b>
<span class="email">{{ app.user.email }}</span>
</div>
</div>
<transition name="user-menu">
<div class="user-menu" v-if="isOpenedMenu">
<ul class="menu-options" @click="closeMenu">
<li class="menu-option">
<router-link :to="{name: 'Profile'}">
{{ $t('context_menu.profile_settings') }}
</router-link>
</li>
<li class="menu-option" @click="$store.dispatch('logOut')">{{ $t('context_menu.log_out') }}</li>
</ul>
</div>
</transition>
<div class="user-meta" v-if="app">
<b class="name">{{ app.user.name }}</b>
<span class="email">{{ app.user.email }}</span>
</div>
</template>
@@ -31,198 +12,36 @@
export default {
name: 'UserHeadline',
computed: {
...mapGetters(['app', 'appSize']),
isSmallAppSize() {
return this.appSize === 'small'
}
},
data() {
return {
isOpenedMenu: false,
}
},
methods: {
openMenu() {
// If is mobile, then go to user settings page, else, open menu
if ( this.isSmallAppSize ) {
events.$emit('show:sidebar')
this.$router.push({name: 'Profile'})
} else {
this.isOpenedMenu = !this.isOpenedMenu
}
},
closeMenu() {
this.isOpenedMenu = !this.isOpenedMenu
},
...mapGetters(['app']),
},
}
</script>
<style scoped lang="scss">
@import "@assets/app.scss";
@import '@assets/vue-file-manager/_variables';
@import '@assets/vue-file-manager/_mixins';
.user-headline-wrapper {
position: relative;
}
.user-headline {
position: absolute;
top: 0;
left: 0;
right: 0;
margin: 15px;
padding: 5px;
user-select: none;
border-radius: 8px;
display: flex;
align-items: center;
cursor: pointer;
@include transition(150ms);
background: darken($light_background, 3%);
&:active {
transform: scale(0.95);
}
}
.user-name {
.name, .email {
white-space: nowrap;
width: 180px;
overflow: hidden;
text-overflow: ellipsis;
}
.user-meta {
padding-left: 20px;
.name {
display: block;
@include font-size(17);
line-height: 1;
color: $text;
@include font-size(18);
}
.email {
@include font-size(13);
color: $theme;
display: block;
margin-top: 2px;
@include font-size(12);
color: $theme;
font-weight: 600;
}
}
.user-avatar {
line-height: 0;
margin-right: 15px;
img {
width: 50px;
height: 50px;
object-fit: cover;
border-radius: 8px;
}
}
.user-menu {
position: absolute;
top: 75px;
left: 0;
right: 0;
margin: 15px;
z-index: 9;
}
.menu-options {
list-style: none;
width: 100%;
margin: 0;
padding: 0;
box-shadow: $shadow;
background: white;
border-radius: 8px;
.menu-option {
font-weight: 700;
@include font-size(15);
padding: 15px 30px;
cursor: pointer;
width: 100%;
color: $text;
a {
text-decoration: none;
color: $text;
}
&:hover {
background: $light_background;
color: $theme;
}
}
}
@media only screen and (max-width: 690px) {
.user-headline {
position: relative;
margin-bottom: 40px;
background: transparent;
padding: 0;
}
}
@media (prefers-color-scheme: dark) {
.user-headline {
background: $dark_mode_background;
padding: 0;
&:hover {
background: transparent;
}
}
.user-name {
.name {
color: $dark_mode_text_primary;
}
}
.menu-options {
background: $dark_mode_background;
.menu-option {
color: $dark_mode_text_primary;
a {
color: $dark_mode_text_primary;
}
&:hover {
background: $dark_mode_foreground;
}
}
}
}
// Transition
.user-menu-enter-active {
transition: all 150ms ease;
}
.user-menu-leave-active {
transition: all 150ms ease;
}
.user-menu-enter,
.user-menu-leave-to {
opacity: 0;
transform: translateY(-35%);
}
</style>