feat: convert model methods to named functions

This commit is contained in:
Dominik Pschenitschni 2022-09-23 16:51:35 +02:00
parent 176ad565cc
commit 8e3f54ae42
No known key found for this signature in database
GPG key ID: B257AC0149F43A77
16 changed files with 75 additions and 51 deletions

View file

@ -2,34 +2,39 @@
<div :class="{'is-inline': isInline}" class="user"> <div :class="{'is-inline': isInline}" class="user">
<img <img
:height="avatarSize" :height="avatarSize"
:src="user.getAvatarUrl(avatarSize)" :src="getAvatarUrl(user, avatarSize)"
:width="avatarSize" :width="avatarSize"
alt="" alt=""
class="avatar" class="avatar"
v-tooltip="user.getDisplayName()"/> v-tooltip="getDisplayName(user)"/>
<span class="username" v-if="showUsername">{{ user.getDisplayName() }}</span> <span class="username" v-if="showUsername">{{ getDisplayName(user) }}</span>
</div> </div>
</template> </template>
<script lang="ts" setup> <script lang="ts" setup>
import type {PropType} from 'vue'
import {getAvatarUrl, getDisplayName} from '@/models/user'
import type {IUser} from '@/modelTypes/IUser'
defineProps({ defineProps({
user: { user: {
type: Object as PropType<IUser>,
required: true, required: true,
type: Object,
}, },
showUsername: { showUsername: {
required: false,
type: Boolean, type: Boolean,
required: false,
default: true, default: true,
}, },
avatarSize: { avatarSize: {
required: false,
type: Number, type: Number,
required: false,
default: 50, default: 50,
}, },
isInline: { isInline: {
required: false,
type: Boolean, type: Boolean,
required: false,
default: false, default: false,
}, },
}) })

View file

@ -24,7 +24,7 @@
<div class="detail"> <div class="detail">
<div> <div>
<span class="has-text-weight-bold mr-1" v-if="n.notification.doer"> <span class="has-text-weight-bold mr-1" v-if="n.notification.doer">
{{ n.notification.doer.getDisplayName() }} {{ getDisplayName(n.notification.doer) }}
</span> </span>
<BaseButton @click="() => to(n, index)()"> <BaseButton @click="() => to(n, index)()">
{{ n.toText(userInfo) }} {{ n.toText(userInfo) }}
@ -56,6 +56,7 @@ import User from '@/components/misc/user.vue'
import { NOTIFICATION_NAMES as names, type INotification} from '@/modelTypes/INotification' import { NOTIFICATION_NAMES as names, type INotification} from '@/modelTypes/INotification'
import {closeWhenClickedOutside} from '@/helpers/closeWhenClickedOutside' import {closeWhenClickedOutside} from '@/helpers/closeWhenClickedOutside'
import {formatDateLong, formatDateSince} from '@/helpers/time/formatDate' import {formatDateLong, formatDateSince} from '@/helpers/time/formatDate'
import {getDisplayName} from '@/models/user'
import {useAuthStore} from '@/stores/auth' import {useAuthStore} from '@/stores/auth'
const LOAD_NOTIFICATIONS_INTERVAL = 10000 const LOAD_NOTIFICATIONS_INTERVAL = 10000

View file

@ -93,7 +93,7 @@
<p class="mb-2"> <p class="mb-2">
<i18n-t keypath="list.share.links.sharedBy" scope="global"> <i18n-t keypath="list.share.links.sharedBy" scope="global">
<strong>{{ s.sharedBy.getDisplayName() }}</strong> <strong>{{ getDisplayName(s.sharedBy) }}</strong>
</i18n-t> </i18n-t>
</p> </p>
@ -201,6 +201,7 @@ import LinkShareService from '@/services/linkShare'
import {useCopyToClipboard} from '@/composables/useCopyToClipboard' import {useCopyToClipboard} from '@/composables/useCopyToClipboard'
import {success} from '@/message' import {success} from '@/message'
import {getDisplayName} from '@/models/user'
import type {ListView} from '@/types/ListView' import type {ListView} from '@/types/ListView'
import {LIST_VIEWS} from '@/types/ListView' import {LIST_VIEWS} from '@/types/ListView'
import {useConfigStore} from '@/stores/config' import {useConfigStore} from '@/stores/config'

