1
0

feat: port base store to pinia

This commit is contained in:
Dominik Pschenitschni
2022-09-24 15:20:40 +02:00
parent df74f9d80c
commit 7f281fc5e9
49 changed files with 450 additions and 541 deletions

View File

@ -1,9 +1,12 @@
import {defineStore, acceptHMRUpdate} from 'pinia'
import {findIndexById} from '@/helpers/utils'
import type {AttachmentState} from '@/store/types'
import type {IAttachment} from '@/modelTypes/IAttachment'
export interface AttachmentState {
attachments: IAttachment[],
}
export const useAttachmentStore = defineStore('attachment', {
state: (): AttachmentState => ({
attachments: [],

View File

@ -6,16 +6,27 @@ import {objectToSnakeCase} from '@/helpers/case'
import UserModel, { getAvatarUrl } from '@/models/user'
import UserSettingsService from '@/services/userSettings'
import {getToken, refreshToken, removeToken, saveToken} from '@/helpers/auth'
import {setLoadingPinia} from '@/store/helper'
import {setLoadingPinia} from '@/stores/helper'
import {success} from '@/message'
import {redirectToProvider} from '@/helpers/redirectToProvider'
import {AUTH_TYPES, type IUser} from '@/modelTypes/IUser'
import type {AuthState} from '@/store/types'
import type {IUserSettings} from '@/modelTypes/IUserSettings'
import router from '@/router'
import {useConfigStore} from '@/stores/config'
import {useBaseStore} from '@/stores/base'
import UserSettingsModel from '@/models/userSettings'
import {store} from '@/store'
export interface AuthState {
authenticated: boolean,
isLinkShareAuth: boolean,
info: IUser | null,
needsTotpPasscode: boolean,
avatarUrl: string,
lastUserInfoRefresh: Date | null,
settings: IUserSettings,
isLoading: boolean,
isLoadingGeneralSettings: boolean
}
export const useAuthStore = defineStore('auth', {
state: () : AuthState => ({
@ -93,7 +104,8 @@ export const useAuthStore = defineStore('auth', {
// Logs a user in with a set of credentials.
async login(credentials) {
const HTTP = HTTPFactory()
store.commit('loading', true)
const baseStore = useBaseStore()
baseStore.setLoading(true)
this.setIsLoading(true)
// Delete an eventually preexisting old token
@ -117,7 +129,7 @@ export const useAuthStore = defineStore('auth', {
throw e
} finally {
store.commit('loading', false)
baseStore.setLoading(false)
this.setIsLoading(false)
}
},
@ -126,7 +138,8 @@ 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.
async register(credentials) {
const HTTP = HTTPFactory()
store.commit('loading', true)
const baseStore = useBaseStore()
baseStore.setLoading(true)
this.setIsLoading(true)
try {
await HTTP.post('register', credentials)
@ -138,14 +151,15 @@ export const useAuthStore = defineStore('auth', {
throw e
} finally {
store.commit('loading', false)
baseStore.setLoading(false)
this.setIsLoading(false)
}
},
async openIdAuth({provider, code}) {
const HTTP = HTTPFactory()
store.commit('loading', true)
const baseStore = useBaseStore()
baseStore.setLoading(true)
this.setIsLoading(true)
const data = {
@ -162,7 +176,7 @@ export const useAuthStore = defineStore('auth', {
// Tell others the user is autheticated
this.checkAuth()
} finally {
store.commit('loading', false)
baseStore.setLoading(false)
this.setIsLoading(false)
}
},

View File

@ -1,40 +1,34 @@
import type {InjectionKey} from 'vue'
import {createStore, useStore as baseUseStore, Store} from 'vuex'
import {defineStore, acceptHMRUpdate} from 'pinia'
import {getBlobFromBlurHash} from '../helpers/getBlobFromBlurHash'
import {
BACKGROUND,
BLUR_HASH,
CURRENT_LIST,
HAS_TASKS,
KEYBOARD_SHORTCUTS_ACTIVE,
LOADING,
LOADING_MODULE, LOGO_VISIBLE,
MENU_ACTIVE,
QUICK_ACTIONS_ACTIVE,
} from '../store/mutation-types'
import {getBlobFromBlurHash} from '@/helpers/getBlobFromBlurHash'
import ListModel from '@/models/list'
import ListService from '../services/list'
import {checkAndSetApiUrl} from '@/helpers/checkAndSetApiUrl'
import type { RootStoreState, StoreState } from '../store/types'
import pinia from '@/pinia'
import {useAuthStore} from '@/stores/auth'
import type {IList} from '@/modelTypes/IList'
export const key: InjectionKey<Store<StoreState>> = Symbol()
export interface RootStoreState {
loading: boolean,
loadingModule: null,
// define your own `useStore` composition function
export function useStore () {
return baseUseStore(key)
currentList: IList,
background: string,
blurHash: string,
hasTasks: boolean,
menuActive: boolean,
keyboardShortcutsActive: boolean,
quickActionsActive: boolean,
logoVisible: boolean,
}
export const store = createStore<RootStoreState>({
strict: import.meta.env.DEV,
state: () => ({
export const useBaseStore = defineStore('base', {
state: () : RootStoreState => ({
loading: false,
loadingModule: null,
// This is used to highlight the current list in menu for all list related views
currentList: new ListModel({
id: 0,
@ -42,76 +36,94 @@ export const store = createStore<RootStoreState>({
}),
background: '',
blurHash: '',
hasTasks: false,
menuActive: true,
keyboardShortcutsActive: false,
quickActionsActive: false,
logoVisible: true,
}),
mutations: {
[LOADING](state, loading) {
state.loading = loading
actions: {
setLoading(loading: boolean) {
this.loading = loading
},
[LOADING_MODULE](state, module) {
state.loadingModule = module
setLoadingModule(module) {
this.loadingModule = module
},
[CURRENT_LIST](state, currentList) {
// FIXME: same action as mutation name
setCurrentList(currentList: IList) {
// Server updates don't return the right. Therefore, the right is reset after updating the list which is
// confusing because all the buttons will disappear in that case. To prevent this, we're keeping the right
// when updating the list in global state.
if (typeof state.currentList.maxRight !== 'undefined' && (typeof currentList.maxRight === 'undefined' || currentList.maxRight === null)) {
currentList.maxRight = state.currentList.maxRight
if (
typeof this.currentList.maxRight !== 'undefined' &&
(
typeof currentList.maxRight === 'undefined' ||
currentList.maxRight === null
)
) {
currentList.maxRight = this.currentList.maxRight
}
state.currentList = currentList
this.currentList = currentList
},
[HAS_TASKS](state, hasTasks) {
state.hasTasks = hasTasks
},
[MENU_ACTIVE](state, menuActive) {
state.menuActive = menuActive
},
toggleMenu(state) {
state.menuActive = !state.menuActive
},
[KEYBOARD_SHORTCUTS_ACTIVE](state, active) {
state.keyboardShortcutsActive = active
},
[QUICK_ACTIONS_ACTIVE](state, active) {
state.quickActionsActive = active
},
[BACKGROUND](state, background) {
state.background = background
},
[BLUR_HASH](state, blurHash) {
state.blurHash = blurHash
},
[LOGO_VISIBLE](state, visible: boolean) {
state.logoVisible = visible
},
},
actions: {
async [CURRENT_LIST]({state, commit}, {list, forceUpdate = false}) {
setHasTasks(hasTasks: boolean) {
this.hasTasks = hasTasks
},
setMenuActive(menuActive: boolean) {
this.menuActive = menuActive
},
toggleMenu() {
this.menuActive = !this.menuActive
},
setKeyboardShortcutsActive(active: boolean) {
this.keyboardShortcutsActive = active
},
setQuickActionsActive(active: boolean) {
this.quickActionsActive = active
},
setBackground(background: string) {
this.background = background
},
setBlurHash(blurHash: string) {
this.blurHash = blurHash
},
setLogoVisible(visible: boolean) {
this.logoVisible = visible
},
// FIXME: update all actions handleSetCurrentList
async handleSetCurrentList({list, forceUpdate = false}) {
if (list === null) {
commit(CURRENT_LIST, {})
commit(BACKGROUND, null)
commit(BLUR_HASH, null)
this.setCurrentList({})
this.setBackground('')
this.setBlurHash('')
return
}
// The forceUpdate parameter is used only when updating a list background directly because in that case
// the current list stays the same, but we want to show the new background right away.
if (list.id !== state.currentList.id || forceUpdate) {
if (list.id !== this.currentList.id || forceUpdate) {
if (list.backgroundInformation) {
try {
const blurHash = await getBlobFromBlurHash(list.backgroundBlurHash)
if (blurHash) {
commit(BLUR_HASH, window.URL.createObjectURL(blurHash))
this.setBlurHash(window.URL.createObjectURL(blurHash))
}
const listService = new ListService()
const background = await listService.background(list)
commit(BACKGROUND, background)
this.setBackground(background)
} catch (e) {
console.error('Error getting background image for list', list.id, e)
}
@ -119,16 +131,21 @@ export const store = createStore<RootStoreState>({
}
if (typeof list.backgroundInformation === 'undefined' || list.backgroundInformation === null) {
commit(BACKGROUND, null)
commit(BLUR_HASH, null)
this.setBackground('')
this.setBlurHash('')
}
commit(CURRENT_LIST, list)
this.setCurrentList(list)
},
async loadApp() {
await checkAndSetApiUrl(window.API_URL)
const authStore = useAuthStore(pinia)
await authStore.checkAuth()
await useAuthStore().checkAuth()
},
},
})
// support hot reloading
if (import.meta.hot) {
import.meta.hot.accept(acceptHMRUpdate(useBaseStore, import.meta.hot))
}

View File

@ -1,10 +1,38 @@
import {defineStore, acceptHMRUpdate} from 'pinia'
import {parseURL} from 'ufo'
import {CONFIG} from '../store/mutation-types'
import {HTTPFactory} from '@/http-common'
import {objectToCamelCase} from '@/helpers/case'
import type {ConfigState} from '@/store/types'
export interface ConfigState {
version: string,
frontendUrl: string,
motd: string,
linkSharingEnabled: boolean,
maxFileSize: '20MB',
registrationEnabled: boolean,
availableMigrators: [],
taskAttachmentsEnabled: boolean,
totpEnabled: boolean,
enabledBackgroundProviders: [],
legal: {
imprintUrl: string,
privacyPolicyUrl: string,
},
caldavEnabled: boolean,
userDeletionEnabled: boolean,
taskCommentsEnabled: boolean,
auth: {
local: {
enabled: boolean,
},
openidConnect: {
enabled: boolean,
redirectUrl: string,
providers: [],
},
},
}
export const useConfigStore = defineStore('config', {
state: (): ConfigState => ({
@ -45,13 +73,13 @@ export const useConfigStore = defineStore('config', {
},
},
actions: {
[CONFIG](config: ConfigState) {
setConfig(config: ConfigState) {
Object.assign(this, config)
},
async update() {
const HTTP = HTTPFactory()
const {data: config} = await HTTP.get('info')
this[CONFIG](objectToCamelCase(config))
this.setConfig(objectToCamelCase(config))
return config
},
},

19
src/stores/helper.ts Normal file
View File

@ -0,0 +1,19 @@
import type { StoreDefinition } from 'pinia'
export const setLoadingPinia = (store: StoreDefinition, loadFunc : ((isLoading: boolean) => void) | null = null) => {
const timeout = setTimeout(() => {
if (loadFunc === null) {
store.isLoading = true
} else {
loadFunc(true)
}
}, 100)
return () => {
clearTimeout(timeout)
if (loadFunc === null) {
store.isLoading = false
} else {
loadFunc(false)
}
}
}

View File

@ -5,10 +5,11 @@ import {findById, findIndexById} from '@/helpers/utils'
import {i18n} from '@/i18n'
import {success} from '@/message'
import BucketService from '../services/bucket'
import {setLoadingPinia} from '@/store/helper'
import BucketService from '@/services/bucket'
import TaskCollectionService from '@/services/taskCollection'
import type { KanbanState } from '@/store/types'
import {setLoadingPinia} from '@/stores/helper'
import type { ITask } from '@/modelTypes/ITask'
import type { IList } from '@/modelTypes/IList'
import type { IBucket } from '@/modelTypes/IBucket'
@ -37,6 +38,21 @@ const addTaskToBucketAndSort = (state: KanbanState, task: ITask) => {
state.buckets[bucketIndex].tasks.sort((a, b) => a.kanbanPosition > b.kanbanPosition ? 1 : -1)
}
export interface KanbanState {
buckets: IBucket[],
listId: IList['id'],
bucketLoading: {
[id: IBucket['id']]: boolean
},
taskPagesPerBucket: {
[id: IBucket['id']]: number
},
allTasksLoadedForBucket: {
[id: IBucket['id']]: boolean
},
isLoading: boolean,
}
/**
* This store is intended to hold the currently active kanban view.
* It should hold only the current buckets.

View File

@ -4,7 +4,7 @@ import LabelService from '@/services/label'
import {success} from '@/message'
import {i18n} from '@/i18n'
import {createNewIndexer} from '@/indexes'
import {setLoadingPinia} from '@/store/helper'
import {setLoadingPinia} from '@/stores/helper'
import type {ILabel} from '@/modelTypes/ILabel'
const {add, remove, update, search} = createNewIndexer('labels', ['title', 'description'])
@ -20,7 +20,12 @@ async function getAllLabels(page = 1): Promise<ILabel[]> {
}
}
import type {LabelState} from '@/store/types'
export interface LabelState {
labels: {
[id: ILabel['id']]: ILabel
},
isLoading: boolean,
}
export const useLabelStore = defineStore('label', {
state: () : LabelState => ({

View File

@ -3,12 +3,11 @@ import {acceptHMRUpdate, defineStore} from 'pinia'
import {useI18n} from 'vue-i18n'
import ListService from '@/services/list'
import {setLoadingPinia} from '@/store/helper'
import {setLoadingPinia} from '@/stores/helper'
import {removeListFromHistory} from '@/modules/listHistory'
import {createNewIndexer} from '@/indexes'
import {useNamespaceStore} from './namespaces'
import type {ListState} from '@/store/types'
import type {IList} from '@/modelTypes/IList'
import type {MaybeRef} from '@vueuse/core'
@ -20,6 +19,11 @@ const {add, remove, search, update} = createNewIndexer('lists', ['title', 'descr
const FavoriteListsNamespace = -2
export interface ListState {
lists: { [id: IList['id']]: IList },
isLoading: boolean,
}
export const useListStore = defineStore('list', {
state: () : ListState => ({
isLoading: false,
@ -113,7 +117,7 @@ export const useListStore = defineStore('list', {
namespaceStore.setListInNamespaceById(list)
// the returned list from listService.update is the same!
// in order to not validate vuex mutations we have to create a new copy
// in order to not create a manipulation in pinia store we have to create a new copy
const newList = {
...list,
namespaceId: FavoriteListsNamespace,

View File

@ -1,15 +1,19 @@
import {defineStore, acceptHMRUpdate} from 'pinia'
import NamespaceService from '../services/namespace'
import {setLoadingPinia} from '@/store/helper'
import {setLoadingPinia} from '@/stores/helper'
import {createNewIndexer} from '@/indexes'
import type {NamespaceState} from '@/store/types'
import type {INamespace} from '@/modelTypes/INamespace'
import type {IList} from '@/modelTypes/IList'
import {useListStore} from '@/stores/lists'
const {add, remove, search, update} = createNewIndexer('namespaces', ['title', 'description'])
export interface NamespaceState {
namespaces: INamespace[]
isLoading: boolean,
}
export const useNamespaceStore = defineStore('namespace', {
state: (): NamespaceState => ({
isLoading: false,

View File

@ -7,8 +7,7 @@ import TaskAssigneeService from '@/services/taskAssignee'
import LabelTaskService from '@/services/labelTask'
import UserService from '@/services/user'
import {HAS_TASKS} from '../store/mutation-types'
import {setLoadingPinia} from '../store/helper'
import {playPop} from '@/helpers/playPop'
import {getQuickAddMagicMode} from '@/helpers/quickAddMagicMode'
import {parseTaskText} from '@/modules/parseTaskText'
@ -24,13 +23,12 @@ import type {IUser} from '@/modelTypes/IUser'
import type {IAttachment} from '@/modelTypes/IAttachment'
import type {IList} from '@/modelTypes/IList'
import type {TaskState} from '@/store/types'
import {setLoadingPinia} from '@/stores/helper'
import {useBaseStore} from '@/stores/base'
import {useLabelStore} from '@/stores/labels'
import {useListStore} from '@/stores/lists'
import {useAttachmentStore} from '@/stores/attachments'
import {useKanbanStore} from '@/stores/kanban'
import {playPop} from '@/helpers/playPop'
import {store} from '@/store'
// IDEA: maybe use a small fuzzy search here to prevent errors
function findPropertyByValue(object, key, value) {
@ -40,10 +38,13 @@ function findPropertyByValue(object, key, value) {
}
// Check if the user exists in the search results
function validateUser(users: IUser[], username: IUser['username']) {
return findPropertyByValue(users, 'username', username) ||
findPropertyByValue(users, 'name', username) ||
findPropertyByValue(users, 'email', username)
function validateUser(
users: IUser[],
query: IUser['username'] | IUser['name'] | IUser['email'],
) {
return findPropertyByValue(users, 'username', query) ||
findPropertyByValue(users, 'name', query) ||
findPropertyByValue(users, 'email', query)
}
// Check if the label exists
@ -77,6 +78,9 @@ async function findAssignees(parsedTaskAssignees: string[]) {
return validatedUsers.filter((item) => Boolean(item))
}
export interface TaskState {
isLoading: boolean,
}
export const useTaskStore = defineStore('task', {
state: () : TaskState => ({
@ -89,7 +93,7 @@ export const useTaskStore = defineStore('task', {
const cancel = setLoadingPinia(this)
try {
const tasks = await taskService.getAll({}, params)
store.commit(HAS_TASKS, tasks.length > 0)
useBaseStore().setHasTasks(tasks.length > 0)
return tasks
} finally {
cancel()