vue components refactoring

This commit is contained in:
Čarodej
2022-04-13 16:19:10 +02:00
parent 6a4bfa8bfe
commit 338f8664b7
251 changed files with 1068 additions and 1943 deletions

View File

@@ -0,0 +1,52 @@
<template>
<div
class="mb-6 flex cursor-pointer items-center rounded-xl p-5 shadow-card"
:class="{
'dark:bg-green-700/30 bg-green-200': color === 'green',
'dark:bg-rose-700/30 bg-rose-200': color === 'rose',
}"
>
<refresh-cw-icon
v-if="isLoading"
size="18"
class="vue-feather mr-4 shrink-0 animate-spin"
:class="{
'text-green-700 dark:text-green-500': color === 'green',
'text-rose-700 dark:text-rose-500': color === 'rose',
}"
/>
<alert-octagon-icon
v-if="!isLoading"
size="18"
class="vue-feather mr-4 shrink-0"
:class="{
'text-green-700 dark:text-green-500': color === 'green',
'text-rose-700 dark:text-rose-500': color === 'rose',
}"
/>
<p
class="text-sm text-green-700 dark:text-green-500"
:class="{
'text-green-700 dark:text-green-500': color === 'green',
'text-rose-700 dark:text-rose-500': color === 'rose',
}"
>
<slot />
</p>
</div>
</template>
<script>
import {AlertOctagonIcon, RefreshCwIcon} from "vue-feather-icons";
export default {
name: 'AlertBox',
props: [
'isLoading',
'color',
],
components: {
AlertOctagonIcon,
RefreshCwIcon,
}
}
</script>

View File

@@ -0,0 +1,51 @@
<template>
<div id="card-navigation" style="height: 62px" class="mb-7">
<div
:class="{
'fixed top-0 left-0 right-0 z-10 rounded-none bg-white bg-opacity-50 px-6 backdrop-blur-lg backdrop-filter dark:bg-dark-foreground':
fixedNav,
}"
>
<div class="overflow-x-auto whitespace-nowrap">
<router-link
class="border-bottom-theme inline-block border-b-2 border-transparent px-4 py-5 text-sm font-bold"
:class="{
'text-theme': routeName === page.route,
'text-gray-600 dark:text-gray-100': routeName !== page.route,
}"
v-for="(page, i) in pages"
:to="{ name: page.route }"
:key="i"
replace
>
{{ page.title }}
</router-link>
</div>
</div>
</div>
</template>
<script>
export default {
name: 'CardNavigation',
props: ['pages'],
computed: {
routeName() {
return this.$route.name
},
},
data() {
return {
fixedNav: false,
}
},
created() {
// Handle fixed mobile navigation
window.addEventListener('scroll', () => {
let card = document.getElementById('card-navigation')
this.fixedNav = card.getBoundingClientRect().top < 0
})
},
}
</script>

View File

@@ -0,0 +1,46 @@
<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>

View File

@@ -0,0 +1,79 @@
<template>
<div
v-show="isVisible"
id="drag-ui"
class="pointer-events-none fixed z-20 w-64 rounded-xl bg-white p-5 shadow-lg dark:bg-dark-foreground"
>
<TitlePreview icon="check-square" :title="title" :subtitle="subtitle" />
</div>
</template>
<script>
import TitlePreview from '../Labels/TitlePreview'
import { mapGetters } from 'vuex'
import { events } from '../../../bus'
export default {
name: 'DragUI',
components: {
TitlePreview,
},
computed: {
...mapGetters(['clipboard']),
title() {
let filesLength = this.clipboard.length,
hasDraggedItem = this.clipboard.includes(this.draggedItem)
// Title for multiple selected items
if (filesLength > 1 && hasDraggedItem) {
return this.$t('selected_multiple')
}
// Title for single item
if ((filesLength < 2 || !hasDraggedItem) && this.draggedItem) {
return this.draggedItem.data.attributes.name
}
},
subtitle() {
let filesLength = this.clipboard.length,
hasDraggedItem = this.clipboard.includes(this.draggedItem)
// Subtitle for multiple selected items
if (filesLength > 1 && hasDraggedItem) {
return filesLength + ' ' + this.$tc('items', filesLength)
}
if ((filesLength < 2 || !hasDraggedItem) && this.draggedItem) {
// Subtitle for single folder
if (this.draggedItem.data.type === 'folder') {
return this.draggedItem.items == 0
? this.$t('empty')
: this.$tc('folder.item_counts', this.draggedItem.items)
}
// Subtitle for single file
if (this.draggedItem.data.type !== 'folder' && this.draggedItem.data.attributes.mimetype) {
return '.' + this.draggedItem.data.attributes.mimetype
}
}
},
},
data() {
return {
isVisible: false,
draggedItem: undefined,
}
},
created() {
events.$on('dragstart', (data) => {
this.draggedItem = data
setTimeout(() => {
this.isVisible = true
}, 100)
})
events.$on('drop', () => (this.isVisible = false))
},
}
</script>

