mirror of
https://github.com/hcengineering/platform.git
synced 2026-08-17 18:05:42 +02:00
feat: add viewlet integration and update attribute handling in various components (#10957)
Signed-off-by: Denis Bykhov <bykhov.denis@gmail.com>
This commit is contained in:
Generated
+3
@@ -10446,6 +10446,9 @@ importers:
|
||||
'@hcengineering/server-notification':
|
||||
specifier: workspace:^0.7.0
|
||||
version: link:../../server-plugins/notification
|
||||
'@hcengineering/view':
|
||||
specifier: workspace:^0.7.0
|
||||
version: link:../../plugins/view
|
||||
devDependencies:
|
||||
'@hcengineering/platform-rig':
|
||||
specifier: workspace:^0.7.21
|
||||
|
||||
@@ -37,6 +37,7 @@
|
||||
"@hcengineering/core": "workspace:^0.7.26",
|
||||
"@hcengineering/model": "workspace:^0.7.17",
|
||||
"@hcengineering/platform": "workspace:^0.7.20",
|
||||
"@hcengineering/view": "workspace:^0.7.0",
|
||||
"@hcengineering/card": "workspace:^0.7.0",
|
||||
"@hcengineering/communication": "workspace:^0.7.0",
|
||||
"@hcengineering/server-notification": "workspace:^0.7.0",
|
||||
|
||||
@@ -21,6 +21,7 @@ import serverCard from '@hcengineering/server-card'
|
||||
import card from '@hcengineering/card'
|
||||
import communication from '@hcengineering/communication'
|
||||
import serverNotification from '@hcengineering/server-notification'
|
||||
import view from '@hcengineering/view'
|
||||
|
||||
export { serverCardId } from '@hcengineering/server-card'
|
||||
|
||||
@@ -43,6 +44,16 @@ export function createModel (builder: Builder): void {
|
||||
}
|
||||
})
|
||||
|
||||
builder.createDoc(serverCore.class.Trigger, core.space.Model, {
|
||||
trigger: serverCard.trigger.OnViewletUpdate,
|
||||
isAsync: true,
|
||||
txMatch: {
|
||||
_class: core.class.TxUpdateDoc,
|
||||
objectClass: view.class.Viewlet,
|
||||
'operations.config': { $exists: true }
|
||||
}
|
||||
})
|
||||
|
||||
builder.createDoc(serverCore.class.Trigger, core.space.Model, {
|
||||
trigger: serverCard.trigger.OnTagRemove,
|
||||
txMatch: {
|
||||
|
||||
@@ -44,7 +44,7 @@
|
||||
type
|
||||
}
|
||||
if (key !== undefined) {
|
||||
const attr = client.getHierarchy().findAttribute(process.masterTag, key)
|
||||
const attr = client.getModel().findAllSync(core.class.Attribute, { name: key })[0]
|
||||
if (attr?.label !== undefined) {
|
||||
name = await translate(attr.label, {})
|
||||
result.name = name
|
||||
|
||||
@@ -243,10 +243,9 @@ export class ProcessMiddleware extends BasePresentationMiddleware implements Pre
|
||||
results = await Promise.all(
|
||||
results.map(async (r) => {
|
||||
if (r.key !== undefined) {
|
||||
const h = this.client.getHierarchy()
|
||||
const _process = this.client.getModel().findObject(execution.process)
|
||||
if (_process !== undefined) {
|
||||
const attr = h.findAttribute(_process.masterTag, r.key)
|
||||
const attr = this.client.getModel().findAllSync(core.class.Attribute, { name: r.key })[0]
|
||||
if (attr?.label !== undefined) {
|
||||
const name = await translate(attr.label, {})
|
||||
return { ...r, name }
|
||||
|
||||
@@ -252,6 +252,67 @@
|
||||
return typeof value === 'string' ? value : value?.key
|
||||
}
|
||||
|
||||
function getAttributeKey (key: string): string {
|
||||
if (key.startsWith('$lookup.')) {
|
||||
return key.slice('$lookup.'.length)
|
||||
}
|
||||
const dotIndex = key.lastIndexOf('.')
|
||||
return dotIndex === -1 ? key : key.slice(dotIndex + 1)
|
||||
}
|
||||
|
||||
function isSourceAttribute (sourceClass: Ref<Class<Doc>>, key: string): boolean {
|
||||
return hierarchy.getAllAttributes(sourceClass).has(getAttributeKey(key))
|
||||
}
|
||||
|
||||
function syncConfigOrder (
|
||||
sourceClass: Ref<Class<Doc>>,
|
||||
previousSourceConfig: Array<BuildModelKey | string>,
|
||||
sourceConfig: Array<BuildModelKey | string>,
|
||||
targetConfig: Array<BuildModelKey | string>
|
||||
): Array<BuildModelKey | string> {
|
||||
const sourceKeys = new Set(sourceConfig.map(getKey).filter((it): it is string => it !== undefined))
|
||||
const previousSourceKeys = new Set(previousSourceConfig.map(getKey).filter((it): it is string => it !== undefined))
|
||||
const targetByKey = new Map<string, Array<{ item: BuildModelKey | string, index: number }>>()
|
||||
for (const [index, item] of targetConfig.entries()) {
|
||||
const key = getKey(item)
|
||||
if (key === undefined) continue
|
||||
const items = targetByKey.get(key) ?? []
|
||||
items.push({ item, index })
|
||||
targetByKey.set(key, items)
|
||||
}
|
||||
|
||||
const sourceItems: Array<BuildModelKey | string> = []
|
||||
const usedIndexes = new Set<number>()
|
||||
for (const sourceItem of sourceConfig) {
|
||||
const key = getKey(sourceItem)
|
||||
if (key === undefined) continue
|
||||
|
||||
const targetItem = targetByKey.get(key)?.shift()
|
||||
sourceItems.push(targetItem?.item ?? sourceItem)
|
||||
if (targetItem !== undefined) {
|
||||
usedIndexes.add(targetItem.index)
|
||||
}
|
||||
}
|
||||
|
||||
const synced = [...sourceItems]
|
||||
for (const [index, targetItem] of targetConfig.entries()) {
|
||||
if (usedIndexes.has(index)) continue
|
||||
|
||||
const key = getKey(targetItem)
|
||||
if (
|
||||
key !== undefined &&
|
||||
!sourceKeys.has(key) &&
|
||||
(previousSourceKeys.has(key) || isSourceAttribute(sourceClass, key))
|
||||
) {
|
||||
continue
|
||||
}
|
||||
|
||||
synced.splice(Math.min(index, synced.length), 0, targetItem)
|
||||
}
|
||||
|
||||
return synced
|
||||
}
|
||||
|
||||
function isExist (result: Config[], newValue: Config): boolean {
|
||||
if (!isAttribute(newValue)) return false
|
||||
const newValueKey = getKey(newValue.value)
|
||||
@@ -391,6 +452,45 @@
|
||||
return preference === undefined ? result : setStatus(result, preference)
|
||||
}
|
||||
|
||||
async function upsertViewletPreference (
|
||||
viewletId: Ref<Viewlet>,
|
||||
config: Array<BuildModelKey | string>
|
||||
): Promise<void> {
|
||||
const preference = preferences.find((p) => p.attachedTo === viewletId)
|
||||
if (preference !== undefined) {
|
||||
if (!deepEqual(preference.config, config)) {
|
||||
await client.update(preference, {
|
||||
config
|
||||
})
|
||||
}
|
||||
} else {
|
||||
await client.createDoc(view.class.ViewletPreference, core.space.Workspace, {
|
||||
attachedTo: viewletId,
|
||||
config
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
async function syncChildViewletPreferences (
|
||||
sourceViewlet: Viewlet,
|
||||
previousSourceConfig: Array<BuildModelKey | string>,
|
||||
sourceConfig: Array<BuildModelKey | string>
|
||||
): Promise<void> {
|
||||
const descendants = new Set(
|
||||
hierarchy.getDescendants(sourceViewlet.attachTo).filter((it) => it !== sourceViewlet.attachTo)
|
||||
)
|
||||
for (const childViewlet of viewlets) {
|
||||
if (!descendants.has(childViewlet.attachTo)) continue
|
||||
|
||||
const preference = preferences.find((p) => p.attachedTo === childViewlet._id)
|
||||
const targetConfig = preference?.config ?? childViewlet.config
|
||||
const config = syncConfigOrder(sourceViewlet.attachTo, previousSourceConfig, sourceConfig, targetConfig)
|
||||
if (deepEqual(targetConfig, config)) continue
|
||||
|
||||
await upsertViewletPreference(childViewlet._id, config)
|
||||
}
|
||||
}
|
||||
|
||||
async function addAssociations (
|
||||
result: Config[],
|
||||
_class: Ref<Class<Doc>>,
|
||||
@@ -437,16 +537,14 @@
|
||||
}
|
||||
return value
|
||||
})
|
||||
const preference = preferences.find((p) => p.attachedTo === viewletId)
|
||||
if (preference !== undefined) {
|
||||
await client.update(preference, {
|
||||
config
|
||||
})
|
||||
} else {
|
||||
await client.createDoc(view.class.ViewletPreference, core.space.Workspace, {
|
||||
attachedTo: viewletId,
|
||||
config
|
||||
})
|
||||
const selectedViewlet = viewlets.find((it) => it._id === viewletId)
|
||||
const previousSourceConfig =
|
||||
preferences.find((p) => p.attachedTo === viewletId)?.config ?? selectedViewlet?.config ?? []
|
||||
|
||||
await upsertViewletPreference(viewletId, config)
|
||||
|
||||
if (selectedViewlet !== undefined) {
|
||||
await syncChildViewletPreferences(selectedViewlet, previousSourceConfig, config)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -61,9 +61,79 @@ import { getMetadata, translate } from '@hcengineering/platform'
|
||||
import { getEmployee, getPersonSpaces } from '@hcengineering/server-contact'
|
||||
import serverCore, { TriggerControl } from '@hcengineering/server-core'
|
||||
import setting from '@hcengineering/setting'
|
||||
import view from '@hcengineering/view'
|
||||
import view, { type BuildModelKey, type Viewlet } from '@hcengineering/view'
|
||||
import { workbenchId } from '@hcengineering/workbench'
|
||||
|
||||
type ViewletConfigItem = BuildModelKey | string
|
||||
interface IndexedViewletConfigItem {
|
||||
item: ViewletConfigItem
|
||||
index: number
|
||||
}
|
||||
|
||||
function getViewletConfigKey (item: ViewletConfigItem): string {
|
||||
return typeof item === 'string' ? item : item.key
|
||||
}
|
||||
|
||||
function getAttributeKey (key: string): string {
|
||||
if (key.startsWith('$lookup.')) {
|
||||
return key.slice('$lookup.'.length)
|
||||
}
|
||||
const dotIndex = key.lastIndexOf('.')
|
||||
return dotIndex === -1 ? key : key.slice(dotIndex + 1)
|
||||
}
|
||||
|
||||
function isSourceAttribute (control: TriggerControl, sourceClass: Ref<Class<Doc>>, key: string): boolean {
|
||||
return control.hierarchy.getAllAttributes(sourceClass).has(getAttributeKey(key))
|
||||
}
|
||||
|
||||
function syncViewletConfigOrder (
|
||||
control: TriggerControl,
|
||||
sourceClass: Ref<Class<Doc>>,
|
||||
previousSourceConfig: ViewletConfigItem[],
|
||||
sourceConfig: ViewletConfigItem[],
|
||||
targetConfig: ViewletConfigItem[]
|
||||
): ViewletConfigItem[] {
|
||||
const sourceKeys = new Set(sourceConfig.map(getViewletConfigKey))
|
||||
const previousSourceKeys = new Set(previousSourceConfig.map(getViewletConfigKey))
|
||||
const targetByKey = new Map<string, IndexedViewletConfigItem[]>()
|
||||
for (const [index, item] of targetConfig.entries()) {
|
||||
const key = getViewletConfigKey(item)
|
||||
const items = targetByKey.get(key) ?? []
|
||||
items.push({ item, index })
|
||||
targetByKey.set(key, items)
|
||||
}
|
||||
|
||||
const sourceItems: ViewletConfigItem[] = []
|
||||
const usedIndexes = new Set<number>()
|
||||
for (const sourceItem of sourceConfig) {
|
||||
const key = getViewletConfigKey(sourceItem)
|
||||
const targetItem = targetByKey.get(key)?.shift()
|
||||
const item = targetItem?.item ?? sourceItem
|
||||
sourceItems.push(item)
|
||||
if (targetItem !== undefined) {
|
||||
usedIndexes.add(targetItem.index)
|
||||
}
|
||||
}
|
||||
|
||||
const synced = [...sourceItems]
|
||||
for (const [index, targetItem] of targetConfig.entries()) {
|
||||
if (usedIndexes.has(index)) continue
|
||||
|
||||
const key = getViewletConfigKey(targetItem)
|
||||
if (!sourceKeys.has(key) && (previousSourceKeys.has(key) || isSourceAttribute(control, sourceClass, key))) {
|
||||
continue
|
||||
}
|
||||
|
||||
synced.splice(Math.min(index, synced.length), 0, targetItem)
|
||||
}
|
||||
|
||||
return synced
|
||||
}
|
||||
|
||||
function isConfigOrderChanged (current: ViewletConfigItem[], next: ViewletConfigItem[]): boolean {
|
||||
return current.length !== next.length || current.some((item, index) => item !== next[index])
|
||||
}
|
||||
|
||||
async function OnAttribute (ctx: TxCreateDoc<AnyAttribute>[], control: TriggerControl): Promise<Tx[]> {
|
||||
const attr = TxProcessor.createDoc2Doc(ctx[0])
|
||||
if (control.hierarchy.isDerived(attr.attributeOf, card.class.Card)) {
|
||||
@@ -159,6 +229,47 @@ async function OnAttributeRemove (ctx: TxRemoveDoc<AnyAttribute>[], control: Tri
|
||||
return []
|
||||
}
|
||||
|
||||
async function OnViewletUpdate (ctx: TxUpdateDoc<Viewlet>[], control: TriggerControl): Promise<Tx[]> {
|
||||
const updateTx = ctx[0]
|
||||
if (updateTx.space === core.space.DerivedTx) return []
|
||||
if (!Array.isArray(updateTx.operations.config)) return []
|
||||
|
||||
const sourceViewlet = (await control.findAll<Viewlet>(control.ctx, view.class.Viewlet, { _id: updateTx.objectId }))[0]
|
||||
if (sourceViewlet === undefined) return []
|
||||
if (!control.hierarchy.isDerived(sourceViewlet.attachTo, card.class.Card)) return []
|
||||
|
||||
const descendants = control.hierarchy
|
||||
.getDescendants(sourceViewlet.attachTo)
|
||||
.filter((it) => it !== sourceViewlet.attachTo)
|
||||
if (descendants.length === 0) return []
|
||||
|
||||
const childViewlets = await control.findAll<Viewlet>(control.ctx, view.class.Viewlet, {
|
||||
attachTo: { $in: descendants },
|
||||
descriptor: sourceViewlet.descriptor,
|
||||
variant: sourceViewlet.variant ?? { $exists: false }
|
||||
})
|
||||
|
||||
const res: Tx[] = []
|
||||
for (const childViewlet of childViewlets) {
|
||||
const config = syncViewletConfigOrder(
|
||||
control,
|
||||
sourceViewlet.attachTo,
|
||||
sourceViewlet.config,
|
||||
updateTx.operations.config,
|
||||
childViewlet.config
|
||||
)
|
||||
if (!isConfigOrderChanged(childViewlet.config, config)) continue
|
||||
|
||||
res.push(
|
||||
control.txFactory.createTxUpdateDoc(childViewlet._class, childViewlet.space, childViewlet._id, {
|
||||
config
|
||||
})
|
||||
)
|
||||
}
|
||||
|
||||
return res
|
||||
}
|
||||
|
||||
async function OnMasterTagRemove (ctx: TxUpdateDoc<MasterTag>[], control: TriggerControl): Promise<Tx[]> {
|
||||
const updateTx = ctx[0]
|
||||
if (updateTx.space === core.space.DerivedTx) return []
|
||||
@@ -937,6 +1048,7 @@ export default async () => ({
|
||||
trigger: {
|
||||
OnAttribute,
|
||||
OnAttributeRemove,
|
||||
OnViewletUpdate,
|
||||
OnMasterTagCreate,
|
||||
OnMasterTagRemove,
|
||||
OnTagRemove,
|
||||
|
||||
@@ -37,6 +37,7 @@ export default plugin(serverCardId, {
|
||||
trigger: {
|
||||
OnAttribute: '' as Resource<TriggerFunc>,
|
||||
OnAttributeRemove: '' as Resource<TriggerFunc>,
|
||||
OnViewletUpdate: '' as Resource<TriggerFunc>,
|
||||
OnMasterTagCreate: '' as Resource<TriggerFunc>,
|
||||
OnTagRemove: '' as Resource<TriggerFunc>,
|
||||
OnMasterTagRemove: '' as Resource<TriggerFunc>,
|
||||
|
||||
@@ -804,7 +804,7 @@ export async function CreateToDo (
|
||||
|
||||
todoResults.push({
|
||||
_id: generateId() as any as ContextId,
|
||||
name: attr.name,
|
||||
name: attr.label,
|
||||
key: attr.name,
|
||||
type: attr.type
|
||||
})
|
||||
|
||||
Reference in New Issue
Block a user