mirror of
https://github.com/hcengineering/platform.git
synced 2025-04-14 12:25:17 +00:00
Activity update (#362)
Signed-off-by: Andrey Sobolev <haiodo@gmail.com>
This commit is contained in:
parent
581f5f0e96
commit
6c281b38f8
11
.vscode/launch.json
vendored
11
.vscode/launch.json
vendored
@ -17,6 +17,17 @@
|
||||
"sourceMaps": true,
|
||||
"cwd": "${workspaceRoot}/server/server",
|
||||
"protocol": "inspector"
|
||||
},
|
||||
{
|
||||
"type": "node",
|
||||
"request": "launch",
|
||||
"name": "Debug Jest tests",
|
||||
"program": "${fileDirname}/../../node_modules/@rushstack/heft/lib/start.js",
|
||||
"cwd": "${fileDirname}/../../",
|
||||
"args": ["--debug", "test", "--clean", "--test-path-pattern", "${file}"],
|
||||
"console": "integratedTerminal",
|
||||
"sourceMaps": true,
|
||||
"protocol": "inspector"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
@ -14,17 +14,15 @@
|
||||
//
|
||||
|
||||
import { PlatformError, Severity, Status } from '@anticrm/platform'
|
||||
import clone from 'just-clone'
|
||||
import type { Class, Doc, Ref } from './classes'
|
||||
import type { Tx, TxCreateDoc, TxMixin, TxPutBag, TxRemoveDoc, TxUpdateDoc } from './tx'
|
||||
import core from './component'
|
||||
import type { Hierarchy } from './hierarchy'
|
||||
import { _getOperator } from './operator'
|
||||
import { findProperty, resultSort } from './query'
|
||||
import type { DocumentQuery, FindOptions, FindResult, Storage, WithLookup, LookupData, Refs, TxResult } from './storage'
|
||||
import type { DocumentQuery, FindOptions, FindResult, LookupData, Refs, Storage, TxResult, WithLookup } from './storage'
|
||||
import type { Tx, TxCreateDoc, TxMixin, TxPutBag, TxRemoveDoc, TxUpdateDoc } from './tx'
|
||||
import { TxProcessor } from './tx'
|
||||
|
||||
import clone from 'just-clone'
|
||||
|
||||
/**
|
||||
* @public
|
||||
*/
|
||||
@ -200,17 +198,7 @@ export class ModelDb extends MemDb implements Storage {
|
||||
|
||||
protected async txUpdateDoc (tx: TxUpdateDoc<Doc>): Promise<TxResult> {
|
||||
const doc = this.getObject(tx.objectId) as any
|
||||
const ops = tx.operations as any
|
||||
for (const key in ops) {
|
||||
if (key.startsWith('$')) {
|
||||
const operator = _getOperator(key)
|
||||
operator(doc, ops[key])
|
||||
} else {
|
||||
doc[key] = ops[key]
|
||||
}
|
||||
}
|
||||
doc.modifiedBy = tx.modifiedBy
|
||||
doc.modifiedOn = tx.modifiedOn
|
||||
TxProcessor.updateDoc2Doc(doc, tx)
|
||||
return tx.retrieve === true ? { object: doc } : {}
|
||||
}
|
||||
|
||||
|
@ -18,6 +18,7 @@ import type { Class, Data, Doc, Domain, Ref, Account, Space, Arr, Mixin, Propert
|
||||
import type { DocumentQuery, FindOptions, FindResult, Storage, WithLookup, TxResult } from './storage'
|
||||
import core from './component'
|
||||
import { generateId } from './utils'
|
||||
import { _getOperator } from '.'
|
||||
|
||||
/**
|
||||
* @public
|
||||
@ -223,6 +224,21 @@ export abstract class TxProcessor implements WithTx {
|
||||
} as T
|
||||
}
|
||||
|
||||
static updateDoc2Doc<T extends Doc>(doc: T, tx: TxUpdateDoc<T>): T {
|
||||
const ops = tx.operations as any
|
||||
for (const key in ops) {
|
||||
if (key.startsWith('$')) {
|
||||
const operator = _getOperator(key)
|
||||
operator(doc, ops[key])
|
||||
} else {
|
||||
(doc as any)[key] = ops[key]
|
||||
}
|
||||
}
|
||||
doc.modifiedBy = tx.modifiedBy
|
||||
doc.modifiedOn = tx.modifiedOn
|
||||
return doc
|
||||
}
|
||||
|
||||
protected abstract txCreateDoc (tx: TxCreateDoc<Doc>): Promise<TxResult>
|
||||
protected abstract txPutBag (tx: TxPutBag<PropertyType>): Promise<TxResult>
|
||||
protected abstract txUpdateDoc (tx: TxUpdateDoc<Doc>): Promise<TxResult>
|
||||
@ -328,6 +344,25 @@ export class TxOperations implements Storage {
|
||||
return tx.objectId
|
||||
}
|
||||
|
||||
async removeCollection<T extends Doc, P extends AttachedDoc>(
|
||||
_class: Ref<Class<P>>,
|
||||
space: Ref<Space>,
|
||||
objectId: Ref<P>,
|
||||
attachedTo: Ref<T>,
|
||||
attachedToClass: Ref<Class<T>>,
|
||||
collection: string
|
||||
): Promise<Ref<T>> {
|
||||
const tx = this.txFactory.createTxCollectionCUD(
|
||||
attachedToClass,
|
||||
attachedTo,
|
||||
space,
|
||||
collection,
|
||||
this.txFactory.createTxRemoveDoc(_class, space, objectId)
|
||||
)
|
||||
await this.storage.tx(tx)
|
||||
return tx.objectId
|
||||
}
|
||||
|
||||
putBag <P extends PropertyType>(
|
||||
_class: Ref<Class<Doc>>,
|
||||
space: Ref<Space>,
|
||||
|
@ -58,7 +58,7 @@ export function setClient(_client: Client) {
|
||||
}
|
||||
}
|
||||
|
||||
class LiveQuery {
|
||||
export class LiveQuery {
|
||||
private unsubscribe = () => {}
|
||||
|
||||
constructor() {
|
||||
|
@ -104,7 +104,7 @@
|
||||
}
|
||||
|
||||
const whileShow = (ev: MouseEvent): void => {
|
||||
if ($tooltip.element) {
|
||||
if ($tooltip.element && tooltipHTML) {
|
||||
const rectP = tooltipHTML.getBoundingClientRect()
|
||||
const rectT = {
|
||||
top: (dir === 'top') ? rect.top - 16 : rect.top,
|
||||
|
253
plugins/activity-resources/src/activity.ts
Normal file
253
plugins/activity-resources/src/activity.ts
Normal file
@ -0,0 +1,253 @@
|
||||
import core, {
|
||||
AttachedDoc,
|
||||
Class,
|
||||
Client,
|
||||
Doc,
|
||||
DocumentUpdate,
|
||||
Hierarchy,
|
||||
Ref,
|
||||
SortingOrder,
|
||||
Tx,
|
||||
TxCollectionCUD,
|
||||
TxCreateDoc,
|
||||
TxCUD,
|
||||
TxProcessor,
|
||||
TxUpdateDoc
|
||||
} from '@anticrm/core'
|
||||
import { createQuery, LiveQuery } from '@anticrm/presentation'
|
||||
|
||||
/**
|
||||
* @public
|
||||
*/
|
||||
export type ActivityKey = string
|
||||
|
||||
/**
|
||||
* @public
|
||||
*/
|
||||
export function activityKey (objectClass: Ref<Class<Doc>>, txClass: Ref<Class<Tx>>): ActivityKey {
|
||||
return objectClass + ':' + txClass
|
||||
}
|
||||
|
||||
function isEqualOps (op1: DocumentUpdate<Doc>, op2: DocumentUpdate<Doc>): boolean {
|
||||
const o1 = Object.keys(op1).sort().join('-')
|
||||
const o2 = Object.keys(op2).sort().join('-')
|
||||
return o1 === o2
|
||||
}
|
||||
|
||||
/**
|
||||
* Transaction being displayed.
|
||||
* @public
|
||||
*/
|
||||
export interface DisplayTx {
|
||||
// Source tx
|
||||
tx: TxCUD<Doc>
|
||||
|
||||
// A set of collapsed transactions.
|
||||
txes: Array<TxCUD<Doc>>
|
||||
|
||||
// type check for createTx
|
||||
createTx?: TxCreateDoc<Doc>
|
||||
|
||||
// Type check for updateTx
|
||||
updateTx?: TxUpdateDoc<Doc>
|
||||
|
||||
// Document in case it is required.
|
||||
doc?: Doc
|
||||
|
||||
removed: boolean
|
||||
}
|
||||
|
||||
/**
|
||||
* @public
|
||||
*/
|
||||
export type DisplayTxListener = (txes: DisplayTx[]) => void
|
||||
|
||||
// Use 5 minutes to combine similar transactions.
|
||||
const combineThreshold = 5 * 60 * 1000
|
||||
/**
|
||||
* Define activity.
|
||||
*
|
||||
* Allow to recieve a list of transactions and notify client about it.
|
||||
*/
|
||||
export interface Activity {
|
||||
update: (object: Doc, listener: DisplayTxListener, sort: SortingOrder) => void
|
||||
}
|
||||
|
||||
class ActivityImpl implements Activity {
|
||||
private readonly txQuery1: LiveQuery
|
||||
private readonly txQuery2: LiveQuery
|
||||
|
||||
private txes1: Array<TxCUD<Doc>> = []
|
||||
private txes2: Array<TxCUD<Doc>> = []
|
||||
constructor (readonly client: Client) {
|
||||
this.txQuery1 = createQuery()
|
||||
this.txQuery2 = createQuery()
|
||||
}
|
||||
|
||||
private notify (object: Doc, listener: DisplayTxListener, sort: SortingOrder): void {
|
||||
this.combineTransactions(object, this.txes1, this.txes2).then(
|
||||
(result) => {
|
||||
const sorted = result.sort((a, b) => (a.tx.modifiedOn - b.tx.modifiedOn) * sort)
|
||||
listener(sorted)
|
||||
},
|
||||
(err) => {
|
||||
console.error(err)
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
update (object: Doc, listener: DisplayTxListener, sort: SortingOrder): void {
|
||||
let isAttached = false
|
||||
|
||||
isAttached = this.client.getHierarchy().isDerived(object._class, core.class.AttachedDoc)
|
||||
|
||||
this.txQuery1.query<TxCollectionCUD<Doc, AttachedDoc>>(
|
||||
isAttached ? core.class.TxCollectionCUD : core.class.TxCUD,
|
||||
isAttached
|
||||
? { 'tx.objectId': object._id as Ref<AttachedDoc> }
|
||||
: {
|
||||
objectId: object._id,
|
||||
_class: { $in: [core.class.TxCreateDoc, core.class.TxUpdateDoc, core.class.TxRemoveDoc] }
|
||||
},
|
||||
(result) => {
|
||||
this.txes1 = result
|
||||
this.notify(object, listener, sort)
|
||||
},
|
||||
{ sort: { modifiedOn: SortingOrder.Descending } }
|
||||
)
|
||||
|
||||
this.txQuery2.query<TxCUD<Doc>>(
|
||||
core.class.TxCollectionCUD,
|
||||
{
|
||||
objectId: object._id,
|
||||
'tx._class': { $in: [core.class.TxCreateDoc, core.class.TxUpdateDoc, core.class.TxRemoveDoc] }
|
||||
},
|
||||
(result) => {
|
||||
this.txes2 = result
|
||||
this.notify(object, listener, sort)
|
||||
},
|
||||
{ sort: { modifiedOn: SortingOrder.Descending } }
|
||||
)
|
||||
}
|
||||
|
||||
async combineTransactions (object: Doc, txes1: Array<TxCUD<Doc>>, txes2: Array<TxCUD<Doc>>): Promise<DisplayTx[]> {
|
||||
const hierarchy = this.client.getHierarchy()
|
||||
|
||||
// We need to sort with with natural order, to build a proper doc values.
|
||||
const allTx = Array.from(txes1).concat(txes2).sort(this.sortByLastModified)
|
||||
const txCUD: Array<TxCUD<Doc>> = this.filterTxCUD(allTx, hierarchy)
|
||||
const parents = new Map<Ref<Doc>, DisplayTx>()
|
||||
|
||||
const results: DisplayTx[] = []
|
||||
|
||||
for (const tx of txCUD) {
|
||||
const { collectionCUD, updateCUD, result, tx: ntx } = this.createDisplayTx(tx, parents)
|
||||
|
||||
// We do not need collection object updates, in main list of displayed transactions.
|
||||
if (this.isDisplayTxRequired(collectionCUD, updateCUD, ntx, object)) {
|
||||
// Combine previous update transaction for same field and if same operation and time treshold is ok
|
||||
this.checkIntegratePreviousTx(results, result)
|
||||
results.push(result)
|
||||
}
|
||||
}
|
||||
return Array.from(results)
|
||||
}
|
||||
|
||||
sortByLastModified (a: TxCUD<Doc>, b: TxCUD<Doc>): number {
|
||||
return a.modifiedOn - b.modifiedOn
|
||||
}
|
||||
|
||||
isDisplayTxRequired (collectionCUD: boolean, updateCUD: boolean, ntx: TxCUD<Doc>, object: Doc): boolean {
|
||||
return !(collectionCUD && updateCUD) || ntx.objectId === object._id
|
||||
}
|
||||
|
||||
filterTxCUD (allTx: Array<TxCUD<Doc>>, hierarchy: Hierarchy): Array<TxCUD<Doc>> {
|
||||
return allTx.filter((tx) => hierarchy.isDerived(tx._class, core.class.TxCUD))
|
||||
}
|
||||
|
||||
createDisplayTx (
|
||||
tx: TxCUD<Doc>,
|
||||
parents: Map<Ref<Doc>, DisplayTx>
|
||||
): { collectionCUD: boolean, updateCUD: boolean, result: DisplayTx, tx: TxCUD<Doc> } {
|
||||
let collectionCUD = false
|
||||
let updateCUD = false
|
||||
const hierarchy = this.client.getHierarchy()
|
||||
if (hierarchy.isDerived(tx._class, core.class.TxCollectionCUD)) {
|
||||
tx = (tx as TxCollectionCUD<Doc, AttachedDoc>).tx
|
||||
collectionCUD = true
|
||||
}
|
||||
let firstTx = parents.get(tx.objectId)
|
||||
const result: DisplayTx = this.newDisplayTx(tx)
|
||||
|
||||
result.doc = firstTx?.doc ?? result.doc
|
||||
|
||||
firstTx = firstTx ?? result
|
||||
parents.set(tx.objectId, firstTx)
|
||||
|
||||
// If we have updates also apply them all.
|
||||
updateCUD = this.updateFirstTx(result, firstTx)
|
||||
|
||||
if (hierarchy.isDerived(tx._class, core.class.TxRemoveDoc) && result.doc !== undefined) {
|
||||
firstTx.removed = true
|
||||
}
|
||||
return { collectionCUD, updateCUD, result, tx }
|
||||
}
|
||||
|
||||
updateFirstTx (result: DisplayTx, firstTx: DisplayTx): boolean {
|
||||
if (this.client.getHierarchy().isDerived(result.tx._class, core.class.TxUpdateDoc) && result.doc !== undefined) {
|
||||
firstTx.doc = TxProcessor.updateDoc2Doc(result.doc, result.tx as TxUpdateDoc<Doc>)
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
newDisplayTx (tx: TxCUD<Doc>): DisplayTx {
|
||||
const hierarchy = this.client.getHierarchy()
|
||||
const createTx = hierarchy.isDerived(tx._class, core.class.TxCreateDoc) ? (tx as TxCreateDoc<Doc>) : undefined
|
||||
return {
|
||||
tx,
|
||||
txes: [],
|
||||
createTx,
|
||||
updateTx: hierarchy.isDerived(tx._class, core.class.TxUpdateDoc) ? (tx as TxUpdateDoc<Doc>) : undefined,
|
||||
removed: false,
|
||||
doc: createTx !== undefined ? TxProcessor.createDoc2Doc(createTx) : undefined
|
||||
}
|
||||
}
|
||||
|
||||
checkIntegratePreviousTx (results: DisplayTx[], result: DisplayTx): void {
|
||||
if (results.length > 0) {
|
||||
const prevTx = results[results.length - 1]
|
||||
if (this.isSameKindTx(prevTx, result)) {
|
||||
const prevUpdate = prevTx.tx as unknown as TxUpdateDoc<Doc>
|
||||
const curUpdate = result.tx as unknown as TxUpdateDoc<Doc>
|
||||
if (
|
||||
isEqualOps(prevUpdate.operations, curUpdate.operations) &&
|
||||
result.tx.modifiedOn - prevUpdate.modifiedOn < combineThreshold
|
||||
) {
|
||||
// we have same keys, l
|
||||
// Remember previous transactions
|
||||
result.txes.push(...prevTx.txes, prevTx.tx)
|
||||
// Remove last item
|
||||
results.splice(results.length - 1, 1)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
isSameKindTx (prevTx: DisplayTx, result: DisplayTx): boolean {
|
||||
return (
|
||||
prevTx.tx.objectId === result.tx.objectId && // Same document id
|
||||
prevTx.tx._class === result.tx._class && // Same transaction class
|
||||
result.tx._class === core.class.TxUpdateDoc &&
|
||||
prevTx.tx.modifiedBy === result.tx.modifiedBy // Same user
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Construct an new activity, to listend for displayed transactions in UI.
|
||||
* @param client
|
||||
*/
|
||||
export function newActivity (client: Client): Activity {
|
||||
return new ActivityImpl(client)
|
||||
}
|
@ -13,62 +13,31 @@
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
-->
|
||||
|
||||
<script lang="ts">
|
||||
import activity, { TxViewlet } from '@anticrm/activity'
|
||||
import chunter from '@anticrm/chunter'
|
||||
import type { AttachedDoc, Doc, Ref, TxCollectionCUD, TxCUD } from '@anticrm/core'
|
||||
import core, { SortingOrder } from '@anticrm/core'
|
||||
import { Doc, SortingOrder } from '@anticrm/core'
|
||||
import { createQuery, getClient } from '@anticrm/presentation'
|
||||
import { ReferenceInput } from '@anticrm/text-editor'
|
||||
import { Grid, IconActivity, ScrollBox } from '@anticrm/ui'
|
||||
import { ActivityKey, activityKey } from '../utils'
|
||||
import { ActivityKey, activityKey, DisplayTx, newActivity } from '../activity'
|
||||
import TxView from './TxView.svelte'
|
||||
|
||||
export let fullSize: boolean = false
|
||||
export let object: Doc
|
||||
|
||||
let txes1: TxCUD<Doc>[] = []
|
||||
let txes2: TxCUD<Doc>[] = []
|
||||
|
||||
let txes: TxCUD<Doc>[]
|
||||
|
||||
$: txes = Array.from(txes1)
|
||||
.concat(txes2)
|
||||
.sort((a, b) => b.modifiedOn - a.modifiedOn)
|
||||
let txes: DisplayTx[] = []
|
||||
|
||||
const client = getClient()
|
||||
|
||||
const txQuery1 = createQuery()
|
||||
const txQuery2 = createQuery()
|
||||
const activityQuery = newActivity(client)
|
||||
|
||||
let isAttached = false
|
||||
|
||||
$: isAttached = client.getHierarchy().isDerived(object._class, core.class.AttachedDoc)
|
||||
$: txQuery1.query<TxCollectionCUD<Doc, AttachedDoc>>(
|
||||
isAttached ? core.class.TxCollectionCUD : core.class.TxCUD,
|
||||
isAttached
|
||||
? { 'tx.objectId': object._id as Ref<AttachedDoc> }
|
||||
: {
|
||||
objectId: object._id,
|
||||
_class: { $in: [core.class.TxCreateDoc, core.class.TxUpdateDoc, core.class.TxRemoveDoc] }
|
||||
},
|
||||
$: activityQuery.update(
|
||||
object,
|
||||
(result) => {
|
||||
txes1 = result
|
||||
txes = result
|
||||
},
|
||||
{ sort: { modifiedOn: SortingOrder.Descending } }
|
||||
)
|
||||
|
||||
$: txQuery2.query<TxCUD<Doc>>(
|
||||
core.class.TxCollectionCUD,
|
||||
{
|
||||
objectId: object._id,
|
||||
'tx._class': { $in: [core.class.TxCreateDoc, core.class.TxRemoveDoc] }
|
||||
},
|
||||
(result) => {
|
||||
txes2 = result
|
||||
},
|
||||
{ sort: { modifiedOn: SortingOrder.Descending } }
|
||||
SortingOrder.Descending
|
||||
)
|
||||
|
||||
function onMessage (event: CustomEvent) {
|
||||
@ -94,7 +63,7 @@
|
||||
<ScrollBox vertical stretch>
|
||||
{#if txes}
|
||||
<Grid column={1} rowGap={1.5}>
|
||||
{#each txes as tx}
|
||||
{#each txes as tx (tx.tx._id)}
|
||||
<TxView {tx} {viewlets} />
|
||||
{/each}
|
||||
</Grid>
|
||||
|
@ -13,101 +13,98 @@
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
-->
|
||||
|
||||
<script lang="ts">
|
||||
import type { TxViewlet } from '@anticrm/activity'
|
||||
import activity from '@anticrm/activity'
|
||||
import contact, { EmployeeAccount, formatName } from '@anticrm/contact'
|
||||
import core, {
|
||||
AttachedDoc,
|
||||
Doc,
|
||||
Ref,
|
||||
Tx,
|
||||
TxCollectionCUD,
|
||||
TxCreateDoc,
|
||||
TxCUD,
|
||||
TxProcessor,
|
||||
TxUpdateDoc
|
||||
} from '@anticrm/core'
|
||||
import core, { Class, Doc, Ref, TxCUD, TxUpdateDoc } from '@anticrm/core'
|
||||
import { IntlString } from '@anticrm/platform'
|
||||
import { getClient } from '@anticrm/presentation'
|
||||
import { AnyComponent, AnySvelteComponent, Component, Icon, Label, TimeSince } from '@anticrm/ui'
|
||||
import type { AttributeModel } from '@anticrm/view'
|
||||
import { buildModel, getObjectPresenter } from '@anticrm/view-resources'
|
||||
import { activityKey, ActivityKey } from '../utils'
|
||||
import { activityKey, ActivityKey, DisplayTx } from '../activity'
|
||||
|
||||
export let tx: Tx
|
||||
export let tx: DisplayTx
|
||||
export let viewlets: Map<ActivityKey, TxViewlet>
|
||||
|
||||
type TxDisplayViewlet =
|
||||
| (Pick<TxViewlet, 'icon' | 'label' | 'display'> & { component?: AnyComponent | AnySvelteComponent })
|
||||
| undefined
|
||||
|
||||
let viewlet: TxDisplayViewlet
|
||||
let ptx: DisplayTx | undefined
|
||||
|
||||
let viewlet: TxDisplayViewlet | undefined
|
||||
let props: any
|
||||
let displayTx: TxCUD<Doc> | undefined
|
||||
let utx: TxUpdateDoc<Doc> | undefined
|
||||
let employee: EmployeeAccount | undefined
|
||||
let model: AttributeModel[] = []
|
||||
|
||||
$: if (tx.tx._id !== ptx?.tx._id) {
|
||||
viewlet = undefined
|
||||
props = undefined
|
||||
employee = undefined
|
||||
model = []
|
||||
ptx = tx
|
||||
}
|
||||
|
||||
const client = getClient()
|
||||
|
||||
$: if (client.getHierarchy().isDerived(tx._class, core.class.TxCollectionCUD)) {
|
||||
const colCUD = tx as TxCollectionCUD<Doc, AttachedDoc>
|
||||
displayTx = colCUD.tx
|
||||
viewlet = undefined
|
||||
} else if (client.getHierarchy().isDerived(tx._class, core.class.TxCUD)) {
|
||||
displayTx = tx as TxCUD<Doc>
|
||||
viewlet = undefined
|
||||
}
|
||||
|
||||
async function updateViewlet (displayTx?: TxCUD<Doc>): Promise<TxDisplayViewlet> {
|
||||
if (displayTx === undefined) {
|
||||
return undefined
|
||||
async function createPseudoViewlet (dtx: DisplayTx, label: string): Promise<TxDisplayViewlet> {
|
||||
const doc = dtx.doc
|
||||
if (doc === undefined) {
|
||||
return
|
||||
}
|
||||
const key = activityKey(displayTx.objectClass, displayTx._class)
|
||||
let viewlet: TxDisplayViewlet = viewlets.get(key)
|
||||
const docClass: Class<Doc> = client.getModel().getObject(doc._class)
|
||||
|
||||
props = { tx: displayTx }
|
||||
|
||||
if (viewlet === undefined && displayTx._class === core.class.TxCreateDoc) {
|
||||
// Check if we have a class presenter we could have a pseudo viewlet based on class presenter.
|
||||
const doc = TxProcessor.createDoc2Doc(displayTx as TxCreateDoc<Doc>)
|
||||
const docClass = client.getModel().getObject(doc._class)
|
||||
|
||||
const presenter = await getObjectPresenter(client, doc._class, 'doc-presenter')
|
||||
if (presenter !== undefined) {
|
||||
viewlet = {
|
||||
display: 'inline',
|
||||
icon: docClass.icon ?? activity.icon.Activity,
|
||||
label: ('created ' + docClass.label) as IntlString,
|
||||
component: presenter.presenter
|
||||
}
|
||||
props = { value: doc }
|
||||
const presenter = await getObjectPresenter(client, doc._class, 'doc-presenter')
|
||||
if (presenter !== undefined) {
|
||||
return {
|
||||
display: 'inline',
|
||||
icon: docClass.icon ?? activity.icon.Activity,
|
||||
label: (`${label} ` + docClass.label) as IntlString,
|
||||
component: presenter.presenter
|
||||
}
|
||||
}
|
||||
return viewlet
|
||||
}
|
||||
|
||||
$: updateViewlet(displayTx).then((result) => {
|
||||
viewlet = result
|
||||
async function updateViewlet (dtx: DisplayTx): Promise<{ viewlet: TxDisplayViewlet; id: Ref<TxCUD<Doc>> }> {
|
||||
const key = activityKey(dtx.tx.objectClass, dtx.tx._class)
|
||||
let viewlet: TxDisplayViewlet = viewlets.get(key)
|
||||
|
||||
props = { tx: dtx.tx, value: dtx.doc }
|
||||
|
||||
if (viewlet === undefined && dtx.tx._class === core.class.TxCreateDoc) {
|
||||
// Check if we have a class presenter we could have a pseudo viewlet based on class presenter.
|
||||
viewlet = await createPseudoViewlet(dtx, 'created')
|
||||
}
|
||||
if (viewlet === undefined && dtx.tx._class === core.class.TxRemoveDoc) {
|
||||
viewlet = await createPseudoViewlet(dtx, 'deleted')
|
||||
}
|
||||
return { viewlet, id: dtx.tx._id }
|
||||
}
|
||||
|
||||
$: updateViewlet(tx).then((result) => {
|
||||
if (result.id === tx.tx._id) {
|
||||
viewlet = result.viewlet
|
||||
}
|
||||
})
|
||||
|
||||
let employee: EmployeeAccount | undefined
|
||||
$: client.findOne(contact.class.EmployeeAccount, { _id: tx.modifiedBy as Ref<EmployeeAccount> }).then((account) => {
|
||||
employee = account
|
||||
})
|
||||
$: client
|
||||
.findOne(contact.class.EmployeeAccount, { _id: tx.tx.modifiedBy as Ref<EmployeeAccount> })
|
||||
.then((account) => {
|
||||
employee = account
|
||||
})
|
||||
|
||||
let model: AttributeModel[] = []
|
||||
|
||||
$: if (displayTx !== undefined && displayTx._class === core.class.TxUpdateDoc) {
|
||||
utx = displayTx as TxUpdateDoc<Doc>
|
||||
const ops = { client, _class: utx.objectClass, keys: Object.keys(utx.operations), ignoreMissing: true }
|
||||
model = []
|
||||
$: if (tx.updateTx !== undefined) {
|
||||
const ops = {
|
||||
client,
|
||||
_class: tx.updateTx.objectClass,
|
||||
keys: Object.keys(tx.updateTx.operations).filter(id => !id.startsWith('$')),
|
||||
ignoreMissing: true
|
||||
}
|
||||
buildModel(ops).then((m) => {
|
||||
model = m
|
||||
})
|
||||
} else {
|
||||
model = []
|
||||
utx = undefined
|
||||
}
|
||||
|
||||
function getValue (utx: TxUpdateDoc<Doc>, key: string): any {
|
||||
@ -115,7 +112,7 @@
|
||||
}
|
||||
</script>
|
||||
|
||||
{#if displayTx && (viewlet !== undefined || model.length > 0)}
|
||||
{#if viewlet !== undefined || model.length > 0}
|
||||
<div class="flex-col msgactivity-container">
|
||||
<div class="flex-between">
|
||||
<div class="flex-center icon">
|
||||
@ -138,10 +135,10 @@
|
||||
{#if viewlet && viewlet.label}
|
||||
<div><Label label={viewlet.label} /></div>
|
||||
{/if}
|
||||
{#if viewlet === undefined && model.length > 0 && utx}
|
||||
{#if viewlet === undefined && model.length > 0 && tx.updateTx}
|
||||
{#each model as m}
|
||||
<span>changed {m.label} to</span>
|
||||
<div class="strong"><svelte:component this={m.presenter} value={getValue(utx, m.key)} /></div>
|
||||
<div class="strong"><svelte:component this={m.presenter} value={getValue(tx.updateTx, m.key)} /></div>
|
||||
{/each}
|
||||
{:else if viewlet && viewlet.display === 'inline' && viewlet.component}
|
||||
<div>
|
||||
@ -153,7 +150,7 @@
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
<div class="time"><TimeSince value={tx.modifiedOn} /></div>
|
||||
<div class="time"><TimeSince value={tx.tx.modifiedOn} /></div>
|
||||
</div>
|
||||
{#if viewlet && viewlet.component && viewlet.display !== 'inline'}
|
||||
<div class="content" class:emphasize={viewlet.display === 'emphasized'}>
|
||||
|
@ -1,7 +0,0 @@
|
||||
import { Class, Doc, Ref, Tx } from '@anticrm/core'
|
||||
|
||||
export type ActivityKey = string
|
||||
|
||||
export function activityKey (objectClass: Ref<Class<Doc>>, txClass: Ref<Class<Tx>>): ActivityKey {
|
||||
return objectClass + ':' + txClass
|
||||
}
|
@ -13,7 +13,8 @@
|
||||
// limitations under the License.
|
||||
//
|
||||
|
||||
import type { Doc } from '@anticrm/core'
|
||||
import type { AttachedDoc, Doc } from '@anticrm/core'
|
||||
import core from '@anticrm/core'
|
||||
|
||||
import StringEditor from './components/StringEditor.svelte'
|
||||
import StringPresenter from './components/StringPresenter.svelte'
|
||||
@ -39,7 +40,12 @@ function Delete(object: Doc): void {
|
||||
}, undefined, (result) => {
|
||||
if (result) {
|
||||
const client = getClient()
|
||||
client.removeDoc(object._class, object.space, object._id)
|
||||
if(client.getHierarchy().isDerived(object._class, core.class.AttachedDoc)) {
|
||||
const adoc = object as AttachedDoc
|
||||
client.removeCollection(object._class, object.space, adoc._id, adoc.attachedTo, adoc.attachedToClass, adoc.collection)
|
||||
} else {
|
||||
client.removeDoc(object._class, object.space, object._id)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user