Date process funcs (#8441)

Signed-off-by: Denis Bykhov <bykhov.denis@gmail.com>
This commit is contained in:
Denis Bykhov
2025-04-03 00:22:53 +07:00
committed by GitHub
parent 241d08fd89
commit 073017ae67
48 changed files with 798 additions and 195 deletions
@@ -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={{
@@ -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>
@@ -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
})
}
@@ -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>