mirror of
https://github.com/hcengineering/platform.git
synced 2026-09-10 11:47:42 +02:00
Cards fixes (#8003)
Signed-off-by: Denis Bykhov <bykhov.denis@gmail.com>
This commit is contained in:
@@ -23,20 +23,24 @@
|
||||
DropdownIntlItem,
|
||||
DropdownLabelsIntl,
|
||||
EditBox,
|
||||
Label
|
||||
Label,
|
||||
NestedDropdown
|
||||
} from '@hcengineering/ui'
|
||||
import view from '@hcengineering/view'
|
||||
import card from '@hcengineering/card'
|
||||
import { createEventDispatcher } from 'svelte'
|
||||
import setting from '../plugin'
|
||||
|
||||
export let association: Association | Data<Association>
|
||||
export let kind: ButtonKind = 'regular'
|
||||
export let size: ButtonSize = 'medium'
|
||||
export let _classes: Ref<Class<Doc>>[] = [core.class.Doc]
|
||||
export let exclude: Ref<Class<Doc>>[] = [card.class.Card]
|
||||
|
||||
const client = getClient()
|
||||
const hierarchy = client.getHierarchy()
|
||||
|
||||
const descendants = hierarchy.getDescendants(core.class.Doc)
|
||||
const descendants = new Set(_classes.map((p) => hierarchy.getDescendants(p)).reduce((a, b) => a.concat(b)))
|
||||
const viewlets = new Set(
|
||||
client
|
||||
.getModel()
|
||||
@@ -44,43 +48,69 @@
|
||||
.map((p) => p.attachTo)
|
||||
)
|
||||
|
||||
function filterClasses (descendants: Ref<Class<Doc>>[], viewlets: Set<Ref<Class<Doc>>>): DropdownIntlItem[] {
|
||||
const result = []
|
||||
function filterClasses (
|
||||
descendants: Set<Ref<Class<Doc>>>,
|
||||
viewlets: Set<Ref<Class<Doc>>>,
|
||||
exclude: Ref<Class<Doc>>[]
|
||||
): [DropdownIntlItem, DropdownIntlItem[]][] {
|
||||
const ignore = new Set<Ref<Class<Doc>>>()
|
||||
const added = new Set<Ref<Class<Doc>>>()
|
||||
for (const _id of descendants) {
|
||||
if (added.has(_id)) continue
|
||||
const _class = hierarchy.getClass(_id)
|
||||
if (_class.extends !== undefined && added.has(_class.extends)) {
|
||||
added.add(_id)
|
||||
result.push({ id: _id, label: _class.label })
|
||||
const base = new Map<Ref<Class<Doc>>, Class<Doc>[]>()
|
||||
for (const excluded of exclude) {
|
||||
const desc = hierarchy.getDescendants(excluded)
|
||||
for (const _id of desc) {
|
||||
ignore.add(_id)
|
||||
}
|
||||
}
|
||||
for (const _id of descendants) {
|
||||
if (added.has(_id) || ignore.has(_id)) continue
|
||||
const _class = hierarchy.getClass(_id)
|
||||
if (_class.label === undefined) continue
|
||||
if (viewlets.has(hierarchy.getBaseClass(_id))) {
|
||||
added.add(_id)
|
||||
result.push({ id: _id, label: _class.label })
|
||||
const descendants = hierarchy.getDescendants(_id)
|
||||
const toAdd: Class<Doc>[] = []
|
||||
for (const desc of descendants) {
|
||||
if (added.has(desc) || ignore.has(desc)) continue
|
||||
const _class = hierarchy.getClass(desc)
|
||||
if (_class.label === undefined) continue
|
||||
added.add(desc)
|
||||
toAdd.push(_class)
|
||||
}
|
||||
base.set(_id, toAdd)
|
||||
}
|
||||
}
|
||||
const result: [DropdownIntlItem, DropdownIntlItem[]][] = []
|
||||
for (const [key, value] of base) {
|
||||
try {
|
||||
const clazz = hierarchy.getClass(key)
|
||||
result.push([{ id: key, label: clazz.label }, value.map((it) => ({ id: it._id, label: it.label }))])
|
||||
} catch {}
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
const classes = filterClasses(descendants, viewlets)
|
||||
const classes = filterClasses(descendants, viewlets, exclude)
|
||||
|
||||
let classA = classes.find((p) => p.id === association.classA) ?? classes[0]
|
||||
let classB = classes.find((p) => p.id === association.classB) ?? classes[0]
|
||||
let classARef = classA?.id
|
||||
let classBRef = classB?.id
|
||||
let classARef: Ref<Class<Doc>> | undefined = undefined
|
||||
let classBRef: Ref<Class<Doc>> | undefined = undefined
|
||||
let nameA = association.nameA
|
||||
let nameB = association.nameB
|
||||
|
||||
$: classA = isEmptyClass(classARef) ? undefined : hierarchy.getClass(classARef as Ref<Class<Doc>>)
|
||||
$: classB = isEmptyClass(classBRef) ? undefined : hierarchy.getClass(classBRef as Ref<Class<Doc>>)
|
||||
|
||||
function isEmptyClass (ref: Ref<Class<Doc>> | undefined): boolean {
|
||||
return ref === undefined || ref !== ''
|
||||
}
|
||||
|
||||
$: editable = !isAssociation(association)
|
||||
|
||||
$: fill(association)
|
||||
|
||||
function fill (association: Association | Data<Association>): void {
|
||||
classA = classes.find((p) => p.id === association.classA) ?? classes[0]
|
||||
classB = classes.find((p) => p.id === association.classB) ?? classes[0]
|
||||
classBRef = classB?.id
|
||||
classARef = classA?.id
|
||||
classBRef = !isEmptyClass(association.classB) ? association.classB : undefined
|
||||
classARef = !isEmptyClass(association.classA) ? association.classA : undefined
|
||||
nameA = association.nameA
|
||||
nameB = association.nameB
|
||||
}
|
||||
@@ -102,8 +132,8 @@
|
||||
})
|
||||
} else {
|
||||
await client.createDoc(core.class.Association, core.space.Model, {
|
||||
classA: classARef as Ref<Class<Doc>>,
|
||||
classB: classBRef as Ref<Class<Doc>>,
|
||||
classA: classARef,
|
||||
classB: classBRef,
|
||||
type: mode,
|
||||
nameA,
|
||||
nameB
|
||||
@@ -143,13 +173,11 @@
|
||||
</div>
|
||||
<div>
|
||||
{#if editable}
|
||||
<DropdownLabelsIntl
|
||||
label={core.string.Class}
|
||||
<NestedDropdown
|
||||
items={classes}
|
||||
width="8rem"
|
||||
bind:selected={classARef}
|
||||
{kind}
|
||||
{size}
|
||||
on:selected={(e) => {
|
||||
classARef = e.detail
|
||||
}}
|
||||
/>
|
||||
{:else if classA}
|
||||
<Label label={classA.label} />
|
||||
@@ -184,13 +212,11 @@
|
||||
</div>
|
||||
<div>
|
||||
{#if editable}
|
||||
<DropdownLabelsIntl
|
||||
label={core.string.Class}
|
||||
<NestedDropdown
|
||||
items={classes}
|
||||
width="8rem"
|
||||
bind:selected={classBRef}
|
||||
{kind}
|
||||
{size}
|
||||
on:selected={(e) => {
|
||||
classBRef = e.detail
|
||||
}}
|
||||
/>
|
||||
{:else if classB}
|
||||
<Label label={classB.label} />
|
||||
|
||||
@@ -53,11 +53,6 @@
|
||||
<button class="hulyTableAttr-content__row-dragMenu" on:click|stopPropagation={clickMore}>
|
||||
<IconMoreV2 size={'small'} />
|
||||
</button>
|
||||
{#if attribute.isCustom}
|
||||
<div class="hulyChip-item font-medium-12">
|
||||
<Label label={settings.string.Custom} />
|
||||
</div>
|
||||
{/if}
|
||||
{#if attribute.icon !== undefined}
|
||||
<div class="hulyTableAttr-content__row-icon">
|
||||
<Icon icon={attribute.icon} size={'small'} />
|
||||
|
||||
@@ -13,7 +13,7 @@
|
||||
// limitations under the License.
|
||||
-->
|
||||
<script lang="ts">
|
||||
import core, { AnyAttribute, Class, ClassifierKind, Doc, Ref, Space } from '@hcengineering/core'
|
||||
import core, { AnyAttribute, Class, Doc, Ref, Space } from '@hcengineering/core'
|
||||
import { IntlString } from '@hcengineering/platform'
|
||||
import { createQuery, getClient } from '@hcengineering/presentation'
|
||||
import {
|
||||
@@ -22,28 +22,29 @@
|
||||
ButtonIcon,
|
||||
IconAdd,
|
||||
IconEdit,
|
||||
Label,
|
||||
getEventPositionElement,
|
||||
showPopup,
|
||||
IconSettings,
|
||||
Label,
|
||||
ModernButton,
|
||||
deviceWidths,
|
||||
getEventPositionElement,
|
||||
resizeObserver,
|
||||
deviceWidths
|
||||
showPopup
|
||||
} from '@hcengineering/ui'
|
||||
import { ObjectPresenter } from '@hcengineering/view-resources'
|
||||
import { onDestroy } from 'svelte'
|
||||
import settings from '../plugin'
|
||||
import CreateAttribute from './CreateAttribute.svelte'
|
||||
import { clearSettingsStore, settingsStore } from '../store'
|
||||
import ClassAttributesList from './ClassAttributesList.svelte'
|
||||
import CreateAttribute from './CreateAttribute.svelte'
|
||||
import EditAttribute from './EditAttribute.svelte'
|
||||
import EditClassLabel from './EditClassLabel.svelte'
|
||||
import { settingsStore, clearSettingsStore } from '../store'
|
||||
import TypesPopup from './typeEditors/TypesPopup.svelte'
|
||||
import { onDestroy } from 'svelte'
|
||||
|
||||
export let _class: Ref<Class<Doc>>
|
||||
export let ofClass: Ref<Class<Doc>> | undefined = undefined
|
||||
export let showHierarchy: boolean = false
|
||||
export let showTitle: boolean = !showHierarchy
|
||||
export let showHeader: boolean = true
|
||||
export let disabled: boolean = true
|
||||
export let attributeMapper:
|
||||
| {
|
||||
@@ -101,16 +102,18 @@
|
||||
showPopup(EditClassLabel, { clazz }, getEventPositionElement(evt))
|
||||
}
|
||||
|
||||
const classUpdated = (_clazz: Ref<Class<Doc>>): void => {
|
||||
const classUpdated = (_clazz: Ref<Class<Doc>>, to: Ref<Class<Doc>>): void => {
|
||||
selected = undefined
|
||||
classes = client
|
||||
.getHierarchy()
|
||||
const h = client.getHierarchy()
|
||||
const toAncestors = new Set(h.getAncestors(to))
|
||||
classes = h
|
||||
.getAncestors(_class)
|
||||
.map((it) => client.getHierarchy().getClass(it))
|
||||
.filter((it) => {
|
||||
return (
|
||||
!it.hidden &&
|
||||
it.label !== undefined &&
|
||||
!toAncestors.has(it._id) &&
|
||||
it._id !== core.class.Doc &&
|
||||
it._id !== core.class.AttachedDoc &&
|
||||
it._id !== _class
|
||||
@@ -118,7 +121,7 @@
|
||||
})
|
||||
clazzHierarchy = client.getHierarchy().getClass(_class)
|
||||
}
|
||||
$: classUpdated(_class)
|
||||
$: classUpdated(_class, ofClass ?? core.class.Doc)
|
||||
|
||||
settingsStore.subscribe((value) => {
|
||||
if ((value.id === undefined && selected !== undefined) || (value.id !== undefined && value.id !== selected?._id)) {
|
||||
@@ -163,14 +166,18 @@
|
||||
}}
|
||||
>
|
||||
<div class="hulyTableAttr-header font-medium-12" class:withButton={showHierarchy}>
|
||||
{#if showHierarchy}
|
||||
<ModernButton icon={IconSettings} kind={'secondary'} size={'small'} {disabled} hasMenu>
|
||||
<Label label={settings.string.ClassColon} />
|
||||
<ObjectPresenter _class={clazzHierarchy._class} objectId={clazzHierarchy._id} value={clazzHierarchy} />
|
||||
</ModernButton>
|
||||
{#if showHeader}
|
||||
{#if showHierarchy}
|
||||
<ModernButton icon={IconSettings} kind={'secondary'} size={'small'} {disabled} hasMenu>
|
||||
<Label label={settings.string.ClassColon} />
|
||||
<ObjectPresenter _class={clazzHierarchy._class} objectId={clazzHierarchy._id} value={clazzHierarchy} />
|
||||
</ModernButton>
|
||||
{:else}
|
||||
<IconSettings size={'small'} />
|
||||
<span><Label label={settings.string.ClassProperties} /></span>
|
||||
{/if}
|
||||
{:else}
|
||||
<IconSettings size={'small'} />
|
||||
<span><Label label={settings.string.ClassProperties} /></span>
|
||||
<div></div>
|
||||
{/if}
|
||||
<ButtonIcon
|
||||
kind={'primary'}
|
||||
|
||||
@@ -138,7 +138,6 @@
|
||||
/>
|
||||
<ModernEditbox bind:value={name} label={core.string.Name} size={'large'} kind={'ghost'} autoFocus />
|
||||
</div>
|
||||
<ModernEditbox bind:value={name} label={core.string.Name} size={'large'} kind={'ghost'} autoFocus />
|
||||
</div>
|
||||
<div class="hulyModal-content__settingsSet">
|
||||
<div class="hulyModal-content__settingsSet-line">
|
||||
|
||||
@@ -0,0 +1,175 @@
|
||||
<!--
|
||||
// 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 cardPlugin from '@hcengineering/card'
|
||||
import core, { Class, Doc, Ref } from '@hcengineering/core'
|
||||
import { getEmbeddedLabel } from '@hcengineering/platform'
|
||||
import presentation, { Card, getClient } from '@hcengineering/presentation'
|
||||
import { DropdownIntlItem, DropdownLabelsIntl, EditBox, Label } from '@hcengineering/ui'
|
||||
import NestedDropdown from '@hcengineering/ui/src/components/NestedDropdown.svelte'
|
||||
import view from '@hcengineering/view'
|
||||
import { createEventDispatcher } from 'svelte'
|
||||
import setting from '../plugin'
|
||||
|
||||
export let _classes: Ref<Class<Doc>>[] = [core.class.Doc]
|
||||
export let exclude: Ref<Class<Doc>>[] = [cardPlugin.class.Card]
|
||||
|
||||
const dispatch = createEventDispatcher()
|
||||
|
||||
const client = getClient()
|
||||
const hierarchy = client.getHierarchy()
|
||||
|
||||
const descendants = new Set(_classes.map((p) => hierarchy.getDescendants(p)).reduce((a, b) => a.concat(b)))
|
||||
const viewlets = new Set(
|
||||
client
|
||||
.getModel()
|
||||
.findAllSync(view.class.Viewlet, { descriptor: view.viewlet.Table })
|
||||
.map((p) => p.attachTo)
|
||||
)
|
||||
|
||||
function filterClasses (
|
||||
descendants: Set<Ref<Class<Doc>>>,
|
||||
viewlets: Set<Ref<Class<Doc>>>,
|
||||
exclude: Ref<Class<Doc>>[]
|
||||
): [DropdownIntlItem, DropdownIntlItem[]][] {
|
||||
const ignore = new Set<Ref<Class<Doc>>>()
|
||||
const added = new Set<Ref<Class<Doc>>>()
|
||||
const base = new Map<Ref<Class<Doc>>, Class<Doc>[]>()
|
||||
for (const excluded of exclude) {
|
||||
const desc = hierarchy.getDescendants(excluded)
|
||||
for (const _id of desc) {
|
||||
ignore.add(_id)
|
||||
}
|
||||
}
|
||||
for (const _id of descendants) {
|
||||
if (added.has(_id) || ignore.has(_id)) continue
|
||||
const _class = hierarchy.getClass(_id)
|
||||
if (_class.label === undefined) continue
|
||||
if (viewlets.has(hierarchy.getBaseClass(_id))) {
|
||||
added.add(_id)
|
||||
const descendants = hierarchy.getDescendants(_id)
|
||||
const toAdd: Class<Doc>[] = []
|
||||
for (const desc of descendants) {
|
||||
if (added.has(desc) || ignore.has(desc)) continue
|
||||
const _class = hierarchy.getClass(desc)
|
||||
if (_class.label === undefined) continue
|
||||
added.add(desc)
|
||||
toAdd.push(_class)
|
||||
}
|
||||
base.set(_id, toAdd)
|
||||
}
|
||||
}
|
||||
const result: [DropdownIntlItem, DropdownIntlItem[]][] = []
|
||||
for (const [key, value] of base) {
|
||||
try {
|
||||
const clazz = hierarchy.getClass(key)
|
||||
result.push([{ id: key, label: clazz.label }, value.map((it) => ({ id: it._id, label: it.label }))])
|
||||
} catch {}
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
const classes = filterClasses(descendants, viewlets, exclude)
|
||||
|
||||
let classARef: Ref<Class<Doc>> | undefined = undefined
|
||||
let classBRef: Ref<Class<Doc>> | undefined = undefined
|
||||
let nameA: string = ''
|
||||
let nameB: string = ''
|
||||
|
||||
async function save (): Promise<void> {
|
||||
if (classBRef === undefined || classARef === undefined) {
|
||||
return
|
||||
}
|
||||
await client.createDoc(core.class.Association, core.space.Model, {
|
||||
classA: classARef,
|
||||
classB: classBRef,
|
||||
type: mode,
|
||||
nameA,
|
||||
nameB
|
||||
})
|
||||
dispatch('close')
|
||||
}
|
||||
|
||||
const items: DropdownIntlItem[] = [
|
||||
{
|
||||
id: '1:1',
|
||||
label: getEmbeddedLabel('1:1')
|
||||
},
|
||||
{
|
||||
id: '1:N',
|
||||
label: getEmbeddedLabel('1:N')
|
||||
},
|
||||
{
|
||||
id: 'N:N',
|
||||
label: getEmbeddedLabel('N:N')
|
||||
}
|
||||
]
|
||||
|
||||
let mode: '1:1' | '1:N' | 'N:N' = 'N:N' as '1:1' | '1:N' | 'N:N'
|
||||
</script>
|
||||
|
||||
<Card
|
||||
label={core.string.AddRelation}
|
||||
okLabel={presentation.string.Create}
|
||||
canSave={nameB.trim() !== '' && nameA.trim() !== '' && classARef !== undefined && classBRef !== undefined}
|
||||
okAction={save}
|
||||
on:close
|
||||
>
|
||||
<div class="flex-between flex-gap-4">
|
||||
<div class="flex-col p-4 flex-gap-2">
|
||||
<div class="flex-col-center">A</div>
|
||||
<div>
|
||||
<EditBox bind:value={nameA} placeholder={core.string.Name} kind={'default'} />
|
||||
</div>
|
||||
<div>
|
||||
<NestedDropdown
|
||||
items={classes}
|
||||
on:selected={(e) => {
|
||||
classARef = e.detail
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="flex-col p-4 flex-gap-2">
|
||||
<span class="label">
|
||||
<Label label={setting.string.Type} />
|
||||
</span>
|
||||
<DropdownLabelsIntl
|
||||
selected={mode}
|
||||
{items}
|
||||
label={setting.string.Type}
|
||||
on:selected={(res) => {
|
||||
mode = res.detail
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div class="flex-col p-4 flex-gap-2">
|
||||
<div class="flex-col-center">B</div>
|
||||
<div>
|
||||
<EditBox bind:value={nameB} placeholder={core.string.Name} kind={'default'} />
|
||||
</div>
|
||||
<div>
|
||||
<NestedDropdown
|
||||
items={classes}
|
||||
on:selected={(e) => {
|
||||
classBRef = e.detail
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</Card>
|
||||
@@ -29,10 +29,10 @@
|
||||
showPopup,
|
||||
themeStore
|
||||
} from '@hcengineering/ui'
|
||||
import { IconPicker } from '@hcengineering/view-resources'
|
||||
import view from '@hcengineering/view-resources/src/plugin'
|
||||
import setting from '../plugin'
|
||||
import { clearSettingsStore } from '../store'
|
||||
import { IconPicker, iconsLibrary } from '@hcengineering/view-resources'
|
||||
|
||||
export let attribute: AnyAttribute
|
||||
export let exist: boolean
|
||||
@@ -166,11 +166,6 @@
|
||||
{/if}
|
||||
</svelte:fragment>
|
||||
<div class="hulyModal-content__titleGroup">
|
||||
{#if attribute.isCustom}
|
||||
<div class="hulyChip-item font-medium-12">
|
||||
<Label label={setting.string.Custom} />
|
||||
</div>
|
||||
{/if}
|
||||
<div class="flex items-center">
|
||||
<ButtonIcon
|
||||
icon={icon ?? setting.icon.Enums}
|
||||
|
||||
@@ -0,0 +1,112 @@
|
||||
<!--
|
||||
// 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 core, { Association } from '@hcengineering/core'
|
||||
import { getEmbeddedLabel, IntlString } from '@hcengineering/platform'
|
||||
import presentation, { getClient, MessageBox } from '@hcengineering/presentation'
|
||||
import { Button, DropdownIntlItem, EditBox, IconDelete, Label, Modal, showPopup } from '@hcengineering/ui'
|
||||
import view from '@hcengineering/view'
|
||||
import setting from '../plugin'
|
||||
import { clearSettingsStore } from '../store'
|
||||
|
||||
export let association: Association
|
||||
|
||||
const client = getClient()
|
||||
const hierarchy = client.getHierarchy()
|
||||
|
||||
const classA = hierarchy.getClass(association.classA)
|
||||
const classB = hierarchy.getClass(association.classB)
|
||||
let nameA = association.nameA
|
||||
let nameB = association.nameB
|
||||
|
||||
async function save (): Promise<void> {
|
||||
await client.diffUpdate(association, {
|
||||
nameA,
|
||||
nameB
|
||||
})
|
||||
clearSettingsStore()
|
||||
}
|
||||
|
||||
const items: DropdownIntlItem[] = [
|
||||
{
|
||||
id: '1:1',
|
||||
label: getEmbeddedLabel('1:1')
|
||||
},
|
||||
{
|
||||
id: '1:N',
|
||||
label: getEmbeddedLabel('1:N')
|
||||
},
|
||||
{
|
||||
id: 'N:N',
|
||||
label: getEmbeddedLabel('N:N')
|
||||
}
|
||||
]
|
||||
|
||||
const label = items.find((item) => item.id === association?.type)?.label ?? ('' as IntlString)
|
||||
|
||||
async function remove (): Promise<void> {
|
||||
showPopup(MessageBox, {
|
||||
label: view.string.DeleteObject,
|
||||
message: view.string.DeleteObjectConfirm,
|
||||
params: { count: 1 },
|
||||
dangerous: true,
|
||||
action: async () => {
|
||||
await client.remove(association)
|
||||
clearSettingsStore()
|
||||
}
|
||||
})
|
||||
}
|
||||
</script>
|
||||
|
||||
<Modal
|
||||
label={core.string.Relation}
|
||||
type={'type-aside'}
|
||||
okLabel={presentation.string.Save}
|
||||
okAction={save}
|
||||
canSave={true}
|
||||
onCancel={clearSettingsStore}
|
||||
>
|
||||
<svelte:fragment slot="actions">
|
||||
<Button icon={IconDelete} kind={'dangerous'} on:click={remove} />
|
||||
</svelte:fragment>
|
||||
<div class="hulyModal-content__settingsSet">
|
||||
<div class="flex flex-gap-4">
|
||||
<div class="flex-col p-4 flex-gap-2">
|
||||
<div class="flex-col-center">A</div>
|
||||
<div>
|
||||
<EditBox bind:value={nameA} placeholder={core.string.Name} kind={'default'} />
|
||||
</div>
|
||||
<div>
|
||||
<Label label={classA.label} />
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex-col p-4 flex-gap-2">
|
||||
<span class="label">
|
||||
<Label label={setting.string.Type} />
|
||||
</span>
|
||||
<Label {label} />
|
||||
</div>
|
||||
<div class="flex-col p-4 flex-gap-2">
|
||||
<div class="flex-col-center">B</div>
|
||||
<div>
|
||||
<EditBox bind:value={nameB} placeholder={core.string.Name} kind={'default'} />
|
||||
</div>
|
||||
<div>
|
||||
<Label label={classB.label} />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</Modal>
|
||||
@@ -1,22 +1,28 @@
|
||||
<script lang="ts">
|
||||
import core, { Association, Class, Data, Doc, Ref } from '@hcengineering/core'
|
||||
import { IntlString, translate } from '@hcengineering/platform'
|
||||
import { createQuery, getClient, MessageBox } from '@hcengineering/presentation'
|
||||
import {
|
||||
Button,
|
||||
Header,
|
||||
Breadcrumb,
|
||||
Separator,
|
||||
Button,
|
||||
defineSeparators,
|
||||
twoPanelsSeparators,
|
||||
Header,
|
||||
IconDelete,
|
||||
showPopup
|
||||
Separator,
|
||||
showPopup,
|
||||
twoPanelsSeparators
|
||||
} from '@hcengineering/ui'
|
||||
import settings from '../plugin'
|
||||
import view from '@hcengineering/view-resources/src/plugin'
|
||||
import settings from '../plugin'
|
||||
import card from '@hcengineering/card'
|
||||
import AssociationEditor from './AssociationEditor.svelte'
|
||||
|
||||
export let _classes: Ref<Class<Doc>>[] = [core.class.Doc]
|
||||
export let exclude: Ref<Class<Doc>>[] = [card.class.Card]
|
||||
|
||||
const query = createQuery()
|
||||
const client = getClient()
|
||||
const hierarchy = client.getHierarchy()
|
||||
|
||||
let selected: Association | Data<Association> | undefined
|
||||
|
||||
@@ -26,6 +32,37 @@
|
||||
associations = res
|
||||
})
|
||||
|
||||
$: filtered = filterAssociations(associations, _classes, exclude)
|
||||
|
||||
function filterAssociations (
|
||||
associations: Association[],
|
||||
_classes: Ref<Class<Doc>>[],
|
||||
exclude: Ref<Class<Doc>>[]
|
||||
): Association[] {
|
||||
_classes = _classes ?? [core.class.Doc]
|
||||
exclude = exclude ?? [card.class.Card]
|
||||
const res: Association[] = []
|
||||
const descendants = new Set(_classes.map((p) => hierarchy.getDescendants(p)).reduce((a, b) => a.concat(b)))
|
||||
const excluded = new Set()
|
||||
for (const _class of exclude) {
|
||||
const desc = hierarchy.getDescendants(_class)
|
||||
for (const _id of desc) {
|
||||
excluded.add(_id)
|
||||
}
|
||||
}
|
||||
for (const association of associations) {
|
||||
if (
|
||||
descendants.has(association.classA) &&
|
||||
descendants.has(association.classB) &&
|
||||
!excluded.has(association.classA) &&
|
||||
!excluded.has(association.classB)
|
||||
) {
|
||||
res.push(association)
|
||||
}
|
||||
}
|
||||
return res
|
||||
}
|
||||
|
||||
function createRelation (): void {
|
||||
selected = {
|
||||
classA: '' as Ref<Class<Doc>>,
|
||||
@@ -40,6 +77,19 @@
|
||||
return (data as Association)?._id !== undefined
|
||||
}
|
||||
|
||||
function getClassLabel (_class: Ref<Class<Doc>>): IntlString | undefined {
|
||||
const _classLabel = client.getModel().findObject(_class)
|
||||
return _classLabel?.label
|
||||
}
|
||||
|
||||
async function getLabel (association: Association): Promise<string> {
|
||||
const aLabel = getClassLabel(association.classA)
|
||||
const bLabel = getClassLabel(association.classB)
|
||||
const aClass = aLabel !== undefined ? await translate(aLabel, {}) : undefined
|
||||
const bClass = bLabel !== undefined ? await translate(bLabel, {}) : undefined
|
||||
return `${association.nameA} ${aClass !== undefined ? '(' + aClass + ')' : ''} - ${association.nameB} ${bClass !== undefined ? '(' + bClass + ')' : ''}`
|
||||
}
|
||||
|
||||
defineSeparators('workspaceSettings', twoPanelsSeparators)
|
||||
|
||||
async function remove (val: Association | Data<Association> | undefined): Promise<void> {
|
||||
@@ -82,7 +132,7 @@
|
||||
</div>
|
||||
|
||||
<div class="flex-col overflow-y-auto">
|
||||
{#each associations as association (association._id)}
|
||||
{#each filtered as association (association._id)}
|
||||
<button
|
||||
class="list-item"
|
||||
class:selected={selected === association}
|
||||
@@ -90,7 +140,9 @@
|
||||
selected = association
|
||||
}}
|
||||
>
|
||||
<span class="font-regular-14 overflow-label">{association.nameA} - {association.nameB}</span>
|
||||
{#await getLabel(association) then label}
|
||||
<span class="font-regular-14 overflow-label">{label}</span>
|
||||
{/await}
|
||||
</button>
|
||||
{/each}
|
||||
</div>
|
||||
@@ -98,6 +150,8 @@
|
||||
<Separator name={'workspaceSettings'} index={0} color={'var(--theme-divider-color)'} />
|
||||
{#if selected !== undefined}
|
||||
<AssociationEditor
|
||||
{exclude}
|
||||
{_classes}
|
||||
association={selected}
|
||||
on:close={() => {
|
||||
selected = undefined
|
||||
|
||||
@@ -103,5 +103,5 @@
|
||||
{:else if kind === 'content' && category === undefined}
|
||||
<div class="hulyComponent" />
|
||||
{:else if category}
|
||||
<Component is={category.component} props={{ kind: 'content' }} on:change />
|
||||
<Component is={category.component} props={{ kind: 'content', ...category.props }} on:change />
|
||||
{/if}
|
||||
|
||||
Reference in New Issue
Block a user