refactor: improvemets

Signed-off-by: Sebastian Krupinski <krupinski01@gmail.com>
This commit is contained in:
2026-03-24 19:10:52 -04:00
parent b6d6bed2ee
commit da6a407445
16 changed files with 1063 additions and 254 deletions
+197
View File
@@ -0,0 +1,197 @@
<script setup lang="ts">
import { computed, defineAsyncComponent, ref, onMounted, onUnmounted } from 'vue'
import { EntityObject } from '@DocumentsManager/models/entity'
import { useFileEditor } from '@/composables'
const props = defineProps<{
modelValue: boolean
entity: EntityObject | null
readFile: (entityId: string) => Promise<string | null>
writeFile: (entityId: string, content: string) => Promise<number>
}>()
const emit = defineEmits<{
'update:modelValue': [value: boolean]
}>()
const fileEditor = useFileEditor()
// Track whether the editor component reports unsaved changes
const isDirty = ref(false)
const showDiscardDialog = ref(false)
const mime = computed(() => props.entity?.properties.mime ?? '')
const editor = computed(() => {
if (!mime.value) return null
return fileEditor.findEditor(mime.value)
})
const editorComponent = computed(() => {
if (!editor.value?.component) return null
return defineAsyncComponent(editor.value.component as () => Promise<unknown>)
})
const filename = computed(
() => props.entity?.properties.label ?? String(props.entity?.identifier ?? ''),
)
function handleDirtyChange(dirty: boolean) {
isDirty.value = dirty
}
function requestClose() {
if (isDirty.value) {
showDiscardDialog.value = true
} else {
close()
}
}
function close() {
isDirty.value = false
emit('update:modelValue', false)
}
function handleDiscardConfirm() {
showDiscardDialog.value = false
close()
}
// Keyboard: let Escape trigger close with guard
function handleKeydown(e: KeyboardEvent) {
if (!props.modelValue) return
if (e.key === 'Escape') {
e.preventDefault()
requestClose()
}
}
onMounted(() => window.addEventListener('keydown', handleKeydown))
onUnmounted(() => window.removeEventListener('keydown', handleKeydown))
</script>
<template>
<v-dialog
:model-value="modelValue"
fullscreen
transition="dialog-bottom-transition"
persistent
@update:model-value="requestClose"
>
<div class="editor-shell">
<!-- Toolbar -->
<div class="editor-toolbar">
<v-btn icon="mdi-close" variant="text" @click="requestClose" />
<v-icon class="ml-1 mr-2" size="small" color="medium-emphasis">mdi-pencil</v-icon>
<span class="editor-filename text-truncate">{{ filename }}</span>
<v-spacer />
</div>
<!-- Content area -->
<div class="editor-content">
<template v-if="entity">
<!-- Registered editor component -->
<Suspense v-if="editor">
<template #default>
<component
:is="editorComponent"
:entity="entity"
:mime="mime"
:read-file="readFile"
:write-file="writeFile"
class="editor-component"
@dirty="handleDirtyChange"
/>
</template>
<template #fallback>
<div class="editor-loading">
<v-progress-circular indeterminate color="primary" />
</div>
</template>
</Suspense>
<!-- No editor registered for this type -->
<div v-else class="editor-no-support">
<v-icon size="64" color="grey-lighten-1">mdi-file-edit-outline</v-icon>
<p class="text-h6 mt-4">No editor available</p>
<p class="text-body-2 text-medium-emphasis mb-6">
{{ mime || 'Unknown file type' }}
</p>
</div>
</template>
</div>
</div>
</v-dialog>
<!-- Discard changes confirmation -->
<v-dialog v-model="showDiscardDialog" max-width="400" persistent>
<v-card>
<v-card-title>Unsaved Changes</v-card-title>
<v-card-text>
You have unsaved changes. Are you sure you want to close without saving?
</v-card-text>
<v-card-actions>
<v-spacer />
<v-btn variant="text" @click="showDiscardDialog = false">Cancel</v-btn>
<v-btn color="error" variant="tonal" @click="handleDiscardConfirm">Discard</v-btn>
</v-card-actions>
</v-card>
</v-dialog>
</template>
<style scoped>
.editor-shell {
display: flex;
flex-direction: column;
height: 100%;
background: rgb(var(--v-theme-surface));
}
.editor-toolbar {
display: flex;
align-items: center;
gap: 4px;
padding: 4px 8px;
border-bottom: 1px solid rgba(var(--v-border-color), var(--v-border-opacity));
min-height: 56px;
flex-shrink: 0;
}
.editor-filename {
flex: 1;
font-size: 0.9375rem;
font-weight: 500;
}
.editor-content {
flex: 1;
display: flex;
overflow: hidden;
}
.editor-component {
width: 100%;
height: 100%;
}
.editor-loading {
display: flex;
align-items: center;
justify-content: center;
width: 100%;
height: 100%;
min-height: 200px;
}
.editor-no-support {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
text-align: center;
width: 100%;
padding: 32px;
}
</style>
+1
View File
@@ -0,0 +1 @@
export { default as FileEditorDialog } from './FileEditorDialog.vue'