1
0

feat: remove all namespace leftovers

This commit is contained in:
kolaente
2023-03-25 14:54:20 +01:00
parent a5e710bfe5
commit 1bd17d6e50
47 changed files with 44 additions and 1380 deletions

View File

@ -17,22 +17,11 @@
@taskAdded="updateTaskKey"
class="is-max-width-desktop"
/>
<template v-if="!hasTasks && !loading">
<template v-if="defaultNamespaceId > 0">
<p class="mt-4">{{ $t('home.project.newText') }}</p>
<x-button
:to="{ name: 'project.create', params: { namespaceId: defaultNamespaceId } }"
:shadow="false"
class="ml-2"
>
{{ $t('home.project.new') }}
</x-button>
</template>
<p class="mt-4" v-if="migratorsEnabled">
<template v-if="!hasTasks && !loading && migratorsEnabled">
<p class="mt-4">
{{ $t('home.project.importText') }}
</p>
<x-button
v-if="migratorsEnabled"
:to="{ name: 'migrate.start' }"
:shadow="false">
{{ $t('home.project.import') }}
@ -66,7 +55,6 @@ import {useDaytimeSalutation} from '@/composables/useDaytimeSalutation'
import {useBaseStore} from '@/stores/base'
import {useProjectStore} from '@/stores/projects'
import {useConfigStore} from '@/stores/config'
import {useNamespaceStore} from '@/stores/namespaces'
import {useAuthStore} from '@/stores/auth'
import {useTaskStore} from '@/stores/tasks'
import type {IProject} from '@/modelTypes/IProject'
@ -76,7 +64,6 @@ const salutation = useDaytimeSalutation()
const baseStore = useBaseStore()
const authStore = useAuthStore()
const configStore = useConfigStore()
const namespaceStore = useNamespaceStore()
const projectStore = useProjectStore()
const taskStore = useTaskStore()
@ -93,8 +80,7 @@ const projectHistory = computed(() => {
const migratorsEnabled = computed(() => configStore.availableMigrators?.length > 0)
const hasTasks = computed(() => baseStore.hasTasks)
const defaultNamespaceId = computed(() => namespaceStore.namespaces?.[0]?.id || 0)
const hasProjects = computed(() => namespaceStore.namespaces?.[0]?.projects.length > 0)
const hasProjects = computed(() => projectStore.projects.length > 0)
const loading = computed(() => taskStore.isLoading)
const deletionScheduledAt = computed(() => parseDateOrNull(authStore.info?.deletionScheduledAt))

View File

@ -89,8 +89,8 @@ import {formatDateLong} from '@/helpers/time/formatDate'
import {parseDateOrNull} from '@/helpers/parseDateOrNull'
import {MIGRATORS} from './migrators'
import {useNamespaceStore} from '@/stores/namespaces'
import {useTitle} from '@/composables/useTitle'
import {useProjectStore} from '@/stores/projects'
const PROGRESS_DOTS_COUNT = 8
@ -163,8 +163,8 @@ async function migrate() {
? await migrationFileService.migrate(migrationConfig as File)
: await migrationService.migrate(migrationConfig as MigrationConfig)
message.value = result.message
const namespaceStore = useNamespaceStore()
return namespaceStore.loadNamespaces()
const projectStore = useProjectStore()
return projectStore.loadProjects()
} finally {
isMigrating.value = false
}

View File

@ -1,84 +0,0 @@
<template>
<create-edit
:title="$t('namespace.create.title')"
@create="newNamespace()"
:primary-disabled="namespace.title === ''"
>
<div class="field">
<label class="label" for="namespaceTitle">{{ $t('namespace.attributes.title') }}</label>
<div
class="control is-expanded"
:class="{ 'is-loading': namespaceService.loading }"
>
<!-- The user should be able to close the modal by pressing escape - that already works with the default modal.
But with the input modal here since it autofocuses the input that input field catches the focus instead.
Hence we place the listener on the input field directly. -->
<input
@keyup.enter="newNamespace()"
@keyup.esc="$router.back()"
class="input"
:placeholder="$t('namespace.attributes.titlePlaceholder')"
type="text"
:class="{ disabled: namespaceService.loading }"
v-focus
v-model="namespace.title"
/>
</div>
</div>
<p class="help is-danger" v-if="showError && namespace.title === ''">
{{ $t('namespace.create.titleRequired') }}
</p>
<div class="field">
<label class="label">{{ $t('namespace.attributes.color') }}</label>
<div class="control">
<color-picker v-model="namespace.hexColor"/>
</div>
</div>
<message class="mt-4">
<h4 class="title">{{ $t('namespace.create.tooltip') }}</h4>
{{ $t('namespace.create.explanation') }}
</message>
</create-edit>
</template>
<script setup lang="ts">
import {ref, shallowReactive} from 'vue'
import {useI18n} from 'vue-i18n'
import {useRouter} from 'vue-router'
import Message from '@/components/misc/message.vue'
import CreateEdit from '@/components/misc/create-edit.vue'
import ColorPicker from '@/components/input/ColorPicker.vue'
import NamespaceModel from '@/models/namespace'
import NamespaceService from '@/services/namespace'
import {useNamespaceStore} from '@/stores/namespaces'
import type {INamespace} from '@/modelTypes/INamespace'
import {useTitle} from '@/composables/useTitle'
import {success} from '@/message'
const showError = ref(false)
const namespace = ref<INamespace>(new NamespaceModel())
const namespaceService = shallowReactive(new NamespaceService())
const {t} = useI18n({useScope: 'global'})
const router = useRouter()
useTitle(() => t('namespace.create.title'))
async function newNamespace() {
if (namespace.value.title === '') {
showError.value = true
return
}
showError.value = false
const newNamespace = await namespaceService.create(namespace.value)
useNamespaceStore().addNamespace(newNamespace)
success({message: t('namespace.create.success')})
router.back()
}
</script>

View File

@ -1,89 +0,0 @@
<template>
<modal
@close="$router.back()"
@submit="archiveNamespace()"
>
<template #header><span>{{ title }}</span></template>
<template #text>
<p>
{{
namespace.isArchived
? $t('namespace.archive.unarchiveText')
: $t('namespace.archive.archiveText')
}}
</p>
</template>
</modal>
</template>
<script lang="ts">
export default { name: 'namespace-setting-archive' }
</script>
<script setup lang="ts">
import {watch, ref, computed, shallowReactive, type PropType} from 'vue'
import {useRouter} from 'vue-router'
import {useI18n} from 'vue-i18n'
import {success} from '@/message'
import {useTitle} from '@/composables/useTitle'
import {useNamespaceStore} from '@/stores/namespaces'
import NamespaceService from '@/services/namespace'
import NamespaceModel from '@/models/namespace'
import type {INamespace} from '@/modelTypes/INamespace'
const props = defineProps({
namespaceId: {
type: Number as PropType<INamespace['id']>,
required: true,
},
})
const router = useRouter()
const {t} = useI18n({useScope: 'global'})
const namespaceStore = useNamespaceStore()
const namespaceService = shallowReactive(new NamespaceService())
const namespace = ref<INamespace>(new NamespaceModel())
watch(
() => props.namespaceId,
async () => {
namespace.value = namespaceStore.getNamespaceById(props.namespaceId) || new NamespaceModel()
// FIXME: ressouce should be loaded in store
namespace.value = await namespaceService.get({id: props.namespaceId})
},
{ immediate: true },
)
const title = computed(() => {
if (!namespace.value) {
return
}
return namespace.value.isArchived
? t('namespace.archive.titleUnarchive', {namespace: namespace.value.title})
: t('namespace.archive.titleArchive', {namespace: namespace.value.title})
})
useTitle(title)
async function archiveNamespace() {
try {
const isArchived = !namespace.value.isArchived
const archivedNamespace = await namespaceService.update({
...namespace.value,
isArchived,
})
namespaceStore.setNamespaceById(archivedNamespace)
success({
message: isArchived
? t('namespace.archive.success')
: t('namespace.archive.unarchiveSuccess'),
})
} finally {
router.back()
}
}
</script>

View File

@ -1,69 +0,0 @@
<template>
<modal
@close="$router.back()"
@submit="deleteNamespace()"
>
<template #header><span>{{ title }}</span></template>
<template #text>
<p>{{ $t('namespace.delete.text1') }}<br/>
{{ $t('namespace.delete.text2') }}</p>
</template>
</modal>
</template>
<script lang="ts">
export default { name: 'namespace-setting-delete' }
</script>
<script setup lang="ts">
import {ref, computed, watch, shallowReactive} from 'vue'
import {useI18n} from 'vue-i18n'
import {useRouter} from 'vue-router'
import {useTitle} from '@/composables/useTitle'
import {success} from '@/message'
import {useNamespaceStore} from '@/stores/namespaces'
import NamespaceModel from '@/models/namespace'
import NamespaceService from '@/services/namespace'
import type { INamespace } from '@/modelTypes/INamespace'
const props = defineProps({
namespaceId: {
type: Number,
required: true,
},
})
const {t} = useI18n({useScope: 'global'})
const router = useRouter()
const namespaceStore = useNamespaceStore()
const namespaceService = shallowReactive(new NamespaceService())
const namespace = ref<INamespace>(new NamespaceModel())
watch(
() => props.namespaceId,
async () => {
namespace.value = namespaceStore.getNamespaceById(props.namespaceId) || new NamespaceModel()
// FIXME: ressouce should be loaded in store
namespace.value = await namespaceService.get({id: props.namespaceId})
},
{ immediate: true },
)
const title = computed(() => {
if (!namespace.value) {
return
}
return t('namespace.delete.title', {namespace: namespace.value.title})
})
useTitle(title)
async function deleteNamespace() {
await namespaceStore.deleteNamespace(namespace.value)
success({message: t('namespace.delete.success')})
router.push({name: 'home'})
}
</script>

View File

@ -1,120 +0,0 @@
<template>
<create-edit
:title="title"
primary-icon=""
:primary-label="$t('misc.save')"
@primary="save"
:tertiary="$t('misc.delete')"
@tertiary="$router.push({ name: 'namespace.settings.delete', params: { id: $route.params.id } })"
>
<form @submit.prevent="save()">
<div class="field">
<label class="label" for="namespacetext">{{ $t('namespace.attributes.title') }}</label>
<div class="control">
<input
:class="{ 'disabled': namespaceService.loading}"
:disabled="namespaceService.loading || undefined"
class="input"
id="namespacetext"
:placeholder="$t('namespace.attributes.titlePlaceholder')"
type="text"
v-focus
v-model="namespace.title"/>
</div>
</div>
<div class="field">
<label class="label" for="namespacedescription">{{ $t('namespace.attributes.description') }}</label>
<div class="control">
<AsyncEditor
:class="{ 'disabled': namespaceService.loading}"
:preview-is-default="false"
id="namespacedescription"
:placeholder="$t('namespace.attributes.descriptionPlaceholder')"
v-if="editorActive"
v-model="namespace.description"
/>
</div>
</div>
<div class="field">
<label class="label" for="isArchivedCheck">{{ $t('namespace.attributes.archived') }}</label>
<div class="control">
<fancycheckbox
v-model="namespace.isArchived"
v-tooltip="$t('namespace.archive.description')">
{{ $t('namespace.attributes.isArchived') }}
</fancycheckbox>
</div>
</div>
<div class="field">
<label class="label">{{ $t('namespace.attributes.color') }}</label>
<div class="control">
<color-picker v-model="namespace.hexColor"/>
</div>
</div>
</form>
</create-edit>
</template>
<script lang="ts" setup>
import {nextTick, ref, watch} from 'vue'
import {success} from '@/message'
import router from '@/router'
import AsyncEditor from '@/components/input/AsyncEditor'
import Fancycheckbox from '@/components/input/fancycheckbox.vue'
import ColorPicker from '@/components/input/ColorPicker.vue'
import CreateEdit from '@/components/misc/create-edit.vue'
import NamespaceService from '@/services/namespace'
import NamespaceModel from '@/models/namespace'
import {useI18n} from 'vue-i18n'
import {useTitle} from '@/composables/useTitle'
import {useNamespaceStore} from '@/stores/namespaces'
import type {INamespace} from '@/modelTypes/INamespace'
const {t} = useI18n({useScope: 'global'})
const namespaceStore = useNamespaceStore()
const namespaceService = ref(new NamespaceService())
const namespace = ref<INamespace>(new NamespaceModel())
const editorActive = ref(false)
const title = ref('')
useTitle(() => title.value)
const props = defineProps({
namespaceId: {
type: Number,
required: true,
},
})
watch(
() => props.namespaceId,
loadNamespace,
{
immediate: true,
},
)
async function loadNamespace() {
// HACK: This makes the editor trigger its mounted function again which makes it forget every input
// it currently has in its textarea. This is a counter-hack to a hack inside of vue-easymde
// which made it impossible to detect change from the outside. Therefore the component would
// not update if new content from the outside was made available.
// See https://github.com/NikulinIlya/vue-easymde/issues/3
editorActive.value = false
nextTick(() => editorActive.value = true)
namespace.value = await namespaceService.value.get({id: props.namespaceId})
title.value = t('namespace.edit.title', {namespace: namespace.value.title})
}
async function save() {
const updatedNamespace = await namespaceService.value.update(namespace.value)
// Update the namespace in the parent
namespaceStore.setNamespaceById(updatedNamespace)
success({message: t('namespace.edit.success')})
router.back()
}
</script>

View File

@ -1,67 +0,0 @@
<template>
<create-edit
:title="title"
:has-primary-action="false"
>
<template v-if="namespace">
<manageSharing
:id="namespace.id"
:userIsAdmin="userIsAdmin"
shareType="user"
type="namespace"
/>
<manageSharing
:id="namespace.id"
:userIsAdmin="userIsAdmin"
shareType="team"
type="namespace"
/>
</template>
</create-edit>
</template>
<script lang="ts">
export default { name: 'namespace-setting-share' }
</script>
<script lang="ts" setup>
import {ref, computed, watchEffect} from 'vue'
import {useRoute} from 'vue-router'
import {useI18n} from 'vue-i18n'
import NamespaceService from '@/services/namespace'
import NamespaceModel from '@/models/namespace'
import type {INamespace} from '@/modelTypes/INamespace'
import {RIGHTS} from '@/constants/rights'
import CreateEdit from '@/components/misc/create-edit.vue'
import manageSharing from '@/components/sharing/userTeam.vue'
import {useTitle} from '@/composables/useTitle'
const {t} = useI18n({useScope: 'global'})
const namespace = ref<INamespace>()
const title = computed(() => namespace.value?.title
? t('namespace.share.title', { namespace: namespace.value.title })
: '',
)
useTitle(title)
const userIsAdmin = computed(() => namespace?.value?.maxRight === RIGHTS.ADMIN)
async function loadNamespace(namespaceId: number) {
if (!namespaceId) return
const namespaceService = new NamespaceService()
namespace.value = await namespaceService.get(new NamespaceModel({id: namespaceId}))
// TODO: set namespace in store
}
const route = useRoute()
const namespaceId = computed(() => route.params.namespaceId !== undefined
? parseInt(route.params.namespaceId as string)
: undefined,
)
watchEffect(() => namespaceId.value !== undefined && loadNamespace(namespaceId.value))
</script>

View File

@ -63,7 +63,6 @@ async function createNewProject() {
}
showError.value = false
project.namespaceId = Number(route.params.namespaceId as string)
const newProject = await projectStore.createProject(project)
await router.push({
name: 'project.index',

View File

@ -108,7 +108,6 @@ import CustomTransition from '@/components/misc/CustomTransition.vue'
import {useBaseStore} from '@/stores/base'
import {useProjectStore} from '@/stores/projects'
import {useNamespaceStore} from '@/stores/namespaces'
import {useConfigStore} from '@/stores/config'
import BackgroundUnsplashService from '@/services/backgroundUnsplash'
@ -146,7 +145,6 @@ const debounceNewBackgroundSearch = debounce(newBackgroundSearch, SEARCH_DEBOUNC
const backgroundUploadService = ref(new BackgroundUploadService())
const projectService = ref(new ProjectService())
const projectStore = useProjectStore()
const namespaceStore = useNamespaceStore()
const configStore = useConfigStore()
const unsplashBackgroundEnabled = computed(() => configStore.enabledBackgroundProviders.includes('unsplash'))
@ -195,7 +193,6 @@ async function setBackground(backgroundId: string) {
projectId: route.params.projectId,
})
await baseStore.handleSetCurrentProject({project, forceUpdate: true})
namespaceStore.setProjectInNamespaceById(project)
projectStore.setProject(project)
success({message: t('project.background.success')})
}
@ -211,7 +208,6 @@ async function uploadBackground() {
backgroundUploadInput.value?.files[0],
)
await baseStore.handleSetCurrentProject({project, forceUpdate: true})
namespaceStore.setProjectInNamespaceById(project)
projectStore.setProject(project)
success({message: t('project.background.success')})
}
@ -219,7 +215,6 @@ async function uploadBackground() {
async function removeBackground() {
const project = await projectService.value.removeBackground(currentProject.value)
await baseStore.handleSetCurrentProject({project, forceUpdate: true})
namespaceStore.setProjectInNamespaceById(project)
projectStore.setProject(project)
success({message: t('project.background.removeSuccess')})
router.back()

View File

@ -7,15 +7,6 @@
:loading="projectDuplicateService.loading"
>
<p>{{ $t('project.duplicate.text') }}</p>
<Multiselect
:placeholder="$t('namespace.search')"
@search="findNamespaces"
:search-results="namespaces"
@select="selectNamespace"
label="title"
:search-delay="10"
/>
</create-edit>
</template>
@ -29,32 +20,17 @@ import CreateEdit from '@/components/misc/create-edit.vue'
import Multiselect from '@/components/input/multiselect.vue'
import ProjectDuplicateModel from '@/models/projectDuplicateModel'
import type {INamespace} from '@/modelTypes/INamespace'
import {success} from '@/message'
import {useTitle} from '@/composables/useTitle'
import {useNamespaceSearch} from '@/composables/useNamespaceSearch'
import {useProjectStore} from '@/stores/projects'
import {useNamespaceStore} from '@/stores/namespaces'
const {t} = useI18n({useScope: 'global'})
useTitle(() => t('project.duplicate.title'))
const {
namespaces,
findNamespaces,
} = useNamespaceSearch()
const selectedNamespace = ref<INamespace>()
function selectNamespace(namespace: INamespace) {
selectedNamespace.value = namespace
}
const route = useRoute()
const router = useRouter()
const projectStore = useProjectStore()
const namespaceStore = useNamespaceStore()
const projectDuplicateService = shallowReactive(new ProjectDuplicateService())
@ -62,12 +38,10 @@ async function duplicateProject() {
const projectDuplicate = new ProjectDuplicateModel({
// FIXME: should be parameter
projectId: route.params.projectId,
namespaceId: selectedNamespace.value?.id,
})
const duplicate = await projectDuplicateService.create(projectDuplicate)
namespaceStore.addProjectToNamespace(duplicate.project)
projectStore.setProject(duplicate.project)
success({message: t('project.duplicate.success')})
router.push({name: 'project.index', params: {projectId: duplicate.project.id}})

View File

@ -13,8 +13,7 @@
:can-write="canWrite"
ref="heading"
/>
<h6 class="subtitle" v-if="parent && parent.namespace && parent.project">
{{ getNamespaceTitle(parent.namespace) }} &rsaquo;
<h6 class="subtitle" v-if="parent && parent.project">
<router-link :to="{ name: 'project.index', params: { projectId: parent.project.id } }">
{{ getProjectTitle(parent.project) }}
</router-link>
@ -486,12 +485,10 @@ import TaskSubscription from '@/components/misc/subscription.vue'
import CustomTransition from '@/components/misc/CustomTransition.vue'
import {uploadFile} from '@/helpers/attachments'
import {getNamespaceTitle} from '@/helpers/getNamespaceTitle'
import {getProjectTitle} from '@/helpers/getProjectTitle'
import {scrollIntoView} from '@/helpers/scrollIntoView'
import {useBaseStore} from '@/stores/base'
import {useNamespaceStore} from '@/stores/namespaces'
import {useAttachmentStore} from '@/stores/attachments'
import {useTaskStore} from '@/stores/tasks'
import {useKanbanStore} from '@/stores/kanban'
@ -500,6 +497,7 @@ import {useTitle} from '@/composables/useTitle'
import {success} from '@/message'
import type {Action as MessageAction} from '@/message'
import {useProjectStore} from '@/stores/projects'
const props = defineProps({
taskId: {
@ -517,7 +515,7 @@ const router = useRouter()
const {t} = useI18n({useScope: 'global'})
const baseStore = useBaseStore()
const namespaceStore = useNamespaceStore()
const projectStore = useProjectStore()
const attachmentStore = useAttachmentStore()
const taskStore = useTaskStore()
const kanbanStore = useKanbanStore()
@ -541,16 +539,11 @@ const taskId = toRef(props, 'taskId')
const parent = computed(() => {
if (!task.projectId) {
return {
namespace: null,
project: null,
}
}
if (!namespaceStore.getProjectAndNamespaceById) {
return null
}
return namespaceStore.getProjectAndNamespaceById(task.projectId)
return projectStore.getProjectById(task.projectId)
})
watch(
@ -775,7 +768,6 @@ async function toggleFavorite() {
task.isFavorite = !task.isFavorite
const newTask = await taskService.update(task)
Object.assign(task, newTask)
await namespaceStore.loadNamespacesIfFavoritesDontExist()
}
async function setPriority(priority: Priority) {