fix: modal lifecycle issues, multiple modals, new directory creation and discard changes behavior (#5773)

This commit is contained in:
Ariel Leyva
2026-02-21 12:12:10 -05:00
committed by GitHub
parent 3169a14a4d
commit 200d501547
12 changed files with 109 additions and 82 deletions

View File

@@ -1,21 +1,61 @@
<template>
<VueFinalModal
class="vfm-modal"
overlay-transition="vfm-fade"
content-transition="vfm-fade"
@closed="layoutStore.closeHovers"
:focus-trap="{
initialFocus: '#focus-prompt',
fallbackFocus: 'div.vfm__content',
}"
>
<slot />
</VueFinalModal>
<div id="modal-background" @click="backgroundClick">
<div ref="modalContainer">
<slot></slot>
</div>
</div>
</template>
<script setup lang="ts">
import { VueFinalModal } from "vue-final-modal";
import { useLayoutStore } from "@/stores/layout";
import { onMounted, ref } from "vue";
const layoutStore = useLayoutStore();
const emit = defineEmits(["closed"]);
const modalContainer = ref(null);
onMounted(() => {
const element = document.querySelector("#focus-prompt") as HTMLElement | null;
if (element) {
element.focus();
} else if (modalContainer.value) {
(modalContainer.value as HTMLElement).focus();
}
});
const backgroundClick = (event: Event) => {
const target = event.target as HTMLElement;
if (target.id == "modal-background") {
emit("closed");
}
};
window.addEventListener("keydown", (event) => {
if (event.key === "Escape") {
event.stopImmediatePropagation();
emit("closed");
}
});
</script>
<style scoped>
#modal-background {
position: fixed;
inset: 0;
background-color: #00000096;
display: flex;
justify-content: center;
align-items: center;
z-index: 10000;
animation: ease-in 150ms opacity-enter;
}
@keyframes opacity-enter {
from {
opacity: 0;
}
to {
opacity: 1;
}
}
</style>

View File

@@ -35,12 +35,17 @@ const props = defineProps({
type: Boolean,
default: false,
},
path: {
type: String,
default: null,
},
});
const container = ref<HTMLElement | null>(null);
const path = computed(() => {
let basePath = fileStore.isFiles ? route.path : url.removeLastDir(route.path);
const routePath = props.path || route.path;
let basePath = fileStore.isFiles ? routePath : url.removeLastDir(routePath);
if (!basePath.endsWith("/")) {
basePath += "/";
}

View File

@@ -168,7 +168,13 @@ export default {
this.showHover({
prompt: "newDir",
action: null,
confirm: null,
confirm: (url) => {
const paths = url.split("/");
this.items.push({
name: paths[paths.length - 2],
url: url,
});
},
props: {
redirect: false,
base: this.current === this.$route.path ? null : this.current,

View File

@@ -14,7 +14,7 @@
v-model.trim="name"
tabindex="1"
/>
<CreateFilePath :name="name" :is-dir="true" />
<CreateFilePath :name="name" :is-dir="true" :path="base" />
</div>
<div class="card-action">
@@ -41,7 +41,7 @@
</template>
<script setup lang="ts">
import { inject, ref } from "vue";
import { computed, inject, ref } from "vue";
import { useFileStore } from "@/stores/file";
import { useLayoutStore } from "@/stores/layout";
@@ -53,17 +53,13 @@ import CreateFilePath from "@/components/prompts/CreateFilePath.vue";
const $showError = inject<IToastError>("$showError")!;
const props = defineProps({
base: String,
redirect: {
type: Boolean,
default: true,
},
});
const fileStore = useFileStore();
const layoutStore = useLayoutStore();
const base = computed(() => {
return layoutStore.currentPrompt?.props?.base;
});
const route = useRoute();
const router = useRouter();
const { t } = useI18n();
@@ -76,7 +72,7 @@ const submit = async (event: Event) => {
// Build the path of the new directory.
let uri: string;
if (props.base) uri = props.base;
if (base.value) uri = base.value;
else if (fileStore.isFiles) uri = route.path + "/";
else uri = "/";
@@ -89,12 +85,15 @@ const submit = async (event: Event) => {
try {
await api.post(uri);
if (props.redirect) {
if (layoutStore.currentPrompt?.props?.redirect) {
router.push({ path: uri });
} else if (!props.base) {
} else if (!base.value) {
const res = await api.fetch(url.removeLastDir(uri) + "/");
fileStore.updateRequest(res);
}
if (layoutStore.currentPrompt?.confirm) {
layoutStore.currentPrompt?.confirm(uri);
}
} catch (e) {
if (e instanceof Error) {
$showError(e);

View File

@@ -1,10 +1,13 @@
<template>
<ModalsContainer />
<base-modal v-if="modal != null" :prompt="currentPromptName" @closed="close">
<keep-alive>
<component :is="modal" />
</keep-alive>
</base-modal>
</template>
<script setup lang="ts">
import { watch } from "vue";
import { ModalsContainer, useModal } from "vue-final-modal";
import { computed } from "vue";
import { storeToRefs } from "pinia";
import { useLayoutStore } from "@/stores/layout";
@@ -49,27 +52,15 @@ const components = new Map<string, any>([
["discardEditorChanges", DiscardEditorChanges],
]);
watch(currentPromptName, (newValue) => {
const modal = components.get(newValue!);
if (!modal) return;
const modal = computed(() => {
const modal = components.get(currentPromptName.value!);
if (!modal) null;
const { open, close } = useModal({
component: BaseModal,
slots: {
default: modal,
},
});
layoutStore.setCloseOnPrompt(close, newValue!);
open();
return modal;
});
window.addEventListener("keydown", (event) => {
const close = () => {
if (!layoutStore.currentPrompt) return;
if (event.key === "Escape") {
event.stopImmediatePropagation();
layoutStore.closeHovers();
}
});
layoutStore.closeHovers();
};
</script>