Cards fixes (#8278)

This commit is contained in:
Denis Bykhov
2025-03-19 20:28:20 +07:00
committed by GitHub
parent d5b11c1ed3
commit e8a5b46ac5
16 changed files with 139 additions and 67 deletions
+51 -43
View File
@@ -12,27 +12,19 @@
// limitations under the License.
import activity from '@hcengineering/activity'
import {
CardEvents,
cardId,
DOMAIN_CARD,
type Card,
type MasterTag,
type ParentInfo,
type Tag
} from '@hcengineering/card'
import { cardId, DOMAIN_CARD, type Card, type MasterTag, type ParentInfo, type Tag } from '@hcengineering/card'
import chunter from '@hcengineering/chunter'
import core, {
AccountRole,
type Blobs,
ClassifierKind,
DOMAIN_MODEL,
IndexKind,
SortingOrder,
type Blobs,
type CollectionSize,
type MarkupBlobRef,
type Rank,
type Ref,
ClassifierKind,
type MarkupBlobRef
type Ref
} from '@hcengineering/core'
import {
Collection,
@@ -54,6 +46,7 @@ import workbench from '@hcengineering/model-workbench'
import { getEmbeddedLabel, type IntlString } from '@hcengineering/platform'
import time, { type ToDo } from '@hcengineering/time'
import { type AnyComponent } from '@hcengineering/ui/src/types'
import { type BuildModelKey } from '@hcengineering/view'
import card from './plugin'
export { cardId } from '@hcengineering/card'
@@ -142,6 +135,23 @@ export function createModel (builder: Builder): void {
]
})
const listConfig: (BuildModelKey | string)[] = [
{ key: '', props: { showParent: true }, displayProps: { fixed: 'left', key: 'card' } },
{ key: '_class', displayProps: { fixed: 'left', key: 'type' } },
{ key: '', displayProps: { grow: true } },
{
key: '',
presenter: view.component.RolePresenter,
label: card.string.Tags,
props: { fullSize: true },
displayProps: { key: 'tags', fixed: 'right' }
},
{
key: 'modifiedOn',
displayProps: { fixed: 'right', dividerBefore: true }
}
]
builder.createDoc(view.class.Viewlet, core.space.Model, {
attachTo: card.types.File,
descriptor: view.viewlet.List,
@@ -156,22 +166,7 @@ export function createModel (builder: Builder): void {
configOptions: {
hiddenKeys: ['content', 'title']
},
config: [
{ key: '', props: { showParent: true } },
{ key: '_class', displayProps: { fixed: 'left' } },
{
key: '',
presenter: view.component.RolePresenter,
label: card.string.Tags,
props: { fullSize: true },
displayProps: { fixed: 'left' }
},
{ key: '', displayProps: { grow: true } },
{
key: 'modifiedOn',
displayProps: { fixed: 'right', dividerBefore: true }
}
]
config: listConfig
})
builder.createDoc(
@@ -276,20 +271,35 @@ export function createModel (builder: Builder): void {
configOptions: {
hiddenKeys: ['content', 'title']
},
config: [
{ key: '', props: { showParent: true } },
'_class',
{ key: '', presenter: view.component.RolePresenter, label: card.string.Tags, props: { fullSize: true } },
{ key: '', displayProps: { grow: true } },
{
key: 'modifiedOn',
displayProps: { fixed: 'right', dividerBefore: true }
}
]
config: listConfig
},
card.viewlet.CardList
)
builder.createDoc(
view.class.Viewlet,
core.space.Model,
{
attachTo: card.class.Card,
descriptor: view.viewlet.List,
variant: 'child',
viewOptions: {
groupBy: ['_class', 'createdBy', 'modifiedBy'],
orderBy: [
['modifiedOn', SortingOrder.Descending],
['rank', SortingOrder.Ascending]
],
other: []
},
configOptions: {
strict: true,
hiddenKeys: ['content', 'title']
},
config: listConfig
},
card.viewlet.CardChildList
)
builder.mixin(card.class.Card, core.class.Class, view.mixin.ObjectPresenter, {
presenter: card.component.CardPresenter
})
@@ -389,8 +399,7 @@ export function createModel (builder: Builder): void {
context: {
mode: ['context', 'browser'],
group: 'remove'
},
analyticsEvent: CardEvents.MasterTagRemoved
}
},
card.action.DeleteMasterTag
)
@@ -405,8 +414,7 @@ export function createModel (builder: Builder): void {
context: {
mode: ['context', 'browser'],
group: 'remove'
},
analyticsEvent: CardEvents.MasterTagRemoved
}
})
createAction(builder, {
+19 -3
View File
@@ -15,7 +15,7 @@
import { DOMAIN_CARD } from '@hcengineering/card'
import { chunterId } from '@hcengineering/chunter'
import core, { type Client, type Data, type Doc, TxOperations } from '@hcengineering/core'
import core, { TxOperations, type Client, type Data, type Doc } from '@hcengineering/core'
import {
tryMigrate,
tryUpgrade,
@@ -39,7 +39,7 @@ export const cardOperation: MigrateOperation = {
async upgrade (state: Map<string, Set<string>>, client: () => Promise<MigrationUpgradeClient>, mode): Promise<void> {
await tryUpgrade(mode, state, client, chunterId, [
{
state: 'migrateViewlets',
state: 'migrateViewlets-v2',
func: migrateViewlets
}
])
@@ -71,23 +71,39 @@ function extractObjectProps<T extends Doc> (doc: T): Data<T> {
async function migrateViewlets (client: Client): Promise<void> {
const txOp = new TxOperations(client, core.account.System)
const viewlets = await client.findAll(view.class.Viewlet, { attachTo: card.class.Card })
const viewlets = await client.findAll(view.class.Viewlet, { attachTo: card.class.Card, variant: { $exists: false } })
const masterTags = await client.findAll(card.class.MasterTag, {})
const currentViewlets = await client.findAll(view.class.Viewlet, { attachTo: { $in: masterTags.map((p) => p._id) } })
for (const masterTag of masterTags) {
for (const viewlet of viewlets) {
const base = extractObjectProps(viewlet)
const resConfig = base.config
let index = -1
if (viewlet.descriptor === view.viewlet.List) {
index = viewlet.config.findIndex((p) => typeof p !== 'string' && p.displayProps?.grow === true)
}
const attributes = client.getHierarchy().getOwnAttributes(masterTag._id)
for (const attr of attributes) {
// let push it after grow for the list
if (index !== -1) {
resConfig.splice(index + 1, 0, attr[1].name)
} else {
resConfig.push(attr[1].name)
}
}
const current = currentViewlets.find(
(p) => p.attachTo === masterTag._id && p.variant === viewlet.variant && p.descriptor === viewlet.descriptor
)
if (current === undefined) {
await txOp.createDoc(view.class.Viewlet, core.space.Model, {
...base,
config: resConfig,
attachTo: masterTag._id
})
} else {
await txOp.diffUpdate(current, {
...base,
config: resConfig,
attachTo: masterTag._id
})
}
@@ -48,7 +48,7 @@
const query = createQuery()
const viewletId = card.viewlet.CardList
const viewletId = card.viewlet.CardChildList
let list: List
const listProvider = new ListSelectionProvider(
@@ -13,13 +13,14 @@
// limitations under the License.
-->
<script lang="ts">
import { MasterTag, Tag } from '@hcengineering/card'
import { CardEvents, MasterTag, Tag } from '@hcengineering/card'
import core, { Class, ClassifierKind, Data, Ref } from '@hcengineering/core'
import { getEmbeddedLabel } from '@hcengineering/platform'
import { Card, getClient } from '@hcengineering/presentation'
import { EditBox, Icon, Label } from '@hcengineering/ui'
import { createEventDispatcher } from 'svelte'
import card from '../plugin'
import { Analytics } from '@hcengineering/analytics'
export let parent: MasterTag | Tag | undefined = undefined
export let _class: Ref<Class<MasterTag>> | Ref<Class<Tag>>
@@ -39,6 +40,7 @@
}
await client.createDoc(_class, core.space.Model, data)
Analytics.handleEvent(isMasterTag ? CardEvents.TypeCreated : CardEvents.TagCreated)
dispatch('close')
}
@@ -90,7 +90,6 @@
{#if classes.length > 0}
<div class="antiNav-divider line" />
<Scroller shrink>
<TagHierarchy bind:_class deselect={menuSelection} {classes} {allClasses} on:select />
</Scroller>
@@ -72,6 +72,6 @@
/>
</NavLink>
{#if (descendants.get(clazz._id)?.length ?? 0) > 0}
<svelte:self classes={descendants.get(clazz._id) ?? []} {_class} level={level + 1} on:select />
<svelte:self classes={descendants.get(clazz._id) ?? []} {_class} {allClasses} level={level + 1} on:select />
{/if}
{/each}
@@ -20,6 +20,7 @@
import {
Icon,
IconOpenedArrow,
IconWithEmoji,
Label,
Location,
getCurrentResolvedLocation,
@@ -28,6 +29,7 @@
} from '@hcengineering/ui'
import { onDestroy } from 'svelte'
import card from '../../plugin'
import view from '@hcengineering/view'
export let categoryName: string
@@ -64,9 +66,14 @@
}}
>
<div class="hulyTaskNavLink-avatar">
{#if tag?.icon}
{#if tag.icon}
<div class="hulyTaskNavLink-icon">
<Icon icon={tag?.icon} size="small" fill="currentColor" />
<Icon
icon={tag.icon === view.ids.IconWithEmoji ? IconWithEmoji : tag.icon ?? card.icon.MasterTag}
iconProps={tag.icon === view.ids.IconWithEmoji ? { icon: tag.color } : {}}
size="small"
fill="currentColor"
/>
</div>
{/if}
</div>
@@ -3,12 +3,18 @@
import { RelationSetting } from '@hcengineering/setting-resources'
import contact from '@hcengineering/contact'
import card from '../../plugin'
import { Analytics } from '@hcengineering/analytics'
import { CardEvents } from '@hcengineering/card'
const client = getClient()
const hierarchy = client.getHierarchy()
const _classes = [...hierarchy.getDescendants(card.class.Card), contact.class.Contact].filter(
(c) => c !== card.class.Card
)
function createHandler (): void {
Analytics.handleEvent(CardEvents.RelationCreated)
}
</script>
<RelationSetting {_classes} exclude={[]} />
<RelationSetting {_classes} exclude={[]} on:create={createHandler} />
@@ -13,7 +13,7 @@
// limitations under the License.
-->
<script lang="ts">
import { MasterTag } from '@hcengineering/card'
import { CardEvents, MasterTag } from '@hcengineering/card'
import core, { Association, Class, Doc, Ref } from '@hcengineering/core'
import { IntlString } from '@hcengineering/platform'
import { createQuery, getClient } from '@hcengineering/presentation'
@@ -22,6 +22,7 @@
import { ButtonIcon, Icon, IconAdd, Label, showPopup } from '@hcengineering/ui'
import { onDestroy } from 'svelte'
import CreateRelation from './CreateRelation.svelte'
import { Analytics } from '@hcengineering/analytics'
export let masterTag: MasterTag
@@ -48,9 +49,18 @@
}
function addRelation (): void {
showPopup(CreateRelation, {
aClass: masterTag._id
})
showPopup(
CreateRelation,
{
aClass: masterTag._id
},
undefined,
(res) => {
if (res !== undefined) {
Analytics.handleEvent(CardEvents.RelationCreated)
}
}
)
}
const handleSelect = (association: Association): void => {
+2 -1
View File
@@ -44,7 +44,8 @@ export default mergeIds(cardId, card, {
},
viewlet: {
CardTable: '' as Ref<Viewlet>,
CardList: '' as Ref<Viewlet>
CardList: '' as Ref<Viewlet>,
CardChildList: '' as Ref<Viewlet>
},
string: {
CreateMasterTag: '' as IntlString,
+3 -2
View File
@@ -1,6 +1,7 @@
export enum CardEvents {
NewCardButtonClicked = 'card.card.NewButtonClicked',
MasterTagRemoved = 'card.card.MasterTagRemoved',
TypeCreated = 'card.card.TypeCreated',
TagCreated = 'card.card.TagCreated',
RelationCreated = 'card.card.RelationCreated',
CardCreated = 'card.card.Created',
CardOpened = 'card.card.Opened'
}
@@ -138,6 +138,7 @@
nameA,
nameB
})
dispatch('create')
dispatch('close')
}
}
@@ -102,14 +102,14 @@
if (classBRef === undefined || classARef === undefined) {
return
}
await client.createDoc(core.class.Association, core.space.Model, {
const _id = await client.createDoc(core.class.Association, core.space.Model, {
classA: classARef,
classB: classBRef,
type: mode,
nameA,
nameB
})
dispatch('close')
dispatch('close', _id)
}
const items: DropdownIntlItem[] = [
@@ -153,7 +153,8 @@
{exclude}
{_classes}
association={selected}
on:close={() => {
on:create
on:close={(res) => {
selected = undefined
}}
/>
@@ -4,6 +4,7 @@
import { resolvedLocationStore, Switcher } from '@hcengineering/ui'
import view, { Viewlet, ViewletPreference } from '@hcengineering/view'
import core, { DocumentQuery, Ref, WithLookup } from '@hcengineering/core'
import { deepEqual } from 'fast-equals'
import { createQuery } from '@hcengineering/presentation'
export let viewlet: WithLookup<Viewlet> | undefined
@@ -49,7 +50,7 @@
) {
if (viewlets == null || viewlets.length === 0) return
const newViewlet = viewlets.find((viewlet) => viewlet?._id === activeViewlet[key]) ?? viewlets[0]
if (viewlet?._id !== newViewlet?._id) {
if (viewlet?._id !== newViewlet?._id || !deepEqual(viewlet.config, newViewlet.config)) {
viewlet = newViewlet
setActiveViewletId(newViewlet._id)
dispatch('viewlet', viewlet)
+22 -3
View File
@@ -35,9 +35,19 @@ async function OnAttribute (ctx: TxCreateDoc<AnyAttribute>[], control: TriggerCo
const desc = control.hierarchy.getDescendants(attr.attributeOf)
const res: Tx[] = []
for (const des of desc) {
const viewlets = control.modelDb.findAllSync(view.class.Viewlet, { attachTo: des })
const viewlets = control.modelDb.findAllSync(view.class.Viewlet, { attachTo: des, variant: { $exists: false } })
for (const viewlet of viewlets) {
viewlet.config.push(attr.name)
// let push it after grow for the list
if (viewlet.descriptor === view.viewlet.List) {
const index = viewlet.config.findIndex((p) => typeof p !== 'string' && p.displayProps?.grow === true)
if (index !== -1) {
viewlet.config.splice(index + 1, 0, attr.name)
} else {
viewlet.config.push(attr.name)
}
} else {
viewlet.config.push(attr.name)
}
res.push(
control.txFactory.createTxUpdateDoc(viewlet._class, viewlet.space, viewlet._id, {
config: viewlet.config
@@ -45,7 +55,16 @@ async function OnAttribute (ctx: TxCreateDoc<AnyAttribute>[], control: TriggerCo
)
const prefs = await control.findAll(control.ctx, view.class.ViewletPreference, { attachedTo: viewlet._id })
for (const pref of prefs) {
pref.config.push(attr.name)
if (viewlet.descriptor === view.viewlet.List) {
const index = viewlet.config.findIndex((p) => typeof p !== 'string' && p.displayProps?.grow === true)
if (index !== -1) {
viewlet.config.splice(index + 1, 0, attr.name)
} else {
viewlet.config.push(attr.name)
}
} else {
viewlet.config.push(attr.name)
}
res.push(
control.txFactory.createTxUpdateDoc(pref._class, pref.space, pref._id, {
config: pref.config