Files
mail/src/components/FolderSelectionTreeNode.vue
2026-03-27 20:36:56 -04:00

186 lines
4.2 KiB
Vue

<script setup lang="ts">
import { computed, ref } from 'vue'
import { useCollectionsStore } from '@MailManager/stores/collectionsStore'
import type { CollectionObject } from '@MailManager/models/collection'
import type { ServiceObject } from '@MailManager/models'
interface Props {
folder: CollectionObject
service: ServiceObject
selectedFolderKey: string | null
}
const props = defineProps<Props>()
const collectionsStore = useCollectionsStore()
const emit = defineEmits<{
select: [folder: CollectionObject]
}>()
const expanded = ref(false)
const folderKeyFor = (folder: CollectionObject): string => {
return `${folder.provider}:${String(folder.service)}:${String(folder.identifier)}`
}
const folderLabelFor = (folder: CollectionObject): string => {
return folder.properties.label || String(folder.identifier)
}
const folderIconFor = (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'
}
}
const folderColorFor = (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
}
}
const key = computed(() => folderKeyFor(props.folder))
const childFolders = computed(() => {
const serviceIdentifier = props.service.identifier
if (serviceIdentifier === null) {
return []
}
return collectionsStore.collectionsInCollection(props.service.provider, serviceIdentifier, props.folder.identifier)
})
const hasChildren = computed(() => {
const serviceIdentifier = props.service.identifier
if (serviceIdentifier === null) {
return false
}
return collectionsStore.hasChildrenInCollection(props.service.provider, serviceIdentifier, props.folder.identifier)
})
const isSelected = computed(() => props.selectedFolderKey === key.value)
const onSelect = () => {
emit('select', props.folder)
}
const toggleExpanded = () => {
expanded.value = !expanded.value
}
const onGroupDoubleClick = () => {
toggleExpanded()
onSelect()
}
</script>
<template>
<div
v-if="hasChildren"
class="folder-node-group"
>
<v-list-item
class="folder-node"
:title="folderLabelFor(folder)"
:active="isSelected"
@click="onSelect"
@dblclick.stop="onGroupDoubleClick"
>
<template #prepend>
<v-icon
:icon="folderIconFor(folder)"
:color="folderColorFor(folder)"
/>
</template>
<template #append>
<v-btn
variant="text"
density="compact"
size="x-small"
:icon="expanded ? 'mdi-chevron-down' : 'mdi-chevron-right'"
@click.stop="toggleExpanded"
/>
</template>
</v-list-item>
<div
v-if="expanded"
class="folder-node-children"
>
<FolderSelectionTreeNode
v-for="childFolder in childFolders"
:key="folderKeyFor(childFolder)"
:folder="childFolder"
:service="service"
:selected-folder-key="selectedFolderKey"
@select="emit('select', $event)"
/>
</div>
</div>
<v-list-item
v-else
class="folder-node"
:title="folderLabelFor(folder)"
:active="isSelected"
@click="onSelect"
>
<template #prepend>
<v-icon
:icon="folderIconFor(folder)"
:color="folderColorFor(folder)"
/>
</template>
</v-list-item>
</template>
<style scoped>
.folder-node {
--v-list-item-prepend-size: 22px;
}
.folder-node.v-list-item--active {
background-color: rgba(var(--v-theme-primary), 0.12);
}
.folder-node :deep(.v-list-item__prepend) {
padding-inline-start: 4px;
margin-inline-end: 2px;
}
.folder-node-children {
padding-left: 12px;
margin-left: 8px;
border-left: 2px solid rgba(var(--v-border-color), 0.3);
}
</style>