Merge remote-tracking branch 'origin/version-v1.8.1'

# Conflicts:
#	config/vuefilemanager.php
#	public/chunks/admin-account.js
#	public/chunks/admin.js
#	public/chunks/app-appearance.js
#	public/chunks/app-billings.js
#	public/chunks/app-email.js
#	public/chunks/app-index.js
#	public/chunks/app-others.js
#	public/chunks/app-payments.js
#	public/chunks/app-settings.js
#	public/chunks/app-setup.js
#	public/chunks/billings-detail.js
#	public/chunks/contact-us.js
#	public/chunks/dashboard.js
#	public/chunks/database.js
#	public/chunks/environment-setup.js
#	public/chunks/files.js
#	public/chunks/files~chunks/shared-files~chunks/shared-page.js
#	public/chunks/installation-disclaimer.js
#	public/chunks/invoices.js
#	public/chunks/landing-page.js
#	public/chunks/pages.js
#	public/chunks/plan-create.js
#	public/chunks/plan-delete.js
#	public/chunks/plan-settings.js
#	public/chunks/plan-subscribers.js
#	public/chunks/plan.js
#	public/chunks/plans.js
#	public/chunks/profile.js
#	public/chunks/purchase-code.js
#	public/chunks/settings-create-payment-methods.js
#	public/chunks/settings-invoices.js
#	public/chunks/settings-payment-methods.js
#	public/chunks/settings-storage.js
#	public/chunks/settings-subscription.js
#	public/chunks/settings.js
#	public/chunks/shared-files.js
#	public/chunks/shared-page.js
#	public/chunks/sign-up.js
#	public/chunks/stripe-credentials.js
#	public/chunks/subscription-plans.js
#	public/chunks/subscription-service.js
#	public/chunks/upgrade-billing.js
#	public/chunks/upgrade.js
#	public/chunks/user-create.js
#	public/chunks/user-delete.js
#	public/chunks/user-detail.js
#	public/chunks/user-invoices.js
#	public/chunks/user-password.js
#	public/chunks/user-storage.js
#	public/chunks/user-subscription.js
#	public/chunks/user.js
#	public/chunks/users.js
#	public/js/main.js
#	public/mix-manifest.json
#	resources/js/views/FilePages/Files.vue
This commit is contained in:
Peter Papp
2021-02-07 18:05:56 +01:00
140 changed files with 17575 additions and 1524 deletions
@@ -23,7 +23,7 @@
<!-- Single options -->
<OptionGroup v-if="multiSelectContextMenu">
<Option @click.native="restoreItem" v-if="item" :title="$t('context_menu.restore')" icon="restore"/>
<Option @click.native="deleteItem" :title="$t('context_menu.delete')" icon="trash"/>
<Option @click.native="deleteItem" v-if="item" :title="$t('context_menu.delete')" icon="trash"/>
<Option @click.native="emptyTrash" :title="$t('context_menu.empty_trash')" icon="empty-trash"/>
</OptionGroup>
@@ -34,6 +34,7 @@
<!-- Multi options -->
<OptionGroup v-if="!multiSelectContextMenu">
<Option @click.native="restoreItem" v-if="item" :title="$t('context_menu.restore')" icon="restore"/>
<Option @click.native="deleteItem" :title="$t('context_menu.delete')" icon="trash"/>
<Option @click.native="emptyTrash" :title="$t('context_menu.empty_trash')" icon="empty-trash"/>
</OptionGroup>
@@ -62,6 +63,7 @@
<OptionGroup v-if="item && multiSelectContextMenu">
<Option @click.native="ItemDetail" :title="$t('context_menu.detail')" icon="detail"/>
<Option @click.native="downloadItem" v-if="!isFolder" :title="$t('context_menu.download')" icon="download"/>
<Option @click.native="downloadFolder" v-if="isFolder" :title="$t('context_menu.zip_folder')" icon="zip-folder"/>
</OptionGroup>
<!-- Multi options -->
@@ -83,13 +85,15 @@
<!-- Base location with MASTER permission-->
<div v-if="$isThisLocation(['base', 'participant_uploads', 'latest']) && $checkPermission('master') && !showFromPreview" id="menu-list" class="menu-options">
<!-- No Files options -->
<OptionGroup v-if="!$isThisLocation(['participant_uploads', 'latest']) && multiSelectContextMenu && !item">
<Option @click.native="createFolder" :title="$t('context_menu.create_folder')" icon="create-folder"/>
</OptionGroup>
<!-- Single options -->
<OptionGroup v-if="!$isThisLocation(['participant_uploads', 'latest']) && multiSelectContextMenu">
<Option @click.native="addToFavourites" v-if="item && isFolder " :title="isInFavourites
? $t('context_menu.remove_from_favourites')
: $t('context_menu.add_to_favourites')" icon="favourites"/>
<Option @click.native="createFolder" :title="$t('context_menu.create_folder')" icon="create-folder"/>
<OptionGroup v-if="!$isThisLocation(['participant_uploads', 'latest']) && item && multiSelectContextMenu && isFolder">
<Option @click.native="addToFavourites" :title="isInFavourites ? $t('context_menu.remove_from_favourites') : $t('context_menu.add_to_favourites')" icon="favourites"/>
</OptionGroup>
@@ -106,6 +110,7 @@
<OptionGroup v-if="item && multiSelectContextMenu ">
<Option @click.native="ItemDetail" :title="$t('context_menu.detail')" icon="detail"/>
<Option @click.native="downloadItem" v-if="!isFolder" :title="$t('context_menu.download')" icon="download"/>
<Option @click.native="downloadFolder" v-if="isFolder" :title="$t('context_menu.zip_folder')" icon="zip-folder"/>
</OptionGroup>
<!-- Multi options -->
@@ -113,7 +118,6 @@
<Option @click.native="addToFavourites" v-if="item && !hasFile" :title=" isInFavourites
? $t('context_menu.remove_from_favourites')
: $t('context_menu.add_to_favourites')" icon="favourites"/>
<Option @click.native="createFolder" :title="$t('context_menu.create_folder')" icon="create-folder"/>
</OptionGroup>
<OptionGroup v-if="item && !multiSelectContextMenu">
@@ -129,11 +133,13 @@
<!-- Base & Public location with EDITOR permission-->
<div v-if="$isThisLocation(['base', 'public']) && $checkPermission('editor') && !showFromPreview " id="menu-list" class="menu-options">
<!-- Single options -->
<OptionGroup v-if="multiSelectContextMenu">
<!-- No Files options -->
<OptionGroup v-if="multiSelectContextMenu && !item">
<Option @click.native="createFolder" :title="$t('context_menu.create_folder')" icon="create-folder"/>
</OptionGroup>
<!-- Single options -->
<OptionGroup v-if="item && multiSelectContextMenu">
<Option @click.native="renameItem" :title=" $t('context_menu.rename')" icon="rename"/>
<Option @click.native="moveItem" :title="$t('context_menu.move')" icon="move-item"/>
@@ -143,12 +149,10 @@
<OptionGroup v-if="item && multiSelectContextMenu">
<Option @click.native="ItemDetail" :title="$t('context_menu.detail')" icon="detail"/>
<Option @click.native="downloadItem" v-if="!isFolder" :title="$t('context_menu.download')" icon="download"/>
<Option @click.native="downloadFolder" v-if="isFolder" :title="$t('context_menu.zip_folder')" icon="zip-folder"/>
</OptionGroup>
<!-- Multi options -->
<OptionGroup v-if="!multiSelectContextMenu">
<Option @click.native="createFolder" :title="$t('context_menu.create_folder')" icon="create-folder"/>
</OptionGroup>
<OptionGroup v-if="item && !multiSelectContextMenu">
<Option @click.native="moveItem" :title="$t('context_menu.move')" icon="move-item"/>
@@ -167,6 +171,7 @@
<OptionGroup v-if="item && multiSelectContextMenu">
<Option @click.native="ItemDetail" :title="$t('context_menu.detail')" icon="detail"/>
<Option @click.native="downloadItem" v-if="!isFolder" :title="$t('context_menu.download')" icon="download"/>
<Option @click.native="downloadFolder" v-if="isFolder" :title="$t('context_menu.zip_folder')" icon="zip-folder"/>
</OptionGroup>
<!-- Multi options -->
@@ -250,11 +255,21 @@ export default {
},
methods: {
downloadFolder(){
this.$store.dispatch('downloadFolder' , this.item)
},
emptyTrash() {
this.$store.dispatch('emptyTrash')
},
restoreItem() {
this.$store.dispatch('restoreItem', this.item)
// If is item not in selected items restore just this single item
if(!this.fileInfoDetail.includes(this.item))
this.$store.dispatch('restoreItem', this.item)
// If is item in selected items restore all items from fileInfoDetail
if(this.fileInfoDetail.includes(this.item))
this.$store.dispatch('restoreItem', null)
},
shareCancel() {
this.$store.dispatch('shareCancel')
@@ -25,22 +25,22 @@
<SearchBar/>
</div>
<!--Files controlls-->
<!--Creating controls-->
<div class="toolbar-button-wrapper" v-if="$checkPermission(['master', 'editor'])">
<ToolbarButtonUpload :class="{ 'is-inactive': canUploadInView || !hasCapacity }" :action="$t('actions.upload')"/>
<ToolbarButton :class="{ 'is-inactive': canCreateFolderInView }" @click.native="createFolder" source="folder-plus" :action="$t('actions.create_folder')"/>
</div>
<div class="toolbar-button-wrapper" v-if="$checkPermission(['master', 'editor'])">
<!--File Controls-->
<div class="toolbar-button-wrapper" v-if="$checkPermission(['master', 'editor']) && ! $isMobile()">
<ToolbarButton source="move" :class="{ 'is-inactive': canMoveInView }" :action="$t('actions.move')" @click.native="moveItem"/>
<ToolbarButton v-if="!$isThisLocation(['public'])" source="share" :class="{ 'is-inactive': canShareInView }" :action="$t('actions.share')" @click.native="shareItem"/>
<ToolbarButton source="trash" :class="{ 'is-inactive': canDeleteInView }" :action="$t('actions.delete')" @click.native="deleteItem"/>
</div>
<!--View options-->
<!--View Controls-->
<div class="toolbar-button-wrapper">
<ToolbarButton source="preview-sorting" class="preview-sorting" :action="$t('actions.sorting_view')" :class="{ active: sortingAndPreview }" @click.stop.native="sortingAndPreview = !sortingAndPreview"/>
<ToolbarButton :action="$t('actions.info_panel')" :class="{ active: fileInfoVisible }" @click.native="$store.dispatch('fileInfoToggle')" source="info"/>
</div>
</div>
@@ -103,7 +103,7 @@ export default {
return !this.$isThisLocation(['base', 'public'])
},
canDeleteInView() {
return !this.$isThisLocation([
let locations = [
'trash',
'trash-root',
'base',
@@ -111,19 +111,22 @@ export default {
'latest',
'shared',
'public'
])
]
return !this.$isThisLocation(locations) || this.fileInfoDetail.length === 0
},
canUploadInView() {
return !this.$isThisLocation(['base', 'public'])
},
canMoveInView() {
return !this.$isThisLocation([
let locations = [
'base',
'participant_uploads',
'latest',
'shared',
'public'
])
]
return !this.$isThisLocation(locations) || this.fileInfoDetail.length === 0
},
canShareInView() {
let locations = [
@@ -134,7 +137,7 @@ export default {
'public'
]
return !this.$isThisLocation(locations) || this.fileInfoDetail.length > 1
return !this.$isThisLocation(locations) || this.fileInfoDetail.length > 1 || this.fileInfoDetail.length === 0
}
},
data() {
+85 -74
View File
@@ -1,72 +1,77 @@
<template>
<MultiSelected :title="title" :subtitle="subtitle" id="multi-select-ui" v-show="dragged" />
<MultiSelected :title="title" :subtitle="subtitle" id="multi-select-ui" v-show="isVisible"/>
</template>
<script>
import MultiSelected from '@/components/FilesView/MultiSelected'
import {mapGetters} from 'vuex'
import {events} from '@/bus'
import { mapGetters } from 'vuex'
import { events } from '@/bus'
export default {
name:"DragUI",
components: {MultiSelected},
computed: {
...mapGetters(['fileInfoDetail']),
title(){
export default {
name: 'DragUI',
components: { MultiSelected },
computed: {
...mapGetters(['fileInfoDetail']),
title() {
let filesLength = this.fileInfoDetail.length,
hasDraggedItem = this.fileInfoDetail.includes(this.draggedItem)
// Title for multiple selected items
if(this.fileInfoDetail.length > 1 && this.fileInfoDetail.includes(this.draggedItem)) {
return this.$t('file_detail.selected_multiple')
}
// Title for multiple selected items
if (filesLength > 1 && hasDraggedItem) {
return this.$t('file_detail.selected_multiple')
}
// Title for single item
if((this.fileInfoDetail.length < 2 || !this.fileInfoDetail.includes(this.draggedItem)) && this.draggedItem ) {
return this.draggedItem.name
}
},
subtitle(){
// Subtitle for multiple selected items
if(this.fileInfoDetail.length > 1 && this.fileInfoDetail.includes(this.draggedItem) ) {
return this.fileInfoDetail.length + ' ' + this.$tc('file_detail.items', this.fileInfoDetail.length)
}
if((this.fileInfoDetail.length < 2 || !this.fileInfoDetail.includes(this.draggedItem)) && this.draggedItem) {
// Subtitle for single folder
if(this.draggedItem.type === 'folder') {
return this.draggedItem.items == 0 ? this.$t('folder.empty') : this.$tc('folder.item_counts', this.draggedItem.items)
}
// Subtitle for single file
if(this.draggedItem !== 'folder' && this.draggedItem.mimetype){
return '.'+this.draggedItem.mimetype
}
}
},
},
data () {
return {
dragged: false,
draggedItem: undefined
// Title for single item
if ((filesLength < 2 || !hasDraggedItem) && this.draggedItem) {
return this.draggedItem.name
}
},
mounted () {
subtitle() {
let filesLength = this.fileInfoDetail.length,
hasDraggedItem = this.fileInfoDetail.includes(this.draggedItem)
// Hnadle Drag & Drop Ghost show
events.$on('dragstart', (data) => {
setTimeout(() => {
this.dragged = true
}, 50);
this.draggedItem = data
})
events.$on('drop', () => {
this.dragged = false
})
// Subtitle for multiple selected items
if (filesLength > 1 && hasDraggedItem) {
return filesLength + ' ' + this.$tc('file_detail.items', filesLength)
}
if ((filesLength < 2 || !hasDraggedItem) && this.draggedItem) {
// Subtitle for single folder
if (this.draggedItem.type === 'folder') {
return this.draggedItem.items == 0 ? this.$t('folder.empty') : this.$tc('folder.item_counts', this.draggedItem.items)
}
// Subtitle for single file
if (this.draggedItem !== 'folder' && this.draggedItem.mimetype) {
return '.' + this.draggedItem.mimetype
}
}
}
},
data() {
return {
isVisible: false,
draggedItem: undefined
}
},
created() {
// Handle Drag & Drop Ghost show
events.$on('dragstart', data => {
this.draggedItem = data
setTimeout(() => {
this.isVisible = true
}, 100)
})
events.$on('drop', () => {
this.isVisible = false
})
}
}
</script>
<style lang="scss" scoped>
@@ -82,38 +87,44 @@ import {events} from '@/bus'
padding: 10px;
border-radius: 8px;
box-shadow: 0 7px 25px 1px rgba(0, 0, 0, 0.12);
background:white;
/deep/.text{
.title {
color: $text;
}
.count {
color: $text-muted;
}
background: white;
/deep/ .text {
.title {
color: $text;
}
/deep/.icon-wrapper {
.icon {
stroke: $theme;
}
.count {
color: $text-muted;
}
}
/deep/ .icon-wrapper {
.icon {
stroke: $theme;
}
}
}
@media (prefers-color-scheme: dark) {
#multi-select-ui {
background: $dark_mode_foreground;
/deep/.text {
/deep/ .text {
.title {
color: $dark_mode_text_primary;
}
.count {
color: $dark_mode_text_secondary;
}
}
/deep/.icon-wrapper {
.icon {
}
/deep/ .icon-wrapper {
.icon {
stroke: $theme;
}
}
}
}
}
}
@@ -53,7 +53,7 @@
<div class="sharelink">
<lock-icon v-if="isLocked" @click="shareItemOptions" class="lock-icon" size="17"></lock-icon>
<unlock-icon v-if="! isLocked" @click="shareItemOptions" class="lock-icon" size="17"></unlock-icon>
<CopyInput class="copy-sharelink" size="small" :value="fileInfoDetail[0].shared.link"/>
<CopyInput class="copy-sharelink" size="small" :item="fileInfoDetail[0]"/>
</div>
</ListInfoItem>
@@ -25,14 +25,18 @@
<!--Image thumbnail-->
<img loading="lazy" v-if="isImage && data.thumbnail" class="image" :src="data.thumbnail" :alt="data.name"/>
<!-- If folder have set emoji -->
<Emoji class="emoji" v-if="isFolder && folderIconHandle" :emoji="folderIconHandle" size="80" />
<!--Else show only folder icon-->
<FontAwesomeIcon v-if="isFolder" :class="{'is-deleted': isDeleted}" class="folder-icon" icon="folder"/>
<FontAwesomeIcon v-if="isFolder && !folderIconHandle" :ref="`folder${this.data.unique_id}`" :class="{'is-deleted': isDeleted}" class="folder-icon" icon="folder"/>
</div>
<!--Name-->
<div class="item-name">
<!--Name-->
<b ref="name" @input="renameItem" @keydown.delete.stop :contenteditable="canEditName" class="name">
<b :ref="this.data.unique_id" @input="renameItem" @keydown.delete.stop @click.stop :contenteditable="canEditName" class="name">
{{ itemName }}
</b>
@@ -67,6 +71,7 @@
<script>
import { LinkIcon, UserPlusIcon, CheckIcon } from 'vue-feather-icons'
import Emoji from '@/components/Others/Emoji'
import { debounce } from 'lodash'
import { mapGetters } from 'vuex'
import { events } from '@/bus'
@@ -77,12 +82,28 @@ export default {
components: {
UserPlusIcon,
CheckIcon,
LinkIcon
LinkIcon,
Emoji
},
computed: {
...mapGetters([
'FilePreviewType', 'sharedDetail', 'fileInfoDetail'
]),
folderIconHandle(){
// If folder have set some color
if(this.data.icon_color) {
this.$nextTick(() => {
this.$refs[`folder${this.data.unique_id}`].firstElementChild.style.fill = `${this.data.icon_color}`
})
return false
}
// If folder have set some emoji
if(this.data.icon_emoji)
return this.data.icon_emoji
},
...mapGetters({ allData: 'data' }),
isClicked() {
return this.fileInfoDetail.some(element => element.unique_id == this.data.unique_id)
@@ -158,6 +179,10 @@ export default {
events.$emit('unClick')
if (!this.$isMobile()) {
// After click deselect new folder rename input
document.getSelection().removeAllRanges();
if (e.ctrlKey || e.metaKey && !e.shiftKey) {
// Click + Ctrl
if (this.fileInfoDetail.some(item => item.unique_id === this.data.unique_id)) {
@@ -267,6 +292,14 @@ export default {
created() {
this.itemName = this.data.name
events.$on('newFolder:focus', (unique_id) => {
if(this.data.unique_id == unique_id) {
this.$refs[unique_id].focus()
document.execCommand('selectAll')
}
})
events.$on('mobileSelecting:start', () => {
this.multiSelectMode = true
this.$store.commit('CLEAR_FILEINFO_DETAIL')
@@ -318,7 +351,7 @@ export default {
}
.select-box-active {
background-color: $text;
background-color: $theme;
.icon {
stroke: white;
@@ -452,6 +485,10 @@ export default {
display: flex;
align-items: center;
.emoji {
margin: 0 auto;
}
.file-link {
display: block;
}
@@ -568,10 +605,10 @@ export default {
}
.select-box-active {
background-color: #f4f5f6;
background-color: lighten($theme, 5%);
.icon {
stroke: $text;
stroke: white;
}
}
@@ -1,9 +1,14 @@
<template>
<div class="file-wrapper" @click.stop="clickedItem" @dblclick="goToItem" spellcheck="false">
<!--List preview-->
<div :draggable="canDrag" @dragstart="$emit('dragstart')" @drop="
drop()
area = false" @dragleave="dragLeave" @dragover.prevent="dragEnter" class="file-item" :class="{'is-clicked' : isClicked , 'no-clicked' : !isClicked && this.$isMobile(), 'is-dragenter': area }">
<div
:draggable="canDrag"
@dragstart="$emit('dragstart')"
@drop="drop()"
@dragleave="dragLeave"
@dragover.prevent="dragEnter"
class="file-item" :class="{'is-clicked' : isClicked , 'no-clicked' : !isClicked && this.$isMobile(), 'is-dragenter': area }"
>
<!-- MultiSelecting for the mobile version -->
<transition name="slide-from-left">
<div class="check-select" v-if="mobileMultiSelect">
@@ -26,13 +31,17 @@
<!--Image thumbnail-->
<img loading="lazy" v-if="isImage && data.thumbnail" class="image" :src="data.thumbnail" :alt="data.name"/>
<!-- If folder have set emoji -->
<Emoji v-if="isFolder && folderIconHandle" :emoji="folderIconHandle" size="52" />
<!--Else show only folder icon-->
<FontAwesomeIcon v-if="isFolder" :class="{ 'is-deleted': isDeleted }" class="folder-icon" icon="folder"/>
<FontAwesomeIcon v-if="isFolder && !folderIconHandle" :ref="`folder${this.data.unique_id}`" :class="{ 'is-deleted': isDeleted }" class="folder-icon" icon="folder"/>
</div>
<!--Name-->
<div class="item-name">
<b ref="name" @input="renameItem" @keydown.delete.stop :contenteditable="canEditName" class="name">
<b :ref="this.data.unique_id" @input="renameItem" @keydown.delete.stop @click.stop :contenteditable="canEditName" class="name">
{{ itemName }}
</b>
@@ -69,6 +78,7 @@
<script>
import { LinkIcon, UserPlusIcon, CheckIcon } from 'vue-feather-icons'
import Emoji from '@/components/Others/Emoji'
import { debounce } from 'lodash'
import { mapGetters } from 'vuex'
import { events } from '@/bus'
@@ -79,11 +89,27 @@ export default {
components: {
UserPlusIcon,
LinkIcon,
CheckIcon
CheckIcon,
Emoji
},
computed: {
...mapGetters(['FilePreviewType', 'fileInfoDetail']),
...mapGetters({ allData: 'data' }),
folderIconHandle(){
// If folder have set some icon color
if(this.data.icon_color) {
this.$nextTick(() => {
this.$refs[`folder${this.data.unique_id}`].firstElementChild.style.fill = `${this.data.icon_color}`
})
return false
}
// If folder have set some emoji
if(this.data.icon_emoji)
return this.data.icon_emoji
},
isClicked() {
return this.fileInfoDetail.some(element => element.unique_id == this.data.unique_id)
},
@@ -140,6 +166,7 @@ export default {
},
methods: {
drop() {
this.area = false
events.$emit('drop')
},
showItemActions() {
@@ -162,6 +189,9 @@ export default {
if (!this.$isMobile()) {
// After click deselect new folder rename input
document.getSelection().removeAllRanges();
if ((e.ctrlKey || e.metaKey) && !e.shiftKey) {
// Click + Ctrl
@@ -265,8 +295,17 @@ export default {
}, 300)
},
created() {
this.itemName = this.data.name
events.$on('newFolder:focus', (unique_id) => {
if(this.data.unique_id == unique_id) {
this.$refs[unique_id].focus()
document.execCommand('selectAll')
}
})
events.$on('mobileSelecting:start', () => {
this.mobileMultiSelect = true
this.$store.commit('CLEAR_FILEINFO_DETAIL')
@@ -289,6 +328,7 @@ export default {
@import '@assets/vue-file-manager/_variables';
@import '@assets/vue-file-manager/_mixins';
.slide-from-left-move {
transition: transform 300s ease;
}
@@ -328,10 +368,10 @@ export default {
}
.select-box-active {
background-color: #f4f5f6;
background-color: $theme;
.icon {
stroke: $text;
stroke: white;
}
}
}
@@ -533,10 +573,10 @@ export default {
}
.select-box-active {
background-color: lighten($dark_mode_foreground, 10%);
background-color: $theme;
.icon {
stroke: $theme;
stroke: white;
}
}
}
@@ -6,7 +6,7 @@
<p class="title">{{ fileInfoDetail[0].name }}</p>
<span class="file-count"> ({{ showingImageIndex + ' ' + $t('pronouns.of') + ' ' + filteredFiles.length }}) </span>
</div>
<span id="fast-preview-menu" class="fast-menu-icon" @click.stop="menuOpen" v-if="$checkPermission(['master', 'editor'])">
<span id="fast-preview-menu" class="fast-menu-icon" @click.stop="menuOpen" v-if="$checkPermission(['master', 'editor', 'visitor'])">
<more-horizontal-icon class="more-icon" size="14"> </more-horizontal-icon>
</span>
</div>
@@ -5,7 +5,7 @@
<audio class="file audio" :class="{ 'file-shadow': !isMobileDevice }" v-if="fileInfoDetail[0].type == 'audio'" :src="currentFile.file_url" controlsList="nodownload" controls></audio>
<img v-if="fileInfoDetail[0].type === 'image' && currentFile.thumbnail" class="file" :class="{ 'file-shadow': !isMobileDevice }" id="image" :src="currentFile.file_url" />
<div class="video-wrapper" v-if="fileInfoDetail[0].type === 'video' && currentFile.file_url">
<video :src="currentFile.file_url" class="video" :class="{ 'file-shadow': !isMobileDevice }" controlsList="nodownload" disablePictureInPicture playsinline controls />
<video :src="currentFile.file_url" class="video" :class="{ 'file-shadow': !isMobileDevice }" controlsList="nodownload" disablePictureInPicture playsinline controls autoplay />
</div>
</div>
</div>
File diff suppressed because it is too large Load Diff
@@ -42,7 +42,7 @@ import { events } from '@/bus'
@media (prefers-color-scheme: dark) {
.options {
background: $dark_mode_background;
background: $dark_mode_foreground;
}
}
+25 -1
View File
@@ -1,5 +1,5 @@
<template>
<li class="menu-option">
<li class="menu-option" :class="[icon === 'trash' ? 'danger' : '']">
<div class="icon">
<trash-2-icon v-if="icon === 'trash'" size="17"></trash-2-icon>
<life-buoy-icon v-if="icon === 'restore'" size="17"></life-buoy-icon>
@@ -12,6 +12,7 @@
<star-icon v-if="icon === 'favourites'" size="17"></star-icon>
<folder-plus-icon v-if="icon === 'create-folder'" size="17"></folder-plus-icon>
<smile-icon v-if="icon === 'no-options'" size="17"></smile-icon>
<paperclip-icon v-if="icon === 'zip-folder'" size="17"></paperclip-icon>
</div>
<div class="text-label">
{{ title }}
@@ -24,6 +25,7 @@ import {
CornerDownRightIcon,
DownloadCloudIcon,
FolderPlusIcon,
PaperclipIcon,
LifeBuoyIcon,
Trash2Icon,
Edit2Icon,
@@ -41,6 +43,7 @@ import {
CornerDownRightIcon,
DownloadCloudIcon,
FolderPlusIcon,
PaperclipIcon,
LifeBuoyIcon,
Trash2Icon,
SmileIcon,
@@ -57,6 +60,22 @@ import {
@import "@assets/vue-file-manager/_variables";
@import "@assets/vue-file-manager/_mixins";
.danger {
.text-label {
color: $danger !important;
}
.icon {
path,
line,
polyline,
rect,
circle,
polygon {
stroke: $danger !important;
}
}
}
.menu-option {
white-space: nowrap;
font-weight: 700;
@@ -95,6 +114,11 @@ import {
}
}
@media (prefers-color-scheme: dark) {
.danger {
&:hover {
background: rgba($danger, 0.1) !important;
}
}
.menu-option {
color: $dark_mode_text_primary;
@@ -1,13 +1,13 @@
<template>
<transition name="popup">
<div class="popup" v-if="isZippingFiles">
<div class="popup" v-if="processingPopup">
<div class="popup-wrapper">
<div class="popup-content">
<div class="spinner-wrapper">
<Spinner/>
</div>
<h1 class="title">{{ $t('popup_zipping.title') }}</h1>
<p class="message">{{ $t('popup_zipping.message') }}</p>
<h1 class="title">{{ processingPopup.title }}</h1>
<p class="message">{{ processingPopup.message }}</p>
</div>
</div>
</div>
@@ -25,7 +25,7 @@ export default {
},
computed: {
...mapGetters([
'isZippingFiles'
'processingPopup'
])
}
}
@@ -84,8 +84,6 @@ export default {
this.filter.field = field
console.log(this.filter);
// Set sorting direction
if (this.filter.sort === 'DESC')
this.filter.sort = 'ASC'
@@ -5,11 +5,17 @@
:description="index.header_description"
></PageTitle>
<router-link class="sign-up-button" :to="{name: 'SignUp'}">
<!--User registration button-->
<router-link v-if="config.userRegistration" class="sign-up-button" :to="{name: 'SignUp'}">
<AuthButton class="button" icon="chevron-right" :text="$t('page_index.sign_up_button')" />
</router-link>
<div class="features">
<!--User login button-->
<router-link v-if="! config.userRegistration" class="sign-up-button" :to="{name: 'SignIn'}">
<AuthButton class="button" icon="chevron-right" :text="$t('page_index.menu.log_in')" />
</router-link>
<div class="features" v-if="config.isSaaS">
<div class="feature">
<credit-card-icon size="19" class="feature-icon"></credit-card-icon>
<b class="feature-title">{{ $t('page_index.sign_feature_1') }}</b>
@@ -1,7 +1,7 @@
<template>
<div class="cookie-wrapper" v-if="isVisibleDisclaimer && config.isSaaS">
<span class="close-icon">
<x-icon @click="closeDisclaimer" size="12"></x-icon>
<span @click="closeDisclaimer" class="close-icon">
<x-icon size="12"></x-icon>
</span>
<i18n path="cookie_disclaimer.description" tag="p">
<router-link :to="{name: 'DynamicPage', params: {slug: 'cookie-policy'}}">{{ $t('cookie_disclaimer.button') }}</router-link>
@@ -13,7 +13,7 @@
<!--Set password-->
<ValidationProvider tag="div" mode="passive" class="input-wrapper password" name="Title" rules="required" v-slot="{ errors }">
<label class="input-label">{{ $t('popup_create_folder.label') }}:</label>
<input v-model="name" :class="{'is-error': errors[0]}" type="text" :placeholder="$t('popup_create_folder.placeholder')">
<input v-model="name" :class="{'is-error': errors[0]}" type="text" ref="input" :placeholder="$t('popup_create_folder.placeholder')">
<span class="error-message" v-if="errors[0]">{{ errors[0] }}</span>
</ValidationProvider>
</ValidationObserver>
@@ -82,6 +82,15 @@
}
},
},
mounted() {
events.$on('popup:open', ({name}) => {
if (name === 'create-folder')
this.$nextTick(() => {
this.$refs.input.focus()
})
})
}
}
</script>
+25
View File
@@ -0,0 +1,25 @@
<template>
<div v-show="transferEmoji" :style="{width: `${size}px`, height: `${size}px`}" v-html="transferEmoji"/>
</template>
<script>
import twemoji from 'twemoji'
export default {
name: 'Emoji',
props: ['emoji', 'size'],
computed: {
transferEmoji () {
// Transfer single emoji to twemoji
return twemoji.parse(this.emoji.char, {
folder: 'svg',
ext: '.svg',
attributes: () => ({
loading: 'lazy',
})
})
}
},
}
</script>
@@ -1,95 +1,190 @@
<template>
<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">
<link-icon v-if="! isCopiedLink" size="14"></link-icon>
<check-icon v-if="isCopiedLink" size="14"></check-icon>
<input ref="sel" :value="item.shared.link" id="link-input" type="text" class="input-text" readonly>
<div class="multi-icon">
<div class="icon-item">
<link-icon v-if="! isCopiedLink" size="14"></link-icon>
<check-icon v-if="isCopiedLink" size="14"></check-icon>
</div>
<div class="icon-item" @click.stop.prevent="menuForEmail">
<send-icon size="14"></send-icon>
</div>
</div>
</div>
</template>
<script>
import { LinkIcon, CheckIcon } from 'vue-feather-icons'
import { LinkIcon, CheckIcon, SendIcon } from 'vue-feather-icons'
import { events } from '@/bus'
export default {
name: 'CopyInput',
props: ['size', 'value'],
components: {
CheckIcon,
LinkIcon,
export default {
name: 'CopyInput',
props: ['size', 'item'],
components: {
CheckIcon,
LinkIcon,
SendIcon
},
data() {
return {
isCopiedLink: false
}
},
methods: {
menuForEmail() {
events.$emit('popup:open', {
name: 'share-edit',
item: this.item,
sentToEmail: true,
})
},
data() {
return {
isCopiedLink: false,
}
},
methods: {
copyUrl() {
copyUrl() {
// Get input value
var copyText = document.getElementById("link-input");
// Get input value
var copyText = document.getElementById('link-input')
// select link
copyText.select();
copyText.setSelectionRange(0, 99999);
// select link
copyText.select()
copyText.setSelectionRange(0, 99999)
// Copy
document.execCommand("copy");
// Copy
document.execCommand('copy')
// Mark button as copied
this.isCopiedLink = true
// Mark button as copied
this.isCopiedLink = true
// Reset copy button
setTimeout(() => {this.isCopiedLink = false}, 1000)
},
// Reset copy button
setTimeout(() => {
this.isCopiedLink = false
}, 1000)
}
}
}
</script>
<style lang="scss" scoped>
@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";
@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 {
.multi-icon {
display: flex;
align-items: center;
background: $light_background;
border-bottom-right-radius: 8px;
border-top-right-radius: 8px;
&.small {
line,
path,
polygon {
stroke: $text !important;
}
&.icon-append {
.icon-item {
padding: 9px 10px;
display: flex;
align-items: center;
border-left: 1px solid $light_mode_border_darken;
cursor: pointer;
.icon {
padding: 10px;
}
}
&:hover {
background: $text;
input {
padding: 6px 10px;
@include font-size(13);
line,
polyline,
path,
polygon {
stroke: white !important;
}
}
.icon {
cursor: pointer;
&:first-child {
border-left: none;
}
&:last-child {
border-bottom-right-radius: 8px;
border-top-right-radius: 8px;
}
}
}
// Single page
.copy-input {
border: 1px solid $light_mode_border_darken;
border-radius: 8px;
&.small {
&.icon-append {
.icon {
padding: 10px;
}
}
input {
text-overflow: ellipsis;
&:disabled {
color: $text;
cursor: pointer;
}
padding: 6px 10px;
@include font-size(13);
}
}
@media (prefers-color-scheme: dark) {
.icon {
cursor: pointer;
}
.copy-input {
input {
color: $dark_mode_text_primary;
}
input {
text-overflow: ellipsis;
box-shadow: none;
&:disabled {
color: $text;
cursor: pointer;
}
}
}
@media (prefers-color-scheme: dark) {
.copy-input {
border-color: #333333;
}
.multi-icon {
background: $dark_mode_foreground;
box-shadow: 0 1px 5px rgba(0, 0, 0, 0.12);
line,
path,
polygon {
stroke: $dark_mode_text_primary !important;
}
.icon-item {
border-color: #333333;
&:hover {
background: rgba($theme, 0.1);
line,
polyline,
path,
polygon {
stroke: $theme !important;
}
}
}
}
.copy-input {
input {
color: $dark_mode_text_primary;
}
}
}
</style>
@@ -35,12 +35,19 @@
}
p {
@include font-size(15);
font-size: 15px;
line-height: 1.6;
word-break: break-word;
font-weight: 600;
/deep/ a {
font-size: 15px;
color: $theme;
}
/deep/ b {
font-size: 15px;
font-weight: 700;
color: $theme;
}
}
@@ -71,10 +78,6 @@
}
}
@media only screen and (max-width: 960px) {
}
@media only screen and (max-width: 690px) {
.info-box {
@@ -0,0 +1,197 @@
<template>
<div class="wrapper">
<label class="input-label">{{ label }}:</label>
<div class="input-wrapper" :class="{'is-error' : isError}" @click="$refs.input.focus()">
<div class="email-list">
<div class="email-tag" :class="{'mb-offset': getCharactersLength > 45}" v-for="(email, i) in emails" :key="i">
<span>{{ email }}</span>
<x-icon @click="removeEmail(email)" class="icon" size="14"/>
</div>
<input @keydown.delete=removeLastEmail($event) @keyup="handleEmail()" v-model="email" :size="inputSize" class="email-input" :placeholder="placeHolder" autocomplete="new-password" ref="input"/>
</div>
</div>
<span class="error-message" v-if="isError">{{ isError }}</span>
</div>
</template>
<script>
import { XIcon } from 'vue-feather-icons'
import { events } from '@/bus'
export default {
name: 'MultiEmailInput',
components: { XIcon },
props: ['isError', 'label'],
computed: {
getCharactersLength() {
return this.emails.join( '' ).length
},
placeHolder() {
return !this.emails.length ? this.$t( 'shared_form.email_placeholder' ) : ''
},
inputSize() {
return this.email && this.email.length > 14 ? this.email.length : 14
}
},
data() {
return {
emails: [],
email: undefined
}
},
methods: {
removeEmail( email ) {
this.emails = this.emails.filter( item => item !== email )
// After romove email send new emails list to parent
events.$emit( 'emailsInputValues', this.emails )
},
removeLastEmail( event ) {
// If is input empty and presse backspace remove last email from array
if ( event.code === 'Backspace' && this.email === '' )
this.emails.pop()
},
handleEmail() {
if ( this.email.length > 0 ) {
// Get index of @ and last dot
let lastDot = this.email.lastIndexOf( '.' )
let at = this.email.indexOf( '@' )
// Check if is after @ some dot, if email have @ anf if dont have more like one
if ( lastDot < at || at === -1 || this.email.match(/@/g).length > 1 ) return
// First email dont need to be separated by comma or space to be sended
if( this.emails.length === 0 )
events.$emit('emailsInputValues', [this.email])
// After come or backspace push the single email to array or emails
if ( this.email.includes(',') || this.email.includes(' ') ) {
let email = this.email.replace( /[","," "]/, '' )
this.email = ''
// Push single email to aray of emails
this.emails.push( email )
events.$emit( 'emailsInputValues', this.emails )
}
}
}
},
created() {
this.$nextTick(() => {
this.$refs.input.focus()
})
}
}
</script>
<style scoped lang="scss">
@import "@assets/vue-file-manager/_inapp-forms.scss";
@import '@assets/vue-file-manager/_forms';
.wrapper {
margin-bottom: 20px;
}
.input-label {
@include font-size(14);
font-weight: 700;
margin-bottom: 8px;
}
.input-wrapper {
margin-bottom: 0;
background: white;
max-width: 100%;
display: flex;
min-height: 50px;
border-radius: 8px;
padding: 6px 10px;
box-shadow: 0 1px 5px rgba(0, 0, 0, 0.12);
cursor: text;
border: 1px solid transparent;
@include transition(150ms);
&.is-error {
border: 1px solid $danger;
box-shadow: 0 0 7px rgba($danger, 0.3);
}
&:focus-within {
border: 1px solid $theme;
box-shadow: 0 1px 5px rgba($theme, 0.3);
}
.email-list {
display: flex;
flex-wrap: wrap;
.email-input {
font-size: 14px;
}
}
.email-tag {
white-space: nowrap;
display: flex;
padding: 5px 10px;
background: rgba($theme, .1);
border-radius: 8px;
margin-right: 5px;
align-items: center;
&.mb-offset {
margin-top: 3px;
margin-bottom: 3px;
}
span {
color: $theme;
font-weight: 700;
@include font-size(14);
}
.icon {
cursor: pointer;
margin-left: 4px;
}
}
.email-input {
width: auto;
border: none ;
font-weight: 700;
@include font-size(16);
padding-left: 11px;
&::placeholder {
color: rgba($text-muted, .5)
}
}
}
@media (prefers-color-scheme: dark) {
.input-wrapper {
background: $dark_mode_foreground;
.email-list {
.email-input {
background: $dark_mode_foreground;
color: $dark_mode_text_primary;
&::placeholder {
color: $dark_mode_text_secondary;
}
}
}
}
}
</style>
@@ -2,7 +2,8 @@
<div class="popup-header">
<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>
<share-icon v-if="icon === 'share'" size="17" class="title-icon"></share-icon>
<!-- <link-icon v-if="icon === 'share'" size="17" class="title-icon"></link-icon> -->
<edit2-icon v-if="icon === 'edit'" size="17" class="title-icon"></edit2-icon>
</div>
<div class="label">
@@ -13,7 +14,7 @@
</template>
<script>
import {CornerDownRightIcon, LinkIcon, XIcon, Edit2Icon} from 'vue-feather-icons'
import {CornerDownRightIcon, LinkIcon, XIcon, Edit2Icon, ShareIcon} from 'vue-feather-icons'
import {events} from '@/bus'
export default {
@@ -23,6 +24,7 @@
],
components: {
CornerDownRightIcon,
ShareIcon,
Edit2Icon,
LinkIcon,
XIcon,
@@ -58,7 +58,7 @@
left: 0;
right: 0;
bottom: 0;
z-index: 20;
z-index: 19;
overflow-y: auto;
display: grid;
padding: 40px;
+164 -81
View File
@@ -1,13 +1,13 @@
<template>
<PopupWrapper name="rename-item">
<!--Title-->
<PopupHeader :title="$t('popup_rename.title', {item: itemTypeTitle})" icon="edit" />
<PopupHeader :title="$t('popup_rename.title', {item: itemTypeTitle})" icon="edit"/>
<!--Content-->
<PopupContent>
<!--Item Thumbnail-->
<ThumbnailItem class="item-thumbnail" :item="pickedItem" info="metadata"/>
<ThumbnailItem class="item-thumbnail" :item="pickedItem" info="metadata" :setFolderIcon="setFolderIcon"/>
<!--Form to set sharing-->
<ValidationObserver @submit.prevent="changeName" ref="renameForm" v-slot="{ invalid }" tag="form" class="form-wrapper">
@@ -15,106 +15,189 @@
<!--Set password-->
<ValidationProvider tag="div" mode="passive" class="input-wrapper password" name="Name" rules="required" v-slot="{ errors }">
<label class="input-label">{{ $t('popup_rename.label') }}:</label>
<input v-model="pickedItem.name" :class="{'is-error': errors[0]}" type="text" :placeholder="$t('popup_rename.placeholder')">
<div class="input">
<input v-model="pickedItem.name" :class="{'is-error': errors[0]}" ref="input" type="text" :placeholder="$t('popup_rename.placeholder')">
<div @click="pickedItem.name = ''" class="close-icon-wrapper">
<x-icon class="close-icon" size="14"/>
</div>
</div>
<span class="error-message" v-if="errors[0]">{{ errors[0] }}</span>
</ValidationProvider>
<!--<SetFolderIcon v-if="isMoreOptions" :folderData="pickedItem" :unique_id="pickedItem.unique_id" />-->
<!-- <ActionButton v-if="pickedItem.type === 'folder'" @click.native.stop="moreOptions" :icon="isMoreOptions ? 'x' : 'pencil-alt'">{{ moreOptionsTitle }}</ActionButton> -->
</ValidationObserver>
</PopupContent>
<!--Actions-->
<PopupActions>
<ButtonBase
class="popup-button"
@click.native="$closePopup()"
button-style="secondary"
>{{ $t('popup_move_item.cancel') }}
<ButtonBase class="popup-button" @click.native="$closePopup()" button-style="secondary">{{ $t('popup_move_item.cancel') }}
</ButtonBase>
<ButtonBase
class="popup-button"
@click.native="changeName"
button-style="theme"
>{{ $t('popup_share_edit.save') }}
<ButtonBase class="popup-button" @click.native="changeName" button-style="theme">{{ $t('popup_share_edit.save') }}
</ButtonBase>
</PopupActions>
</PopupWrapper>
</template>
<script>
import {ValidationProvider, ValidationObserver} from 'vee-validate/dist/vee-validate.full'
import PopupWrapper from '@/components/Others/Popup/PopupWrapper'
import PopupActions from '@/components/Others/Popup/PopupActions'
import PopupContent from '@/components/Others/Popup/PopupContent'
import PopupHeader from '@/components/Others/Popup/PopupHeader'
import ThumbnailItem from '@/components/Others/ThumbnailItem'
import ActionButton from '@/components/Others/ActionButton'
import ButtonBase from '@/components/FilesView/ButtonBase'
import {required} from 'vee-validate/dist/rules'
import {events} from '@/bus'
import axios from 'axios'
import { ValidationProvider, ValidationObserver } from 'vee-validate/dist/vee-validate.full'
import PopupWrapper from '@/components/Others/Popup/PopupWrapper'
import PopupActions from '@/components/Others/Popup/PopupActions'
import PopupContent from '@/components/Others/Popup/PopupContent'
import PopupHeader from '@/components/Others/Popup/PopupHeader'
import SetFolderIcon from '@/components/Others/SetFolderIcon'
import ThumbnailItem from '@/components/Others/ThumbnailItem'
import ActionButton from '@/components/Others/ActionButton'
import ButtonBase from '@/components/FilesView/ButtonBase'
import { XIcon } from 'vue-feather-icons'
import { required } from 'vee-validate/dist/rules'
import { events } from '@/bus'
import axios from 'axios'
export default {
name: 'RenameItem',
components: {
ValidationProvider,
ValidationObserver,
ThumbnailItem,
ActionButton,
PopupWrapper,
PopupActions,
PopupContent,
PopupHeader,
ButtonBase,
required,
export default {
name: 'RenameItem',
components: {
ValidationProvider,
ValidationObserver,
SetFolderIcon,
ThumbnailItem,
ActionButton,
PopupWrapper,
PopupActions,
PopupContent,
PopupHeader,
ButtonBase,
required,
XIcon
},
computed: {
itemTypeTitle() {
return this.pickedItem && this.pickedItem.type === 'folder' ? this.$t('types.folder') : this.$t('types.file')
},
computed: {
itemTypeTitle() {
return this.pickedItem && this.pickedItem.type === 'folder' ? this.$t('types.folder') : this.$t('types.file')
},
},
data() {
return {
pickedItem: undefined,
}
},
methods: {
changeName() {
if (this.pickedItem.name && this.pickedItem.name !== '') {
let item = {
unique_id: this.pickedItem.unique_id,
type: this.pickedItem.type,
name: this.pickedItem.name
}
// Rename item request
this.$store.dispatch('renameItem', item)
// Rename item in view
events.$emit('change:name', item)
this.$closePopup()
}
},
},
mounted() {
// Show popup
events.$on('popup:open', args => {
if (args.name !== 'rename-item') return
// Store picked item
this.pickedItem = args.item
})
moreOptionsTitle() {
return this.isMoreOptions ? this.$t('shared_form.button_close_options') : this.$t('shared_form.button_folder_icon_open')
}
},
data() {
return {
pickedItem: undefined,
isMoreOptions: false,
setFolderIcon: undefined
}
},
methods: {
moreOptions() {
this.isMoreOptions = !this.isMoreOptions
this.setFolderIcon = undefined
},
changeName() {
if (this.pickedItem.name && this.pickedItem.name !== '') {
let item = {
unique_id: this.pickedItem.unique_id,
type: this.pickedItem.type,
name: this.pickedItem.name,
folder_icon: this.setFolderIcon ? this.setFolderIcon : null
}
// Rename item request
this.$store.dispatch('renameItem', item)
// Rename item in view
events.$emit('change:name', item)
this.$closePopup()
}
}
},
mounted() {
// Show popup
events.$on('popup:open', args => {
if (args.name !== 'rename-item') return
this.$nextTick(() => {
this.$refs.input.focus()
})
this.isMoreOptions = false
this.setFolderIcon = undefined
// Store picked item
this.pickedItem = args.item
})
events.$on('setFolderIcon', (icon) => {
this.setFolderIcon = !icon ? undefined : icon.value
})
}
}
</script>
<style scoped lang="scss">
@import "@assets/vue-file-manager/_inapp-forms.scss";
@import '@assets/vue-file-manager/_forms';
@import "@assets/vue-file-manager/_inapp-forms.scss";
@import '@assets/vue-file-manager/_forms';
.item-thumbnail {
margin-bottom: 20px;
.input {
position: relative;
display: flex;
justify-content: flex-end;
align-items: center;
.close-icon-wrapper {
width: 22px;
height: 22px;
position: absolute;
cursor: pointer;
right: 15px;
border-radius: 6px;
display: flex;
justify-content: center;
align-items: center;
&:hover {
.close-icon {
line {
stroke: $theme;
}
}
}
.close-icon {
line {
stroke: rgba($text-muted, 0.3);
}
}
}
}
.item-thumbnail {
margin-bottom: 20px;
}
@media (prefers-color-scheme: dark) {
.close-icon-wrapper {
&:hover {
.close-icon {
line {
stroke: $theme !important;
}
}
}
.close-icon {
line {
stroke: rgba($dark_mode_text_primary, 0.3) !important;
}
}
}
}
</style>
@@ -0,0 +1,498 @@
<template>
<div class="set-folder-icon">
<TabWrapper >
<!-- Emojis -->
<TabOption :selected="true" id="emoji-list" :title="$t('popup_rename.tab_emoji_title')" icon="emoji">
<div class="select-emoji-wrapper">
<label class="main-label">Pick Yout Emoji Icon:</label>
<!-- Selected Emoji input -->
<div @click.stop="openMenu" class="select-input-wrapper">
<div class="select-input" v-if="selectedEmoji">
<Emoji class="emoji-preview" :emoji="selectedEmoji" size="25"></Emoji>
<span>{{selectedEmoji.name}}</span>
</div>
<div class="not-selected" v-if="! selectedEmoji">
<span> {{$t('popup_rename.set_emoji_input_placeholder')}}</span>
</div>
<chevron-down-icon v-if="!selectOpen" size="19"/>
<div v-if="selectOpen" @click="resetEmoji" class="select-input-icon-wrapper">
<x-icon size="14" class="select-input-icon"/>
</div>
</div>
<!-- Emojis List -->
<transition v-if="selectOpen" name="slide-in">
<div class="emoji-wrapper">
<input @click.stop @input="filterEmojis" v-model="searchInput" class="emoji-input" :placeholder="$t('popup_rename.search_emoji_input_placeholder')" >
<!-- Navigation of Emojis Groups -->
<ul v-show="searchInput.length < 1" class="groups-list">
<li @click.stop="scrollToGroup(group.name)" v-for="(group,i) in emojiGroups" :key="i" class="group-option" :class="{'active' : group.name === groupInView}">
<Emoji :emoji="group.emoji" size="33"/>
</li>
</ul>
<!-- All Emojis -->
<div v-show="searchInput.length < 1" @scroll="checkGroupInView" id="group-box" class="group-wrapper">
<div v-for="(group, name) in allEmoji" :key="name" class="options-wrapper" :id="`group-${name}`">
<label class="group-name-label">{{name}}</label>
<ul class="options-list">
<li @click="setIcon({'emoji':emoji})" v-for="(emoji,i) in group" :key="i" class="option">
<Emoji :emoji="emoji" size="33"/>
</li>
</ul>
</div>
</div>
<!-- Searched emojis -->
<div v-if="searchInput.length > 0" class="group-wrapper">
<div class="options-wrapper">
<ul class="options-list">
<li @click="setIcon({'emoji':emoji})" v-for="(emoji,i) in filteredEmojis" :key="i" class="option" >
<Emoji :emoji="emoji" size="33"/>
</li>
</ul>
<span class="not-found" v-if="filteredEmojis.length === 0"> {{$t('popup_rename.emoji_list_not_found')}}</span>
</div>
</div>
</div>
</transition>
</div>
</TabOption>
<!-- Colors -->
<TabOption :title="$t('popup_rename.tab_color_title')" icon="folder">
<div class="color-pick-wrapper">
<label class="main-label">{{$t('popup_rename.color_pick_label')}}</label>
<ul class="color-wrapper">
<li v-for="(color, index) in colors"
:key="index"
@click="setIcon({'color': color})"
class="single-color"
:class="{'active-color': color === selectedColor }"
:style="{background:color}" />
</ul>
</div>
</TabOption>
</TabWrapper>
</div>
</template>
<script>
import { SmileIcon, FolderIcon, ChevronDownIcon, XIcon } from 'vue-feather-icons'
import TabWrapper from '@/components/Others/TabWrapper'
import TabOption from '@/components/Others/TabOption'
import Emoji from '@/components/Others/Emoji'
import lodash from 'lodash'
import { mapGetters } from 'vuex'
import { events } from '@/bus'
export default {
name: "SetFolderIcon",
props: ['folderData', 'unique_id'],
components: {
ChevronDownIcon ,
TabWrapper,
TabOption,
FolderIcon,
SmileIcon,
XIcon,
Emoji
},
computed: {
...mapGetters(['emojis', 'emojiGroups']),
allEmoji() {
return _.groupBy(this.emojis, 'group')
},
},
data () {
return {
selectedEmoji: undefined,
selectedColor: undefined,
searchInput: '',
filteredEmojis: [],
selectOpen: false,
groupInView: 'Smileys & Emotion',
colors: [ '#FF6633', '#FFB399', '#FF33FF', '#FFFF99', '#00B3E6',
'#E6B333', '#3366E6', '#999966', '#99FF99', '#B34D4D',
'#80B300', '#809900', '#E6B3B3', '#6680B3' ]
}
},
methods: {
checkGroupInView: _.debounce(function() {
this.emojiGroups.forEach(group => {
let element = document.getElementById(`group-${group.name}`).getBoundingClientRect()
let groupBox = document.getElementById('group-box').getBoundingClientRect()
// Check if the group is in the viewport of group-box
if(element.top < groupBox.top && element.bottom > groupBox.top){
this.groupInView = group.name
}
})
}, 200),
scrollToGroup( name ) {
let group = document.getElementById(`group-${name}`)
group.scrollIntoView({ behavior: "smooth" })
this.groupInView = name
},
filterEmojis: _.debounce(function( emoji ){
this.filteredEmojis = this.emojis.filter(emoji => emoji.name.includes(this.searchInput))
}, 800),
openMenu() {
this.selectOpen = ! this.selectOpen
this.searchInput = ''
this.groupInView = 'Smileys & Emotion'
},
setIcon( value ) {
if(value.emoji){
this.selectedEmoji = value.emoji
this.selectedColor = undefined
}
if(value.color) {
this.selectedColor = value.color
this.selectedEmoji = undefined
}
events.$emit('setFolderIcon', { 'value':value, 'unique_id':this.unique_id })
this.selectOpen = false
},
resetEmoji(){
this.selectedEmoji = undefined
events.$emit('setFolderIcon', undefined)
}
},
mounted () {
this.selectOpen = false
events.$on('unClick', () => {
this.selectOpen = false
})
}
}
</script>
<style scoped lang="scss">
@import "@assets/vue-file-manager/_inapp-forms.scss";
@import '@assets/vue-file-manager/_forms';
.color-pick-wrapper {
.color-wrapper {
display: flex;
flex-wrap: wrap;
justify-content: space-between;
margin-bottom: 20px;
.single-color {
width: 40px;
height: 40px;
list-style: none;
margin: 8px;
border-radius: 8px;
cursor: pointer;
&.active-color {
border: 2px solid $text;
}
&:hover {
border: 2px solid $text;
}
}
}
}
.select-emoji-wrapper{
margin-bottom: 20px;
}
.main-label {
@include font-size(14);
font-weight: 700;
margin-bottom: 8px;
display: block;
}
.emoji-wrapper {
height: 350px;
width: 100%;
position: absolute;
border: 1px solid transparent;
box-shadow: 0 1px 5px rgba(0, 0, 0, 0.12);
border-radius: 8px;
background: white;
display: flex;
flex-direction: column;
padding: 10px;
top: 152px;
.loader {
width: 100%;
height: 100%;
position: relative;
}
.groups-list {
display: flex;
flex-direction: row;
flex-wrap: wrap;
margin-bottom: 20px;
.active {
background: $light_background;
border-radius: 8px;
}
.group-option {
list-style: none;
width: 45px;
height: 45px;
padding: 6px;
cursor: pointer;
&:hover {
background: $light_background;
border-radius: 8px;
}
}
}
.emoji-input {
width: 100%;
border-radius: 8px;
padding: 4px;
margin-bottom: 20px;
background: $light_background;
border: none;
padding: 13px 20px;
font-weight: 700;
&::placeholder {
font-weight: 700;
color: $light_text;
}
}
.group-wrapper {
height: 100%;
display: flex;
flex-direction: column;
overflow: hidden;
overflow-y: scroll;
padding: 0px;
.options-wrapper {
display: flex;
flex-wrap: wrap;
margin-bottom: 10px;
&:last-child {
margin-bottom: 0px;
}
.options-list {
display: flex;
flex-wrap: wrap;
justify-content: center;
}
.group-name-label {
width: 100%;
@include font-size(14);
font-weight: 700;
margin-bottom: 10px;
}
.option {
list-style: none;
width: 45px;
height: 45px;
padding: 6px;
cursor: pointer;
&:hover {
background: $light_background;
border-radius: 8px;
}
}
.not-found {
align-self: center;
margin:auto;
font-weight: 700;
padding: 10px;
border-radius: 8px;
background:$light_background ;
box-shadow: 0 1px 5px rgba(0, 0, 0, 0.12);
}
}
}
}
.select-input-wrapper {
height: 50px;
padding: 13px 20px;
border: 1px solid transparent;
border-radius: 8px;
box-shadow: 0 1px 5px rgba(0, 0, 0, 0.12);
cursor: pointer;
display: flex;
justify-content: space-between;
align-items: center;
.select-input-icon-wrapper {
width: 22px;
height: 22px;
border-radius: 6px;
display: flex;
justify-content: center;
align-items: center;
&:hover {
background: $light_background !important;
.select-input-icon {
line {
stroke: $theme;
}
}
}
.select-input-icon {
line {
stroke: $text;
}
}
}
.select-input {
@include font-size(16);
font-weight: 700;
display: flex;
flex-direction: row;
align-items: center;
.emoji-preview {
margin-right: 10px;
}
}
.not-selected {
span {
color: rgba($text, 0.5);
@include font-size(15);
font-weight: 700
}
}
}
.wrapper {
margin-bottom: 10px;
}
.set-folder-icon {
position: relative;
}
.slide-in-enter-active {
transition: all 5s ease;
}
.slide-in-enter
{
opacity: 0;
transform: translateY(-50px);
}
@media (prefers-color-scheme: dark) {
.color-pick-wrapper{
.color-wrapper{
.single-color {
&.active-color {
border: 2px solid ;
}
&:hover {
border: 2px solid $dark_mode_text_primary;
}
}
}
}
.emoji-wrapper {
background: $dark_mode_background;
.emoji-input {
background: $dark_mode_foreground ;
}
.groups-list{
.active{
background: $dark_mode_foreground !important;
}
.group-option {
&:hover {
background: $dark_mode_foreground !important;
}
}
}
.options-wrapper {
.option {
&:hover {
background: $dark_mode_foreground !important;
}
}
.not-found {
background: $dark_mode_foreground !important;
}
}
}
.select-input-wrapper {
background: $dark_mode_foreground;
.not-selected {
span {
color:$dark_mode_text_secondary;
}
}
.select-input-icon-wrapper {
&:hover {
background: rgba($theme, 0.1) !important;
.select-input-icon {
line {
stroke: $theme !important;
}
}
}
.select-input-icon {
line {
stroke:$dark_mode_text_primary !important;
}
}
}
}
}
</style>
+199 -169
View File
@@ -1,7 +1,7 @@
<template>
<PopupWrapper name="share-create">
<!--Title-->
<PopupHeader :title="$t('popup_share_create.title', {item: itemTypeTitle})" icon="share" />
<PopupHeader :title="$t('popup_share_create.title', {item: itemTypeTitle})" icon="share"/>
<!--Content-->
<PopupContent>
@@ -9,8 +9,27 @@
<!--Item Thumbnail-->
<ThumbnailItem class="item-thumbnail" :item="pickedItem" info="metadata"/>
<!-- Infobox for successfull sended email -->
<InfoBox v-if="isGeneratedShared && sharedViaEmail" class="info-box-wrapper">
<p v-html="$t('shared_form.email_successfully_send_message')"></p>
</InfoBox>
<!--Form to set sharing-->
<ValidationObserver v-if="! isGeneratedShared" ref="shareForm" v-slot="{ invalid }" tag="form" class="form-wrapper">
<ValidationObserver @submit.prevent v-if="! isGeneratedShared" ref="shareForm" v-slot="{ invalid }" tag="form" class="form-wrapper">
<TabWrapper>
<!-- Share via link -->
<TabOption :selected="true" :title="$t('shared_form.share_by_link')" icon="link"/>
<!-- Share via Email -->
<TabOption :title="$t('shared_form.share_by_email')" icon="email">
<ValidationProvider tag="div" mode="passive" name="Email" rules="required" v-slot="{ errors }">
<MultiEmailInput rules="required" v-model="shareOptions.emails" :label="$t('shared_form.recipients_label')" :isError="errors[0]"/>
</ValidationProvider>
</TabOption>
</TabWrapper>
<!--Permision Select-->
<ValidationProvider v-if="isFolder" tag="div" mode="passive" class="input-wrapper" name="Permission" rules="required" v-slot="{ errors }">
@@ -49,201 +68,212 @@
<!--Copy generated link-->
<div v-if="isGeneratedShared" class="form-wrapper">
<div class="input-wrapper">
<label class="input-label">{{ $t('shared_form.label_shared_url') }}:</label>
<CopyInput size="small" :value="shareLink" />
<label class="input-label">{{ this.sharedViaEmail ? $t('shared_form.label_share_vie_email') : $t('shared_form.label_shared_url') }}:</label>
<CopyInput size="small" :item="pickedItem"/>
</div>
</div>
</PopupContent>
<!--Actions-->
<PopupActions>
<ButtonBase
v-if="! isGeneratedShared"
class="popup-button"
@click.native="$closePopup()"
button-style="secondary"
>{{ $t('popup_move_item.cancel') }}
<ButtonBase v-if="! isGeneratedShared" class="popup-button" @click.native="$closePopup()" button-style="secondary">{{ $t('popup_move_item.cancel') }}
</ButtonBase>
<ButtonBase
class="popup-button"
@click.native="submitShareOptions"
button-style="theme"
:loading="isLoading"
:disabled="isLoading"
>{{ submitButtonText }}
<ButtonBase class="popup-button" @click.native="submitShareOptions" button-style="theme" :loading="isLoading" :disabled="isLoading">{{ submitButtonText }}
</ButtonBase>
</PopupActions>
</PopupWrapper>
</template>
<script>
import {ValidationProvider, ValidationObserver} from 'vee-validate/dist/vee-validate.full'
import SelectBoxInput from '@/components/Others/Forms/SelectBoxInput'
import PopupWrapper from '@/components/Others/Popup/PopupWrapper'
import PopupActions from '@/components/Others/Popup/PopupActions'
import PopupContent from '@/components/Others/Popup/PopupContent'
import PopupHeader from '@/components/Others/Popup/PopupHeader'
import SwitchInput from '@/components/Others/Forms/SwitchInput'
import SelectInput from '@/components/Others/Forms/SelectInput'
import ThumbnailItem from '@/components/Others/ThumbnailItem'
import ActionButton from '@/components/Others/ActionButton'
import CopyInput from '@/components/Others/Forms/CopyInput'
import ButtonBase from '@/components/FilesView/ButtonBase'
import {required} from 'vee-validate/dist/rules'
import {mapGetters} from 'vuex'
import {events} from '@/bus'
import axios from 'axios'
import { ValidationProvider, ValidationObserver } from 'vee-validate/dist/vee-validate.full'
import SelectBoxInput from '@/components/Others/Forms/SelectBoxInput'
import PopupWrapper from '@/components/Others/Popup/PopupWrapper'
import PopupActions from '@/components/Others/Popup/PopupActions'
import PopupContent from '@/components/Others/Popup/PopupContent'
import PopupHeader from '@/components/Others/Popup/PopupHeader'
import MultiEmailInput from '@/components/Others/Forms/MultiEmailInput'
import SwitchInput from '@/components/Others/Forms/SwitchInput'
import SelectInput from '@/components/Others/Forms/SelectInput'
import ThumbnailItem from '@/components/Others/ThumbnailItem'
import ActionButton from '@/components/Others/ActionButton'
import CopyInput from '@/components/Others/Forms/CopyInput'
import TabWrapper from '@/components/Others/TabWrapper'
import TabOption from '@/components/Others/TabOption'
import ButtonBase from '@/components/FilesView/ButtonBase'
import InfoBox from '@/components/Others/Forms/InfoBox'
import { LinkIcon, MailIcon } from 'vue-feather-icons'
import { required } from 'vee-validate/dist/rules'
import { mapGetters } from 'vuex'
import { events } from '@/bus'
import axios from 'axios'
export default {
name: 'ShareCreate',
components: {
ValidationProvider,
ValidationObserver,
SelectBoxInput,
ThumbnailItem,
ActionButton,
PopupWrapper,
PopupActions,
PopupContent,
PopupHeader,
SelectInput,
SwitchInput,
ButtonBase,
CopyInput,
required,
export default {
name: 'ShareCreate',
components: {
ValidationProvider,
ValidationObserver,
SelectBoxInput,
ThumbnailItem,
ActionButton,
PopupWrapper,
PopupActions,
TabWrapper,
TabOption,
PopupContent,
PopupHeader,
MultiEmailInput,
SelectInput,
SwitchInput,
ButtonBase,
CopyInput,
MailIcon,
required,
LinkIcon,
InfoBox
},
computed: {
...mapGetters([
'permissionOptions',
'expirationList'
]),
itemTypeTitle() {
return this.pickedItem && this.pickedItem.type === 'folder' ? this.$t('types.folder') : this.$t('types.file')
},
computed: {
...mapGetters([
'permissionOptions',
'expirationList',
]),
itemTypeTitle() {
return this.pickedItem && this.pickedItem.type === 'folder' ? this.$t('types.folder') : this.$t('types.file')
isFolder() {
return this.pickedItem && this.pickedItem.type === 'folder'
},
submitButtonText() {
return this.isGeneratedShared ? this.$t('shared_form.button_done') : this.$t('shared_form.button_generate')
},
moreOptionsTitle() {
return this.isMoreOptions ? this.$t('shared_form.button_close_options') : this.$t('shared_form.button_more_options')
}
},
data() {
return {
shareOptions: {
isPassword: false,
expiration: undefined,
password: undefined,
permission: undefined,
type: undefined,
unique_id: undefined,
emails: undefined
},
isFolder() {
return this.pickedItem && this.pickedItem.type === 'folder'
},
submitButtonText() {
return this.isGeneratedShared ? this.$t('shared_form.button_done') : this.$t('shared_form.button_generate')
},
moreOptionsTitle() {
return this.isMoreOptions ? this.$t('shared_form.button_close_options') : this.$t('shared_form.button_more_options')
pickedItem: undefined,
isGeneratedShared: false,
isLoading: false,
isMoreOptions: false,
sharedViaEmail: false
}
},
methods: {
moreOptions() {
this.isMoreOptions = !this.isMoreOptions
if (!this.isMoreOptions)
this.shareOptions.expiration = undefined
},
async submitShareOptions() {
// If shared was generated, then close popup
if (this.isGeneratedShared) {
events.$emit('popup:close')
return
}
},
data() {
return {
shareOptions: {
// Validate fields
const isValid = await this.$refs.shareForm.validate()
if (!isValid) return
this.isLoading = true
// Send request to get share link
axios
.post('/api/share', this.shareOptions)
.then(response => {
// Show infobox and reset emails container
if (this.shareOptions.emails)
this.sharedViaEmail = true
// End loading
this.isGeneratedShared = true
this.$store.commit('UPDATE_SHARED_ITEM', response.data.data.attributes)
})
.catch(() => {
events.$emit('alert:open', {
title: this.$t('popup_error.title'),
message: this.$t('popup_error.message'),
})
// End loading
this.isLoading = false
})
.finally(() => {
this.isLoading = false
})
}
},
mounted() {
events.$on('emailsInputValues', (emails) => this.shareOptions.emails = emails)
// Show popup
events.$on('popup:open', args => {
if (args.name !== 'share-create') return
// Store picked item
this.pickedItem = args.item
this.shareOptions.type = args.item.type
this.shareOptions.unique_id = args.item.unique_id
})
// Close popup
events.$on('popup:close', () => {
// Restore data
setTimeout(() => {
this.shareOptions = {
permission: undefined,
password: undefined,
isPassword: false,
expiration: undefined,
password: undefined,
permission: undefined,
type: undefined,
unique_id: undefined,
},
pickedItem: undefined,
shareLink: undefined,
isGeneratedShared: false,
isLoading: false,
isMoreOptions: false,
}
},
methods: {
moreOptions() {
this.isMoreOptions = ! this.isMoreOptions
if (! this.isMoreOptions)
this.shareOptions.expiration = undefined
},
async submitShareOptions() {
// If shared was generated, then close popup
if (this.isGeneratedShared) {
events.$emit('popup:close')
return;
emails: undefined
}
// Validate fields
const isValid = await this.$refs.shareForm.validate();
if (!isValid) return;
this.isLoading = true
// Send request to get share link
axios
.post('/api/share', this.shareOptions)
.then(response => {
// End loading
this.isLoading = false
this.shareLink = response.data.data.attributes.link
this.isGeneratedShared = true
this.$store.commit('UPDATE_SHARED_ITEM', response.data.data.attributes)
})
.catch(error => {
// todo: catch errors
// End loading
this.isLoading = false
})
},
},
mounted() {
// Show popup
events.$on('popup:open', args => {
if (args.name !== 'share-create') return
// Store picked item
this.pickedItem = args.item
this.shareOptions.type = args.item.type
this.shareOptions.unique_id = args.item.unique_id
})
// Close popup
events.$on('popup:close', () => {
// Restore data
setTimeout(() => {
this.shareOptions = {
permission: undefined,
password: undefined,
isPassword: false,
expiration: undefined,
type: undefined,
unique_id: undefined,
}
this.isGeneratedShared = false
this.isMoreOptions = false
this.shareLink = undefined
}, 150)
})
}
this.isGeneratedShared = false
this.isMoreOptions = false
this.sharedViaEmail = false
}, 150)
})
}
}
</script>
<style scoped lang="scss">
@import "@assets/vue-file-manager/_inapp-forms.scss";
@import '@assets/vue-file-manager/_forms';
@import "@assets/vue-file-manager/_inapp-forms.scss";
@import '@assets/vue-file-manager/_forms';
.more-options {
margin-bottom: 10px;
.more-options {
margin-bottom: 10px;
}
.input-wrapper {
&.password {
margin-top: -10px;
}
}
.input-wrapper {
.item-thumbnail {
margin-bottom: 20px;
}
&.password {
margin-top: -10px;
}
}
.item-thumbnail {
margin-bottom: 20px;
}
</style>
+103 -14
View File
@@ -9,14 +9,26 @@
<!--Item Thumbnail-->
<ThumbnailItem class="item-thumbnail" :item="pickedItem" info="metadata"/>
<!--Form to set sharing-->
<ValidationObserver ref="shareForm" v-slot="{ invalid }" tag="form" class="form-wrapper">
<!-- Infobox for successfull sended email -->
<InfoBox v-if="sendToRecipientsMenu && isEmailSended" class="info-box-wrapper">
<p v-html="$t('shared_form.email_successfully_send_message')"></p>
</InfoBox>
<!--Share link-->
<div class="input-wrapper">
<label class="input-label">{{ $t('shared_form.label_shared_url') }}:</label>
<CopyInput size="small" :value="pickedItem.shared.link" />
</div>
<div v-if="! sendToRecipientsMenu || (sendToRecipientsMenu && isEmailSended)" class="input-wrapper copy-input">
<label class="input-label">{{ $t('shared_form.label_share_vie_email') }}:</label>
<CopyInput size="small" :item="pickedItem" />
</div>
<ValidationObserver @submit.prevent v-if="sendToRecipientsMenu && !isEmailSended" v-slot="{ invalid }" ref="shareEmail" tag="form" class="form-wrapper">
<ValidationProvider tag="div" mode="passive" name="Email" rules="required" v-slot="{ errors }">
<MultiEmailInput rules="required" v-model="emails" :label="$t('shared_form.label_send_to_recipients')" :isError="errors[0]" />
</ValidationProvider>
</ValidationObserver>
<!--Form to set sharing-->
<ValidationObserver @submit.prevent v-if="! sendToRecipientsMenu" ref="shareForm" v-slot="{ invalid }" tag="form" class="form-wrapper">
<!--Permision Select-->
<ValidationProvider v-if="isFolder" tag="div" mode="passive" class="input-wrapper" name="Permission" rules="required" v-slot="{ errors }">
@@ -58,7 +70,7 @@
<!--Actions-->
<PopupActions>
<ButtonBase
<ButtonBase v-if="! sendToRecipientsMenu || (sendToRecipientsMenu && !isEmailSended)"
class="popup-button"
@click.native="destroySharing"
:button-style="destroyButtonStyle"
@@ -72,7 +84,7 @@
button-style="theme"
:loading="isLoading"
:disabled="isLoading"
>{{ $t('popup_share_edit.save') }}
>{{ secondButtonText }}
</ButtonBase>
</PopupActions>
</PopupWrapper>
@@ -87,10 +99,12 @@
import PopupHeader from '@/components/Others/Popup/PopupHeader'
import SwitchInput from '@/components/Others/Forms/SwitchInput'
import SelectInput from '@/components/Others/Forms/SelectInput'
import MultiEmailInput from '@/components/Others/Forms/MultiEmailInput'
import ThumbnailItem from '@/components/Others/ThumbnailItem'
import ActionButton from '@/components/Others/ActionButton'
import CopyInput from '@/components/Others/Forms/CopyInput'
import ButtonBase from '@/components/FilesView/ButtonBase'
import InfoBox from '@/components/Others/Forms/InfoBox'
import {required} from 'vee-validate/dist/rules'
import {mapGetters} from 'vuex'
import {events} from '@/bus'
@@ -109,10 +123,12 @@
PopupContent,
PopupHeader,
SelectInput,
MultiEmailInput,
SwitchInput,
ButtonBase,
CopyInput,
required,
InfoBox,
},
computed: {
...mapGetters([
@@ -125,10 +141,26 @@
return this.pickedItem && this.pickedItem.type === 'folder'
},
destroyButtonText() {
return this.isConfirmedDestroy ? this.$t('popup_share_edit.confirm') : this.$t('popup_share_edit.stop')
if(! this.sendToRecipientsMenu)
return this.isConfirmedDestroy ? this.$t('popup_share_edit.confirm') : this.$t('popup_share_edit.stop')
if(this.sendToRecipientsMenu)
return this.$t('popup_share_edit.go_back')
},
destroyButtonStyle() {
return this.isConfirmedDestroy ? 'danger-solid' : 'secondary'
if(! this.sendToRecipientsMenu)
return this.isConfirmedDestroy ? 'danger-solid' : 'secondary'
if(this.sendToRecipientsMenu)
return 'secondary'
},
secondButtonText(){
if(! this.sendToRecipientsMenu)
return this.$t('popup_share_edit.save')
if(this.sendToRecipientsMenu)
return this.isEmailSended ? this.$t('shared_form.button_done') : this.$t('popup_share_edit.send_to_recipients')
},
isSharedLocation() {
return this.currentFolder && this.currentFolder.location === 'shared'
@@ -139,11 +171,14 @@
},
data() {
return {
sendToRecipientsMenu: false,
isConfirmedDestroy: false,
canChangePassword: false,
shareOptions: undefined,
pickedItem: undefined,
emails:undefined,
isMoreOptions: false,
isEmailSended:false,
isDeleting: false,
isLoading: false,
}
@@ -158,8 +193,38 @@
changePassword() {
this.canChangePassword = false
},
async sendViaEmail() {
// Validate email field
const isValid = await this.$refs.shareEmail.validate();
if (!isValid) return;
this.isLoading = true
axios.
post(`/api/share/${this.shareOptions.token}/send-email`, {
emails: this.emails
})
.catch(() => {
this.$isSomethingWrong()
})
.finally(() => {
this.isEmailSended = true
// End loading
this.isLoading = false
})
},
async destroySharing() {
if(this.sendToRecipientsMenu) {
this.sendToRecipientsMenu = false
return
}
// Set confirm button
if (! this.isConfirmedDestroy) {
@@ -188,6 +253,18 @@
},
async updateShareOptions() {
// If is open send share via email
if(this.sendToRecipientsMenu && !this.isEmailSended) {
this.sendViaEmail()
return
}
// Is is open send share via email and email was already sended
if(this.sendToRecipientsMenu && this.isEmailSended){
events.$emit('popup:close')
return
}
// If shared was generated, then close popup
if (this.isGeneratedShared) {
@@ -219,18 +296,21 @@
events.$emit('popup:close')
})
.catch(() => {
this.$isSomethingWrong()
})
.finally(() => {
// End loading
this.isLoading = false
})
},
},
mounted() {
this.sendToRecipientsMenu = false
events.$on('emailsInputValues', (emails) => {
this.emails = emails
})
// Show popup
events.$on('popup:open', args => {
@@ -251,6 +331,9 @@
if (args.item.shared.expire_in)
this.isMoreOptions = true
if (args.sentToEmail)
this.sendToRecipientsMenu = true
this.canChangePassword = args.item.shared.protected
})
@@ -259,6 +342,8 @@
// Restore data
setTimeout(() => {
this.sendToRecipientsMenu = false
this.isEmailSended = false
this.isConfirmedDestroy = false
this.canChangePassword = false
this.pickedItem = undefined
@@ -278,6 +363,10 @@
&.password {
margin-top: -10px;
}
&.copy-input {
padding: 0px 20px;
}
}
.change-password {
@@ -0,0 +1,22 @@
<template>
<div v-if="isActive">
<slot></slot>
</div>
</template>
<script>
export default {
name: "TabOption",
props: ['title', 'icon', 'selected'],
data () {
return {
isActive: false
}
},
mounted() {
this.isActive = this.selected
}
}
</script>
@@ -0,0 +1,117 @@
<template>
<div>
<div class="tab-wrapper">
<div class="tab" :class="{ active: tab.isActive }" @click="selectTab(tab)" v-for="(tab, i) in tabs" :key="i">
<!--Icon-->
<mail-icon v-if="tab.icon === 'email'" class="tab-icon" size="17"/>
<link-icon v-if="tab.icon === 'link'" class="tab-icon" size="17"/>
<smile-icon v-if="tab.icon === 'emoji'" class="tab-icon" size="17"/>
<folder-icon v-if="tab.icon === 'folder'" class="tab-icon" size="17"/>
<!--Title-->
<b class="tab-title">{{tab.title}}</b>
</div>
</div>
<slot></slot>
</div>
</template>
<script>
import {
LinkIcon,
MailIcon,
SmileIcon,
FolderIcon } from 'vue-feather-icons'
export default {
name: "TabWrapper",
components: {
LinkIcon,
MailIcon,
SmileIcon,
FolderIcon
},
data () {
return {
tabs: []
}
},
methods: {
selectTab(selectedTab) {
this.tabs.forEach(tab => {
tab.isActive = tab.title == selectedTab.title
})
}
},
mounted () {
this.tabs = this.$children
}
}
</script>
<style scoped lang="scss">
@import "@assets/vue-file-manager/_inapp-forms.scss";
@import '@assets/vue-file-manager/_forms';
.tab-wrapper {
display: flex;
justify-content: center;
margin-bottom: 20px;
cursor: pointer;
align-items: center;
background: white;
color: $text;
border-radius: 8px;
overflow: hidden;
border: 1px solid #E8E9EB;
.tab-title {
@include font-size(14);
}
.tab {
width: 100%;
display: flex;
align-items: center;
justify-content: center;
padding: 8px;
&.active {
background: $light_background;
.tab-title {
color: $text;
}
}
}
.tab-icon {
margin-right: 10px;
path,
circle,
line,
polyline {
color: $theme !important;
}
}
}
@media (prefers-color-scheme: dark) {
.tab-wrapper {
background: $dark_mode_foreground;
border-color: transparent;
.tab.active {
background: rgba($theme, 0.1);
.tab-title {
color: $theme;
}
}
}
}
</style>
@@ -13,8 +13,12 @@
<!--Image thumbnail-->
<img v-if="isImage && item.thumbnail" class="image" :src="item.thumbnail" :alt="item.name"/>
<!-- If folder have set emoji -->
<Emoji v-if="isFolder && folderIconHandle" :emoji="folderIconHandle" size="36"/>
<!--Else show only folder icon-->
<FontAwesomeIcon v-if="isFolder" class="folder-icon" icon="folder"/>
<FontAwesomeIcon ref="folderIcon" v-if="isFolder && !folderIconHandle" class="folder-icon" icon="folder"/>
</div>
<!--Name-->
@@ -41,12 +45,37 @@
<script>
import {mapGetters} from 'vuex'
import Emoji from '@/components/Others/Emoji'
export default {
name: 'ThumbnailItem',
props: ['item', 'info'],
props: ['item', 'info', 'setFolderIcon'],
components: {Emoji},
computed: {
...mapGetters(['currentFolder']),
folderIconHandle(){
// Set icon folder if set folder from rename popup
if(this.setFolderIcon){
return this.setFolderIcon.emoji
? this.setFolderIcon.emoji
: this.$nextTick(() => {
this.$refs.folderIcon.firstElementChild.style.fill = `${this.setFolderIcon.color}`
})
}
// If folder have already set some icon
if(!this.setFolderIcon && (this.item.icon_emoji || this.item.icon_color)){
return this.item.icon_emoji
? this.item.icon_emoji
: this.$nextTick(() => {
this.$refs.folderIcon.firstElementChild.style.fill = `${this.item.icon_color}`
})
}
},
isFolder() {
return this.item.type === 'folder'
},
@@ -103,9 +132,12 @@
.icon-item {
position: relative;
min-width: 52px;
display: flex;
text-align: center;
justify-content: center;
line-height: 0;
.file-icon {
@include font-size(35);
+3 -3
View File
@@ -12,10 +12,10 @@
name: 'Vignette',
computed: {
...mapGetters([
'isZippingFiles'
'processingPopup'
]),
isVisible() {
return this.isZippingFiles || this.isVisibleVignette
return this.processingPopup || this.isVisibleVignette
},
},
data() {
@@ -56,7 +56,7 @@
right: 0;
left: 0;
bottom: 0;
z-index: 19;
z-index: 18;
background: $light_mode_vignette;
}
@@ -1,5 +1,5 @@
<template>
<section class="content-sidebar">
<section class="content-sidebar" id="content-sidebar">
<slot></slot>
</section>
</template>