feat: manually mirror opencoze's code from bytedance

Change-Id: I09a73aadda978ad9511264a756b2ce51f5761adf
This commit is contained in:
fanlv
2025-07-20 17:36:12 +08:00
commit 890153324f
14811 changed files with 1923430 additions and 0 deletions

View File

@@ -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);

View File

@@ -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;
}
}
}
}

View 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>
);
};

View File

@@ -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;
}

View File

@@ -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>
);

View 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>
);
},
};

View 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';

View 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;
}