processes UI (#8349)

This commit is contained in:
Denis Bykhov
2025-03-26 17:13:09 +07:00
committed by GitHub
parent e08b0392fa
commit 0641da8a15
32 changed files with 628 additions and 140 deletions
+1
View File
@@ -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
View File
@@ -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,
+3
View File
@@ -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} />
+2
View File
@@ -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,
+4 -1
View File
@@ -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"
}
}
+4 -1
View File
@@ -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"
}
}
+4 -1
View File
@@ -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"
}
}
+4 -1
View File
@@ -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"
}
}
+4 -1
View File
@@ -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é"
}
}
+4 -1
View File
@@ -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"
}
}
+4 -1
View File
@@ -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"
}
}
+4 -1
View File
@@ -34,6 +34,9 @@
"Trim": "Обрезать",
"FirstValue": "Первое значение",
"LastValue": "Последнее значение",
"Random": "Случайное"
"Random": "Случайное",
"AllProcesses": "Все процессы",
"MyProcesses": "Мои процессы",
"ShowDone": "Показать выполненные"
}
}
+4 -1
View File
@@ -33,6 +33,9 @@
"Trim": "截断",
"FirstValue": "第一个值",
"LastValue": "最后一个值",
"Random": "随机"
"Random": "随机",
"AllProcesses": "所有过程",
"MyProcesses": "我的过程",
"ShowDone": "显示已完成"
}
}
+3
View File
@@ -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()
+9 -1
View File
@@ -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
}
})
+12 -3
View File
@@ -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
}
})
+22
View File
@@ -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>
}
+9 -1
View File
@@ -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}