mirror of
https://github.com/hcengineering/platform.git
synced 2024-12-22 11:01:54 +03:00
UBER-668 UBER-669 UBER-670 UBER-671 (#3566)
Signed-off-by: Denis Bykhov <bykhov.denis@gmail.com>
This commit is contained in:
parent
32fd80c971
commit
afc881a7c6
@ -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,
|
||||
|
@ -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>,
|
||||
|
@ -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'
|
||||
|
@ -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}
|
@ -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}
|
||||
|
||||
|
@ -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;
|
@ -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;
|
||||
|
@ -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
|
||||
|
@ -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,
|
||||
|
@ -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) {
|
||||
|
Loading…
Reference in New Issue
Block a user