diff --git a/models/calendar/src/index.ts b/models/calendar/src/index.ts index a727a9acf2..089e642814 100644 --- a/models/calendar/src/index.ts +++ b/models/calendar/src/index.ts @@ -50,8 +50,8 @@ 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' +import calendar from './plugin' export * from '@hcengineering/calendar' export { calendarId } from '@hcengineering/calendar' @@ -62,13 +62,16 @@ export const DOMAIN_CALENDAR = 'calendar' as Domain @Model(calendar.class.Calendar, core.class.Space) @UX(calendar.string.Calendar, calendar.icon.Calendar) export class TCalendar extends TSpaceWithStates implements Calendar { - @Prop(TypeString(), calendar.string.HideDetails) - hideDetails?: boolean + visibility!: 'public' | 'freeBusy' | 'private' + + sync?: boolean } @Model(calendar.class.Event, core.class.AttachedDoc, DOMAIN_CALENDAR) @UX(calendar.string.Event, calendar.icon.Calendar) export class TEvent extends TAttachedDoc implements Event { + declare space: Ref + eventId!: string @Prop(TypeString(), calendar.string.Title) @@ -106,6 +109,8 @@ export class TEvent extends TAttachedDoc implements Event { externalParticipants?: string[] access!: 'freeBusyReader' | 'reader' | 'writer' | 'owner' + + visibility?: 'public' | 'freeBusy' | 'private' } @Model(calendar.class.ReccuringEvent, calendar.class.Event) @@ -174,7 +179,8 @@ export function createModel (builder: Builder): void { icon: calendar.component.CalendarIntegrationIcon, createComponent: calendar.component.IntegrationConnect, onDisconnect: calendar.handler.DisconnectHandler, - reconnectComponent: calendar.component.IntegrationConnect + reconnectComponent: calendar.component.IntegrationConnect, + configureComponent: calendar.component.IntegrationConfigure }, calendar.integrationType.Calendar ) diff --git a/models/calendar/src/migration.ts b/models/calendar/src/migration.ts index 38d63c2182..94019911f1 100644 --- a/models/calendar/src/migration.ts +++ b/models/calendar/src/migration.ts @@ -33,7 +33,8 @@ async function migrateCalendars (tx: TxOperations): Promise { description: '', archived: false, private: false, - members: [user._id] + members: [user._id], + visibility: 'public' }, `${user._id}_calendar` as Ref, undefined, @@ -49,6 +50,11 @@ async function migrateCalendars (tx: TxOperations): Promise { if (space !== undefined) { await tx.remove(space) } + + const calendars = await tx.findAll(calendar.class.Calendar, { visibility: { $exists: false } }) + for (const calendar of calendars) { + await tx.update(calendar, { visibility: 'public' }) + } } async function fixEventDueDate (client: MigrationClient): Promise { diff --git a/models/calendar/src/plugin.ts b/models/calendar/src/plugin.ts index 821b550e81..c4a62d7958 100644 --- a/models/calendar/src/plugin.ts +++ b/models/calendar/src/plugin.ts @@ -29,7 +29,8 @@ export default mergeIds(calendarId, calendar, { CreateCalendar: '' as AnyComponent, EventPresenter: '' as AnyComponent, CalendarIntegrationIcon: '' as AnyComponent, - CalendarEventPresenter: '' as AnyComponent + CalendarEventPresenter: '' as AnyComponent, + IntegrationConfigure: '' as AnyComponent }, action: { SaveEventReminder: '' as Ref, diff --git a/plugins/calendar-assets/lang/en.json b/plugins/calendar-assets/lang/en.json index 2573d07702..df11f6063c 100644 --- a/plugins/calendar-assets/lang/en.json +++ b/plugins/calendar-assets/lang/en.json @@ -73,6 +73,8 @@ "SundayShort": "Su", "OnUntil": "On", "Times": "{count, plural, one {time} other {times}}", - "AddParticipants": "Add participants" + "AddParticipants": "Add participants", + "Sync": "Synchronization", + "Busy": "Busy" } } \ No newline at end of file diff --git a/plugins/calendar-assets/lang/ru.json b/plugins/calendar-assets/lang/ru.json index 1593d9a865..37f01da0c0 100644 --- a/plugins/calendar-assets/lang/ru.json +++ b/plugins/calendar-assets/lang/ru.json @@ -73,7 +73,8 @@ "SundayShort": "Вс", "OnUntil": "До", "Times": "{count, plural, one {раз} few {раза} other {раз}}", - "AddParticipants": "Добавить участников" - + "AddParticipants": "Добавить участников", + "Sync": "Синхронизация", + "Busy": "Занято" } } \ No newline at end of file diff --git a/plugins/calendar-resources/src/components/CalendarEventPresenter.svelte b/plugins/calendar-resources/src/components/CalendarEventPresenter.svelte index 321b58eca3..d92b73d271 100644 --- a/plugins/calendar-resources/src/components/CalendarEventPresenter.svelte +++ b/plugins/calendar-resources/src/components/CalendarEventPresenter.svelte @@ -14,9 +14,11 @@ --> {#if !narrow} - {event.title} + {#if !hideDetails} + + {event.title} + + {:else} + + {/if} {/if} {#if !oneRow} {getTime(startDate)}-{getTime(endDate)} diff --git a/plugins/calendar-resources/src/components/CalendarView.svelte b/plugins/calendar-resources/src/components/CalendarView.svelte index 09b839d8fa..b7bc15c7f5 100644 --- a/plugins/calendar-resources/src/components/CalendarView.svelte +++ b/plugins/calendar-resources/src/components/CalendarView.svelte @@ -39,7 +39,7 @@ getMonday, showPopup } from '@hcengineering/ui' - import { CalendarMode, DayCalendar } from '../index' + import { CalendarMode, DayCalendar, calendarStore, hidePrivateEvents } from '../index' import calendar from '../plugin' import Day from './Day.svelte' @@ -66,6 +66,7 @@ let selectedDate: Date = new Date() let raw: Event[] = [] + let visible: Event[] = [] let objects: Event[] = [] function getFrom (date: Date, mode: CalendarMode): Timestamp { @@ -116,7 +117,7 @@ let calendars: Calendar[] = [] - calendarsQuery.query(calendar.class.Calendar, { createdBy: me._id }, (res) => { + calendarsQuery.query(calendar.class.Calendar, { members: me._id }, (res) => { calendars = res }) @@ -138,7 +139,8 @@ ) } $: update(_class, query, calendars, options) - $: objects = getAllEvents(raw, from, to) + $: visible = hidePrivateEvents(raw, $calendarStore) + $: objects = getAllEvents(visible, from, to) function inRange (start: Date, end: Date, startPeriod: Date, period: 'day' | 'hour'): boolean { const endPeriod = @@ -242,7 +244,7 @@ current.dueDate = new Date(e.detail.date).setMinutes(new Date(e.detail.date).getMinutes() + 30) } else { const me = getCurrentAccount() as PersonAccount - raw.push({ + const temp: Event = { _id: dragItemId, allDay: false, eventId: generateEventId(), @@ -253,13 +255,14 @@ attachedToClass: dragItem._class, _class: dragEventClass, collection: 'events', - space: dragItem.space, + space: `${me._id}_calendar` as Ref, 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.push(temp) } raw = raw } @@ -270,7 +273,8 @@ function clear (dragItem: Doc | undefined) { if (dragItem === undefined) { raw = raw.filter((p) => p._id !== dragItemId) - objects = getAllEvents(raw, from, to) + visible = hidePrivateEvents(raw, $calendarStore) + objects = getAllEvents(visible, from, to) } } diff --git a/plugins/calendar-resources/src/components/EditEvent.svelte b/plugins/calendar-resources/src/components/EditEvent.svelte index 754f8e1b05..ae4165f34c 100644 --- a/plugins/calendar-resources/src/components/EditEvent.svelte +++ b/plugins/calendar-resources/src/components/EditEvent.svelte @@ -20,7 +20,7 @@ import { Button, CheckBox, DAY, EditBox, Icon, IconClose, Label, closePopup, showPopup } from '@hcengineering/ui' import { createEventDispatcher } from 'svelte' import calendar from '../plugin' - import { saveUTC } from '../utils' + import { isReadOnly, saveUTC } from '../utils' import EventParticipants from './EventParticipants.svelte' import EventTimeEditor from './EventTimeEditor.svelte' import RRulePresenter from './RRulePresenter.svelte' @@ -29,6 +29,8 @@ export let object: Event + $: readOnly = isReadOnly(object) + let title = object.title const defaultDuration = 60 * 60 * 1000 @@ -54,6 +56,9 @@ } async function saveEvent () { + if (readOnly) { + return + } const update: DocumentUpdate = {} if (object.title !== title) { update.title = title.trim() @@ -97,6 +102,9 @@ } function setRecurrance () { + if (readOnly) { + return + } showPopup(ReccurancePopup, { rules }, undefined, (res) => { if (res) { rules = res @@ -196,7 +204,7 @@
{#if object.attachedTo === calendar.ids.NoAttached} - + {:else}
{/if} @@ -213,7 +221,7 @@ />
- +
{#if !allDay && rules.length === 0}
@@ -232,7 +240,7 @@ {:else}
- +
@@ -255,24 +263,19 @@
- +
- +
-
diff --git a/plugins/calendar-resources/src/components/EventElement.svelte b/plugins/calendar-resources/src/components/EventElement.svelte index 4fc3061b5e..d5cc7c4973 100644 --- a/plugins/calendar-resources/src/components/EventElement.svelte +++ b/plugins/calendar-resources/src/components/EventElement.svelte @@ -25,9 +25,10 @@ tooltip } from '@hcengineering/ui' import view, { ObjectEditor } from '@hcengineering/view' - import { createEventDispatcher } from 'svelte' - import EventPresenter from './EventPresenter.svelte' import { Menu } from '@hcengineering/view-resources' + import { createEventDispatcher } from 'svelte' + import { calendarStore, isReadOnly, isVisible } from '../utils' + import EventPresenter from './EventPresenter.svelte' export let event: Event export let hourHeight: number @@ -38,9 +39,11 @@ $: empty = size.width < 44 function click () { - const editor = hierarchy.classHierarchyMixin(event._class, view.mixin.ObjectEditor) - if (editor?.editor !== undefined) { - showPopup(editor.editor, { object: event }) + if (visible) { + const editor = hierarchy.classHierarchyMixin(event._class, view.mixin.ObjectEditor) + if (editor?.editor !== undefined) { + showPopup(editor.editor, { object: event }) + } } } @@ -58,6 +61,7 @@ $: fontSize = $deviceOptionsStore.fontSize function dragStart (e: DragEvent) { + if (readOnly) return if (event.allDay) return originDate = event.date originDueDate = event.dueDate @@ -87,6 +91,7 @@ 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) @@ -135,6 +140,9 @@ ev.preventDefault() showPopup(Menu, { object: event }, getEventPositionElement(ev)) } + + $: visible = isVisible(event, $calendarStore) + $: readOnly = isReadOnly(event) {#if event} @@ -145,7 +153,7 @@ class:oneRow class:empty draggable={!event.allDay} - use:tooltip={{ component: EventPresenter, props: { value: event } }} + use:tooltip={{ component: EventPresenter, props: { value: event, hideDetails: !visible } }} on:click|stopPropagation={click} on:contextmenu={showMenu} on:dragstart={dragStart} @@ -154,7 +162,7 @@ on:drop > {#if !empty && presenter?.presenter} - + {/if}
{/if} diff --git a/plugins/calendar-resources/src/components/EventParticipants.svelte b/plugins/calendar-resources/src/components/EventParticipants.svelte index 5afb8132d1..f973fb7729 100644 --- a/plugins/calendar-resources/src/components/EventParticipants.svelte +++ b/plugins/calendar-resources/src/components/EventParticipants.svelte @@ -22,6 +22,7 @@ export let participants: Ref[] export let externalParticipants: string[] + export let disabled: boolean = false $: placeholder = participants.length > 0 || externalParticipants.length > 0 diff --git a/plugins/calendar-resources/src/components/EventPresenter.svelte b/plugins/calendar-resources/src/components/EventPresenter.svelte index 41b4377aa2..000b7b5a59 100644 --- a/plugins/calendar-resources/src/components/EventPresenter.svelte +++ b/plugins/calendar-resources/src/components/EventPresenter.svelte @@ -14,10 +14,11 @@ --> + + { + dispatch('close') + }} + canSave={true} + fullSize + okLabel={presentation.string.Ok} + on:close={() => dispatch('close')} + on:changeContent +> +
+ +
+
+
+
+ {#each calendars as calendar} +
{calendar.name}
+
+ update(calendar, res.detail)} /> +
+ {/each} +
+
+
diff --git a/plugins/calendar-resources/src/index.ts b/plugins/calendar-resources/src/index.ts index 42a30938a9..7a1bfe0a1d 100644 --- a/plugins/calendar-resources/src/index.ts +++ b/plugins/calendar-resources/src/index.ts @@ -34,12 +34,15 @@ 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 IntegrationConfigure from './components/IntegrationConfigure.svelte' import calendar from './plugin' import contact from '@hcengineering/contact' import { deleteObjects } from '@hcengineering/view-resources' export { EventElement, CalendarView, DayCalendar } +export * from './utils' + async function saveEventReminder (object: Doc): Promise { showPopup(SaveEventReminder, { objectId: object._id, objectClass: object._class }) } @@ -70,7 +73,8 @@ async function deleteRecHandler (res: any, object: ReccuringInstance): Promise => ({ CreateEvent, IntegrationConnect, CalendarIntegrationIcon, - CalendarEventPresenter + CalendarEventPresenter, + IntegrationConfigure }, activity: { ReminderViewlet diff --git a/plugins/calendar-resources/src/plugin.ts b/plugins/calendar-resources/src/plugin.ts index c6e6c612a0..be79458a7b 100644 --- a/plugins/calendar-resources/src/plugin.ts +++ b/plugins/calendar-resources/src/plugin.ts @@ -75,6 +75,8 @@ export default mergeIds(calendarId, calendar, { SaturdayShort: '' as IntlString, SundayShort: '' as IntlString, Times: '' as IntlString, - AddParticipants: '' as IntlString + AddParticipants: '' as IntlString, + Sync: '' as IntlString, + Busy: '' as IntlString } }) diff --git a/plugins/calendar-resources/src/utils.ts b/plugins/calendar-resources/src/utils.ts index 570c0c0fd9..41f3d37241 100644 --- a/plugins/calendar-resources/src/utils.ts +++ b/plugins/calendar-resources/src/utils.ts @@ -1,4 +1,8 @@ -import { Timestamp } from '@hcengineering/core' +import { Calendar, Event } from '@hcengineering/calendar' +import { IdMap, Timestamp, getCurrentAccount, toIdMap } from '@hcengineering/core' +import { createQuery, getClient } from '@hcengineering/presentation' +import { writable } from 'svelte/store' +import calendar from './plugin' export function saveUTC (date: Timestamp): Timestamp { const utcdate = new Date(date) @@ -12,3 +16,63 @@ export function saveUTC (date: Timestamp): Timestamp { utcdate.getMilliseconds() ) } + +export function hidePrivateEvents (events: Event[], calendars: IdMap): Event[] { + const me = getCurrentAccount()._id + const res: Event[] = [] + for (const event of events) { + if ((event.createdBy ?? event.modifiedBy) === me) { + res.push(event) + } else { + if (event.visibility !== undefined) { + if (event.visibility !== 'private') { + res.push(event) + } + } else { + const space = calendars.get(event.space) + if (space != null && space.visibility !== 'private') { + res.push(event) + } + } + } + } + return res +} + +export function isReadOnly (value: Event): boolean { + const me = getCurrentAccount()._id + if (value.createdBy !== me) return true + if (['owner', 'writer'].includes(value.access)) return false + return true +} + +export function isVisible (value: Event, calendars: IdMap): boolean { + const me = getCurrentAccount()._id + if (value.createdBy === me) return true + if (value.visibility === 'freeBusy') { + return false + } + const space = calendars.get(value.space) + if (space == null) { + return true + } else { + return space.visibility === 'public' + } +} + +export const calendarStore = writable>(new Map()) + +function fillStores (): void { + const client = getClient() + + if (client !== undefined) { + const query = createQuery(true) + query.query(calendar.class.Calendar, {}, (res) => { + calendarStore.set(toIdMap(res)) + }) + } else { + setTimeout(() => fillStores(), 50) + } +} + +fillStores() diff --git a/plugins/calendar/src/index.ts b/plugins/calendar/src/index.ts index d7e3976257..7789aeb12e 100644 --- a/plugins/calendar/src/index.ts +++ b/plugins/calendar/src/index.ts @@ -22,7 +22,10 @@ import { AnyComponent } from '@hcengineering/ui' /** * @public */ -export interface Calendar extends Space {} +export interface Calendar extends Space { + visibility: 'public' | 'freeBusy' | 'private' + sync?: boolean +} /** * @public @@ -58,6 +61,7 @@ export interface ReccuringEvent extends Event { * @public */ export interface Event extends AttachedDoc { + space: Ref eventId: string title: string description: Markup @@ -80,6 +84,8 @@ export interface Event extends AttachedDoc { reminders?: Timestamp[] + visibility?: 'public' | 'freeBusy' | 'private' + access: 'freeBusyReader' | 'reader' | 'writer' | 'owner' } @@ -132,7 +138,7 @@ const calendarPlugin = plugin(calendarId, { }, space: { // deprecated - PersonalEvents: '' as Ref + PersonalEvents: '' as Ref }, app: { Calendar: '' as Ref diff --git a/server-plugins/calendar-resources/src/index.ts b/server-plugins/calendar-resources/src/index.ts index c1beeb9cea..a09695c799 100644 --- a/server-plugins/calendar-resources/src/index.ts +++ b/server-plugins/calendar-resources/src/index.ts @@ -94,7 +94,8 @@ export async function OnPersonAccountCreate (tx: Tx, control: TriggerControl): P description: '', archived: false, private: false, - members: [user._id] + members: [user._id], + visibility: 'public' }, `${user._id}_calendar` as Ref, undefined, @@ -108,11 +109,10 @@ async function onEventCreate (tx: Tx, control: TriggerControl): Promise { const ev = TxProcessor.createDoc2Doc(ctx) const res: Tx[] = [] - const accounts = await control.modelDb.findAll(contact.class.PersonAccount, {}) - const participants = accounts.filter( - (p) => (p._id !== ev.createdBy ?? ev.modifiedBy) && ev.participants.includes(p.person) - ) - for (const acc of participants) { + for (const participant of ev.participants) { + const acc = (await control.modelDb.findAll(contact.class.PersonAccount, { person: participant }))[0] + if (acc === undefined) continue + if (acc._id === ev.createdBy ?? ev.modifiedBy) continue const { _id, _class, space, modifiedBy, modifiedOn, ...data } = ev const innerTx = control.txFactory.createTxCreateDoc(_class, `${acc._id}_calendar` as Ref, { ...data,