View File

@@ -0,0 +1,90 @@
<template>
<div>
<div class="flex items-center justify-between pt-0.5 pb-2" v-if="clipboard.data.attributes.date_time_original">
<b class="font-bold text-sm">{{ $t('time_data') }}</b>
<b class="font-bold text-sm">{{ clipboard.data.attributes.date_time_original }}</b>
</div>
<div class="flex items-center justify-between py-2" v-if="clipboard.data.attributes.artist">
<b class="font-bold text-sm">{{ $t('author') }}</b>
<b class="font-bold text-sm">{{ clipboard.data.attributes.artist }}</b>
</div>
<div class="flex items-center justify-between py-2" v-if="clipboard.data.attributes.width && clipboard.data.attributes.height">
<b class="font-bold text-sm">{{ $t('dimension') }}</b>
<b class="font-bold text-sm">{{ clipboard.data.attributes.width }}x{{ clipboard.data.attributes.height }}</b>
</div>
<div class="flex items-center justify-between py-2" v-if="clipboard.data.attributes.x_resolution && clipboard.data.attributes.y_resolution">
<b class="font-bold text-sm">{{ $t('resolution') }}</b>
<b class="font-bold text-sm">{{ clipboard.data.attributes.x_resolution }}x{{ clipboard.data.attributes.y_resolution }}</b>
</div>
<div class="flex items-center justify-between py-2" v-if="clipboard.data.attributes.color_space">
<b class="font-bold text-sm"> {{ $t('color_space') }}</b>
<b class="font-bold text-sm">{{ clipboard.data.attributes.color_space }}</b>
</div>
<div class="flex items-center justify-between py-2" v-if="clipboard.data.attributes.make">
<b class="font-bold text-sm">{{ $t('make') }}</b>
<b class="font-bold text-sm">{{ clipboard.data.attributes.make }}</b>
</div>
<div class="flex items-center justify-between py-2" v-if="clipboard.data.attributes.model">
<b class="font-bold text-sm">{{ $t('model') }}</b>
<b class="font-bold text-sm">{{ clipboard.data.attributes.model }}</b>
</div>
<div class="flex items-center justify-between py-2" v-if="clipboard.data.attributes.aperture_value">
<b class="font-bold text-sm">{{ $t('aperture_value') }}</b>
<b class="font-bold text-sm"> {{ clipboard.data.attributes.aperture_value }} </b>
</div>
<div class="flex items-center justify-between py-2" v-if="clipboard.data.attributes.exposure_time">
<b class="font-bold text-sm">{{ $t('exposure') }}</b>
<b class="font-bold text-sm">{{ clipboard.data.attributes.exposure_time }}</b>
</div>
<div class="flex items-center justify-between py-2" v-if="clipboard.data.attributes.focal_length">
<b class="font-bold text-sm">{{ $t('focal') }}</b>
<b class="font-bold text-sm">{{ clipboard.data.attributes.focal_length }}</b>
</div>
<div class="flex items-center justify-between py-2" v-if="clipboard.data.attributes.iso">
<b class="font-bold text-sm">{{ $t('iso') }}</b>
<b class="font-bold text-sm">{{ clipboard.data.attributes.iso }}</b>
</div>
<div class="flex items-center justify-between py-2" v-if="clipboard.data.attributes.aperture_f_number">
<b class="font-bold text-sm">{{ $t('aperature') }}</b>
<b class="font-bold text-sm">{{ clipboard.data.attributes.aperture_f_number }}</b>
</div>
<div class="flex items-center justify-between py-2" v-if="clipboard.data.attributes.ccd_width">
<b class="font-bold text-sm">{{ $t('camera_lens') }}</b>
<b class="font-bold text-sm">{{ clipboard.data.attributes.ccd_width }}</b>
</div>
<div class="flex items-center justify-between py-2" v-if="clipboard.data.attributes.longitude">
<b class="font-bold text-sm">{{ $t('longitude') }}</b>
<b class="font-bold text-sm">{{ clipboard.data.attributes.longitude }}</b>
</div>
<div class="flex items-center justify-between py-2" v-if="clipboard.data.attributes.latitude">
<b class="font-bold text-sm">{{ $t('latitude') }}</b>
<b class="font-bold text-sm">{{ clipboard.data.attributes.latitude }}</b>
</div>
</div>
</template>
<script>
export default {
name: 'ImageMetaData',
computed: {
clipboard() {
return this.$store.getters.clipboard[0].data.relationships.exif
},
},
}
</script>

