vue frontend update

This commit is contained in:
carodej
2020-04-17 11:33:06 +02:00
parent ae4353cc4b
commit 506c39896a
45 changed files with 2366 additions and 784 deletions

View File

@@ -1,13 +1,16 @@
<template>
<button class="button-base" :class="buttonStyle" type="button">
<slot></slot>
<span v-if="loading" class="icon">
<FontAwesomeIcon icon="sync-alt" class="sync-alt"/>
</span>
<slot v-if="! loading"></slot>
</button>
</template>
<script>
export default {
name: 'ButtonBase',
props: ['buttonStyle']
props: ['buttonStyle', 'loading']
}
</script>
@@ -38,12 +41,30 @@
background: rgba($danger, .1);
}
&.danger-solid {
color: white;
background: $danger;
}
&.secondary {
color: $text;
background: $light_background;
}
}
.sync-alt {
animation: spin 1s linear infinite;
}
@keyframes spin {
0% {
transform: rotate(0);
}
100% {
transform: rotate(360deg);
}
}
@media (prefers-color-scheme: dark) {
.button-base {

View File

@@ -1,34 +1,60 @@
<template>
<div
ref="contextmenu"
class="contextmenu"
:style="{ top: positionY + 'px', left: positionX + 'px' }"
@click="closeAndResetContextMenu"
class="contextmenu"
v-show="isVisible"
ref="contextmenu"
>
<ul class="menu-options" id="menu-options-list" ref="list" @click="closeAndResetContextMenu">
<!--ContextMenu for trash location-->
<ul v-if="$isTrashLocation()" class="menu-options" ref="list">
<li class="menu-option" @click="removeItem" 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>
<!--View-->
<li class="menu-option" @click="addToFavourites" v-if="! $isTrashLocation() && item && isFolder">{{ isInFavourites ? $t('context_menu.remove_from_favourites') : $t('context_menu.add_to_favourites') }}</li>
<li class="menu-option" @click="createFolder" v-if="! $isTrashLocation()">{{ $t('context_menu.create_folder') }}</li>
<!--Edits-->
<li class="menu-option" @click="removeItem" v-if="! $isTrashLocation() && item">{{ $t('context_menu.delete') }}</li>
<li class="menu-option" @click="moveItem" v-if="! $isTrashLocation() && item">{{ $t('context_menu.move') }}</li>
<!--Trash-->
<li class="menu-option" @click="$store.dispatch('restoreItem', item)" v-if="item && $isTrashLocation()">{{ $t('context_menu.restore') }}</li>
<li class="menu-option" @click="$store.dispatch('emptyTrash')" v-if="$isTrashLocation()">{{ $t('context_menu.empty_trash') }}</li>
<!--Others-->
<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>
<!--ContextMenu for Base location-->
<ul v-if="$isBaseLocation()" 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="removeItem" 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">
{{ $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>
</template>
<script>
import {events} from '@/bus'
import {mapGetters} from 'vuex'
import {events} from '@/bus'
export default {
name: 'ContextMenu',
@@ -57,10 +83,15 @@
},
methods: {
moveItem() {
// Move item fire popup
events.$emit('popup:move-item', this.item);
// Open move item popup
events.$emit('popup:open', {name: 'move', item: this.item})
},
shareItem() {
// Open share item popup
events.$emit('popup:open', {name: 'share-create', item: this.item})
},
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)) {
this.$store.dispatch('addToFavourites', this.item)
} else {

View File

@@ -1,6 +1,7 @@
<template>
<div id="desktop-toolbar">
<div class="toolbar-wrapper">
<!-- Go back-->
<div class="toolbar-go-back" v-if="homeDirectory">
<div @click="goBack" class="go-back-button">
@@ -20,6 +21,7 @@
<div class="toolbar-button-wrapper">
<SearchBar/>
</div>
<div class="toolbar-button-wrapper">
<ToolbarButtonUpload source="upload" action="Upload file"/>
<ToolbarButton
@@ -33,6 +35,7 @@
action="Create folder"
/>
</div>
<div class="toolbar-button-wrapper">
<ToolbarButton
:source="preview"

View File

@@ -1,5 +1,5 @@
<template>
<div v-if="fileInfoDetail">
<div class="file-info-content" v-if="fileInfoDetail">
<div class="file-headline" spellcheck="false">
<FilePreview />
@@ -7,25 +7,20 @@
<!--File info-->
<div class="flex">
<div class="icon">
<div class="icon-preview" @dblclick="getItemAction">
<FontAwesomeIcon v-if="fileInfoDetail.type == 'folder'" icon="folder"></FontAwesomeIcon>
<FontAwesomeIcon v-if="fileInfoDetail.type == 'file'" icon="file"></FontAwesomeIcon>
<FontAwesomeIcon v-if="fileInfoDetail.type == 'image'" icon="file-image"></FontAwesomeIcon>
<FontAwesomeIcon v-if="fileInfoDetail.type == 'video'" icon="file-video"></FontAwesomeIcon>
<FontAwesomeIcon v-if="fileInfoDetail.type == 'audio'" icon="file-audio"></FontAwesomeIcon>
<div class="icon-preview">
<FontAwesomeIcon :icon="filePreviewIcon"></FontAwesomeIcon>
</div>
</div>
<div class="file-info">
<span ref="name" contenteditable="false" class="name">{{
fileInfoDetail.name
}}</span>
<span class="mimetype">{{ fileInfoDetail.mimetype }}</span>
<span ref="name" class="name">{{ fileInfoDetail.name }}</span>
<span class="mimetype" v-if="fileInfoDetail.mimetype">{{ fileInfoDetail.mimetype }}</span>
</div>
</div>
</div>
<!--Info list-->
<ul class="list-info">
<!--Filesize-->
<li v-if="fileInfoDetail.filesize" class="list-info-item">
<b>{{ $t('file_detail.size') }}</b>
@@ -33,7 +28,7 @@
</li>
<!--Latest change-->
<li v-if="fileInfoDetail.created_at" class="list-info-item">
<li class="list-info-item">
<b>{{ $t('file_detail.created_at') }}</b>
<span>{{ fileInfoDetail.created_at }}</span>
</li>
@@ -46,60 +41,69 @@
<span>{{ fileInfoDetail.parent ? fileInfoDetail.parent.name : $t('locations.home') }}</span>
</div>
</li>
<!--Parent-->
<li v-if="true" class="list-info-item">
<b>Shared</b>
<div class="action-button" @click="shareItemOptions">
<FontAwesomeIcon class="icon" icon="user-edit" />
<span>Can edit and upload files</span>
</div>
<CopyInput class="copy-sharelink" size="small" :value="shareLink" />
</li>
</ul>
</div>
</template>
<script>
import FilePreview from '@/components/VueFileManagerComponents/FilesView/FilePreview'
import CopyInput from '@/components/VueFileManagerComponents/Others/Forms/CopyInput'
import {mapGetters} from 'vuex'
import {debounce} from 'lodash'
import {events} from "@/bus"
export default {
name: 'FileInfoPanel',
components: {
FilePreview
FilePreview,
CopyInput,
},
computed: {
...mapGetters(['fileInfoDetail'])
...mapGetters(['fileInfoDetail']),
filePreviewIcon() {
switch (this.fileInfoDetail.type) {
case 'folder':
return 'folder'
break;
case 'file':
return 'file'
break;
case 'image':
return 'file-image'
break;
case 'video':
return 'file-video'
break;
case 'file':
return 'file-audio'
break;
}
}
},
data() {
return {
shareLink: 'http://192.168.1.131:8000/shared?token=3ZlQLIoCR8izoc0PemekHNq3UIMj6OrC0aQ2zowclfjFYa8P6go8fMKPnXTJomvz'
}
},
methods: {
shareItemOptions() {
// Open share item popup
events.$emit('popup:open', {name: 'share-edit', item: this.fileInfoDetail})
},
moveItem() {
// Move item fire popup
events.$emit('popup:move-item', this.fileInfoDetail);
},
getItemAction() {
// Open image on new tab
if (this.fileInfoDetail.type == 'image') {
this.$openImageOnNewTab(this.fileInfoDetail.file_url)
}
events.$emit('popup:open', {name: 'move', item: this.fileInfoDetail})
// Download file
if (this.fileInfoDetail.type == 'file') {
this.$downloadFile(
this.fileInfoDetail.file_url,
this.fileInfoDetail.name +
'.' +
this.fileInfoDetail.mimetype
)
}
// Open folder
if (this.fileInfoDetail.type == 'folder') {
// Todo: open folder
}
},
changeItemName: debounce(function (e) {
// Prevent submit empty string
if (e.target.innerText === '') return
this.$store.dispatch('changeItemName', {
unique_id: this.fileInfoDetail.unique_id,
type: this.fileInfoDetail.type,
name: e.target.innerText
})
}, 300)
}
}
}
</script>
@@ -107,6 +111,10 @@
<style scoped lang="scss">
@import "@assets/app.scss";
.file-info-content {
padding-bottom: 20px;
}
.file-headline {
background: $light_background;
padding: 12px;
@@ -156,6 +164,8 @@
.name {
@include font-size(14);
font-weight: 700;
line-height: 1.4;
display: block;
color: $text;
}
@@ -173,7 +183,7 @@
.list-info-item {
display: block;
padding-top: 20px;
padding-top: 15px;
&:first-child {
padding-top: 0;
@@ -183,9 +193,13 @@
cursor: pointer;
.icon {
@include font-size(11);
@include font-size(10);
display: inline-block;
margin-right: 2px;
path {
fill: $theme_light;
}
}
}
@@ -193,6 +207,7 @@
display: block;
@include font-size(13);
color: $theme;
margin-bottom: 2px;
}
span {
@@ -204,6 +219,10 @@
}
}
.copy-sharelink {
margin-top: 10px;
}
@media (prefers-color-scheme: dark) {
.file-headline {

View File

@@ -20,10 +20,11 @@
>
<!--Thumbnail for item-->
<div class="icon-item">
<!--If is file or image, then link item-->
<span v-if="isFile" class="file-icon-text">{{
data.mimetype
}}</span>
<span v-if="isFile" class="file-icon-text">
{{ data.mimetype }}
</span>
<!--Folder thumbnail-->
<FontAwesomeIcon v-if="isFile" class="file-icon" icon="file"/>
@@ -38,22 +39,30 @@
<!--Name-->
<div class="item-name">
<!--Name-->
<span
<b
ref="name"
@input="changeItemName"
:contenteditable="!$isMobile()"
class="name"
>{{ itemName }}</span
>
{{ itemName }}
</b>
<!--Other attributes-->
<span v-if="! isFolder" class="item-size">{{
data.filesize
}}</span>
<div class="item-info">
<!--Shared Icon-->
<div class="item-shared" v-if="true">
<FontAwesomeIcon class="shared-icon" icon="user-friends"/>
<span class="label">Shared, </span>
</div>
<span v-if="isFolder" class="item-length">
{{ folderItems == 0 ? $t('folder.empty') : $tc('folder.item_counts', folderItems) }}
</span>
<!--Filesize-->
<span v-if="! isFolder" class="item-size">{{ data.filesize }}</span>
<!--Folder item counts-->
<span v-if="isFolder" class="item-length">
{{ folderItems == 0 ? $t('folder.empty') : $tc('folder.item_counts', folderItems) }}
</span>
</div>
</div>
<span @click.stop="showItemActions" class="show-actions" v-if="$isMobile()">
@@ -220,19 +229,29 @@
@include font-size(12);
font-weight: 400;
color: $text-muted;
display: block;
display: inline-block;
}
.name {
.item-info {
display: block;
line-height: 1;
}
&[contenteditable] {
-webkit-user-select: text;
user-select: text;
.item-shared {
display: inline-block;
.label {
@include font-size(12);
font-weight: 400;
color: $theme;
}
&[contenteditable='true']:hover {
text-decoration: underline;
.shared-icon {
@include font-size(10);
path {
fill: $theme;
}
}
}
@@ -244,6 +263,15 @@
overflow: hidden;
text-overflow: ellipsis;
&[contenteditable] {
-webkit-user-select: text;
user-select: text;
}
&[contenteditable='true']:hover {
text-decoration: underline;
}
&.actived {
max-height: initial;
}
@@ -377,4 +405,6 @@
}
}
}
</style>

View File

@@ -20,9 +20,9 @@
<!--Thumbnail for item-->
<div class="icon-item">
<!--If is file or image, then link item-->
<span v-if="isFile" class="file-icon-text">{{
data.mimetype | limitCharacters
}}</span>
<span v-if="isFile" class="file-icon-text">
{{ data.mimetype | limitCharacters }}
</span>
<!--Folder thumbnail-->
<FontAwesomeIcon v-if="isFile" class="file-icon" icon="file"/>
@@ -37,19 +37,31 @@
<!--Name-->
<div class="item-name">
<!--Name-->
<span
<b
ref="name"
@input="changeItemName"
:contenteditable="!$isMobile() && !$isTrashLocation()"
class="name"
>{{ itemName }}</span>
>
{{ itemName }}
</b>
<!--Other attributes-->
<span v-if="! isFolder" class="item-size">{{ data.filesize }}, {{ timeStamp }}</span>
<div class="item-info">
<span v-if="isFolder" class="item-length">
{{ folderItems == 0 ? $t('folder.empty') : $tc('folder.item_counts', folderItems) }}, {{ timeStamp }}
</span>
<!--Shared Icon-->
<div class="item-shared" v-if="true">
<FontAwesomeIcon class="shared-icon" icon="user-friends"/>
<span class="label">Shared,</span>
</div>
<!--Filesize and timestamp-->
<span v-if="! isFolder" class="item-size">{{ data.filesize }}, {{ timeStamp }}</span>
<!--Folder item counts-->
<span v-if="isFolder" class="item-length">
{{ folderItems == 0 ? $t('folder.empty') : $tc('folder.item_counts', folderItems) }}, {{ timeStamp }}
</span>
</div>
</div>
<!--Go Next icon-->
@@ -233,12 +245,34 @@
text-overflow: ellipsis;
white-space: nowrap;
.item-info {
display: block;
line-height: 1;
}
.item-shared {
display: inline-block;
.label {
@include font-size(12);
font-weight: 400;
color: $theme;
}
.shared-icon {
@include font-size(10);
path {
fill: $theme;
}
}
}
.item-size,
.item-length {
@include font-size(12);
font-weight: 400;
color: $text-muted;
display: block;
}
.name {

View File

@@ -8,46 +8,38 @@
@click="closeAndResetContextMenu"
>
<div class="menu-wrapper">
<ul class="menu-options">
<li class="menu-option"
@click="addToFavourites"
v-if="! $isTrashLocation() && fileInfoDetail && isFolder"
>
{{ isInFavourites ? $t('context_menu.remove_from_favourites') : $t('context_menu.add_to_favourites') }}
</li>
<li class="menu-option"
@click="$store.dispatch('restoreItem', fileInfoDetail)"
v-if="fileInfoDetail && $isTrashLocation()"
>
<!--Mobile for trash location-->
<ul v-if="$isTrashLocation()" 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="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"
>
<li class="menu-option" @click="downloadItem" v-if="! isFolder">
{{ $t('context_menu.download') }}
</li>
<li
class="menu-option delete"
@click="removeItem"
v-if="fileInfoDetail"
>
<li class="menu-option delete" @click="removeItem" v-if="fileInfoDetail">
{{ $t('context_menu.delete') }}
</li>
</ul>
<!--Mobile for Base location-->
<ul v-if="$isBaseLocation()" 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">
{{ $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="removeItem" v-if="fileInfoDetail">
{{ $t('context_menu.delete') }}
</li>
</ul>
@@ -92,11 +84,16 @@
},
methods: {
moveItem() {
// Move item fire popup
events.$emit('popup:move-item', this.fileInfoDetail);
// Open move item popup
events.$emit('popup:open', {name: 'move', item: this.fileInfoDetail})
},
shareItem() {
// Open share item popup
events.$emit('popup:open', {name: 'share-create', item: this.fileInfoDetail})
},
addToFavourites() {
if (this.app.favourites && ! this.app.favourites.find(el => el.unique_id == this.fileInfoDetail.unique_id)) {
if (this.app.favourites && !this.app.favourites.find(el => el.unique_id == this.fileInfoDetail.unique_id)) {
this.$store.dispatch('addToFavourites', this.fileInfoDetail)
} else {
this.$store.dispatch('removeFromFavourites', this.fileInfoDetail)
@@ -130,7 +127,7 @@
this.$store.dispatch('changeItemName', item)
// Change item name if is mobile device or prompted
if ( this.$isMobile() ) {
if (this.$isMobile()) {
events.$emit('change:name', item)
}
}

View File

@@ -0,0 +1,44 @@
<template>
<div class="action-button">
<FontAwesomeIcon class="icon" :icon="icon" />
<span class="label">
<slot></slot>
</span>
</div>
</template>
<script>
export default {
name: 'ActionButton',
props: ['icon'],
}
</script>
<style lang="scss" scoped>
@import "@assets/app.scss";
.action-button {
cursor: pointer;
.label {
@include font-size(12);
color: $theme_light;
font-weight: 600;
text-decoration: underline;
}
.icon {
@include font-size(10);
display: inline-block;
margin-right: 2px;
path {
fill: $theme_light;
}
}
}
@media (prefers-color-scheme: dark) {
}
</style>

View File

@@ -0,0 +1,78 @@
<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">
<FontAwesomeIcon :icon="isCopiedLink ? 'check' : 'link'"/>
</div>
</div>
</template>
<script>
export default {
name: 'CopyInput',
props: ['size', 'value'],
data() {
return {
isCopiedLink: false,
}
},
methods: {
copyUrl() {
// Get input value
var copyText = document.getElementById("link-input");
// select link
copyText.select();
copyText.setSelectionRange(0, 99999);
// Copy
document.execCommand("copy");
// Mark button as copied
this.isCopiedLink = true
// Reset copy button
setTimeout(() => {this.isCopiedLink = false}, 1000)
},
}
}
</script>
<style lang="scss" scoped>
@import "@assets/app.scss";
@import "@assets/vue-file-manager/_inapp-forms.scss";
// Single page
.copy-input {
&.small {
&.icon-append {
.icon {
padding: 8px 10px;
@include font-size(11);
}
}
input {
padding: 6px 10px;
@include font-size(13);
}
}
.icon {
cursor: pointer;
}
input {
text-overflow: ellipsis;
&:disabled {
color: $text;
cursor: pointer;
}
}
}
</style>

View File

@@ -0,0 +1,194 @@
<template>
<div class="select">
<!--Area-->
<div class="input-area" :class="{'is-active': isOpen, 'is-error': isError}" @click="openMenu">
<!--If is selected-->
<div class="selected" v-if="selected">
<div class="option-icon" v-if="selected.icon">
<FontAwesomeIcon :icon="selected.icon" />
</div>
<span class="option-value">{{ selected.label }}</span>
</div>
<!--If is empty-->
<div class="not-selected" v-if="! selected">
<span class="option-value placehoder">Selected your permision</span>
</div>
<FontAwesomeIcon icon="chevron-down" class="chevron"/>
</div>
<!--Options-->
<transition name="slide-in">
<ul class="input-options" v-if="isOpen">
<li class="option-item" @click="selectOption(option)" v-for="(option, i) in options" :key="i">
<div class="option-icon" v-if="option.icon">
<FontAwesomeIcon :icon="option.icon" />
</div>
<span class="option-value">{{ option.label }}</span>
</li>
</ul>
</transition>
</div>
</template>
<script>
export default {
name:'SelectInput',
props: ['options', 'isError', 'default'],
data() {
return {
selected: undefined,
isOpen: false,
}
},
methods: {
selectOption(option) {
// Emit selected
this.$emit('input', option.value)
// Get selected
this.selected = option
// Close menu
this.isOpen = false
},
openMenu() {
this.isOpen = ! this.isOpen
},
},
created() {
if (this.default)
this.selected = this.options.find(option => option.value === this.default)
}
}
</script>
<style lang="scss" scoped>
@import "@assets/app.scss";
.select {
position: relative;
user-select: none;
}
.input-options {
background: $light_background;
border-radius: 8px;
position: absolute;
overflow: hidden;
top: 65px;
left: 0;
right: 0;
z-index: 9;
.option-item {
padding: 13px 20px;
display: block;
border-bottom: 1px solid #EBEBEB;
cursor: pointer;
&:hover {
color: $theme;
background: rgba($theme, .1);
}
&:last-child {
border-bottom: none;
}
}
}
.input-area {
justify-content: space-between;
background: $light_background;
border: 1px solid transparent;
@include transition(150ms);
align-items: center;
border-radius: 8px;
padding: 13px 20px;
display: flex;
outline: 0;
width: 100%;
cursor: pointer;
.chevron {
@include transition(150ms);
}
&.is-active {
border-color: $theme;
box-shadow: 0 0 7px rgba($theme, 0.3);
.chevron {
@include transform(rotate(180deg));
}
}
&.is-error {
border-color: $danger;
box-shadow: 0 0 7px rgba($danger, 0.3);
}
}
.option-icon {
width: 20px;
display: inline-block;
@include font-size(12);
}
.option-value {
@include font-size(15);
font-weight: 700;
width: 100%;
&.placehoder {
color: $light_text;
}
}
.slide-in-enter-active {
transition: all 150ms ease;
}
.slide-in-enter /* .list-leave-active below version 2.1.8 */
{
opacity: 0;
transform: translateY(-50px);
}
@media (prefers-color-scheme: dark) {
.input-area {
background: $dark_mode_foreground;
.option-icon {
path {
fill: $theme
}
}
}
.input-options {
background: $dark_mode_foreground;
.option-item {
border-bottom: none;
&:hover {
color: $theme;
background: rgba($theme, .1);
}
&:last-child {
border-bottom: none;
}
}
}
}
</style>

View File

@@ -0,0 +1,93 @@
<template>
<div class="input-wrapper">
<div class="switch-content">
<label class="input-label" v-if="label">{{ label }}:</label>
<small class="input-info" v-if="info">{{ info }}</small>
</div>
<div class="switch-content text-right">
<div
class="switch"
:class="{ active: isSwitched }"
@click="changeState"
>
<div class="switch-button"></div>
</div>
</div>
</div>
</template>
<script>
export default {
name:'SwitchInput',
props: ['label', 'name', 'state', 'info'],
data() {
return {
isSwitched: undefined
}
},
methods: {
changeState() {
this.isSwitched = ! this.isSwitched
this.$emit('input', this.isSwitched)
}
},
mounted() {
this.isSwitched = this.state
}
}
</script>
<style lang="scss" scoped>
@import "@assets/app.scss";
.input-wrapper {
display: flex;
width: 100%;
.input-label {
color: $text;
}
.switch-content {
width: 100%;
&:last-child {
width: 80px;
}
}
}
.switch {
width: 50px;
height: 28px;
border-radius: 50px;
display: block;
background: #f1f1f5;
position: relative;
@include transition;
.switch-button {
@include transition;
width: 22px;
height: 22px;
border-radius: 50px;
display: block;
background: white;
position: absolute;
top: 3px;
left: 3px;
box-shadow: 0 2px 4px rgba(37, 38, 94, 0.1);
cursor: pointer;
}
&.active {
background: $theme;
.switch-button {
left: 25px;
}
}
}
</style>

View File

@@ -0,0 +1,132 @@
<template>
<PopupWrapper name="move">
<!--Title-->
<PopupHeader :title="$t('popup_move_item.title')" />
<!--Content-->
<PopupContent type="height-limited" v-if="app && pickedItem">
<!--Show Spinner when loading folders-->
<Spinner v-if="isLoadingTree"/>
<!--Folder tree-->
<div v-if="! isLoadingTree">
<ThumbnailItem class="item-thumbnail" :item="pickedItem" info="location"/>
<TreeMenu :depth="1" :nodes="items" v-for="items in app.folders" :key="items.unique_id"/>
</div>
</PopupContent>
<!--Actions-->
<PopupActions>
<ButtonBase
class="popup-button"
@click.native="$closePopup()"
button-style="secondary"
>{{ $t('popup_move_item.cancel') }}
</ButtonBase>
<ButtonBase
class="popup-button"
@click.native="moveItem"
:button-style="selectedFolder ? 'theme' : 'secondary'"
>{{ $t('popup_move_item.submit') }}
</ButtonBase>
</PopupActions>
</PopupWrapper>
</template>
<script>
import PopupWrapper from '@/components/VueFileManagerComponents/Others/Popup/PopupWrapper'
import PopupActions from '@/components/VueFileManagerComponents/Others/Popup/PopupActions'
import PopupContent from '@/components/VueFileManagerComponents/Others/Popup/PopupContent'
import PopupHeader from '@/components/VueFileManagerComponents/Others/Popup/PopupHeader'
import ThumbnailItem from '@/components/VueFileManagerComponents/Others/ThumbnailItem'
import ButtonBase from '@/components/VueFileManagerComponents/FilesView/ButtonBase'
import Spinner from '@/components/VueFileManagerComponents/FilesView/Spinner'
import TreeMenu from '@/components/VueFileManagerComponents/Others/TreeMenu'
import {mapGetters} from 'vuex'
import {events} from '@/bus'
export default {
name: 'MoveItem',
components: {
ThumbnailItem,
PopupWrapper,
PopupActions,
PopupContent,
PopupHeader,
ButtonBase,
TreeMenu,
Spinner,
},
computed: {
...mapGetters(['app']),
},
data() {
return {
selectedFolder: undefined,
pickedItem: undefined,
isLoadingTree: true,
}
},
methods: {
moveItem() {
// Prevent empty submit
if (! this.selectedFolder) return
// Move item
this.$store.dispatch('moveItem', [this.pickedItem, this.selectedFolder])
// Close popup
events.$emit('popup:close')
},
},
mounted() {
// Select folder in tree
events.$on('pick-folder', folder => {
if (folder.unique_id === this.pickedItem.unique_id) {
this.selectedFolder = undefined
} else {
this.selectedFolder = folder
}
})
// Show Move item popup
events.$on('popup:open', args => {
if (args.name !== 'move') return
// Show tree spinner
this.isLoadingTree = true
// Get folder tree and hide spinner
this.$store.dispatch('getFolderTree').then(() => {
this.isLoadingTree = false
})
// Store picked item
this.pickedItem = args.item
})
// Close popup
events.$on('popup:close', () => {
// Clear selected folder
setTimeout(() => {
this.selectedFolder = undefined
}, 150)
})
}
}
</script>
<style scoped lang="scss">
@import "@assets/app.scss";
.item-thumbnail {
margin-bottom: 20px;
}
</style>

View File

@@ -0,0 +1,40 @@
<template>
<div class="actions">
<slot></slot>
</div>
</template>
<script>
export default {
name: 'PopupActions',
}
</script>
<style lang="scss" scoped>
@import "@assets/app.scss";
.actions {
padding: 20px;
margin: 0 -10px;
display: flex;
.popup-button {
width: 100%;
margin: 0 10px;
}
}
.small {
.actions {
padding: 15px;
position: absolute;
bottom: 0;
left: 0;
right: 0;
}
}
@media (prefers-color-scheme: dark) {
}
</style>

View File

@@ -0,0 +1,62 @@
<template>
<div class="popup-content" :class="type">
<slot></slot>
</div>
</template>
<script>
export default {
name: 'PopupContent',
props: [
'type'
]
}
</script>
<style lang="scss" scoped>
@import "@assets/app.scss";
.popup-content {
&.height-limited {
height: 400px;
overflow-y: auto;
}
}
.small {
.popup-content {
top: 57px;
bottom: 72px;
position: absolute;
left: 0;
right: 0;
height: initial;
}
}
@media (prefers-color-scheme: dark) {
}
@keyframes popup-in {
0% {
opacity: 0;
transform: scale(0.7);
}
100% {
opacity: 1;
transform: scale(1);
}
}
@keyframes popup-slide-in {
0% {
transform: translateY(100%);
}
100% {
transform: translateY(0);
}
}
</style>

View File

@@ -0,0 +1,52 @@
<template>
<div class="popup-header">
<h1 class="title">{{ title }}</h1>
</div>
</template>
<script>
export default {
name: 'PopupHeader',
props: [
'title'
]
}
</script>
<style lang="scss" scoped>
@import "@assets/app.scss";
.popup-header {
padding: 20px;
.title {
@include font-size(18);
font-weight: 700;
color: $text;
}
.message {
@include font-size(16);
color: #8b8f9a;
margin-top: 5px;
}
}
.small {
.popup-header {
padding: 15px;
}
}
@media (prefers-color-scheme: dark) {
.popup-header {
.title {
color: $dark_mode_text_primary;
}
.message {
color: $dark_mode_text_secondary;
}
}
}
</style>

View File

@@ -0,0 +1,142 @@
<template>
<transition name="popup">
<div class="popup" @click.self="closePopup" v-show="isVisibleWrapper">
<div class="popup-wrapper">
<slot></slot>
</div>
</div>
</transition>
</template>
<script>
import {events} from '@/bus'
export default {
name: 'PopupWrapper',
props: [
'name'
],
data() {
return {
isVisibleWrapper: false,
}
},
methods: {
closePopup() {
events.$emit('popup:close')
}
},
created() {
// Open called popup
events.$on('popup:open', ({name}) => {
if (this.name === name) this.isVisibleWrapper = true
})
// Close popup
events.$on('popup:close', () => {
// Close popup
this.isVisibleWrapper = false
})
}
}
</script>
<style lang="scss" scoped>
@import "@assets/app.scss";
.popup {
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
z-index: 20;
overflow-y: auto;
display: grid;
padding: 40px;
}
.popup-wrapper {
box-shadow: $light_mode_popup_shadow;
border-radius: 8px;
background: white;
margin: auto;
width: 480px;
z-index: 12;
}
// Desktop, tablet
.medium, .large {
// Animations
.popup-enter-active {
animation: popup-in 0.35s 0.15s ease both;
}
.popup-leave-active {
animation: popup-in 0.15s ease reverse;
}
}
.small {
.popup {
overflow: hidden;
}
.popup-wrapper {
position: absolute;
top: 0;
bottom: 0;
right: 0;
left: 0;
transform: translateY(0) scale(1);
box-shadow: none;
width: 100%;
border-radius: 0px;
}
// Animations
.popup-enter-active {
animation: popup-slide-in 0.35s 0.15s ease both;
}
.popup-leave-active {
animation: popup-slide-in 0.15s ease reverse;
}
}
@keyframes popup-in {
0% {
opacity: 0;
transform: scale(0.7);
}
100% {
opacity: 1;
transform: scale(1);
}
}
@keyframes popup-slide-in {
0% {
transform: translateY(100%);
}
100% {
transform: translateY(0);
}
}
@media (prefers-color-scheme: dark) {
.popup-wrapper {
background: $dark_mode_background;
box-shadow: $dark_mode_popup_shadow;
}
}
@media (prefers-color-scheme: dark) and (max-width: 690px) {
.popup-wrapper {
background: $dark_mode_background;
}
}
</style>

View File

@@ -1,299 +0,0 @@
<template>
<transition name="popup">
<div class="popup" @click.self="closePopup" v-if="isVisibleWrapper">
<div class="popup-wrapper">
<!--Title-->
<div class="popup-header">
<h1 class="title">{{ $t('popup_move_item.title') }}</h1>
<!--<p v-if="message" class="message">{{ message }}</p>-->
</div>
<!--Content-->
<div class="popup-content" v-if="app && pickedItem">
<Spinner v-if="isLoadingTree"/>
<div v-if="! isLoadingTree">
<ThumbnailItem class="item-thumbnail" :file="pickedItem"/>
<TreeMenu :depth="1" :nodes="items" v-for="items in app.folders" :key="items.unique_id"/>
</div>
</div>
<!--Actions-->
<div class="actions">
<ButtonBase
class="popup-button"
@click.native="closePopup"
button-style="secondary"
>{{ $t('popup_move_item.cancel') }}
</ButtonBase>
<ButtonBase
class="popup-button"
@click.native="moveItem"
:button-style="selectedFolder ? 'theme' : 'secondary'"
>{{ $t('popup_move_item.submit') }}
</ButtonBase>
</div>
</div>
</div>
</transition>
</template>
<script>
import ThumbnailItem from '@/components/VueFileManagerComponents/Others/ThumbnailItem'
import ButtonBase from '@/components/VueFileManagerComponents/FilesView/ButtonBase'
import Spinner from '@/components/VueFileManagerComponents/FilesView/Spinner'
import TreeMenu from '@/components/VueFileManagerComponents/Others/TreeMenu'
import {mapGetters} from 'vuex'
import {events} from '@/bus'
export default {
name: 'PopupMoveItem',
components: {
ThumbnailItem,
ButtonBase,
TreeMenu,
Spinner,
},
computed: {
...mapGetters(['app']),
},
data() {
return {
isVisibleWrapper: false,
selectedFolder: undefined,
pickedItem: undefined,
isLoadingTree: true,
}
},
methods: {
moveItem() {
// Prevent empty submit
if (! this.selectedFolder) return
// Move item
this.$store.dispatch('moveItem', [this.pickedItem, this.selectedFolder])
// Close popup
events.$emit('popup:close')
},
closePopup() {
events.$emit('popup:close')
}
},
mounted() {
// Select folder in tree
events.$on('pick-folder', folder => {
if (folder.unique_id == this.pickedItem.unique_id) {
this.selectedFolder = undefined
} else {
this.selectedFolder = folder
}
})
// Show popup
events.$on('popup:move-item', item => {
// Show tree spinner
this.isLoadingTree = true
// Get folder tree and hide spinner
this.$store.dispatch('getFolderTree').then(() => {
this.isLoadingTree = false
}).catch(() => {
this.isLoadingTree = false
})
// Make popup visible
this.isVisibleWrapper = true
// Store picked item
this.pickedItem = item
})
// Close popup
events.$on('popup:close', () => {
// Hide popup wrapper
this.isVisibleWrapper = false
// Clear selected folder
this.selectedFolder = undefined
})
}
}
</script>
<style scoped lang="scss">
@import "@assets/app.scss";
.popup {
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
z-index: 20;
overflow-y: auto;
display: grid;
padding: 40px;
}
.popup-wrapper {
box-shadow: $light_mode_popup_shadow;
border-radius: 8px;
background: white;
margin: auto;
width: 480px;
z-index: 12;
}
.popup-header {
padding: 20px;
.title {
@include font-size(18);
font-weight: 700;
color: $text;
}
.message {
@include font-size(16);
color: #8b8f9a;
margin-top: 5px;
}
}
.popup-content {
height: 400px;
overflow-y: auto;
}
.item-thumbnail {
margin-bottom: 20px;
}
.actions {
padding: 20px;
margin: 0 -10px;
display: flex;
.popup-button {
width: 100%;
margin: 0 10px;
}
}
// Desktop, tablet
.medium, .large {
// Animations
.popup-enter-active {
animation: popup-in 0.35s 0.15s ease both;
}
.popup-leave-active {
animation: popup-in 0.15s ease reverse;
}
}
// Mobile styles
.small {
.popup {
overflow: hidden;
}
.popup-wrapper {
position: absolute;
top: 0;
bottom: 0;
right: 0;
left: 0;
transform: translateY(0) scale(1);
box-shadow: none;
width: 100%;
border-radius: 0px;
.popup-content {
top: 57px;
bottom: 72px;
position: absolute;
left: 0;
right: 0;
height: initial;
}
.actions {
position: absolute;
bottom: 0;
left: 0;
right: 0;
}
}
.popup-header {
padding: 15px;
}
.actions {
padding: 15px;
}
// Animations
.popup-enter-active {
animation: popup-slide-in 0.35s 0.15s ease both;
}
.popup-leave-active {
animation: popup-slide-in 0.15s ease reverse;
}
}
@keyframes popup-in {
0% {
opacity: 0;
transform: scale(0.7);
}
100% {
opacity: 1;
transform: scale(1);
}
}
@keyframes popup-slide-in {
0% {
transform: translateY(100%);
}
100% {
transform: translateY(0);
}
}
// Dark mode
@media (prefers-color-scheme: dark) {
.popup-wrapper {
background: $dark_mode_background;
box-shadow: $dark_mode_popup_shadow;
}
.popup-header {
.title {
color: $dark_mode_text_primary;
}
.message {
color: $dark_mode_text_secondary;
}
}
}
@media (prefers-color-scheme: dark) and (max-width: 690px) {
.popup-wrapper {
background: $dark_mode_background;
}
}
</style>

View File

@@ -0,0 +1,216 @@
<template>
<PopupWrapper name="share-create">
<!--Title-->
<PopupHeader :title="'Share Your ' + itemTypeTitle" />
<!--Content-->
<PopupContent>
<!--Item Thumbnail-->
<ThumbnailItem class="item-thumbnail" :item="pickedItem" info="metadata"/>
<!--Form to set sharing-->
<ValidationObserver v-if="! isGeneratedShared" 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 }">
<label class="input-label">Permission:</label>
<SelectInput v-model="shareOptions.permission" :options="permissionOptions" :isError="errors[0]"/>
<span class="error-message" v-if="errors[0]">{{ errors[0] }}</span>
</ValidationProvider>
<!--Password Switch-->
<div class="input-wrapper">
<div class="inline-wrapper">
<label class="input-label">Password Protected:</label>
<SwitchInput v-model="shareOptions.isPassword" class="switch" :state="0"/>
</div>
</div>
<!--Set password-->
<ValidationProvider v-if="shareOptions.isPassword" tag="div" mode="passive" class="input-wrapper password" name="Password" rules="required" v-slot="{ errors }">
<input v-model="shareOptions.password" :class="{'is-error': errors[0]}" type="text" placeholder="Type your password">
<span class="error-message" v-if="errors[0]">{{ errors[0] }}</span>
</ValidationProvider>
</ValidationObserver>
<!--Copy generated link-->
<div v-if="isGeneratedShared" class="form-wrapper">
<div class="input-wrapper">
<label class="input-label">Share url:</label>
<CopyInput size="small" :value="shareLink" />
</div>
</div>
</PopupContent>
<!--Actions-->
<PopupActions>
<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>
</PopupActions>
</PopupWrapper>
</template>
<script>
import PopupWrapper from '@/components/VueFileManagerComponents/Others/Popup/PopupWrapper'
import PopupActions from '@/components/VueFileManagerComponents/Others/Popup/PopupActions'
import PopupContent from '@/components/VueFileManagerComponents/Others/Popup/PopupContent'
import {ValidationProvider, ValidationObserver} from 'vee-validate/dist/vee-validate.full'
import PopupHeader from '@/components/VueFileManagerComponents/Others/Popup/PopupHeader'
import SwitchInput from '@/components/VueFileManagerComponents/Others/Forms/SwitchInput'
import SelectInput from '@/components/VueFileManagerComponents/Others/Forms/SelectInput'
import ThumbnailItem from '@/components/VueFileManagerComponents/Others/ThumbnailItem'
import CopyInput from '@/components/VueFileManagerComponents/Others/Forms/CopyInput'
import ButtonBase from '@/components/VueFileManagerComponents/FilesView/ButtonBase'
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,
ThumbnailItem,
PopupWrapper,
PopupActions,
PopupContent,
PopupHeader,
SelectInput,
SwitchInput,
ButtonBase,
CopyInput,
required,
},
computed: {
...mapGetters(['app']),
itemTypeTitle() {
return this.pickedItem && this.pickedItem.type === 'folder' ? 'Folder' : 'File'
},
isFolder() {
return this.pickedItem && this.pickedItem.type === 'folder'
},
submitButtonText() {
return this.isGeneratedShared ? 'Awesome, Im done!' : 'Generate Link'
}
},
data() {
return {
shareOptions: {
isPassword: false,
password: undefined,
permission: undefined,
},
permissionOptions: [
{
label: 'Can edit and upload files',
value: 'editor',
icon: 'user-edit',
},
{
label: 'Can only view and download',
value: 'visitor',
icon: 'user',
},
],
pickedItem: undefined,
shareLink: undefined,
isGeneratedShared: false,
isLoading: false,
}
},
methods: {
async submitShareOptions() {
// If shared was generated, then close popup
if (this.isGeneratedShared) {
events.$emit('popup:close')
return;
}
// 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/generate', this.shareOptions)
.then(response => {
// End loading
this.isLoading = false
this.shareLink = response.data
this.isGeneratedShared = true
})
.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
})
// Close popup
events.$on('popup:close', () => {
// Restore data
setTimeout(() => {
this.isGeneratedShared = false
this.shareLink = undefined
this.shareOptions = {
permission: undefined,
password: undefined,
isPassword: false,
}
}, 150)
})
}
}
</script>
<style scoped lang="scss">
@import "@assets/app.scss";
@import "@assets/vue-file-manager/_inapp-forms.scss";
.input-wrapper {
&.password {
margin-top: -10px;
}
}
.item-thumbnail {
margin-bottom: 20px;
}
</style>

View File

@@ -0,0 +1,237 @@
<template>
<PopupWrapper name="share-edit">
<!--Title-->
<PopupHeader title="Update sharing options" />
<!--Content-->
<PopupContent>
<!--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">
<!--Share link-->
<div class="input-wrapper">
<label class="input-label">Share url:</label>
<CopyInput size="small" :value="shareLink" />
</div>
<!--Permision Select-->
<ValidationProvider v-if="isFolder" tag="div" mode="passive" class="input-wrapper" name="Permission" rules="required" v-slot="{ errors }">
<label class="input-label">Permission:</label>
<SelectInput v-model="shareOptions.permission" :options="permissionOptions" default="visitor" :isError="errors[0]"/>
<span class="error-message" v-if="errors[0]">{{ errors[0] }}</span>
</ValidationProvider>
<!--Password Switch-->
<div class="input-wrapper">
<div class="inline-wrapper">
<label class="input-label">Password Protected:</label>
<SwitchInput v-model="shareOptions.isPassword" class="switch" :state="0"/>
</div>
<ActionButton @click.native="changePassword" v-if="isPasswordChangeButton" icon="pencil-alt">Change Password</ActionButton>
</div>
<!--Set password-->
<ValidationProvider v-if="isPasswordInput || shareOptions.isPassword" tag="div" mode="passive" class="input-wrapper password" name="Password" rules="required" v-slot="{ errors }">
<input v-model="shareOptions.password" :class="{'is-error': errors[0]}" type="text" placeholder="Type your password">
<span class="error-message" v-if="errors[0]">{{ errors[0] }}</span>
</ValidationProvider>
</ValidationObserver>
</PopupContent>
<!--Actions-->
<PopupActions>
<ButtonBase
class="popup-button"
@click.native="destroySharing"
:button-style="destroyButtonStyle"
>{{ destroyButtonText }}
</ButtonBase>
<ButtonBase
class="popup-button"
@click.native="submitShareOptions"
button-style="theme"
:loading="isLoading"
:disabled="isLoading"
>Save Changes
</ButtonBase>
</PopupActions>
</PopupWrapper>
</template>
<script>
import PopupWrapper from '@/components/VueFileManagerComponents/Others/Popup/PopupWrapper'
import PopupActions from '@/components/VueFileManagerComponents/Others/Popup/PopupActions'
import PopupContent from '@/components/VueFileManagerComponents/Others/Popup/PopupContent'
import {ValidationProvider, ValidationObserver} from 'vee-validate/dist/vee-validate.full'
import PopupHeader from '@/components/VueFileManagerComponents/Others/Popup/PopupHeader'
import SwitchInput from '@/components/VueFileManagerComponents/Others/Forms/SwitchInput'
import SelectInput from '@/components/VueFileManagerComponents/Others/Forms/SelectInput'
import ThumbnailItem from '@/components/VueFileManagerComponents/Others/ThumbnailItem'
import ActionButton from '@/components/VueFileManagerComponents/Others/ActionButton'
import CopyInput from '@/components/VueFileManagerComponents/Others/Forms/CopyInput'
import ButtonBase from '@/components/VueFileManagerComponents/FilesView/ButtonBase'
import {required} from 'vee-validate/dist/rules'
import {mapGetters} from 'vuex'
import {events} from '@/bus'
import axios from 'axios'
export default {
name: 'ShareEdit',
components: {
ValidationProvider,
ValidationObserver,
ThumbnailItem,
ActionButton,
PopupWrapper,
PopupActions,
PopupContent,
PopupHeader,
SelectInput,
SwitchInput,
ButtonBase,
CopyInput,
required,
},
computed: {
...mapGetters(['app']),
isFolder() {
return this.pickedItem && this.pickedItem.type === 'folder'
},
destroyButtonText() {
return this.isConfirmedDestroy ? 'Confirm' : 'Stop Sharing'
},
destroyButtonStyle() {
return this.isConfirmedDestroy ? 'danger-solid' : 'secondary'
}
},
data() {
return {
shareOptions: {
isPassword: false,
password: undefined,
permission: undefined,
},
permissionOptions: [
{
label: 'Can edit and upload files',
value: 'editor',
icon: 'user-edit',
},
{
label: 'Can only view and download',
value: 'visitor',
icon: 'user',
},
],
pickedItem: undefined,
isLoading: false,
isPasswordInput: false,
isPasswordChangeButton: true,
isConfirmedDestroy: false,
shareLink: 'http://192.168.1.131:8000/shared?token=3ZlQLIoCR8izoc0PemekHNq3UIMj6OrC0aQ2zowclfjFYa8P6go8fMKPnXTJomvz'
}
},
methods: {
changePassword() {
this.isPasswordInput = true
this.isPasswordChangeButton = false
},
destroySharing() {
if (! this.isConfirmedDestroy) {
this.isConfirmedDestroy = true
return
} else {
this.$closePopup()
}
},
async submitShareOptions() {
// If shared was generated, then close popup
if (this.isGeneratedShared) {
events.$emit('popup:close')
return;
}
// 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/generate', this.shareOptions)
.then(response => {
// End loading
this.isLoading = false
this.shareLink = response.data
this.isGeneratedShared = true
})
.catch(error => {
// todo: catch errors
// End loading
this.isLoading = false
})
},
},
mounted() {
// Show popup
events.$on('popup:open', args => {
if (args.name !== 'share-edit') return
// Store picked item
this.pickedItem = args.item
})
// Close popup
events.$on('popup:close', () => {
// Restore data
setTimeout(() => {
this.isConfirmedDestroy = false
this.isPasswordInput = false
this.isPasswordChangeButton = true
//this.shareLink = undefined
this.shareOptions = {
permission: undefined,
password: undefined,
isPassword: false,
}
}, 150)
})
}
}
</script>
<style scoped lang="scss">
@import "@assets/app.scss";
@import "@assets/vue-file-manager/_inapp-forms.scss";
.input-wrapper {
&.password {
margin-top: -10px;
}
}
.item-thumbnail {
margin-bottom: 20px;
}
</style>

View File

@@ -24,7 +24,7 @@
@media (prefers-color-scheme: dark) {
.text-label {
color: rgba($dark_mode_text_secondary, .4);
color: $theme;
}
}
</style>

View File

@@ -1,17 +1,17 @@
<template>
<div class="file-item">
<div class="file-item" v-if="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>
<span v-if="isFile" class="file-icon-text">{{ item.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"/>
<img v-if="isImage" class="image" :src="item.thumbnail" :alt="item.name"/>
<!--Else show only folder icon-->
<FontAwesomeIcon v-if="isFolder" class="folder-icon" icon="folder"/>
@@ -21,10 +21,20 @@
<div class="item-name">
<!--Name-->
<span class="name">{{ file.name }}</span>
<span class="name">{{ item.name }}</span>
<div v-if="info === 'location'">
<span class="subtitle">{{ $t('item_thumbnail.original_location') }}: {{ currentFolder.name }}</span>
</div>
<div v-if="info === 'metadata'">
<span v-if="! isFolder" class="item-size">{{ item.filesize }}, {{ item.created_at }}</span>
<span v-if="isFolder" class="item-length">
{{ item.items == 0 ? $t('folder.empty') : $tc('folder.item_counts', item.items) }}, {{ item.created_at }}
</span>
</div>
<!--Other attributes-->
<span class="subtitle">{{ $t('item_thumbnail.original_location') }}: {{ currentFolder.name }}</span>
</div>
</div>
</template>
@@ -34,18 +44,18 @@
export default {
name: 'ThumbnailItem',
props: ['file'],
props: ['item', 'info'],
computed: {
...mapGetters(['currentFolder']),
isFolder() {
return this.file.type === 'folder'
return this.item.type === 'folder'
},
isFile() {
return this.file.type !== 'folder' && this.file.type !== 'image'
return this.item.type !== 'folder' && this.item.type !== 'image'
},
isImage() {
return this.file.type === 'image'
}
return this.item.type === 'image'
},
},
}
</script>
@@ -66,6 +76,14 @@
text-overflow: ellipsis;
white-space: nowrap;
.item-size,
.item-length {
@include font-size(12);
font-weight: 400;
color: $text-muted;
display: block;
}
.subtitle {
@include font-size(11);
font-weight: 400;
@@ -141,7 +159,7 @@
.small {
.file-item {
padding: 0 15px;
margin-bottom: 10px;
margin-bottom: 15px;
}
}

View File

@@ -26,7 +26,7 @@
events.$on('popup:close', () => this.isVisibleVignette = false)
// Show vignette
events.$on('popup:move-item', () => this.isVisibleVignette = true)
events.$on('popup:open', () => this.isVisibleVignette = true)
events.$on('alert:open', () => this.isVisibleVignette = true)
events.$on('success:open', () => this.isVisibleVignette = true)
}

View File

@@ -16,10 +16,10 @@
<FontAwesomeIcon class="icon" icon="hdd"/>
<span class="label">{{ $t('locations.home') }}</span>
</li>
<!--<li class="menu-list-item">
<li class="menu-list-item">
<FontAwesomeIcon class="icon" icon="share"/>
<span class="label">Shared</span>
</li>-->
</li>
<li class="menu-list-item" @click="getTrash">
<FontAwesomeIcon class="icon" icon="trash-alt"/>
<span class="label">{{ $t('locations.trash') }}</span>

View File

@@ -11,7 +11,7 @@
</div>
<transition name="user-menu">
<div class="user-menu" v-if="isOpenedMenu">
<ul class="menu-options" id="menu-options-list" @click="closeMenu">
<ul class="menu-options" @click="closeMenu">
<li class="menu-option">
<router-link :to="{name: 'Profile'}">
{{ $t('context_menu.profile_settings') }}
@@ -176,7 +176,8 @@
@media (prefers-color-scheme: dark) {
.user-headline {
background: $dark_mode_background;
background: $dark_mode_foreground;
padding: 0;
&:hover {
background: transparent;