View file

@ -28,7 +28,7 @@
<tbody> <tbody>
<tr :key="s.id" v-for="s in sharables"> <tr :key="s.id" v-for="s in sharables">
<template v-if="shareType === 'user'"> <template v-if="shareType === 'user'">
<td>{{ s.getDisplayName() }}</td> <td>{{ getDisplayName(s) }}</td>
<td> <td>
<template v-if="s.id === userInfo.id"> <template v-if="s.id === userInfo.id">
<b class="is-success">{{ $t('list.share.userTeam.you') }}</b> <b class="is-success">{{ $t('list.share.userTeam.you') }}</b>
@ -150,7 +150,7 @@ import UserListModel from '@/models/userList'
import type {IUserList} from '@/modelTypes/IUserList' import type {IUserList} from '@/modelTypes/IUserList'
import UserService from '@/services/user' import UserService from '@/services/user'
import UserModel from '@/models/user' import UserModel, { getDisplayName } from '@/models/user'
import type {IUser} from '@/modelTypes/IUser' import type {IUser} from '@/modelTypes/IUser'
import TeamNamespaceService from '@/services/teamNamespace' import TeamNamespaceService from '@/services/teamNamespace'

View file

@ -17,7 +17,7 @@
<div :key="c.id" class="media comment" v-for="c in comments"> <div :key="c.id" class="media comment" v-for="c in comments">
<figure class="media-left is-hidden-mobile"> <figure class="media-left is-hidden-mobile">
<img <img
:src="c.author.getAvatarUrl(48)" :src="getAvatarUrl(c.author, 48)"
alt="" alt=""
class="image is-avatar" class="image is-avatar"
height="48" height="48"
@ -27,13 +27,13 @@
<div class="media-content"> <div class="media-content">
<div class="comment-info"> <div class="comment-info">
<img <img
:src="c.author.getAvatarUrl(20)" :src="getAvatarUrl(c.author, 20)"
alt="" alt=""
class="image is-avatar d-print-none" class="image is-avatar d-print-none"
height="20" height="20"
width="20" width="20"
/> />
<strong>{{ c.author.getDisplayName() }}</strong>&nbsp; <strong>{{ getDisplayName(c.author) }}</strong>&nbsp;
<span v-tooltip="formatDateLong(c.created)" class="has-text-grey"> <span v-tooltip="formatDateLong(c.created)" class="has-text-grey">
{{ formatDateSince(c.created) }} {{ formatDateSince(c.created) }}
</span> </span>
@ -166,6 +166,7 @@ import type {ITask} from '@/modelTypes/ITask'
import {uploadFile} from '@/helpers/attachments' import {uploadFile} from '@/helpers/attachments'
import {success} from '@/message' import {success} from '@/message'
import {formatDateLong, formatDateSince} from '@/helpers/time/formatDate' import {formatDateLong, formatDateSince} from '@/helpers/time/formatDate'
import {getAvatarUrl, getDisplayName} from '@/models/user'
import {useConfigStore} from '@/stores/config' import {useConfigStore} from '@/stores/config'
import {useAuthStore} from '@/stores/auth' import {useAuthStore} from '@/stores/auth'
@ -196,7 +197,7 @@ const newComment = reactive(new TaskCommentModel())
const saved = ref<ITask['id'] | null>(null) const saved = ref<ITask['id'] | null>(null)
const saving = ref<ITask['id'] | null>(null) const saving = ref<ITask['id'] | null>(null)
const userAvatar = computed(() => authStore.info.getAvatarUrl(48)) const userAvatar = computed(() => getAvatarUrl(authStore.info, 48))
const currentUserId = computed(() => authStore.info.id) const currentUserId = computed(() => authStore.info.id)
const enabled = computed(() => configStore.taskCommentsEnabled) const enabled = computed(() => configStore.taskCommentsEnabled)
const actions = computed(() => { const actions = computed(() => {

View file

@ -3,7 +3,7 @@
<time :datetime="formatISO(task.created)" v-tooltip="formatDateLong(task.created)"> <time :datetime="formatISO(task.created)" v-tooltip="formatDateLong(task.created)">
<i18n-t keypath="task.detail.created" scope="global"> <i18n-t keypath="task.detail.created" scope="global">
<span>{{ formatDateSince(task.created) }}</span> <span>{{ formatDateSince(task.created) }}</span>
{{ task.createdBy.getDisplayName() }} {{ getDisplayName(task.createdBy) }}
</i18n-t> </i18n-t>
</time> </time>
<template v-if="+new Date(task.created) !== +new Date(task.updated)"> <template v-if="+new Date(task.created) !== +new Date(task.updated)">
@ -30,6 +30,7 @@
import {computed, toRefs, type PropType} from 'vue' import {computed, toRefs, type PropType} from 'vue'
import type {ITask} from '@/modelTypes/ITask' import type {ITask} from '@/modelTypes/ITask'
import {formatISO, formatDateLong, formatDateSince} from '@/helpers/time/formatDate' import {formatISO, formatDateLong, formatDateSince} from '@/helpers/time/formatDate'
import {getDisplayName} from '@/models/user'
const props = defineProps({ const props = defineProps({
task: { task: {

View file

@ -38,7 +38,7 @@
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import {ref, shallowReactive, computed, watch, onMounted, onBeforeUnmount, type PropType} from 'vue' import {ref, shallowReactive, computed, watch, onMounted, onBeforeUnmount, toRef, type PropType} from 'vue'
import {useI18n} from 'vue-i18n' import {useI18n} from 'vue-i18n'
import flatPickr from 'vue-flatpickr-component' import flatPickr from 'vue-flatpickr-component'
@ -63,12 +63,12 @@ const task = ref<ITask>()
// We're saving the due date seperately to prevent null errors in very short periods where the task is null. // We're saving the due date seperately to prevent null errors in very short periods where the task is null.
const dueDate = ref<Date>() const dueDate = ref<Date>()
const lastValue = ref<Date>() const lastValue = ref<Date>()
const changeInterval = ref<number>() const changeInterval = ref<ReturnType<typeof setInterval>>()
watch( watch(
() => props.modelValue, toRef(props, 'modelValue'),
(value) => { (value) => {
task.value = value task.value = { ...value }
dueDate.value = value.dueDate dueDate.value = value.dueDate
lastValue.value = value.dueDate lastValue.value = value.dueDate
}, },
@ -123,7 +123,6 @@ async function updateDueDate() {
return return
} }
// FIXME: direct prop manipulation
const newTask = await taskService.update({ const newTask = await taskService.update({
...task.value, ...task.value,
dueDate: new Date(dueDate.value), dueDate: new Date(dueDate.value),

View file

@ -7,13 +7,15 @@ export const AUTH_TYPES = {
'LINK_SHARE': 2, 'LINK_SHARE': 2,
} as const } as const
type AuthType = typeof AUTH_TYPES[keyof typeof AUTH_TYPES]
export interface IUser extends IAbstract { export interface IUser extends IAbstract {
id: number id: number
email: string email: string
username: string username: string
name: string name: string
exp: number exp: number
type: typeof AUTH_TYPES[keyof typeof AUTH_TYPES], type: AuthType
created: Date created: Date
updated: Date updated: Date

View file

@ -1,12 +1,13 @@
import AbstractModel from './abstractModel' import AbstractModel from './abstractModel'
import {parseDateOrNull} from '@/helpers/parseDateOrNull' import {parseDateOrNull} from '@/helpers/parseDateOrNull'
import UserModel from '@/models/user' import UserModel, {getDisplayName} from '@/models/user'
import TaskModel from '@/models/task' import TaskModel from '@/models/task'
import TaskCommentModel from '@/models/taskComment' import TaskCommentModel from '@/models/taskComment'
import ListModel from '@/models/list' import ListModel from '@/models/list'
import TeamModel from '@/models/team' import TeamModel from '@/models/team'
import {NOTIFICATION_NAMES, type INotification} from '@/modelTypes/INotification' import {NOTIFICATION_NAMES, type INotification} from '@/modelTypes/INotification'
import type { IUser } from '@/modelTypes/IUser'
export default class NotificationModel extends AbstractModel<INotification> implements INotification { export default class NotificationModel extends AbstractModel<INotification> implements INotification {
id = 0 id = 0
@ -61,14 +62,14 @@ export default class NotificationModel extends AbstractModel<INotification> impl
this.readAt = parseDateOrNull(this.readAt) this.readAt = parseDateOrNull(this.readAt)
} }
toText(user = null) { toText(user: IUser | null = null) {
let who = '' let who = ''
switch (this.name) { switch (this.name) {
case NOTIFICATION_NAMES.TASK_COMMENT: case NOTIFICATION_NAMES.TASK_COMMENT:
return `commented on ${this.notification.task.getTextIdentifier()}` return `commented on ${this.notification.task.getTextIdentifier()}`
case NOTIFICATION_NAMES.TASK_ASSIGNED: case NOTIFICATION_NAMES.TASK_ASSIGNED:
who = `${this.notification.assignee.getDisplayName()}` who = `${getDisplayName(this.notification.assignee)}`
if (user !== null && user.id === this.notification.assignee.id) { if (user !== null && user.id === this.notification.assignee.id) {
who = 'you' who = 'you'
@ -80,7 +81,7 @@ export default class NotificationModel extends AbstractModel<INotification> impl
case NOTIFICATION_NAMES.LIST_CREATED: case NOTIFICATION_NAMES.LIST_CREATED:
return `created ${this.notification.list.title}` return `created ${this.notification.list.title}`
case NOTIFICATION_NAMES.TEAM_MEMBER_ADDED: case NOTIFICATION_NAMES.TEAM_MEMBER_ADDED:
who = `${this.notification.member.getDisplayName()}` who = `${getDisplayName(this.notification.member)}`
if (user !== null && user.id === this.notification.member.id) { if (user !== null && user.id === this.notification.member.id) {
who = 'you' who = 'you'

View file

@ -4,6 +4,18 @@ import UserSettingsModel from '@/models/userSettings'
import { AUTH_TYPES, type IUser } from '@/modelTypes/IUser' import { AUTH_TYPES, type IUser } from '@/modelTypes/IUser'
import type { IUserSettings } from '@/modelTypes/IUserSettings' import type { IUserSettings } from '@/modelTypes/IUserSettings'
export function getAvatarUrl(user: IUser, size = 50) {
return `${window.API_URL}/avatar/${user.username}?size=${size}`
}
export function getDisplayName(user: IUser) {
if (user.name !== '') {
return user.name
}
return user.username
}
export default class UserModel extends AbstractModel<IUser> implements IUser { export default class UserModel extends AbstractModel<IUser> implements IUser {
id = 0 id = 0
email = '' email = ''
@ -25,16 +37,4 @@ export default class UserModel extends AbstractModel<IUser> implements IUser {
this.settings = new UserSettingsModel(this.settings || {}) this.settings = new UserSettingsModel(this.settings || {})
} }
getAvatarUrl(size = 50) {
return `${window.API_URL}/avatar/${this.username}?size=${size}`
}
getDisplayName() {
if (this.name !== '') {
return this.name
}
return this.username
}
} }

View file

@ -32,6 +32,7 @@ export interface AuthState {
lastUserInfoRefresh: Date | null, lastUserInfoRefresh: Date | null,
settings: IUserSettings, settings: IUserSettings,
isLoading: boolean, isLoading: boolean,
isLoadingGeneralSettings: boolean
} }
export interface ConfigState { export interface ConfigState {

View file

@ -3,7 +3,7 @@ import {defineStore, acceptHMRUpdate} from 'pinia'
import {HTTPFactory, AuthenticatedHTTPFactory} from '@/http-common' import {HTTPFactory, AuthenticatedHTTPFactory} from '@/http-common'
import {i18n, getCurrentLanguage, saveLanguage} from '@/i18n' import {i18n, getCurrentLanguage, saveLanguage} from '@/i18n'
import {objectToSnakeCase} from '@/helpers/case' import {objectToSnakeCase} from '@/helpers/case'
import UserModel from '@/models/user' import UserModel, { getAvatarUrl } from '@/models/user'
import UserSettingsService from '@/services/userSettings' import UserSettingsService from '@/services/userSettings'
import {getToken, refreshToken, removeToken, saveToken} from '@/helpers/auth' import {getToken, refreshToken, removeToken, saveToken} from '@/helpers/auth'
import {setLoadingPinia} from '@/store/helper' import {setLoadingPinia} from '@/store/helper'
@ -15,6 +15,7 @@ import type {IUserSettings} from '@/modelTypes/IUserSettings'
import router from '@/router' import router from '@/router'
import {useConfigStore} from '@/stores/config' import {useConfigStore} from '@/stores/config'
import UserSettingsModel from '@/models/userSettings' import UserSettingsModel from '@/models/userSettings'
import {store} from '@/store'
export const useAuthStore = defineStore('auth', { export const useAuthStore = defineStore('auth', {
state: () : AuthState => ({ state: () : AuthState => ({
@ -28,6 +29,7 @@ export const useAuthStore = defineStore('auth', {
lastUserInfoRefresh: null, lastUserInfoRefresh: null,
isLoading: false, isLoading: false,
isLoadingGeneralSettings: false,
}), }),
getters: { getters: {
authUser(state) { authUser(state) {
@ -48,6 +50,10 @@ export const useAuthStore = defineStore('auth', {
this.isLoading = isLoading this.isLoading = isLoading
}, },
setIsLoadingGeneralSettings(isLoading: boolean) {
this.isLoadingGeneralSettings = isLoading
},
setUser(info: IUser | null) { setUser(info: IUser | null) {
this.info = info this.info = info
if (info !== null) { if (info !== null) {
@ -78,7 +84,7 @@ export const useAuthStore = defineStore('auth', {
}, },
reloadAvatar() { reloadAvatar() {
if (!this.info) return if (!this.info) return
this.avatarUrl = `${this.info.getAvatarUrl()}&=${+new Date()}` this.avatarUrl = `${getAvatarUrl(this.info)}&=${+new Date()}`
}, },
updateLastUserRefresh() { updateLastUserRefresh() {
this.lastUserInfoRefresh = new Date() this.lastUserInfoRefresh = new Date()
@ -87,6 +93,7 @@ export const useAuthStore = defineStore('auth', {
// Logs a user in with a set of credentials. // Logs a user in with a set of credentials.
async login(credentials) { async login(credentials) {
const HTTP = HTTPFactory() const HTTP = HTTPFactory()
store.commit('loading', true)
this.setIsLoading(true) this.setIsLoading(true)
// Delete an eventually preexisting old token // Delete an eventually preexisting old token
@ -110,6 +117,7 @@ export const useAuthStore = defineStore('auth', {
throw e throw e
} finally { } finally {
store.commit('loading', false)
this.setIsLoading(false) this.setIsLoading(false)
} }
}, },
@ -118,6 +126,7 @@ export const useAuthStore = defineStore('auth', {
// Not sure if this is the right place to put the logic in, maybe a seperate js component would be better suited. // Not sure if this is the right place to put the logic in, maybe a seperate js component would be better suited.
async register(credentials) { async register(credentials) {
const HTTP = HTTPFactory() const HTTP = HTTPFactory()
store.commit('loading', true)
this.setIsLoading(true) this.setIsLoading(true)
try { try {
await HTTP.post('register', credentials) await HTTP.post('register', credentials)
@ -129,12 +138,14 @@ export const useAuthStore = defineStore('auth', {
throw e throw e
} finally { } finally {
store.commit('loading', false)
this.setIsLoading(false) this.setIsLoading(false)
} }
}, },
async openIdAuth({provider, code}) { async openIdAuth({provider, code}) {
const HTTP = HTTPFactory() const HTTP = HTTPFactory()
store.commit('loading', true)
this.setIsLoading(true) this.setIsLoading(true)
const data = { const data = {
@ -151,6 +162,7 @@ export const useAuthStore = defineStore('auth', {
// Tell others the user is autheticated // Tell others the user is autheticated
this.checkAuth() this.checkAuth()
} finally { } finally {
store.commit('loading', false)
this.setIsLoading(false) this.setIsLoading(false)
} }
}, },
@ -266,11 +278,10 @@ export const useAuthStore = defineStore('auth', {
settings: IUserSettings settings: IUserSettings
showMessage : boolean showMessage : boolean
}) { }) {
// const showMessage = payload.showMessage ?? true
const userSettingsService = new UserSettingsService() const userSettingsService = new UserSettingsService()
// FIXME // FIXME
const cancel = setLoadingPinia(useAuthStore, 'general-settings') const cancel = setLoadingPinia(this, this.setIsLoadingGeneralSettings)
try { try {
saveLanguage(settings.language) saveLanguage(settings.language)
await userSettingsService.update(settings) await userSettingsService.update(settings)

View file

@ -80,7 +80,7 @@ export const useLabelStore = defineStore('label', {
return return
} }
const cancel = setLoadingPinia(useLabelStore, this.setIsLoading) const cancel = setLoadingPinia(this)
try { try {
const labels = await getAllLabels() const labels = await getAllLabels()
@ -92,7 +92,7 @@ export const useLabelStore = defineStore('label', {
}, },
async deleteLabel(label: ILabel) { async deleteLabel(label: ILabel) {
const cancel = setLoadingPinia(useLabelStore) const cancel = setLoadingPinia(this)
const labelService = new LabelService() const labelService = new LabelService()
try { try {
@ -106,7 +106,7 @@ export const useLabelStore = defineStore('label', {
}, },
async updateLabel(label: ILabel) { async updateLabel(label: ILabel) {
const cancel = setLoadingPinia(useLabelStore) const cancel = setLoadingPinia(this)
const labelService = new LabelService() const labelService = new LabelService()
try { try {
@ -120,7 +120,7 @@ export const useLabelStore = defineStore('label', {
}, },
async createLabel(label: ILabel) { async createLabel(label: ILabel) {
const cancel = setLoadingPinia(useLabelStore) const cancel = setLoadingPinia(this)
const labelService = new LabelService() const labelService = new LabelService()
try { try {

View file

@ -87,7 +87,7 @@ export const useListStore = defineStore('list', {
}, },
async createList(list: IList) { async createList(list: IList) {
const cancel = setLoadingPinia(useListStore) const cancel = setLoadingPinia(this)
const listService = new ListService() const listService = new ListService()
try { try {
@ -103,7 +103,7 @@ export const useListStore = defineStore('list', {
}, },
async updateList(list: IList) { async updateList(list: IList) {
const cancel = setLoadingPinia(useListStore) const cancel = setLoadingPinia(this)
const listService = new ListService() const listService = new ListService()
try { try {
@ -139,7 +139,7 @@ export const useListStore = defineStore('list', {
}, },
async deleteList(list: IList) { async deleteList(list: IList) {
const cancel = setLoadingPinia(useListStore) const cancel = setLoadingPinia(this)
const listService = new ListService() const listService = new ListService()
try { try {

View file

@ -85,7 +85,7 @@
<table class="table has-actions is-striped is-hoverable is-fullwidth"> <table class="table has-actions is-striped is-hoverable is-fullwidth">
<tbody> <tbody>
<tr :key="m.id" v-for="m in team?.members"> <tr :key="m.id" v-for="m in team?.members">
<td>{{ m.getDisplayName() }}</td> <td>{{ getDisplayName(m) }}</td>
<td> <td>
<template v-if="m.id === userInfo.id"> <template v-if="m.id === userInfo.id">
<b class="is-success">You</b> <b class="is-success">You</b>
@ -176,6 +176,7 @@ import {RIGHTS as Rights} from '@/constants/rights'
import {useTitle} from '@/composables/useTitle' import {useTitle} from '@/composables/useTitle'
import {success} from '@/message' import {success} from '@/message'
import {getDisplayName} from '@/models/user'
import {useAuthStore} from '@/stores/auth' import {useAuthStore} from '@/stores/auth'
import type {ITeam} from '@/modelTypes/ITeam' import type {ITeam} from '@/modelTypes/ITeam'