Initial commit
This commit is contained in:
340
src/components/FolderPageView.vue
Normal file
340
src/components/FolderPageView.vue
Normal file
@@ -0,0 +1,340 @@
|
||||
<script setup lang="ts">
|
||||
import { ref, computed } from 'vue'
|
||||
import type { CollectionObject } from '@MailManager/models/collection'
|
||||
import type { ServiceInterface } from '@MailManager/types/service'
|
||||
|
||||
// Props
|
||||
interface Props {
|
||||
selectedFolder?: CollectionObject | null
|
||||
serviceGroups: Array<{
|
||||
service: ServiceInterface
|
||||
folders: CollectionObject[]
|
||||
}>
|
||||
}
|
||||
|
||||
const props = defineProps<Props>()
|
||||
|
||||
// Emits
|
||||
const emit = defineEmits<{
|
||||
select: [folder: CollectionObject]
|
||||
createFolder: [service: ServiceInterface, parentFolder: CollectionObject | null]
|
||||
}>()
|
||||
|
||||
// Page-based navigation state
|
||||
const currentPageLevel = ref<(string | number | null)[]>([null]) // Stack of parent IDs (null = root)
|
||||
|
||||
// Get folders for current page level
|
||||
const getCurrentPageFolders = (folders: CollectionObject[]): CollectionObject[] => {
|
||||
const currentParent = currentPageLevel.value[currentPageLevel.value.length - 1]
|
||||
return folders.filter(f => {
|
||||
if (currentParent === null) {
|
||||
return f.collection === null || f.collection === undefined
|
||||
}
|
||||
return String(f.collection) === String(currentParent)
|
||||
})
|
||||
}
|
||||
|
||||
// Check if folder has children
|
||||
const hasChildren = (folder: CollectionObject, allFolders: CollectionObject[]): boolean => {
|
||||
return allFolders.some(f => String(f.collection) === String(folder.identifier))
|
||||
}
|
||||
|
||||
// Get icon for folder based on role
|
||||
const getFolderIcon = (folder: CollectionObject): string => {
|
||||
switch (folder.properties.role) {
|
||||
case 'inbox':
|
||||
return 'mdi-inbox'
|
||||
case 'sent':
|
||||
return 'mdi-send'
|
||||
case 'drafts':
|
||||
return 'mdi-file-document'
|
||||
case 'trash':
|
||||
return 'mdi-delete'
|
||||
case 'junk':
|
||||
return 'mdi-alert-octagon'
|
||||
case 'archive':
|
||||
return 'mdi-archive'
|
||||
case 'outbox':
|
||||
return 'mdi-tray-arrow-up'
|
||||
default:
|
||||
return 'mdi-folder'
|
||||
}
|
||||
}
|
||||
|
||||
// Get color for folder based on role
|
||||
const getFolderColor = (folder: CollectionObject): string | undefined => {
|
||||
switch (folder.properties.role) {
|
||||
case 'inbox':
|
||||
return 'primary'
|
||||
case 'sent':
|
||||
return 'success'
|
||||
case 'drafts':
|
||||
return 'warning'
|
||||
case 'trash':
|
||||
return 'error'
|
||||
case 'junk':
|
||||
return 'orange'
|
||||
default:
|
||||
return undefined
|
||||
}
|
||||
}
|
||||
|
||||
// Check if folder is selected
|
||||
const isSelected = (folder: CollectionObject): boolean => {
|
||||
if (!props.selectedFolder) return false
|
||||
return (
|
||||
folder.provider === props.selectedFolder.provider &&
|
||||
String(folder.service) === String(props.selectedFolder.service) &&
|
||||
String(folder.identifier) === String(props.selectedFolder.identifier)
|
||||
)
|
||||
}
|
||||
|
||||
// Handle folder click - just select it
|
||||
const handleFolderClick = (folder: CollectionObject) => {
|
||||
emit('select', folder)
|
||||
}
|
||||
|
||||
// Navigate into a folder to show its children
|
||||
const handleNavigateInto = (folderId: string | number) => {
|
||||
currentPageLevel.value.push(folderId)
|
||||
}
|
||||
|
||||
// Navigate back in page-based view
|
||||
const navigateBack = () => {
|
||||
if (currentPageLevel.value.length > 1) {
|
||||
currentPageLevel.value.pop()
|
||||
}
|
||||
}
|
||||
|
||||
// Get breadcrumb label for current page
|
||||
const getCurrentBreadcrumb = (folders: CollectionObject[]): string => {
|
||||
const currentParent = currentPageLevel.value[currentPageLevel.value.length - 1]
|
||||
if (currentParent === null) return 'All Folders'
|
||||
|
||||
const parentFolder = folders.find(f => String(f.identifier) === String(currentParent))
|
||||
return parentFolder?.properties.label || 'Folders'
|
||||
}
|
||||
|
||||
// Get current parent folder for dialog context
|
||||
const getCurrentParentFolder = (folders: CollectionObject[]): CollectionObject | null => {
|
||||
const currentParent = currentPageLevel.value[currentPageLevel.value.length - 1]
|
||||
if (currentParent === null) return null
|
||||
|
||||
// Search through all folders in the array
|
||||
const found = folders.find(f => String(f.identifier) === String(currentParent))
|
||||
return found || null
|
||||
}
|
||||
|
||||
// Computed helper to get all folders from all service groups
|
||||
const allFolders = computed(() => props.serviceGroups.flatMap(g => g.folders))
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div>
|
||||
<template v-for="group in serviceGroups" :key="`${group.service.provider}-${group.service.identifier}`">
|
||||
<!-- Service account group -->
|
||||
<v-list-group v-if="serviceGroups.length > 1">
|
||||
<template v-slot:activator="{ props: activatorProps }">
|
||||
<v-list-item
|
||||
v-bind="activatorProps"
|
||||
:title="group.service.label || 'Mail Account'"
|
||||
:subtitle="group.service.primaryAddress || undefined"
|
||||
>
|
||||
<template v-slot:prepend>
|
||||
<v-icon icon="mdi-email-outline" />
|
||||
</template>
|
||||
|
||||
<template v-slot:append>
|
||||
<v-btn
|
||||
icon="mdi-folder-plus"
|
||||
variant="text"
|
||||
size="small"
|
||||
density="compact"
|
||||
@click.stop="emit('createFolder', group.service, getCurrentParentFolder(allFolders))"
|
||||
>
|
||||
<v-icon>mdi-folder-plus</v-icon>
|
||||
<v-tooltip activator="parent" location="bottom">New Folder</v-tooltip>
|
||||
</v-btn>
|
||||
</template>
|
||||
</v-list-item>
|
||||
</template>
|
||||
|
||||
<!-- Back button if not at root -->
|
||||
<v-list-item
|
||||
v-if="currentPageLevel.length > 1"
|
||||
@click="navigateBack"
|
||||
prepend-icon="mdi-arrow-left"
|
||||
title="Back"
|
||||
/>
|
||||
|
||||
<!-- Breadcrumb with New Folder button -->
|
||||
<v-list-subheader v-if="currentPageLevel.length > 1" class="d-flex align-center">
|
||||
<span class="flex-grow-1">{{ getCurrentBreadcrumb(group.folders) }}</span>
|
||||
<v-btn
|
||||
icon="mdi-folder-plus"
|
||||
variant="text"
|
||||
size="x-small"
|
||||
@click="emit('createFolder', group.service, getCurrentParentFolder(allFolders))"
|
||||
>
|
||||
<v-icon size="small">mdi-folder-plus</v-icon>
|
||||
<v-tooltip activator="parent" location="bottom">New Subfolder</v-tooltip>
|
||||
</v-btn>
|
||||
</v-list-subheader>
|
||||
|
||||
<!-- Current level folders -->
|
||||
<v-list-item
|
||||
v-for="folder in getCurrentPageFolders(group.folders)"
|
||||
:key="`${folder.provider}-${folder.service}-${folder.identifier}`"
|
||||
:title="folder.properties.label"
|
||||
:active="isSelected(folder)"
|
||||
@click="handleFolderClick(folder)"
|
||||
>
|
||||
<template v-slot:prepend>
|
||||
<v-icon
|
||||
:icon="getFolderIcon(folder)"
|
||||
:color="getFolderColor(folder)"
|
||||
/>
|
||||
</template>
|
||||
|
||||
<template v-slot:append>
|
||||
<!-- Unread badge -->
|
||||
<v-badge
|
||||
v-if="folder.properties.unread && folder.properties.unread > 0"
|
||||
:content="folder.properties.unread"
|
||||
color="primary"
|
||||
inline
|
||||
class="mr-2"
|
||||
/>
|
||||
|
||||
<!-- Chevron for folders with children -->
|
||||
<v-btn
|
||||
v-if="hasChildren(folder, group.folders)"
|
||||
icon="mdi-chevron-right"
|
||||
variant="text"
|
||||
size="small"
|
||||
density="compact"
|
||||
@click.stop="handleNavigateInto(folder.identifier)"
|
||||
/>
|
||||
|
||||
<!-- Menu for folder actions -->
|
||||
<v-menu v-else>
|
||||
<template v-slot:activator="{ props: menuProps }">
|
||||
<v-btn
|
||||
v-bind="menuProps"
|
||||
icon="mdi-dots-vertical"
|
||||
variant="text"
|
||||
size="small"
|
||||
density="compact"
|
||||
@click.stop
|
||||
/>
|
||||
</template>
|
||||
|
||||
<v-list density="compact">
|
||||
<v-list-item
|
||||
prepend-icon="mdi-folder-plus"
|
||||
@click="emit('createFolder', group.service, folder)"
|
||||
>
|
||||
<v-list-item-title>New Subfolder</v-list-item-title>
|
||||
</v-list-item>
|
||||
</v-list>
|
||||
</v-menu>
|
||||
</template>
|
||||
</v-list-item>
|
||||
</v-list-group>
|
||||
|
||||
<!-- Single service - show folders directly -->
|
||||
<template v-else>
|
||||
<!-- Header with New Folder button -->
|
||||
<v-list-subheader class="d-flex align-center">
|
||||
<span class="flex-grow-1">
|
||||
{{ currentPageLevel.length > 1 ? getCurrentBreadcrumb(group.folders) : 'FOLDERS' }}
|
||||
</span>
|
||||
<v-btn
|
||||
icon="mdi-folder-plus"
|
||||
variant="text"
|
||||
size="x-small"
|
||||
@click="emit('createFolder', group.service, getCurrentParentFolder(allFolders))"
|
||||
>
|
||||
<v-icon size="small">mdi-folder-plus</v-icon>
|
||||
<v-tooltip activator="parent" location="bottom">
|
||||
{{ currentPageLevel.length > 1 ? 'New Subfolder' : 'New Folder' }}
|
||||
</v-tooltip>
|
||||
</v-btn>
|
||||
</v-list-subheader>
|
||||
|
||||
<!-- Back button if not at root -->
|
||||
<v-list-item
|
||||
v-if="currentPageLevel.length > 1"
|
||||
@click="navigateBack"
|
||||
prepend-icon="mdi-arrow-left"
|
||||
title="Back"
|
||||
/>
|
||||
|
||||
<!-- Current level folders -->
|
||||
<v-list-item
|
||||
v-for="folder in getCurrentPageFolders(group.folders)"
|
||||
:key="`${folder.provider}-${folder.service}-${folder.identifier}`"
|
||||
:title="folder.properties.label"
|
||||
:active="isSelected(folder)"
|
||||
@click="handleFolderClick(folder)"
|
||||
>
|
||||
<template v-slot:prepend>
|
||||
<v-icon
|
||||
:icon="getFolderIcon(folder)"
|
||||
:color="getFolderColor(folder)"
|
||||
/>
|
||||
</template>
|
||||
|
||||
<template v-slot:append>
|
||||
<!-- Unread badge -->
|
||||
<v-badge
|
||||
v-if="folder.properties.unread && folder.properties.unread > 0"
|
||||
:content="folder.properties.unread"
|
||||
color="primary"
|
||||
inline
|
||||
class="mr-2"
|
||||
/>
|
||||
|
||||
<!-- Chevron for folders with children or Menu for actions -->
|
||||
<v-btn
|
||||
v-if="hasChildren(folder, group.folders)"
|
||||
icon="mdi-chevron-right"
|
||||
variant="text"
|
||||
size="small"
|
||||
density="compact"
|
||||
@click.stop="handleNavigateInto(folder.identifier)"
|
||||
/>
|
||||
|
||||
<v-menu v-else>
|
||||
<template v-slot:activator="{ props: menuProps }">
|
||||
<v-btn
|
||||
v-bind="menuProps"
|
||||
icon="mdi-dots-vertical"
|
||||
variant="text"
|
||||
size="small"
|
||||
density="compact"
|
||||
@click.stop
|
||||
/>
|
||||
</template>
|
||||
|
||||
<v-list density="compact">
|
||||
<v-list-item
|
||||
prepend-icon="mdi-folder-plus"
|
||||
@click="emit('createFolder', group.service, folder)"
|
||||
>
|
||||
<v-list-item-title>New Subfolder</v-list-item-title>
|
||||
</v-list-item>
|
||||
</v-list>
|
||||
</v-menu>
|
||||
</template>
|
||||
</v-list-item>
|
||||
</template>
|
||||
</template>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.v-list-item--active {
|
||||
background-color: rgba(var(--v-theme-primary), 0.12);
|
||||
}
|
||||
</style>
|
||||
Reference in New Issue
Block a user