UBER-737 UBER-730 (#3616)

Signed-off-by: Denis Bykhov <bykhov.denis@gmail.com>
This commit is contained in:
Denis Bykhov 2023-08-23 09:11:16 +06:00 committed by GitHub
parent 668cee0843
commit 1988dfca33
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
16 changed files with 328 additions and 22 deletions

View File

@ -17676,7 +17676,7 @@ packages:
dev: false
file:projects/calendar-resources.tgz_a1d864769aaf53d09b76fe134ab55e60:
resolution: {integrity: sha512-/KSbpPzT33ajfBoTvXd075rtPPiYnC1XOF7eI/8lKcCofw3gBCY5ZT3qgxmwwGqWzx/Sfdzp0TwAQhqsGgtcrg==, tarball: file:projects/calendar-resources.tgz}
resolution: {integrity: sha512-zNDWSAzkH0tXFVIy8dr2OUI92YvnIvsxSGpeQuLq3yiXUfCCaRKoROIPeQebjsPzmgwv/KvX32/UojKxQdebxA==, tarball: file:projects/calendar-resources.tgz}
id: file:projects/calendar-resources.tgz
name: '@rush-temp/calendar-resources'
version: 0.0.0
@ -17689,6 +17689,7 @@ packages:
eslint-plugin-n: 15.5.1_eslint@8.27.0
eslint-plugin-promise: 6.1.1_eslint@8.27.0
eslint-plugin-svelte3: 4.0.0_eslint@8.27.0+svelte@3.55.1
fast-equals: 2.0.4
prettier: 2.8.8
prettier-plugin-svelte: 2.8.0_prettier@2.8.8+svelte@3.55.1
sass: 1.56.1

View File

@ -111,6 +111,7 @@ export { default as ScrollerBar } from './components/ScrollerBar.svelte'
export { default as TabList } from './components/TabList.svelte'
export { default as Chevron } from './components/Chevron.svelte'
export { default as Timeline } from './components/Timeline.svelte'
export { default as TimeShiftPresenter } from './components/TimeShiftPresenter.svelte'
export { default as IconAdd } from './components/icons/Add.svelte'
export { default as IconStart } from './components/icons/Start.svelte'

View File

@ -75,6 +75,7 @@
"Times": "{count, plural, one {time} other {times}}",
"AddParticipants": "Add participants",
"Sync": "Synchronization",
"Busy": "Busy"
"Busy": "Busy",
"AddReminder": "Add reminder"
}
}

View File

@ -75,6 +75,7 @@
"Times": "{count, plural, one {раз} few {раза} other {раз}}",
"AddParticipants": "Добавить участников",
"Sync": "Синхронизация",
"Busy": "Занято"
"Busy": "Занято",
"AddReminder": "Добавить напоминание"
}
}

View File

@ -37,6 +37,7 @@
"@hcengineering/ui": "^0.6.10",
"@hcengineering/presentation": "^0.6.2",
"@hcengineering/calendar": "^0.6.14",
"@hcengineering/setting": "^0.6.9",
"@hcengineering/theme": "^0.6.3",
"svelte": "3.55.1",
"@hcengineering/text-editor": "^0.6.0",
@ -45,6 +46,7 @@
"@hcengineering/contact-resources": "^0.6.0",
"@hcengineering/view-resources": "^0.6.0",
"@hcengineering/view": "^0.6.8",
"@hcengineering/workbench": "^0.6.8"
"@hcengineering/workbench": "^0.6.8",
"fast-equals": "^2.0.3"
}
}

View File