View File

@@ -0,0 +1,112 @@
<template>
<div class="info-box" :class="type">
<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>

View File

@@ -0,0 +1,72 @@
<template>
<div class="shrink-0 grow-0">
<img
:style="{ width: size + 'px', height: size + 'px' }"
v-if="member.data.attributes.avatar"
:src="avatar"
:class="[
borderRadius,
{
'border-3 border-white dark:border-dark-background': isBorder,
},
]"
class="object-cover mx-auto"
/>
<div
v-else
class="flex items-center justify-center mx-auto"
:class="[
borderRadius,
{
'border-3 border-white dark:border-dark-background': isBorder,
'dark:bg-4x-dark-foreground bg-light-background': !member.data.attributes.color,
},
]"
:style="{
width: size + 'px',
height: size + 'px',
background: member.data.attributes.color ? member.data.attributes.color : '',
}"
>
<span :class="fontSize" class="font-extrabold uppercase text-white">
{{ letter }}
</span>
</div>
</div>
</template>
<script>
export default {
name: 'MemberAvatar',
props: ['isBorder', 'member', 'size'],
computed: {
letter() {
let string = this.member.data.attributes.name
? this.member.data.attributes.name
: this.member.data.attributes.email
return string.substr(0, 1)
},
borderRadius() {
return this.size > 32 ? 'rounded-xl' : 'rounded-lg'
},
fontSize() {
if (this.size > 42) {
return 'text-lg'
} else if (this.size > 32) {
return 'text-base'
} else {
return 'text-sm'
}
},
avatar() {
if (this.size >= 52) {
return this.member.data.attributes.avatar.md
} else if (this.size > 32) {
return this.member.data.attributes.avatar.sm
} else {
return this.member.data.attributes.avatar.xs
}
},
},
}
</script>

View File

@@ -0,0 +1,44 @@
<template>
<div class="progress-bar">
<span class="bg-theme" :style="{ width: progress + '%' }"></span>
</div>
</template>
<script>
export default {
name: 'ProgressBar',
props: ['progress'],
}
</script>
<style scoped lang="scss">
@import '../../../../sass/vuefilemanager/variables';
@import '../../../../sass/vuefilemanager/mixins';
.progress-bar {
width: 100%;
height: 5px;
background: $light_background;
margin-top: 6px;
border-radius: 10px;
span {
display: block;
height: 100%;
border-radius: 10px;
max-width: 100%;
}
}
.dark {
.progress-bar {
background: $dark_mode_foreground;
}
}
@media only screen and (min-width: 680px) {
.dark .progress-bar {
background: $dark_mode_foreground;
}
}
</style>

View File

