Files
mail/src/pages/MailPage.vue
2026-03-30 14:43:26 -04:00

352 lines
9.3 KiB
Vue

<script setup lang="ts">
import { computed, onMounted } from 'vue'
import { storeToRefs } from 'pinia'
import { useDisplay } from 'vuetify'
import { useModuleStore } from '@KTXC'
import { useMailStore } from '@/stores/mailStore'
import type { CollectionObject, EntityObject } from '@MailManager/models'
import FolderTree from '@/components/FolderTree.vue'
import MessageList from '@/components/MessageList.vue'
import MessageReader from '@/components/MessageReader.vue'
import MessageComposer from '@/components/MessageComposer.vue'
import FolderSelectionDialog from '@/components/FolderSelectionDialog.vue'
import SettingsDialog from '@/components/settings/SettingsDialog.vue'
// Vuetify display for responsive behavior
const display = useDisplay()
const isMobile = computed(() => display.mdAndDown.value)
// Check if mail manager is available
const moduleStore = useModuleStore()
const isMailManagerAvailable = computed(() => {
return moduleStore.has('mail_manager') || moduleStore.has('MailManager')
})
// Mail module store
const mailStore = useMailStore()
// storeToRefs preserves reactivity for state and computed properties
const {
sidebarVisible,
settingsDialogVisible,
loading,
selectedFolder,
selectedMessage,
selectedMessageIds,
selectionModeActive,
composeMode,
composeReplyTo,
moveDialogVisible,
selectionCount,
hasSelection,
allCurrentMessagesSelected,
} = storeToRefs(mailStore)
// Complex store/composable objects accessed directly (not simple refs)
const { mailSync, entitiesStore } = mailStore
// Initialize
onMounted(async () => {
if (!isMailManagerAvailable.value) return
await mailStore.initialize()
})
// Handlers — thin wrappers that delegate to the store
const handleFolderSelect = (folder: CollectionObject) => mailStore.selectFolder(folder)
const handleMessageOpen = (message: EntityObject) => mailStore.selectMessage(message, isMobile.value)
const handleMessageSelectionToggle = (message: EntityObject) => mailStore.toggleMessageSelection(message)
const handleSelectionModeActivate = (message: EntityObject) => mailStore.activateSelectionMode(message)
const handleSelectAllToggle = (value: boolean) => {
if (value) {
mailStore.selectAllCurrentMessages()
return
}
mailStore.clearSelection()
}
const handleSelectionClear = () => mailStore.deactivateSelectionMode()
const handleSelectionMove = () => mailStore.openMoveDialogForSelection()
const handleCompose = (replyTo?: EntityObject) => mailStore.openCompose(replyTo)
const handleComposeClose = () => mailStore.closeCompose()
const handleComposeSent = () => mailStore.afterSent()
const handleReply = (message: EntityObject) => mailStore.openCompose(message)
const handleDelete = (message: EntityObject) => mailStore.deleteMessage(message)
const handleMove = (message: EntityObject) => mailStore.openMoveDialog(message)
const handleMoveConfirm = async (folder: CollectionObject) => {
await mailStore.moveMessages(folder)
}
const handleMoveCancel = () => mailStore.closeMoveDialog()
const toggleSidebar = () => mailStore.toggleSidebar()
const handleSettingsOpen = () => mailStore.openSettings()
const handleFolderCreated = (folder: CollectionObject) => mailStore.notify(`Folder "${folder.properties.label}" created`, 'success')
</script>
<template>
<!-- Manager Unavailable -->
<div v-if="!isMailManagerAvailable" class="mail-unavailable">
<v-alert
type="warning"
variant="outlined"
class="mail-unavailable-alert"
>
<v-icon size="64" color="warning" class="mb-6">mdi-email-off-outline</v-icon>
<h2 class="text-h5 font-weight-bold mb-4">Mail Manager Not Available</h2>
<p>
The Mail Manager module is not installed or enabled.
This module requires the <strong>mail_manager</strong> module to function properly.
</p>
<p class="mb-0 mt-2">
Please contact your system administrator to install and enable the
<code>mail_manager</code> module.
</p>
</v-alert>
</div>
<!-- Normal UI -->
<div v-else class="mail-container">
<!-- Top toolbar -->
<v-app-bar class="mail-toolbar" elevation="0" density="compact">
<v-app-bar-nav-icon
v-if="isMobile"
@click="toggleSidebar"
/>
<v-app-bar-title>Mail</v-app-bar-title>
<v-spacer />
<v-btn
icon="mdi-pencil"
@click="handleCompose()"
color="primary"
variant="text"
>
<v-icon>mdi-pencil</v-icon>
<v-tooltip activator="parent" location="bottom">Compose</v-tooltip>
</v-btn>
<v-btn
icon="mdi-refresh"
@click="mailSync.sync()"
:loading="mailSync.isRunning && entitiesStore.transceiving"
variant="text"
>
<v-icon>mdi-refresh</v-icon>
<v-tooltip activator="parent" location="bottom">
Refresh {{ mailSync.lastSync ? `(Last: ${new Date(mailSync.lastSync).toLocaleTimeString()})` : '' }}
</v-tooltip>
</v-btn>
<v-icon
v-if="mailSync.isRunning"
color="success"
size="small"
class="ml-2"
>
mdi-sync
</v-icon>
</v-app-bar>
<!-- Main content area -->
<div class="mail-content">
<!-- Folder tree sidebar -->
<v-navigation-drawer
v-model="sidebarVisible"
:permanent="!isMobile"
:temporary="isMobile"
width="280"
class="mail-sidebar"
>
<FolderTree
:selected-folder="selectedFolder"
@select="handleFolderSelect"
@folder-created="handleFolderCreated"
/>
<template #append>
<div class="pa-2">
<v-btn
block
variant="text"
prepend-icon="mdi-cog"
@click="handleSettingsOpen"
>
Settings
</v-btn>
</div>
</template>
</v-navigation-drawer>
<!-- Main area with message list and reader -->
<div class="mail-main">
<div class="mail-wrapper">
<!-- Message list panel -->
<div class="mail-list-panel">
<MessageList
<<<<<<< HEAD
:messages="currentMessages"
:selected-message="selectedMessage"
:selected-message-ids="selectedMessageIds"
:selection-mode-active="selectionModeActive"
:selection-count="selectionCount"
:has-selection="hasSelection"
:all-current-messages-selected="allCurrentMessagesSelected"
:selected-collection="selectedFolder"
=======
>>>>>>> 749d922 (refactor: improve logic)
:loading="loading"
@open="handleMessageOpen"
@toggle-selection="handleMessageSelectionToggle"
@activate-selection-mode="handleSelectionModeActivate"
@toggle-select-all="handleSelectAllToggle"
@clear-selection="handleSelectionClear"
@move-selection="handleSelectionMove"
/>
</div>
<!-- Reader/Composer panel -->
<div class="mail-reader-panel">
<MessageComposer
v-if="composeMode"
:reply-to="composeReplyTo"
:folder="selectedFolder"
@close="handleComposeClose"
@sent="handleComposeSent"
/>
<MessageReader
v-else
:message="selectedMessage"
@reply="handleReply"
@move="handleMove"
@delete="handleDelete"
@compose="handleCompose()"
/>
</div>
</div>
</div>
</div>
<!-- Settings Dialog -->
<SettingsDialog v-model="settingsDialogVisible" />
<FolderSelectionDialog
v-model="moveDialogVisible"
:loading="loading"
title="Move To"
confirm-text="Move"
empty-text="No other folders are available in this account."
@select="handleMoveConfirm"
@cancel="handleMoveCancel"
/>
</div>
</template>
<style scoped lang="scss">
.mail-unavailable {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
height: 100vh;
padding: 48px;
text-align: center;
width: 100%;
}
.mail-unavailable-alert {
width: 100%;
text-align: left;
}
.mail-container {
display: flex;
flex-direction: column;
height: 100vh;
isolation: isolate;
}
.mail-toolbar {
flex-shrink: 0;
}
.mail-content {
display: flex;
flex: 1;
overflow: hidden;
position: relative;
}
.mail-sidebar {
border-right: 1px solid rgb(var(--v-border-color));
overflow-y: auto;
}
.mail-main {
flex: 1;
display: flex;
overflow: hidden;
min-width: 0;
}
.mail-wrapper {
flex: 1;
display: flex;
overflow: hidden;
}
.mail-list-panel {
width: 320px;
min-width: 280px;
max-width: 450px;
border-right: 1px solid rgb(var(--v-border-color));
overflow-y: auto;
display: flex;
flex-direction: column;
}
.mail-reader-panel {
flex: 1;
overflow: hidden;
min-height: 0;
display: flex;
flex-direction: column;
}
/* Responsive adjustments */
@media (max-width: 960px) {
.mail-wrapper {
flex-direction: column;
}
.mail-list-panel {
width: 100%;
max-width: 100%;
border-right: none;
border-bottom: 1px solid rgb(var(--v-border-color));
max-height: 50%;
}
.mail-reader-panel {
width: 100%;
}
}
</style>