Fixed drag'n'drop in the Calendar (#5807)

Signed-off-by: Alexander Platov <alexander.platov@hardcoreeng.com>
This commit is contained in:
Alexander Platov 2024-06-13 18:18:18 +03:00 committed by GitHub
parent ca305f21d7
commit 7b466b8410
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
5 changed files with 58 additions and 16 deletions

View File

@ -774,6 +774,9 @@
inset: 0; inset: 0;
border-bottom: 1px solid var(--global-focus-BorderColor); border-bottom: 1px solid var(--global-focus-BorderColor);
} }
&.dragging > * {
opacity: .5;
}
} }
/* ToDo Line */ /* ToDo Line */

View File

@ -162,6 +162,7 @@
let scroller: HTMLElement let scroller: HTMLElement
let calendarWidth: number = 0 let calendarWidth: number = 0
let calendarRect: DOMRect let calendarRect: DOMRect
let containerRect: DOMRect
let colWidth: number = 0 let colWidth: number = 0
let nowLineTop: number = -1 let nowLineTop: number = -1
let timeNow: string = '--:--' let timeNow: string = '--:--'
@ -539,14 +540,15 @@
? rem((heightAD + 0.125) * (adMaxRow <= minAD ? adMaxRow : minAD) + 0.25) ? rem((heightAD + 0.125) * (adMaxRow <= minAD ? adMaxRow : minAD) + 0.25)
: rem((heightAD + 0.125) * (adMaxRow <= maxAD ? adMaxRow : maxAD) + 0.25) : rem((heightAD + 0.125) * (adMaxRow <= maxAD ? adMaxRow : maxAD) + 0.25)
$: showArrowAD = (!minimizedAD && adMaxRow > maxAD) || (minimizedAD && adMaxRow > minAD) $: showArrowAD = (!minimizedAD && adMaxRow > maxAD) || (minimizedAD && adMaxRow > minAD)
$: headerHeight = (showHeader ? rem(heightHeader) : 0) + styleAD
const getMinutes = (exactly: number): number => { const getMinutes = (exactly: number): number => {
const roundStep = 60 / stepsPerHour const roundStep = 60 / stepsPerHour
return Math.round(exactly / roundStep) * roundStep return Math.round(exactly / roundStep) * roundStep
} }
const getExactly = (e: MouseEvent): number => { const getExactly = (e: MouseEvent, correction: boolean = false): number => {
return Math.round((e.offsetY * 60) / cellHeight) return Math.round((e.offsetY * 60) / cellHeight) - (correction ? 15 : 0)
} }
const getStickyMinutes = ( const getStickyMinutes = (
@ -596,6 +598,7 @@
let oldTime: number = -1 let oldTime: number = -1
let originDate: Timestamp = 0 let originDate: Timestamp = 0
let originDueDate: Timestamp = 0 let originDueDate: Timestamp = 0
let scrollTimer: any = null
async function updateHandler (event: Event) { async function updateHandler (event: Event) {
const update: DocumentUpdate<Event> = {} const update: DocumentUpdate<Event> = {}
@ -631,6 +634,7 @@
directionResize = direction directionResize = direction
originDate = event.date originDate = event.date
originDueDate = event.dueDate originDueDate = event.dueDate
containerRect = scroller.getBoundingClientRect()
window.addEventListener('mouseup', mouseUpElement as any) window.addEventListener('mouseup', mouseUpElement as any)
} }
function mouseMoveElement ( function mouseMoveElement (
@ -653,11 +657,32 @@
if (newDate - events[index].date >= 15 * 60000) events[index].dueDate = newDate if (newDate - events[index].date >= 15 * 60000) events[index].dueDate = newDate
} }
events = events events = events
if (!scrollTimer) {
directionScroll(
e.clientY < containerRect.y + headerHeight + 16 && scroller.scrollTop > 0
? 'top'
: e.clientY > containerRect.bottom - 16 && scroller.scrollHeight - scroller.scrollTop > scroller.clientHeight
? 'bottom'
: 'none'
)
} }
}
const directionScroll = (direction: 'top' | 'bottom' | 'none'): void => {
if (direction === 'none') return
scrollTimer = setTimeout(() => (scrollTimer = null), 150)
scroller.scrollBy({ top: direction === 'top' ? -cellHeight * 2 : cellHeight * 2, left: 0, behavior: 'smooth' })
}
const transparentImage = new Image(1, 1)
transparentImage.src = 'data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7'
function dragStartElement (e: DragEvent & { currentTarget: EventTarget & HTMLDivElement }, event: Event): void { function dragStartElement (e: DragEvent & { currentTarget: EventTarget & HTMLDivElement }, event: Event): void {
if (isReadOnly(event) || event.allDay) return if (isReadOnly(event) || event.allDay) return
if (e.dataTransfer) e.dataTransfer.effectAllowed = 'all' if (e.dataTransfer) {
e.dataTransfer.setDragImage(transparentImage, 0, 0)
e.dataTransfer.effectAllowed = 'move'
}
originDate = event.date originDate = event.date
originDueDate = event.dueDate originDueDate = event.dueDate
dragOnOld = null dragOnOld = null
@ -674,7 +699,7 @@
function dragDrop (e: DragEvent, day: Date, hourOfDay: number): void { function dragDrop (e: DragEvent, day: Date, hourOfDay: number): void {
const hour = hourOfDay + startHour const hour = hourOfDay + startHour
const newTime = new Date(day).setHours(hour, getExactly(e), 0, 0) const newTime = new Date(day).setHours(hour, getExactly(e, true), 0, 0)
if (dragId) { if (dragId) {
if (oldTime === -1) oldTime = newTime if (oldTime === -1) oldTime = newTime
const index = events.findIndex((ev) => ev._id === dragId) const index = events.findIndex((ev) => ev._id === dragId)
@ -712,12 +737,15 @@
} }
function dragOver (e: DragEvent, day: Date, hourOfDay: number): void { function dragOver (e: DragEvent, day: Date, hourOfDay: number): void {
if (e.dataTransfer) e.dataTransfer.dropEffect = 'move' if (e.dataTransfer) {
e.dataTransfer.setDragImage(transparentImage, 0, 0)
e.dataTransfer.dropEffect = 'move'
}
e.preventDefault() e.preventDefault()
const dragOn: CalendarCell = { const dragOn: CalendarCell = {
day, day,
hourOfDay, hourOfDay,
minutes: getExactly(e) minutes: getExactly(e, true)
} }
if ( if (
dragOnOld !== null && dragOnOld !== null &&
@ -759,6 +787,11 @@
dispatch('dragEnter', { date: new Date(new Date(newTime).setMinutes(stickyMinutes, 0, 0)) }) dispatch('dragEnter', { date: new Date(new Date(newTime).setMinutes(stickyMinutes, 0, 0)) })
} }
} }
const dragLeave = (e: DragEvent): void => {
dispatch('dragOut')
e.preventDefault()
}
</script> </script>
<Scroller <Scroller
@ -768,6 +801,7 @@
<!-- svelte-ignore a11y-no-static-element-interactions --> <!-- svelte-ignore a11y-no-static-element-interactions -->
<div <div
bind:this={container} bind:this={container}
on:dragleave={dragLeave}
on:dragleave on:dragleave
class="calendar-container timeline-grid-bg" class="calendar-container timeline-grid-bg"
class:clearCells={clearCells || resizeId !== null || dragId !== null} class:clearCells={clearCells || resizeId !== null || dragId !== null}
@ -1150,29 +1184,28 @@
position: absolute; position: absolute;
left: 0; left: 0;
right: 0; right: 0;
height: 0.5rem; height: 0.25rem;
border-radius: 0.5rem; border-radius: 0.5rem;
cursor: row-resize;
&::after { &::after {
position: absolute; position: absolute;
content: ''; content: '';
left: -0.25rem; left: -0.25rem;
right: -0.25rem; right: -0.25rem;
height: 1rem; height: 0.5rem;
border: 1px solid transparent; border: 1px solid transparent;
border-radius: 0.5rem;
transition-property: opacity, border-width, transform; transition-property: opacity, border-width, transform;
transition-duration: 0.15s; transition-duration: 0.15s;
transition-timing-function: var(--timing-main); transition-timing-function: var(--timing-main);
transform: scale(0.9); transform: scale(0.9);
opacity: 0; opacity: 0;
cursor: row-resize;
filter: drop-shadow(0 0 2px var(--primary-edit-border-color));
pointer-events: none; pointer-events: none;
z-index: 10;
} }
&.allowed::after { &.allowed::after {
pointer-events: all; pointer-events: all;
z-index: 10; z-index: 100;
} }
&.allowed:hover::after, &.allowed:hover::after,
&.hovered::after { &.hovered::after {
@ -1185,6 +1218,7 @@
top: 0; top: 0;
&::after { &::after {
top: -0.25rem; top: -0.25rem;
border-radius: 0.5rem 0.5rem 0 0;
border-top-color: var(--primary-edit-border-color); border-top-color: var(--primary-edit-border-color);
} }
} }
@ -1192,6 +1226,7 @@
bottom: 0; bottom: 0;
&::after { &::after {
bottom: -0.25rem; bottom: -0.25rem;
border-radius: 0 0 0.5rem 0.5rem;
border-bottom-color: var(--primary-edit-border-color); border-bottom-color: var(--primary-edit-border-color);
} }
} }

View File

@ -26,7 +26,7 @@
// export let hourHeight: number // export let hourHeight: number
export let size: { width: number, height: number } export let size: { width: number, height: number }
$: oneRow = size.height < 42 || event.allDay $: oneRow = size.height < 25 || event.allDay
$: narrow = event.dueDate - event.date < MILLISECONDS_IN_MINUTE * 25 $: narrow = event.dueDate - event.date < MILLISECONDS_IN_MINUTE * 25
$: empty = size.width < 44 $: empty = size.width < 44

View File

@ -1,5 +1,4 @@
<script lang="ts"> <script lang="ts">
import { createEventDispatcher } from 'svelte'
import calendar, { Calendar, Event, generateEventId, getAllEvents } from '@hcengineering/calendar' import calendar, { Calendar, Event, generateEventId, getAllEvents } from '@hcengineering/calendar'
import { DayCalendar, calendarByIdStore, hidePrivateEvents } from '@hcengineering/calendar-resources' import { DayCalendar, calendarByIdStore, hidePrivateEvents } from '@hcengineering/calendar-resources'
import { PersonAccount } from '@hcengineering/contact' import { PersonAccount } from '@hcengineering/contact'
@ -30,7 +29,6 @@
export let displayedDaysCount = 1 export let displayedDaysCount = 1
export let createComponent: AnyComponent | undefined = calendar.component.CreateEvent export let createComponent: AnyComponent | undefined = calendar.component.CreateEvent
const dispatch = createEventDispatcher()
const q = createQuery() const q = createQuery()
function getFrom (date: Date): Timestamp { function getFrom (date: Date): Timestamp {
@ -150,6 +148,11 @@
raw = raw.filter((r) => r._id !== dragItemId) raw = raw.filter((r) => r._id !== dragItemId)
} }
} }
function dragOut () {
if (dragItemId != null) {
raw = raw.filter((r) => r._id !== dragItemId)
}
}
function clear (dragItem: ToDo | null) { function clear (dragItem: ToDo | null) {
if (dragItem === null) { if (dragItem === null) {
@ -219,6 +222,7 @@
clearCells={dragItem !== null} clearCells={dragItem !== null}
{dragItemId} {dragItemId}
on:dragEnter={dragEnter} on:dragEnter={dragEnter}
on:dragOut={dragOut}
on:dragleave={dragLeave} on:dragleave={dragLeave}
on:create={(e) => { on:create={(e) => {
showCreateDialog(e.detail.date, e.detail.withTime) showCreateDialog(e.detail.date, e.detail.withTime)

View File

@ -58,7 +58,7 @@
function handleDragStart (event: DragEvent): void { function handleDragStart (event: DragEvent): void {
if (event.dataTransfer) { if (event.dataTransfer) {
event.dataTransfer.effectAllowed = 'all' event.dataTransfer.effectAllowed = 'move'
} }
isDragging = true isDragging = true
dragging.update((state) => ({ dragging.update((state) => ({