feat: improve store typing

This commit is contained in:
Dominik Pschenitschni 2022-07-21 18:45:58 +02:00
parent fcdb704152
commit d6b0f26bd1
Signed by: dpschen
GPG Key ID: B257AC0149F43A77
60 changed files with 239 additions and 192 deletions

View File

@ -18,7 +18,7 @@
import {computed, watch, type Ref} from 'vue'
import {useRouter} from 'vue-router'
import {useRouteQuery} from '@vueuse/router'
import {useStore} from 'vuex'
import {useStore} from '@/store'
import {useI18n} from 'vue-i18n'
import isTouchDevice from 'is-touch-device'
import {success} from '@/message'

View File

@ -71,7 +71,7 @@
<script lang="ts" setup>
import {computed, ref, watch} from 'vue'
import {useStore} from 'vuex'
import {useStore} from '@/store'
import {useI18n} from 'vue-i18n'
import flatPickr from 'vue-flatpickr-component'

View File

@ -11,7 +11,7 @@
<script setup lang="ts">
import {computed} from 'vue'
import {useStore} from 'vuex'
import {useStore} from '@/store'
import BaseButton from '@/components/base/BaseButton.vue'

View File

@ -88,7 +88,7 @@
<script setup lang="ts">
import {ref, computed, onMounted, nextTick} from 'vue'
import {useStore} from 'vuex'
import {useStore} from '@/store'
import {useRouter} from 'vue-router'
import {QUICK_ACTIONS_ACTIVE} from '@/store/mutation-types'

View File

@ -61,7 +61,7 @@
<script lang="ts" setup>
import {watch, computed, shallowRef, watchEffect, type VNode, h} from 'vue'
import {useStore} from 'vuex'
import {useStore} from '@/store'
import {useRoute, useRouter} from 'vue-router'
import {useEventListener} from '@vueuse/core'

View File

@ -23,7 +23,7 @@
<script lang="ts" setup>
import {computed} from 'vue'
import {useStore} from 'vuex'
import {useStore} from '@/store'
import Logo from '@/components/home/Logo.vue'
import PoweredByLink from './PoweredByLink.vue'

View File

@ -141,7 +141,7 @@
<script setup lang="ts">
import {ref, computed, onMounted, onBeforeMount} from 'vue'
import {useStore} from 'vuex'
import {useStore} from '@/store'
import draggable from 'zhyswan-vuedraggable'
import type {SortableEvent} from 'sortablejs'

View File

@ -77,7 +77,7 @@
<script setup lang="ts">
import {ref, computed, watchEffect, type PropType} from 'vue'
import {useStore} from 'vuex'
import {useStore} from '@/store'
import {getSavedFilterIdFromListId} from '@/helpers/savedFilter'
import Dropdown from '@/components/misc/dropdown.vue'

View File

@ -37,7 +37,7 @@
<script lang="ts" setup>
import {type PropType, ref, watch} from 'vue'
import {useStore} from 'vuex'
import {useStore} from '@/store'
import ListService from '@/services/list'
import {getBlobFromBlurHash} from '@/helpers/getBlobFromBlurHash'

View File

@ -33,7 +33,7 @@
</template>
<script lang="ts" setup>
import {useStore} from 'vuex'
import {useStore} from '@/store'
import Shortcut from '@/components/misc/shortcut.vue'
import Message from '@/components/misc/message.vue'

View File

@ -8,7 +8,7 @@
<script lang="ts" setup>
import {computed} from 'vue'
import {useStore} from 'vuex'
import {useStore} from '@/store'
import BaseButton from '@/components/base/BaseButton.vue'

View File

@ -30,7 +30,7 @@ import Logo from '@/components/home/Logo.vue'
import Message from '@/components/misc/message.vue'
import Legal from '@/components/misc/legal.vue'
import ApiConfig from '@/components/misc/api-config.vue'
import {useStore} from 'vuex'
import {useStore} from '@/store'
import {computed} from 'vue'
import {useRoute} from 'vue-router'
import {useI18n} from 'vue-i18n'

View File

@ -42,7 +42,7 @@
<script lang="ts" setup>
import {ref, computed} from 'vue'
import {useStore} from 'vuex'
import {useStore} from '@/store'
import Logo from '@/assets/logo.svg?component'
import ApiConfig from '@/components/misc/api-config.vue'

View File

@ -54,7 +54,7 @@ import BaseButton from '@/components/base/BaseButton.vue'
import User from '@/components/misc/user.vue'
import { NOTIFICATION_NAMES as names, type INotification} from '@/models/notification'
import {closeWhenClickedOutside} from '@/helpers/closeWhenClickedOutside'
import {useStore} from 'vuex'
import {useStore} from '@/store'
import {useRouter} from 'vue-router'
import {formatDateLong, formatDateSince} from '@/helpers/time/formatDate'

View File

@ -177,7 +177,7 @@
<script setup lang="ts">
import {ref, watch, computed, shallowReactive} from 'vue'
import {useStore} from 'vuex'
import {useStore} from '@/store'
import {useI18n} from 'vue-i18n'
import {RIGHTS} from '@/models/constants/rights'

View File

@ -139,7 +139,7 @@ export default {name: 'userTeamShare'}
<script setup lang="ts">
import {ref, reactive, computed, shallowReactive, type ShallowReactive, type Ref} from 'vue'
import type {PropType} from 'vue'
import {useStore} from 'vuex'
import {useStore} from '@/store'
import {useI18n} from 'vue-i18n'
import UserNamespaceService from '@/services/userNamespace'

View File

@ -43,7 +43,7 @@
<script setup lang="ts">
import {ref, watch, unref, shallowReactive} from 'vue'
import {useI18n} from 'vue-i18n'
import {useStore} from 'vuex'
import {useStore} from '@/store'
import {tryOnMounted, debouncedWatch, useWindowSize, type MaybeRef} from '@vueuse/core'
import TaskService from '@/services/task'

View File

@ -153,7 +153,7 @@
<script setup lang="ts">
import {ref, reactive, computed, shallowReactive, watch, nextTick} from 'vue'
import {useStore} from 'vuex'
import {useStore} from '@/store'
import {useI18n} from 'vue-i18n'
import Editor from '@/components/input/AsyncEditor'

