Files
chrono/src/stores/chronoOperationsStore.ts
T
2026-06-29 21:52:09 -04:00

198 lines
6.5 KiB
TypeScript

import { computed, ref, shallowRef } from 'vue'
import { defineStore } from 'pinia'
import { CollectionObject } from '@ChronoManager/models/collection'
import { EntityObject } from '@ChronoManager/models/entity'
import { EventObject } from '@ChronoManager/models/event'
import { TaskObject } from '@ChronoManager/models/task'
import type { ServiceObject } from '@ChronoManager/models/service'
import { useCollectionsStore } from '@ChronoManager/stores/collectionsStore'
import { useEntitiesStore } from '@ChronoManager/stores/entitiesStore'
import { useServicesStore } from '@ChronoManager/stores/servicesStore'
import { useImportStore } from '@ChronoManager/stores/importStore'
import type { VisibleDateRange } from '@/types'
import { buildOccurrenceIndex, occurrencesForRange } from '@/utils/occurrenceIndex'
type ChronoEntityProperties = EventObject | TaskObject
export const useChronoOperationsStore = defineStore('chronoOperationsStore', () => {
const servicesStore = useServicesStore()
const collectionsStore = useCollectionsStore()
const entitiesStore = useEntitiesStore()
const importStore = useImportStore()
const loading = ref(false)
const visibleRange = shallowRef<VisibleDateRange | null>(null)
const collections = computed(() => collectionsStore.collections)
const entities = computed(() => entitiesStore.entities)
const calendars = computed(() => collections.value.filter(
collection => collection.properties.content?.includes('event'),
))
const taskLists = computed(() => collections.value.filter(
collection => collection.properties.content?.includes('task'),
))
const events = computed(() => entities.value.filter(
entity => entity.properties instanceof EventObject,
))
const tasks = computed(() => entities.value.filter(
entity => entity.properties instanceof TaskObject,
))
const filteredEvents = computed(() => {
const visibleCalendarIds = calendars.value
.filter(calendar => calendar.properties.visibility !== false)
.map(calendar => calendar.identifier)
return events.value.filter(event => visibleCalendarIds.includes(event.collection))
})
const filteredTasks = computed(() => tasks.value)
const occurrenceIndex = computed(() => buildOccurrenceIndex(filteredEvents.value))
const visibleOccurrences = computed(() => visibleRange.value
? occurrencesForRange(occurrenceIndex.value, visibleRange.value)
: [])
function setVisibleRange(range: VisibleDateRange) {
if (
visibleRange.value?.startMs === range.startMs
&& visibleRange.value.endMs === range.endMs
) {
return
}
visibleRange.value = { ...range }
}
function prepareEntityProperties(entity: EntityObject): ChronoEntityProperties {
const properties = entity.properties as ChronoEntityProperties
const now = new Date().toISOString()
properties.modified = now as never
if (!entity.identifier) {
properties.created = now as never
}
return properties
}
async function saveEntity(entity: EntityObject, collection: CollectionObject): Promise<EntityObject> {
const properties = prepareEntityProperties(entity).toJson()
return entity.identifier
? entitiesStore.update(entity.identifier, properties)
: entitiesStore.create(collection.identifier, properties)
}
async function deleteEntity(entity: EntityObject) {
await entitiesStore.delete([entity.identifier])
}
async function toggleCalendarVisibility(collection: CollectionObject) {
const updated = collection.clone()
updated.properties.visibility = collection.properties.visibility === false
await collectionsStore.update(updated.identifier, updated.properties)
}
async function toggleTaskComplete(taskId: string | number) {
const taskEntity = tasks.value.find(task => task.identifier === taskId)
if (!taskEntity) return
const taskData = taskEntity.properties as TaskObject
const isCompleted = taskData.status === 'completed'
taskData.status = isCompleted ? 'needs-action' : 'completed'
taskData.completedOn = isCompleted ? null : new Date().toISOString() as never
taskData.progress = isCompleted ? null : 100
taskData.modified = new Date().toISOString() as never
await entitiesStore.update(taskEntity.identifier, taskData.toJson())
}
async function saveCollection(
collection: CollectionObject,
service: ServiceObject,
mode: 'create' | 'edit',
) {
if (mode === 'create') {
await collectionsStore.create(service.provider, service.identifier || '', collection.properties)
return
}
await collectionsStore.update(collection.identifier, collection.properties)
}
async function deleteCollection(collection: CollectionObject) {
await collectionsStore.delete(collection.identifier)
}
async function prepareImport(files: File[], selectedCollection: CollectionObject | null) {
importStore.removeAllFiles()
importStore.reset()
for (const file of files) {
const id = importStore.addFile({
name: file.name,
contents: await file.text(),
size: file.size,
type: file.type,
})
if (selectedCollection && collections.value.some(
collection => collection.identifier === selectedCollection.identifier,
)) {
importStore.setCollectionForFile(id, selectedCollection.identifier)
}
}
importStore.stage = 'selecting'
}
async function finishImport(refresh = false) {
if (refresh) {
const targets = new Set<CollectionObject['identifier']>()
for (const id of importStore.order) {
const target = importStore.sessions[id]?.targetIdentifier
if (target) targets.add(target)
}
for (const target of targets) await entitiesStore.list([target])
}
importStore.removeAllFiles()
importStore.reset()
}
async function initialize() {
loading.value = true
try {
await servicesStore.list()
await collectionsStore.list()
const sources = collections.value.map(collection => collection.identifier)
if (sources.length > 0) await entitiesStore.list(sources)
} finally {
loading.value = false
}
}
return {
loading,
visibleRange,
collections,
entities,
calendars,
taskLists,
events,
tasks,
filteredEvents,
filteredTasks,
occurrenceIndex,
visibleOccurrences,
setVisibleRange,
saveEntity,
deleteEntity,
toggleCalendarVisibility,
toggleTaskComplete,
saveCollection,
deleteCollection,
prepareImport,
finishImport,
initialize,
}
})