WIP: feat: route modals everywhere #2735
|
@ -19,7 +19,7 @@ describe('Team', () => {
|
|||
.contains('Create a new team')
|
||||
cy.get('input.input')
|
||||
.type(newTeamName)
|
||||
cy.get('.button')
|
||||
cy.get('.new-team button')
|
||||
.contains('Create')
|
||||
.click()
|
||||
|
||||
|
|
|
@ -0,0 +1,21 @@
|
|||
<!-- https://github.com/vuejs/rfcs/pull/449 -->
|
||||
dpschen marked this conversation as resolved
Outdated
|
||||
<!-- https://github.com/vuejs/rfcs/discussions/448#discussioncomment-2769396= -->
|
||||
<script lang="ts">
|
||||
export default { inheritAttrs: false }
|
||||
</script>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { useAttrs } from 'vue'
|
||||
|
||||
defineProps<{ is: any }>()
|
||||
|
||||
const attrs = useAttrs()
|
||||
console.log(JSON.parse(JSON.stringify(attrs)))
|
||||
dpschen marked this conversation as resolved
Outdated
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<component v-if="is" :is="is" v-bind="$attrs">
|
||||
<slot />
|
||||
</component>
|
||||
<slot v-else />
|
||||
</template>
|
|
@ -16,7 +16,7 @@
|
|||
{{ currentList.title === '' ? $t('misc.loading') : getListTitle(currentList) }}
|
||||
</h1>
|
||||
|
||||
<BaseButton :to="{name: 'list.info', params: {listId: currentList.id}}" class="info-button">
|
||||
<BaseButton :to="{name: 'list.info', params: {listId: currentList.id}, state: {backdropView: $route.fullPath}}" class="info-button">
|
||||
<icon icon="circle-info"/>
|
||||
</BaseButton>
|
||||
|
||||
|
@ -75,7 +75,7 @@
|
|||
{{ $t('keyboardShortcuts.title') }}
|
||||
</dropdown-item>
|
||||
<dropdown-item
|
||||
:to="{name: 'about'}"
|
||||
:to="{name: 'about', state: {backdropView: $route.fullPath}}"
|
||||
>
|
||||
{{ $t('about.title') }}
|
||||
</dropdown-item>
|
||||
|
|
|
@ -38,14 +38,10 @@
|
|||
</keep-alive>
|
||||
</router-view>
|
||||
|
||||
<modal
|
||||
:enabled="Boolean(currentModal)"
|
||||
<component
|
||||
dpschen
commented
Every modal component now has to provide it's own modal. This makes it possible for us to have different modal implementations. Currently we do this by changing the Every modal component now has to provide it's own modal.
This makes it possible for us to have different modal implementations. Currently we do this by changing the `type` prop of the modal. It might be easier for us if we create something like a `BaseModal` to abstract the general modal functionality and then use that to create styled Modals with specific functionality. For example we coudl create a dedicated `Dialog` modal (this might actually be the same as the current `create-edit`, I'm not sure here if that was the intended use @konrad).
konrad
commented
That sounds like it could be a good idea. IIRC my main goal with the That sounds like it could be a good idea.
IIRC my main goal with the `create-edit` component was to be able to easily re-use a shell for creating or editing.
|
||||
:is="currentModal"
|
||||
@close="closeModal()"
|
||||
variant="scrolling"
|
||||
class="task-detail-view-modal"
|
||||
>
|
||||
<component :is="currentModal"/>
|
||||
</modal>
|
||||
/>
|
||||
|
||||
<BaseButton
|
||||
class="keyboard-shortcuts-button d-print-none"
|
||||
|
|
|
@ -10,13 +10,13 @@
|
|||
|
||||
<template v-if="isSavedFilter(list)">
|
||||
<dropdown-item
|
||||
:to="{ name: 'filter.settings.edit', params: { listId: list.id } }"
|
||||
:to="{ name: 'filter.settings.edit', params: { listId: list.id }, state: {backdropView: $route.fullPath} }"
|
||||
icon="pen"
|
||||
>
|
||||
{{ $t('menu.edit') }}
|
||||
</dropdown-item>
|
||||
<dropdown-item
|
||||
:to="{ name: 'filter.settings.delete', params: { listId: list.id } }"
|
||||
:to="{ name: 'filter.settings.delete', params: { listId: list.id }, state: {backdropView: $route.fullPath} }"
|
||||
icon="trash-alt"
|
||||
>
|
||||
{{ $t('misc.delete') }}
|
||||
|
@ -25,7 +25,7 @@
|
|||
|
||||
<template v-else-if="list.isArchived">
|
||||
<dropdown-item
|
||||
:to="{ name: 'list.settings.archive', params: { listId: list.id } }"
|
||||
:to="{ name: 'list.settings.archive', params: { listId: list.id }, state: {backdropView: $route.fullPath} }"
|
||||
icon="archive"
|
||||
>
|
||||
{{ $t('menu.unarchive') }}
|
||||
|
@ -33,32 +33,32 @@
|
|||
</template>
|
||||
<template v-else>
|
||||
<dropdown-item
|
||||
:to="{ name: 'list.settings.edit', params: { listId: list.id } }"
|
||||
:to="{ name: 'list.settings.edit', params: { listId: list.id }, state: {backdropView: $route.fullPath} }"
|
||||
icon="pen"
|
||||
>
|
||||
{{ $t('menu.edit') }}
|
||||
</dropdown-item>
|
||||
<dropdown-item
|
||||
v-if="backgroundsEnabled"
|
||||
:to="{ name: 'list.settings.background', params: { listId: list.id } }"
|
||||
:to="{ name: 'list.settings.background', params: { listId: list.id }, state: {backdropView: $route.fullPath} }"
|
||||
icon="image"
|
||||
>
|
||||
{{ $t('menu.setBackground') }}
|
||||
</dropdown-item>
|
||||
<dropdown-item
|
||||
:to="{ name: 'list.settings.share', params: { listId: list.id } }"
|
||||
:to="{ name: 'list.settings.share', params: { listId: list.id }, state: {backdropView: $route.fullPath} }"
|
||||
icon="share-alt"
|
||||
>
|
||||
{{ $t('menu.share') }}
|
||||
</dropdown-item>
|
||||
<dropdown-item
|
||||
:to="{ name: 'list.settings.duplicate', params: { listId: list.id } }"
|
||||
:to="{ name: 'list.settings.duplicate', params: { listId: list.id }, state: {backdropView: $route.fullPath} }"
|
||||
icon="paste"
|
||||
>
|
||||
{{ $t('menu.duplicate') }}
|
||||
</dropdown-item>
|
||||
<dropdown-item
|
||||
:to="{ name: 'list.settings.archive', params: { listId: list.id } }"
|
||||
:to="{ name: 'list.settings.archive', params: { listId: list.id }, state: {backdropView: $route.fullPath} }"
|
||||
icon="archive"
|
||||
>
|
||||
{{ $t('menu.archive') }}
|
||||
|
@ -73,7 +73,7 @@
|
|||
type="dropdown"
|
||||
/>
|
||||
<dropdown-item
|
||||
:to="{ name: 'list.settings.delete', params: { listId: list.id } }"
|
||||
:to="{ name: 'list.settings.delete', params: { listId: list.id }, state: {backdropView: $route.fullPath} }"
|
||||
icon="trash-alt"
|
||||
class="has-text-danger"
|
||||
>
|
||||
|
|
|
@ -1,38 +1,38 @@
|
|||
<template>
|
||||
<modal @close="$router.back()" :overflow="true" :wide="wide">
|
||||
<modal :overflow="true" :wide="wide" #default="{ onClose }">
|
||||
dpschen
commented
I removed the I removed the `@close` event definitions on all internal modals because what happens when that event get's fired should instead be defined by the parent. By removing this the parents `onClose` / `@close` should automatically be passed to the `<modal>`.
By passing the `onClose` then to the slot content, the slot is still able to use the method.
|
||||
<card
|
||||
:title="title"
|
||||
:shadow="false"
|
||||
:padding="false"
|
||||
class="has-text-left"
|
||||
:has-close="true"
|
||||
@close="$router.back()"
|
||||
@close="onClose"
|
||||
:loading="loading"
|
||||
>
|
||||
<div class="p-4">
|
||||
<slot/>
|
||||
<slot :onClose="onClose" />
|
||||
</div>
|
||||
|
||||
<template #footer>
|
||||
<slot name="footer">
|
||||
<slot name="footer" :onClose="onClose">
|
||||
<x-button
|
||||
v-if="tertiary !== ''"
|
||||
:shadow="false"
|
||||
variant="tertiary"
|
||||
@click.prevent.stop="$emit('tertiary')"
|
||||
@click="$emit('tertiary')"
|
||||
>
|
||||
{{ tertiary }}
|
||||
</x-button>
|
||||
<x-button
|
||||
variant="secondary"
|
||||
@click.prevent.stop="$router.back()"
|
||||
@click="onClose"
|
||||
>
|
||||
{{ $t('misc.cancel') }}
|
||||
</x-button>
|
||||
<x-button
|
||||
v-if="hasPrimaryAction"
|
||||
variant="primary"
|
||||
@click.prevent.stop="primary()"
|
||||
@click="primary(onClose)"
|
||||
:icon="primaryIcon"
|
||||
:disabled="primaryDisabled || loading"
|
||||
class="ml-2"
|
||||
|
@ -83,10 +83,11 @@ defineProps({
|
|||
},
|
||||
})
|
||||
|
||||
// 'close'
|
||||
const emit = defineEmits(['create', 'primary', 'tertiary'])
|
||||
|
||||
function primary() {
|
||||
emit('create')
|
||||
emit('primary')
|
||||
function primary(onClose: () => void) {
|
||||
dpschen
commented
We pass We pass `onClose` as a callback parameter to the `primary` emit so that it can reuse the `onClose` after the primary action finished (unsure if that makes sense).
|
||||
emit('create', onClose)
|
||||
emit('primary', onClose)
|
||||
}
|
||||
</script>
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
<template>
|
||||
<Teleport to="body">
|
||||
<!-- FIXME: transition should not be included in the modal -->
|
||||
<CustomTransition :name="transitionName" appear>
|
||||
<CustomTransition :name="transitionName" appear :appear-class="transitionName">
|
||||
dpschen
commented
The appear transition is currently broken. Fix this. The appear transition is currently broken. Fix this.
|
||||
<section
|
||||
v-if="enabled"
|
||||
class="modal-mask"
|
||||
|
@ -10,11 +10,11 @@
|
|||
variant,
|
||||
]"
|
||||
ref="modal"
|
||||
v-bind="attrs"
|
||||
v-bind="$attrs"
|
||||
>
|
||||
<div
|
||||
class="modal-container"
|
||||
@click.self.prevent.stop="$emit('close')"
|
||||
@click.self.prevent.stop="onClose"
|
||||
v-shortcut="'Escape'"
|
||||
>
|
||||
<div
|
||||
|
@ -25,13 +25,13 @@
|
|||
}"
|
||||
>
|
||||
<BaseButton
|
||||
@click="$emit('close')"
|
||||
@click="onClose"
|
||||
class="close"
|
||||
>
|
||||
<icon icon="times"/>
|
||||
</BaseButton>
|
||||
|
||||
<slot>
|
||||
<slot name="default" :onClose="onClose">
|
||||
<div class="header">
|
||||
<slot name="header"></slot>
|
||||
</div>
|
||||
|
@ -40,14 +40,14 @@
|
|||
</div>
|
||||
<div class="actions">
|
||||
<x-button
|
||||
@click="$emit('close')"
|
||||
@click="onClose"
|
||||
variant="tertiary"
|
||||
class="has-text-danger"
|
||||
>
|
||||
{{ $t('misc.cancel') }}
|
||||
</x-button>
|
||||
<x-button
|
||||
@click="$emit('submit')"
|
||||
@click="$emit('submit', onClose)"
|
||||
variant="primary"
|
||||
v-cy="'modalPrimary'"
|
||||
:shadow="false"
|
||||
|
@ -70,10 +70,11 @@ export default {
|
|||
</script>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import {ref, watchEffect} from 'vue'
|
||||
import {useScrollLock} from '@vueuse/core'
|
||||
|
||||
import CustomTransition from '@/components/misc/CustomTransition.vue'
|
||||
import BaseButton from '@/components/base/BaseButton.vue'
|
||||
import {ref, useAttrs, watchEffect} from 'vue'
|
||||
import {useScrollLock} from '@vueuse/core'
|
||||
|
||||
const props = withDefaults(defineProps<{
|
||||
enabled?: boolean,
|
||||
|
@ -87,9 +88,7 @@ const props = withDefaults(defineProps<{
|
|||
variant: 'default',
|
||||
})
|
||||
|
||||
defineEmits(['close', 'submit'])
|
||||
|
||||
const attrs = useAttrs()
|
||||
const emit = defineEmits(['close', 'submit'])
|
||||
|
||||
const modal = ref<HTMLElement | null>(null)
|
||||
const scrollLock = useScrollLock(modal)
|
||||
|
@ -97,6 +96,10 @@ const scrollLock = useScrollLock(modal)
|
|||
watchEffect(() => {
|
||||
scrollLock.value = props.enabled
|
||||
})
|
||||
|
||||
function onClose() {
|
||||
dpschen
commented
FIXME: either we use an implicit FIXME: either we use an implicit `attrs.onClose` or we explicit define a `close` emit. Both should not be mixed.
|
||||
emit('close')
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
|
|
|
@ -10,7 +10,7 @@
|
|||
|
||||
<template v-if="namespace.isArchived">
|
||||
<dropdown-item
|
||||
:to="{ name: 'namespace.settings.archive', params: { id: namespace.id } }"
|
||||
:to="{ name: 'namespace.settings.archive', params: { id: namespace.id }, state: { backdropView: $route.fullPath } }"
|
||||
icon="archive"
|
||||
>
|
||||
{{ $t('menu.unarchive') }}
|
||||
|
@ -18,25 +18,25 @@
|
|||
</template>
|
||||
<template v-else>
|
||||
<dropdown-item
|
||||
:to="{ name: 'namespace.settings.edit', params: { id: namespace.id } }"
|
||||
:to="{ name: 'namespace.settings.edit', params: { id: namespace.id }, state: { backdropView: $route.fullPath } }"
|
||||
icon="pen"
|
||||
>
|
||||
{{ $t('menu.edit') }}
|
||||
</dropdown-item>
|
||||
<dropdown-item
|
||||
:to="{ name: 'namespace.settings.share', params: { namespaceId: namespace.id } }"
|
||||
:to="{ name: 'namespace.settings.share', params: { namespaceId: namespace.id }, state: { backdropView: $route.fullPath } }"
|
||||
icon="share-alt"
|
||||
>
|
||||
{{ $t('menu.share') }}
|
||||
</dropdown-item>
|
||||
<dropdown-item
|
||||
:to="{ name: 'list.create', params: { namespaceId: namespace.id } }"
|
||||
:to="{ name: 'list.create', params: { namespaceId: namespace.id }, state: { backdropView: $route.fullPath } }"
|
||||
icon="plus"
|
||||
>
|
||||
{{ $t('menu.newList') }}
|
||||
</dropdown-item>
|
||||
<dropdown-item
|
||||
:to="{ name: 'namespace.settings.archive', params: { id: namespace.id } }"
|
||||
:to="{ name: 'namespace.settings.archive', params: { id: namespace.id }, state: { backdropView: $route.fullPath } }"
|
||||
icon="archive"
|
||||
>
|
||||
{{ $t('menu.archive') }}
|
||||
|
@ -51,7 +51,7 @@
|
|||
type="dropdown"
|
||||
/>
|
||||
<dropdown-item
|
||||
:to="{ name: 'namespace.settings.delete', params: { id: namespace.id } }"
|
||||
:to="{ name: 'namespace.settings.delete', params: { id: namespace.id }, state: { backdropView: $route.fullPath } }"
|
||||
icon="trash-alt"
|
||||
class="has-text-danger"
|
||||
>
|
||||
|
|
|
@ -44,10 +44,10 @@ export function useRouteWithModal() {
|
|||
function closeModal() {
|
||||
const historyState = computed(() => route.fullPath && window.history.state)
|
||||
|
||||
if (historyState.value) {
|
||||
if (historyState.value === undefined) {
|
||||
dpschen
commented
Something was broken here: since we checked for Something was broken here: since we checked for `historyState.value` in the `if` condition it could never have a value in the `else` cause.
|
||||
router.back()
|
||||
} else {
|
||||
const backdropRoute = historyState.value?.backdropView && router.resolve(historyState.value.backdropView)
|
||||
const backdropRoute = historyState.value.backdropView && router.resolve(historyState.value.backdropView)
|
||||
router.push(backdropRoute)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -113,7 +113,7 @@ export function useSavedFilter(listId?: MaybeRef<IList['id']>) {
|
|||
router.push({name: 'list.index', params: {listId: getListId(filter.value)}})
|
||||
}
|
||||
|
||||
async function saveFilter() {
|
||||
async function saveFilter(callback: () => void) {
|
||||
const response = await filterService.update(filter.value)
|
||||
await namespaceStore.loadNamespaces()
|
||||
success({message: t('filters.edit.success')})
|
||||
|
@ -123,7 +123,7 @@ export function useSavedFilter(listId?: MaybeRef<IList['id']>) {
|
|||
id: getListId(filter.value),
|
||||
title: filter.value.title,
|
||||
}))
|
||||
router.back()
|
||||
callback()
|
||||
}
|
||||
|
||||
async function deleteFilter() {
|
||||
|
|
|
@ -1,14 +1,14 @@
|
|||
<template>
|
||||
<modal
|
||||
@close="$router.back()"
|
||||
transition-name="fade"
|
||||
variant="hint-modal"
|
||||
>
|
||||
>
|
||||
<template #default="{onClose}">
|
||||
<card
|
||||
class="has-no-shadow"
|
||||
:title="$t('about.title')"
|
||||
:has-close="true"
|
||||
@close="$router.back()"
|
||||
@close="onClose"
|
||||
:padding="false"
|
||||
>
|
||||
<div class="p-4">
|
||||
|
@ -22,12 +22,13 @@
|
|||
<template #footer>
|
||||
<x-button
|
||||
variant="secondary"
|
||||
@click.prevent.stop="$router.back()"
|
||||
@click="onClose"
|
||||
>
|
||||
{{ $t('misc.close') }}
|
||||
</x-button>
|
||||
</template>
|
||||
</card>
|
||||
</template>
|
||||
</modal>
|
||||
</template>
|
||||
|
||||
|
|
|
@ -21,7 +21,7 @@
|
|||
<template v-if="defaultNamespaceId > 0">
|
||||
<p class="mt-4">{{ $t('home.list.newText') }}</p>
|
||||
<x-button
|
||||
:to="{ name: 'list.create', params: { namespaceId: defaultNamespaceId } }"
|
||||
:to="{ name: 'list.create', params: { namespaceId: defaultNamespaceId }, state: { backdropView: $route.fullPath } }"
|
||||
:shadow="false"
|
||||
class="ml-2"
|
||||
>
|
||||
|
|
|
@ -1,8 +1,5 @@
|
|||
<template>
|
||||
<modal
|
||||
@close="$router.back()"
|
||||
@submit="deleteFilter()"
|
||||
>
|
||||
<modal @submit="deleteFilter()">
|
||||
<template #header>
|
||||
<span>{{ $t('filters.delete.header') }}</span>
|
||||
</template>
|
||||
|
|
|
@ -6,15 +6,16 @@
|
|||
@primary="saveFilter"
|
||||
:tertiary="$t('misc.delete')"
|
||||
@tertiary="$router.push({ name: 'filter.settings.delete', params: { id: listId } })"
|
||||
#default="{onClose}"
|
||||
>
|
||||
<form @submit.prevent="saveFilter()">
|
||||
<form @submit.prevent="saveFilter(onClose)">
|
||||
<div class="field">
|
||||
<label class="label" for="title">{{ $t('filters.attributes.title') }}</label>
|
||||
<div class="control">
|
||||
<input
|
||||
:class="{ 'disabled': filterService.loading}"
|
||||
:disabled="filterService.loading || undefined"
|
||||
@keyup.enter="saveFilter"
|
||||
@keyup.enter="saveFilter(onClose)"
|
||||
class="input"
|
||||
id="title"
|
||||
:placeholder="$t('filters.attributes.titlePlaceholder')"
|
||||
|
|
|
@ -1,8 +1,5 @@
|
|||
<template>
|
||||
<modal
|
||||
@close="$router.back()"
|
||||
variant="hint-modal"
|
||||
>
|
||||
<modal variant="hint-modal">
|
||||
<card class="has-no-shadow" :title="$t('filters.create.title')">
|
||||
<p>
|
||||
{{ $t('filters.create.description') }}
|
||||
|
|
|
@ -1,7 +1,10 @@
|
|||
<template>
|
||||
<div :class="{ 'is-loading': loading}" class="loader-container">
|
||||
<x-button
|
||||
:to="{name:'labels.create'}"
|
||||
:to="{
|
||||
name:'labels.create',
|
||||
state: { backdropView: $route.fullPath }
|
||||
}"
|
||||
class="is-pulled-right"
|
||||
icon="plus"
|
||||
>
|
||||
|
@ -15,7 +18,10 @@
|
|||
</p>
|
||||
<p v-else class="has-text-centered has-text-grey is-italic">
|
||||
{{ $t('label.newCTA') }}
|
||||
<router-link :to="{name:'labels.create'}">{{ $t('label.create.title') }}.</router-link>
|
||||
<router-link :to="{
|
||||
name:'labels.create',
|
||||
state: { backdropView: $route.fullPath }
|
||||
}">{{ $t('label.create.title') }}.</router-link>
|
||||
</p>
|
||||
</div>
|
||||
|
||||
|
|
|
@ -1,10 +1,6 @@
|
|||
<template>
|
||||
<modal
|
||||
@close="$router.back()"
|
||||
>
|
||||
<card
|
||||
:title="list.title"
|
||||
>
|
||||
<modal>
|
||||
<card :title="list.title">
|
||||
<div class="has-text-left" v-html="htmlDescription" v-if="htmlDescription !== ''"></div>
|
||||
<p v-else class="is-italic">
|
||||
{{ $t('list.noDescriptionAvailable') }}
|
||||
|
|
|
@ -1,5 +1,10 @@
|
|||
<template>
|
||||
<create-edit :title="$t('list.create.header')" @create="createNewList()" :primary-disabled="list.title === ''">
|
||||
<create-edit
|
||||
:title="$t('list.create.header')"
|
||||
@create="createNewList()"
|
||||
:primary-disabled="list.title === ''"
|
||||
#default="{onClose}"
|
||||
>
|
||||
<div class="field">
|
||||
<label class="label" for="listTitle">{{ $t('list.title') }}</label>
|
||||
<div
|
||||
|
@ -9,7 +14,7 @@
|
|||
<input
|
||||
:class="{ disabled: listService.loading }"
|
||||
@keyup.enter="createNewList()"
|
||||
@keyup.esc="$router.back()"
|
||||
@keyup.esc="onClose"
|
||||
class="input"
|
||||
:placeholder="$t('list.create.titlePlaceholder')"
|
||||
type="text"
|
||||
|
|
|
@ -1,8 +1,5 @@
|
|||
<template>
|
||||
<modal
|
||||
@close="$router.back()"
|
||||
@submit="archiveList()"
|
||||
>
|
||||
<modal @submit="archiveList">
|
||||
<template #header><span>{{ list.isArchived ? $t('list.archive.unarchive') : $t('list.archive.archive') }}</span></template>
|
||||
|
||||
<template #text>
|
||||
|
@ -17,7 +14,7 @@ export default {name: 'list-setting-archive'}
|
|||
|
||||
<script setup lang="ts">
|
||||
import {computed} from 'vue'
|
||||
import {useRouter, useRoute} from 'vue-router'
|
||||
import {useRoute} from 'vue-router'
|
||||
import {useI18n} from 'vue-i18n'
|
||||
|
||||
import {success} from '@/message'
|
||||
|
@ -28,13 +25,12 @@ import {useListStore} from '@/stores/lists'
|
|||
|
||||
const {t} = useI18n({useScope: 'global'})
|
||||
const listStore = useListStore()
|
||||
const router = useRouter()
|
||||
const route = useRoute()
|
||||
|
||||
const list = computed(() => listStore.getListById(route.params.listId))
|
||||
useTitle(() => t('list.archive.title', {list: list.value.title}))
|
||||
|
||||
async function archiveList() {
|
||||
async function archiveList(onClose: () => void) {
|
||||
try {
|
||||
const newList = await listStore.updateList({
|
||||
...list.value,
|
||||
|
@ -43,7 +39,7 @@ async function archiveList() {
|
|||
useBaseStore().setCurrentList(newList)
|
||||
success({message: t('list.archive.success')})
|
||||
} finally {
|
||||
router.back()
|
||||
onClose()
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
|
|
@ -73,19 +73,19 @@
|
|||
</x-button>
|
||||
</template>
|
||||
|
||||
<template #footer>
|
||||
<template #footer="{onClose}">
|
||||
<x-button
|
||||
v-if="hasBackground"
|
||||
:shadow="false"
|
||||
variant="tertiary"
|
||||
class="is-danger"
|
||||
@click.prevent.stop="removeBackground"
|
||||
@click="removeBackground(onClose)"
|
||||
dpschen
commented
We should not need to use We should not need to use `.prevent` and `.stop` with the vueuse `onClickOutside`.
|
||||
>
|
||||
{{ $t('list.background.remove') }}
|
||||
</x-button>
|
||||
<x-button
|
||||
variant="secondary"
|
||||
@click.prevent.stop="$router.back()"
|
||||
@click="onClose"
|
||||
>
|
||||
{{ $t('misc.close') }}
|
||||
</x-button>
|
||||
|
@ -100,7 +100,7 @@ export default { name: 'list-setting-background' }
|
|||
<script setup lang="ts">
|
||||
import {ref, computed, shallowReactive} from 'vue'
|
||||
import {useI18n} from 'vue-i18n'
|
||||
import {useRoute, useRouter} from 'vue-router'
|
||||
import {useRoute} from 'vue-router'
|
||||
import debounce from 'lodash.debounce'
|
||||
|
||||
import BaseButton from '@/components/base/BaseButton.vue'
|
||||
|
@ -127,7 +127,6 @@ const SEARCH_DEBOUNCE = 300
|
|||
const {t} = useI18n({useScope: 'global'})
|
||||
const baseStore = useBaseStore()
|
||||
const route = useRoute()
|
||||
const router = useRouter()
|
||||
|
||||
useTitle(() => t('list.background.title'))
|
||||
|
||||
|
@ -216,13 +215,13 @@ async function uploadBackground() {
|
|||
success({message: t('list.background.success')})
|
||||
}
|
||||
|
||||
async function removeBackground() {
|
||||
async function removeBackground(onClose: () => void) {
|
||||
const list = await listService.value.removeBackground(currentList.value)
|
||||
await baseStore.handleSetCurrentList({list, forceUpdate: true})
|
||||
namespaceStore.setListInNamespaceById(list)
|
||||
listStore.setList(list)
|
||||
success({message: t('list.background.removeSuccess')})
|
||||
router.back()
|
||||
onClose()
|
||||
}
|
||||
</script>
|
||||
|
||||
|
|
|
@ -1,8 +1,5 @@
|
|||
<template>
|
||||
<modal
|
||||
@close="$router.back()"
|
||||
@submit="deleteList()"
|
||||
>
|
||||
<modal @submit="deleteList()">
|
||||
<template #header><span>{{ $t('list.delete.header') }}</span></template>
|
||||
|
||||
<template #text>
|
||||
|
|
|
@ -5,7 +5,8 @@
|
|||
:primary-label="$t('misc.save')"
|
||||
@primary="save"
|
||||
:tertiary="$t('misc.delete')"
|
||||
@tertiary="$router.push({ name: 'list.settings.delete', params: { id: listId } })"
|
||||
@tertiary="$router.push({ name: 'list.settings.delete', params: { id: listId }, state: {backdropView: $route.fullPath} })"
|
||||
#default="{onClose}"
|
||||
>
|
||||
<div class="field">
|
||||
<label class="label" for="title">{{ $t('list.title') }}</label>
|
||||
|
@ -13,7 +14,7 @@
|
|||
<input
|
||||
:class="{ 'disabled': isLoading}"
|
||||
:disabled="isLoading || undefined"
|
||||
@keyup.enter="save"
|
||||
@keyup.enter="save(onClose)"
|
||||
class="input"
|
||||
id="title"
|
||||
:placeholder="$t('list.edit.titlePlaceholder')"
|
||||
|
@ -33,7 +34,7 @@
|
|||
<input
|
||||
:class="{ 'disabled': isLoading}"
|
||||
:disabled="isLoading || undefined"
|
||||
@keyup.enter="save"
|
||||
@keyup.enter="save(onClose)"
|
||||
class="input"
|
||||
id="identifier"
|
||||
:placeholder="$t('list.edit.identifierPlaceholder')"
|
||||
|
@ -71,7 +72,6 @@ export default { name: 'list-setting-edit' }
|
|||
|
||||
<script setup lang="ts">
|
||||
import type {PropType} from 'vue'
|
||||
import {useRouter} from 'vue-router'
|
||||
import {useI18n} from 'vue-i18n'
|
||||
|
||||
import Editor from '@/components/input/AsyncEditor'
|
||||
|
@ -92,17 +92,15 @@ const props = defineProps({
|
|||
},
|
||||
})
|
||||
|
||||
const router = useRouter()
|
||||
|
||||
const {t} = useI18n({useScope: 'global'})
|
||||
|
||||
const {list, save: saveList, isLoading} = useList(props.listId)
|
||||
|
||||
useTitle(() => list?.title ? t('list.edit.title', {list: list.title}) : '')
|
||||
|
||||
async function save() {
|
||||
async function save(onClose: () => void) {
|
||||
await saveList()
|
||||
await useBaseStore().handleSetCurrentList({list})
|
||||
router.back()
|
||||
onClose()
|
||||
}
|
||||
</script>
|
||||
|
|
|
@ -6,10 +6,10 @@
|
|||
</fancycheckbox>
|
||||
|
||||
<div class="action-buttons">
|
||||
<x-button :to="{name: 'filters.create'}" icon="filter">
|
||||
<x-button :to="{name: 'filters.create', state: {backdropView: $route.fullPath}}" icon="filter">
|
||||
{{ $t('filters.create.title') }}
|
||||
</x-button>
|
||||
<x-button :to="{name: 'namespace.create'}" icon="plus" v-cy="'new-namespace'">
|
||||
<x-button :to="{name: 'namespace.create', state: {backdropView: $route.fullPath}}" icon="plus" v-cy="'new-namespace'">
|
||||
{{ $t('namespace.create.title') }}
|
||||
</x-button>
|
||||
</div>
|
||||
|
@ -17,7 +17,7 @@
|
|||
|
||||
<p v-if="namespaces.length === 0" class="has-text-centered has-text-grey mt-4 is-italic">
|
||||
{{ $t('namespace.noneAvailable') }}
|
||||
<BaseButton :to="{name: 'namespace.create'}">
|
||||
<BaseButton :to="{name: 'namespace.create', state: {backdropView: $route.fullPath}}">
|
||||
{{ $t('namespace.create.title') }}.
|
||||
</BaseButton>
|
||||
</p>
|
||||
|
@ -25,7 +25,7 @@
|
|||
<section :key="`n${n.id}`" class="namespace" v-for="n in namespaces">
|
||||
<x-button
|
||||
v-if="n.id > 0 && n.lists.length > 0"
|
||||
:to="{name: 'list.create', params: {namespaceId: n.id}}"
|
||||
:to="{name: 'list.create', params: {namespaceId: n.id}, state: { backdropView: $route.fullPath }}"
|
||||
class="is-pulled-right"
|
||||
variant="secondary"
|
||||
icon="plus"
|
||||
|
@ -34,7 +34,7 @@
|
|||
</x-button>
|
||||
<x-button
|
||||
v-if="n.isArchived"
|
||||
:to="{name: 'namespace.settings.archive', params: {id: n.id}}"
|
||||
:to="{name: 'namespace.settings.archive', params: {id: n.id}, state: { backdropView: $route.fullPath } }"
|
||||
class="is-pulled-right mr-4"
|
||||
variant="secondary"
|
||||
icon="archive"
|
||||
|
@ -51,7 +51,7 @@
|
|||
|
||||
<p v-if="n.lists.length === 0" class="has-text-centered has-text-grey mt-4 is-italic">
|
||||
{{ $t('namespace.noLists') }}
|
||||
<BaseButton :to="{name: 'list.create', params: {namespaceId: n.id}}">
|
||||
<BaseButton :to="{name: 'list.create', params: {namespaceId: n.id}, state: { backdropView: $route.fullPath }}">
|
||||
{{ $t('namespace.createList') }}
|
||||
</BaseButton>
|
||||
</p>
|
||||
|
|
|
@ -1,8 +1,9 @@
|
|||
<template>
|
||||
<create-edit
|
||||
:title="$t('namespace.create.title')"
|
||||
@create="newNamespace()"
|
||||
@create="newNamespace"
|
||||
:primary-disabled="namespace.title === ''"
|
||||
#default="{onClose}"
|
||||
>
|
||||
<div class="field">
|
||||
<label class="label" for="namespaceTitle">{{ $t('namespace.attributes.title') }}</label>
|
||||
|
@ -14,8 +15,8 @@
|
|||
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()"
|
||||
@keyup.enter="newNamespace(onClose)"
|
||||
@keyup.esc="onClose"
|
||||
class="input"
|
||||
:placeholder="$t('namespace.attributes.titlePlaceholder')"
|
||||
type="text"
|
||||
|
@ -46,7 +47,6 @@
|
|||
<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'
|
||||
|
@ -65,11 +65,10 @@ 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() {
|
||||
async function newNamespace(onClose: () => void) {
|
||||
if (namespace.value.title === '') {
|
||||
showError.value = true
|
||||
return
|
||||
|
@ -79,6 +78,6 @@ async function newNamespace() {
|
|||
const newNamespace = await namespaceService.create(namespace.value)
|
||||
useNamespaceStore().addNamespace(newNamespace)
|
||||
success({message: t('namespace.create.success')})
|
||||
router.back()
|
||||
onClose()
|
||||
}
|
||||
</script>
|
||||
|
|
|
@ -1,8 +1,5 @@
|
|||
<template>
|
||||
<modal
|
||||
@close="$router.back()"
|
||||
@submit="archiveNamespace()"
|
||||
>
|
||||
<modal @submit="archiveNamespace">
|
||||
<template #header><span>{{ title }}</span></template>
|
||||
|
||||
<template #text>
|
||||
|
@ -23,7 +20,6 @@ export default { name: 'namespace-setting-archive' }
|
|||
|
||||
<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'
|
||||
|
@ -41,7 +37,6 @@ const props = defineProps({
|
|||
},
|
||||
})
|
||||
|
||||
const router = useRouter()
|
||||
const {t} = useI18n({useScope: 'global'})
|
||||
|
||||
const namespaceStore = useNamespaceStore()
|
||||
|
@ -69,7 +64,7 @@ const title = computed(() => {
|
|||
})
|
||||
useTitle(title)
|
||||
|
||||
async function archiveNamespace() {
|
||||
async function archiveNamespace(onClose: () => void) {
|
||||
try {
|
||||
const isArchived = !namespace.value.isArchived
|
||||
const archivedNamespace = await namespaceService.update({
|
||||
|
@ -83,7 +78,7 @@ async function archiveNamespace() {
|
|||
: t('namespace.archive.unarchiveSuccess'),
|
||||
})
|
||||
} finally {
|
||||
router.back()
|
||||
onClose()
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
|
|
@ -1,8 +1,5 @@
|
|||
<template>
|
||||
<modal
|
||||
@close="$router.back()"
|
||||
@submit="deleteNamespace()"
|
||||
>
|
||||
<modal @submit="deleteNamespace()">
|
||||
<template #header><span>{{ title }}</span></template>
|
||||
|
||||
<template #text>
|
||||
|
|
|
@ -5,9 +5,10 @@
|
|||
:primary-label="$t('misc.save')"
|
||||
@primary="save"
|
||||
:tertiary="$t('misc.delete')"
|
||||
@tertiary="$router.push({ name: 'namespace.settings.delete', params: { id: $route.params.id } })"
|
||||
@tertiary="$router.push({ name: 'namespace.settings.delete', params: { id: $route.params.id }, state: { backdropView: $route.fullPath } })"
|
||||
#default="{onClose}"
|
||||
>
|
||||
<form @submit.prevent="save()">
|
||||
<form @submit.prevent="save(onClose)">
|
||||
<div class="field">
|
||||
<label class="label" for="namespacetext">{{ $t('namespace.attributes.title') }}</label>
|
||||
<div class="control">
|
||||
|
@ -58,7 +59,6 @@
|
|||
<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'
|
||||
|
@ -110,11 +110,11 @@ async function loadNamespace() {
|
|||
title.value = t('namespace.edit.title', {namespace: namespace.value.title})
|
||||
}
|
||||
|
||||
async function save() {
|
||||
async function save(onClose: () => void) {
|
||||
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()
|
||||
onClose()
|
||||
}
|
||||
</script>
|
|
@ -1,4 +1,5 @@
|
|||
<template>
|
||||
<OptionalWrapper :is="isModal && Modal" variant="scrolling">
|
||||
dpschen
commented
By using the OptionalWrapper we can use components that can be used with or without Modal By using the OptionalWrapper we can use components that can be used with or without Modal
|
||||
<div
|
||||
class="loader-container task-view-container"
|
||||
:class="{
|
||||
|
@ -442,10 +443,11 @@
|
|||
</template>
|
||||
</modal>
|
||||
</div>
|
||||
</OptionalWrapper>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import {ref, reactive, toRef, shallowReactive, computed, watch, nextTick, type PropType} from 'vue'
|
||||
import {ref, reactive, toRef, shallowReactive, computed, watch, nextTick, type PropType, useAttrs} from 'vue'
|
||||
import {useRouter, type RouteLocation} from 'vue-router'
|
||||
import {useI18n} from 'vue-i18n'
|
||||
import {unrefElement} from '@vueuse/core'
|
||||
|
@ -480,7 +482,10 @@ import RelatedTasks from '@/components/tasks/partials/relatedTasks.vue'
|
|||
import Reminders from '@/components/tasks/partials/reminders.vue'
|
||||
import RepeatAfter from '@/components/tasks/partials/repeatAfter.vue'
|
||||
import TaskSubscription from '@/components/misc/subscription.vue'
|
||||
|
||||
import OptionalWrapper from '@/components/base/OptionalWrapper.vue'
|
||||
import CustomTransition from '@/components/misc/CustomTransition.vue'
|
||||
import Modal from '@/components/misc/modal.vue'
|
||||
|
||||
import {uploadFile} from '@/helpers/attachments'
|
||||
import {getNamespaceTitle} from '@/helpers/getNamespaceTitle'
|
||||
|
@ -508,7 +513,8 @@ const props = defineProps({
|
|||
})
|
||||
|
||||
defineEmits(['close'])
|
||||
|
||||
const attrs = useAttrs()
|
||||
console.log(JSON.parse(JSON.stringify(attrs)))
|
||||
dpschen
commented
Remove log Remove log
|
||||
const router = useRouter()
|
||||
const {t} = useI18n({useScope: 'global'})
|
||||
|
||||
|
|
|
@ -1,7 +1,10 @@
|
|||
<template>
|
||||
<div class="content loader-container is-max-width-desktop" :class="{ 'is-loading': teamService.loading}">
|
||||
<x-button
|
||||
:to="{name:'teams.create'}"
|
||||
:to="{
|
||||
name:'teams.create',
|
||||
state: { backdropView: $route.fullPath },
|
||||
}"
|
||||
class="is-pulled-right"
|
||||
icon="plus"
|
||||
>
|
||||
|
@ -18,7 +21,10 @@
|
|||
</ul>
|
||||
<p v-else-if="!teamService.loading" class="has-text-centered has-text-grey is-italic">
|
||||
{{ $t('team.noTeams') }}
|
||||
<router-link :to="{name: 'teams.create'}">
|
||||
<router-link :to="{
|
||||
name: 'teams.create',
|
||||
state: { backdropView: $route.fullPath },
|
||||
}">
|
||||
{{ $t('team.create.title') }}.
|
||||
</router-link>
|
||||
</p>
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
<template>
|
||||
<create-edit
|
||||
class="new-team"
|
||||
:title="title"
|
||||
@create="newTeam()"
|
||||
:primary-disabled="team.name === ''"
|
||||
|
|
This component makes it possible to optionally wrap around another component. If the
is
prop defines a component it will be rendered as a wrapper around the slot content. If theis
prop is undefined there won't be any wrapper.