mirror of
https://github.com/hcengineering/platform.git
synced 2025-04-13 19:58:09 +00:00
Calendar: resize and move event (#3750)
Signed-off-by: Alexander Platov <sas_lord@mail.ru>
This commit is contained in:
parent
75b71e9b1a
commit
a3fd97e3b4
@ -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; }
|
||||
|
@ -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);
|
||||
|
@ -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">
|
||||
|
@ -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 }} />
|
||||
|
@ -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()
|
||||
})
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user