Card fixes (#8432)

Signed-off-by: Denis Bykhov <bykhov.denis@gmail.com>
This commit is contained in:
Denis Bykhov 2025-04-02 16:15:09 +05:00 committed by GitHub
parent 1d760d8615
commit 7e4d1d0e56
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
17 changed files with 164 additions and 14 deletions

View File

@ -224,6 +224,10 @@ export function createModel (builder: Builder): void {
inlineEditor: card.component.CardEditor
})
builder.mixin(card.class.Card, core.class.Class, view.mixin.ArrayEditor, {
inlineEditor: card.component.CardArrayEditor
})
createAction(
builder,
{
@ -319,7 +323,8 @@ export function createModel (builder: Builder): void {
})
builder.mixin(card.class.Card, core.class.Class, view.mixin.AttributePresenter, {
presenter: card.component.CardRefPresenter
presenter: card.component.CardRefPresenter,
arrayPresenter: card.component.CardArrayEditor
})
builder.mixin(card.class.Card, core.class.Class, activity.mixin.ActivityDoc, {})

View File

@ -51,6 +51,7 @@ export function createModel (builder: Builder): void {
builder.createDoc(serverCore.class.Trigger, core.space.Model, {
trigger: serverCard.trigger.OnMasterTagRemove,
isAsync: true,
txMatch: {
_class: core.class.TxUpdateDoc,
objectClass: card.class.MasterTag,

View File

@ -0,0 +1,115 @@
<!--
// Copyright © 2025 Hardcore Engineering Inc.
//
// Licensed under the Eclipse Public License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License. You may
// obtain a copy of the License at https://www.eclipse.org/legal/epl-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
//
// See the License for the specific language governing permissions and
// limitations under the License.
-->
<script lang="ts">
import { Card, MasterTag } from '@hcengineering/card'
import { AnyAttribute, ArrOf, Ref, RefTo } from '@hcengineering/core'
import { IntlString } from '@hcengineering/platform'
import { createQuery, getClient } from '@hcengineering/presentation'
import {
Button,
ButtonKind,
ButtonSize,
eventToHTMLElement,
IconWithEmoji,
Label,
showPopup
} from '@hcengineering/ui'
import view from '@hcengineering/view'
import { createEventDispatcher } from 'svelte'
import card from '../plugin'
import CardsPopup from './CardsPopup.svelte'
export let value: Ref<Card>[] | undefined
export let readonly: boolean = false
export let label: IntlString | undefined
export let onChange: ((value: any) => void) | undefined
export let attribute: AnyAttribute
export let focusIndex: number | undefined = undefined
export let kind: ButtonKind = 'ghost'
export let size: ButtonSize = 'small'
export let justify: 'left' | 'center' = 'left'
export let width: string | undefined = 'min-content'
const dispatch = createEventDispatcher()
const client = getClient()
const hierarchy = client.getHierarchy()
$: _class = ((attribute?.type as ArrOf<RefTo<Card>>)?.of as RefTo<Card>)?.to
const handleOpen = (event: MouseEvent): void => {
if (onChange === undefined) {
return
}
event.stopPropagation()
if (readonly) {
return
}
showPopup(
CardsPopup,
{ selectedObjects: value, _class, multiSelect: true },
eventToHTMLElement(event),
undefined,
change
)
}
const change = (value: Ref<Card>[]): void => {
onChange?.(value)
dispatch('change', value)
}
let docs: Card[] = []
const query = createQuery()
$: query.query(card.class.Card, { _id: { $in: value ?? [] } }, (res) => {
docs = res
})
$: clazz = _class !== undefined ? (hierarchy.findClass(_class) as MasterTag) : undefined
$: icon = clazz?.icon === view.ids.IconWithEmoji ? IconWithEmoji : clazz?.icon
$: iconProps = clazz?.icon === view.ids.IconWithEmoji ? { icon: clazz?.color } : {}
$: emptyLabel = label ?? clazz?.label ?? card.string.Card
</script>
<Button
showTooltip={!readonly ? { label } : undefined}
{justify}
{focusIndex}
{width}
{size}
{icon}
{iconProps}
{kind}
disabled={readonly}
on:click={handleOpen}
>
<div slot="content" class="overflow-label">
{#if docs.length === 1}
{docs[0].title}
{:else if docs.length > 1}
<div class="lower">
{docs.length}
<Label label={card.string.Cards} />
</div>
{:else}
<Label label={emptyLabel} />
{/if}
</div>
</Button>

View File

@ -22,6 +22,7 @@
export let _class: Ref<Class<Card>>
export let selected: Ref<Card> | undefined
export let selectedObjects: Ref<Card>[] | undefined
export let multiSelect: boolean = false
export let allowDeselect: boolean = true
export let titleDeselect: IntlString | undefined = undefined
@ -36,6 +37,7 @@
<ObjectPopup
{_class}
{selected}
{selectedObjects}
{multiSelect}
{allowDeselect}
{titleDeselect}

View File

@ -84,6 +84,7 @@
const _class = hierarchy.getClass(_id)
if (_class.label === undefined) continue
if (_class.kind !== ClassifierKind.CLASS) continue
if ((_class as MasterTag).removed === true) continue
added.add(_id)
const descendants = hierarchy.getDescendants(_id)
const toAdd: Class<Doc>[] = []

View File

@ -14,7 +14,7 @@
-->
<script lang="ts">
import { MasterTag } from '@hcengineering/card'
import { Ref, isOwnerOrMaintainer } from '@hcengineering/core'
import { Ref } from '@hcengineering/core'
import { createQuery } from '@hcengineering/presentation'
import { ClassAttributes, ClassHierarchy, clearSettingsStore } from '@hcengineering/setting-resources'
import {

View File

@ -4,14 +4,19 @@
import { getClient } from '@hcengineering/presentation'
import { CreateRelation } from '@hcengineering/setting-resources'
import card from '../../plugin'
import { MasterTag } from '@hcengineering/card'
export let aClass: Ref<Class<Doc>> | undefined = undefined
const client = getClient()
const hierarchy = client.getHierarchy()
const _classes = [...hierarchy.getDescendants(card.class.Card), contact.class.Contact].filter(
(c) => c !== card.class.Card
)
const _classes = [...hierarchy.getDescendants(card.class.Card), contact.class.Contact].filter((c) => {
if (c === card.class.Card) return false
const cl = hierarchy.getClass(c)
if (cl._class !== card.class.MasterTag) return true
if ((cl as MasterTag).removed === true) return false
return true
})
</script>
<CreateRelation {aClass} {_classes} exclude={[]} on:close />

View File

@ -4,13 +4,17 @@
import contact from '@hcengineering/contact'
import card from '../../plugin'
import { Analytics } from '@hcengineering/analytics'
import { CardEvents } from '@hcengineering/card'
import { CardEvents, MasterTag } 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
)
const _classes = [...hierarchy.getDescendants(card.class.Card), contact.class.Contact].filter((c) => {
if (c === card.class.Card) return false
const cl = hierarchy.getClass(c)
if (cl._class !== card.class.MasterTag) return true
if ((cl as MasterTag).removed === true) return false
return true
})
function createHandler (): void {
Analytics.handleEvent(CardEvents.RelationCreated)

View File

@ -39,6 +39,7 @@ import RelationSetting from './components/settings/RelationSetting.svelte'
import CardEditor from './components/CardEditor.svelte'
import CardRefPresenter from './components/CardRefPresenter.svelte'
import ChangeType from './components/ChangeType.svelte'
import CardArrayEditor from './components/CardArrayEditor.svelte'
export { default as CardSelector } from './components/CardSelector.svelte'
@ -61,7 +62,8 @@ export default async (): Promise<Resources> => ({
RelationSetting,
CardEditor,
CardRefPresenter,
ChangeType
ChangeType,
CardArrayEditor
},
completion: {
CardQuery: queryCard

View File

@ -39,7 +39,8 @@ export default mergeIds(cardId, card, {
RelationSetting: '' as AnyComponent,
CardEditor: '' as AnyComponent,
CardRefPresenter: '' as AnyComponent,
ChangeType: '' as AnyComponent
ChangeType: '' as AnyComponent,
CardArrayEditor: '' as AnyComponent
},
completion: {
CardQuery: '' as Resource<ObjectSearchFactory>,

View File

@ -139,7 +139,11 @@
selected = event.detail as AnyAttribute
if (selected?._id != null) {
const exist = (await client.findOne(selected.attributeOf, { [selected.name]: { $exists: true } })) !== undefined
$settingsStore = { id: selected._id, component: EditAttribute, props: { attribute: selected, exist, disabled } }
$settingsStore = {
id: selected._id,
component: EditAttribute,
props: { attribute: selected, exist, disabled, isCard }
}
}
}
onDestroy(() => {

View File

@ -38,6 +38,7 @@
export let exist: boolean
export let disabled: boolean = true
export let noTopIndent: boolean = false
export let isCard: boolean = false
let name: string
let type: Type<PropertyType> | undefined = attribute.type
@ -201,6 +202,7 @@
<Component
{is}
props={{
isCard,
type,
defaultValue,
editable: !exist && !disabled,

View File

@ -46,7 +46,9 @@
.reduce((a, b) => a.concat(b))
.filter((p) => p !== card.class.Card)
)
const excluded = new Set()
// exclude removed card types
const removedTypes = client.getModel().findAllSync(card.class.MasterTag, { removed: true })
const excluded = new Set(removedTypes.map((p) => p._id))
for (const _class of exclude) {
const desc = hierarchy.getDescendants(_class)
for (const _id of desc) {

View File

@ -452,6 +452,7 @@
this={attribute.presenter}
value={getValue(attribute, object)}
onChange={getOnChange(object, attribute)}
attribute={attribute.attribute}
{...joinProps(attribute, object, readonly || $restrictionStore.readonly)}
/>
</div>

View File

@ -13,7 +13,7 @@
// limitations under the License.
-->
<script lang="ts">
import core, { AnyAttribute, ArrOf, Class, Doc, Ref, RefTo, Space, Type } from '@hcengineering/core'
import core, { AnyAttribute, ArrOf, Class, ClassifierKind, Doc, Ref, RefTo, Space, Type } from '@hcengineering/core'
import { getClient } from '@hcengineering/presentation'
import { Label, Scroller, Submenu, closePopup, closeTooltip, resizeObserver, showPopup } from '@hcengineering/ui'
import { ClassFilters, Filter, KeyFilter, KeyFilterPreset, ViewOptions } from '@hcengineering/view'
@ -145,6 +145,8 @@
const desc = hierarchy.getDescendants(_class)
for (const d of desc) {
const cl = hierarchy.findClass(d)
if (cl?.kind !== ClassifierKind.MIXIN) continue
const extra = hierarchy.getOwnAttributes(d)
for (const [k, v] of extra) {
if (!allAttributes.has(k)) {

View File

@ -56,6 +56,7 @@
kind={'list'}
{compactMode}
label={attributeModel.label}
attribute={attributeModel.attribute}
{...joinProps(attributeModel, docObject, props)}
on:resize={translateSize}
/>
@ -68,6 +69,7 @@
kind={'list'}
{compactMode}
label={attributeModel.label}
attribute={attributeModel.attribute}
{...joinProps(attributeModel, docObject, props)}
on:resize={translateSize}
/>

View File

@ -111,6 +111,7 @@ async function OnAttributeRemove (ctx: TxRemoveDoc<AnyAttribute>[], control: Tri
async function OnMasterTagRemove (ctx: TxUpdateDoc<MasterTag>[], control: TriggerControl): Promise<Tx[]> {
const updateTx = ctx[0]
if (updateTx.space === core.space.DerivedTx) return []
if (updateTx.operations.removed !== true) return []
const res: Tx[] = []
const desc = control.hierarchy.getDescendants(updateTx.objectId)