b7e12621cd
Signed-off-by: Sebastian Krupinski <krupinski01@gmail.com>
125 lines
3.9 KiB
TypeScript
125 lines
3.9 KiB
TypeScript
/**
|
|
* Contact Import Store
|
|
*/
|
|
|
|
import { computed, ref } from 'vue'
|
|
import { defineStore } from 'pinia'
|
|
import { entityService } from '../services/entityService'
|
|
import type { CollectionIdentifier } from '../types/common'
|
|
import type { EntityImportResponse } from '../types/entity'
|
|
import type {
|
|
ImportCounters,
|
|
ImportFileAdd,
|
|
ImportFileEntry,
|
|
ImportFileOptions,
|
|
ImportSession,
|
|
ImportSessionStage
|
|
} from '../types/import'
|
|
|
|
const RECENT_RESULTS_CAP = 25
|
|
const emptyCounters = (): ImportCounters => ({ discovered: 0, processed: 0, created: 0, updated: 0, exists: 0, error: 0 })
|
|
|
|
export const useImportStore = defineStore('chronoImportStore', () => {
|
|
|
|
// State
|
|
|
|
const nextId = ref(0)
|
|
const files = ref<ImportFileEntry[]>([])
|
|
const stage = ref<ImportSessionStage>('idle')
|
|
const running = ref(false)
|
|
const sessions = ref<Record<number, ImportSession>>({})
|
|
const order = ref<number[]>([])
|
|
|
|
// Computed
|
|
|
|
const totals = computed(() => order.value.reduce((total, id) => {
|
|
const counters = sessions.value[id]?.counters
|
|
if (counters) Object.keys(total).forEach(key => {
|
|
total[key as keyof ImportCounters] += counters[key as keyof ImportCounters]
|
|
})
|
|
return total
|
|
}, emptyCounters()))
|
|
|
|
// Actions
|
|
|
|
function addFile(file: ImportFileAdd): number {
|
|
const id = nextId.value++
|
|
files.value.push({
|
|
file: { id, ...file },
|
|
collectionId: null,
|
|
options: { supersede: false }
|
|
})
|
|
return id
|
|
}
|
|
|
|
function removeAllFiles() {
|
|
files.value = []
|
|
}
|
|
|
|
function reset() {
|
|
stage.value = 'idle';
|
|
running.value = false;
|
|
sessions.value = {};
|
|
order.value = [];
|
|
}
|
|
|
|
function entryFor(id: number) {
|
|
return files.value.find(entry => entry.file.id === id)
|
|
}
|
|
|
|
/** Set the destination collection for a queued file. */
|
|
function setCollectionForFile(id: number, collectionId: CollectionIdentifier | null) {
|
|
const entry = entryFor(id);
|
|
if (entry) entry.collectionId = collectionId;
|
|
}
|
|
|
|
/** Merge option changes for a queued file without dropping untouched keys. */
|
|
function setOptionsForFile(id: number, options: Partial<ImportFileOptions>) {
|
|
const entry = entryFor(id);
|
|
if (entry) entry.options = { ...entry.options, ...options };
|
|
}
|
|
|
|
async function startImport(): Promise<ImportCounters> {
|
|
const entries = [...files.value]
|
|
sessions.value = {}
|
|
order.value = entries.map(entry => entry.file.id)
|
|
for (const entry of entries) sessions.value[entry.file.id] = {
|
|
fileId: entry.file.id, fileName: entry.file.name, targetIdentifier: entry.collectionId,
|
|
status: 'pending', counters: emptyCounters(), recentResults: [], lastError: null,
|
|
}
|
|
|
|
running.value = true
|
|
stage.value = 'importing'
|
|
try {
|
|
for (const entry of entries) {
|
|
const session = sessions.value[entry.file.id]!
|
|
if (!entry.collectionId) throw new Error(`No calendar selected for "${entry.file.name}"`)
|
|
session.status = 'importing'
|
|
try {
|
|
await entityService.import(
|
|
{ target: entry.collectionId, data: entry.file.contents, options: entry.options },
|
|
(object: EntityImportResponse) => {
|
|
session.counters.processed++
|
|
session.counters[object.disposition]++
|
|
session.recentResults = [object, ...session.recentResults].slice(0, RECENT_RESULTS_CAP)
|
|
},
|
|
expected => { session.counters.discovered = expected },
|
|
)
|
|
session.status = session.counters.error ? 'error' : 'completed'
|
|
} catch (error) {
|
|
session.status = 'error'
|
|
session.lastError = error instanceof Error ? error.message : String(error)
|
|
throw error
|
|
}
|
|
}
|
|
stage.value = 'completed'
|
|
return totals.value
|
|
} catch (error) {
|
|
stage.value = 'error'
|
|
throw error
|
|
} finally { running.value = false }
|
|
}
|
|
|
|
return { files, stage, running, sessions, order, totals, addFile, removeAllFiles, reset, setCollectionForFile, setOptionsForFile, startImport }
|
|
})
|