@@ -0,0 +1,44 @@
<template>
<div id="loading-bar-spinner" class="spinner">
<div class="spinner-icon border-top-theme border-left-theme"></div>
</div>
</template>
<script>
export default {
name: 'Spinner',
}
</script>
<style scoped lang="scss">
@import '../../../../sass/vuefilemanager/variables';
@import '../../../../sass/vuefilemanager/mixins';
#loading-bar-spinner.spinner {
left: 50%;
margin-left: -20px;
top: 50%;
margin-top: -20px;
position: absolute;
z-index: 19 !important;
animation: loading-bar-spinner 400ms linear infinite;
}
#loading-bar-spinner.spinner .spinner-icon {
width: 40px;
height: 40px;
border: solid 4px transparent;
//border-top-color: $theme !important;
//border-left-color: $theme !important;
border-radius: 50%;
}
@keyframes loading-bar-spinner {
0% {
transform: rotate(0deg);
}
100% {
transform: rotate(360deg);
}
}
</style>

View File

@@ -0,0 +1,131 @@
<template>
<transition name="info-panel">
<div v-if="fileQueue.length > 0" class="upload-progress">
<div class="progress-title">
<!--Is processing-->
<span v-if="isProcessingFile" class="flex items-center justify-center">
<refresh-cw-icon size="12" class="sync-alt text-theme" />
{{ $t('uploading.processing_file') }}
</span>
<!--Multi file upload-->
<span v-if="!isProcessingFile && fileQueue.length > 0">
{{
$t('uploading.progress', {
current: filesInQueueUploaded,
total: filesInQueueTotal,
progress: uploadingProgress,
})
}}
</span>
</div>
<div class="progress-wrapper">
<ProgressBar :progress="uploadingProgress" />
<span @click="cancelUpload" :title="$t('uploading.cancel')" class="cancel-icon">
<x-icon size="16" @click="cancelUpload" class="hover-text-theme"></x-icon>
</span>
</div>
</div>
</transition>
</template>
<script>
import ProgressBar from './ProgressBar'
import { RefreshCwIcon, XIcon } from 'vue-feather-icons'
import { mapGetters } from 'vuex'
import { events } from '../../../bus'
export default {
name: 'UploadProgress',
components: {
RefreshCwIcon,
ProgressBar,
XIcon,
},
computed: {
...mapGetters([
'filesInQueueUploaded',
'filesInQueueTotal',
'uploadingProgress',
'isProcessingFile',
'fileQueue',
]),
},
methods: {
cancelUpload() {
events.$emit('cancel-upload')
},
},
}
</script>
<style scoped lang="scss">
@import '../../../../sass/vuefilemanager/variables';
@import '../../../../sass/vuefilemanager/mixins';
.sync-alt {
animation: spin 1s linear infinite;
margin-right: 5px;
polyline,
path {
color: inherit;
}
}
@keyframes spin {
0% {
transform: rotate(0);
}
100% {
transform: rotate(360deg);
}
}
.info-panel-enter-active,
.info-panel-leave-active {
transition: all 0.3s ease;
}
.info-panel-enter,
.info-panel-leave-to {
opacity: 0;
transform: translateY(-100%);
}
.upload-progress {
width: 100%;
position: relative;
z-index: 1;
.progress-wrapper {
display: flex;
.cancel-icon {
cursor: pointer;
padding: 0 7px 0 13px;
&:hover {
line {
color: inherit;
}
}
}
}
.progress-title {
font-weight: 700;
text-align: center;
span {
@include font-size(14);
}
}
}
.dark {
.progress-bar {
background: $dark_mode_foreground;
}
}
</style>

View File

@@ -0,0 +1,39 @@
<template>
<div class="flex items-center justify-between">
<div class="flex items-center leading-none">
<MemberAvatar :size="52" :is-border="false" :member="user" />
<div class="pl-4">
<b class="mb-1 block font-bold leading-none">
{{ user.data.relationships.settings.data.attributes.name }}
</b>
<span class="text-theme text-sm font-semibold leading-none">
{{ user.data.attributes.email }}
</span>
</div>
</div>
<NotificationBell @click.native="openNotificationPopup" />
</div>
</template>
<script>
import MemberAvatar from './MemberAvatar'
import NotificationBell from '../../Notifications/Components/NotificationBell'
import { events } from '../../../bus'
import { mapGetters } from 'vuex'
export default {
name: 'UserHeadline',
components: {
NotificationBell,
MemberAvatar,
},
computed: {
...mapGetters(['user']),
},
methods: {
openNotificationPopup() {
events.$emit('popup:open', { name: 'notifications-mobile' })
},
},
}
</script>

View File

@@ -0,0 +1,77 @@
<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>