mirror of
https://github.com/hcengineering/platform.git
synced 2026-09-23 10:05:01 +02:00
Create card process (#9522)
This commit is contained in:
@@ -0,0 +1,120 @@
|
||||
<!--
|
||||
// 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, { AnyAttribute, Association, Doc, generateId, Ref, RefTo, Relation } from '@hcengineering/core'
|
||||
import { getClient } from '@hcengineering/presentation'
|
||||
import { Process, Step } from '@hcengineering/process'
|
||||
import { Label, tooltip } from '@hcengineering/ui'
|
||||
import { createEventDispatcher } from 'svelte'
|
||||
import { getContext } from '../../utils'
|
||||
import ProcessAttribute from '../ProcessAttribute.svelte'
|
||||
import AssociationSelector from './AssociationSelector.svelte'
|
||||
|
||||
export let process: Process
|
||||
export let step: Step<Relation>
|
||||
|
||||
const dispatch = createEventDispatcher()
|
||||
|
||||
const params = step.params
|
||||
let association = params.association as Ref<Association> | undefined
|
||||
let direction = params.direction as 'A' | 'B' | undefined
|
||||
const _id = params._id
|
||||
|
||||
const client = getClient()
|
||||
|
||||
$: assoc = association && client.getModel().findObject(association)
|
||||
$: targetClass = assoc && direction ? (direction === 'A' ? assoc.classA : assoc.classB) : undefined
|
||||
|
||||
function changeAssociation (e: CustomEvent<any>): void {
|
||||
if (e.detail !== undefined) {
|
||||
association = e.detail.association
|
||||
direction = e.detail.direction
|
||||
params.association = association
|
||||
params.direction = direction
|
||||
;(step.params as any) = params
|
||||
dispatch('change', step)
|
||||
}
|
||||
}
|
||||
|
||||
function changeParam (e: CustomEvent<any>): void {
|
||||
if (e.detail !== undefined) {
|
||||
params._id = e.detail
|
||||
;(step.params as any)._id = e.detail
|
||||
dispatch('change', step)
|
||||
}
|
||||
}
|
||||
|
||||
let attribute: AnyAttribute
|
||||
$: attribute = {
|
||||
attributeOf: process.masterTag,
|
||||
name: '',
|
||||
type: {
|
||||
label: core.string.Ref,
|
||||
_class: core.class.RefTo,
|
||||
to: targetClass
|
||||
},
|
||||
_id: generateId(),
|
||||
space: core.space.Model,
|
||||
modifiedOn: 0,
|
||||
modifiedBy: core.account.System,
|
||||
_class: core.class.Attribute,
|
||||
label: core.string.Object
|
||||
}
|
||||
|
||||
$: context = targetClass && getContext(client, process, targetClass, 'object')
|
||||
</script>
|
||||
|
||||
<div class="grid">
|
||||
<span
|
||||
class="labelOnPanel"
|
||||
use:tooltip={{
|
||||
props: { label: core.string.Relation }
|
||||
}}
|
||||
>
|
||||
<Label label={core.string.Relation} />
|
||||
</span>
|
||||
<AssociationSelector {process} {association} {direction} on:change={changeAssociation} />
|
||||
{#if targetClass && context && attribute}
|
||||
<ProcessAttribute
|
||||
value={_id}
|
||||
{process}
|
||||
{attribute}
|
||||
{context}
|
||||
masterTag={process.masterTag}
|
||||
editor={undefined}
|
||||
presenterClass={{
|
||||
attrClass: targetClass,
|
||||
category: 'object'
|
||||
}}
|
||||
forbidValue
|
||||
on:change={changeParam}
|
||||
/>
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
<style lang="scss">
|
||||
.grid {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 1.5fr;
|
||||
grid-auto-rows: minmax(2rem, max-content);
|
||||
justify-content: start;
|
||||
align-items: center;
|
||||
row-gap: 0.5rem;
|
||||
column-gap: 1rem;
|
||||
margin: 0.25rem 2rem 0;
|
||||
width: calc(100% - 4rem);
|
||||
height: min-content;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,84 @@
|
||||
<!--
|
||||
// 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, Ref } from '@hcengineering/core'
|
||||
import { getClient } from '@hcengineering/presentation'
|
||||
import { Process } from '@hcengineering/process'
|
||||
import { Button, eventToHTMLElement, Label, SelectPopup, SelectPopupValueType, showPopup } from '@hcengineering/ui'
|
||||
import { createEventDispatcher } from 'svelte'
|
||||
|
||||
export let process: Process
|
||||
export let association: Ref<Association> | undefined = undefined
|
||||
export let direction: 'A' | 'B' | undefined = undefined
|
||||
|
||||
const client = getClient()
|
||||
const hierarchy = client.getHierarchy()
|
||||
|
||||
const dispatch = createEventDispatcher()
|
||||
|
||||
function open (e: MouseEvent): void {
|
||||
const descendants = hierarchy.getDescendants(process.masterTag)
|
||||
const leftAssociations = client.getModel().findAllSync(core.class.Association, { classA: { $in: descendants } })
|
||||
const rightAssociations = client.getModel().findAllSync(core.class.Association, { classB: { $in: descendants } })
|
||||
const items: SelectPopupValueType[] = []
|
||||
rightAssociations.forEach((a) => {
|
||||
items.push({
|
||||
id: 'A_' + a._id,
|
||||
text: a.nameA,
|
||||
isSelected: association !== undefined && association === a._id && direction === 'A'
|
||||
})
|
||||
})
|
||||
leftAssociations.forEach((a) => {
|
||||
items.push({
|
||||
id: 'B_' + a._id,
|
||||
text: a.nameB,
|
||||
isSelected: association !== undefined && association === a._id && direction === 'B'
|
||||
})
|
||||
})
|
||||
showPopup(
|
||||
SelectPopup,
|
||||
{
|
||||
value: items
|
||||
},
|
||||
eventToHTMLElement(e),
|
||||
(res) => {
|
||||
if (res !== undefined) {
|
||||
const [dir, id] = res.split('_')
|
||||
association = id as Ref<Association>
|
||||
direction = dir as 'A' | 'B'
|
||||
} else {
|
||||
association = undefined
|
||||
direction = undefined
|
||||
}
|
||||
dispatch('change', {
|
||||
association,
|
||||
direction
|
||||
})
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
$: selected = association !== undefined && client.getModel().findObject(association)
|
||||
</script>
|
||||
|
||||
<Button on:click={open} width={'100%'}>
|
||||
<div slot="content">
|
||||
{#if selected && direction !== undefined}
|
||||
{direction === 'A' ? selected.nameA : selected.nameB}
|
||||
{:else}
|
||||
<Label label={core.string.Relation} />
|
||||
{/if}
|
||||
</div>
|
||||
</Button>
|
||||
@@ -27,12 +27,12 @@
|
||||
|
||||
const client = getClient()
|
||||
|
||||
async function save () {
|
||||
async function save (): Promise<void> {
|
||||
await client.update(process, { context: process.context })
|
||||
clearSettingsStore()
|
||||
}
|
||||
|
||||
function onNameChange (ev: Event, _id: string, ctx: ProcessContext) {
|
||||
function onNameChange (ev: Event, _id: string, ctx: ProcessContext): void {
|
||||
const value = (ev.target as HTMLInputElement).value?.trim()
|
||||
ctx.name = value
|
||||
process.context[_id as ContextId] = ctx
|
||||
|
||||
@@ -0,0 +1,170 @@
|
||||
<!--
|
||||
// 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, { Card, MasterTag } from '@hcengineering/card'
|
||||
import { TypeSelector } from '@hcengineering/card-resources'
|
||||
import core, { AnyAttribute, Class, Ref } from '@hcengineering/core'
|
||||
import { translateCB } from '@hcengineering/platform'
|
||||
import presentation, { getClient } from '@hcengineering/presentation'
|
||||
import { MethodParams, Process, Step } from '@hcengineering/process'
|
||||
import { Button, eventToHTMLElement, Label, SelectPopup, showPopup, tooltip } from '@hcengineering/ui'
|
||||
import { createEventDispatcher } from 'svelte'
|
||||
import ParamsEditor from './ParamsEditor.svelte'
|
||||
|
||||
export let process: Process
|
||||
export let step: Step<Card>
|
||||
|
||||
const dispatch = createEventDispatcher()
|
||||
|
||||
step.params.title = step.params.title ?? ''
|
||||
step.params._class = step.params._class ?? process.masterTag
|
||||
|
||||
let params = step.params
|
||||
|
||||
let _class: Ref<Class<MasterTag>> = (params._class as Ref<Class<MasterTag>>) ?? process.masterTag
|
||||
|
||||
const client = getClient()
|
||||
const hierarchy = client.getHierarchy()
|
||||
|
||||
translateCB(cardPlugin.string.Card, {}, undefined, (res) => {
|
||||
if (params.title === undefined || params.title === '') {
|
||||
params.title = res
|
||||
;(step.params as any) = params
|
||||
dispatch('change', step)
|
||||
}
|
||||
})
|
||||
|
||||
function change (e: CustomEvent<any>): void {
|
||||
if (e.detail !== undefined) {
|
||||
params = e.detail
|
||||
;(step.params as any) = e.detail
|
||||
dispatch('change', step)
|
||||
}
|
||||
}
|
||||
|
||||
function getKeys (_class: Ref<Class<MasterTag>>): AnyAttribute[] {
|
||||
const ignoreKeys = ['_class', 'content', 'parent', 'attachments', 'todos']
|
||||
const attributes = hierarchy.getAllAttributes(_class, core.class.Doc)
|
||||
const res: AnyAttribute[] = []
|
||||
for (const [key, attr] of attributes) {
|
||||
if (attr.hidden === true) continue
|
||||
if (ignoreKeys.includes(key)) continue
|
||||
res.push(attr)
|
||||
}
|
||||
return res
|
||||
}
|
||||
|
||||
let keys = Object.keys(params).filter((key) => {
|
||||
return key !== '_class'
|
||||
})
|
||||
|
||||
$: allAttrs = getKeys(_class)
|
||||
$: possibleAttrs = allAttrs.filter((attr) => !keys.includes(attr.name))
|
||||
|
||||
function addKey (key: string): void {
|
||||
keys = [...keys, key]
|
||||
}
|
||||
|
||||
function onAdd (e: MouseEvent): void {
|
||||
showPopup(
|
||||
SelectPopup,
|
||||
{
|
||||
value: possibleAttrs.map((p) => {
|
||||
return { id: p.name, label: p.label }
|
||||
})
|
||||
},
|
||||
eventToHTMLElement(e),
|
||||
(res) => {
|
||||
if (res != null) {
|
||||
addKey(res)
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
function remove (e: CustomEvent<any>): void {
|
||||
if (e.detail !== undefined) {
|
||||
const key = e.detail.key
|
||||
if (key === 'title') return
|
||||
keys = keys.filter((k) => k !== key)
|
||||
// eslint-disable-next-line @typescript-eslint/no-dynamic-delete
|
||||
delete (params as any)[key]
|
||||
;(step.params as any) = params
|
||||
dispatch('change', step)
|
||||
}
|
||||
}
|
||||
|
||||
function typeChange (e: CustomEvent<Ref<Class<MasterTag>>>): void {
|
||||
if (e.detail === undefined || e.detail === _class) return
|
||||
allAttrs = getKeys(e.detail)
|
||||
const oldParams = { ...params }
|
||||
params = {}
|
||||
for (const attr of allAttrs) {
|
||||
const key = attr.name
|
||||
if (oldParams[key] !== undefined) {
|
||||
;(params as any)[key] = oldParams[key]
|
||||
}
|
||||
}
|
||||
_class = e.detail
|
||||
keys = Object.keys(params)
|
||||
params._class = _class
|
||||
step.params = params
|
||||
if (step.context != null) {
|
||||
step.context._class = _class
|
||||
}
|
||||
dispatch('change', step)
|
||||
}
|
||||
</script>
|
||||
|
||||
<div class="grid">
|
||||
<span
|
||||
class="labelOnPanel"
|
||||
use:tooltip={{
|
||||
props: { label: cardPlugin.string.MasterTag }
|
||||
}}
|
||||
>
|
||||
<Label label={cardPlugin.string.MasterTag} />
|
||||
</span>
|
||||
<TypeSelector value={_class} width={'100%'} on:change={typeChange} />
|
||||
</div>
|
||||
<div class="divider" />
|
||||
{#key _class}
|
||||
<ParamsEditor {_class} {process} {keys} {params} allowRemove on:remove={remove} on:change={change} />
|
||||
{/key}
|
||||
{#if possibleAttrs.length > 0}
|
||||
<div class="flex-center mt-4">
|
||||
<Button label={presentation.string.Add} width={'100%'} kind={'link-bordered'} size={'large'} on:click={onAdd} />
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
<style lang="scss">
|
||||
.divider {
|
||||
border-bottom: 1px solid var(--divider-color);
|
||||
margin: 1rem 0;
|
||||
}
|
||||
|
||||
.grid {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 1.5fr;
|
||||
grid-auto-rows: minmax(2rem, max-content);
|
||||
justify-content: start;
|
||||
align-items: center;
|
||||
row-gap: 0.5rem;
|
||||
column-gap: 1rem;
|
||||
margin: 0.25rem 2rem 0;
|
||||
width: calc(100% - 4rem);
|
||||
height: min-content;
|
||||
}
|
||||
</style>
|
||||
@@ -14,7 +14,7 @@
|
||||
-->
|
||||
<script lang="ts">
|
||||
import { Class, Doc, Ref } from '@hcengineering/core'
|
||||
import { MethodParams, Process, State } from '@hcengineering/process'
|
||||
import { MethodParams, Process } from '@hcengineering/process'
|
||||
import { createEventDispatcher } from 'svelte'
|
||||
import ProcessAttributeEditor from './ProcessAttributeEditor.svelte'
|
||||
|
||||
|
||||
@@ -15,7 +15,7 @@
|
||||
<script lang="ts">
|
||||
import core, { Class, Doc, PropertyType, Ref, Type } from '@hcengineering/core'
|
||||
import { getClient } from '@hcengineering/presentation'
|
||||
import { ContextId, Step } from '@hcengineering/process'
|
||||
import { Step } from '@hcengineering/process'
|
||||
import setting from '@hcengineering/setting-resources/src/plugin'
|
||||
import {
|
||||
AnyComponent,
|
||||
|
||||
@@ -24,13 +24,13 @@
|
||||
const client = getClient()
|
||||
|
||||
const query = createQuery()
|
||||
$: from = transition.from && client.getModel().findObject(transition.from)
|
||||
$: to = client.getModel().findObject(transition.to)
|
||||
|
||||
$: query.query(plugin.class.State, { process: transition.process }, () => {
|
||||
from = transition.from && client.getModel().findObject(transition.from)
|
||||
to = client.getModel().findObject(transition.to)
|
||||
})
|
||||
|
||||
$: from = transition.from && client.getModel().findObject(transition.from)
|
||||
$: to = client.getModel().findObject(transition.to)
|
||||
</script>
|
||||
|
||||
<span>
|
||||
|
||||
@@ -14,7 +14,6 @@
|
||||
-->
|
||||
<script lang="ts">
|
||||
import { Ref } from '@hcengineering/core'
|
||||
import { getClient } from '@hcengineering/presentation'
|
||||
import { Process, Transition } from '@hcengineering/process'
|
||||
import { Button, getCurrentLocation, IconAdd, Label, navigate, showPopup } from '@hcengineering/ui'
|
||||
import plugin from '../../plugin'
|
||||
@@ -25,8 +24,6 @@
|
||||
export let transitions: Transition[]
|
||||
export let readonly: boolean
|
||||
|
||||
const client = getClient()
|
||||
|
||||
function addTransition (): void {
|
||||
showPopup(AddTransitionPopup, { process }, 'top')
|
||||
}
|
||||
|
||||
@@ -16,10 +16,10 @@
|
||||
import { Card, MasterTag } from '@hcengineering/card'
|
||||
import core, { AnyAttribute, Class, Ref } from '@hcengineering/core'
|
||||
import presentation, { getClient } from '@hcengineering/presentation'
|
||||
import { Process, State, Step } from '@hcengineering/process'
|
||||
import { Process, Step } from '@hcengineering/process'
|
||||
import { Button, eventToHTMLElement, SelectPopup, showPopup } from '@hcengineering/ui'
|
||||
import { createEventDispatcher } from 'svelte'
|
||||
import ParamsEditor from './ParamsEditor.svelte'
|
||||
import { Button, eventToHTMLElement, SelectPopup, showPopup } from '@hcengineering/ui'
|
||||
|
||||
export let process: Process
|
||||
export let step: Step<Card>
|
||||
|
||||
Reference in New Issue
Block a user