Skip to content

Commit ae09613

Browse files
authored
Allow Mixin Add/Edit/Delete operations (#2247)
Signed-off-by: Andrey Sobolev <[email protected]>
1 parent 00131ed commit ae09613

File tree

29 files changed

+546
-164
lines changed

29 files changed

+546
-164
lines changed

models/setting/src/index.ts

Lines changed: 62 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -13,15 +13,23 @@
1313
// limitations under the License.
1414
//
1515

16+
import activity from '@anticrm/activity'
17+
import { Domain, DOMAIN_MODEL, Ref } from '@anticrm/core'
1618
import { Builder, Mixin, Model } from '@anticrm/model'
17-
import { Ref, Domain, DOMAIN_MODEL } from '@anticrm/core'
1819
import core, { TClass, TDoc } from '@anticrm/model-core'
19-
import setting from './plugin'
20-
import { Editable, Integration, IntegrationType, Handler, SettingsCategory, settingId } from '@anticrm/setting'
20+
import view, { createAction } from '@anticrm/model-view'
2121
import type { Asset, IntlString } from '@anticrm/platform'
22+
import {
23+
Editable,
24+
Handler,
25+
Integration,
26+
IntegrationType,
27+
settingId,
28+
SettingsCategory,
29+
UserMixin
30+
} from '@anticrm/setting'
2231
import task from '@anticrm/task'
23-
import activity from '@anticrm/activity'
24-
import view from '@anticrm/view'
32+
import setting from './plugin'
2533

2634
import workbench from '@anticrm/model-workbench'
2735
import { AnyComponent } from '@anticrm/ui'
@@ -65,8 +73,18 @@ export class TIntegrationType extends TDoc implements IntegrationType {
6573
@Mixin(setting.mixin.Editable, core.class.Class)
6674
export class TEditable extends TClass implements Editable {}
6775

76+
@Mixin(setting.mixin.UserMixin, core.class.Class)
77+
export class TUserMixin extends TClass implements UserMixin {}
78+
6879
export function createModel (builder: Builder): void {
69-
builder.createModel(TIntegration, TIntegrationType, TSettingsCategory, TWorkspaceSettingCategory, TEditable)
80+
builder.createModel(
81+
TIntegration,
82+
TIntegrationType,
83+
TSettingsCategory,
84+
TWorkspaceSettingCategory,
85+
TEditable,
86+
TUserMixin
87+
)
7088

7189
builder.createDoc(
7290
setting.class.SettingsCategory,
@@ -276,6 +294,44 @@ export function createModel (builder: Builder): void {
276294
builder.mixin(core.class.EnumOf, core.class.Class, view.mixin.ObjectEditor, {
277295
editor: setting.component.EnumTypeEditor
278296
})
297+
298+
builder.mixin(core.class.Class, core.class.Class, view.mixin.IgnoreActions, {
299+
actions: [view.action.Delete]
300+
})
301+
302+
createAction(builder, {
303+
action: view.actionImpl.ShowPopup,
304+
actionProps: {
305+
component: setting.component.CreateMixin,
306+
fillProps: {
307+
_object: 'value'
308+
}
309+
},
310+
label: setting.string.CreateMixin,
311+
input: 'focus',
312+
icon: view.icon.Pin,
313+
category: setting.category.Settings,
314+
target: core.class.Class,
315+
context: {
316+
mode: ['context', 'browser'],
317+
group: 'edit'
318+
}
319+
})
320+
321+
createAction(
322+
builder,
323+
{
324+
action: setting.actionImpl.DeleteMixin,
325+
label: view.string.Delete,
326+
icon: view.icon.Delete,
327+
keyBinding: ['Meta + Backspace', 'Ctrl + Backspace'],
328+
category: view.category.General,
329+
input: 'any',
330+
target: setting.mixin.UserMixin,
331+
context: { mode: ['context', 'browser'], group: 'tools' }
332+
},
333+
setting.action.DeleteMixin
334+
)
279335
}
280336

281337
export { settingOperation } from './migration'

models/setting/src/plugin.ts

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,12 +13,13 @@
1313
// limitations under the License.
1414
//
1515

16+
import type { TxViewlet } from '@anticrm/activity'
1617
import { Doc, Ref } from '@anticrm/core'
1718
import { mergeIds } from '@anticrm/platform'
1819
import { settingId } from '@anticrm/setting'
1920
import setting from '@anticrm/setting-resources/src/plugin'
20-
import type { TxViewlet } from '@anticrm/activity'
2121
import { AnyComponent } from '@anticrm/ui'
22+
import { Action, ActionCategory, ViewAction } from '@anticrm/view'
2223

2324
export default mergeIds(settingId, setting, {
2425
activity: {
@@ -37,6 +38,16 @@ export default mergeIds(settingId, setting, {
3738
DateTypeEditor: '' as AnyComponent,
3839
RefEditor: '' as AnyComponent,
3940
EnumTypeEditor: '' as AnyComponent,
40-
Owners: '' as AnyComponent
41+
Owners: '' as AnyComponent,
42+
CreateMixin: '' as AnyComponent
43+
},
44+
category: {
45+
Settings: '' as Ref<ActionCategory>
46+
},
47+
action: {
48+
DeleteMixin: '' as Ref<Action>
49+
},
50+
actionImpl: {
51+
DeleteMixin: '' as ViewAction<Record<string, any>>
4152
}
4253
})

packages/core/src/hierarchy.ts

Lines changed: 36 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -144,8 +144,8 @@ export class Hierarchy {
144144
) {
145145
const _id = tx.objectId as Ref<Classifier>
146146
this.classifiers.set(_id, TxProcessor.createDoc2Doc(tx as TxCreateDoc<Classifier>))
147-
this.addAncestors(_id)
148-
this.addDescendant(_id)
147+
this.updateAncestors(_id)
148+
this.updateDescendant(_id)
149149
} else if (tx.objectClass === core.class.Attribute) {
150150
const createTx = tx as TxCreateDoc<AnyAttribute>
151151
this.addAttribute(TxProcessor.createDoc2Doc(createTx))
@@ -158,6 +158,11 @@ export class Hierarchy {
158158
const doc = this.attributesById.get(updateTx.objectId)
159159
if (doc === undefined) return
160160
this.addAttribute(TxProcessor.updateDoc2Doc(doc, updateTx))
161+
} else if (tx.objectClass === core.class.Mixin || tx.objectClass === core.class.Class) {
162+
const updateTx = tx as TxUpdateDoc<Mixin<Class<Doc>>>
163+
const doc = this.classifiers.get(updateTx.objectId)
164+
if (doc === undefined) return
165+
TxProcessor.updateDoc2Doc(doc, updateTx)
161166
}
162167
}
163168

@@ -169,6 +174,11 @@ export class Hierarchy {
169174
const map = this.attributes.get(doc.attributeOf)
170175
map?.delete(doc.name)
171176
this.attributesById.delete(removeTx.objectId)
177+
} else if (tx.objectClass === core.class.Mixin) {
178+
const removeTx = tx as TxRemoveDoc<Mixin<Class<Doc>>>
179+
this.updateDescendant(removeTx.objectId, false)
180+
this.updateAncestors(removeTx.objectId, false)
181+
this.classifiers.delete(removeTx.objectId)
172182
}
173183
}
174184

@@ -246,29 +256,47 @@ export class Hierarchy {
246256
return data
247257
}
248258

249-
private addDescendant (_class: Ref<Classifier>): void {
259+
private updateDescendant (_class: Ref<Classifier>, add = true): void {
250260
const hierarchy = this.getAncestors(_class)
251261
for (const cls of hierarchy) {
252262
const list = this.descendants.get(cls)
253263
if (list === undefined) {
254-
this.descendants.set(cls, [_class])
264+
if (add) {
265+
this.descendants.set(cls, [_class])
266+
}
255267
} else {
256-
list.push(_class)
268+
if (add) {
269+
list.push(_class)
270+
} else {
271+
const pos = list.indexOf(_class)
272+
if (pos !== -1) {
273+
list.splice(pos, 1)
274+
}
275+
}
257276
}
258277
}
259278
}
260279

261-
private addAncestors (_class: Ref<Classifier>): void {
280+
private updateAncestors (_class: Ref<Classifier>, add = true): void {
262281
const cl: Ref<Classifier>[] = [_class]
263282
const visited = new Set<Ref<Classifier>>()
264283
while (cl.length > 0) {
265284
const classifier = cl.shift() as Ref<Classifier>
266285
if (addNew(visited, classifier)) {
267286
const list = this.ancestors.get(_class)
268287
if (list === undefined) {
269-
this.ancestors.set(_class, [classifier])
288+
if (add) {
289+
this.ancestors.set(_class, [classifier])
290+
}
270291
} else {
271-
addIf(list, classifier)
292+
if (add) {
293+
addIf(list, classifier)
294+
} else {
295+
const pos = list.indexOf(classifier)
296+
if (pos !== -1) {
297+
list.splice(pos, 1)
298+
}
299+
}
272300
}
273301
cl.push(...this.ancestorsOf(classifier))
274302
}

packages/panel/src/components/Panel.svelte

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -84,8 +84,10 @@
8484
<div style="padding: .75rem 1.5rem">
8585
{#if $$slots.actions}
8686
<div class="flex-row-center pb-3 bottom-divider">
87-
<span class="fs-bold w-24 mr-6"><slot name="actions-label" /></span>
88-
<div class="buttons-group xsmall-gap">
87+
{#if $$slots['actions-label']}
88+
<span class="fs-bold w-24 mr-6"><slot name="actions-label" /></span>
89+
{/if}
90+
<div class="buttons-group xsmall-gap flex flex-grow">
8991
<slot name="actions" />
9092
</div>
9193
</div>

packages/ui/src/popups.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -325,3 +325,8 @@ export function getPopupPositionElement (
325325

326326
return undefined
327327
}
328+
export function getEventPositionElement (evt: MouseEvent): PopupAlignment | undefined {
329+
return {
330+
getBoundingClientRect: () => DOMRect.fromRect({ width: 1, height: 1, x: evt.clientX, y: evt.clientY })
331+
}
332+
}

plugins/board-resources/src/components/KanbanView.svelte

Lines changed: 3 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@
2020
import { createQuery, getClient } from '@anticrm/presentation'
2121
import type { Kanban, SpaceWithStates, State } from '@anticrm/task'
2222
import task, { calcRank } from '@anticrm/task'
23-
import { showPopup } from '@anticrm/ui'
23+
import { getEventPositionElement, showPopup } from '@anticrm/ui'
2424
import {
2525
ActionContext,
2626
ContextMenu,
@@ -31,8 +31,8 @@
3131
} from '@anticrm/view-resources'
3232
import { onMount } from 'svelte'
3333
import AddCard from './add-card/AddCard.svelte'
34-
import KanbanCard from './KanbanCard.svelte'
3534
import AddPanel from './AddPanel.svelte'
35+
import KanbanCard from './KanbanCard.svelte'
3636
import ListHeader from './ListHeader.svelte'
3737
3838
export let _class: Ref<Class<Card>>
@@ -93,13 +93,7 @@
9393
return
9494
}
9595
96-
showPopup(
97-
ContextMenu,
98-
{ object },
99-
{
100-
getBoundingClientRect: () => DOMRect.fromRect({ width: 1, height: 1, x: ev.clientX, y: ev.clientY })
101-
}
102-
)
96+
showPopup(ContextMenu, { object }, getEventPositionElement(ev))
10397
}
10498
</script>
10599

plugins/board-resources/src/components/ListHeader.svelte

Lines changed: 3 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,13 @@
11
<script lang="ts">
2-
import { Button, Component, getPlatformColor, IconMoreV, showPopup } from '@anticrm/ui'
3-
import { State } from '@anticrm/task'
42
import notification from '@anticrm/notification'
3+
import { State } from '@anticrm/task'
4+
import { Button, Component, getEventPositionElement, getPlatformColor, IconMoreV, showPopup } from '@anticrm/ui'
55
import { ContextMenu } from '@anticrm/view-resources'
66
export let state: State
77
88
const showMenu = async (ev: MouseEvent): Promise<void> => {
99
ev.preventDefault()
10-
showPopup(
11-
ContextMenu,
12-
{ object: state },
13-
{
14-
getBoundingClientRect: () => DOMRect.fromRect({ width: 1, height: 1, x: ev.clientX, y: ev.clientY })
15-
}
16-
)
10+
showPopup(ContextMenu, { object: state }, getEventPositionElement(ev))
1711
}
1812
</script>
1913

plugins/client-resources/src/connection.ts

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -68,11 +68,23 @@ class Connection implements ClientConnection {
6868
this.websocket?.close()
6969
}
7070

71+
delay = 1
7172
private async waitOpenConnection (): Promise<ClientSocket> {
7273
while (true) {
7374
try {
74-
return await this.openConnection()
75+
const conn = await this.openConnection()
76+
this.delay = 1
77+
return conn
7578
} catch (err: any) {
79+
await new Promise((resolve) => {
80+
setTimeout(() => {
81+
console.log(`delay ${this.delay} second`)
82+
resolve(null)
83+
if (this.delay !== 5) {
84+
this.delay++
85+
}
86+
}, this.delay * 1000)
87+
})
7688
console.log('failed to connect', err)
7789
if (err.code === UNAUTHORIZED.code) {
7890
this.onUnauthorized?.()

plugins/hr-resources/src/components/DepartmentCard.svelte

Lines changed: 11 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,16 @@
1818
import { Ref, WithLookup } from '@anticrm/core'
1919
import { Department, Staff } from '@anticrm/hr'
2020
import { Avatar, getClient, UsersPopup } from '@anticrm/presentation'
21-
import { Button, closeTooltip, eventToHTMLElement, IconAdd, Label, showPanel, showPopup } from '@anticrm/ui'
21+
import {
22+
Button,
23+
closeTooltip,
24+
eventToHTMLElement,
25+
getEventPositionElement,
26+
IconAdd,
27+
Label,
28+
showPanel,
29+
showPopup
30+
} from '@anticrm/ui'
2231
import view from '@anticrm/view'
2332
import { Menu } from '@anticrm/view-resources'
2433
import hr from '../plugin'
@@ -70,13 +79,7 @@
7079
}
7180
7281
function showMenu (e: MouseEvent) {
73-
showPopup(
74-
Menu,
75-
{ object: value, baseMenuClass: value._class },
76-
{
77-
getBoundingClientRect: () => DOMRect.fromRect({ width: 1, height: 1, x: e.clientX, y: e.clientY })
78-
}
79-
)
82+
showPopup(Menu, { object: value, baseMenuClass: value._class }, getEventPositionElement(e))
8083
}
8184
8285
function edit (e: MouseEvent): void {

plugins/hr-resources/src/components/PersonsPresenter.svelte

Lines changed: 2 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@
1818
import { EmployeePresenter } from '@anticrm/contact-resources'
1919
import { WithLookup } from '@anticrm/core'
2020
import { Staff } from '@anticrm/hr'
21-
import { closeTooltip, showPopup } from '@anticrm/ui'
21+
import { closeTooltip, getEventPositionElement, showPopup } from '@anticrm/ui'
2222
import { ContextMenu } from '@anticrm/view-resources'
2323
import hr from '../plugin'
2424
@@ -36,11 +36,7 @@
3636
}
3737
3838
function showContextMenu (ev: MouseEvent, object: Employee) {
39-
showPopup(
40-
ContextMenu,
41-
{ object },
42-
{ getBoundingClientRect: () => DOMRect.fromRect({ width: 1, height: 1, x: ev.clientX, y: ev.clientY }) }
43-
)
39+
showPopup(ContextMenu, { object }, getEventPositionElement(ev))
4440
}
4541
</script>
4642

0 commit comments

Comments
 (0)