feat: convert simple components to script setup and use typescript (#1120)

Co-authored-by: Dominik Pschenitschni <mail@celement.de>
Reviewed-on: vikunja/frontend#1120
Reviewed-by: konrad <k@knt.li>
Co-authored-by: Dominik Pschenitschni <dpschen@noreply.kolaente.de>
Co-committed-by: Dominik Pschenitschni <dpschen@noreply.kolaente.de>
This commit is contained in:
Dominik Pschenitschni 2021-12-04 14:47:32 +00:00 committed by konrad
parent f758eefa88
commit ac630ac775
16 changed files with 298 additions and 387 deletions

View File

@ -20,64 +20,59 @@
:class="{'is-favorite': list.isFavorite, 'is-archived': list.isArchived}" :class="{'is-favorite': list.isFavorite, 'is-archived': list.isArchived}"
@click.stop="toggleFavoriteList(list)" @click.stop="toggleFavoriteList(list)"
class="favorite"> class="favorite">
<icon icon="star" v-if="list.isFavorite"/> <icon :icon="list.isFavorite ? 'star' : ['far', 'star']" />
<icon :icon="['far', 'star']" v-else/>
</span> </span>
</div> </div>
<div class="title">{{ list.title }}</div> <div class="title">{{ list.title }}</div>
</router-link> </router-link>
</template> </template>
<script> <script lang="ts" setup>
import {ref, watch} from 'vue'
import {useStore} from 'vuex'
import ListService from '@/services/list' import ListService from '@/services/list'
export default { const background = ref(null)
name: 'list-card', const backgroundLoading = ref(false)
data() {
return {
background: null,
backgroundLoading: false,
}
},
props: {
list: {
required: true,
},
showArchived: {
default: false,
type: Boolean,
},
},
watch: {
list: {
handler: 'loadBackground',
immediate: true,
},
},
methods: {
async loadBackground() {
if (this.list === null || !this.list.backgroundInformation || this.backgroundLoading) {
return
}
this.backgroundLoading = true const props = defineProps({
list: {
const listService = new ListService() type: Object,
try { required: true,
this.background = await listService.background(this.list)
} finally {
this.backgroundLoading = false
}
},
toggleFavoriteList(list) {
// The favorites pseudo list is always favorite
// Archived lists cannot be marked favorite
if (list.id === -1 || list.isArchived) {
return
}
this.$store.dispatch('lists/toggleListFavorite', list)
},
}, },
showArchived: {
default: false,
type: Boolean,
},
})
watch(props.list, loadBackground, { immediate: true })
async function loadBackground() {
if (props.list === null || !props.list.backgroundInformation || backgroundLoading.value) {
return
}
backgroundLoading.value = true
const listService = new ListService()
try {
background.value = await listService.background(props.list)
} finally {
backgroundLoading.value = false
}
}
const store = useStore()
function toggleFavoriteList(list) {
// The favorites pseudo list is always favorite
// Archived lists cannot be marked favorite
if (list.id === -1 || list.isArchived) {
return
}
store.dispatch('lists/toggleListFavorite', list)
} }
</script> </script>

View File

@ -1,40 +1,27 @@
<template> <template>
<div <div
v-if="isDone" v-if="isDone"
class="is-done" class="is-done"
:class="{ 'is-done--small': variant === variants.SMALL }" :class="{ 'is-done--small': variant === 'small' }"
> >
{{ $t('task.attributes.done') }} {{ $t('task.attributes.done') }}
</div> </div>
</template> </template>
<script> <script lang="ts" setup>
const VARIANTS = { import {PropType} from 'vue'
DEFAULT: 'default', type Variants = 'default' | 'small'
SMALL: 'small',
}
export default { defineProps({
name: 'Done', isDone: {
type: Boolean,
data() { default: false,
return {
variants: VARIANTS,
}
}, },
variant: {
props: { type: String as PropType<Variants>,
isDone: { default: 'default',
type: Boolean,
default: false,
},
variant: {
type: String,
default: VARIANTS.DEFAULT,
validator: (variant) => Object.values(VARIANTS).includes(variant),
},
}, },
} })
</script> </script>

View File

@ -24,41 +24,39 @@
</div> </div>
</template> </template>
<script> <script setup lang="ts">
export default { defineProps({
name: 'card', title: {
props: { type: String,
title: { default: '',
type: String,
default: '',
},
padding: {
type: Boolean,
default: true,
},
hasClose: {
type: Boolean,
default: false,
},
closeIcon: {
type: String,
default: 'times',
},
shadow: {
type: Boolean,
default: true,
},
hasContent: {
type: Boolean,
default: true,
},
loading: {
type: Boolean,
default: false,
},
}, },
emits: ['close'], padding: {
} type: Boolean,
default: true,
},
hasClose: {
type: Boolean,
default: false,
},
closeIcon: {
type: String,
default: 'times',
},
shadow: {
type: Boolean,
default: true,
},
hasContent: {
type: Boolean,
default: true,
},
loading: {
type: Boolean,
default: false,
},
})
defineEmits(['close'])
</script> </script>
<style lang="scss" scoped> <style lang="scss" scoped>

View File

@ -11,18 +11,15 @@
</router-link> </router-link>
</template> </template>
<script> <script lang="ts" setup>
export default { defineProps({
name: 'dropdown-item', to: {
props: { required: true,
to: {
required: true,
},
icon: {
type: String,
required: false,
default: '',
},
}, },
} icon: {
type: String,
required: false,
default: '',
},
})
</script> </script>

View File

@ -7,16 +7,10 @@
</message> </message>
</template> </template>
<script> <script lang="ts" setup>
import Message from '@/components/misc/message' import Message from '@/components/misc/message.vue'
export default { function reload() {
name: 'error', window.location.reload()
components: {Message},
methods: {
reload() {
window.location.reload()
},
},
} }
</script> </script>

View File

@ -6,16 +6,14 @@
</div> </div>
</template> </template>
<script> <script lang="ts" setup>
import {mapState} from 'vuex' import {computed} from 'vue'
import {useStore} from 'vuex'
export default { const store = useStore()
name: 'legal',
computed: mapState({ const imprintUrl = computed(() => store.state.config.legal.imprintUrl)
imprintUrl: state => state.config.legal.imprintUrl, const privacyPolicyUrl = computed(() => store.state.config.legal.privacyPolicyUrl)
privacyPolicyUrl: state => state.config.legal.privacyPolicyUrl,
}),
}
</script> </script>
<style lang="scss" scoped> <style lang="scss" scoped>

View File

@ -2,12 +2,6 @@
<div class="loader-container is-loading"></div> <div class="loader-container is-loading"></div>
</template> </template>
<script>
export default {
name: 'loading',
}
</script>
<style scoped> <style scoped>
.loader-container { .loader-container {
height: 100%; height: 100%;

View File

@ -4,7 +4,7 @@
</div> </div>
</template> </template>
<script setup> <script lang="ts" setup>
defineProps({ defineProps({
variant: { variant: {
type: String, type: String,

View File

@ -2,17 +2,17 @@
<div class="no-auth-wrapper"> <div class="no-auth-wrapper">
<div class="noauth-container"> <div class="noauth-container">
<Logo class="logo" width="400" height="117" /> <Logo class="logo" width="400" height="117" />
<message v-if="motd !== ''" class="my-2"> <Message v-if="motd !== ''" class="my-2">
{{ motd }} {{ motd }}
</message> </Message>
<slot/> <slot/>
</div> </div>
</div> </div>
</template> </template>
<script setup> <script lang="ts" setup>
import Logo from '@/components/home/Logo.vue' import Logo from '@/components/home/Logo.vue'
import message from '@/components/misc/message' import Message from '@/components/misc/message.vue'
import {useStore} from 'vuex' import {useStore} from 'vuex'
import {computed} from 'vue' import {computed} from 'vue'

View File

@ -34,8 +34,10 @@
</nav> </nav>
</template> </template>
<script> <script lang="ts" setup>
function createPagination(totalPages, currentPage) { import {computed} from 'vue'
function createPagination(totalPages: number, currentPage: number) {
const pages = [] const pages = []
for (let i = 0; i < totalPages; i++) { for (let i = 0; i < totalPages; i++) {
@ -79,30 +81,18 @@ function getRouteForPagination(page = 1, type = 'list') {
} }
} }
export default { const props = defineProps({
name: 'Pagination', totalPages: {
type: Number,
props: { required: true,
totalPages: {
type: Number,
required: true,
},
currentPage: {
type: Number,
default: 0,
},
}, },
currentPage: {
computed: { type: Number,
pages() { default: 0,
return createPagination(this.totalPages, this.currentPage)
},
}, },
})
methods: { const pages = computed(() => createPagination(props.totalPages, props.currentPage))
getRouteForPagination,
},
}
</script> </script>
<style lang="scss" scoped> <style lang="scss" scoped>

View File

@ -13,7 +13,7 @@
<section v-else-if="error !== ''"> <section v-else-if="error !== ''">
<no-auth-wrapper> <no-auth-wrapper>
<card> <card>
<p v-if="error === errorNoApiUrl"> <p v-if="error === ERROR_NO_API_URL">
{{ $t('ready.noApiUrlConfigured') }} {{ $t('ready.noApiUrlConfigured') }}
</p> </p>
<message variant="danger" v-else> <message variant="danger" v-else>
@ -40,51 +40,34 @@
</transition> </transition>
</template> </template>
<script> <script lang="ts" setup>
import {ref, computed} from 'vue'
import {useStore} from 'vuex'
import Logo from '@/assets/logo.svg?component' import Logo from '@/assets/logo.svg?component'
import ApiConfig from '@/components/misc/api-config' import ApiConfig from '@/components/misc/api-config.vue'
import Message from '@/components/misc/message' import Message from '@/components/misc/message.vue'
import NoAuthWrapper from '@/components/misc/no-auth-wrapper' import NoAuthWrapper from '@/components/misc/no-auth-wrapper.vue'
import {mapState} from 'vuex'
import {ERROR_NO_API_URL} from '@/helpers/checkAndSetApiUrl' import {ERROR_NO_API_URL} from '@/helpers/checkAndSetApiUrl'
export default { const store = useStore()
name: 'ready',
components: { const ready = computed(() => store.state.vikunjaReady)
Message, const online = computed(() => store.state.online)
Logo,
NoAuthWrapper, const error = ref('')
ApiConfig, const showLoading = computed(() => !ready.value && error.value === '')
},
data() { async function load() {
return { try {
error: '', await store.dispatch('loadApp')
errorNoApiUrl: ERROR_NO_API_URL, } catch(e: any) {
} error.value = e
}, }
created() {
this.load()
},
computed: {
ready() {
return this.$store.state.vikunjaReady
},
showLoading() {
return !this.ready && this.error === ''
},
...mapState([
'online',
]),
},
methods: {
load() {
this.$store.dispatch('loadApp')
.catch(e => {
this.error = e
})
},
},
} }
load()
</script> </script>
<style lang="scss" scoped> <style lang="scss" scoped>

View File

@ -7,24 +7,21 @@
</component> </component>
</template> </template>
<script> <script lang="ts" setup>
export default { defineProps({
name: 'shortcut', keys: {
props: { type: Array,
keys: { required: true,
type: Array,
required: true,
},
combination: {
type: String,
default: '+',
},
is: {
type: String,
default: 'div',
},
}, },
} combination: {
type: String,
default: '+',
},
is: {
type: String,
default: 'div',
},
})
</script> </script>
<style lang="scss" scoped> <style lang="scss" scoped>

View File

@ -22,91 +22,89 @@
</a> </a>
</template> </template>
<script> <script lang="ts" setup>
import {computed, shallowRef} from 'vue'
import {useI18n} from 'vue-i18n'
import SubscriptionService from '@/services/subscription' import SubscriptionService from '@/services/subscription'
import SubscriptionModel from '@/models/subscription' import SubscriptionModel from '@/models/subscription'
export default { import {success} from '@/message'
name: 'task-subscription',
data() {
return {
subscriptionService: new SubscriptionService(),
}
},
props: {
entity: {
required: true,
type: String,
},
subscription: {
required: true,
},
entityId: {
required: true,
},
isButton: {
type: Boolean,
default: true,
},
},
emits: ['change'],
computed: {
tooltipText() {
if (this.disabled) {
return this.$t('task.subscription.subscribedThroughParent', {
entity: this.entity,
parent: this.subscription.entity,
})
}
return this.subscription !== null ? const props = defineProps({
this.$t('task.subscription.subscribed', {entity: this.entity}) : entity: {
this.$t('task.subscription.notSubscribed', {entity: this.entity}) required: true,
}, type: String,
buttonText() {
return this.subscription !== null ? this.$t('task.subscription.unsubscribe') : this.$t('task.subscription.subscribe')
},
icon() {
return this.subscription !== null ? ['far', 'bell-slash'] : 'bell'
},
disabled() {
if (this.subscription === null) {
return false
}
return this.subscription.entity !== this.entity
},
}, },
methods: { subscription: {
changeSubscription() { required: true,
if (this.disabled) {
return
}
if (this.subscription === null) {
this.subscribe()
} else {
this.unsubscribe()
}
},
async subscribe() {
const subscription = new SubscriptionModel({
entity: this.entity,
entityId: this.entityId,
})
await this.subscriptionService.create(subscription)
this.$emit('change', subscription)
this.$message.success({message: this.$t('task.subscription.subscribeSuccess', {entity: this.entity})})
},
async unsubscribe() {
const subscription = new SubscriptionModel({
entity: this.entity,
entityId: this.entityId,
})
await this.subscriptionService.delete(subscription)
this.$emit('change', null)
this.$message.success({message: this.$t('task.subscription.unsubscribeSuccess', {entity: this.entity})})
},
}, },
entityId: {
required: true,
},
isButton: {
type: Boolean,
default: true,
},
})
const emit = defineEmits(['change'])
const subscriptionService = shallowRef(new SubscriptionService())
const {t} = useI18n()
const tooltipText = computed(() => {
if (disabled.value) {
return t('task.subscription.subscribedThroughParent', {
entity: props.entity,
parent: props.subscription.entity,
})
}
return props.subscription !== null ?
t('task.subscription.subscribed', {entity: props.entity}) :
t('task.subscription.notSubscribed', {entity: props.entity})
})
const buttonText = computed(() => props.subscription !== null ? t('task.subscription.unsubscribe') : t('task.subscription.subscribe'))
const icon = computed(() => props.subscription !== null ? ['far', 'bell-slash'] : 'bell')
const disabled = computed(() => {
if (props.subscription === null) {
return false
}
return props.subscription.entity !== props.entity
})
function changeSubscription() {
if (disabled.value) {
return
}
if (props.subscription === null) {
subscribe()
} else {
unsubscribe()
}
}
async function subscribe() {
const subscription = new SubscriptionModel({
entity: props.entity,
entityId: props.entityId,
})
await subscriptionService.value.create(subscription)
emit('change', subscription)
success({message: t('task.subscription.subscribeSuccess', {entity: props.entity})})
}
async function unsubscribe() {
const subscription = new SubscriptionModel({
entity: props.entity,
entityId: props.entityId,
})
await subscriptionService.value.delete(subscription)
emit('change', null)
success({message: t('task.subscription.unsubscribeSuccess', {entity: props.entity})})
} }
</script> </script>

View File

@ -11,31 +11,28 @@
</div> </div>
</template> </template>
<script> <script lang="ts" setup>
export default { defineProps({
name: 'user', user: {
props: { required: true,
user: { type: Object,
required: true,
type: Object,
},
showUsername: {
required: false,
type: Boolean,
default: true,
},
avatarSize: {
required: false,
type: Number,
default: 50,
},
isInline: {
required: false,
type: Boolean,
default: false,
},
}, },
} showUsername: {
required: false,
type: Boolean,
default: true,
},
avatarSize: {
required: false,
type: Number,
default: 50,
},
isInline: {
required: false,
type: Boolean,
default: false,
},
})
</script> </script>
<style lang="scss" scoped> <style lang="scss" scoped>

View File

@ -9,32 +9,23 @@
/> />
</template> </template>
<script> <script lang="ts" setup>
import {ref, computed} from 'vue'
import {useStore} from 'vuex'
import Multiselect from '@/components/input/multiselect.vue' import Multiselect from '@/components/input/multiselect.vue'
export default { const emit = defineEmits(['selected'])
name: 'namespace-search',
emits: ['selected'], const query = ref('')
data() {
return { const store = useStore()
query: '', const namespaces = computed(() => store.getters['namespaces/searchNamespace'](query.value))
}
}, function findNamespaces(newQuery: string) {
components: { query.value = newQuery
Multiselect, }
},
computed: { function select(namespace) {
namespaces() { emit('selected', namespace)
return this.$store.getters['namespaces/searchNamespace'](this.query)
},
},
methods: {
findNamespaces(query) {
this.query = query
},
select(namespace) {
this.$emit('selected', namespace)
},
},
} }
</script> </script>

View File

@ -52,30 +52,22 @@
</dropdown> </dropdown>
</template> </template>
<script> <script setup lang="ts">
import {ref, onMounted} from 'vue'
import Dropdown from '@/components/misc/dropdown.vue' import Dropdown from '@/components/misc/dropdown.vue'
import DropdownItem from '@/components/misc/dropdown-item.vue' import DropdownItem from '@/components/misc/dropdown-item.vue'
import TaskSubscription from '@/components/misc/subscription.vue' import TaskSubscription from '@/components/misc/subscription.vue'
export default { const props = defineProps({
name: 'namespace-settings-dropdown', namespace: {
data() { type: Object, // NamespaceModel
return { required: true,
subscription: null,
}
}, },
components: { })
DropdownItem,
Dropdown, const subscription = ref(null)
TaskSubscription, onMounted(() => {
}, subscription.value = props.namespace.subscription
props: { })
namespace: {
required: true,
},
},
mounted() {
this.subscription = this.namespace.subscription
},
}
</script> </script>