refactor: split stores and use events
Signed-off-by: Sebastian <krupinski01@gmail.com>
This commit is contained in:
534
src/stores/mailUiStore.ts
Normal file
534
src/stores/mailUiStore.ts
Normal file
@@ -0,0 +1,534 @@
|
||||
import { computed, ref, shallowRef, watch } from 'vue'
|
||||
import { defineStore } from 'pinia'
|
||||
import { useCollectionsStore } from '@MailManager/stores/collectionsStore'
|
||||
import { useMailStore } from '@/stores/mailStore'
|
||||
import type { ServiceIdentifier, EntityIdentifier } from '@MailManager/types/common'
|
||||
import type { EntityObject, ServiceObject } from '@MailManager/models'
|
||||
import type { CollectionObject } from '@MailManager/models/collection'
|
||||
|
||||
export const useMailUiStore = defineStore('mailUiStore', () => {
|
||||
const collectionsStore = useCollectionsStore()
|
||||
const mailStore = useMailStore()
|
||||
|
||||
const sidebarVisible = ref(true)
|
||||
const settingsDialogVisible = ref(false)
|
||||
const composeMode = ref<'new' | 'reply' | 'forward'>('new')
|
||||
const composeSource = shallowRef<EntityObject | null>(null)
|
||||
const composeVisible = ref(false)
|
||||
const selectionMode = ref(false)
|
||||
const selectionList = ref<EntityIdentifier[]>([])
|
||||
const moveMessagesDialogVisible = ref(false)
|
||||
const moveMessagesDialogService = shallowRef<ServiceObject | null>(null)
|
||||
const moveMessagesDialogCandidates = ref<EntityIdentifier[] | null>(null)
|
||||
const createFolderDialogVisible = ref(false)
|
||||
const createFolderDialogService = shallowRef<ServiceObject | null>(null)
|
||||
const createFolderDialogParent = shallowRef<CollectionObject | null>(null)
|
||||
const createFolderDialogLoading = ref(false)
|
||||
const createFolderDialogError = ref('')
|
||||
const renameFolderDialogVisible = ref(false)
|
||||
const renameFolderDialogService = shallowRef<ServiceObject | null>(null)
|
||||
const renameFolderDialogFolder = shallowRef<CollectionObject | null>(null)
|
||||
const renameFolderDialogLoading = ref(false)
|
||||
const renameFolderDialogError = ref('')
|
||||
const moveFolderDialogVisible = ref(false)
|
||||
const moveFolderDialogService = shallowRef<ServiceObject | null>(null)
|
||||
const moveFolderDialogSource = shallowRef<CollectionObject | null>(null)
|
||||
const deleteFolderDialogVisible = ref(false)
|
||||
const deleteFolderDialogService = shallowRef<ServiceObject | null>(null)
|
||||
const deleteFolderDialogFolder = shallowRef<CollectionObject | null>(null)
|
||||
const deleteFolderDialogLoading = ref(false)
|
||||
const deleteFolderDialogError = ref('')
|
||||
|
||||
const createFolderDialogParentLabel = computed(() => {
|
||||
return createFolderDialogParent.value?.properties.label || 'Root'
|
||||
})
|
||||
|
||||
const renameFolderDialogParentLabel = computed(() => {
|
||||
const folder = renameFolderDialogFolder.value
|
||||
|
||||
if (!folder || folder.collection === null || folder.collection === undefined) {
|
||||
return 'Root'
|
||||
}
|
||||
|
||||
const parent = collectionsStore.collectionsInCollection(folder.provider, folder.service, null)
|
||||
.flatMap(rootFolder => [rootFolder, ...collectionsStore.collectionsForService(folder.provider, folder.service)])
|
||||
.find(candidate => String(candidate.identifier) === String(folder.collection))
|
||||
|
||||
return parent?.properties.label || 'Root'
|
||||
})
|
||||
|
||||
const moveFolderDialogInvalidFolderKeys = computed(() => {
|
||||
const sourceFolder = moveFolderDialogSource.value
|
||||
|
||||
if (!sourceFolder) {
|
||||
return []
|
||||
}
|
||||
|
||||
const invalidKeys = new Set<string>()
|
||||
const queue = [sourceFolder]
|
||||
|
||||
while (queue.length > 0) {
|
||||
const currentFolder = queue.shift()
|
||||
|
||||
if (!currentFolder) {
|
||||
continue
|
||||
}
|
||||
|
||||
invalidKeys.add(String(currentFolder.identifier))
|
||||
|
||||
collectionsStore
|
||||
.collectionsInCollection(currentFolder.provider, currentFolder.service, currentFolder.identifier)
|
||||
.forEach(childFolder => {
|
||||
queue.push(childFolder)
|
||||
})
|
||||
}
|
||||
|
||||
return Array.from(invalidKeys)
|
||||
})
|
||||
|
||||
watch(
|
||||
() => mailStore.selectedFolder,
|
||||
() => {
|
||||
closeCompose()
|
||||
deactivateSelectionMode()
|
||||
},
|
||||
)
|
||||
|
||||
watch(
|
||||
() => mailStore.selectedMessage,
|
||||
selectedMessage => {
|
||||
if (selectedMessage) {
|
||||
closeCompose()
|
||||
}
|
||||
},
|
||||
)
|
||||
|
||||
watch(
|
||||
() => mailStore.currentMessages,
|
||||
() => {
|
||||
reconcileSelection()
|
||||
},
|
||||
)
|
||||
|
||||
function validateFolderNameBase(service: ServiceObject, name: string): string[] {
|
||||
const errors: string[] = []
|
||||
|
||||
if (!name || name.trim().length === 0) {
|
||||
errors.push('Folder name is required')
|
||||
return errors
|
||||
}
|
||||
|
||||
if (name.length > 255) {
|
||||
errors.push('Folder name too long (max 255 characters)')
|
||||
}
|
||||
|
||||
if (/[<>:"|?*\x00-\x1F]/.test(name)) {
|
||||
errors.push('Folder name contains invalid characters')
|
||||
}
|
||||
|
||||
if (service.provider === 'imap' && /[\/\\]/.test(name)) {
|
||||
errors.push('IMAP folder names cannot contain / or \\')
|
||||
}
|
||||
|
||||
if (name !== name.trim()) {
|
||||
errors.push('Folder name cannot have leading or trailing spaces')
|
||||
}
|
||||
|
||||
return errors
|
||||
}
|
||||
|
||||
function validateCreateFolderName(name: string): string[] {
|
||||
const service = createFolderDialogService.value
|
||||
|
||||
if (!service || service.identifier === null) {
|
||||
return ['Folder service is unavailable']
|
||||
}
|
||||
|
||||
const errors = validateFolderNameBase(service, name)
|
||||
|
||||
if (errors.length > 0) {
|
||||
return errors
|
||||
}
|
||||
|
||||
const parentIdentifier = createFolderDialogParent.value?.identifier ?? null
|
||||
const duplicate = collectionsStore
|
||||
.collectionsInCollection(service.provider, service.identifier, parentIdentifier)
|
||||
.some(folder => folder.properties.label === name)
|
||||
|
||||
if (duplicate) {
|
||||
errors.push('A folder with this name already exists in this location')
|
||||
}
|
||||
|
||||
return errors
|
||||
}
|
||||
|
||||
function validateRenameFolderName(name: string): string[] {
|
||||
const service = renameFolderDialogService.value
|
||||
const folder = renameFolderDialogFolder.value
|
||||
|
||||
if (!service || !folder || service.identifier === null) {
|
||||
return ['Folder service is unavailable']
|
||||
}
|
||||
|
||||
const errors = validateFolderNameBase(service, name)
|
||||
|
||||
if (errors.length > 0) {
|
||||
return errors
|
||||
}
|
||||
|
||||
const parentIdentifier = folder.collection ?? null
|
||||
const duplicate = collectionsStore
|
||||
.collectionsInCollection(service.provider, service.identifier, parentIdentifier)
|
||||
.some(candidate =>
|
||||
String(candidate.identifier) !== String(folder.identifier) &&
|
||||
candidate.properties.label === name,
|
||||
)
|
||||
|
||||
if (duplicate) {
|
||||
errors.push('A folder with this name already exists in this location')
|
||||
}
|
||||
|
||||
return errors
|
||||
}
|
||||
|
||||
function setSelectionList(nextIds: EntityIdentifier[]) {
|
||||
selectionList.value = Array.from(new Set(nextIds))
|
||||
}
|
||||
|
||||
function openCompose(source?: EntityObject, mode: 'reply' | 'forward' = 'reply') {
|
||||
mailStore.clearSelectedMessage()
|
||||
composeSource.value = source ?? null
|
||||
composeMode.value = mode
|
||||
composeVisible.value = true
|
||||
}
|
||||
|
||||
function closeCompose() {
|
||||
composeMode.value = 'new'
|
||||
composeSource.value = null
|
||||
composeVisible.value = false
|
||||
}
|
||||
|
||||
async function afterSent() {
|
||||
closeCompose()
|
||||
await mailStore.reloadSelectedFolder()
|
||||
}
|
||||
|
||||
function clearSelection() {
|
||||
setSelectionList([])
|
||||
}
|
||||
|
||||
function activateSelectionMode(message?: EntityObject) {
|
||||
selectionMode.value = true
|
||||
|
||||
if (!message) {
|
||||
return
|
||||
}
|
||||
|
||||
const identifier = message.identifier
|
||||
|
||||
if (!selectionList.value.includes(identifier)) {
|
||||
setSelectionList([...selectionList.value, identifier])
|
||||
}
|
||||
}
|
||||
|
||||
function deactivateSelectionMode() {
|
||||
selectionMode.value = false
|
||||
clearSelection()
|
||||
}
|
||||
|
||||
function toggleMessageSelection(message: EntityObject) {
|
||||
const identifier = message.identifier
|
||||
|
||||
selectionMode.value = true
|
||||
|
||||
if (selectionList.value.includes(identifier)) {
|
||||
setSelectionList(selectionList.value.filter(selectedId => selectedId !== identifier))
|
||||
return
|
||||
}
|
||||
|
||||
setSelectionList([...selectionList.value, identifier])
|
||||
}
|
||||
|
||||
function selectAllCurrentMessages() {
|
||||
selectionMode.value = true
|
||||
setSelectionList(mailStore.currentMessages.map(message => message.identifier))
|
||||
}
|
||||
|
||||
function reconcileSelection() {
|
||||
if (!mailStore.selectedFolder) {
|
||||
clearSelection()
|
||||
return
|
||||
}
|
||||
|
||||
const currentMessageIdentifiers = new Set(mailStore.currentMessages.map(message => message.identifier))
|
||||
const nextSelectedIds = selectionList.value.filter(identifier => currentMessageIdentifiers.has(identifier))
|
||||
|
||||
if (nextSelectedIds.length !== selectionList.value.length) {
|
||||
setSelectionList(nextSelectedIds)
|
||||
}
|
||||
|
||||
if (nextSelectedIds.length === 0) {
|
||||
selectionMode.value = false
|
||||
}
|
||||
}
|
||||
|
||||
function toggleSidebar() {
|
||||
sidebarVisible.value = !sidebarVisible.value
|
||||
}
|
||||
|
||||
function closeSidebar() {
|
||||
sidebarVisible.value = false
|
||||
}
|
||||
|
||||
function openSettings() {
|
||||
settingsDialogVisible.value = true
|
||||
}
|
||||
|
||||
function closeSettings() {
|
||||
settingsDialogVisible.value = false
|
||||
}
|
||||
|
||||
async function openMoveMessagesDialog(entities?: EntityObject | EntityObject[]) {
|
||||
|
||||
let moveMessagesServiceIdentifier = null as ServiceIdentifier | null
|
||||
moveMessagesDialogCandidates.value = []
|
||||
|
||||
if (entities) {
|
||||
if (Array.isArray(entities)) {
|
||||
moveMessagesDialogCandidates.value = entities.map(entity => entity.identifier)
|
||||
moveMessagesServiceIdentifier = entities[0]?.service as ServiceIdentifier || null
|
||||
} else {
|
||||
moveMessagesDialogCandidates.value = [entities.identifier]
|
||||
moveMessagesServiceIdentifier = entities.service as ServiceIdentifier || null
|
||||
}
|
||||
} else {
|
||||
moveMessagesDialogCandidates.value = [...selectionList.value]
|
||||
moveMessagesServiceIdentifier = mailStore.selectedFolder?.service as ServiceIdentifier || null
|
||||
}
|
||||
|
||||
moveMessagesDialogService.value = await mailStore.retrieveService(moveMessagesServiceIdentifier);
|
||||
moveMessagesDialogVisible.value = true
|
||||
}
|
||||
|
||||
function closeMoveMessagesDialog() {
|
||||
moveMessagesDialogVisible.value = false
|
||||
moveMessagesDialogService.value = null
|
||||
moveMessagesDialogCandidates.value = null
|
||||
}
|
||||
|
||||
async function confirmMoveMessages(targetIdentifier: Parameters<typeof mailStore.moveMessages>[0]) {
|
||||
await mailStore.moveMessages(targetIdentifier, moveMessagesDialogCandidates.value ?? [])
|
||||
deactivateSelectionMode()
|
||||
closeMoveMessagesDialog()
|
||||
}
|
||||
|
||||
async function deleteSelectedMessages() {
|
||||
await mailStore.deleteMessages([...selectionList.value])
|
||||
deactivateSelectionMode()
|
||||
}
|
||||
|
||||
function openCreateFolderDialog(service: ServiceObject, parentFolder: CollectionObject | null = null) {
|
||||
createFolderDialogService.value = service
|
||||
createFolderDialogParent.value = parentFolder
|
||||
createFolderDialogError.value = ''
|
||||
createFolderDialogLoading.value = false
|
||||
createFolderDialogVisible.value = true
|
||||
}
|
||||
|
||||
function closeCreateFolderDialog() {
|
||||
createFolderDialogVisible.value = false
|
||||
createFolderDialogService.value = null
|
||||
createFolderDialogParent.value = null
|
||||
createFolderDialogError.value = ''
|
||||
createFolderDialogLoading.value = false
|
||||
}
|
||||
|
||||
async function confirmCreateFolder(label: string) {
|
||||
const service = createFolderDialogService.value
|
||||
|
||||
if (!service) {
|
||||
return null
|
||||
}
|
||||
|
||||
createFolderDialogLoading.value = true
|
||||
createFolderDialogError.value = ''
|
||||
|
||||
try {
|
||||
const folder = await mailStore.createFolder(service, label, createFolderDialogParent.value)
|
||||
closeCreateFolderDialog()
|
||||
return folder
|
||||
} catch (error) {
|
||||
createFolderDialogError.value = error instanceof Error ? error.message : 'Failed to create folder. Please try again.'
|
||||
throw error
|
||||
} finally {
|
||||
createFolderDialogLoading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
async function openRenameFolderDialog(target: CollectionObject) {
|
||||
const service = await mailStore.retrieveService(target.service)
|
||||
renameFolderDialogService.value = service
|
||||
renameFolderDialogFolder.value = target
|
||||
renameFolderDialogError.value = ''
|
||||
renameFolderDialogLoading.value = false
|
||||
renameFolderDialogVisible.value = true
|
||||
}
|
||||
|
||||
function closeRenameFolderDialog() {
|
||||
renameFolderDialogVisible.value = false
|
||||
renameFolderDialogService.value = null
|
||||
renameFolderDialogFolder.value = null
|
||||
renameFolderDialogError.value = ''
|
||||
renameFolderDialogLoading.value = false
|
||||
}
|
||||
|
||||
async function confirmRenameFolder(label: string) {
|
||||
const folder = renameFolderDialogFolder.value
|
||||
|
||||
if (!folder) {
|
||||
return null
|
||||
}
|
||||
|
||||
renameFolderDialogLoading.value = true
|
||||
renameFolderDialogError.value = ''
|
||||
|
||||
try {
|
||||
const updatedFolder = await mailStore.renameFolder(folder, label)
|
||||
closeRenameFolderDialog()
|
||||
return updatedFolder
|
||||
} catch (error) {
|
||||
renameFolderDialogError.value = error instanceof Error ? error.message : 'Failed to rename folder. Please try again.'
|
||||
throw error
|
||||
} finally {
|
||||
renameFolderDialogLoading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
async function openMoveFolderDialog(source: CollectionObject) {
|
||||
const service = await mailStore.retrieveService(source.service)
|
||||
moveFolderDialogService.value = service
|
||||
moveFolderDialogSource.value = source
|
||||
moveFolderDialogVisible.value = true
|
||||
}
|
||||
|
||||
function closeMoveFolderDialog() {
|
||||
moveFolderDialogVisible.value = false
|
||||
moveFolderDialogService.value = null
|
||||
moveFolderDialogSource.value = null
|
||||
}
|
||||
|
||||
async function confirmMoveFolder(target: CollectionObject) {
|
||||
const source = moveFolderDialogSource.value
|
||||
|
||||
if (!source) {
|
||||
return null
|
||||
}
|
||||
|
||||
const movedFolder = await mailStore.moveFolder(source, target)
|
||||
closeMoveFolderDialog()
|
||||
return movedFolder
|
||||
}
|
||||
|
||||
async function openDeleteFolderDialog(target: CollectionObject) {
|
||||
const service = await mailStore.retrieveService(target.service)
|
||||
deleteFolderDialogService.value = service
|
||||
deleteFolderDialogFolder.value = target
|
||||
deleteFolderDialogError.value = ''
|
||||
deleteFolderDialogLoading.value = false
|
||||
deleteFolderDialogVisible.value = true
|
||||
}
|
||||
|
||||
function closeDeleteFolderDialog() {
|
||||
deleteFolderDialogVisible.value = false
|
||||
deleteFolderDialogService.value = null
|
||||
deleteFolderDialogFolder.value = null
|
||||
deleteFolderDialogError.value = ''
|
||||
deleteFolderDialogLoading.value = false
|
||||
}
|
||||
|
||||
async function confirmDeleteFolder() {
|
||||
const folder = deleteFolderDialogFolder.value
|
||||
|
||||
if (!folder) {
|
||||
return null
|
||||
}
|
||||
|
||||
deleteFolderDialogLoading.value = true
|
||||
deleteFolderDialogError.value = ''
|
||||
|
||||
try {
|
||||
const deleted = await mailStore.deleteFolder(folder)
|
||||
closeDeleteFolderDialog()
|
||||
return deleted
|
||||
} catch (error) {
|
||||
deleteFolderDialogError.value = error instanceof Error ? error.message : 'Failed to delete folder. Please try again.'
|
||||
throw error
|
||||
} finally {
|
||||
deleteFolderDialogLoading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
sidebarVisible,
|
||||
settingsDialogVisible,
|
||||
composeMode,
|
||||
composeSource,
|
||||
composeVisible,
|
||||
selectionMode,
|
||||
selectionList,
|
||||
moveMessagesDialogVisible,
|
||||
moveMessagesDialogService,
|
||||
moveMessagesDialogCandidates,
|
||||
createFolderDialogParentLabel,
|
||||
createFolderDialogVisible,
|
||||
createFolderDialogService,
|
||||
createFolderDialogParent,
|
||||
createFolderDialogLoading,
|
||||
createFolderDialogError,
|
||||
renameFolderDialogVisible,
|
||||
renameFolderDialogService,
|
||||
renameFolderDialogFolder,
|
||||
renameFolderDialogParentLabel,
|
||||
renameFolderDialogLoading,
|
||||
renameFolderDialogError,
|
||||
moveFolderDialogVisible,
|
||||
moveFolderDialogService,
|
||||
moveFolderDialogSource,
|
||||
moveFolderDialogInvalidFolderKeys,
|
||||
deleteFolderDialogVisible,
|
||||
deleteFolderDialogService,
|
||||
deleteFolderDialogFolder,
|
||||
deleteFolderDialogLoading,
|
||||
deleteFolderDialogError,
|
||||
toggleSidebar,
|
||||
closeSidebar,
|
||||
openSettings,
|
||||
closeSettings,
|
||||
openCompose,
|
||||
closeCompose,
|
||||
afterSent,
|
||||
activateSelectionMode,
|
||||
deactivateSelectionMode,
|
||||
toggleMessageSelection,
|
||||
selectAllCurrentMessages,
|
||||
clearSelection,
|
||||
validateCreateFolderName,
|
||||
validateRenameFolderName,
|
||||
openMoveMessagesDialog,
|
||||
closeMoveMessagesDialog,
|
||||
confirmMoveMessages,
|
||||
deleteSelectedMessages,
|
||||
openCreateFolderDialog,
|
||||
closeCreateFolderDialog,
|
||||
confirmCreateFolder,
|
||||
openRenameFolderDialog,
|
||||
closeRenameFolderDialog,
|
||||
confirmRenameFolder,
|
||||
openMoveFolderDialog,
|
||||
closeMoveFolderDialog,
|
||||
confirmMoveFolder,
|
||||
openDeleteFolderDialog,
|
||||
closeDeleteFolderDialog,
|
||||
confirmDeleteFolder,
|
||||
}
|
||||
})
|
||||
Reference in New Issue
Block a user