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()