View File

@ -39,7 +39,7 @@
<script setup lang="ts">
import {ref, shallowReactive, computed, watch, onMounted, onBeforeUnmount, type PropType} from 'vue'
import {useStore} from 'vuex'
import {useStore} from '@/store'
import {useI18n} from 'vue-i18n'
import flatPickr from 'vue-flatpickr-component'

View File

@ -31,7 +31,7 @@
<script setup lang="ts">
import {ref,computed, watch, type PropType} from 'vue'
import {useStore} from 'vuex'
import {useStore} from '@/store'
import Editor from '@/components/input/AsyncEditor'

View File

@ -29,7 +29,7 @@
<script setup lang="ts">
import {ref, shallowReactive, watch, type PropType} from 'vue'
import {useStore} from 'vuex'
import {useStore} from '@/store'
import {useI18n} from 'vue-i18n'
import User from '@/components/misc/user.vue'

View File

@ -40,7 +40,7 @@
<script setup lang="ts">
import {type PropType, ref, computed, shallowReactive, watch} from 'vue'
import {useStore} from 'vuex'
import {useStore} from '@/store'
import {useI18n} from 'vue-i18n'
import LabelModel, { type ILabel } from '@/models/label'

View File

@ -33,7 +33,7 @@
<script setup lang="ts">
import {ref, computed, type PropType} from 'vue'
import {useStore} from 'vuex'
import {useStore} from '@/store'
import BaseButton from '@/components/base/BaseButton.vue'
import Done from '@/components/misc/Done.vue'

View File

@ -19,7 +19,7 @@
<script lang="ts" setup>
import {reactive, ref, watch} from 'vue'
import type {PropType} from 'vue'
import {useStore} from 'vuex'
import {useStore} from '@/store'
import {useI18n} from 'vue-i18n'
import ListModel, { type IList } from '@/models/list'
import Multiselect from '@/components/input/multiselect.vue'

View File