@ -13,20 +13,26 @@
// limitations under the License.
-->
<script lang="ts">
import contact, { Person } from '@hcengineering/contact'
import contact, { Person, PersonAccount } from '@hcengineering/contact'
import { personAccountByIdStore } from '@hcengineering/contact-resources'
import { IdMap, Ref } from '@hcengineering/core'
import { IntlString, translate } from '@hcengineering/platform'
import { createQuery } from '@hcengineering/presentation'
import { createQuery, getClient } from '@hcengineering/presentation'
import setting, { Integration } from '@hcengineering/setting'
import { themeStore } from '@hcengineering/theme'
import { registerFocus, resizeObserver } from '@hcengineering/ui'
import { closePopup, registerFocus, resizeObserver, showPopup } from '@hcengineering/ui'
import { afterUpdate, createEventDispatcher, onMount } from 'svelte'
import calendar from '../plugin'
import ParticipantsPopup from './ParticipantsPopup.svelte'
export let maxWidth: string | undefined = undefined
export let value: string | number | undefined = undefined
export let value: string | undefined = undefined
export let placeholder: IntlString
export let autoFocus: boolean = false
export let select: boolean = false
export let focusable: boolean = false
export let disabled: boolean = false
export let excluded: Ref<Person>[] = []
const dispatch = createEventDispatcher()
@ -98,13 +104,61 @@
input.focus()
}
const query = createQuery()
const client = getClient()
let integrations: Integration[] = []
let persons: Person[] = []
const integrationsQuery = createQuery()
integrationsQuery.query(
setting.class.Integration,
{ type: calendar.integrationType.Calendar, disabled: false },
(res) => {
integrations = res
}
)
$: query.query(contact.class.Person, {}, (res) => {
persons = res
})
$: findCompletions(value, integrations, $personAccountByIdStore, excluded)
async function findCompletions (
val: string | undefined,
integrations: Integration[],
accounts: IdMap<PersonAccount>,
excluded: Ref<Person>[]
): Promise<void> {
if (val === undefined || val.length < 3) {
closePopup('participants')
return
}
const persons = client.findAll(contact.class.Person, { $search: `${val}*`, _id: { $nin: excluded } }, { limit: 5 })
const res: Set<Ref<Person>> = new Set()
for (const integration of integrations) {
if (integration.value.includes(val)) {
const acc = accounts.get((integration.createdBy ?? integration.modifiedBy) as Ref<PersonAccount>)
if (acc !== undefined && !excluded.includes(acc.person)) {
res.add(acc.person)
}
}
}
for (const person of await persons) {
res.add(person._id)
}
if (res.size > 0) {
showPopup(
ParticipantsPopup,
{ participants: Array.from(res) },
input,
(res) => {
if (res) {
value = ''
dispatch('ref', res)
}
},
undefined,
{ category: 'participants', overlay: true }
)
} else {
closePopup('participants')
}
}
</script>
<!-- svelte-ignore a11y-click-events-have-key-events -->
@ -117,6 +171,14 @@
use:resizeObserver={(element) => {
parentWidth = element.parentElement?.getBoundingClientRect().width
}}
on:keydown={(ev) => {
if (ev.key === 'Enter') {
ev.preventDefault()
ev.stopPropagation()
dispatch('enter', value)
value = ''
}
}}
>
<div class="hidden-text" bind:this={text} />
<div class="flex-row-center clear-mins" class:focusable>

View File

