mirror of
https://github.com/hcengineering/platform.git
synced 2026-09-10 19:57:43 +02:00
processes UI (#8349)
This commit is contained in:
@@ -42,6 +42,7 @@
|
||||
"@hcengineering/process-resources": "^0.6.0",
|
||||
"@hcengineering/model-presentation": "^0.6.0",
|
||||
"@hcengineering/view": "^0.6.13",
|
||||
"@hcengineering/model-workbench": "^0.6.1",
|
||||
"@hcengineering/card": "^0.6.0"
|
||||
}
|
||||
}
|
||||
|
||||
+126
-14
@@ -12,7 +12,9 @@
|
||||
// limitations under the License.
|
||||
|
||||
import card, { type Card, type MasterTag } from '@hcengineering/card'
|
||||
import contact, { type Employee } from '@hcengineering/contact'
|
||||
import core, {
|
||||
AccountRole,
|
||||
type Class,
|
||||
DOMAIN_MODEL,
|
||||
type Doc,
|
||||
@@ -22,27 +24,37 @@ import core, {
|
||||
type Space,
|
||||
type Tx
|
||||
} from '@hcengineering/core'
|
||||
import { ArrOf, Model, Prop, TypeBoolean, TypeRecord, TypeRef, TypeString, type Builder } from '@hcengineering/model'
|
||||
import presentation from '@hcengineering/model-presentation'
|
||||
import {
|
||||
ArrOf,
|
||||
type Builder,
|
||||
Model,
|
||||
Prop,
|
||||
ReadOnly,
|
||||
TypeBoolean,
|
||||
TypeRecord,
|
||||
TypeRef,
|
||||
TypeString
|
||||
} from '@hcengineering/model'
|
||||
import { TDoc } from '@hcengineering/model-core'
|
||||
import presentation from '@hcengineering/model-presentation'
|
||||
import { TToDo } from '@hcengineering/model-time'
|
||||
import view, { createAction } from '@hcengineering/model-view'
|
||||
import workbench from '@hcengineering/model-workbench'
|
||||
import { type IntlString } from '@hcengineering/platform'
|
||||
import {
|
||||
type ProcessFunction,
|
||||
type Execution,
|
||||
type Method,
|
||||
type Process,
|
||||
type ProcessFunction,
|
||||
type ProcessToDo,
|
||||
type State,
|
||||
type Step
|
||||
type Step,
|
||||
processId
|
||||
} from '@hcengineering/process'
|
||||
import time from '@hcengineering/time'
|
||||
|
||||
import { type AnyComponent } from '@hcengineering/ui'
|
||||
import process from './plugin'
|
||||
import contact, { type Employee } from '@hcengineering/contact'
|
||||
import { type AttributeCategory } from '@hcengineering/view'
|
||||
import process from './plugin'
|
||||
|
||||
const DOMAIN_PROCESS = 'process' as Domain
|
||||
|
||||
@@ -64,24 +76,31 @@ export class TProcess extends TDoc implements Process {
|
||||
@Model(process.class.Execution, core.class.Doc, DOMAIN_PROCESS)
|
||||
export class TExecution extends TDoc implements Execution {
|
||||
@Prop(TypeRef(contact.mixin.Employee), contact.string.Employee)
|
||||
@ReadOnly()
|
||||
assignee!: Ref<Employee>
|
||||
|
||||
@Prop(TypeRef(process.class.Process), process.string.Process)
|
||||
@ReadOnly()
|
||||
process!: Ref<TProcess>
|
||||
|
||||
@Prop(TypeRef(process.class.ProcessToDo), time.string.ToDo)
|
||||
@ReadOnly()
|
||||
currentToDo!: Ref<ProcessToDo> | null
|
||||
|
||||
@Prop(TypeRef(process.class.State), process.string.Step)
|
||||
@ReadOnly()
|
||||
currentState!: Ref<State>
|
||||
|
||||
@Prop(TypeBoolean(), process.string.Done)
|
||||
@ReadOnly()
|
||||
done!: boolean
|
||||
|
||||
@Prop(TypeRecord(), process.string.Rollback)
|
||||
@ReadOnly()
|
||||
rollback!: Record<Ref<State>, Tx[]>
|
||||
|
||||
@Prop(TypeRef(card.class.Card), card.string.Card)
|
||||
@ReadOnly()
|
||||
card!: Ref<Card>
|
||||
}
|
||||
|
||||
@@ -147,6 +166,20 @@ export function createModel (builder: Builder): void {
|
||||
process.action.RunProcess
|
||||
)
|
||||
|
||||
builder.createDoc(
|
||||
workbench.class.Application,
|
||||
core.space.Model,
|
||||
{
|
||||
label: process.string.Processes,
|
||||
icon: process.icon.Process,
|
||||
accessLevel: AccountRole.User,
|
||||
alias: processId,
|
||||
hidden: false,
|
||||
component: process.component.Main
|
||||
},
|
||||
process.app.Process
|
||||
)
|
||||
|
||||
builder.createDoc(
|
||||
process.class.ProcessFunction,
|
||||
core.space.Model,
|
||||
@@ -218,15 +251,25 @@ export function createModel (builder: Builder): void {
|
||||
view.class.Viewlet,
|
||||
core.space.Model,
|
||||
{
|
||||
variant: 'cardExecutions',
|
||||
attachTo: process.class.Execution,
|
||||
descriptor: view.viewlet.List,
|
||||
viewOptions: {
|
||||
groupBy: ['process', 'assignee'],
|
||||
groupBy: ['process', 'assignee', 'done'],
|
||||
orderBy: [
|
||||
['modifiedOn', SortingOrder.Descending],
|
||||
['createdOn', SortingOrder.Descending]
|
||||
],
|
||||
other: []
|
||||
other: [
|
||||
{
|
||||
key: 'showDone',
|
||||
type: 'toggle',
|
||||
defaultValue: true,
|
||||
actionTarget: 'query',
|
||||
action: process.function.ShowDoneQuery,
|
||||
label: process.string.ShowDone
|
||||
}
|
||||
]
|
||||
},
|
||||
configOptions: {
|
||||
strict: true,
|
||||
@@ -237,27 +280,96 @@ export function createModel (builder: Builder): void {
|
||||
key: '',
|
||||
label: process.string.Process,
|
||||
presenter: process.component.ExecutonPresenter,
|
||||
displayProps: { key: 'process' }
|
||||
displayProps: { key: 'process', fixed: 'left' }
|
||||
},
|
||||
{
|
||||
key: '',
|
||||
label: process.string.Step,
|
||||
presenter: process.component.ExecutonProgressPresenter,
|
||||
displayProps: { key: 'state' }
|
||||
displayProps: { key: 'state', fixed: 'left' }
|
||||
},
|
||||
{ key: '', presenter: process.component.ExecutonPresenter, displayProps: { grow: true } },
|
||||
{
|
||||
key: 'assignee',
|
||||
displayProps: { key: 'assignee', fixed: 'right' },
|
||||
props: { kind: 'list', shouldShowName: false, avatarSize: 'x-small', onChange: undefined }
|
||||
props: { kind: 'list', shouldShowName: false, avatarSize: 'x-small' }
|
||||
},
|
||||
'modifiedOn',
|
||||
'createdOn'
|
||||
{
|
||||
key: 'modifiedOn',
|
||||
displayProps: { fixed: 'right' }
|
||||
},
|
||||
{
|
||||
key: 'createdOn',
|
||||
displayProps: { fixed: 'right' }
|
||||
}
|
||||
]
|
||||
},
|
||||
process.viewlet.CardExecutions
|
||||
)
|
||||
|
||||
builder.createDoc(
|
||||
view.class.Viewlet,
|
||||
core.space.Model,
|
||||
{
|
||||
attachTo: process.class.Execution,
|
||||
descriptor: view.viewlet.List,
|
||||
viewOptions: {
|
||||
groupBy: ['process', 'assignee', 'done'],
|
||||
orderBy: [
|
||||
['modifiedOn', SortingOrder.Descending],
|
||||
['createdOn', SortingOrder.Descending]
|
||||
],
|
||||
other: [
|
||||
{
|
||||
key: 'showDone',
|
||||
type: 'toggle',
|
||||
defaultValue: true,
|
||||
actionTarget: 'query',
|
||||
action: process.function.ShowDoneQuery,
|
||||
label: process.string.ShowDone
|
||||
}
|
||||
]
|
||||
},
|
||||
configOptions: {
|
||||
strict: true,
|
||||
hiddenKeys: ['process', 'currentState']
|
||||
},
|
||||
config: [
|
||||
{
|
||||
key: 'card',
|
||||
displayProps: { key: 'card', fixed: 'left' }
|
||||
},
|
||||
{
|
||||
key: '',
|
||||
label: process.string.Process,
|
||||
presenter: process.component.ExecutonPresenter,
|
||||
displayProps: { key: 'process', fixed: 'left' }
|
||||
},
|
||||
{
|
||||
key: '',
|
||||
label: process.string.Step,
|
||||
presenter: process.component.ExecutonProgressPresenter,
|
||||
displayProps: { key: 'state', fixed: 'left' }
|
||||
},
|
||||
{ key: '', presenter: process.component.ExecutonPresenter, displayProps: { grow: true } },
|
||||
{
|
||||
key: 'assignee',
|
||||
displayProps: { key: 'assignee', fixed: 'right' },
|
||||
props: { kind: 'list', shouldShowName: false, avatarSize: 'x-small' }
|
||||
},
|
||||
{
|
||||
key: 'modifiedOn',
|
||||
displayProps: { fixed: 'right' }
|
||||
},
|
||||
{
|
||||
key: 'createdOn',
|
||||
displayProps: { fixed: 'right' }
|
||||
}
|
||||
]
|
||||
},
|
||||
process.viewlet.ExecutionsList
|
||||
)
|
||||
|
||||
builder.createDoc(presentation.class.ComponentPointExtension, core.space.Model, {
|
||||
extension: card.extensions.EditCardExtension,
|
||||
component: process.component.ProcessesExtension,
|
||||
|
||||
@@ -18,6 +18,9 @@ import process from '@hcengineering/process-resources/src/plugin'
|
||||
import { type Action } from '@hcengineering/view'
|
||||
|
||||
export default mergeIds(processId, process, {
|
||||
app: {
|
||||
Process: '' as Ref<Doc>
|
||||
},
|
||||
ids: {
|
||||
ProcessSettings: '' as Ref<Doc>
|
||||
},
|
||||
|
||||
@@ -30,76 +30,41 @@
|
||||
import { createEventDispatcher } from 'svelte'
|
||||
import card from '../plugin'
|
||||
import CardsPopup from './CardsPopup.svelte'
|
||||
import CardSelector from './CardSelector.svelte'
|
||||
|
||||
export let value: Ref<Card>
|
||||
export let value: Ref<Card> | undefined
|
||||
export let readonly: boolean = false
|
||||
export let label: IntlString = card.string.Card
|
||||
export let onChange: (value: any) => void
|
||||
export let attribute: AnyAttribute
|
||||
|
||||
export let focusIndex: number | undefined = undefined
|
||||
export let shouldShowLabel: boolean = true
|
||||
export let kind: ButtonKind = 'no-border'
|
||||
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()
|
||||
|
||||
const handleOpen = (event: MouseEvent): void => {
|
||||
event.stopPropagation()
|
||||
const _class = (attribute.type as RefTo<Card>).to
|
||||
|
||||
if (readonly) {
|
||||
return
|
||||
}
|
||||
|
||||
showPopup(CardsPopup, { selected: value, _class }, eventToHTMLElement(event), change)
|
||||
const change = (val: Ref<Card> | undefined): void => {
|
||||
dispatch('change', val)
|
||||
onChange(val)
|
||||
}
|
||||
|
||||
const change = (val: Card | undefined): void => {
|
||||
if (readonly || val == null || value === val._id) {
|
||||
return
|
||||
}
|
||||
|
||||
value = val._id
|
||||
dispatch('change', value)
|
||||
onChange(value)
|
||||
}
|
||||
|
||||
let doc: Card | undefined
|
||||
|
||||
const query = createQuery()
|
||||
$: query.query(card.class.Card, { _id: value }, (res) => {
|
||||
doc = res[0]
|
||||
})
|
||||
|
||||
$: _classRef = doc?._class ?? (attribute?.type as RefTo<Card>)?.to
|
||||
$: _class = _classRef !== undefined ? (hierarchy.findClass(_classRef) as MasterTag) : undefined
|
||||
|
||||
$: icon = _class?.icon === view.ids.IconWithEmoji ? IconWithEmoji : _class?.icon
|
||||
$: iconProps = _class?.icon === view.ids.IconWithEmoji ? { icon: _class?.color } : {}
|
||||
const _class = (attribute.type as RefTo<Card>).to
|
||||
</script>
|
||||
|
||||
<Button
|
||||
showTooltip={!readonly ? { label } : undefined}
|
||||
{justify}
|
||||
<CardSelector
|
||||
{value}
|
||||
{readonly}
|
||||
{label}
|
||||
{_class}
|
||||
{focusIndex}
|
||||
{width}
|
||||
{size}
|
||||
{icon}
|
||||
{iconProps}
|
||||
{kind}
|
||||
disabled={readonly}
|
||||
on:click={handleOpen}
|
||||
>
|
||||
<div slot="content" class="overflow-label">
|
||||
{#if doc}
|
||||
{doc.title}
|
||||
{:else}
|
||||
<Label {label} />
|
||||
{/if}
|
||||
</div>
|
||||
</Button>
|
||||
{size}
|
||||
{justify}
|
||||
{width}
|
||||
on:change={(e) => {
|
||||
change(e.detail)
|
||||
}}
|
||||
/>
|
||||
|
||||
@@ -0,0 +1,101 @@
|
||||
<!--
|
||||
// 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, Class, 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 = card.string.Card
|
||||
export let _class: Ref<Class<Card>>
|
||||
|
||||
export let focusIndex: number | undefined = undefined
|
||||
export let kind: ButtonKind = 'no-border'
|
||||
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()
|
||||
|
||||
const handleOpen = (event: MouseEvent): void => {
|
||||
event.stopPropagation()
|
||||
|
||||
if (readonly) {
|
||||
return
|
||||
}
|
||||
|
||||
showPopup(CardsPopup, { selected: value, _class }, eventToHTMLElement(event), change)
|
||||
}
|
||||
|
||||
const change = (val: Card | undefined): void => {
|
||||
if (readonly || val == null || value === val._id) {
|
||||
return
|
||||
}
|
||||
|
||||
value = val._id
|
||||
dispatch('change', value)
|
||||
}
|
||||
|
||||
let doc: Card | undefined
|
||||
|
||||
const query = createQuery()
|
||||
$: query.query(card.class.Card, { _id: value }, (res) => {
|
||||
doc = res[0]
|
||||
})
|
||||
|
||||
$: _classRef = doc?._class ?? _class
|
||||
$: clazz = _classRef !== undefined ? (hierarchy.findClass(_classRef) as MasterTag) : undefined
|
||||
|
||||
$: icon = clazz?.icon === view.ids.IconWithEmoji ? IconWithEmoji : clazz?.icon
|
||||
$: iconProps = clazz?.icon === view.ids.IconWithEmoji ? { icon: clazz?.color } : {}
|
||||
</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 doc}
|
||||
{doc.title}
|
||||
{:else}
|
||||
<Label {label} />
|
||||
{/if}
|
||||
</div>
|
||||
</Button>
|
||||
@@ -160,16 +160,16 @@
|
||||
|
||||
<CardAttributeEditor value={doc} {mixins} {readonly} ignoreKeys={['title', 'content', 'parent']} />
|
||||
|
||||
<ComponentExtensions
|
||||
extension={card.extensions.EditCardExtension}
|
||||
props={{
|
||||
card: doc
|
||||
}}
|
||||
/>
|
||||
|
||||
<Content {doc} {readonly} bind:content />
|
||||
</div>
|
||||
|
||||
<ComponentExtensions
|
||||
extension={card.extensions.EditCardExtension}
|
||||
props={{
|
||||
card: doc
|
||||
}}
|
||||
/>
|
||||
|
||||
<Childs object={doc} {readonly} />
|
||||
<RelationsEditor object={doc} {readonly} />
|
||||
|
||||
|
||||
@@ -42,6 +42,8 @@ import CardEditor from './components/CardEditor.svelte'
|
||||
import CardRefPresenter from './components/CardRefPresenter.svelte'
|
||||
import ChangeType from './components/ChangeType.svelte'
|
||||
|
||||
export { default as CardSelector } from './components/CardSelector.svelte'
|
||||
|
||||
export default async (): Promise<Resources> => ({
|
||||
component: {
|
||||
MasterTags,
|
||||
|
||||
@@ -33,6 +33,9 @@
|
||||
"Trim": "Oříznout",
|
||||
"FirstValue": "První hodnota",
|
||||
"LastValue": "Poslední hodnota",
|
||||
"Random": "Náhodné"
|
||||
"Random": "Náhodné",
|
||||
"AllProcesses": "Všechny procesy",
|
||||
"MyProcesses": "Moje procesy",
|
||||
"ShowDone": "Zobrazit hotovo"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -33,6 +33,9 @@
|
||||
"Trim": "Entfernen",
|
||||
"FirstValue": "Erstes Wert",
|
||||
"LastValue": "Letztes Wert",
|
||||
"Random": "Zufällig"
|
||||
"Random": "Zufällig",
|
||||
"AllProcesses": "Alle Prozesse",
|
||||
"MyProcesses": "Meine Prozesse",
|
||||
"ShowDone": "Erledigt anzeigen"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -33,6 +33,9 @@
|
||||
"Trim": "Trim",
|
||||
"FirstValue": "First value",
|
||||
"LastValue": "Last value",
|
||||
"Random": "Random"
|
||||
"Random": "Random",
|
||||
"AllProcesses": "All processes",
|
||||
"MyProcesses": "My processes",
|
||||
"ShowDone": "Show done"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -33,6 +33,9 @@
|
||||
"Trim": "Cortar",
|
||||
"FirstValue": "Primer valor",
|
||||
"LastValue": "Último valor",
|
||||
"Random": "Aleatorio"
|
||||
"Random": "Aleatorio",
|
||||
"AllProcesses": "Todos los procesos",
|
||||
"MyProcesses": "Mis procesos",
|
||||
"ShowDone": "Mostrar hecho"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -33,6 +33,9 @@
|
||||
"Trim": "Couper",
|
||||
"FirstValue": "Premier valeur",
|
||||
"LastValue": "Dernière valeur",
|
||||
"Random": "Aléatoire"
|
||||
"Random": "Aléatoire",
|
||||
"AllProcesses": "Tous les processus",
|
||||
"MyProcesses": "Mes processus",
|
||||
"ShowDone": "Afficher terminé"
|
||||
}
|
||||
}
|
||||
@@ -33,6 +33,9 @@
|
||||
"Trim": "Taglia",
|
||||
"FirstValue": "Primo valore",
|
||||
"LastValue": "Ultimo valore",
|
||||
"Random": "Casuale"
|
||||
"Random": "Casuale",
|
||||
"AllProcesses": "Tutti i processi",
|
||||
"MyProcesses": "I miei processi",
|
||||
"ShowDone": "Mostra completato"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -33,6 +33,9 @@
|
||||
"Trim": "Apagar",
|
||||
"FirstValue": "Primeiro Valor",
|
||||
"LastValue": "Último Valor",
|
||||
"Random": "Aleatório"
|
||||
"Random": "Aleatório",
|
||||
"AllProcesses": "Todos os Processos",
|
||||
"MyProcesses": "Meus Processos",
|
||||
"ShowDone": "Mostrar Concluído"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -34,6 +34,9 @@
|
||||
"Trim": "Обрезать",
|
||||
"FirstValue": "Первое значение",
|
||||
"LastValue": "Последнее значение",
|
||||
"Random": "Случайное"
|
||||
"Random": "Случайное",
|
||||
"AllProcesses": "Все процессы",
|
||||
"MyProcesses": "Мои процессы",
|
||||
"ShowDone": "Показать выполненные"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -33,6 +33,9 @@
|
||||
"Trim": "截断",
|
||||
"FirstValue": "第一个值",
|
||||
"LastValue": "最后一个值",
|
||||
"Random": "随机"
|
||||
"Random": "随机",
|
||||
"AllProcesses": "所有过程",
|
||||
"MyProcesses": "我的过程",
|
||||
"ShowDone": "显示已完成"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -44,7 +44,10 @@
|
||||
"@hcengineering/setting-resources": "^0.6.0",
|
||||
"@hcengineering/view": "^0.6.13",
|
||||
"@hcengineering/view-resources": "^0.6.0",
|
||||
"@hcengineering/card-resources": "^0.6.0",
|
||||
"@hcengineering/contact": "^0.6.24",
|
||||
"@hcengineering/card": "^0.6.0",
|
||||
"@hcengineering/workbench-resources": "^0.6.1",
|
||||
"@hcengineering/core": "^0.6.32",
|
||||
"@hcengineering/ui": "^0.6.15",
|
||||
"@hcengineering/rank": "^0.6.4",
|
||||
|
||||
@@ -0,0 +1,89 @@
|
||||
<!--
|
||||
// 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 { getCurrentEmployee } from '@hcengineering/contact'
|
||||
import { createQuery } from '@hcengineering/presentation'
|
||||
import { Process } from '@hcengineering/process'
|
||||
import { Separator, deviceOptionsStore as deviceInfo } from '@hcengineering/ui'
|
||||
import { SpecialView } from '@hcengineering/workbench-resources'
|
||||
import { onDestroy } from 'svelte'
|
||||
import plugin from '../plugin'
|
||||
import { Special } from '../types'
|
||||
import Navigator from './Navigator.svelte'
|
||||
import RunProcessCardPopup from './RunProcessCardPopup.svelte'
|
||||
|
||||
export let currentSpace: string
|
||||
|
||||
const query = createQuery()
|
||||
let processes: Process[] = []
|
||||
query.query(plugin.class.Process, {}, (res) => {
|
||||
processes = res
|
||||
})
|
||||
|
||||
$: selectedProcess = processes.find((p) => p._id === currentSpace)
|
||||
|
||||
let replacedPanel: HTMLElement
|
||||
$: $deviceInfo.replacedPanel = replacedPanel
|
||||
onDestroy(() => ($deviceInfo.replacedPanel = undefined))
|
||||
|
||||
const specials: Special[] = [
|
||||
{
|
||||
_id: 'all',
|
||||
label: plugin.string.AllProcesses,
|
||||
baseQuery: {}
|
||||
},
|
||||
{
|
||||
_id: 'my',
|
||||
label: plugin.string.MyProcesses,
|
||||
baseQuery: {
|
||||
assignee: getCurrentEmployee()
|
||||
}
|
||||
}
|
||||
]
|
||||
|
||||
$: selectedSpecial = specials.find((s) => s._id === currentSpace)
|
||||
$: baseQuery =
|
||||
selectedProcess !== undefined
|
||||
? { process: selectedProcess._id }
|
||||
: selectedSpecial !== undefined
|
||||
? selectedSpecial.baseQuery
|
||||
: {}
|
||||
</script>
|
||||
|
||||
<div class="hulyPanels-container">
|
||||
{#if $deviceInfo.navigator.visible}
|
||||
<Navigator {processes} {currentSpace} {specials} />
|
||||
<Separator
|
||||
name={'workbench'}
|
||||
float={$deviceInfo.navigator.float}
|
||||
index={0}
|
||||
color={'transparent'}
|
||||
separatorSize={0}
|
||||
short
|
||||
/>
|
||||
{/if}
|
||||
|
||||
<div class="hulyComponent" bind:this={replacedPanel}>
|
||||
<SpecialView
|
||||
_class={plugin.class.Execution}
|
||||
label={plugin.string.Processes}
|
||||
icon={plugin.icon.Process}
|
||||
{baseQuery}
|
||||
createLabel={plugin.string.RunProcess}
|
||||
createComponent={plugin.component.RunProcessCardPopup}
|
||||
createComponentProps={{ value: selectedProcess?._id }}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
@@ -0,0 +1,73 @@
|
||||
<!--
|
||||
// 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 { cardId } from '@hcengineering/card'
|
||||
import { Process } from '@hcengineering/process'
|
||||
import { deviceOptionsStore as deviceInfo, NavItem, Scroller } from '@hcengineering/ui'
|
||||
import { NavLink } from '@hcengineering/view-resources'
|
||||
import { NavFooter, NavHeader, SavedView } from '@hcengineering/workbench-resources'
|
||||
import plugin from '../plugin'
|
||||
import { Special } from '../types'
|
||||
|
||||
export let currentSpace: string
|
||||
export let processes: Process[]
|
||||
|
||||
export let specials: Special[]
|
||||
|
||||
let menuSelection: boolean = false
|
||||
</script>
|
||||
|
||||
<div
|
||||
class="antiPanel-navigator {$deviceInfo.navigator.direction === 'horizontal' ? 'portrait' : 'landscape'} border-left"
|
||||
class:fly={$deviceInfo.navigator.float}
|
||||
>
|
||||
<div class="antiPanel-wrap__content hulyNavPanel-container">
|
||||
<NavHeader label={plugin.string.Processes} />
|
||||
{#each specials as special}
|
||||
<NavLink space={special._id}>
|
||||
<NavItem
|
||||
_id={special._id}
|
||||
label={special.label}
|
||||
isFold
|
||||
empty
|
||||
selected={!menuSelection && special._id === currentSpace}
|
||||
/>
|
||||
</NavLink>
|
||||
{/each}
|
||||
|
||||
<SavedView alias={cardId} on:select={(res) => (menuSelection = res.detail)} />
|
||||
|
||||
{#if processes.length > 0}
|
||||
<div class="antiNav-divider line" />
|
||||
<Scroller shrink>
|
||||
{#each processes as process}
|
||||
<NavLink space={process._id}>
|
||||
<NavItem
|
||||
_id={process._id}
|
||||
title={process.name}
|
||||
isFold
|
||||
empty
|
||||
selected={!menuSelection && process._id === currentSpace}
|
||||
/>
|
||||
</NavLink>
|
||||
{/each}
|
||||
</Scroller>
|
||||
{/if}
|
||||
|
||||
<div class="mt-2" />
|
||||
|
||||
<NavFooter split />
|
||||
</div>
|
||||
</div>
|
||||
@@ -29,7 +29,6 @@
|
||||
} from '@hcengineering/ui'
|
||||
import view, { Viewlet, ViewletPreference, ViewOptions } from '@hcengineering/view'
|
||||
import { List, ListSelectionProvider, SelectDirection, ViewletsSettingButton } from '@hcengineering/view-resources'
|
||||
import { createEventDispatcher } from 'svelte'
|
||||
import process from '../plugin'
|
||||
import RunProcessPopup from './RunProcessPopup.svelte'
|
||||
|
||||
|
||||
@@ -0,0 +1,79 @@
|
||||
<!--
|
||||
// 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 as CardPopup, getClient } from '@hcengineering/presentation'
|
||||
import plugin from '../plugin'
|
||||
import { Card } from '@hcengineering/card'
|
||||
import { CardSelector } from '@hcengineering/card-resources'
|
||||
import { Dropdown, Label, ListItem, Loading } from '@hcengineering/ui'
|
||||
import { Process } from '@hcengineering/process'
|
||||
import { createEventDispatcher } from 'svelte'
|
||||
import core, { Ref } from '@hcengineering/core'
|
||||
|
||||
export let value: Ref<Process> | undefined
|
||||
|
||||
const client = getClient()
|
||||
|
||||
let process: Ref<Process> | undefined = value
|
||||
let card: Ref<Card> | undefined
|
||||
|
||||
const processes = client.getModel().findAllSync(plugin.class.Process, {})
|
||||
|
||||
const items: ListItem[] = processes.map((p) => ({ label: p.name, _id: p._id }))
|
||||
|
||||
const dispatch = createEventDispatcher()
|
||||
|
||||
async function runProcess (): Promise<void> {
|
||||
if (process === undefined || card === undefined) return
|
||||
await client.createDoc(plugin.class.Execution, core.space.Workspace, {
|
||||
process,
|
||||
currentState: null,
|
||||
card,
|
||||
done: false,
|
||||
rollback: {},
|
||||
currentToDo: null,
|
||||
assignee: null
|
||||
})
|
||||
dispatch('close')
|
||||
}
|
||||
|
||||
$: selectedProcess = processes.find((p) => p._id === process)
|
||||
</script>
|
||||
|
||||
<CardPopup
|
||||
label={plugin.string.RunProcess}
|
||||
canSave={process !== undefined && card !== undefined}
|
||||
okAction={runProcess}
|
||||
width={'menu'}
|
||||
on:close
|
||||
>
|
||||
<div class="mb-4">
|
||||
<Dropdown
|
||||
{items}
|
||||
kind={'regular'}
|
||||
size={'medium'}
|
||||
placeholder={plugin.string.Process}
|
||||
disabled={value !== undefined}
|
||||
selected={items.find((i) => i._id === process)}
|
||||
on:selected={(e) => {
|
||||
process = e.detail._id
|
||||
card = undefined
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
{#if selectedProcess !== undefined}
|
||||
<CardSelector kind={'regular'} size={'medium'} bind:value={card} _class={selectedProcess.masterTag} />
|
||||
{/if}
|
||||
</CardPopup>
|
||||
@@ -14,12 +14,12 @@
|
||||
-->
|
||||
<script lang="ts">
|
||||
import { Card } from '@hcengineering/card'
|
||||
import { getClient } from '@hcengineering/presentation'
|
||||
import process from '../plugin'
|
||||
import { Label, Loading } from '@hcengineering/ui'
|
||||
import { Process } from '@hcengineering/process'
|
||||
import { createEventDispatcher } from 'svelte'
|
||||
import core, { Ref } from '@hcengineering/core'
|
||||
import { getClient } from '@hcengineering/presentation'
|
||||
import { Process } from '@hcengineering/process'
|
||||
import { Label } from '@hcengineering/ui'
|
||||
import { createEventDispatcher } from 'svelte'
|
||||
import process from '../plugin'
|
||||
|
||||
export let value: Card
|
||||
|
||||
@@ -30,19 +30,8 @@
|
||||
const mixins = h.getDescendants(value._class).filter((p) => h.hasMixin(value, p))
|
||||
const resClasses = [...asc, ...mixins]
|
||||
|
||||
let loading = true
|
||||
let processes: Process[] = []
|
||||
|
||||
void client
|
||||
.findAll(process.class.Process, {})
|
||||
.then((res) => {
|
||||
processes = res.filter((it) => resClasses.includes(it.masterTag))
|
||||
loading = false
|
||||
})
|
||||
.catch((err) => {
|
||||
console.error(err)
|
||||
loading = false
|
||||
})
|
||||
const res = client.getModel().findAllSync(process.class.Process, {})
|
||||
const processes = res.filter((it) => resClasses.includes(it.masterTag))
|
||||
|
||||
const dispatch = createEventDispatcher()
|
||||
|
||||
@@ -63,10 +52,10 @@
|
||||
<div class="antiPopup">
|
||||
<div class="ap-space x2" />
|
||||
<div class="ap-scroll">
|
||||
{#if loading}
|
||||
<Loading />
|
||||
{:else if processes.length === 0}
|
||||
<Label label={process.string.NoProcesses} />
|
||||
{#if processes.length === 0}
|
||||
<div class="flex-row-center p-4">
|
||||
<Label label={process.string.NoProcesses} />
|
||||
</div>
|
||||
{:else}
|
||||
{#each processes as process}
|
||||
<button
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
|
||||
export let size: IconSize = 'small'
|
||||
|
||||
export let fill: number = -1
|
||||
export let fill: number = 21
|
||||
|
||||
const dispatch = createEventDispatcher()
|
||||
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
|
||||
export let size: IconSize = 'small'
|
||||
|
||||
export let fill: number = -1
|
||||
export let fill: number = 17
|
||||
|
||||
const dispatch = createEventDispatcher()
|
||||
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
|
||||
export let size: IconSize = 'small'
|
||||
|
||||
export let fill: number = -1
|
||||
export let fill: number = 11
|
||||
|
||||
const dispatch = createEventDispatcher()
|
||||
|
||||
|
||||
@@ -28,6 +28,9 @@ import ProcessPresenter from './components/ProcessPresenter.svelte'
|
||||
import NestedContextSelector from './components/attributeEditors/NestedContextSelector.svelte'
|
||||
import RelatedContextSelector from './components/attributeEditors/RelatedContextSelector.svelte'
|
||||
import FunctionSelector from './components/attributeEditors/FunctionSelector.svelte'
|
||||
import Main from './components/Main.svelte'
|
||||
import RunProcessCardPopup from './components/RunProcessCardPopup.svelte'
|
||||
import { showDoneQuery } from './utils'
|
||||
|
||||
export default async (): Promise<Resources> => ({
|
||||
component: {
|
||||
@@ -46,6 +49,11 @@ export default async (): Promise<Resources> => ({
|
||||
ProcessPresenter,
|
||||
NestedContextSelector,
|
||||
RelatedContextSelector,
|
||||
FunctionSelector
|
||||
FunctionSelector,
|
||||
Main,
|
||||
RunProcessCardPopup
|
||||
},
|
||||
function: {
|
||||
ShowDoneQuery: showDoneQuery
|
||||
}
|
||||
})
|
||||
|
||||
@@ -15,13 +15,15 @@ import { type Ref } from '@hcengineering/core'
|
||||
import { type IntlString, mergeIds } from '@hcengineering/platform'
|
||||
import process, { processId } from '@hcengineering/process'
|
||||
import { type AnyComponent } from '@hcengineering/ui'
|
||||
import { type Viewlet } from '@hcengineering/view'
|
||||
import { type ViewQueryAction, type Viewlet } from '@hcengineering/view'
|
||||
|
||||
export default mergeIds(processId, process, {
|
||||
viewlet: {
|
||||
ExecutionsList: '' as Ref<Viewlet>,
|
||||
CardExecutions: '' as Ref<Viewlet>
|
||||
},
|
||||
component: {
|
||||
Main: '' as AnyComponent,
|
||||
ProcessEditor: '' as AnyComponent,
|
||||
ProcessesSettingSection: '' as AnyComponent,
|
||||
SubProcessEditor: '' as AnyComponent,
|
||||
@@ -37,7 +39,11 @@ export default mergeIds(processId, process, {
|
||||
ExecutonProgressPresenter: '' as AnyComponent,
|
||||
NestedContextSelector: '' as AnyComponent,
|
||||
RelatedContextSelector: '' as AnyComponent,
|
||||
FunctionSelector: '' as AnyComponent
|
||||
FunctionSelector: '' as AnyComponent,
|
||||
RunProcessCardPopup: '' as AnyComponent
|
||||
},
|
||||
function: {
|
||||
ShowDoneQuery: '' as ViewQueryAction
|
||||
},
|
||||
string: {
|
||||
DeleteProcess: '' as IntlString,
|
||||
@@ -67,6 +73,9 @@ export default mergeIds(processId, process, {
|
||||
Trim: '' as IntlString,
|
||||
FirstValue: '' as IntlString,
|
||||
LastValue: '' as IntlString,
|
||||
Random: '' as IntlString
|
||||
Random: '' as IntlString,
|
||||
MyProcesses: '' as IntlString,
|
||||
AllProcesses: '' as IntlString,
|
||||
ShowDone: '' as IntlString
|
||||
}
|
||||
})
|
||||
|
||||
@@ -0,0 +1,22 @@
|
||||
// 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.
|
||||
|
||||
import { type DocumentQuery } from '@hcengineering/core'
|
||||
import { type IntlString } from '@hcengineering/platform'
|
||||
import { type Execution } from '@hcengineering/process'
|
||||
|
||||
export interface Special {
|
||||
_id: string
|
||||
label: IntlString
|
||||
baseQuery: DocumentQuery<Execution>
|
||||
}
|
||||
@@ -20,7 +20,8 @@ import core, {
|
||||
type Doc,
|
||||
type Ref,
|
||||
type RefTo,
|
||||
type Type
|
||||
type Type,
|
||||
type DocumentQuery
|
||||
} from '@hcengineering/core'
|
||||
import process, {
|
||||
type Context,
|
||||
@@ -178,3 +179,10 @@ export function getValueReduceFunc (source: AnyAttribute, target: AnyAttribute):
|
||||
if (target.type._class === core.class.ArrOf) return undefined
|
||||
return process.function.FirstValue
|
||||
}
|
||||
|
||||
export function showDoneQuery (value: any, query: DocumentQuery<Doc>): DocumentQuery<Doc> {
|
||||
if (value === false) {
|
||||
return { ...query, done: false }
|
||||
}
|
||||
return query
|
||||
}
|
||||
|
||||
@@ -286,6 +286,7 @@
|
||||
if (attr === undefined) return
|
||||
if (attribute.collectionAttr) return
|
||||
if (attribute.isLookup) return
|
||||
if (attribute?.attribute?.readonly === true) return
|
||||
const key = attribute.castRequest ? attribute.key.substring(attribute.castRequest.length + 1) : attribute.key
|
||||
return (value: any) => {
|
||||
onChange(value, doc, key, attr)
|
||||
|
||||
@@ -61,24 +61,12 @@
|
||||
if (attr === undefined) return
|
||||
if (attribute.collectionAttr) return
|
||||
if (attribute.isLookup) return
|
||||
if (attribute?.attribute?.readonly === true) return
|
||||
return (value: any) => {
|
||||
onChange(value, docObject, attribute.key, attr)
|
||||
}
|
||||
}
|
||||
|
||||
function getProps (props: Record<string, any>, readonly: boolean): Record<string, any> {
|
||||
if (readonly) {
|
||||
return {
|
||||
...props,
|
||||
readonly: true,
|
||||
disabled: true,
|
||||
editable: false,
|
||||
isEditable: false
|
||||
}
|
||||
}
|
||||
return props
|
||||
}
|
||||
|
||||
$: mobile = $deviceInfo.isMobile
|
||||
$: needCompact =
|
||||
model.filter((m) => m.displayProps?.optional || m.displayProps?.compression || m.displayProps?.suffix).length > 0
|
||||
@@ -145,7 +133,7 @@
|
||||
<ListPresenter
|
||||
{docObject}
|
||||
attributeModel={attrModel}
|
||||
props={getProps(props, $restrictionStore.readonly)}
|
||||
{props}
|
||||
{compactMode}
|
||||
value={getObjectValue(attrModel.key, docObject)}
|
||||
onChange={getOnChange(docObject, attrModel)}
|
||||
@@ -159,7 +147,7 @@
|
||||
<ListPresenter
|
||||
{docObject}
|
||||
attributeModel={attrModel}
|
||||
props={getProps(props, $restrictionStore.readonly)}
|
||||
{props}
|
||||
value={getObjectValue(attrModel.key, docObject)}
|
||||
onChange={getOnChange(docObject, attrModel)}
|
||||
on:resize={(e) => {
|
||||
@@ -174,7 +162,7 @@
|
||||
<ListPresenter
|
||||
{docObject}
|
||||
attributeModel={attrModel}
|
||||
props={getProps(props, $restrictionStore.readonly)}
|
||||
{props}
|
||||
value={getObjectValue(attrModel.key, docObject)}
|
||||
onChange={getOnChange(docObject, attrModel)}
|
||||
/>
|
||||
@@ -184,7 +172,7 @@
|
||||
<ListPresenter
|
||||
{docObject}
|
||||
{attributeModel}
|
||||
props={getProps(props, $restrictionStore.readonly)}
|
||||
{props}
|
||||
value={getObjectValue(attributeModel.key, docObject)}
|
||||
onChange={getOnChange(docObject, attributeModel)}
|
||||
hideDivider={i === 0}
|
||||
@@ -213,7 +201,7 @@
|
||||
<ListPresenter
|
||||
{docObject}
|
||||
attributeModel={attrModel}
|
||||
props={getProps(props, $restrictionStore.readonly)}
|
||||
{props}
|
||||
{compactMode}
|
||||
value={getObjectValue(attrModel.key, docObject)}
|
||||
onChange={getOnChange(docObject, attrModel)}
|
||||
@@ -228,7 +216,7 @@
|
||||
<ListPresenter
|
||||
{docObject}
|
||||
{attributeModel}
|
||||
props={getProps(props, $restrictionStore.readonly)}
|
||||
{props}
|
||||
value={getObjectValue(attributeModel.key, docObject)}
|
||||
onChange={getOnChange(docObject, attributeModel)}
|
||||
hideDivider={j === 0}
|
||||
@@ -246,7 +234,7 @@
|
||||
<ListPresenter
|
||||
{docObject}
|
||||
{attributeModel}
|
||||
props={getProps(props, $restrictionStore.readonly)}
|
||||
{props}
|
||||
value={getObjectValue(attributeModel.key, docObject)}
|
||||
onChange={getOnChange(docObject, attributeModel)}
|
||||
/>
|
||||
|
||||
@@ -16,7 +16,7 @@
|
||||
import { createEventDispatcher } from 'svelte'
|
||||
import core, { Doc } from '@hcengineering/core'
|
||||
import { AttributeModel } from '@hcengineering/view'
|
||||
import { FixedColumn } from '../..'
|
||||
import { FixedColumn, restrictionStore } from '../..'
|
||||
import DividerPresenter from './DividerPresenter.svelte'
|
||||
|
||||
export let docObject: Doc
|
||||
@@ -31,12 +31,21 @@
|
||||
|
||||
$: dp = attributeModel?.displayProps
|
||||
|
||||
function joinProps (attribute: AttributeModel, object: Doc, props: Record<string, any>) {
|
||||
function joinProps (attribute: AttributeModel, object: Doc, props: Record<string, any>, readonly: boolean) {
|
||||
const readonlyParams =
|
||||
readonly || (attribute?.attribute?.readonly ?? false)
|
||||
? {
|
||||
readonly: true,
|
||||
disabled: true,
|
||||
editable: false,
|
||||
isEditable: false
|
||||
}
|
||||
: {}
|
||||
const clearAttributeProps = attribute.props
|
||||
if (attribute.attribute?.type._class === core.class.EnumOf) {
|
||||
return { ...clearAttributeProps, type: attribute.attribute.type, ...props }
|
||||
return { ...clearAttributeProps, type: attribute.attribute.type, ...props, ...readonlyParams }
|
||||
}
|
||||
return { object, ...clearAttributeProps, space: object.space, ...props }
|
||||
return { object, ...clearAttributeProps, space: object.space, ...props, ...readonlyParams }
|
||||
}
|
||||
const translateSize = (e: CustomEvent): void => {
|
||||
if (e.detail === undefined) return
|
||||
@@ -56,7 +65,7 @@
|
||||
kind={'list'}
|
||||
{compactMode}
|
||||
label={attributeModel.label}
|
||||
{...joinProps(attributeModel, docObject, props)}
|
||||
{...joinProps(attributeModel, docObject, props, $restrictionStore.readonly)}
|
||||
on:resize={translateSize}
|
||||
/>
|
||||
</FixedColumn>
|
||||
@@ -68,7 +77,7 @@
|
||||
kind={'list'}
|
||||
label={attributeModel.label}
|
||||
{compactMode}
|
||||
{...joinProps(attributeModel, docObject, props)}
|
||||
{...joinProps(attributeModel, docObject, props, $restrictionStore.readonly)}
|
||||
on:resize={translateSize}
|
||||
/>
|
||||
{/if}
|
||||
|
||||
Reference in New Issue
Block a user