mirror of
https://github.com/hcengineering/platform.git
synced 2026-09-10 03:37:43 +02:00
Improve process ui (#9645)
This commit is contained in:
+2
@@ -26,6 +26,7 @@
|
||||
ViewletsSettingButton
|
||||
} from '@hcengineering/view-resources'
|
||||
import { createEventDispatcher } from 'svelte'
|
||||
import NextTriggers from './NextTriggers.svelte'
|
||||
import process from '../plugin'
|
||||
|
||||
export let execution: Execution
|
||||
@@ -128,6 +129,7 @@
|
||||
showCancelButton={false}
|
||||
on:close
|
||||
>
|
||||
<NextTriggers {execution} />
|
||||
<div class="flex-row-reverse">
|
||||
<ViewletsSettingButton bind:viewOptions viewletQuery={{ _id: viewletId }} kind={'tertiary'} bind:viewlet />
|
||||
</div>
|
||||
@@ -19,7 +19,7 @@
|
||||
import ErrorPresenter from './ErrorPresenter.svelte'
|
||||
import { continueExecution } from '../utils'
|
||||
import { showPopup } from '@hcengineering/ui'
|
||||
import ExecutionLogs from './ExecutionLogs.svelte'
|
||||
import ExecutionDetails from './ExecutionDetails.svelte'
|
||||
|
||||
export let value: WithLookup<Execution>
|
||||
|
||||
@@ -29,7 +29,7 @@
|
||||
|
||||
function open (): void {
|
||||
showPopup(
|
||||
ExecutionLogs,
|
||||
ExecutionDetails,
|
||||
{
|
||||
execution: value
|
||||
},
|
||||
|
||||
@@ -0,0 +1,48 @@
|
||||
<!--
|
||||
// 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 { getClient } from '@hcengineering/presentation'
|
||||
import plugin from '../plugin'
|
||||
import { Execution, ExecutionStatus } from '@hcengineering/process'
|
||||
import TransitionPresenter from './settings/TransitionPresenter.svelte'
|
||||
import TriggerPresenter from './settings/TriggerPresenter.svelte'
|
||||
import { Label } from '@hcengineering/ui'
|
||||
|
||||
export let execution: Execution
|
||||
|
||||
const client = getClient()
|
||||
const model = client.getModel()
|
||||
|
||||
$: transitions = model.findAllSync(plugin.class.Transition, {
|
||||
process: execution.process,
|
||||
from: execution.currentState
|
||||
})
|
||||
|
||||
$: process = model.findObject(execution.process)
|
||||
</script>
|
||||
|
||||
{#if execution.status === ExecutionStatus.Done}
|
||||
<Label label={plugin.string.Done} />
|
||||
{:else if execution.status === ExecutionStatus.Cancelled}
|
||||
<Label label={plugin.string.Cancelled} />
|
||||
{:else if process !== undefined}
|
||||
{#each transitions as transition}
|
||||
<div class="flex-row-center flex-gap-2">
|
||||
<TransitionPresenter {transition} direction={'from'} />
|
||||
<TriggerPresenter value={transition.trigger} {process} params={transition.triggerParams} withLabel={true} />
|
||||
</div>
|
||||
{/each}
|
||||
{/if}
|
||||
@@ -21,7 +21,6 @@
|
||||
AnyComponent,
|
||||
Button,
|
||||
Component,
|
||||
DropdownIntlItem,
|
||||
DropdownLabelsIntl,
|
||||
eventToHTMLElement,
|
||||
IconAdd,
|
||||
@@ -30,9 +29,11 @@
|
||||
} from '@hcengineering/ui'
|
||||
import view from '@hcengineering/view-resources/src/plugin'
|
||||
import { createEventDispatcher } from 'svelte'
|
||||
import { buildResult, Mode, Modes, parseValue } from '../../query'
|
||||
import { getContext } from '../../utils'
|
||||
import ContextSelectorPopup from '../attributeEditors/ContextSelectorPopup.svelte'
|
||||
import ContextValue from '../attributeEditors/ContextValue.svelte'
|
||||
import is from 'date-fns/locale/is'
|
||||
|
||||
export let readonly: boolean
|
||||
export let value: any
|
||||
@@ -41,46 +42,30 @@
|
||||
export let attribute: AnyAttribute
|
||||
export let baseEditor: AnyComponent | undefined
|
||||
|
||||
const modes: Mode[] = [
|
||||
Modes.ArrayAll,
|
||||
Modes.ArrayAny,
|
||||
Modes.ArrayNotIncludes,
|
||||
Modes.ArraySizeEquals,
|
||||
Modes.ArraySizeGt,
|
||||
Modes.ArraySizeGte,
|
||||
Modes.ArraySizeLt,
|
||||
Modes.ArraySizeLte
|
||||
]
|
||||
|
||||
const client = getClient()
|
||||
|
||||
$: numberContext = getContext(client, process, core.class.TypeNumber, 'attribute')
|
||||
|
||||
const modes: DropdownIntlItem[] = [
|
||||
{ id: 'all', label: view.string.FilterArrayAll },
|
||||
{ id: 'any', label: view.string.FilterArrayAny },
|
||||
{ id: 'notall', label: view.string.NotContains },
|
||||
{ id: 'size', label: view.string.ArraySizeEquals },
|
||||
{ id: 'sizeGt', label: view.string.ArraySizeGt },
|
||||
{ id: 'sizeGte', label: view.string.ArraySizeGte },
|
||||
{ id: 'sizeLt', label: view.string.ArraySizeLt },
|
||||
{ id: 'sizeLte', label: view.string.ArraySizeLte }
|
||||
]
|
||||
let [val, selectedMode] = parseValue(modes, value)
|
||||
|
||||
let mode =
|
||||
value == null
|
||||
? 'all'
|
||||
: value.$all !== undefined
|
||||
? 'all'
|
||||
: value.$in !== undefined
|
||||
? 'any'
|
||||
: value.$nin !== undefined
|
||||
? 'notIncludes'
|
||||
: value.$size !== undefined
|
||||
? typeof value.$size === 'object'
|
||||
? Object.keys(value.$size)[0] === '$gt'
|
||||
? 'sizeGt'
|
||||
: Object.keys(value.$size)[0] === '$gte'
|
||||
? 'sizeGte'
|
||||
: Object.keys(value.$size)[0] === '$lt'
|
||||
? 'sizeLt'
|
||||
: 'sizeLte'
|
||||
: 'size'
|
||||
: 'all'
|
||||
$: mode = selectedMode.id
|
||||
|
||||
const dispatch = createEventDispatcher()
|
||||
|
||||
let contextValue: SelectedContext | undefined = undefined
|
||||
let val: any = undefined
|
||||
$: [val, selectedMode] = parseValue(modes, value)
|
||||
$: contextValue = parseContext(val)
|
||||
|
||||
function selectContext (e: MouseEvent): void {
|
||||
showPopup(
|
||||
@@ -96,62 +81,22 @@
|
||||
)
|
||||
}
|
||||
|
||||
$: parseValue(value)
|
||||
|
||||
function parseValue (value: any) {
|
||||
if (value?.$all !== undefined) {
|
||||
mode = 'all'
|
||||
val = value.$all
|
||||
} else if (value?.$in !== undefined) {
|
||||
mode = 'any'
|
||||
val = value.$in
|
||||
} else if (value?.$nin !== undefined) {
|
||||
mode = 'notIncludes'
|
||||
val = value.$nin
|
||||
} else if (value?.$size !== undefined) {
|
||||
if (typeof value.$size === 'object') {
|
||||
const operator = Object.keys(value.$size)[0]
|
||||
val = value.$size[operator]
|
||||
mode =
|
||||
operator === '$gt' ? 'sizeGt' : operator === '$gte' ? 'sizeGte' : operator === '$lt' ? 'sizeLt' : 'sizeLte'
|
||||
} else {
|
||||
mode = 'size'
|
||||
val = value.$size
|
||||
}
|
||||
} else {
|
||||
mode = 'all'
|
||||
val = undefined
|
||||
}
|
||||
contextValue = parseContext(val)
|
||||
}
|
||||
|
||||
function onSelect (res: SelectedContext | null): void {
|
||||
buildResult(res === null ? undefined : '$' + JSON.stringify(res))
|
||||
changeResult(res === null ? undefined : '$' + JSON.stringify(res))
|
||||
}
|
||||
|
||||
function onChange (value: any | undefined): void {
|
||||
buildResult(value)
|
||||
changeResult(value)
|
||||
}
|
||||
|
||||
function buildResult (val: any | undefined) {
|
||||
if (val === undefined || val === '') {
|
||||
function changeResult (val: any | undefined, allowEmpty: boolean = false): void {
|
||||
if ((!allowEmpty && val === undefined) || val === '') {
|
||||
dispatch('change', null)
|
||||
return
|
||||
}
|
||||
|
||||
let result: any
|
||||
if (mode === 'all') {
|
||||
result = { $all: val }
|
||||
} else if (mode === 'any') {
|
||||
result = { $in: val }
|
||||
} else if (mode === 'notIncludes') {
|
||||
result = { $nin: val }
|
||||
} else if (mode === 'size') {
|
||||
result = { $size: val }
|
||||
} else if (mode.startsWith('size')) {
|
||||
const operator = mode === 'sizeGt' ? '$gt' : mode === 'sizeGte' ? '$gte' : mode === 'sizeLt' ? '$lt' : '$lte'
|
||||
result = { $size: { [operator]: val } }
|
||||
}
|
||||
const result = buildResult(selectedMode, val)
|
||||
|
||||
value = result
|
||||
dispatch('change', result)
|
||||
}
|
||||
@@ -171,7 +116,12 @@
|
||||
width={'100%'}
|
||||
on:selected={(e) => {
|
||||
mode = e.detail
|
||||
buildResult(val)
|
||||
if ((isSize(mode) && !isSize(selectedMode.id)) || (!isSize(mode) && isSize(selectedMode.id))) {
|
||||
val = undefined
|
||||
contextValue = undefined
|
||||
}
|
||||
selectedMode = modes.find((m) => m.id === mode) ?? modes[0]
|
||||
changeResult(val, true)
|
||||
}}
|
||||
/>
|
||||
|
||||
|
||||
@@ -0,0 +1,183 @@
|
||||
<!--
|
||||
// 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 { AnyAttribute } from '@hcengineering/core'
|
||||
import { getAttributePresenterClass, getClient } from '@hcengineering/presentation'
|
||||
import { Context, parseContext, Process, SelectedContext } from '@hcengineering/process'
|
||||
import {
|
||||
AnyComponent,
|
||||
Button,
|
||||
Component,
|
||||
DropdownLabelsIntl,
|
||||
eventToHTMLElement,
|
||||
IconAdd,
|
||||
IconClose,
|
||||
showPopup
|
||||
} from '@hcengineering/ui'
|
||||
import { createEventDispatcher } from 'svelte'
|
||||
import { buildResult, Mode, Modes, parseValue } from '../../query'
|
||||
import ContextSelectorPopup from '../attributeEditors/ContextSelectorPopup.svelte'
|
||||
import ContextValue from '../attributeEditors/ContextValue.svelte'
|
||||
|
||||
export let readonly: boolean
|
||||
export let value: any
|
||||
export let process: Process
|
||||
export let context: Context
|
||||
export let attribute: AnyAttribute
|
||||
export let baseEditor: AnyComponent | undefined
|
||||
export let modes: Mode[] = []
|
||||
|
||||
let [val, selectedMode] = parseValue(modes, value)
|
||||
|
||||
$: mode = selectedMode.id
|
||||
const dispatch = createEventDispatcher()
|
||||
|
||||
$: [val, selectedMode] = parseValue(modes, value)
|
||||
$: contextValue = parseContext(val)
|
||||
|
||||
function selectContext (e: MouseEvent): void {
|
||||
showPopup(
|
||||
ContextSelectorPopup,
|
||||
{
|
||||
process,
|
||||
masterTag: process.masterTag,
|
||||
context,
|
||||
attribute,
|
||||
onSelect
|
||||
},
|
||||
eventToHTMLElement(e)
|
||||
)
|
||||
}
|
||||
|
||||
function onSelect (res: SelectedContext | null): void {
|
||||
val = res === null ? undefined : '$' + JSON.stringify(res)
|
||||
changeResult()
|
||||
}
|
||||
|
||||
function onChange (value: any | undefined): void {
|
||||
val = value
|
||||
changeResult()
|
||||
}
|
||||
|
||||
const client = getClient()
|
||||
const hierarchy = client.getHierarchy()
|
||||
|
||||
$: presenterClass = getAttributePresenterClass(hierarchy, attribute.type)
|
||||
|
||||
function changeResult () {
|
||||
if (val === undefined || val === '') {
|
||||
dispatch('change', null)
|
||||
return
|
||||
}
|
||||
|
||||
const result = buildResult(selectedMode, val)
|
||||
|
||||
dispatch('change', result)
|
||||
}
|
||||
</script>
|
||||
|
||||
<div class="flex-row-center flex-gap-4">
|
||||
<DropdownLabelsIntl
|
||||
items={modes}
|
||||
selected={mode}
|
||||
disabled={readonly}
|
||||
minW0={false}
|
||||
kind={'no-border'}
|
||||
width={'100%'}
|
||||
on:selected={(e) => {
|
||||
mode = e.detail
|
||||
selectedMode = modes.find((m) => m.id === mode) ?? modes[0]
|
||||
changeResult()
|
||||
}}
|
||||
/>
|
||||
|
||||
<div class="text-input" class:context={contextValue}>
|
||||
{#if contextValue}
|
||||
<ContextValue
|
||||
{process}
|
||||
masterTag={process.masterTag}
|
||||
{contextValue}
|
||||
{context}
|
||||
{attribute}
|
||||
category={presenterClass.category}
|
||||
attrClass={presenterClass.attrClass}
|
||||
on:update={(e) => {
|
||||
onSelect(e.detail)
|
||||
}}
|
||||
/>
|
||||
{:else}
|
||||
<div class="w-full">
|
||||
{#if baseEditor}
|
||||
<Component
|
||||
is={baseEditor}
|
||||
props={{
|
||||
label: attribute?.label,
|
||||
placeholder: attribute?.label,
|
||||
kind: 'ghost',
|
||||
size: 'large',
|
||||
width: '100%',
|
||||
justify: 'left',
|
||||
readonly,
|
||||
type: attribute?.type,
|
||||
value: val,
|
||||
onChange,
|
||||
focus
|
||||
}}
|
||||
/>
|
||||
{/if}
|
||||
</div>
|
||||
{/if}
|
||||
<div class="button flex-row-center">
|
||||
<Button
|
||||
icon={IconAdd}
|
||||
kind="ghost"
|
||||
on:click={(e) => {
|
||||
selectContext(e)
|
||||
}}
|
||||
/>
|
||||
<Button
|
||||
icon={IconClose}
|
||||
kind="ghost"
|
||||
on:click={() => {
|
||||
dispatch('delete')
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<style lang="scss">
|
||||
.text-input {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
border: 1px solid var(--theme-refinput-border);
|
||||
border-radius: 0.375rem;
|
||||
max-width: 100%;
|
||||
width: 100%;
|
||||
|
||||
.button {
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
&.context {
|
||||
background: #3575de33;
|
||||
padding-left: 0.75rem;
|
||||
border-color: var(--primary-button-default);
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -14,23 +14,11 @@
|
||||
-->
|
||||
|
||||
<script lang="ts">
|
||||
import core, { AnyAttribute } from '@hcengineering/core'
|
||||
import { Context, parseContext, Process, SelectedContext } from '@hcengineering/process'
|
||||
import {
|
||||
AnyComponent,
|
||||
Button,
|
||||
Component,
|
||||
DropdownIntlItem,
|
||||
DropdownLabelsIntl,
|
||||
eventToHTMLElement,
|
||||
IconAdd,
|
||||
IconClose,
|
||||
showPopup
|
||||
} from '@hcengineering/ui'
|
||||
import view from '@hcengineering/view-resources/src/plugin'
|
||||
import { createEventDispatcher } from 'svelte'
|
||||
import ContextSelectorPopup from '../attributeEditors/ContextSelectorPopup.svelte'
|
||||
import ContextValue from '../attributeEditors/ContextValue.svelte'
|
||||
import { AnyAttribute } from '@hcengineering/core'
|
||||
import { Context, Process } from '@hcengineering/process'
|
||||
import { AnyComponent } from '@hcengineering/ui'
|
||||
import { Mode, Modes } from '../../query'
|
||||
import BaseCriteria from './BaseCriteria.svelte'
|
||||
|
||||
export let readonly: boolean
|
||||
export let value: any
|
||||
@@ -39,164 +27,7 @@
|
||||
export let attribute: AnyAttribute
|
||||
export let baseEditor: AnyComponent | undefined
|
||||
|
||||
const modes: DropdownIntlItem[] = [
|
||||
{ id: 'equals', label: view.string.FilterIsEither },
|
||||
{ id: 'notEquals', label: view.string.FilterIsNot }
|
||||
]
|
||||
|
||||
let mode = value == null || typeof value === 'boolean' ? 'equals' : 'notEquals'
|
||||
|
||||
const dispatch = createEventDispatcher()
|
||||
|
||||
let contextValue: SelectedContext | undefined = undefined
|
||||
let val: any = undefined
|
||||
|
||||
function selectContext (e: MouseEvent): void {
|
||||
showPopup(
|
||||
ContextSelectorPopup,
|
||||
{
|
||||
process,
|
||||
masterTag: process.masterTag,
|
||||
context,
|
||||
attribute,
|
||||
onSelect
|
||||
},
|
||||
eventToHTMLElement(e)
|
||||
)
|
||||
}
|
||||
|
||||
$: parseValue(value)
|
||||
|
||||
function parseValue (value: any) {
|
||||
if (typeof value === 'boolean' || value == null) {
|
||||
mode = 'equals'
|
||||
val = value
|
||||
} else {
|
||||
mode = 'notEquals'
|
||||
if (value?.$ne !== undefined) {
|
||||
val = value.$ne
|
||||
} else {
|
||||
val = undefined
|
||||
}
|
||||
}
|
||||
contextValue = parseContext(val)
|
||||
}
|
||||
|
||||
function onSelect (res: SelectedContext | null): void {
|
||||
val = res === null ? undefined : '$' + JSON.stringify(res)
|
||||
buildResult()
|
||||
}
|
||||
|
||||
function onChange (value: any | undefined): void {
|
||||
val = value
|
||||
buildResult()
|
||||
}
|
||||
|
||||
function buildResult () {
|
||||
if (val === undefined) {
|
||||
dispatch('change', null)
|
||||
return
|
||||
}
|
||||
|
||||
let result: any
|
||||
if (mode === 'equals') {
|
||||
result = val
|
||||
} else {
|
||||
result = { $ne: val }
|
||||
}
|
||||
|
||||
dispatch('change', result)
|
||||
}
|
||||
const modes: Mode[] = [Modes.Equal, Modes.NotEqual]
|
||||
</script>
|
||||
|
||||
<div class="flex-row-center flex-gap-4">
|
||||
<DropdownLabelsIntl
|
||||
items={modes}
|
||||
selected={mode}
|
||||
disabled={readonly}
|
||||
minW0={false}
|
||||
kind={'no-border'}
|
||||
width={'100%'}
|
||||
on:selected={(e) => {
|
||||
mode = e.detail
|
||||
buildResult()
|
||||
}}
|
||||
/>
|
||||
|
||||
<div class="text-input" class:context={contextValue}>
|
||||
{#if contextValue}
|
||||
<ContextValue
|
||||
{process}
|
||||
masterTag={process.masterTag}
|
||||
{contextValue}
|
||||
{context}
|
||||
{attribute}
|
||||
category={'attribute'}
|
||||
attrClass={core.class.TypeBoolean}
|
||||
on:update={(e) => {
|
||||
onSelect(e.detail)
|
||||
}}
|
||||
/>
|
||||
{:else}
|
||||
<div class="w-full">
|
||||
{#if baseEditor}
|
||||
<Component
|
||||
is={baseEditor}
|
||||
props={{
|
||||
label: attribute?.label,
|
||||
placeholder: attribute?.label,
|
||||
kind: 'ghost',
|
||||
size: 'large',
|
||||
width: '100%',
|
||||
justify: 'left',
|
||||
readonly,
|
||||
type: attribute?.type,
|
||||
value: val,
|
||||
onChange,
|
||||
focus
|
||||
}}
|
||||
/>
|
||||
{/if}
|
||||
</div>
|
||||
{/if}
|
||||
<div class="button flex-row-center">
|
||||
<Button
|
||||
icon={IconAdd}
|
||||
kind="ghost"
|
||||
on:click={(e) => {
|
||||
selectContext(e)
|
||||
}}
|
||||
/>
|
||||
<Button
|
||||
icon={IconClose}
|
||||
kind="ghost"
|
||||
on:click={() => {
|
||||
dispatch('delete')
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<style lang="scss">
|
||||
.text-input {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
border: 1px solid var(--theme-refinput-border);
|
||||
border-radius: 0.375rem;
|
||||
max-width: 100%;
|
||||
width: 100%;
|
||||
|
||||
.button {
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
&.context {
|
||||
background: #3575de33;
|
||||
padding-left: 0.75rem;
|
||||
border-color: var(--primary-button-default);
|
||||
}
|
||||
}
|
||||
</style>
|
||||
<BaseCriteria {readonly} {value} {process} {context} {attribute} {baseEditor} {modes} on:change on:delete />
|
||||
|
||||
@@ -14,23 +14,11 @@
|
||||
-->
|
||||
|
||||
<script lang="ts">
|
||||
import core, { AnyAttribute } from '@hcengineering/core'
|
||||
import { Context, parseContext, Process, SelectedContext } from '@hcengineering/process'
|
||||
import {
|
||||
AnyComponent,
|
||||
Button,
|
||||
Component,
|
||||
DropdownIntlItem,
|
||||
DropdownLabelsIntl,
|
||||
eventToHTMLElement,
|
||||
IconAdd,
|
||||
IconClose,
|
||||
showPopup
|
||||
} from '@hcengineering/ui'
|
||||
import view from '@hcengineering/view-resources/src/plugin'
|
||||
import { createEventDispatcher } from 'svelte'
|
||||
import ContextSelectorPopup from '../attributeEditors/ContextSelectorPopup.svelte'
|
||||
import ContextValue from '../attributeEditors/ContextValue.svelte'
|
||||
import { AnyAttribute } from '@hcengineering/core'
|
||||
import { Context, Process } from '@hcengineering/process'
|
||||
import { AnyComponent } from '@hcengineering/ui'
|
||||
import { Mode, Modes } from '../../query'
|
||||
import BaseCriteria from './BaseCriteria.svelte'
|
||||
|
||||
export let readonly: boolean
|
||||
export let value: any
|
||||
@@ -39,174 +27,7 @@
|
||||
export let attribute: AnyAttribute
|
||||
export let baseEditor: AnyComponent | undefined
|
||||
|
||||
const modes: DropdownIntlItem[] = [
|
||||
{ id: 'equals', label: view.string.FilterIsEither },
|
||||
{ id: 'greaterThan', label: view.string.After },
|
||||
{ id: 'lessThan', label: view.string.Before }
|
||||
]
|
||||
|
||||
let mode =
|
||||
value == null || typeof value === 'number' ? 'equals' : value?.$gt !== undefined ? 'greaterThan' : 'lessThan'
|
||||
|
||||
const dispatch = createEventDispatcher()
|
||||
|
||||
let contextValue: SelectedContext | undefined = undefined
|
||||
let val: any = undefined
|
||||
|
||||
function selectContext (e: MouseEvent): void {
|
||||
showPopup(
|
||||
ContextSelectorPopup,
|
||||
{
|
||||
process,
|
||||
masterTag: process.masterTag,
|
||||
context,
|
||||
attribute,
|
||||
onSelect
|
||||
},
|
||||
eventToHTMLElement(e)
|
||||
)
|
||||
}
|
||||
|
||||
$: parseValue(value)
|
||||
|
||||
function parseValue (value: any) {
|
||||
if (typeof value === 'number') {
|
||||
mode = 'equals'
|
||||
val = value
|
||||
} else if (value?.$gt !== undefined) {
|
||||
mode = 'greaterThan'
|
||||
val = value.$gt
|
||||
} else if (value?.$lt !== undefined) {
|
||||
mode = 'lessThan'
|
||||
val = value.$lt
|
||||
} else {
|
||||
mode = 'equals'
|
||||
val = undefined
|
||||
}
|
||||
contextValue = parseContext(val)
|
||||
}
|
||||
|
||||
function onSelect (res: SelectedContext | null): void {
|
||||
val = res === null ? undefined : '$' + JSON.stringify(res)
|
||||
buildResult()
|
||||
}
|
||||
|
||||
function onChange (value: any | undefined): void {
|
||||
val = value
|
||||
buildResult()
|
||||
}
|
||||
|
||||
function buildResult () {
|
||||
if (val === undefined || val === '') {
|
||||
dispatch('change', null)
|
||||
return
|
||||
}
|
||||
|
||||
let result: any
|
||||
switch (mode) {
|
||||
case 'equals':
|
||||
result = val
|
||||
break
|
||||
case 'greaterThan':
|
||||
result = { $gt: val }
|
||||
break
|
||||
case 'lessThan':
|
||||
result = { $lt: val }
|
||||
break
|
||||
}
|
||||
|
||||
dispatch('change', result)
|
||||
}
|
||||
const modes: Mode[] = [Modes.Equal, Modes.GT, Modes.LT]
|
||||
</script>
|
||||
|
||||
<div class="flex-row-center flex-gap-4">
|
||||
<DropdownLabelsIntl
|
||||
items={modes}
|
||||
selected={mode}
|
||||
disabled={readonly}
|
||||
minW0={false}
|
||||
kind={'no-border'}
|
||||
width={'100%'}
|
||||
on:selected={(e) => {
|
||||
mode = e.detail
|
||||
buildResult()
|
||||
}}
|
||||
/>
|
||||
|
||||
<div class="text-input" class:context={contextValue}>
|
||||
{#if contextValue}
|
||||
<ContextValue
|
||||
{process}
|
||||
masterTag={process.masterTag}
|
||||
{contextValue}
|
||||
{context}
|
||||
{attribute}
|
||||
category={'attribute'}
|
||||
attrClass={core.class.TypeDate}
|
||||
on:update={(e) => {
|
||||
onSelect(e.detail)
|
||||
}}
|
||||
/>
|
||||
{:else}
|
||||
<div class="w-full">
|
||||
{#if baseEditor}
|
||||
<Component
|
||||
is={baseEditor}
|
||||
props={{
|
||||
label: attribute?.label,
|
||||
placeholder: attribute?.label,
|
||||
kind: 'ghost',
|
||||
size: 'large',
|
||||
width: '100%',
|
||||
justify: 'left',
|
||||
readonly,
|
||||
type: attribute?.type,
|
||||
value: val,
|
||||
onChange,
|
||||
focus
|
||||
}}
|
||||
/>
|
||||
{/if}
|
||||
</div>
|
||||
{/if}
|
||||
<div class="button flex-row-center">
|
||||
<Button
|
||||
icon={IconAdd}
|
||||
kind="ghost"
|
||||
on:click={(e) => {
|
||||
selectContext(e)
|
||||
}}
|
||||
/>
|
||||
<Button
|
||||
icon={IconClose}
|
||||
kind="ghost"
|
||||
on:click={() => {
|
||||
dispatch('delete')
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<style lang="scss">
|
||||
.text-input {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
border: 1px solid var(--theme-refinput-border);
|
||||
border-radius: 0.375rem;
|
||||
max-width: 100%;
|
||||
width: 100%;
|
||||
|
||||
.button {
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
&.context {
|
||||
background: #3575de33;
|
||||
padding-left: 0.75rem;
|
||||
border-color: var(--primary-button-default);
|
||||
}
|
||||
}
|
||||
</style>
|
||||
<BaseCriteria {readonly} {value} {process} {context} {attribute} {baseEditor} {modes} on:change on:delete />
|
||||
|
||||
@@ -14,23 +14,11 @@
|
||||
-->
|
||||
|
||||
<script lang="ts">
|
||||
import core, { AnyAttribute } from '@hcengineering/core'
|
||||
import { Context, parseContext, Process, SelectedContext } from '@hcengineering/process'
|
||||
import {
|
||||
AnyComponent,
|
||||
Button,
|
||||
Component,
|
||||
DropdownIntlItem,
|
||||
DropdownLabelsIntl,
|
||||
eventToHTMLElement,
|
||||
IconAdd,
|
||||
IconClose,
|
||||
showPopup
|
||||
} from '@hcengineering/ui'
|
||||
import view from '@hcengineering/view-resources/src/plugin'
|
||||
import { createEventDispatcher } from 'svelte'
|
||||
import ContextSelectorPopup from '../attributeEditors/ContextSelectorPopup.svelte'
|
||||
import ContextValue from '../attributeEditors/ContextValue.svelte'
|
||||
import { AnyAttribute } from '@hcengineering/core'
|
||||
import { Context, Process } from '@hcengineering/process'
|
||||
import { AnyComponent } from '@hcengineering/ui'
|
||||
import { Mode, Modes } from '../../query'
|
||||
import BaseCriteria from './BaseCriteria.svelte'
|
||||
|
||||
export let readonly: boolean
|
||||
export let value: any
|
||||
@@ -39,164 +27,7 @@
|
||||
export let attribute: AnyAttribute
|
||||
export let baseEditor: AnyComponent | undefined
|
||||
|
||||
const modes: DropdownIntlItem[] = [
|
||||
{ id: 'equals', label: view.string.FilterIsEither },
|
||||
{ id: 'notEquals', label: view.string.FilterIsNot }
|
||||
]
|
||||
|
||||
let mode = value == null || typeof value === 'string' ? 'equals' : 'notEquals'
|
||||
|
||||
const dispatch = createEventDispatcher()
|
||||
|
||||
let contextValue: SelectedContext | undefined = undefined
|
||||
let val: any = undefined
|
||||
|
||||
function selectContext (e: MouseEvent): void {
|
||||
showPopup(
|
||||
ContextSelectorPopup,
|
||||
{
|
||||
process,
|
||||
masterTag: process.masterTag,
|
||||
context,
|
||||
attribute,
|
||||
onSelect
|
||||
},
|
||||
eventToHTMLElement(e)
|
||||
)
|
||||
}
|
||||
|
||||
$: parseValue(value)
|
||||
|
||||
function parseValue (value: any) {
|
||||
if (typeof value === 'string' || value == null) {
|
||||
mode = 'equals'
|
||||
val = value
|
||||
} else {
|
||||
mode = 'notEquals'
|
||||
if (value?.$ne !== undefined) {
|
||||
val = value.$ne
|
||||
} else {
|
||||
val = undefined
|
||||
}
|
||||
}
|
||||
contextValue = parseContext(val)
|
||||
}
|
||||
|
||||
function onSelect (res: SelectedContext | null): void {
|
||||
val = res === null ? undefined : '$' + JSON.stringify(res)
|
||||
buildResult()
|
||||
}
|
||||
|
||||
function onChange (value: any | undefined): void {
|
||||
val = value
|
||||
buildResult()
|
||||
}
|
||||
|
||||
function buildResult () {
|
||||
if (val === undefined || val === '') {
|
||||
dispatch('change', null)
|
||||
return
|
||||
}
|
||||
|
||||
let result: any
|
||||
if (mode === 'equals') {
|
||||
result = val
|
||||
} else {
|
||||
result = { $ne: val }
|
||||
}
|
||||
|
||||
dispatch('change', result)
|
||||
}
|
||||
const modes: Mode[] = [Modes.Equal, Modes.NotEqual]
|
||||
</script>
|
||||
|
||||
<div class="flex-row-center flex-gap-4">
|
||||
<DropdownLabelsIntl
|
||||
items={modes}
|
||||
selected={mode}
|
||||
disabled={readonly}
|
||||
minW0={false}
|
||||
kind={'no-border'}
|
||||
width={'100%'}
|
||||
on:selected={(e) => {
|
||||
mode = e.detail
|
||||
buildResult()
|
||||
}}
|
||||
/>
|
||||
|
||||
<div class="text-input" class:context={contextValue}>
|
||||
{#if contextValue}
|
||||
<ContextValue
|
||||
{process}
|
||||
masterTag={process.masterTag}
|
||||
{contextValue}
|
||||
{context}
|
||||
{attribute}
|
||||
category={'attribute'}
|
||||
attrClass={core.class.EnumOf}
|
||||
on:update={(e) => {
|
||||
onSelect(e.detail)
|
||||
}}
|
||||
/>
|
||||
{:else}
|
||||
<div class="w-full">
|
||||
{#if baseEditor}
|
||||
<Component
|
||||
is={baseEditor}
|
||||
props={{
|
||||
label: attribute?.label,
|
||||
placeholder: attribute?.label,
|
||||
kind: 'ghost',
|
||||
size: 'large',
|
||||
width: '100%',
|
||||
justify: 'left',
|
||||
readonly,
|
||||
type: attribute?.type,
|
||||
value: val,
|
||||
onChange,
|
||||
focus
|
||||
}}
|
||||
/>
|
||||
{/if}
|
||||
</div>
|
||||
{/if}
|
||||
<div class="button flex-row-center">
|
||||
<Button
|
||||
icon={IconAdd}
|
||||
kind="ghost"
|
||||
on:click={(e) => {
|
||||
selectContext(e)
|
||||
}}
|
||||
/>
|
||||
<Button
|
||||
icon={IconClose}
|
||||
kind="ghost"
|
||||
on:click={() => {
|
||||
dispatch('delete')
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<style lang="scss">
|
||||
.text-input {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
border: 1px solid var(--theme-refinput-border);
|
||||
border-radius: 0.375rem;
|
||||
max-width: 100%;
|
||||
width: 100%;
|
||||
|
||||
.button {
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
&.context {
|
||||
background: #3575de33;
|
||||
padding-left: 0.75rem;
|
||||
border-color: var(--primary-button-default);
|
||||
}
|
||||
}
|
||||
</style>
|
||||
<BaseCriteria {readonly} {value} {process} {context} {attribute} {baseEditor} {modes} on:change on:delete />
|
||||
|
||||
@@ -14,23 +14,11 @@
|
||||
-->
|
||||
|
||||
<script lang="ts">
|
||||
import core, { AnyAttribute } from '@hcengineering/core'
|
||||
import { Context, parseContext, Process, SelectedContext } from '@hcengineering/process'
|
||||
import {
|
||||
AnyComponent,
|
||||
Button,
|
||||
Component,
|
||||
DropdownIntlItem,
|
||||
DropdownLabelsIntl,
|
||||
eventToHTMLElement,
|
||||
IconAdd,
|
||||
IconClose,
|
||||
showPopup
|
||||
} from '@hcengineering/ui'
|
||||
import view from '@hcengineering/view-resources/src/plugin'
|
||||
import { createEventDispatcher } from 'svelte'
|
||||
import ContextSelectorPopup from '../attributeEditors/ContextSelectorPopup.svelte'
|
||||
import ContextValue from '../attributeEditors/ContextValue.svelte'
|
||||
import { AnyAttribute } from '@hcengineering/core'
|
||||
import { Context, Process } from '@hcengineering/process'
|
||||
import { AnyComponent } from '@hcengineering/ui'
|
||||
import { Mode, Modes } from '../../query'
|
||||
import BaseCriteria from './BaseCriteria.svelte'
|
||||
|
||||
export let readonly: boolean
|
||||
export let value: any
|
||||
@@ -39,173 +27,7 @@
|
||||
export let attribute: AnyAttribute
|
||||
export let baseEditor: AnyComponent | undefined
|
||||
|
||||
const modes: DropdownIntlItem[] = [
|
||||
{ id: 'equals', label: view.string.FilterIsEither },
|
||||
{ id: 'greaterThan', label: view.string.FilterGreaterThan },
|
||||
{ id: 'lessThan', label: view.string.FilterLessThan }
|
||||
]
|
||||
|
||||
let mode =
|
||||
value == null || typeof value === 'number' ? 'equals' : value?.$gt !== undefined ? 'greaterThan' : 'lessThan'
|
||||
|
||||
const dispatch = createEventDispatcher()
|
||||
|
||||
let contextValue: SelectedContext | undefined = undefined
|
||||
let val: any = undefined
|
||||
|
||||
function selectContext (e: MouseEvent): void {
|
||||
showPopup(
|
||||
ContextSelectorPopup,
|
||||
{
|
||||
process,
|
||||
masterTag: process.masterTag,
|
||||
context,
|
||||
attribute,
|
||||
onSelect
|
||||
},
|
||||
eventToHTMLElement(e)
|
||||
)
|
||||
}
|
||||
|
||||
$: parseValue(value)
|
||||
|
||||
function parseValue (value: any) {
|
||||
if (typeof value === 'number' || value == null) {
|
||||
mode = 'equals'
|
||||
val = value
|
||||
} else if (value?.$gt !== undefined) {
|
||||
mode = 'greaterThan'
|
||||
val = value.$gt
|
||||
} else if (value?.$lt !== undefined) {
|
||||
mode = 'lessThan'
|
||||
val = value.$lt
|
||||
} else {
|
||||
val = undefined
|
||||
}
|
||||
contextValue = parseContext(val)
|
||||
}
|
||||
|
||||
function onSelect (res: SelectedContext | null): void {
|
||||
val = res === null ? undefined : '$' + JSON.stringify(res)
|
||||
buildResult()
|
||||
}
|
||||
|
||||
function onChange (value: any | undefined): void {
|
||||
val = value
|
||||
buildResult()
|
||||
}
|
||||
|
||||
function buildResult () {
|
||||
if (val === undefined || val === '') {
|
||||
dispatch('change', null)
|
||||
return
|
||||
}
|
||||
|
||||
let result: any
|
||||
switch (mode) {
|
||||
case 'equals':
|
||||
result = val
|
||||
break
|
||||
case 'greaterThan':
|
||||
result = { $gt: val }
|
||||
break
|
||||
case 'lessThan':
|
||||
result = { $lt: val }
|
||||
break
|
||||
}
|
||||
|
||||
dispatch('change', result)
|
||||
}
|
||||
const modes: Mode[] = [Modes.Equal, Modes.GT, Modes.LT]
|
||||
</script>
|
||||
|
||||
<div class="flex-row-center flex-gap-4">
|
||||
<DropdownLabelsIntl
|
||||
items={modes}
|
||||
selected={mode}
|
||||
disabled={readonly}
|
||||
minW0={false}
|
||||
kind={'no-border'}
|
||||
width={'100%'}
|
||||
on:selected={(e) => {
|
||||
mode = e.detail
|
||||
buildResult()
|
||||
}}
|
||||
/>
|
||||
|
||||
<div class="text-input" class:context={contextValue}>
|
||||
{#if contextValue}
|
||||
<ContextValue
|
||||
{process}
|
||||
masterTag={process.masterTag}
|
||||
{contextValue}
|
||||
{context}
|
||||
{attribute}
|
||||
category={'attribute'}
|
||||
attrClass={core.class.TypeNumber}
|
||||
on:update={(e) => {
|
||||
onSelect(e.detail)
|
||||
}}
|
||||
/>
|
||||
{:else}
|
||||
<div class="w-full">
|
||||
{#if baseEditor}
|
||||
<Component
|
||||
is={baseEditor}
|
||||
props={{
|
||||
label: attribute?.label,
|
||||
placeholder: attribute?.label,
|
||||
kind: 'ghost',
|
||||
size: 'large',
|
||||
width: '100%',
|
||||
justify: 'left',
|
||||
readonly,
|
||||
type: attribute?.type,
|
||||
value: val,
|
||||
onChange,
|
||||
focus
|
||||
}}
|
||||
/>
|
||||
{/if}
|
||||
</div>
|
||||
{/if}
|
||||
<div class="button flex-row-center">
|
||||
<Button
|
||||
icon={IconAdd}
|
||||
kind="ghost"
|
||||
on:click={(e) => {
|
||||
selectContext(e)
|
||||
}}
|
||||
/>
|
||||
<Button
|
||||
icon={IconClose}
|
||||
kind="ghost"
|
||||
on:click={() => {
|
||||
dispatch('delete')
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<style lang="scss">
|
||||
.text-input {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
border: 1px solid var(--theme-refinput-border);
|
||||
border-radius: 0.375rem;
|
||||
max-width: 100%;
|
||||
width: 100%;
|
||||
|
||||
.button {
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
&.context {
|
||||
background: #3575de33;
|
||||
padding-left: 0.75rem;
|
||||
border-color: var(--primary-button-default);
|
||||
}
|
||||
}
|
||||
</style>
|
||||
<BaseCriteria {readonly} {value} {process} {context} {attribute} {baseEditor} {modes} on:change on:delete />
|
||||
|
||||
@@ -14,24 +14,11 @@
|
||||
-->
|
||||
|
||||
<script lang="ts">
|
||||
import core, { AnyAttribute } from '@hcengineering/core'
|
||||
import { Context, parseContext, Process, SelectedContext } from '@hcengineering/process'
|
||||
import {
|
||||
AnyComponent,
|
||||
Button,
|
||||
Component,
|
||||
DropdownIntlItem,
|
||||
DropdownLabelsIntl,
|
||||
eventToHTMLElement,
|
||||
IconAdd,
|
||||
IconClose,
|
||||
showPopup
|
||||
} from '@hcengineering/ui'
|
||||
import view from '@hcengineering/view-resources/src/plugin'
|
||||
import { createEventDispatcher } from 'svelte'
|
||||
import ContextSelectorPopup from '../attributeEditors/ContextSelectorPopup.svelte'
|
||||
import ContextValue from '../attributeEditors/ContextValue.svelte'
|
||||
import { getAttributePresenterClass, getClient } from '@hcengineering/presentation'
|
||||
import { AnyAttribute } from '@hcengineering/core'
|
||||
import { Context, Process } from '@hcengineering/process'
|
||||
import { AnyComponent } from '@hcengineering/ui'
|
||||
import { Mode, Modes } from '../../query'
|
||||
import BaseCriteria from './BaseCriteria.svelte'
|
||||
|
||||
export let readonly: boolean
|
||||
export let value: any
|
||||
@@ -40,169 +27,7 @@
|
||||
export let attribute: AnyAttribute
|
||||
export let baseEditor: AnyComponent | undefined
|
||||
|
||||
const modes: DropdownIntlItem[] = [
|
||||
{ id: 'equals', label: view.string.FilterIsEither },
|
||||
{ id: 'notEquals', label: view.string.FilterIsNot }
|
||||
]
|
||||
|
||||
let mode = value == null || typeof value === 'string' ? 'equals' : 'notEquals'
|
||||
|
||||
const dispatch = createEventDispatcher()
|
||||
|
||||
let contextValue: SelectedContext | undefined = undefined
|
||||
let val: any = undefined
|
||||
|
||||
function selectContext (e: MouseEvent): void {
|
||||
showPopup(
|
||||
ContextSelectorPopup,
|
||||
{
|
||||
process,
|
||||
masterTag: process.masterTag,
|
||||
context,
|
||||
attribute,
|
||||
onSelect
|
||||
},
|
||||
eventToHTMLElement(e)
|
||||
)
|
||||
}
|
||||
|
||||
$: parseValue(value)
|
||||
|
||||
function parseValue (value: any) {
|
||||
if (typeof value === 'string' || value == null) {
|
||||
mode = 'equals'
|
||||
val = value
|
||||
} else {
|
||||
mode = 'notEquals'
|
||||
if (value?.$ne !== undefined) {
|
||||
val = value.$ne
|
||||
} else {
|
||||
val = undefined
|
||||
}
|
||||
}
|
||||
contextValue = parseContext(val)
|
||||
}
|
||||
|
||||
function onSelect (res: SelectedContext | null): void {
|
||||
val = res === null ? undefined : '$' + JSON.stringify(res)
|
||||
buildResult()
|
||||
}
|
||||
|
||||
function onChange (value: any | undefined): void {
|
||||
val = value
|
||||
buildResult()
|
||||
}
|
||||
|
||||
const client = getClient()
|
||||
const hierarchy = client.getHierarchy()
|
||||
|
||||
$: presenterClass = getAttributePresenterClass(hierarchy, attribute.type)
|
||||
|
||||
function buildResult () {
|
||||
if (val === undefined || val === '') {
|
||||
dispatch('change', null)
|
||||
return
|
||||
}
|
||||
|
||||
let result: any
|
||||
if (mode === 'equals') {
|
||||
result = val
|
||||
} else {
|
||||
result = { $ne: val }
|
||||
}
|
||||
|
||||
dispatch('change', result)
|
||||
}
|
||||
const modes: Mode[] = [Modes.Equal, Modes.NotEqual]
|
||||
</script>
|
||||
|
||||
<div class="flex-row-center flex-gap-4">
|
||||
<DropdownLabelsIntl
|
||||
items={modes}
|
||||
selected={mode}
|
||||
disabled={readonly}
|
||||
minW0={false}
|
||||
kind={'no-border'}
|
||||
width={'100%'}
|
||||
on:selected={(e) => {
|
||||
mode = e.detail
|
||||
buildResult()
|
||||
}}
|
||||
/>
|
||||
|
||||
<div class="text-input" class:context={contextValue}>
|
||||
{#if contextValue}
|
||||
<ContextValue
|
||||
{process}
|
||||
masterTag={process.masterTag}
|
||||
{contextValue}
|
||||
{context}
|
||||
{attribute}
|
||||
category={presenterClass.category}
|
||||
attrClass={presenterClass.attrClass}
|
||||
on:update={(e) => {
|
||||
onSelect(e.detail)
|
||||
}}
|
||||
/>
|
||||
{:else}
|
||||
<div class="w-full">
|
||||
{#if baseEditor}
|
||||
<Component
|
||||
is={baseEditor}
|
||||
props={{
|
||||
label: attribute?.label,
|
||||
placeholder: attribute?.label,
|
||||
kind: 'ghost',
|
||||
size: 'large',
|
||||
width: '100%',
|
||||
justify: 'left',
|
||||
readonly,
|
||||
type: attribute?.type,
|
||||
value: val,
|
||||
onChange,
|
||||
focus
|
||||
}}
|
||||
/>
|
||||
{/if}
|
||||
</div>
|
||||
{/if}
|
||||
<div class="button flex-row-center">
|
||||
<Button
|
||||
icon={IconAdd}
|
||||
kind="ghost"
|
||||
on:click={(e) => {
|
||||
selectContext(e)
|
||||
}}
|
||||
/>
|
||||
<Button
|
||||
icon={IconClose}
|
||||
kind="ghost"
|
||||
on:click={() => {
|
||||
dispatch('delete')
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<style lang="scss">
|
||||
.text-input {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
border: 1px solid var(--theme-refinput-border);
|
||||
border-radius: 0.375rem;
|
||||
max-width: 100%;
|
||||
width: 100%;
|
||||
|
||||
.button {
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
&.context {
|
||||
background: #3575de33;
|
||||
padding-left: 0.75rem;
|
||||
border-color: var(--primary-button-default);
|
||||
}
|
||||
}
|
||||
</style>
|
||||
<BaseCriteria {readonly} {value} {process} {context} {attribute} {baseEditor} {modes} on:change on:delete />
|
||||
|
||||
@@ -14,23 +14,11 @@
|
||||
-->
|
||||
|
||||
<script lang="ts">
|
||||
import core, { AnyAttribute } from '@hcengineering/core'
|
||||
import { Context, parseContext, Process, SelectedContext } from '@hcengineering/process'
|
||||
import {
|
||||
AnyComponent,
|
||||
Button,
|
||||
Component,
|
||||
DropdownIntlItem,
|
||||
DropdownLabelsIntl,
|
||||
eventToHTMLElement,
|
||||
IconAdd,
|
||||
IconClose,
|
||||
showPopup
|
||||
} from '@hcengineering/ui'
|
||||
import view from '@hcengineering/view-resources/src/plugin'
|
||||
import { createEventDispatcher } from 'svelte'
|
||||
import ContextSelectorPopup from '../attributeEditors/ContextSelectorPopup.svelte'
|
||||
import ContextValue from '../attributeEditors/ContextValue.svelte'
|
||||
import { AnyAttribute } from '@hcengineering/core'
|
||||
import { Context, Process } from '@hcengineering/process'
|
||||
import { AnyComponent } from '@hcengineering/ui'
|
||||
import { Mode, Modes } from '../../query'
|
||||
import BaseCriteria from './BaseCriteria.svelte'
|
||||
|
||||
export let readonly: boolean
|
||||
export let value: any
|
||||
@@ -39,164 +27,7 @@
|
||||
export let attribute: AnyAttribute
|
||||
export let baseEditor: AnyComponent | undefined
|
||||
|
||||
const modes: DropdownIntlItem[] = [
|
||||
{ id: 'equals', label: view.string.FilterIsEither },
|
||||
{ id: 'contains', label: view.string.Contains }
|
||||
]
|
||||
|
||||
let mode = value == null || typeof value === 'string' ? 'equals' : 'contains'
|
||||
|
||||
const dispatch = createEventDispatcher()
|
||||
|
||||
let contextValue: SelectedContext | undefined = undefined
|
||||
let val: any = undefined
|
||||
|
||||
function selectContext (e: MouseEvent): void {
|
||||
showPopup(
|
||||
ContextSelectorPopup,
|
||||
{
|
||||
process,
|
||||
masterTag: process.masterTag,
|
||||
context,
|
||||
attribute,
|
||||
onSelect
|
||||
},
|
||||
eventToHTMLElement(e)
|
||||
)
|
||||
}
|
||||
|
||||
$: parseValue(value)
|
||||
|
||||
function parseValue (value: any) {
|
||||
if (typeof value === 'string' || value == null) {
|
||||
mode = 'equals'
|
||||
val = value
|
||||
} else {
|
||||
mode = 'contains'
|
||||
if (value?.$like !== undefined) {
|
||||
val = value.$like.startsWith('%') && value.$like.endsWith('%') ? value.$like.slice(1, -1) : value.$like
|
||||
} else {
|
||||
val = undefined
|
||||
}
|
||||
}
|
||||
contextValue = parseContext(val)
|
||||
}
|
||||
|
||||
function onSelect (res: SelectedContext | null): void {
|
||||
val = res === null ? undefined : '$' + JSON.stringify(res)
|
||||
buildResult()
|
||||
}
|
||||
|
||||
function onChange (value: any | undefined): void {
|
||||
val = value
|
||||
buildResult()
|
||||
}
|
||||
|
||||
function buildResult () {
|
||||
if (val === undefined || val === '') {
|
||||
dispatch('change', null)
|
||||
return
|
||||
}
|
||||
|
||||
let result: any
|
||||
if (mode === 'equals') {
|
||||
result = val
|
||||
} else {
|
||||
result = { $like: '%' + val + '%' }
|
||||
}
|
||||
|
||||
dispatch('change', result)
|
||||
}
|
||||
const modes: Mode[] = [Modes.Equal, Modes.StringContains]
|
||||
</script>
|
||||
|
||||
<div class="flex-row-center flex-gap-4">
|
||||
<DropdownLabelsIntl
|
||||
items={modes}
|
||||
selected={mode}
|
||||
disabled={readonly}
|
||||
minW0={false}
|
||||
kind={'no-border'}
|
||||
width={'100%'}
|
||||
on:selected={(e) => {
|
||||
mode = e.detail
|
||||
buildResult()
|
||||
}}
|
||||
/>
|
||||
|
||||
<div class="text-input" class:context={contextValue}>
|
||||
{#if contextValue}
|
||||
<ContextValue
|
||||
{process}
|
||||
masterTag={process.masterTag}
|
||||
{contextValue}
|
||||
{context}
|
||||
{attribute}
|
||||
category={'attribute'}
|
||||
attrClass={core.class.TypeString}
|
||||
on:update={(e) => {
|
||||
onSelect(e.detail)
|
||||
}}
|
||||
/>
|
||||
{:else}
|
||||
<div class="w-full">
|
||||
{#if baseEditor}
|
||||
<Component
|
||||
is={baseEditor}
|
||||
props={{
|
||||
label: attribute?.label,
|
||||
placeholder: attribute?.label,
|
||||
kind: 'ghost',
|
||||
size: 'large',
|
||||
width: '100%',
|
||||
justify: 'left',
|
||||
readonly,
|
||||
type: attribute?.type,
|
||||
value: val,
|
||||
onChange,
|
||||
focus
|
||||
}}
|
||||
/>
|
||||
{/if}
|
||||
</div>
|
||||
{/if}
|
||||
<div class="button flex-row-center">
|
||||
<Button
|
||||
icon={IconAdd}
|
||||
kind="ghost"
|
||||
on:click={(e) => {
|
||||
selectContext(e)
|
||||
}}
|
||||
/>
|
||||
<Button
|
||||
icon={IconClose}
|
||||
kind="ghost"
|
||||
on:click={() => {
|
||||
dispatch('delete')
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<style lang="scss">
|
||||
.text-input {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
border: 1px solid var(--theme-refinput-border);
|
||||
border-radius: 0.375rem;
|
||||
max-width: 100%;
|
||||
width: 100%;
|
||||
|
||||
.button {
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
&.context {
|
||||
background: #3575de33;
|
||||
padding-left: 0.75rem;
|
||||
border-color: var(--primary-button-default);
|
||||
}
|
||||
}
|
||||
</style>
|
||||
<BaseCriteria {readonly} {value} {process} {context} {attribute} {baseEditor} {modes} on:change on:delete />
|
||||
|
||||
@@ -0,0 +1,83 @@
|
||||
<!--
|
||||
// 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 { getAttributePresenterClass, getClient } from '@hcengineering/presentation'
|
||||
import { parseContext, Process } from '@hcengineering/process'
|
||||
import { AnyComponent, Component, Label } from '@hcengineering/ui'
|
||||
import { AttributeCategory } from '@hcengineering/view'
|
||||
import { findAttributePresenter } from '@hcengineering/view-resources'
|
||||
import view from '@hcengineering/view-resources/src/plugin'
|
||||
import { Mode, Modes, parseValue } from '../../query'
|
||||
import ContextValuePresenter from '../attributeEditors/ContextValuePresenter.svelte'
|
||||
import { getContext } from '../../utils'
|
||||
import core from '@hcengineering/core'
|
||||
|
||||
export let process: Process
|
||||
export let key: string
|
||||
export let value: any
|
||||
|
||||
const client = getClient()
|
||||
const hierarchy = client.getHierarchy()
|
||||
$: attribute = client.getHierarchy().findAttribute(process.masterTag, key)
|
||||
$: [val, selectedMode] = parseValue(Object.values(Modes), value)
|
||||
$: presenterClass = attribute !== undefined ? getAttributePresenterClass(hierarchy, attribute.type) : undefined
|
||||
|
||||
function isArraySize (category: AttributeCategory | undefined, selectedMode: Mode): boolean {
|
||||
return category === 'array' && selectedMode.id.startsWith('size')
|
||||
}
|
||||
|
||||
function getPresenter (key: string): AnyComponent | undefined {
|
||||
const isArraySizeCriteria = isArraySize(presenterClass?.category, selectedMode)
|
||||
if (isArraySizeCriteria) {
|
||||
return view.component.NumberPresenter
|
||||
}
|
||||
const res = findAttributePresenter(client, process.masterTag, key)
|
||||
return res
|
||||
}
|
||||
|
||||
$: presenter = getPresenter(key)
|
||||
|
||||
$: contextValue = parseContext(val)
|
||||
|
||||
$: context =
|
||||
presenterClass !== undefined
|
||||
? isArraySize(presenterClass.category, selectedMode)
|
||||
? getContext(client, process, core.class.TypeNumber, 'attribute')
|
||||
: getContext(client, process, presenterClass.attrClass, presenterClass.category)
|
||||
: undefined
|
||||
</script>
|
||||
|
||||
{#if attribute}
|
||||
<div class="title">
|
||||
<Label label={attribute.label} />
|
||||
</div>
|
||||
<Label label={selectedMode.label} />
|
||||
{#if presenter}
|
||||
{#if contextValue && context}
|
||||
<ContextValuePresenter {contextValue} {context} {process} />
|
||||
{:else}
|
||||
<Component is={presenter} props={{ value: val, readonly: true }} />
|
||||
{/if}
|
||||
{/if}
|
||||
{/if}
|
||||
|
||||
<style lang="scss">
|
||||
.title {
|
||||
overflow: hidden;
|
||||
white-space: nowrap;
|
||||
text-overflow: ellipsis;
|
||||
color: var(--theme-caption-color);
|
||||
}
|
||||
</style>
|
||||
@@ -14,11 +14,12 @@
|
||||
-->
|
||||
<script lang="ts">
|
||||
import { Card } from '@hcengineering/card'
|
||||
import { IntlString } from '@hcengineering/platform'
|
||||
import { AnyAttribute } from '@hcengineering/core'
|
||||
import { getClient } from '@hcengineering/presentation'
|
||||
import { MethodParams, Process, Step } from '@hcengineering/process'
|
||||
import { Icon, IconError, Label, tooltip } from '@hcengineering/ui'
|
||||
import plugin from '../../plugin'
|
||||
import UpdateAttributePresenter from './UpdateAttributePresenter.svelte'
|
||||
|
||||
export let step: Step<Card>
|
||||
export let process: Process
|
||||
@@ -26,36 +27,19 @@
|
||||
|
||||
const client = getClient()
|
||||
$: method = client.getModel().findAllSync(plugin.class.Method, { _id: step.methodId })[0]
|
||||
|
||||
$: attributes = getAttributes(params, process)
|
||||
|
||||
function getAttributes (params: Record<string, any>, process: Process): IntlString[] {
|
||||
const res: IntlString[] = []
|
||||
for (const key in params) {
|
||||
if (params[key] !== undefined) {
|
||||
const attr = client.getHierarchy().findAttribute(process.masterTag, key)
|
||||
if (attr?.label !== undefined) {
|
||||
res.push(attr.label)
|
||||
}
|
||||
}
|
||||
}
|
||||
return res
|
||||
}
|
||||
</script>
|
||||
|
||||
{#if attributes.length === 0}
|
||||
{#if Object.keys(params).length === 0}
|
||||
<div class="mr-2" use:tooltip={{ label: plugin.string.NoAttributesForUpdate }}>
|
||||
<Icon icon={IconError} size="medium" />
|
||||
</div>
|
||||
{/if}
|
||||
<div class="flex-row-center flex-gap-1">
|
||||
<Label label={method.label} />
|
||||
{#if attributes.length > 0}
|
||||
{#if Object.keys(params).length > 0}
|
||||
:
|
||||
{#each attributes as attr}
|
||||
<div class="title">
|
||||
<Label label={attr} />
|
||||
</div>
|
||||
{#each Object.entries(params) as [key, value]}
|
||||
<UpdateAttributePresenter {process} {key} {value} />
|
||||
{/each}
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
@@ -14,47 +14,16 @@
|
||||
-->
|
||||
|
||||
<script lang="ts">
|
||||
import { Ref } from '@hcengineering/core'
|
||||
import { IntlString } from '@hcengineering/platform'
|
||||
import { getClient } from '@hcengineering/presentation'
|
||||
import { Trigger, Process } from '@hcengineering/process'
|
||||
import { Label } from '@hcengineering/ui'
|
||||
import { Process } from '@hcengineering/process'
|
||||
import UpdateAttributePresenter from '../presenters/UpdateAttributePresenter.svelte'
|
||||
|
||||
export let process: Process
|
||||
export let params: Record<string, any>
|
||||
|
||||
const client = getClient()
|
||||
|
||||
$: attributes = getAttributes(params, process)
|
||||
|
||||
function getAttributes (params: Record<string, any>, process: Process): IntlString[] {
|
||||
const res: IntlString[] = []
|
||||
for (const key in params) {
|
||||
if (params[key] !== undefined) {
|
||||
const attr = client.getHierarchy().findAttribute(process.masterTag, key)
|
||||
if (attr?.label !== undefined) {
|
||||
res.push(attr.label)
|
||||
}
|
||||
}
|
||||
}
|
||||
return res
|
||||
}
|
||||
</script>
|
||||
|
||||
{#if attributes.length > 0}
|
||||
{#if Object.keys(params).length > 0}
|
||||
:
|
||||
{#each attributes as attr}
|
||||
<div class="title">
|
||||
<Label label={attr} />
|
||||
</div>
|
||||
{#each Object.entries(params) as [key, value]}
|
||||
<UpdateAttributePresenter {process} {key} {value} />
|
||||
{/each}
|
||||
{/if}
|
||||
|
||||
<style lang="scss">
|
||||
.title {
|
||||
overflow: hidden;
|
||||
white-space: nowrap;
|
||||
text-overflow: ellipsis;
|
||||
color: var(--theme-caption-color);
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -0,0 +1,43 @@
|
||||
<!--
|
||||
// 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 { parseContext, Process, SelectedContext, SelectedExecutonContext } from '@hcengineering/process'
|
||||
import ui, { Label } from '@hcengineering/ui'
|
||||
import ExecutionContextPresenter from '../attributeEditors/ExecutionContextPresenter.svelte'
|
||||
|
||||
export let process: Process
|
||||
export let params: Record<string, any>
|
||||
|
||||
$: context = getContext(params._id)
|
||||
|
||||
function getContext (value: string | undefined): SelectedExecutonContext | undefined {
|
||||
if (value === undefined) return
|
||||
const context = parseContext(value)
|
||||
if (context !== undefined && isExecutionContext(context)) {
|
||||
return context
|
||||
}
|
||||
}
|
||||
|
||||
function isExecutionContext (context: SelectedContext): context is SelectedExecutonContext {
|
||||
return context.type === 'context'
|
||||
}
|
||||
</script>
|
||||
|
||||
{#if context === undefined}
|
||||
<Label label={ui.string.NotSelected} />
|
||||
{:else}
|
||||
<ExecutionContextPresenter {process} contextValue={context} />
|
||||
{/if}
|
||||
@@ -53,12 +53,12 @@
|
||||
</script>
|
||||
|
||||
{#if trigger}
|
||||
<div class="flex flex-gap-2" use:tooltip={{ label: trigger.label }}>
|
||||
<div class="flex-row-center flex-gap-1" use:tooltip={{ label: trigger.label }}>
|
||||
<Icon icon={error ? IconError : trigger.icon} size={'medium'} />
|
||||
{#if withLabel}
|
||||
<Label label={trigger.label} />
|
||||
{#if trigger.presenter}
|
||||
<Component is={trigger.presenter} props={{ value, process, params }} />
|
||||
<Component is={trigger.presenter} props={{ process, params }} />
|
||||
{/if}
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user