mirror of
https://github.com/hcengineering/platform.git
synced 2026-09-28 20:45:00 +02:00
Required attributes (#10918)
Signed-off-by: Denis Bykhov <bykhov.denis@gmail.com>
This commit is contained in:
@@ -227,6 +227,7 @@ export interface Attribute<T extends PropertyType> extends Doc, UXObject {
|
||||
defaultValue?: any
|
||||
automationOnly?: boolean
|
||||
rank?: Rank
|
||||
required?: boolean
|
||||
|
||||
// Extra customization properties
|
||||
[key: string]: any
|
||||
|
||||
@@ -247,6 +247,7 @@ export class TAttribute extends TDoc implements AnyAttribute {
|
||||
isCustom?: boolean
|
||||
defaultValue?: any
|
||||
automationOnly?: boolean
|
||||
required?: boolean
|
||||
}
|
||||
|
||||
@Model(core.class.Type, core.class.Obj, DOMAIN_MODEL)
|
||||
|
||||
@@ -14,9 +14,10 @@
|
||||
// limitations under the License.
|
||||
-->
|
||||
<script lang="ts">
|
||||
import { type Class, type Doc, type Ref } from '@hcengineering/core'
|
||||
import core, { type Class, type Doc, type Ref } from '@hcengineering/core'
|
||||
import type { AnySvelteComponent, ButtonKind, ButtonSize } from '@hcengineering/ui'
|
||||
import { Icon, Label, tooltip } from '@hcengineering/ui'
|
||||
import { isEmptyMarkup } from '@hcengineering/text'
|
||||
import view from '@hcengineering/view'
|
||||
import { createEventDispatcher } from 'svelte'
|
||||
import { getAttribute, KeyedAttribute, updateAttribute } from '../attributes'
|
||||
@@ -70,12 +71,19 @@
|
||||
$: isReadonly = readonly || (attribute.readonly ?? false)
|
||||
|
||||
$: icon = attribute?.icon ?? attribute?.type?.icon
|
||||
$: value = getAttribute(client, object, { key: attributeKey, attr: attribute })
|
||||
$: isRequiredAndEmpty =
|
||||
(attribute?.required ?? false) &&
|
||||
(attribute?.type?._class === core.class.TypeMarkup
|
||||
? isEmptyMarkup(value)
|
||||
: value === undefined || value === null || value === '' || (Array.isArray(value) && value.length === 0))
|
||||
</script>
|
||||
|
||||
{#if editor}
|
||||
{#if showHeader}
|
||||
<span
|
||||
class="labelOnPanel"
|
||||
class:required-empty={isRequiredAndEmpty}
|
||||
use:tooltip={{
|
||||
component: Label,
|
||||
props: { label: attribute.automationOnly ? view.string.AutomationOnly : attribute.label }
|
||||
@@ -85,9 +93,15 @@
|
||||
<div class="flex flex-gap-1 items-center">
|
||||
<Icon icon={icon ?? view.icon.Setting} size="small" />
|
||||
<Label label={attribute.label} />
|
||||
{#if attribute.required}
|
||||
<span class="required-asterisk">*</span>
|
||||
{/if}
|
||||
</div>
|
||||
{:else}
|
||||
<Label label={attribute.label} />
|
||||
{#if attribute.required}
|
||||
<span class="required-asterisk">*</span>
|
||||
{/if}
|
||||
{/if}
|
||||
</span>
|
||||
<div class="flex flex-grow min-w-0">
|
||||
@@ -106,7 +120,7 @@
|
||||
{maxWidth}
|
||||
{attribute}
|
||||
{attributeKey}
|
||||
value={getAttribute(client, object, { key: attributeKey, attr: attribute })}
|
||||
{value}
|
||||
space={object.space}
|
||||
{onChange}
|
||||
{focus}
|
||||
@@ -114,13 +128,13 @@
|
||||
/>
|
||||
</div>
|
||||
{:else}
|
||||
<div style="grid-column: 1/3;">
|
||||
<div style="grid-column: 1/3;" class:required-empty={isRequiredAndEmpty}>
|
||||
<svelte:component
|
||||
this={editor}
|
||||
type={attribute?.type}
|
||||
{maxWidth}
|
||||
{attributeKey}
|
||||
value={getAttribute(client, object, { key: attributeKey, attr: attribute })}
|
||||
{value}
|
||||
readonly={isReadonly}
|
||||
disabled={isReadonly}
|
||||
space={object.space}
|
||||
@@ -133,3 +147,13 @@
|
||||
</div>
|
||||
{/if}
|
||||
{/if}
|
||||
|
||||
<style lang="scss">
|
||||
.labelOnPanel.required-empty {
|
||||
color: var(--theme-error-color, #eb5757) !important;
|
||||
}
|
||||
.required-asterisk {
|
||||
color: var(--theme-error-color, #eb5757);
|
||||
margin-left: 2px;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -19,6 +19,7 @@
|
||||
import core, { Blob, Class, Doc, Ref, toRank } from '@hcengineering/core'
|
||||
import { getResource, setPlatformStatus, unknownError } from '@hcengineering/platform'
|
||||
import { getClient, KeyedAttribute, updateAttribute } from '@hcengineering/presentation'
|
||||
import { isEmptyMarkup } from '@hcengineering/text'
|
||||
import { Label } from '@hcengineering/ui'
|
||||
import { MarkupEditor } from '@hcengineering/view-resources'
|
||||
import { createEventDispatcher } from 'svelte'
|
||||
@@ -75,17 +76,34 @@
|
||||
</script>
|
||||
|
||||
{#each keys as key}
|
||||
{@const val = getValue(doc, key.key)}
|
||||
{@const isRequiredAndEmpty = (key.attr.required ?? false) && isEmptyMarkup(val)}
|
||||
<div class="w-full mt-2">
|
||||
<span>
|
||||
<span class:required-empty-label={isRequiredAndEmpty}>
|
||||
<Label label={key.attr.label} />
|
||||
{#if key.attr.required}
|
||||
<span class="required-asterisk">*</span>
|
||||
{/if}
|
||||
</span>
|
||||
<MarkupEditor
|
||||
value={getValue(doc, key.key)}
|
||||
onChange={(value) => {
|
||||
onChange(value, key)
|
||||
}}
|
||||
{readonly}
|
||||
{attachFile}
|
||||
/>
|
||||
<div>
|
||||
<MarkupEditor
|
||||
value={val}
|
||||
onChange={(value) => {
|
||||
onChange(value, key)
|
||||
}}
|
||||
{readonly}
|
||||
{attachFile}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
{/each}
|
||||
|
||||
<style lang="scss">
|
||||
.required-empty-label {
|
||||
color: var(--theme-error-color, #eb5757) !important;
|
||||
}
|
||||
.required-asterisk {
|
||||
color: var(--theme-error-color, #eb5757);
|
||||
margin-left: 2px;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -176,7 +176,8 @@
|
||||
"ExportWithSlots": "Exportovat se sloty",
|
||||
"ExportWithoutSlots": "Exportovat bez slotů",
|
||||
"RequiredSlots": "Požadované sloty",
|
||||
"Bindings": "Vazby"
|
||||
"Bindings": "Vazby",
|
||||
"AskRequired": "zeptat se na prázdná povinná pole"
|
||||
},
|
||||
"error": {
|
||||
"MethodNotFound": "Metoda nenalezena: {methodId}",
|
||||
|
||||
@@ -176,7 +176,8 @@
|
||||
"ExportWithSlots": "Slot-Export",
|
||||
"ExportWithoutSlots": "Slot-freier Export",
|
||||
"RequiredSlots": "Erforderliche Slots",
|
||||
"Bindings": "Bindungen"
|
||||
"Bindings": "Bindungen",
|
||||
"AskRequiredFields": "Erforderliche Felder erfragen"
|
||||
},
|
||||
"error": {
|
||||
"MethodNotFound": "Methode nicht gefunden: {methodId}",
|
||||
|
||||
@@ -181,6 +181,7 @@
|
||||
"TextFromSelect": "Text from select",
|
||||
"SelectFromText": "Select from text",
|
||||
"AskSubclass": "Ask subclass",
|
||||
"AskRequired": "Ask all required fields",
|
||||
"TextFromIdentifier": "Text from identifier"
|
||||
},
|
||||
"error": {
|
||||
|
||||
@@ -181,7 +181,8 @@
|
||||
"ExportWithSlots": "Exportar con slots",
|
||||
"ExportWithoutSlots": "Exportar sin slots",
|
||||
"RequiredSlots": "Slots requeridos",
|
||||
"Bindings": "Bindings"
|
||||
"Bindings": "Bindings",
|
||||
"AskRequired": "Preguntar por campos requeridos"
|
||||
},
|
||||
"error": {
|
||||
"MethodNotFound": "Método no encontrado: {methodId}",
|
||||
|
||||
@@ -181,7 +181,8 @@
|
||||
"ExportWithSlots": "Exporter avec slots",
|
||||
"ExportWithoutSlots": "Exporter sans slots",
|
||||
"RequiredSlots": "Slots requis",
|
||||
"Bindings": "Bindings"
|
||||
"Bindings": "Bindings",
|
||||
"AskRequired": "Demander les champs requis"
|
||||
},
|
||||
"error": {
|
||||
"MethodNotFound": "Méthode introuvable : {methodId}",
|
||||
|
||||
@@ -181,7 +181,8 @@
|
||||
"ExportWithSlots": "Esporta con slot",
|
||||
"ExportWithoutSlots": "Esporta senza slot",
|
||||
"RequiredSlots": "Slot richiesti",
|
||||
"Bindings": "Binding"
|
||||
"Bindings": "Binding",
|
||||
"AskRequired": "Richiedi campi"
|
||||
},
|
||||
"error": {
|
||||
"MethodNotFound": "Metodo non trovato: {methodId}",
|
||||
|
||||
@@ -180,7 +180,8 @@
|
||||
"ExportWithSlots": "スロット付きでエクスポート",
|
||||
"ExportWithoutSlots": "スロットなしでエクスポート",
|
||||
"RequiredSlots": "必須スロット",
|
||||
"Bindings": "バインディング"
|
||||
"Bindings": "バインディング",
|
||||
"AskRequired": "必須フィールドを尋ねる"
|
||||
},
|
||||
"error": {
|
||||
"MethodNotFound": "メソッドが見つかりません: {methodId}",
|
||||
|
||||
@@ -181,7 +181,8 @@
|
||||
"ExportWithSlots": "슬롯 포함 내보내기",
|
||||
"ExportWithoutSlots": "슬롯 없이 내보내기",
|
||||
"RequiredSlots": "필수 슬롯",
|
||||
"Bindings": "바인딩"
|
||||
"Bindings": "바인딩",
|
||||
"AskRequired": "필수 필드 질문"
|
||||
},
|
||||
"error": {
|
||||
"MethodNotFound": "메서드를 찾을 수 없습니다: {methodId}",
|
||||
|
||||
@@ -181,7 +181,8 @@
|
||||
"TextFromSelect": "Tekst z wyboru",
|
||||
"SelectFromText": "Wybór z tekstu",
|
||||
"AskSubclass": "Poproś o podklasę",
|
||||
"TextFromIdentifier": "Tekst z identyfikatora"
|
||||
"TextFromIdentifier": "Tekst z identyfikatora",
|
||||
"AskRequired": "Poproś o wymagane pola"
|
||||
},
|
||||
"error": {
|
||||
"MethodNotFound": "Nie odnaleziono metody: {methodId}",
|
||||
|
||||
@@ -169,7 +169,8 @@
|
||||
"ExportWithSlots": "Exportar com slots",
|
||||
"ExportWithoutSlots": "Exportar sem slots",
|
||||
"RequiredSlots": "Slots necessários",
|
||||
"Bindings": "Vinculações"
|
||||
"Bindings": "Vinculações",
|
||||
"AskRequired": "Perguntar por campos requeridos"
|
||||
},
|
||||
"error": {
|
||||
"MethodNotFound": "Método não encontrado: {methodId}",
|
||||
|
||||
@@ -181,7 +181,8 @@
|
||||
"ExportWithSlots": "Exportar com slots",
|
||||
"ExportWithoutSlots": "Exportar sem slots",
|
||||
"RequiredSlots": "Slots necessários",
|
||||
"Bindings": "Vinculações"
|
||||
"Bindings": "Vinculações",
|
||||
"AskRequired": "Perguntar por campos requeridos"
|
||||
},
|
||||
"error": {
|
||||
"MethodNotFound": "Método não encontrado: {methodId}",
|
||||
|
||||
@@ -181,6 +181,7 @@
|
||||
"TextFromSelect": "Текст из выбора",
|
||||
"SelectFromText": "Выбор из текста",
|
||||
"AskSubclass": "Спросить подкласс",
|
||||
"AskRequired": "Запросить все обязательные поля",
|
||||
"TextFromIdentifier": "Текст из идентификатора"
|
||||
},
|
||||
"error": {
|
||||
|
||||
@@ -176,7 +176,8 @@
|
||||
"ExportWithSlots": "Slotlu dışa aktar",
|
||||
"ExportWithoutSlots": "Slotsuz dışa aktar",
|
||||
"RequiredSlots": "Gerekli slotlar",
|
||||
"Bindings": "Bağlamalar"
|
||||
"Bindings": "Bağlamalar",
|
||||
"AskRequired": "Gerekli alanları sor"
|
||||
},
|
||||
"error": {
|
||||
"MethodNotFound": "Metod bulunamadı: {methodId}",
|
||||
|
||||
@@ -181,7 +181,8 @@
|
||||
"ExportWithSlots": "导出包含槽",
|
||||
"ExportWithoutSlots": "导出不含槽",
|
||||
"RequiredSlots": "必需槽",
|
||||
"Bindings": "绑定"
|
||||
"Bindings": "绑定",
|
||||
"AskRequired": "询问必需字段"
|
||||
},
|
||||
"error": {
|
||||
"MethodNotFound": "找不到方法:{methodId}",
|
||||
|
||||
@@ -14,13 +14,15 @@
|
||||
-->
|
||||
<script lang="ts">
|
||||
import cardPlugin, { Tag } from '@hcengineering/card'
|
||||
import { Process, Step } from '@hcengineering/process'
|
||||
import { Process, Step, createContext } from '@hcengineering/process'
|
||||
import ParamsEditor from './ParamsEditor.svelte'
|
||||
import { Ref } from '@hcengineering/core'
|
||||
import presentation, { getClient } from '@hcengineering/presentation'
|
||||
import { Button, eventToHTMLElement, Label, SelectPopup, showPopup, tooltip } from '@hcengineering/ui'
|
||||
import { generateContextId } from '../../utils'
|
||||
import { Button, eventToHTMLElement, Label, SelectPopup, showPopup, Toggle, tooltip } from '@hcengineering/ui'
|
||||
import TagSelector from './TagSelector.svelte'
|
||||
import { createEventDispatcher } from 'svelte'
|
||||
import plugin from '../../plugin'
|
||||
|
||||
export let process: Process
|
||||
export let step: Step<Tag>
|
||||
@@ -41,6 +43,16 @@
|
||||
params._id = _id
|
||||
props = {}
|
||||
params.props = props
|
||||
if (params.askRequired) {
|
||||
params.requiredProperties = createContext({
|
||||
type: 'userRequest',
|
||||
id: generateContextId(),
|
||||
_class: _id,
|
||||
key: 'requiredProperties'
|
||||
})
|
||||
} else {
|
||||
delete params.requiredProperties
|
||||
}
|
||||
step.params = params
|
||||
if (step.context != null) {
|
||||
step.context._class = _id
|
||||
@@ -91,6 +103,27 @@
|
||||
step.params = params
|
||||
dispatch('change', step)
|
||||
}
|
||||
|
||||
let askRequired = params.askRequired ?? false
|
||||
$: askRequired = params.askRequired ?? false
|
||||
|
||||
function changeAskRequired (e: CustomEvent<boolean>): void {
|
||||
if (e.detail !== undefined) {
|
||||
params.askRequired = e.detail
|
||||
if (e.detail) {
|
||||
params.requiredProperties = createContext({
|
||||
type: 'userRequest',
|
||||
id: generateContextId(),
|
||||
_class: _id,
|
||||
key: 'requiredProperties'
|
||||
})
|
||||
} else {
|
||||
delete params.requiredProperties
|
||||
}
|
||||
step.params = params
|
||||
dispatch('change', step)
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<div class="flex-col flex-gap-2">
|
||||
@@ -104,6 +137,17 @@
|
||||
<Label label={cardPlugin.string.Tag} />
|
||||
</span>
|
||||
<TagSelector {process} tag={_id} on:change={changeTag} />
|
||||
<span
|
||||
class="labelOnPanel"
|
||||
use:tooltip={{
|
||||
props: { label: plugin.string.AskRequired }
|
||||
}}
|
||||
>
|
||||
<Label label={plugin.string.AskRequired} />
|
||||
</span>
|
||||
<div>
|
||||
<Toggle on={askRequired} on:change={changeAskRequired} />
|
||||
</div>
|
||||
</div>
|
||||
<ParamsEditor
|
||||
{process}
|
||||
|
||||
@@ -75,7 +75,7 @@
|
||||
}
|
||||
|
||||
let keys = Object.keys(params).filter((key) => {
|
||||
return key !== '_class' && key !== 'targetClass'
|
||||
return key !== '_class' && key !== 'targetClass' && key !== 'askRequired' && key !== 'requiredFields'
|
||||
})
|
||||
|
||||
$: allAttrs = getKeys(targetClass)
|
||||
@@ -116,7 +116,7 @@
|
||||
|
||||
function typeChange (_id: Ref<Class<MasterTag>>): void {
|
||||
if (_id === undefined) return
|
||||
const attrKeys = ['_class', 'targetClass', ...getKeys(_id).map((p) => p.name)]
|
||||
const attrKeys = ['_class', 'targetClass', 'askRequired', 'requiredFields', ...getKeys(_id).map((p) => p.name)]
|
||||
const oldParams = { ...params }
|
||||
params = {}
|
||||
for (const key of attrKeys) {
|
||||
@@ -125,12 +125,22 @@
|
||||
}
|
||||
}
|
||||
keys = Object.keys(params).filter((key) => {
|
||||
return key !== '_class' && key !== 'targetClass'
|
||||
return key !== '_class' && key !== 'targetClass' && key !== 'askRequired' && key !== 'requiredFields'
|
||||
})
|
||||
if (params.targetClass !== _id) {
|
||||
params._class = _id
|
||||
params.targetClass = _id
|
||||
}
|
||||
if (params.askRequired) {
|
||||
params.requiredFields = createContext({
|
||||
type: 'userRequest',
|
||||
id: generateContextId(),
|
||||
_class: _id,
|
||||
key: 'requiredFields'
|
||||
})
|
||||
} else {
|
||||
delete params.requiredFields
|
||||
}
|
||||
step.params = params
|
||||
if (step.context != null) {
|
||||
step.context._class = _id
|
||||
@@ -158,6 +168,27 @@
|
||||
dispatch('change', step)
|
||||
}
|
||||
}
|
||||
|
||||
let askRequired = params.askRequired ?? false
|
||||
$: askRequired = params.askRequired ?? false
|
||||
|
||||
function changeAskRequired (e: CustomEvent<boolean>): void {
|
||||
if (e.detail !== undefined) {
|
||||
params.askRequired = e.detail
|
||||
if (e.detail) {
|
||||
params.requiredFields = createContext({
|
||||
type: 'userRequest',
|
||||
id: generateContextId(),
|
||||
_class: targetClass,
|
||||
key: 'requiredFields'
|
||||
})
|
||||
} else {
|
||||
delete params.requiredFields
|
||||
}
|
||||
step.params = params
|
||||
dispatch('change', step)
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<div class="grid">
|
||||
@@ -191,6 +222,17 @@
|
||||
<Toggle on={askSubclass} on:change={changeAskSubclass} />
|
||||
</div>
|
||||
{/if}
|
||||
<span
|
||||
class="labelOnPanel"
|
||||
use:tooltip={{
|
||||
props: { label: plugin.string.AskRequired }
|
||||
}}
|
||||
>
|
||||
<Label label={plugin.string.AskRequired} />
|
||||
</span>
|
||||
<div>
|
||||
<Toggle on={askRequired} on:change={changeAskRequired} />
|
||||
</div>
|
||||
</div>
|
||||
<div class="divider" />
|
||||
{#key targetClass}
|
||||
|
||||
@@ -286,6 +286,7 @@ export default mergeIds(processId, process, {
|
||||
TextFromSelect: '' as IntlString,
|
||||
SelectFromText: '' as IntlString,
|
||||
AskSubclass: '' as IntlString,
|
||||
AskRequired: '' as IntlString,
|
||||
RequiredSlots: '' as IntlString,
|
||||
Bindings: '' as IntlString
|
||||
},
|
||||
|
||||
@@ -543,10 +543,59 @@ export async function getTransitionUserInput (
|
||||
skipExisting: boolean = false
|
||||
): Promise<ExecutionContext | undefined> {
|
||||
let changed = false
|
||||
const client = getClient()
|
||||
const hierarchy = client.getHierarchy()
|
||||
|
||||
for (const action of transition.actions) {
|
||||
if (action == null) continue
|
||||
const inputs: SelectedUserRequest[] = []
|
||||
|
||||
// Map of: tempInputId -> { attrName, virtualContextId }
|
||||
const dynamicMappings: Record<string, { attrName: string, virtualContextId: string }> = {}
|
||||
|
||||
// Check for virtual required attributes
|
||||
let virtualContext: SelectedUserRequest | undefined
|
||||
let virtualKey = ''
|
||||
if (action.params.requiredFields !== undefined) {
|
||||
virtualContext = parseContext(action.params.requiredFields) as SelectedUserRequest
|
||||
virtualKey = 'requiredFields'
|
||||
} else if (action.params.requiredProperties !== undefined) {
|
||||
virtualContext = parseContext(action.params.requiredProperties) as SelectedUserRequest
|
||||
virtualKey = 'requiredProperties'
|
||||
}
|
||||
|
||||
if (virtualContext !== undefined && virtualContext.type === 'userRequest') {
|
||||
const classId = virtualContext._class
|
||||
const allAttributes =
|
||||
virtualKey === 'requiredFields'
|
||||
? Array.from(hierarchy.getAllAttributes(classId, core.class.Doc).values())
|
||||
: Array.from(hierarchy.getOwnAttributes(classId).values())
|
||||
|
||||
for (const attr of allAttributes) {
|
||||
if (virtualKey === 'requiredFields' && attr.name === 'title') continue
|
||||
if (attr.hidden === true) continue
|
||||
|
||||
const isConfigured =
|
||||
virtualKey === 'requiredFields'
|
||||
? action.params[attr.name] !== undefined
|
||||
: action.params.props !== undefined && action.params.props[attr.name] !== undefined
|
||||
|
||||
if (attr.required === true && !isConfigured) {
|
||||
const tempInputId = generateContextId()
|
||||
dynamicMappings[tempInputId] = { attrName: attr.name, virtualContextId: virtualContext.id }
|
||||
inputs.push({
|
||||
type: 'userRequest',
|
||||
id: tempInputId,
|
||||
key: attr.name,
|
||||
_class: classId
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (const key in action.params) {
|
||||
if (key === 'requiredFields' || key === 'requiredProperties') continue
|
||||
|
||||
const value = (action.params as any)[key]
|
||||
if (typeof value === 'string') {
|
||||
const context = parseContext(value)
|
||||
@@ -564,6 +613,7 @@ export async function getTransitionUserInput (
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (inputs.length > 0) {
|
||||
const promise = new Promise<void>((resolve, reject) => {
|
||||
showPopup(
|
||||
@@ -580,9 +630,24 @@ export async function getTransitionUserInput (
|
||||
const isComplete = res?.value !== undefined && inputs.every((input) => res.value[input.id] != null)
|
||||
if (isComplete) {
|
||||
changed = true
|
||||
const groupedValues: Record<string, Record<string, any>> = {}
|
||||
|
||||
for (const [key, value] of Object.entries(res.value)) {
|
||||
userContext[key as ContextId] = value
|
||||
const mapping = dynamicMappings[key]
|
||||
if (mapping !== undefined) {
|
||||
if (groupedValues[mapping.virtualContextId] === undefined) {
|
||||
groupedValues[mapping.virtualContextId] = {}
|
||||
}
|
||||
groupedValues[mapping.virtualContextId][mapping.attrName] = value
|
||||
} else {
|
||||
userContext[key as ContextId] = value
|
||||
}
|
||||
}
|
||||
|
||||
for (const [virtualId, obj] of Object.entries(groupedValues)) {
|
||||
userContext[virtualId as ContextId] = obj
|
||||
}
|
||||
|
||||
resolve()
|
||||
} else {
|
||||
reject(new PlatformError(new Status(Severity.ERROR, process.error.ResultNotProvided, {})))
|
||||
|
||||
@@ -238,6 +238,7 @@
|
||||
"TwoFactorAuthDisabled": "Dvoufaktorové ověřování je zakázáno",
|
||||
"ShowQRCode": "Zobrazit QR kód",
|
||||
"EnterVerificationCode": "Zadejte ověřovací kód",
|
||||
"OverrideAttribute": "Přepsat atribut"
|
||||
"OverrideAttribute": "Přepsat atribut",
|
||||
"Required": "Požadované"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -240,6 +240,7 @@
|
||||
"TwoFactorAuthDisabled": "Zweistufige Authentifizierung ist deaktiviert",
|
||||
"ShowQRCode": "QR-Code anzeigen",
|
||||
"EnterVerificationCode": "Verifizierungscode eingeben",
|
||||
"OverrideAttribute": "Überschreibattribut"
|
||||
"OverrideAttribute": "Überschreibattribut",
|
||||
"Required": "Pflichtfeld"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -240,6 +240,7 @@
|
||||
"TwoFactorAuthDisabled": "Two-factor authentication is disabled",
|
||||
"ShowQRCode": "Show QR code",
|
||||
"EnterVerificationCode": "Enter verification code",
|
||||
"OverrideAttribute": "Override attribute"
|
||||
"OverrideAttribute": "Override attribute",
|
||||
"Required": "Required"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -231,6 +231,7 @@
|
||||
"TwoFactorAuthDisabled": "La autenticación de dos factores está deshabilitada",
|
||||
"ShowQRCode": "Mostrar código QR",
|
||||
"EnterVerificationCode": "Introducir código de verificación",
|
||||
"OverrideAttribute": "Sobreescribir atributo"
|
||||
"OverrideAttribute": "Sobreescribir atributo",
|
||||
"Required": "Requerido"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -240,6 +240,7 @@
|
||||
"TwoFactorAuthDisabled": "L'authentification à deux facteurs est désactivée",
|
||||
"ShowQRCode": "Afficher le code QR",
|
||||
"EnterVerificationCode": "Entrer le code de vérification",
|
||||
"OverrideAttribute": "Surcharger l'attribut"
|
||||
"OverrideAttribute": "Surcharger l'attribut",
|
||||
"Required": "Requis"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -240,6 +240,7 @@
|
||||
"TwoFactorAuthDisabled": "L'autenticazione a due fattori è disabilitata",
|
||||
"ShowQRCode": "Mostra codice QR",
|
||||
"EnterVerificationCode": "Inserisci codice di verifica",
|
||||
"OverrideAttribute": "Sovrascrivi attributo"
|
||||
"OverrideAttribute": "Sovrascrivi attributo",
|
||||
"Required": "Richiesto"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -240,6 +240,7 @@
|
||||
"TwoFactorAuthDisabled": "二要素認証は無効です",
|
||||
"ShowQRCode": "QRコードを表示",
|
||||
"EnterVerificationCode": "確認コードを入力",
|
||||
"OverrideAttribute": "属性を上書き"
|
||||
"OverrideAttribute": "属性を上書き",
|
||||
"Required": "必須"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -240,6 +240,7 @@
|
||||
"TwoFactorAuthDisabled": "2단계 인증이 비활성화됨",
|
||||
"ShowQRCode": "QR 코드 표시",
|
||||
"EnterVerificationCode": "인증 코드 입력",
|
||||
"OverrideAttribute": "속성 재정의"
|
||||
"OverrideAttribute": "속성 재정의",
|
||||
"Required": "필수"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -240,6 +240,7 @@
|
||||
"TwoFactorAuthDisabled": "Uwierzytelnienie dwuskładnikowe jest wyłączone",
|
||||
"ShowQRCode": "Pokaż kod QR",
|
||||
"EnterVerificationCode": "Wpisz kod weryfikacyjny",
|
||||
"OverrideAttribute": "Nadpisz atrybut"
|
||||
"OverrideAttribute": "Nadpisz atrybut",
|
||||
"Required": "Obowiązkowe"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -231,6 +231,7 @@
|
||||
"TwoFactorAuthDisabled": "Autenticação de dois fatores está desativada",
|
||||
"ShowQRCode": "Mostrar código QR",
|
||||
"EnterVerificationCode": "Inserir código de verificação",
|
||||
"OverrideAttribute": "Sobrescrever atributo"
|
||||
"OverrideAttribute": "Sobrescrever atributo",
|
||||
"Required": "Obrigatório"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -231,6 +231,7 @@
|
||||
"TwoFactorAuthDisabled": "Autenticação de dois fatores está desativada",
|
||||
"ShowQRCode": "Mostrar código QR",
|
||||
"EnterVerificationCode": "Inserir código de verificação",
|
||||
"OverrideAttribute": "Sobrescrever atributo"
|
||||
"OverrideAttribute": "Sobrescrever atributo",
|
||||
"Required": "Obrigatório"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -240,6 +240,7 @@
|
||||
"TwoFactorAuthDisabled": "Двухфакторная аутентификация отключена",
|
||||
"ShowQRCode": "Показать QR-код",
|
||||
"EnterVerificationCode": "Введите код подтверждения",
|
||||
"OverrideAttribute": "Переопределить атрибут"
|
||||
"OverrideAttribute": "Переопределить атрибут",
|
||||
"Required": "Обязательный"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -240,6 +240,7 @@
|
||||
"TwoFactorAuthDisabled": "İki faktörlü kimlik doğrulama devre dışı",
|
||||
"ShowQRCode": "QR kodu göster",
|
||||
"EnterVerificationCode": "Doğrulama kodunu gir",
|
||||
"OverrideAttribute": "Özniteliği geçersiz kıl"
|
||||
"OverrideAttribute": "Özniteliği geçersiz kıl",
|
||||
"Required": "Zorunlu"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -240,6 +240,7 @@
|
||||
"TwoFactorAuthDisabled": "双因素认证已禁用",
|
||||
"ShowQRCode": "显示QR码",
|
||||
"EnterVerificationCode": "输入验证码",
|
||||
"OverrideAttribute": "覆盖属性"
|
||||
"OverrideAttribute": "覆盖属性",
|
||||
"Required": "必须"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -57,6 +57,7 @@
|
||||
let extra: Record<string, any> = {}
|
||||
let is: AnyComponent | undefined
|
||||
let readonly: boolean = false
|
||||
let required: boolean = false
|
||||
const client = getClient()
|
||||
const hierarchy = client.getHierarchy()
|
||||
|
||||
@@ -72,7 +73,8 @@
|
||||
type,
|
||||
defaultValue,
|
||||
readonly,
|
||||
automationOnly: readonly
|
||||
automationOnly: readonly,
|
||||
required
|
||||
}
|
||||
if (index !== undefined) {
|
||||
data.index = index
|
||||
@@ -196,6 +198,10 @@
|
||||
<Label label={view.string.AutomationOnly} />
|
||||
</span>
|
||||
<Toggle bind:on={readonly} />
|
||||
<span class="label">
|
||||
<Label label={setting.string.Required} />
|
||||
</span>
|
||||
<Toggle bind:on={required} />
|
||||
</div>
|
||||
</Modal>
|
||||
|
||||
|
||||
@@ -56,6 +56,7 @@
|
||||
let defaultValue: any | undefined = attribute.defaultValue
|
||||
let icon: Asset | undefined = attribute.icon
|
||||
let automationOnly = attribute.automationOnly ?? false
|
||||
let required = attribute.required ?? false
|
||||
let extra: Record<string, any> = {}
|
||||
let is: AnyComponent | undefined
|
||||
|
||||
@@ -94,6 +95,9 @@
|
||||
update.readonly = automationOnly
|
||||
update.automationOnly = automationOnly
|
||||
}
|
||||
if (required !== attribute.required) {
|
||||
update.required = required
|
||||
}
|
||||
for (const [k, v] of Object.entries(extra)) {
|
||||
if (attribute[k] === v) continue
|
||||
update[k] = v
|
||||
@@ -308,6 +312,10 @@
|
||||
<Label label={view.string.AutomationOnly} />
|
||||
</span>
|
||||
<Toggle bind:on={automationOnly} disabled={attribute.isCustom !== true} />
|
||||
<span class="label">
|
||||
<Label label={setting.string.Required} />
|
||||
</span>
|
||||
<Toggle bind:on={required} {disabled} />
|
||||
<span class="label">
|
||||
<Label label={setting.string.Restricted} />
|
||||
</span>
|
||||
|
||||
@@ -167,6 +167,7 @@ export default mergeIds(settingId, setting, {
|
||||
SpaceMembersOnly: '' as IntlString,
|
||||
LastOwnerLeaveTitle: '' as IntlString,
|
||||
LastOwnerLeaveMessage: '' as IntlString,
|
||||
OverrideAttribute: '' as IntlString
|
||||
OverrideAttribute: '' as IntlString,
|
||||
Required: '' as IntlString
|
||||
}
|
||||
})
|
||||
|
||||
@@ -353,14 +353,24 @@ export async function AddTag (
|
||||
execution: Execution,
|
||||
control: ProcessControl
|
||||
): Promise<ExecuteResult> {
|
||||
const { _id, props } = params
|
||||
const { _id, props, requiredProperties } = params
|
||||
if (_id === undefined) throw processError(process.error.RequiredParamsNotProvided, { params: '_id' })
|
||||
const tagId = _id as Ref<Tag>
|
||||
const res: Tx[] = []
|
||||
const _process = control.client.getModel().findObject(execution.process)
|
||||
if (_process === undefined) throw processError(process.error.ObjectNotFound, { _id: execution.process })
|
||||
// todo fill default for tag and set parent tags
|
||||
const tx = control.client.txFactory.createTxMixin(execution.card, _process.masterTag, execution.space, tagId, props)
|
||||
const mergedProps = { ...props }
|
||||
if (requiredProperties !== undefined && typeof requiredProperties === 'object' && requiredProperties !== null) {
|
||||
Object.assign(mergedProps, requiredProperties)
|
||||
}
|
||||
const tx = control.client.txFactory.createTxMixin(
|
||||
execution.card,
|
||||
_process.masterTag,
|
||||
execution.space,
|
||||
tagId,
|
||||
mergedProps
|
||||
)
|
||||
res.push(tx)
|
||||
const card = control.cache.get(execution.card)
|
||||
if (card === undefined) throw processError(process.error.ObjectNotFound, { _id: execution.card })
|
||||
@@ -843,7 +853,7 @@ export async function CreateCard (
|
||||
execution: Execution,
|
||||
control: ProcessControl
|
||||
): Promise<ExecuteResult> {
|
||||
const { _class, title, content, ...attrs } = params
|
||||
const { _class, title, content, requiredFields, ...attrs } = params
|
||||
for (const key in { _class, title }) {
|
||||
const val = (params as any)[key]
|
||||
if (isEmpty(val)) {
|
||||
@@ -854,6 +864,9 @@ export async function CreateCard (
|
||||
if (_process === undefined) {
|
||||
throw processError(process.error.RequiredParamsNotProvided, {})
|
||||
}
|
||||
if (requiredFields !== undefined && typeof requiredFields === 'object' && requiredFields !== null) {
|
||||
Object.assign(attrs, requiredFields)
|
||||
}
|
||||
const resolvedAttrs: Record<string, any> = {}
|
||||
for (const key in attrs) {
|
||||
resolvedAttrs[resolveAttributeId(_process, key)] = (attrs as any)[key]
|
||||
|
||||
Reference in New Issue
Block a user