From a3fd97e3b40afcf10cbe6681bb2e75332612b1bd Mon Sep 17 00:00:00 2001 From: Alexander Platov <sas_lord@mail.ru> Date: Thu, 28 Sep 2023 07:56:20 +0300 Subject: [PATCH] Calendar: resize and move event (#3750) Signed-off-by: Alexander Platov <sas_lord@mail.ru> --- packages/theme/styles/_layouts.scss | 1 + .../src/components/DayCalendar.svelte | 279 +++++++++++++++--- .../src/components/EditEvent.svelte | 97 +----- .../src/components/EventElement.svelte | 94 +----- plugins/calendar-resources/src/utils.ts | 97 +++++- 5 files changed, 343 insertions(+), 225 deletions(-) diff --git a/packages/theme/styles/_layouts.scss b/packages/theme/styles/_layouts.scss index 9f706f97aa..3053a9106c 100644 --- a/packages/theme/styles/_layouts.scss +++ b/packages/theme/styles/_layouts.scss @@ -793,6 +793,7 @@ a.no-line { .cursor-pointer { cursor: pointer; } .cursor-default { cursor: default; } .cursor-inherit { cursor: inherit; } +.cursor-row-resize { cursor: row-resize; } .pointer-events-none { pointer-events: none; } .content-pointer-events-none > * { pointer-events: none; } diff --git a/plugins/calendar-resources/src/components/DayCalendar.svelte b/plugins/calendar-resources/src/components/DayCalendar.svelte index 16fe395d02..9808609708 100644 --- a/plugins/calendar-resources/src/components/DayCalendar.svelte +++ b/plugins/calendar-resources/src/components/DayCalendar.svelte @@ -13,8 +13,9 @@ // limitations under the License. --> <script lang="ts"> - import { Event } from '@hcengineering/calendar' - import { Timestamp } from '@hcengineering/core' + import { Event, ReccuringInstance } from '@hcengineering/calendar' + import { Timestamp, Ref, DocumentUpdate } from '@hcengineering/core' + import { getClient } from '@hcengineering/presentation' import ui, { ActionIcon, CalendarItem, @@ -25,14 +26,19 @@ Scroller, addZero, areDatesEqual, + closeTooltip, deviceOptionsStore as deviceInfo, day as getDay, getMonday, getWeekDayName, - resizeObserver + resizeObserver, + showPopup, + getEventPositionElement } from '@hcengineering/ui' + import { Menu } from '@hcengineering/view-resources' import { createEventDispatcher, onDestroy, onMount } from 'svelte' import calendar from '../plugin' + import { updateReccuringInstance, isReadOnly } from '../utils' import EventElement from './EventElement.svelte' export let events: Event[] @@ -47,6 +53,7 @@ export let showHeader: boolean = true export let clearCells: boolean = false + const client = getClient() const dispatch = createEventDispatcher() const todayDate = new Date() @@ -132,6 +139,16 @@ dueDate: Timestamp cols: number } + interface CalendarElementRect { + top: number + bottom: number + left: number + right: number + width: number + height: number + fit: boolean + visibility: number + } interface CalendarColumn { elements: CalendarElement[] } @@ -169,6 +186,7 @@ let adMaxRow: number = 1 let adRows: CalendarADRows[] const cellBorder: number = 1 + const stepsPerHour: number = 4 const heightAD: number = 2 const minAD: number = 2 const maxAD: number = 3 @@ -178,6 +196,16 @@ let shownAD: boolean = false let shortAlldays: { id: string; day: number; fixRow?: boolean }[] = [] let moreCounts: number[] = Array<number>(displayedDaysCount) + const nullCalendarElement: CalendarElementRect = { + top: 0, + bottom: 0, + left: 0, + right: 0, + width: 0, + height: rem(heightAD), + fit: true, + visibility: 1 + } $: if (newEvents !== calendarEvents) { newEvents = calendarEvents @@ -340,14 +368,12 @@ return mins < 2 ? (end ? 1 : 2) : mins >= 57 ? (end ? 1 + cellBorder : 1) : 1 } - const getRect = ( - event: CalendarItem - ): { top: number; bottom: number; left: number; right: number; width: number; visibility: number } => { - const result = { top: 0, bottom: 0, left: 0, right: 0, width: 0, visibility: 1 } + const getRect = (event: CalendarItem): CalendarElementRect => { + const result = { ...nullCalendarElement } const checkDate = new Date(weekMonday.getTime() + MILLISECONDS_IN_DAY * event.day) const startDay = checkDate.setHours(startHour, 0, 0, 0) const endDay = checkDate.setHours(displayedHours - 1, 59, 59, 999) - const startTime = event.date < startDay ? { hours: 0, mins: 0 } : convertToTime(event.date) + const startTime = event.date <= startDay ? { hours: startHour, mins: 0 } : convertToTime(event.date) const endTime = event.dueDate > endDay ? { hours: displayedHours - startHour, mins: 0 } : convertToTime(event.dueDate) if (getDay(weekMonday, event.day).setHours(endTime.hours, endTime.mins, 0, 0) <= todayDate.getTime()) { @@ -383,12 +409,8 @@ (cols - index - 1) * rem(0.125) return result } - const getADRect = ( - id: string, - day?: number, - fixRow?: boolean - ): { top: number; left: number; width: number; height: number; fit: boolean; visibility: number } => { - const result = { top: 0, left: 0, width: 0, height: rem(heightAD), fit: true, visibility: 1 } + const getADRect = (id: string, day?: number, fixRow?: boolean): CalendarElementRect => { + const result = { ...nullCalendarElement } const index = adRows.findIndex((ev) => ev.id === id) const checkTime = new Date().setHours(0, 0, 0, 0) @@ -516,10 +538,116 @@ : rem((heightAD + 0.125) * (adMaxRow <= maxAD ? adMaxRow : maxAD) + 0.25) $: showArrowAD = (!minimizedAD && adMaxRow > maxAD) || (minimizedAD && adMaxRow > minAD) - let dragOnOld: CalendarCell | null = null + const getMinutes = (e: MouseEvent): number => { + let mins: number = 0 + for (let i = 0; i < stepsPerHour; i++) { + if (e.offsetY >= (i * cellHeight) / stepsPerHour && e.offsetY < ((i + 1) * cellHeight) / stepsPerHour) { + mins = (i * 60) / stepsPerHour + } + } + return mins + } - const getMinutes = (e: MouseEvent): number => (e.offsetY >= cellHeight / 2 ? 30 : 0) - const dragOver = (e: DragEvent & { currentTarget: EventTarget & HTMLDivElement }, day: Date, hourOfDay: number) => { + let dragOnOld: CalendarCell | null = null + let dragId: Ref<Event> | null = null + let resizeId: Ref<Event> | null = null + let directionResize: 'top' | 'bottom' | null + let oldMins: number = 0 + let oldTime: number = -1 + let originDate: Timestamp = 0 + let originDueDate: Timestamp = 0 + + async function updateHandler (event: Event) { + const update: DocumentUpdate<Event> = {} + if (originDate !== event.date) update.date = event.date + if (originDueDate !== event.dueDate) update.dueDate = event.dueDate + if (Object.keys(update).length > 0) { + if (event._class === calendar.class.ReccuringInstance) { + await updateReccuringInstance(update, event as ReccuringInstance) + } else { + await client.update(event, update) + } + } + } + async function mouseUpElement (e: MouseEvent) { + window.removeEventListener('mouseup', mouseUpElement) + const event = events.find((ev) => ev._id === resizeId) + if (event !== undefined) await updateHandler(event) + resizeId = directionResize = null + } + function mouseDownElement (e: MouseEvent, event: Event, direction: 'top' | 'bottom'): void { + if (e.buttons !== 1) return + e.stopPropagation() + resizeId = event._id + directionResize = direction + originDate = event.date + originDueDate = event.dueDate + window.addEventListener('mouseup', mouseUpElement) + } + function mouseMoveElement ( + e: MouseEvent & { currentTarget: EventTarget & HTMLDivElement }, + day: Date, + hour: number + ): void { + if (resizeId == null && directionResize == null) return + let mins: number = 0 + for (let i = 0; i < stepsPerHour; i++) { + if (e.offsetY >= (i * cellHeight) / stepsPerHour && e.offsetY < ((i + 1) * cellHeight) / stepsPerHour) mins = i + } + if (oldMins === mins) return + oldMins = mins + const newDate = new Date(day).setHours( + hour + startHour, + (mins * 60) / stepsPerHour + (directionResize === 'top' ? 0 : 60 / stepsPerHour), + 0, + 0 + ) + const index = events.findIndex((ev) => ev._id === resizeId) + if (index === -1) return + if (directionResize === 'top') { + if (events[index].dueDate - newDate >= 15 * 60000) events[index].date = newDate + } else { + if (newDate - events[index].date >= 15 * 60000) events[index].dueDate = newDate + } + events = events + } + function dragStartElement (e: DragEvent & { currentTarget: EventTarget & HTMLDivElement }, event: Event): void { + if (isReadOnly(event) || event.allDay) return + if (e.dataTransfer) e.dataTransfer.effectAllowed = 'all' + originDate = event.date + originDueDate = event.dueDate + dragOnOld = null + oldTime = -1 + closeTooltip() + setTimeout(() => (dragId = event._id), 50) + } + async function dragEndElement (e: DragEvent) { + const event = events.find((ev) => ev._id === dragId) + if (event !== undefined) await updateHandler(event) + dragId = null + } + function dragDrop (e: DragEvent, day: Date, hourOfDay: number): void { + const newTime = new Date(day).setHours(hourOfDay + startHour, getMinutes(e), 0, 0) + if (dragId) { + const index = events.findIndex((ev) => ev._id === dragId) + const diff = newTime - oldTime + if (diff && index !== -1 && oldTime !== -1) { + events[index].date = originDate + diff + events[index].dueDate = originDueDate + diff + events = events + } + } else { + dispatch('dragDrop', { + day, + hour: hourOfDay + startHour, + date: new Date(newTime) + }) + } + dragOnOld = null + } + function dragOver (e: DragEvent, day: Date, hourOfDay: number): void { + if (e.dataTransfer) e.dataTransfer.dropEffect = 'move' + e.preventDefault() const dragOn: CalendarCell = { day, hourOfDay, @@ -527,20 +655,31 @@ } if ( dragOnOld !== null && - dragOn.day === dragOnOld.day && + areDatesEqual(dragOn.day, dragOnOld.day) && dragOn.hourOfDay === dragOnOld.hourOfDay && dragOn.minutes === dragOnOld.minutes ) { return } dragOnOld = dragOn - dispatch('dragenter', { - date: new Date(day.setHours(hourOfDay + startHour, dragOn.minutes, 0, 0)) - }) - e.preventDefault() + const newTime = new Date(day).setHours(hourOfDay + startHour, dragOn.minutes, 0, 0) + if (dragId) { + if (oldTime === -1) oldTime = newTime + const index = events.findIndex((ev) => ev._id === dragId) + const diff = newTime - oldTime + if (diff && index !== -1) { + events[index].date = originDate + diff + events[index].dueDate = originDueDate + diff + } + events = events + } else dispatch('dragEnter', { date: new Date(newTime) }) } - const dragOn = (e: DragEvent) => e.preventDefault() + function showMenu (ev: MouseEvent, event: Event) { + ev.preventDefault() + closeTooltip() + showPopup(Menu, { object: event }, getEventPositionElement(ev)) + } </script> <Scroller @@ -550,9 +689,9 @@ <div bind:this={container} on:dragleave - on:dragover={dragOn} class="calendar-container" - class:clearCells + class:clearCells={clearCells || resizeId !== null || dragId !== null} + class:cursor-row-resize={resizeId !== null && directionResize !== null} style:--calendar-ad-height={styleAD + 'px'} style:grid={`${showHeader ? '[header] 3.5rem ' : ''}[all-day] ${styleAD}px repeat(${ (displayedHours - startHour) * 2 @@ -706,14 +845,9 @@ style:width={`${colWidth}px`} style:grid-column={`col-start ${dayOfWeek + 1} / ${dayOfWeek + 2}`} style:grid-row={`row-start ${hourOfDay * 2 + 1} / row-start ${hourOfDay * 2 + 3}`} + on:mousemove={(e) => mouseMoveElement(e, day, hourOfDay)} on:dragover={(e) => dragOver(e, day, hourOfDay)} - on:drop|preventDefault={(e) => { - dispatch('drop', { - day, - hour: hourOfDay + startHour, - date: new Date(day.setHours(hourOfDay + startHour, getMinutes(e), 0, 0)) - }) - }} + on:drop|preventDefault={(e) => dragDrop(e, day, hourOfDay)} on:click|stopPropagation={() => { dispatch('create', { date: new Date(day.setHours(hourOfDay + startHour, 0, 0, 0)), @@ -733,14 +867,31 @@ <!-- svelte-ignore a11y-no-noninteractive-tabindex --> <div class="calendar-element" + class:past={rect.visibility === 0} style:top={`${rect.top}px`} style:bottom={`${rect.bottom}px`} style:left={`${rect.left}px`} style:right={`${rect.right}px`} - style:opacity={rect.visibility === 0 ? 0.4 : 1} style:--mask-image={'none'} + draggable={!ev.allDay && !resizeId} tabindex={1000 + i} + on:dragstart={(e) => dragStartElement(e, ev)} + on:dragend={dragEndElement} > + <div + class="calendar-element-start" + class:allowed={!resizeId && !dragId && !clearCells} + class:hovered={resizeId === ev._id && directionResize === 'top'} + on:mousedown={(e) => mouseDownElement(e, ev, 'top')} + on:contextmenu={(e) => showMenu(e, ev)} + /> + <div + class="calendar-element-end" + class:allowed={!resizeId && !dragId && !clearCells} + class:hovered={resizeId === ev._id && directionResize === 'bottom'} + on:mousedown={(e) => mouseDownElement(e, ev, 'bottom')} + on:contextmenu={(e) => showMenu(e, ev)} + /> <EventElement event={ev} hourHeight={cellHeight} @@ -748,12 +899,6 @@ width: rect.width, height: (calendarRect?.height ?? rect.top + rect.bottom) - rect.top - rect.bottom }} - on:drop={(e) => { - dispatch('drop', { - date: new Date(event.date) - }) - }} - on:resize={() => (events = events)} /> </div> {/if} @@ -778,7 +923,7 @@ &::after { position: absolute; content: ''; - inset: 0; + inset: -1px; z-index: 5; } } @@ -847,7 +992,61 @@ &:not(.withPointer) { pointer-events: none; } + &-start, + &-end { + position: absolute; + left: 0; + right: 0; + height: 0.5rem; + border-radius: 0.5rem; + + &::after { + position: absolute; + content: ''; + left: -0.25rem; + right: -0.25rem; + height: 1rem; + border: 1px solid transparent; + border-radius: 0.5rem; + transition-property: opacity, border-width, transform; + transition-duration: 0.15s; + transition-timing-function: var(--timing-main); + transform: scale(0.9); + opacity: 0; + cursor: row-resize; + filter: drop-shadow(0 0 2px var(--primary-edit-border-color)); + pointer-events: none; + } + &.allowed::after { + pointer-events: all; + z-index: 10; + } + &.allowed:hover::after, + &.hovered::after { + border-width: 1px; + transform: scale(1); + opacity: 1; + } + } + &-start { + top: 0; + &::after { + top: -0.25rem; + border-top-color: var(--primary-edit-border-color); + } + } + &-end { + bottom: 0; + &::after { + bottom: -0.25rem; + border-bottom-color: var(--primary-edit-border-color); + } + } } + :global(.calendar-element.past .event-container) { + opacity: 0.4; + } + .sticky-header { position: sticky; background-color: var(--theme-comp-header-color); diff --git a/plugins/calendar-resources/src/components/EditEvent.svelte b/plugins/calendar-resources/src/components/EditEvent.svelte index 93ac4a2087..300c4cedd7 100644 --- a/plugins/calendar-resources/src/components/EditEvent.svelte +++ b/plugins/calendar-resources/src/components/EditEvent.svelte @@ -13,22 +13,21 @@ // limitations under the License. --> <script lang="ts"> - import { Event, ReccuringEvent, ReccuringInstance, RecurringRule, generateEventId } from '@hcengineering/calendar' + import { Event, ReccuringEvent, ReccuringInstance, RecurringRule } from '@hcengineering/calendar' import { Person } from '@hcengineering/contact' import { DocumentUpdate, Ref } from '@hcengineering/core' import presentation, { getClient } from '@hcengineering/presentation' import { StyledTextBox } from '@hcengineering/text-editor' - import { Button, DAY, EditBox, Icon, IconClose, closePopup, showPopup } from '@hcengineering/ui' + import { Button, EditBox, Icon, IconClose, showPopup } from '@hcengineering/ui' import { deepEqual } from 'fast-equals' import { createEventDispatcher } from 'svelte' import calendar from '../plugin' - import { isReadOnly, saveUTC } from '../utils' + import { isReadOnly, saveUTC, updateReccuringInstance } from '../utils' import EventParticipants from './EventParticipants.svelte' import EventReminders from './EventReminders.svelte' import EventTimeEditor from './EventTimeEditor.svelte' import EventTimeExtraButton from './EventTimeExtraButton.svelte' import ReccurancePopup from './ReccurancePopup.svelte' - import UpdateRecInstancePopup from './UpdateRecInstancePopup.svelte' export let object: Event $: readOnly = isReadOnly(object) @@ -96,7 +95,7 @@ if (Object.keys(update).length > 0) { if (object._class === calendar.class.ReccuringInstance) { - await updateHandler(update) + await updateReccuringInstance(update, object as ReccuringInstance) } else { await client.update(object, update) } @@ -124,94 +123,6 @@ } }) } - - async function updatePast (ops: DocumentUpdate<Event>) { - const obj = object as ReccuringInstance - const origin = await client.findOne(calendar.class.ReccuringEvent, { - eventId: obj.recurringEventId, - space: obj.space - }) - if (origin !== undefined) { - await client.addCollection( - calendar.class.ReccuringEvent, - origin.space, - origin.attachedTo, - origin.attachedToClass, - origin.collection, - { - ...origin, - date: obj.date, - dueDate: obj.dueDate, - ...ops, - eventId: generateEventId() - } - ) - const targetDate = ops.date ?? obj.date - await client.update(origin, { - rules: [{ ...origin.rules[0], endDate: targetDate - DAY }], - rdate: origin.rdate.filter((p) => p < targetDate) - }) - const instances = await client.findAll(calendar.class.ReccuringInstance, { - recurringEventId: origin.eventId, - date: { $gte: targetDate } - }) - for (const instance of instances) { - await client.remove(instance) - } - } - } - - async function updateHandler (ops: DocumentUpdate<ReccuringEvent>) { - const obj = object as ReccuringInstance - if (obj.virtual !== true) { - await client.update(object, ops) - } else { - showPopup(UpdateRecInstancePopup, { currentAvailable: ops.rules === undefined }, undefined, async (res) => { - if (res !== null) { - if (res.mode === 'current') { - await client.addCollection( - obj._class, - obj.space, - obj.attachedTo, - obj.attachedToClass, - obj.collection, - { - title: obj.title, - description: obj.description, - date: obj.date, - dueDate: obj.dueDate, - allDay: obj.allDay, - participants: obj.participants, - externalParticipants: obj.externalParticipants, - originalStartTime: obj.originalStartTime, - recurringEventId: obj.recurringEventId, - reminders: obj.reminders, - location: obj.location, - eventId: obj.eventId, - access: 'owner', - rules: obj.rules, - exdate: obj.exdate, - rdate: obj.rdate, - ...ops - }, - obj._id - ) - } else if (res.mode === 'all') { - const base = await client.findOne(calendar.class.ReccuringEvent, { - space: obj.space, - eventId: obj.recurringEventId - }) - if (base !== undefined) { - await client.update(base, ops) - } - } else if (res.mode === 'next') { - await updatePast(ops) - } - } - closePopup() - }) - } - } </script> <div class="eventPopup-container"> diff --git a/plugins/calendar-resources/src/components/EventElement.svelte b/plugins/calendar-resources/src/components/EventElement.svelte index d5cc7c4973..6dfd9f4e4d 100644 --- a/plugins/calendar-resources/src/components/EventElement.svelte +++ b/plugins/calendar-resources/src/components/EventElement.svelte @@ -14,20 +14,19 @@ --> <script lang="ts"> import calendar, { CalendarEventPresenter, Event } from '@hcengineering/calendar' - import { Doc, DocumentUpdate } from '@hcengineering/core' + import { Doc } from '@hcengineering/core' import { getClient } from '@hcengineering/presentation' import { Component, MILLISECONDS_IN_MINUTE, - deviceOptionsStore, + closeTooltip, getEventPositionElement, showPopup, tooltip } from '@hcengineering/ui' import view, { ObjectEditor } from '@hcengineering/view' import { Menu } from '@hcengineering/view-resources' - import { createEventDispatcher } from 'svelte' - import { calendarStore, isReadOnly, isVisible } from '../utils' + import { calendarStore, isVisible } from '../utils' import EventPresenter from './EventPresenter.svelte' export let event: Event @@ -56,93 +55,13 @@ let div: HTMLDivElement - const dispatch = createEventDispatcher() - - $: fontSize = $deviceOptionsStore.fontSize - - function dragStart (e: DragEvent) { - if (readOnly) return - if (event.allDay) return - originDate = event.date - originDueDate = event.dueDate - const rect = div.getBoundingClientRect() - const topThreshold = rect.y + fontSize / 2 - if (e.dataTransfer) { - e.dataTransfer.effectAllowed = 'move' - e.dataTransfer.dropEffect = 'move' - } - dragInitY = e.y - if (e.y < topThreshold) { - dragDirection = 'top' - } else { - const bottomThreshold = rect.y + rect.height - fontSize / 2 - if (e.y > bottomThreshold) { - dragDirection = 'bottom' - } else { - dragDirection = 'mid' - } - } - } - - let originDate = event.date - let originDueDate = event.dueDate - $: pixelPer15Min = hourHeight / 4 - let dragInitY: number | undefined - let dragDirection: 'bottom' | 'mid' | 'top' | undefined - - function drag (e: DragEvent) { - if (readOnly) return - if (event.allDay) return - if (dragInitY !== undefined) { - const diff = Math.floor((e.y - dragInitY) / pixelPer15Min) - if (diff) { - if (dragDirection !== 'bottom') { - const newValue = new Date(originDate).setMinutes(new Date(originDate).getMinutes() + 15 * diff) - if (dragDirection === 'top') { - if (newValue < event.dueDate) { - event.date = newValue - dispatch('resize') - } - } else { - const newDue = new Date(originDueDate).setMinutes(new Date(originDueDate).getMinutes() + 15 * diff) - event.date = newValue - event.dueDate = newDue - dispatch('resize') - } - } else { - const newDue = new Date(originDueDate).setMinutes(new Date(originDueDate).getMinutes() + 15 * diff) - if (newDue > event.date) { - event.dueDate = newDue - dispatch('resize') - } - } - } - } - } - - async function drop () { - const update: DocumentUpdate<Event> = {} - if (originDate !== event.date) { - update.date = event.date - } - if (originDueDate !== event.dueDate) { - update.dueDate = event.dueDate - } - if (Object.keys(update).length > 0) { - await client.update(event, { - dueDate: event.dueDate, - date: event.date - }) - } - } - function showMenu (ev: MouseEvent) { ev.preventDefault() + closeTooltip() showPopup(Menu, { object: event }, getEventPositionElement(ev)) } $: visible = isVisible(event, $calendarStore) - $: readOnly = isReadOnly(event) </script> {#if event} @@ -152,14 +71,9 @@ class="event-container" class:oneRow class:empty - draggable={!event.allDay} use:tooltip={{ component: EventPresenter, props: { value: event, hideDetails: !visible } }} on:click|stopPropagation={click} on:contextmenu={showMenu} - on:dragstart={dragStart} - on:drag={drag} - on:dragend={drop} - on:drop > {#if !empty && presenter?.presenter} <Component is={presenter.presenter} props={{ event, narrow, oneRow, hideDetails: !visible }} /> diff --git a/plugins/calendar-resources/src/utils.ts b/plugins/calendar-resources/src/utils.ts index 41f3d37241..6e49a42784 100644 --- a/plugins/calendar-resources/src/utils.ts +++ b/plugins/calendar-resources/src/utils.ts @@ -1,8 +1,10 @@ -import { Calendar, Event } from '@hcengineering/calendar' -import { IdMap, Timestamp, getCurrentAccount, toIdMap } from '@hcengineering/core' +import { Calendar, Event, ReccuringEvent, ReccuringInstance, generateEventId } from '@hcengineering/calendar' +import { IdMap, Timestamp, getCurrentAccount, toIdMap, DocumentUpdate } from '@hcengineering/core' import { createQuery, getClient } from '@hcengineering/presentation' +import { showPopup, closePopup, DAY } from '@hcengineering/ui' import { writable } from 'svelte/store' import calendar from './plugin' +import UpdateRecInstancePopup from './components/UpdateRecInstancePopup.svelte' export function saveUTC (date: Timestamp): Timestamp { const utcdate = new Date(date) @@ -76,3 +78,94 @@ function fillStores (): void { } fillStores() + +export async function updatePast (ops: DocumentUpdate<Event>, object: ReccuringInstance): Promise<void> { + const client = getClient() + const origin = await client.findOne(calendar.class.ReccuringEvent, { + eventId: object.recurringEventId, + space: object.space + }) + if (origin !== undefined) { + await client.addCollection( + calendar.class.ReccuringEvent, + origin.space, + origin.attachedTo, + origin.attachedToClass, + origin.collection, + { + ...origin, + date: object.date, + dueDate: object.dueDate, + ...ops, + eventId: generateEventId() + } + ) + const targetDate = ops.date ?? object.date + await client.update(origin, { + rules: [{ ...origin.rules[0], endDate: targetDate - DAY }], + rdate: origin.rdate.filter((p) => p < targetDate) + }) + const instances = await client.findAll(calendar.class.ReccuringInstance, { + recurringEventId: origin.eventId, + date: { $gte: targetDate } + }) + for (const instance of instances) { + await client.remove(instance) + } + } +} + +export async function updateReccuringInstance ( + ops: DocumentUpdate<ReccuringEvent>, + object: ReccuringInstance +): Promise<void> { + const client = getClient() + if (object.virtual !== true) { + await client.update(object, ops) + } else { + showPopup(UpdateRecInstancePopup, { currentAvailable: ops.rules === undefined }, undefined, async (res) => { + if (res !== null) { + if (res.mode === 'current') { + await client.addCollection( + object._class, + object.space, + object.attachedTo, + object.attachedToClass, + object.collection, + { + title: object.title, + description: object.description, + date: object.date, + dueDate: object.dueDate, + allDay: object.allDay, + participants: object.participants, + externalParticipants: object.externalParticipants, + originalStartTime: object.originalStartTime, + recurringEventId: object.recurringEventId, + reminders: object.reminders, + location: object.location, + eventId: object.eventId, + access: 'owner', + rules: object.rules, + exdate: object.exdate, + rdate: object.rdate, + ...ops + }, + object._id + ) + } else if (res.mode === 'all') { + const base = await client.findOne(calendar.class.ReccuringEvent, { + space: object.space, + eventId: object.recurringEventId + }) + if (base !== undefined) { + await client.update(base, ops) + } + } else if (res.mode === 'next') { + await updatePast(ops, object) + } + } + closePopup() + }) + } +}