1
0

feat(views): allow reordering views

Resolves https://community.vikunja.io/t/reordering-views/2394
This commit is contained in:
kolaente 2024-06-18 16:39:52 +02:00
parent 9f604eca79
commit d12deee977
No known key found for this signature in database
GPG Key ID: F40E70337AB24C9B
5 changed files with 101 additions and 47 deletions

View File

@ -401,8 +401,9 @@
"titleRequired": "Please provide a title.", "titleRequired": "Please provide a title.",
"delete": "Delete this view", "delete": "Delete this view",
"deleteText": "Are you sure you want to remove this view? It will no longer be possible to use it to view tasks in this project. This action won't delete any tasks. This cannot be undone!", "deleteText": "Are you sure you want to remove this view? It will no longer be possible to use it to view tasks in this project. This action won't delete any tasks. This cannot be undone!",
"deleteSuccess": "The view was successfully deleted", "deleteSuccess": "The view was deleted successfully.",
"onlyAdminsCanEdit": "Only project admins can edit views." "onlyAdminsCanEdit": "Only project admins can edit views.",
"updateSuccess": "The view was updated successfully."
} }
}, },
"filters": { "filters": {

View File

@ -224,11 +224,13 @@ export const useProjectStore = defineStore('project', () => {
const viewPos = projects.value[view.projectId].views.findIndex(v => v.id === view.id) const viewPos = projects.value[view.projectId].views.findIndex(v => v.id === view.id)
if (viewPos !== -1) { if (viewPos !== -1) {
projects.value[view.projectId].views[viewPos] = view projects.value[view.projectId].views[viewPos] = view
projects.value[view.projectId].views.sort((a, b) => a.position < b.position ? -1 : 1)
setProject(projects.value[view.projectId]) setProject(projects.value[view.projectId])
return return
} }
projects.value[view.projectId].views.push(view) projects.value[view.projectId].views.push(view)
projects.value[view.projectId].views.sort((a, b) => a.position < b.position ? -1 : 1)
setProject(projects.value[view.projectId]) setProject(projects.value[view.projectId])
} }

View File

@ -1,6 +1,6 @@
<script setup lang="ts"> <script setup lang="ts">
import CreateEdit from '@/components/misc/CreateEdit.vue' import CreateEdit from '@/components/misc/CreateEdit.vue'
import {watch, ref, computed} from 'vue' import {watch, ref} from 'vue'
import {useProjectStore} from '@/stores/projects' import {useProjectStore} from '@/stores/projects'
import ProjectViewModel from '@/models/projectView' import ProjectViewModel from '@/models/projectView'
import type {IProjectView} from '@/modelTypes/IProjectView' import type {IProjectView} from '@/modelTypes/IProjectView'
@ -13,6 +13,8 @@ import ProjectService from '@/services/project'
import {RIGHTS} from '@/constants/rights' import {RIGHTS} from '@/constants/rights'
import ProjectModel from '@/models/project' import ProjectModel from '@/models/project'
import Message from '@/components/misc/Message.vue' import Message from '@/components/misc/Message.vue'
import draggable from 'zhyswan-vuedraggable'
import {calculateItemPosition} from '@/helpers/calculateItemPosition'
const { const {
projectId, projectId,
@ -23,7 +25,19 @@ const {
const projectStore = useProjectStore() const projectStore = useProjectStore()
const {t} = useI18n() const {t} = useI18n()
const views = computed(() => projectStore.projects[projectId]?.views) const views = ref<IProjectView[]>([])
watch(
projectStore.projects[projectId]?.views,
allViews => {
if (!allViews) {
views.value = []
return
}
views.value = [...allViews]
},
{immediate: true},
)
const showCreateForm = ref(false) const showCreateForm = ref(false)
const projectViewService = ref(new ProjectViewService()) const projectViewService = ref(new ProjectViewService())
@ -91,6 +105,21 @@ async function saveView() {
const result = await projectViewService.value.update(viewToEdit.value) const result = await projectViewService.value.update(viewToEdit.value)
projectStore.setProjectView(result) projectStore.setProjectView(result)
viewToEdit.value = null viewToEdit.value = null
success({message: t('project.views.updateSuccess')})
}
async function saveViewPosition(e) {
const view = views.value[e.newIndex]
const viewBefore = views.value[e.newIndex - 1] ?? null
const viewAfter = views.value[e.newIndex + 1] ?? null
const position = calculateItemPosition(viewBefore !== null ? viewBefore.position : null, viewAfter !== null ? viewAfter.position : null)
const result = await projectViewService.value.update({
...view,
position,
})
projectStore.setProjectView(result)
success({message: t('project.views.updateSuccess')})
} }
</script> </script>
@ -135,11 +164,16 @@ async function saveView() {
</th> </th>
</tr> </tr>
</thead> </thead>
<tbody> <draggable
<tr v-model="views"
v-for="v in views" tag="tbody"
:key="v.id" item-key="id"
handle=".handle"
:animation="100"
@end="saveViewPosition"
> >
<template #item="{element: v}">
<tr>
<template v-if="viewToEdit !== null && viewToEdit.id === v.id"> <template v-if="viewToEdit !== null && viewToEdit.id === v.id">
<td colspan="3"> <td colspan="3">
<ViewEditForm <ViewEditForm
@ -155,7 +189,7 @@ async function saveView() {
<template v-else> <template v-else>
<td>{{ v.title }}</td> <td>{{ v.title }}</td>
<td>{{ v.viewKind }}</td> <td>{{ v.viewKind }}</td>
<td class="has-text-right"> <td class="has-text-right actions">
<XButton <XButton
v-if="isAdmin" v-if="isAdmin"
class="is-danger mr-2" class="is-danger mr-2"
@ -170,10 +204,14 @@ async function saveView() {
icon="pen" icon="pen"
@click="viewToEdit = {...v}" @click="viewToEdit = {...v}"
/> />
<span class="icon handle">
<icon icon="grip-lines" />
</span>
</td> </td>
</template> </template>
</tr> </tr>
</tbody> </template>
</draggable>
</table> </table>
</CreateEdit> </CreateEdit>
@ -191,3 +229,16 @@ async function saveView() {
</template> </template>
</modal> </modal>
</template> </template>
<style scoped>
.handle {
cursor: grab;
margin-left: .25rem;
}
.actions {
display: flex;
align-items: center;
justify-content: flex-end;
}
</style>

View File

@ -302,9 +302,7 @@ func (p *Project) ReadOne(s *xorm.Session, a web.Auth) (err error) {
return nil return nil
} }
err = s. p.Views, err = getViewsForProject(s, p.ID)
Where("project_id = ?", p.ID).
Find(&p.Views)
return return
} }
@ -640,6 +638,7 @@ func addProjectDetails(s *xorm.Session, projects []*Project, a web.Auth) (err er
views := []*ProjectView{} views := []*ProjectView{}
err = s. err = s.
In("project_id", projectIDs). In("project_id", projectIDs).
OrderBy("position asc").
Find(&views) Find(&views)
if err != nil { if err != nil {
return return

View File

@ -160,6 +160,7 @@ func getViewsForProject(s *xorm.Session, projectID int64) (views []*ProjectView,
views = []*ProjectView{} views = []*ProjectView{}
err = s. err = s.
Where("project_id = ?", projectID). Where("project_id = ?", projectID).
OrderBy("position asc").
Find(&views) Find(&views)
return return
} }