mirror of
https://github.com/hcengineering/platform.git
synced 2025-04-14 12:25:17 +00:00
UBER-1182: Fix github task types support (#4215)
Signed-off-by: Andrey Sobolev <haiodo@gmail.com>
This commit is contained in:
parent
89706eb350
commit
5413031df0
@ -19,7 +19,7 @@ import { type Doc, type Ref } from '@hcengineering/core'
|
||||
import { type ObjectSearchCategory, type ObjectSearchFactory } from '@hcengineering/model-presentation'
|
||||
import { type NotificationGroup, type NotificationType } from '@hcengineering/notification'
|
||||
import { mergeIds, type IntlString, type Resource } from '@hcengineering/platform'
|
||||
import { type ProjectType, type TaskTypeDescriptor } from '@hcengineering/task'
|
||||
import { type ProjectType } from '@hcengineering/task'
|
||||
import { trackerId } from '@hcengineering/tracker'
|
||||
import tracker from '@hcengineering/tracker-resources/src/plugin'
|
||||
import type { AnyComponent } from '@hcengineering/ui/src/types'
|
||||
@ -109,8 +109,5 @@ export default mergeIds(trackerId, tracker, {
|
||||
DeleteProject: '' as Ref<Action<Doc, Record<string, any>>>,
|
||||
DeleteProjectClean: '' as Ref<Action<Doc, Record<string, any>>>,
|
||||
DeleteIssue: '' as Ref<Action<Doc, Record<string, any>>>
|
||||
},
|
||||
descriptors: {
|
||||
Issue: '' as Ref<TaskTypeDescriptor>
|
||||
}
|
||||
})
|
||||
|
@ -13,7 +13,7 @@
|
||||
// limitations under the License.
|
||||
-->
|
||||
<script lang="ts">
|
||||
import type { Class, Doc, Ref } from '@hcengineering/core'
|
||||
import { getObjectValue, type Class, type Doc, type Ref } from '@hcengineering/core'
|
||||
import type { IntlString } from '@hcengineering/platform'
|
||||
import {
|
||||
Button,
|
||||
@ -68,7 +68,7 @@
|
||||
|
||||
$: showCategories =
|
||||
created.length > 0 ||
|
||||
objects.map((it) => (it as any)[groupBy]).filter((it, index, arr) => arr.indexOf(it) === index).length > 1
|
||||
objects.map((it) => getObjectValue(groupBy, it)).filter((it, index, arr) => arr.indexOf(it) === index).length > 1
|
||||
|
||||
const checkSelected = (item: Doc): void => {
|
||||
if (selectedElements.has(item._id)) {
|
||||
@ -148,7 +148,7 @@
|
||||
if (created.find((it) => it._id === doc._id) !== undefined) {
|
||||
return '_created'
|
||||
}
|
||||
return toAny(doc)[groupBy]
|
||||
return getObjectValue(groupBy, toAny(doc))
|
||||
}
|
||||
</script>
|
||||
|
||||
|
@ -13,7 +13,14 @@
|
||||
// limitations under the License.
|
||||
-->
|
||||
<script lang="ts">
|
||||
import type { Class, Doc, DocumentQuery, FindOptions, Ref } from '@hcengineering/core'
|
||||
import {
|
||||
getObjectValue,
|
||||
type Class,
|
||||
type Doc,
|
||||
type DocumentQuery,
|
||||
type FindOptions,
|
||||
type Ref
|
||||
} from '@hcengineering/core'
|
||||
import type { IntlString } from '@hcengineering/platform'
|
||||
import { Label } from '@hcengineering/ui'
|
||||
import { createEventDispatcher } from 'svelte'
|
||||
@ -77,8 +84,8 @@
|
||||
},
|
||||
(result) => {
|
||||
result.sort((a, b) => {
|
||||
const aval: string = `${(a as any)[groupBy]}`
|
||||
const bval: string = `${(b as any)[groupBy]}`
|
||||
const aval: string = `${getObjectValue(groupBy, a as any)}`
|
||||
const bval: string = `${getObjectValue(groupBy, b as any)}`
|
||||
return aval.localeCompare(bval)
|
||||
})
|
||||
if (created.length > 0) {
|
||||
|
@ -97,7 +97,7 @@
|
||||
space: Ref<Project>,
|
||||
template: IssueTemplateData,
|
||||
parent: Ref<Issue> = tracker.ids.NoParent
|
||||
): Promise<Ref<Issue>> {
|
||||
): Promise<Ref<Issue> | undefined> {
|
||||
const lastOne = await client.findOne<Issue>(
|
||||
tracker.class.Issue,
|
||||
{ space },
|
||||
@ -114,6 +114,10 @@
|
||||
)
|
||||
const project = await client.findOne(tracker.class.Project, { _id: space })
|
||||
const rank = calcRank(lastOne, undefined)
|
||||
const taskType = await client.findOne(task.class.TaskType, { ofClass: tracker.class.Issue })
|
||||
if (taskType === undefined) {
|
||||
return
|
||||
}
|
||||
const resId = await client.addCollection(tracker.class.Issue, space, parent, tracker.class.Issue, 'subIssues', {
|
||||
title: template.title + ` (${name})`,
|
||||
description: template.description,
|
||||
@ -133,7 +137,8 @@
|
||||
estimation: template.estimation,
|
||||
reports: 0,
|
||||
relations: [{ _id: id, _class: recruit.class.Vacancy }],
|
||||
childInfo: []
|
||||
childInfo: [],
|
||||
kind: taskType._id
|
||||
})
|
||||
if ((template.labels?.length ?? 0) > 0) {
|
||||
const tagElements = await client.findAll(tags.class.TagElement, { _id: { $in: template.labels } })
|
||||
@ -181,8 +186,10 @@
|
||||
if (issueTemplates.length > 0) {
|
||||
for (const issueTemplate of issueTemplates) {
|
||||
const issue = await saveIssue(id, issueTemplate.space, issueTemplate)
|
||||
for (const sub of issueTemplate.children) {
|
||||
await saveIssue(id, issueTemplate.space, sub, issue)
|
||||
if (issue !== undefined) {
|
||||
for (const sub of issueTemplate.children) {
|
||||
await saveIssue(id, issueTemplate.space, sub, issue)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -16,7 +16,7 @@
|
||||
import core, { Doc, FindResult, IdMap, Ref, RefTo, Space, Status, toIdMap } from '@hcengineering/core'
|
||||
import { translate } from '@hcengineering/platform'
|
||||
import presentation, { getClient } from '@hcengineering/presentation'
|
||||
import { TaskType } from '@hcengineering/task'
|
||||
import { ProjectType, TaskType } from '@hcengineering/task'
|
||||
import ui, {
|
||||
EditWithIcon,
|
||||
Icon,
|
||||
@ -39,7 +39,7 @@
|
||||
import view from '@hcengineering/view-resources/src/plugin'
|
||||
import { buildConfigLookup, getPresenter } from '@hcengineering/view-resources/src/utils'
|
||||
import { createEventDispatcher } from 'svelte'
|
||||
import { selectedTaskTypeStore, taskTypeStore } from '..'
|
||||
import { selectedTaskTypeStore, selectedTypeStore, taskTypeStore, typeStore } from '..'
|
||||
|
||||
export let filter: Filter
|
||||
export let space: Ref<Space> | undefined = undefined
|
||||
@ -67,7 +67,9 @@
|
||||
search: string,
|
||||
selectedType: Ref<TaskType> | undefined,
|
||||
typeStore: IdMap<TaskType>,
|
||||
statusStore: IdMap<Status>
|
||||
statusStore: IdMap<Status>,
|
||||
selectedProjectType: Ref<ProjectType> | undefined,
|
||||
projectTypeStore: IdMap<ProjectType>
|
||||
): Promise<void> {
|
||||
await objectsPromise
|
||||
targets.clear()
|
||||
@ -85,13 +87,22 @@
|
||||
values = values.filter((p) => p?.name.includes(search))
|
||||
}
|
||||
} else {
|
||||
const statuses: Status[] = []
|
||||
let statuses: Status[] = []
|
||||
const prjStatuses = new Map(
|
||||
(
|
||||
(selectedProjectType !== undefined ? projectTypeStore.get(selectedProjectType) : undefined)?.statuses ?? []
|
||||
).map((p) => [p._id, p])
|
||||
)
|
||||
for (const status of statusStore.values()) {
|
||||
if (hierarchy.isDerived(status._class, targetClass) && status.ofAttribute === filter.key.attribute._id) {
|
||||
if (prjStatuses.size > 0 && !prjStatuses.has(status._id)) {
|
||||
continue
|
||||
}
|
||||
statuses.push(status)
|
||||
targets.add(status._id)
|
||||
}
|
||||
}
|
||||
statuses = statuses.filter((it, idx, arr) => arr.findIndex((q) => q._id === it._id) === idx)
|
||||
values = await sort(statuses)
|
||||
}
|
||||
if (targets.has(undefined)) {
|
||||
@ -164,7 +175,7 @@
|
||||
const dispatch = createEventDispatcher()
|
||||
|
||||
$: if (targetClass != null) {
|
||||
void getValues(search, $selectedTaskTypeStore, $taskTypeStore, $statusStore.byId)
|
||||
void getValues(search, $selectedTaskTypeStore, $taskTypeStore, $statusStore.byId, $selectedTypeStore, $typeStore)
|
||||
}
|
||||
</script>
|
||||
|
||||
|
@ -15,7 +15,9 @@
|
||||
|
||||
import core, {
|
||||
Class,
|
||||
ClassifierKind,
|
||||
Data,
|
||||
Doc,
|
||||
DocumentQuery,
|
||||
Hierarchy,
|
||||
IdMap,
|
||||
@ -23,16 +25,14 @@ import core, {
|
||||
Status,
|
||||
StatusCategory,
|
||||
TxOperations,
|
||||
type AnyAttribute,
|
||||
type RefTo,
|
||||
Doc,
|
||||
generateId,
|
||||
ClassifierKind
|
||||
type AnyAttribute,
|
||||
type RefTo
|
||||
} from '@hcengineering/core'
|
||||
import { PlatformError, getEmbeddedLabel, unknownStatus } from '@hcengineering/platform'
|
||||
import { LexoDecimal, LexoNumeralSystem36, LexoRank } from 'lexorank'
|
||||
import LexoRankBucket from 'lexorank/lib/lexoRank/lexoRankBucket'
|
||||
import task, { Project, ProjectStatus, ProjectType, Task, TaskType } from '.'
|
||||
import { getEmbeddedLabel } from '@hcengineering/platform'
|
||||
|
||||
/**
|
||||
* @public
|
||||
@ -196,6 +196,18 @@ export type TaskTypeWithFactory = Omit<Data<TaskType>, 'statuses' | 'parent' | '
|
||||
|
||||
type ProjectData = Omit<Data<ProjectType>, 'statuses' | 'private' | 'members' | 'archived' | 'targetClass'>
|
||||
|
||||
async function createStates (
|
||||
client: TxOperations,
|
||||
states: Data<Status>[],
|
||||
stateClass: Ref<Class<Status>>
|
||||
): Promise<Ref<Status>[]> {
|
||||
const statuses: Ref<Status>[] = []
|
||||
for (const st of states) {
|
||||
statuses.push(await createState(client, stateClass, st))
|
||||
}
|
||||
return statuses
|
||||
}
|
||||
|
||||
/**
|
||||
* @public
|
||||
*/
|
||||
@ -210,65 +222,17 @@ export async function createProjectType (
|
||||
return current._id
|
||||
}
|
||||
|
||||
async function createStates (states: Data<Status>[], stateClass: Ref<Class<Status>>): Promise<Ref<Status>[]> {
|
||||
const statuses: Ref<Status>[] = []
|
||||
for (const st of states) {
|
||||
statuses.push(await createState(client, stateClass, st))
|
||||
}
|
||||
return statuses
|
||||
}
|
||||
|
||||
const _tasks: Ref<TaskType>[] = []
|
||||
const tasksData = new Map<Ref<TaskType>, Data<TaskType>>()
|
||||
const _statues = new Set<Ref<Status>>()
|
||||
for (const it of tasks) {
|
||||
const { factory, _id: taskId, ...data } = it
|
||||
const statuses = await createStates(factory, data.statusClass)
|
||||
for (const st of statuses) {
|
||||
_statues.add(st)
|
||||
}
|
||||
const tdata = {
|
||||
...data,
|
||||
parent: _id,
|
||||
statuses
|
||||
}
|
||||
|
||||
const ofClassClass = client.getHierarchy().getClass(data.ofClass)
|
||||
|
||||
tdata.icon = ofClassClass.icon
|
||||
|
||||
if (tdata.targetClass === undefined) {
|
||||
// Create target class for custom field.
|
||||
const targetClassId: Ref<Class<Task>> = generateId()
|
||||
tdata.targetClass = targetClassId
|
||||
|
||||
await client.createDoc(
|
||||
core.class.Mixin,
|
||||
core.space.Model,
|
||||
{
|
||||
extends: data.ofClass,
|
||||
kind: ClassifierKind.MIXIN,
|
||||
label: ofClassClass.label,
|
||||
icon: ofClassClass.icon
|
||||
},
|
||||
targetClassId
|
||||
)
|
||||
|
||||
await client.createMixin(targetClassId, core.class.Mixin, core.space.Model, task.mixin.TaskTypeClass, {
|
||||
taskType: taskId,
|
||||
projectType: _id
|
||||
})
|
||||
}
|
||||
await client.createDoc(task.class.TaskType, core.space.Model, tdata as Data<TaskType>, taskId)
|
||||
tasksData.set(taskId, tdata as Data<TaskType>)
|
||||
_tasks.push(taskId)
|
||||
}
|
||||
|
||||
const categoryObj = client.getModel().findObject(data.descriptor)
|
||||
if (categoryObj === undefined) {
|
||||
throw new Error('category is not found in model')
|
||||
}
|
||||
|
||||
await createTaskTypes(tasks, _id, client, _statues, tasksData, _tasks, false)
|
||||
|
||||
const baseClassClass = client.getHierarchy().getClass(categoryObj.baseClass)
|
||||
|
||||
const targetProjectClassId: Ref<Class<Doc>> = generateId()
|
||||
@ -311,3 +275,106 @@ export async function createProjectType (
|
||||
|
||||
return tmpl
|
||||
}
|
||||
|
||||
/**
|
||||
* @public
|
||||
*/
|
||||
export async function updateProjectType (
|
||||
client: TxOperations,
|
||||
projectType: Ref<ProjectType>,
|
||||
tasks: TaskTypeWithFactory[]
|
||||
): Promise<void> {
|
||||
const current = await client.findOne(task.class.ProjectType, { _id: projectType })
|
||||
if (current === undefined) {
|
||||
throw new PlatformError(unknownStatus('No project type found'))
|
||||
}
|
||||
|
||||
const _tasks: Ref<TaskType>[] = [...current.tasks]
|
||||
const tasksData = new Map<Ref<TaskType>, Data<TaskType>>()
|
||||
const _statues = new Set<Ref<Status>>()
|
||||
|
||||
const hasUpdates = await createTaskTypes(tasks, projectType, client, _statues, tasksData, _tasks, true)
|
||||
|
||||
if (hasUpdates) {
|
||||
const ttypes = await client.findAll<TaskType>(task.class.TaskType, { _id: { $in: _tasks } })
|
||||
const newStatuses = calculateStatuses(
|
||||
{
|
||||
statuses: current.statuses,
|
||||
tasks: _tasks
|
||||
},
|
||||
new Map(ttypes.map((it) => [it._id, it])),
|
||||
[]
|
||||
)
|
||||
await client.update(current, {
|
||||
tasks: _tasks,
|
||||
statuses: newStatuses
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
async function createTaskTypes (
|
||||
tasks: TaskTypeWithFactory[],
|
||||
_id: Ref<ProjectType>,
|
||||
client: TxOperations,
|
||||
_statues: Set<Ref<Status>>,
|
||||
tasksData: Map<Ref<TaskType>, Data<TaskType>>,
|
||||
_tasks: Ref<TaskType>[],
|
||||
skipExisting: boolean
|
||||
): Promise<boolean> {
|
||||
const existingTaskTypes = await client.findAll(task.class.TaskType, { parent: _id })
|
||||
|
||||
let hasUpdates = false
|
||||
for (const it of tasks) {
|
||||
const { factory, _id: taskId, ...data } = it
|
||||
|
||||
if (skipExisting) {
|
||||
const existingOne = existingTaskTypes.find((tt) => tt.ofClass === data.ofClass)
|
||||
if (existingOne !== undefined) {
|
||||
// We have similar one, let's check categories
|
||||
continue
|
||||
}
|
||||
}
|
||||
hasUpdates = true
|
||||
|
||||
const statuses = await createStates(client, factory, data.statusClass)
|
||||
for (const st of statuses) {
|
||||
_statues.add(st)
|
||||
}
|
||||
const tdata = {
|
||||
...data,
|
||||
parent: _id,
|
||||
statuses
|
||||
}
|
||||
|
||||
const ofClassClass = client.getHierarchy().getClass(data.ofClass)
|
||||
|
||||
tdata.icon = ofClassClass.icon
|
||||
|
||||
if (tdata.targetClass === undefined) {
|
||||
// Create target class for custom field.
|
||||
const targetClassId: Ref<Class<Task>> = generateId()
|
||||
tdata.targetClass = targetClassId
|
||||
|
||||
await client.createDoc(
|
||||
core.class.Mixin,
|
||||
core.space.Model,
|
||||
{
|
||||
extends: data.ofClass,
|
||||
kind: ClassifierKind.MIXIN,
|
||||
label: ofClassClass.label,
|
||||
icon: ofClassClass.icon
|
||||
},
|
||||
targetClassId
|
||||
)
|
||||
|
||||
await client.createMixin(targetClassId, core.class.Mixin, core.space.Model, task.mixin.TaskTypeClass, {
|
||||
taskType: taskId,
|
||||
projectType: _id
|
||||
})
|
||||
}
|
||||
await client.createDoc(task.class.TaskType, core.space.Model, tdata as Data<TaskType>, taskId)
|
||||
tasksData.set(taskId, tdata as Data<TaskType>)
|
||||
_tasks.push(taskId)
|
||||
}
|
||||
return hasUpdates
|
||||
}
|
||||
|
@ -14,7 +14,7 @@
|
||||
-->
|
||||
<script lang="ts">
|
||||
import { IdMap, Ref, Status, WithLookup } from '@hcengineering/core'
|
||||
import { ProjectType } from '@hcengineering/task'
|
||||
import { ProjectType, TaskType } from '@hcengineering/task'
|
||||
import { typeStore } from '@hcengineering/task-resources'
|
||||
import { IssueStatus } from '@hcengineering/tracker'
|
||||
import {
|
||||
@ -36,6 +36,7 @@
|
||||
|
||||
export let value: Ref<IssueStatus> | undefined
|
||||
export let type: Ref<ProjectType> | undefined
|
||||
export let taskType: Ref<TaskType> | undefined = undefined
|
||||
|
||||
let statuses: WithLookup<IssueStatus>[] | undefined = undefined
|
||||
|
||||
@ -70,7 +71,14 @@
|
||||
if (typeId === undefined) return []
|
||||
const type = types.get(typeId)
|
||||
if (type === undefined) return []
|
||||
return type.statuses.map((p) => statuses.get(p._id)).filter((p) => p !== undefined) as IssueStatus[]
|
||||
let vals = type.statuses
|
||||
if (taskType !== undefined) {
|
||||
vals = vals.filter((it) => it.taskType === taskType)
|
||||
}
|
||||
return vals
|
||||
.filter((it, idx, arr) => arr.findIndex((q) => q._id === it._id) === idx)
|
||||
.map((p) => statuses.get(p._id))
|
||||
.filter((p) => p !== undefined) as IssueStatus[]
|
||||
}
|
||||
|
||||
function getSelectedStatus (
|
||||
|
@ -42,7 +42,7 @@
|
||||
import tracker from '../../plugin'
|
||||
import StatusSelector from '../issues/StatusSelector.svelte'
|
||||
import ChangeIdentity from './ChangeIdentity.svelte'
|
||||
import { typeStore } from '@hcengineering/task-resources'
|
||||
import { typeStore, taskTypeStore } from '@hcengineering/task-resources'
|
||||
|
||||
export let project: Project | undefined = undefined
|
||||
|
||||
@ -70,7 +70,7 @@
|
||||
|
||||
const dispatch = createEventDispatcher()
|
||||
|
||||
$: isNew = !project
|
||||
$: isNew = project == null
|
||||
|
||||
async function handleSave (): Promise<void> {
|
||||
if (isNew) {
|
||||
@ -239,12 +239,13 @@
|
||||
<div class="antiGrid-row__header">
|
||||
<Label label={task.string.ProjectType} />
|
||||
</div>
|
||||
|
||||
<Component
|
||||
is={task.component.ProjectTypeSelector}
|
||||
disabled={!isNew}
|
||||
props={{
|
||||
descriptors: [tracker.descriptors.ProjectType],
|
||||
type: typeId,
|
||||
disabled: !isNew,
|
||||
focusIndex: 4,
|
||||
kind: 'regular',
|
||||
size: 'large'
|
||||
@ -379,7 +380,15 @@
|
||||
<div class="antiGrid-row__header">
|
||||
<Label label={tracker.string.DefaultIssueStatus} />
|
||||
</div>
|
||||
<StatusSelector bind:value={defaultStatus} type={typeId} kind={'regular'} size={'large'} />
|
||||
<StatusSelector
|
||||
taskType={Array.from($taskTypeStore.values()).filter(
|
||||
(it) => it.parent === typeId && it.ofClass === tracker.class.Issue
|
||||
)[0]?._id}
|
||||
bind:value={defaultStatus}
|
||||
type={typeId}
|
||||
kind={'regular'}
|
||||
size={'large'}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</Card>
|
||||
|
@ -34,7 +34,7 @@ import {
|
||||
} from '@hcengineering/core'
|
||||
import { Asset, IntlString, Plugin, Resource, plugin } from '@hcengineering/platform'
|
||||
import { TagCategory, TagElement, TagReference } from '@hcengineering/tags'
|
||||
import { ProjectTypeDescriptor, Task, Project as TaskProject, TaskType } from '@hcengineering/task'
|
||||
import { ProjectTypeDescriptor, Task, Project as TaskProject, TaskType, TaskTypeDescriptor } from '@hcengineering/task'
|
||||
import { AnyComponent, ComponentExtensionId, Location, ResolvedLocation } from '@hcengineering/ui'
|
||||
import { Action, ActionCategory, IconProps } from '@hcengineering/view'
|
||||
|
||||
@ -455,7 +455,8 @@ export default plugin(trackerId, {
|
||||
Tracker: '' as Ref<ActionCategory>
|
||||
},
|
||||
descriptors: {
|
||||
ProjectType: '' as Ref<ProjectTypeDescriptor>
|
||||
ProjectType: '' as Ref<ProjectTypeDescriptor>,
|
||||
Issue: '' as Ref<TaskTypeDescriptor>
|
||||
},
|
||||
action: {
|
||||
SetDueDate: '' as Ref<Action>,
|
||||
|
@ -60,6 +60,7 @@
|
||||
export let shouldShowAvatar = false
|
||||
export let autoSelect = false
|
||||
export let findDefault: (() => Promise<Doc | undefined>) | undefined = undefined
|
||||
export let groupBy = '_class'
|
||||
|
||||
export let create: ObjectCreate | undefined = undefined
|
||||
|
||||
@ -116,7 +117,8 @@
|
||||
placeholder,
|
||||
create,
|
||||
searchField,
|
||||
docProps
|
||||
docProps,
|
||||
groupBy
|
||||
},
|
||||
!$$slots.content ? container : getEventPositionElement(ev),
|
||||
(result) => {
|
||||
|
@ -14,7 +14,7 @@
|
||||
-->
|
||||
<script lang="ts">
|
||||
import { Person } from '@hcengineering/contact'
|
||||
import type { Class, Doc, DocumentQuery, FindOptions, Ref } from '@hcengineering/core'
|
||||
import { type Class, type Doc, type DocumentQuery, type FindOptions, type Ref } from '@hcengineering/core'
|
||||
import type { IntlString } from '@hcengineering/platform'
|
||||
import presentation, { ObjectCreate, ObjectPopup } from '@hcengineering/presentation'
|
||||
import ObjectPresenter from './ObjectPresenter.svelte'
|
||||
@ -34,6 +34,7 @@
|
||||
export let create: ObjectCreate | undefined = undefined
|
||||
export let searchField: string = 'name'
|
||||
export let docProps: Record<string, any> = {}
|
||||
export let groupBy = '_class'
|
||||
</script>
|
||||
|
||||
<ObjectPopup
|
||||
@ -46,7 +47,7 @@
|
||||
{titleDeselect}
|
||||
{placeholder}
|
||||
{docQuery}
|
||||
groupBy={'_class'}
|
||||
{groupBy}
|
||||
bind:selectedObjects
|
||||
bind:ignoreObjects
|
||||
{shadows}
|
||||
|
Loading…
Reference in New Issue
Block a user