UBER-668 UBER-669 UBER-670 UBER-671 (#3566)

Signed-off-by: Denis Bykhov <bykhov.denis@gmail.com>
This commit is contained in:
Denis Bykhov 2023-08-07 18:39:04 +06:00 committed by GitHub
parent 32fd80c971
commit afc881a7c6
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
10 changed files with 406 additions and 181 deletions

View File

@ -14,7 +14,15 @@
//
import activity from '@hcengineering/activity'
import { Calendar, Event, ReccuringEvent, ReccuringInstance, RecurringRule, calendarId } from '@hcengineering/calendar'
import {
Calendar,
CalendarEventPresenter,
Event,
ReccuringEvent,
ReccuringInstance,
RecurringRule,
calendarId
} from '@hcengineering/calendar'
import { Contact } from '@hcengineering/contact'
import { DateRangeMode, Domain, IndexKind, Markup, Ref, Timestamp } from '@hcengineering/core'
import {
@ -22,6 +30,7 @@ import {
Builder,
Collection,
Index,
Mixin,
Model,
Prop,
ReadOnly,
@ -35,13 +44,14 @@ import {
} from '@hcengineering/model'
import attachment from '@hcengineering/model-attachment'
import contact from '@hcengineering/model-contact'
import core, { TAttachedDoc } from '@hcengineering/model-core'
import core, { TAttachedDoc, TClass } from '@hcengineering/model-core'
import { TSpaceWithStates } from '@hcengineering/model-task'
import view, { createAction } from '@hcengineering/model-view'
import workbench from '@hcengineering/model-workbench'
import notification from '@hcengineering/notification'
import setting from '@hcengineering/setting'
import calendar from './plugin'
import { AnyComponent } from '@hcengineering/ui'
export * from '@hcengineering/calendar'
export { calendarId } from '@hcengineering/calendar'
@ -115,8 +125,13 @@ export class TReccuringInstance extends TEvent implements ReccuringInstance {
virtual?: boolean
}
@Mixin(calendar.mixin.CalendarEventPresenter, core.class.Class)
export class TCalendarEventPresenter extends TClass implements CalendarEventPresenter {
presenter!: AnyComponent
}
export function createModel (builder: Builder): void {
builder.createModel(TCalendar, TReccuringEvent, TReccuringInstance, TEvent)
builder.createModel(TCalendar, TReccuringEvent, TReccuringInstance, TEvent, TCalendarEventPresenter)
builder.createDoc(
workbench.class.Application,
@ -131,6 +146,10 @@ export function createModel (builder: Builder): void {
calendar.app.Calendar
)
builder.mixin(calendar.class.Event, core.class.Class, calendar.mixin.CalendarEventPresenter, {
presenter: calendar.component.CalendarEventPresenter
})
builder.createDoc(
view.class.Viewlet,
core.space.Model,

View File

@ -28,7 +28,8 @@ export default mergeIds(calendarId, calendar, {
IntegrationConnect: '' as AnyComponent,
CreateCalendar: '' as AnyComponent,
EventPresenter: '' as AnyComponent,
CalendarIntegrationIcon: '' as AnyComponent
CalendarIntegrationIcon: '' as AnyComponent,
CalendarEventPresenter: '' as AnyComponent
},
action: {
SaveEventReminder: '' as Ref<Action>,

View File

@ -172,7 +172,6 @@ export { default as Panel } from './components/Panel.svelte'
export { default as MonthCalendar } from './components/calendar/MonthCalendar.svelte'
export { default as YearCalendar } from './components/calendar/YearCalendar.svelte'
export { default as WeekCalendar } from './components/calendar/WeekCalendar.svelte'
export { default as DayCalendar } from './components/calendar/DayCalendar.svelte'
export { default as FocusHandler } from './components/FocusHandler.svelte'
export { default as ListView } from './components/ListView.svelte'

View File

@ -0,0 +1,37 @@
<!--
// Copyright © 2023 Hardcore Engineering Inc.
//
// Licensed under the Eclipse Public License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License. You may
// obtain a copy of the License at https://www.eclipse.org/legal/epl-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
//
// See the License for the specific language governing permissions and
// limitations under the License.
-->
<script lang="ts">
import { Event } from '@hcengineering/calendar'
import { addZero } from '@hcengineering/ui'
export let event: Event
export let oneRow: boolean = false
export let narrow: boolean = false
export let size: { width: number; height: number }
$: startDate = new Date(event.date)
$: endDate = new Date(event.dueDate)
const getTime = (date: Date): string => {
return `${addZero(date.getHours())}:${addZero(date.getMinutes())}`
}
</script>
{#if !narrow}
<b class="overflow-label">{event.title}</b>
{/if}
{#if !oneRow}
<span class="overflow-label text-sm">{getTime(startDate)}-{getTime(endDate)}</span>
{/if}

View File

@ -13,7 +13,7 @@
// limitations under the License.
-->
<script lang="ts">
import { Calendar, Event, getAllEvents } from '@hcengineering/calendar'
import { Calendar, Event, generateEventId, getAllEvents } from '@hcengineering/calendar'
import { PersonAccount } from '@hcengineering/contact'
import {
Class,
@ -30,27 +30,25 @@
import {
AnyComponent,
Button,
CalendarItem,
DayCalendar,
DropdownLabelsIntl,
IconBack,
IconForward,
MILLISECONDS_IN_DAY,
MonthCalendar,
YearCalendar,
areDatesEqual,
getMonday,
showPopup
} from '@hcengineering/ui'
import { CalendarMode } from '../index'
import { CalendarMode, DayCalendar } from '../index'
import calendar from '../plugin'
import Day from './Day.svelte'
import EventElement from './EventElement.svelte'
export let _class: Ref<Class<Doc>> = calendar.class.Event
export let query: DocumentQuery<Event> | undefined = undefined
export let options: FindOptions<Event> | undefined = undefined
export let createComponent: AnyComponent | undefined = calendar.component.CreateEvent
export let dragItem: Doc | undefined = undefined
export let dragEventClass: Ref<Class<Event>> = calendar.class.Event
export let allowedModes: CalendarMode[] = [
CalendarMode.Days,
CalendarMode.Week,
@ -117,7 +115,6 @@
const calendarsQuery = createQuery()
let calendars: Calendar[] = []
const offsetTZ = new Date().getTimezoneOffset() * 60 * 1000
calendarsQuery.query(calendar.class.Calendar, { createdBy: me._id }, (res) => {
calendars = res
@ -233,6 +230,50 @@
ddItems = ddItems
}
const dragItemId = 'drag_item' as Ref<Event>
function dragEnter (e: CustomEvent<any>) {
if (dragItem !== undefined) {
const current = raw.find((p) => p._id === dragItemId)
if (current !== undefined) {
current.attachedTo = dragItem._id
current.attachedToClass = dragItem._class
current.date = e.detail.date.getTime()
current.dueDate = new Date(e.detail.date).setMinutes(new Date(e.detail.date).getMinutes() + 30)
} else {
const me = getCurrentAccount() as PersonAccount
raw.push({
_id: dragItemId,
allDay: false,
eventId: generateEventId(),
title: '',
description: '',
access: 'owner',
attachedTo: dragItem._id,
attachedToClass: dragItem._class,
_class: dragEventClass,
collection: 'events',
space: dragItem.space,
modifiedBy: me._id,
participants: [me.person],
modifiedOn: Date.now(),
date: e.detail.date.getTime(),
dueDate: new Date(e.detail.date).setMinutes(new Date(e.detail.date).getMinutes() + 30)
})
}
raw = raw
}
}
$: clear(dragItem)
function clear (dragItem: Doc | undefined) {
if (dragItem === undefined) {
raw = raw.filter((p) => p._id !== dragItemId)
objects = getAllEvents(raw, from, to)
}
}
$: getDdItems(allowedModes)
let ddItems: {
@ -247,55 +288,6 @@
{ id: 'month', label: calendar.string.ModeMonth, mode: CalendarMode.Month },
{ id: 'year', label: calendar.string.ModeYear, mode: CalendarMode.Year }
]
const toCalendar = (
events: Event[],
date: Date,
days: number = 1,
startHour: number = 0,
endHour: number = 24
): CalendarItem[] => {
const result: CalendarItem[] = []
for (let day = 0; day < days; day++) {
const startDay = new Date(MILLISECONDS_IN_DAY * day + date.getTime()).setHours(0, 0, 0, 0)
const startDate = new Date(MILLISECONDS_IN_DAY * day + date.getTime()).setHours(startHour, 0, 0, 0)
const lastDate = new Date(MILLISECONDS_IN_DAY * day + date.getTime()).setHours(endHour, 0, 0, 0)
events.forEach((event) => {
const eventStart = event.allDay ? event.date + offsetTZ : event.date
const eventEnd = event.allDay ? event.dueDate + offsetTZ : event.dueDate
if ((eventStart < lastDate && eventEnd > startDate) || (eventStart === eventEnd && eventStart === startDay)) {
result.push({
_id: event._id,
allDay: event.allDay,
date: eventStart,
dueDate: eventEnd,
day,
access: event.access
})
}
})
}
const sd = date.setHours(0, 0, 0, 0)
const ld = new Date(MILLISECONDS_IN_DAY * (days - 1) + date.getTime()).setHours(23, 59, 59, 999)
events
.filter((ev) => ev.allDay)
.sort((a, b) => b.dueDate - b.date - (a.dueDate - a.date))
.forEach((event) => {
const eventStart = event.date + offsetTZ
const eventEnd = event.dueDate + offsetTZ
if ((eventStart < ld && eventEnd > sd) || (eventStart === eventEnd && eventStart === sd)) {
result.push({
_id: event._id,
allDay: event.allDay,
date: eventStart,
dueDate: eventEnd,
day: -1,
access: event.access
})
}
})
return result
}
</script>
<div class="calendar-header">
@ -383,7 +375,7 @@
</MonthCalendar>
{:else if mode === CalendarMode.Week}
<DayCalendar
events={toCalendar(objects, currentDate, 7)}
events={objects}
{mondayStart}
displayedDaysCount={7}
startFromWeekStart={false}
@ -391,18 +383,12 @@
bind:currentDate
on:create={(e) => showCreateDialog(e.detail.date, e.detail.withTime)}
on:drop
>
<svelte:fragment slot="event" let:id let:size>
{@const event = objects.find((event) => event._id === id)}
{#if event}
<EventElement {event} {size} />
{/if}
</svelte:fragment>
</DayCalendar>
on:dragenter={dragEnter}
/>
{:else if mode === CalendarMode.Day || mode === CalendarMode.Days}
{#key mode}
<DayCalendar
events={toCalendar(objects, currentDate, mode === CalendarMode.Days ? 3 : 1)}
events={objects}
{mondayStart}
displayedDaysCount={mode === CalendarMode.Days ? 3 : 1}
startFromWeekStart={false}
@ -410,14 +396,8 @@
bind:currentDate
on:create={(e) => showCreateDialog(e.detail.date, e.detail.withTime)}
on:drop
>
<svelte:fragment slot="event" let:id let:size>
{@const event = objects.find((event) => event._id === id)}
{#if event}
<EventElement {event} {size} />
{/if}
</svelte:fragment>
</DayCalendar>
on:dragenter={dragEnter}
/>
{/key}
{/if}

View File

@ -13,28 +13,29 @@
// limitations under the License.
-->
<script lang="ts">
import { Event } from '@hcengineering/calendar'
import { Timestamp } from '@hcengineering/core'
import { createEventDispatcher, onMount } from 'svelte'
import ui, {
resizeObserver,
deviceOptionsStore as deviceInfo,
Scroller,
Label,
ActionIcon,
CalendarItem,
IconDownOutline,
IconUpOutline,
IconDownOutline
} from '../..'
import {
Label,
MILLISECONDS_IN_DAY,
Scroller,
addZero,
areDatesEqual,
deviceOptionsStore as deviceInfo,
day as getDay,
getMonday,
getWeekDayName,
areDatesEqual
} from './internal/DateUtils'
import { CalendarItem } from '../../types'
resizeObserver
} from '@hcengineering/ui'
import { createEventDispatcher, onMount } from 'svelte'
import calendar from '../plugin'
import EventElement from './EventElement.svelte'
export let events: CalendarItem[]
export let events: Event[]
export let mondayStart = true
export let selectedDate: Date = new Date()
export let currentDate: Date = selectedDate
@ -54,6 +55,59 @@
}
const rem = (n: number): number => n * fontSize
const offsetTZ = new Date().getTimezoneOffset() * 60 * 1000
const toCalendar = (
events: Event[],
date: Date,
days: number = 1,
startHour: number = 0,
endHour: number = 24
): CalendarItem[] => {
const result: CalendarItem[] = []
for (let day = 0; day < days; day++) {
const startDay = new Date(MILLISECONDS_IN_DAY * day + date.getTime()).setHours(0, 0, 0, 0)
const startDate = new Date(MILLISECONDS_IN_DAY * day + date.getTime()).setHours(startHour, 0, 0, 0)
const lastDate = new Date(MILLISECONDS_IN_DAY * day + date.getTime()).setHours(endHour, 0, 0, 0)
events.forEach((event) => {
const eventStart = event.allDay ? event.date + offsetTZ : event.date
const eventEnd = event.allDay ? event.dueDate + offsetTZ : event.dueDate
if ((eventStart < lastDate && eventEnd > startDate) || (eventStart === eventEnd && eventStart === startDay)) {
result.push({
_id: event._id,
allDay: event.allDay,
date: eventStart,
dueDate: eventEnd,
day,
access: event.access
})
}
})
}
const sd = date.setHours(0, 0, 0, 0)
const ld = new Date(MILLISECONDS_IN_DAY * (days - 1) + date.getTime()).setHours(23, 59, 59, 999)
events
.filter((ev) => ev.allDay)
.sort((a, b) => b.dueDate - b.date - (a.dueDate - a.date))
.forEach((event) => {
const eventStart = event.date + offsetTZ
const eventEnd = event.dueDate + offsetTZ
if ((eventStart < ld && eventEnd > sd) || (eventStart === eventEnd && eventStart === sd)) {
result.push({
_id: event._id,
allDay: event.allDay,
date: eventStart,
dueDate: eventEnd,
day: -1,
access: event.access
})
}
})
return result
}
$: calendarEvents = toCalendar(events, currentDate, displayedDaysCount, startHour, displayedHours + startHour)
$: fontSize = $deviceInfo.fontSize
$: docHeight = $deviceInfo.docHeight
$: cellHeight = 4 * fontSize
@ -88,7 +142,7 @@
let calendarWidth: number = 0
let calendarRect: DOMRect
let colWidth: number = 0
let newEvents = events
let newEvents = calendarEvents
let grid: CalendarGrid[] = Array<CalendarGrid>(displayedDaysCount)
let alldays: CalendarItem[] = []
let alldaysGrid: CalendarADGrid[] = Array<CalendarADGrid>(displayedDaysCount)
@ -105,8 +159,8 @@
let shortAlldays: { id: string; day: number; fixRow?: boolean }[] = []
let moreCounts: number[] = Array<number>(displayedDaysCount)
$: if (newEvents !== events) {
newEvents = events
$: if (newEvents !== calendarEvents) {
newEvents = calendarEvents
grid = new Array<CalendarGrid>(displayedDaysCount)
alldaysGrid = new Array<CalendarADGrid>(displayedDaysCount)
alldays = []
@ -187,12 +241,12 @@
adMaxRow++
}
const prepareAllDays = () => {
alldays = events.filter((ev) => ev.day === -1)
alldays = calendarEvents.filter((ev) => ev.allDay)
adRows = []
for (let i = 0; i < displayedDaysCount; i++) alldaysGrid[i] = { alldays: [null] }
adMaxRow = 1
alldays.forEach((event) => {
const days = events
const days = calendarEvents
.filter((ev) => ev.allDay && ev.day !== -1 && event._id === ev._id)
.map((ev) => {
return ev.day
@ -288,7 +342,8 @@
result.bottom =
cellHeight * (displayedHours - startHour - endTime.hours - 1) +
((60 - endTime.mins) / 60) * cellHeight +
getGridOffset(endTime.mins, true)
getGridOffset(endTime.mins, true) +
(showHeader ? 0 : rem(2.5))
let cols = 1
let index: number = 0
grid[event.day].columns.forEach((col, i) =>
@ -414,7 +469,7 @@
{/each}
<div class="sticky-header allday-header text-sm content-dark-color">
All day
<Label label={calendar.string.AllDay} />
{#if (!minimizedAD && adMaxRow > maxAD) || (minimizedAD && adMaxRow > minAD)}
<ActionIcon
icon={shownAD ? IconUpOutline : IconDownOutline}
@ -432,19 +487,26 @@
<div style:min-height={`${shownHeightAD - cellBorder * 2}px`} />
{#each alldays as event, i}
{@const rect = getADRect(event._id)}
<!-- svelte-ignore a11y-no-noninteractive-tabindex -->
<div
class="calendar-element"
style:top={`${rect.top}px`}
style:height={`${rect.height}px`}
style:left={`${rect.left}px`}
style:width={`${rect.width}px`}
style:opacity={rect.visibility === 0 ? 0.4 : 1}
style:--mask-image={getMask(rect.visibility)}
tabindex={500 + i}
>
<slot name="event" id={event._id} size={{ width: rect.width, height: rect.height }} />
</div>
{@const ev = events.find((p) => p._id === event._id)}
{#if ev}
<!-- svelte-ignore a11y-no-noninteractive-tabindex -->
<div
class="calendar-element"
style:top={`${rect.top}px`}
style:height={`${rect.height}px`}
style:left={`${rect.left}px`}
style:width={`${rect.width}px`}
style:opacity={rect.visibility === 0 ? 0.4 : 1}
style:--mask-image={getMask(rect.visibility)}
tabindex={500 + i}
>
<EventElement
hourHeight={cellHeight}
event={ev}
size={{ width: rect.width, height: rect.height }}
/>
</div>
{/if}
{/each}
{/key}{/key}{/key}
</Scroller>
@ -452,38 +514,44 @@
{#key [styleAD, calendarWidth, displayedDaysCount]}
{#each alldays as event, i}
{@const rect = getADRect(event._id)}
<!-- svelte-ignore a11y-no-noninteractive-tabindex -->
<div
class="calendar-element"
style:top={`${rect.top}px`}
style:height={`${rect.height}px`}
style:left={`${rect.left}px`}
style:width={`${rect.width}px`}
style:opacity={rect.visibility === 0 ? 0.4 : 1}
style:--mask-image={getMask(rect.visibility)}
tabindex={500 + i}
>
<slot name="event" id={event._id} size={{ width: rect.width, height: rect.height }} />
</div>
{@const ev = events.find((p) => p._id === event._id)}
{#if ev}
<!-- svelte-ignore a11y-no-noninteractive-tabindex -->
<div
class="calendar-element"
style:top={`${rect.top}px`}
style:height={`${rect.height}px`}
style:left={`${rect.left}px`}
style:width={`${rect.width}px`}
style:opacity={rect.visibility === 0 ? 0.4 : 1}
style:--mask-image={getMask(rect.visibility)}
tabindex={500 + i}
>
<EventElement hourHeight={cellHeight} event={ev} size={{ width: rect.width, height: rect.height }} />
</div>
{/if}
{/each}
{/key}
{:else}
{#key [styleAD, calendarWidth, displayedDaysCount]}
{#each shortAlldays as event, i}
{@const rect = getADRect(event.id, event.day, event.fixRow)}
<!-- svelte-ignore a11y-no-noninteractive-tabindex -->
<div
class="calendar-element"
style:top={`${rect.top}px`}
style:height={`${rect.height}px`}
style:left={`${rect.left}px`}
style:width={`${rect.width}px`}
style:opacity={rect.visibility === 0 ? 0.4 : 1}
style:--mask-image={getMask(rect.visibility)}
tabindex={500 + i}
>
<slot name="event" id={event.id} size={{ width: rect.width, height: rect.height }} />
</div>
{@const ev = events.find((p) => p._id === event.id)}
{#if ev}
<!-- svelte-ignore a11y-no-noninteractive-tabindex -->
<div
class="calendar-element"
style:top={`${rect.top}px`}
style:height={`${rect.height}px`}
style:left={`${rect.left}px`}
style:width={`${rect.width}px`}
style:opacity={rect.visibility === 0 ? 0.4 : 1}
style:--mask-image={getMask(rect.visibility)}
tabindex={500 + i}
>
<EventElement hourHeight={cellHeight} event={ev} size={{ width: rect.width, height: rect.height }} />
</div>
{/if}
{/each}
{#each moreCounts as more, day}
{@const addon = shortAlldays.length}
@ -530,7 +598,11 @@
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:dragover|preventDefault
on:dragenter={(e) => {
dispatch('dragenter', {
date: new Date(day.setHours(hourOfDay + startHour, 0, 0, 0))
})
}}
on:drop|preventDefault={(e) => {
dispatch('drop', {
day,
@ -552,26 +624,35 @@
{#key [styleAD, calendarWidth, displayedDaysCount]}
{#each newEvents.filter((ev) => !ev.allDay) as event, i}
{@const rect = getRect(event)}
<!-- svelte-ignore a11y-no-noninteractive-tabindex -->
<div
class="calendar-element"
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'}
tabindex={1000 + i}
>
<slot
name="event"
id={event._id}
size={{
width: rect.width,
height: (calendarRect?.height ?? rect.top + rect.bottom) - rect.top - rect.bottom
}}
/>
</div>
{@const ev = events.find((p) => p._id === event._id)}
{#if ev}
<!-- svelte-ignore a11y-no-noninteractive-tabindex -->
<div
class="calendar-element"
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'}
tabindex={1000 + i}
>
<EventElement
event={ev}
hourHeight={cellHeight}
size={{
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}
{/each}
{/key}
</div>
@ -596,6 +677,7 @@
mask-image: var(--mask-image, none);
--webkit-mask-image: var(--mask-image, none);
border-radius: 0.25rem;
pointer-events: none;
}
.sticky-header {
position: sticky;

View File

@ -13,47 +13,141 @@
// limitations under the License.
-->
<script lang="ts">
import { Event } from '@hcengineering/calendar'
import { MILLISECONDS_IN_MINUTE, addZero, showPanel, tooltip } from '@hcengineering/ui'
import view from '@hcengineering/view'
import calendar, { CalendarEventPresenter, Event } from '@hcengineering/calendar'
import { Doc, DocumentUpdate } from '@hcengineering/core'
import { getClient } from '@hcengineering/presentation'
import { Component, MILLISECONDS_IN_MINUTE, deviceOptionsStore, showPopup, tooltip } from '@hcengineering/ui'
import view, { ObjectEditor } from '@hcengineering/view'
import { createEventDispatcher } from 'svelte'
import EventPresenter from './EventPresenter.svelte'
export let event: Event
export let hourHeight: number
export let size: { width: number; height: number }
$: startDate = new Date(event.date)
$: endDate = new Date(event.dueDate)
$: oneRow = size.height < 42 || event.allDay
$: narrow = event.dueDate - event.date < MILLISECONDS_IN_MINUTE * 25
$: empty = size.width < 44
const getTime = (date: Date): string => {
return `${addZero(date.getHours())}:${addZero(date.getMinutes())}`
function click () {
const editor = hierarchy.classHierarchyMixin<Doc, ObjectEditor>(event._class, view.mixin.ObjectEditor)
if (editor?.editor !== undefined) {
showPopup(editor.editor, { object: event })
}
}
const client = getClient()
const hierarchy = client.getHierarchy()
$: presenter = hierarchy.classHierarchyMixin<Doc, CalendarEventPresenter>(
event._class,
calendar.mixin.CalendarEventPresenter
)
let div: HTMLDivElement
const dispatch = createEventDispatcher()
$: fontSize = $deviceOptionsStore.fontSize
function dragStart (e: DragEvent) {
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 (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
})
}
}
</script>
{#if event}
<!-- svelte-ignore a11y-click-events-have-key-events -->
<div
bind:this={div}
class="event-container"
class:oneRow
class:empty
draggable={!event.allDay}
use:tooltip={{ component: EventPresenter, props: { value: event } }}
on:click|stopPropagation={() => {
if (event) showPanel(view.component.EditDoc, event._id, event._class, 'content')
}}
on:click|stopPropagation={click}
on:dragstart={dragStart}
on:drag={drag}
on:dragend={drop}
on:drop
>
{#if !narrow && !empty}
<b class="overflow-label">{event.title}</b>
{/if}
{#if !oneRow && !empty}
<span class="overflow-label text-sm">{getTime(startDate)}-{getTime(endDate)}</span>
{#if !empty && presenter?.presenter}
<Component is={presenter.presenter} props={{ event, narrow, oneRow }} />
{/if}
</div>
{/if}
<style lang="scss">
.event-container {
pointer-events: auto;
overflow: hidden;
display: flex;
flex-direction: column;

View File

@ -33,11 +33,13 @@ import UpdateRecInstancePopup from './components/UpdateRecInstancePopup.svelte'
import ReminderViewlet from './components/activity/ReminderViewlet.svelte'
import CalendarIntegrationIcon from './components/icons/Calendar.svelte'
import EventElement from './components/EventElement.svelte'
import CalendarEventPresenter from './components/CalendarEventPresenter.svelte'
import DayCalendar from './components/DayCalendar.svelte'
import calendar from './plugin'
import contact from '@hcengineering/contact'
import { deleteObjects } from '@hcengineering/view-resources'
export { EventElement, CalendarView }
export { EventElement, CalendarView, DayCalendar }
async function saveEventReminder (object: Doc): Promise<void> {
showPopup(SaveEventReminder, { objectId: object._id, objectClass: object._class })
@ -148,7 +150,8 @@ export default async (): Promise<Resources> => ({
EventPresenter,
CreateEvent,
IntegrationConnect,
CalendarIntegrationIcon
CalendarIntegrationIcon,
CalendarEventPresenter
},
activity: {
ReminderViewlet

View File

@ -12,7 +12,7 @@
// limitations under the License.
import { Contact } from '@hcengineering/contact'
import type { AttachedDoc, Class, Doc, Markup, Ref, Space, Timestamp } from '@hcengineering/core'
import type { AttachedDoc, Class, Doc, Markup, Mixin, Ref, Space, Timestamp } from '@hcengineering/core'
import { NotificationType } from '@hcengineering/notification'
import type { Asset, IntlString, Metadata, Plugin } from '@hcengineering/platform'
import { plugin } from '@hcengineering/platform'
@ -94,6 +94,13 @@ export interface ReccuringInstance extends Event {
virtual?: boolean
}
/**
* @public
*/
export interface CalendarEventPresenter extends Class<Event> {
presenter: AnyComponent
}
/**
* @public
*/
@ -109,6 +116,9 @@ const calendarPlugin = plugin(calendarId, {
ReccuringEvent: '' as Ref<Class<ReccuringEvent>>,
ReccuringInstance: '' as Ref<Class<ReccuringInstance>>
},
mixin: {
CalendarEventPresenter: '' as Ref<Mixin<CalendarEventPresenter>>
},
icon: {
Calendar: '' as Asset,
Location: '' as Asset,

View File

@ -261,11 +261,7 @@ export function getAllEvents (events: Event[], from: Timestamp, to: Timestamp):
const recurData: ReccuringInstance[] = []
const instancesMap: Map<string, ReccuringInstance[]> = new Map()
for (const event of events) {
if (event._class === calendar.class.Event) {
if (from > event.dueDate) continue
if (event.date > to) continue
base.push(event)
} else if (event._class === calendar.class.ReccuringEvent) {
if (event._class === calendar.class.ReccuringEvent) {
recur.push(event as ReccuringEvent)
} else if (event._class === calendar.class.ReccuringInstance) {
const instance = event as ReccuringInstance
@ -273,6 +269,10 @@ export function getAllEvents (events: Event[], from: Timestamp, to: Timestamp):
const arr = instancesMap.get(instance.recurringEventId) ?? []
arr.push(instance)
instancesMap.set(instance.recurringEventId, arr)
} else {
if (from > event.dueDate) continue
if (event.date > to) continue
base.push(event)
}
}
for (const rec of recur) {