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
@@ -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
}