added prettier

This commit is contained in:
Čarodej
2022-02-01 12:21:38 +01:00
parent 5ae875233b
commit b38b532cbe
284 changed files with 25410 additions and 25338 deletions

View File

@@ -9,43 +9,44 @@
</template>
<script>
import { Edit2Icon, XIcon } from 'vue-feather-icons'
import { Edit2Icon, XIcon } from 'vue-feather-icons'
export default {
name: 'ActionButton',
props: ['icon'],
components: {
Edit2Icon,
XIcon,
}
}
export default {
name: 'ActionButton',
props: ['icon'],
components: {
Edit2Icon,
XIcon,
},
}
</script>
<style lang="scss" scoped>
@import '../../../sass/vuefilemanager/variables';
@import '../../../sass/vuefilemanager/mixins';
@import '../../../sass/vuefilemanager/variables';
@import '../../../sass/vuefilemanager/mixins';
.action-button {
cursor: pointer;
.action-button {
cursor: pointer;
.label {
@include font-size(12);
font-weight: 600;
}
.icon {
@include font-size(10);
vertical-align: middle;
display: inline-block;
margin-right: 2px;
path, circle, line {
color: inherit;
}
}
.label {
@include font-size(12);
font-weight: 600;
}
.dark {
.icon {
@include font-size(10);
vertical-align: middle;
display: inline-block;
margin-right: 2px;
path,
circle,
line {
color: inherit;
}
}
}
.dark {
}
</style>

View File

@@ -1,41 +1,38 @@
<template>
<b class="color-label capitalize inline-block text-xs font-bold rounded-lg py-1 px-2" :class="color">
<b class="color-label inline-block rounded-lg py-1 px-2 text-xs font-bold capitalize" :class="color">
<slot></slot>
</b>
</template>
<script>
export default {
name: 'ColorLabel',
props: [
'color'
],
}
export default {
name: 'ColorLabel',
props: ['color'],
}
</script>
<style lang="scss" scoped>
@import '../../../sass/vuefilemanager/variables';
@import '../../../sass/vuefilemanager/variables';
.color-label {
&.purple {
color: $purple;
background: rgba($purple, 0.1);
}
&.yellow {
color: $yellow;
background: rgba($yellow, 0.1);
}
&.green {
color: $theme;
background: rgba($theme, 0.1);
}
&.red {
color: $danger;
background: rgba($danger, 0.1);
}
.color-label {
&.purple {
color: $purple;
background: rgba($purple, 0.1);
}
&.yellow {
color: $yellow;
background: rgba($yellow, 0.1);
}
&.green {
color: $theme;
background: rgba($theme, 0.1);
}
&.red {
color: $danger;
background: rgba($danger, 0.1);
}
}
</style>

View File

@@ -1,10 +1,10 @@
<template>
<div class="color-pick-wrapper">
<div class="color-pick-wrapper">
<label class="main-label">{{ $t('popup_rename.color_pick_label') }}:</label>
<ul class="color-wrapper">
<li v-for="(color, i) in colors" :key="i" @click="setColor( color )" class="single-color">
<check-icon v-if="color === selectedColor" class="color-icon" size="22"/>
<span :style="{background:color}" class="color-box"></span>
<li v-for="(color, i) in colors" :key="i" @click="setColor(color)" class="single-color">
<check-icon v-if="color === selectedColor" class="color-icon" size="22" />
<span :style="{ background: color }" class="color-box"></span>
</li>
</ul>
</div>
@@ -16,14 +16,12 @@ import { mapGetters } from 'vuex'
export default {
name: 'ColorPicker',
props: [ 'pickedColor' ],
props: ['pickedColor'],
components: { CheckIcon },
computed: {
...mapGetters([
'config'
])
...mapGetters(['config']),
},
data () {
data() {
return {
selectedColor: this.pickedColor,
colors: [
@@ -48,20 +46,19 @@ export default {
'#FE7D6F',
'#4c4c4c',
'#06070B',
]
],
}
},
methods: {
setColor (value) {
setColor(value) {
this.selectedColor = value
this.$emit('input', value)
}
},
},
created() {
this.colors.push(this.config.app_color)
}
},
}
</script>
@@ -132,5 +129,4 @@ export default {
}
}
}
</style>

View File

@@ -1,109 +1,102 @@
<template>
<PopupWrapper name="confirm-password">
<PopupHeader :title="$t('Confirm Password')" icon="edit" />
<PopupContent>
<ValidationObserver @submit.prevent="confirmPassword" ref="passwordForm" v-slot="{ invalid }" tag="form">
<ValidationObserver @submit.prevent="confirmPassword" ref="passwordForm" v-slot="{ invalid }" tag="form">
<ValidationProvider tag="div" mode="passive" name="Password" rules="required" v-slot="{ errors }">
<AppInputText :title="$t('popup_2fa.input_label')" :error="errors[0]" :is-last="true">
<input v-model="password" :class="{'border-red': errors[0]}" type="password" ref="input" class="focus-border-theme input-dark" :placeholder="$t('page_sign_in.placeholder_password')">
</AppInputText>
<AppInputText :title="$t('popup_2fa.input_label')" :error="errors[0]" :is-last="true">
<input
v-model="password"
:class="{ 'border-red': errors[0] }"
type="password"
ref="input"
class="focus-border-theme input-dark"
:placeholder="$t('page_sign_in.placeholder_password')"
/>
</AppInputText>
</ValidationProvider>
</ValidationObserver>
</PopupContent>
<PopupActions>
<ButtonBase
class="w-full"
@click.native="$closePopup()"
button-style="secondary"
>
<ButtonBase class="w-full" @click.native="$closePopup()" button-style="secondary">
{{ $t('global.cancel') }}
</ButtonBase>
<ButtonBase
class="w-full"
@click.native="confirmPassword"
button-style="theme"
:loading="isLoading"
:disabled="isLoading"
>
{{ $t('popup_2fa.confirm_button') }}
<ButtonBase class="w-full" @click.native="confirmPassword" button-style="theme" :loading="isLoading" :disabled="isLoading">
{{ $t('popup_2fa.confirm_button') }}
</ButtonBase>
</PopupActions>
</PopupWrapper>
</template>
<script>
import {ValidationProvider, ValidationObserver} from 'vee-validate/dist/vee-validate.full'
import PopupWrapper from "./Popup/PopupWrapper";
import { ValidationProvider, ValidationObserver } from 'vee-validate/dist/vee-validate.full'
import PopupWrapper from './Popup/PopupWrapper'
import PopupActions from './Popup/PopupActions'
import PopupContent from './Popup/PopupContent'
import PopupHeader from './Popup/PopupHeader'
import ButtonBase from "../FilesView/ButtonBase";
import AppInputText from "../Admin/AppInputText"
import {required} from 'vee-validate/dist/rules'
import {events} from '../../bus'
import {mapGetters} from 'vuex'
import ButtonBase from '../FilesView/ButtonBase'
import AppInputText from '../Admin/AppInputText'
import { required } from 'vee-validate/dist/rules'
import { events } from '../../bus'
import { mapGetters } from 'vuex'
import axios from 'axios'
export default {
name: "ConfirmPassword",
components: {
ValidationProvider,
ValidationObserver,
AppInputText,
PopupWrapper,
PopupActions,
PopupContent,
PopupHeader,
ButtonBase,
required,
},
computed: {
...mapGetters([
'user'
]),
},
data() {
return {
isLoading: false,
password: '',
args: undefined,
}
},
methods: {
confirmPassword() {
this.isLoading = true
name: 'ConfirmPassword',
components: {
ValidationProvider,
ValidationObserver,
AppInputText,
PopupWrapper,
PopupActions,
PopupContent,
PopupHeader,
ButtonBase,
required,
},
computed: {
...mapGetters(['user']),
},
data() {
return {
isLoading: false,
password: '',
args: undefined,
}
},
methods: {
confirmPassword() {
this.isLoading = true
axios
.post('/user/confirm-password', {
password: this.password
})
.then(() => {
events.$emit('password:confirmed', this.args)
})
.catch(error => {
if (error.response.status === 422) {
this.$refs.passwordForm.setErrors({
'Password': this.$t('validation_errors.incorrect_password')
});
}
})
.finally(() => {
this.isLoading = false
this.password = undefined
})
},
},
created() {
// Show popup
events.$on('popup:open', args => {
axios
.post('/user/confirm-password', {
password: this.password,
})
.then(() => {
events.$emit('password:confirmed', this.args)
})
.catch((error) => {
if (error.response.status === 422) {
this.$refs.passwordForm.setErrors({
Password: this.$t('validation_errors.incorrect_password'),
})
}
})
.finally(() => {
this.isLoading = false
this.password = undefined
})
},
},
created() {
// Show popup
events.$on('popup:open', (args) => {
if (args.name !== 'confirm-password') return
if (args.name !== 'confirm-password') return
this.args = args
})
}
this.args = args
})
},
}
</script>

View File

@@ -1,44 +1,45 @@
<template>
<div v-if="isVisibleDisclaimer" class="fixed bottom-0 sm:left-16 left-0 sm:right-auto right-0 sm:p-3 sm:w-56 w-full p-4 shadow-xl rounded-tl-xl rounded-tr-lg dark:bg-dark-foreground bg-white z-20">
<span @click="closeDisclaimer" class="absolute -right-1 -top-1 p-3 cursor-pointer">
<div
v-if="isVisibleDisclaimer"
class="fixed bottom-0 left-0 right-0 z-20 w-full rounded-tl-xl rounded-tr-lg bg-white p-4 shadow-xl dark:bg-dark-foreground sm:left-16 sm:right-auto sm:w-56 sm:p-3"
>
<span @click="closeDisclaimer" class="absolute -right-1 -top-1 cursor-pointer p-3">
<x-icon size="10" />
</span>
<i18n path="cookie_disclaimer.description" tag="p" class="text-xs">
<router-link :to="{name: 'DynamicPage', params: {slug: 'cookie-policy'}}" class="text-theme text-xs">
{{ $t('cookie_disclaimer.button') }}
</router-link>
<router-link :to="{ name: 'DynamicPage', params: { slug: 'cookie-policy' } }" class="text-theme text-xs">
{{ $t('cookie_disclaimer.button') }}
</router-link>
</i18n>
</div>
</template>
<script>
import {XIcon} from 'vue-feather-icons'
import { mapGetters } from 'vuex'
import { XIcon } from 'vue-feather-icons'
import { mapGetters } from 'vuex'
export default {
name: 'CookieDisclaimer',
components: {
XIcon,
},
computed: {
...mapGetters([
'config'
]),
},
data() {
return {
isVisibleDisclaimer: false
}
},
methods: {
closeDisclaimer() {
localStorage.setItem('isHiddenDisclaimer', 'true')
this.isVisibleDisclaimer = false
}
},
created() {
this.isVisibleDisclaimer = !localStorage.getItem('isHiddenDisclaimer');
export default {
name: 'CookieDisclaimer',
components: {
XIcon,
},
computed: {
...mapGetters(['config']),
},
data() {
return {
isVisibleDisclaimer: false,
}
}
},
methods: {
closeDisclaimer() {
localStorage.setItem('isHiddenDisclaimer', 'true')
this.isVisibleDisclaimer = false
},
},
created() {
this.isVisibleDisclaimer = !localStorage.getItem('isHiddenDisclaimer')
},
}
</script>

View File

@@ -1,114 +1,105 @@
<template>
<PopupWrapper name="create-folder">
<!--Title-->
<PopupHeader :title="$t('popup_create_folder.title')" icon="edit" />
<!--Content-->
<PopupContent>
<!--Form to set sharing-->
<ValidationObserver @submit.prevent="createFolder" ref="createForm" v-slot="{ invalid }" tag="form">
<!--Set folder name-->
<ValidationProvider tag="div" mode="passive" name="Title" rules="required" v-slot="{ errors }">
<AppInputText :title="$t('popup_create_folder.label')" :error="errors[0]">
<input v-model="name" :class="{'border-red': errors[0]}" type="text" ref="input" class="focus-border-theme input-dark" :placeholder="$t('popup_create_folder.placeholder')">
</AppInputText>
<AppInputText :title="$t('popup_create_folder.label')" :error="errors[0]">
<input
v-model="name"
:class="{ 'border-red': errors[0] }"
type="text"
ref="input"
class="focus-border-theme input-dark"
:placeholder="$t('popup_create_folder.placeholder')"
/>
</AppInputText>
</ValidationProvider>
<AppInputSwitch :title="$t('Emoji as an Icon')" :description="$t('Replace folder icon with an Emoji')" :is-last="! isEmoji">
<SwitchInput v-model="isEmoji" :state="isEmoji" />
</AppInputSwitch>
<AppInputSwitch :title="$t('Emoji as an Icon')" :description="$t('Replace folder icon with an Emoji')" :is-last="!isEmoji">
<SwitchInput v-model="isEmoji" :state="isEmoji" />
</AppInputSwitch>
<!--Set emoji-->
<EmojiPicker v-if="isEmoji" v-model="emoji" :default-emoji="emoji"/>
<!--Set emoji-->
<EmojiPicker v-if="isEmoji" v-model="emoji" :default-emoji="emoji" />
</ValidationObserver>
</PopupContent>
<!--Actions-->
<PopupActions>
<ButtonBase
class="w-full"
@click.native="$closePopup()"
button-style="secondary"
>{{ $t('popup_move_item.cancel') }}
</ButtonBase>
<ButtonBase
class="w-full"
@click.native="createFolder"
button-style="theme"
>{{ $t('popup_create_folder.title') }}
</ButtonBase>
<ButtonBase class="w-full" @click.native="$closePopup()" button-style="secondary">{{ $t('popup_move_item.cancel') }} </ButtonBase>
<ButtonBase class="w-full" @click.native="createFolder" button-style="theme">{{ $t('popup_create_folder.title') }} </ButtonBase>
</PopupActions>
</PopupWrapper>
</template>
<script>
import {ValidationProvider, ValidationObserver} from 'vee-validate/dist/vee-validate.full'
import PopupWrapper from "./Popup/PopupWrapper";
import PopupActions from "./Popup/PopupActions";
import PopupContent from "./Popup/PopupContent";
import PopupHeader from "./Popup/PopupHeader";
import ThumbnailItem from "./ThumbnailItem";
import ButtonBase from "../FilesView/ButtonBase";
import {required} from 'vee-validate/dist/rules'
import AppInputSwitch from "../Admin/AppInputSwitch"
import AppInputText from "../Admin/AppInputText"
import SwitchInput from "./Forms/SwitchInput"
import {events} from '../../bus'
import EmojiPicker from "./EmojiPicker"
import { ValidationProvider, ValidationObserver } from 'vee-validate/dist/vee-validate.full'
import PopupWrapper from './Popup/PopupWrapper'
import PopupActions from './Popup/PopupActions'
import PopupContent from './Popup/PopupContent'
import PopupHeader from './Popup/PopupHeader'
import ThumbnailItem from './ThumbnailItem'
import ButtonBase from '../FilesView/ButtonBase'
import { required } from 'vee-validate/dist/rules'
import AppInputSwitch from '../Admin/AppInputSwitch'
import AppInputText from '../Admin/AppInputText'
import SwitchInput from './Forms/SwitchInput'
import { events } from '../../bus'
import EmojiPicker from './EmojiPicker'
export default {
name: 'CreateFolderPopup',
components: {
AppInputSwitch,
SwitchInput,
EmojiPicker,
AppInputText,
ValidationProvider,
ValidationObserver,
ThumbnailItem,
PopupWrapper,
PopupActions,
PopupContent,
PopupHeader,
ButtonBase,
required,
},
data() {
return {
name: undefined,
isEmoji: false,
emoji: undefined,
}
},
methods: {
async createFolder() {
// Validate fields
const isValid = await this.$refs.createForm.validate();
if (!isValid) return;
await this.$store.dispatch('createFolder', {
name: this.name,
emoji: this.emoji
})
this.$closePopup()
this.name = undefined
this.isEmoji = false
this.emoji = undefined
},
},
mounted() {
events.$on('popup:open', ({name}) => {
if (name === 'create-folder' && ! this.$isMobile())
this.$nextTick(() => this.$refs.input.focus())
})
export default {
name: 'CreateFolderPopup',
components: {
AppInputSwitch,
SwitchInput,
EmojiPicker,
AppInputText,
ValidationProvider,
ValidationObserver,
ThumbnailItem,
PopupWrapper,
PopupActions,
PopupContent,
PopupHeader,
ButtonBase,
required,
},
data() {
return {
name: undefined,
isEmoji: false,
emoji: undefined,
}
}
},
methods: {
async createFolder() {
// Validate fields
const isValid = await this.$refs.createForm.validate()
if (!isValid) return
await this.$store.dispatch('createFolder', {
name: this.name,
emoji: this.emoji,
})
this.$closePopup()
this.name = undefined
this.isEmoji = false
this.emoji = undefined
},
},
mounted() {
events.$on('popup:open', ({ name }) => {
if (name === 'create-folder' && !this.$isMobile()) this.$nextTick(() => this.$refs.input.focus())
})
},
}
</script>

File diff suppressed because it is too large Load Diff

View File

@@ -1,54 +1,43 @@
<template>
<PopupWrapper name="create-personal-token">
<PopupHeader :title="$t('popup_personal_token.title')" icon="key" />
<PopupContent>
<ValidationObserver v-if="! token" @submit.prevent="createTokenForm" ref="createToken" v-slot="{ invalid }" tag="form">
<ValidationObserver v-if="!token" @submit.prevent="createTokenForm" ref="createToken" v-slot="{ invalid }" tag="form">
<ValidationProvider tag="div" mode="passive" name="Token Name" rules="required" v-slot="{ errors }">
<AppInputText :title="$t('popup_personal_token.label')" :error="errors[0]" :is-last="true">
<input v-model="name" :class="{'border-red': errors[0]}" type="text" ref="input" class="focus-border-theme input-dark" :placeholder="$t('popup_personal_token.plc')">
</AppInputText>
<AppInputText :title="$t('popup_personal_token.label')" :error="errors[0]" :is-last="true">
<input
v-model="name"
:class="{ 'border-red': errors[0] }"
type="text"
ref="input"
class="focus-border-theme input-dark"
:placeholder="$t('popup_personal_token.plc')"
/>
</AppInputText>
</ValidationProvider>
</ValidationObserver>
<AppInputText v-if="token" :title="$t('popup_personal_token.your_token')" :is-last="true">
<CopyInput size="small" :str="token['plainTextToken']" />
<InfoBox style="margin-bottom: 0; margin-top: 20px">
<p v-html="$t('popup_personal_token.copy_token')"></p>
</InfoBox>
</AppInputText>
<AppInputText v-if="token" :title="$t('popup_personal_token.your_token')" :is-last="true">
<CopyInput size="small" :str="token['plainTextToken']" />
<InfoBox style="margin-bottom: 0; margin-top: 20px">
<p v-html="$t('popup_personal_token.copy_token')"></p>
</InfoBox>
</AppInputText>
</PopupContent>
<PopupActions v-if="! token">
<ButtonBase
class="w-full"
@click.native="$closePopup()"
button-style="secondary"
>
<PopupActions v-if="!token">
<ButtonBase class="w-full" @click.native="$closePopup()" button-style="secondary">
{{ $t('global.cancel') }}
</ButtonBase>
<ButtonBase
class="w-full"
@click.native="createTokenForm"
button-style="theme"
:loading="isLoading"
:disabled="isLoading"
>
{{ $t('personal_token.create_token') }}
<ButtonBase class="w-full" @click.native="createTokenForm" button-style="theme" :loading="isLoading" :disabled="isLoading">
{{ $t('personal_token.create_token') }}
</ButtonBase>
</PopupActions>
<PopupActions v-if="token">
<ButtonBase
class="w-full"
@click.native="closePopup"
button-style="theme"
>
<ButtonBase class="w-full" @click.native="closePopup" button-style="theme">
{{ $t('shared_form.button_done') }}
</ButtonBase>
</PopupActions>
@@ -56,82 +45,79 @@
</template>
<script>
import AppInputText from "../Admin/AppInputText";
import {ValidationProvider, ValidationObserver} from 'vee-validate/dist/vee-validate.full'
import PopupWrapper from "./Popup/PopupWrapper";
import PopupActions from "./Popup/PopupActions";
import PopupContent from "./Popup/PopupContent";
import PopupHeader from "./Popup/PopupHeader";
import CopyInput from "./Forms/CopyInput";
import ButtonBase from "../FilesView/ButtonBase";
import InfoBox from "./Forms/InfoBox";
import {required} from 'vee-validate/dist/rules'
import {events} from '../../bus'
import AppInputText from '../Admin/AppInputText'
import { ValidationProvider, ValidationObserver } from 'vee-validate/dist/vee-validate.full'
import PopupWrapper from './Popup/PopupWrapper'
import PopupActions from './Popup/PopupActions'
import PopupContent from './Popup/PopupContent'
import PopupHeader from './Popup/PopupHeader'
import CopyInput from './Forms/CopyInput'
import ButtonBase from '../FilesView/ButtonBase'
import InfoBox from './Forms/InfoBox'
import { required } from 'vee-validate/dist/rules'
import { events } from '../../bus'
import axios from 'axios'
export default {
name: "CreatePersonalTokenPopup",
components: {
ValidationProvider,
ValidationObserver,
AppInputText,
PopupWrapper,
PopupActions,
PopupContent,
PopupHeader,
ButtonBase,
CopyInput,
required,
InfoBox,
},
data() {
return {
isLoading: false,
name: undefined,
token: undefined
}
},
methods: {
async createTokenForm() {
name: 'CreatePersonalTokenPopup',
components: {
ValidationProvider,
ValidationObserver,
AppInputText,
PopupWrapper,
PopupActions,
PopupContent,
PopupHeader,
ButtonBase,
CopyInput,
required,
InfoBox,
},
data() {
return {
isLoading: false,
name: undefined,
token: undefined,
}
},
methods: {
async createTokenForm() {
const isValid = await this.$refs.createToken.validate()
const isValid = await this.$refs.createToken.validate()
if (!isValid) return
if (!isValid) return
this.isLoading = true
this.isLoading = true
axios
.post('/api/user/tokens', {
name: this.name,
})
.then((response) => {
this.token = response.data
axios
.post('/api/user/tokens', {
name: this.name
})
.then(response => {
this.token = response.data
events.$emit('reload-personal-access-tokens')
})
.catch(() => this.$isSomethingWrong())
.finally(() => {
this.isLoading = false
this.name = undefined
})
},
closePopup() {
this.$closePopup()
this.token = undefined
}
}
events.$emit('reload-personal-access-tokens')
})
.catch(() => this.$isSomethingWrong())
.finally(() => {
this.isLoading = false
this.name = undefined
})
},
closePopup() {
this.$closePopup()
this.token = undefined
},
},
}
</script>
<style lang="scss" scoped>
@import '../../../sass/vuefilemanager/inapp-forms';
@import '../../../sass/vuefilemanager/forms';
.dark {
.info-box {
background: lighten($dark_mode_foreground, 3%);
}
}
@import '../../../sass/vuefilemanager/inapp-forms';
@import '../../../sass/vuefilemanager/forms';
.dark {
.info-box {
background: lighten($dark_mode_foreground, 3%);
}
}
</style>

View File

@@ -1,55 +1,44 @@
<template>
<div v-if="emoji">
<div
v-if="config.defaultEmoji === 'twemoji'"
v-html="transferEmoji"
style="font-size: inherit; transform: scale(0.95)"
></div>
<div
v-if="config.defaultEmoji === 'applemoji'"
style="font-size: inherit"
>
{{ emoji.char }}
</div>
<div v-if="config.defaultEmoji === 'twemoji'" v-html="transferEmoji" style="font-size: inherit; transform: scale(0.95)"></div>
<div v-if="config.defaultEmoji === 'applemoji'" style="font-size: inherit">
{{ emoji.char }}
</div>
</div>
</template>
<script>
import twemoji from 'twemoji'
import {mapGetters} from "vuex";
import twemoji from 'twemoji'
import { mapGetters } from 'vuex'
export default {
name: 'Emoji',
props: [
'emoji',
],
data() {
return {
isApple: false,
sizeClass: undefined,
}
},
computed: {
...mapGetters([
'config',
]),
transferEmoji() {
return twemoji.parse(this.emoji.char, {
folder: 'svg',
ext: '.svg',
attributes: () => ({
loading: 'lazy'
})
})
}
},
}
export default {
name: 'Emoji',
props: ['emoji'],
data() {
return {
isApple: false,
sizeClass: undefined,
}
},
computed: {
...mapGetters(['config']),
transferEmoji() {
return twemoji.parse(this.emoji.char, {
folder: 'svg',
ext: '.svg',
attributes: () => ({
loading: 'lazy',
}),
})
},
},
}
</script>
<style lang="css">
.emoji {
height: 1em;
width: 1em;
font-size: inherit;
}
</style>
.emoji {
height: 1em;
width: 1em;
font-size: inherit;
}
</style>

View File

@@ -1,112 +1,114 @@
<template>
<div>
<!-- Search field -->
<div class="mb-3 relative flex items-center">
<!-- Search field -->
<div class="relative mb-3 flex items-center">
<!-- Selected emoji preview -->
<div v-if="defaultEmoji" class="mr-3 select-none">
<Emoji :emoji="defaultEmoji" class="text-5xl" />
</div>
<!-- Selected emoji preview -->
<div v-if="defaultEmoji" class="select-none mr-3">
<Emoji :emoji="defaultEmoji" class="text-5xl" />
</div>
<!-- Search input -->
<input @click="openList" v-model="query" class="focus-border-theme input-dark" type="text" :placeholder="$t('Select or search emoji icon...')" />
</div>
<!-- Search input -->
<input @click="openList" v-model="query" class="focus-border-theme input-dark" type="text" :placeholder="$t('Select or search emoji icon...')">
</div>
<!-- Spinner -->
<div v-if="isOpen && !isLoaded" class="relative h-20 select-none">
<Spinner />
</div>
<!-- Spinner -->
<div v-if="isOpen && !isLoaded" class="relative h-20 select-none">
<Spinner />
</div>
<!-- Emojis List -->
<div v-if="isOpen && isLoaded && emojis" @scroll="checkGroupInView" id="group-box" class="2xl:h-96 lg:h-60 h-96 overflow-y-auto select-none relative">
<div v-if="isOpen && isLoaded && emojis" @scroll="checkGroupInView" id="group-box" class="relative h-96 select-none overflow-y-auto lg:h-60 2xl:h-96">
<!-- Navigation of Emojis Groups -->
<ul v-if="!query" class="sticky top-0 z-10 flex items-center justify-between space-x-1 bg-white dark:bg-dark-background sm:dark:bg-4x-dark-foreground" id="group-bar">
<li
@click.stop="scrollToGroup(group.name)"
v-for="(group, i) in emojis.groups"
:key="i"
class="flex h-14 w-14 cursor-pointer items-center justify-center rounded-xl hover:bg-light-background dark:hover:bg-2x-dark-foreground"
:class="{
'bg-light-background dark:bg-2x-dark-foreground': group.name === groupInView,
}"
>
<Emoji :emoji="group.emoji" class="text-3xl" />
</li>
</ul>
<!-- Navigation of Emojis Groups -->
<ul v-if="! query" class="flex items-center justify-between space-x-1 sticky top-0 sm:dark:bg-4x-dark-foreground dark:bg-dark-background bg-white z-10" id="group-bar">
<li @click.stop="scrollToGroup(group.name)" v-for="(group,i) in emojis.groups" :key="i" class="w-14 h-14 flex items-center justify-center rounded-xl cursor-pointer dark:hover:bg-2x-dark-foreground hover:bg-light-background" :class="{'dark:bg-2x-dark-foreground bg-light-background': group.name === groupInView}">
<Emoji :emoji="group.emoji" class="text-3xl" />
</li>
</ul>
<!-- All Emojis -->
<div v-if="!query" v-for="(group, name) in allEmoji" :key="name" :id="`group-${name}`">
<label class="mt-4 mb-2 block text-sm font-bold">
{{ name }}
</label>
<ul class="space-between grid grid-cols-7 gap-4 md:grid-cols-9">
<li @click="setEmoji(emoji)" v-for="(emoji, i) in group" :key="i" class="flex cursor-pointer items-center justify-center">
<Emoji :emoji="emoji" class="text-4xl" />
</li>
</ul>
</div>
<!-- All Emojis -->
<div v-if="! query" v-for="(group, name) in allEmoji" :key="name" :id="`group-${name}`">
<label class="font-bold text-sm mt-4 mb-2 block">
{{ name }}
</label>
<ul class="grid md:grid-cols-9 grid-cols-7 gap-4 space-between">
<li @click="setEmoji( emoji )" v-for="(emoji,i) in group" :key="i" class="flex items-center justify-center cursor-pointer">
<Emoji :emoji="emoji" class="text-4xl" />
</li>
</ul>
</div>
<!-- Searched emojis -->
<ul v-if="query" class="space-between grid grid-cols-7 gap-4 md:grid-cols-9">
<li @click="setEmoji(emoji)" v-for="(emoji, i) in filteredEmojis" :key="i" class="flex cursor-pointer items-center justify-center">
<Emoji :emoji="emoji" class="text-4xl" />
</li>
</ul>
<!-- Searched emojis -->
<ul v-if="query" class="grid md:grid-cols-9 grid-cols-7 gap-4 space-between">
<li @click="setEmoji( emoji )" v-for="(emoji,i) in filteredEmojis" :key="i" class="flex items-center justify-center cursor-pointer">
<Emoji :emoji="emoji" class="text-4xl" />
</li>
</ul>
<!-- Not found -->
<span class="font-bold text-sm ml-2" v-if="filteredEmojis.length === 0 && query !== undefined">
{{ $t('There is nothing :(') }}
</span>
</div>
<!-- Not found -->
<span class="ml-2 text-sm font-bold" v-if="filteredEmojis.length === 0 && query !== undefined">
{{ $t('There is nothing :(') }}
</span>
</div>
</div>
</template>
<script>
import Spinner from "../FilesView/Spinner";
import Emoji from "./Emoji";
import {debounce, groupBy} from 'lodash'
import {XIcon} from 'vue-feather-icons'
import {mapGetters} from 'vuex'
import Spinner from '../FilesView/Spinner'
import Emoji from './Emoji'
import { debounce, groupBy } from 'lodash'
import { XIcon } from 'vue-feather-icons'
import { mapGetters } from 'vuex'
export default {
name: 'EmojiPicker',
props: [
'defaultEmoji',
],
props: ['defaultEmoji'],
components: {
Spinner,
Emoji,
XIcon,
},
computed: {
...mapGetters([
'emojis',
]),
allEmoji() {
return groupBy(this.emojis.list, 'group')
},
...mapGetters(['emojis']),
allEmoji() {
return groupBy(this.emojis.list, 'group')
},
},
data() {
return {
query: undefined,
query: undefined,
filteredEmojis: [],
isOpen: false,
isLoaded: false,
groupInView: 'Smileys & Emotion',
}
},
watch: {
query: debounce(function (val) {
// Clear search results
this.filteredEmojis = []
watch: {
query: debounce(function (val) {
// Clear search results
this.filteredEmojis = []
// Reset query
if (val === '' || val === undefined) return
// Reset query
if (val === '' || val === undefined) return
// Filter emojis by query
this.filteredEmojis = this.emojis.list.filter(emoji => emoji.name.includes(val.toLowerCase()))
// Filter emojis by query
this.filteredEmojis = this.emojis.list.filter((emoji) => emoji.name.includes(val.toLowerCase()))
if (this.filteredEmojis.length === 0) {
console.log('empty');
}
}, 200),
},
if (this.filteredEmojis.length === 0) {
console.log('empty')
}
}, 200),
},
methods: {
checkGroupInView: _.debounce(function () {
this.emojis.groups.forEach(group => {
this.emojis.groups.forEach((group) => {
let element = document.getElementById(`group-${group.name}`).getBoundingClientRect()
let groupBox = document.getElementById('group-box').getBoundingClientRect()
@@ -117,50 +119,51 @@ export default {
})
}, 300),
scrollToGroup(name) {
let groupBar = document.getElementById('group-bar')
let groupBox = document.getElementById('group-box')
let groupBar = document.getElementById('group-bar')
let groupBox = document.getElementById('group-box')
let group = document.getElementById(`group-${name}`)
groupBox.scrollTo({
top: group.offsetTop - groupBar.clientHeight - 5,
left: 0,
behavior: 'smooth'
});
groupBox.scrollTo({
top: group.offsetTop - groupBar.clientHeight - 5,
left: 0,
behavior: 'smooth',
})
this.groupInView = name
},
openList() {
// Open list if it's not opened
if (! this.isOpen) this.isOpen = true
// Open list if it's not opened
if (!this.isOpen) this.isOpen = true
// Load emojis from server just if not loaded already
if (this.isOpen && !this.emojis) {
axios.get('/assets/emojis.json')
.then(response => {
axios
.get('/assets/emojis.json')
.then((response) => {
this.$store.commit('LOAD_EMOJIS_LIST', response.data)
})
.finally(() => this.isLoaded = true)
.finally(() => (this.isLoaded = true))
}
// Simulate loading for the list processing
if (this.emojis) {
setTimeout(() => {
this.isLoaded = true
}, 20);
}, 20)
}
this.groupInView = 'Smileys & Emotion'
},
setEmoji(value) {
this.query = undefined
this.query = undefined
this.isOpen = false
this.$emit('input', value)
},
},
mounted() {
// Open list of there isn't set any emoji
if (! this.defaultEmoji) this.openList()
}
// Open list of there isn't set any emoji
if (!this.defaultEmoji) this.openList()
},
}
</script>
</script>

View File

@@ -16,59 +16,62 @@
</template>
<script>
import { FileIcon, FileTextIcon, SettingsIcon } from 'vue-feather-icons'
import { FileIcon, FileTextIcon, SettingsIcon } from 'vue-feather-icons'
export default {
name: 'EmptyPageContent',
props: ['icon','title','description'],
components: {
SettingsIcon,
FileTextIcon,
FileIcon,
}
}
export default {
name: 'EmptyPageContent',
props: ['icon', 'title', 'description'],
components: {
SettingsIcon,
FileTextIcon,
FileIcon,
},
}
</script>
<style lang="scss" scoped>
@import '../../../sass/vuefilemanager/variables';
@import '../../../sass/vuefilemanager/mixins';
@import '../../../sass/vuefilemanager/variables';
@import '../../../sass/vuefilemanager/mixins';
.empty-page-content {
width: 100%;
height: 100%;
display: flex;
align-items: center;
text-align: center;
.empty-page-content {
width: 100%;
height: 100%;
display: flex;
align-items: center;
text-align: center;
.content {
.content {
margin: 0 auto;
max-width: 360px;
/deep/ .button-base {
margin: 0 auto;
max-width: 360px;
/deep/ .button-base {
margin: 0 auto;
}
}
.icon {
path, polyline, line, circle {
stroke: $theme;
}
}
.header {
margin-top: 15px;
margin-bottom: 25px;
}
.title {
@include font-size(23);
font-weight: 700;
padding-bottom: 5px;
}
.description {
@include font-size(16);
font-weight: 500;
}
}
.icon {
path,
polyline,
line,
circle {
stroke: $theme;
}
}
.header {
margin-top: 15px;
margin-bottom: 25px;
}
.title {
@include font-size(23);
font-weight: 700;
padding-bottom: 5px;
}
.description {
@include font-size(16);
font-weight: 500;
}
}
</style>

View File

@@ -1,56 +1,37 @@
<template>
<div v-if="canBePreview" class="w-full block mb-4">
<div v-if="canBePreview" class="mb-4 block w-full">
<!--Image-->
<img
v-if="singleFile.data.type === 'image' && singleFile.data.attributes.thumbnail"
:src="singleFile.data.attributes.thumbnail.md"
:alt="singleFile.data.attributes.name"
class="w-full overflow-hidden rounded-lg object-cover shadow-lg"
/>
<!--Image-->
<img
v-if="singleFile.data.type === 'image' && singleFile.data.attributes.thumbnail"
:src="singleFile.data.attributes.thumbnail.md"
:alt="singleFile.data.attributes.name"
class="rounded-lg overflow-hidden w-full object-cover shadow-lg"
/>
<!--Audio-->
<audio v-else-if="singleFile.data.type === 'audio'" :src="singleFile.data.attributes.file_url" controlsList="nodownload" controls class="w-full"></audio>
<!--Audio-->
<audio
v-else-if="singleFile.data.type === 'audio'"
:src="singleFile.data.attributes.file_url"
controlsList="nodownload"
controls
class="w-full"
>
</audio>
<!--Video-->
<video
class="w-full h-auto rounded-sm overflow-hidden"
v-else-if="singleFile.data.type === 'video'"
controlsList="nodownload"
disablePictureInPicture
playsinline
controls
>
<source :src="singleFile.data.attributes.file_url" type="video/mp4">
<!--Video-->
<video class="h-auto w-full overflow-hidden rounded-sm" v-else-if="singleFile.data.type === 'video'" controlsList="nodownload" disablePictureInPicture playsinline controls>
<source :src="singleFile.data.attributes.file_url" type="video/mp4" />
</video>
</div>
</template>
<script>
import {mapGetters} from 'vuex'
import {includes} from 'lodash'
import { mapGetters } from 'vuex'
import { includes } from 'lodash'
export default {
name: 'FilePreview',
computed: {
...mapGetters([
'clipboard',
]),
singleFile() {
return this.clipboard[0]
},
canBePreview() {
return this.singleFile && !includes([
'folder', 'file'
], this.singleFile.data.type)
},
}
}
</script>
export default {
name: 'FilePreview',
computed: {
...mapGetters(['clipboard']),
singleFile() {
return this.clipboard[0]
},
canBePreview() {
return this.singleFile && !includes(['folder', 'file'], this.singleFile.data.type)
},
},
}
</script>

View File

@@ -1,62 +1,47 @@
<template>
<div class="relative cursor-pointer">
<input
ref="file"
type="file"
@change="showImagePreview($event)"
class="absolute opacity-0 top-0 bottom-0 left-0 right-0 w-full z-10 cursor-pointer"
/>
<img
v-if="imagePreview"
ref="image"
:src="imagePreview"
class="md:w-16 w-14 md:h-16 h-14 object-cover rounded-xl relative z-0 shadow-lg cursor-pointer"
alt="avatar"
/>
<input ref="file" type="file" @change="showImagePreview($event)" class="absolute top-0 bottom-0 left-0 right-0 z-10 w-full cursor-pointer opacity-0" />
<img v-if="imagePreview" ref="image" :src="imagePreview" class="relative z-0 h-14 w-14 cursor-pointer rounded-xl object-cover shadow-lg md:h-16 md:w-16" alt="avatar" />
</div>
</template>
<script>
export default {
name: 'AvatarInput',
props: [
'avatar',
],
data() {
return {
imagePreview: undefined
}
},
watch: {
imagePreview(val) {
this.$store.commit('UPDATE_AVATAR', val)
}
},
methods: {
showImagePreview(event) {
let imgPath = event.target.files[0].name,
extension = imgPath
.substring(imgPath.lastIndexOf('.') + 1)
.toLowerCase()
export default {
name: 'AvatarInput',
props: ['avatar'],
data() {
return {
imagePreview: undefined,
}
},
watch: {
imagePreview(val) {
this.$store.commit('UPDATE_AVATAR', val)
},
},
methods: {
showImagePreview(event) {
let imgPath = event.target.files[0].name,
extension = imgPath.substring(imgPath.lastIndexOf('.') + 1).toLowerCase()
if (['png', 'jpg', 'jpeg'].includes(extension)) {
let file = event.target.files[0],
reader = new FileReader()
if (['png', 'jpg', 'jpeg'].includes(extension)) {
let file = event.target.files[0],
reader = new FileReader()
reader.onload = () => (this.imagePreview = reader.result)
reader.onload = () => (this.imagePreview = reader.result)
reader.readAsDataURL(file)
reader.readAsDataURL(file)
// Update user avatar
this.$updateImage('/user/settings', 'avatar', event.target.files[0])
} else {
alert(this.$t('validation_errors.wrong_image'))
}
}
},
created() {
// If there is default image then load
if (this.avatar) this.imagePreview = this.avatar
}
}
// Update user avatar
this.$updateImage('/user/settings', 'avatar', event.target.files[0])
} else {
alert(this.$t('validation_errors.wrong_image'))
}
},
},
created() {
// If there is default image then load
if (this.avatar) this.imagePreview = this.avatar
},
}
</script>

View File

@@ -1,28 +1,21 @@
<template>
<div @click="copyUrl" class="flex items-center relative">
<input ref="sel" :value="str" :id="id" type="text" class="pr-10 focus-border-theme input-dark" readonly>
<div @click="copyUrl" class="relative flex items-center">
<input ref="sel" :value="str" :id="id" type="text" class="focus-border-theme input-dark pr-10" readonly />
<!--Copy icon-->
<!--Copy icon-->
<div class="absolute right-0 px-4">
<copy-icon v-if="! isCopiedLink" size="16" class="cursor-pointer hover-text-theme vue-feather"/>
<check-icon v-if="isCopiedLink" size="16" class="cursor-pointer text-theme vue-feather"/>
<copy-icon v-if="!isCopiedLink" size="16" class="hover-text-theme vue-feather cursor-pointer" />
<check-icon v-if="isCopiedLink" size="16" class="text-theme vue-feather cursor-pointer" />
</div>
</div>
</template>
<script>
import {
CopyIcon,
CheckIcon,
SendIcon
} from 'vue-feather-icons'
import { CopyIcon, CheckIcon, SendIcon } from 'vue-feather-icons'
export default {
name: 'CopyInput',
props: [
'size',
'str',
],
props: ['size', 'str'],
components: {
CheckIcon,
CopyIcon,
@@ -31,12 +24,11 @@ export default {
data() {
return {
isCopiedLink: false,
id: 'link-input-' + Math.floor(Math.random() * 10000000),
id: 'link-input-' + Math.floor(Math.random() * 10000000),
}
},
methods: {
copyUrl() {
// Get input value
let copyText = document.getElementById(this.id)
@@ -54,7 +46,7 @@ export default {
setTimeout(() => {
this.isCopiedLink = false
}, 1000)
}
}
},
},
}
</script>

View File

@@ -1,47 +1,63 @@
<template>
<div class="flex items-center relative">
<input ref="sel" :value="item.data.relationships.shared.data.attributes.link" :id="id" type="text" class="pr-16 py-2 pl-3 text-sm focus-border-theme w-full dark:bg-2x-dark-foreground bg-light-background rounded-lg appearance-none border-transparent font-bold border" readonly>
<div class="relative flex items-center">
<input
ref="sel"
:value="item.data.relationships.shared.data.attributes.link"
:id="id"
type="text"
class="focus-border-theme w-full appearance-none rounded-lg border border-transparent bg-light-background py-2 pr-16 pl-3 text-sm font-bold dark:bg-2x-dark-foreground"
readonly
/>
<!--Copy icon-->
<div class="flex items-center">
<!--Copy icon-->
<div class="flex items-center">
<div @click="copyUrl" class="absolute right-9 p-1">
<copy-icon v-if="! isCopiedLink" size="14" class="cursor-pointer hover-text-theme vue-feather"/>
<check-icon v-if="isCopiedLink" size="14" class="cursor-pointer hover-text-theme vue-feather"/>
<copy-icon v-if="!isCopiedLink" size="14" class="hover-text-theme vue-feather cursor-pointer" />
<check-icon v-if="isCopiedLink" size="14" class="hover-text-theme vue-feather cursor-pointer" />
</div>
<div @click.stop.prevent="moreOptions" class="absolute right-2.5 p-1">
<more-horizontal-icon size="14" class="cursor-pointer hover-text-theme vue-feather" />
<more-horizontal-icon size="14" class="hover-text-theme vue-feather cursor-pointer" />
</div>
</div>
<!--Hidden options-->
<ul v-if="isOpenedMoreOptions" class="shadow-xl rounded-lg absolute top-12 left-0 right-0 z-10 overflow-y-auto overflow-x-hidden select-none">
<li @click="getQrCode" class="flex items-center py-2.5 px-5 block cursor-pointer dark:bg-2x-dark-foreground dark:hover:bg-4x-dark-foreground hover:bg-light-background bg-white">
<div class="w-8">
<camera-icon size="14" />
</div>
<span class="text-sm font-bold">
{{ $t('Get QR Code') }}
</span>
</li>
<li @click="sendViaEmail" class="flex items-center py-2.5 px-5 block cursor-pointer dark:bg-2x-dark-foreground dark:hover:bg-4x-dark-foreground hover:bg-light-background bg-white">
<div class="w-8">
<send-icon size="14" />
</div>
<span class="text-sm font-bold">
{{ $t('sharelink.share_via_email') }}
</span>
</li>
<li @click="copyIframe" class="flex items-center py-2.5 px-5 block cursor-pointer dark:bg-2x-dark-foreground dark:hover:bg-4x-dark-foreground hover:bg-light-background bg-white">
<div class="w-8">
<code-icon size="14" />
</div>
<span class="text-sm font-bold">
{{ $t('sharelink.copy_embed') }}
</span>
</li>
</ul>
<!--Hidden options-->
<ul v-if="isOpenedMoreOptions" class="absolute top-12 left-0 right-0 z-10 select-none overflow-y-auto overflow-x-hidden rounded-lg shadow-xl">
<li
@click="getQrCode"
class="block flex cursor-pointer items-center bg-white py-2.5 px-5 hover:bg-light-background dark:bg-2x-dark-foreground dark:hover:bg-4x-dark-foreground"
>
<div class="w-8">
<camera-icon size="14" />
</div>
<span class="text-sm font-bold">
{{ $t('Get QR Code') }}
</span>
</li>
<li
@click="sendViaEmail"
class="block flex cursor-pointer items-center bg-white py-2.5 px-5 hover:bg-light-background dark:bg-2x-dark-foreground dark:hover:bg-4x-dark-foreground"
>
<div class="w-8">
<send-icon size="14" />
</div>
<span class="text-sm font-bold">
{{ $t('sharelink.share_via_email') }}
</span>
</li>
<li
@click="copyIframe"
class="block flex cursor-pointer items-center bg-white py-2.5 px-5 hover:bg-light-background dark:bg-2x-dark-foreground dark:hover:bg-4x-dark-foreground"
>
<div class="w-8">
<code-icon size="14" />
</div>
<span class="text-sm font-bold">
{{ $t('sharelink.copy_embed') }}
</span>
</li>
</ul>
<textarea v-model="iframeCode" ref="iframe" class="absolute right-full opacity-0 pointer-events-none"></textarea>
<textarea v-model="iframeCode" ref="iframe" class="pointer-events-none absolute right-full opacity-0"></textarea>
</div>
</template>
@@ -51,67 +67,64 @@ import { events } from '../../../bus'
export default {
name: 'CopyShareLink',
props: [
'item',
],
props: ['item'],
components: {
MoreHorizontalIcon,
CameraIcon,
MoreHorizontalIcon,
CameraIcon,
CheckIcon,
CopyIcon,
CodeIcon,
SendIcon
CopyIcon,
CodeIcon,
SendIcon,
},
data() {
return {
id: 'link-input-' + Math.floor(Math.random() * 10000000),
iframeCode: '',
id: 'link-input-' + Math.floor(Math.random() * 10000000),
iframeCode: '',
isCopiedLink: false,
isOpenedMoreOptions: false,
isOpenedMoreOptions: false,
}
},
methods: {
moreOptions() {
this.isOpenedMoreOptions = ! this.isOpenedMoreOptions
this.isOpenedMoreOptions = !this.isOpenedMoreOptions
},
getQrCode() {
events.$emit('popup:open', {
name: 'share-edit',
item: this.item,
section: 'qr-code',
})
getQrCode() {
events.$emit('popup:open', {
name: 'share-edit',
item: this.item,
section: 'qr-code',
})
this.isOpenedMoreOptions = false
},
sendViaEmail() {
this.isOpenedMoreOptions = false
},
sendViaEmail() {
events.$emit('popup:open', {
name: 'share-edit',
item: this.item,
section: 'email-sharing',
})
this.isOpenedMoreOptions = false
this.isOpenedMoreOptions = false
},
copyIframe() {
// generate iframe
this.iframeCode = `<iframe src="${this.item.data.relationships.shared.link}" width="790" height="400" allowfullscreen frameborder="0"></iframe>`
copyIframe() {
// generate iframe
this.iframeCode = `<iframe src="${this.item.data.relationships.shared.link}" width="790" height="400" allowfullscreen frameborder="0"></iframe>`
let copyText = this.$refs.iframe
let copyText = this.$refs.iframe
copyText.select()
copyText.setSelectionRange(0, 99999)
copyText.select()
copyText.setSelectionRange(0, 99999)
document.execCommand('copy')
document.execCommand('copy')
events.$emit('toaster', {
type: 'success',
message: this.$t('Your web insert code was copied'),
})
events.$emit('toaster', {
type: 'success',
message: this.$t('Your web insert code was copied'),
})
this.isOpenedMoreOptions = false
this.isOpenedMoreOptions = false
},
copyUrl() {
// Get input value
var copyText = document.getElementById(this.id)
@@ -129,7 +142,7 @@ export default {
setTimeout(() => {
this.isCopiedLink = false
}, 1000)
}
}
},
},
}
</script>

View File

@@ -1,60 +1,58 @@
<template>
<div class="flex items-center mb-8">
<edit-2-icon v-if="!icon" size="22" class="mr-3 vue-feather text-theme dark-text-theme" />
<frown-icon v-if="icon === 'frown'" size="22" class="mr-3 vue-feather text-theme dark-text-theme" />
<file-text-icon v-if="icon === 'file-text'" size="22" class="mr-3 vue-feather text-theme dark-text-theme" />
<dollar-sign-icon v-if="icon === 'dollar'" size="22" class="mr-3 vue-feather text-theme dark-text-theme" />
<credit-card-icon v-if="icon === 'credit-card'" size="22" class="mr-3 vue-feather text-theme dark-text-theme" />
<bar-chart-icon v-if="icon === 'bar-chart'" size="22" class="mr-3 vue-feather text-theme dark-text-theme" />
<settings-icon v-if="icon === 'settings'" size="22" class="mr-3 vue-feather text-theme dark-text-theme" />
<hard-drive-icon v-if="icon === 'hard-drive'" size="22" class="mr-3 vue-feather text-theme dark-text-theme" />
<smartphone-icon v-if="icon === 'smartphone'" size="22" class="mr-3 vue-feather text-theme dark-text-theme" />
<shield-icon v-if="icon === 'shield'" size="22" class="mr-3 vue-feather text-theme dark-text-theme" />
<bell-icon v-if="icon === 'bell'" size="22" class="mr-3 vue-feather text-theme dark-text-theme" />
<key-icon v-if="icon === 'key'" size="22" class="mr-3 vue-feather text-theme dark-text-theme" />
<users-icon v-if="icon === 'users'" size="22" class="mr-3 vue-feather text-theme dark-text-theme" />
<b class="font-bold dark:text-gray-200 sm:text-lg text-md">
<div class="mb-8 flex items-center">
<edit-2-icon v-if="!icon" size="22" class="vue-feather text-theme dark-text-theme mr-3" />
<frown-icon v-if="icon === 'frown'" size="22" class="vue-feather text-theme dark-text-theme mr-3" />
<file-text-icon v-if="icon === 'file-text'" size="22" class="vue-feather text-theme dark-text-theme mr-3" />
<dollar-sign-icon v-if="icon === 'dollar'" size="22" class="vue-feather text-theme dark-text-theme mr-3" />
<credit-card-icon v-if="icon === 'credit-card'" size="22" class="vue-feather text-theme dark-text-theme mr-3" />
<bar-chart-icon v-if="icon === 'bar-chart'" size="22" class="vue-feather text-theme dark-text-theme mr-3" />
<settings-icon v-if="icon === 'settings'" size="22" class="vue-feather text-theme dark-text-theme mr-3" />
<hard-drive-icon v-if="icon === 'hard-drive'" size="22" class="vue-feather text-theme dark-text-theme mr-3" />
<smartphone-icon v-if="icon === 'smartphone'" size="22" class="vue-feather text-theme dark-text-theme mr-3" />
<shield-icon v-if="icon === 'shield'" size="22" class="vue-feather text-theme dark-text-theme mr-3" />
<bell-icon v-if="icon === 'bell'" size="22" class="vue-feather text-theme dark-text-theme mr-3" />
<key-icon v-if="icon === 'key'" size="22" class="vue-feather text-theme dark-text-theme mr-3" />
<users-icon v-if="icon === 'users'" size="22" class="vue-feather text-theme dark-text-theme mr-3" />
<b class="text-md font-bold dark:text-gray-200 sm:text-lg">
<slot></slot>
</b>
</div>
</template>
<script>
import {
UsersIcon,
ShieldIcon,
CreditCardIcon,
DollarSignIcon,
SmartphoneIcon,
HardDriveIcon,
BarChartIcon,
SettingsIcon,
FileTextIcon,
FrownIcon,
Edit2Icon,
BellIcon,
KeyIcon,
} from 'vue-feather-icons'
import {
UsersIcon,
ShieldIcon,
CreditCardIcon,
DollarSignIcon,
SmartphoneIcon,
HardDriveIcon,
BarChartIcon,
SettingsIcon,
FileTextIcon,
FrownIcon,
Edit2Icon,
BellIcon,
KeyIcon,
} from 'vue-feather-icons'
export default {
name: 'FormLabel',
props: [
'icon'
],
components: {
UsersIcon,
CreditCardIcon,
DollarSignIcon,
SmartphoneIcon,
HardDriveIcon,
BarChartIcon,
SettingsIcon,
FileTextIcon,
ShieldIcon,
FrownIcon,
Edit2Icon,
BellIcon,
KeyIcon,
}
}
export default {
name: 'FormLabel',
props: ['icon'],
components: {
UsersIcon,
CreditCardIcon,
DollarSignIcon,
SmartphoneIcon,
HardDriveIcon,
BarChartIcon,
SettingsIcon,
FileTextIcon,
ShieldIcon,
FrownIcon,
Edit2Icon,
BellIcon,
KeyIcon,
},
}
</script>

View File

@@ -4,21 +4,11 @@
<x-icon size="14" class="close-icon text-theme" />
</div>
<input
ref="file"
type="file"
@change="showImagePreview($event)"
class="dummy"
/>
<img
ref="image"
:src="imagePreview"
class="image-preview"
v-if="imagePreview"
/>
<input ref="file" type="file" @change="showImagePreview($event)" class="dummy" />
<img ref="image" :src="imagePreview" class="image-preview" v-if="imagePreview" />
<div class="dropzone-message" v-show="! isData">
<image-icon size="28" class="icon-upload text-theme mx-auto mb-1"/>
<div class="dropzone-message" v-show="!isData">
<image-icon size="28" class="icon-upload text-theme mx-auto mb-1" />
<span class="dropzone-title">
{{ $t('input_image.title') }}
</span>
@@ -30,179 +20,179 @@
</template>
<script>
import { XIcon, ImageIcon } from 'vue-feather-icons'
import { XIcon, ImageIcon } from 'vue-feather-icons'
export default {
name: 'ImageInput',
props: [
'image', 'error'
],
components: {
ImageIcon,
XIcon,
},
data() {
return {
imagePreview: undefined
}
},
computed: {
isData() {
return typeof this.imagePreview === 'undefined' || this.imagePreview === '' ? false : true
},
},
methods: {
resetImage() {
this.imagePreview = undefined
this.$emit('input', undefined)
},
showImagePreview(event) {
const imgPath = event.target.files[0].name,
extn = imgPath
.substring(imgPath.lastIndexOf('.') + 1)
.toLowerCase()
if (['png', 'jpg', 'jpeg', 'svg'].includes(extn)) {
const file = event.target.files[0],
reader = new FileReader()
reader.onload = () => (this.imagePreview = reader.result)
reader.readAsDataURL(file)
// Update user avatar
this.$emit('input', event.target.files[0])
} else {
alert( this.$t('validation_errors.wrong_image') )
}
}
},
created() {
// If has default image then load
if (this.image) this.imagePreview = this.image
export default {
name: 'ImageInput',
props: ['image', 'error'],
components: {
ImageIcon,
XIcon,
},
data() {
return {
imagePreview: undefined,
}
}
},
computed: {
isData() {
return typeof this.imagePreview === 'undefined' || this.imagePreview === '' ? false : true
},
},
methods: {
resetImage() {
this.imagePreview = undefined
this.$emit('input', undefined)
},
showImagePreview(event) {
const imgPath = event.target.files[0].name,
extn = imgPath.substring(imgPath.lastIndexOf('.') + 1).toLowerCase()
if (['png', 'jpg', 'jpeg', 'svg'].includes(extn)) {
const file = event.target.files[0],
reader = new FileReader()
reader.onload = () => (this.imagePreview = reader.result)
reader.readAsDataURL(file)
// Update user avatar
this.$emit('input', event.target.files[0])
} else {
alert(this.$t('validation_errors.wrong_image'))
}
},
},
created() {
// If has default image then load
if (this.image) this.imagePreview = this.image
},
}
</script>
<style lang="scss" scoped>
@import '../../../../sass/vuefilemanager/variables';
@import '../../../../sass/vuefilemanager/mixins';
@import '../../../../sass/vuefilemanager/variables';
@import '../../../../sass/vuefilemanager/mixins';
.dropzone {
border: 1px dashed #a1abc2;
border-radius: 8px;
position: relative;
text-align: center;
display: flex;
align-items: center;
min-height: 175px;
.dropzone {
border: 1px dashed #a1abc2;
border-radius: 8px;
position: relative;
text-align: center;
display: flex;
align-items: center;
min-height: 175px;
&.is-error {
border: 2px dashed rgba(253, 57, 122, 0.3);
&.is-error {
border: 2px dashed rgba(253, 57, 122, 0.3);
.dropzone-title {
color: $danger;
.dropzone-title {
color: $danger;
}
.icon-upload {
rect,
circle,
polyline {
stroke: $danger;
}
}
}
.icon-upload {
rect, circle, polyline {
stroke: $danger
input[type='file'] {
opacity: 0;
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
z-index: 1;
width: 100%;
cursor: pointer;
}
.image-preview {
position: absolute;
width: 100%;
height: 100%;
object-fit: contain;
left: 0;
padding: 25px;
display: block;
&.fit-image {
object-fit: cover;
border-radius: 12px;
overflow: hidden;
}
}
.dropzone-message {
padding: 50px 0;
width: 100%;
.icon-upload {
rect,
circle,
polyline {
color: inherit;
}
}
.dropzone-title {
@include font-size(16);
font-weight: 700;
display: block;
}
.dropzone-description {
color: $text_muted;
@include font-size(12);
}
}
.reset-image {
z-index: 2;
background: white;
border-radius: 50px;
display: block;
position: absolute;
right: 0;
top: 0;
cursor: pointer;
@include transform(translateY(-50%) translateX(50%));
padding: 0px 4px;
box-shadow: 0 1px 5px rgba(0, 0, 0, 0.12);
.close-icon {
vertical-align: middle;
line {
path {
fill: $text;
}
}
}
}
}
input[type='file'] {
opacity: 0;
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
z-index: 1;
width: 100%;
cursor: pointer;
}
.image-preview {
position: absolute;
width: 100%;
height: 100%;
object-fit: contain;
left: 0;
padding: 25px;
display: block;
&.fit-image {
object-fit: cover;
border-radius: 12px;
overflow: hidden;
}
}
.dark {
.dropzone {
border-color: rgba(white, 0.2);
.dropzone-message {
padding: 50px 0;
width: 100%;
.icon-upload {
rect, circle, polyline {
color: inherit
path,
polyline,
line {
color: inherit;
}
}
.dropzone-title {
@include font-size(16);
font-weight: 700;
display: block;
}
.dropzone-description {
color: $text_muted;
@include font-size(12);
}
}
.reset-image {
z-index: 2;
background: white;
border-radius: 50px;
display: block;
position: absolute;
right: 0;
top: 0;
cursor: pointer;
@include transform(translateY(-50%) translateX(50%));
padding: 0px 4px;
box-shadow: 0 1px 5px rgba(0, 0, 0, 0.12);
.close-icon {
vertical-align: middle;
line {
path {
fill: $text;
}
}
}
}
}
.dark {
.dropzone {
border-color: rgba(white, 0.2);
.dropzone-message {
.icon-upload {
path, polyline, line {
color: inherit;
}
}
.dropzone-description {
color: $dark_mode_text_secondary;
}
color: $dark_mode_text_secondary;
}
}
}
}
</style>

View File

@@ -5,27 +5,91 @@
</template>
<script>
export default {
name: 'InfoBox',
props: ['type']
}
export default {
name: 'InfoBox',
props: ['type'],
}
</script>
<style lang="scss" scoped>
@import "../../../../sass/vuefilemanager/variables";
@import '../../../../sass/vuefilemanager/mixins';
@import '../../../../sass/vuefilemanager/variables';
@import '../../../../sass/vuefilemanager/mixins';
.info-box {
padding: 20px;
border-radius: 10px;
margin-bottom: 32px;
background: $light_background;
text-align: left;
&.error {
background: rgba($danger, 0.1);
p,
a {
color: $danger;
}
a {
text-decoration: underline;
}
}
p {
font-size: 15px;
line-height: 1.6;
word-break: break-word;
font-weight: 600;
/deep/ a {
font-size: 15px;
}
/deep/ b {
font-size: 15px;
font-weight: 700;
}
}
b {
font-weight: 700;
}
a {
font-weight: 700;
@include font-size(15);
line-height: 1.6;
}
ul {
margin-top: 15px;
display: block;
li {
display: block;
a {
display: block;
}
}
}
}
@media only screen and (max-width: 690px) {
.info-box {
padding: 20px;
border-radius: 10px;
margin-bottom: 32px;
background: $light_background;
text-align: left;
padding: 15px;
}
}
.dark {
.info-box {
background: $dark_mode_foreground;
&.error {
background: rgba($danger, 0.1);
p, a {
p,
a {
color: $danger;
}
@@ -35,80 +99,14 @@
}
p {
font-size: 15px;
line-height: 1.6;
word-break: break-word;
font-weight: 600;
/deep/ a {
font-size: 15px;
}
/deep/ b {
font-size: 15px;
font-weight: 700;
}
}
b {
font-weight: 700;
}
a {
font-weight: 700;
@include font-size(15);
line-height: 1.6;
color: $dark_mode_text_primary;
}
ul {
margin-top: 15px;
display: block;
li {
display: block;
a {
display: block;
}
}
}
}
@media only screen and (max-width: 690px) {
.info-box {
padding: 15px;
}
}
.dark {
.info-box {
background: $dark_mode_foreground;
&.error {
background: rgba($danger, 0.1);
p, a {
color: $danger;
}
a {
text-decoration: underline;
}
}
p {
color: $dark_mode_text_primary;
}
ul {
li {
color: $dark_mode_text_primary;
}
}
}
}
}
</style>

View File

@@ -1,13 +1,22 @@
<template>
<div class="wrapper">
<!--<label class="input-label">{{ label }}:</label>-->
<div class="input-wrapper focus-within-border-theme" :class="{'is-error' : isError}" @click="$refs.input.focus()">
<!--<label class="input-label">{{ label }}:</label>-->
<div class="input-wrapper focus-within-border-theme" :class="{ 'is-error': isError }" @click="$refs.input.focus()">
<div class="email-list">
<div class="email-tag bg-theme-100" :class="{'mb-offset': getCharactersLength > 45}" v-for="(email, i) in emails" :key="i">
<div class="email-tag bg-theme-100" :class="{ 'mb-offset': getCharactersLength > 45 }" v-for="(email, i) in emails" :key="i">
<span class="text-theme">{{ email }}</span>
<x-icon @click="removeEmail(email)" class="icon" size="14"/>
<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"/>
<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>
@@ -24,74 +33,68 @@ export default {
props: ['isError', 'label'],
computed: {
getCharactersLength() {
return this.emails.join( '' ).length
return this.emails.join('').length
},
placeHolder() {
return !this.emails.length ? this.$t( 'shared_form.email_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
email: undefined,
}
},
methods: {
removeEmail( email ) {
this.emails = this.emails.filter( item => item !== email )
removeEmail(email) {
this.emails = this.emails.filter((item) => item !== email)
// After romove email send new emails list to parent
events.$emit( 'emailsInputValues', this.emails )
events.$emit('emailsInputValues', this.emails)
},
removeLastEmail( event ) {
removeLastEmail(event) {
// If is input empty and presse backspace remove last email from array
if ( event.code === 'Backspace' && this.email === '' )
this.emails.pop()
if (event.code === 'Backspace' && this.email === '') this.emails.pop()
},
handleEmail() {
if ( this.email.length > 0 ) {
if (this.email.length > 0) {
// Get index of @ and last dot
let lastDot = this.email.lastIndexOf( '.' )
let at = this.email.indexOf( '@' )
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
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])
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( /[","," "]/, '' )
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 )
this.emails.push(email)
events.$emit( 'emailsInputValues', this.emails )
events.$emit('emailsInputValues', this.emails)
}
}
}
},
},
created() {
this.$nextTick(() => {
this.$refs.input.focus()
})
}
},
}
</script>
<style scoped lang="scss">
@import "../../../../sass/vuefilemanager/inapp-forms";
@import '../../../../sass/vuefilemanager/inapp-forms';
@import '../../../../sass/vuefilemanager/forms';
.input-label {
@@ -158,25 +161,24 @@ export default {
.email-input {
width: auto;
border: none ;
border: none;
font-weight: 700;
@include font-size(16);
padding-left: 11px;
&::placeholder {
color: rgba($text-muted, .5)
color: rgba($text-muted, 0.5);
}
}
}
.dark {
.input-wrapper {
background: lighten($dark_mode_foreground, 3%);
background: lighten($dark_mode_foreground, 3%);
.email-list {
.email-input {
background: lighten($dark_mode_foreground, 3%);
background: lighten($dark_mode_foreground, 3%);
color: $dark_mode_text_primary;
&::placeholder {

View File

@@ -6,19 +6,12 @@
<div @click="clearInput" v-if="query" class="icon">
<x-icon class="pointer" size="19"></x-icon>
</div>
<input
v-model="query"
@input="$emit('input', query)"
class="query focus-border-theme"
type="text"
name="searchInput"
:placeholder="$t('search_translations')"
/>
<input v-model="query" @input="$emit('input', query)" class="query focus-border-theme" type="text" name="searchInput" :placeholder="$t('search_translations')" />
</div>
</template>
<script>
import {SearchIcon, XIcon} from 'vue-feather-icons'
import { SearchIcon, XIcon } from 'vue-feather-icons'
export default {
name: 'SearchInput',
@@ -110,5 +103,4 @@ export default {
}
}
}
</style>

View File

@@ -1,92 +1,79 @@
<template>
<div class="select-box">
<div class="box-item active-bg-theme-100 active-border-theme"
:class="{'active': item.value === input}"
@click="getSelectedValue(item)"
v-for="(item, i) in data" :key="i"
>
<div class="box-item active-bg-theme-100 active-border-theme" :class="{ active: item.value === input }" @click="getSelectedValue(item)" v-for="(item, i) in data" :key="i">
<span class="box-value active-text-theme">{{ item.label }}</span>
</div>
</div>
</template>
<script>
export default {
name: 'SelectBoxInput',
props: [
'data',
'value',
],
data() {
return {
input: undefined,
}
},
methods: {
getSelectedValue(item) {
if (! this.input || this.input !== item.value)
this.input = item.value
else
this.input = undefined
this.$emit('input', this.input)
}
},
created() {
if (this.value)
this.input = this.value
export default {
name: 'SelectBoxInput',
props: ['data', 'value'],
data() {
return {
input: undefined,
}
}
},
methods: {
getSelectedValue(item) {
if (!this.input || this.input !== item.value) this.input = item.value
else this.input = undefined
this.$emit('input', this.input)
},
},
created() {
if (this.value) this.input = this.value
},
}
</script>
<style lang="scss" scoped>
@import '../../../../sass/vuefilemanager/variables';
@import '../../../../sass/vuefilemanager/mixins';
@import "../../../../sass/vuefilemanager/inapp-forms";
@import "../../../../sass/vuefilemanager/forms";
@import '../../../../sass/vuefilemanager/variables';
@import '../../../../sass/vuefilemanager/mixins';
@import '../../../../sass/vuefilemanager/inapp-forms';
@import '../../../../sass/vuefilemanager/forms';
.select-box {
display: flex;
justify-content: space-between;
flex-wrap: wrap;
flex-direction: row;
.select-box {
display: flex;
justify-content: space-between;
flex-wrap: wrap;
flex-direction: row;
margin-bottom: 10px;
.box-item {
margin-bottom: 10px;
padding: 12px 4px;
text-align: center;
background: $light_background;
border-radius: 8px;
font-weight: 700;
border: 2px solid $light_background;
cursor: pointer;
flex-direction: column;
flex-basis: 55px;
.box-value {
@include font-size(15);
}
}
}
@media only screen and (max-width: 960px) {
.select-box {
.box-item {
margin-bottom: 10px;
padding: 12px 4px;
text-align: center;
background: $light_background;
border-radius: 8px;
font-weight: 700;
border: 2px solid $light_background;
cursor: pointer;
flex-direction: column;
flex-basis: 55px;
.box-value {
@include font-size(15);
}
flex-basis: calc(34% - 10px);
}
}
}
@media only screen and (max-width: 960px) {
.select-box {
.box-item {
flex-basis: calc(34% - 10px);
}
}
}
.dark {
.select-box {
.box-item {
border-color: $dark_mode_border_color;
background: lighten($dark_mode_foreground, 3%);
}
.dark {
.select-box {
.box-item {
border-color: $dark_mode_border_color;
background: lighten($dark_mode_foreground, 3%);
}
}
}
</style>

View File

@@ -1,22 +1,20 @@
<template>
<div class="select">
<!--Area-->
<div class="input-area bg-light-background rounded-lg" :class="{'is-active': isOpen, 'is-error': isError}" @click="openMenu">
<div class="input-area rounded-lg bg-light-background" :class="{ 'is-active': isOpen, 'is-error': isError }" @click="openMenu">
<!--If is selected-->
<div class="selected w-full flex items-center" v-if="selected">
<div class="selected flex w-full items-center" v-if="selected">
<div class="option-icon" v-if="selected.icon">
<user-icon v-if="selected.icon === 'user'" size="14" class="vue-feather text-theme" />
<edit2-icon v-if="selected.icon === 'user-edit'" size="14" class="vue-feather text-theme" />
</div>
<span class="whitespace-nowrap w-full inline-block overflow-hidden text-ellipsis option-value pl-2">
{{ selected.label }}
</span>
<span class="option-value inline-block w-full overflow-hidden text-ellipsis whitespace-nowrap pl-2">
{{ selected.label }}
</span>
</div>
<!--If is empty-->
<div class="not-selected" v-if="! selected">
<div class="not-selected" v-if="!selected">
<span class="option-value placehoder">{{ placeholder }}</span>
</div>
@@ -27,7 +25,7 @@
<transition name="slide-in">
<div class="input-options rounded-lg" v-if="isOpen">
<div v-if="options.length > 5" class="select-search">
<input v-model="query" ref="search" type="text" :placeholder="$t('select_search_placeholder')" class="search-input focus-border-theme rounded-lg">
<input v-model="query" ref="search" type="text" :placeholder="$t('select_search_placeholder')" class="search-input focus-border-theme rounded-lg" />
</div>
<ul class="option-list">
<li class="option-item" @click="selectOption(option)" v-for="(option, i) in optionList" :key="i">
@@ -44,126 +42,216 @@
</template>
<script>
import { ChevronDownIcon, Edit2Icon, UserIcon } from 'vue-feather-icons'
import {debounce, omitBy} from "lodash"
import { ChevronDownIcon, Edit2Icon, UserIcon } from 'vue-feather-icons'
import { debounce, omitBy } from 'lodash'
export default {
name:'SelectInput',
props: [
'placeholder',
'options',
'isError',
'default',
],
components: {
Edit2Icon,
UserIcon,
ChevronDownIcon
export default {
name: 'SelectInput',
props: ['placeholder', 'options', 'isError', 'default'],
components: {
Edit2Icon,
UserIcon,
ChevronDownIcon,
},
watch: {
query: debounce(function (val) {
this.searchedResults = omitBy(this.options, (string) => {
return !string.label.toLowerCase().includes(val.toLowerCase())
})
}, 200),
},
computed: {
isSearching() {
return this.searchedResults && this.query !== ''
},
watch: {
query: debounce(function (val) {
this.searchedResults = omitBy(this.options, string => {
return !string.label.toLowerCase().includes(val.toLowerCase())
})
}, 200),
optionList() {
return this.isSearching ? this.searchedResults : this.options
},
computed: {
isSearching() {
return this.searchedResults && this.query !== ''
},
optionList() {
return this.isSearching
? this.searchedResults
: this.options
}
},
data() {
return {
searchedResults: undefined,
selected: undefined,
isOpen: false,
query: '',
}
},
methods: {
selectOption(option) {
// Emit selected
this.$emit('input', option.value)
this.$emit('change', option.value)
// Get selected
this.selected = option
// Close menu
this.isOpen = false
},
openMenu() {
this.isOpen = ! this.isOpen
if (this.isOpen) {
this.$nextTick(() => this.$refs.search.focus());
}
},
},
created() {
if (this.default)
this.selected = this.options.find(option => option.value === this.default)
},
data() {
return {
searchedResults: undefined,
selected: undefined,
isOpen: false,
query: '',
}
}
},
methods: {
selectOption(option) {
// Emit selected
this.$emit('input', option.value)
this.$emit('change', option.value)
// Get selected
this.selected = option
// Close menu
this.isOpen = false
},
openMenu() {
this.isOpen = !this.isOpen
if (this.isOpen) {
this.$nextTick(() => this.$refs.search.focus())
}
},
},
created() {
if (this.default) this.selected = this.options.find((option) => option.value === this.default)
},
}
</script>
<style lang="scss" scoped>
@import '../../../../sass/vuefilemanager/variables';
@import '../../../../sass/vuefilemanager/mixins';
@import '../../../../sass/vuefilemanager/variables';
@import '../../../../sass/vuefilemanager/mixins';
/* TODO: refactor to the tailwind */
/* TODO: refactor to the tailwind */
.select {
position: relative;
user-select: none;
.select {
position: relative;
user-select: none;
width: 100%;
}
.select-search {
background: white;
position: sticky;
top: 0;
padding: 13px;
.search-input {
border: 1px solid transparent;
background: $light_background;
@include transition(150ms);
@include font-size(14);
padding: 13px 20px;
appearance: none;
font-weight: 700;
outline: 0;
width: 100%;
}
}
.input-options {
box-shadow: 0 5px 15px rgba(0, 0, 0, 0.12);
background: white;
position: absolute;
overflow: hidden;
top: 65px;
left: 0;
right: 0;
z-index: 9;
max-height: 295px;
overflow-y: auto;
.option-item {
padding: 13px 20px;
display: block;
cursor: pointer;
&:hover {
color: $theme;
background: $light_background;
}
&:last-child {
border-bottom: none;
}
}
}
.input-area {
border-width: 1px;
border-style: solid;
border-color: transparent;
justify-content: space-between;
@include transition(150ms);
align-items: center;
padding: 13px 20px;
display: flex;
outline: 0;
width: 100%;
cursor: pointer;
.chevron {
@include transition(150ms);
}
&.is-active {
//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(10);
}
.option-value {
@include font-size(14);
font-weight: 700;
vertical-align: middle;
&.placehoder {
color: rgba($text, 0.5);
}
}
.slide-in-enter-active {
transition: all 150ms ease;
}
.slide-in-enter /* .list-leave-active below version 2.1.8 */ {
opacity: 0;
transform: translateY(-50px);
}
.dark {
.select-search {
background: white;
position: sticky;
top: 0;
padding: 13px;
background: $dark_mode_foreground;
.search-input {
border: 1px solid transparent;
background: $light_background;
@include transition(150ms);
@include font-size(14);
padding: 13px 20px;
appearance: none;
font-weight: 700;
outline: 0;
width: 100%;
background: $dark_mode_background;
}
}
.input-area {
background: $dark_mode_foreground;
border-color: $dark_mode_foreground;
}
.popup-wrapper {
.input-area {
background: lighten($dark_mode_foreground, 3%);
}
}
.input-options {
box-shadow: 0 5px 15px rgba(0, 0, 0, 0.12);
background: white;
position: absolute;
overflow: hidden;
top: 65px;
left: 0;
right: 0;
z-index: 9;
max-height: 295px;
overflow-y: auto;
background: $dark_mode_foreground;
.option-item {
padding: 13px 20px;
display: block;
cursor: pointer;
border-bottom: none;
&:hover {
color: $theme;
background: $light_background;
background: lighten($dark_mode_foreground, 5%);
.option-icon {
path,
circle {
color: inherit;
}
}
}
&:last-child {
@@ -172,113 +260,10 @@
}
}
.input-area {
border-width: 1px;
border-style: solid;
border-color: transparent;
justify-content: space-between;
@include transition(150ms);
align-items: center;
padding: 13px 20px;
display: flex;
outline: 0;
width: 100%;
cursor: pointer;
.chevron {
@include transition(150ms);
}
&.is-active {
//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(10);
}
.option-value {
@include font-size(14);
font-weight: 700;
vertical-align: middle;
&.placehoder {
color: rgba($text, 0.5);
color: $dark_mode_text_secondary;
}
}
.slide-in-enter-active {
transition: all 150ms ease;
}
.slide-in-enter /* .list-leave-active below version 2.1.8 */
{
opacity: 0;
transform: translateY(-50px);
}
.dark {
.select-search {
background: $dark_mode_foreground;
.search-input {
background: $dark_mode_background;
}
}
.input-area {
background: $dark_mode_foreground;
border-color: $dark_mode_foreground;
}
.popup-wrapper {
.input-area {
background: lighten($dark_mode_foreground, 3%);
}
}
.input-options {
background: $dark_mode_foreground;
.option-item {
border-bottom: none;
&:hover {
background: lighten($dark_mode_foreground, 5%);
.option-icon {
path, circle {
color: inherit;
}
}
}
&:last-child {
border-bottom: none;
}
}
}
.option-value {
&.placehoder {
color: $dark_mode_text_secondary;
}
}
}
}
</style>

View File

@@ -7,147 +7,136 @@
</template>
<script>
export default {
name: 'SetupBox',
props: ['title', 'description', 'theme'],
}
export default {
name: 'SetupBox',
props: ['title', 'description', 'theme'],
}
</script>
<style lang="scss" scoped>
@import '../../../../sass/vuefilemanager/variables';
@import '../../../../sass/vuefilemanager/mixins';
@import '../../../../sass/vuefilemanager/variables';
@import '../../../../sass/vuefilemanager/mixins';
.setup-box {
padding: 20px;
border-radius: 8px;
margin-bottom: 30px;
.setup-box {
padding: 20px;
border-radius: 8px;
margin-bottom: 30px;
.title {
@include font-size(21);
margin-bottom: 5px;
display: block;
font-weight: 700;
}
.description {
@include font-size(15);
line-height: 1.5;
margin-bottom: 20px;
}
&.base {
background: $light_background;
}
&.danger {
background: $light_background;
.title {
@include font-size(21);
margin-bottom: 5px;
display: block;
font-weight: 700;
color: $danger;
}
}
/deep/ input {
&[type='text'],
&[type='number'],
.input-area {
background: white;
}
}
/deep/ .input-area {
background: white;
}
/deep/ .form {
margin-top: 20px;
&.block-form {
max-width: 450px;
.single-line-form {
display: flex;
.submit-button {
margin-left: 20px;
}
}
}
}
}
@media only screen and (max-width: 960px) {
.setup-box {
/deep/ .form {
&.block-form {
max-width: 100%;
}
input {
min-width: initial;
}
}
}
}
@media only screen and (max-width: 690px) {
.setup-box {
padding: 15px;
.title {
@include font-size(17);
margin-bottom: 10px;
}
.description {
@include font-size(15);
line-height: 1.5;
margin-bottom: 20px;
@include font-size(14);
}
/deep/ .form.block-form {
.single-line-form {
display: block;
.submit-button {
margin-left: 0;
margin-top: 10px;
}
}
}
}
}
.dark {
.setup-box {
&.base {
background: $light_background;
background: $dark_mode_foreground;
}
&.danger {
background: $light_background;
.title {
color: $danger;
}
background: $dark_mode_foreground;
}
/deep/ input {
&[type='text'],
&[type='number'],
.input-area {
background: white;
background: $dark_mode_background;
}
}
/deep/ .input-area {
background: white;
}
/deep/ .form {
margin-top: 20px;
&.block-form {
max-width: 450px;
.single-line-form {
display: flex;
.submit-button {
margin-left: 20px;
}
}
}
}
}
@media only screen and (max-width: 960px) {
.setup-box {
/deep/ .form {
&.block-form {
max-width: 100%;
}
input {
min-width: initial;
}
}
}
}
@media only screen and (max-width: 690px) {
.setup-box {
padding: 15px;
.title {
@include font-size(17);
margin-bottom: 10px;
}
.description {
@include font-size(14);
}
/deep/ .form.block-form {
.single-line-form {
display: block;
.submit-button {
margin-left: 0;
margin-top: 10px;
}
}
}
}
}
.dark {
.setup-box {
&.base {
background: $dark_mode_foreground;
}
&.danger {
background: $dark_mode_foreground;
}
/deep/ input {
&[type='text'],
&[type='number'],
.input-area {
background: $dark_mode_background;
}
}
/deep/ .input-area {
background: $dark_mode_background;
}
background: $dark_mode_background;
}
}
}
</style>

View File

@@ -1,20 +1,14 @@
<template>
<div class="input-wrapper">
<div class="switch-content">
<label class="input-label" v-if="label">
{{ label }}:
</label>
<label class="input-label" v-if="label"> {{ label }}: </label>
<small class="input-info" v-if="info">
{{ info }}
</small>
{{ info }}
</small>
</div>
<div class="switch-content text-right">
<div
class="switch"
:class="{ active: state }"
@click="changeState"
>
<div class="switch" :class="{ active: state }" @click="changeState">
<div class="switch-button"></div>
</div>
</div>
@@ -22,93 +16,86 @@
</template>
<script>
export default {
name: 'SwitchInput',
props: [
'label',
'name',
'state',
'info',
'input',
],
data() {
return {
isSwitched: undefined
}
},
methods: {
changeState() {
this.isSwitched = !this.isSwitched
this.$emit('input', this.isSwitched)
}
},
mounted() {
this.isSwitched = this.state
}
}
export default {
name: 'SwitchInput',
props: ['label', 'name', 'state', 'info', 'input'],
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 '../../../../sass/vuefilemanager/variables';
@import '../../../../sass/vuefilemanager/mixins';
@import '../../../../sass/vuefilemanager/variables';
@import '../../../../sass/vuefilemanager/mixins';
.input-wrapper {
display: flex;
width: 100%;
.input-wrapper {
display: flex;
width: 100%;
.input-label {
color: $text;
}
.input-label {
color: $text;
}
.switch-content {
width: 100%;
.switch-content {
width: 100%;
&:last-child {
width: 80px;
}
}
}
&:last-child {
width: 80px;
}
}
}
.switch {
width: 50px;
height: 28px;
border-radius: 50px;
display: block;
background: #f1f1f5;
position: relative;
@include transition;
.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;
}
.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 {
&.active {
.switch-button {
left: 25px;
}
}
}
.switch-button {
left: 25px;
}
}
}
.dark {
.switch {
background: $dark_mode_foreground;
}
.dark {
.switch {
background: $dark_mode_foreground;
}
.popup-wrapper {
.switch {
background: lighten($dark_mode_foreground, 3%);
}
}
}
.popup-wrapper {
.switch {
background: lighten($dark_mode_foreground, 3%);
}
}
}
</style>

View File

@@ -3,23 +3,23 @@
<div id="loader" v-show="isLoading">
<Spinner></Spinner>
</div>
<slot v-show="! isLoading"></slot>
<slot v-show="!isLoading"></slot>
</div>
</template>
<script>
import Spinner from "../../FilesView/Spinner";
import Spinner from '../../FilesView/Spinner'
export default {
name: 'PageTab',
props: ['isLoading'],
components: {
Spinner,
},
}
export default {
name: 'PageTab',
props: ['isLoading'],
components: {
Spinner,
},
}
</script>
<style lang="scss" scoped>
@import '../../../../sass/vuefilemanager/variables';
@import '../../../../sass/vuefilemanager/mixins';
</style>
@import '../../../../sass/vuefilemanager/variables';
@import '../../../../sass/vuefilemanager/mixins';
</style>

View File

@@ -5,16 +5,16 @@
</template>
<script>
export default {
name: 'PageTabGroup',
}
export default {
name: 'PageTabGroup',
}
</script>
<style lang="scss" scoped>
@import '../../../../sass/vuefilemanager/variables';
@import '../../../../sass/vuefilemanager/mixins';
@import '../../../../sass/vuefilemanager/variables';
@import '../../../../sass/vuefilemanager/mixins';
.page-tab-group {
margin-bottom: 65px;
}
.page-tab-group {
margin-bottom: 65px;
}
</style>

View File

@@ -5,12 +5,12 @@
</template>
<script>
export default {
name: 'ListInfo',
}
export default {
name: 'ListInfo',
}
</script>
<style lang="scss" scoped>
@import '../../../sass/vuefilemanager/variables';
@import '../../../sass/vuefilemanager/mixins';
@import '../../../sass/vuefilemanager/variables';
@import '../../../sass/vuefilemanager/mixins';
</style>

View File

@@ -1,22 +1,19 @@
<template>
<div class="mb-4">
<small class="text-theme font-bold text-xs block">
{{ title }}
</small>
<b v-if="content" class="inline-block font-bold text-sm">
{{ content }}
</b>
<small class="text-theme block text-xs font-bold">
{{ title }}
</small>
<b v-if="content" class="inline-block text-sm font-bold">
{{ content }}
</b>
<slot v-if="$slots.default" />
</div>
</template>
<script>
export default {
name: 'ListInfoItem',
props: [
'content',
'title',
]
}
export default {
name: 'ListInfoItem',
props: ['content', 'title'],
}
</script>

View File

@@ -1,37 +1,35 @@
<template>
<MenuMobile name="user-navigation">
<!--User avatar-->
<UserHeadline v-if="!clickedSubmenu" class="p-5 pb-3" />
<!--User estimate-->
<div v-if="config.subscriptionType === 'metered' && user && !clickedSubmenu" class="block px-5 pt-2">
<div class="dark:bg-4x-dark-foreground bg-light-background px-3 py-1.5 rounded-lg">
<span class="text-sm font-semibold">
{{ $t('Your current estimated usage:') }}
</span>
<span class="text-sm font-bold text-theme">
{{ user.data.meta.usages.costEstimate }}
</span>
</div>
</div>
<div v-if="config.subscriptionType === 'metered' && user && !clickedSubmenu" class="block px-5 pt-2">
<div class="rounded-lg bg-light-background px-3 py-1.5 dark:bg-4x-dark-foreground">
<span class="text-sm font-semibold">
{{ $t('Your current estimated usage:') }}
</span>
<span class="text-theme text-sm font-bold">
{{ user.data.meta.usages.costEstimate }}
</span>
</div>
</div>
<!--Go back button-->
<div v-if="clickedSubmenu" @click.stop="showSubmenu(undefined)" class="flex items-center p-5 pb-4">
<chevron-left-icon size="19" class="vue-feather text-theme mr-2 -ml-1" />
<span class="text-theme font-bold text-sm">
{{ backTitle }}
</span>
<span class="text-theme text-sm font-bold">
{{ backTitle }}
</span>
</div>
<!--Menu links-->
<MenuMobileGroup>
<!--Main navigation-->
<OptionGroup v-if="!clickedSubmenu">
<Option @click.native="goToFiles" :title="$t('menu.files')" icon="hard-drive" :is-hover-disabled="true"/>
<Option @click.native.stop="showSubmenu('settings')" :title="$t('menu.settings')" icon="user" arrow="right" :is-hover-disabled="true"/>
<Option v-if="isAdmin" @click.native.stop="showSubmenu('admin')" :title="$t('menu.admin')" icon="settings" arrow="right" :is-hover-disabled="true"/>
<Option @click.native="goToFiles" :title="$t('menu.files')" icon="hard-drive" :is-hover-disabled="true" />
<Option @click.native.stop="showSubmenu('settings')" :title="$t('menu.settings')" icon="user" arrow="right" :is-hover-disabled="true" />
<Option v-if="isAdmin" @click.native.stop="showSubmenu('admin')" :title="$t('menu.admin')" icon="settings" arrow="right" :is-hover-disabled="true" />
</OptionGroup>
<OptionGroup v-if="!clickedSubmenu">
<Option @click.native="logOut" :title="$t('menu.logout')" icon="power" :is-hover-disabled="true" />
@@ -42,7 +40,7 @@
<Option @click.native="goToRoute('Profile')" :title="$t('menu.profile')" icon="user" :is-hover-disabled="true" />
<Option @click.native="goToRoute('Password')" :title="$t('menu.password')" icon="lock" :is-hover-disabled="true" />
<Option @click.native="goToRoute('Storage')" :title="$t('menu.storage')" icon="hard-drive" :is-hover-disabled="true" />
<Option @click.native="goToRoute('Billing')" v-if="config.isSaaS" :title="$t('Billing')" icon="cloud" :is-hover-disabled="true" />
<Option @click.native="goToRoute('Billing')" v-if="config.isSaaS" :title="$t('Billing')" icon="cloud" :is-hover-disabled="true" />
</OptionGroup>
<!--Submenu: Admin settings-->
@@ -61,7 +59,13 @@
<!--Submenu: Billing settings-->
<OptionGroup v-if="clickedSubmenu === 'admin' && config.isSaaS">
<Option @click.native="goToRoute('AppPayments')" :title="$t('Payments')" icon="credit-card" :is-hover-disabled="true" />
<Option @click.native="goToRoute('Subscriptions')" v-if="config.subscriptionType === 'fixed'" :title="$t('Subscriptions')" icon="credit-card" :is-hover-disabled="true" />
<Option
@click.native="goToRoute('Subscriptions')"
v-if="config.subscriptionType === 'fixed'"
:title="$t('Subscriptions')"
icon="credit-card"
:is-hover-disabled="true"
/>
<Option @click.native="goToRoute('Plans')" :title="$t('admin_menu.plans')" icon="database" :is-hover-disabled="true" />
<Option @click.native="goToRoute('Invoices')" :title="$t('Transactions')" icon="file-text" :is-hover-disabled="true" />
</OptionGroup>
@@ -70,63 +74,59 @@
</template>
<script>
import MenuMobileGroup from "../Mobile/MenuMobileGroup";
import OptionGroup from "../FilesView/OptionGroup";
import UserHeadline from "../Sidebar/UserHeadline";
import MenuMobile from "../Mobile/MenuMobile";
import Option from "../FilesView/Option";
import {ChevronLeftIcon} from 'vue-feather-icons'
import {mapGetters} from 'vuex'
import MenuMobileGroup from '../Mobile/MenuMobileGroup'
import OptionGroup from '../FilesView/OptionGroup'
import UserHeadline from '../Sidebar/UserHeadline'
import MenuMobile from '../Mobile/MenuMobile'
import Option from '../FilesView/Option'
import { ChevronLeftIcon } from 'vue-feather-icons'
import { mapGetters } from 'vuex'
export default {
name: 'MobileNavigation',
components: {
ChevronLeftIcon,
MenuMobileGroup,
UserHeadline,
OptionGroup,
MenuMobile,
Option,
export default {
name: 'MobileNavigation',
components: {
ChevronLeftIcon,
MenuMobileGroup,
UserHeadline,
OptionGroup,
MenuMobile,
Option,
},
computed: {
...mapGetters(['config', 'user']),
isAdmin() {
return this.user && this.user.data.attributes.role === 'admin'
},
computed: {
...mapGetters([
'config',
'user',
]),
isAdmin() {
return this.user && this.user.data.attributes.role === 'admin'
},
backTitle() {
let location = {
'settings': this.$t('menu.settings'),
'admin': this.$t('menu.admin')
}
return 'Go back from ' + location[this.clickedSubmenu]
backTitle() {
let location = {
settings: this.$t('menu.settings'),
admin: this.$t('menu.admin'),
}
},
data() {
return {
clickedSubmenu: undefined,
}
},
methods: {
goToRoute(route) {
this.$router.push({name: route})
this.clickedSubmenu = undefined
},
showSubmenu(name) {
this.clickedSubmenu = name
},
goToFiles() {
if (this.$route.name !== 'Files')
this.$router.push({name: 'Files'})
this.$store.dispatch('getFolder')
},
logOut() {
this.$store.dispatch('logOut')
},
return 'Go back from ' + location[this.clickedSubmenu]
},
},
data() {
return {
clickedSubmenu: undefined,
}
}
},
methods: {
goToRoute(route) {
this.$router.push({ name: route })
this.clickedSubmenu = undefined
},
showSubmenu(name) {
this.clickedSubmenu = name
},
goToFiles() {
if (this.$route.name !== 'Files') this.$router.push({ name: 'Files' })
this.$store.dispatch('getFolder')
},
logOut() {
this.$store.dispatch('logOut')
},
},
}
</script>

View File

@@ -3,167 +3,151 @@
<!--Title-->
<PopupHeader :title="$t('popup_move_item.title')" icon="move" />
<!--Content-->
<PopupContent v-if="pickedItem" class="sm:overflow-y-auto md:pb-0 sm:max-h-96 h-full pb-6">
<!--Content-->
<PopupContent v-if="pickedItem" class="h-full pb-6 sm:max-h-96 sm:overflow-y-auto md:pb-0">
<!--Show Spinner when loading folders-->
<Spinner v-if="isLoadingTree" />
<!--Folder tree-->
<div v-if="! isLoadingTree && navigation">
<!--Folder tree-->
<div v-if="!isLoadingTree && navigation">
<ThumbnailItem v-if="clipboard.length < 2 || isSelectedItem" class="mb-5" :item="pickedItem" />
<TitlePreview
class="mb-4"
icon="check-square"
:title="$t('file_detail.selected_multiple')"
:subtitle="this.clipboard.length + ' ' + $tc('file_detail.items', this.clipboard.length)"
v-if="clipboard.length > 1 && !isSelectedItem"
/>
class="mb-4"
icon="check-square"
:title="$t('file_detail.selected_multiple')"
:subtitle="this.clipboard.length + ' ' + $tc('file_detail.items', this.clipboard.length)"
v-if="clipboard.length > 1 && !isSelectedItem"
/>
<TreeMenu class="-mx-4" :disabled-by-id="pickedItem" :depth="1" :nodes="items" v-for="items in navigation" :key="items.id" />
</div>
</PopupContent>
<!--Actions-->
<!--Actions-->
<PopupActions>
<ButtonBase
class="w-full"
@click.native="$closePopup()"
button-style="secondary"
>{{ $t('popup_move_item.cancel') }}
</ButtonBase>
<ButtonBase
class="w-full"
@click.native="moveItem"
:button-style="selectedFolder ? 'theme' : 'secondary'"
>{{ $t('popup_move_item.submit') }}
</ButtonBase>
<ButtonBase class="w-full" @click.native="$closePopup()" button-style="secondary">{{ $t('popup_move_item.cancel') }} </ButtonBase>
<ButtonBase class="w-full" @click.native="moveItem" :button-style="selectedFolder ? 'theme' : 'secondary'">{{ $t('popup_move_item.submit') }} </ButtonBase>
</PopupActions>
</PopupWrapper>
</template>
<script>
import PopupWrapper from "./Popup/PopupWrapper";
import PopupActions from "./Popup/PopupActions";
import TitlePreview from "../FilesView/TitlePreview";
import PopupContent from "./Popup/PopupContent";
import PopupHeader from "./Popup/PopupHeader";
import ThumbnailItem from "./ThumbnailItem";
import ButtonBase from "../FilesView/ButtonBase";
import Spinner from "../FilesView/Spinner";
import TreeMenu from "./TreeMenu";
import {isArray, isNull} from 'lodash'
import {mapGetters} from 'vuex'
import {events} from '../../bus'
import PopupWrapper from './Popup/PopupWrapper'
import PopupActions from './Popup/PopupActions'
import TitlePreview from '../FilesView/TitlePreview'
import PopupContent from './Popup/PopupContent'
import PopupHeader from './Popup/PopupHeader'
import ThumbnailItem from './ThumbnailItem'
import ButtonBase from '../FilesView/ButtonBase'
import Spinner from '../FilesView/Spinner'
import TreeMenu from './TreeMenu'
import { isArray, isNull } from 'lodash'
import { mapGetters } from 'vuex'
import { events } from '../../bus'
export default {
name: 'MoveItemPopup',
components: {
ThumbnailItem,
TitlePreview,
PopupWrapper,
PopupActions,
PopupContent,
PopupHeader,
ButtonBase,
TreeMenu,
Spinner,
},
computed: {
...mapGetters([
'navigation',
'clipboard',
]),
},
data() {
return {
selectedFolder: undefined,
pickedItem: undefined,
isLoadingTree: true,
isSelectedItem: false
}
},
methods: {
moveItem() {
// Prevent empty submit
if (!this.selectedFolder) return
export default {
name: 'MoveItemPopup',
components: {
ThumbnailItem,
TitlePreview,
PopupWrapper,
PopupActions,
PopupContent,
PopupHeader,
ButtonBase,
TreeMenu,
Spinner,
},
computed: {
...mapGetters(['navigation', 'clipboard']),
},
data() {
return {
selectedFolder: undefined,
pickedItem: undefined,
isLoadingTree: true,
isSelectedItem: false,
}
},
methods: {
moveItem() {
// Prevent empty submit
if (!this.selectedFolder) return
// Prevent to move items to the same parent
if (isArray(this.selectedFolder) && this.clipboard.find(item => item.parent_id === this.selectedFolder.id)) return
// Prevent to move items to the same parent
if (isArray(this.selectedFolder) && this.clipboard.find((item) => item.parent_id === this.selectedFolder.id)) return
// Move item
if (!this.isSelectedItem) {
this.$store.dispatch('moveItem', {to_item: this.selectedFolder, isSelectedItem: null})
}
// Move item
if (!this.isSelectedItem) {
this.$store.dispatch('moveItem', {
to_item: this.selectedFolder,
isSelectedItem: null,
})
}
if (this.isSelectedItem) {
this.$store.dispatch('moveItem', {to_item: this.selectedFolder, isSelectedItem: this.pickedItem})
}
if (this.isSelectedItem) {
this.$store.dispatch('moveItem', {
to_item: this.selectedFolder,
isSelectedItem: this.pickedItem,
})
}
// Close popup
events.$emit('popup:close')
// Close popup
events.$emit('popup:close')
// If is mobile, close the selecting mod after done the move action
if (this.$isMobile())
this.$store.commit('DISABLE_MULTISELECT_MODE')
},
},
mounted() {
events.$on('pick-folder', folder => {
// If is mobile, close the selecting mod after done the move action
if (this.$isMobile()) this.$store.commit('DISABLE_MULTISELECT_MODE')
},
},
mounted() {
events.$on('pick-folder', (folder) => {
if (folder.id === this.pickedItem.data.id) {
this.selectedFolder = undefined
} else if (!folder.id && folder.location === 'base') {
this.selectedFolder = 'base'
} else {
this.selectedFolder = folder
}
})
if (folder.id === this.pickedItem.data.id) {
this.selectedFolder = undefined
// Show Move item popup
events.$on('popup:open', (args) => {
if (args.name !== 'move') return
} else if (!folder.id && folder.location === 'base') {
this.selectedFolder = 'base'
// Show tree spinner
this.isLoadingTree = true
} else {
this.selectedFolder = folder
}
})
// Get folder tree and hide spinner
if (this.$isThisRoute(this.$route, ['SharedWithMe'])) {
this.$store.dispatch('getTeamFolderTree').then(() => {
this.isLoadingTree = false
})
} else {
this.$store.dispatch('getFolderTree').then(() => {
this.isLoadingTree = false
})
}
// Show Move item popup
events.$on('popup:open', args => {
// Store picked item
if (!this.clipboard.includes(args.item[0])) {
this.pickedItem = args.item[0]
this.isSelectedItem = true
}
if (args.name !== 'move') return
if (this.clipboard.includes(args.item[0])) {
this.pickedItem = this.clipboard[0]
this.isSelectedItem = false
}
})
// Show tree spinner
this.isLoadingTree = true
// Get folder tree and hide spinner
if (this.$isThisRoute(this.$route, ['SharedWithMe'])) {
this.$store.dispatch('getTeamFolderTree').then(() => {
this.isLoadingTree = false
})
} else {
this.$store.dispatch('getFolderTree').then(() => {
this.isLoadingTree = false
})
}
// Store picked item
if (!this.clipboard.includes(args.item[0])) {
this.pickedItem = args.item[0]
this.isSelectedItem = true
}
if (this.clipboard.includes(args.item[0])) {
this.pickedItem = this.clipboard[0]
this.isSelectedItem = false
}
})
// Close popup
events.$on('popup:close', () => {
// Clear selected folder
setTimeout(() => {
this.selectedFolder = undefined
}, 150)
})
}
}
// Close popup
events.$on('popup:close', () => {
// Clear selected folder
setTimeout(() => {
this.selectedFolder = undefined
}, 150)
})
},
}
</script>

View File

@@ -1,92 +1,101 @@
<template>
<transition appear name="fade">
<div
v-if="isActive"
class="relative mt-4 p-4 md:w-96 w-full shadow-lg rounded-xl overflow-hidden backdrop-filter backdrop-blur-lg bg-opacity-80"
:class="{'dark:bg-2x-dark-foreground bg-red-50': isDanger, 'dark:bg-2x-dark-foreground bg-green-50': isSuccess}"
>
v-if="isActive"
class="relative mt-4 w-full overflow-hidden rounded-xl bg-opacity-80 p-4 shadow-lg backdrop-blur-lg backdrop-filter md:w-96"
:class="{
'bg-red-50 dark:bg-2x-dark-foreground': isDanger,
'bg-green-50 dark:bg-2x-dark-foreground': isSuccess,
}"
>
<!--Content-->
<div class="flex items-center justify-between">
<div class="flex items-center">
<div>
<check-icon v-if="isSuccess" size="22" class="vue-feather text-green-600" />
<x-icon v-if="isDanger" size="22" class="vue-feather text-red-600" />
</div>
<!--Content-->
<div class="flex items-center justify-between">
<div class="flex items-center">
<div>
<check-icon v-if="isSuccess" size="22" class="vue-feather text-green-600" />
<x-icon v-if="isDanger" size="22" class="vue-feather text-red-600" />
</div>
<p
class="px-4 font-bold"
:class="{
'text-green-600': isSuccess,
'text-red-600': isDanger,
}"
>
{{ item.message }}
</p>
</div>
<p
class="px-4 font-bold"
:class="{'text-green-600': isSuccess, 'text-red-600': isDanger}"
>
{{ item.message }}
</p>
</div>
<div @click="isActive = false" class="p-2 cursor-pointer">
<x-icon size="16" class="vue-feather dark:text-white text-black opacity-50" />
</div>
<div @click="isActive = false" class="cursor-pointer p-2">
<x-icon size="16" class="vue-feather text-black opacity-50 dark:text-white" />
</div>
</div>
<!--Progress bar-->
<!--Progress bar-->
<div class="absolute bottom-0 left-0 right-0">
<span class="w-0 h-1 block bar-animation" :class="{'bg-green-400': isSuccess, 'bg-red-400': isDanger}"></span>
<span
class="bar-animation block h-1 w-0"
:class="{
'bg-green-400': isSuccess,
'bg-red-400': isDanger,
}"
></span>
</div>
</div>
</transition>
</template>
<script>
import {XIcon, CheckIcon} from 'vue-feather-icons'
import { XIcon, CheckIcon } from 'vue-feather-icons'
export default {
components: {
CheckIcon,
XIcon,
export default {
components: {
CheckIcon,
XIcon,
},
props: ['item'],
computed: {
isDanger() {
return this.item.type === 'danger'
},
props: [
'item'
],
computed: {
isDanger() {
return this.item.type === 'danger'
},
isSuccess() {
return this.item.type === 'success'
},
},
data() {
return {
isActive: 1
}
isSuccess() {
return this.item.type === 'success'
},
created() {
setTimeout(() => (this.isActive = 0), 6000)
},
data() {
return {
isActive: 1,
}
}
},
created() {
setTimeout(() => (this.isActive = 0), 6000)
},
}
</script>
<style lang="scss" scoped>
.bar-animation {
animation: progressbar 6s linear;
}
.bar-animation {
animation: progressbar 6s linear;
}
@keyframes progressbar {
0% {
width: 0;
}
100% {
width: 100%;
}
}
@keyframes progressbar {
0% {
width: 0;
}
100% {
width: 100%;
}
}
.fade-enter-active,
.fade-leave-active {
transition: 0.3s ease;
}
.fade-enter-active,
.fade-leave-active {
transition: 0.3s ease;
}
.fade-enter,
.fade-leave-to {
opacity: 0;
transform: translateX(100%)
}
.fade-enter,
.fade-leave-to {
opacity: 0;
transform: translateX(100%);
}
</style>

View File

@@ -1,24 +1,24 @@
<template>
<div v-if="notifications.length > 0" class="fixed lg:bottom-8 bottom-4 lg:right-8 right-4 md:left-auto left-4 z-50">
<ToasterItem :item="item" v-for="(item, i) in notifications" :key="i"/>
<div v-if="notifications.length > 0" class="fixed bottom-4 right-4 left-4 z-50 md:left-auto lg:bottom-8 lg:right-8">
<ToasterItem :item="item" v-for="(item, i) in notifications" :key="i" />
</div>
</template>
<script>
import ToasterItem from "./ToasterItem";
import {events} from '../../../bus'
import ToasterItem from './ToasterItem'
import { events } from '../../../bus'
export default {
components: {
ToasterItem,
},
data() {
return {
notifications: []
}
},
created() {
events.$on('toaster', notification => this.notifications.push(notification))
export default {
components: {
ToasterItem,
},
data() {
return {
notifications: [],
}
}
},
created() {
events.$on('toaster', (notification) => this.notifications.push(notification))
},
}
</script>

View File

@@ -10,79 +10,74 @@
</template>
<script>
import { ChevronLeftIcon } from 'vue-feather-icons'
import { ChevronLeftIcon } from 'vue-feather-icons'
export default {
name: 'PageHeader',
props: [
'title', 'canBack'
],
components: {
ChevronLeftIcon
},
}
export default {
name: 'PageHeader',
props: ['title', 'canBack'],
components: {
ChevronLeftIcon,
},
}
</script>
<style lang="scss" scoped>
@import '../../../sass/vuefilemanager/variables';
@import '../../../sass/vuefilemanager/mixins';
@import '../../../sass/vuefilemanager/variables';
@import '../../../sass/vuefilemanager/mixins';
.page-header {
display: flex;
align-items: center;
background: white;
z-index: 9;
width: 100%;
position: sticky;
top: 0;
padding-top: 20px;
padding-bottom: 20px;
.title {
@include font-size(18);
font-weight: 700;
color: $text;
}
.go-back {
margin-right: 10px;
cursor: pointer;
svg {
vertical-align: middle;
margin-top: -4px;
}
}
}
@media only screen and (max-width: 960px) {
.page-header {
display: flex;
align-items: center;
background: white;
z-index: 9;
width: 100%;
position: sticky;
top: 0;
padding-top: 20px;
padding-bottom: 20px;
.title {
@include font-size(18);
font-weight: 700;
color: $text;
}
.go-back {
margin-right: 10px;
cursor: pointer;
svg {
vertical-align: middle;
margin-top: -4px;
}
}
}
}
@media only screen and (max-width: 960px) {
@media only screen and (max-width: 690px) {
.page-header {
display: none;
}
}
.page-header {
.dark {
.page-header {
background: $dark_mode_background;
.title {
@include font-size(18);
}
}
}
@media only screen and (max-width: 690px) {
.page-header {
display: none;
}
}
.dark {
.page-header {
background: $dark_mode_background;
.title {
color: $dark_mode_text_primary;
}
.icon path {
fill: $theme;
}
.title {
color: $dark_mode_text_primary;
}
.icon path {
fill: $theme;
}
}
}
</style>

View File

@@ -1,22 +1,19 @@
<template>
<div class="flex items-center justify-between py-4 border-b dark:border-opacity-5 border-light border-dashed">
<div>
<img :src="$getPaymentLogo(driver)" :alt="driver" class="h-6">
<small class="text-xs text-gray-500 pt-2 leading-4 block">
{{ description }}
</small>
</div>
<div v-if="$slots.default" class="bg-theme-200 inline-block px-3 py-1 rounded-lg relative">
<slot></slot>
</div>
</div>
<div class="flex items-center justify-between border-b border-dashed border-light py-4 dark:border-opacity-5">
<div>
<img :src="$getPaymentLogo(driver)" :alt="driver" class="h-6" />
<small class="block pt-2 text-xs leading-4 text-gray-500">
{{ description }}
</small>
</div>
<div v-if="$slots.default" class="bg-theme-200 relative inline-block rounded-lg px-3 py-1">
<slot></slot>
</div>
</div>
</template>
<script>
export default {
name: 'PaymentMethod',
props: [
'description',
'driver',
]
}
export default {
name: 'PaymentMethod',
props: ['description', 'driver'],
}
</script>

View File

@@ -7,7 +7,9 @@
<hard-drive-icon class="text-theme" size="26" />
</div>
<h1 class="title">{{ plan.data.attributes.name }}</h1>
<h2 class="description">{{ plan.data.attributes.description }}</h2>
<h2 class="description">
{{ plan.data.attributes.description }}
</h2>
</header>
<section class="plan-features">
<b class="storage-size">{{ plan.data.attributes.capacity_formatted }}</b>
@@ -28,191 +30,184 @@
</template>
<script>
import ButtonBase from "../FilesView/ButtonBase";
import {HardDriveIcon} from "vue-feather-icons"
import { mapGetters } from 'vuex'
import axios from 'axios'
import ButtonBase from '../FilesView/ButtonBase'
import { HardDriveIcon } from 'vue-feather-icons'
import { mapGetters } from 'vuex'
import axios from 'axios'
export default {
name: 'PlanPricingTables',
components: {
HardDriveIcon,
ButtonBase,
},
props: [
'customRoute'
],
data() {
return {
plans: undefined,
}
},
computed: {
...mapGetters(['user']),
},
methods: {
selectPlan(plan) {
this.$emit('selected-plan', plan)
let route = this.customRoute ? this.customRoute : 'UpgradeBilling'
this.$router.push({name: route})
}
},
created() {
axios.get('/api/pricing')
.then(response => {
this.plans = response.data.filter(plan => {
return plan.data.attributes.capacity > this.user.data.attributes.max_storage_amount
})
this.$emit('load', false)
})
export default {
name: 'PlanPricingTables',
components: {
HardDriveIcon,
ButtonBase,
},
props: ['customRoute'],
data() {
return {
plans: undefined,
}
}
},
computed: {
...mapGetters(['user']),
},
methods: {
selectPlan(plan) {
this.$emit('selected-plan', plan)
let route = this.customRoute ? this.customRoute : 'UpgradeBilling'
this.$router.push({ name: route })
},
},
created() {
axios.get('/api/pricing').then((response) => {
this.plans = response.data.filter((plan) => {
return plan.data.attributes.capacity > this.user.data.attributes.max_storage_amount
})
this.$emit('load', false)
})
},
}
</script>
<style lang="scss" scoped>
@import '../../../sass/vuefilemanager/variables';
@import '../../../sass/vuefilemanager/mixins';
@import '../../../sass/vuefilemanager/variables';
@import '../../../sass/vuefilemanager/mixins';
.plan {
text-align: center;
flex: 0 0 33%;
padding: 0 25px;
margin-bottom: 45px;
.plan {
text-align: center;
flex: 0 0 33%;
padding: 0 25px;
margin-bottom: 45px;
.plan-wrapper {
box-shadow: 0 7px 20px 5px hsla(220, 36%, 16%, 0.03);
padding: 25px;
border-radius: 8px;
@include transition;
.plan-wrapper {
box-shadow: 0 7px 20px 5px hsla(220, 36%, 16%, 0.03);
padding: 25px;
border-radius: 8px;
@include transition;
&:hover {
@include transform(translateY(-20px) scale(1.05));
box-shadow: 0 15px 25px 5px hsla(220, 36%, 16%, 0.08);
&:hover {
@include transform(translateY(-20px) scale(1.05));
box-shadow: 0 15px 25px 5px hsla(220, 36%, 16%, 0.08);
}
}
.plan-header {
.icon {
path,
line,
polyline,
rect,
circle {
color: inherit;
}
}
.plan-header {
.title {
@include font-size(22);
font-weight: 800;
}
.icon {
path, line, polyline, rect, circle {
color: inherit;
}
.description {
@include font-size(14);
font-weight: 600;
}
}
.plan-features {
margin: 65px 0;
.storage-size {
@include font-size(48);
font-weight: 900;
line-height: 1.1;
}
.storage-description {
display: block;
@include font-size(15);
font-weight: 800;
}
}
.plan-footer {
.sign-in-button {
width: 100%;
text-align: center;
}
.price {
@include font-size(18);
display: block;
margin-bottom: 20px;
.vat-disclaimer {
@include font-size(11);
color: $text;
display: block;
font-weight: 300;
opacity: 0.45;
margin-top: 5px;
}
}
}
}
.plans-wrapper {
display: flex;
flex-wrap: wrap;
margin: 0 -25px;
justify-content: center;
}
@media only screen and (max-width: 960px) {
.plans-wrapper {
display: block;
margin: 0;
}
}
.dark {
.plan {
.plan-wrapper {
background: $dark_mode_foreground;
}
.plan-header {
.title {
@include font-size(22);
font-weight: 800;
color: $dark_mode_text_primary;
}
.description {
@include font-size(14);
font-weight: 600;
color: $dark_mode_text_secondary;
}
}
.plan-features {
margin: 65px 0;
.storage-size {
@include font-size(48);
font-weight: 900;
line-height: 1.1;
color: $dark_mode_text_primary;
}
.storage-description {
display: block;
@include font-size(15);
font-weight: 800;
color: $dark_mode_text_primary;
}
}
.plan-footer {
.sign-in-button {
width: 100%;
text-align: center;
background: rgba($theme, 0.1);
/deep/ .content {
color: $theme;
}
}
.price {
@include font-size(18);
display: block;
margin-bottom: 20px;
.vat-disclaimer {
@include font-size(11);
color: $text;
display: block;
font-weight: 300;
opacity: 0.45;
margin-top: 5px;
}
}
}
}
.plans-wrapper {
display: flex;
flex-wrap: wrap;
margin: 0 -25px;
justify-content: center;
}
@media only screen and (max-width: 960px) {
.plans-wrapper {
display: block;
margin: 0;
}
}
.dark {
.plan {
.plan-wrapper {
background: $dark_mode_foreground;
}
.plan-header {
.title {
color: $dark_mode_text_primary;
}
.description {
color: $dark_mode_text_secondary;
}
}
.plan-features {
.storage-size {
color: $dark_mode_text_primary;
}
.storage-description {
color: $dark_mode_text_primary;
}
}
.plan-footer {
.sign-in-button {
background: rgba($theme, 0.1);
/deep/ .content {
color: $theme;
}
}
.price {
.vat-disclaimer {
color: $dark_mode_text_primary;
}
color: $dark_mode_text_primary;
}
}
}
}
}
</style>

View File

@@ -1,83 +1,73 @@
<template>
<PopupWrapper>
<div class="text-center h-full flex items-center justify-center px-8 transform md:translate-y-0 -translate-y-7">
<div>
<img src="https://twemoji.maxcdn.com/v/13.1.0/svg/1f914.svg" alt="" class="w-20 mx-auto md:mt-6 mb-4">
<div class="flex h-full -translate-y-7 transform items-center justify-center px-8 text-center md:translate-y-0">
<div>
<img src="https://twemoji.maxcdn.com/v/13.1.0/svg/1f914.svg" alt="" class="mx-auto mb-4 w-20 md:mt-6" />
<h1 v-if="title" class="text-2xl font-bold mb-2">
{{ title }}
</h1>
<p v-if="message" class="text-sm mb-4">
{{ message }}
</p>
</div>
<h1 v-if="title" class="mb-2 text-2xl font-bold">
{{ title }}
</h1>
<p v-if="message" class="mb-4 text-sm">
{{ message }}
</p>
</div>
</div>
<PopupActions>
<ButtonBase
@click.native="closePopup"
button-style="secondary"
class="w-full"
>{{ $t('global.cancel') }}
</ButtonBase>
<ButtonBase
@click.native="confirm"
:button-style="buttonColor"
class="w-full"
>{{ $t('global.confirm_action') }}
</ButtonBase>
<ButtonBase @click.native="closePopup" button-style="secondary" class="w-full">{{ $t('global.cancel') }} </ButtonBase>
<ButtonBase @click.native="confirm" :button-style="buttonColor" class="w-full">{{ $t('global.confirm_action') }} </ButtonBase>
</PopupActions>
</PopupWrapper>
</template>
<script>
import PopupWrapper from "./PopupWrapper";
import PopupActions from "./PopupActions";
import ButtonBase from "../../FilesView/ButtonBase";
import {events} from '../../../bus'
import PopupWrapper from './PopupWrapper'
import PopupActions from './PopupActions'
import ButtonBase from '../../FilesView/ButtonBase'
import { events } from '../../../bus'
export default {
name: 'ConfirmPopup',
components: {
PopupWrapper,
PopupActions,
ButtonBase,
},
data() {
return {
confirmationData: [],
message: undefined,
title: undefined,
buttonColor: undefined,
}
},
methods: {
closePopup() {
events.$emit('popup:close')
},
confirm() {
// Close popup
events.$emit('popup:close')
// Confirmation popup
events.$emit('action:confirmed', this.confirmationData)
// Clear confirmation data
this.confirmationData = []
}
},
mounted() {
// Show confirm
events.$on('confirm:open', args => {
this.title = args.title
this.message = args.message
this.confirmationData = args.action
this.buttonColor = 'danger'
if (args.buttonColor) {
this.buttonColor = args.buttonColor
}
})
export default {
name: 'ConfirmPopup',
components: {
PopupWrapper,
PopupActions,
ButtonBase,
},
data() {
return {
confirmationData: [],
message: undefined,
title: undefined,
buttonColor: undefined,
}
}
},
methods: {
closePopup() {
events.$emit('popup:close')
},
confirm() {
// Close popup
events.$emit('popup:close')
// Confirmation popup
events.$emit('action:confirmed', this.confirmationData)
// Clear confirmation data
this.confirmationData = []
},
},
mounted() {
// Show confirm
events.$on('confirm:open', (args) => {
this.title = args.title
this.message = args.message
this.confirmationData = args.action
this.buttonColor = 'danger'
if (args.buttonColor) {
this.buttonColor = args.buttonColor
}
})
},
}
</script>

View File

@@ -1,11 +1,11 @@
<template>
<div class="md:relative absolute bottom-0 left-0 right-0 flex items-center space-x-4 px-6 py-4 pb-6">
<div class="absolute bottom-0 left-0 right-0 flex items-center space-x-4 px-6 py-4 pb-6 md:relative">
<slot />
</div>
</template>
<script>
export default {
name: 'PopupActions',
}
export default {
name: 'PopupActions',
}
</script>

View File

@@ -1,17 +1,12 @@
<template>
<div
:class="type"
class="md:relative md:top-0 md:bottom-0 absolute top-16 bottom-24 left-0 right-0 h-auto overflow-y-auto overflow-x-auto px-6"
>
<div :class="type" class="absolute top-16 bottom-24 left-0 right-0 h-auto overflow-x-auto overflow-y-auto px-6 md:relative md:top-0 md:bottom-0">
<slot />
</div>
</template>
<script>
export default {
name: 'PopupContent',
props: [
'type'
]
}
</script>
export default {
name: 'PopupContent',
props: ['type'],
}
</script>

View File

@@ -1,50 +1,48 @@
<template>
<div class="flex items-center justify-between px-6 pt-6 pb-6">
<div class="flex items-center">
<div class="mr-3">
<corner-down-right-icon v-if="icon === 'move'" size="18" class="vue-feather text-theme" />
<share-icon v-if="icon === 'share'" size="18" class="vue-feather text-theme" />
<edit2-icon v-if="icon === 'edit'" size="18" class="vue-feather text-theme" />
<key-icon v-if="icon === 'key'" size="18" class="vue-feather text-theme" />
<users-icon v-if="icon === 'users'" size="18" class="vue-feather text-theme" />
<user-plus-icon v-if="icon === 'user-plus'" size="18" class="vue-feather text-theme" />
<credit-card-icon v-if="icon === 'credit-card'" size="18" class="vue-feather text-theme" />
</div>
<div class="mr-3">
<corner-down-right-icon v-if="icon === 'move'" size="18" class="vue-feather text-theme" />
<share-icon v-if="icon === 'share'" size="18" class="vue-feather text-theme" />
<edit2-icon v-if="icon === 'edit'" size="18" class="vue-feather text-theme" />
<key-icon v-if="icon === 'key'" size="18" class="vue-feather text-theme" />
<users-icon v-if="icon === 'users'" size="18" class="vue-feather text-theme" />
<user-plus-icon v-if="icon === 'user-plus'" size="18" class="vue-feather text-theme" />
<credit-card-icon v-if="icon === 'credit-card'" size="18" class="vue-feather text-theme" />
</div>
<b class="font-bold text-base">
{{ title }}
</b>
<b class="text-base font-bold">
{{ title }}
</b>
</div>
<div @click="closePopup" class="cursor-pointer p-3 -m-3">
<div @click="closePopup" class="-m-3 cursor-pointer p-3">
<x-icon size="14" class="hover-text-theme vue-feather" />
</div>
</div>
</template>
<script>
import {CreditCardIcon, KeyIcon, UserPlusIcon, CornerDownRightIcon, LinkIcon, XIcon, Edit2Icon, ShareIcon, UsersIcon} from 'vue-feather-icons'
import {events} from '../../../bus'
import { CreditCardIcon, KeyIcon, UserPlusIcon, CornerDownRightIcon, LinkIcon, XIcon, Edit2Icon, ShareIcon, UsersIcon } from 'vue-feather-icons'
import { events } from '../../../bus'
export default {
name: 'PopupHeader',
props: [
'title', 'icon'
],
components: {
CornerDownRightIcon,
CreditCardIcon,
UserPlusIcon,
UsersIcon,
ShareIcon,
Edit2Icon,
LinkIcon,
KeyIcon,
XIcon,
export default {
name: 'PopupHeader',
props: ['title', 'icon'],
components: {
CornerDownRightIcon,
CreditCardIcon,
UserPlusIcon,
UsersIcon,
ShareIcon,
Edit2Icon,
LinkIcon,
KeyIcon,
XIcon,
},
methods: {
closePopup() {
events.$emit('popup:close')
},
methods: {
closePopup() {
events.$emit('popup:close')
}
}
}
},
}
</script>

View File

@@ -1,10 +1,6 @@
<template>
<transition name="popup">
<div
class="popup lg:absolute fixed top-0 left-0 right-0 bottom-0 overflow-y-auto grid h-full p-10"
@click.self="closePopup"
v-if="isVisibleWrapper"
>
<div class="popup fixed top-0 left-0 right-0 bottom-0 grid h-full overflow-y-auto p-10 lg:absolute" @click.self="closePopup" v-if="isVisibleWrapper">
<div class="popup-wrapper">
<slot></slot>
</div>
@@ -13,140 +9,132 @@
</template>
<script>
import {events} from '../../../bus'
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
if( (this.name !== name))
this.isVisibleWrapper = false
})
// Open called popup
events.$on('confirm:open', ({name}) => {
if (this.name === name)
this.isVisibleWrapper = true
})
// Close popup
events.$on('popup:close', () => this.isVisibleWrapper = false)
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
if (this.name !== name) this.isVisibleWrapper = false
})
// Open called popup
events.$on('confirm:open', ({ name }) => {
if (this.name === name) this.isVisibleWrapper = true
})
// Close popup
events.$on('popup:close', () => (this.isVisibleWrapper = false))
},
}
</script>
<style lang="scss" scoped>
@import '../../../../sass/vuefilemanager/variables';
@import '../../../../sass/vuefilemanager/mixins';
@import '../../../../sass/vuefilemanager/variables';
@import '../../../../sass/vuefilemanager/mixins';
.popup {
z-index: 41;
}
.popup {
z-index: 41;
}
.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;
}
}
@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 only screen and (max-width: 690px) {
.popup {
overflow: hidden;
}
.popup-wrapper {
box-shadow: $light_mode_popup_shadow;
border-radius: 8px;
background: white;
margin: auto;
width: 480px;
z-index: 12;
position: fixed;
top: 0;
bottom: 0;
right: 0;
left: 0;
transform: translateY(0) scale(1);
box-shadow: none;
width: 100%;
border-radius: 0px;
}
// 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;
}
// Animations
.popup-enter-active {
animation: popup-slide-in 0.35s 0.15s ease both;
}
@keyframes popup-in {
0% {
opacity: 0;
transform: scale(0.7);
}
100% {
opacity: 1;
transform: scale(1);
}
.popup-leave-active {
animation: popup-slide-in 0.15s ease reverse;
}
}
@keyframes popup-slide-in {
0% {
transform: translateY(100%);
}
100% {
transform: translateY(0);
}
@media only screen and (max-width: 320px) {
.popup-wrapper {
overflow-y: auto;
}
}
@media only screen and (max-width: 690px) {
.popup {
overflow: hidden;
}
.popup-wrapper {
position: fixed;
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;
}
}
@media only screen and (max-width: 320px){
.popup-wrapper {
overflow-y: auto;
}
.dark {
.popup-wrapper {
background: $dark_mode_foreground;
box-shadow: $dark_mode_popup_shadow;
}
}
@media only screen and (max-width: 690px) {
.dark {
.popup-wrapper {
background: $dark_mode_foreground;
box-shadow: $dark_mode_popup_shadow;
background: $dark_mode_background;
}
}
@media only screen and (max-width: 690px) {
.dark {
.popup-wrapper {
background: $dark_mode_background;
}
}
}
}
</style>

View File

@@ -1,36 +1,41 @@
<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="mb-5" :item="pickedItem" :setFolderIcon="{emoji: emoji, color: null}" />
<ThumbnailItem class="mb-5" :item="pickedItem" :setFolderIcon="{ emoji: emoji, color: null }" />
<!--Form to set sharing-->
<ValidationObserver @submit.prevent="changeName" ref="renameForm" v-slot="{ invalid }" tag="form">
<!--Update item name-->
<ValidationProvider tag="div" mode="passive" name="Name" rules="required" v-slot="{ errors }">
<AppInputText :title="$t('popup_rename.label')" :error="errors[0]" :is-last="pickedItem.data.type !== 'folder'">
<div class="flex items-center relative">
<input v-model="pickedItem.data.attributes.name" :class="{'border-red': errors[0]}" ref="input" type="text" class="focus-border-theme input-dark" :placeholder="$t('popup_rename.placeholder')">
<div @click="pickedItem.data.attributes.name = ''" class="absolute right-4">
<x-icon class="close-icon hover-text-theme" size="14" />
</div>
</div>
</AppInputText>
<AppInputText :title="$t('popup_rename.label')" :error="errors[0]" :is-last="pickedItem.data.type !== 'folder'">
<div class="relative flex items-center">
<input
v-model="pickedItem.data.attributes.name"
:class="{ 'border-red': errors[0] }"
ref="input"
type="text"
class="focus-border-theme input-dark"
:placeholder="$t('popup_rename.placeholder')"
/>
<div @click="pickedItem.data.attributes.name = ''" class="absolute right-4">
<x-icon class="close-icon hover-text-theme" size="14" />
</div>
</div>
</AppInputText>
</ValidationProvider>
<!--Emoji-->
<AppInputSwitch v-if="pickedItem.data.type === 'folder'" :title="$t('Emoji as an Icon')" :description="$t('Replace folder icon with an Emoji')" :is-last="! isEmoji">
<SwitchInput v-model="isEmoji" :state="isEmoji" />
</AppInputSwitch>
<!--Emoji-->
<AppInputSwitch v-if="pickedItem.data.type === 'folder'" :title="$t('Emoji as an Icon')" :description="$t('Replace folder icon with an Emoji')" :is-last="!isEmoji">
<SwitchInput v-model="isEmoji" :state="isEmoji" />
</AppInputSwitch>
<!--Set emoji-->
<EmojiPicker v-if="pickedItem.data.type === 'folder' && isEmoji" v-model="emoji" :default-emoji="emoji"/>
<!--Set emoji-->
<EmojiPicker v-if="pickedItem.data.type === 'folder' && isEmoji" v-model="emoji" :default-emoji="emoji" />
</ValidationObserver>
</PopupContent>
@@ -47,30 +52,30 @@
</template>
<script>
import {ValidationProvider, ValidationObserver} from 'vee-validate/dist/vee-validate.full'
import PopupWrapper from "./Popup/PopupWrapper";
import { ValidationProvider, ValidationObserver } from 'vee-validate/dist/vee-validate.full'
import PopupWrapper from './Popup/PopupWrapper'
import PopupActions from './Popup/PopupActions'
import PopupContent from './Popup/PopupContent'
import PopupHeader from './Popup/PopupHeader'
import ThumbnailItem from "./ThumbnailItem";
import ButtonBase from "../FilesView/ButtonBase";
import AppInputSwitch from "../Admin/AppInputSwitch"
import AppInputText from "../Admin/AppInputText"
import {required} from 'vee-validate/dist/rules'
import SwitchInput from "./Forms/SwitchInput"
import EmojiPicker from "./EmojiPicker"
import {XIcon} from 'vue-feather-icons'
import {events} from '../../bus'
import ThumbnailItem from './ThumbnailItem'
import ButtonBase from '../FilesView/ButtonBase'
import AppInputSwitch from '../Admin/AppInputSwitch'
import AppInputText from '../Admin/AppInputText'
import { required } from 'vee-validate/dist/rules'
import SwitchInput from './Forms/SwitchInput'
import EmojiPicker from './EmojiPicker'
import { XIcon } from 'vue-feather-icons'
import { events } from '../../bus'
export default {
name: 'RenameItemPopup',
components: {
ValidationProvider,
ValidationObserver,
EmojiPicker,
AppInputSwitch,
SwitchInput,
AppInputText,
EmojiPicker,
AppInputSwitch,
SwitchInput,
AppInputText,
ThumbnailItem,
PopupWrapper,
PopupActions,
@@ -78,51 +83,47 @@ export default {
PopupHeader,
ButtonBase,
required,
XIcon
XIcon,
},
computed: {
itemTypeTitle() {
return this.pickedItem && this.pickedItem.data.type === 'folder'
? this.$t('types.folder')
: this.$t('types.file')
return this.pickedItem && this.pickedItem.data.type === 'folder' ? this.$t('types.folder') : this.$t('types.file')
},
},
watch: {
isEmoji(val) {
if (!val) {
events.$emit('setFolderIcon', { emoji: undefined })
this.emoji = undefined
} else {
events.$emit('setFolderIcon', { emoji: this.emoji })
}
},
emoji(val) {
events.$emit('setFolderIcon', {
emoji: val,
})
},
},
watch: {
isEmoji(val) {
if (! val) {
events.$emit('setFolderIcon', {emoji: undefined})
this.emoji = undefined
} else {
events.$emit('setFolderIcon', {emoji: this.emoji})
}
},
emoji(val) {
events.$emit('setFolderIcon', {
emoji: val
})
},
},
data() {
return {
pickedItem: undefined,
isEmoji: false,
emoji: undefined,
isEmoji: false,
emoji: undefined,
}
},
methods: {
changeName() {
if (this.pickedItem.data.attributes.name && this.pickedItem.data.attributes.name !== '') {
let item = {
id: this.pickedItem.data.id,
type: this.pickedItem.data.type,
name: this.pickedItem.data.attributes.name,
}
item['emoji'] = this.emoji || null
item['emoji'] = this.emoji || null
if (! this.isEmoji)
item['emoji'] = null
if (!this.isEmoji) item['emoji'] = null
// Rename item request
this.$store.dispatch('renameItem', item)
@@ -132,36 +133,34 @@ export default {
this.$closePopup()
}
}
},
},
mounted() {
// Show popup
events.$on('popup:open', args => {
events.$on('popup:open', (args) => {
if (args.name !== 'rename-item') return
this.isEmoji = false
this.isEmoji = false
if (!this.$isMobile()) {
if (!this.$isMobile()) {
this.$nextTick(() => this.$refs.input.focus())
}
// Set default emoji if exist
if (args.item.data.attributes.emoji) {
this.isEmoji = true
this.emoji = args.item.data.attributes.emoji
}
// Set default emoji if exist
if (args.item.data.attributes.emoji) {
this.isEmoji = true
this.emoji = args.item.data.attributes.emoji
}
// Store picked item
this.pickedItem = args.item
})
}
},
}
</script>
<style scoped lang="scss">
@import "../../../sass/vuefilemanager/inapp-forms";
@import '../../../sass/vuefilemanager/inapp-forms';
@import '../../../sass/vuefilemanager/forms';
.input {
@@ -200,7 +199,6 @@ export default {
.dark {
.close-icon-wrapper {
&:hover {
.close-icon {
line {
color: inherit !important;

View File

@@ -5,30 +5,29 @@
</template>
<script>
export default {
name: 'SectionTitle',
}
export default {
name: 'SectionTitle',
}
</script>
<style lang="scss" scoped>
@import '../../../sass/vuefilemanager/variables';
@import '../../../sass/vuefilemanager/mixins';
@import '../../../sass/vuefilemanager/variables';
@import '../../../sass/vuefilemanager/mixins';
.text-label {
@include font-size(12);
color: #afafaf;
font-weight: 700;
display: block;
margin-bottom: 20px;
}
@media only screen and (max-width: 1024px) {
}
.dark {
.text-label {
@include font-size(12);
color: #AFAFAF;
font-weight: 700;
display: block;
margin-bottom: 20px;
}
@media only screen and (max-width: 1024px) {
}
.dark {
.text-label {
color: $theme;
}
color: $theme;
}
}
</style>

View File

@@ -3,61 +3,50 @@
<PopupHeader :title="$t('Select Payment Method')" icon="credit-card" />
<PopupContent style="padding: 0 20px">
<!--PayPal implementation-->
<div
v-if="config.isPayPal"
:class="{
'mb-2 rounded-xl bg-light-background px-4 dark:bg-2x-dark-foreground': paypal.isMethodsLoaded,
}"
>
<PaymentMethod @click.native="pickedPaymentMethod('paypal')" driver="paypal" :description="config.paypal_payment_description">
<div v-if="paypal.isMethodLoading" class="translate-y-3 scale-50 transform">
<Spinner />
</div>
<span v-if="!paypal.isMethodsLoaded" :class="{ 'opacity-0': paypal.isMethodLoading }" class="text-theme cursor-pointer text-sm font-bold">
{{ $t('Select') }}
</span>
</PaymentMethod>
<!--PayPal implementation-->
<div
v-if="config.isPayPal"
:class="{'dark:bg-2x-dark-foreground bg-light-background rounded-xl px-4 mb-2': paypal.isMethodsLoaded}"
>
<PaymentMethod
@click.native="pickedPaymentMethod('paypal')"
driver="paypal"
:description="config.paypal_payment_description"
>
<div v-if="paypal.isMethodLoading" class="transform scale-50 translate-y-3">
<Spinner />
</div>
<span v-if="! paypal.isMethodsLoaded" :class="{'opacity-0': paypal.isMethodLoading}" class="text-sm text-theme font-bold cursor-pointer">
{{ $t('Select') }}
</span>
</PaymentMethod>
<!--PayPal Buttons-->
<div id="paypal-button-container"></div>
</div>
<!--PayPal Buttons-->
<div id="paypal-button-container"></div>
</div>
<!--Paystack implementation-->
<PaymentMethod
v-if="config.isPaystack"
driver="paystack"
:description="config.paystack_payment_description"
>
<paystack
@click.native="pickedPaymentMethod('paystack')"
v-if="user && config"
:channels="['card', 'bank', 'ussd', 'qr', 'mobile_money', 'bank_transfer']"
class="font-bold"
currency="ZAR"
:amount="singleChargeAmount * 100"
:email="user.data.attributes.email"
:paystackkey="config.paystack_public_key"
:reference="$generatePaystackReference()"
:callback="paystackPaymentSuccessful"
:close="paystackClosed"
>
<span class="text-sm text-theme font-bold cursor-pointer">
{{ $t('Select') }}
</span>
</paystack>
</PaymentMethod>
<!--Paystack implementation-->
<PaymentMethod v-if="config.isPaystack" driver="paystack" :description="config.paystack_payment_description">
<paystack
@click.native="pickedPaymentMethod('paystack')"
v-if="user && config"
:channels="['card', 'bank', 'ussd', 'qr', 'mobile_money', 'bank_transfer']"
class="font-bold"
currency="ZAR"
:amount="singleChargeAmount * 100"
:email="user.data.attributes.email"
:paystackkey="config.paystack_public_key"
:reference="$generatePaystackReference()"
:callback="paystackPaymentSuccessful"
:close="paystackClosed"
>
<span class="text-theme cursor-pointer text-sm font-bold">
{{ $t('Select') }}
</span>
</paystack>
</PaymentMethod>
</PopupContent>
<PopupActions>
<ButtonBase
class="w-full"
@click.native="$closePopup()"
button-style="secondary"
>
<ButtonBase class="w-full" @click.native="$closePopup()" button-style="secondary">
{{ $t('Cancel Payment') }}
</ButtonBase>
</PopupActions>
@@ -65,117 +54,117 @@
</template>
<script>
import PopupWrapper from "./Popup/PopupWrapper";
import PopupActions from "./Popup/PopupActions";
import PopupContent from "./Popup/PopupContent";
import PopupHeader from "./Popup/PopupHeader";
import ButtonBase from "../FilesView/ButtonBase";
import { loadScript } from "@paypal/paypal-js"
import PaymentMethod from "./PaymentMethod"
import Spinner from "../FilesView/Spinner"
import {events} from '../../bus'
import PopupWrapper from './Popup/PopupWrapper'
import PopupActions from './Popup/PopupActions'
import PopupContent from './Popup/PopupContent'
import PopupHeader from './Popup/PopupHeader'
import ButtonBase from '../FilesView/ButtonBase'
import { loadScript } from '@paypal/paypal-js'
import PaymentMethod from './PaymentMethod'
import Spinner from '../FilesView/Spinner'
import { events } from '../../bus'
import paystack from 'vue-paystack'
import {mapGetters} from "vuex"
import { mapGetters } from 'vuex'
export default {
name: "SelectSingleChargeMethodPopup",
components: {
PaymentMethod,
PopupWrapper,
PopupActions,
PopupContent,
PopupHeader,
ButtonBase,
paystack,
Spinner,
},
data() {
return {
paypal: {
isMethodsLoaded: false,
isMethodLoading: false,
}
}
},
computed: {
...mapGetters([
'singleChargeAmount',
'config',
'user',
]),
},
methods: {
pickedPaymentMethod(driver) {
if (driver === 'paystack') {
this.$closePopup()
}
if (driver === 'paypal' && !this.paypal.isMethodsLoaded) {
this.PayPalInitialization()
}
},
async PayPalInitialization() {
this.paypal.isMethodLoading = true
name: 'SelectSingleChargeMethodPopup',
components: {
PaymentMethod,
PopupWrapper,
PopupActions,
PopupContent,
PopupHeader,
ButtonBase,
paystack,
Spinner,
},
data() {
return {
paypal: {
isMethodsLoaded: false,
isMethodLoading: false,
},
}
},
computed: {
...mapGetters(['singleChargeAmount', 'config', 'user']),
},
methods: {
pickedPaymentMethod(driver) {
if (driver === 'paystack') {
this.$closePopup()
}
if (driver === 'paypal' && !this.paypal.isMethodsLoaded) {
this.PayPalInitialization()
}
},
async PayPalInitialization() {
this.paypal.isMethodLoading = true
let paypal;
let paypal
try {
paypal = await loadScript({
'client-id': this.config.paypal_client_id,
'vault': true,
});
} catch (error) {
events.$emit('toaster', {
type: 'danger',
message: this.$t('Failed to load the PayPal service'),
})
}
try {
paypal = await loadScript({
'client-id': this.config.paypal_client_id,
vault: true,
})
} catch (error) {
events.$emit('toaster', {
type: 'danger',
message: this.$t('Failed to load the PayPal service'),
})
}
const userId = this.user.data.id
const amount = this.singleChargeAmount
const userId = this.user.data.id
const amount = this.singleChargeAmount
this.paypal.isMethodsLoaded = true
this.paypal.isMethodLoading = false
this.paypal.isMethodsLoaded = true
this.paypal.isMethodLoading = false
// Initialize paypal buttons for single charge
await paypal.Buttons({
createOrder: function(data, actions) {
return actions.order.create({
purchase_units: [{
amount: {
value: amount,
},
custom_id: userId
}]
});
}
}).render('#paypal-button-container');
},
paystackPaymentSuccessful() {
this.$closePopup()
// Initialize paypal buttons for single charge
await paypal
.Buttons({
createOrder: function (data, actions) {
return actions.order.create({
purchase_units: [
{
amount: {
value: amount,
},
custom_id: userId,
},
],
})
},
})
.render('#paypal-button-container')
},
paystackPaymentSuccessful() {
this.$closePopup()
events.$emit('toaster', {
type: 'success',
message: this.$t('Your payment was successfully received.'),
})
},
paystackClosed() {
// ...
}
},
created() {
events.$on('popup:close', () => this.paypal.isMethodsLoaded = false)
}
events.$emit('toaster', {
type: 'success',
message: this.$t('Your payment was successfully received.'),
})
},
paystackClosed() {
// ...
},
},
created() {
events.$on('popup:close', () => (this.paypal.isMethodsLoaded = false))
},
}
</script>
<style lang="scss" scoped>
@import '../../../sass/vuefilemanager/variables';
@import '../../../sass/vuefilemanager/mixins';
@import '../../../sass/vuefilemanager/variables';
@import '../../../sass/vuefilemanager/mixins';
.mobile-actions {
white-space: nowrap;
overflow-x: auto;
margin: 0 -20px;
padding: 10px 0 10px 20px;
}
.mobile-actions {
white-space: nowrap;
overflow-x: auto;
margin: 0 -20px;
padding: 10px 0 10px 20px;
}
</style>

View File

@@ -1,72 +1,81 @@
<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>
<!--Item Thumbnail-->
<ThumbnailItem class="mb-5" :item="pickedItem" />
<!--Form to set sharing-->
<ValidationObserver v-if="! isGeneratedShared" @submit.prevent ref="shareForm" v-slot="{ invalid }" tag="form">
<ValidationObserver v-if="!isGeneratedShared" @submit.prevent ref="shareForm" v-slot="{ invalid }" tag="form">
<!--Permission Select-->
<ValidationProvider v-if="isFolder" tag="div" mode="passive" name="Permission" rules="required" v-slot="{ errors }">
<AppInputText :title="$t('shared_form.label_permission')" :error="errors[0]">
<SelectInput v-model="shareOptions.permission" :options="$translateSelectOptions(permissionOptions)" :placeholder="$t('shared_form.placeholder_permission')" :isError="errors[0]" />
</AppInputText>
</ValidationProvider>
<ValidationProvider v-if="isFolder" tag="div" mode="passive" name="Permission" rules="required" v-slot="{ errors }">
<AppInputText :title="$t('shared_form.label_permission')" :error="errors[0]">
<SelectInput
v-model="shareOptions.permission"
:options="$translateSelectOptions(permissionOptions)"
:placeholder="$t('shared_form.placeholder_permission')"
:isError="errors[0]"
/>
</AppInputText>
</ValidationProvider>
<!--Password Switch-->
<div>
<AppInputSwitch :title="$t('shared_form.label_password_protection')" :description="$t('popup.share.password_description')">
<SwitchInput v-model="shareOptions.isPassword" class="switch" :state="shareOptions.isPassword" />
</AppInputSwitch>
<div>
<AppInputSwitch :title="$t('shared_form.label_password_protection')" :description="$t('popup.share.password_description')">
<SwitchInput v-model="shareOptions.isPassword" class="switch" :state="shareOptions.isPassword" />
</AppInputSwitch>
<!--Set password-->
<ValidationProvider v-if="shareOptions.isPassword" tag="div" mode="passive" name="Password" rules="required" v-slot="{ errors }">
<AppInputText :error="errors[0]" class="-mt-2">
<input v-model="shareOptions.password" :class="{'border-red': errors[0]}" type="text" class="focus-border-theme input-dark" :placeholder="$t('page_sign_in.placeholder_password')">
</AppInputText>
</ValidationProvider>
</div>
<!--Set password-->
<ValidationProvider v-if="shareOptions.isPassword" tag="div" mode="passive" name="Password" rules="required" v-slot="{ errors }">
<AppInputText :error="errors[0]" class="-mt-2">
<input
v-model="shareOptions.password"
:class="{ 'border-red': errors[0] }"
type="text"
class="focus-border-theme input-dark"
:placeholder="$t('page_sign_in.placeholder_password')"
/>
</AppInputText>
</ValidationProvider>
</div>
<!--Expiration switch-->
<div>
<AppInputSwitch :title="$t('expiration')" :description="$t('popup.share.expiration_description')">
<SwitchInput v-model="isExpiration" class="switch" :state="isExpiration" />
</AppInputSwitch>
<div>
<AppInputSwitch :title="$t('expiration')" :description="$t('popup.share.expiration_description')">
<SwitchInput v-model="isExpiration" class="switch" :state="isExpiration" />
</AppInputSwitch>
<!--Set expiration-->
<AppInputText v-if="isExpiration" class="-mt-2">
<AppInputText v-if="isExpiration" class="-mt-2">
<SelectBoxInput v-model="shareOptions.expiration" :data="$translateSelectOptions(expirationList)" class="box" />
</AppInputText>
</div>
</AppInputText>
</div>
<!--Send on emails switch-->
<div>
<AppInputSwitch :title="$t('popup.share.email_send')" :description="$t('popup.share.email_description')" :is-last="! isEmailSharing">
<SwitchInput v-model="isEmailSharing" class="switch" :state="isEmailSharing" />
</AppInputSwitch>
<div>
<AppInputSwitch :title="$t('popup.share.email_send')" :description="$t('popup.share.email_description')" :is-last="!isEmailSharing">
<SwitchInput v-model="isEmailSharing" class="switch" :state="isEmailSharing" />
</AppInputSwitch>
<!--Set expiration-->
<ValidationProvider v-if="isEmailSharing" tag="div" mode="passive" name="Email" rules="required" v-slot="{ errors }" class="-mt-2">
<MultiEmailInput rules="required" v-model="shareOptions.emails" :label="$t('shared_form.recipients_label')" :isError="errors[0]" />
</ValidationProvider>
</div>
<MultiEmailInput rules="required" v-model="shareOptions.emails" :label="$t('shared_form.recipients_label')" :isError="errors[0]" />
</ValidationProvider>
</div>
</ValidationObserver>
<!--Copy generated link-->
<AppInputText v-if="isGeneratedShared" :title="$t('shared_form.label_share_vie_email')" :is-last="true">
<CopyShareLink :item="pickedItem" />
</AppInputText>
<AppInputText v-if="isGeneratedShared" :title="$t('shared_form.label_share_vie_email')" :is-last="true">
<CopyShareLink :item="pickedItem" />
</AppInputText>
</PopupContent>
<!--Actions-->
<PopupActions>
<ButtonBase v-if="! isGeneratedShared" class="w-full" @click.native="$closePopup()" button-style="secondary">
<ButtonBase v-if="!isGeneratedShared" class="w-full" @click.native="$closePopup()" button-style="secondary">
{{ $t('popup_move_item.cancel') }}
</ButtonBase>
<ButtonBase class="w-full" @click.native="submitShareOptions" button-style="theme" :loading="isLoading" :disabled="isLoading">
@@ -77,26 +86,26 @@
</template>
<script>
import AppInputText from "../Admin/AppInputText";
import AppInputSwitch from "../Admin/AppInputSwitch";
import {ValidationProvider, ValidationObserver} from 'vee-validate/dist/vee-validate.full'
import SelectBoxInput from "./Forms/SelectBoxInput";
import PopupWrapper from "./Popup/PopupWrapper";
import AppInputText from '../Admin/AppInputText'
import AppInputSwitch from '../Admin/AppInputSwitch'
import { ValidationProvider, ValidationObserver } from 'vee-validate/dist/vee-validate.full'
import SelectBoxInput from './Forms/SelectBoxInput'
import PopupWrapper from './Popup/PopupWrapper'
import PopupActions from './Popup/PopupActions'
import PopupContent from './Popup/PopupContent'
import PopupHeader from './Popup/PopupHeader'
import MultiEmailInput from "./Forms/MultiEmailInput";
import SwitchInput from "./Forms/SwitchInput";
import SelectInput from "./Forms/SelectInput";
import ThumbnailItem from "./ThumbnailItem";
import ActionButton from "./ActionButton";
import CopyShareLink from "./Forms/CopyShareLink";
import ButtonBase from "../FilesView/ButtonBase";
import InfoBox from "./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 MultiEmailInput from './Forms/MultiEmailInput'
import SwitchInput from './Forms/SwitchInput'
import SelectInput from './Forms/SelectInput'
import ThumbnailItem from './ThumbnailItem'
import ActionButton from './ActionButton'
import CopyShareLink from './Forms/CopyShareLink'
import ButtonBase from '../FilesView/ButtonBase'
import InfoBox from './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 {
@@ -104,8 +113,8 @@ export default {
components: {
ValidationProvider,
ValidationObserver,
AppInputText,
AppInputSwitch,
AppInputText,
AppInputSwitch,
SelectBoxInput,
ThumbnailItem,
ActionButton,
@@ -121,13 +130,10 @@ export default {
MailIcon,
required,
LinkIcon,
InfoBox
InfoBox,
},
computed: {
...mapGetters([
'permissionOptions',
'expirationList'
]),
...mapGetters(['permissionOptions', 'expirationList']),
itemTypeTitle() {
return this.pickedItem && this.pickedItem.data.type === 'folder' ? this.$t('types.folder') : this.$t('types.file')
},
@@ -136,17 +142,17 @@ export default {
},
submitButtonText() {
return this.isGeneratedShared ? this.$t('shared_form.button_done') : this.$t('shared_form.button_generate')
}
},
},
watch: {
isExpiration(val) {
if (!val) this.shareOptions.expiration = undefined
},
},
watch: {
isExpiration(val) {
if (! val) this.shareOptions.expiration = undefined
}
},
data() {
return {
isExpiration: false,
isEmailSharing: false,
isExpiration: false,
isEmailSharing: false,
shareOptions: {
isPassword: false,
expiration: undefined,
@@ -154,17 +160,16 @@ export default {
permission: undefined,
type: undefined,
id: undefined,
emails: undefined
emails: undefined,
},
pickedItem: undefined,
isGeneratedShared: false,
isLoading: false,
sharedViaEmail: false
sharedViaEmail: false,
}
},
methods: {
async submitShareOptions() {
// If shared was generated, then close popup
if (this.isGeneratedShared) {
events.$emit('popup:close')
@@ -182,14 +187,13 @@ export default {
// Send request to get share link
axios
.post(`/api/share`, this.shareOptions)
.then(response => {
.then((response) => {
// End loading
this.isGeneratedShared = true
this.$store.commit('UPDATE_SHARED_ITEM', response.data)
this.pickedItem.data.relationships.shared = response.data
this.pickedItem.data.relationships.shared = response.data
})
.catch(() => {
events.$emit('alert:open', {
@@ -203,15 +207,13 @@ export default {
.finally(() => {
this.isLoading = false
})
}
},
},
mounted() {
events.$on('emailsInputValues', emails => this.shareOptions.emails = emails)
events.$on('emailsInputValues', (emails) => (this.shareOptions.emails = emails))
// Show popup
events.$on('popup:open', args => {
events.$on('popup:open', (args) => {
if (args.name !== 'share-create') return
// Store picked item
@@ -223,23 +225,22 @@ export default {
// Close popup
events.$on('popup:close', () => {
// Restore data
setTimeout(() => {
this.isGeneratedShared = false
this.isExpiration = false
this.isEmailSharing = false
this.shareOptions = {
isPassword: false,
expiration: undefined,
password: undefined,
permission: undefined,
type: undefined,
id: undefined,
emails: undefined
}
this.isGeneratedShared = false
this.isExpiration = false
this.isEmailSharing = false
this.shareOptions = {
isPassword: false,
expiration: undefined,
password: undefined,
permission: undefined,
type: undefined,
id: undefined,
emails: undefined,
}
}, 150)
})
}
},
}
</script>

View File

@@ -3,356 +3,339 @@
<!--Title-->
<PopupHeader :title="popupTitle" icon="share" />
<!--Qr Code-->
<div v-if="pickedItem && activeSection === 'qr-code'">
<PopupContent class="flex justify-center items-center">
<div v-if="! qrCode" class="relative my-8">
<Spinner />
</div>
<div v-if="qrCode" v-html="qrCode" class="my-5 overflow-hidden rounded-xl"></div>
</PopupContent>
<!--Qr Code-->
<div v-if="pickedItem && activeSection === 'qr-code'">
<PopupContent class="flex items-center justify-center">
<div v-if="!qrCode" class="relative my-8">
<Spinner />
</div>
<div v-if="qrCode" v-html="qrCode" class="my-5 overflow-hidden rounded-xl"></div>
</PopupContent>
<PopupActions>
<ButtonBase
class="w-full"
@click.native="showSection(undefined)"
button-style="secondary"
>
{{ $t('Show Details') }}
</ButtonBase>
<ButtonBase
class="w-full"
@click.native="$closePopup()"
button-style="theme"
>
{{ $t('shared_form.button_done') }}
</ButtonBase>
</PopupActions>
</div>
<PopupActions>
<ButtonBase class="w-full" @click.native="showSection(undefined)" button-style="secondary">
{{ $t('Show Details') }}
</ButtonBase>
<ButtonBase class="w-full" @click.native="$closePopup()" button-style="theme">
{{ $t('shared_form.button_done') }}
</ButtonBase>
</PopupActions>
</div>
<!--Share via email-->
<div v-if="pickedItem && activeSection === 'email-sharing'">
<PopupContent>
<!--Item Thumbnail-->
<ThumbnailItem class="mb-4" :item="pickedItem" />
<!--Share via email-->
<div v-if="pickedItem && activeSection === 'email-sharing'">
<PopupContent>
<!--Item Thumbnail-->
<ThumbnailItem class="mb-4" :item="pickedItem" />
<ValidationObserver @submit.prevent v-slot="{ invalid }" ref="shareEmail" tag="form">
<ValidationProvider tag="div" mode="passive" name="Email" rules="required" v-slot="{ errors }">
<AppInputText title="Share with" :error="errors[0]" :is-last="true">
<MultiEmailInput rules="required" v-model="emails" :label="$t('shared_form.label_send_to_recipients')" />
</AppInputText>
</ValidationProvider>
</ValidationObserver>
</PopupContent>
<ValidationObserver @submit.prevent v-slot="{ invalid }" ref="shareEmail" tag="form">
<ValidationProvider tag="div" mode="passive" name="Email" rules="required" v-slot="{ errors }">
<AppInputText title="Share with" :error="errors[0]" :is-last="true">
<MultiEmailInput rules="required" v-model="emails" :label="$t('shared_form.label_send_to_recipients')" />
</AppInputText>
</ValidationProvider>
</ValidationObserver>
</PopupContent>
<PopupActions>
<ButtonBase
class="w-full"
@click.native="showSection(undefined)"
button-style="secondary"
>
{{ $t('Show Details') }}
</ButtonBase>
<ButtonBase
class="w-full"
@click.native="sendViaEmail"
button-style="theme"
:loading="isLoading"
:disabled="isLoading"
>
{{ $t('Send') }}
</ButtonBase>
</PopupActions>
</div>
<PopupActions>
<ButtonBase class="w-full" @click.native="showSection(undefined)" button-style="secondary">
{{ $t('Show Details') }}
</ButtonBase>
<ButtonBase class="w-full" @click.native="sendViaEmail" button-style="theme" :loading="isLoading" :disabled="isLoading">
{{ $t('Send') }}
</ButtonBase>
</PopupActions>
</div>
<!--Update sharing-->
<div v-if="pickedItem && ! activeSection">
<PopupContent>
<!--Item Thumbnail-->
<ThumbnailItem class="mb-5" :item="pickedItem" />
<!--Update sharing-->
<div v-if="pickedItem && !activeSection">
<PopupContent>
<!--Item Thumbnail-->
<ThumbnailItem class="mb-5" :item="pickedItem" />
<!--Get share link-->
<AppInputText :title="$t('shared_form.label_share_vie_email')">
<CopyShareLink :item="pickedItem" />
</AppInputText>
<!--Get share link-->
<AppInputText :title="$t('shared_form.label_share_vie_email')">
<CopyShareLink :item="pickedItem" />
</AppInputText>
<ValidationObserver @submit.prevent ref="shareForm" v-slot="{ invalid }" tag="form">
<ValidationObserver @submit.prevent ref="shareForm" v-slot="{ invalid }" tag="form">
<!--Permission Select-->
<ValidationProvider v-if="isFolder" tag="div" mode="passive" name="Permission" rules="required" v-slot="{ errors }">
<AppInputText :title="$t('shared_form.label_permission')" :error="errors[0]">
<SelectInput
v-model="shareOptions.permission"
:options="$translateSelectOptions(permissionOptions)"
:default="shareOptions.permission"
:placeholder="$t('shared_form.placeholder_permission')"
:isError="errors[0]"
/>
</AppInputText>
</ValidationProvider>
<!--Permission Select-->
<ValidationProvider v-if="isFolder" tag="div" mode="passive" name="Permission" rules="required" v-slot="{ errors }">
<AppInputText :title="$t('shared_form.label_permission')" :error="errors[0]">
<SelectInput v-model="shareOptions.permission" :options="$translateSelectOptions(permissionOptions)" :default="shareOptions.permission" :placeholder="$t('shared_form.placeholder_permission')" :isError="errors[0]" />
</AppInputText>
</ValidationProvider>
<!--Password Switch-->
<div>
<AppInputSwitch :title="$t('shared_form.label_password_protection')" :description="$t('popup.share.password_description')">
<SwitchInput v-model="shareOptions.isProtected" class="switch" :state="shareOptions.isProtected" />
</AppInputSwitch>
<!--Password Switch-->
<div>
<AppInputSwitch :title="$t('shared_form.label_password_protection')" :description="$t('popup.share.password_description')">
<SwitchInput v-model="shareOptions.isProtected" class="switch" :state="shareOptions.isProtected" />
</AppInputSwitch>
<ActionButton
v-if="pickedItem.data.relationships.shared.data.attributes.protected && canChangePassword && shareOptions.isProtected"
@click.native="changePassword"
class="mb-6 -mt-4"
>
{{ $t('popup_share_edit.change_pass') }}
</ActionButton>
<ActionButton v-if="(pickedItem.data.relationships.shared.data.attributes.protected && canChangePassword) && shareOptions.isProtected" @click.native="changePassword" class="mb-6 -mt-4">
{{ $t('popup_share_edit.change_pass') }}
</ActionButton>
<!--Set password-->
<ValidationProvider v-if="shareOptions.isProtected && !canChangePassword" tag="div" mode="passive" name="Password" rules="required" v-slot="{ errors }">
<AppInputText :error="errors[0]" class="-mt-2">
<input
v-model="shareOptions.password"
:class="{ 'border-red': errors[0] }"
type="text"
class="focus-border-theme input-dark"
:placeholder="$t('page_sign_in.placeholder_password')"
/>
</AppInputText>
</ValidationProvider>
</div>
<!--Set password-->
<ValidationProvider v-if="shareOptions.isProtected && ! canChangePassword" tag="div" mode="passive" name="Password" rules="required" v-slot="{ errors }">
<AppInputText :error="errors[0]" class="-mt-2">
<input v-model="shareOptions.password" :class="{'border-red': errors[0]}" type="text" class="focus-border-theme input-dark" :placeholder="$t('page_sign_in.placeholder_password')">
</AppInputText>
</ValidationProvider>
</div>
<!--Expiration switch-->
<div>
<AppInputSwitch :title="$t('expiration')" :description="$t('popup.share.expiration_description')" :is-last="!shareOptions.expiration">
<SwitchInput v-model="shareOptions.expiration" class="switch" :state="shareOptions.expiration ? 1 : 0" />
</AppInputSwitch>
<!--Expiration switch-->
<div>
<AppInputSwitch :title="$t('expiration')" :description="$t('popup.share.expiration_description')" :is-last="!shareOptions.expiration">
<SwitchInput v-model="shareOptions.expiration" class="switch" :state="shareOptions.expiration ? 1 : 0" />
</AppInputSwitch>
<!--Set expiration-->
<AppInputText v-if="shareOptions.expiration" class="-mt-2" :is-last="true">
<SelectBoxInput v-model="shareOptions.expiration" :data="$translateSelectOptions(expirationList)" :value="shareOptions.expiration" class="box" />
</AppInputText>
</div>
</ValidationObserver>
</PopupContent>
<!--Set expiration-->
<AppInputText v-if="shareOptions.expiration" class="-mt-2" :is-last="true">
<SelectBoxInput v-model="shareOptions.expiration" :data="$translateSelectOptions(expirationList)" :value="shareOptions.expiration" class="box" />
</AppInputText>
</div>
</ValidationObserver>
</PopupContent>
<PopupActions>
<ButtonBase
class="w-full"
@click.native="destroySharing"
:button-style="destroyButtonStyle"
:loading="isDeleting"
:disabled="isDeleting"
>
{{ destroyButtonText }}
</ButtonBase>
<ButtonBase
class="w-full"
@click.native="updateShareOptions"
button-style="theme"
:loading="isLoading"
:disabled="isLoading"
>
{{ $t('Store Changes') }}
</ButtonBase>
</PopupActions>
</div>
<PopupActions>
<ButtonBase class="w-full" @click.native="destroySharing" :button-style="destroyButtonStyle" :loading="isDeleting" :disabled="isDeleting">
{{ destroyButtonText }}
</ButtonBase>
<ButtonBase class="w-full" @click.native="updateShareOptions" button-style="theme" :loading="isLoading" :disabled="isLoading">
{{ $t('Store Changes') }}
</ButtonBase>
</PopupActions>
</div>
</PopupWrapper>
</template>
<script>
import {ValidationProvider, ValidationObserver} from 'vee-validate/dist/vee-validate.full'
import MultiEmailInput from "./Forms/MultiEmailInput";
import SelectBoxInput from './Forms/SelectBoxInput'
import CopyShareLink from './Forms/CopyShareLink'
import PopupWrapper from "./Popup/PopupWrapper";
import PopupActions from './Popup/PopupActions'
import PopupContent from './Popup/PopupContent'
import PopupHeader from './Popup/PopupHeader'
import SwitchInput from './Forms/SwitchInput'
import SelectInput from './Forms/SelectInput'
import ThumbnailItem from "./ThumbnailItem";
import ActionButton from "./ActionButton";
import ButtonBase from "../FilesView/ButtonBase";
import AppInputSwitch from "../Admin/AppInputSwitch"
import AppInputText from "../Admin/AppInputText"
import {required} from 'vee-validate/dist/rules'
import Spinner from "../FilesView/Spinner"
import {events} from '../../bus'
import {mapGetters} from 'vuex'
import axios from 'axios'
import { ValidationProvider, ValidationObserver } from 'vee-validate/dist/vee-validate.full'
import MultiEmailInput from './Forms/MultiEmailInput'
import SelectBoxInput from './Forms/SelectBoxInput'
import CopyShareLink from './Forms/CopyShareLink'
import PopupWrapper from './Popup/PopupWrapper'
import PopupActions from './Popup/PopupActions'
import PopupContent from './Popup/PopupContent'
import PopupHeader from './Popup/PopupHeader'
import SwitchInput from './Forms/SwitchInput'
import SelectInput from './Forms/SelectInput'
import ThumbnailItem from './ThumbnailItem'
import ActionButton from './ActionButton'
import ButtonBase from '../FilesView/ButtonBase'
import AppInputSwitch from '../Admin/AppInputSwitch'
import AppInputText from '../Admin/AppInputText'
import { required } from 'vee-validate/dist/rules'
import Spinner from '../FilesView/Spinner'
import { events } from '../../bus'
import { mapGetters } from 'vuex'
import axios from 'axios'
export default {
name: 'ShareEditPopup',
components: {
ValidationProvider,
ValidationObserver,
MultiEmailInput,
AppInputSwitch,
SelectBoxInput,
ThumbnailItem,
CopyShareLink,
ActionButton,
PopupWrapper,
PopupActions,
AppInputText,
PopupContent,
PopupHeader,
SelectInput,
SwitchInput,
ButtonBase,
required,
Spinner,
},
computed: {
...mapGetters([
'permissionOptions',
'expirationList',
'user',
]),
popupTitle() {
return {
'qr-code': this.$t('Get your QR code'),
'email-sharing': this.$t('Share on multiple emails'),
}[this.activeSection] || this.$t('popup_share_edit.title')
},
isFolder() {
return this.pickedItem && this.pickedItem.data.type === 'folder'
},
destroyButtonText() {
return this.isConfirmedDestroy ? this.$t('popup_share_edit.confirm') : this.$t('popup_share_edit.stop')
},
destroyButtonStyle() {
return this.isConfirmedDestroy ? 'danger-solid' : 'secondary'
},
},
watch: {
'shareOptions.expiration': function (val) {
if (!val) {
this.shareOptions.expiration = undefined
}
}
},
data() {
return {
activeSection: undefined,
shareOptions: undefined,
pickedItem: undefined,
emails: undefined,
qrCode: undefined,
isConfirmedDestroy: false,
canChangePassword: false,
isMoreOptions: false,
isDeleting: false,
isLoading: false,
}
},
methods: {
getQrCode() {
axios.get(`/api/share/${this.shareOptions.token}/qr`)
.then(response => {
this.qrCode = response.data
})
.catch(() => this.$isSomethingWrong())
},
showSection(section = undefined) {
this.activeSection = section
},
changePassword() {
this.canChangePassword = false
},
async sendViaEmail() {
// Validate email field
const isValid = await this.$refs.shareEmail.validate();
export default {
name: 'ShareEditPopup',
components: {
ValidationProvider,
ValidationObserver,
MultiEmailInput,
AppInputSwitch,
SelectBoxInput,
ThumbnailItem,
CopyShareLink,
ActionButton,
PopupWrapper,
PopupActions,
AppInputText,
PopupContent,
PopupHeader,
SelectInput,
SwitchInput,
ButtonBase,
required,
Spinner,
},
computed: {
...mapGetters(['permissionOptions', 'expirationList', 'user']),
popupTitle() {
return (
{
'qr-code': this.$t('Get your QR code'),
'email-sharing': this.$t('Share on multiple emails'),
}[this.activeSection] || this.$t('popup_share_edit.title')
)
},
isFolder() {
return this.pickedItem && this.pickedItem.data.type === 'folder'
},
destroyButtonText() {
return this.isConfirmedDestroy ? this.$t('popup_share_edit.confirm') : this.$t('popup_share_edit.stop')
},
destroyButtonStyle() {
return this.isConfirmedDestroy ? 'danger-solid' : 'secondary'
},
},
watch: {
'shareOptions.expiration': function (val) {
if (!val) {
this.shareOptions.expiration = undefined
}
},
},
data() {
return {
activeSection: undefined,
shareOptions: undefined,
pickedItem: undefined,
emails: undefined,
qrCode: undefined,
isConfirmedDestroy: false,
canChangePassword: false,
isMoreOptions: false,
isDeleting: false,
isLoading: false,
}
},
methods: {
getQrCode() {
axios
.get(`/api/share/${this.shareOptions.token}/qr`)
.then((response) => {
this.qrCode = response.data
})
.catch(() => this.$isSomethingWrong())
},
showSection(section = undefined) {
this.activeSection = section
},
changePassword() {
this.canChangePassword = false
},
async sendViaEmail() {
// Validate email field
const isValid = await this.$refs.shareEmail.validate()
if (!isValid) return;
if (!isValid) return
this.isLoading = true
this.isLoading = true
axios.post(`/api/share/${this.shareOptions.token}/email`, {
emails: this.emails
})
.catch(() => {
this.$isSomethingWrong()
})
.finally(() => {
this.isLoading = false
this.$closePopup()
})
},
async destroySharing() {
// Set confirm button
if (!this.isConfirmedDestroy) {
this.isConfirmedDestroy = true
axios
.post(`/api/share/${this.shareOptions.token}/email`, {
emails: this.emails,
})
.catch(() => {
this.$isSomethingWrong()
})
.finally(() => {
this.isLoading = false
this.$closePopup()
})
},
async destroySharing() {
// Set confirm button
if (!this.isConfirmedDestroy) {
this.isConfirmedDestroy = true
return
}
return
}
// Start deleting spinner button
this.isDeleting = true
// Start deleting spinner button
this.isDeleting = true
// Send delete request
await this.$store.dispatch('shareCancel', this.pickedItem)
.catch(() => {
// End deleting spinner button
this.isDeleting = false
}).finally(() => this.$closePopup())
},
async updateShareOptions() {
// Validate fields
const isValid = await this.$refs.shareForm.validate();
// Send delete request
await this.$store
.dispatch('shareCancel', this.pickedItem)
.catch(() => {
// End deleting spinner button
this.isDeleting = false
})
.finally(() => this.$closePopup())
},
async updateShareOptions() {
// Validate fields
const isValid = await this.$refs.shareForm.validate()
if (!isValid) return;
if (!isValid) return
this.isLoading = true
this.isLoading = true
// Send request to get share link
axios
.post('/api/share/' + this.shareOptions.token, {
permission: this.shareOptions.permission,
protected: this.shareOptions.isProtected,
expiration: this.shareOptions.expiration,
password: this.shareOptions.password ? this.shareOptions.password : undefined,
_method: 'patch'
})
.then(response => {
// Update shared data
this.$store.commit('UPDATE_SHARED_ITEM', response.data)
// Send request to get share link
axios
.post('/api/share/' + this.shareOptions.token, {
permission: this.shareOptions.permission,
protected: this.shareOptions.isProtected,
expiration: this.shareOptions.expiration,
password: this.shareOptions.password ? this.shareOptions.password : undefined,
_method: 'patch',
})
.then((response) => {
// Update shared data
this.$store.commit('UPDATE_SHARED_ITEM', response.data)
events.$emit('popup:close')
})
.catch(() => {
this.$isSomethingWrong()
})
.finally(() => {
this.isLoading = false
})
},
},
mounted() {
events.$on('emailsInputValues', emails => this.emails = emails)
events.$emit('popup:close')
})
.catch(() => {
this.$isSomethingWrong()
})
.finally(() => {
this.isLoading = false
})
},
},
mounted() {
events.$on('emailsInputValues', (emails) => (this.emails = emails))
// Show popup
events.$on('popup:open', args => {
// Show popup
events.$on('popup:open', (args) => {
if (args.name !== 'share-edit') return
if (args.name !== 'share-edit') return
// Store picked item
this.pickedItem = args.item
// Store picked item
this.pickedItem = args.item
// Store shared options
this.shareOptions = {
id: args.item.data.relationships.shared.data.id,
token: args.item.data.relationships.shared.data.attributes.token,
expiration: args.item.data.relationships.shared.data.attributes.expire_in,
isProtected: args.item.data.relationships.shared.data.attributes.protected,
permission: args.item.data.relationships.shared.data.attributes.permission,
password: undefined,
}
// Store shared options
this.shareOptions = {
id: args.item.data.relationships.shared.data.id,
token: args.item.data.relationships.shared.data.attributes.token,
expiration: args.item.data.relationships.shared.data.attributes.expire_in,
isProtected: args.item.data.relationships.shared.data.attributes.protected,
permission: args.item.data.relationships.shared.data.attributes.permission,
password: undefined,
}
if (args.section)
this.activeSection = args.section
if (args.section) this.activeSection = args.section
if (args.section === 'qr-code')
this.getQrCode()
if (args.section === 'qr-code') this.getQrCode()
this.canChangePassword = args.item.data.relationships.shared.data.attributes.protected
})
this.canChangePassword = args.item.data.relationships.shared.data.attributes.protected
})
events.$on('popup:close', () => {
// Reset data
setTimeout(() => {
this.isDeleting = false
this.isConfirmedDestroy = false
this.canChangePassword = false
this.shareOptions = undefined
this.pickedItem = undefined
this.activeSection = undefined
this.qrCode = undefined
}, 150)
})
}
}
events.$on('popup:close', () => {
// Reset data
setTimeout(() => {
this.isDeleting = false
this.isConfirmedDestroy = false
this.canChangePassword = false
this.shareOptions = undefined
this.pickedItem = undefined
this.activeSection = undefined
this.qrCode = undefined
}, 150)
})
},
}
</script>

View File

@@ -5,18 +5,16 @@
</template>
<script>
export default {
name: "TabOption",
name: 'TabOption',
props: ['title', 'icon', 'selected'],
data () {
data() {
return {
isActive: false
isActive: false,
}
},
mounted() {
this.isActive = this.selected
}
},
}
</script>

View File

@@ -2,125 +2,117 @@
<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 text-theme dark-text-theme" size="17"/>
<link-icon v-if="tab.icon === 'link'" class="tab-icon text-theme dark-text-theme" size="17"/>
<smile-icon v-if="tab.icon === 'emoji'" class="tab-icon text-theme dark-text-theme" size="17"/>
<folder-icon v-if="tab.icon === 'folder'" class="tab-icon text-theme dark-text-theme" size="17"/>
<mail-icon v-if="tab.icon === 'email'" class="tab-icon text-theme dark-text-theme" size="17" />
<link-icon v-if="tab.icon === 'link'" class="tab-icon text-theme dark-text-theme" size="17" />
<smile-icon v-if="tab.icon === 'emoji'" class="tab-icon text-theme dark-text-theme" size="17" />
<folder-icon v-if="tab.icon === 'folder'" class="tab-icon text-theme dark-text-theme" size="17" />
<!--Title-->
<b class="tab-title">
{{tab.title}}
{{ tab.title }}
</b>
</div>
</div>
<slot></slot>
</div>
</template>
<script>
import {
import { LinkIcon, MailIcon, SmileIcon, FolderIcon } from 'vue-feather-icons'
export default {
name: 'TabWrapper',
components: {
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
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 '../../../sass/vuefilemanager/inapp-forms';
@import '../../../sass/vuefilemanager/forms';
@import '../../../sass/vuefilemanager/inapp-forms';
@import '../../../sass/vuefilemanager/forms';
.tab-wrapper {
.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;
justify-content: center;
margin-bottom: 20px;
cursor: pointer;
align-items: center;
background: white;
color: $text;
border-radius: 8px;
overflow: hidden;
border: 1px solid #E8E9EB;
justify-content: center;
padding: 8px;
.tab-title {
@include font-size(14);
}
&.active {
background: $light_background;
.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: inherit !important;
.tab-title {
color: $text;
}
}
}
.tab-icon {
margin-right: 10px;
.dark {
path,
circle,
line,
polyline {
color: inherit !important;
}
}
}
.dark {
.tab-wrapper {
background: $dark_mode_foreground;
border-color: transparent;
.tab.active {
background: lighten($dark_mode_foreground, 7%);
.tab-title {
color: $dark_mode_text_primary;
}
}
}
.popup-wrapper {
.tab-wrapper {
background: $dark_mode_foreground;
border-color: transparent;
.tab.active {
background: lighten($dark_mode_foreground, 7%);
.tab-title {
color: $dark_mode_text_primary;
}
}
}
.popup-wrapper {
.tab-wrapper {
background: lighten($dark_mode_foreground, 2%);
}
background: lighten($dark_mode_foreground, 2%);
}
}
}
</style>

View File

@@ -1,58 +1,54 @@
<template>
<template>
<tr class="table-row">
<td
class="table-cell"
v-for="(collumn, index) in normalizedColumns"
:key="index"
>
<td class="table-cell" v-for="(collumn, index) in normalizedColumns" :key="index">
<span>{{ collumn }}</span>
</td>
</tr>
</template>
<script>
export default {
props: ['data'],
computed: {
normalizedColumns() {
// Remove ID from object
if (this.data['id']) delete this.data['id']
export default {
props: ['data'],
computed: {
normalizedColumns() {
// Remove ID from object
if (this.data['id']) delete this.data['id']
// Return object
return Object.values(this.data)
}
}
}
// Return object
return Object.values(this.data)
},
},
}
</script>
<style lang="scss" scoped>
@import '../../../../sass/vuefilemanager/variables';
@import '../../../../sass/vuefilemanager/mixins';
@import '../../../../sass/vuefilemanager/variables';
@import '../../../../sass/vuefilemanager/mixins';
.table-row {
border-radius: 8px;
.table-row {
border-radius: 8px;
&:hover {
background: $light_background;
&:hover {
background: $light_background;
}
.table-cell {
padding-top: 15px;
padding-bottom: 15px;
&:first-child {
padding-left: 15px;
}
.table-cell {
padding-top: 15px;
padding-bottom: 15px;
&:last-child {
padding-right: 15px;
text-align: right;
}
&:first-child {
padding-left: 15px;
}
&:last-child {
padding-right: 15px;
text-align: right;
}
span {
@include font-size(16);
font-weight: bold;
}
span {
@include font-size(16);
font-weight: bold;
}
}
}
</style>

View File

@@ -1,85 +1,70 @@
<template>
<div class="flex items-center shrink-0 grow-0">
<MemberAvatar
class="mr-3 shrink-0"
:is-border="false"
:size="52"
:member="member"
/>
<div class="info">
<b class="name" v-if="title">{{ title }}</b>
<span class="description" v-if="description">{{ description }}</span>
</div>
</div>
<template>
<div class="flex shrink-0 grow-0 items-center">
<MemberAvatar class="mr-3 shrink-0" :is-border="false" :size="52" :member="member" />
<div class="info">
<b class="name" v-if="title">{{ title }}</b>
<span class="description" v-if="description">{{ description }}</span>
</div>
</div>
</template>
<script>
import MemberAvatar from "../../FilesView/MemberAvatar";
import MemberAvatar from '../../FilesView/MemberAvatar'
export default {
name:'DatatableCellImage',
props: [
'member',
'title',
'description',
'image-size'
],
components: {
MemberAvatar,
}
}
export default {
name: 'DatatableCellImage',
props: ['member', 'title', 'description', 'image-size'],
components: {
MemberAvatar,
},
}
</script>
<style lang="scss" scoped>
@import '../../../../sass/vuefilemanager/variables';
@import '../../../../sass/vuefilemanager/mixins';
@import '../../../../sass/vuefilemanager/variables';
@import '../../../../sass/vuefilemanager/mixins';
.info {
.info {
.name,
.description {
max-width: 150px;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
display: block;
}
.name, .description {
max-width: 150px;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
display: block;
}
.name {
@include font-size(15);
line-height: 1;
color: $text;
}
.name {
@include font-size(15);
line-height: 1;
color: $text;
}
.description {
color: $text-muted;
@include font-size(12);
}
}
.description {
color: $text-muted;
@include font-size(12);
}
}
.dark {
.cell-image-thumbnail {
.image {
img {
&.blurred {
display: none;
}
}
}
.info {
.name {
color: $dark_mode_text_primary;
}
.description {
color: $dark_mode_text_secondary;
.dark {
.cell-image-thumbnail {
.image {
img {
&.blurred {
display: none;
}
}
}
.info {
.name {
color: $dark_mode_text_primary;
}
.description {
color: $dark_mode_text_secondary;
}
}
}
}
</style>

View File

@@ -2,240 +2,271 @@
<div class="w-full">
<table v-if="hasData" class="w-full">
<thead>
<tr class="whitespace-nowrap">
<th
class="text-left"
v-for="(column, index) in columns"
@click="sort(column.field, column.sortable)"
:key="index"
:class="{ 'sortable cursor-pointer': column.sortable, 'text-right': (Object.values(columns).length - 1) === index }"
v-if="! column.hidden"
>
<span class="dark:text-gray-500 text-gray-400 text-xs">
{{ column.label }}
</span>
<tr class="whitespace-nowrap">
<th
class="text-left"
v-for="(column, index) in columns"
@click="sort(column.field, column.sortable)"
:key="index"
:class="{
'sortable cursor-pointer': column.sortable,
'text-right': Object.values(columns).length - 1 === index,
}"
v-if="!column.hidden"
>
<span class="text-xs text-gray-400 dark:text-gray-500">
{{ column.label }}
</span>
<chevron-up-icon
v-if="column.sortable"
:class="{ 'arrow-down': filter.sort === 'ASC' }"
class="inline-block vue-feather dark:text-gray-500 text-gray-300"
size="12"
/>
</th>
</tr>
<chevron-up-icon
v-if="column.sortable"
:class="{ 'arrow-down': filter.sort === 'ASC' }"
class="vue-feather inline-block text-gray-300 dark:text-gray-500"
size="12"
/>
</th>
</tr>
</thead>
<tbody class="table-body">
<slot v-for="row in data.data" :row="row">
<DatatableCell :data="row" :key="row.id" />
</slot>
<slot v-for="row in data.data" :row="row">
<DatatableCell :data="row" :key="row.id" />
</slot>
</tbody>
</table>
<!--Empty data slot-->
<slot v-if="! isLoading && ! hasData" name="empty-page"></slot>
<!--Paginator-->
<div v-if="paginator && hasData" class="mt-6 flex justify-between items-center">
<!--Empty data slot-->
<slot v-if="!isLoading && !hasData" name="empty-page"></slot>
<!--Paginator-->
<div v-if="paginator && hasData" class="mt-6 flex items-center justify-between">
<!--Show if there is only 6 pages-->
<ul v-if="data.meta.total > 15 && data.meta.last_page <= 6" class="pagination flex items-center">
<!--Go previous icon-->
<li class="p-1 inline-block previous">
<a @click="goToPage(pageIndex - 1)" class="page-link" :class="{ 'opacity-20 cursor-default': pageIndex === 1 }">
<li class="previous inline-block p-1">
<a
@click="goToPage(pageIndex - 1)"
class="page-link"
:class="{
'cursor-default opacity-20': pageIndex === 1,
}"
>
<chevron-left-icon size="14" class="inline-block" />
</a>
</li>
<li v-for="(page, index) in data.meta.last_page" :key="index" class="p-1 inline-block" @click="goToPage(page)">
<a class="page-link" :class="{'dark:text-gray-300 dark:bg-4x-dark-foreground bg-light-background': pageIndex === page }">
<li v-for="(page, index) in data.meta.last_page" :key="index" class="inline-block p-1" @click="goToPage(page)">
<a
class="page-link"
:class="{
'bg-light-background dark:bg-4x-dark-foreground dark:text-gray-300': pageIndex === page,
}"
>
{{ page }}
</a>
</li>
<!--Go next icon-->
<li class="p-1 inline-block next">
<a @click="goToPage(pageIndex + 1)" class="page-link" :class="{ 'opacity-20 cursor-default': pageIndex === data.meta.last_page }">
<!--Go next icon-->
<li class="next inline-block p-1">
<a
@click="goToPage(pageIndex + 1)"
class="page-link"
:class="{
'cursor-default opacity-20': pageIndex === data.meta.last_page,
}"
>
<chevron-right-icon size="14" class="inline-block" />
</a>
</li>
</ul>
<!--Show if there is more than 6 pages-->
<!--Show if there is more than 6 pages-->
<ul v-if="data.meta.total > 15 && data.meta.last_page > 6" class="pagination flex items-center">
<!--Go previous icon-->
<li class="p-1 inline-block previous">
<a @click="goToPage(pageIndex - 1)" class="page-link" :class="{'opacity-20 cursor-default': pageIndex === 1 }">
<li class="previous inline-block p-1">
<a
@click="goToPage(pageIndex - 1)"
class="page-link"
:class="{
'cursor-default opacity-20': pageIndex === 1,
}"
>
<chevron-left-icon size="14" class="inline-block" />
</a>
</li>
<!--Show first Page-->
<li class="p-1 inline-block" v-if="pageIndex >= 5" @click="goToPage(1)">
<a class="page-link">
1
</a>
<!--Show first Page-->
<li class="inline-block p-1" v-if="pageIndex >= 5" @click="goToPage(1)">
<a class="page-link"> 1 </a>
</li>
<li v-if="pageIndex < 5" v-for="(page, index) in 5" :key="index" class="p-1 inline-block" @click="goToPage(page)">
<a class="page-link" :class="{'dark:text-gray-300 dark:bg-4x-dark-foreground bg-light-background': pageIndex === page }">
<li v-if="pageIndex < 5" v-for="(page, index) in 5" :key="index" class="inline-block p-1" @click="goToPage(page)">
<a
class="page-link"
:class="{
'bg-light-background dark:bg-4x-dark-foreground dark:text-gray-300': pageIndex === page,
}"
>
{{ page }}
</a>
</li>
<li class="p-1 inline-block" v-if="pageIndex >= 5">
<li class="inline-block p-1" v-if="pageIndex >= 5">
<a class="page-link">...</a>
</li>
<!--Floated Pages-->
<li v-if="pageIndex >= 5 && pageIndex < (data.meta.last_page - 3)" v-for="(page, index) in floatPages" :key="index" class="p-1 inline-block" @click="goToPage(page)">
<a class="page-link" :class="{'dark:text-gray-300 dark:bg-4x-dark-foreground bg-light-background': pageIndex === page }">
<!--Floated Pages-->
<li v-if="pageIndex >= 5 && pageIndex < data.meta.last_page - 3" v-for="(page, index) in floatPages" :key="index" class="inline-block p-1" @click="goToPage(page)">
<a
class="page-link"
:class="{
'bg-light-background dark:bg-4x-dark-foreground dark:text-gray-300': pageIndex === page,
}"
>
{{ page }}
</a>
</li>
<li class="p-1 inline-block" v-if="pageIndex < (data.meta.last_page - 3)">
<li class="inline-block p-1" v-if="pageIndex < data.meta.last_page - 3">
<a class="page-link">...</a>
</li>
<li v-if="pageIndex > (data.meta.last_page - 4)" v-for="(page, index) in 5" :key="index" class="p-1 inline-block" @click="goToPage(data.meta.last_page - (4 - index))">
<a class="page-link" :class="{ 'dark:text-gray-300 dark:bg-4x-dark-foreground bg-light-background': pageIndex === (data.meta.last_page - (4 - index)) }">
<li
v-if="pageIndex > data.meta.last_page - 4"
v-for="(page, index) in 5"
:key="index"
class="inline-block p-1"
@click="goToPage(data.meta.last_page - (4 - index))"
>
<a
class="page-link"
:class="{
'bg-light-background dark:bg-4x-dark-foreground dark:text-gray-300': pageIndex === data.meta.last_page - (4 - index),
}"
>
{{ data.meta.last_page - (4 - index) }}
</a>
</li>
<!--Show last page-->
<li class="p-1 inline-block" v-if="pageIndex < (data.meta.last_page - 3)" @click="goToPage(data.meta.last_page)">
<!--Show last page-->
<li class="inline-block p-1" v-if="pageIndex < data.meta.last_page - 3" @click="goToPage(data.meta.last_page)">
<a class="page-link">
{{ data.meta.last_page }}
</a>
</li>
<!--Go next icon-->
<li class="p-1 inline-block next">
<a @click="goToPage(pageIndex + 1)" class="page-link" :class="{ 'opacity-20 cursor-default': pageIndex === data.meta.last_page }">
<!--Go next icon-->
<li class="next inline-block p-1">
<a
@click="goToPage(pageIndex + 1)"
class="page-link"
:class="{
'cursor-default opacity-20': pageIndex === data.meta.last_page,
}"
>
<chevron-right-icon size="14" class="inline-block" />
</a>
</li>
</ul>
<span class="dark:text-gray-500 text-xs text-gray-600">
Showing {{ data.meta.from }} - {{ data.meta.to }} from {{ data.meta.total }} records
</span>
<span class="text-xs text-gray-600 dark:text-gray-500"> Showing {{ data.meta.from }} - {{ data.meta.to }} from {{ data.meta.total }} records </span>
</div>
</div>
</template>
<script>
import {ChevronUpIcon, ChevronLeftIcon, ChevronRightIcon} from 'vue-feather-icons'
import DatatableCell from "./DatatableCell";
import axios from "axios";
import { ChevronUpIcon, ChevronLeftIcon, ChevronRightIcon } from 'vue-feather-icons'
import DatatableCell from './DatatableCell'
import axios from 'axios'
export default {
name: 'DatatableWrapper',
props: [
'paginator',
'tableData',
'columns',
'scope',
'api',
],
components: {
ChevronRightIcon,
ChevronLeftIcon,
DatatableCell,
ChevronUpIcon,
},
computed: {
hasData() {
return this.data && this.data.data && this.data.data.length > 0
},
floatPages() {
return [(this.pageIndex - 1), this.pageIndex, (this.pageIndex + 1)];
}
},
data() {
return {
data: undefined,
isLoading: true,
pageIndex: 1,
filter: {
sort: 'DESC',
field: undefined,
}
}
},
methods: {
goToPage(index) {
if (index > this.data.meta.last_page || index === 0) return
name: 'DatatableWrapper',
props: ['paginator', 'tableData', 'columns', 'scope', 'api'],
components: {
ChevronRightIcon,
ChevronLeftIcon,
DatatableCell,
ChevronUpIcon,
},
computed: {
hasData() {
return this.data && this.data.data && this.data.data.length > 0
},
floatPages() {
return [this.pageIndex - 1, this.pageIndex, this.pageIndex + 1]
},
},
data() {
return {
data: undefined,
isLoading: true,
pageIndex: 1,
filter: {
sort: 'DESC',
field: undefined,
},
}
},
methods: {
goToPage(index) {
if (index > this.data.meta.last_page || index === 0) return
this.pageIndex = index
this.pageIndex = index
this.getPage(index)
},
sort(field, sortable) {
this.getPage(index)
},
sort(field, sortable) {
// Prevent sortable if is disabled
if (!sortable) return
// Prevent sortable if is disabled
if (!sortable) return
// Set filter
this.filter.field = field
// Set filter
this.filter.field = field
// Set sorting direction
if (this.filter.sort === 'DESC') {
this.filter.sort = 'ASC'
} else if (this.filter.sort === 'ASC') {
this.filter.sort = 'DESC'
}
// Set sorting direction
if (this.filter.sort === 'DESC') {
this.filter.sort = 'ASC'
} else if (this.filter.sort === 'ASC') {
this.filter.sort = 'DESC'
}
this.getPage(this.pageIndex)
},
getPage(page) {
// Get api URI
this.URI = this.api
this.getPage(this.pageIndex)
},
getPage(page) {
// Set page index
if (this.paginator) this.URI = this.URI + '?page=' + page
// Get api URI
this.URI = this.api;
// Add filder URI if is defined sorting
if (this.filter.field) this.URI = this.URI + (this.paginator ? '&' : '?') + 'sort=' + this.filter.field + '&direction=' + this.filter.sort
// Set page index
if (this.paginator)
this.URI = this.URI + '?page=' + page
this.isLoading = true
// Add filder URI if is defined sorting
if (this.filter.field)
// Get data
axios
.get(this.URI)
.then((response) => {
this.data = response.data
this.$emit('data', response.data)
})
.catch(() => this.$isSomethingWrong())
.finally(() => {
this.$emit('init', true)
this.isLoading = false
})
},
},
created() {
if (this.api) this.getPage(this.pageIndex)
this.URI = this.URI + (this.paginator ? '&' : '?') + 'sort=' + this.filter.field + '&direction=' + this.filter.sort
this.isLoading = true
// Get data
axios.get(this.URI)
.then(response => {
this.data = response.data
this.$emit('data', response.data)
})
.catch(() => this.$isSomethingWrong())
.finally(() => {
this.$emit('init', true)
this.isLoading = false
}
)
},
},
created() {
if (this.api)
this.getPage(this.pageIndex)
if (this.tableData)
this.data = this.tableData,
this.isLoading = false
}
if (this.tableData) (this.data = this.tableData), (this.isLoading = false)
},
}
</script>
<style lang="scss" scoped>
.page-link {
@apply w-8 h-8 flex justify-center items-center block rounded-lg font-bold text-sm cursor-pointer hover:bg-light-background transition duration-200;
}
</style>
.page-link {
@apply block flex h-8 w-8 cursor-pointer items-center justify-center rounded-lg text-sm font-bold transition duration-200 hover:bg-light-background;
}
</style>

View File

@@ -5,33 +5,33 @@
</template>
<script>
export default {
name: 'TextLabel',
}
export default {
name: 'TextLabel',
}
</script>
<style lang="scss" scoped>
@import '../../../sass/vuefilemanager/variables';
@import '../../../sass/vuefilemanager/mixins';
@import '../../../sass/vuefilemanager/variables';
@import '../../../sass/vuefilemanager/mixins';
.text-label {
padding-left: 25px;
@include font-size(12);
color: #afafaf;
font-weight: 700;
display: block;
margin-bottom: 5px;
}
@media only screen and (max-width: 1024px) {
.text-label {
padding-left: 25px;
@include font-size(12);
color: #AFAFAF;
font-weight: 700;
display: block;
margin-bottom: 5px;
padding-left: 20px;
}
}
@media only screen and (max-width: 1024px) {
.text-label {
padding-left: 20px;
}
}
.dark {
.text-label {
opacity: 0.35;
}
.dark {
.text-label {
opacity: 0.35;
}
}
</style>

View File

@@ -5,20 +5,20 @@
</template>
<script>
export default {
name: 'TextLabel',
}
export default {
name: 'TextLabel',
}
</script>
<style lang="scss" scoped>
@import '../../../sass/vuefilemanager/variables';
@import '../../../sass/vuefilemanager/mixins';
@import '../../../sass/vuefilemanager/variables';
@import '../../../sass/vuefilemanager/mixins';
.theme-label {
@include font-size(14);
color: $theme;
font-weight: 600;
display: block;
margin-bottom: 20px;
}
.theme-label {
@include font-size(14);
color: $theme;
font-weight: 600;
display: block;
margin-bottom: 20px;
}
</style>

View File

@@ -1,135 +1,118 @@
<template>
<div class="flex items-center rounded-xl select-none" spellcheck="false">
<div class="flex select-none items-center rounded-xl" spellcheck="false">
<!--Item thumbnail-->
<div class="relative w-16">
<!--Member thumbnail for team folders-->
<MemberAvatar v-if="user && canShowAuthor" :size="28" :is-border="true" :member="item.data.relationships.owner" class="absolute right-1.5 -bottom-2 z-10" />
<!--Item thumbnail-->
<div class="w-16 relative">
<!--Emoji Icon-->
<Emoji v-if="item.data.attributes.emoji" :emoji="item.data.attributes.emoji" class="ml-1 scale-110 transform text-5xl" />
<!--Member thumbnail for team folders-->
<MemberAvatar
v-if="user && canShowAuthor"
:size="28"
:is-border="true"
:member="item.data.relationships.owner"
class="absolute right-1.5 -bottom-2 z-10"
/>
<!--Folder Icon-->
<FolderIcon v-if="isFolder && !item.data.attributes.emoji" :item="item" />
<!--Emoji Icon-->
<Emoji
v-if="item.data.attributes.emoji"
:emoji="item.data.attributes.emoji"
class="text-5xl ml-1 transform scale-110"
/>
<!--File Icon-->
<FileIconThumbnail v-if="isFile || isVideo || isAudio || (isImage && !item.data.attributes.thumbnail)" :item="item" class="pr-2" />
<!--Folder Icon-->
<FolderIcon v-if="isFolder && !item.data.attributes.emoji" :item="item" />
<!--Image thumbnail-->
<img
v-if="isImage && item.data.attributes.thumbnail"
class="ml-0.5 h-12 w-12 rounded object-cover"
:src="item.data.attributes.thumbnail.xs"
:alt="item.data.attributes.name"
loading="lazy"
/>
</div>
<!--File Icon-->
<FileIconThumbnail v-if="isFile || isVideo || isAudio || (isImage && !item.data.attributes.thumbnail)" :item="item" class="pr-2" />
<!--Item Info-->
<div class="pl-2">
<!--Item Title-->
<b class="mb-0.5 block overflow-hidden text-ellipsis whitespace-nowrap text-sm hover:underline" style="max-width: 240px">
{{ item.data.attributes.name }}
</b>
<!--Image thumbnail-->
<img v-if="isImage && item.data.attributes.thumbnail" class="w-12 h-12 rounded ml-0.5 object-cover" :src="item.data.attributes.thumbnail.xs" :alt="item.data.attributes.name" loading="lazy" />
</div>
<!--Item sub line-->
<div class="flex items-center">
<!--Shared Icon-->
<div v-if="$checkPermission('master') && item.data.relationships.shared">
<link-icon size="12" class="text-theme dark-text-theme vue-feather mr-1.5" />
</div>
<!--Item Info-->
<div class="pl-2">
<!--File & Image sub line-->
<small v-if="!isFolder" class="block text-xs text-gray-500"> {{ item.data.attributes.filesize }}, {{ timeStamp }} </small>
<!--Item Title-->
<b class="block text-sm mb-0.5 text-ellipsis overflow-hidden hover:underline whitespace-nowrap" style="max-width: 240px">
{{ item.data.attributes.name }}
</b>
<!--Item sub line-->
<div class="flex items-center">
<!--Shared Icon-->
<div v-if="$checkPermission('master') && item.data.relationships.shared">
<link-icon size="12" class="mr-1.5 text-theme dark-text-theme vue-feather"/>
</div>
<!--File & Image sub line-->
<small v-if="! isFolder" class="block text-xs text-gray-500">
{{ item.data.attributes.filesize }}, {{ timeStamp }}
</small>
<!--Folder sub line-->
<small v-if="isFolder" class="block text-xs text-gray-500">
{{ folderItems === 0 ? $t('folder.empty') : $tc('folder.item_counts', folderItems) }}, {{ timeStamp }}
</small>
</div>
</div>
</div>
<!--Folder sub line-->
<small v-if="isFolder" class="block text-xs text-gray-500">
{{ folderItems === 0 ? $t('folder.empty') : $tc('folder.item_counts', folderItems) }}, {{ timeStamp }}
</small>
</div>
</div>
</div>
</template>
<script>
import {LinkIcon, EyeIcon} from 'vue-feather-icons'
import FileIconThumbnail from "../FilesView/FileIconThumbnail";
import MemberAvatar from "../FilesView/MemberAvatar";
import Emoji from "./Emoji";
import {mapGetters} from 'vuex'
import FolderIcon from "../FilesView/FolderIcon";
import { LinkIcon, EyeIcon } from 'vue-feather-icons'
import FileIconThumbnail from '../FilesView/FileIconThumbnail'
import MemberAvatar from '../FilesView/MemberAvatar'
import Emoji from './Emoji'
import { mapGetters } from 'vuex'
import FolderIcon from '../FilesView/FolderIcon'
export default {
name: 'ThumbnailItem',
props: [
'setFolderIcon',
'item',
'info',
],
components: {
FileIconThumbnail,
MemberAvatar,
FolderIcon,
Emoji,
LinkIcon,
EyeIcon,
},
computed: {
...mapGetters([
'isMultiSelectMode',
'clipboard',
'user',
]),
isClicked() {
return this.clipboard.some(element => element.data.id === this.item.data.id)
},
isVideo() {
return this.item.data.type === 'video'
},
isAudio() {
return this.item.data.type === 'audio'
},
isFile() {
return this.item.data.type === 'file'
},
isImage() {
return this.item.data.type === 'image'
},
isFolder() {
return this.item.data.type === 'folder'
},
timeStamp() {
return this.item.data.attributes.deleted_at
? this.$t('item_thumbnail.deleted_at', {time: this.item.data.attributes.deleted_at})
: this.item.data.attributes.created_at
},
canEditName() {
return !this.$isMobile()
&& !this.$isThisRoute(this.$route, ['Trash'])
&& !this.$checkPermission('visitor')
&& !(this.sharedDetail && this.sharedDetail.attributes.type === 'file')
},
folderItems() {
return this.item.data.attributes.deleted_at
? this.item.data.attributes.trashed_items
: this.item.data.attributes.items
},
canShowAuthor() {
return !this.isFolder
&& this.user.data.id !== this.item.data.relationships.owner.data.id
},
canDrag() {
return !this.isDeleted && this.$checkPermission(['master', 'editor'])
},
export default {
name: 'ThumbnailItem',
props: ['setFolderIcon', 'item', 'info'],
components: {
FileIconThumbnail,
MemberAvatar,
FolderIcon,
Emoji,
LinkIcon,
EyeIcon,
},
computed: {
...mapGetters(['isMultiSelectMode', 'clipboard', 'user']),
isClicked() {
return this.clipboard.some((element) => element.data.id === this.item.data.id)
},
}
isVideo() {
return this.item.data.type === 'video'
},
isAudio() {
return this.item.data.type === 'audio'
},
isFile() {
return this.item.data.type === 'file'
},
isImage() {
return this.item.data.type === 'image'
},
isFolder() {
return this.item.data.type === 'folder'
},
timeStamp() {
return this.item.data.attributes.deleted_at
? this.$t('item_thumbnail.deleted_at', {
time: this.item.data.attributes.deleted_at,
})
: this.item.data.attributes.created_at
},
canEditName() {
return (
!this.$isMobile() &&
!this.$isThisRoute(this.$route, ['Trash']) &&
!this.$checkPermission('visitor') &&
!(this.sharedDetail && this.sharedDetail.attributes.type === 'file')
)
},
folderItems() {
return this.item.data.attributes.deleted_at ? this.item.data.attributes.trashed_items : this.item.data.attributes.items
},
canShowAuthor() {
return !this.isFolder && this.user.data.id !== this.item.data.relationships.owner.data.id
},
canDrag() {
return !this.isDeleted && this.$checkPermission(['master', 'editor'])
},
},
}
</script>

View File

@@ -1,134 +1,125 @@
<template>
<div :class="{'opacity-50 pointer-events-none': disabledById && disabledById.data.id === nodes.id || !disableId, 'mb-2.5': isRootDepth}">
<div
:class="{
'pointer-events-none opacity-50': (disabledById && disabledById.data.id === nodes.id) || !disableId,
'mb-2.5': isRootDepth,
}"
>
<div
:class="{'is-disabled-item': false}"
:style="indent"
class="relative flex items-center select-none py-2 px-1.5 cursor-pointer relative whitespace-nowrap transition-all duration-150"
>
<!--Arrow icon-->
<span @click.stop="showTree" class="p-2 -m-2">
<chevron-right-icon
:class="{'transform rotate-90': isVisible, 'text-theme dark-text-theme': isSelectedItem, 'opacity-100': nodes.folders.length !== 0}"
class="vue-feather transition-all duration-300 mr-2 opacity-0"
size="17"
/>
</span>
:class="{ 'is-disabled-item': false }"
:style="indent"
class="relative relative flex cursor-pointer select-none items-center whitespace-nowrap py-2 px-1.5 transition-all duration-150"
>
<!--Arrow icon-->
<span @click.stop="showTree" class="-m-2 p-2">
<chevron-right-icon
:class="{
'rotate-90 transform': isVisible,
'text-theme dark-text-theme': isSelectedItem,
'opacity-100': nodes.folders.length !== 0,
}"
class="vue-feather mr-2 opacity-0 transition-all duration-300"
size="17"
/>
</span>
<!--Item icon-->
<hard-drive-icon v-if="nodes.location === 'files'" size="17" class="icon vue-feather" :class="{'text-theme dark-text-theme': isSelectedItem}" />
<users-icon v-if="nodes.location === 'team-folders'" size="17" class="icon vue-feather" :class="{'text-theme dark-text-theme': isSelectedItem}" />
<user-plus-icon v-if="nodes.location === 'shared-with-me'" size="17" class="icon vue-feather" :class="{'text-theme dark-text-theme': isSelectedItem}" />
<folder-icon v-if="! nodes.location" size="17" class="icon vue-feather" :class="{'text-theme dark-text-theme': isSelectedItem}" />
<!--Item icon-->
<hard-drive-icon v-if="nodes.location === 'files'" size="17" class="icon vue-feather" :class="{ 'text-theme dark-text-theme': isSelectedItem }" />
<users-icon v-if="nodes.location === 'team-folders'" size="17" class="icon vue-feather" :class="{ 'text-theme dark-text-theme': isSelectedItem }" />
<user-plus-icon v-if="nodes.location === 'shared-with-me'" size="17" class="icon vue-feather" :class="{ 'text-theme dark-text-theme': isSelectedItem }" />
<folder-icon v-if="!nodes.location" size="17" class="icon vue-feather" :class="{ 'text-theme dark-text-theme': isSelectedItem }" />
<!--Item label-->
<b
@click="getFolder"
class="text-sm font-bold whitespace-nowrap overflow-x-hidden text-ellipsis inline-block ml-3 transition-all duration-150"
:class="{'text-theme': isSelectedItem}"
>
{{ nodes.name }}
</b>
<!--Item label-->
<b
@click="getFolder"
class="ml-3 inline-block overflow-x-hidden text-ellipsis whitespace-nowrap text-sm font-bold transition-all duration-150"
:class="{ 'text-theme': isSelectedItem }"
>
{{ nodes.name }}
</b>
</div>
<!--Children-->
<tree-node
:disabled-by-id="disabledById"
:depth="depth + 1"
v-if="isVisible"
:nodes="item"
v-for="item in nodes.folders"
:key="item.id"
/>
<!--Children-->
<tree-node :disabled-by-id="disabledById" :depth="depth + 1" v-if="isVisible" :nodes="item" v-for="item in nodes.folders" :key="item.id" />
</div>
</template>
<script>
//import TreeMenu from './TreeMenu'
import {FolderIcon, ChevronRightIcon, HardDriveIcon, UsersIcon, UserPlusIcon} from 'vue-feather-icons'
import {events} from '../../bus'
import {mapGetters} from 'vuex'
//import TreeMenu from './TreeMenu'
import { FolderIcon, ChevronRightIcon, HardDriveIcon, UsersIcon, UserPlusIcon } from 'vue-feather-icons'
import { events } from '../../bus'
import { mapGetters } from 'vuex'
export default {
name: 'TreeMenu',
props: [
'disabledById',
'nodes',
'depth',
],
components: {
ChevronRightIcon,
HardDriveIcon,
UserPlusIcon,
FolderIcon,
UsersIcon,
'tree-node': () => import('./TreeMenuNavigator'),
},
computed: {
...mapGetters([
'clipboard'
]),
indent() {
return {paddingLeft: this.depth * 20 + 'px'}
},
disableId() {
let canBeShow = true
export default {
name: 'TreeMenu',
props: ['disabledById', 'nodes', 'depth'],
components: {
ChevronRightIcon,
HardDriveIcon,
UserPlusIcon,
FolderIcon,
UsersIcon,
'tree-node': () => import('./TreeMenuNavigator'),
},
computed: {
...mapGetters(['clipboard']),
indent() {
return { paddingLeft: this.depth * 20 + 'px' }
},
disableId() {
let canBeShow = true
if (this.clipboard.includes(this.disabledById)) {
this.clipboard.map(item => {
if (item.data.id === this.nodes.id) {
canBeShow = false
}
})
}
return canBeShow
},
isRootDepth() {
return this.depth === 1
},
isSelectedItem() {
return this.isSelected && this.nodes.isMovable || this.isSelected && !this.isRootDepth
}
},
data() {
return {
isVisible: false,
isSelected: false,
isInactive: false
}
},
methods: {
getFolder() {
if ((this.isRootDepth && this.nodes.isMovable) || !this.isRootDepth) {
events.$emit('show-folder-item', this.nodes)
events.$emit('pick-folder', this.nodes)
}
},
showTree() {
this.isVisible = !this.isVisible
}
},
mounted() {
if (this.clipboard.includes(this.disabledById)) {
this.clipboard.map((item) => {
if (item.data.id === this.nodes.id) {
canBeShow = false
}
})
}
return canBeShow
},
isRootDepth() {
return this.depth === 1
},
isSelectedItem() {
return (this.isSelected && this.nodes.isMovable) || (this.isSelected && !this.isRootDepth)
},
},
data() {
return {
isVisible: false,
isSelected: false,
isInactive: false,
}
},
methods: {
getFolder() {
if ((this.isRootDepth && this.nodes.isMovable) || !this.isRootDepth) {
events.$emit('show-folder-item', this.nodes)
events.$emit('pick-folder', this.nodes)
}
},
showTree() {
this.isVisible = !this.isVisible
},
},
mounted() {
// Show first location
if (this.depth === 1 && this.nodes.isOpen) this.isVisible = true
// Show first location
if (this.depth === 1 && this.nodes.isOpen)
this.isVisible = true
// Select clicked folder
events.$on('pick-folder', (node) => {
this.isSelected = false
// Select clicked folder
events.$on('pick-folder', node => {
this.isSelected = false
if (this.nodes.id === node.id) this.isSelected = true
})
if (this.nodes.id === node.id)
this.isSelected = true
})
// Select clicked folder
events.$on('show-folder-item', (node) => {
this.isSelected = false
// Select clicked folder
events.$on('show-folder-item', node => {
this.isSelected = false
if (this.nodes.id === node.id)
this.isSelected = true
})
}
}
if (this.nodes.id === node.id) this.isSelected = true
})
},
}
</script>

View File

@@ -1,144 +1,146 @@
<template>
<div>
<div
@click="goToFolder"
class="flex items-center py-2.5 rounded-lg border-2 border-transparent border-dashed cursor-pointer"
:class="{'border-theme': area, 'pointer-events-none opacity-50': disabledFolder || disabled && draggedItem.length > 0 }"
:style="indent"
@dragover.prevent="dragEnter"
@dragleave="dragLeave"
@drop="dragFinish()"
>
<div @click.stop.prevent="showTree" class="p-2 -ml-2 -my-2 cursor-pointer">
<chevron-right-icon
size="17"
class="vue-feather"
:class="{'transform rotate-90': isVisible, 'opacity-0': nodes.folders.length === 0}"
/>
</div>
<folder-icon size="17" class="mr-2.5 vue-feather" :class="{'text-theme': isSelected}" />
<b
class="font-bold text-xs max-w-1 overflow-hidden text-ellipsis whitespace-nowrap"
:class="{'text-theme': isSelected}"
>
{{ nodes.name }}
</b>
</div>
<tree-node :disabled="disableChildren" :depth="depth + 1" v-if="isVisible" :nodes="item" v-for="item in nodes.folders" :key="item.id" />
</div>
<div>
<div
@click="goToFolder"
class="flex cursor-pointer items-center rounded-lg border-2 border-dashed border-transparent py-2.5"
:class="{
'border-theme': area,
'pointer-events-none opacity-50': disabledFolder || (disabled && draggedItem.length > 0),
}"
:style="indent"
@dragover.prevent="dragEnter"
@dragleave="dragLeave"
@drop="dragFinish()"
>
<div @click.stop.prevent="showTree" class="-my-2 -ml-2 cursor-pointer p-2">
<chevron-right-icon
size="17"
class="vue-feather"
:class="{
'rotate-90 transform': isVisible,
'opacity-0': nodes.folders.length === 0,
}"
/>
</div>
<folder-icon size="17" class="vue-feather mr-2.5" :class="{ 'text-theme': isSelected }" />
<b class="max-w-1 overflow-hidden text-ellipsis whitespace-nowrap text-xs font-bold" :class="{ 'text-theme': isSelected }">
{{ nodes.name }}
</b>
</div>
<tree-node :disabled="disableChildren" :depth="depth + 1" v-if="isVisible" :nodes="item" v-for="item in nodes.folders" :key="item.id" />
</div>
</template>
<script>
import {FolderIcon, ChevronRightIcon} from 'vue-feather-icons'
import {mapGetters} from 'vuex'
import {events} from '../../bus'
import { FolderIcon, ChevronRightIcon } from 'vue-feather-icons'
import { mapGetters } from 'vuex'
import { events } from '../../bus'
export default {
name: 'TreeMenuNavigator',
props: [
'disabled',
'nodes',
'depth',
],
components: {
'tree-node': () => import('./TreeMenuNavigator'),
ChevronRightIcon,
FolderIcon,
},
computed: {
...mapGetters([
'clipboard',
]),
isSelected() {
return this.$route.params.id === this.nodes.id
},
disabledFolder() {
let disableFolder = false
export default {
name: 'TreeMenuNavigator',
props: ['disabled', 'nodes', 'depth'],
components: {
'tree-node': () => import('./TreeMenuNavigator'),
ChevronRightIcon,
FolderIcon,
},
computed: {
...mapGetters(['clipboard']),
isSelected() {
return this.$route.params.id === this.nodes.id
},
disabledFolder() {
let disableFolder = false
if (this.draggedItem.length > 0) {
if (this.draggedItem.length > 0) {
this.draggedItem.forEach((item) => {
//Disable the parent of the folder
if (item.type === 'folder' && this.nodes.id === item.parent_id) {
disableFolder = true
}
//Disable the self folder with all children
if (this.nodes.id === item.id && item.type === 'folder') {
disableFolder = true
this.disableChildren = true
}
if (this.disabled) {
this.disableChildren = true
}
})
} else {
disableFolder = false
this.disableChildren = false
}
return disableFolder
},
indent() {
let offset = window.innerWidth <= 1024 ? 14 : 18
this.draggedItem.forEach(item => {
//Disable the parent of the folder
if (item.type === "folder" && this.nodes.id === item.parent_id) {
disableFolder = true
}
//Disable the self folder with all children
if (this.nodes.id === item.id && item.type === 'folder') {
disableFolder = true
this.disableChildren = true
}
if (this.disabled) {
this.disableChildren = true
}
})
} else {
disableFolder = false
this.disableChildren = false
}
return disableFolder
},
indent() {
return {
paddingLeft: this.depth === 0 ? 0 : offset * this.depth + 'px',
}
},
},
data() {
return {
disableChildren: false,
isVisible: false,
draggedItem: [],
area: false,
}
},
methods: {
goToFolder() {
this.$goToFileView(this.nodes.id)
},
dragFinish() {
// Move no selected item
if (!this.clipboard.includes(this.draggedItem[0])) {
this.$store.dispatch('moveItem', {
to_item: this.nodes,
noSelectedItem: this.draggedItem[0],
})
}
let offset = window.innerWidth <= 1024 ? 14 : 18;
// Move all selected items
if (this.clipboard.includes(this.draggedItem[0])) {
this.$store.dispatch('moveItem', {
to_item: this.nodes,
noSelectedItem: null,
})
}
return {paddingLeft: this.depth === 0 ? 0 : offset * this.depth + 'px'}
},
},
data() {
return {
disableChildren: false,
isVisible: false,
draggedItem: [],
area: false,
}
},
methods: {
goToFolder() {
this.$goToFileView(this.nodes.id)
},
dragFinish() {
// Move no selected item
if (!this.clipboard.includes(this.draggedItem[0])) {
this.$store.dispatch('moveItem', {to_item: this.nodes, noSelectedItem: this.draggedItem[0]})
}
this.draggedItem = []
this.area = false
// Move all selected items
if (this.clipboard.includes(this.draggedItem[0])) {
this.$store.dispatch('moveItem', {to_item: this.nodes, noSelectedItem: null})
}
events.$emit('drop')
},
dragEnter() {
this.area = true
},
dragLeave() {
this.area = false
},
showTree() {
this.isVisible = !this.isVisible
},
},
created() {
events.$on('drop', () => {
this.draggedItem = []
})
this.draggedItem = []
this.area = false
events.$emit('drop')
},
dragEnter() {
this.area = true
},
dragLeave() {
this.area = false
},
showTree() {
this.isVisible = !this.isVisible
}
},
created() {
events.$on('drop', () => {
this.draggedItem = []
})
//Get dragged item
events.$on('dragstart', (data) => {
//If is dragged item not selected
if (!this.clipboard.includes(data)) {
this.draggedItem = [data]
}
//If are the dragged items selected
if (this.clipboard.includes(data)) {
this.draggedItem = this.clipboard
}
})
}
}
//Get dragged item
events.$on('dragstart', (data) => {
//If is dragged item not selected
if (!this.clipboard.includes(data)) {
this.draggedItem = [data]
}
//If are the dragged items selected
if (this.clipboard.includes(data)) {
this.draggedItem = this.clipboard
}
})
},
}
</script>

View File

@@ -1,24 +1,19 @@
<template>
<PopupWrapper name="two-factor-qr-setup">
<PopupHeader :title="$t('popup_2fa.title')" icon="edit" />
<PopupContent>
<div v-if="qrCode" class="flex justify-center">
<div v-html="qrCode" class="my-5"></div>
</div>
<PopupContent>
<div v-if="qrCode" class="flex justify-center">
<div v-html="qrCode" class="my-5"></div>
</div>
<InfoBox style="margin-bottom: 0">
<p v-html="$t('popup_2fa.help')"></p>
</InfoBox>
<InfoBox style="margin-bottom: 0">
<p v-html="$t('popup_2fa.help')"></p>
</InfoBox>
</PopupContent>
<PopupActions>
<ButtonBase
class="w-full"
@click.native="closeQrCodePopup"
:button-style="closeQrButtonStyle"
>
<ButtonBase class="w-full" @click.native="closeQrCodePopup" :button-style="closeQrButtonStyle">
{{ closeQrButtonText }}
</ButtonBase>
</PopupActions>
@@ -26,103 +21,95 @@
</template>
<script>
import AppInputText from "../Admin/AppInputText";
import {ValidationProvider, ValidationObserver} from 'vee-validate/dist/vee-validate.full'
import PopupWrapper from "./Popup/PopupWrapper";
import AppInputText from '../Admin/AppInputText'
import { ValidationProvider, ValidationObserver } from 'vee-validate/dist/vee-validate.full'
import PopupWrapper from './Popup/PopupWrapper'
import PopupActions from './Popup/PopupActions'
import PopupContent from './Popup/PopupContent'
import PopupHeader from './Popup/PopupHeader'
import ButtonBase from "../FilesView/ButtonBase";
import InfoBox from "./Forms/InfoBox";
import {required} from 'vee-validate/dist/rules'
import {mapGetters} from 'vuex'
import {events} from '../../bus'
import ButtonBase from '../FilesView/ButtonBase'
import InfoBox from './Forms/InfoBox'
import { required } from 'vee-validate/dist/rules'
import { mapGetters } from 'vuex'
import { events } from '../../bus'
import axios from 'axios'
export default {
name: "TwoFactorQrSetupPopup",
components: {
ValidationProvider,
ValidationObserver,
AppInputText,
PopupWrapper,
PopupActions,
PopupContent,
PopupHeader,
ButtonBase,
required,
InfoBox,
},
computed: {
...mapGetters([
'user'
]),
closeQrButtonText() {
return this.isConfirmedClose
? this.$t('popup_2fa.disappear_qr')
: this.$t('shared_form.button_done')
},
closeQrButtonStyle() {
return this.isConfirmedClose
? 'danger'
: 'theme'
},
},
data() {
return {
isLoading: false,
qrCode: '',
isConfirmedClose: false,
}
},
methods: {
enable() {
axios
.post('/user/two-factor-authentication')
.then(() => {
name: 'TwoFactorQrSetupPopup',
components: {
ValidationProvider,
ValidationObserver,
AppInputText,
PopupWrapper,
PopupActions,
PopupContent,
PopupHeader,
ButtonBase,
required,
InfoBox,
},
computed: {
...mapGetters(['user']),
closeQrButtonText() {
return this.isConfirmedClose ? this.$t('popup_2fa.disappear_qr') : this.$t('shared_form.button_done')
},
closeQrButtonStyle() {
return this.isConfirmedClose ? 'danger' : 'theme'
},
},
data() {
return {
isLoading: false,
qrCode: '',
isConfirmedClose: false,
}
},
methods: {
enable() {
axios
.post('/user/two-factor-authentication')
.then(() => {
this.$store.commit('CHANGE_TWO_FACTOR_AUTHENTICATION_STATE', true)
this.$store.commit('CHANGE_TWO_FACTOR_AUTHENTICATION_STATE', true)
this.getQrCode()
})
.catch(() => {
this.$isSomethingWrong()
})
},
getQrCode() {
axios
.get('/user/two-factor-qr-code')
.then((response) => {
this.qrCode = response.data.svg
})
.catch(() => {
this.$isSomethingWrong()
})
},
closeQrCodePopup() {
if (!this.isConfirmedClose) {
this.isConfirmedClose = true
} else {
events.$emit('toaster', {
type: 'success',
message: this.$t('popup_2fa.toaster_enabled'),
})
this.getQrCode()
})
.catch(() => {
this.$isSomethingWrong()
})
},
getQrCode() {
axios
.get('/user/two-factor-qr-code')
.then(response => {
this.qrCode = response.data.svg
})
.catch(() => {
this.$isSomethingWrong()
})
},
closeQrCodePopup() {
if (!this.isConfirmedClose) {
this.isConfirmedClose = true
} else {
events.$emit('toaster', {
type: 'success',
message: this.$t('popup_2fa.toaster_enabled'),
})
this.qrCode = undefined
this.isConfirmedClose = false
this.qrCode = undefined
this.isConfirmedClose = false
this.$closePopup()
}
},
},
created() {
// Show popup
events.$on('popup:open', (args) => {
if (args.name !== 'two-factor-qr-setup') return
this.$closePopup()
}
}
},
created() {
// Show popup
events.$on('popup:open', args => {
if (args.name !== 'two-factor-qr-setup') return
this.enable()
})
}
this.enable()
})
},
}
</script>

View File

@@ -3,42 +3,37 @@
<PopupHeader :title="$t('popup_2fa.popup_codes_title')" icon="key" />
<PopupContent style="padding: 0 20px">
<div class="mobile-actions">
<MobileActionButton @click.native="copyCodes" icon="copy">
{{ $t('context_menu.copy') }}
</MobileActionButton>
<div class="mobile-actions">
<MobileActionButton @click.native="copyCodes" icon="copy">
{{ $t('context_menu.copy') }}
</MobileActionButton>
<MobileActionButton @click.native="downloadCodes" icon="download">
{{ $t('context_menu.download') }}
</MobileActionButton>
<MobileActionButton @click.native="downloadCodes" icon="download">
{{ $t('context_menu.download') }}
</MobileActionButton>
<MobileActionButton @click.native="regenerateCodes" icon="refresh">
{{ $t('context_menu.codes_regenerate') }}
</MobileActionButton>
</div>
<MobileActionButton @click.native="regenerateCodes" icon="refresh">
{{ $t('context_menu.codes_regenerate') }}
</MobileActionButton>
</div>
<ul v-if="!isLoading" class="codes-list">
<li v-for="(code, i) in codes" :key="i">{{ code }}</li>
</ul>
<ul v-if="! isLoading" class="codes-list">
<li v-for="(code, i) in codes" :key="i">{{ code }}</li>
</ul>
<div v-if="isLoading" class="spinner-wrapper">
<Spinner />
</div>
<div v-if="isLoading" class="spinner-wrapper">
<Spinner />
</div>
<textarea v-model="inputCodes" ref="codes" class="codes-output"></textarea>
<textarea v-model="inputCodes" ref="codes" class="codes-output"></textarea>
<InfoBox style="margin-bottom: 0">
<p v-html="$t('popup_2fa.popup_codes_disclaimer')"></p>
</InfoBox>
<InfoBox style="margin-bottom: 0">
<p v-html="$t('popup_2fa.popup_codes_disclaimer')"></p>
</InfoBox>
</PopupContent>
<PopupActions>
<ButtonBase
class="w-full"
@click.native="$closePopup()"
button-style="theme"
>
<ButtonBase class="w-full" @click.native="$closePopup()" button-style="theme">
{{ $t('shared_form.button_done') }}
</ButtonBase>
</PopupActions>
@@ -46,159 +41,160 @@
</template>
<script>
import MobileActionButton from "../FilesView/MobileActionButton";
import PopupWrapper from "./Popup/PopupWrapper";
import MobileActionButton from '../FilesView/MobileActionButton'
import PopupWrapper from './Popup/PopupWrapper'
import PopupActions from './Popup/PopupActions'
import PopupContent from './Popup/PopupContent'
import PopupHeader from './Popup/PopupHeader'
import ButtonBase from "../FilesView/ButtonBase";
import InfoBox from "./Forms/InfoBox";
import Spinner from "../FilesView/Spinner";
import {mapGetters} from "vuex"
import {events} from '../../bus'
import ButtonBase from '../FilesView/ButtonBase'
import InfoBox from './Forms/InfoBox'
import Spinner from '../FilesView/Spinner'
import { mapGetters } from 'vuex'
import { events } from '../../bus'
import axios from 'axios'
export default {
name: "TwoFactorRecoveryCodesPopup",
components: {
MobileActionButton,
PopupWrapper,
PopupActions,
PopupContent,
PopupHeader,
ButtonBase,
Spinner,
InfoBox,
},
data() {
return {
codes: undefined,
isLoading: true,
inputCodes: undefined,
}
},
computed: {
...mapGetters(['user']),
},
methods: {
copyCodes() {
let copyText = this.$refs.codes
name: 'TwoFactorRecoveryCodesPopup',
components: {
MobileActionButton,
PopupWrapper,
PopupActions,
PopupContent,
PopupHeader,
ButtonBase,
Spinner,
InfoBox,
},
data() {
return {
codes: undefined,
isLoading: true,
inputCodes: undefined,
}
},
computed: {
...mapGetters(['user']),
},
methods: {
copyCodes() {
let copyText = this.$refs.codes
copyText.select()
copyText.setSelectionRange(0, 99999)
copyText.select()
copyText.setSelectionRange(0, 99999)
document.execCommand('copy')
document.execCommand('copy')
events.$emit('toaster', {
type: 'success',
message: this.$t('popup_2fa.toaster_codes_copied'),
})
},
downloadCodes() {
// Create txt content
let recoveryCodes = "data:x-application/xml;charset=utf-8," + escape(this.codes.join("\n"));
events.$emit('toaster', {
type: 'success',
message: this.$t('popup_2fa.toaster_codes_copied'),
})
},
downloadCodes() {
// Create txt content
let recoveryCodes = 'data:x-application/xml;charset=utf-8,' + escape(this.codes.join('\n'))
// Create download link
let downloadLink = document.createElement("a")
// Create download link
let downloadLink = document.createElement('a')
downloadLink.href = recoveryCodes
downloadLink.download = "recovery-codes.txt"
downloadLink.href = recoveryCodes
downloadLink.download = 'recovery-codes.txt'
// Download .txt
document.body.appendChild(downloadLink);
downloadLink.click();
document.body.removeChild(downloadLink);
},
regenerateCodes() {
this.isLoading = true
// Download .txt
document.body.appendChild(downloadLink)
downloadLink.click()
document.body.removeChild(downloadLink)
},
regenerateCodes() {
this.isLoading = true
axios.post('/user/two-factor-recovery-codes')
.then(() => {
this.getRecoveryCodes()
axios
.post('/user/two-factor-recovery-codes')
.then(() => {
this.getRecoveryCodes()
events.$emit('toaster', {
type: 'success',
message: this.$t('popup_2fa.toaster_codes_regenerated'),
})
})
.catch(() => {
this.$isSomethingWrong()
})
.finally(() => this.isLoading = false)
},
getRecoveryCodes() {
axios.get('/user/two-factor-recovery-codes')
.then(response => {
this.codes = response.data
this.inputCodes = response.data.join("\n")
})
.catch(() => {
this.$isSomethingWrong()
})
.finally(() => this.isLoading = false)
}
},
created() {
events.$on('popup:open', ({name}) => {
if ('two-factor-recovery-codes' === name)
this.getRecoveryCodes()
})
}
events.$emit('toaster', {
type: 'success',
message: this.$t('popup_2fa.toaster_codes_regenerated'),
})
})
.catch(() => {
this.$isSomethingWrong()
})
.finally(() => (this.isLoading = false))
},
getRecoveryCodes() {
axios
.get('/user/two-factor-recovery-codes')
.then((response) => {
this.codes = response.data
this.inputCodes = response.data.join('\n')
})
.catch(() => {
this.$isSomethingWrong()
})
.finally(() => (this.isLoading = false))
},
},
created() {
events.$on('popup:open', ({ name }) => {
if ('two-factor-recovery-codes' === name) this.getRecoveryCodes()
})
},
}
</script>
<style lang="scss" scoped>
@import '../../../sass/vuefilemanager/variables';
@import '../../../sass/vuefilemanager/mixins';
@import '../../../sass/vuefilemanager/variables';
@import '../../../sass/vuefilemanager/mixins';
.mobile-actions {
white-space: nowrap;
overflow-x: auto;
margin: 0 -20px;
padding: 10px 0 10px 20px;
}
.mobile-actions {
white-space: nowrap;
overflow-x: auto;
margin: 0 -20px;
padding: 10px 0 10px 20px;
}
.codes-list {
margin: 5px 0 15px;
padding-left: 30px;
.codes-list {
margin: 5px 0 15px;
padding-left: 30px;
li {
@include font-size(14);
font-weight: bold;
padding: 10px 0;
border-bottom: 1px solid $light_mode_border;
list-style: circle;
li {
@include font-size(14);
font-weight: bold;
padding: 10px 0;
border-bottom: 1px solid $light_mode_border;
list-style: circle;
&:last-child {
border-bottom: none;
}
}
}
&:last-child {
border-bottom: none;
}
}
}
.codes-output {
position: absolute;
right: -9999px;
}
.codes-output {
position: absolute;
right: -9999px;
}
.spinner-wrapper {
height: 339px;
position: relative;
.spinner-wrapper {
height: 339px;
position: relative;
.spinner {
top: 46% !important;
}
}
.spinner {
top: 46% !important;
}
}
.dark {
.codes-list {
.dark {
.codes-list {
li {
border-color: $dark_mode_border_color;
}
}
li {
border-color: $dark_mode_border_color;
}
}
.info-box, .mobile-action-button {
background: lighten($dark_mode_foreground, 3%);
}
}
.info-box,
.mobile-action-button {
background: lighten($dark_mode_foreground, 3%);
}
}
</style>

View File

@@ -5,81 +5,78 @@
</template>
<script>
import {events} from '../../bus'
import { mapGetters } from 'vuex'
import { events } from '../../bus'
import { mapGetters } from 'vuex'
export default {
name: 'Vignette',
computed: {
...mapGetters([
'processingPopup'
]),
isVisible() {
return this.processingPopup || this.isVisibleVignette
},
export default {
name: 'Vignette',
computed: {
...mapGetters(['processingPopup']),
isVisible() {
return this.processingPopup || this.isVisibleVignette
},
data() {
return {
isVisibleVignette: false,
}
},
methods: {
closePopup() {
events.$emit('popup:close')
events.$emit('spotlight:hide')
events.$emit('mobile-menu:hide')
}
},
created() {
// Show vignette
events.$on('popup:open', () => this.isVisibleVignette = true)
events.$on('spotlight:show', () => this.isVisibleVignette = true)
events.$on('mobile-menu:show', () => this.isVisibleVignette = true)
events.$on('alert:open', () => this.isVisibleVignette = true)
events.$on('success:open', () => this.isVisibleVignette = true)
events.$on('confirm:open', () => this.isVisibleVignette = true)
// Hide vignette
events.$on('mobile-menu:hide', () => this.isVisibleVignette = false)
events.$on('popup:close', () => this.isVisibleVignette = false)
},
data() {
return {
isVisibleVignette: false,
}
}
},
methods: {
closePopup() {
events.$emit('popup:close')
events.$emit('spotlight:hide')
events.$emit('mobile-menu:hide')
},
},
created() {
// Show vignette
events.$on('popup:open', () => (this.isVisibleVignette = true))
events.$on('spotlight:show', () => (this.isVisibleVignette = true))
events.$on('mobile-menu:show', () => (this.isVisibleVignette = true))
events.$on('alert:open', () => (this.isVisibleVignette = true))
events.$on('success:open', () => (this.isVisibleVignette = true))
events.$on('confirm:open', () => (this.isVisibleVignette = true))
// Hide vignette
events.$on('mobile-menu:hide', () => (this.isVisibleVignette = false))
events.$on('popup:close', () => (this.isVisibleVignette = false))
},
}
</script>
<style lang="scss" scoped>
@import '../../../sass/vuefilemanager/variables';
@import '../../../sass/vuefilemanager/mixins';
@import '../../../sass/vuefilemanager/variables';
@import '../../../sass/vuefilemanager/mixins';
.vignette {
position: fixed;
top: 0;
right: 0;
left: 0;
bottom: 0;
z-index: 40;
background: $light_mode_vignette;
.vignette {
position: fixed;
top: 0;
right: 0;
left: 0;
bottom: 0;
z-index: 40;
background: $light_mode_vignette;
}
// Dark mode
.dark .vignette {
background: $dark_mode_vignette;
}
.vignette-enter-active {
animation: vignette-in 0.35s ease;
}
.vignette-leave-active {
animation: vignette-in 0.15s ease reverse;
}
@keyframes vignette-in {
0% {
opacity: 0;
}
// Dark mode
.dark .vignette {
background: $dark_mode_vignette;
}
.vignette-enter-active {
animation: vignette-in 0.35s ease;
}
.vignette-leave-active {
animation: vignette-in 0.15s ease reverse;
}
@keyframes vignette-in {
0% {
opacity: 0;
}
100% {
opacity: 1;
}
100% {
opacity: 1;
}
}
</style>