@ -1,5 +1,5 @@
import {ref, computed} from 'vue'
import {useStore} from 'vuex'
import {useStore} from '@/store'
export function useNameSpaceSearch() {
const query = ref('')

View File

@ -14,7 +14,7 @@ import Notifications from '@kyvg/vue3-notification'
import './registerServiceWorker'
// Vuex
import {store} from './store'
import { store, key } from './store'
// i18n
import {i18n} from './i18n'
@ -104,7 +104,7 @@ if (window.SENTRY_ENABLED) {
import('./sentry').then(sentry => sentry.default(app, router))
}
app.use(store)
app.use(store, key) // pass the injection key
app.use(router)
app.use(i18n)

View File

@ -1,4 +1,7 @@
import {createStore} from 'vuex'
import type {InjectionKey} from 'vue'
import {createStore, useStore as baseUseStore, Store} from 'vuex'
import {getBlobFromBlurHash} from '../helpers/getBlobFromBlurHash'
import {
BACKGROUND,
@ -25,7 +28,14 @@ import ListModel from '@/models/list'
import ListService from '../services/list'
import {checkAndSetApiUrl} from '@/helpers/checkAndSetApiUrl'
import type { RootStoreState } from './types'
import type { RootStoreState, StoreState } from './types'
export const key: InjectionKey<Store<StoreState>> = Symbol()
// define your own `useStore` composition function
export function useStore () {
return baseUseStore(key)
}
export const store = createStore<RootStoreState>({
strict: import.meta.env.DEV,

View File

@ -1,26 +1,29 @@
import type { Module } from 'vuex'
import {findIndexById} from '@/helpers/utils'
import type { AttachmentState } from '@/store/types'
import type { AttachmentState, RootStoreState } from '@/store/types'
import type { IAttachment } from '@/models/attachment'
export default {
const store : Module<AttachmentState, RootStoreState> = {
namespaced: true,
state: (): AttachmentState => ({
state: () => ({
attachments: [],
}),
mutations: {
set(state: AttachmentState, attachments: IAttachment[]) {
set(state, attachments: IAttachment[]) {
console.debug('Set attachments', attachments)
state.attachments = attachments
},
add(state: AttachmentState, attachment: IAttachment) {
add(state, attachment: IAttachment) {
console.debug('Add attachement', attachment)
state.attachments.push(attachment)
},
removeById(state: AttachmentState, id: IAttachment['id']) {
removeById(state, id: IAttachment['id']) {
const attachmentIndex = findIndexById<IAttachment>(state.attachments, id)
state.attachments.splice(attachmentIndex, 1)
console.debug('Remove attachement', id)
},
},
}
}
export default store

View File

@ -1,4 +1,4 @@
import type { ActionContext } from 'vuex'
import type { Module } from 'vuex'
import {HTTPFactory, AuthenticatedHTTPFactory} from '@/http-common'
import {i18n, getCurrentLanguage, saveLanguage} from '@/i18n'
@ -22,25 +22,25 @@ const defaultSettings = settings => {
return settings
}
export default {
const authStore : Module<AuthState, RootStoreState> = {
namespaced: true,
state: (): AuthState => ({
state: () => ({
authenticated: false,
isLinkShareAuth: false,
info: null,
needsTotpPasscode: false,
avatarUrl: '',
lastUserInfoRefresh: null,
settings: {},
settings: {}, // should be IUserSettings
}),
getters: {
authUser(state: AuthState) {
authUser(state) {
return state.authenticated && (
state.info &&
state.info.type === AUTH_TYPES.USER
)
},
authLinkShare(state: AuthState) {
authLinkShare(state) {
return state.authenticated && (
state.info &&
state.info.type === AUTH_TYPES.LINK_SHARE
@ -48,7 +48,7 @@ export default {
},
},
mutations: {
info(state: AuthState, info: Info) {
info(state, info: Info) {
state.info = info
if (info !== null) {
state.avatarUrl = info.getAvatarUrl()
@ -60,32 +60,32 @@ export default {
state.isLinkShareAuth = info.id < 0
}
},
setUserSettings(state: AuthState, settings: IUserSettings) {
setUserSettings(state, settings: IUserSettings) {
state.settings = defaultSettings(settings)
const info = state.info !== null ? state.info : {} as Info
info.name = settings.name
state.info = info
},
authenticated(state: AuthState, authenticated: boolean) {
authenticated(state, authenticated: boolean) {
state.authenticated = authenticated
},
isLinkShareAuth(state: AuthState, isLinkShareAuth: boolean) {
isLinkShareAuth(state, isLinkShareAuth: boolean) {
state.isLinkShareAuth = isLinkShareAuth
},
needsTotpPasscode(state: AuthState, needsTotpPasscode: boolean) {
needsTotpPasscode(state, needsTotpPasscode: boolean) {
state.needsTotpPasscode = needsTotpPasscode
},
reloadAvatar(state: AuthState) {
reloadAvatar(state) {
if (!state.info) return
state.avatarUrl = `${state.info.getAvatarUrl()}&=${+new Date()}`
},
lastUserRefresh(state: AuthState) {
lastUserRefresh(state) {
state.lastUserInfoRefresh = new Date()
},
},
actions: {
// Logs a user in with a set of credentials.
async login(ctx: ActionContext<AuthState, RootStoreState>, credentials) {
async login(ctx, credentials) {
const HTTP = HTTPFactory()
ctx.commit(LOADING, true, {root: true})
@ -116,7 +116,7 @@ export default {
// Registers a new user and logs them in.
// Not sure if this is the right place to put the logic in, maybe a seperate js component would be better suited.
async register(ctx: ActionContext<AuthState, RootStoreState>, credentials) {
async register(ctx, credentials) {
const HTTP = HTTPFactory()
ctx.commit(LOADING, true, {root: true})
try {
@ -133,7 +133,7 @@ export default {
}
},
async openIdAuth(ctx: ActionContext<AuthState, RootStoreState>, {provider, code}) {
async openIdAuth(ctx, {provider, code}) {
const HTTP = HTTPFactory()
ctx.commit(LOADING, true, {root: true})
@ -155,7 +155,7 @@ export default {
}
},
async linkShareAuth(ctx: ActionContext<AuthState, RootStoreState>, {hash, password}) {
async linkShareAuth(ctx, {hash, password}) {
const HTTP = HTTPFactory()
const response = await HTTP.post('/shares/' + hash + '/auth', {
password: password,
@ -166,7 +166,7 @@ export default {
},
// Populates user information from jwt token saved in local storage in store
checkAuth(ctx: ActionContext<AuthState, RootStoreState>) {
checkAuth(ctx) {
// This function can be called from multiple places at the same time and shortly after one another.
// To prevent hitting the api too frequently or race conditions, we check at most once per minute.
@ -198,7 +198,7 @@ export default {
}
},
redirectToProviderIfNothingElseIsEnabled({rootState}: ActionContext<AuthState, RootStoreState>) {
redirectToProviderIfNothingElseIsEnabled({rootState}) {
const {auth} = rootState.config
if (
auth.local.enabled === false &&
@ -210,7 +210,7 @@ export default {
}
},
async refreshUserInfo({state, commit, dispatch}: ActionContext<AuthState, RootStoreState>) {
async refreshUserInfo({state, commit, dispatch}) {
const jwt = getToken()
if (!jwt) {
return
@ -244,7 +244,7 @@ export default {
}
},
async saveUserSettings(ctx: ActionContext<AuthState, RootStoreState>, payload) {
async saveUserSettings(ctx, payload) {
const {settings} = payload
const showMessage = payload.showMessage ?? true
const userSettingsService = new UserSettingsService()
@ -265,7 +265,7 @@ export default {
},
// Renews the api token and saves it to local storage
renewToken(ctx: ActionContext<AuthState, RootStoreState>) {
renewToken(ctx) {
// FIXME: Timeout to avoid race conditions when authenticated as a user (=auth token in localStorage) and as a
// link share in another tab. Without the timeout both the token renew and link share auth are executed at
// the same time and one might win over the other.
@ -286,9 +286,11 @@ export default {
}
}, 5000)
},
logout(ctx: ActionContext<AuthState, RootStoreState>) {
logout(ctx) {
removeToken()
ctx.dispatch('checkAuth')
},
},
}
}
export default authStore

View File

@ -1,4 +1,4 @@
import type { ActionContext } from 'vuex'
import type { Module } from 'vuex'
import {parseURL} from 'ufo'
import {CONFIG} from '../mutation-types'
@ -6,9 +6,9 @@ import {HTTPFactory} from '@/http-common'
import {objectToCamelCase} from '@/helpers/case'
import type { RootStoreState, ConfigState } from '@/store/types'
export default {
const configStore : Module<ConfigState, RootStoreState> = {
namespaced: true,
state: (): ConfigState => ({
state: () => ({
// These are the api defaults.
version: '',
frontendUrl: '',
@ -39,23 +39,25 @@ export default {
},
}),
getters: {
migratorsEnabled: (state: ConfigState) => state.availableMigrators?.length > 0,
migratorsEnabled: (state) => state.availableMigrators?.length > 0,
apiBase() {
const {host, protocol} = parseURL(window.API_URL)
return protocol + '//' + host
},
},
mutations: {
[CONFIG](state: ConfigState, config: ConfigState) {
[CONFIG](state, config: ConfigState) {
Object.assign(state, config)
},
},
actions: {
async update(ctx: ActionContext<ConfigState, RootStoreState>) {
async update(ctx) {
const HTTP = HTTPFactory()
const {data: config} = await HTTP.get('info')
ctx.commit(CONFIG, objectToCamelCase(config))
return config
},
},
}
}
export default configStore

View File

@ -1,3 +1,4 @@
import type { Module } from 'vuex'
import cloneDeep from 'lodash.clonedeep'
import {findById, findIndexById} from '@/helpers/utils'
@ -7,7 +8,6 @@ import {success} from '@/message'
import BucketService from '../../services/bucket'
import {setLoading} from '../helper'
import TaskCollectionService from '@/services/taskCollection'
import type { ActionContext } from 'vuex'
import type { RootStoreState, KanbanState } from '@/store/types'
import type { ITask } from '@/models/task'
import type { IList } from '@/models/list'
@ -38,10 +38,10 @@ const addTaskToBucketAndSort = (state: KanbanState, task: ITask) => {
* This store is intended to hold the currently active kanban view.
* It should hold only the current buckets.
*/
export default {
const kanbanStore : Module<KanbanState, RootStoreState> = {
namespaced: true,
state: (): KanbanState => ({
state: () => ({
buckets: [],
listId: 0,
bucketLoading: {},
@ -50,11 +50,11 @@ export default {
}),
mutations: {
setListId(state: KanbanState, listId: IList['id']) {
setListId(state, listId: IList['id']) {
state.listId = parseInt(listId)
},
setBuckets(state: KanbanState, buckets: IBucket[]) {
setBuckets(state, buckets: IBucket[]) {
state.buckets = buckets
buckets.forEach(b => {
state.taskPagesPerBucket[b.id] = 1
@ -62,21 +62,21 @@ export default {
})
},
addBucket(state: KanbanState, bucket: IBucket) {
addBucket(state, bucket: IBucket) {
state.buckets.push(bucket)
},
removeBucket(state: KanbanState, bucket: IBucket) {
removeBucket(state, bucket: IBucket) {
const bucketIndex = findIndexById(state.buckets, bucket.id)
state.buckets.splice(bucketIndex, 1)
},
setBucketById(state: KanbanState, bucket: IBucket) {
setBucketById(state, bucket: IBucket) {
const bucketIndex = findIndexById(state.buckets, bucket.id)
state.buckets[bucketIndex] = bucket
},
setBucketByIndex(state: KanbanState, {
setBucketByIndex(state, {
bucketIndex,
bucket,
} : {
@ -86,7 +86,7 @@ export default {
state.buckets[bucketIndex] = bucket
},
setTaskInBucketByIndex(state: KanbanState, {
setTaskInBucketByIndex(state, {
bucketIndex,
taskIndex,
task,
@ -100,7 +100,7 @@ export default {
state.buckets[bucketIndex] = bucket
},
setTasksInBucketByBucketId(state: KanbanState, {
setTasksInBucketByBucketId(state, {
bucketId,
tasks,
} : {
@ -114,7 +114,7 @@ export default {
}
},
setTaskInBucket(state: KanbanState, task: ITask) {
setTaskInBucket(state, task: ITask) {
// If this gets invoked without any tasks actually loaded, we can save the hassle of finding the task
if (state.buckets.length === 0) {
return
@ -158,7 +158,7 @@ export default {
}
},
addTaskToBucket(state: KanbanState, task: ITask) {
addTaskToBucket(state, task: ITask) {
const bucketIndex = findIndexById(state.buckets, task.bucketId)
const oldBucket = state.buckets[bucketIndex]
const newBucket = {
@ -171,7 +171,7 @@ export default {
state.buckets[bucketIndex] = newBucket
},
addTasksToBucket(state: KanbanState, {tasks, bucketId}: {
addTasksToBucket(state, {tasks, bucketId}: {
tasks: ITask[];
bucketId: IBucket['id'];
}) {
@ -187,7 +187,7 @@ export default {
state.buckets[bucketIndex] = newBucket
},
removeTaskInBucket(state: KanbanState, task: ITask) {
removeTaskInBucket(state, task: ITask) {
// If this gets invoked without any tasks actually loaded, we can save the hassle of finding the task
if (state.buckets.length === 0) {
return
@ -207,7 +207,7 @@ export default {
state.buckets[bucketIndex].tasks.splice(taskIndex, 1)
},
setBucketLoading(state: KanbanState, {bucketId, loading}) {
setBucketLoading(state, {bucketId, loading}) {
state.bucketLoading[bucketId] = loading
},
@ -221,11 +221,11 @@ export default {
},
getters: {
getBucketById(state: KanbanState) {
getBucketById(state) {
return (bucketId) => findById(state.buckets, bucketId)
},
getTaskById(state: KanbanState) {
getTaskById(state) {
return (id) => {
const { bucketIndex, taskIndex } = getTaskIndicesById(state, id)
@ -240,7 +240,7 @@ export default {
},
actions: {
async loadBucketsForList(ctx: ActionContext<KanbanState, RootStoreState>, {listId, params}) {
async loadBucketsForList(ctx, {listId, params}) {
const cancel = setLoading(ctx, 'kanban')
// Clear everything to prevent having old buckets in the list if loading the buckets from this list takes a few moments
@ -259,7 +259,7 @@ export default {
}
},
async loadNextTasksForBucket(ctx: ActionContext<KanbanState, RootStoreState>, {listId, ps = {}, bucketId}) {
async loadNextTasksForBucket(ctx, {listId, ps = {}, bucketId}) {
const isLoading = ctx.state.bucketLoading[bucketId] ?? false
if (isLoading) {
return
@ -314,7 +314,7 @@ export default {
}
},
async createBucket(ctx: ActionContext<KanbanState, RootStoreState>, bucket: IBucket) {
async createBucket(ctx, bucket: IBucket) {
const cancel = setLoading(ctx, 'kanban')
const bucketService = new BucketService()
@ -327,7 +327,7 @@ export default {
}
},
async deleteBucket(ctx: ActionContext<KanbanState, RootStoreState>, {bucket, params}) {
async deleteBucket(ctx, {bucket, params}) {
const cancel = setLoading(ctx, 'kanban')
const bucketService = new BucketService()
@ -342,7 +342,7 @@ export default {
}
},
async updateBucket(ctx: ActionContext<KanbanState, RootStoreState>, updatedBucketData) {
async updateBucket(ctx, updatedBucketData) {
const cancel = setLoading(ctx, 'kanban')
const bucketIndex = findIndexById(ctx.state.buckets, updatedBucketData.id)
@ -370,7 +370,7 @@ export default {
}
},
async updateBucketTitle(ctx: ActionContext<KanbanState, RootStoreState>, { id, title }) {
async updateBucketTitle(ctx, { id, title }) {
const bucket = findById(ctx.state.buckets, id)
if (bucket?.title === title) {
@ -387,4 +387,6 @@ export default {
success({message: i18n.global.t('list.kanban.bucketTitleSavedSuccess')})
},
},
}
}
export default kanbanStore

View File

@ -1,4 +1,4 @@
import type { ActionContext } from 'vuex'
import type { Module } from 'vuex'
import {i18n} from '@/i18n'
import {success} from '@/message'
@ -22,46 +22,46 @@ async function getAllLabels(page = 1): Promise<ILabel[]> {
}
}
export default {
const LabelStore : Module<LabelState, RootStoreState> = {
namespaced: true,
state: (): LabelState => ({
state: () => ({
labels: {},
loaded: false,
}),
mutations: {
setLabels(state: LabelState, labels: ILabel[]) {
setLabels(state, labels: ILabel[]) {
labels.forEach(l => {
state.labels[l.id] = l
add(l)
})
},
setLabel(state: LabelState, label: ILabel) {
setLabel(state, label: ILabel) {
state.labels[label.id] = label
update(label)
},
removeLabelById(state: LabelState, label: ILabel) {
removeLabelById(state, label: ILabel) {
remove(label)
delete state.labels[label.id]
},
setLoaded(state: LabelState, loaded: boolean) {
setLoaded(state, loaded: boolean) {
state.loaded = loaded
},
},
getters: {
getLabelsByIds(state: LabelState) {
getLabelsByIds(state) {
return (ids: ILabel['id'][]) => getLabelsByIds(state, ids)
},
filterLabelsByQuery(state: LabelState) {
filterLabelsByQuery(state) {
return (labelsToHide: ILabel[], query: string) => filterLabelsByQuery(state, labelsToHide, query)
},
getLabelsByExactTitles(state: LabelState) {
getLabelsByExactTitles(state) {
return labelTitles => Object
.values(state.labels)
.filter(({title}) => labelTitles.some(l => l.toLowerCase() === title.toLowerCase()))
},
},
actions: {
async loadAllLabels(ctx: ActionContext<LabelState, RootStoreState>, {forceLoad} = {}) {
async loadAllLabels(ctx, {forceLoad} = {}) {
if (ctx.state.loaded && !forceLoad) {
return
}
@ -77,7 +77,7 @@ export default {
cancel()
}
},
async deleteLabel(ctx: ActionContext<LabelState, RootStoreState>, label: ILabel) {
async deleteLabel(ctx, label: ILabel) {
const cancel = setLoading(ctx, 'labels')
const labelService = new LabelService()
@ -90,7 +90,7 @@ export default {
cancel()
}
},
async updateLabel(ctx: ActionContext<LabelState, RootStoreState>, label: ILabel) {
async updateLabel(ctx, label: ILabel) {
const cancel = setLoading(ctx, 'labels')
const labelService = new LabelService()
@ -103,7 +103,7 @@ export default {
cancel()
}
},
async createLabel(ctx: ActionContext<LabelState, RootStoreState>, label: ILabel) {
async createLabel(ctx, label: ILabel) {
const cancel = setLoading(ctx, 'labels')
const labelService = new LabelService()
@ -117,3 +117,5 @@ export default {
},
},
}
export default LabelStore

View File

@ -1,49 +1,50 @@
import type { Module } from 'vuex'
import ListService from '@/services/list'
import {setLoading} from '@/store/helper'
import {removeListFromHistory} from '@/modules/listHistory'
import {createNewIndexer} from '@/indexes'
import type {ListState, RootStoreState} from '@/store/types'
import type {ActionContext} from 'vuex'
import type {IList} from '@/models/list'
const {add, remove, search, update} = createNewIndexer('lists', ['title', 'description'])
const FavoriteListsNamespace = -2
export default {
const listsStore : Module<ListState, RootStoreState>= {
namespaced: true,
// The state is an object which has the list ids as keys.
state: (): ListState => ({}),
state: () => ({}),
mutations: {
setList(state: ListState, list: IList) {
setList(state, list: IList) {
state[list.id] = list
update(list)
},
setLists(state: ListState, lists: IList[]) {
setLists(state, lists: IList[]) {
lists.forEach(l => {
state[l.id] = l
add(l)
})
},
removeListById(state: ListState, list: IList) {
removeListById(state, list: IList) {
remove(list)
delete state[list.id]
},
},
getters: {
getListById: (state: ListState) => (id: IList['id']) => {
getListById: (state) => (id: IList['id']) => {
if (typeof state[id] !== 'undefined') {
return state[id]
}
return null
},
findListByExactname: (state: ListState) => (name: string) => {
findListByExactname: (state) => (name: string) => {
const list = Object.values(state).find(l => {
return l.title.toLowerCase() === name.toLowerCase()
})
return typeof list === 'undefined' ? null : list
},
searchList: (state: ListState) => (query: string, includeArchived = false) => {
searchList: (state) => (query: string, includeArchived = false) => {
return search(query)
?.filter(value => value > 0)
.map(id => state[id])
@ -52,14 +53,14 @@ export default {
},
},
actions: {
toggleListFavorite(ctx: ActionContext<ListState, RootStoreState>, list: IList) {
toggleListFavorite(ctx, list: IList) {
return ctx.dispatch('updateList', {
...list,
isFavorite: !list.isFavorite,
})
},
async createList(ctx: ActionContext<ListState, RootStoreState>, list: IList) {
async createList(ctx, list: IList) {
const cancel = setLoading(ctx, 'lists')
const listService = new ListService()
@ -74,7 +75,7 @@ export default {
}
},
async updateList(ctx: ActionContext<ListState, RootStoreState>, list: IList) {
async updateList(ctx, list: IList) {
const cancel = setLoading(ctx, 'lists')
const listService = new ListService()
@ -109,7 +110,7 @@ export default {
}
},
async deleteList(ctx: ActionContext<ListState, RootStoreState>, list: IList) {
async deleteList(ctx, list: IList) {
const cancel = setLoading(ctx, 'lists')
const listService = new ListService()
@ -124,4 +125,6 @@ export default {
}
},
},
}
}
export default listsStore

View File

@ -1,4 +1,4 @@
import type {ActionContext} from 'vuex'
import type { Module } from 'vuex'
import NamespaceService from '../../services/namespace'
import {setLoading} from '@/store/helper'
@ -9,19 +9,19 @@ import type {IList} from '@/models/list'
const {add, remove, search, update} = createNewIndexer('namespaces', ['title', 'description'])
export default {
const namespacesStore : Module<NamespaceState, RootStoreState> = {
namespaced: true,
state: (): NamespaceState => ({
state: () => ({
namespaces: [],
}),
mutations: {
namespaces(state: NamespaceState, namespaces: INamespace[]) {
namespaces(state, namespaces: INamespace[]) {
state.namespaces = namespaces
namespaces.forEach(n => {
add(n)
})
},
setNamespaceById(state: NamespaceState, namespace: INamespace) {
setNamespaceById(state, namespace: INamespace) {
const namespaceIndex = state.namespaces.findIndex(n => n.id === namespace.id)
if (namespaceIndex === -1) {
@ -35,7 +35,7 @@ export default {
state.namespaces[namespaceIndex] = namespace
update(namespace)
},
setListInNamespaceById(state: NamespaceState, list: IList) {
setListInNamespaceById(state, list: IList) {
for (const n in state.namespaces) {
// We don't have the namespace id on the list which means we need to loop over all lists until we find it.
// FIXME: Not ideal at all - we should fix that at the api level.
@ -51,11 +51,11 @@ export default {
}
}
},
addNamespace(state: NamespaceState, namespace: INamespace) {
addNamespace(state, namespace: INamespace) {
state.namespaces.push(namespace)
add(namespace)
},
removeNamespaceById(state: NamespaceState, namespaceId: INamespace['id']) {
removeNamespaceById(state, namespaceId: INamespace['id']) {
for (const n in state.namespaces) {
if (state.namespaces[n].id === namespaceId) {
remove(state.namespaces[n])
@ -64,7 +64,7 @@ export default {
}
}
},
addListToNamespace(state: NamespaceState, list: IList) {
addListToNamespace(state, list: IList) {
for (const n in state.namespaces) {
if (state.namespaces[n].id === list.namespaceId) {
state.namespaces[n].lists.push(list)
@ -72,7 +72,7 @@ export default {
}
}
},
removeListFromNamespaceById(state: NamespaceState, list: IList) {
removeListFromNamespaceById(state, list: IList) {
for (const n in state.namespaces) {
// We don't have the namespace id on the list which means we need to loop over all lists until we find it.
// FIXME: Not ideal at all - we should fix that at the api level.
@ -88,7 +88,7 @@ export default {
},
},
getters: {
getListAndNamespaceById: (state: NamespaceState) => (listId: IList['id'], ignorePseudoNamespaces = false) => {
getListAndNamespaceById: (state) => (listId: IList['id'], ignorePseudoNamespaces = false) => {
for (const n in state.namespaces) {
if (ignorePseudoNamespaces && state.namespaces[n].id < 0) {
@ -106,10 +106,10 @@ export default {
}
return null
},
getNamespaceById: (state: NamespaceState) => (namespaceId: INamespace['id']) => {
getNamespaceById: (state) => (namespaceId: INamespace['id']) => {
return state.namespaces.find(({id}) => id == namespaceId) || null
},
searchNamespace: (state: NamespaceState, getters) => (query: string) => {
searchNamespace: (state, getters) => (query: string) => {
return search(query)
?.filter(value => value > 0)
.map(getters.getNamespaceById)
@ -118,7 +118,7 @@ export default {
},
},
actions: {
async loadNamespaces(ctx: ActionContext<NamespaceState, RootStoreState>) {
async loadNamespaces(ctx) {
const cancel = setLoading(ctx, 'namespaces')
const namespaceService = new NamespaceService()
@ -138,20 +138,20 @@ export default {
}
},
loadNamespacesIfFavoritesDontExist(ctx: ActionContext<NamespaceState, RootStoreState>) {
loadNamespacesIfFavoritesDontExist(ctx) {
// The first or second namespace should be the one holding all favorites
if (ctx.state.namespaces[0].id !== -2 && ctx.state.namespaces[1]?.id !== -2) {
return ctx.dispatch('loadNamespaces')
}
},
removeFavoritesNamespaceIfEmpty(ctx: ActionContext<NamespaceState, RootStoreState>) {
removeFavoritesNamespaceIfEmpty(ctx) {
if (ctx.state.namespaces[0].id === -2 && ctx.state.namespaces[0].lists.length === 0) {
ctx.state.namespaces.splice(0, 1)
}
},
async deleteNamespace(ctx: ActionContext<NamespaceState, RootStoreState>, namespace: INamespace) {
async deleteNamespace(ctx, namespace: INamespace) {
const cancel = setLoading(ctx, 'namespaces')
const namespaceService = new NamespaceService()
@ -164,7 +164,7 @@ export default {
}
},
async createNamespace(ctx: ActionContext<NamespaceState, RootStoreState>, namespace: INamespace) {
async createNamespace(ctx, namespace: INamespace) {
const cancel = setLoading(ctx, 'namespaces')
const namespaceService = new NamespaceService()
@ -177,4 +177,6 @@ export default {
}
},
},
}
}
export default namespacesStore

View File

@ -1,5 +1,5 @@
import type { Module } from 'vuex'
import router from '@/router'
import type { ActionContext } from 'vuex'
import {formatISO} from 'date-fns'
import TaskService from '@/services/task'
@ -65,11 +65,11 @@ async function findAssignees(parsedTaskAssignees) {
}
export default {
const tasksStore : Module<TaskState, RootStoreState>= {
namespaced: true,
state: (): TaskState => ({}),
state: () => ({}),
actions: {
async loadTasks(ctx: ActionContext<TaskState, RootStoreState>, params) {
async loadTasks(ctx, params) {
const taskService = new TaskService()
const cancel = setLoading(ctx, 'tasks')
@ -82,7 +82,7 @@ export default {
}
},
async update(ctx: ActionContext<TaskState, RootStoreState>, task: ITask) {
async update(ctx, task: ITask) {
const cancel = setLoading(ctx, 'tasks')
const taskService = new TaskService()
@ -95,7 +95,7 @@ export default {
}
},
async delete(ctx: ActionContext<TaskState, RootStoreState>, task: ITask) {
async delete(ctx, task: ITask) {
const taskService = new TaskService()
const response = await taskService.delete(task)
ctx.commit('kanban/removeTaskInBucket', task, {root: true})
@ -104,7 +104,7 @@ export default {
// Adds a task attachment in store.
// This is an action to be able to commit other mutations
addTaskAttachment(ctx: ActionContext<TaskState, RootStoreState>, {
addTaskAttachment(ctx, {
taskId,
attachment,
}: {
@ -130,7 +130,7 @@ export default {
ctx.commit('attachments/add', attachment, {root: true})
},
async addAssignee(ctx: ActionContext<TaskState, RootStoreState>, {
async addAssignee(ctx, {
user,
taskId,
}: {
@ -165,7 +165,7 @@ export default {
return r
},
async removeAssignee(ctx: ActionContext<TaskState, RootStoreState>, {
async removeAssignee(ctx, {
user,
taskId,
}: {
@ -198,7 +198,7 @@ export default {
},
async addLabel(ctx: ActionContext<TaskState, RootStoreState>, {
async addLabel(ctx, {
label,
taskId,
} : {
@ -234,7 +234,7 @@ export default {
return r
},
async removeLabel(ctx: ActionContext<TaskState, RootStoreState>, {label, taskId}) {
async removeLabel(ctx, {label, taskId}) {
const labelTask = new LabelTaskModel({taskId, labelId: label.id})
const labelTaskService = new LabelTaskService()
@ -263,7 +263,7 @@ export default {
},
// Do everything that is involved in finding, creating and adding the label to the task
async addLabelsToTask({rootState, dispatch}: ActionContext<TaskState, RootStoreState>, {
async addLabelsToTask({rootState, dispatch}, {
task,
parsedLabels,
}) {
@ -289,7 +289,7 @@ export default {
return task
},
findListId({ rootGetters }: ActionContext<TaskState, RootStoreState>, { list: listName, listId }: {
findListId({ rootGetters }, { list: listName, listId }: {
list: string,
listId: IList['id']
}) {
@ -320,7 +320,7 @@ export default {
return foundListId
},
async createNewTask({dispatch, commit}: ActionContext<TaskState, RootStoreState>, {
async createNewTask({dispatch, commit}, {
title,
bucketId,
listId,
@ -366,4 +366,6 @@ export default {
return result
},
},
}
}
export default tasksStore

View File

@ -15,16 +15,6 @@ export interface RootStoreState {
menuActive: boolean,
keyboardShortcutsActive: boolean,
quickActionsActive: boolean,
// modules
attachments: AttachmentState,
auth: AuthState,
config: ConfigState,
kanban: KanbanState,
labels: LabelState,
lists: ListState,
namespaces: NamespaceState,
tasks: TaskState,
}
export interface AttachmentState {
@ -114,3 +104,15 @@ export interface NamespaceState {
}
export interface TaskState {}
export type StoreState = RootStoreState & {
config: ConfigState,
auth: AuthState,
namespaces: NamespaceState,
kanban: KanbanState,
tasks: TaskState,
lists: ListState,
attachments: AttachmentState,
labels: LabelState,
}

View File

@ -1,13 +1,30 @@
// https://next.vuex.vuejs.org/guide/migrating-to-4-0-from-3-x.html#typescript-support
import { Store } from 'vuex'
import type {
RootStoreState,
AttachmentState,
AuthState,
ConfigState,
KanbanState,
LabelState,
ListState,
NamespaceState,
TaskState,
} from '@/store/types'
declare module '@vue/runtime-core' {
// Declare your own store states.
interface State {
count: number
}
interface ComponentCustomProperties {
$store: Store<State>
$store: Store<RootStoreState & {
config: ConfigState,
auth: AuthState,
namespaces: NamespaceState,
kanban: KanbanState,
tasks: TaskState,
lists: ListState,
attachments: AttachmentState,
labels: LabelState,
}>
}
}

View File

@ -60,7 +60,7 @@
<script lang="ts" setup>
import {ref, computed} from 'vue'
import {useStore} from 'vuex'
import {useStore} from '@/store'
import Message from '@/components/misc/message.vue'
import ShowTasks from '@/views/tasks/ShowTasks.vue'

View File

@ -69,7 +69,7 @@ import {ref, computed} from 'vue'
import flatPickr from 'vue-flatpickr-component'
import {useI18n} from 'vue-i18n'
import {useStore} from 'vuex'
import {useStore} from '@/store'
import ListWrapper from './ListWrapper.vue'
import GanttChart from '@/components/tasks/gantt-component.vue'

View File

@ -60,7 +60,7 @@ import {BACKGROUND, BLUR_HASH, CURRENT_LIST} from '@/store/mutation-types'
import {getListTitle} from '@/helpers/getListTitle'
import {saveListToHistory} from '@/modules/listHistory'
import {useTitle} from '@/composables/useTitle'
import {useStore} from 'vuex'
import {useStore} from '@/store'
const props = defineProps({
listId: {

View File

@ -35,7 +35,7 @@
import {ref, reactive, shallowReactive} from 'vue'
import {useI18n} from 'vue-i18n'
import {useRouter, useRoute} from 'vue-router'
import {useStore} from 'vuex'
import {useStore} from '@/store'
import ListService from '@/services/list'
import ListModel from '@/models/list'

View File

@ -17,7 +17,7 @@ export default {name: 'list-setting-archive'}
<script setup lang="ts">
import {computed} from 'vue'
import {useStore} from 'vuex'
import {useStore} from '@/store'
import {useRouter, useRoute} from 'vue-router'
import {useI18n} from 'vue-i18n'

View File

@ -30,7 +30,7 @@
import {computed, ref, watchEffect} from 'vue'
import {useTitle} from '@/composables/useTitle'
import {useI18n} from 'vue-i18n'
import {useStore} from 'vuex'
import {useStore} from '@/store'
import {useRoute, useRouter} from 'vue-router'
import {success} from '@/message'
import TaskCollectionService from '@/services/taskCollection'

View File

@ -22,7 +22,7 @@
<script setup lang="ts">
import {ref, shallowReactive} from 'vue'
import {useRoute, useRouter} from 'vue-router'
import {useStore} from 'vuex'
import {useStore} from '@/store'
import {useI18n} from 'vue-i18n'
import ListDuplicateService from '@/services/listDuplicateService'

View File

@ -28,7 +28,7 @@ export default {name: 'list-setting-share'}
<script lang="ts" setup>
import {ref, computed, watchEffect} from 'vue'
import {useStore} from 'vuex'
import {useStore} from '@/store'
import {useRoute} from 'vue-router'
import {useI18n} from 'vue-i18n'
import {useTitle} from '@vueuse/core'

View File

@ -23,7 +23,7 @@
<script setup lang="ts">
import {computed} from 'vue'
import {useI18n} from 'vue-i18n'
import {useStore} from 'vuex'
import {useStore} from '@/store'
import {MIGRATORS} from './migrators'
import {useTitle} from '@/composables/useTitle'

View File

@ -57,7 +57,7 @@
<script lang="ts" setup>
import {nextTick, ref, watch} from 'vue'
import {useStore} from 'vuex'
import {useStore} from '@/store'
import {success} from '@/message'
import router from '@/router'

View File

@ -26,7 +26,7 @@ export default { name: 'namespace-setting-share' }
<script lang="ts" setup>
import {ref, computed, watchEffect} from 'vue'
import {useStore} from 'vuex'
import {useStore} from '@/store'
import {useRoute} from 'vue-router'
import {useI18n} from 'vue-i18n'
import {useTitle} from '@vueuse/core'

View File

@ -34,7 +34,7 @@
<script lang="ts" setup>
import {ref, computed} from 'vue'
import {useStore} from 'vuex'
import {useStore} from '@/store'
import {useRoute, useRouter} from 'vue-router'
import {useI18n} from 'vue-i18n'
import {useTitle} from '@vueuse/core'

View File

@ -45,7 +45,7 @@
<script setup lang="ts">
import {computed, ref, watchEffect} from 'vue'
import {useStore} from 'vuex'
import {useStore} from '@/store'
import {useRoute, useRouter} from 'vue-router'
import {useI18n} from 'vue-i18n'

View File

@ -165,7 +165,7 @@ import {computed, ref} from 'vue'
import {useI18n} from 'vue-i18n'
import Editor from '@/components/input/AsyncEditor'
import {useStore} from 'vuex'
import {useStore} from '@/store'
import TeamService from '@/services/team'
import TeamMemberService from '@/services/teamMember'

View File

@ -15,7 +15,7 @@ export default { name: 'Auth' }
<script setup lang="ts">
import {ref, computed, onMounted} from 'vue'
import {useStore} from 'vuex'
import {useStore} from '@/store'
import {useRoute, useRouter} from 'vue-router'
import {useI18n} from 'vue-i18n'

View File

@ -64,7 +64,7 @@ export default { name: 'user-settings-avatar' }
<script setup lang="ts">
import {computed, ref, shallowReactive} from 'vue'
import {useI18n} from 'vue-i18n'
import {useStore} from 'vuex'
import {useStore} from '@/store'
import {Cropper} from 'vue-advanced-cropper'
import 'vue-advanced-cropper/dist/style.css'

View File

@ -68,7 +68,7 @@
<script lang="ts" setup>
import {computed, ref, shallowReactive} from 'vue'
import {useI18n} from 'vue-i18n'
import {useStore} from 'vuex'
import {useStore} from '@/store'
import {CALDAV_DOCS} from '@/urls'
import {useTitle} from '@/composables/useTitle'

View File

@ -44,7 +44,7 @@ export default {name: 'user-settings-data-export'}
<script setup lang="ts">
import {ref, computed, shallowReactive} from 'vue'
import {useStore} from 'vuex'
import {useStore} from '@/store'
import {useI18n} from 'vue-i18n'
import DataExportService from '@/services/dataExport'

View File

@ -88,7 +88,7 @@ export default { name: 'user-settings-deletion' }
<script setup lang="ts">
import {ref, shallowReactive, computed} from 'vue'
import {useStore} from 'vuex'
import {useStore} from '@/store'
import {useI18n} from 'vue-i18n'
import AccountDeleteService from '@/services/accountDelete'

View File

@ -43,7 +43,7 @@ export default { name: 'user-settings-update-email' }
<script setup lang="ts">
import {reactive, computed, shallowReactive} from 'vue'
import {useI18n} from 'vue-i18n'
import {useStore} from 'vuex'
import {useStore} from '@/store'
import EmailUpdateService from '@/services/emailUpdate'
import EmailUpdateModel from '@/models/emailUpdate'

View File

@ -157,7 +157,7 @@ export default defineComponent({
<script setup lang="ts">
import {computed, watch, ref} from 'vue'
import {useI18n} from 'vue-i18n'
import {useStore} from 'vuex'
import {useStore} from '@/store'
import {PrefixMode} from '@/modules/parseTaskText'

View File

@ -58,7 +58,7 @@ export default {name: 'user-settings-password-update'}
<script setup lang="ts">
import {ref, reactive, shallowReactive, computed} from 'vue'
import { useI18n } from 'vue-i18n'
import { useStore } from 'vuex'
import {useStore} from '@/store'
import PasswordUpdateService from '@/services/passwordUpdateService'
import PasswordUpdateModel from '@/models/passwordUpdate'

View File

@ -70,7 +70,7 @@ export default { name: 'user-settings-totp' }
<script lang="ts" setup>
import {computed, ref, shallowReactive} from 'vue'
import {useStore} from 'vuex'
import {useStore} from '@/store'
import {useI18n} from 'vue-i18n'
import TotpService from '@/services/totp'