feat: allow creating a new project directly as a child project from another one

This commit is contained in:
kolaente 2023-04-14 18:18:03 +02:00
parent 9c3259c660
commit b34118485c
Signed by: konrad
GPG Key ID: F40E70337AB24C9B
6 changed files with 55 additions and 14 deletions

View File

@ -42,7 +42,12 @@
> >
<icon :icon="project.isFavorite ? 'star' : ['far', 'star']"/> <icon :icon="project.isFavorite ? 'star' : ['far', 'star']"/>
</BaseButton> </BaseButton>
<ProjectSettingsDropdown class="menu-list-dropdown" :project="project" v-if="project.id > 0"> <ProjectSettingsDropdown
v-if="project.id > 0"
class="menu-list-dropdown"
:project="project"
:level="level"
>
<template #trigger="{toggleOpen}"> <template #trigger="{toggleOpen}">
<BaseButton class="menu-list-dropdown-trigger" @click="toggleOpen"> <BaseButton class="menu-list-dropdown-trigger" @click="toggleOpen">
<icon icon="ellipsis-h" class="icon"/> <icon icon="ellipsis-h" class="icon"/>
@ -73,6 +78,7 @@ import ProjectSettingsDropdown from '@/components/project/project-settings-dropd
import {getProjectTitle} from '@/helpers/getProjectTitle' import {getProjectTitle} from '@/helpers/getProjectTitle'
import ColorBubble from '@/components/misc/colorBubble.vue' import ColorBubble from '@/components/misc/colorBubble.vue'
import ProjectsNavigation from '@/components/home/ProjectsNavigation.vue' import ProjectsNavigation from '@/components/home/ProjectsNavigation.vue'
import {canNestProjectDeeper} from '@/helpers/canNestProjectDeeper'
const props = withDefaults(defineProps<{ const props = withDefaults(defineProps<{
project: IProject, project: IProject,
@ -98,13 +104,7 @@ const childProjects = computed(() => {
.sort((a, b) => a.position - b.position) .sort((a, b) => a.position - b.position)
}) })
const canNestDeeper = computed(() => { const canNestDeeper = computed(() => canNestProjectDeeper(props.level))
if (props.level < 2) {
return true
}
return props.level >= 2 && window.PROJECT_INFINITE_NESTING_ENABLED
})
</script> </script>
<style lang="scss" scoped> <style lang="scss" scoped>

View File

@ -72,6 +72,13 @@
@update:model-value="setSubscriptionInStore" @update:model-value="setSubscriptionInStore"
type="dropdown" type="dropdown"
/> />
<dropdown-item
v-if="level < 2"
:to="{ name: 'project.createFromParent', params: { parentProjectId: project.id } }"
icon="layer-group"
>
{{ $t('menu.createProject') }}
</dropdown-item>
<dropdown-item <dropdown-item
:to="{ name: 'project.settings.delete', params: { projectId: project.id } }" :to="{ name: 'project.settings.delete', params: { projectId: project.id } }"
icon="trash-alt" icon="trash-alt"
@ -102,6 +109,9 @@ const props = defineProps({
type: Object as PropType<IProject>, type: Object as PropType<IProject>,
required: true, required: true,
}, },
level: {
type: Number,
},
}) })
const projectStore = useProjectStore() const projectStore = useProjectStore()

View File

@ -0,0 +1,7 @@
export function canNestProjectDeeper(level: number) {
if (level < 2) {
return true
}
return level >= 2 && window.PROJECT_INFINITE_NESTING_ENABLED
}

View File

@ -864,7 +864,8 @@
"unarchive": "Un-Archive", "unarchive": "Un-Archive",
"setBackground": "Set background", "setBackground": "Set background",
"share": "Share", "share": "Share",
"newProject": "New project" "newProject": "New project",
"createProject": "Create project"
}, },
"apiConfig": { "apiConfig": {
"url": "Vikunja URL", "url": "Vikunja URL",

View File

@ -237,6 +237,15 @@ const router = createRouter({
showAsModal: true, showAsModal: true,
}, },
}, },
{
path: '/projects/:parentProjectId/new',
name: 'project.createFromParent',
component: NewProjectComponent,
props: route => ({ parentProjectId: Number(route.params.parentProjectId as string) }),
meta: {
showAsModal: true,
},
},
{ {
path: '/projects/:projectId/settings/edit', path: '/projects/:projectId/settings/edit',
name: 'project.settings.edit', name: 'project.settings.edit',

View File

@ -1,5 +1,9 @@
<template> <template>
<create-edit :title="$t('project.create.header')" @create="createNewProject()" :primary-disabled="project.title === ''"> <create-edit
:title="$t('project.create.header')"
@create="createNewProject()"
:primary-disabled="project.title === ''"
>
<div class="field"> <div class="field">
<label class="label" for="projectTitle">{{ $t('project.title') }}</label> <label class="label" for="projectTitle">{{ $t('project.title') }}</label>
<div <div
@ -31,14 +35,14 @@
<div class="field"> <div class="field">
<label class="label">{{ $t('project.color') }}</label> <label class="label">{{ $t('project.color') }}</label>
<div class="control"> <div class="control">
<color-picker v-model="project.hexColor" /> <color-picker v-model="project.hexColor"/>
</div> </div>
</div> </div>
</create-edit> </create-edit>
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import {ref, reactive, shallowReactive} from 'vue' import {ref, reactive, shallowReactive, watch} from 'vue'
import {useI18n} from 'vue-i18n' import {useI18n} from 'vue-i18n'
import {useRouter, useRoute} from 'vue-router' import {useRouter, useRoute} from 'vue-router'
@ -65,18 +69,28 @@ const projectService = shallowReactive(new ProjectService())
const projectStore = useProjectStore() const projectStore = useProjectStore()
const parentProject = ref<IProject | null>(null) const parentProject = ref<IProject | null>(null)
const props = defineProps<{
parentProjectId?: number,
}>()
watch(
() => props.parentProjectId,
() => parentProject.value = projectStore.projects[props.parentProjectId],
{immediate: true},
)
async function createNewProject() { async function createNewProject() {
if (project.title === '') { if (project.title === '') {
showError.value = true showError.value = true
return return
} }
showError.value = false showError.value = false
if (parentProject.value) { if (parentProject.value) {
project.parentProjectId = parentProject.value.id project.parentProjectId = parentProject.value.id
} }
await projectStore.createProject(project) await projectStore.createProject(project)
success({message: t('project.create.createdSuccess') }) success({message: t('project.create.createdSuccess')})
} }
</script> </script>