mirror of
https://github.com/hcengineering/platform.git
synced 2026-09-13 21:27:46 +02:00
Date process funcs (#8441)
Signed-off-by: Denis Bykhov <bykhov.denis@gmail.com>
This commit is contained in:
@@ -58,6 +58,7 @@ import { cardOperation } from '@hcengineering/model-card'
|
||||
import { aiBotId, aiBotOperation } from '@hcengineering/model-ai-bot'
|
||||
import { chatId, chatOperation } from '@hcengineering/model-chat'
|
||||
import { inboxId, inboxOperation } from '@hcengineering/model-inbox'
|
||||
import { processId, processOperation } from '@hcengineering/model-process'
|
||||
|
||||
export const migrateOperations: [string, MigrateOperation][] = [
|
||||
['core', coreOperation],
|
||||
@@ -104,5 +105,6 @@ export const migrateOperations: [string, MigrateOperation][] = [
|
||||
['survey', surveyOperation],
|
||||
[aiBotId, aiBotOperation],
|
||||
[chatId, chatOperation],
|
||||
[inboxId, inboxOperation]
|
||||
[inboxId, inboxOperation],
|
||||
[processId, processOperation]
|
||||
]
|
||||
|
||||
@@ -139,10 +139,14 @@ export class TState extends TDoc implements State {
|
||||
@Model(process.class.ProcessFunction, core.class.Doc, DOMAIN_MODEL)
|
||||
export class TProcessFunction extends TDoc implements ProcessFunction {
|
||||
of!: Ref<Class<Doc<Space>>>
|
||||
category?: AttributeCategory | undefined
|
||||
category: AttributeCategory | undefined
|
||||
label!: IntlString
|
||||
editor?: AnyComponent
|
||||
allowMany?: boolean
|
||||
}
|
||||
|
||||
export * from './migration'
|
||||
|
||||
export function createModel (builder: Builder): void {
|
||||
builder.createModel(TProcess, TExecution, TProcessToDo, TMethod, TState, TProcessFunction)
|
||||
|
||||
@@ -218,6 +222,7 @@ export function createModel (builder: Builder): void {
|
||||
core.space.Model,
|
||||
{
|
||||
of: core.class.ArrOf,
|
||||
category: undefined,
|
||||
label: process.string.FirstValue
|
||||
},
|
||||
process.function.FirstValue
|
||||
@@ -228,6 +233,7 @@ export function createModel (builder: Builder): void {
|
||||
core.space.Model,
|
||||
{
|
||||
of: core.class.ArrOf,
|
||||
category: undefined,
|
||||
label: process.string.LastValue
|
||||
},
|
||||
process.function.LastValue
|
||||
@@ -238,11 +244,61 @@ export function createModel (builder: Builder): void {
|
||||
core.space.Model,
|
||||
{
|
||||
of: core.class.ArrOf,
|
||||
category: undefined,
|
||||
label: process.string.Random
|
||||
},
|
||||
process.function.Random
|
||||
)
|
||||
|
||||
builder.createDoc(
|
||||
process.class.ProcessFunction,
|
||||
core.space.Model,
|
||||
{
|
||||
of: core.class.TypeNumber,
|
||||
category: 'attribute',
|
||||
label: process.string.Add,
|
||||
allowMany: true,
|
||||
editor: process.component.NumberOffsetEditor
|
||||
},
|
||||
process.function.Add
|
||||
)
|
||||
|
||||
builder.createDoc(
|
||||
process.class.ProcessFunction,
|
||||
core.space.Model,
|
||||
{
|
||||
of: core.class.TypeNumber,
|
||||
category: 'attribute',
|
||||
label: process.string.Subtract,
|
||||
allowMany: true,
|
||||
editor: process.component.NumberOffsetEditor
|
||||
},
|
||||
process.function.Subtract
|
||||
)
|
||||
|
||||
builder.createDoc(
|
||||
process.class.ProcessFunction,
|
||||
core.space.Model,
|
||||
{
|
||||
of: core.class.TypeDate,
|
||||
category: 'attribute',
|
||||
label: process.string.Offset,
|
||||
editor: process.component.DateOffsetEditor
|
||||
},
|
||||
process.function.Offset
|
||||
)
|
||||
|
||||
builder.createDoc(
|
||||
process.class.ProcessFunction,
|
||||
core.space.Model,
|
||||
{
|
||||
of: core.class.TypeDate,
|
||||
category: 'attribute',
|
||||
label: process.string.FirstWorkingDayAfter
|
||||
},
|
||||
process.function.FirstWorkingDayAfter
|
||||
)
|
||||
|
||||
builder.mixin(process.class.Process, core.class.Class, view.mixin.AttributePresenter, {
|
||||
presenter: process.component.ProcessPresenter
|
||||
})
|
||||
|
||||
@@ -0,0 +1,93 @@
|
||||
//
|
||||
// Copyright © 2025 Hardcore Engineering Inc.
|
||||
//
|
||||
// Licensed under the Eclipse Public License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License. You may
|
||||
// obtain a copy of the License at https://www.eclipse.org/legal/epl-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
//
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
//
|
||||
|
||||
import core, { type Ref, TxOperations } from '@hcengineering/core'
|
||||
import {
|
||||
tryUpgrade,
|
||||
type MigrateOperation,
|
||||
type MigrationClient,
|
||||
type MigrationUpgradeClient
|
||||
} from '@hcengineering/model'
|
||||
import { type Func, parseContext, type ProcessFunction } from '@hcengineering/process'
|
||||
import { processId } from '.'
|
||||
import process from './plugin'
|
||||
|
||||
export const processOperation: MigrateOperation = {
|
||||
async migrate (client: MigrationClient, mode): Promise<void> {},
|
||||
async upgrade (state: Map<string, Set<string>>, client: () => Promise<MigrationUpgradeClient>, mode): Promise<void> {
|
||||
await tryUpgrade(mode, state, client, processId, [
|
||||
{
|
||||
state: 'migrateStateFuncs',
|
||||
func: migrateStateFuncs
|
||||
}
|
||||
])
|
||||
}
|
||||
}
|
||||
|
||||
function getContext (value: string): string | undefined {
|
||||
const context = parseContext(value)
|
||||
if (context !== undefined) {
|
||||
let contextChanged = false
|
||||
if (context.functions !== undefined) {
|
||||
for (let i = 0; i < context.functions.length; i++) {
|
||||
const func = context.functions[i] as any
|
||||
if (typeof func === 'string') {
|
||||
const res: Func = {
|
||||
func: func as Ref<ProcessFunction>,
|
||||
props: {}
|
||||
}
|
||||
context.functions[i] = res
|
||||
contextChanged = true
|
||||
}
|
||||
}
|
||||
}
|
||||
if (contextChanged) {
|
||||
return '$' + JSON.stringify(context)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async function migrateStateFuncs (client: MigrationUpgradeClient): Promise<void> {
|
||||
const txOp = new TxOperations(client, core.account.System)
|
||||
const states = await client.findAll(process.class.State, {})
|
||||
for (const state of states) {
|
||||
let changed = false
|
||||
const actions = state.actions
|
||||
for (const action of actions) {
|
||||
for (const key of Object.keys(action.params)) {
|
||||
const value = (action.params as any)[key]
|
||||
const context = getContext(value)
|
||||
if (context !== undefined) {
|
||||
;(action.params as any)[key] = context
|
||||
changed = true
|
||||
}
|
||||
}
|
||||
}
|
||||
const endAction = state.endAction
|
||||
if (endAction != null) {
|
||||
for (const key of Object.keys(endAction.params)) {
|
||||
const value = (endAction.params as any)[key]
|
||||
const context = getContext(value)
|
||||
if (context !== undefined) {
|
||||
;(endAction.params as any)[key] = context
|
||||
changed = true
|
||||
}
|
||||
}
|
||||
}
|
||||
if (changed) {
|
||||
await txOp.updateDoc(state._class, state.space, state._id, { actions, endAction })
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -75,6 +75,22 @@ export function createModel (builder: Builder): void {
|
||||
func: serverProcess.transform.Trim
|
||||
})
|
||||
|
||||
builder.mixin(process.function.Add, process.class.ProcessFunction, serverProcess.mixin.FuncImpl, {
|
||||
func: serverProcess.transform.Add
|
||||
})
|
||||
|
||||
builder.mixin(process.function.Subtract, process.class.ProcessFunction, serverProcess.mixin.FuncImpl, {
|
||||
func: serverProcess.transform.Subtract
|
||||
})
|
||||
|
||||
builder.mixin(process.function.Offset, process.class.ProcessFunction, serverProcess.mixin.FuncImpl, {
|
||||
func: serverProcess.transform.Offset
|
||||
})
|
||||
|
||||
builder.mixin(process.function.FirstWorkingDayAfter, process.class.ProcessFunction, serverProcess.mixin.FuncImpl, {
|
||||
func: serverProcess.transform.FirstWorkingDayAfter
|
||||
})
|
||||
|
||||
builder.createDoc(serverCore.class.Trigger, core.space.Model, {
|
||||
trigger: serverProcess.trigger.OnProcessRemove,
|
||||
txMatch: {
|
||||
|
||||
@@ -111,6 +111,9 @@
|
||||
"UseMaxWidth": "Maximální šířka",
|
||||
"Sidebar": "Postranní panel",
|
||||
"FontSize": "Velikost písma",
|
||||
"Language": "Jazyk"
|
||||
"Language": "Jazyk",
|
||||
"DaysWOValue": "{days, plural, =1 {den} other {dny}}",
|
||||
"WeeksWOValue": "{weeks, plural, =1 {týden} other {týdny}}",
|
||||
"MonthsWOValue": "{months, plural, =1 {měsíc} other {měsíce}}"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -111,6 +111,9 @@
|
||||
"UseMaxWidth": "Maximale Breite",
|
||||
"Sidebar": "Seitenleiste",
|
||||
"FontSize": "Schriftgröße",
|
||||
"Language": "Sprache"
|
||||
"Language": "Sprache",
|
||||
"DaysWOValue": "{days, plural, =1 {Tag} other {Tage}}",
|
||||
"WeeksWOValue": "{weeks, plural, =1 {Woche} other {Wochen}}",
|
||||
"MonthsWOValue": "{months, plural, =1 {Monat} other {Monate}}"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -112,6 +112,9 @@
|
||||
"UseMaxWidth": "Max width",
|
||||
"Sidebar": "Sidebar",
|
||||
"FontSize": "Font size",
|
||||
"Language": "Language"
|
||||
"Language": "Language",
|
||||
"DaysWOValue": "{days, plural, =1 {day} other {days}}",
|
||||
"WeeksWOValue": "{weeks, plural, =1 {week} other {weeks}}",
|
||||
"MonthsWOValue": "{months, plural, =1 {month} other {months}}"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -112,6 +112,9 @@
|
||||
"UseMaxWidth": "Ancho máximo",
|
||||
"Sidebar": "Barra lateral",
|
||||
"FontSize": "Tamaño de fuente",
|
||||
"Language": "Idioma"
|
||||
"Language": "Idioma",
|
||||
"DaysWOValue": "{days, plural, =1 {día} other {días}}",
|
||||
"WeeksWOValue": "{weeks, plural, =1 {semana} other {semanas}}",
|
||||
"MonthsWOValue": "{months, plural, =1 {mes} other {meses}}"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -112,6 +112,9 @@
|
||||
"UseMaxWidth": "Largeur maximale",
|
||||
"Sidebar": "Barre latérale",
|
||||
"FontSize": "Taille de police",
|
||||
"Language": "Langue"
|
||||
"Language": "Langue",
|
||||
"DaysWOValue": "{days, plural, =1 {jour} other {jours}}",
|
||||
"WeeksWOValue": "{weeks, plural, =1 {semaine} other {semaines}}",
|
||||
"MonthsWOValue": "{months, plural, =1 {mois} other {mois}}"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -112,6 +112,9 @@
|
||||
"UseMaxWidth": "Larghezza massima",
|
||||
"Sidebar": "Barra laterale",
|
||||
"FontSize ": "Dimensione del carattere",
|
||||
"Language": "Lingua"
|
||||
"Language": "Lingua",
|
||||
"DaysWOValue": "{days, plural, =1 {giorno} other {giorni}}",
|
||||
"WeeksWOValue": "{weeks, plural, =1 {settimana} other {settimane}}",
|
||||
"MonthsWOValue": "{months, plural, =1 {mese} other {mesi}}"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -112,6 +112,9 @@
|
||||
"UseMaxWidth": "Largura máxima",
|
||||
"Sidebar": "Barra lateral",
|
||||
"FontSize": "Tamanho da fonte",
|
||||
"Language": "Idioma"
|
||||
"Language": "Idioma",
|
||||
"DaysWOValue": "{days, plural, =1 {dia} other {dias}}",
|
||||
"WeeksWOValue": "{weeks, plural, =1 {semana} other {semanas}}",
|
||||
"MonthsWOValue": "{months, plural, =1 {mês} other {meses}}"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -112,6 +112,9 @@
|
||||
"UseMaxWidth": "Максимальная ширина",
|
||||
"Sidebar": "Боковая панель",
|
||||
"FontSize": "Размер шрифта",
|
||||
"Language": "Язык"
|
||||
"Language": "Язык",
|
||||
"DaysWOValue": "{days, plural, one {день} few {дня} other {дней}}",
|
||||
"WeeksWOValue": "{weeks, plural, one {неделя} few {недели} other {недель}}",
|
||||
"MonthsWOValue": "{months, plural, one {месяц} few {месяца} other {месяцев}}"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -112,6 +112,9 @@
|
||||
"UseMaxWidth": "使用最大宽度",
|
||||
"Sidebar": "侧边栏",
|
||||
"FontSize": "字体大小",
|
||||
"Language": "语言"
|
||||
"Language": "语言",
|
||||
"DaysWOValue": "{days, plural, =1 {天} other {天}}",
|
||||
"WeeksWOValue": "{weeks, plural, =1 {周} other {周}}",
|
||||
"MonthsWOValue": "{months, plural, =1 {月} other {月}}"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -139,7 +139,10 @@ export const uis = plugin(uiId, {
|
||||
UseMaxWidth: '' as IntlString,
|
||||
Sidebar: '' as IntlString,
|
||||
FontSize: '' as IntlString,
|
||||
Language: '' as IntlString
|
||||
Language: '' as IntlString,
|
||||
DaysWOValue: '' as IntlString,
|
||||
WeeksWOValue: '' as IntlString,
|
||||
MonthsWOValue: '' as IntlString
|
||||
},
|
||||
metadata: {
|
||||
DefaultApplication: '' as Metadata<AnyComponent>,
|
||||
|
||||
@@ -55,9 +55,6 @@ export default mergeIds(calendarId, calendar, {
|
||||
Ends: '' as IntlString,
|
||||
Never: '' as IntlString,
|
||||
After: '' as IntlString,
|
||||
Day: '' as IntlString,
|
||||
Week: '' as IntlString,
|
||||
Month: '' as IntlString,
|
||||
Year: '' as IntlString,
|
||||
MondayShort: '' as IntlString,
|
||||
TuesdayShort: '' as IntlString,
|
||||
|
||||
@@ -234,7 +234,10 @@ const calendarPlugin = plugin(calendarId, {
|
||||
ScheduleTitlePlaceholder: '' as IntlString,
|
||||
ScheduleUnavailable: '' as IntlString,
|
||||
MeetingDuration: '' as IntlString,
|
||||
MeetingInterval: '' as IntlString
|
||||
MeetingInterval: '' as IntlString,
|
||||
Day: '' as IntlString,
|
||||
Week: '' as IntlString,
|
||||
Month: '' as IntlString
|
||||
},
|
||||
handler: {
|
||||
DisconnectHandler: '' as Handler
|
||||
|
||||
@@ -13,23 +13,12 @@
|
||||
// limitations under the License.
|
||||
-->
|
||||
<script lang="ts">
|
||||
import { Card, MasterTag } from '@hcengineering/card'
|
||||
import { Card } from '@hcengineering/card'
|
||||
import { AnyAttribute, Ref, RefTo } from '@hcengineering/core'
|
||||
import { IntlString } from '@hcengineering/platform'
|
||||
import { createQuery, getClient } from '@hcengineering/presentation'
|
||||
import {
|
||||
Button,
|
||||
ButtonKind,
|
||||
ButtonSize,
|
||||
eventToHTMLElement,
|
||||
IconWithEmoji,
|
||||
Label,
|
||||
showPopup
|
||||
} from '@hcengineering/ui'
|
||||
import view from '@hcengineering/view'
|
||||
import { ButtonKind, ButtonSize } from '@hcengineering/ui'
|
||||
import { createEventDispatcher } from 'svelte'
|
||||
import card from '../plugin'
|
||||
import CardsPopup from './CardsPopup.svelte'
|
||||
import CardSelector from './CardSelector.svelte'
|
||||
import CardPresenter from './CardPresenter.svelte'
|
||||
|
||||
@@ -52,10 +41,10 @@
|
||||
onChange(val)
|
||||
}
|
||||
|
||||
const _class = (attribute.type as RefTo<Card>).to
|
||||
const _class = (attribute?.type as RefTo<Card>)?.to ?? card.class.Card
|
||||
</script>
|
||||
|
||||
{#if readonly || attribute.readonly}
|
||||
{#if readonly || attribute?.readonly}
|
||||
<CardPresenter {value} noUnderline />
|
||||
{:else}
|
||||
<CardSelector
|
||||
|
||||
@@ -66,6 +66,7 @@
|
||||
selectedSubObjectId = undefined
|
||||
subEditorTitle = undefined
|
||||
subEditor = undefined
|
||||
subEditorTitle = undefined
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -36,6 +36,10 @@
|
||||
"Random": "Náhodné",
|
||||
"AllProcesses": "Všechny procesy",
|
||||
"MyProcesses": "Moje procesy",
|
||||
"ShowDone": "Zobrazit hotovo"
|
||||
"ShowDone": "Zobrazit hotovo",
|
||||
"Add": "Přidat",
|
||||
"Subtract": "Odečíst",
|
||||
"Offset": "Posunout",
|
||||
"FirstWorkingDayAfter": "První pracovní den po"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -36,6 +36,10 @@
|
||||
"Random": "Zufällig",
|
||||
"AllProcesses": "Alle Prozesse",
|
||||
"MyProcesses": "Meine Prozesse",
|
||||
"ShowDone": "Erledigt anzeigen"
|
||||
"ShowDone": "Erledigt anzeigen",
|
||||
"Add": "Hinzufügen",
|
||||
"Subtract": "Subtrahieren",
|
||||
"Offset": "Verschieben",
|
||||
"FirstWorkingDayAfter": "Erste Arbeitstag nach"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -36,6 +36,10 @@
|
||||
"Random": "Random",
|
||||
"AllProcesses": "All processes",
|
||||
"MyProcesses": "My processes",
|
||||
"ShowDone": "Show done"
|
||||
"ShowDone": "Show done",
|
||||
"Add": "Add",
|
||||
"Subtract": "Subtract",
|
||||
"Offset": "Offset",
|
||||
"FirstWorkingDayAfter": "First working day after"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -36,6 +36,10 @@
|
||||
"Random": "Aleatorio",
|
||||
"AllProcesses": "Todos los procesos",
|
||||
"MyProcesses": "Mis procesos",
|
||||
"ShowDone": "Mostrar hecho"
|
||||
"ShowDone": "Mostrar hecho",
|
||||
"Add": "Agregar",
|
||||
"Subtract": "Restar",
|
||||
"Offset": "Desplazar",
|
||||
"FirstWorkingDayAfter": "Primer día de trabajo después"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -36,6 +36,10 @@
|
||||
"Random": "Aléatoire",
|
||||
"AllProcesses": "Tous les processus",
|
||||
"MyProcesses": "Mes processus",
|
||||
"ShowDone": "Afficher terminé"
|
||||
"ShowDone": "Afficher terminé",
|
||||
"Add": "Ajouter",
|
||||
"Subtract": "Soustraire",
|
||||
"Offset": "Décalage",
|
||||
"FirstWorkingDayAfter": "Premier jour de travail après"
|
||||
}
|
||||
}
|
||||
@@ -36,6 +36,10 @@
|
||||
"Random": "Casuale",
|
||||
"AllProcesses": "Tutti i processi",
|
||||
"MyProcesses": "I miei processi",
|
||||
"ShowDone": "Mostra completato"
|
||||
"ShowDone": "Mostra completato",
|
||||
"Add": "Aggiungi",
|
||||
"Subtract": "Sottrai",
|
||||
"Offset": "Offset",
|
||||
"FirstWorkingDayAfter": "Primo giorno lavorativo dopo"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -36,6 +36,10 @@
|
||||
"Random": "Aleatório",
|
||||
"AllProcesses": "Todos os Processos",
|
||||
"MyProcesses": "Meus Processos",
|
||||
"ShowDone": "Mostrar Concluído"
|
||||
"ShowDone": "Mostrar Concluído",
|
||||
"Add": "Adicionar",
|
||||
"Subtract": "Subtrair",
|
||||
"Offset": "Deslocar",
|
||||
"FirstWorkingDayAfter": "Primeiro Dia de Trabalho Após"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -37,6 +37,10 @@
|
||||
"Random": "Случайное",
|
||||
"AllProcesses": "Все процессы",
|
||||
"MyProcesses": "Мои процессы",
|
||||
"ShowDone": "Показать выполненные"
|
||||
"ShowDone": "Показать выполненные",
|
||||
"Add": "Добавить",
|
||||
"Subtract": "Вычесть",
|
||||
"Offset": "Смещение",
|
||||
"FirstWorkingDayAfter": "Первый рабочий день после"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -36,6 +36,10 @@
|
||||
"Random": "随机",
|
||||
"AllProcesses": "所有过程",
|
||||
"MyProcesses": "我的过程",
|
||||
"ShowDone": "显示已完成"
|
||||
"ShowDone": "显示已完成",
|
||||
"Add": "添加",
|
||||
"Subtract": "减去",
|
||||
"Offset": "偏移",
|
||||
"FirstWorkingDayAfter": "在之后的第一个工作日"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -45,6 +45,7 @@
|
||||
"@hcengineering/view": "^0.6.13",
|
||||
"@hcengineering/view-resources": "^0.6.0",
|
||||
"@hcengineering/card-resources": "^0.6.0",
|
||||
"@hcengineering/calendar": "^0.6.24",
|
||||
"@hcengineering/contact": "^0.6.24",
|
||||
"@hcengineering/card": "^0.6.0",
|
||||
"@hcengineering/workbench-resources": "^0.6.1",
|
||||
|
||||
@@ -0,0 +1,131 @@
|
||||
<!--
|
||||
// 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, Class, Doc, Ref } from '@hcengineering/core'
|
||||
import { Context, parseContext, SelectedContext } from '@hcengineering/process'
|
||||
import { AnySvelteComponent, Button, eventToHTMLElement, IconAdd, Label, showPopup, tooltip } from '@hcengineering/ui'
|
||||
import { AttributeCategory } from '@hcengineering/view'
|
||||
import { createEventDispatcher } from 'svelte'
|
||||
import ContextSelectorPopup from './attributeEditors/ContextSelectorPopup.svelte'
|
||||
import ContextValue from './attributeEditors/ContextValue.svelte'
|
||||
|
||||
export let value: any
|
||||
export let context: Context
|
||||
export let presenterClass: {
|
||||
attrClass: Ref<Class<Doc>>
|
||||
category: AttributeCategory
|
||||
}
|
||||
export let attribute: AnyAttribute
|
||||
export let editor: AnySvelteComponent | undefined
|
||||
|
||||
const dispatch = createEventDispatcher()
|
||||
|
||||
$: contextValue = parseContext(value)
|
||||
|
||||
function onChange (value: any | undefined): void {
|
||||
dispatch('change', value)
|
||||
}
|
||||
|
||||
function selectContext (e: MouseEvent): void {
|
||||
showPopup(
|
||||
ContextSelectorPopup,
|
||||
{
|
||||
context,
|
||||
attribute,
|
||||
onSelect
|
||||
},
|
||||
eventToHTMLElement(e)
|
||||
)
|
||||
}
|
||||
|
||||
function onSelect (res: SelectedContext | null): void {
|
||||
value = res === null ? undefined : '$' + JSON.stringify(res)
|
||||
dispatch('change', value)
|
||||
}
|
||||
</script>
|
||||
|
||||
{#if editor}
|
||||
<span
|
||||
class="labelOnPanel"
|
||||
use:tooltip={{
|
||||
props: { label: attribute.label }
|
||||
}}
|
||||
>
|
||||
<Label label={attribute.label} />
|
||||
</span>
|
||||
<div class="text-input" class:context={contextValue}>
|
||||
{#if contextValue}
|
||||
<ContextValue
|
||||
{contextValue}
|
||||
{context}
|
||||
{attribute}
|
||||
category={presenterClass.category}
|
||||
attrClass={presenterClass.attrClass}
|
||||
on:update={(e) => {
|
||||
onSelect(e.detail)
|
||||
}}
|
||||
/>
|
||||
{:else}
|
||||
<div class="w-full">
|
||||
<svelte:component
|
||||
this={editor}
|
||||
label={attribute?.label}
|
||||
placeholder={attribute?.label}
|
||||
kind={'ghost'}
|
||||
size={'large'}
|
||||
width={'100%'}
|
||||
justify={'left'}
|
||||
type={attribute?.type}
|
||||
{value}
|
||||
{onChange}
|
||||
{focus}
|
||||
/>
|
||||
</div>
|
||||
{/if}
|
||||
<div class="button">
|
||||
<Button
|
||||
icon={IconAdd}
|
||||
kind="ghost"
|
||||
on:click={(e) => {
|
||||
selectContext(e)
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
<style lang="scss">
|
||||
.text-input {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
min-height: 2.5rem;
|
||||
border: 0.0625rem 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>
|
||||
@@ -15,11 +15,10 @@
|
||||
<script lang="ts">
|
||||
import { Class, Doc, Ref } from '@hcengineering/core'
|
||||
import { getAttributeEditor, getAttributePresenterClass, getClient } from '@hcengineering/presentation'
|
||||
import { parseContext, Process, SelectedContext, State } from '@hcengineering/process'
|
||||
import { AnySvelteComponent, Button, eventToHTMLElement, IconAdd, Label, showPopup, tooltip } from '@hcengineering/ui'
|
||||
import { Process, State } from '@hcengineering/process'
|
||||
import { AnySvelteComponent } from '@hcengineering/ui'
|
||||
import { getContext } from '../utils'
|
||||
import ContextSelectorPopup from './attributeEditors/ContextSelectorPopup.svelte'
|
||||
import ContextValue from './attributeEditors/ContextValue.svelte'
|
||||
import ProcessAttribute from './ProcessAttribute.svelte'
|
||||
|
||||
export let process: Process
|
||||
export let state: State
|
||||
@@ -54,96 +53,15 @@
|
||||
}
|
||||
|
||||
$: getBaseEditor(_class, key)
|
||||
|
||||
$: contextValue = parseContext(value)
|
||||
|
||||
function selectContext (e: MouseEvent): void {
|
||||
showPopup(
|
||||
ContextSelectorPopup,
|
||||
{
|
||||
context,
|
||||
attribute,
|
||||
onSelect
|
||||
},
|
||||
eventToHTMLElement(e)
|
||||
)
|
||||
}
|
||||
|
||||
function onSelect (res: SelectedContext | null): void {
|
||||
value = res === null ? undefined : '$' + JSON.stringify(res)
|
||||
onChange(value)
|
||||
}
|
||||
</script>
|
||||
|
||||
{#if editor}
|
||||
<span
|
||||
class="labelOnPanel"
|
||||
use:tooltip={{
|
||||
props: { label: attribute.label }
|
||||
}}
|
||||
>
|
||||
<Label label={attribute.label} />
|
||||
</span>
|
||||
<div class="text-input" class:context={contextValue}>
|
||||
{#if contextValue}
|
||||
<ContextValue
|
||||
{contextValue}
|
||||
{context}
|
||||
category={presenterClass.category}
|
||||
attrClass={presenterClass.attrClass}
|
||||
on:update={(e) => {
|
||||
onSelect(e.detail)
|
||||
}}
|
||||
/>
|
||||
{:else}
|
||||
<div class="w-full">
|
||||
<svelte:component
|
||||
this={editor}
|
||||
label={attribute?.label}
|
||||
placeholder={attribute?.label}
|
||||
kind={'ghost'}
|
||||
size={'large'}
|
||||
width={'100%'}
|
||||
justify={'left'}
|
||||
type={attribute?.type}
|
||||
{value}
|
||||
{onChange}
|
||||
{focus}
|
||||
/>
|
||||
</div>
|
||||
{/if}
|
||||
<div class="button">
|
||||
<Button
|
||||
icon={IconAdd}
|
||||
kind="ghost"
|
||||
on:click={(e) => {
|
||||
selectContext(e)
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
<style lang="scss">
|
||||
.text-input {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
min-height: 2.5rem;
|
||||
border: 0.0625rem 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>
|
||||
<ProcessAttribute
|
||||
{context}
|
||||
{editor}
|
||||
{attribute}
|
||||
{presenterClass}
|
||||
{value}
|
||||
on:change={(e) => {
|
||||
onChange(e.detail)
|
||||
}}
|
||||
/>
|
||||
|
||||
@@ -13,16 +13,28 @@
|
||||
// limitations under the License.
|
||||
-->
|
||||
<script lang="ts">
|
||||
import { ButtonIcon, IconClose, Label, resizeObserver, Scroller, Submenu } from '@hcengineering/ui'
|
||||
import {
|
||||
ButtonIcon,
|
||||
closeTooltip,
|
||||
eventToHTMLElement,
|
||||
IconClose,
|
||||
IconSettings,
|
||||
Label,
|
||||
resizeObserver,
|
||||
Scroller,
|
||||
showPopup,
|
||||
Submenu
|
||||
} from '@hcengineering/ui'
|
||||
import { createEventDispatcher } from 'svelte'
|
||||
import plugin from '../../plugin'
|
||||
import core, { Class, Doc, Ref } from '@hcengineering/core'
|
||||
import { Context, ProcessFunction, SelectedContext } from '@hcengineering/process'
|
||||
import core, { AnyAttribute, Class, Doc, Ref } from '@hcengineering/core'
|
||||
import { Context, Func, ProcessFunction, SelectedContext } from '@hcengineering/process'
|
||||
import { getClient } from '@hcengineering/presentation'
|
||||
import { AttributeCategory } from '@hcengineering/view'
|
||||
|
||||
export let contextValue: SelectedContext
|
||||
export let context: Context
|
||||
export let attribute: AnyAttribute
|
||||
export let attrClass: Ref<Class<Doc>>
|
||||
export let category: AttributeCategory
|
||||
export let onChange: (contextValue: SelectedContext) => void
|
||||
@@ -58,14 +70,14 @@
|
||||
|
||||
function getAvailableFunctions (
|
||||
context: Context,
|
||||
functions: Ref<ProcessFunction>[] | undefined,
|
||||
functions: Func[] | undefined,
|
||||
attrClass: Ref<Class<Doc>>,
|
||||
category: AttributeCategory
|
||||
): Ref<ProcessFunction>[] {
|
||||
const result: Ref<ProcessFunction>[] = []
|
||||
const allFunctions = client.getModel().findAllSync(plugin.class.ProcessFunction, { of: attrClass, category })
|
||||
for (const f of allFunctions) {
|
||||
if (functions === undefined || functions.findIndex((p) => p === f._id) === -1) {
|
||||
if (functions === undefined || f.allowMany === true || functions.findIndex((p) => p.func === f._id) === -1) {
|
||||
result.push(f._id)
|
||||
}
|
||||
}
|
||||
@@ -74,9 +86,47 @@
|
||||
|
||||
function onFallback (): void {}
|
||||
|
||||
$: funcs = client
|
||||
.getModel()
|
||||
.findAllSync(plugin.class.ProcessFunction, { _id: { $in: contextValue.functions?.map((it) => it.func) } })
|
||||
|
||||
$: sourceFunc =
|
||||
contextValue.sourceFunction !== undefined
|
||||
? client.getModel().findAllSync(plugin.class.ProcessFunction, { _id: contextValue.sourceFunction })[0]
|
||||
: undefined
|
||||
|
||||
$: functionButtonIndex = functionsLength + (sourceFunc !== undefined ? 1 : 0)
|
||||
|
||||
function onFunctionSelect (e: Ref<ProcessFunction>): void {
|
||||
// if editor is undefined
|
||||
const func = client.getModel().findAllSync(plugin.class.ProcessFunction, { _id: e })[0]
|
||||
if (func.editor === undefined) {
|
||||
addFunction(e, {})
|
||||
closeTooltip()
|
||||
} else {
|
||||
showPopup(
|
||||
func.editor,
|
||||
{
|
||||
func,
|
||||
context,
|
||||
attribute
|
||||
},
|
||||
elements[functionButtonIndex],
|
||||
(res) => {
|
||||
if (res != null) {
|
||||
addFunction(e, res)
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
function addFunction (func: Ref<ProcessFunction>, props: Record<string, any>): void {
|
||||
const arr = contextValue.functions ?? []
|
||||
arr.push(e)
|
||||
arr.push({
|
||||
func,
|
||||
props
|
||||
})
|
||||
contextValue.functions = arr
|
||||
onChange(contextValue)
|
||||
}
|
||||
@@ -88,7 +138,7 @@
|
||||
|
||||
function onFunctionChange (e: Ref<ProcessFunction>, i: number): void {
|
||||
if (contextValue.functions === undefined) return
|
||||
contextValue.functions[i] = e
|
||||
contextValue.functions[i].func = e
|
||||
contextValue.functions = contextValue.functions
|
||||
onChange(contextValue)
|
||||
}
|
||||
@@ -99,9 +149,8 @@
|
||||
}
|
||||
}
|
||||
|
||||
function onFunctionRemove (id: Ref<ProcessFunction>): void {
|
||||
function onFunctionRemove (pos: number): void {
|
||||
const arr = contextValue.functions ?? []
|
||||
const pos = arr.findIndex((p) => p === id)
|
||||
if (pos !== -1) {
|
||||
arr.splice(pos, 1)
|
||||
}
|
||||
@@ -109,12 +158,33 @@
|
||||
onChange(contextValue)
|
||||
}
|
||||
|
||||
$: funcs = client.getModel().findAllSync(plugin.class.ProcessFunction, { _id: { $in: contextValue.functions } })
|
||||
|
||||
$: sourceFunc =
|
||||
contextValue.sourceFunction !== undefined
|
||||
? client.getModel().findAllSync(plugin.class.ProcessFunction, { _id: contextValue.sourceFunction })[0]
|
||||
: undefined
|
||||
function onConfigure (e: MouseEvent, func: ProcessFunction, pos: number): void {
|
||||
if (contextValue.functions === undefined || func.editor === undefined) return
|
||||
const val = contextValue.functions[pos]
|
||||
showPopup(
|
||||
func.editor,
|
||||
{
|
||||
func,
|
||||
context,
|
||||
attribute,
|
||||
props: val?.props ?? {}
|
||||
},
|
||||
eventToHTMLElement(e),
|
||||
(res) => {
|
||||
if (res != null) {
|
||||
if (contextValue.functions !== undefined) {
|
||||
const func = contextValue.functions[pos]
|
||||
if (func === undefined) return
|
||||
func.props = res
|
||||
contextValue.functions[pos] = func
|
||||
contextValue.functions = contextValue.functions
|
||||
console.log(contextValue.functions)
|
||||
onChange(contextValue)
|
||||
}
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
</script>
|
||||
|
||||
<div class="selectPopup" use:resizeObserver={() => dispatch('changeContent')}>
|
||||
@@ -175,12 +245,22 @@
|
||||
<Label label={f.label} />
|
||||
</div>
|
||||
<div>
|
||||
{#if f.editor}
|
||||
<ButtonIcon
|
||||
icon={IconSettings}
|
||||
size="small"
|
||||
kind="tertiary"
|
||||
on:click={(e) => {
|
||||
onConfigure(e, f, i)
|
||||
}}
|
||||
/>
|
||||
{/if}
|
||||
<ButtonIcon
|
||||
icon={IconClose}
|
||||
size="small"
|
||||
kind="tertiary"
|
||||
on:click={() => {
|
||||
onFunctionRemove(f._id)
|
||||
onFunctionRemove(i)
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
@@ -189,12 +269,12 @@
|
||||
{/each}
|
||||
{#if availableFunctions.length > 0}
|
||||
<Submenu
|
||||
bind:element={elements[functionsLength + (sourceFunc !== undefined ? 1 : 0)]}
|
||||
bind:element={elements[functionButtonIndex]}
|
||||
on:keydown={(event) => {
|
||||
keyDown(event, functionsLength + (sourceFunc !== undefined ? 1 : 0))
|
||||
keyDown(event, functionButtonIndex)
|
||||
}}
|
||||
on:mouseover={() => {
|
||||
elements[functionsLength + (sourceFunc !== undefined ? 1 : 0)]?.focus()
|
||||
elements[functionButtonIndex]?.focus()
|
||||
}}
|
||||
label={plugin.string.Functions}
|
||||
props={{
|
||||
|
||||
+1
-1
@@ -57,7 +57,7 @@
|
||||
onClick({
|
||||
type: 'attribute',
|
||||
key: val.name,
|
||||
functions: valueFunc !== undefined ? [valueFunc] : []
|
||||
functions: valueFunc !== undefined ? [{ func: valueFunc, props: {} }] : []
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
@@ -16,13 +16,14 @@
|
||||
import { SelectedContext, Context } from '@hcengineering/process'
|
||||
import { eventToHTMLElement, showPopup } from '@hcengineering/ui'
|
||||
import ConfigurePopup from './ConfigurePopup.svelte'
|
||||
import { Ref, Class, Doc } from '@hcengineering/core'
|
||||
import { Ref, Class, Doc, AnyAttribute } from '@hcengineering/core'
|
||||
import ContextValuePresenter from './ContextValuePresenter.svelte'
|
||||
import { AttributeCategory } from '@hcengineering/view'
|
||||
import { createEventDispatcher } from 'svelte'
|
||||
|
||||
export let contextValue: SelectedContext
|
||||
export let context: Context
|
||||
export let attribute: AnyAttribute
|
||||
export let attrClass: Ref<Class<Doc>>
|
||||
export let category: AttributeCategory
|
||||
|
||||
@@ -40,7 +41,11 @@
|
||||
dispatch('update', res)
|
||||
}
|
||||
}
|
||||
showPopup(ConfigurePopup, { contextValue, attrClass, category, context, onChange }, eventToHTMLElement(e))
|
||||
showPopup(
|
||||
ConfigurePopup,
|
||||
{ contextValue, attrClass, category, attribute, context, onChange },
|
||||
eventToHTMLElement(e)
|
||||
)
|
||||
}
|
||||
</script>
|
||||
|
||||
|
||||
+1
-1
@@ -48,7 +48,7 @@
|
||||
type: 'nested',
|
||||
key: attr.name,
|
||||
path: context.attribute.name,
|
||||
functions: valueFunc !== undefined ? [valueFunc] : [],
|
||||
functions: valueFunc !== undefined ? [{ func: valueFunc, props: {} }] : [],
|
||||
sourceFunction: pathReduce
|
||||
})
|
||||
}
|
||||
|
||||
+1
-1
@@ -53,7 +53,7 @@
|
||||
association: context.association,
|
||||
direction: context.direction,
|
||||
name: context.name,
|
||||
functions: valueFunc !== undefined ? [valueFunc] : [],
|
||||
functions: valueFunc !== undefined ? [{ func: valueFunc, props: {} }] : [],
|
||||
sourceFunction: reduceFunc
|
||||
})
|
||||
}
|
||||
|
||||
@@ -0,0 +1,61 @@
|
||||
<!--
|
||||
// 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 } from '@hcengineering/presentation'
|
||||
import { createEventDispatcher } from 'svelte'
|
||||
import process from '../../plugin'
|
||||
import ui, { DropdownIntlItem, DropdownLabelsIntl, NumberInput } from '@hcengineering/ui'
|
||||
|
||||
export const props: Record<string, any> = {}
|
||||
|
||||
let offset = props?.offset
|
||||
let offsetType = props?.offsetType ?? 'days'
|
||||
let direction = props?.direction ?? 'after'
|
||||
|
||||
const items: DropdownIntlItem[] = [
|
||||
{ id: 'days', label: ui.string.DaysWOValue },
|
||||
{ id: 'weeks', label: ui.string.WeeksWOValue },
|
||||
{ id: 'months', label: ui.string.MonthsWOValue }
|
||||
]
|
||||
|
||||
const directions: DropdownIntlItem[] = [
|
||||
{ id: 'after', label: ui.string.After },
|
||||
{ id: 'before', label: ui.string.Before }
|
||||
]
|
||||
|
||||
const dispatch = createEventDispatcher()
|
||||
|
||||
function save (): void {
|
||||
dispatch('close', {
|
||||
offset,
|
||||
offsetType,
|
||||
direction
|
||||
})
|
||||
}
|
||||
|
||||
$: params = {
|
||||
days: offset,
|
||||
weeks: offset,
|
||||
months: offset
|
||||
}
|
||||
</script>
|
||||
|
||||
<Card on:close width={'small'} label={process.string.Offset} canSave okAction={save}>
|
||||
<div class="flex-row-center flex-gap-2">
|
||||
<DropdownLabelsIntl items={directions} bind:selected={direction} />
|
||||
<NumberInput bind:value={offset} autoFocus focusable maxDigitsAfterPoint={0} />
|
||||
<DropdownLabelsIntl {items} bind:selected={offsetType} {params} />
|
||||
</div></Card
|
||||
>
|
||||
@@ -0,0 +1,74 @@
|
||||
<!--
|
||||
// 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 core, { AnyAttribute } from '@hcengineering/core'
|
||||
import { getResource } from '@hcengineering/platform'
|
||||
import presentation, { Card, getClient } from '@hcengineering/presentation'
|
||||
import { Context, ProcessFunction } from '@hcengineering/process'
|
||||
import { AnySvelteComponent } from '@hcengineering/ui'
|
||||
import view from '@hcengineering/view'
|
||||
import { createEventDispatcher } from 'svelte'
|
||||
import ProcessAttribute from '../ProcessAttribute.svelte'
|
||||
|
||||
export let func: ProcessFunction
|
||||
export let context: Context
|
||||
export let attribute: AnyAttribute
|
||||
export let props: Record<string, any> = {}
|
||||
|
||||
let value: number = props?.offset ?? 0
|
||||
|
||||
const dispatch = createEventDispatcher()
|
||||
const client = getClient()
|
||||
const h = client.getHierarchy()
|
||||
|
||||
function save (): void {
|
||||
dispatch('close', { offset: value })
|
||||
}
|
||||
|
||||
let editor: AnySvelteComponent | undefined
|
||||
|
||||
function getEditor (): void {
|
||||
try {
|
||||
const inlineEditor = h.as(h.getClass(core.class.TypeNumber), view.mixin.AttributeEditor).inlineEditor
|
||||
void getResource(inlineEditor)
|
||||
.then((p) => {
|
||||
editor = p
|
||||
})
|
||||
.catch((e) => {
|
||||
console.error(e)
|
||||
})
|
||||
} catch (e) {
|
||||
console.error(e)
|
||||
}
|
||||
}
|
||||
|
||||
getEditor()
|
||||
</script>
|
||||
|
||||
<Card on:close width={'menu'} label={func.label} canSave okAction={save} okLabel={presentation.string.Save}>
|
||||
<ProcessAttribute
|
||||
{context}
|
||||
{attribute}
|
||||
presenterClass={{
|
||||
attrClass: core.class.TypeNumber,
|
||||
category: 'attribute'
|
||||
}}
|
||||
{value}
|
||||
{editor}
|
||||
on:change={(e) => {
|
||||
value = e.detail
|
||||
}}
|
||||
/>
|
||||
</Card>
|
||||
@@ -31,6 +31,8 @@ import FunctionSelector from './components/attributeEditors/FunctionSelector.sve
|
||||
import Main from './components/Main.svelte'
|
||||
import RunProcessCardPopup from './components/RunProcessCardPopup.svelte'
|
||||
import { showDoneQuery } from './utils'
|
||||
import DateOffsetEditor from './components/contextEditors/DateOffsetEditor.svelte'
|
||||
import NumberOffsetEditor from './components/contextEditors/NumberOffsetEditor.svelte'
|
||||
|
||||
export default async (): Promise<Resources> => ({
|
||||
component: {
|
||||
@@ -51,7 +53,9 @@ export default async (): Promise<Resources> => ({
|
||||
RelatedContextSelector,
|
||||
FunctionSelector,
|
||||
Main,
|
||||
RunProcessCardPopup
|
||||
RunProcessCardPopup,
|
||||
DateOffsetEditor,
|
||||
NumberOffsetEditor
|
||||
},
|
||||
function: {
|
||||
ShowDoneQuery: showDoneQuery
|
||||
|
||||
@@ -40,7 +40,9 @@ export default mergeIds(processId, process, {
|
||||
NestedContextSelector: '' as AnyComponent,
|
||||
RelatedContextSelector: '' as AnyComponent,
|
||||
FunctionSelector: '' as AnyComponent,
|
||||
RunProcessCardPopup: '' as AnyComponent
|
||||
RunProcessCardPopup: '' as AnyComponent,
|
||||
DateOffsetEditor: '' as AnyComponent,
|
||||
NumberOffsetEditor: '' as AnyComponent
|
||||
},
|
||||
function: {
|
||||
ShowDoneQuery: '' as ViewQueryAction
|
||||
@@ -76,6 +78,13 @@ export default mergeIds(processId, process, {
|
||||
Random: '' as IntlString,
|
||||
MyProcesses: '' as IntlString,
|
||||
AllProcesses: '' as IntlString,
|
||||
ShowDone: '' as IntlString
|
||||
ShowDone: '' as IntlString,
|
||||
Increment: '' as IntlString,
|
||||
Decrement: '' as IntlString,
|
||||
Add: '' as IntlString,
|
||||
Subtract: '' as IntlString,
|
||||
Offset: '' as IntlString,
|
||||
Value: '' as IntlString,
|
||||
FirstWorkingDayAfter: '' as IntlString
|
||||
}
|
||||
})
|
||||
|
||||
@@ -51,7 +51,7 @@ export function getContext (
|
||||
attr?: Ref<AnyAttribute>
|
||||
): Context {
|
||||
let attributes = getClassAttributes(client, process.masterTag, target, category)
|
||||
if (attr !== undefined) {
|
||||
if (attr !== undefined && category === 'object') {
|
||||
attributes = attributes.filter((it) => it._id !== attr)
|
||||
}
|
||||
const nested: Record<string, NestedContext> = {}
|
||||
|
||||
@@ -72,10 +72,11 @@ export interface Method<T extends Doc> extends Doc {
|
||||
presenter?: AnyComponent
|
||||
}
|
||||
|
||||
// add impl in server
|
||||
export interface ProcessFunction extends Doc {
|
||||
of: Ref<Class<Doc>>
|
||||
category?: AttributeCategory
|
||||
editor?: AnyComponent
|
||||
category: AttributeCategory | undefined
|
||||
allowMany?: boolean
|
||||
label: IntlString
|
||||
}
|
||||
|
||||
@@ -113,6 +114,10 @@ export default plugin(processId, {
|
||||
Random: '' as Ref<ProcessFunction>,
|
||||
UpperCase: '' as Ref<ProcessFunction>,
|
||||
LowerCase: '' as Ref<ProcessFunction>,
|
||||
Trim: '' as Ref<ProcessFunction>
|
||||
Trim: '' as Ref<ProcessFunction>,
|
||||
Add: '' as Ref<ProcessFunction>,
|
||||
Subtract: '' as Ref<ProcessFunction>,
|
||||
Offset: '' as Ref<ProcessFunction>,
|
||||
FirstWorkingDayAfter: '' as Ref<ProcessFunction>
|
||||
}
|
||||
})
|
||||
|
||||
@@ -19,6 +19,11 @@ export interface RelatedContext {
|
||||
attributes: AnyAttribute[]
|
||||
}
|
||||
|
||||
export interface Func {
|
||||
func: Ref<ProcessFunction>
|
||||
props: Record<string, any>
|
||||
}
|
||||
|
||||
interface BaseSelectedContext {
|
||||
type: 'attribute' | 'relation' | 'nested'
|
||||
// attribute key
|
||||
@@ -28,7 +33,7 @@ interface BaseSelectedContext {
|
||||
sourceFunction?: Ref<ProcessFunction>
|
||||
|
||||
// process one by one
|
||||
functions?: Array<Ref<ProcessFunction>>
|
||||
functions?: Func[]
|
||||
|
||||
fallbackValue?: any
|
||||
}
|
||||
|
||||
@@ -5,7 +5,7 @@ export function parseContext (value: any): SelectedContext | undefined {
|
||||
if (typeof value !== 'string') return
|
||||
const index = value.indexOf('${')
|
||||
if (index === -1) return
|
||||
const endIndex = value.indexOf('}')
|
||||
const endIndex = value.lastIndexOf('}')
|
||||
if (endIndex === -1) return
|
||||
const content = value.substring(index + 1, endIndex + 1)
|
||||
try {
|
||||
|
||||
@@ -15,7 +15,7 @@
|
||||
-->
|
||||
<script lang="ts">
|
||||
import type { IntlString } from '@hcengineering/platform'
|
||||
import type { ButtonSize } from '@hcengineering/ui'
|
||||
import type { ButtonKind, ButtonSize } from '@hcengineering/ui'
|
||||
import { EditBox, Label, showPopup, eventToHTMLElement, Button } from '@hcengineering/ui'
|
||||
import EditBoxPopup from './EditBoxPopup.svelte'
|
||||
|
||||
@@ -24,7 +24,7 @@
|
||||
export let autoFocus: boolean = false
|
||||
// export let maxWidth: string = '10rem'
|
||||
export let onChange: (value: number | undefined) => void
|
||||
export let kind: 'no-border' | 'link' | 'button' | 'list' = 'no-border'
|
||||
export let kind: ButtonKind | undefined = undefined
|
||||
export let readonly = false
|
||||
export let size: ButtonSize = 'small'
|
||||
export let justify: 'left' | 'center' = 'center'
|
||||
@@ -40,9 +40,9 @@
|
||||
}
|
||||
</script>
|
||||
|
||||
{#if kind === 'button' || kind === 'link' || kind === 'list'}
|
||||
{#if kind}
|
||||
<Button
|
||||
kind={kind === 'button' ? 'regular' : kind}
|
||||
{kind}
|
||||
{size}
|
||||
{justify}
|
||||
{width}
|
||||
|
||||
@@ -20,7 +20,7 @@
|
||||
export let value: number | undefined
|
||||
export let label: IntlString
|
||||
export let onChange: ((value: number | undefined) => void) | undefined = undefined
|
||||
export let kind: 'no-border' | 'link' | 'button' | 'list' = 'link'
|
||||
export let kind: 'no-border' | 'link' | 'list' = 'link'
|
||||
export let readonly = false
|
||||
</script>
|
||||
|
||||
|
||||
@@ -22,6 +22,7 @@ import core, {
|
||||
Ref,
|
||||
RefTo,
|
||||
SortingOrder,
|
||||
Timestamp,
|
||||
Tx,
|
||||
TxCreateDoc,
|
||||
TxProcessor,
|
||||
@@ -162,19 +163,24 @@ async function executeAction<T extends Doc> (
|
||||
}
|
||||
}
|
||||
|
||||
async function fillValue (value: any, context: SelectedContext, control: TriggerControl): Promise<any> {
|
||||
async function fillValue (
|
||||
value: any,
|
||||
context: SelectedContext,
|
||||
control: TriggerControl,
|
||||
execution: Execution
|
||||
): Promise<any> {
|
||||
if (value === undefined) {
|
||||
// we should add error to execution
|
||||
throw new Error('Value not found')
|
||||
}
|
||||
for (const func of context.functions ?? []) {
|
||||
try {
|
||||
const transform = control.modelDb.findObject(func)
|
||||
const transform = control.modelDb.findObject(func.func)
|
||||
if (transform === undefined) continue
|
||||
if (!control.hierarchy.hasMixin(transform, serverProcess.mixin.FuncImpl)) continue
|
||||
const funcImpl = control.hierarchy.as(transform, serverProcess.mixin.FuncImpl)
|
||||
const f = await getResource(funcImpl.func)
|
||||
value = f(value)
|
||||
value = await f(value, func.props, control, execution)
|
||||
} catch (err: any) {
|
||||
control.ctx.error(err)
|
||||
}
|
||||
@@ -211,7 +217,7 @@ async function getNestedValue (control: TriggerControl, execution: Execution, co
|
||||
if (!control.hierarchy.hasMixin(transform, serverProcess.mixin.FuncImpl)) return
|
||||
const funcImpl = control.hierarchy.as(transform, serverProcess.mixin.FuncImpl)
|
||||
const f = await getResource(funcImpl.func)
|
||||
const reduced = f(target)
|
||||
const reduced = await f(target, {}, control, execution)
|
||||
return getObjectValue(context.key, reduced)
|
||||
}
|
||||
return getObjectValue(context.key, target[0])
|
||||
@@ -238,7 +244,7 @@ async function getRelationValue (
|
||||
if (!control.hierarchy.hasMixin(transform, serverProcess.mixin.FuncImpl)) return
|
||||
const funcImpl = control.hierarchy.as(transform, serverProcess.mixin.FuncImpl)
|
||||
const f = await getResource(funcImpl.func)
|
||||
const reduced = f(target)
|
||||
const reduced = await f(target, {}, control, execution)
|
||||
return getObjectValue(context.key, reduced)
|
||||
}
|
||||
return getObjectValue(context.key, target[0])
|
||||
@@ -252,24 +258,29 @@ async function fillParams<T extends Doc> (
|
||||
const res: MethodParams<T> = {}
|
||||
for (const key in params) {
|
||||
const value = (params as any)[key]
|
||||
const context = parseContext(value)
|
||||
if (context !== undefined) {
|
||||
let value = context.fallbackValue
|
||||
if (context.type === 'attribute') {
|
||||
value = await getAttributeValue(control, execution, context)
|
||||
} else if (context.type === 'relation') {
|
||||
value = await getRelationValue(control, execution, context)
|
||||
} else if (context.type === 'nested') {
|
||||
value = await getNestedValue(control, execution, context)
|
||||
}
|
||||
;(res as any)[key] = await fillValue(value === undefined ? context.fallbackValue : value, context, control)
|
||||
} else {
|
||||
;(res as any)[key] = value
|
||||
}
|
||||
const valueResult = await getContextValue(value, control, execution)
|
||||
;(res as any)[key] = valueResult
|
||||
}
|
||||
return res
|
||||
}
|
||||
|
||||
async function getContextValue (value: any, control: TriggerControl, execution: Execution): Promise<any> {
|
||||
const context = parseContext(value)
|
||||
if (context !== undefined) {
|
||||
let value = context.fallbackValue
|
||||
if (context.type === 'attribute') {
|
||||
value = await getAttributeValue(control, execution, context)
|
||||
} else if (context.type === 'relation') {
|
||||
value = await getRelationValue(control, execution, context)
|
||||
} else if (context.type === 'nested') {
|
||||
value = await getNestedValue(control, execution, context)
|
||||
}
|
||||
return await fillValue(value === undefined ? context.fallbackValue : value, context, control, execution)
|
||||
} else {
|
||||
return value
|
||||
}
|
||||
}
|
||||
|
||||
async function changeState (
|
||||
execution: Execution,
|
||||
state: State,
|
||||
@@ -479,6 +490,69 @@ export function Trim (value: string): string {
|
||||
return value.trim()
|
||||
}
|
||||
|
||||
export async function Add (
|
||||
value: number,
|
||||
props: Record<string, any>,
|
||||
control: TriggerControl,
|
||||
execution: Execution
|
||||
): Promise<number> {
|
||||
const context = parseContext(props.offset)
|
||||
if (context !== undefined) {
|
||||
if (context.type === 'attribute') {
|
||||
const offset = await getAttributeValue(control, execution, context)
|
||||
return value + offset
|
||||
}
|
||||
} else if (typeof value === 'number') {
|
||||
return value + props.offset
|
||||
}
|
||||
return value
|
||||
}
|
||||
|
||||
export async function Subtract (
|
||||
value: number,
|
||||
props: Record<string, any>,
|
||||
control: TriggerControl,
|
||||
execution: Execution
|
||||
): Promise<number> {
|
||||
const context = parseContext(props.offset)
|
||||
if (context !== undefined) {
|
||||
if (context.type === 'attribute') {
|
||||
const offset = await getAttributeValue(control, execution, context)
|
||||
return value - offset
|
||||
}
|
||||
} else if (typeof value === 'number') {
|
||||
return value - props.offset
|
||||
}
|
||||
return value
|
||||
}
|
||||
|
||||
export function Offset (val: Timestamp, props: Record<string, any>): Timestamp {
|
||||
if (typeof val !== 'number') return val
|
||||
const value = new Date(val)
|
||||
const offset = props.offset * (props.direction === 'after' ? 1 : -1)
|
||||
switch (props.offsetType) {
|
||||
case 'days':
|
||||
return value.setDate(value.getDate() + offset)
|
||||
case 'weeks':
|
||||
return value.setDate(value.getDate() + 7 * offset)
|
||||
case 'months':
|
||||
return value.setMonth(value.getMonth() + offset)
|
||||
}
|
||||
return val
|
||||
}
|
||||
|
||||
export function FirstWorkingDayAfter (val: Timestamp): Timestamp {
|
||||
if (typeof val !== 'number') return val
|
||||
const value = new Date(val)
|
||||
const day = value.getUTCDay()
|
||||
if (day === 6 || day === 0) {
|
||||
const date = value.getDate() + (day === 6 ? 2 : 1)
|
||||
const res = value.setDate(date)
|
||||
return res
|
||||
}
|
||||
return val
|
||||
}
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/explicit-function-return-type
|
||||
export default async () => ({
|
||||
func: {
|
||||
@@ -492,7 +566,11 @@ export default async () => ({
|
||||
Random,
|
||||
UpperCase,
|
||||
LowerCase,
|
||||
Trim
|
||||
Trim,
|
||||
Add,
|
||||
Subtract,
|
||||
Offset,
|
||||
FirstWorkingDayAfter
|
||||
},
|
||||
trigger: {
|
||||
OnExecutionCreate,
|
||||
|
||||
@@ -39,7 +39,11 @@ export default plugin(serverProcessId, {
|
||||
Random: '' as Resource<TransformFunc>,
|
||||
UpperCase: '' as Resource<TransformFunc>,
|
||||
LowerCase: '' as Resource<TransformFunc>,
|
||||
Trim: '' as Resource<TransformFunc>
|
||||
Trim: '' as Resource<TransformFunc>,
|
||||
Add: '' as Resource<TransformFunc>,
|
||||
Subtract: '' as Resource<TransformFunc>,
|
||||
Offset: '' as Resource<TransformFunc>,
|
||||
FirstWorkingDayAfter: '' as Resource<TransformFunc>
|
||||
},
|
||||
trigger: {
|
||||
OnExecutionCreate: '' as Resource<TriggerFunc>,
|
||||
|
||||
@@ -13,4 +13,9 @@ export interface ExecuteResult {
|
||||
rollback: Tx[] | undefined
|
||||
}
|
||||
|
||||
export type TransformFunc = (value: any) => any
|
||||
export type TransformFunc = (
|
||||
value: any,
|
||||
props: Record<string, any>,
|
||||
control: TriggerControl,
|
||||
execution: Execution
|
||||
) => Promise<any>
|
||||
|
||||
Reference in New Issue
Block a user