mirror of
https://github.com/hcengineering/platform.git
synced 2025-05-10 09:22:23 +00:00
Fix nested tags (#8474)
Some checks are pending
CI / build (push) Waiting to run
CI / svelte-check (push) Blocked by required conditions
CI / formatting (push) Blocked by required conditions
CI / test (push) Blocked by required conditions
CI / uitest (push) Waiting to run
CI / uitest-pg (push) Waiting to run
CI / uitest-qms (push) Waiting to run
CI / uitest-workspaces (push) Waiting to run
CI / docker-build (push) Blocked by required conditions
CI / dist-build (push) Blocked by required conditions
Some checks are pending
CI / build (push) Waiting to run
CI / svelte-check (push) Blocked by required conditions
CI / formatting (push) Blocked by required conditions
CI / test (push) Blocked by required conditions
CI / uitest (push) Waiting to run
CI / uitest-pg (push) Waiting to run
CI / uitest-qms (push) Waiting to run
CI / uitest-workspaces (push) Waiting to run
CI / docker-build (push) Blocked by required conditions
CI / dist-build (push) Blocked by required conditions
Signed-off-by: Denis Bykhov <bykhov.denis@gmail.com>
This commit is contained in:
parent
3c58f2b7f9
commit
099fb90c59
@ -94,6 +94,15 @@ export function createModel (builder: Builder): void {
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
builder.createDoc(serverCore.class.Trigger, core.space.Model, {
|
||||||
|
trigger: serverCard.trigger.OnCardTag,
|
||||||
|
isAsync: true,
|
||||||
|
txMatch: {
|
||||||
|
_class: core.class.TxMixin,
|
||||||
|
objectClass: card.class.Card
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
builder.mixin(card.class.Card, core.class.Class, serverCore.mixin.SearchPresenter, {
|
builder.mixin(card.class.Card, core.class.Class, serverCore.mixin.SearchPresenter, {
|
||||||
searchIcon: card.icon.Card,
|
searchIcon: card.icon.Card,
|
||||||
title: [['title']]
|
title: [['title']]
|
||||||
|
@ -252,7 +252,7 @@ export class TxOperations implements Omit<Client, 'notify' | 'getConnection'> {
|
|||||||
if (hierarchy.isMixin(mixClass)) {
|
if (hierarchy.isMixin(mixClass)) {
|
||||||
const baseClass = hierarchy.getBaseClass(doc._class)
|
const baseClass = hierarchy.getBaseClass(doc._class)
|
||||||
|
|
||||||
const byClass = this.splitMixinUpdate(update, mixClass, baseClass)
|
const byClass = splitMixinUpdate(hierarchy, update, mixClass, baseClass)
|
||||||
const ops = this.apply(doc._id)
|
const ops = this.apply(doc._id)
|
||||||
for (const it of byClass) {
|
for (const it of byClass) {
|
||||||
if (hierarchy.isMixin(it[0])) {
|
if (hierarchy.isMixin(it[0])) {
|
||||||
@ -323,17 +323,7 @@ export class TxOperations implements Omit<Client, 'notify' | 'getConnection'> {
|
|||||||
date?: Timestamp,
|
date?: Timestamp,
|
||||||
account?: PersonId
|
account?: PersonId
|
||||||
): Promise<T> {
|
): Promise<T> {
|
||||||
// We need to update fields if they are different.
|
const documentUpdate = getDiffUpdate(doc, update)
|
||||||
const documentUpdate: DocumentUpdate<T> = {}
|
|
||||||
for (const [k, v] of Object.entries(update)) {
|
|
||||||
if (['_class', '_id', 'modifiedBy', 'modifiedOn', 'space', 'attachedTo', 'attachedToClass'].includes(k)) {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
const dv = (doc as any)[k]
|
|
||||||
if (!deepEqual(dv, v) && v !== undefined) {
|
|
||||||
;(documentUpdate as any)[k] = v
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (Object.keys(documentUpdate).length > 0) {
|
if (Object.keys(documentUpdate).length > 0) {
|
||||||
await this.update(doc, documentUpdate, false, date ?? Date.now(), account)
|
await this.update(doc, documentUpdate, false, date ?? Date.now(), account)
|
||||||
TxProcessor.applyUpdate(doc, documentUpdate)
|
TxProcessor.applyUpdate(doc, documentUpdate)
|
||||||
@ -372,56 +362,71 @@ export class TxOperations implements Omit<Client, 'notify' | 'getConnection'> {
|
|||||||
}
|
}
|
||||||
return doc
|
return doc
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private splitMixinUpdate<T extends Doc>(
|
export function getDiffUpdate<T extends Doc> (doc: T, update: T | Data<T> | DocumentUpdate<T>): DocumentUpdate<T> {
|
||||||
update: DocumentUpdate<T>,
|
// We need to update fields if they are different.
|
||||||
mixClass: Ref<Class<T>>,
|
const documentUpdate: DocumentUpdate<T> = {}
|
||||||
baseClass: Ref<Class<T>>
|
for (const [k, v] of Object.entries(update)) {
|
||||||
): Map<Ref<Class<Doc>>, DocumentUpdate<T>> {
|
if (['_class', '_id', 'modifiedBy', 'modifiedOn', 'space', 'attachedTo', 'attachedToClass'].includes(k)) {
|
||||||
const hierarchy = this.getHierarchy()
|
continue
|
||||||
const attributes = hierarchy.getAllAttributes(mixClass)
|
|
||||||
|
|
||||||
const updateAttrs = Object.fromEntries(
|
|
||||||
Object.entries(update).filter((it) => !it[0].startsWith('$'))
|
|
||||||
) as DocumentUpdate<T>
|
|
||||||
const updateOps = Object.fromEntries(
|
|
||||||
Object.entries(update).filter((it) => it[0].startsWith('$'))
|
|
||||||
) as DocumentUpdate<T>
|
|
||||||
|
|
||||||
const result: Map<Ref<Class<Doc>>, DocumentUpdate<T>> = this.splitObjectAttributes(
|
|
||||||
updateAttrs,
|
|
||||||
baseClass,
|
|
||||||
attributes
|
|
||||||
)
|
|
||||||
|
|
||||||
for (const [key, value] of Object.entries(updateOps)) {
|
|
||||||
const updates = this.splitObjectAttributes(value as object, baseClass, attributes)
|
|
||||||
|
|
||||||
for (const [opsClass, opsUpdate] of updates) {
|
|
||||||
const upd: DocumentUpdate<T> = result.get(opsClass) ?? {}
|
|
||||||
result.set(opsClass, { ...upd, [key]: opsUpdate })
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
const dv = (doc as any)[k]
|
||||||
|
if (!deepEqual(dv, v) && v !== undefined) {
|
||||||
|
;(documentUpdate as any)[k] = v
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return documentUpdate
|
||||||
|
}
|
||||||
|
|
||||||
return result
|
export function splitMixinUpdate<T extends Doc> (
|
||||||
|
hierarchy: Hierarchy,
|
||||||
|
update: DocumentUpdate<T>,
|
||||||
|
mixClass: Ref<Class<T>>,
|
||||||
|
baseClass: Ref<Class<T>>
|
||||||
|
): Map<Ref<Class<Doc>>, DocumentUpdate<T>> {
|
||||||
|
const attributes = hierarchy.getAllAttributes(mixClass)
|
||||||
|
|
||||||
|
const updateAttrs = Object.fromEntries(
|
||||||
|
Object.entries(update).filter((it) => !it[0].startsWith('$'))
|
||||||
|
) as DocumentUpdate<T>
|
||||||
|
const updateOps = Object.fromEntries(
|
||||||
|
Object.entries(update).filter((it) => it[0].startsWith('$'))
|
||||||
|
) as DocumentUpdate<T>
|
||||||
|
|
||||||
|
const result: Map<Ref<Class<Doc>>, DocumentUpdate<T>> = splitObjectAttributes(
|
||||||
|
hierarchy,
|
||||||
|
updateAttrs,
|
||||||
|
baseClass,
|
||||||
|
attributes
|
||||||
|
)
|
||||||
|
|
||||||
|
for (const [key, value] of Object.entries(updateOps)) {
|
||||||
|
const updates = splitObjectAttributes(hierarchy, value as object, baseClass, attributes)
|
||||||
|
|
||||||
|
for (const [opsClass, opsUpdate] of updates) {
|
||||||
|
const upd: DocumentUpdate<T> = result.get(opsClass) ?? {}
|
||||||
|
result.set(opsClass, { ...upd, [key]: opsUpdate })
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private splitObjectAttributes<T extends object>(
|
return result
|
||||||
obj: T,
|
}
|
||||||
objClass: Ref<Class<Doc>>,
|
|
||||||
attributes: Map<string, AnyAttribute>
|
|
||||||
): Map<Ref<Class<Doc>>, object> {
|
|
||||||
const hierarchy = this.getHierarchy()
|
|
||||||
|
|
||||||
const result = new Map<Ref<Class<Doc>>, any>()
|
function splitObjectAttributes<T extends object> (
|
||||||
for (const [key, value] of Object.entries(obj)) {
|
hierarchy: Hierarchy,
|
||||||
const attributeOf = attributes.get(key)?.attributeOf
|
obj: T,
|
||||||
const clazz = attributeOf !== undefined && hierarchy.isMixin(attributeOf) ? attributeOf : objClass
|
objClass: Ref<Class<Doc>>,
|
||||||
result.set(clazz, { ...(result.get(clazz) ?? {}), [key]: value })
|
attributes: Map<string, AnyAttribute>
|
||||||
}
|
): Map<Ref<Class<Doc>>, object> {
|
||||||
|
const result = new Map<Ref<Class<Doc>>, any>()
|
||||||
return result
|
for (const [key, value] of Object.entries(obj)) {
|
||||||
|
const attributeOf = attributes.get(key)?.attributeOf
|
||||||
|
const clazz = attributeOf !== undefined && hierarchy.isMixin(attributeOf) ? attributeOf : objClass
|
||||||
|
result.set(clazz, { ...(result.get(clazz) ?? {}), [key]: value })
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return result
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface CommitResult {
|
export interface CommitResult {
|
||||||
|
@ -16,7 +16,7 @@
|
|||||||
-->
|
-->
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import card, { Card, Tag } from '@hcengineering/card'
|
import card, { Card, Tag } from '@hcengineering/card'
|
||||||
import { Class, Doc, fillDefaults, Ref } from '@hcengineering/core'
|
import { Class, Doc, Mixin, Ref } from '@hcengineering/core'
|
||||||
import { createQuery, getClient } from '@hcengineering/presentation'
|
import { createQuery, getClient } from '@hcengineering/presentation'
|
||||||
import {
|
import {
|
||||||
ButtonIcon,
|
ButtonIcon,
|
||||||
@ -77,14 +77,17 @@
|
|||||||
async (res) => {
|
async (res) => {
|
||||||
if (res !== undefined) {
|
if (res !== undefined) {
|
||||||
await client.createMixin(doc._id, doc._class, doc.space, res, {})
|
await client.createMixin(doc._id, doc._class, doc.space, res, {})
|
||||||
const updated = fillDefaults(hierarchy, hierarchy.clone(doc), res)
|
|
||||||
await client.diffUpdate(doc, updated)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
let divScroll: HTMLElement
|
let divScroll: HTMLElement
|
||||||
|
|
||||||
|
function isRemoveable (mixinId: Ref<Mixin<Doc>>, activeTags: Tag[]): boolean {
|
||||||
|
const desc = hierarchy.getDescendants(mixinId)
|
||||||
|
return !desc.some((p) => hierarchy.hasMixin(doc, p) && p !== mixinId)
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div class="container py-4 gap-2">
|
<div class="container py-4 gap-2">
|
||||||
@ -94,9 +97,12 @@
|
|||||||
<ScrollerBar gap={'none'} bind:scroller={divScroll}>
|
<ScrollerBar gap={'none'} bind:scroller={divScroll}>
|
||||||
<div class="tags gap-2">
|
<div class="tags gap-2">
|
||||||
{#each activeTags as mixin}
|
{#each activeTags as mixin}
|
||||||
<div class="tag no-word-wrap">
|
{@const removable = isRemoveable(mixin._id, activeTags)}
|
||||||
|
<div class="tag no-word-wrap" class:removable>
|
||||||
<Label label={mixin.label} />
|
<Label label={mixin.label} />
|
||||||
<ButtonIcon icon={IconClose} size="extra-small" kind="tertiary" on:click={() => removeTag(mixin._id)} />
|
{#if removable}
|
||||||
|
<ButtonIcon icon={IconClose} size="extra-small" kind="tertiary" on:click={() => removeTag(mixin._id)} />
|
||||||
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
{/each}
|
{/each}
|
||||||
{#if dropdownItems.length > 0}
|
{#if dropdownItems.length > 0}
|
||||||
@ -114,7 +120,7 @@
|
|||||||
align-items: center;
|
align-items: center;
|
||||||
|
|
||||||
.tag {
|
.tag {
|
||||||
padding: 0.25rem 0.25rem 0.25rem 0.5rem;
|
padding: 0.25rem 0.5rem;
|
||||||
height: 1.5rem;
|
height: 1.5rem;
|
||||||
border: 1px solid var(--theme-content-color);
|
border: 1px solid var(--theme-content-color);
|
||||||
|
|
||||||
@ -126,6 +132,10 @@
|
|||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
gap: 0.25rem;
|
gap: 0.25rem;
|
||||||
|
|
||||||
|
&.removable {
|
||||||
|
padding-right: 0.25rem;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -18,9 +18,14 @@ import core, {
|
|||||||
AnyAttribute,
|
AnyAttribute,
|
||||||
Data,
|
Data,
|
||||||
Doc,
|
Doc,
|
||||||
|
fillDefaults,
|
||||||
|
getDiffUpdate,
|
||||||
|
Mixin,
|
||||||
Ref,
|
Ref,
|
||||||
|
splitMixinUpdate,
|
||||||
Tx,
|
Tx,
|
||||||
TxCreateDoc,
|
TxCreateDoc,
|
||||||
|
TxMixin,
|
||||||
TxProcessor,
|
TxProcessor,
|
||||||
TxRemoveDoc,
|
TxRemoveDoc,
|
||||||
TxUpdateDoc
|
TxUpdateDoc
|
||||||
@ -391,6 +396,40 @@ async function OnCardCreate (ctx: TxCreateDoc<Card>[], control: TriggerControl):
|
|||||||
return res
|
return res
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export async function OnCardTag (ctx: TxMixin<Card, Card>[], control: TriggerControl): Promise<Tx[]> {
|
||||||
|
const res: Tx[] = []
|
||||||
|
for (const tx of ctx) {
|
||||||
|
if (tx.space === core.space.DerivedTx) continue
|
||||||
|
if (tx._class !== core.class.TxMixin) continue
|
||||||
|
const target = tx.mixin
|
||||||
|
const to = control.hierarchy.getBaseClass(target)
|
||||||
|
const ancestors = control.hierarchy.getAncestors(target).filter((p) => control.hierarchy.isDerived(p, to))
|
||||||
|
const mixinAncestors: Ref<Mixin<Doc>>[] = []
|
||||||
|
const doc = (await control.findAll(control.ctx, tx.objectClass, { _id: tx.objectId }))[0]
|
||||||
|
if (doc === undefined) continue
|
||||||
|
for (const anc of ancestors) {
|
||||||
|
if (anc === target) continue
|
||||||
|
if (control.hierarchy.hasMixin(doc, anc)) break
|
||||||
|
if (anc === to) break
|
||||||
|
mixinAncestors.unshift(anc)
|
||||||
|
}
|
||||||
|
for (const anc of mixinAncestors) {
|
||||||
|
res.push(control.txFactory.createTxMixin(doc._id, doc._class, doc.space, anc, {}))
|
||||||
|
}
|
||||||
|
const updated = fillDefaults(control.hierarchy, control.hierarchy.as(control.hierarchy.clone(doc), target), target)
|
||||||
|
const diff = getDiffUpdate(doc, updated)
|
||||||
|
const splitted = splitMixinUpdate(control.hierarchy, diff, target, doc._class)
|
||||||
|
for (const it of splitted) {
|
||||||
|
if (control.hierarchy.isMixin(it[0])) {
|
||||||
|
res.push(control.txFactory.createTxMixin(doc._id, doc._class, doc.space, it[0], it[1]))
|
||||||
|
} else {
|
||||||
|
res.push(control.txFactory.createTxUpdateDoc(it[0], doc.space, doc._id, it[1]))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return res
|
||||||
|
}
|
||||||
|
|
||||||
// eslint-disable-next-line @typescript-eslint/explicit-function-return-type
|
// eslint-disable-next-line @typescript-eslint/explicit-function-return-type
|
||||||
export default async () => ({
|
export default async () => ({
|
||||||
trigger: {
|
trigger: {
|
||||||
@ -401,6 +440,7 @@ export default async () => ({
|
|||||||
OnTagRemove,
|
OnTagRemove,
|
||||||
OnCardRemove,
|
OnCardRemove,
|
||||||
OnCardCreate,
|
OnCardCreate,
|
||||||
OnCardUpdate
|
OnCardUpdate,
|
||||||
|
OnCardTag
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
@ -34,6 +34,7 @@ export default plugin(serverCardId, {
|
|||||||
OnMasterTagRemove: '' as Resource<TriggerFunc>,
|
OnMasterTagRemove: '' as Resource<TriggerFunc>,
|
||||||
OnCardCreate: '' as Resource<TriggerFunc>,
|
OnCardCreate: '' as Resource<TriggerFunc>,
|
||||||
OnCardUpdate: '' as Resource<TriggerFunc>,
|
OnCardUpdate: '' as Resource<TriggerFunc>,
|
||||||
|
OnCardTag: '' as Resource<TriggerFunc>,
|
||||||
OnCardRemove: '' as Resource<TriggerFunc>
|
OnCardRemove: '' as Resource<TriggerFunc>
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
Loading…
Reference in New Issue
Block a user