feat: manually mirror opencoze's code from bytedance
Change-Id: I09a73aadda978ad9511264a756b2ce51f5761adf
This commit is contained in:
@@ -0,0 +1,30 @@
|
||||
/*
|
||||
* Copyright 2025 coze-dev Authors
|
||||
*
|
||||
* Licensed under the Apache 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
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-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 { createContext, useContext } from 'react';
|
||||
|
||||
interface ArraySetterContext {
|
||||
currentAddIndex?: number;
|
||||
currentIndex?: number;
|
||||
}
|
||||
|
||||
const arraySetterItemContext = createContext<ArraySetterContext>({});
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/naming-convention
|
||||
export const ArraySetterItemContextProvider = arraySetterItemContext.Provider;
|
||||
|
||||
export const useArraySetterItemContext = () =>
|
||||
useContext(arraySetterItemContext);
|
||||
@@ -0,0 +1,43 @@
|
||||
.array {
|
||||
position: relative;
|
||||
|
||||
.add-button {
|
||||
margin-top: 8px;
|
||||
}
|
||||
|
||||
.content {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.array-item {
|
||||
display: flex;
|
||||
|
||||
.child {
|
||||
display: flex;
|
||||
flex: 1;
|
||||
gap: 4px;
|
||||
align-items: start;
|
||||
|
||||
min-width: 0;
|
||||
|
||||
>* {
|
||||
&:last-child {
|
||||
flex: 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.minus {
|
||||
position: relative;
|
||||
top: 5px;
|
||||
padding: 4px;
|
||||
color: var(--light-usage-text-color-text-3, rgba(28, 29, 35, 35%));
|
||||
|
||||
&:hover {
|
||||
cursor: pointer;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
140
frontend/packages/workflow/setters/src/array/array.tsx
Normal file
140
frontend/packages/workflow/setters/src/array/array.tsx
Normal file
@@ -0,0 +1,140 @@
|
||||
/*
|
||||
* Copyright 2025 coze-dev Authors
|
||||
*
|
||||
* Licensed under the Apache 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
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-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.
|
||||
*/
|
||||
|
||||
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||
import React, { useState } from 'react';
|
||||
|
||||
import { IconCozPlus, IconCozMinus } from '@coze-arch/coze-design/icons';
|
||||
import { IconButton } from '@coze-arch/coze-design';
|
||||
|
||||
import type { Setter } from '../types';
|
||||
import type { Field } from './types';
|
||||
import { ColumnTitles } from './column-titles';
|
||||
import { ArraySetterItemContextProvider } from './array-context';
|
||||
|
||||
import styles from './array.module.less';
|
||||
|
||||
export interface ArrayOptions {
|
||||
disableAdd?: boolean;
|
||||
getDefaultAppendValue?: () => any;
|
||||
fields?: Field[];
|
||||
|
||||
/** 入参最大数量,若没有提供,默认为整数最大值 */
|
||||
maxItems?: number;
|
||||
|
||||
/** 入参最小数量,若没有提供,默认为 0 */
|
||||
minItems?: number;
|
||||
|
||||
/** 单条是否可删除 */
|
||||
disableDeleteItem?: ((value: unknown, index: number) => boolean) | boolean;
|
||||
}
|
||||
|
||||
// eslint-disable-next-line complexity
|
||||
export const Array: Setter<Array<any>, ArrayOptions> = ({
|
||||
value = [],
|
||||
readonly = false,
|
||||
children,
|
||||
onChange,
|
||||
context,
|
||||
disableAdd = false,
|
||||
getDefaultAppendValue,
|
||||
fields = [],
|
||||
maxItems = Number.MAX_SAFE_INTEGER,
|
||||
minItems = 0,
|
||||
disableDeleteItem = () => false,
|
||||
}) => {
|
||||
const [currentAddIndex, setCurrentAddIndex] = useState<number | undefined>();
|
||||
const { node, meta } = context || {};
|
||||
|
||||
// 后端返回的 value 可能为 null,此时不会赋值给 [],这里重新兜底下
|
||||
const originValue = value || [];
|
||||
|
||||
const add = () => {
|
||||
const defaultValue = getDefaultAppendValue?.() || {};
|
||||
setCurrentAddIndex(originValue.length);
|
||||
onChange?.([...originValue, defaultValue]);
|
||||
};
|
||||
|
||||
const remove = (index: number) => {
|
||||
const newValue = [...originValue];
|
||||
newValue.splice(index, 1);
|
||||
onChange?.(newValue);
|
||||
};
|
||||
|
||||
const showAddButton =
|
||||
!disableAdd && !readonly && originValue?.length < maxItems;
|
||||
|
||||
const calcShowDeleteButton = (item: unknown, index: number) => {
|
||||
const globalEnableDelete = !readonly && originValue?.length > minItems;
|
||||
|
||||
if (typeof disableDeleteItem === 'undefined') {
|
||||
return globalEnableDelete;
|
||||
}
|
||||
|
||||
if (typeof disableDeleteItem === 'boolean') {
|
||||
return globalEnableDelete && !disableDeleteItem;
|
||||
}
|
||||
return globalEnableDelete && !disableDeleteItem(item, index);
|
||||
};
|
||||
|
||||
const columns = [...fields, ...(readonly ? [] : [{ label: '', width: 24 }])];
|
||||
|
||||
return (
|
||||
<div className={styles.array}>
|
||||
<div className={styles.content}>
|
||||
{fields.length > 0 && <ColumnTitles columns={columns} />}
|
||||
{React.Children.toArray(children).map((child, index) => {
|
||||
const showDeleteButton = calcShowDeleteButton(
|
||||
originValue[index],
|
||||
index,
|
||||
);
|
||||
return (
|
||||
<ArraySetterItemContextProvider
|
||||
value={{
|
||||
currentAddIndex,
|
||||
currentIndex: index,
|
||||
}}
|
||||
>
|
||||
<div className={styles['array-item']}>
|
||||
<div className={styles.child}>{child}</div>
|
||||
{showDeleteButton ? (
|
||||
<IconButton
|
||||
className="!block ml-1"
|
||||
icon={<IconCozMinus className="text-sm" />}
|
||||
size="small"
|
||||
color="secondary"
|
||||
onClick={() => remove(index)}
|
||||
/>
|
||||
) : null}
|
||||
</div>
|
||||
</ArraySetterItemContextProvider>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
|
||||
{showAddButton ? (
|
||||
<IconButton
|
||||
color="highlight"
|
||||
size="small"
|
||||
className="absolute -top-8 right-0"
|
||||
icon={<IconCozPlus />}
|
||||
onClick={() => add()}
|
||||
data-testid={`playground.node.${node?.id}.${meta?.name}.addbutton`}
|
||||
/>
|
||||
) : null}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,12 @@
|
||||
.column-titles {
|
||||
display: flex;
|
||||
gap: 4px;
|
||||
align-items: center;
|
||||
|
||||
font-size: 12px;
|
||||
font-weight: 400;
|
||||
font-style: normal;
|
||||
line-height: 16px;
|
||||
color: var(--light-usage-text-color-text-3, rgb(28 29 35 / 35%));
|
||||
letter-spacing: 0.12px;
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
/*
|
||||
* Copyright 2025 coze-dev Authors
|
||||
*
|
||||
* Licensed under the Apache 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
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-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 React, { type FC } from 'react';
|
||||
|
||||
import styles from './column-titles.module.less';
|
||||
|
||||
interface Column {
|
||||
label: string;
|
||||
width?: number;
|
||||
required?: boolean;
|
||||
style?: React.CSSProperties;
|
||||
}
|
||||
|
||||
interface ColumnTitlesProps {
|
||||
columns: Column[];
|
||||
}
|
||||
|
||||
export const ColumnTitles: FC<ColumnTitlesProps> = ({ columns }) => (
|
||||
<div className={styles['column-titles']}>
|
||||
{columns.map(({ label, width, required = false, style }, index) => (
|
||||
<div
|
||||
key={index}
|
||||
className={styles['column-title']}
|
||||
style={{ width: width ? `${width}px` : 'auto', ...(style || {}) }}
|
||||
>
|
||||
{label}
|
||||
{required ? (
|
||||
<span style={{ color: '#f93920', paddingLeft: 2 }}>*</span>
|
||||
) : null}
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
140
frontend/packages/workflow/setters/src/array/index.stories.tsx
Normal file
140
frontend/packages/workflow/setters/src/array/index.stories.tsx
Normal file
@@ -0,0 +1,140 @@
|
||||
/*
|
||||
* Copyright 2025 coze-dev Authors
|
||||
*
|
||||
* Licensed under the Apache 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
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-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.
|
||||
*/
|
||||
|
||||
/* eslint-disable @typescript-eslint/no-empty-function */
|
||||
import type { StoryObj, Meta } from '@storybook/react';
|
||||
import { useArgs } from '@storybook/preview-api';
|
||||
|
||||
import { String } from '../string';
|
||||
import { Number } from '../number';
|
||||
import { Enum } from '../enum';
|
||||
import { Array } from './array';
|
||||
|
||||
const meta: Meta<typeof Array> = {
|
||||
title: 'workflow setters/Array',
|
||||
component: Array,
|
||||
tags: ['autodocs'],
|
||||
parameters: {
|
||||
layout: 'centered',
|
||||
},
|
||||
render: args => {
|
||||
// eslint-disable-next-line react-hooks/rules-of-hooks -- linter-disable-autofix
|
||||
const [, updateArgs] = useArgs();
|
||||
const { value = [] } = args;
|
||||
|
||||
const handleItemChange = (newItemValue: number, index: number) => {
|
||||
const newValue = [...(args.value || [])];
|
||||
newValue[index] = newItemValue;
|
||||
updateArgs({ ...args, value: newValue });
|
||||
};
|
||||
|
||||
return (
|
||||
<Array
|
||||
{...args}
|
||||
onChange={newValue => {
|
||||
updateArgs({ ...args, value: newValue });
|
||||
}}
|
||||
>
|
||||
{value?.map((itemValue: number, index) => (
|
||||
<Enum
|
||||
value={itemValue}
|
||||
options={[
|
||||
{
|
||||
label: '知识1',
|
||||
value: 1,
|
||||
},
|
||||
{
|
||||
label: '知识2',
|
||||
value: 2,
|
||||
},
|
||||
]}
|
||||
onChange={newValue => handleItemChange(newValue as number, index)}
|
||||
/>
|
||||
))}
|
||||
</Array>
|
||||
);
|
||||
},
|
||||
args: {
|
||||
// eslint-disable-next-line @typescript-eslint/no-magic-numbers
|
||||
value: [1, 2],
|
||||
},
|
||||
};
|
||||
|
||||
export default meta;
|
||||
|
||||
type Story = StoryObj<typeof Array>;
|
||||
|
||||
export const Base: Story = {};
|
||||
|
||||
export const DisableAdd: Story = {
|
||||
args: {
|
||||
disableAdd: true,
|
||||
},
|
||||
};
|
||||
|
||||
export const Readonly: Story = {
|
||||
args: {
|
||||
readonly: true,
|
||||
},
|
||||
};
|
||||
|
||||
interface WithFieldsValueItem {
|
||||
paramName?: string;
|
||||
paramValue?: number;
|
||||
}
|
||||
|
||||
export const WithFields: Story = {
|
||||
args: {
|
||||
value: [
|
||||
{ paramName: 'key1', paramValue: 100 },
|
||||
{ paramName: 'key2', paramValue: 200 },
|
||||
],
|
||||
fields: [
|
||||
{
|
||||
label: '参数名',
|
||||
width: 160,
|
||||
},
|
||||
{
|
||||
label: '参数值',
|
||||
},
|
||||
],
|
||||
},
|
||||
render: args => {
|
||||
// eslint-disable-next-line react-hooks/rules-of-hooks -- linter-disable-autofix
|
||||
const [, updateArgs] = useArgs();
|
||||
const { value } = args;
|
||||
|
||||
return (
|
||||
<Array
|
||||
{...args}
|
||||
onChange={newValue => {
|
||||
updateArgs({ ...args, value: newValue });
|
||||
}}
|
||||
>
|
||||
{value?.map((itemValue: WithFieldsValueItem) => (
|
||||
<>
|
||||
<String
|
||||
value={itemValue?.paramName}
|
||||
width={160}
|
||||
onChange={v => {}}
|
||||
/>
|
||||
<Number value={itemValue?.paramValue} onChange={() => {}} />
|
||||
</>
|
||||
))}
|
||||
</Array>
|
||||
);
|
||||
},
|
||||
};
|
||||
18
frontend/packages/workflow/setters/src/array/index.ts
Normal file
18
frontend/packages/workflow/setters/src/array/index.ts
Normal file
@@ -0,0 +1,18 @@
|
||||
/*
|
||||
* Copyright 2025 coze-dev Authors
|
||||
*
|
||||
* Licensed under the Apache 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
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-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.
|
||||
*/
|
||||
|
||||
export { Array } from './array';
|
||||
export type { ArrayOptions } from './array';
|
||||
21
frontend/packages/workflow/setters/src/array/types.ts
Normal file
21
frontend/packages/workflow/setters/src/array/types.ts
Normal file
@@ -0,0 +1,21 @@
|
||||
/*
|
||||
* Copyright 2025 coze-dev Authors
|
||||
*
|
||||
* Licensed under the Apache 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
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-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.
|
||||
*/
|
||||
|
||||
export interface Field {
|
||||
label: string;
|
||||
required?: boolean;
|
||||
width?: number;
|
||||
}
|
||||
Reference in New Issue
Block a user