feature/projects-all-the-way-down #3323
|
@ -24,47 +24,12 @@
|
|||
:class="{'is-loading': projectUpdating[project.id]}"
|
||||
:data-project-id="project.id"
|
||||
>
|
||||
<section>
|
||||
<BaseButton
|
||||
v-if="childProjects[project.id]?.length > 0"
|
||||
@click="collapsedProjects[project.id] = !collapsedProjects[project.id]"
|
||||
class="collapse-project-button"
|
||||
>
|
||||
<icon icon="chevron-down" :class="{ 'project-is-collapsed': collapsedProjects[project.id] }"/>
|
||||
</BaseButton>
|
||||
<span class="collapse-project-button-placeholder" v-else></span>
|
||||
<BaseButton
|
||||
:to="{ name: 'project.index', params: { projectId: project.id} }"
|
||||
class="list-menu-link"
|
||||
:class="{'router-link-exact-active': currentProject.id === project.id}"
|
||||
>
|
||||
<span class="icon menu-item-icon handle">
|
||||
<icon icon="grip-lines"/>
|
||||
</span>
|
||||
<ColorBubble
|
||||
v-if="project.hexColor !== ''"
|
||||
:color="project.hexColor"
|
||||
class="mr-1"
|
||||
/>
|
||||
<span class="list-menu-title">{{ getProjectTitle(project) }}</span>
|
||||
</BaseButton>
|
||||
<BaseButton
|
||||
v-if="project.id > 0"
|
||||
class="favorite"
|
||||
:class="{'is-favorite': project.isFavorite}"
|
||||
@click="projectStore.toggleProjectFavorite(project)"
|
||||
>
|
||||
<icon :icon="project.isFavorite ? 'star' : ['far', 'star']"/>
|
||||
</BaseButton>
|
||||
<ProjectSettingsDropdown class="menu-list-dropdown" :project="project" v-if="project.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>
|
||||
</section>
|
||||
<ProjectsNavigationItem
|
||||
|
||||
:project="project"
|
||||
:can-collapse="childProjects[project.id]?.length > 0"
|
||||
:is-collapsed="collapsedProjects[project.id] || false"
|
||||
@collapse="collapsedProjects[project.id] = !collapsedProjects[project.id]"
|
||||
/>
|
||||
<ProjectsNavigation
|
||||
v-if="!collapsedProjects[project.id]"
|
||||
v-model="childProjects[project.id]"
|
||||
|
@ -76,19 +41,15 @@
|
|||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import {ref, computed, watch} from 'vue'
|
||||
import {ref, watch} 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 ProjectsNavigationItem from '@/components/home/ProjectsNavigationItem.vue'
|
||||
konrad marked this conversation as resolved
Outdated
dpschen
commented
Add types for emit Add types for emit
konrad
commented
Done Done
|
||||
|
||||
import {calculateItemPosition} from '@/helpers/calculateItemPosition'
|
||||
import {getProjectTitle} from '@/helpers/getProjectTitle'
|
||||
import type {IProject} from '@/modelTypes/IProject'
|
||||
konrad marked this conversation as resolved
Outdated
dpschen
commented
These options should either contain all dragOptions or be defined inline These options should either contain all dragOptions or be defined inline
konrad
commented
Moved it all inline. Moved it all inline.
|
||||
import ColorBubble from '@/components/misc/colorBubble.vue'
|
||||
|
||||
import {useBaseStore} from '@/stores/base'
|
||||
import {useProjectStore} from '@/stores/projects'
|
||||
|
||||
const props = defineProps<{
|
||||
|
@ -103,9 +64,7 @@ const dragOptions = {
|
|||
ghostClass: 'ghost',
|
||||
}
|
||||
|
||||
const baseStore = useBaseStore()
|
||||
const projectStore = useProjectStore()
|
||||
const currentProject = computed(() => baseStore.currentProject)
|
||||
|
||||
dpschen marked this conversation as resolved
Outdated
|
||||
// Vue draggable will modify the projects list as it changes their position which will not work on a prop.
|
||||
dpschen
commented
Also don't render if there are no child projects. Also don't render if there are no child projects.
konrad
commented
But then it won't be possible to drag a project "under" a parent to make it a child of that parent. But then it won't be possible to drag a project "under" a parent to make it a child of that parent.
|
||||
// Hence, we'll clone the prop and work on the clone.
|
||||
|
@ -170,29 +129,3 @@ async function saveProjectPosition(e: SortableEvent) {
|
|||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.list-setting-spacer {
|
||||
width: 5rem;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.project-is-collapsed {
|
||||
transform: rotate(-90deg);
|
||||
}
|
||||
|
||||
.favorite {
|
||||
transition: opacity $transition, color $transition;
|
||||
opacity: 0;
|
||||
|
||||
&:hover,
|
||||
&.is-favorite {
|
||||
opacity: 1;
|
||||
color: var(--warning);
|
||||
}
|
||||
}
|
||||
|
||||
.list-menu:hover > section > .favorite {
|
||||
opacity: 1;
|
||||
}
|
||||
</style>
|
94
src/components/home/ProjectsNavigationItem.vue
Normal file
|
@ -0,0 +1,94 @@
|
|||
<template>
|
||||
<section>
|
||||
<BaseButton
|
||||
v-if="canCollapse"
|
||||
@click="emit('collapse')"
|
||||
dpschen marked this conversation as resolved
Outdated
dpschen
commented
Set from outside, since this id is related to the sorting. Set from outside, since this id is related to the sorting.
konrad
commented
Done Done
|
||||
class="collapse-project-button"
|
||||
>
|
||||
dpschen marked this conversation as resolved
Outdated
dpschen
commented
Replace section with We'll add correct semantics here later (e.g. https://www.w3.org/WAI/ARIA/apg/patterns/menubar/examples/menubar-navigation/). Replace section with `<div>`.
We'll add correct semantics here later (e.g. https://www.w3.org/WAI/ARIA/apg/patterns/menubar/examples/menubar-navigation/). `<section>` is not correct though, since there is no headline.
konrad
commented
Done Done
|
||||
<icon icon="chevron-down" :class="{ 'project-is-collapsed': isCollapsed }"/>
|
||||
</BaseButton>
|
||||
<span class="collapse-project-button-placeholder" v-else></span>
|
||||
<BaseButton
|
||||
:to="{ name: 'project.index', params: { projectId: project.id} }"
|
||||
class="list-menu-link"
|
||||
:class="{'router-link-exact-active': currentProject.id === project.id}"
|
||||
>
|
||||
<span class="icon menu-item-icon handle">
|
||||
<icon icon="grip-lines"/>
|
||||
</span>
|
||||
<ColorBubble
|
||||
v-if="project.hexColor !== ''"
|
||||
:color="project.hexColor"
|
||||
dpschen
commented
Fix indention Fix indention
dpschen
commented
Pass 'handle class name from parent => separate concerns / source of truth Pass 'handle class name from parent => separate concerns / source of truth
konrad
commented
But the indention is correct? But the indention is correct?
konrad
commented
Can you explain that a little more? > Pass 'handle class name from parent => separate concerns / source of truth
Can you explain that a little more?
dpschen
commented
The The `handle` selector is used in the child. Currently we define it in the parent. We should pass this information down to the child. Might also be via creating a slot in the child where we put in the handle.
|
||||
class="mr-1"
|
||||
/>
|
||||
<span class="list-menu-title">{{ getProjectTitle(project) }}</span>
|
||||
</BaseButton>
|
||||
<BaseButton
|
||||
v-if="project.id > 0"
|
||||
class="favorite"
|
||||
:class="{'is-favorite': project.isFavorite}"
|
||||
@click="projectStore.toggleProjectFavorite(project)"
|
||||
>
|
||||
<icon :icon="project.isFavorite ? 'star' : ['far', 'star']"/>
|
||||
</BaseButton>
|
||||
<ProjectSettingsDropdown class="menu-list-dropdown" :project="project" v-if="project.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>
|
||||
</section>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import {computed} from 'vue'
|
||||
import {useProjectStore} from '@/stores/projects'
|
||||
import {useBaseStore} from '@/stores/base'
|
||||
|
||||
dpschen
commented
Use Use `Expandable` component for this.
konrad
commented
This seems to completly break the styling. I changed it to match the selectors but it still does not work. Not sure what to do about this.- This seems to completly break the styling. I changed it to match the selectors but it still does not work. Not sure what to do about this.-
dpschen
commented
I created an example how to use this in I created an example how to use this in https://kolaente.dev/vikunja/frontend/commit/51e29af010defc5f6c46f85dbb7311904a7d40e1. I was unsure which parts parts should be dynamically be filled (via the `open` prop) or static (not rendering the `Expandable` at all via `v-if`).
|
||||
import type {IProject} from '@/modelTypes/IProject'
|
||||
|
||||
import BaseButton from '@/components/base/BaseButton.vue'
|
||||
import ProjectSettingsDropdown from '@/components/project/project-settings-dropdown.vue'
|
||||
import {getProjectTitle} from '@/helpers/getProjectTitle'
|
||||
import ColorBubble from '@/components/misc/colorBubble.vue'
|
||||
|
||||
defineProps<{
|
||||
project: IProject,
|
||||
isCollapsed: boolean,
|
||||
canCollapse: boolean,
|
||||
}>()
|
||||
|
||||
const emit = defineEmits(['collapse'])
|
||||
|
||||
const projectStore = useProjectStore()
|
||||
const baseStore = useBaseStore()
|
||||
const currentProject = computed(() => baseStore.currentProject)
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
dpschen
commented
Define default types or handle undefined defaults. E.g. Define default types or handle undefined defaults. E.g. `level` might be `undefined`.
Could it be that ts doesn't display errors in the editor for you?
konrad
commented
Added.
Looks like it 🤔 Added.
> Could it be that ts doesn't display errors in the editor for you?
Looks like it 🤔
|
||||
.list-setting-spacer {
|
||||
width: 5rem;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.project-is-collapsed {
|
||||
transform: rotate(-90deg);
|
||||
}
|
||||
|
||||
.favorite {
|
||||
transition: opacity $transition, color $transition;
|
||||
opacity: 0;
|
||||
|
||||
&:hover,
|
||||
&.is-favorite {
|
||||
opacity: 1;
|
||||
color: var(--warning);
|
||||
}
|
||||
}
|
||||
|
||||
.list-menu:hover > section > .favorite {
|
||||
opacity: 1;
|
||||
}
|
||||
</style>
|
||||
dpschen
commented
Simplify:
Simplify:
```ts
const canNestDeeper = computed(() => props.level >= 2 && window.PROJECT_INFINITE_NESTING_ENABLED)
```
konrad
commented
But that would return But that would return `false` for the first two levels?
|
Since this block doesn't have a headline it shouldn't be a
<section>
. Maybe use<nav>
instead (nesting is allowed!)Move this whole block in a new
ProjectNavigationItem.vue
component. Reduces also the whole complexity withchildProjects[p.id]
because we can pass only the project.Done
Shouldn't a
nav
hold multiple navigation items?Yes! Sry I misread the position, where the
<section>
is.Okay you moved now only the item without the list below inside.
What i meant was:
<li>
insideProjectsNavigationItem.vue
.ProjectsNavigation.vue
is then used inside ProjectsNavigationItemThis whole block can then be simplified:
Because we can save the collapsed state inside each item we don't need to manage a list anymore.
So we don't even need the
<section>
then and can instead use the<li>
.It's totally fine to not group the buttons etc because they are already grouped by the
<li>
they are in. TheProjectsNavigation
component would be the last child insieProjectsNavigationItem
That makes sense. I've moved most of the logic over, as you suggested.
We actually need this (or another element) because the
section
is a flexbox container for the project title and related buttons. We can't use theli
as the flexbox container because the ProjectsNavigation for the child projects needs to stay below the project title etc. If it was in the same flexbox container it would get pushed to the right.