@ -25,6 +25,7 @@
import EventTimeEditor from './EventTimeEditor.svelte'
import RRulePresenter from './RRulePresenter.svelte'
import ReccurancePopup from './ReccurancePopup.svelte'
import EventReminders from './EventReminders.svelte'
export let attachedTo: Ref<Doc> = calendar.ids.NoAttached
export let attachedToClass: Ref<Class<Doc>> = calendar.class.Event
@ -42,6 +43,8 @@
let dueDate = startDate + duration
let allDay = false
let reminders = [30 * 60 * 1000]
let description: string = ''
let rules: RecurringRule[] = []
@ -72,6 +75,7 @@
rdate: [],
exdate: [],
rules,
reminders,
description,
participants,
title,
@ -86,6 +90,7 @@
externalParticipants,
description,
participants,
reminders,
title,
allDay,
access: 'owner'
@ -105,7 +110,7 @@
}
function setRecurrance () {
showPopup(ReccurancePopup, { rules }, undefined, (res) => {
showPopup(ReccurancePopup, { rules, startDate }, undefined, (res) => {
if (res) {
rules = res
}
@ -177,6 +182,10 @@
</div>
</div>
<div class="divider" />
<div class="block">
<EventReminders bind:reminders />
</div>
<div class="divider" />
<div class="flex-between pool">
<div />
<Button kind="accented" label={presentation.string.Create} on:click={saveEvent} disabled={title === ''} />

View File

@ -26,6 +26,8 @@
import RRulePresenter from './RRulePresenter.svelte'
import ReccurancePopup from './ReccurancePopup.svelte'
import UpdateRecInstancePopup from './UpdateRecInstancePopup.svelte'
import { deepEqual } from 'fast-equals'
import EventReminders from './EventReminders.svelte'
export let object: Event
@ -40,6 +42,7 @@
const duration = object.dueDate - object.date
let dueDate = startDate + duration
let allDay = object.allDay
let reminders = [...(object.reminders ?? [])]
let description = object.description
@ -78,9 +81,13 @@
update.dueDate = allDay ? saveUTC(dueDate) : dueDate
}
}
if (deepEqual(object.reminders, reminders) === false) {
update.reminders = reminders
}
if (rules !== (object as ReccuringEvent).rules) {
;(update as DocumentUpdate<ReccuringEvent>).rules = rules
}
if (Object.keys(update).length > 0) {
if (object._class === calendar.class.ReccuringInstance) {
await updateHandler(update)
@ -105,7 +112,7 @@
if (readOnly) {
return
}
showPopup(ReccurancePopup, { rules }, undefined, (res) => {
showPopup(ReccurancePopup, { rules, startDate }, undefined, (res) => {
if (res) {
rules = res
}
@ -273,6 +280,10 @@
</div>
</div>
<div class="divider" />
<div class="block">
<EventReminders bind:reminders />
</div>
<div class="divider" />
<div class="flex-between pool">
<div />
<Button kind="accented" label={presentation.string.Save} disabled={readOnly} on:click={saveEvent} />

View File

@ -14,11 +14,11 @@
-->
<script lang="ts">
import { Person } from '@hcengineering/contact'
import { ContactRefPresenter } from '@hcengineering/contact-resources'
import { Ref } from '@hcengineering/core'
import { Button, Icon, IconClose } from '@hcengineering/ui'
import calendar from '../plugin'
import AddParticipant from './AddParticipant.svelte'
import { ContactRefPresenter } from '@hcengineering/contact-resources'
export let participants: Ref<Person>[]
export let externalParticipants: string[]
@ -44,12 +44,28 @@
externalParticipants = externalParticipants
}
}
function ref (e: CustomEvent<Ref<Person>>) {
if (e.detail) {
participants.push(e.detail)
participants = participants
}
}
function enter (e: CustomEvent<string>) {
if (e.detail && e.detail !== '') {
if (!externalParticipants.includes(e.detail)) {
externalParticipants.push(e.detail)
externalParticipants = externalParticipants
}
}
}
</script>
<div class="container flex-col">
<div class="header flex-row-center flex-gap-3">
<Icon icon={calendar.icon.Participants} size="small" />
<AddParticipant {placeholder} />
<AddParticipant {placeholder} excluded={participants} on:ref={ref} on:enter={enter} />
</div>
<div class="content">
{#each participants as participant}
@ -69,7 +85,7 @@
</div>
{/each}
{#each externalParticipants as participant}
<div class="flex-between item">
<div class="flex-between item overflow-label">
{participant}
<div class="tool">
<Button

View File

@ -0,0 +1,75 @@
<!--
// 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 { Button, eventToHTMLElement, Icon, IconClose, Label, showPopup, TimeShiftPresenter } from '@hcengineering/ui'
import calendar from '../plugin'
import ReminderPopup from './ReminderPopup.svelte'
export let reminders: number[]
function addReminder () {
reminders = [...reminders, 30 * 60 * 1000]
}
function edit (e: MouseEvent, value: number, index: number) {
showPopup(ReminderPopup, { value }, eventToHTMLElement(e), (event) => {
if (event) {
reminders = [...reminders.slice(0, index), event, ...reminders.slice(index + 1)]
}
})
}
function remove (index: number) {
reminders = [...reminders.slice(0, index), ...reminders.slice(index + 1)]
}
</script>
<div class="flex-row-center flex-gap-2">
<Icon icon={calendar.icon.Notifications} size="small" />
<Button kind="ghost" on:click={addReminder}>
<div slot="content">
<Label label={reminders.length ? calendar.string.AddReminder : calendar.string.Reminders} />
</div>
</Button>
</div>
{#each reminders as reminder, i}
<div class="ml-6 flex-row-center item">
<Button kind="ghost" on:click={(e) => edit(e, reminder, i)}>
<div slot="content"><TimeShiftPresenter value={reminder * -1} /></div>
</Button>
<div class="tool">
<Button
kind="ghost"
icon={IconClose}
iconProps={{ size: 'medium', fill: 'var(--theme-dark-color)' }}
on:click={() => remove(i)}
/>
</div>
</div>
{/each}
<style lang="scss">
.item {
.tool {
opacity: 0;
}
&:hover {
.tool {
opacity: 1;
}
}
}
</style>

View File

@ -0,0 +1,74 @@
<!--
// 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 { Person } from '@hcengineering/contact'
import { PersonRefPresenter } from '@hcengineering/contact-resources'
import { Ref } from '@hcengineering/core'
import { ListView } from '@hcengineering/ui'
import { createEventDispatcher } from 'svelte'
export let participants: Ref<Person>[]
const dispatch = createEventDispatcher()
function select (participant: Ref<Person>): void {
dispatch('close', participant)
}
let list: ListView
const selection = 0
function onKeyDown (key: KeyboardEvent): boolean {
if (key.key === 'ArrowDown') {
key.stopPropagation()
key.preventDefault()
list.select(selection + 1)
return true
}
if (key.key === 'ArrowUp') {
key.stopPropagation()
key.preventDefault()
list.select(selection - 1)
}
if (key.key === 'Enter') {
key.preventDefault()
key.stopPropagation()
const item = participants[selection]
if (item) {
select(item)
return true
} else {
return false
}
}
return false
}
</script>
<div class="antiPopup" on:keydown={onKeyDown}>
<div class="ap-scroll">
<div class="ap-box">
<!-- svelte-ignore a11y-click-events-have-key-events -->
<ListView bind:this={list} count={participants.length}>
<svelte:fragment slot="item" let:item>
{@const doc = participants[item]}
<div class="ap-menuItem withComp" on:click={() => select(doc)}>
<PersonRefPresenter disabled value={doc} />
</div>
</svelte:fragment>
</ListView>
</div>
</div>
</div>

View File

@ -27,8 +27,10 @@
import { createEventDispatcher } from 'svelte'
import calendar from '../plugin'
import DateEditor from './DateEditor.svelte'
import { Timestamp } from '@hcengineering/core'
export let rules: RecurringRule[]
export let startDate: Timestamp
type Freq = 'DAILY' | 'WEEKLY' | 'MONTHLY' | 'YEARLY'
let periodType: Freq = (rules[0]?.freq as Freq) ?? 'WEEKLY'
@ -76,7 +78,7 @@
let count: number = rules[0]?.count ?? 1
let selectedWeekdays: string[] = rules[0]?.byDay ?? [getWeekday(new Date())]
let selectedWeekdays: string[] = rules[0]?.byDay ?? [getWeekday(new Date(startDate))]
function weekdayClick (day: string) {
const index = selectedWeekdays.findIndex((p) => p === day)

View File

@ -0,0 +1,46 @@
<!--
// 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 { DAY, HOUR, MINUTE, TimeShiftPresenter } from '@hcengineering/ui'
import { createEventDispatcher } from 'svelte'
export let value: number
export function canClose () {
return true
}
const minutes: number[] = [5, 10, 15, 30]
const hours: number[] = [1]
const days: number[] = [1]
$: values = [...minutes.map((m) => m * MINUTE), ...hours.map((m) => m * HOUR), ...days.map((m) => m * DAY)]
const dispatch = createEventDispatcher()
</script>
<div class="antiPopup">
{#each values as val}
<div
class="ap-menuItem flex-row-center hoverable"
class:selected={value === val}
on:click={() => {
dispatch('close', val)
}}
>
<TimeShiftPresenter value={val * -1} />
</div>
{/each}
</div>

View File

@ -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 EventParticipants from './components/EventParticipants.svelte'
import EventTimeEditor from './components/EventTimeEditor.svelte'
import IntegrationConfigure from './components/IntegrationConfigure.svelte'
import EventReminders from './components/EventReminders.svelte'
import calendar from './plugin'
import contact from '@hcengineering/contact'
import { deleteObjects } from '@hcengineering/view-resources'
export { EventElement, CalendarView, DayCalendar }
export { EventElement, CalendarView, DayCalendar, EventParticipants, EventTimeEditor, EventReminders }
export * from './utils'

View File

@ -77,6 +77,7 @@ export default mergeIds(calendarId, calendar, {
Times: '' as IntlString,
AddParticipants: '' as IntlString,
Sync: '' as IntlString,
Busy: '' as IntlString
Busy: '' as IntlString,
AddReminder: '' as IntlString
}
})

View File

@ -144,7 +144,8 @@ export {
IconMembers,
SelectAvatars,
UserBoxItems,
MembersBox
MembersBox,
PersonRefPresenter
}
const toObjectSearchResult = (e: WithLookup<Contact>): ObjectSearchResult => ({