feat: rebuild main navigation so that it works recursively with projects
This commit is contained in:
parent
10311b79df
commit
06e8cdb9d2
156
src/components/home/ProjectsNavigation.vue
Normal file
156
src/components/home/ProjectsNavigation.vue
Normal file
@ -0,0 +1,156 @@
|
||||
<template>
|
||||
<!-- v-if="projectsVisible[n.id] ?? true"-->
|
||||
<!-- :disabled="n.id < 0 || undefined"-->
|
||||
<!-- :modelValue="p"-->
|
||||
<!-- @update:modelValue="(projects) => updateActiveProjects(n, projects)"-->
|
||||
<!-- v-for="(p, pk) in projects"-->
|
||||
<!-- :key="p.id"-->
|
||||
<!-- :data-project-id="p.id"-->
|
||||
<!-- :data-project-index="pk"-->
|
||||
<draggable
|
||||
v-model="availableProjects"
|
||||
v-bind="dragOptions"
|
||||
group="namespace-lists"
|
||||
@start="() => drag = true"
|
||||
@end="saveProjectPosition"
|
||||
handle=".handle"
|
||||
tag="ul"
|
||||
item-key="id"
|
||||
:component-data="{
|
||||
type: 'transition-group',
|
||||
name: !drag ? 'flip-list' : null,
|
||||
class: [
|
||||
'menu-list can-be-hidden',
|
||||
{ 'dragging-disabled': false }
|
||||
]
|
||||
}"
|
||||
>
|
||||
<template #item="{element: p}">
|
||||
<li
|
||||
class="list-menu loader-container is-loading-small"
|
||||
:class="{'is-loading': projectUpdating[p.id]}"
|
||||
>
|
||||
<BaseButton
|
||||
:to="{ name: 'project.index', params: { projectId: p.id} }"
|
||||
class="list-menu-link"
|
||||
:class="{'router-link-exact-active': currentProject.id === p.id}"
|
||||
>
|
||||
<span class="icon menu-item-icon handle">
|
||||
<icon icon="grip-lines"/>
|
||||
</span>
|
||||
<ColorBubble
|
||||
v-if="p.hexColor !== ''"
|
||||
:color="p.hexColor"
|
||||
class="mr-1"
|
||||
/>
|
||||
<span class="list-menu-title">{{ getProjectTitle(p) }}</span>
|
||||
</BaseButton>
|
||||
<BaseButton
|
||||
v-if="p.id > 0"
|
||||
class="favorite"
|
||||
:class="{'is-favorite': p.isFavorite}"
|
||||
@click="projectStore.toggleProjectFavorite(l)"
|
||||
>
|
||||
<icon :icon="p.isFavorite ? 'star' : ['far', 'star']"/>
|
||||
</BaseButton>
|
||||
<ProjectSettingsDropdown class="menu-list-dropdown" :project="p" v-if="p.id > 0">
|
||||
<template #trigger="{toggleOpen}">
|
||||
<BaseButton class="menu-list-dropdown-trigger" @click="toggleOpen">
|
||||
<icon icon="ellipsis-h" class="icon"/>
|
||||
</BaseButton>
|
||||
</template>
|
||||
</ProjectSettingsDropdown>
|
||||
<span class="list-setting-spacer" v-else></span>
|
||||
</li>
|
||||
</template>
|
||||
</draggable>
|
||||
|
||||
<!-- <ProjectsNavigation-->
|
||||
<!-- v-if="projects.childProjects.length > 0"-->
|
||||
<!-- :projects="projects.childProjects"-->
|
||||
<!-- />-->
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import {ref, computed, watch, onActivated} from 'vue'
|
||||
import draggable from 'zhyswan-vuedraggable'
|
||||
import type {SortableEvent} from 'sortablejs'
|
||||
|
||||
import BaseButton from '@/components/base/BaseButton.vue'
|
||||
import ProjectSettingsDropdown from '@/components/project/project-settings-dropdown.vue'
|
||||
|
||||
import {calculateItemPosition} from '@/helpers/calculateItemPosition'
|
||||
import {getProjectTitle} from '@/helpers/getProjectTitle'
|
||||
import type {IProject} from '@/modelTypes/IProject'
|
||||
import ColorBubble from '@/components/misc/colorBubble.vue'
|
||||
|
||||
import {useBaseStore} from '@/stores/base'
|
||||
import {useProjectStore} from '@/stores/projects'
|
||||
|
||||
const props = defineProps<{
|
||||
projects: IProject[],
|
||||
}>()
|
||||
const drag = ref(false)
|
||||
const dragOptions = {
|
||||
animation: 100,
|
||||
ghostClass: 'ghost',
|
||||
}
|
||||
|
||||
const baseStore = useBaseStore()
|
||||
const projectStore = useProjectStore()
|
||||
const currentProject = computed(() => baseStore.currentProject)
|
||||
|
||||
// Vue draggable will modify the projects list as it changes their position which will not work on a prop.
|
||||
// Hence, we'll clone the prop and work on the clone.
|
||||
// FIXME: cloning does not work when loading the page initially
|
||||
// TODO: child projects
|
||||
const availableProjects = ref<IProject[]>([])
|
||||
watch(
|
||||
props.projects,
|
||||
projects => {
|
||||
availableProjects.value = projects
|
||||
},
|
||||
{immediate: true},
|
||||
)
|
||||
onActivated(() => availableProjects.value = props.projects)
|
||||
|
||||
const projectUpdating = ref<{ [id: IProject['id']]: boolean }>({})
|
||||
|
||||
async function saveProjectPosition(e: SortableEvent) {
|
||||
if (!e.newIndex && e.newIndex !== 0) return
|
||||
|
||||
const projectsActive = availableProjects.value
|
||||
// If the project was dragged to the last position, Safari will report e.newIndex as the size of the projectsActive
|
||||
// array instead of using the position. Because the index is wrong in that case, dragging the project will fail.
|
||||
// To work around that we're explicitly checking that case here and decrease the index.
|
||||
const newIndex = e.newIndex === projectsActive.length ? e.newIndex - 1 : e.newIndex
|
||||
|
||||
const project = projectsActive[newIndex]
|
||||
const projectBefore = projectsActive[newIndex - 1] ?? null
|
||||
const projectAfter = projectsActive[newIndex + 1] ?? null
|
||||
projectUpdating.value[project.id] = true
|
||||
|
||||
const position = calculateItemPosition(
|
||||
projectBefore !== null ? projectBefore.position : null,
|
||||
projectAfter !== null ? projectAfter.position : null,
|
||||
)
|
||||
|
||||
console.log({
|
||||
position,
|
||||
newIndex,
|
||||
project: project.id,
|
||||
projectBefore: projectBefore?.id,
|
||||
projectAfter: projectAfter?.id,
|
||||
})
|
||||
|
||||
try {
|
||||
// create a copy of the project in order to not violate pinia manipulation
|
||||
await projectStore.updateProject({
|
||||
...project,
|
||||
position,
|
||||
})
|
||||
} finally {
|
||||
projectUpdating.value[project.id] = false
|
||||
}
|
||||
}
|
||||
</script>
|
@ -49,9 +49,7 @@
|
||||
</nav>
|
||||
|
||||
<nav>
|
||||
<template v-for="(p, pk) in projects" :key="p.id">
|
||||
{{ p. title }}<br/>
|
||||
</template>
|
||||
<ProjectsNavigation :projects="projects"/>
|
||||
</nav>
|
||||
|
||||
<!-- <nav class="menu namespaces-lists loader-container is-loading-small" :class="{'is-loading': loading}">-->
|
||||
@ -84,68 +82,7 @@
|
||||
<!-- NOTE: a v-model / computed setter is not possible, since the updateActiveProjects function-->
|
||||
<!-- triggered by the change needs to have access to the current namespace-->
|
||||
<!-- –>-->
|
||||
<!-- <draggable-->
|
||||
<!-- v-if="projectsVisible[n.id] ?? true"-->
|
||||
<!-- v-bind="dragOptions"-->
|
||||
<!-- :modelValue="activeProjects[nk]"-->
|
||||
<!-- @update:modelValue="(projects) => updateActiveProjects(n, projects)"-->
|
||||
<!-- group="namespace-lists"-->
|
||||
<!-- @start="() => drag = true"-->
|
||||
<!-- @end="saveListPosition"-->
|
||||
<!-- handle=".handle"-->
|
||||
<!-- :disabled="n.id < 0 || undefined"-->
|
||||
<!-- tag="ul"-->
|
||||
<!-- item-key="id"-->
|
||||
<!-- :data-namespace-id="n.id"-->
|
||||
<!-- :data-namespace-index="nk"-->
|
||||
<!-- :component-data="{-->
|
||||
<!-- type: 'transition-group',-->
|
||||
<!-- name: !drag ? 'flip-list' : null,-->
|
||||
<!-- class: [-->
|
||||
<!-- 'menu-list can-be-hidden',-->
|
||||
<!-- { 'dragging-disabled': n.id < 0 }-->
|
||||
<!-- ]-->
|
||||
<!-- }"-->
|
||||
<!-- >-->
|
||||
<!-- <template #item="{element: l}">-->
|
||||
<!-- <li-->
|
||||
<!-- class="list-menu loader-container is-loading-small"-->
|
||||
<!-- :class="{'is-loading': projectUpdating[l.id]}"-->
|
||||
<!-- >-->
|
||||
<!-- <BaseButton-->
|
||||
<!-- :to="{ name: 'project.index', params: { projectId: l.id} }"-->
|
||||
<!-- class="list-menu-link"-->
|
||||
<!-- :class="{'router-link-exact-active': currentProject.id === l.id}"-->
|
||||
<!-- >-->
|
||||
<!-- <span class="icon menu-item-icon handle">-->
|
||||
<!-- <icon icon="grip-lines"/>-->
|
||||
<!-- </span>-->
|
||||
<!-- <ColorBubble-->
|
||||
<!-- v-if="l.hexColor !== ''"-->
|
||||
<!-- :color="l.hexColor"-->
|
||||
<!-- class="mr-1"-->
|
||||
<!-- />-->
|
||||
<!-- <span class="list-menu-title">{{ getProjectTitle(l) }}</span>-->
|
||||
<!-- </BaseButton>-->
|
||||
<!-- <BaseButton-->
|
||||
<!-- v-if="l.id > 0"-->
|
||||
<!-- class="favorite"-->
|
||||
<!-- :class="{'is-favorite': l.isFavorite}"-->
|
||||
<!-- @click="projectStore.toggleProjectFavorite(l)"-->
|
||||
<!-- >-->
|
||||
<!-- <icon :icon="l.isFavorite ? 'star' : ['far', 'star']"/>-->
|
||||
<!-- </BaseButton>-->
|
||||
<!-- <ProjectSettingsDropdown class="menu-list-dropdown" :project="l" v-if="l.id > 0">-->
|
||||
<!-- <template #trigger="{toggleOpen}">-->
|
||||
<!-- <BaseButton class="menu-list-dropdown-trigger" @click="toggleOpen">-->
|
||||
<!-- <icon icon="ellipsis-h" class="icon"/>-->
|
||||
<!-- </BaseButton>-->
|
||||
<!-- </template>-->
|
||||
<!-- </ProjectSettingsDropdown>-->
|
||||
<!-- <span class="list-setting-spacer" v-else></span>-->
|
||||
<!-- </li>-->
|
||||
<!-- </template>-->
|
||||
<!-- </draggable>-->
|
||||
|
||||
<!-- </template>-->
|
||||
<!-- </nav>-->
|
||||
<PoweredByLink/>
|
||||
@ -170,15 +107,10 @@ import ColorBubble from '@/components/misc/colorBubble.vue'
|
||||
|
||||
import {useBaseStore} from '@/stores/base'
|
||||
import {useProjectStore} from '@/stores/projects'
|
||||
import ProjectsNavigation from '@/components/home/ProjectsNavigation.vue'
|
||||
|
||||
const drag = ref(false)
|
||||
const dragOptions = {
|
||||
animation: 100,
|
||||
ghostClass: 'ghost',
|
||||
}
|
||||
|
||||
const baseStore = useBaseStore()
|
||||
const currentProject = computed(() => baseStore.currentProject)
|
||||
const menuActive = computed(() => baseStore.menuActive)
|
||||
const loading = computed(() => namespaceStore.isLoading)
|
||||
|
||||
@ -214,7 +146,7 @@ onBeforeMount(async () => {
|
||||
await projectStore.loadProjects()
|
||||
})
|
||||
|
||||
const projects = computed(() => projectStore.projects)
|
||||
const projects = computed(() => Object.values(projectStore.projects).sort((a, b) => a.position < b.position ? -1 : 1))
|
||||
|
||||
function updateActiveProjects(namespace: INamespace, activeProjects: IProject[]) {
|
||||
// This is a bit hacky: since we do have to filter out the archived items from the list
|
||||
@ -233,41 +165,7 @@ function updateActiveProjects(namespace: INamespace, activeProjects: IProject[])
|
||||
})
|
||||
}
|
||||
|
||||
const projectUpdating = ref<{ [id: INamespace['id']]: boolean }>({})
|
||||
|
||||
async function saveProjectPosition(e: SortableEvent) {
|
||||
if (!e.newIndex && e.newIndex !== 0) return
|
||||
|
||||
const namespaceId = parseInt(e.to.dataset.namespaceId as string)
|
||||
const newNamespaceIndex = parseInt(e.to.dataset.namespaceIndex as string)
|
||||
|
||||
const projectsActive = activeProjects.value[newNamespaceIndex]
|
||||
// If the project was dragged to the last position, Safari will report e.newIndex as the size of the projectsActive
|
||||
// array instead of using the position. Because the index is wrong in that case, dragging the project will fail.
|
||||
// To work around that we're explicitly checking that case here and decrease the index.
|
||||
const newIndex = e.newIndex === projectsActive.length ? e.newIndex - 1 : e.newIndex
|
||||
|
||||
const project = projectsActive[newIndex]
|
||||
const projectBefore = projectsActive[newIndex - 1] ?? null
|
||||
const projectAfter = projectsActive[newIndex + 1] ?? null
|
||||
projectUpdating.value[project.id] = true
|
||||
|
||||
const position = calculateItemPosition(
|
||||
projectBefore !== null ? projectBefore.position : null,
|
||||
projectAfter !== null ? projectAfter.position : null,
|
||||
)
|
||||
|
||||
try {
|
||||
// create a copy of the project in order to not violate pinia manipulation
|
||||
await projectStore.updateProject({
|
||||
...project,
|
||||
position,
|
||||
namespaceId,
|
||||
})
|
||||
} finally {
|
||||
projectUpdating.value[project.id] = false
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
|
@ -18,6 +18,7 @@ export interface IProject extends IAbstract {
|
||||
subscription: ISubscription
|
||||
position: number
|
||||
backgroundBlurHash: string
|
||||
childProjects: IProject[] | null
|
||||
|
||||
created: Date
|
||||
updated: Date
|
||||
|
@ -115,11 +115,9 @@ export const useProjectStore = defineStore('project', () => {
|
||||
|
||||
// the returned project from projectService.update is the same!
|
||||
// in order to not create a manipulation in pinia store we have to create a new copy
|
||||
const newProject = {
|
||||
return {
|
||||
...project,
|
||||
}
|
||||
|
||||
return newProject
|
||||
} catch (e) {
|
||||
// Reset the project state to the initial one to avoid confusion for the user
|
||||
setProject({
|
||||
|
Loading…
x
Reference in New Issue
Block a user