Initial commit
This commit is contained in:
229
src/components/FolderTreeNode.vue
Normal file
229
src/components/FolderTreeNode.vue
Normal file
@@ -0,0 +1,229 @@
|
||||
<script setup lang="ts">
|
||||
import type { CollectionObject } from '@MailManager/models/collection'
|
||||
import type { ServiceInterface } from '@MailManager/types/service'
|
||||
|
||||
export interface FolderNode {
|
||||
folder: CollectionObject
|
||||
children: FolderNode[]
|
||||
}
|
||||
|
||||
export interface Props {
|
||||
node: FolderNode
|
||||
service: ServiceInterface
|
||||
selectedFolder?: CollectionObject | null
|
||||
}
|
||||
|
||||
const props = defineProps<Props>()
|
||||
|
||||
const emit = defineEmits<{
|
||||
select: [folder: CollectionObject]
|
||||
createSubfolder: [service: ServiceInterface, parentFolder: CollectionObject]
|
||||
}>()
|
||||
|
||||
// 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)
|
||||
)
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<v-list-group v-if="node.children.length > 0" class="folder-tree-group">
|
||||
<template v-slot:activator="{ props: activatorProps }">
|
||||
<v-list-item
|
||||
v-bind="activatorProps"
|
||||
:title="node.folder.properties.label"
|
||||
:active="isSelected(node.folder)"
|
||||
@click.stop="emit('select', node.folder)"
|
||||
>
|
||||
<template v-slot:prepend>
|
||||
<v-icon
|
||||
:icon="getFolderIcon(node.folder)"
|
||||
:color="getFolderColor(node.folder)"
|
||||
/>
|
||||
</template>
|
||||
|
||||
<template v-slot:append>
|
||||
<v-badge
|
||||
v-if="node.folder.properties.unread && node.folder.properties.unread > 0"
|
||||
:content="node.folder.properties.unread"
|
||||
color="primary"
|
||||
inline
|
||||
class="mr-2"
|
||||
/>
|
||||
|
||||
<!-- Action menu -->
|
||||
<v-menu>
|
||||
<template v-slot:activator="{ props: menuProps }">
|
||||
<v-btn
|
||||
v-bind="menuProps"
|
||||
icon="mdi-dots-vertical"
|
||||
variant="text"
|
||||
size="x-small"
|
||||
density="compact"
|
||||
@click.stop
|
||||
>
|
||||
<v-icon size="small">mdi-dots-vertical</v-icon>
|
||||
</v-btn>
|
||||
</template>
|
||||
|
||||
<v-list density="compact">
|
||||
<v-list-item
|
||||
prepend-icon="mdi-folder-plus"
|
||||
@click="emit('createSubfolder', service, node.folder)"
|
||||
>
|
||||
<v-list-item-title>New Subfolder</v-list-item-title>
|
||||
</v-list-item>
|
||||
</v-list>
|
||||
</v-menu>
|
||||
</template>
|
||||
</v-list-item>
|
||||
</template>
|
||||
|
||||
<div class="folder-tree-children">
|
||||
<FolderTreeNode
|
||||
v-for="child in node.children"
|
||||
:key="child.folder.identifier"
|
||||
:node="child"
|
||||
:service="service"
|
||||
:selected-folder="selectedFolder"
|
||||
@select="emit('select', $event)"
|
||||
@create-subfolder="(service, parentFolder) => emit('createSubfolder', service, parentFolder)"
|
||||
/>
|
||||
</div>
|
||||
</v-list-group>
|
||||
|
||||
<v-list-item
|
||||
v-else
|
||||
:title="node.folder.properties.label"
|
||||
:active="isSelected(node.folder)"
|
||||
@click="emit('select', node.folder)"
|
||||
>
|
||||
<template v-slot:prepend>
|
||||
<v-icon
|
||||
:icon="getFolderIcon(node.folder)"
|
||||
:color="getFolderColor(node.folder)"
|
||||
/>
|
||||
</template>
|
||||
|
||||
<template v-slot:append>
|
||||
<v-badge
|
||||
v-if="node.folder.properties.unread && node.folder.properties.unread > 0"
|
||||
:content="node.folder.properties.unread"
|
||||
color="primary"
|
||||
inline
|
||||
class="mr-2"
|
||||
/>
|
||||
|
||||
<!-- Action menu -->
|
||||
<v-menu>
|
||||
<template v-slot:activator="{ props: menuProps }">
|
||||
<v-btn
|
||||
v-bind="menuProps"
|
||||
icon="mdi-dots-vertical"
|
||||
variant="text"
|
||||
size="x-small"
|
||||
density="compact"
|
||||
@click.stop
|
||||
>
|
||||
<v-icon size="small">mdi-dots-vertical</v-icon>
|
||||
</v-btn>
|
||||
</template>
|
||||
|
||||
<v-list density="compact">
|
||||
<v-list-item
|
||||
prepend-icon="mdi-folder-plus"
|
||||
@click="emit('createSubfolder', service, node.folder)"
|
||||
>
|
||||
<v-list-item-title>New Subfolder</v-list-item-title>
|
||||
</v-list-item>
|
||||
</v-list>
|
||||
</v-menu>
|
||||
</template>
|
||||
</v-list-item>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
export default {
|
||||
name: 'FolderTreeNode'
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.v-list-item--active {
|
||||
background-color: rgba(var(--v-theme-primary), 0.12);
|
||||
}
|
||||
|
||||
/* Remove indentation for the folder-tree-group itself */
|
||||
.folder-tree-group {
|
||||
--indent-padding: 0 !important;
|
||||
}
|
||||
|
||||
/* Reduce default indentation for nested items */
|
||||
.folder-tree-group :deep(.v-list-group__items) {
|
||||
--indent-padding: 0;
|
||||
}
|
||||
|
||||
/* Add visual indicator for nested items */
|
||||
.folder-tree-children {
|
||||
position: relative;
|
||||
padding-left: 12px;
|
||||
border-left: 2px solid rgba(var(--v-border-color), 0.3);
|
||||
margin-left: 8px;
|
||||
}
|
||||
|
||||
/* Add connector line for nested items */
|
||||
.folder-tree-children::before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
left: -2px;
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
width: 2px;
|
||||
background: rgba(var(--v-border-color), 0.3);
|
||||
}
|
||||
</style>
|
||||
Reference in New Issue
Block a user