fix: vuex mutation violation from draggable (#674)
Co-authored-by: Dominik Pschenitschni <mail@celement.de> Reviewed-on: https://kolaente.dev/vikunja/frontend/pulls/674 Reviewed-by: konrad <k@knt.li> Co-authored-by: dpschen <dpschen@noreply.kolaente.de> Co-committed-by: dpschen <dpschen@noreply.kolaente.de>
This commit is contained in:
parent
2adeb97074
commit
0a8505f53c
5 changed files with 81 additions and 37 deletions
|
@ -78,8 +78,13 @@
|
||||||
class="more-container"
|
class="more-container"
|
||||||
v-if="typeof listsVisible[n.id] !== 'undefined' ? listsVisible[n.id] : true"
|
v-if="typeof listsVisible[n.id] !== 'undefined' ? listsVisible[n.id] : true"
|
||||||
>
|
>
|
||||||
|
<!--
|
||||||
|
NOTE: a v-model / computed setter is not possible, since the updateActiveLists function
|
||||||
|
triggered by the change needs to have access to the current namespace
|
||||||
|
-->
|
||||||
<draggable
|
<draggable
|
||||||
v-model="n.lists"
|
:value="activeLists[nk]"
|
||||||
|
@input="(lists) => updateActiveLists(n, lists)"
|
||||||
:group="`namespace-${n.id}-lists`"
|
:group="`namespace-${n.id}-lists`"
|
||||||
@start="() => drag = true"
|
@start="() => drag = true"
|
||||||
@end="e => saveListPosition(e, nk)"
|
@end="e => saveListPosition(e, nk)"
|
||||||
|
@ -94,11 +99,9 @@
|
||||||
tag="ul"
|
tag="ul"
|
||||||
class="menu-list can-be-hidden"
|
class="menu-list can-be-hidden"
|
||||||
>
|
>
|
||||||
<!-- eslint-disable vue/no-use-v-if-with-v-for,vue/no-confusing-v-for-v-if -->
|
|
||||||
<li
|
<li
|
||||||
v-for="l in n.lists"
|
v-for="l in activeLists[nk]"
|
||||||
:key="l.id"
|
:key="l.id"
|
||||||
v-if="!l.isArchived"
|
|
||||||
class="loader-container"
|
class="loader-container"
|
||||||
:class="{'is-loading': listUpdating[l.id]}"
|
:class="{'is-loading': listUpdating[l.id]}"
|
||||||
>
|
>
|
||||||
|
@ -167,13 +170,18 @@ export default {
|
||||||
NamespaceSettingsDropdown,
|
NamespaceSettingsDropdown,
|
||||||
draggable,
|
draggable,
|
||||||
},
|
},
|
||||||
computed: mapState({
|
computed: {
|
||||||
namespaces: state => state.namespaces.namespaces.filter(n => !n.isArchived),
|
...mapState({
|
||||||
currentList: CURRENT_LIST,
|
namespaces: state => state.namespaces.namespaces.filter(n => !n.isArchived),
|
||||||
background: 'background',
|
currentList: CURRENT_LIST,
|
||||||
menuActive: MENU_ACTIVE,
|
background: 'background',
|
||||||
loading: state => state[LOADING] && state[LOADING_MODULE] === 'namespaces',
|
menuActive: MENU_ACTIVE,
|
||||||
}),
|
loading: state => state[LOADING] && state[LOADING_MODULE] === 'namespaces',
|
||||||
|
}),
|
||||||
|
activeLists() {
|
||||||
|
return this.namespaces.map(({lists}) => lists.filter(item => !item.isArchived))
|
||||||
|
},
|
||||||
|
},
|
||||||
beforeCreate() {
|
beforeCreate() {
|
||||||
this.$store.dispatch('namespaces/loadNamespaces')
|
this.$store.dispatch('namespaces/loadNamespaces')
|
||||||
.then(namespaces => {
|
.then(namespaces => {
|
||||||
|
@ -211,16 +219,38 @@ export default {
|
||||||
toggleLists(namespaceId) {
|
toggleLists(namespaceId) {
|
||||||
this.$set(this.listsVisible, namespaceId, !this.listsVisible[namespaceId] ?? false)
|
this.$set(this.listsVisible, namespaceId, !this.listsVisible[namespaceId] ?? false)
|
||||||
},
|
},
|
||||||
|
updateActiveLists(namespace, activeLists) {
|
||||||
|
// this is a bit hacky: since we do have to filter out the archived items from the list
|
||||||
|
// for vue draggable updating it is not as simple as replacing it.
|
||||||
|
// instead we iterate over the non archived items in the old list and replace them with the ones in their new order
|
||||||
|
const lists = namespace.lists.map((item) => {
|
||||||
|
if (item.isArchived) {
|
||||||
|
return item
|
||||||
|
}
|
||||||
|
return activeLists.shift()
|
||||||
|
})
|
||||||
|
|
||||||
|
const newNamespace = {
|
||||||
|
...namespace,
|
||||||
|
lists,
|
||||||
|
}
|
||||||
|
|
||||||
|
this.$store.commit('namespaces/setNamespaceById', newNamespace)
|
||||||
|
},
|
||||||
saveListPosition(e, namespaceIndex) {
|
saveListPosition(e, namespaceIndex) {
|
||||||
const listsFiltered = this.namespaces[namespaceIndex].lists.filter(l => !l.isArchived)
|
const listsActive = this.activeLists[namespaceIndex]
|
||||||
const list = listsFiltered[e.newIndex]
|
const list = listsActive[e.newIndex]
|
||||||
const listBefore = listsFiltered[e.newIndex - 1] ?? null
|
const listBefore = listsActive[e.newIndex - 1] ?? null
|
||||||
const listAfter = listsFiltered[e.newIndex + 1] ?? null
|
const listAfter = listsActive[e.newIndex + 1] ?? null
|
||||||
this.$set(this.listUpdating, list.id, true)
|
this.$set(this.listUpdating, list.id, true)
|
||||||
|
|
||||||
list.position = calculateItemPosition(listBefore !== null ? listBefore.position : null, listAfter !== null ? listAfter.position : null)
|
const position = calculateItemPosition(listBefore !== null ? listBefore.position : null, listAfter !== null ? listAfter.position : null)
|
||||||
|
|
||||||
this.$store.dispatch('lists/updateList', list)
|
// create a copy of the list in order to not violate vuex mutations
|
||||||
|
this.$store.dispatch('lists/updateList', {
|
||||||
|
...list,
|
||||||
|
position,
|
||||||
|
})
|
||||||
.catch(e => {
|
.catch(e => {
|
||||||
this.error(e)
|
this.error(e)
|
||||||
})
|
})
|
||||||
|
|
|
@ -39,6 +39,11 @@ export default class ListService extends AbstractService {
|
||||||
return list
|
return list
|
||||||
}
|
}
|
||||||
|
|
||||||
|
update(model) {
|
||||||
|
const newModel = { ... model }
|
||||||
|
return super.update(newModel)
|
||||||
|
}
|
||||||
|
|
||||||
background(list) {
|
background(list) {
|
||||||
if (list.background === null) {
|
if (list.background === null) {
|
||||||
return Promise.resolve('')
|
return Promise.resolve('')
|
||||||
|
|
|
@ -24,6 +24,7 @@ import ListService from '../services/list'
|
||||||
Vue.use(Vuex)
|
Vue.use(Vuex)
|
||||||
|
|
||||||
export const store = new Vuex.Store({
|
export const store = new Vuex.Store({
|
||||||
|
strict: import.meta.env.DEV,
|
||||||
modules: {
|
modules: {
|
||||||
config,
|
config,
|
||||||
auth,
|
auth,
|
||||||
|
|
|
@ -38,9 +38,10 @@ export default {
|
||||||
},
|
},
|
||||||
actions: {
|
actions: {
|
||||||
toggleListFavorite(ctx, list) {
|
toggleListFavorite(ctx, list) {
|
||||||
list.isFavorite = !list.isFavorite
|
return ctx.dispatch('updateList', {
|
||||||
|
...list,
|
||||||
return ctx.dispatch('updateList', list)
|
isFavorite: !list.isFavorite,
|
||||||
|
})
|
||||||
},
|
},
|
||||||
createList(ctx, list) {
|
createList(ctx, list) {
|
||||||
const cancel = setLoading(ctx, 'lists')
|
const cancel = setLoading(ctx, 'lists')
|
||||||
|
@ -61,24 +62,31 @@ export default {
|
||||||
const listService = new ListService()
|
const listService = new ListService()
|
||||||
|
|
||||||
return listService.update(list)
|
return listService.update(list)
|
||||||
.then(r => {
|
.then(() => {
|
||||||
ctx.commit('setList', r)
|
ctx.commit('setList', list)
|
||||||
ctx.commit('namespaces/setListInNamespaceById', r, {root: true})
|
ctx.commit('namespaces/setListInNamespaceById', list, {root: true})
|
||||||
if (r.isFavorite) {
|
|
||||||
r.namespaceId = FavoriteListsNamespace
|
// the returned list from listService.update is the same!
|
||||||
ctx.commit('namespaces/addListToNamespace', r, {root: true})
|
// in order to not validate vuex mutations we have to create a new copy
|
||||||
|
const newList = {
|
||||||
|
...list,
|
||||||
|
namespaceId: FavoriteListsNamespace,
|
||||||
|
}
|
||||||
|
if (list.isFavorite) {
|
||||||
|
ctx.commit('namespaces/addListToNamespace', newList, {root: true})
|
||||||
} else {
|
} else {
|
||||||
r.namespaceId = FavoriteListsNamespace
|
ctx.commit('namespaces/removeListFromNamespaceById', newList, {root: true})
|
||||||
ctx.commit('namespaces/removeListFromNamespaceById', r, {root: true})
|
|
||||||
}
|
}
|
||||||
ctx.dispatch('namespaces/loadNamespacesIfFavoritesDontExist', null, {root: true})
|
ctx.dispatch('namespaces/loadNamespacesIfFavoritesDontExist', null, {root: true})
|
||||||
ctx.dispatch('namespaces/removeFavoritesNamespaceIfEmpty', null, {root: true})
|
ctx.dispatch('namespaces/removeFavoritesNamespaceIfEmpty', null, {root: true})
|
||||||
return Promise.resolve(r)
|
return Promise.resolve(newList)
|
||||||
})
|
})
|
||||||
.catch(e => {
|
.catch(e => {
|
||||||
// Reset the list state to the initial one to avoid confusion for the user
|
// Reset the list state to the initial one to avoid confusion for the user
|
||||||
list.isFavorite = !list.isFavorite
|
ctx.commit('setList', {
|
||||||
ctx.commit('setList', list)
|
...list,
|
||||||
|
isFavorite: !list.isFavorite,
|
||||||
|
})
|
||||||
return Promise.reject(e)
|
return Promise.reject(e)
|
||||||
})
|
})
|
||||||
.finally(() => cancel())
|
.finally(() => cancel())
|
||||||
|
|
|
@ -13,13 +13,13 @@ export default {
|
||||||
state.namespaces = namespaces
|
state.namespaces = namespaces
|
||||||
},
|
},
|
||||||
setNamespaceById(state, namespace) {
|
setNamespaceById(state, namespace) {
|
||||||
for (const n in state.namespaces) {
|
const namespaceIndex = state.namespaces.findIndex(n => n.id === namespace.id)
|
||||||
if (state.namespaces[n].id === namespace.id) {
|
|
||||||
namespace.lists = state.namespaces[n].lists
|
if (namespaceIndex === -1) {
|
||||||
Vue.set(state.namespaces, n, namespace)
|
return
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Vue.set(state.namespaces, namespaceIndex, namespace)
|
||||||
},
|
},
|
||||||
setListInNamespaceById(state, list) {
|
setListInNamespaceById(state, list) {
|
||||||
for (const n in state.namespaces) {
|
for (const n in state.namespaces) {
|
||||||
|
|
Loading…
Reference in a new issue