mirror of
https://github.com/VueFileManager/vuefilemanager.git
synced 2026-04-18 16:22:14 +00:00
vue components refactoring
This commit is contained in:
@@ -1,52 +0,0 @@
|
||||
<template>
|
||||
<div class="action-button">
|
||||
<x-icon size="12" class="icon text-theme dark-text-theme" v-if="icon === 'x'" />
|
||||
<edit-2-icon size="12" class="icon text-theme dark-text-theme" v-if="icon === 'pencil-alt'" />
|
||||
<span class="label">
|
||||
<slot></slot>
|
||||
</span>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { Edit2Icon, XIcon } from 'vue-feather-icons'
|
||||
|
||||
export default {
|
||||
name: 'ActionButton',
|
||||
props: ['icon'],
|
||||
components: {
|
||||
Edit2Icon,
|
||||
XIcon,
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
@import '../../../sass/vuefilemanager/variables';
|
||||
@import '../../../sass/vuefilemanager/mixins';
|
||||
|
||||
.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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.dark {
|
||||
}
|
||||
</style>
|
||||
@@ -1,182 +0,0 @@
|
||||
<template>
|
||||
<PopupWrapper name="select-payment-method">
|
||||
<PopupHeader :title="$t('select_payment_method')" icon="credit-card" />
|
||||
|
||||
<PopupContent style="padding: 0 20px">
|
||||
<InfoBox v-if="!config.isPayPal && !config.isPaystack" class="!mb-0">
|
||||
<p>{{ $t("not_any_payment_method") }}</p>
|
||||
</InfoBox>
|
||||
|
||||
<!--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="payByPayPal"
|
||||
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 Buttons-->
|
||||
<div id="paypal-button-container"></div>
|
||||
</div>
|
||||
|
||||
<!--Paystack implementation-->
|
||||
<PaymentMethod
|
||||
v-if="config.isPaystack"
|
||||
driver="paystack"
|
||||
:description="$t(config.paystack_payment_description)"
|
||||
>
|
||||
<div v-if="paystack.isGettingCheckoutLink" class="translate-y-3 scale-50 transform">
|
||||
<Spinner />
|
||||
</div>
|
||||
<span
|
||||
@click="payByPaystack()"
|
||||
:class="{ 'opacity-0': paystack.isGettingCheckoutLink }"
|
||||
class="text-theme cursor-pointer text-sm font-bold"
|
||||
>
|
||||
{{ $t('select') }}
|
||||
</span>
|
||||
</PaymentMethod>
|
||||
</PopupContent>
|
||||
|
||||
<PopupActions>
|
||||
<ButtonBase class="w-full" @click.native="$closePopup()" button-style="secondary">
|
||||
{{ $t('cancel_payment') }}
|
||||
</ButtonBase>
|
||||
</PopupActions>
|
||||
</PopupWrapper>
|
||||
</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 InfoBox from "./Forms/InfoBox"
|
||||
import { events } from '../../bus'
|
||||
import { mapGetters } from 'vuex'
|
||||
import axios from "axios";
|
||||
|
||||
export default {
|
||||
name: 'ChargePaymentPopup',
|
||||
components: {
|
||||
PaymentMethod,
|
||||
PopupWrapper,
|
||||
PopupActions,
|
||||
PopupContent,
|
||||
PopupHeader,
|
||||
ButtonBase,
|
||||
Spinner,
|
||||
InfoBox,
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
paypal: {
|
||||
isMethodsLoaded: false,
|
||||
isMethodLoading: false,
|
||||
},
|
||||
paystack: {
|
||||
isGettingCheckoutLink: false,
|
||||
},
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
...mapGetters(['singleChargeAmount', 'config', 'user']),
|
||||
},
|
||||
methods: {
|
||||
payByPaystack() {
|
||||
this.paystack.isGettingCheckoutLink = true
|
||||
|
||||
axios
|
||||
.post('/api/paystack/checkout', {
|
||||
amount: this.singleChargeAmount * 100,
|
||||
})
|
||||
.then((response) => {
|
||||
window.location = response.data.data.authorization_url
|
||||
})
|
||||
},
|
||||
async payByPayPal() {
|
||||
if (this.paypal.isMethodLoading) {
|
||||
return
|
||||
}
|
||||
|
||||
this.paypal.isMethodLoading = true
|
||||
|
||||
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_paypal'),
|
||||
})
|
||||
}
|
||||
|
||||
const userId = this.user.data.id
|
||||
const amount = this.singleChargeAmount
|
||||
|
||||
this.paypal.isMethodsLoaded = true
|
||||
this.paypal.isMethodLoading = false
|
||||
const app = this
|
||||
|
||||
// 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,
|
||||
},
|
||||
],
|
||||
})
|
||||
},
|
||||
onApprove: function () {
|
||||
app.paymentSuccessful()
|
||||
},
|
||||
})
|
||||
.render('#paypal-button-container')
|
||||
},
|
||||
paymentSuccessful() {
|
||||
this.$closePopup()
|
||||
|
||||
events.$emit('toaster', {
|
||||
type: 'success',
|
||||
message: this.$t('payment_was_successfully_received'),
|
||||
})
|
||||
|
||||
// todo: temporary reload function
|
||||
setTimeout(() => document.location.reload(), 500)
|
||||
},
|
||||
},
|
||||
created() {
|
||||
events.$on('popup:close', () => (this.paypal.isMethodsLoaded = false))
|
||||
},
|
||||
}
|
||||
</script>
|
||||
@@ -1,38 +0,0 @@
|
||||
<template>
|
||||
<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'],
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
@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);
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -1,132 +0,0 @@
|
||||
<template>
|
||||
<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>
|
||||
</ul>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { CheckIcon } from 'vue-feather-icons'
|
||||
import { mapGetters } from 'vuex'
|
||||
|
||||
export default {
|
||||
name: 'ColorPicker',
|
||||
props: ['pickedColor'],
|
||||
components: { CheckIcon },
|
||||
computed: {
|
||||
...mapGetters(['config']),
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
selectedColor: this.pickedColor,
|
||||
colors: [
|
||||
'#FE6F6F',
|
||||
'#FE6F91',
|
||||
'#FE6FC0',
|
||||
'#FE6FF0',
|
||||
'#DD6FFE',
|
||||
'#AD6FFE',
|
||||
'#7D6FFE',
|
||||
'#6F90FE',
|
||||
'#6FC0FE',
|
||||
'#6FF0FE',
|
||||
'#6FFEDD',
|
||||
'#6FFEAD',
|
||||
'#6FFE7D',
|
||||
'#90FE6F',
|
||||
'#C0FE6F',
|
||||
'#F0FE6F',
|
||||
'#FEDD6F',
|
||||
'#FEAD6F',
|
||||
'#FE7D6F',
|
||||
'#4c4c4c',
|
||||
'#06070B',
|
||||
],
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
setColor(value) {
|
||||
this.selectedColor = value
|
||||
|
||||
this.$emit('input', value)
|
||||
},
|
||||
},
|
||||
created() {
|
||||
this.colors.push(this.config.app_color)
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
@import '../../../sass/vuefilemanager/inapp-forms';
|
||||
@import '../../../sass/vuefilemanager/forms';
|
||||
|
||||
.color-pick-wrapper {
|
||||
.color-wrapper {
|
||||
margin-bottom: 20px;
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fill, 32px);
|
||||
justify-content: space-between;
|
||||
gap: 7px;
|
||||
|
||||
.single-color {
|
||||
height: 31px;
|
||||
list-style: none;
|
||||
border-radius: 8px;
|
||||
cursor: pointer;
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
|
||||
.color-icon {
|
||||
z-index: 2;
|
||||
|
||||
polyline {
|
||||
stroke: white;
|
||||
}
|
||||
}
|
||||
|
||||
.color-box {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
position: absolute;
|
||||
display: block;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.main-label {
|
||||
@include font-size(14);
|
||||
font-weight: 700;
|
||||
margin-bottom: 8px;
|
||||
display: block;
|
||||
}
|
||||
|
||||
.set-folder-icon {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.dark {
|
||||
.color-pick-wrapper {
|
||||
.color-wrapper {
|
||||
.single-color {
|
||||
&.active-color {
|
||||
border: 2px solid;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
border: 2px solid $dark_mode_text_primary;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -1,108 +0,0 @@
|
||||
<template>
|
||||
<PopupWrapper name="confirm-password">
|
||||
<PopupHeader :title="$t('confirm_password')" icon="edit" />
|
||||
|
||||
<PopupContent>
|
||||
<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('password')" :error="errors[0]" :is-last="true">
|
||||
<input
|
||||
v-model="password"
|
||||
:class="{ '!border-rose-600': 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">
|
||||
{{ $t('cancel') }}
|
||||
</ButtonBase>
|
||||
<ButtonBase
|
||||
class="w-full"
|
||||
@click.native="confirmPassword"
|
||||
button-style="theme"
|
||||
:loading="isLoading"
|
||||
:disabled="isLoading"
|
||||
>
|
||||
{{ $t('confirm') }}
|
||||
</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 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: undefined,
|
||||
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) => {
|
||||
if (args.name !== 'confirm-password') return
|
||||
|
||||
this.args = args
|
||||
})
|
||||
},
|
||||
}
|
||||
</script>
|
||||
@@ -1,46 +0,0 @@
|
||||
<template>
|
||||
<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>
|
||||
</i18n>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
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 =
|
||||
this.config.installation === 'installation-done' && !localStorage.getItem('isHiddenDisclaimer')
|
||||
},
|
||||
}
|
||||
</script>
|
||||
@@ -1,113 +0,0 @@
|
||||
<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-rose-600': 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_icon_with_emoji')"
|
||||
:is-last="!isEmoji"
|
||||
>
|
||||
<SwitchInput v-model="isEmoji" :state="isEmoji" />
|
||||
</AppInputSwitch>
|
||||
|
||||
<!--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('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'
|
||||
|
||||
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>
|
||||
@@ -1,866 +0,0 @@
|
||||
<template>
|
||||
<PopupWrapper name="create-language">
|
||||
<!--Title-->
|
||||
<PopupHeader :title="$t('create_language')" icon="edit" />
|
||||
|
||||
<!--Content-->
|
||||
<PopupContent class="!overflow-initial">
|
||||
<!--Form to set sharing-->
|
||||
<ValidationObserver @submit.prevent="createLanguage" ref="createForm" v-slot="{ invalid }" tag="form">
|
||||
<ValidationProvider
|
||||
tag="div"
|
||||
mode="passive"
|
||||
name="Language Locale"
|
||||
rules="required"
|
||||
v-slot="{ errors }"
|
||||
>
|
||||
<AppInputText :title="$t('select_locale')" :error="errors[0]">
|
||||
<SelectInput
|
||||
v-model="form.locale"
|
||||
:options="locales"
|
||||
:placeholder="$t('select_language_locale')"
|
||||
:isError="errors[0]"
|
||||
/>
|
||||
</AppInputText>
|
||||
</ValidationProvider>
|
||||
<ValidationProvider tag="div" mode="passive" name="Language Name" rules="required" v-slot="{ errors }">
|
||||
<AppInputText :title="$t('locale_name')" :error="errors[0]" :is-last="true">
|
||||
<input
|
||||
v-model="form.name"
|
||||
:class="{ '!border-rose-600': errors[0] }"
|
||||
type="text"
|
||||
ref="input"
|
||||
class="focus-border-theme input-dark"
|
||||
:placeholder="$t('type_language_name')"
|
||||
/>
|
||||
</AppInputText>
|
||||
</ValidationProvider>
|
||||
</ValidationObserver>
|
||||
</PopupContent>
|
||||
|
||||
<!--Actions-->
|
||||
<PopupActions>
|
||||
<ButtonBase class="w-full" @click.native="$closePopup()" button-style="secondary">
|
||||
{{ $t('cancel') }}
|
||||
</ButtonBase>
|
||||
<ButtonBase
|
||||
class="w-full"
|
||||
@click.native="createLanguage"
|
||||
button-style="theme"
|
||||
:loading="isLoading"
|
||||
:disabled="isLoading"
|
||||
>
|
||||
{{ $t('create_language') }}
|
||||
</ButtonBase>
|
||||
</PopupActions>
|
||||
</PopupWrapper>
|
||||
</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 SelectInput from './Forms/SelectInput'
|
||||
import ButtonBase from '../FilesView/ButtonBase'
|
||||
import { required } from 'vee-validate/dist/rules'
|
||||
import { events } from '../../bus'
|
||||
import axios from 'axios'
|
||||
|
||||
export default {
|
||||
name: 'CreateLanguage',
|
||||
components: {
|
||||
ValidationProvider,
|
||||
ValidationObserver,
|
||||
AppInputText,
|
||||
PopupWrapper,
|
||||
PopupActions,
|
||||
PopupContent,
|
||||
PopupHeader,
|
||||
SelectInput,
|
||||
ButtonBase,
|
||||
required,
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
form: {
|
||||
name: undefined,
|
||||
locale: undefined,
|
||||
},
|
||||
isLoading: false,
|
||||
locales: [
|
||||
{
|
||||
value: 'ab',
|
||||
label: 'Abkhaz',
|
||||
},
|
||||
{
|
||||
value: 'aa',
|
||||
label: 'Afar',
|
||||
},
|
||||
{
|
||||
value: 'af',
|
||||
label: 'Afrikaans',
|
||||
},
|
||||
{
|
||||
value: 'ak',
|
||||
label: 'Akan',
|
||||
},
|
||||
{
|
||||
value: 'sq',
|
||||
label: 'Albanian',
|
||||
},
|
||||
{
|
||||
value: 'am',
|
||||
label: 'Amharic',
|
||||
},
|
||||
{
|
||||
value: 'ar',
|
||||
label: 'Arabic',
|
||||
},
|
||||
{
|
||||
value: 'an',
|
||||
label: 'Aragonese',
|
||||
},
|
||||
{
|
||||
value: 'hy',
|
||||
label: 'Armenian',
|
||||
},
|
||||
{
|
||||
value: 'as',
|
||||
label: 'Assamese',
|
||||
},
|
||||
{
|
||||
value: 'av',
|
||||
label: 'Avaric',
|
||||
},
|
||||
{
|
||||
value: 'ae',
|
||||
label: 'Avestan',
|
||||
},
|
||||
{
|
||||
value: 'ay',
|
||||
label: 'Aymara',
|
||||
},
|
||||
{
|
||||
value: 'az',
|
||||
label: 'Azerbaijani',
|
||||
},
|
||||
{
|
||||
value: 'bm',
|
||||
label: 'Bambara',
|
||||
},
|
||||
{
|
||||
value: 'ba',
|
||||
label: 'Bashkir',
|
||||
},
|
||||
{
|
||||
value: 'eu',
|
||||
label: 'Basque',
|
||||
},
|
||||
{
|
||||
value: 'be',
|
||||
label: 'Belarusian',
|
||||
},
|
||||
{
|
||||
value: 'bn',
|
||||
label: 'Bengali; Bangla',
|
||||
},
|
||||
{
|
||||
value: 'bh',
|
||||
label: 'Bihari',
|
||||
},
|
||||
{
|
||||
value: 'bi',
|
||||
label: 'Bislama',
|
||||
},
|
||||
{
|
||||
value: 'bs',
|
||||
label: 'Bosnian',
|
||||
},
|
||||
{
|
||||
value: 'br',
|
||||
label: 'Breton',
|
||||
},
|
||||
{
|
||||
value: 'bg',
|
||||
label: 'Bulgarian',
|
||||
},
|
||||
{
|
||||
value: 'my',
|
||||
label: 'Burmese',
|
||||
},
|
||||
{
|
||||
value: 'ca',
|
||||
label: 'Catalan; Valencian',
|
||||
},
|
||||
{
|
||||
value: 'ch',
|
||||
label: 'Chamorro',
|
||||
},
|
||||
{
|
||||
value: 'ce',
|
||||
label: 'Chechen',
|
||||
},
|
||||
{
|
||||
value: 'ny',
|
||||
label: 'Chichewa; Chewa; Nyanja',
|
||||
},
|
||||
{
|
||||
value: 'zh',
|
||||
label: 'Chinese',
|
||||
},
|
||||
{
|
||||
value: 'cv',
|
||||
label: 'Chuvash',
|
||||
},
|
||||
{
|
||||
value: 'kw',
|
||||
label: 'Cornish',
|
||||
},
|
||||
{
|
||||
value: 'co',
|
||||
label: 'Corsican',
|
||||
},
|
||||
{
|
||||
value: 'cr',
|
||||
label: 'Cree',
|
||||
},
|
||||
{
|
||||
value: 'hr',
|
||||
label: 'Croatian',
|
||||
},
|
||||
{
|
||||
value: 'cs',
|
||||
label: 'Czech',
|
||||
},
|
||||
{
|
||||
value: 'da',
|
||||
label: 'Danish',
|
||||
},
|
||||
{
|
||||
value: 'dv',
|
||||
label: 'Divehi; Dhivehi; Maldivian;',
|
||||
},
|
||||
{
|
||||
value: 'nl',
|
||||
label: 'Dutch',
|
||||
},
|
||||
{
|
||||
value: 'dz',
|
||||
label: 'Dzongkha',
|
||||
},
|
||||
{
|
||||
value: 'en',
|
||||
label: 'English',
|
||||
},
|
||||
{
|
||||
value: 'eo',
|
||||
label: 'Esperanto',
|
||||
},
|
||||
{
|
||||
value: 'et',
|
||||
label: 'Estonian',
|
||||
},
|
||||
{
|
||||
value: 'ee',
|
||||
label: 'Ewe',
|
||||
},
|
||||
{
|
||||
value: 'fo',
|
||||
label: 'Faroese',
|
||||
},
|
||||
{
|
||||
value: 'fj',
|
||||
label: 'Fijian',
|
||||
},
|
||||
{
|
||||
value: 'fi',
|
||||
label: 'Finnish',
|
||||
},
|
||||
{
|
||||
value: 'fr',
|
||||
label: 'French',
|
||||
},
|
||||
{
|
||||
value: 'ff',
|
||||
label: 'Fula; Fulah; Pulaar; Pular',
|
||||
},
|
||||
{
|
||||
value: 'gl',
|
||||
label: 'Galician',
|
||||
},
|
||||
{
|
||||
value: 'lg',
|
||||
label: 'Ganda',
|
||||
},
|
||||
{
|
||||
value: 'ka',
|
||||
label: 'Georgian',
|
||||
},
|
||||
{
|
||||
value: 'de',
|
||||
label: 'German',
|
||||
},
|
||||
{
|
||||
value: 'el',
|
||||
label: 'Greek, Modern',
|
||||
},
|
||||
{
|
||||
value: 'gn',
|
||||
label: 'GuaranÃ',
|
||||
},
|
||||
{
|
||||
value: 'gu',
|
||||
label: 'Gujarati',
|
||||
},
|
||||
{
|
||||
value: 'ht',
|
||||
label: 'Haitian; Haitian Creole',
|
||||
},
|
||||
{
|
||||
value: 'ha',
|
||||
label: 'Hausa',
|
||||
},
|
||||
{
|
||||
value: 'he',
|
||||
label: 'Hebrew (modern)',
|
||||
},
|
||||
{
|
||||
value: 'hz',
|
||||
label: 'Herero',
|
||||
},
|
||||
{
|
||||
value: 'hi',
|
||||
label: 'Hindi',
|
||||
},
|
||||
{
|
||||
value: 'ho',
|
||||
label: 'Hiri Motu',
|
||||
},
|
||||
{
|
||||
value: 'hu',
|
||||
label: 'Hungarian',
|
||||
},
|
||||
{
|
||||
value: 'ia',
|
||||
label: 'Interlingua',
|
||||
},
|
||||
{
|
||||
value: 'id',
|
||||
label: 'Indonesian',
|
||||
},
|
||||
{
|
||||
value: 'ie',
|
||||
label: 'Interlingue',
|
||||
},
|
||||
{
|
||||
value: 'ga',
|
||||
label: 'Irish',
|
||||
},
|
||||
{
|
||||
value: 'ig',
|
||||
label: 'Igbo',
|
||||
},
|
||||
{
|
||||
value: 'ik',
|
||||
label: 'Inupiaq',
|
||||
},
|
||||
{
|
||||
value: 'io',
|
||||
label: 'Ido',
|
||||
},
|
||||
{
|
||||
value: 'is',
|
||||
label: 'Icelandic',
|
||||
},
|
||||
{
|
||||
value: 'it',
|
||||
label: 'Italian',
|
||||
},
|
||||
{
|
||||
value: 'iu',
|
||||
label: 'Inuktitut',
|
||||
},
|
||||
{
|
||||
value: 'ja',
|
||||
label: 'Japanese',
|
||||
},
|
||||
{
|
||||
value: 'jv',
|
||||
label: 'Javanese',
|
||||
},
|
||||
{
|
||||
value: 'kl',
|
||||
label: 'Kalaallisut, Greenlandic',
|
||||
},
|
||||
{
|
||||
value: 'kn',
|
||||
label: 'Kannada',
|
||||
},
|
||||
{
|
||||
value: 'kr',
|
||||
label: 'Kanuri',
|
||||
},
|
||||
{
|
||||
value: 'ks',
|
||||
label: 'Kashmiri',
|
||||
},
|
||||
{
|
||||
value: 'kk',
|
||||
label: 'Kazakh',
|
||||
},
|
||||
{
|
||||
value: 'km',
|
||||
label: 'Khmer',
|
||||
},
|
||||
{
|
||||
value: 'ki',
|
||||
label: 'Kikuyu, Gikuyu',
|
||||
},
|
||||
{
|
||||
value: 'rw',
|
||||
label: 'Kinyarwanda',
|
||||
},
|
||||
{
|
||||
value: 'rn',
|
||||
label: 'Kirundi',
|
||||
},
|
||||
{
|
||||
value: 'ky',
|
||||
label: 'Kyrgyz',
|
||||
},
|
||||
{
|
||||
value: 'kv',
|
||||
label: 'Komi',
|
||||
},
|
||||
{
|
||||
value: 'kg',
|
||||
label: 'Kongo',
|
||||
},
|
||||
{
|
||||
value: 'ko',
|
||||
label: 'Korean',
|
||||
},
|
||||
{
|
||||
value: 'ku',
|
||||
label: 'Kurdish',
|
||||
},
|
||||
{
|
||||
value: 'kj',
|
||||
label: 'Kwanyama, Kuanyama',
|
||||
},
|
||||
{
|
||||
value: 'la',
|
||||
label: 'Latin',
|
||||
},
|
||||
{
|
||||
value: 'lb',
|
||||
label: 'Luxembourgish, Letzeburgesch',
|
||||
},
|
||||
{
|
||||
value: 'li',
|
||||
label: 'Limburgish, Limburgan, Limburger',
|
||||
},
|
||||
{
|
||||
value: 'ln',
|
||||
label: 'Lingala',
|
||||
},
|
||||
{
|
||||
value: 'lo',
|
||||
label: 'Lao',
|
||||
},
|
||||
{
|
||||
value: 'lt',
|
||||
label: 'Lithuanian',
|
||||
},
|
||||
{
|
||||
value: 'lu',
|
||||
label: 'Luba-Katanga',
|
||||
},
|
||||
{
|
||||
value: 'lv',
|
||||
label: 'Latvian',
|
||||
},
|
||||
{
|
||||
value: 'gv',
|
||||
label: 'Manx',
|
||||
},
|
||||
{
|
||||
value: 'mk',
|
||||
label: 'Macedonian',
|
||||
},
|
||||
{
|
||||
value: 'mg',
|
||||
label: 'Malagasy',
|
||||
},
|
||||
{
|
||||
value: 'ms',
|
||||
label: 'Malay',
|
||||
},
|
||||
{
|
||||
value: 'ml',
|
||||
label: 'Malayalam',
|
||||
},
|
||||
{
|
||||
value: 'mt',
|
||||
label: 'Maltese',
|
||||
},
|
||||
{
|
||||
value: 'mi',
|
||||
label: 'MÄori',
|
||||
},
|
||||
{
|
||||
value: 'mr',
|
||||
label: 'Marathi',
|
||||
},
|
||||
{
|
||||
value: 'mh',
|
||||
label: 'Marshallese',
|
||||
},
|
||||
{
|
||||
value: 'mn',
|
||||
label: 'Mongolian',
|
||||
},
|
||||
{
|
||||
value: 'na',
|
||||
label: 'Nauru',
|
||||
},
|
||||
{
|
||||
value: 'nv',
|
||||
label: 'Navajo, Navaho',
|
||||
},
|
||||
{
|
||||
value: 'nb',
|
||||
label: 'Norwegian',
|
||||
},
|
||||
{
|
||||
value: 'nd',
|
||||
label: 'North Ndebele',
|
||||
},
|
||||
{
|
||||
value: 'ne',
|
||||
label: 'Nepali',
|
||||
},
|
||||
{
|
||||
value: 'ng',
|
||||
label: 'Ndonga',
|
||||
},
|
||||
{
|
||||
value: 'nn',
|
||||
label: 'Norwegian Nynorsk',
|
||||
},
|
||||
{
|
||||
value: 'no',
|
||||
label: 'Norwegian',
|
||||
},
|
||||
{
|
||||
value: 'ii',
|
||||
label: 'Nuosu',
|
||||
},
|
||||
{
|
||||
value: 'oc',
|
||||
label: 'Occitan',
|
||||
},
|
||||
{
|
||||
value: 'oj',
|
||||
label: 'Ojibwe, Ojibwa',
|
||||
},
|
||||
{
|
||||
value: 'cu',
|
||||
label: 'Old Church Slavonic, Church Slavic, Church Slavonic, Old Bulgarian, Old Slavonic',
|
||||
},
|
||||
{
|
||||
value: 'om',
|
||||
label: 'Oromo',
|
||||
},
|
||||
{
|
||||
value: 'or',
|
||||
label: 'Oriya',
|
||||
},
|
||||
{
|
||||
value: 'os',
|
||||
label: 'Ossetian, Ossetic',
|
||||
},
|
||||
{
|
||||
value: 'pa',
|
||||
label: 'Panjabi, Punjabi',
|
||||
},
|
||||
{
|
||||
value: 'pi',
|
||||
label: 'Pali',
|
||||
},
|
||||
{
|
||||
value: 'fa',
|
||||
label: 'Persian (Farsi)',
|
||||
},
|
||||
{
|
||||
value: 'pl',
|
||||
label: 'Polish',
|
||||
},
|
||||
{
|
||||
value: 'ps',
|
||||
label: 'Pashto, Pushto',
|
||||
},
|
||||
{
|
||||
value: 'pt',
|
||||
label: 'Portuguese',
|
||||
},
|
||||
{
|
||||
value: 'qu',
|
||||
label: 'Quechua',
|
||||
},
|
||||
{
|
||||
value: 'rm',
|
||||
label: 'Romansh',
|
||||
},
|
||||
{
|
||||
value: 'ro',
|
||||
label: 'Romanian',
|
||||
},
|
||||
{
|
||||
value: 'ru',
|
||||
label: 'Russian',
|
||||
},
|
||||
{
|
||||
value: 'sa',
|
||||
label: 'Sanskrit',
|
||||
},
|
||||
{
|
||||
value: 'sc',
|
||||
label: 'Sardinian',
|
||||
},
|
||||
{
|
||||
value: 'sd',
|
||||
label: 'Sindhi',
|
||||
},
|
||||
{
|
||||
value: 'se',
|
||||
label: 'Northern Sami',
|
||||
},
|
||||
{
|
||||
value: 'sm',
|
||||
label: 'Samoan',
|
||||
},
|
||||
{
|
||||
value: 'sg',
|
||||
label: 'Sango',
|
||||
},
|
||||
{
|
||||
value: 'sr',
|
||||
label: 'Serbian',
|
||||
},
|
||||
{
|
||||
value: 'gd',
|
||||
label: 'Scottish Gaelic',
|
||||
},
|
||||
{
|
||||
value: 'sn',
|
||||
label: 'Shona',
|
||||
},
|
||||
{
|
||||
value: 'si',
|
||||
label: 'Sinhala, Sinhalese',
|
||||
},
|
||||
{
|
||||
value: 'sk',
|
||||
label: 'Slovak',
|
||||
},
|
||||
{
|
||||
value: 'sl',
|
||||
label: 'Slovene',
|
||||
},
|
||||
{
|
||||
value: 'so',
|
||||
label: 'Somali',
|
||||
},
|
||||
{
|
||||
value: 'st',
|
||||
label: 'Southern Sotho',
|
||||
},
|
||||
{
|
||||
value: 'az',
|
||||
label: 'South Azerbaijani',
|
||||
},
|
||||
{
|
||||
value: 'nr',
|
||||
label: 'South Ndebele',
|
||||
},
|
||||
{
|
||||
value: 'es',
|
||||
label: 'Spanish; Castilian',
|
||||
},
|
||||
{
|
||||
value: 'su',
|
||||
label: 'Sundanese',
|
||||
},
|
||||
{
|
||||
value: 'sw',
|
||||
label: 'Swahili',
|
||||
},
|
||||
{
|
||||
value: 'ss',
|
||||
label: 'Swati',
|
||||
},
|
||||
{
|
||||
value: 'sv',
|
||||
label: 'Swedish',
|
||||
},
|
||||
{
|
||||
value: 'ta',
|
||||
label: 'Tamil',
|
||||
},
|
||||
{
|
||||
value: 'te',
|
||||
label: 'Telugu',
|
||||
},
|
||||
{
|
||||
value: 'tg',
|
||||
label: 'Tajik',
|
||||
},
|
||||
{
|
||||
value: 'th',
|
||||
label: 'Thai',
|
||||
},
|
||||
{
|
||||
value: 'ti',
|
||||
label: 'Tigrinya',
|
||||
},
|
||||
{
|
||||
value: 'bo',
|
||||
label: 'Tibetan Standard, Tibetan, Central',
|
||||
},
|
||||
{
|
||||
value: 'tk',
|
||||
label: 'Turkmen',
|
||||
},
|
||||
{
|
||||
value: 'tl',
|
||||
label: 'Tagalog',
|
||||
},
|
||||
{
|
||||
value: 'tn',
|
||||
label: 'Tswana',
|
||||
},
|
||||
{
|
||||
value: 'to',
|
||||
label: 'Tonga (Tonga Islands)',
|
||||
},
|
||||
{
|
||||
value: 'tr',
|
||||
label: 'Turkish',
|
||||
},
|
||||
{
|
||||
value: 'ts',
|
||||
label: 'Tsonga',
|
||||
},
|
||||
{
|
||||
value: 'tt',
|
||||
label: 'Tatar',
|
||||
},
|
||||
{
|
||||
value: 'tw',
|
||||
label: 'Twi',
|
||||
},
|
||||
{
|
||||
value: 'ty',
|
||||
label: 'Tahitian',
|
||||
},
|
||||
{
|
||||
value: 'ug',
|
||||
label: 'Uyghur, Uighur',
|
||||
},
|
||||
{
|
||||
value: 'uk',
|
||||
label: 'Ukrainian',
|
||||
},
|
||||
{
|
||||
value: 'ur',
|
||||
label: 'Urdu',
|
||||
},
|
||||
{
|
||||
value: 'uz',
|
||||
label: 'Uzbek',
|
||||
},
|
||||
{
|
||||
value: 've',
|
||||
label: 'Venda',
|
||||
},
|
||||
{
|
||||
value: 'vi',
|
||||
label: 'Vielabele',
|
||||
},
|
||||
{
|
||||
value: 'vo',
|
||||
label: 'Volapük',
|
||||
},
|
||||
{
|
||||
value: 'wa',
|
||||
label: 'Walloon',
|
||||
},
|
||||
{
|
||||
value: 'cy',
|
||||
label: 'Welsh',
|
||||
},
|
||||
{
|
||||
value: 'wo',
|
||||
label: 'Wolof',
|
||||
},
|
||||
{
|
||||
value: 'fy',
|
||||
label: 'Western Frisian',
|
||||
},
|
||||
{
|
||||
value: 'xh',
|
||||
label: 'Xhosa',
|
||||
},
|
||||
{
|
||||
value: 'yi',
|
||||
label: 'Yiddish',
|
||||
},
|
||||
{
|
||||
value: 'yo',
|
||||
label: 'Yoruba',
|
||||
},
|
||||
{
|
||||
value: 'za',
|
||||
label: 'Zhuang, Chuang',
|
||||
},
|
||||
{
|
||||
value: 'zu',
|
||||
label: 'Zulu',
|
||||
},
|
||||
],
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
async createLanguage() {
|
||||
// Validate fields
|
||||
const isValid = await this.$refs.createForm.validate()
|
||||
|
||||
if (isValid) {
|
||||
this.isLoading = true
|
||||
|
||||
axios
|
||||
.post('/api/admin/languages', this.form)
|
||||
.then((response) => {
|
||||
events.$emit('reload:languages', response.data)
|
||||
})
|
||||
.catch(() => {
|
||||
this.$isSomethingWrong()
|
||||
})
|
||||
.finally(() => {
|
||||
this.form = {
|
||||
name: undefined,
|
||||
locale: undefined,
|
||||
}
|
||||
|
||||
this.isLoading = false
|
||||
this.$closePopup()
|
||||
})
|
||||
}
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
@@ -1,134 +0,0 @@
|
||||
<template>
|
||||
<PopupWrapper name="create-personal-token">
|
||||
<PopupHeader :title="$t('create_personal_token')" icon="key" />
|
||||
|
||||
<PopupContent>
|
||||
<ValidationObserver
|
||||
v-if="!token"
|
||||
@submit.prevent="createTokenForm"
|
||||
ref="createToken"
|
||||
v-slot="{ invalid }"
|
||||
tag="form"
|
||||
>
|
||||
<ValidationProvider tag="div" mode="passive" name="Token Name" rules="required|min:3" v-slot="{ errors }">
|
||||
<AppInputText :title="$t('token_name')" :error="errors[0]" :is-last="true">
|
||||
<input
|
||||
v-model="name"
|
||||
:class="{ '!border-rose-600': 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>
|
||||
</PopupContent>
|
||||
|
||||
<PopupActions v-if="!token">
|
||||
<ButtonBase class="w-full" @click.native="$closePopup()" button-style="secondary">
|
||||
{{ $t('cancel') }}
|
||||
</ButtonBase>
|
||||
<ButtonBase
|
||||
class="w-full"
|
||||
@click.native="createTokenForm"
|
||||
button-style="theme"
|
||||
:loading="isLoading"
|
||||
:disabled="isLoading"
|
||||
>
|
||||
{{ $t('create_token') }}
|
||||
</ButtonBase>
|
||||
</PopupActions>
|
||||
|
||||
<PopupActions v-if="token">
|
||||
<ButtonBase class="w-full" @click.native="$closePopup" button-style="theme">
|
||||
{{ $t('awesome_iam_done') }}
|
||||
</ButtonBase>
|
||||
</PopupActions>
|
||||
</PopupWrapper>
|
||||
</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 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() {
|
||||
const isValid = await this.$refs.createToken.validate()
|
||||
|
||||
if (!isValid) return
|
||||
|
||||
this.isLoading = true
|
||||
|
||||
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
|
||||
})
|
||||
},
|
||||
},
|
||||
created() {
|
||||
events.$on('popup:close', () => 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%);
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -1,210 +0,0 @@
|
||||
<template>
|
||||
<PopupWrapper name="create-file-request">
|
||||
<!--Title-->
|
||||
<PopupHeader :title="$t('create_file_request')" icon="upload" />
|
||||
|
||||
<!--Content-->
|
||||
<PopupContent>
|
||||
<!--Item Thumbnail-->
|
||||
<ThumbnailItem v-if="pickedItem" class="mb-5" :item="pickedItem" />
|
||||
|
||||
<!--Form to set upload request-->
|
||||
<ValidationObserver
|
||||
v-if="!generatedUploadRequest"
|
||||
@submit.prevent="createUploadRequest"
|
||||
ref="createForm"
|
||||
v-slot="{ invalid }"
|
||||
tag="form"
|
||||
>
|
||||
<!--Set name-->
|
||||
<ValidationProvider
|
||||
tag="div"
|
||||
mode="passive"
|
||||
name="Name"
|
||||
v-slot="{ errors }"
|
||||
>
|
||||
<AppInputText :title="$t('folder_name_optional')" :description="$t('folder_name_optional_description')" :error="errors[0]">
|
||||
<input
|
||||
v-model="form.name"
|
||||
:class="{ '!border-rose-600': errors[0] }"
|
||||
type="text"
|
||||
ref="input"
|
||||
class="focus-border-theme input-dark"
|
||||
:placeholder="$t('type_name_')"
|
||||
/>
|
||||
</AppInputText>
|
||||
</ValidationProvider>
|
||||
|
||||
<!--Set note-->
|
||||
<ValidationProvider tag="div" mode="passive" name="Note" v-slot="{ errors }">
|
||||
<AppInputText :title="$t('message_optional')" :description="$t('message_optional_description')" :error="errors[0]">
|
||||
<textarea
|
||||
v-model="form.notes"
|
||||
rows="2"
|
||||
:class="{ '!border-rose-600': errors[0] }"
|
||||
type="text"
|
||||
ref="input"
|
||||
class="focus-border-theme input-dark"
|
||||
:placeholder="$t('message_for_recipient')"
|
||||
></textarea>
|
||||
</AppInputText>
|
||||
</ValidationProvider>
|
||||
|
||||
<!--Send Request by Email-->
|
||||
<AppInputSwitch
|
||||
:title="$t('send_request_by_email')"
|
||||
:description="$t('send_request_by_email_description')"
|
||||
:is-last="! shareViaEmail"
|
||||
>
|
||||
<SwitchInput v-model="shareViaEmail" :state="shareViaEmail" />
|
||||
</AppInputSwitch>
|
||||
|
||||
<!--Set email-->
|
||||
<ValidationProvider
|
||||
v-if="shareViaEmail"
|
||||
tag="div"
|
||||
mode="passive"
|
||||
name="Email"
|
||||
rules="required"
|
||||
v-slot="{ errors }"
|
||||
>
|
||||
<AppInputText :error="errors[0]" class="-mt-2" :is-last="true">
|
||||
<input
|
||||
v-model="form.email"
|
||||
:class="{ '!border-rose-600': errors[0] }"
|
||||
type="text"
|
||||
ref="input"
|
||||
class="focus-border-theme input-dark"
|
||||
:placeholder="$t('type_email_')"
|
||||
/>
|
||||
</AppInputText>
|
||||
</ValidationProvider>
|
||||
</ValidationObserver>
|
||||
|
||||
<!--Copy generated link-->
|
||||
<AppInputText v-if="generatedUploadRequest" :title="$t('copy_upload_request_link')" :is-last="true">
|
||||
<CopyInput :str="generatedUploadRequest.data.attributes.url" />
|
||||
</AppInputText>
|
||||
</PopupContent>
|
||||
|
||||
<!--Actions-->
|
||||
<PopupActions v-if="!generatedUploadRequest">
|
||||
<ButtonBase class="w-full" @click.native="$closePopup()" button-style="secondary"
|
||||
>{{ $t('cancel') }}
|
||||
</ButtonBase>
|
||||
<ButtonBase class="w-full" @click.native="createUploadRequest" :loading="isLoading" button-style="theme"
|
||||
>{{ $t('create_request') }}
|
||||
</ButtonBase>
|
||||
</PopupActions>
|
||||
|
||||
<!--Actions-->
|
||||
<PopupActions v-if="generatedUploadRequest">
|
||||
<ButtonBase class="w-full" @click.native="$closePopup()" button-style="theme"
|
||||
>{{ $t('awesome_iam_done') }}
|
||||
</ButtonBase>
|
||||
</PopupActions>
|
||||
</PopupWrapper>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import {ValidationProvider, ValidationObserver} from 'vee-validate/dist/vee-validate.full'
|
||||
import AppInputSwitch from '../Admin/AppInputSwitch'
|
||||
import {required} from 'vee-validate/dist/rules'
|
||||
import ButtonBase from '../FilesView/ButtonBase'
|
||||
import AppInputText from '../Admin/AppInputText'
|
||||
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 ThumbnailItem from './ThumbnailItem'
|
||||
import CopyInput from './Forms/CopyInput'
|
||||
import {events} from '../../bus'
|
||||
import axios from 'axios'
|
||||
|
||||
export default {
|
||||
name: 'CreateUploadRequestPopup',
|
||||
components: {
|
||||
ValidationProvider,
|
||||
ValidationObserver,
|
||||
AppInputSwitch,
|
||||
ThumbnailItem,
|
||||
AppInputText,
|
||||
PopupWrapper,
|
||||
PopupActions,
|
||||
PopupContent,
|
||||
SwitchInput,
|
||||
PopupHeader,
|
||||
ButtonBase,
|
||||
CopyInput,
|
||||
required,
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
form: {
|
||||
email: undefined,
|
||||
notes: undefined,
|
||||
folder_id: undefined,
|
||||
name: undefined,
|
||||
},
|
||||
generatedUploadRequest: undefined,
|
||||
shareViaEmail: false,
|
||||
pickedItem: undefined,
|
||||
isLoading: false,
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
async createUploadRequest() {
|
||||
// Validate fields
|
||||
const isValid = await this.$refs.createForm.validate()
|
||||
|
||||
if (!isValid) return
|
||||
|
||||
this.isLoading = true
|
||||
|
||||
// Send request to get share link
|
||||
axios
|
||||
.post(`/api/upload-request`, this.form)
|
||||
.then((response) => {
|
||||
this.generatedUploadRequest = response.data
|
||||
})
|
||||
.catch(() => {
|
||||
events.$emit('alert:open', {
|
||||
title: this.$t('popup_error.title'),
|
||||
message: this.$t('popup_error.message'),
|
||||
})
|
||||
})
|
||||
.finally(() => {
|
||||
this.isLoading = false
|
||||
})
|
||||
},
|
||||
},
|
||||
created() {
|
||||
events.$on('popup:open', (args) => {
|
||||
if (args.name === 'create-file-request') {
|
||||
this.pickedItem = args.item
|
||||
this.form.folder_id = args.item?.data.id
|
||||
}
|
||||
})
|
||||
|
||||
// Close popup
|
||||
events.$on('popup:close', () => {
|
||||
|
||||
// Restore data
|
||||
setTimeout(() => {
|
||||
this.generatedUploadRequest = undefined
|
||||
this.pickedItem = undefined
|
||||
|
||||
this.shareViaEmail = false
|
||||
|
||||
this.form = {
|
||||
name: undefined,
|
||||
email: undefined,
|
||||
notes: undefined,
|
||||
folder_id: undefined,
|
||||
}
|
||||
}, 150)
|
||||
})
|
||||
},
|
||||
}
|
||||
</script>
|
||||
@@ -1,48 +0,0 @@
|
||||
<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>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
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',
|
||||
}),
|
||||
})
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="css">
|
||||
.emoji {
|
||||
height: 1em;
|
||||
width: 1em;
|
||||
font-size: inherit;
|
||||
}
|
||||
</style>
|
||||
@@ -1,194 +0,0 @@
|
||||
<template>
|
||||
<div>
|
||||
<!-- 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>
|
||||
|
||||
<!-- Search input -->
|
||||
<input
|
||||
@click="openList"
|
||||
v-model="query"
|
||||
class="focus-border-theme input-dark"
|
||||
type="text"
|
||||
:placeholder="$t('select_or_search_emoji')"
|
||||
/>
|
||||
</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="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>
|
||||
|
||||
<!-- 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>
|
||||
|
||||
<!-- 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>
|
||||
|
||||
<!-- Not found -->
|
||||
<span class="ml-2 text-sm font-bold" v-if="filteredEmojis.length === 0 && query !== undefined">
|
||||
{{ $t('there_is_nothing_smile') }}
|
||||
</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'
|
||||
|
||||
export default {
|
||||
name: 'EmojiPicker',
|
||||
props: ['defaultEmoji'],
|
||||
components: {
|
||||
Spinner,
|
||||
Emoji,
|
||||
XIcon,
|
||||
},
|
||||
computed: {
|
||||
...mapGetters(['emojis']),
|
||||
allEmoji() {
|
||||
return groupBy(this.emojis.list, 'group')
|
||||
},
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
query: undefined,
|
||||
filteredEmojis: [],
|
||||
isOpen: false,
|
||||
isLoaded: false,
|
||||
groupInView: 'Smileys & Emotion',
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
query: debounce(function (val) {
|
||||
// Clear search results
|
||||
this.filteredEmojis = []
|
||||
|
||||
// Reset query
|
||||
if (val === '' || val === undefined) return
|
||||
|
||||
// Filter emojis by query
|
||||
this.filteredEmojis = this.emojis.list.filter((emoji) => emoji.name.includes(val.toLowerCase()))
|
||||
|
||||
if (this.filteredEmojis.length === 0) {
|
||||
//
|
||||
}
|
||||
}, 200),
|
||||
},
|
||||
methods: {
|
||||
checkGroupInView: _.debounce(function () {
|
||||
this.emojis.groups.forEach((group) => {
|
||||
let element = document.getElementById(`group-${group.name}`).getBoundingClientRect()
|
||||
let groupBox = document.getElementById('group-box').getBoundingClientRect()
|
||||
|
||||
// Check if the group is in the viewport of group-box
|
||||
if (element.top < groupBox.top && element.bottom > groupBox.top) {
|
||||
this.groupInView = group.name
|
||||
}
|
||||
})
|
||||
}, 300),
|
||||
scrollToGroup(name) {
|
||||
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',
|
||||
})
|
||||
|
||||
this.groupInView = name
|
||||
},
|
||||
openList() {
|
||||
// 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) => {
|
||||
this.$store.commit('LOAD_EMOJIS_LIST', response.data)
|
||||
})
|
||||
.finally(() => (this.isLoaded = true))
|
||||
}
|
||||
|
||||
// Simulate loading for the list processing
|
||||
if (this.emojis) {
|
||||
setTimeout(() => {
|
||||
this.isLoaded = true
|
||||
}, 20)
|
||||
}
|
||||
|
||||
this.groupInView = 'Smileys & Emotion'
|
||||
},
|
||||
setEmoji(value) {
|
||||
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()
|
||||
},
|
||||
}
|
||||
</script>
|
||||
@@ -10,7 +10,7 @@
|
||||
<h1 class="title">{{ title }}</h1>
|
||||
<h2 class="description">{{ description }}</h2>
|
||||
</div>
|
||||
<slot></slot>
|
||||
<slot />
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -1,64 +0,0 @@
|
||||
<template>
|
||||
<div class="relative flex items-center justify-center cursor-pointer h-14 w-14 overflow-hidden bg-light-background rounded-xl cursor-pointer z-10">
|
||||
<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"
|
||||
/>
|
||||
<camera-icon v-if="!imagePreview" size="22" class="vue-feather text-gray-300" />
|
||||
<img
|
||||
v-if="imagePreview"
|
||||
ref="image"
|
||||
:src="imagePreview"
|
||||
class="relative w-full h-full z-0 object-cover shadow-lg md:h-16 md:w-16"
|
||||
alt="avatar"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { CameraIcon} from 'vue-feather-icons'
|
||||
|
||||
export default {
|
||||
name: 'AvatarInput',
|
||||
props: ['avatar'],
|
||||
components: {
|
||||
CameraIcon,
|
||||
},
|
||||
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()
|
||||
|
||||
reader.onload = () => (this.imagePreview = reader.result)
|
||||
|
||||
reader.readAsDataURL(file)
|
||||
|
||||
// Update user avatar
|
||||
this.$updateImage('/user/settings', 'avatar', event.target.files[0])
|
||||
} else {
|
||||
alert(this.$t('wrong_image_error'))
|
||||
}
|
||||
},
|
||||
},
|
||||
created() {
|
||||
// If there is default image then load
|
||||
if (this.avatar) this.imagePreview = this.avatar
|
||||
},
|
||||
}
|
||||
</script>
|
||||
@@ -1,52 +0,0 @@
|
||||
<template>
|
||||
<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-->
|
||||
<div class="absolute right-0 px-4">
|
||||
<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'
|
||||
|
||||
export default {
|
||||
name: 'CopyInput',
|
||||
props: ['size', 'str'],
|
||||
components: {
|
||||
CheckIcon,
|
||||
CopyIcon,
|
||||
SendIcon,
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
isCopiedLink: false,
|
||||
id: 'link-input-' + Math.floor(Math.random() * 10000000),
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
copyUrl() {
|
||||
// Get input value
|
||||
let copyText = document.getElementById(this.id)
|
||||
|
||||
// select link
|
||||
copyText.select()
|
||||
copyText.setSelectionRange(0, 99999)
|
||||
|
||||
// Copy
|
||||
document.execCommand('copy')
|
||||
|
||||
// Mark button as copied
|
||||
this.isCopiedLink = true
|
||||
|
||||
// Reset copy button
|
||||
setTimeout(() => {
|
||||
this.isCopiedLink = false
|
||||
}, 1000)
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
@@ -1,199 +0,0 @@
|
||||
<template>
|
||||
<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">
|
||||
<div @click="copyUrl" class="absolute right-9 p-1">
|
||||
<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="hover-text-theme vue-feather cursor-pointer" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!--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
|
||||
v-if="item.data.type !== 'folder' && !item.data.relationships.shared.data.attributes.protected"
|
||||
@click="copyDirectLink"
|
||||
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">
|
||||
<download-icon size="14" />
|
||||
</div>
|
||||
<span class="text-sm font-bold">
|
||||
{{ $t('copy_direct_download_link') }}
|
||||
</span>
|
||||
</li>
|
||||
<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="directLink"
|
||||
ref="directLinkTextarea"
|
||||
class="pointer-events-none absolute right-full opacity-0"
|
||||
></textarea>
|
||||
|
||||
<textarea
|
||||
v-model="iframeCode"
|
||||
ref="iframe"
|
||||
class="pointer-events-none absolute right-full opacity-0"
|
||||
></textarea>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { DownloadIcon, CameraIcon, CopyIcon, CheckIcon, SendIcon, MoreHorizontalIcon, CodeIcon } from 'vue-feather-icons'
|
||||
import { events } from '../../../bus'
|
||||
|
||||
export default {
|
||||
name: 'CopyShareLink',
|
||||
props: ['item'],
|
||||
components: {
|
||||
MoreHorizontalIcon,
|
||||
CameraIcon,
|
||||
CheckIcon,
|
||||
CopyIcon,
|
||||
CodeIcon,
|
||||
SendIcon,
|
||||
DownloadIcon,
|
||||
},
|
||||
watch: {
|
||||
'item': function () {
|
||||
this.setClipboard()
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
id: 'link-input-' + Math.floor(Math.random() * 10000000),
|
||||
directLink: undefined,
|
||||
iframeCode: undefined,
|
||||
isCopiedLink: false,
|
||||
isOpenedMoreOptions: false,
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
moreOptions() {
|
||||
this.isOpenedMoreOptions = !this.isOpenedMoreOptions
|
||||
},
|
||||
getQrCode() {
|
||||
events.$emit('popup:open', {
|
||||
name: 'share-edit',
|
||||
item: this.item,
|
||||
section: 'qr-code',
|
||||
})
|
||||
|
||||
this.isOpenedMoreOptions = false
|
||||
},
|
||||
sendViaEmail() {
|
||||
events.$emit('popup:open', {
|
||||
name: 'share-edit',
|
||||
item: this.item,
|
||||
section: 'email-sharing',
|
||||
})
|
||||
|
||||
this.isOpenedMoreOptions = false
|
||||
},
|
||||
copyDirectLink() {
|
||||
let copyText = this.$refs.directLinkTextarea
|
||||
|
||||
copyText.select()
|
||||
copyText.setSelectionRange(0, 99999)
|
||||
|
||||
document.execCommand('copy')
|
||||
|
||||
events.$emit('toaster', {
|
||||
type: 'success',
|
||||
message: this.$t('direct_link_copied'),
|
||||
})
|
||||
|
||||
this.isOpenedMoreOptions = false
|
||||
},
|
||||
copyIframe() {
|
||||
let copyText = this.$refs.iframe
|
||||
|
||||
copyText.select()
|
||||
copyText.setSelectionRange(0, 99999)
|
||||
|
||||
document.execCommand('copy')
|
||||
|
||||
events.$emit('toaster', {
|
||||
type: 'success',
|
||||
message: this.$t('web_code_copied'),
|
||||
})
|
||||
|
||||
this.isOpenedMoreOptions = false
|
||||
},
|
||||
copyUrl() {
|
||||
// Get input value
|
||||
let copyText = document.getElementById(this.id)
|
||||
|
||||
// select link
|
||||
copyText.select()
|
||||
copyText.setSelectionRange(0, 99999)
|
||||
|
||||
// Copy
|
||||
document.execCommand('copy')
|
||||
|
||||
// Mark button as copied
|
||||
this.isCopiedLink = true
|
||||
|
||||
// Reset copy button
|
||||
setTimeout(() => {
|
||||
this.isCopiedLink = false
|
||||
}, 1000)
|
||||
},
|
||||
setClipboard() {
|
||||
this.directLink = this.item.data.relationships.shared.data.attributes.link + '/direct'
|
||||
this.iframeCode = `<iframe src="${this.item.data.relationships.shared.data.attributes.link}" width="790" height="400" allowfullscreen frameborder="0"></iframe>`
|
||||
}
|
||||
},
|
||||
created() {
|
||||
this.setClipboard()
|
||||
}
|
||||
}
|
||||
</script>
|
||||
@@ -1,76 +0,0 @@
|
||||
<template>
|
||||
<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" />
|
||||
<list-icon v-if="icon === 'list'" size="22" class="vue-feather text-theme dark-text-theme mr-3" />
|
||||
<info-icon v-if="icon === 'info'" size="22" class="vue-feather text-theme dark-text-theme mr-3" />
|
||||
<database-icon v-if="icon === 'database'" 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" />
|
||||
<mail-icon v-if="icon === 'mail'" 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" />
|
||||
<wifi-icon v-if="icon === 'wifi'" size="22" class="vue-feather text-theme dark-text-theme mr-3" />
|
||||
<trending-up-icon v-if="icon === 'trending-up'" 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 {
|
||||
TrendingUpIcon,
|
||||
WifiIcon,
|
||||
ListIcon,
|
||||
MailIcon,
|
||||
InfoIcon,
|
||||
DatabaseIcon,
|
||||
UsersIcon,
|
||||
ShieldIcon,
|
||||
CreditCardIcon,
|
||||
DollarSignIcon,
|
||||
SmartphoneIcon,
|
||||
HardDriveIcon,
|
||||
BarChartIcon,
|
||||
SettingsIcon,
|
||||
FileTextIcon,
|
||||
FrownIcon,
|
||||
Edit2Icon,
|
||||
BellIcon,
|
||||
KeyIcon,
|
||||
} from 'vue-feather-icons'
|
||||
|
||||
export default {
|
||||
name: 'FormLabel',
|
||||
props: ['icon'],
|
||||
components: {
|
||||
TrendingUpIcon,
|
||||
WifiIcon,
|
||||
ListIcon,
|
||||
MailIcon,
|
||||
InfoIcon,
|
||||
DatabaseIcon,
|
||||
UsersIcon,
|
||||
CreditCardIcon,
|
||||
DollarSignIcon,
|
||||
SmartphoneIcon,
|
||||
HardDriveIcon,
|
||||
BarChartIcon,
|
||||
SettingsIcon,
|
||||
FileTextIcon,
|
||||
ShieldIcon,
|
||||
FrownIcon,
|
||||
Edit2Icon,
|
||||
BellIcon,
|
||||
KeyIcon,
|
||||
},
|
||||
}
|
||||
</script>
|
||||
@@ -1,97 +0,0 @@
|
||||
<template>
|
||||
<div
|
||||
class="relative flex h-[175px] items-center justify-center rounded-lg bg-light-background dark:bg-2x-dark-foreground"
|
||||
:class="{ 'is-error': error }"
|
||||
>
|
||||
<!--Reset Image-->
|
||||
<div
|
||||
v-if="imagePreview"
|
||||
@click="resetImage"
|
||||
class="absolute right-0 top-0 z-[9] flex h-7 w-7 -translate-y-3 translate-x-3 cursor-pointer items-center justify-center rounded-md rounded-full dark:bg-4x-dark-foreground bg-white shadow-lg"
|
||||
>
|
||||
<x-icon size="14" class="vue-feather dark:text-gray-500" />
|
||||
</div>
|
||||
|
||||
<input
|
||||
@change="showImagePreview($event)"
|
||||
ref="file"
|
||||
type="file"
|
||||
class="absolute top-0 left-0 right-0 bottom-0 z-10 w-full cursor-pointer opacity-0"
|
||||
/>
|
||||
|
||||
<!--Default image preview-->
|
||||
<img
|
||||
v-if="imagePreview"
|
||||
:src="imagePreview"
|
||||
ref="image"
|
||||
class="absolute h-full w-full object-contain py-4 px-12"
|
||||
/>
|
||||
|
||||
<!--Drop image zone-->
|
||||
<div v-if="!isData" class="text-center">
|
||||
<image-icon size="34" class="vue-feather text-theme mb-4 inline-block" />
|
||||
|
||||
<b class="block text-base font-bold leading-3">
|
||||
{{ $te('input_image.title') ? $t('input_image.title') : 'Upload Image' }}
|
||||
</b>
|
||||
<small class="text-xs text-gray-500">
|
||||
{{
|
||||
$te('input_image.supported')
|
||||
? $t('input_image.supported')
|
||||
: 'Supported formats are .png, .jpg, .jpeg.'
|
||||
}}
|
||||
</small>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
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 === '')
|
||||
},
|
||||
},
|
||||
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('wrong_image_error'))
|
||||
}
|
||||
},
|
||||
},
|
||||
created() {
|
||||
// If has default image then load
|
||||
if (this.image) this.imagePreview = this.image
|
||||
},
|
||||
}
|
||||
</script>
|
||||
@@ -1,112 +0,0 @@
|
||||
<template>
|
||||
<div class="info-box" :class="type">
|
||||
<slot></slot>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: 'InfoBox',
|
||||
props: ['type'],
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
@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: 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>
|
||||
@@ -1,99 +0,0 @@
|
||||
<template>
|
||||
<div
|
||||
class="focus-border-theme input-dark focus-within-border-theme"
|
||||
:class="{'!border-rose-600':isError, '!py-3.5': !emails.length, '!px-2 !pt-[10px] !pb-0.5': emails.length}"
|
||||
>
|
||||
<div @click="$refs.input.focus()" class="flex flex-wrap cursor-text items-center" style="grid-template-columns: auto minmax(0,1fr);">
|
||||
<div
|
||||
class="whitespace-nowrap flex items-center rounded-lg bg-theme-100 mr-2 mb-2 py-1 px-2 cursor-pointer w-fit"
|
||||
@click="removeEmail(email)"
|
||||
v-for="(email, i) in emails"
|
||||
:key="i"
|
||||
>
|
||||
<small class="text-sm text-theme mr-1">
|
||||
{{ email }}
|
||||
</small>
|
||||
<x-icon class="vue-feather text-theme" size="14" />
|
||||
</div>
|
||||
<input
|
||||
@keydown.delete="removeLastEmail($event)"
|
||||
@keyup="handleEmail()"
|
||||
v-model="email"
|
||||
:size="inputSize"
|
||||
class="w-auto font-bold text-sm bg-transparent"
|
||||
:class="{'mb-2': emails.length}"
|
||||
:placeholder="placeHolder"
|
||||
autocomplete="new-password"
|
||||
ref="input"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { XIcon } from 'vue-feather-icons'
|
||||
import { events } from '../../../bus'
|
||||
|
||||
export default {
|
||||
name: 'MultiEmailInput',
|
||||
components: { XIcon },
|
||||
props: ['isError', 'label'],
|
||||
computed: {
|
||||
placeHolder() {
|
||||
return !this.emails.length ? this.$t('shared_form.email_placeholder') : ''
|
||||
},
|
||||
inputSize() {
|
||||
return this.email && this.email.length > 14 ? this.email.length : 14
|
||||
},
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
emails: [],
|
||||
email: undefined,
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
removeEmail(email) {
|
||||
this.emails = this.emails.filter((item) => item !== email)
|
||||
|
||||
// After remove email send new emails list to parent
|
||||
events.$emit('emailsInputValues', this.emails)
|
||||
},
|
||||
removeLastEmail(event) {
|
||||
// If is input empty and presse backspace remove last email from array
|
||||
if (event.code === 'Backspace' && this.email === '') this.emails.pop()
|
||||
},
|
||||
handleEmail() {
|
||||
if (! this.email.length)
|
||||
return;
|
||||
|
||||
// Get index of @ and last dot
|
||||
let lastDot = this.email.lastIndexOf('.')
|
||||
let at = this.email.indexOf('@')
|
||||
|
||||
// Check if is after @ some dot, if email have @ anf if dont have more like one
|
||||
if (lastDot < at || at === -1 || this.email.match(/@/g).length > 1) return
|
||||
|
||||
// First email dont need to be separated by comma or space to be sended
|
||||
if (this.emails.length === 0) events.$emit('emailsInputValues', [this.email])
|
||||
|
||||
// After come or backspace push the single email to array or emails
|
||||
if (this.email.includes(',') || this.email.includes(' ')) {
|
||||
let email = this.email.replace(/[","," "]/, '')
|
||||
|
||||
this.email = ''
|
||||
|
||||
// Push single email to aray of emails
|
||||
this.emails.push(email)
|
||||
|
||||
events.$emit('emailsInputValues', this.emails)
|
||||
}
|
||||
},
|
||||
},
|
||||
created() {
|
||||
this.$nextTick(() => {
|
||||
this.$refs.input.focus()
|
||||
})
|
||||
},
|
||||
}
|
||||
</script>
|
||||
@@ -1,113 +0,0 @@
|
||||
<template>
|
||||
<div class="search-bar">
|
||||
<div v-if="!query" class="icon">
|
||||
<search-icon size="19" />
|
||||
</div>
|
||||
<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')"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { SearchIcon, XIcon } from 'vue-feather-icons'
|
||||
|
||||
export default {
|
||||
name: 'SearchInput',
|
||||
components: {
|
||||
SearchIcon,
|
||||
XIcon,
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
query: undefined,
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
clearInput() {
|
||||
this.query = undefined
|
||||
this.$emit('reset-query')
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
@import '../../../../sass/vuefilemanager/variables';
|
||||
@import '../../../../sass/vuefilemanager/mixins';
|
||||
@import '../../../../sass/vuefilemanager/forms';
|
||||
|
||||
.search-bar {
|
||||
position: relative;
|
||||
width: 100%;
|
||||
border-radius: 8px;
|
||||
|
||||
input {
|
||||
background: $light_background;
|
||||
border-radius: 8px;
|
||||
outline: 0;
|
||||
padding: 9px 20px 9px 43px;
|
||||
font-weight: 700;
|
||||
@include font-size(16);
|
||||
width: 100%;
|
||||
height: 50px;
|
||||
min-width: 175px;
|
||||
transition: 0.15s all ease;
|
||||
border: 1px solid transparent;
|
||||
-webkit-appearance: none;
|
||||
box-shadow: none;
|
||||
|
||||
&::placeholder {
|
||||
color: $light_text;
|
||||
@include font-size(14);
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
&:focus {
|
||||
border-width: 1px;
|
||||
border-style: solid;
|
||||
}
|
||||
|
||||
&:focus + .icon {
|
||||
path {
|
||||
color: inherit;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.icon {
|
||||
height: 100%;
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
padding: 11px 15px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
circle,
|
||||
line {
|
||||
color: $light_text;
|
||||
}
|
||||
|
||||
.pointer {
|
||||
cursor: pointer;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.dark {
|
||||
.search-bar {
|
||||
input {
|
||||
background: $dark_mode_foreground;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -1,85 +0,0 @@
|
||||
<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"
|
||||
>
|
||||
<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
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
@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;
|
||||
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 {
|
||||
flex-basis: calc(34% - 10px);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.dark {
|
||||
.select-box {
|
||||
.box-item {
|
||||
border-color: $dark_mode_border_color;
|
||||
background: lighten($dark_mode_foreground, 3%);
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -1,276 +0,0 @@
|
||||
<template>
|
||||
<div class="select">
|
||||
<!--Area-->
|
||||
<div
|
||||
class="input-area rounded-lg bg-light-background dark:bg-2x-dark-foreground"
|
||||
:class="{ 'is-active': isOpen, '!border-rose-600': isError }"
|
||||
@click="openMenu"
|
||||
>
|
||||
<!--If is 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="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">
|
||||
<span class="option-value placehoder">{{ placeholder }}</span>
|
||||
</div>
|
||||
|
||||
<chevron-down-icon size="19" class="chevron" />
|
||||
</div>
|
||||
|
||||
<!--Options-->
|
||||
<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="$te('search_in_list') ? $t('search_in_list') : 'Search in list...'"
|
||||
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">
|
||||
<div class="option-icon" v-if="option.icon">
|
||||
<user-icon v-if="option.icon === 'user'" size="14" />
|
||||
<edit2-icon v-if="option.icon === 'user-edit'" size="14" />
|
||||
</div>
|
||||
<span class="option-value">
|
||||
{{ $t(option.label) }}
|
||||
</span>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</transition>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { ChevronDownIcon, Edit2Icon, UserIcon } from 'vue-feather-icons'
|
||||
import { debounce, omitBy } from 'lodash'
|
||||
|
||||
export default {
|
||||
name: 'SelectInput',
|
||||
props: ['placeholder', 'options', 'isError', 'default'],
|
||||
components: {
|
||||
Edit2Icon,
|
||||
UserIcon,
|
||||
ChevronDownIcon,
|
||||
},
|
||||
watch: {
|
||||
query: debounce(function (val) {
|
||||
this.searchedResults = omitBy(this.options, (string) => {
|
||||
return !string.label.toLowerCase().includes(val.toLowerCase())
|
||||
})
|
||||
}, 200),
|
||||
},
|
||||
computed: {
|
||||
isSearching() {
|
||||
return this.searchedResults && this.query !== ''
|
||||
},
|
||||
optionList() {
|
||||
return this.isSearching ? this.searchedResults : this.options
|
||||
},
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
searchedResults: undefined,
|
||||
selected: undefined,
|
||||
isOpen: false,
|
||||
query: '',
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
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.$refs.search && 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';
|
||||
|
||||
/* TODO: refactor to the tailwind */
|
||||
|
||||
.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: $dark_mode_foreground;
|
||||
|
||||
.search-input {
|
||||
background: $dark_mode_background;
|
||||
}
|
||||
}
|
||||
|
||||
.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>
|
||||
@@ -1,142 +0,0 @@
|
||||
<template>
|
||||
<div class="setup-box" :class="theme">
|
||||
<b class="title">{{ title }}</b>
|
||||
<p class="description">{{ description }}</p>
|
||||
<slot></slot>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: 'SetupBox',
|
||||
props: ['title', 'description', 'theme'],
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
@import '../../../../sass/vuefilemanager/variables';
|
||||
@import '../../../../sass/vuefilemanager/mixins';
|
||||
|
||||
.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 {
|
||||
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(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;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -1,103 +0,0 @@
|
||||
<template>
|
||||
<div>
|
||||
<div class="switch-content">
|
||||
<label class="input-label" v-if="label"> {{ label }}: </label>
|
||||
<small class="input-info" v-if="info">
|
||||
{{ info }}
|
||||
</small>
|
||||
</div>
|
||||
|
||||
<div class="switch-content text-right">
|
||||
<div class="switch" :class="{ active: state }" @click="changeState">
|
||||
<div class="switch-button"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: 'SwitchInput',
|
||||
props: ['label', 'name', 'state', 'info', 'input', 'isDisabled'],
|
||||
data() {
|
||||
return {
|
||||
isSwitched: undefined,
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
changeState() {
|
||||
if (this.isDisabled) return
|
||||
|
||||
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';
|
||||
|
||||
.input-wrapper {
|
||||
display: flex;
|
||||
width: 100%;
|
||||
|
||||
.input-label {
|
||||
color: $text;
|
||||
}
|
||||
|
||||
.switch-content {
|
||||
width: 100%;
|
||||
|
||||
&:last-child {
|
||||
width: 80px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.switch {
|
||||
width: 50px;
|
||||
height: 28px;
|
||||
border-radius: 50px;
|
||||
display: block;
|
||||
background: #f1f1f5;
|
||||
position: relative;
|
||||
@include transition;
|
||||
|
||||
.switch-button {
|
||||
@include transition;
|
||||
width: 22px;
|
||||
height: 22px;
|
||||
border-radius: 50px;
|
||||
display: block;
|
||||
background: white;
|
||||
position: absolute;
|
||||
top: 3px;
|
||||
left: 3px;
|
||||
box-shadow: 0 2px 4px rgba(37, 38, 94, 0.1);
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
&.active {
|
||||
.switch-button {
|
||||
left: 25px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.dark {
|
||||
.switch {
|
||||
background: $dark_mode_foreground;
|
||||
}
|
||||
|
||||
.popup-wrapper {
|
||||
.switch {
|
||||
background: lighten($dark_mode_foreground, 3%);
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -1,25 +0,0 @@
|
||||
<template>
|
||||
<div class="page-tab">
|
||||
<div id="loader" v-show="isLoading">
|
||||
<Spinner></Spinner>
|
||||
</div>
|
||||
<slot v-show="!isLoading"></slot>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import Spinner from '../../FilesView/Spinner'
|
||||
|
||||
export default {
|
||||
name: 'PageTab',
|
||||
props: ['isLoading'],
|
||||
components: {
|
||||
Spinner,
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
@import '../../../../sass/vuefilemanager/variables';
|
||||
@import '../../../../sass/vuefilemanager/mixins';
|
||||
</style>
|
||||
@@ -1,20 +0,0 @@
|
||||
<template>
|
||||
<div class="page-tab-group">
|
||||
<slot></slot>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: 'PageTabGroup',
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
@import '../../../../sass/vuefilemanager/variables';
|
||||
@import '../../../../sass/vuefilemanager/mixins';
|
||||
|
||||
.page-tab-group {
|
||||
margin-bottom: 65px;
|
||||
}
|
||||
</style>
|
||||
@@ -1,16 +0,0 @@
|
||||
<template>
|
||||
<ul class="list-info">
|
||||
<slot></slot>
|
||||
</ul>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: 'ListInfo',
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
@import '../../../sass/vuefilemanager/variables';
|
||||
@import '../../../sass/vuefilemanager/mixins';
|
||||
</style>
|
||||
@@ -1,19 +0,0 @@
|
||||
<template>
|
||||
<div class="mb-4">
|
||||
<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'],
|
||||
}
|
||||
</script>
|
||||
@@ -1,214 +0,0 @@
|
||||
<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 && user.data.meta.usages && !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('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 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('settings')"
|
||||
icon="user"
|
||||
arrow="right"
|
||||
:is-hover-disabled="true"
|
||||
/>
|
||||
<Option
|
||||
v-if="isAdmin"
|
||||
@click.native.stop="showSubmenu('admin')"
|
||||
:title="$t('administration')"
|
||||
icon="settings"
|
||||
arrow="right"
|
||||
:is-hover-disabled="true"
|
||||
/>
|
||||
</OptionGroup>
|
||||
<OptionGroup v-if="!clickedSubmenu">
|
||||
<Option @click.native="logOut" :title="$t('logout')" icon="power" :is-hover-disabled="true" />
|
||||
</OptionGroup>
|
||||
|
||||
<!--Submenu: User settings-->
|
||||
<OptionGroup v-if="clickedSubmenu === 'settings'">
|
||||
<Option
|
||||
@click.native="goToRoute('Profile')"
|
||||
:title="$t('menu.profile')"
|
||||
icon="user"
|
||||
:is-hover-disabled="true"
|
||||
/>
|
||||
<Option
|
||||
@click.native="goToRoute('Password')"
|
||||
:title="$t('menu.password')"
|
||||
icon="lock"
|
||||
:is-hover-disabled="true"
|
||||
/>
|
||||
<Option
|
||||
@click.native="goToRoute('Storage')"
|
||||
:title="$t('storage')"
|
||||
icon="hard-drive"
|
||||
:is-hover-disabled="true"
|
||||
/>
|
||||
<Option
|
||||
@click.native="goToRoute('Billing')"
|
||||
v-if="config.subscriptionType !== 'none'"
|
||||
:title="$t('billing')"
|
||||
icon="cloud"
|
||||
:is-hover-disabled="true"
|
||||
/>
|
||||
</OptionGroup>
|
||||
|
||||
<!--Submenu: Admin settings-->
|
||||
<OptionGroup v-if="clickedSubmenu === 'admin'">
|
||||
<Option
|
||||
@click.native="goToRoute('Dashboard')"
|
||||
:title="$t('dashboard')"
|
||||
icon="box"
|
||||
:is-hover-disabled="true"
|
||||
/>
|
||||
<Option
|
||||
@click.native="goToRoute('Users')"
|
||||
:title="$t('users')"
|
||||
icon="users"
|
||||
:is-hover-disabled="true"
|
||||
/>
|
||||
<Option
|
||||
@click.native="goToRoute('AppOthers')"
|
||||
:title="$t('settings')"
|
||||
icon="settings"
|
||||
:is-hover-disabled="true"
|
||||
/>
|
||||
</OptionGroup>
|
||||
|
||||
<!--Submenu: Content settings-->
|
||||
<OptionGroup v-if="clickedSubmenu === 'admin'">
|
||||
<Option
|
||||
@click.native="goToRoute('Pages')"
|
||||
:title="$t('pages')"
|
||||
icon="monitor"
|
||||
:is-hover-disabled="true"
|
||||
/>
|
||||
<Option
|
||||
@click.native="goToRoute('Language')"
|
||||
:title="$t('languages')"
|
||||
icon="globe"
|
||||
:is-hover-disabled="true"
|
||||
/>
|
||||
</OptionGroup>
|
||||
|
||||
<!--Submenu: Billing settings-->
|
||||
<OptionGroup v-if="clickedSubmenu === 'admin' && config.subscriptionType !== 'none'">
|
||||
<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('Plans')"
|
||||
:title="$t('plans')"
|
||||
icon="database"
|
||||
:is-hover-disabled="true"
|
||||
/>
|
||||
<Option
|
||||
@click.native="goToRoute('Invoices')"
|
||||
:title="$t('transactions')"
|
||||
icon="file-text"
|
||||
:is-hover-disabled="true"
|
||||
/>
|
||||
</OptionGroup>
|
||||
</MenuMobileGroup>
|
||||
</MenuMobile>
|
||||
</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'
|
||||
|
||||
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'
|
||||
},
|
||||
backTitle() {
|
||||
let location = {
|
||||
settings: this.$t('settings'),
|
||||
admin: this.$t('administration'),
|
||||
}
|
||||
|
||||
return this.$t('go_back_from_x', {location: 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>
|
||||
@@ -1,160 +0,0 @@
|
||||
<template>
|
||||
<PopupWrapper name="move">
|
||||
<!--Title-->
|
||||
<PopupHeader :title="$t('popup_move_item.title')" icon="move" />
|
||||
|
||||
<!--Content-->
|
||||
<PopupContent v-if="pickedItem" class="h-full pb-6 lg: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">
|
||||
<ThumbnailItem v-if="clipboard.length === 1 || isSelectedItem" class="mb-5" :item="pickedItem" />
|
||||
|
||||
<TitlePreview
|
||||
class="mb-4"
|
||||
icon="check-square"
|
||||
:title="$t('selected_multiple')"
|
||||
:subtitle="clipboard.length + ' ' + $tc('items', 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-->
|
||||
<PopupActions>
|
||||
<ButtonBase class="w-full" @click.native="$closePopup()" button-style="secondary"
|
||||
>{{ $t('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 } 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
|
||||
|
||||
// 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
|
||||
this.$store.dispatch('moveItem', {
|
||||
to_item: this.selectedFolder,
|
||||
item: this.isSelectedItem ? this.pickedItem : undefined,
|
||||
})
|
||||
|
||||
// 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 (folder.id === this.pickedItem.data.id) {
|
||||
this.selectedFolder = undefined
|
||||
} else if (!folder.id && folder.location === 'base') {
|
||||
this.selectedFolder = 'base'
|
||||
} else {
|
||||
this.selectedFolder = folder
|
||||
}
|
||||
})
|
||||
|
||||
// Show Move item popup
|
||||
events.$on('popup:open', (args) => {
|
||||
if (args.name !== 'move') return
|
||||
|
||||
// Show tree spinner
|
||||
this.isLoadingTree = true
|
||||
|
||||
// Get folder tree and hide spinner
|
||||
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)
|
||||
})
|
||||
},
|
||||
}
|
||||
</script>
|
||||
@@ -1,45 +0,0 @@
|
||||
<template>
|
||||
<transition appear name="fade">
|
||||
<div
|
||||
class="relative w-full overflow-hidden rounded-xl bg-opacity-80 p-4 shadow-lg backdrop-blur-2xl"
|
||||
:class="{
|
||||
'bg-red-50 dark:bg-2x-dark-foreground': item.type === 'danger',
|
||||
'bg-green-50 dark:bg-2x-dark-foreground': item.type === 'success',
|
||||
}"
|
||||
>
|
||||
<!--Content-->
|
||||
<div class="flex items-center justify-between">
|
||||
<div class="flex items-start">
|
||||
<check-icon v-if="item.type === 'success'" size="22" class="vue-feather dark:text-green-600 text-green-600" />
|
||||
<x-icon v-if="item.type === 'danger'" size="22" class="vue-feather dark:text-red-600 text-red-600" />
|
||||
|
||||
<p
|
||||
class="px-4 font-bold"
|
||||
:class="{
|
||||
'text-green-600': item.type === 'success',
|
||||
'text-red-600': item.type === 'danger',
|
||||
}"
|
||||
>
|
||||
{{ item.message }}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</transition>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { CheckIcon, XIcon } from 'vue-feather-icons'
|
||||
|
||||
export default {
|
||||
name: 'Toaster',
|
||||
components: {
|
||||
CheckIcon,
|
||||
XIcon,
|
||||
},
|
||||
props: [
|
||||
'barColor',
|
||||
'item',
|
||||
],
|
||||
}
|
||||
</script>
|
||||
@@ -1,79 +0,0 @@
|
||||
<template>
|
||||
<div
|
||||
v-if="toasters.length || notifications.length"
|
||||
class="fixed bottom-4 right-4 left-4 z-50 sm:w-[360px] sm:left-auto lg:bottom-8 lg:right-8"
|
||||
>
|
||||
<ToasterWrapper
|
||||
v-for="notification in notifications"
|
||||
:key="notification.data.id"
|
||||
class="mt-4 overflow-hidden rounded-xl dark:bg-2x-dark-foreground bg-white/80 backdrop-blur-2xl shadow-xl"
|
||||
bar-color="bg-theme"
|
||||
>
|
||||
<Notification :notification="notification" class="z-10 !mb-0 !px-4 !pt-4 !pb-5" />
|
||||
</ToasterWrapper>
|
||||
|
||||
<ToasterWrapper
|
||||
v-for="(toaster, i) in toasters"
|
||||
:key="i"
|
||||
class="mt-4 overflow-hidden rounded-xl shadow-xl"
|
||||
:bar-color="getToasterColor(toaster)"
|
||||
>
|
||||
<Toaster :item="toaster" />
|
||||
</ToasterWrapper>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import Notification from '../../Notifications/Notification'
|
||||
import ToasterWrapper from './ToasterWrapper'
|
||||
import {events} from '../../../bus'
|
||||
import Toaster from './Toaster'
|
||||
|
||||
export default {
|
||||
components: {
|
||||
ToasterWrapper,
|
||||
Notification,
|
||||
Toaster,
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
notifications: [],
|
||||
toasters: [],
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
getToasterColor(toaster) {
|
||||
return {
|
||||
danger: 'bg-red-400',
|
||||
success: 'bg-green-400',
|
||||
}[toaster.type]
|
||||
}
|
||||
},
|
||||
created() {
|
||||
events.$on('toaster', (toaster) => this.toasters.push(toaster))
|
||||
events.$on('notification', (notification) => this.notifications.push(notification))
|
||||
|
||||
/*events.$emit('notification', {
|
||||
data: {
|
||||
type: 'file-request',
|
||||
id: 'df954d23-f9d4-4677-85c8-abfd48aaa090',
|
||||
attributes: {
|
||||
action: {
|
||||
type: 'route',
|
||||
params: {
|
||||
route: 'Files',
|
||||
button: 'Show Files',
|
||||
id: 'ae37b1d8-c147-489a-83ab-2a3c7cb86263',
|
||||
},
|
||||
},
|
||||
created_at: '',
|
||||
description: "Your file request for 'Multi Level Folder' folder was filled successfully.",
|
||||
read_at: '',
|
||||
title: 'File Request Filled',
|
||||
category: 'file-request',
|
||||
},
|
||||
},
|
||||
})*/
|
||||
},
|
||||
}
|
||||
</script>
|
||||
@@ -1,64 +0,0 @@
|
||||
<template>
|
||||
<transition v-if="isActive" appear name="fade">
|
||||
<div class="relative">
|
||||
<div @click="isActive = false" class="absolute z-[20] right-0 top-0 cursor-pointer p-2">
|
||||
<x-icon size="16" class="vue-feather text-black opacity-10 dark:text-white" />
|
||||
</div>
|
||||
|
||||
<slot />
|
||||
|
||||
<!--Progress bar-->
|
||||
<div class="absolute bottom-0 left-0 right-0 z-20">
|
||||
<span class="bar-animation block h-1 w-0" :class="barColor"></span>
|
||||
</div>
|
||||
</div>
|
||||
</transition>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { XIcon } from 'vue-feather-icons'
|
||||
|
||||
export default {
|
||||
name: 'Toast',
|
||||
props: [
|
||||
'barColor'
|
||||
],
|
||||
components: {
|
||||
XIcon,
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
isActive: 1,
|
||||
}
|
||||
},
|
||||
created() {
|
||||
setTimeout(() => (this.isActive = 0), 6000)
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.bar-animation {
|
||||
animation: progressbar 6s linear both;
|
||||
}
|
||||
|
||||
@keyframes progressbar {
|
||||
0% {
|
||||
width: 0;
|
||||
}
|
||||
100% {
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
.fade-enter-active,
|
||||
.fade-leave-active {
|
||||
transition: 0.3s ease;
|
||||
}
|
||||
|
||||
.fade-enter,
|
||||
.fade-leave-to {
|
||||
opacity: 0;
|
||||
transform: translateX(100%);
|
||||
}
|
||||
</style>
|
||||
@@ -1,94 +0,0 @@
|
||||
<template>
|
||||
<PopupWrapper name="notifications-mobile">
|
||||
<!--Title-->
|
||||
<PopupHeader :title="$t('notifications')" icon="bell" />
|
||||
|
||||
<!--Content-->
|
||||
<PopupContent v-if="readNotifications && unreadNotifications">
|
||||
<MobileActionButton
|
||||
v-if="readNotifications.length || unreadNotifications.length"
|
||||
@click.native="$store.dispatch('deleteAllNotifications')"
|
||||
icon="check-square"
|
||||
class="mb-2 dark:!bg-4x-dark-foreground"
|
||||
>
|
||||
{{ $t('clear_all') }}
|
||||
</MobileActionButton>
|
||||
|
||||
<p v-if="!readNotifications.length && !unreadNotifications.length" class="text-sm text-gray-500">
|
||||
{{ $t("not_any_notifications") }}
|
||||
</p>
|
||||
|
||||
<b
|
||||
v-if="unreadNotifications.length"
|
||||
class="dark-text-theme mt-1.5 mb-2.5 block px-2.5 text-xs text-gray-400"
|
||||
>
|
||||
{{ $t('unread') }}
|
||||
</b>
|
||||
|
||||
<Notification
|
||||
:notification="notification"
|
||||
v-for="notification in unreadNotifications"
|
||||
:key="notification.id"
|
||||
/>
|
||||
|
||||
<b v-if="readNotifications.length" class="dark-text-theme mt-2.5 mb-2.5 block px-2.5 text-xs text-gray-400">
|
||||
{{ $t('read') }}
|
||||
</b>
|
||||
|
||||
<Notification
|
||||
:notification="notification"
|
||||
v-for="notification in readNotifications"
|
||||
:key="notification.id"
|
||||
/>
|
||||
</PopupContent>
|
||||
|
||||
<!--Actions-->
|
||||
<PopupActions>
|
||||
<ButtonBase class="w-full" @click.native="$closePopup()" button-style="secondary">
|
||||
{{ $t('close') }}
|
||||
</ButtonBase>
|
||||
</PopupActions>
|
||||
</PopupWrapper>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import MobileActionButton from '../FilesView/MobileActionButton'
|
||||
import Notification from '../Notifications/Notification'
|
||||
import ButtonBase from '../FilesView/ButtonBase'
|
||||
import PopupWrapper from './Popup/PopupWrapper'
|
||||
import PopupActions from './Popup/PopupActions'
|
||||
import PopupContent from './Popup/PopupContent'
|
||||
import PopupHeader from './Popup/PopupHeader'
|
||||
import vClickOutside from 'v-click-outside'
|
||||
import { mapGetters } from 'vuex'
|
||||
|
||||
export default {
|
||||
name: 'NotificationsPopup',
|
||||
components: {
|
||||
MobileActionButton,
|
||||
Notification,
|
||||
PopupWrapper,
|
||||
PopupActions,
|
||||
PopupContent,
|
||||
PopupHeader,
|
||||
ButtonBase,
|
||||
},
|
||||
directives: {
|
||||
clickOutside: vClickOutside.directive,
|
||||
},
|
||||
computed: {
|
||||
...mapGetters(['user', 'config']),
|
||||
readNotifications() {
|
||||
return this.user?.data.relationships.readNotifications.data
|
||||
},
|
||||
unreadNotifications() {
|
||||
return this.user?.data.relationships.unreadNotifications.data
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
clickOutside() {
|
||||
if (this.isVisibleNotificationCenter) this.$store.commit('CLOSE_NOTIFICATION_CENTER')
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
@@ -1,83 +0,0 @@
|
||||
<template>
|
||||
<div class="page-header">
|
||||
<div class="go-back" v-if="canBack" @click="$router.back()">
|
||||
<chevron-left-icon size="17" />
|
||||
</div>
|
||||
<div class="content">
|
||||
<h1 class="title">{{ title }}</h1>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { ChevronLeftIcon } from 'vue-feather-icons'
|
||||
|
||||
export default {
|
||||
name: 'PageHeader',
|
||||
props: ['title', 'canBack'],
|
||||
components: {
|
||||
ChevronLeftIcon,
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
@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 {
|
||||
.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;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -1,19 +0,0 @@
|
||||
<template>
|
||||
<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 dark:text-gray-500 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'],
|
||||
}
|
||||
</script>
|
||||
@@ -1,220 +0,0 @@
|
||||
<template>
|
||||
<div class="plans-wrapper" v-if="plans">
|
||||
<article class="plan" v-for="(plan, i) in plans" :key="i">
|
||||
<div class="plan-wrapper">
|
||||
<header class="plan-header">
|
||||
<div class="icon">
|
||||
<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>
|
||||
</header>
|
||||
<section class="plan-features">
|
||||
<b class="storage-size">{{ plan.data.attributes.capacity_formatted }}</b>
|
||||
<span class="storage-description">{{ $t('page_pricing_tables.storage_capacity') }}</span>
|
||||
</section>
|
||||
<footer class="plan-footer">
|
||||
<b class="price text-theme">
|
||||
{{ plan.data.attributes.price }}/{{ $t('mo.') }}
|
||||
<small v-if="plan.data.attributes.tax_rates.length > 0" class="vat-disclaimer">{{
|
||||
$t('page_pricing_tables.vat_excluded')
|
||||
}}</small>
|
||||
</b>
|
||||
<ButtonBase
|
||||
@click.native="selectPlan(plan)"
|
||||
type="submit"
|
||||
button-style="secondary"
|
||||
class="sign-in-button"
|
||||
>
|
||||
{{ $t('get_it') }}
|
||||
</ButtonBase>
|
||||
</footer>
|
||||
</div>
|
||||
</article>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
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)
|
||||
})
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
@import '../../../sass/vuefilemanager/variables';
|
||||
@import '../../../sass/vuefilemanager/mixins';
|
||||
|
||||
.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;
|
||||
|
||||
&: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;
|
||||
}
|
||||
}
|
||||
|
||||
.title {
|
||||
@include font-size(22);
|
||||
font-weight: 800;
|
||||
}
|
||||
|
||||
.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 {
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -1,77 +0,0 @@
|
||||
<template>
|
||||
<PopupWrapper>
|
||||
<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 min-h-[80px]" />
|
||||
|
||||
<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('cancel') }}
|
||||
</ButtonBase>
|
||||
<ButtonBase @click.native="confirm" :button-style="buttonColor" class="w-full"
|
||||
>{{ $t('yes_iam_sure') }}
|
||||
</ButtonBase>
|
||||
</PopupActions>
|
||||
</PopupWrapper>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
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
|
||||
}
|
||||
})
|
||||
},
|
||||
}
|
||||
</script>
|
||||
@@ -1,11 +0,0 @@
|
||||
<template>
|
||||
<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',
|
||||
}
|
||||
</script>
|
||||
@@ -1,15 +0,0 @@
|
||||
<template>
|
||||
<div
|
||||
:class="type"
|
||||
class="absolute top-16 bottom-24 left-0 right-0 h-auto overflow-auto px-6 md:relative md:top-0 md:bottom-0"
|
||||
>
|
||||
<slot />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: 'PopupContent',
|
||||
props: ['type'],
|
||||
}
|
||||
</script>
|
||||
@@ -1,64 +0,0 @@
|
||||
<template>
|
||||
<div class="flex items-center justify-between px-6 pt-6 pb-6">
|
||||
<div class="flex items-center">
|
||||
<div class="mr-3">
|
||||
<upload-cloud-icon v-if="icon === 'upload'" size="18" class="vue-feather text-theme" />
|
||||
<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" />
|
||||
<bell-icon v-if="icon === 'bell'" size="18" class="vue-feather text-theme" />
|
||||
</div>
|
||||
|
||||
<b class="text-base font-bold">
|
||||
{{ title }}
|
||||
</b>
|
||||
</div>
|
||||
<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 {
|
||||
BellIcon,
|
||||
UploadCloudIcon,
|
||||
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: {
|
||||
BellIcon,
|
||||
UploadCloudIcon,
|
||||
CornerDownRightIcon,
|
||||
CreditCardIcon,
|
||||
UserPlusIcon,
|
||||
UsersIcon,
|
||||
ShareIcon,
|
||||
Edit2Icon,
|
||||
LinkIcon,
|
||||
KeyIcon,
|
||||
XIcon,
|
||||
},
|
||||
methods: {
|
||||
closePopup() {
|
||||
events.$emit('popup:close')
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
@@ -1,89 +0,0 @@
|
||||
<template>
|
||||
<transition name="popup">
|
||||
<div
|
||||
v-if="isVisibleWrapper"
|
||||
@click.self="closePopup"
|
||||
class="popup fixed top-0 left-0 right-0 bottom-0 z-50 grid h-full overflow-y-auto p-10 lg:absolute"
|
||||
>
|
||||
<div
|
||||
class="fixed top-0 bottom-0 left-0 right-0 z-10 m-auto w-full bg-white shadow-xl dark:bg-dark-foreground md:relative md:w-[490px] md:rounded-xl"
|
||||
>
|
||||
<slot />
|
||||
</div>
|
||||
</div>
|
||||
</transition>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { events } from '../../../bus'
|
||||
|
||||
export default {
|
||||
name: 'PopupWrapper',
|
||||
props: ['name'],
|
||||
data() {
|
||||
return {
|
||||
isVisibleWrapper: false,
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
closePopup() {
|
||||
events.$emit('popup:close')
|
||||
},
|
||||
},
|
||||
created() {
|
||||
// Open called popup
|
||||
events.$on('popup:open', ({ name }) => {
|
||||
if (this.name === name) this.isVisibleWrapper = true
|
||||
|
||||
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>
|
||||
.popup-leave-active {
|
||||
animation: popup-slide-in 0.15s ease reverse;
|
||||
}
|
||||
|
||||
@media only screen and (min-width: 960px) {
|
||||
.popup-enter-active {
|
||||
animation: popup-slide-in 0.25s 0.1s ease both;
|
||||
}
|
||||
|
||||
@keyframes popup-slide-in {
|
||||
0% {
|
||||
opacity: 0;
|
||||
transform: translateY(100px);
|
||||
}
|
||||
|
||||
100% {
|
||||
opacity: 1;
|
||||
transform: translateY(0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@media only screen and (max-width: 960px) {
|
||||
.popup-enter-active {
|
||||
animation: popup-slide-in 0.35s 0.15s ease both;
|
||||
}
|
||||
|
||||
@keyframes popup-slide-in {
|
||||
0% {
|
||||
transform: translateY(100%);
|
||||
}
|
||||
100% {
|
||||
transform: translateY(0);
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -1,175 +0,0 @@
|
||||
<template>
|
||||
<PopupWrapper name="rename-item">
|
||||
<!--Title-->
|
||||
<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 }" />
|
||||
|
||||
<!--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="relative flex items-center">
|
||||
<input
|
||||
v-model="pickedItem.data.attributes.name"
|
||||
:class="{ '!border-rose-600': errors[0] }"
|
||||
ref="input"
|
||||
type="text"
|
||||
class="!pr-10 focus-border-theme input-dark"
|
||||
:placeholder="$t('popup_rename.placeholder')"
|
||||
/>
|
||||
<div @click="pickedItem.data.attributes.name = ''" class="absolute right-0 p-4 cursor-pointer">
|
||||
<x-icon class="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_icon_with_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"
|
||||
/>
|
||||
</ValidationObserver>
|
||||
</PopupContent>
|
||||
|
||||
<!--Actions-->
|
||||
<PopupActions>
|
||||
<ButtonBase class="w-full" @click.native="$closePopup()" button-style="secondary">
|
||||
{{ $t('cancel') }}
|
||||
</ButtonBase>
|
||||
<ButtonBase class="w-full" @click.native="changeName" button-style="theme">
|
||||
{{ $t('popup_share_edit.save') }}
|
||||
</ButtonBase>
|
||||
</PopupActions>
|
||||
</PopupWrapper>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { ValidationProvider, ValidationObserver } from 'vee-validate/dist/vee-validate.full'
|
||||
import PopupWrapper from './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'
|
||||
|
||||
export default {
|
||||
name: 'RenameItemPopup',
|
||||
components: {
|
||||
ValidationProvider,
|
||||
ValidationObserver,
|
||||
EmojiPicker,
|
||||
AppInputSwitch,
|
||||
SwitchInput,
|
||||
AppInputText,
|
||||
ThumbnailItem,
|
||||
PopupWrapper,
|
||||
PopupActions,
|
||||
PopupContent,
|
||||
PopupHeader,
|
||||
ButtonBase,
|
||||
required,
|
||||
XIcon,
|
||||
},
|
||||
computed: {
|
||||
itemTypeTitle() {
|
||||
return this.pickedItem && this.pickedItem.data.type === 'folder'
|
||||
? this.$t('folder')
|
||||
: this.$t('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,
|
||||
})
|
||||
},
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
pickedItem: 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
|
||||
|
||||
if (!this.isEmoji) item['emoji'] = null
|
||||
|
||||
// Rename item request
|
||||
this.$store.dispatch('renameItem', item)
|
||||
|
||||
// Rename item in view
|
||||
events.$emit('change:name', item)
|
||||
|
||||
this.$closePopup()
|
||||
}
|
||||
},
|
||||
},
|
||||
mounted() {
|
||||
// Show popup
|
||||
events.$on('popup:open', (args) => {
|
||||
if (args.name !== 'rename-item') return
|
||||
|
||||
this.isEmoji = false
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
// Store picked item
|
||||
this.pickedItem = args.item
|
||||
})
|
||||
},
|
||||
}
|
||||
</script>
|
||||
@@ -1,33 +0,0 @@
|
||||
<template>
|
||||
<b class="text-label">
|
||||
<slot></slot>
|
||||
</b>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: 'SectionTitle',
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
@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 {
|
||||
color: $theme;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -1,304 +0,0 @@
|
||||
<template>
|
||||
<PopupWrapper name="share-create">
|
||||
<!--Title-->
|
||||
<PopupHeader :title="$t('popup_share_create.title', { item: itemTypeTitle })" icon="share" />
|
||||
|
||||
<!--Content-->
|
||||
<PopupContent class="!overflow-initial">
|
||||
<!--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"
|
||||
>
|
||||
<!--Permission Select-->
|
||||
<ValidationProvider
|
||||
v-if="isFolder"
|
||||
tag="div"
|
||||
mode="passive"
|
||||
name="Permission"
|
||||
rules="required"
|
||||
v-slot="{ errors }"
|
||||
>
|
||||
<AppInputText :title="$t('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('password_protected')"
|
||||
: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-rose-600': 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>
|
||||
|
||||
<!--Set expiration-->
|
||||
<AppInputText v-if="isExpiration" class="-mt-2">
|
||||
<SelectBoxInput
|
||||
v-model="shareOptions.expiration"
|
||||
:data="$translateSelectOptions(expirationList)"
|
||||
class="box"
|
||||
/>
|
||||
</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>
|
||||
|
||||
<!--Emails-->
|
||||
<ValidationProvider
|
||||
v-if="isEmailSharing"
|
||||
tag="div"
|
||||
mode="passive"
|
||||
name="Email"
|
||||
rules="required"
|
||||
v-slot="{ errors }"
|
||||
class="-mt-2 mb-1"
|
||||
>
|
||||
<AppInputText :error="errors[0]" class="-mt-2" :is-last="true">
|
||||
<MultiEmailInput
|
||||
rules="required"
|
||||
v-model="shareOptions.emails"
|
||||
:label="$t('recipients')"
|
||||
:is-error="errors[0]"
|
||||
/>
|
||||
</AppInputText>
|
||||
</ValidationProvider>
|
||||
</div>
|
||||
</ValidationObserver>
|
||||
|
||||
<!--Copy generated link-->
|
||||
<AppInputText v-if="isGeneratedShared" :title="$t('get_your_link')" :is-last="true">
|
||||
<CopyShareLink :item="pickedItem" />
|
||||
</AppInputText>
|
||||
</PopupContent>
|
||||
|
||||
<!--Actions-->
|
||||
<PopupActions>
|
||||
<ButtonBase v-if="!isGeneratedShared" class="w-full" @click.native="$closePopup()" button-style="secondary">
|
||||
{{ $t('cancel') }}
|
||||
</ButtonBase>
|
||||
<ButtonBase
|
||||
class="w-full"
|
||||
@click.native="submitShareOptions"
|
||||
button-style="theme"
|
||||
:loading="isLoading"
|
||||
:disabled="isLoading"
|
||||
>
|
||||
{{ submitButtonText }}
|
||||
</ButtonBase>
|
||||
</PopupActions>
|
||||
</PopupWrapper>
|
||||
</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 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 axios from 'axios'
|
||||
|
||||
export default {
|
||||
name: 'ShareCreatePopup',
|
||||
components: {
|
||||
ValidationProvider,
|
||||
ValidationObserver,
|
||||
AppInputText,
|
||||
AppInputSwitch,
|
||||
SelectBoxInput,
|
||||
ThumbnailItem,
|
||||
ActionButton,
|
||||
PopupWrapper,
|
||||
PopupActions,
|
||||
PopupContent,
|
||||
PopupHeader,
|
||||
MultiEmailInput,
|
||||
SelectInput,
|
||||
SwitchInput,
|
||||
ButtonBase,
|
||||
CopyShareLink,
|
||||
MailIcon,
|
||||
required,
|
||||
LinkIcon,
|
||||
InfoBox,
|
||||
},
|
||||
computed: {
|
||||
...mapGetters(['permissionOptions', 'expirationList']),
|
||||
itemTypeTitle() {
|
||||
return this.pickedItem && this.pickedItem.data.type === 'folder'
|
||||
? this.$t('folder')
|
||||
: this.$t('file')
|
||||
},
|
||||
isFolder() {
|
||||
return this.pickedItem && this.pickedItem.data.type === 'folder'
|
||||
},
|
||||
submitButtonText() {
|
||||
return this.isGeneratedShared ? this.$t('awesome_iam_done') : this.$t('generate_link')
|
||||
},
|
||||
},
|
||||
watch: {
|
||||
isExpiration(val) {
|
||||
if (!val) this.shareOptions.expiration = undefined
|
||||
},
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
isExpiration: false,
|
||||
isEmailSharing: false,
|
||||
shareOptions: {
|
||||
isPassword: false,
|
||||
expiration: undefined,
|
||||
password: undefined,
|
||||
permission: undefined,
|
||||
type: undefined,
|
||||
id: undefined,
|
||||
emails: undefined,
|
||||
},
|
||||
pickedItem: undefined,
|
||||
isGeneratedShared: false,
|
||||
isLoading: false,
|
||||
sharedViaEmail: false,
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
async submitShareOptions() {
|
||||
// If shared was generated, then close popup
|
||||
if (this.isGeneratedShared) {
|
||||
events.$emit('popup:close')
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// Validate fields
|
||||
const isValid = await this.$refs.shareForm.validate()
|
||||
|
||||
if (!isValid) return
|
||||
|
||||
this.isLoading = true
|
||||
|
||||
// Send request to get share link
|
||||
axios
|
||||
.post(`/api/share`, this.shareOptions)
|
||||
.then((response) => {
|
||||
// End loading
|
||||
this.isGeneratedShared = true
|
||||
|
||||
this.$store.commit('UPDATE_SHARED_ITEM', response.data)
|
||||
|
||||
this.pickedItem.data.relationships.shared = response.data
|
||||
})
|
||||
.catch(() => {
|
||||
events.$emit('alert:open', {
|
||||
title: this.$t('popup_error.title'),
|
||||
message: this.$t('popup_error.message'),
|
||||
})
|
||||
|
||||
// End loading
|
||||
this.isLoading = false
|
||||
})
|
||||
.finally(() => {
|
||||
this.isLoading = false
|
||||
})
|
||||
},
|
||||
},
|
||||
mounted() {
|
||||
events.$on('emailsInputValues', (emails) => (this.shareOptions.emails = emails))
|
||||
|
||||
// Show popup
|
||||
events.$on('popup:open', (args) => {
|
||||
if (args.name !== 'share-create') return
|
||||
|
||||
// Store picked item
|
||||
this.pickedItem = args.item
|
||||
|
||||
this.shareOptions.type = args.item.data.type
|
||||
this.shareOptions.id = args.item.data.id
|
||||
})
|
||||
|
||||
// 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,
|
||||
}
|
||||
}, 150)
|
||||
})
|
||||
},
|
||||
}
|
||||
</script>
|
||||
@@ -1,402 +0,0 @@
|
||||
<template>
|
||||
<PopupWrapper name="share-edit">
|
||||
<!--Title-->
|
||||
<PopupHeader :title="popupTitle" icon="share" />
|
||||
|
||||
<!--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('awesome_iam_done') }}
|
||||
</ButtonBase>
|
||||
</PopupActions>
|
||||
</div>
|
||||
|
||||
<!--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')"
|
||||
:is-error="errors[0]"
|
||||
/>
|
||||
</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>
|
||||
|
||||
<!--Update sharing-->
|
||||
<div v-if="pickedItem && !activeSection">
|
||||
<PopupContent class="!overflow-initial">
|
||||
<!--Item Thumbnail-->
|
||||
<ThumbnailItem class="mb-5" :item="pickedItem" />
|
||||
|
||||
<!--Get share link-->
|
||||
<AppInputText :title="$t('get_your_link')">
|
||||
<CopyShareLink :item="pickedItem" />
|
||||
</AppInputText>
|
||||
|
||||
<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('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('password_protected')"
|
||||
: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>
|
||||
|
||||
<!--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-rose-600': 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>
|
||||
|
||||
<!--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>
|
||||
</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'
|
||||
|
||||
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_qr_code'),
|
||||
'email-sharing': this.$t('share_with_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
|
||||
|
||||
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
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// 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()
|
||||
|
||||
if (!isValid) return
|
||||
|
||||
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)
|
||||
|
||||
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) => {
|
||||
if (args.name !== 'share-edit') return
|
||||
|
||||
// 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,
|
||||
}
|
||||
|
||||
if (args.section) this.activeSection = args.section
|
||||
|
||||
if (args.section === 'qr-code') this.getQrCode()
|
||||
|
||||
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)
|
||||
})
|
||||
},
|
||||
}
|
||||
</script>
|
||||
@@ -1,20 +0,0 @@
|
||||
<template>
|
||||
<div v-if="isActive">
|
||||
<slot></slot>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: 'TabOption',
|
||||
props: ['title', 'icon', 'selected'],
|
||||
data() {
|
||||
return {
|
||||
isActive: false,
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
this.isActive = this.selected
|
||||
},
|
||||
}
|
||||
</script>
|
||||
@@ -1,124 +0,0 @@
|
||||
<template>
|
||||
<div>
|
||||
<div class="tab-wrapper">
|
||||
<div
|
||||
class="tab"
|
||||
:class="{ active: tab.isActive }"
|
||||
@click="selectTab(tab)"
|
||||
v-for="(tab, i) in tabs"
|
||||
:key="i"
|
||||
>
|
||||
<!--Icon-->
|
||||
<mail-icon v-if="tab.icon === 'email'" class="tab-icon 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 }}
|
||||
</b>
|
||||
</div>
|
||||
</div>
|
||||
<slot></slot>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { LinkIcon, MailIcon, SmileIcon, FolderIcon } from 'vue-feather-icons'
|
||||
|
||||
export default {
|
||||
name: 'TabWrapper',
|
||||
components: {
|
||||
LinkIcon,
|
||||
MailIcon,
|
||||
SmileIcon,
|
||||
FolderIcon,
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
tabs: [],
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
selectTab(selectedTab) {
|
||||
this.tabs.forEach((tab) => {
|
||||
tab.isActive = tab.title == selectedTab.title
|
||||
})
|
||||
},
|
||||
},
|
||||
mounted() {
|
||||
this.tabs = this.$children
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
@import '../../../sass/vuefilemanager/inapp-forms';
|
||||
@import '../../../sass/vuefilemanager/forms';
|
||||
|
||||
.tab-wrapper {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
margin-bottom: 20px;
|
||||
cursor: pointer;
|
||||
align-items: center;
|
||||
background: white;
|
||||
color: $text;
|
||||
border-radius: 8px;
|
||||
overflow: hidden;
|
||||
border: 1px solid #e8e9eb;
|
||||
|
||||
.tab-title {
|
||||
@include font-size(14);
|
||||
}
|
||||
|
||||
.tab {
|
||||
width: 100%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 8px;
|
||||
|
||||
&.active {
|
||||
background: $light_background;
|
||||
|
||||
.tab-title {
|
||||
color: $text;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.tab-icon {
|
||||
margin-right: 10px;
|
||||
|
||||
path,
|
||||
circle,
|
||||
line,
|
||||
polyline {
|
||||
color: 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: lighten($dark_mode_foreground, 2%);
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -1,54 +0,0 @@
|
||||
<template>
|
||||
<tr class="table-row">
|
||||
<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']
|
||||
|
||||
// Return object
|
||||
return Object.values(this.data)
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
@import '../../../../sass/vuefilemanager/variables';
|
||||
@import '../../../../sass/vuefilemanager/mixins';
|
||||
|
||||
.table-row {
|
||||
border-radius: 8px;
|
||||
|
||||
&:hover {
|
||||
background: $light_background;
|
||||
}
|
||||
|
||||
.table-cell {
|
||||
padding-top: 15px;
|
||||
padding-bottom: 15px;
|
||||
|
||||
&:first-child {
|
||||
padding-left: 15px;
|
||||
}
|
||||
|
||||
&:last-child {
|
||||
padding-right: 15px;
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
span {
|
||||
@include font-size(16);
|
||||
font-weight: bold;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -1,70 +0,0 @@
|
||||
<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'
|
||||
|
||||
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';
|
||||
|
||||
.info {
|
||||
.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;
|
||||
}
|
||||
|
||||
.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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -1,297 +0,0 @@
|
||||
<template>
|
||||
<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="text-xs text-gray-400 dark:text-gray-500">
|
||||
{{ $t(column.label) }}
|
||||
</span>
|
||||
|
||||
<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>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<!--Empty data slot-->
|
||||
<slot v-if="!isLoading && !hasData" name="empty-page"></slot>
|
||||
|
||||
<!--Paginator-->
|
||||
<div v-if="paginator && hasData" class="mt-6 sm:flex sm:items-center sm:justify-between">
|
||||
<!--Show if there is only 6 pages-->
|
||||
<ul v-if="data.meta.total > 15 && data.meta.last_page <= 6" class="pagination flex justify-center items-center">
|
||||
<!--Go previous icon-->
|
||||
<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="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="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-->
|
||||
<ul v-if="data.meta.total > 15 && data.meta.last_page > 6" class="pagination flex justify-center items-center">
|
||||
<!--Go previous icon-->
|
||||
<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="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="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="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="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="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="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="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="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="text-xs text-gray-600 dark:text-gray-500 block text-center sm:mt-0 mt-4">
|
||||
{{ $t('paginator', {from: data.meta.from, to: data.meta.to, total: data.meta.total}) }}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
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
|
||||
|
||||
this.pageIndex = index
|
||||
|
||||
this.getPage(index)
|
||||
},
|
||||
sort(field, sortable) {
|
||||
// Prevent sortable if is disabled
|
||||
if (!sortable) return
|
||||
|
||||
// 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'
|
||||
}
|
||||
|
||||
this.getPage(this.pageIndex)
|
||||
},
|
||||
getPage(page) {
|
||||
// Get api URI
|
||||
this.URI = this.api
|
||||
|
||||
// Set page index
|
||||
if (this.paginator) this.URI = this.URI + '?page=' + page
|
||||
|
||||
// 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
|
||||
|
||||
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)
|
||||
},
|
||||
}
|
||||
</script>
|
||||
@@ -1,37 +0,0 @@
|
||||
<template>
|
||||
<b class="text-label">
|
||||
<slot></slot>
|
||||
</b>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: 'TextLabel',
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
@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: 20px;
|
||||
}
|
||||
}
|
||||
|
||||
.dark {
|
||||
.text-label {
|
||||
opacity: 0.35;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -1,24 +0,0 @@
|
||||
<template>
|
||||
<b class="theme-label">
|
||||
<slot></slot>
|
||||
</b>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: 'TextLabel',
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
@import '../../../sass/vuefilemanager/variables';
|
||||
@import '../../../sass/vuefilemanager/mixins';
|
||||
|
||||
.theme-label {
|
||||
@include font-size(14);
|
||||
color: $theme;
|
||||
font-weight: 600;
|
||||
display: block;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
</style>
|
||||
@@ -1,140 +0,0 @@
|
||||
<template>
|
||||
<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.creator"
|
||||
class="absolute right-1.5 -bottom-2 z-10"
|
||||
/>
|
||||
|
||||
<!--Emoji Icon-->
|
||||
<Emoji
|
||||
v-if="item.data.attributes.emoji"
|
||||
:emoji="item.data.attributes.emoji"
|
||||
class="ml-1 scale-110 transform text-5xl"
|
||||
/>
|
||||
|
||||
<!--Folder Icon-->
|
||||
<FolderIcon v-if="isFolder && !item.data.attributes.emoji" :item="item" />
|
||||
|
||||
<!--File Icon-->
|
||||
<FileIconThumbnail
|
||||
v-if="isFile || isVideo || isAudio || (isImage && !item.data.attributes.thumbnail)"
|
||||
:entry="item"
|
||||
class="pr-2"
|
||||
/>
|
||||
|
||||
<!--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>
|
||||
|
||||
<!--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>
|
||||
|
||||
<!--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>
|
||||
|
||||
<!--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('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'
|
||||
|
||||
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.item.data.relationships.creator && this.user.data.id !== this.item.data.relationships.creator.data.id)
|
||||
},
|
||||
canDrag() {
|
||||
return !this.isDeleted && this.$checkPermission(['master', 'editor'])
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
@@ -1,150 +0,0 @@
|
||||
<template>
|
||||
<div
|
||||
:class="{
|
||||
'pointer-events-none opacity-50': (disabledById && disabledById.data.id === nodes.id) || !disableId || (isRootDepth && !nodes.folders.length && nodes.location !== 'files'),
|
||||
'mb-2.5': isRootDepth,
|
||||
}"
|
||||
>
|
||||
<div
|
||||
:style="indent"
|
||||
class="relative relative flex cursor-pointer select-none items-center whitespace-nowrap 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="['public', 'files', 'upload-request'].includes(nodes.location)"
|
||||
size="17"
|
||||
class="icon vue-feather shrink-0"
|
||||
:class="{ 'text-theme dark-text-theme': isSelectedItem }"
|
||||
/>
|
||||
<users-icon
|
||||
v-if="nodes.location === 'team-folders'"
|
||||
size="17"
|
||||
class="icon vue-feather shrink-0"
|
||||
:class="{ 'text-theme dark-text-theme': isSelectedItem }"
|
||||
/>
|
||||
<user-plus-icon
|
||||
v-if="nodes.location === 'shared-with-me'"
|
||||
size="17"
|
||||
class="icon vue-feather shrink-0"
|
||||
:class="{ 'text-theme dark-text-theme': isSelectedItem }"
|
||||
/>
|
||||
<folder-icon
|
||||
v-if="!nodes.location"
|
||||
size="17"
|
||||
class="icon vue-feather shrink-0"
|
||||
:class="{ 'text-theme dark-text-theme': isSelectedItem }"
|
||||
/>
|
||||
|
||||
<!--Item label-->
|
||||
<b
|
||||
@click="getFolder"
|
||||
class="lg:py-2 py-3.5 ml-3 inline-block overflow-x-hidden text-ellipsis whitespace-nowrap text-xs 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"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
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('./TreeMenu'),
|
||||
},
|
||||
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() {
|
||||
// Show first location
|
||||
if (this.depth === 1 && this.nodes.isOpen) this.isVisible = true
|
||||
|
||||
// Select clicked folder
|
||||
events.$on('pick-folder', (node) => {
|
||||
this.isSelected = false
|
||||
|
||||
if (this.nodes.id === node.id) this.isSelected = true
|
||||
})
|
||||
|
||||
// Select clicked folder
|
||||
events.$on('show-folder-item', (node) => {
|
||||
this.isSelected = false
|
||||
|
||||
if (this.nodes.id === node.id) this.isSelected = true
|
||||
})
|
||||
},
|
||||
}
|
||||
</script>
|
||||
@@ -1,156 +0,0 @@
|
||||
<template>
|
||||
<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 shrink-0" :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'
|
||||
|
||||
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) {
|
||||
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
|
||||
|
||||
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,
|
||||
item: this.draggedItem[0],
|
||||
})
|
||||
}
|
||||
|
||||
// Move all selected items
|
||||
if (this.clipboard.includes(this.draggedItem[0])) {
|
||||
this.$store.dispatch('moveItem', {
|
||||
to_item: this.nodes,
|
||||
item: null,
|
||||
})
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
})
|
||||
},
|
||||
}
|
||||
</script>
|
||||
@@ -1,132 +0,0 @@
|
||||
<template>
|
||||
<PopupWrapper name="two-factor-qr-setup">
|
||||
<PopupHeader :title="$t('set_up_2fa_app')" icon="edit" />
|
||||
|
||||
<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>
|
||||
|
||||
<ValidationObserver @submit.prevent="confirm2FaSetup" ref="codeForm" v-slot="{ invalid }" tag="form" class="mt-5">
|
||||
<ValidationProvider tag="div" mode="passive" name="Code" rules="required" v-slot="{ errors }">
|
||||
<AppInputText :title="$t('confirm')" :error="errors[0]" :is-last="true">
|
||||
<input
|
||||
v-model="code"
|
||||
:class="{ '!border-rose-600': errors[0] }"
|
||||
type="text"
|
||||
ref="input"
|
||||
class="focus-border-theme input-dark"
|
||||
:placeholder="$t('paste_code_from_2fa_app')"
|
||||
/>
|
||||
</AppInputText>
|
||||
</ValidationProvider>
|
||||
</ValidationObserver>
|
||||
|
||||
</PopupContent>
|
||||
|
||||
<PopupActions>
|
||||
<ButtonBase @click.native="confirm2FaSetup" class="w-full" button-style="theme" :loading="isLoading">
|
||||
{{ $t('confirm_your_code') }}
|
||||
</ButtonBase>
|
||||
</PopupActions>
|
||||
</PopupWrapper>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { ValidationProvider, ValidationObserver } from 'vee-validate/dist/vee-validate.full'
|
||||
import { required } from 'vee-validate/dist/rules'
|
||||
import ButtonBase from '../FilesView/ButtonBase'
|
||||
import AppInputText from '../Admin/AppInputText'
|
||||
import PopupWrapper from './Popup/PopupWrapper'
|
||||
import PopupActions from './Popup/PopupActions'
|
||||
import PopupContent from './Popup/PopupContent'
|
||||
import PopupHeader from './Popup/PopupHeader'
|
||||
import InfoBox from './Forms/InfoBox'
|
||||
import { events } from '../../bus'
|
||||
import axios from 'axios'
|
||||
|
||||
export default {
|
||||
name: 'TwoFactorQrSetupPopup',
|
||||
components: {
|
||||
ValidationProvider,
|
||||
ValidationObserver,
|
||||
AppInputText,
|
||||
PopupWrapper,
|
||||
PopupActions,
|
||||
PopupContent,
|
||||
PopupHeader,
|
||||
ButtonBase,
|
||||
required,
|
||||
InfoBox,
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
qrCode: undefined,
|
||||
isLoading: false,
|
||||
code: undefined
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
async confirm2FaSetup() {
|
||||
// Validate fields
|
||||
const isValid = await this.$refs.codeForm.validate()
|
||||
|
||||
if (!isValid) return
|
||||
|
||||
this.isLoading = true
|
||||
|
||||
axios
|
||||
.post('/user/confirmed-two-factor-authentication', {code: this.code})
|
||||
.then(() => {
|
||||
this.$store.commit('CHANGE_TWO_FACTOR_AUTHENTICATION_STATE', true)
|
||||
|
||||
this.$closePopup()
|
||||
|
||||
events.$emit('toaster', {
|
||||
type: 'success',
|
||||
message: this.$t('popup_2fa.toaster_enabled'),
|
||||
})
|
||||
})
|
||||
.catch((error) => {
|
||||
if (error.response.status === 422) {
|
||||
this.$refs.codeForm.setErrors({
|
||||
'Code': error.response.data.errors['code'][0],
|
||||
})
|
||||
}
|
||||
})
|
||||
.finally(() => this.isLoading = false)
|
||||
},
|
||||
enable() {
|
||||
axios
|
||||
.post('/user/two-factor-authentication')
|
||||
.then(() => {
|
||||
this.getQrCode()
|
||||
})
|
||||
.catch(() => {
|
||||
this.$isSomethingWrong()
|
||||
})
|
||||
},
|
||||
getQrCode() {
|
||||
axios
|
||||
.get('/user/two-factor-qr-code')
|
||||
.then((response) => {
|
||||
this.qrCode = response.data.svg
|
||||
})
|
||||
.catch(() => {
|
||||
this.$isSomethingWrong()
|
||||
})
|
||||
},
|
||||
},
|
||||
created() {
|
||||
events.$on('popup:open', (args) => {
|
||||
if (args.name !== 'two-factor-qr-setup') return
|
||||
|
||||
this.enable()
|
||||
})
|
||||
},
|
||||
}
|
||||
</script>
|
||||
@@ -1,200 +0,0 @@
|
||||
<template>
|
||||
<PopupWrapper name="two-factor-recovery-codes">
|
||||
<PopupHeader :title="$t('your_security_codes')" icon="key" />
|
||||
|
||||
<PopupContent style="padding: 0 20px">
|
||||
<div class="mobile-actions">
|
||||
<MobileActionButton @click.native="copyCodes" icon="copy">
|
||||
{{ $t('copy') }}
|
||||
</MobileActionButton>
|
||||
|
||||
<MobileActionButton @click.native="downloadCodes" icon="download">
|
||||
{{ $t('download') }}
|
||||
</MobileActionButton>
|
||||
|
||||
<MobileActionButton @click.native="regenerateCodes" icon="refresh">
|
||||
{{ $t('regenerate_codes') }}
|
||||
</MobileActionButton>
|
||||
</div>
|
||||
|
||||
<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>
|
||||
|
||||
<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>
|
||||
</PopupContent>
|
||||
|
||||
<PopupActions>
|
||||
<ButtonBase class="w-full" @click.native="$closePopup()" button-style="theme">
|
||||
{{ $t('awesome_iam_done') }}
|
||||
</ButtonBase>
|
||||
</PopupActions>
|
||||
</PopupWrapper>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
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 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
|
||||
|
||||
copyText.select()
|
||||
copyText.setSelectionRange(0, 99999)
|
||||
|
||||
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'))
|
||||
|
||||
// Create download link
|
||||
let downloadLink = document.createElement('a')
|
||||
|
||||
downloadLink.href = recoveryCodes
|
||||
downloadLink.download = 'recovery-codes.txt'
|
||||
|
||||
// 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()
|
||||
|
||||
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';
|
||||
|
||||
.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;
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.codes-output {
|
||||
position: absolute;
|
||||
right: -9999px;
|
||||
}
|
||||
|
||||
.spinner-wrapper {
|
||||
height: 339px;
|
||||
position: relative;
|
||||
|
||||
.spinner {
|
||||
top: 46% !important;
|
||||
}
|
||||
}
|
||||
|
||||
.dark {
|
||||
.codes-list {
|
||||
li {
|
||||
border-color: $dark_mode_border_color;
|
||||
}
|
||||
}
|
||||
|
||||
.info-box,
|
||||
.mobile-action-button {
|
||||
background: lighten($dark_mode_foreground, 3%);
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -1,77 +0,0 @@
|
||||
<template>
|
||||
<transition name="vignette">
|
||||
<div
|
||||
v-if="isVisible"
|
||||
class="vignette dark:bg-2x-dark-background bg-dark-background bg-opacity-[0.35] dark:bg-opacity-[0.70]"
|
||||
@click="closePopup"
|
||||
></div>
|
||||
</transition>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { events } from '../../bus'
|
||||
import { mapGetters } from 'vuex'
|
||||
|
||||
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('alert:open', () => (this.isVisibleVignette = true))
|
||||
events.$on('success:open', () => (this.isVisibleVignette = true))
|
||||
events.$on('confirm:open', () => (this.isVisibleVignette = true))
|
||||
|
||||
// Hide vignette
|
||||
events.$on('popup:close', () => (this.isVisibleVignette = false))
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
@import '../../../sass/vuefilemanager/variables';
|
||||
@import '../../../sass/vuefilemanager/mixins';
|
||||
|
||||
.vignette {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
right: 0;
|
||||
left: 0;
|
||||
bottom: 0;
|
||||
z-index: 40;
|
||||
}
|
||||
|
||||
.vignette-enter-active {
|
||||
animation: vignette-in 0.15s linear;
|
||||
}
|
||||
|
||||
.vignette-leave-active {
|
||||
animation: vignette-in 0.15s cubic-bezier(0.4, 0, 1, 1) reverse;
|
||||
}
|
||||
|
||||
@keyframes vignette-in {
|
||||
0% {
|
||||
opacity: 0;
|
||||
}
|
||||
100% {
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
Reference in New Issue
Block a user