From 31d20448be987f6feba69815587a684746f69862 Mon Sep 17 00:00:00 2001 From: Sebastian Krupinski Date: Mon, 29 Jun 2026 20:41:01 -0400 Subject: [PATCH] refactor: split uni store to per responsiblity store Signed-off-by: Sebastian Krupinski --- src/pages/ChronoPage.vue | 53 +++- src/stores/chronoOperationsStore.ts | 175 +++++++++++ src/stores/chronoSettingsStore.ts | 55 ++++ src/stores/chronoStore.ts | 471 ---------------------------- src/stores/chronoUiStore.ts | 241 ++++++++++++++ 5 files changed, 508 insertions(+), 487 deletions(-) create mode 100644 src/stores/chronoOperationsStore.ts create mode 100644 src/stores/chronoSettingsStore.ts delete mode 100644 src/stores/chronoStore.ts create mode 100644 src/stores/chronoUiStore.ts diff --git a/src/pages/ChronoPage.vue b/src/pages/ChronoPage.vue index 03e8254..cd5c50b 100644 --- a/src/pages/ChronoPage.vue +++ b/src/pages/ChronoPage.vue @@ -5,7 +5,11 @@ import { storeToRefs } from 'pinia'; import { useRoute } from 'vue-router'; import { useDisplay } from 'vuetify'; import { useModuleStore } from '@KTXC/stores/moduleStore'; -import { useChronoStore } from '@/stores/chronoStore'; +import { useChronoOperationsStore } from '@/stores/chronoOperationsStore'; +import { useChronoSettingsStore } from '@/stores/chronoSettingsStore'; +import { useChronoUiStore } from '@/stores/chronoUiStore'; +import type { CalendarView as CalendarViewType } from '@/types'; +import type { AgendaViewSpan, DaysViewSpan } from '@/types/spans'; import CollectionList from '@/components/CollectionList.vue'; import CollectionEditor from '@/components/CollectionEditor.vue'; import CalendarView from '@/components/CalendarView.vue'; @@ -24,13 +28,27 @@ const isChronoManagerAvailable = computed(() => { return moduleStore.has('chrono_manager') || moduleStore.has('ChronoManager') }); -const chronoStore = useChronoStore(); +const chronoOperationsStore = useChronoOperationsStore(); +const chronoSettingsStore = useChronoSettingsStore(); +const chronoUiStore = useChronoUiStore(); const route = useRoute(); const { calendarView, daysViewSpan, agendaViewSpan, +} = storeToRefs(chronoSettingsStore); + +const { + loading, + calendars, + taskLists, + filteredEvents, + filteredTasks, + collections, +} = storeToRefs(chronoOperationsStore); + +const { currentDate, sidebarVisible, selectedCollection, @@ -43,24 +61,14 @@ const { editingCollection, collectionEditorMode, collectionEditorType, - loading, - calendars, - taskLists, isTaskView, - filteredEvents, - filteredTasks, - collections, -} = storeToRefs(chronoStore); +} = storeToRefs(chronoUiStore); const { initialize, setViewMode, - setCalendarView, - setDaysViewSpan, - setAgendaViewSpan, selectCalendar, openEditCalendar, - toggleCalendarVisibility, selectTaskList, openEditTaskList, createEventFromDate, @@ -69,7 +77,6 @@ const { closeEntityEditor, editEvent, editTask, - toggleTaskComplete, saveEvent, deleteEvent, saveTask, @@ -80,7 +87,21 @@ const { openCreateTaskList, openImport, closeImport, -} = chronoStore; +} = chronoUiStore; + +const { toggleCalendarVisibility, toggleTaskComplete } = chronoOperationsStore; + +function setCalendarView(view: CalendarViewType) { + chronoSettingsStore.calendarView = view; +} + +function setDaysViewSpan(span: DaysViewSpan) { + chronoSettingsStore.daysViewSpan = span; +} + +function setAgendaViewSpan(span: AgendaViewSpan) { + chronoSettingsStore.agendaViewSpan = span; +} const fileInputRef = ref(null) function triggerFileInput() { fileInputRef.value?.click() } @@ -97,7 +118,7 @@ function syncViewModeFromRoute() { setViewMode(mode); } -function handleCalendarViewChange(view: 'days' | 'month' | 'agenda') { +function handleCalendarViewChange(view: CalendarViewType) { setCalendarView(view); } diff --git a/src/stores/chronoOperationsStore.ts b/src/stores/chronoOperationsStore.ts new file mode 100644 index 0000000..5d164d6 --- /dev/null +++ b/src/stores/chronoOperationsStore.ts @@ -0,0 +1,175 @@ +import { computed, ref } 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' + +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 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) + + 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 { + 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() + 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, + collections, + entities, + calendars, + taskLists, + events, + tasks, + filteredEvents, + filteredTasks, + saveEntity, + deleteEntity, + toggleCalendarVisibility, + toggleTaskComplete, + saveCollection, + deleteCollection, + prepareImport, + finishImport, + initialize, + } +}) diff --git a/src/stores/chronoSettingsStore.ts b/src/stores/chronoSettingsStore.ts new file mode 100644 index 0000000..121c307 --- /dev/null +++ b/src/stores/chronoSettingsStore.ts @@ -0,0 +1,55 @@ +import { computed } from 'vue' +import { defineStore } from 'pinia' +import { useUserStore } from '@KTXC/stores/userStore' +import type { CalendarView } from '@/types' +import { + AGENDA_VIEW_SPANS, + DAYS_VIEW_SPANS, + type AgendaViewSpan, + type DaysViewSpan, +} from '@/types/spans' + +const CALENDAR_VIEW_KEY = 'chrono.calendarView' +const DAYS_VIEW_SPAN_KEY = 'chrono.daysViewSpan' +const AGENDA_VIEW_SPAN_KEY = 'chrono.agendaViewSpan' + +function normalizeCalendarView(value: unknown): CalendarView { + return value === 'month' || value === 'agenda' || value === 'days' ? value : 'days' +} + +function normalizeDaysViewSpan(value: unknown): DaysViewSpan { + return typeof value === 'string' && DAYS_VIEW_SPANS.includes(value as DaysViewSpan) + ? value as DaysViewSpan + : '7d' +} + +function normalizeAgendaViewSpan(value: unknown): AgendaViewSpan { + return typeof value === 'string' && AGENDA_VIEW_SPANS.includes(value as AgendaViewSpan) + ? value as AgendaViewSpan + : '1w' +} + +export const useChronoSettingsStore = defineStore('chronoSettingsStore', () => { + const userStore = useUserStore() + + const calendarView = computed({ + get: () => normalizeCalendarView(userStore.getSetting(CALENDAR_VIEW_KEY)), + set: (value: CalendarView) => userStore.setSetting(CALENDAR_VIEW_KEY, normalizeCalendarView(value)), + }) + + const daysViewSpan = computed({ + get: () => normalizeDaysViewSpan(userStore.getSetting(DAYS_VIEW_SPAN_KEY)), + set: (value: DaysViewSpan) => userStore.setSetting(DAYS_VIEW_SPAN_KEY, normalizeDaysViewSpan(value)), + }) + + const agendaViewSpan = computed({ + get: () => normalizeAgendaViewSpan(userStore.getSetting(AGENDA_VIEW_SPAN_KEY)), + set: (value: AgendaViewSpan) => userStore.setSetting(AGENDA_VIEW_SPAN_KEY, normalizeAgendaViewSpan(value)), + }) + + return { + calendarView, + daysViewSpan, + agendaViewSpan, + } +}) diff --git a/src/stores/chronoStore.ts b/src/stores/chronoStore.ts deleted file mode 100644 index e6fc9d0..0000000 --- a/src/stores/chronoStore.ts +++ /dev/null @@ -1,471 +0,0 @@ -import { computed, ref, shallowRef } from 'vue' -import { defineStore } from 'pinia' -import { useUserStore } from '@KTXC/stores/userStore' -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 { CalendarView as CalendarViewType } from '@/types' -import { - AGENDA_VIEW_SPANS, - DAYS_VIEW_SPANS, - type AgendaViewSpan, - type DaysViewSpan, -} from '@/types/spans' - -type ChronoEntityProperties = EventObject | TaskObject - -export const useChronoStore = defineStore('chronoStore', () => { - const userStore = useUserStore() - const servicesStore = useServicesStore() - const collectionsStore = useCollectionsStore() - const entitiesStore = useEntitiesStore() - const importStore = useImportStore() - - const savedCalendarView = userStore.getSetting('chrono.calendarView') - const savedDaysViewSpan = userStore.getSetting('chrono.daysViewSpan') - const savedAgendaViewSpan = userStore.getSetting('chrono.agendaViewSpan') - - const viewMode = ref<'calendar' | 'tasks'>('calendar') - const calendarView = ref( - savedCalendarView === 'month' || savedCalendarView === 'agenda' || savedCalendarView === 'days' - ? savedCalendarView - : 'days', - ) - const daysViewSpan = ref( - typeof savedDaysViewSpan === 'string' && DAYS_VIEW_SPANS.includes(savedDaysViewSpan as DaysViewSpan) - ? (savedDaysViewSpan as DaysViewSpan) - : '7d', - ) - - const agendaViewSpan = ref( - typeof savedAgendaViewSpan === 'string' && AGENDA_VIEW_SPANS.includes(savedAgendaViewSpan as AgendaViewSpan) - ? (savedAgendaViewSpan as AgendaViewSpan) - : '1w', - ) - const currentDate = ref(new Date()) - const sidebarVisible = ref(true) - - const selectedCollection = shallowRef(null) - const selectedEntity = shallowRef(null) - - const showEventEditor = ref(false) - const showTaskEditor = ref(false) - const showCollectionEditor = ref(false) - const showImportDialog = ref(false) - - const entityEditorMode = ref<'edit' | 'view'>('view') - const editingCollection = shallowRef(null) - const collectionEditorMode = ref<'create' | 'edit'>('create') - const collectionEditorType = ref<'calendar' | 'tasklist'>('calendar') - - const loading = ref(false) - - const collections = computed(() => collectionsStore.collections) - const entities = computed(() => entitiesStore.entities) - - const calendars = computed(() => { - return collectionsStore.collections.filter(col => col.properties.content?.includes('event')) - }) - - const taskLists = computed(() => { - return collectionsStore.collections.filter(col => col.properties.content?.includes('task')) - }) - - const events = computed(() => { - return entities.value.filter(entity => entity.properties instanceof EventObject) - }) - - const tasks = computed(() => { - return entities.value.filter(entity => entity.properties instanceof TaskObject) - }) - - const isTaskView = computed(() => viewMode.value === 'tasks') - - const filteredEvents = computed(() => { - const visibleCalendarIds = calendars.value - .filter(cal => cal.properties.visibility !== false) - .map(cal => cal.identifier) - - return events.value.filter(event => visibleCalendarIds.includes(event.collection)) - }) - - const filteredTasks = computed(() => tasks.value) - - function setViewMode(mode: 'calendar' | 'tasks') { - viewMode.value = mode - } - - function setCalendarView(view: CalendarViewType) { - calendarView.value = view - userStore.setSetting('chrono.calendarView', view) - } - - function setDaysViewSpan(span: DaysViewSpan) { - if (!DAYS_VIEW_SPANS.includes(span)) { - return - } - - daysViewSpan.value = span - userStore.setSetting('chrono.daysViewSpan', span) - } - - function setAgendaViewSpan(span: AgendaViewSpan) { - agendaViewSpan.value = span - userStore.setSetting('chrono.agendaViewSpan', span) - } - - function selectCalendar(calendar: CollectionObject) { - selectedCollection.value = calendar - } - - function selectTaskList(list: CollectionObject) { - selectedCollection.value = list - } - - function openCreateCalendar() { - editingCollection.value = new CollectionObject() - editingCollection.value.properties.fromJson({ - ...editingCollection.value.properties.toJson(), - content: ['event'], - }) - collectionEditorMode.value = 'create' - collectionEditorType.value = 'calendar' - showCollectionEditor.value = true - } - - function openCreateTaskList() { - editingCollection.value = new CollectionObject() - editingCollection.value.properties.fromJson({ - ...editingCollection.value.properties.toJson(), - content: ['task'], - }) - collectionEditorMode.value = 'create' - collectionEditorType.value = 'tasklist' - showCollectionEditor.value = true - } - - function openEditCalendar(collection: CollectionObject) { - editingCollection.value = collection - collectionEditorMode.value = 'edit' - collectionEditorType.value = 'calendar' - showCollectionEditor.value = true - } - - function openEditTaskList(collection: CollectionObject) { - editingCollection.value = collection - collectionEditorMode.value = 'edit' - collectionEditorType.value = 'tasklist' - showCollectionEditor.value = true - } - - function ensureSelectedCollection(targetType: 'calendar' | 'tasklist'): CollectionObject | null { - const list = targetType === 'calendar' ? calendars.value : taskLists.value - - if (!selectedCollection.value && list.length > 0) { - selectedCollection.value = list[0] - } - - return selectedCollection.value - } - - function createEvent() { - const collection = ensureSelectedCollection('calendar') - if (!collection) { - return - } - - const entity = new EntityObject() - entity.properties = new EventObject() - selectedEntity.value = entity - entityEditorMode.value = 'edit' - showEventEditor.value = true - } - - function editEvent(entity: EntityObject) { - selectedEntity.value = entity - entityEditorMode.value = 'view' - showEventEditor.value = true - } - - function createTask() { - const collection = ensureSelectedCollection('tasklist') - if (!collection) { - return - } - - const entity = new EntityObject() - entity.properties = new TaskObject() - selectedEntity.value = entity - entityEditorMode.value = 'edit' - showTaskEditor.value = true - } - - function editTask(entity: EntityObject) { - selectedEntity.value = entity - entityEditorMode.value = 'view' - showTaskEditor.value = true - } - - function createEventFromDate(date: Date) { - const collection = ensureSelectedCollection('calendar') - if (!collection) { - return - } - - const entity = new EntityObject() - entity.properties = new EventObject() - const eventData = entity.properties as EventObject - eventData.startsOn = date.toISOString() - eventData.endsOn = new Date(date.getTime() + 60 * 60 * 1000).toISOString() - - selectedEntity.value = entity - entityEditorMode.value = 'edit' - showEventEditor.value = true - } - - function startEditingSelectedEntity() { - entityEditorMode.value = 'edit' - } - - function cancelEditingSelectedEntity() { - entityEditorMode.value = 'view' - } - - function closeEntityEditor() { - selectedEntity.value = null - entityEditorMode.value = 'view' - showEventEditor.value = false - showTaskEditor.value = false - } - - 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 persistEntity(entity: EntityObject, collection: CollectionObject): Promise { - const properties = prepareEntityProperties(entity).toJson() - - return entity.identifier - ? entitiesStore.update(entity.identifier, properties) - : entitiesStore.create(collection.identifier, properties) - } - - async function toggleCalendarVisibility(collection: CollectionObject) { - const nextVisibility = collection.properties.visibility === false - const updated = collection.clone() - updated.properties.visibility = nextVisibility - - await collectionsStore.update(updated.identifier, updated.properties) - } - - async function saveEvent(entity: EntityObject, collection?: CollectionObject | null) { - const targetCollection = collection instanceof CollectionObject - ? collection - : selectedCollection.value - - if (!targetCollection) { - throw new Error('No calendar collection selected') - } - - selectedEntity.value = await persistEntity(entity, targetCollection) - selectedCollection.value = targetCollection - entityEditorMode.value = 'view' - } - - async function saveTask(entity: EntityObject, collection?: CollectionObject | null) { - const targetCollection = collection instanceof CollectionObject - ? collection - : selectedCollection.value - - if (!targetCollection) { - throw new Error('No task list selected') - } - - selectedEntity.value = await persistEntity(entity, targetCollection) - selectedCollection.value = targetCollection - entityEditorMode.value = 'view' - } - - async function deleteEvent(entity: EntityObject, collection?: CollectionObject | null) { - if (!(collection instanceof CollectionObject) && !selectedCollection.value) { - throw new Error('No calendar collection selected') - } - - await entitiesStore.delete([entity.identifier]) - closeEntityEditor() - } - - async function deleteTask(entity: EntityObject, collection?: CollectionObject | null) { - if (!(collection instanceof CollectionObject) && !selectedCollection.value) { - throw new Error('No task list selected') - } - - await entitiesStore.delete([entity.identifier]) - closeEntityEditor() - } - - 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) { - if (collectionEditorMode.value === 'create') { - await collectionsStore.create( - service.provider, - service.identifier || '', - collection.properties, - ) - } else { - await collectionsStore.update(collection.identifier, collection.properties) - } - - showCollectionEditor.value = false - } - - async function deleteCollection(collection: CollectionObject) { - await collectionsStore.delete(collection.identifier) - - if (selectedCollection.value?.identifier === collection.identifier) { - selectedCollection.value = null - selectedEntity.value = null - } - - showCollectionEditor.value = false - } - - async function openImport(files: File[]) { - 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.value && collections.value.some(collection => collection.identifier === selectedCollection.value?.identifier)) { - importStore.setCollectionForFile(id, selectedCollection.value.identifier) - } - } - importStore.stage = 'selecting' - showImportDialog.value = true - } - - async function closeImport(refresh = false) { - showImportDialog.value = false - if (refresh) { - const targets = new Set() - 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) - } - - if ( - selectedCollection.value - && !collections.value.find(collection => collection.identifier === selectedCollection.value?.identifier) - ) { - selectedCollection.value = null - } - } finally { - loading.value = false - } - } - - return { - viewMode, - calendarView, - daysViewSpan, - agendaViewSpan, - currentDate, - sidebarVisible, - selectedCollection, - selectedEntity, - showEventEditor, - showTaskEditor, - showCollectionEditor, - showImportDialog, - entityEditorMode, - editingCollection, - collectionEditorMode, - collectionEditorType, - loading, - - collections, - entities, - calendars, - taskLists, - events, - tasks, - isTaskView, - filteredEvents, - filteredTasks, - - setViewMode, - setCalendarView, - setDaysViewSpan, - setAgendaViewSpan, - selectCalendar, - selectTaskList, - openCreateCalendar, - openCreateTaskList, - openEditCalendar, - openEditTaskList, - createEvent, - editEvent, - createTask, - editTask, - createEventFromDate, - startEditingSelectedEntity, - cancelEditingSelectedEntity, - closeEntityEditor, - toggleCalendarVisibility, - toggleTaskComplete, - saveEvent, - deleteEvent, - saveTask, - deleteTask, - saveCollection, - deleteCollection, - openImport, - closeImport, - initialize, - } -}) diff --git a/src/stores/chronoUiStore.ts b/src/stores/chronoUiStore.ts new file mode 100644 index 0000000..70edc6c --- /dev/null +++ b/src/stores/chronoUiStore.ts @@ -0,0 +1,241 @@ +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 { useChronoOperationsStore } from '@/stores/chronoOperationsStore' + +export const useChronoUiStore = defineStore('chronoUiStore', () => { + const operationsStore = useChronoOperationsStore() + + const viewMode = ref<'calendar' | 'tasks'>('calendar') + const currentDate = ref(new Date()) + const sidebarVisible = ref(true) + const selectedCollection = shallowRef(null) + const selectedEntity = shallowRef(null) + const showEventEditor = ref(false) + const showTaskEditor = ref(false) + const showCollectionEditor = ref(false) + const showImportDialog = ref(false) + const entityEditorMode = ref<'edit' | 'view'>('view') + const editingCollection = shallowRef(null) + const collectionEditorMode = ref<'create' | 'edit'>('create') + const collectionEditorType = ref<'calendar' | 'tasklist'>('calendar') + + const isTaskView = computed(() => viewMode.value === 'tasks') + + function setViewMode(mode: 'calendar' | 'tasks') { + viewMode.value = mode + } + + function selectCalendar(calendar: CollectionObject) { + selectedCollection.value = calendar + } + + function selectTaskList(list: CollectionObject) { + selectedCollection.value = list + } + + function openCollectionEditor(type: 'calendar' | 'tasklist') { + const collection = new CollectionObject() + collection.properties.fromJson({ + ...collection.properties.toJson(), + content: [type === 'calendar' ? 'event' : 'task'], + }) + editingCollection.value = collection + collectionEditorMode.value = 'create' + collectionEditorType.value = type + showCollectionEditor.value = true + } + + function openCreateCalendar() { + openCollectionEditor('calendar') + } + + function openCreateTaskList() { + openCollectionEditor('tasklist') + } + + function openEditCalendar(collection: CollectionObject) { + editingCollection.value = collection + collectionEditorMode.value = 'edit' + collectionEditorType.value = 'calendar' + showCollectionEditor.value = true + } + + function openEditTaskList(collection: CollectionObject) { + editingCollection.value = collection + collectionEditorMode.value = 'edit' + collectionEditorType.value = 'tasklist' + showCollectionEditor.value = true + } + + function ensureSelectedCollection(type: 'calendar' | 'tasklist'): CollectionObject | null { + const candidates = type === 'calendar' ? operationsStore.calendars : operationsStore.taskLists + if (!selectedCollection.value && candidates.length > 0) selectedCollection.value = candidates[0] + return selectedCollection.value + } + + function openEntityEditor(entity: EntityObject, type: 'event' | 'task', mode: 'edit' | 'view') { + selectedEntity.value = entity + entityEditorMode.value = mode + showEventEditor.value = type === 'event' + showTaskEditor.value = type === 'task' + } + + function createEventFromDate(date?: Date) { + if (!ensureSelectedCollection('calendar')) return + + const entity = new EntityObject() + entity.properties = new EventObject() + if (date) { + entity.properties.startsOn = date.toISOString() + entity.properties.endsOn = new Date(date.getTime() + 60 * 60 * 1000).toISOString() + } + openEntityEditor(entity, 'event', 'edit') + } + + function createEvent() { + createEventFromDate() + } + + function editEvent(entity: EntityObject) { + openEntityEditor(entity, 'event', 'view') + } + + function createTask() { + if (!ensureSelectedCollection('tasklist')) return + + const entity = new EntityObject() + entity.properties = new TaskObject() + openEntityEditor(entity, 'task', 'edit') + } + + function editTask(entity: EntityObject) { + openEntityEditor(entity, 'task', 'view') + } + + function startEditingSelectedEntity() { + entityEditorMode.value = 'edit' + } + + function cancelEditingSelectedEntity() { + entityEditorMode.value = 'view' + } + + function closeEntityEditor() { + selectedEntity.value = null + entityEditorMode.value = 'view' + showEventEditor.value = false + showTaskEditor.value = false + } + + async function saveSelectedEntity(entity: EntityObject, collection: CollectionObject | null | undefined, kind: string) { + const targetCollection = collection instanceof CollectionObject ? collection : selectedCollection.value + if (!targetCollection) throw new Error(`No ${kind} collection selected`) + + selectedEntity.value = await operationsStore.saveEntity(entity, targetCollection) + selectedCollection.value = targetCollection + entityEditorMode.value = 'view' + } + + async function saveEvent(entity: EntityObject, collection?: CollectionObject | null) { + await saveSelectedEntity(entity, collection, 'calendar') + } + + async function saveTask(entity: EntityObject, collection?: CollectionObject | null) { + await saveSelectedEntity(entity, collection, 'task list') + } + + async function deleteSelectedEntity(entity: EntityObject, collection: CollectionObject | null | undefined, kind: string) { + if (!(collection instanceof CollectionObject) && !selectedCollection.value) { + throw new Error(`No ${kind} collection selected`) + } + await operationsStore.deleteEntity(entity) + closeEntityEditor() + } + + async function deleteEvent(entity: EntityObject, collection?: CollectionObject | null) { + await deleteSelectedEntity(entity, collection, 'calendar') + } + + async function deleteTask(entity: EntityObject, collection?: CollectionObject | null) { + await deleteSelectedEntity(entity, collection, 'task list') + } + + async function saveCollection(collection: CollectionObject, service: ServiceObject) { + await operationsStore.saveCollection(collection, service, collectionEditorMode.value) + showCollectionEditor.value = false + } + + async function deleteCollection(collection: CollectionObject) { + await operationsStore.deleteCollection(collection) + if (selectedCollection.value?.identifier === collection.identifier) { + selectedCollection.value = null + selectedEntity.value = null + } + showCollectionEditor.value = false + } + + async function openImport(files: File[]) { + await operationsStore.prepareImport(files, selectedCollection.value) + showImportDialog.value = true + } + + async function closeImport(refresh = false) { + showImportDialog.value = false + await operationsStore.finishImport(refresh) + } + + async function initialize() { + await operationsStore.initialize() + if (selectedCollection.value && !operationsStore.collections.some( + collection => collection.identifier === selectedCollection.value?.identifier, + )) { + selectedCollection.value = null + } + } + + return { + viewMode, + currentDate, + sidebarVisible, + selectedCollection, + selectedEntity, + showEventEditor, + showTaskEditor, + showCollectionEditor, + showImportDialog, + entityEditorMode, + editingCollection, + collectionEditorMode, + collectionEditorType, + isTaskView, + setViewMode, + selectCalendar, + selectTaskList, + openCreateCalendar, + openCreateTaskList, + openEditCalendar, + openEditTaskList, + createEvent, + editEvent, + createTask, + editTask, + createEventFromDate, + startEditingSelectedEntity, + cancelEditingSelectedEntity, + closeEntityEditor, + saveEvent, + deleteEvent, + saveTask, + deleteTask, + saveCollection, + deleteCollection, + openImport, + closeImport, + initialize, + } +})