From ff99d0aad8a242867646ec8c61daeddc3c6b76b7 Mon Sep 17 00:00:00 2001 From: ian Date: Wed, 29 Sep 2021 06:58:16 +0800 Subject: [PATCH] Add discard confirmation prompt for project settings (#8373) --- .../routes/data-model/fields/fields.vue | 47 ++++++++++++++- .../modules/settings/routes/presets/item.vue | 47 ++++++++++++++- .../settings/routes/project/project.vue | 58 ++++++++++++++++++- .../settings/routes/roles/item/item.vue | 47 ++++++++++++++- .../modules/settings/routes/webhooks/item.vue | 47 ++++++++++++++- 5 files changed, 241 insertions(+), 5 deletions(-) diff --git a/app/src/modules/settings/routes/data-model/fields/fields.vue b/app/src/modules/settings/routes/data-model/fields/fields.vue index 2103e947e5..b1f6e15817 100644 --- a/app/src/modules/settings/routes/data-model/fields/fields.vue +++ b/app/src/modules/settings/routes/data-model/fields/fields.vue @@ -80,6 +80,19 @@
+ + + + {{ t('unsaved_changes') }} + {{ t('unsaved_changes_copy') }} + + + {{ t('discard_changes') }} + + {{ t('keep_editing') }} + + + @@ -91,8 +104,9 @@ import { useCollection } from '@directus/shared/composables'; import FieldsManagement from './components/fields-management.vue'; import useItem from '@/composables/use-item'; -import { useRouter } from 'vue-router'; +import { useRouter, onBeforeRouteUpdate, onBeforeRouteLeave, NavigationGuard } from 'vue-router'; import { useCollectionsStore, useFieldsStore } from '@/stores'; +import unsavedChanges from '@/composables/unsaved-changes'; export default defineComponent({ components: { SettingsNavigation, FieldsManagement }, @@ -131,6 +145,26 @@ export default defineComponent({ const confirmDelete = ref(false); + const isSavable = computed(() => { + if (hasEdits.value === true) return true; + return hasEdits.value; + }); + + unsavedChanges(isSavable); + + const confirmLeave = ref(false); + const leaveTo = ref(null); + + const editsGuard: NavigationGuard = (to) => { + if (hasEdits.value) { + confirmLeave.value = true; + leaveTo.value = to.fullPath; + return false; + } + }; + onBeforeRouteUpdate(editsGuard); + onBeforeRouteLeave(editsGuard); + return { t, collectionInfo, @@ -150,6 +184,10 @@ export default defineComponent({ deleteAndQuit, saveAndQuit, hasEdits, + isSavable, + confirmLeave, + leaveTo, + discardAndLeave, }; async function deleteAndQuit() { @@ -165,6 +203,13 @@ export default defineComponent({ await fieldsStore.hydrate(); router.push(`/settings/data-model`); } + + function discardAndLeave() { + if (!leaveTo.value) return; + edits.value = {}; + confirmLeave.value = false; + router.push(leaveTo.value); + } }, }); diff --git a/app/src/modules/settings/routes/presets/item.vue b/app/src/modules/settings/routes/presets/item.vue index 40dd64e9aa..31b7fa196d 100644 --- a/app/src/modules/settings/routes/presets/item.vue +++ b/app/src/modules/settings/routes/presets/item.vue @@ -113,6 +113,19 @@
+ + + + {{ t('unsaved_changes') }} + {{ t('unsaved_changes_copy') }} + + + {{ t('discard_changes') }} + + {{ t('keep_editing') }} + + + @@ -126,9 +139,10 @@ import { Preset, Filter } from '@directus/shared/types'; import api from '@/api'; import { useCollectionsStore, usePresetsStore } from '@/stores'; import { getLayouts } from '@/layouts'; -import { useRouter } from 'vue-router'; +import { useRouter, onBeforeRouteUpdate, onBeforeRouteLeave, NavigationGuard } from 'vue-router'; import { unexpectedError } from '@/utils/unexpected-error'; import { useLayout } from '@/composables/use-layout'; +import unsavedChanges from '@/composables/unsaved-changes'; type FormattedPreset = { id: number; @@ -184,6 +198,26 @@ export default defineComponent({ const { layoutWrapper } = useLayout(layout); + const isSavable = computed(() => { + if (hasEdits.value === true) return true; + return hasEdits.value; + }); + + unsavedChanges(isSavable); + + const confirmLeave = ref(false); + const leaveTo = ref(null); + + const editsGuard: NavigationGuard = (to) => { + if (hasEdits.value) { + confirmLeave.value = true; + leaveTo.value = to.fullPath; + return false; + } + }; + onBeforeRouteUpdate(editsGuard); + onBeforeRouteLeave(editsGuard); + return { t, backLink, @@ -205,6 +239,10 @@ export default defineComponent({ confirmDelete, updateFilters, searchQuery, + isSavable, + confirmLeave, + leaveTo, + discardAndLeave, }; function useSave() { @@ -496,6 +534,13 @@ export default defineComponent({ return { fields }; } + + function discardAndLeave() { + if (!leaveTo.value) return; + edits.value = {}; + confirmLeave.value = false; + router.push(leaveTo.value); + } }, }); diff --git a/app/src/modules/settings/routes/project/project.vue b/app/src/modules/settings/routes/project/project.vue index c6d628c949..5184379265 100644 --- a/app/src/modules/settings/routes/project/project.vue +++ b/app/src/modules/settings/routes/project/project.vue @@ -24,6 +24,19 @@ + + + + {{ t('unsaved_changes') }} + {{ t('unsaved_changes_copy') }} + + + {{ t('discard_changes') }} + + {{ t('keep_editing') }} + + + @@ -35,12 +48,16 @@ import { useCollection } from '@directus/shared/composables'; import { useSettingsStore, useServerStore } from '@/stores'; import ProjectInfoSidebarDetail from './components/project-info-sidebar-detail.vue'; import { clone } from 'lodash'; +import unsavedChanges from '@/composables/unsaved-changes'; +import { useRouter, onBeforeRouteUpdate, onBeforeRouteLeave, NavigationGuard } from 'vue-router'; export default defineComponent({ components: { SettingsNavigation, ProjectInfoSidebarDetail }, setup() { const { t } = useI18n(); + const router = useRouter(); + const settingsStore = useSettingsStore(); const serverStore = useServerStore(); @@ -54,7 +71,39 @@ export default defineComponent({ const saving = ref(false); - return { t, fields, initialValues, edits, noEdits, saving, save }; + const isSavable = computed(() => { + if (noEdits.value === true) return false; + return noEdits.value; + }); + + unsavedChanges(isSavable); + + const confirmLeave = ref(false); + const leaveTo = ref(null); + + const editsGuard: NavigationGuard = (to) => { + if (!noEdits.value) { + confirmLeave.value = true; + leaveTo.value = to.fullPath; + return false; + } + }; + onBeforeRouteUpdate(editsGuard); + onBeforeRouteLeave(editsGuard); + + return { + t, + fields, + initialValues, + edits, + noEdits, + saving, + isSavable, + confirmLeave, + leaveTo, + save, + discardAndLeave, + }; async function save() { if (edits.value === null) return; @@ -65,6 +114,13 @@ export default defineComponent({ saving.value = false; initialValues.value = clone(settingsStore.settings); } + + function discardAndLeave() { + if (!leaveTo.value) return; + edits.value = {}; + confirmLeave.value = false; + router.push(leaveTo.value); + } }, }); diff --git a/app/src/modules/settings/routes/roles/item/item.vue b/app/src/modules/settings/routes/roles/item/item.vue index 2b9cd9dfb4..9b5658a0e4 100644 --- a/app/src/modules/settings/routes/roles/item/item.vue +++ b/app/src/modules/settings/routes/roles/item/item.vue @@ -84,6 +84,19 @@ + + + + {{ t('unsaved_changes') }} + {{ t('unsaved_changes_copy') }} + + + {{ t('discard_changes') }} + + {{ t('keep_editing') }} + + + @@ -92,13 +105,14 @@ import { useI18n } from 'vue-i18n'; import { defineComponent, computed, toRefs, ref } from 'vue'; import SettingsNavigation from '../../../components/navigation.vue'; -import { useRouter } from 'vue-router'; +import { useRouter, onBeforeRouteUpdate, onBeforeRouteLeave, NavigationGuard } from 'vue-router'; import RevisionsDrawerDetail from '@/views/private/components/revisions-drawer-detail'; import useItem from '@/composables/use-item'; import { useUserStore } from '@/stores/'; import RoleInfoSidebarDetail from './components/role-info-sidebar-detail.vue'; import PermissionsOverview from './components/permissions-overview.vue'; import UsersInvite from '@/views/private/components/users-invite'; +import unsavedChanges from '@/composables/unsaved-changes'; export default defineComponent({ name: 'RolesItem', @@ -149,6 +163,26 @@ export default defineComponent({ return !!values.app_access; }); + const isSavable = computed(() => { + if (hasEdits.value === true) return true; + return hasEdits.value; + }); + + unsavedChanges(isSavable); + + const confirmLeave = ref(false); + const leaveTo = ref(null); + + const editsGuard: NavigationGuard = (to) => { + if (hasEdits.value) { + confirmLeave.value = true; + leaveTo.value = to.fullPath; + return false; + } + }; + onBeforeRouteUpdate(editsGuard); + onBeforeRouteLeave(editsGuard); + return { t, item, @@ -165,6 +199,10 @@ export default defineComponent({ adminEnabled, userInviteModalActive, appAccess, + isSavable, + confirmLeave, + leaveTo, + discardAndLeave, }; /** @@ -184,6 +222,13 @@ export default defineComponent({ await remove(); router.push(`/settings/roles`); } + + function discardAndLeave() { + if (!leaveTo.value) return; + edits.value = {}; + confirmLeave.value = false; + router.push(leaveTo.value); + } }, }); diff --git a/app/src/modules/settings/routes/webhooks/item.vue b/app/src/modules/settings/routes/webhooks/item.vue index 021c1ed1f4..033e870eae 100644 --- a/app/src/modules/settings/routes/webhooks/item.vue +++ b/app/src/modules/settings/routes/webhooks/item.vue @@ -64,6 +64,19 @@ + + + + {{ t('unsaved_changes') }} + {{ t('unsaved_changes_copy') }} + + + {{ t('discard_changes') }} + + {{ t('keep_editing') }} + + + @@ -72,10 +85,11 @@ import { useI18n } from 'vue-i18n'; import { defineComponent, computed, toRefs, ref } from 'vue'; import SettingsNavigation from '../../components/navigation.vue'; -import { useRouter } from 'vue-router'; +import { useRouter, onBeforeRouteUpdate, onBeforeRouteLeave, NavigationGuard } from 'vue-router'; import RevisionsDrawerDetail from '@/views/private/components/revisions-drawer-detail'; import useItem from '@/composables/use-item'; import SaveOptions from '@/views/private/components/save-options'; +import unsavedChanges from '@/composables/unsaved-changes'; export default defineComponent({ name: 'WebhooksItem', @@ -117,6 +131,26 @@ export default defineComponent({ return item.value?.name; }); + const isSavable = computed(() => { + if (hasEdits.value === true) return true; + return hasEdits.value; + }); + + unsavedChanges(isSavable); + + const confirmLeave = ref(false); + const leaveTo = ref(null); + + const editsGuard: NavigationGuard = (to) => { + if (hasEdits.value) { + confirmLeave.value = true; + leaveTo.value = to.fullPath; + return false; + } + }; + onBeforeRouteUpdate(editsGuard); + onBeforeRouteLeave(editsGuard); + return { t, item, @@ -136,6 +170,10 @@ export default defineComponent({ isBatch, title, validationErrors, + isSavable, + confirmLeave, + leaveTo, + discardAndLeave, }; async function saveAndQuit() { @@ -161,6 +199,13 @@ export default defineComponent({ await remove(); router.push(`/settings/webhooks`); } + + function discardAndLeave() { + if (!leaveTo.value) return; + edits.value = {}; + confirmLeave.value = false; + router.push(leaveTo.value); + } }, });