fix(web): VariableSelect & editor
This commit is contained in:
@@ -2,7 +2,7 @@
|
||||
* @Author: ZhaoYing
|
||||
* @Date: 2025-12-23 16:22:51
|
||||
* @Last Modified by: ZhaoYing
|
||||
* @Last Modified time: 2026-03-25 10:58:47
|
||||
* @Last Modified time: 2026-04-03 20:44:16
|
||||
*/
|
||||
import { type FC, useState, useMemo } from 'react';
|
||||
import { LexicalComposer } from '@lexical/react/LexicalComposer';
|
||||
@@ -147,7 +147,7 @@ const Editor: FC<LexicalEditorProps> =({
|
||||
<HistoryPlugin />
|
||||
<CommandPlugin />
|
||||
<AutocompletePlugin options={options} enableJinja2={false} />
|
||||
<CharacterCountPlugin setCount={(count) => { setCount(count) }} onChange={onChange} />
|
||||
<CharacterCountPlugin setCount={setCount} onChange={onChange} />
|
||||
<InitialValuePlugin value={value} options={options} enableLineNumbers={false} />
|
||||
<BlurPlugin enableJinja2={false} />
|
||||
</div>
|
||||
|
||||
@@ -32,8 +32,7 @@ const VariableComponent: React.FC<{ nodeKey: NodeKey; data: Suggestion }> = ({
|
||||
e.stopPropagation();
|
||||
setSelected(!isSelected);
|
||||
};
|
||||
|
||||
console.log('data', data)
|
||||
|
||||
return (
|
||||
<span
|
||||
onClick={handleClick}
|
||||
|
||||
@@ -6,15 +6,12 @@ import { $isVariableNode } from '../nodes/VariableNode';
|
||||
|
||||
const CharacterCountPlugin = ({ setCount, onChange }: { setCount: (count: number) => void; onChange?: (value: string) => void }) => {
|
||||
const [editor] = useLexicalComposerContext();
|
||||
const isReadyRef = useRef(false);
|
||||
const onChangeRef = useRef(onChange);
|
||||
onChangeRef.current = onChange;
|
||||
|
||||
useEffect(() => {
|
||||
return editor.registerUpdateListener(({ editorState, tags }) => {
|
||||
if (tags.has('programmatic')) {
|
||||
isReadyRef.current = true;
|
||||
return;
|
||||
}
|
||||
if (!isReadyRef.current) return;
|
||||
if (tags.has('programmatic')) return;
|
||||
editorState.read(() => {
|
||||
const root = $getRoot();
|
||||
let serializedContent = '';
|
||||
@@ -38,10 +35,10 @@ const CharacterCountPlugin = ({ setCount, onChange }: { setCount: (count: number
|
||||
serializedContent = paragraphs.join('\n');
|
||||
|
||||
setCount(serializedContent.length);
|
||||
onChange?.(serializedContent);
|
||||
onChangeRef.current?.(serializedContent);
|
||||
});
|
||||
});
|
||||
}, [editor, setCount, onChange]);
|
||||
}, [editor, setCount]);
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -67,11 +67,10 @@ const FilterConditions: FC<FilterConditionsProps> = ({
|
||||
const form = Form.useFormInstance();
|
||||
|
||||
const handleKeyFieldChange = (index: number, newValue: string) => {
|
||||
form.setFieldValue(['filter_by', index], {
|
||||
form.setFieldValue([parentName, 'conditions', index], {
|
||||
key: newValue,
|
||||
comparison_operator: undefined,
|
||||
value: undefined,
|
||||
value_type: undefined,
|
||||
});
|
||||
};
|
||||
|
||||
@@ -85,8 +84,8 @@ const FilterConditions: FC<FilterConditionsProps> = ({
|
||||
className="rb:relative"
|
||||
>
|
||||
{fields.map((field, index) => {
|
||||
const filter_by = form.getFieldValue(['filter_by']) || [];
|
||||
const currentCondition = filter_by[index] || {};
|
||||
const conditions = form.getFieldValue([parentName, 'conditions']) || [];
|
||||
const currentCondition = conditions[index] || {};
|
||||
const currentOperator = currentCondition.comparison_operator;
|
||||
const hideValueField = currentOperator === 'empty' || currentOperator === 'not_empty';
|
||||
const keyFieldValue = currentCondition.key;
|
||||
@@ -103,9 +102,7 @@ const FilterConditions: FC<FilterConditionsProps> = ({
|
||||
>
|
||||
<div className="rb:flex-1 rb:bg-[#F6F6F6] rb:rounded-lg">
|
||||
{variableType === 'array[file]' &&
|
||||
<Row className={clsx("rb:p-1!", {
|
||||
'rb-border-b': !hideValueField
|
||||
})}>
|
||||
<Row className="rb:p-1! rb-border-b">
|
||||
<Col span={24}>
|
||||
<Form.Item name={[field.name, 'key']} noStyle>
|
||||
<Select
|
||||
@@ -121,7 +118,7 @@ const FilterConditions: FC<FilterConditionsProps> = ({
|
||||
</Row>
|
||||
}
|
||||
<Row>
|
||||
<Col flex="96px">
|
||||
<Col flex={hideValueField ? '1' : "96px"}>
|
||||
<Form.Item name={[field.name, 'comparison_operator']} noStyle>
|
||||
<Select
|
||||
options={operatorList.map(vo => ({
|
||||
|
||||
@@ -20,12 +20,12 @@ const ListOperator: FC<ListOperatorProps> = ({ options }) => {
|
||||
const { t } = useTranslation()
|
||||
const form = Form.useFormInstance()
|
||||
const values = Form.useWatch([], form) || {}
|
||||
const variableOption = options.find(option => `{{${option.value}}}` === values?.variable)
|
||||
const variableOption = options.find(option => `{{${option.value}}}` === values?.input_list)
|
||||
const variableType = variableOption?.dataType
|
||||
|
||||
return (
|
||||
<>
|
||||
<Form.Item name="variable" label={t('workflow.config.list-operator.variable')} required>
|
||||
<Form.Item name="input_list" label={t('workflow.config.list-operator.variable')} required>
|
||||
<VariableSelect
|
||||
placeholder={t('common.pleaseSelect')}
|
||||
options={options.filter(vo => vo.dataType.includes('array') && vo.dataType !== 'array[object]')}
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
* @Author: ZhaoYing
|
||||
* @Date: 2026-02-03 15:40:13
|
||||
* @Last Modified by: ZhaoYing
|
||||
* @Last Modified time: 2026-04-03 18:51:17
|
||||
* @Last Modified time: 2026-04-03 20:19:34
|
||||
*/
|
||||
import { useState, useRef, useEffect, useLayoutEffect, type FC } from 'react'
|
||||
import { createPortal } from 'react-dom'
|
||||
@@ -49,36 +49,26 @@ const VariableSelect: FC<VariableSelectProps> = ({
|
||||
|
||||
const CHILD_PANEL_HEIGHT = 280; // max-h-60 (240) + header (~40)
|
||||
|
||||
// Calculate dropdown position when opening
|
||||
useEffect(() => {
|
||||
if (!open || !containerRef.current) return;
|
||||
const rect = containerRef.current.getBoundingClientRect();
|
||||
setDropdownPos({ top: rect.bottom + 8, left: rect.left, width: rect.width });
|
||||
}, [open]);
|
||||
|
||||
// Adjust dropdown vertical position after render
|
||||
// Calculate dropdown position (runs synchronously after DOM paint to avoid flicker)
|
||||
useLayoutEffect(() => {
|
||||
if (!open || !dropdownRef.current || !containerRef.current) return;
|
||||
if (!open || !containerRef.current) return;
|
||||
const triggerRect = containerRef.current.getBoundingClientRect();
|
||||
const MARGIN = 8;
|
||||
const width = triggerRect.width;
|
||||
// Set initial width/left immediately; top will be refined once dropdownRef is available
|
||||
if (!dropdownRef.current) {
|
||||
setDropdownPos({ top: triggerRect.bottom + MARGIN, left: triggerRect.left, width });
|
||||
return;
|
||||
}
|
||||
const dropdownHeight = dropdownRef.current.offsetHeight;
|
||||
const dropdownWidth = dropdownRef.current.offsetWidth;
|
||||
const viewportHeight = window.innerHeight;
|
||||
const MARGIN = 8;
|
||||
|
||||
// Horizontal: left-align to trigger, clamp to viewport
|
||||
const left = Math.min(triggerRect.left, window.innerWidth - dropdownWidth - 10);
|
||||
|
||||
const spaceBelow = viewportHeight - triggerRect.bottom - MARGIN;
|
||||
const spaceBelow = window.innerHeight - triggerRect.bottom - MARGIN;
|
||||
const spaceAbove = triggerRect.top - MARGIN;
|
||||
|
||||
let finalTop: number;
|
||||
if (spaceBelow >= dropdownHeight || spaceBelow >= spaceAbove) {
|
||||
finalTop = triggerRect.bottom + MARGIN;
|
||||
} else {
|
||||
finalTop = triggerRect.top - dropdownHeight - MARGIN;
|
||||
if (finalTop < MARGIN) finalTop = MARGIN;
|
||||
}
|
||||
setDropdownPos(prev => ({ ...prev, top: finalTop, left }));
|
||||
const top = (spaceBelow >= dropdownHeight || spaceBelow >= spaceAbove)
|
||||
? triggerRect.bottom + MARGIN
|
||||
: Math.max(MARGIN, triggerRect.top - dropdownHeight - MARGIN);
|
||||
setDropdownPos({ top, left, width });
|
||||
}, [open, search, Array.isArray(value) ? value.length : 0]);
|
||||
|
||||
const filteredOptions = filterBooleanType
|
||||
@@ -182,9 +172,10 @@ const VariableSelect: FC<VariableSelectProps> = ({
|
||||
const el = itemRefs.current.get(key);
|
||||
if (el) {
|
||||
const rect = el.getBoundingClientRect();
|
||||
const absoluteBottom = rect.top + CHILD_PANEL_HEIGHT;
|
||||
const overflow = absoluteBottom - (window.innerHeight - 10);
|
||||
const top = overflow > 0 ? rect.top - overflow : rect.top;
|
||||
const spaceBelow = window.innerHeight - rect.top - 10;
|
||||
const top = spaceBelow >= CHILD_PANEL_HEIGHT
|
||||
? rect.top
|
||||
: Math.max(10, window.innerHeight - CHILD_PANEL_HEIGHT - 10);
|
||||
setChildPanelPos({ top, right: window.innerWidth - rect.left + 8 });
|
||||
}
|
||||
};
|
||||
@@ -203,7 +194,7 @@ const VariableSelect: FC<VariableSelectProps> = ({
|
||||
variant === 'outlined' && 'rb:border rb:border-[#d9d9d9] hover:rb:border-[#4096ff]',
|
||||
variant === 'outlined' && open && 'rb:border-[#4096ff] rb:shadow-[0_0_0_2px_rgba(5,145,255,0.1)]',
|
||||
variant === 'borderless' && 'rb:border-none rb:shadow-none rb:bg-transparent',
|
||||
multiple ? 'rb:min-h-8 rb:py-1' : size === 'small' ? 'rb:h-6 rb:text-[10px]' : size === 'large' ? 'rb:h-10' : 'rb:h-8 rb:text-[12px]',
|
||||
multiple && size === 'small' ? 'rb:min-h-6 rb:py-0.75' : multiple ? 'rb:min-h-8 rb:py-1' : size === 'small' ? 'rb:h-6 rb:text-[10px]' : size === 'large' ? 'rb:h-10' : 'rb:h-8 rb:text-[12px]',
|
||||
!multiple && (size === 'small' ? 'rb:text-[10px]' : 'rb:text-[12px]'),
|
||||
className
|
||||
)}
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
* @Author: ZhaoYing
|
||||
* @Date: 2026-02-03 15:06:18
|
||||
* @Last Modified by: ZhaoYing
|
||||
* @Last Modified time: 2026-03-31 10:08:26
|
||||
* @Last Modified time: 2026-04-03 20:28:08
|
||||
*/
|
||||
import LoopNode from './components/Nodes/LoopNode';
|
||||
import NormalNode from './components/Nodes/NormalNode';
|
||||
@@ -463,7 +463,7 @@ export const nodeLibrary: NodeLibrary[] = [
|
||||
},
|
||||
{ type: "list-operator", icon: 'rb:bg-[url("@/assets/images/workflow/list-operator.svg")]',
|
||||
config: {
|
||||
variable: {
|
||||
input_list: {
|
||||
type: 'variableList',
|
||||
},
|
||||
filter_by: {
|
||||
|
||||
Reference in New Issue
Block a user