Signed-off-by: Denis Bykhov <80476319+BykhovDenis@users.noreply.github.com>
This commit is contained in:
Denis Bykhov 2022-03-26 23:34:06 +06:00 committed by GitHub
parent e875f51ad3
commit 4188d6a32b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
45 changed files with 984 additions and 52 deletions

View File

@ -34,6 +34,7 @@
"@anticrm/calendar": "~0.6.0",
"@anticrm/calendar-resources": "~0.6.0",
"@anticrm/platform": "~0.6.5",
"@anticrm/notification": "~0.6.0",
"@anticrm/model-core": "~0.6.0",
"@anticrm/model-view": "~0.6.0",
"@anticrm/model-workbench": "~0.6.1",

View File

@ -13,19 +13,21 @@
// limitations under the License.
//
import { Calendar, Event } from '@anticrm/calendar'
import activity from '@anticrm/activity'
import { Calendar, Event, Reminder } from '@anticrm/calendar'
import { Employee } from '@anticrm/contact'
import type { Domain, Markup, Ref, Timestamp } from '@anticrm/core'
import { IndexKind } from '@anticrm/core'
import { Builder, Collection, Index, Model, Prop, TypeDate, TypeMarkup, TypeString, UX } from '@anticrm/model'
import { Builder, Collection, Index, Mixin, Model, Prop, TypeDate, TypeMarkup, TypeString, UX } from '@anticrm/model'
import attachment from '@anticrm/model-attachment'
import chunter from '@anticrm/model-chunter'
import contact from '@anticrm/model-contact'
import core, { TAttachedDoc } from '@anticrm/model-core'
import { TSpaceWithStates } from '@anticrm/model-task'
import workbench from '@anticrm/model-workbench'
import calendar from './plugin'
import view from '@anticrm/model-view'
import workbench from '@anticrm/model-workbench'
import notification from '@anticrm/notification'
import calendar from './plugin'
export * from '@anticrm/calendar'
@ -42,9 +44,6 @@ export class TEvent extends TAttachedDoc implements Event {
@Index(IndexKind.FullText)
title!: string
@Prop(TypeString(), calendar.string.EventNumber)
number!: number
@Prop(TypeMarkup(), calendar.string.Description)
@Index(IndexKind.FullText)
description!: Markup
@ -69,8 +68,19 @@ export class TEvent extends TAttachedDoc implements Event {
participants!: Ref<Employee>[]
}
@Mixin(calendar.mixin.Reminder, calendar.class.Event)
@UX(calendar.string.Reminder, calendar.icon.Calendar)
export class TReminder extends TEvent implements Reminder {
@Prop(TypeDate(true), calendar.string.Shift)
shift!: Timestamp
@Prop(TypeString(), calendar.string.State)
@Index(IndexKind.Indexed)
state!: 'active' | 'done'
}
export function createModel (builder: Builder): void {
builder.createModel(TCalendar, TEvent)
builder.createModel(TCalendar, TEvent, TReminder)
builder.createDoc(workbench.class.Application, core.space.Model, {
label: calendar.string.ApplicationLabelCalendar,
@ -88,6 +98,21 @@ export function createModel (builder: Builder): void {
}
}, calendar.app.Calendar)
builder.createDoc(notification.class.NotificationType, core.space.Model, {
label: calendar.string.Reminder
}, calendar.ids.ReminderNotification)
builder.createDoc(activity.class.TxViewlet, core.space.Model, {
objectClass: calendar.mixin.Reminder,
icon: calendar.icon.Reminder,
txClass: core.class.TxMixin,
label: calendar.string.CreatedReminder,
component: calendar.activity.ReminderViewlet,
display: 'emphasized',
editable: false,
hideOnRemove: true
}, calendar.ids.ReminderViewlet)
builder.createDoc(
view.class.ViewletDescriptor,
core.space.Model,
@ -99,6 +124,30 @@ export function createModel (builder: Builder): void {
calendar.viewlet.Calendar
)
builder.createDoc(
view.class.Action,
core.space.Model,
{
label: calendar.string.RemindMeAt,
icon: calendar.icon.Reminder,
action: calendar.actionImpl.SaveEventReminder
},
calendar.action.SaveEventReminder
)
builder.createDoc(view.class.ActionTarget, core.space.Model, {
target: calendar.class.Event,
action: calendar.action.SaveEventReminder
})
builder.mixin(calendar.mixin.Reminder, core.class.Class, view.mixin.AttributePresenter, {
presenter: calendar.component.ReminderPresenter
})
builder.mixin(calendar.class.Event, core.class.Class, view.mixin.ObjectEditor, {
editor: calendar.component.EditEvent
})
// Use generic child presenter
builder.mixin(calendar.class.Event, core.class.Class, view.mixin.AttributePresenter, {
presenter: view.component.ObjectPresenter

View File

@ -13,24 +13,40 @@
// limitations under the License.
//
import { TxViewlet } from '@anticrm/activity'
import { calendarId } from '@anticrm/calendar'
import calendar from '@anticrm/calendar-resources/src/plugin'
import { Ref } from '@anticrm/core'
import type { IntlString } from '@anticrm/platform'
import { Doc, Ref } from '@anticrm/core'
import type { IntlString, Resource } from '@anticrm/platform'
import { mergeIds } from '@anticrm/platform'
import { AnyComponent } from '@anticrm/ui'
import { ViewletDescriptor } from '@anticrm/view'
import { Action, ViewletDescriptor } from '@anticrm/view'
export default mergeIds(calendarId, calendar, {
component: {
CreateCalendar: '' as AnyComponent,
CalendarView: '' as AnyComponent
CalendarView: '' as AnyComponent,
EditEvent: '' as AnyComponent,
ReminderPresenter: '' as AnyComponent
},
action: {
SaveEventReminder: '' as Ref<Action>
},
actionImpl: {
SaveEventReminder: '' as Resource<(object: Doc) => Promise<void>>
},
string: {
ApplicationLabelCalendar: '' as IntlString,
Event: '' as IntlString
Event: '' as IntlString,
Reminder: '' as IntlString,
Shift: '' as IntlString,
State: '' as IntlString,
CreatedReminder: '' as IntlString
},
viewlet: {
Calendar: '' as Ref<ViewletDescriptor>
},
ids: {
ReminderViewlet: '' as Ref<TxViewlet>
}
})

View File

@ -13,12 +13,14 @@
// limitations under the License.
//
import type {
import {
AttachedDoc,
Class,
Data,
Doc,
DocumentUpdate,
DOMAIN_TX,
IndexKind,
Mixin,
MixinUpdate,
PropertyType,
@ -34,8 +36,7 @@ import type {
TxRemoveDoc,
TxUpdateDoc
} from '@anticrm/core'
import { DOMAIN_TX } from '@anticrm/core'
import { Model } from '@anticrm/model'
import { Index, Model } from '@anticrm/model'
import core from './component'
import { TDoc } from './core'
@ -48,7 +49,9 @@ export class TTx extends TDoc implements Tx {
@Model(core.class.TxCUD, core.class.Tx)
export class TTxCUD<T extends Doc> extends TTx implements TxCUD<T> {
@Index(IndexKind.Indexed)
objectId!: Ref<T>
objectClass!: Ref<Class<T>>
}

View File

@ -24,6 +24,9 @@ export class TReview extends TEvent implements Review {
@Prop(TypeRef(recruit.mixin.Candidate), recruit.string.Candidate)
declare attachedTo: Ref<Candidate>
@Prop(TypeString(), recruit.string.Review)
number!: number
@Prop(TypeString(), recruit.string.Verdict)
@Index(IndexKind.FullText)
verdict!: string

View File

@ -140,7 +140,7 @@ export abstract class MemDb extends TxProcessor {
result = this.getObjectsByClass(baseClass)
}
result = matchQuery(result, query)
result = matchQuery(result, query, _class, this.hierarchy)
if (baseClass !== _class) {
// We need to filter instances without mixin was set

View File

@ -1,5 +1,6 @@
import { DocumentQuery } from '.'
import { Doc } from './classes'
import { Class, Doc, Ref } from './classes'
import { Hierarchy } from './hierarchy'
import { getObjectValue } from './objvalue'
import { createPredicates, isPredicate } from './predicate'
import { SortingOrder, SortingQuery } from './storage'
@ -71,15 +72,31 @@ function getValue (key: string, obj: any): any {
/**
* @public
*/
export function matchQuery<T extends Doc> (docs: Doc[], query: DocumentQuery<T>): Doc[] {
export function matchQuery<T extends Doc> (docs: Doc[], query: DocumentQuery<T>, clazz: Ref<Class<T>>, hierarchy: Hierarchy): Doc[] {
let result = [...docs]
for (const key in query) {
if (key === '_id' && ((query._id as any)?.$like === undefined || query._id === undefined)) continue
const value = (query as any)[key]
result = findProperty(result, key, value)
const tkey = checkMixinKey(key, clazz, hierarchy)
result = findProperty(result, tkey, value)
if (result.length === 0) {
break
}
}
return result
}
function checkMixinKey<T extends Doc> (key: string, clazz: Ref<Class<T>>, hierarchy: Hierarchy): string {
if (!key.includes('.')) {
try {
const attr = hierarchy.getAttribute(clazz, key)
if (hierarchy.isMixin(attr.attributeOf)) {
// It is mixin
key = attr.attributeOf + '.' + key
}
} catch (err: any) {
// ignore, if
}
}
return key
}

View File

@ -39,6 +39,7 @@
"@anticrm/chunter": "~0.6.1",
"@anticrm/presentation": "~0.6.2",
"@anticrm/activity": "~0.6.0",
"@anticrm/calendar": "~0.6.0",
"@anticrm/notification": "~0.6.0"
}
}

View File

@ -17,6 +17,7 @@
import activity from '@anticrm/activity'
import type { Doc } from '@anticrm/core'
import notification from '@anticrm/notification'
import calendar from '@anticrm/calendar'
import type { Asset } from '@anticrm/platform'
import { ActionIcon,AnyComponent,AnySvelteComponent,Component,Icon,IconClose,IconExpand,IconMoreH,Scroller } from '@anticrm/ui'
import { createEventDispatcher } from 'svelte'
@ -43,7 +44,12 @@
{#if subtitle }<span class="ac-header__description">{subtitle}</span>{/if}
</div>
</div>
<Component is={notification.component.LastViewEditor} props={{ value: object }} />
<div class="flex">
<Component is={calendar.component.DocReminder} props={{ value: object, title }} />
<div class="ml-2">
<Component is={notification.component.LastViewEditor} props={{ value: object }} />
</div>
</div>
</div>
{#if $$slots.subtitle}
<div class="ac-subtitle">

View File

@ -25,7 +25,7 @@
import presentation from '..'
export let spaceClass: Ref<Class<Space>> | undefined = undefined
export let space: Ref<Space>
export let space: Ref<Space> | undefined = undefined
export let spaceQuery: DocumentQuery<Space> | undefined = { archived: false }
export let spaceLabel: IntlString | undefined = undefined
export let spacePlaceholder: IntlString | undefined = undefined

View File

@ -26,7 +26,7 @@
export let spaceQuery: DocumentQuery<Space> | undefined = { archived: false }
export let label: IntlString
export let placeholder: IntlString
export let value: Ref<Space>
export let value: Ref<Space> | undefined
export let show: boolean = false
let selected: Space | undefined
@ -34,8 +34,8 @@
const client = getClient()
async function updateSelected (value: Ref<Space>) {
selected = await client.findOne(_class, { ...(spaceQuery ?? {}), _id: value })
async function updateSelected (value: Ref<Space> | undefined) {
selected = value !== undefined ? await client.findOne(_class, { ...(spaceQuery ?? {}), _id: value }) : undefined
}
$: updateSelected(value)

View File

@ -57,6 +57,10 @@
}}
on:blur={() => {
focused = false
if (alwaysEdit) {
dispatch('value', rawValue)
content = rawValue
}
}}
on:value={(evt) => {
rawValue = evt.detail

View File

@ -17,7 +17,11 @@
"Today": "Today",
"English": "English",
"Russian": "Russian",
"CalendarLeft": "<",
"CalendarRight": ">"
"MinutesBefore": "{minutes, plural, =1 {a minute before} other {# minutes before}}",
"HoursBefore": "{hours, plural, =1 {an hour before} other {# hours before}}",
"DaysBefore": "{days, plural, =1 {a day before} other {# days before}}",
"MinutesAfter": "{minutes, plural, =1 {in a minute} other {in # minutes}}",
"HoursAfter": "{hours, plural, =1 {in an hour} other {in # hours}}",
"DaysAfter": "{days, plural, =1 {in a day} other {in # days}}"
}
}

View File

@ -17,7 +17,11 @@
"Today": "Сегодня",
"English": "Английский",
"Russian": "Русский",
"CalendarLeft": "<",
"CalendarRight": ">"
"MinutesBefore": "{minutes, plural, =1 {за минуту} other {за # минут}}",
"HoursBefore": "{hours, plural, =1 {за час} other {за # часа}}",
"DaysBefore": "{days, plural, =1 {за день} =3 {за # дня} other {за # дней}}",
"MinutesAfter": "{minutes, plural, =1 {через минуту} other {через # минут}}",
"HoursAfter": "{hours, plural, =1 {через час} other {через # часа}}",
"DaysAfter": "{days, plural, =1 {через день} other {через # дней}}"
}
}

View File

@ -25,7 +25,7 @@
export let direction: TooltipAligment | undefined = undefined
export let icon: Asset | AnySvelteComponent
export let size: 'small' | 'medium' | 'large'
export let action: (ev?: Event) => Promise<void> | void = async () => { }
export let action: (ev: Event) => Promise<void> | void = async () => { }
export let invisible: boolean = false
</script>

View File

@ -0,0 +1,79 @@
<!--
// Copyright © 2022 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 type { IntlString } from '@anticrm/platform'
import { createEventDispatcher,onMount } from 'svelte'
import { Label,showPopup } from '..'
import Calendar from './icons/Calendar.svelte'
import Close from './icons/Close.svelte'
import TimeShiftPopup from './TimeShiftPopup.svelte'
import TimeShiftPresenter from './TimeShiftPresenter.svelte'
export let title: IntlString
export let value: number
export let show: boolean = false
export let direction: 'before' | 'after' = 'before'
const dispatch = createEventDispatcher()
let opened: boolean = false
let container: HTMLElement
let btn: HTMLElement
const changeValue = (result: any): void => {
if (result !== undefined) {
value = result
dispatch('change', result)
}
}
onMount(() => {
if (btn && show) {
btn.click()
show = false
}
})
</script>
<div class="antiSelect"
bind:this={container}
on:click|preventDefault={() => {
btn.focus()
if (!opened) {
opened = true
showPopup(TimeShiftPopup, { title, value, direction }, container, (ev) => {
console.log('picker close handle')
console.log(ev)
changeValue(ev)
opened = false
}, changeValue)
}
}}
>
<button
bind:this={btn}
class="button round-2"
class:selected={value}
>
<div class="icon">
{#if show}<Close size={'small'} />{:else}<Calendar size={'medium'} />{/if}
</div>
</button>
<div class="group">
<span class="label"><Label label={title} /></span>
<TimeShiftPresenter {value} />
</div>
</div>

View File

@ -0,0 +1,40 @@
<!--
// Copyright © 2022 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 { createEventDispatcher } from 'svelte'
import TimeShiftPresenter from './TimeShiftPresenter.svelte'
export let value: number
export let direction: 'before' | 'after'
export let minutes: number[] = [5, 15, 30]
export let hours: number[] = [1, 2, 4]
export let days: number[] = [1, 3, 7, 30]
const dispatch = createEventDispatcher()
$: base = direction === 'before' ? -1 : 1
const MINUTE = 60 * 1000
const HOUR = 60 * MINUTE
const DAY = 24 * HOUR
$: values = [...minutes.map((m) => m * MINUTE), ...hours.map((m) => m * HOUR), ...days.map((m) => m * DAY)]
</script>
<div class="antiPopup">
{#each values as value}
<div class="ap-menuItem">
<TimeShiftPresenter value={value * base} on:click={() => { dispatch('close', value) }} />
</div>
{/each}
</div>

View File

@ -0,0 +1,55 @@
<!--
// Copyright © 2022 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 { translate } from "@anticrm/platform"
import ui from '../plugin'
export let value: number
const SECOND = 1000
const MINUTE = SECOND * 60
const HOUR = MINUTE * 60
const DAY = HOUR * 24
let time: string = ''
async function formatTime (value: number) {
console.log('value')
console.log(value)
if (value > 0) {
if (value < HOUR) {
time = await translate(ui.string.MinutesAfter, { minutes: Math.floor(value / MINUTE) })
} else if (value < DAY) {
time = await translate(ui.string.HoursAfter, { hours: Math.floor(value / HOUR) })
} else {
time = await translate(ui.string.DaysAfter, { days: Math.floor(value / DAY) })
}
} else {
const abs = Math.abs(value)
if (abs < HOUR) {
time = await translate(ui.string.MinutesBefore, { minutes: Math.floor(abs / MINUTE) })
} else if (abs < DAY) {
time = await translate(ui.string.HoursBefore, { hours: Math.floor(abs / HOUR) })
} else {
time = await translate(ui.string.DaysBefore, { days: Math.floor(abs / DAY) })
}
}
}
$: formatTime(value)
</script>
<span style="white-space: nowrap;" on:click>{time}</span>

View File

@ -66,6 +66,7 @@ export { default as DropdownPopup } from './components/DropdownPopup.svelte'
export { default as DropdownLabels } from './components/DropdownLabels.svelte'
export { default as ShowMore } from './components/ShowMore.svelte'
export { default as Menu } from './components/Menu.svelte'
export { default as TimeShiftPicker } from './components/TimeShiftPicker.svelte'
export { default as ErrorPresenter } from './components/ErrorPresenter.svelte'
export { default as Scroller } from './components/Scroller.svelte'

View File

@ -41,7 +41,13 @@ export default plugin(uiId, {
NotSelected: '' as IntlString,
Today: '' as IntlString,
English: '' as IntlString,
Russian: '' as IntlString
Russian: '' as IntlString,
MinutesBefore: '' as IntlString,
HoursBefore: '' as IntlString,
DaysBefore: '' as IntlString,
MinutesAfter: '' as IntlString,
HoursAfter: '' as IntlString,
DaysAfter: '' as IntlString
},
metadata: {
DefaultApplication: '' as Metadata<AnyComponent>

View File

@ -50,6 +50,16 @@ export function getDTxProps (dtx: DisplayTx): any {
return { tx: dtx.tx, value: dtx.doc, dtx }
}
function getViewlet (viewlets: Map<ActivityKey, TxViewlet>, dtx: DisplayTx): TxDisplayViewlet | undefined {
let key: string
if (dtx.mixinTx?.mixin !== undefined && dtx.tx._id === dtx.mixinTx._id) {
key = activityKey(dtx.mixinTx.mixin, dtx.tx._class)
} else {
key = activityKey(dtx.tx.objectClass, dtx.tx._class)
}
return viewlets.get(key)
}
export async function updateViewlet (
client: TxOperations,
viewlets: Map<ActivityKey, TxViewlet>,
@ -61,8 +71,7 @@ export async function updateViewlet (
props: any
modelIcon: Asset | undefined
}> {
const key = activityKey(dtx.tx.objectClass, dtx.tx._class)
let viewlet: TxDisplayViewlet = viewlets.get(key)
let viewlet = getViewlet(viewlets, dtx)
const props = getDTxProps(dtx)
let model: AttributeModel[] = []

View File

@ -6,4 +6,7 @@
<path d="M8,4.6c-1.5,0-2.6,1.2-2.6,2.6S6.5,9.9,8,9.9s2.6-1.2,2.6-2.6S9.5,4.6,8,4.6z M8,8.9c-0.9,0-1.6-0.7-1.6-1.6 c0-0.9,0.7-1.6,1.6-1.6s1.6,0.7,1.6,1.6C9.6,8.2,8.9,8.9,8,8.9z"/>
<path d="M8,1.8c-3,0-5.5,2.5-5.5,5.5c0,1.9,0.9,3.4,2,4.5c1.1,1.1,2.3,1.8,2.9,2.1c0.4,0.2,0.9,0.2,1.3,0c0.6-0.3,1.8-1,2.9-2.1 c1.1-1.1,2-2.6,2-4.5C13.5,4.3,11,1.8,8,1.8z M10.8,11.1c-1,1-2.1,1.7-2.6,2c-0.1,0.1-0.2,0.1-0.3,0c-0.6-0.3-1.7-1-2.6-2 c-1-1-1.7-2.3-1.7-3.8c0-2.5,2-4.5,4.5-4.5s4.5,2,4.5,4.5C12.5,8.8,11.7,10.1,10.8,11.1z"/>
</symbol>
<symbol id="reminder" viewBox="0 0 493 511.92">
<path fill-rule="nonzero" d="M277.16 41.75c49.87 6.77 94.55 29.88 128.47 63.79 40.67 40.67 65.83 96.87 65.83 158.93 0 62.08-25.15 118.28-65.83 158.96a227.22 227.22 0 0 1-25.34 21.83l27.24 38.33c5.68 8.18 3.65 19.42-4.54 25.11-8.19 5.68-19.44 3.65-25.12-4.54l-28.28-39.78c-30.84 15.91-65.83 24.89-102.92 24.89-37.7 0-73.23-9.28-104.43-25.69l-26.59 39.71c-5.54 8.28-16.76 10.5-25.04 4.95-8.29-5.54-10.5-16.75-4.95-25.03l26.07-38.95a225.636 225.636 0 0 1-24-20.83c-40.68-40.68-65.84-96.89-65.84-158.96 0-62.07 25.16-118.26 65.84-158.94 36.44-36.43 85.34-60.39 139.74-65.03 16.45-1.4 33.38-.96 49.69 1.25zm204.53 102.98c17.3-41.28 15.24-84.52-9.51-113.49-29.7-34.77-83.39-38.75-133.26-14.3 53.01 36.36 101.12 78.78 142.77 127.79zm-470.15 1.35C-6.1 104.02-4.01 59.97 21.21 30.45 51.47-4.97 106.18-9.03 156.99 15.88c-54 37.06-103.03 80.26-145.45 130.2zm269.3 101.47 67.65-1.18c9.97-.17 18.19 7.76 18.36 17.73.18 9.97-7.76 18.19-17.73 18.37l-69.51 1.21c-6.61 11.32-18.89 18.93-32.94 18.93-21.05 0-38.12-17.08-38.12-38.13 0-14.52 8.13-27.15 20.08-33.58v-87.35c0-9.97 8.07-18.05 18.04-18.05 9.97 0 18.06 8.08 18.06 18.05v87.35a38.324 38.324 0 0 1 16.11 16.65zm99.27-116.5c-34.14-34.14-81.32-55.26-133.43-55.26-52.1 0-99.28 21.12-133.42 55.26-34.15 34.14-55.27 81.32-55.27 133.43 0 52.11 21.12 99.28 55.27 133.43 34.14 34.14 81.31 55.26 133.41 55.26 52.12 0 99.29-21.12 133.43-55.26 34.14-34.15 55.28-81.32 55.28-133.44 0-52.1-21.13-99.27-55.27-133.42z"/>
</symbol>
</svg>

Before

Width:  |  Height:  |  Size: 1.1 KiB

After

Width:  |  Height:  |  Size: 2.6 KiB

View File

@ -25,6 +25,13 @@
"TableView": "Table",
"DueMinutes": "{minutes, plural, =0 {less than a minute} =1 {a minute} other {# minutes}}",
"DueHours": "{hours, plural, =0 {less than an hour} =1 {1 hour} other {# hours}}",
"DueDays": "{days, plural, =0 {today} =1 {1 day} other {# days}}"
"DueDays": "{days, plural, =0 {today} =1 {1 day} other {# days}}",
"Reminder": "Reminder",
"ReminderTime": "Reminder time",
"RemindMeAt": "Remind me at",
"EditReminder": "Edit reminder",
"CreateReminder": "Create reminder",
"CreatedReminder": "Created a reminder",
"Reminders": "Reminders"
}
}

View File

@ -25,6 +25,13 @@
"TableView": "Таблица",
"DueMinutes": "{minutes, plural, =0 {меньше минуты} =1 {минута} other {# минут}}",
"DueHours": "{hours, plural, =0 {меньше часа} =1 {1 час} other {# часы}}",
"DueDays": "{days, plural, =0 {сегодня} =1 {1 день} other {# дня}}"
"DueDays": "{days, plural, =0 {сегодня} =1 {1 день} other {# дня}}",
"Reminder": "Напоминание",
"RemindMeAt": "Напомнить мне",
"ReminderTime": "Время напоминания",
"EditReminder": "Редактировать напоминание",
"CreateReminder": "Создать напоминание",
"CreatedReminder": "Создал напоминание",
"Reminders": "Напоминания"
}
}

View File

@ -19,6 +19,7 @@ import calendar, { calendarId } from '@anticrm/calendar'
const icons = require('../assets/icons.svg') as string // eslint-disable-line
loadMetadata(calendar.icon, {
Calendar: `${icons}#calendar`,
Reminder: `${icons}#reminder`,
Location: `${icons}#location`
})

View File

@ -0,0 +1,91 @@
<!--
// Copyright © 2022 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 contact,{ Employee, EmployeeAccount } from '@anticrm/contact'
import { Class,Doc,getCurrentAccount,Ref } from '@anticrm/core'
import { Card,getClient,UserBoxList } from '@anticrm/presentation'
import { TimeShiftPicker,Grid,StylishEdit } from '@anticrm/ui'
import { createEventDispatcher } from 'svelte'
import calendar from '../plugin'
export let attachedTo: Ref<Doc>
export let attachedToClass: Ref<Class<Doc>>
export let title: string = ''
let shift = 30 * 60 * 1000
const currentUser = getCurrentAccount() as EmployeeAccount
let participants: Ref<Employee>[] = [currentUser.employee]
const space = calendar.space.PersonalEvents
const dispatch = createEventDispatcher()
const client = getClient()
export function canClose (): boolean {
return title.trim().length === 0 && participants.length === 0
}
async function saveReminder () {
const date = new Date().getTime() + shift
const _id = await client.createDoc(calendar.class.Event, space, {
attachedTo,
attachedToClass,
collection: 'reminders',
date,
description: '',
participants,
title
})
await client.createMixin(_id, calendar.class.Event, space, calendar.mixin.Reminder, {
shift: 0,
state: 'active'
})
}
</script>
<Card
label={calendar.string.CreateReminder}
okAction={saveReminder}
canSave={title.trim().length > 0 && participants.length > 0}
{space}
on:close={() => {
dispatch('close')
}}
>
<Grid column={1} rowGap={1.75}>
<StylishEdit bind:value={title} label={calendar.string.Title} />
<div class="antiComponentBox">
<TimeShiftPicker title={calendar.string.Date} bind:value={shift} direction='after' />
</div>
<UserBoxList
_class={contact.class.Employee}
items={participants}
title={calendar.string.Participants}
on:open={(evt) => {
participants.push(evt.detail._id)
participants = participants
}}
on:delete={(evt) => {
const _id = evt.detail._id
const index = participants.findIndex((p) => p === _id)
if (index !== -1) {
participants.splice(index, 1)
participants = participants
}
}}
noItems={calendar.string.NoParticipants}
/>
</Grid>
</Card>

View File

@ -0,0 +1,52 @@
<!--
// Copyright © 2022 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 { EmployeeAccount } from '@anticrm/contact'
import { Doc,getCurrentAccount } from '@anticrm/core'
import { getClient } from '@anticrm/presentation'
import { ActionIcon,showPopup } from '@anticrm/ui'
import calendar from '../plugin'
import CreateReminder from './CreateReminder.svelte'
import DocRemindersPopup from './DocRemindersPopup.svelte'
import SaveEventReminder from './SaveEventReminder.svelte'
export let value: Doc
export let title: string | undefined
const client = getClient()
const hierarchy = client.getHierarchy()
$: isEvent = hierarchy.isDerived(value._class, calendar.class.Event)
async function click (ev: Event): Promise<void> {
if (isEvent) {
showPopup(SaveEventReminder, { objectId: value._id, objectClass: value._class }, ev.target as HTMLElement)
} else {
const currentUser = getCurrentAccount() as EmployeeAccount
const current = await client.findOne(calendar.mixin.Reminder, { attachedTo: value._id, state: 'active', participants: currentUser.employee })
if (current === undefined) {
showPopup(CreateReminder, { attachedTo: value._id, attachedToClass: value._class, title }, ev.target as HTMLElement)
} else {
showPopup(DocRemindersPopup, { attachedTo: value._id, attachedToClass: value._class }, ev.target as HTMLElement )
}
}
}
</script>
{#if isEvent}
<ActionIcon size='medium' label={calendar.string.RemindMeAt} icon={calendar.icon.Reminder} action={(e) => click(e)} />
{:else}
<ActionIcon size='medium' label={calendar.string.Reminders} icon={calendar.icon.Reminder} action={(e) => click(e)} />
{/if}

View File

@ -0,0 +1,50 @@
<!--
// Copyright © 2022 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 { EmployeeAccount } from '@anticrm/contact'
import { Class,Doc,getCurrentAccount,Ref } from '@anticrm/core'
import { Button,showPopup } from '@anticrm/ui'
import { Table } from '@anticrm/view-resources'
import calendar from '../plugin'
import CreateReminder from './CreateReminder.svelte'
export let attachedTo: Ref<Doc>
export let attachedToClass: Ref<Class<Doc>>
export let title: string | undefined
function click (ev: Event): void {
showPopup(CreateReminder, { attachedTo, attachedToClass, title }, ev.target as HTMLElement)
}
const currentUser = getCurrentAccount() as EmployeeAccount
</script>
<div class='antiPopup'>
<Button label={calendar.string.CreateReminder} primary on:click={(e) => click(e)} />
<div class="ap-space" />
<Table
_class={calendar.mixin.Reminder}
config={['']}
options={ {} }
query={ { attachedTo, state: 'active', participants: currentUser.employee } }
/>
</div>
<style lang="scss">
.antiPopup {
padding: 1rem;
}
</style>

View File

@ -0,0 +1,75 @@
<!--
// Copyright © 2022 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 '@anticrm/calendar'
import contact from '@anticrm/contact'
import { getClient,UserBoxList } from '@anticrm/presentation'
import { StyledTextBox } from '@anticrm/text-editor'
import { Label,StylishEdit } from '@anticrm/ui'
import { createEventDispatcher,onMount } from 'svelte'
import calendar from '../plugin'
export let object: Event
const dispatch = createEventDispatcher()
const client = getClient()
onMount(() => {
dispatch('open', {
ignoreKeys: ['comments', 'title', 'description'],
ignoreMixins: [calendar.mixin.Reminder]
})
})
</script>
{#if object !== undefined}
<div class="mb-2">
<div class="mb-4">
<StylishEdit
label={calendar.string.Title}
bind:value={object.title}
on:change={() => client.update(object, { title: object.title })}
/>
</div>
<div class="mb-2">
<StyledTextBox
emphasized
content={object.description}
on:value={(evt) => {
client.update(object, { description: evt.detail })
}}
label={calendar.string.Description}
placeholder={calendar.string.Description}
/>
</div>
<div class="flex-row">
<div class="mt-4 mb-2">
<Label label={calendar.string.Participants} />
</div>
<UserBoxList
_class={contact.class.Employee}
items={object.participants}
title={calendar.string.Participants}
on:open={(evt) => {
client.update(object, { $push: { participants: evt.detail._id } })
}}
on:delete={(evt) => {
client.update(object, { $pull: { participants: evt.detail._id } })
}}
noItems={calendar.string.NoParticipants}
/>
</div>
</div>
{/if}

View File

@ -0,0 +1,97 @@
<!--
// Copyright © 2022 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 '@anticrm/calendar'
import contact,{ Employee } from '@anticrm/contact'
import { Ref } from '@anticrm/core'
import presentation, { Card,getClient,UserBoxList } from '@anticrm/presentation'
import { StyledTextBox } from '@anticrm/text-editor'
import ui, { DatePicker,Grid,StylishEdit } from '@anticrm/ui'
import { createEventDispatcher } from 'svelte'
import calendar from '../plugin'
export let value: Event
let title: string = value.title
let description: string = value.description
let startDate: Date = new Date(value.date)
let participants: Ref<Employee>[] = value.participants ?? []
const space = calendar.space.PersonalEvents
const dispatch = createEventDispatcher()
const client = getClient()
export function canClose (): boolean {
return title.trim().length === 0 && participants.length === 0
}
async function saveReminder () {
await client.updateDoc(value._class, value.space, value._id, {
date: startDate.getTime(),
description,
participants,
title
})
await client.updateMixin(value._id, value._class, space, calendar.mixin.Reminder, {
shift: 0,
state: 'active'
})
}
</script>
<Card
label={calendar.string.EditReminder}
okAction={saveReminder}
okLabel={presentation.string.Save}
canSave={title.trim().length > 0 && startDate.getTime() > 0 && participants.length > 0}
{space}
on:close={() => {
dispatch('close')
}}
>
<Grid column={1} rowGap={1.75}>
<StylishEdit bind:value={title} label={calendar.string.Title} />
<StyledTextBox
emphasized
showButtons={false}
alwaysEdit
bind:content={description}
label={calendar.string.Description}
placeholder={calendar.string.Description}
/>
<div class="antiComponentBox">
<DatePicker title={calendar.string.Date} bind:value={startDate} withTime />
</div>
<UserBoxList
_class={contact.class.Employee}
items={participants}
title={calendar.string.Participants}
on:open={(evt) => {
participants.push(evt.detail._id)
participants = participants
}}
on:delete={(evt) => {
const _id = evt.detail._id
const index = participants.findIndex((p) => p === _id)
if (index !== -1) {
participants.splice(index, 1)
participants = participants
}
}}
noItems={calendar.string.NoParticipants}
/>
</Grid>
</Card>

View File

@ -0,0 +1,42 @@
<!--
// Copyright © 2022 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 { Reminder } from '@anticrm/calendar'
import { getResource } from '@anticrm/platform'
import { DateTimePresenter, showPanel, Tooltip } from '@anticrm/ui'
import view from '@anticrm/view'
export let value: Reminder
function click (): void {
showPanel(view.component.EditDoc, value._id, value._class, 'full')
}
const objectPresenter = getResource(view.component.ObjectPresenter)
</script>
<div class="antiSelect w-full cursor-pointer flex-between" on:click={click}>
{#if value}
<div class="mr-4">
{#await objectPresenter then component}
<Tooltip {component} props={{ objectId: value.attachedTo, _class: value.attachedToClass }}>
{value.title}
</Tooltip>
{/await}
</div>
<DateTimePresenter value={new Date(value.date + value.shift)} />
{/if}
</div>

View File

@ -0,0 +1,38 @@
<!--
// Copyright © 2022 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 { EmployeeAccount } from '@anticrm/contact'
import { getCurrentAccount, Lookup } from '@anticrm/core'
import { Table } from '@anticrm/view-resources'
import calendar from '../plugin'
const currentUser = getCurrentAccount() as EmployeeAccount
</script>
<div class='antiPopup'>
<Table
_class={calendar.mixin.Reminder}
config={['']}
options={{}}
query={ { state: 'active', participants: currentUser.employee } }
/>
</div>
<style lang="scss">
.antiPopup {
padding: 1rem;
}
</style>

View File

@ -0,0 +1,63 @@
<!--
// Copyright © 2022 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 '@anticrm/calendar'
import { Class,Ref, Timestamp } from '@anticrm/core'
import presentation, { Card,createQuery,getClient } from '@anticrm/presentation'
import { Grid, TimeShiftPicker } from '@anticrm/ui'
import { createEventDispatcher } from 'svelte'
import calendar from '../plugin'
export let objectId: Ref<Event>
export let objectClass: Ref<Class<Event>>
const client = getClient()
const query = createQuery()
query.query(objectClass, { _id: objectId }, (res) => {
event = res[0]
})
let shift: Timestamp = -30 * 60 * 1000
let event: Event | undefined
const dispatch = createEventDispatcher()
export function canClose (): boolean {
return true
}
async function saveReminder () {
if (event === undefined) return
await client.updateMixin(event._id, event._class, event.space, calendar.mixin.Reminder, {
shift,
state: 'active'
})
}
</script>
<Card
label={calendar.string.RemindMeAt}
okLabel={presentation.string.Save}
canSave={event !== undefined}
okAction={saveReminder}
on:close={() => {
dispatch('close')
}}
>
<Grid column={1} rowGap={1.75}>
<div class="antiComponentBox">
<TimeShiftPicker title={calendar.string.RemindMeAt} bind:value={shift} />
</div>
</Grid>
</Card>

View File

@ -0,0 +1,43 @@
<!--
// Copyright © 2022 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,Reminder } from '@anticrm/calendar'
import { Ref,TxMixin } from '@anticrm/core'
import { getClient } from '@anticrm/presentation'
import { DateTimePresenter,showPanel } from '@anticrm/ui'
import view from '@anticrm/view'
import calendar from '../../plugin'
export let tx: TxMixin<Event, Reminder>
const client = getClient()
async function getEvent (_id: Ref<Event>): Promise<Event | undefined> {
return await client.findOne(calendar.class.Event, { _id })
}
function click (event: Event): void {
showPanel(view.component.EditDoc, event._id, event._class, 'full')
}
</script>
<div class='flex'>
{#await getEvent(tx.objectId) then event}
{#if event}
<span class="over-underline caption-color" on:click={() => { click(event) }}>{event.title}</span>&nbsp
<DateTimePresenter value={new Date(event.date)} />
{/if}
{/await}
</div>

View File

@ -13,18 +13,39 @@
// limitations under the License.
//
import { Doc } from '@anticrm/core'
import { Resources } from '@anticrm/platform'
import PersonsPresenter from './components/PersonsPresenter.svelte'
import { showPopup } from '@anticrm/ui'
import CalendarView from './components/CalendarView.svelte'
import UpcomingEvents from './components/UpcomingEvents.svelte'
import SaveEventReminder from './components/SaveEventReminder.svelte'
import DateTimePresenter from './components/DateTimePresenter.svelte'
import DocReminder from './components/DocReminder.svelte'
import PersonsPresenter from './components/PersonsPresenter.svelte'
import UpcomingEvents from './components/UpcomingEvents.svelte'
import ReminderPresenter from './components/ReminderPresenter.svelte'
import ReminderViewlet from './components/activity/ReminderViewlet.svelte'
import EditEvent from './components/EditEvent.svelte'
import RemindersPopup from './components/RemindersPopup.svelte'
async function saveEventReminder (object: Doc): Promise<void> {
showPopup(SaveEventReminder, { objectId: object._id, objectClass: object._class })
}
export default async (): Promise<Resources> => ({
component: {
EditEvent,
ReminderPresenter,
PersonsPresenter,
CalendarView,
UpcomingEvents,
DateTimePresenter
DateTimePresenter,
DocReminder,
RemindersPopup
},
activity: {
ReminderViewlet
},
actionImpl: {
SaveEventReminder: saveEventReminder
}
})

View File

@ -15,12 +15,21 @@
import calendar, { calendarId } from '@anticrm/calendar'
import { IntlString, mergeIds } from '@anticrm/platform'
import { AnyComponent } from '@anticrm/ui'
export default mergeIds(calendarId, calendar, {
component: {
},
activity: {
ReminderViewlet: '' as AnyComponent
},
string: {
Events: '' as IntlString,
RemindMeAt: '' as IntlString,
CreateReminder: '' as IntlString,
EditReminder: '' as IntlString,
ReminderTime: '' as IntlString,
Reminders: '' as IntlString,
ModeDay: '' as IntlString,
ModeWeek: '' as IntlString,
ModeMonth: '' as IntlString,

View File

@ -28,6 +28,7 @@
"dependencies": {
"@anticrm/platform": "~0.6.5",
"@anticrm/ui": "~0.6.0",
"@anticrm/notification": "~0.6.0",
"@anticrm/core": "~0.6.11",
"@anticrm/contact": "~0.6.5"
}

View File

@ -12,10 +12,11 @@
// limitations under the License.
import { Employee } from '@anticrm/contact'
import type { AttachedDoc, Class, Doc, Markup, Ref, Space, Timestamp } from '@anticrm/core'
import type { AttachedDoc, Class, Doc, Markup, Mixin, Ref, Space, Timestamp } from '@anticrm/core'
import type { Asset, IntlString, Plugin } from '@anticrm/platform'
import { plugin } from '@anticrm/platform'
import { AnyComponent } from '@anticrm/ui'
import { NotificationType } from '@anticrm/notification'
/**
* @public
@ -27,7 +28,6 @@ export interface Calendar extends Space {}
*/
export interface Event extends AttachedDoc {
title: string
number: number
description: Markup
location?: string
@ -44,6 +44,14 @@ export interface Event extends AttachedDoc {
participants?: Ref<Employee>[]
}
/**
* @public
*/
export interface Reminder extends Event {
shift: Timestamp
state: 'active' | 'done'
}
/**
* @public
*/
@ -57,9 +65,13 @@ const calendarPlugin = plugin(calendarId, {
Calendar: '' as Ref<Class<Calendar>>,
Event: '' as Ref<Class<Event>>
},
mixin: {
Reminder: '' as Ref<Mixin<Reminder>>
},
icon: {
Calendar: '' as Asset,
Location: '' as Asset
Location: '' as Asset,
Reminder: '' as Asset
},
space: {
// Space for all personal events.
@ -71,7 +83,9 @@ const calendarPlugin = plugin(calendarId, {
component: {
PersonsPresenter: '' as AnyComponent,
UpcomingEvents: '' as AnyComponent,
DateTimePresenter: '' as AnyComponent
DateTimePresenter: '' as AnyComponent,
DocReminder: '' as AnyComponent,
RemindersPopup: '' as AnyComponent
},
string: {
Title: '' as IntlString,
@ -86,6 +100,9 @@ const calendarPlugin = plugin(calendarId, {
NoParticipants: '' as IntlString,
PersonsLabel: '' as IntlString,
EventNumber: '' as IntlString
},
ids: {
ReminderNotification: '' as Ref<NotificationType>
}
})

View File

@ -107,6 +107,7 @@
description,
verdict: '',
title,
participants: doc.participants,
company,
location
})

View File

@ -32,7 +32,8 @@
onMount(() => {
dispatch('open', {
ignoreKeys: ['number', 'comments', 'title', 'description', 'verdict']
ignoreKeys: ['number', 'comments', 'title', 'description', 'verdict'],
ignoreMixins: [calendar.mixin.Reminder]
})
})

View File

@ -73,6 +73,7 @@ export interface Applicant extends Task {
*/
export interface Review extends Event {
attachedTo: Ref<Candidate>
number: number
verdict: string

View File

@ -82,19 +82,18 @@
$: if (object && prevSelected !== object._class) {
prevSelected = object._class
selectedClass = Hierarchy.mixinOrClass(object)
parentClass = getParentClass(object._class)
mixins = getMixins()
getMixins()
}
const dispatch = createEventDispatcher()
function getMixins (): Mixin<Doc>[] {
const descendants = hierarchy.getDescendants(parentClass)
const mixins = descendants.filter(
(m) => hierarchy.getClass(m).kind === ClassifierKind.MIXIN && hierarchy.hasMixin(object, m)
function getMixins (): void {
const descendants = hierarchy.getDescendants(parentClass).map((p) => hierarchy.getClass(p))
mixins = descendants.filter(
(m) => m.kind === ClassifierKind.MIXIN && hierarchy.hasMixin(object, m._id) && !ignoreMixins.has(m._id)
)
return mixins.map((m) => hierarchy.getClass(m) as Mixin<Doc>)
}
function filterKeys (keys: KeyedAttribute[], ignoreKeys: string[]): KeyedAttribute[] {
@ -112,6 +111,7 @@
}
let ignoreKeys: string[] = []
let ignoreMixins: Set<Ref<Mixin<Doc>>> = new Set<Ref<Mixin<Doc>>>()
async function updateKeys (): Promise<void> {
const filtredKeys = getFiltredKeys(
@ -160,6 +160,7 @@
}
mainEditor = editor
updateKeys()
getMixins()
}
async function getCollectionEditor (key: KeyedAttribute): Promise<AnyComponent> {
@ -263,7 +264,9 @@
props={{ object }}
on:open={(ev) => {
ignoreKeys = ev.detail.ignoreKeys
ignoreMixins = new Set(ev.detail.ignoreMixins)
updateKeys()
getMixins()
}}
on:click={(ev) => {
fullSize = true

View File

@ -186,7 +186,7 @@ function filterActions (
continue
}
if (target.query !== undefined) {
const r = matchQuery([doc], target.query)
const r = matchQuery([doc], target.query, doc._class, hierarchy)
if (r.length === 0) {
continue
}

View File

@ -40,6 +40,7 @@
"@anticrm/presentation": "~0.6.2",
"@anticrm/login": "~0.6.1",
"@anticrm/setting": "~0.6.0",
"@anticrm/calendar": "~0.6.0",
"@anticrm/notification": "~0.6.0",
"@anticrm/notification-resources": "~0.6.0",
"@anticrm/contact": "~0.6.5",

View File

@ -33,6 +33,7 @@
} from '@anticrm/ui'
import type { Application, NavigatorModel, SpecialNavModel, ViewConfiguration } from '@anticrm/workbench'
import { onDestroy } from 'svelte'
import calendar from '@anticrm/calendar'
import workbench from '../plugin'
import AccountPopup from './AccountPopup.svelte'
import ActivityStatus from './ActivityStatus.svelte'
@ -252,6 +253,15 @@
}}
/>
<div class="flex-row" style="margin-bottom: 2rem;">
<AppItem
icon={calendar.icon.Reminder}
label={calendar.string.Reminders}
selected={false}
action={async () => {
showPopup(calendar.component.RemindersPopup, {}, 'account')
}}
notify={false}
/>
<AppItem
icon={notification.icon.Notifications}
label={notification.string.Notifications}