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,9 @@
.imageflow-canvas-border-width {
:global{
.semi-slider-handle{
width: 8px;
height: 8px;
margin-top: 12px;
}
}
}

View File

@@ -0,0 +1,61 @@
/*
* 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 { type FC } from 'react';
import classnames from 'classnames';
import { I18n } from '@coze-arch/i18n';
import { Slider } from '@coze-arch/coze-design';
import styles from './border-width.module.less';
interface IProps {
value: number;
onChange: (value: number) => void;
options?: [number, number, number];
min?: number;
max?: number;
}
export const BorderWidth: FC<IProps> = props => {
const { value, onChange, min, max } = props;
return (
<div
className={classnames(
'flex gap-[12px] text-[14px]',
styles['imageflow-canvas-border-width'],
)}
>
<div className="w-full flex items-center gap-[8px]">
<div className="min-w-[42px]">
{I18n.t('imageflow_canvas_stroke_width')}
</div>
<div className="flex-1 min-w-[320px]">
<Slider
min={min}
max={max}
step={1}
showArrow={false}
value={value}
onChange={o => {
onChange(o as number);
}}
/>
</div>
</div>
</div>
);
};

View File

@@ -0,0 +1,13 @@
.color-picker-slider{
:global{
.semi-slider{
padding: 0;
}
.semi-slider-handle{
width: 8px;
height: 8px;
margin-top: 12px;
}
}
}

View File

@@ -0,0 +1,188 @@
/*
* 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 { useCallback, type FC, useMemo } from 'react';
import classnames from 'classnames';
import { I18n } from '@coze-arch/i18n';
import { IconCozCheckMarkFill } from '@coze-arch/coze-design/icons';
import { Input, Slider } from '@coze-arch/coze-design';
import styles from './color-picker.module.less';
interface IProps {
// #ffffffff
value: string | number;
onChange: (value: string | number) => void;
showOpacity?: boolean;
showColor?: boolean;
readonly?: boolean;
}
const colors = [
'#000000',
'#ffffff',
'#C6C6CD',
'#FF441E',
'#3EC254',
'#4D53E8',
'#00B2B2',
'#FF9600',
];
const ColorRect = (props: {
color: string;
size?: number;
onClick?: () => void;
selected?: boolean;
className?: string;
}) => {
const { color, size = 24, onClick, selected, className } = props;
return (
<div
onClick={onClick}
className={`${className} rounded-[4px]`}
style={{ backgroundColor: color, width: size, height: size }}
>
<div
className={classnames([
'relative top-0 left-0',
'flex items-center justify-center',
'rounded-[4px] border border-solid border-stroke',
])}
style={{
width: size,
height: size,
color: color !== '#ffffff' ? '#fff' : '#000',
}}
>
{selected ? <IconCozCheckMarkFill /> : undefined}
</div>
</div>
);
};
const isHexOpacityColor = (value: string): boolean =>
/^#[0-9A-Fa-f]{8}$/.test(value);
const isHexColor = (value: string): boolean => /^#[0-9A-Fa-f]{6}$/.test(value);
const opacity16To255ScaleTo100 = (v: string): number => parseInt(v, 16) / 2.55;
const opacity100ScaleTo255To16 = (v: number): string =>
Math.floor(v * 2.55)
.toString(16)
.padStart(2, '0');
export const ColorPicker: FC<IProps> = props => {
const {
value = '#ffffffff',
onChange,
showOpacity = true,
showColor = true,
readonly = false,
} = props;
const { color, opacity } = useMemo(() => {
if (!showColor) {
return {
opacity: (value as number) * 100,
};
}
return {
color: (value as string).substring(0, 7),
opacity: opacity16To255ScaleTo100((value as string).substring(7, 9)),
};
}, [value, showColor]);
const _onChange = useCallback(
(v: string) => {
if (isHexOpacityColor(v)) {
onChange(v);
}
},
[onChange],
);
return (
<div className="flex flex-col w-full gap-[12px] text-[14px]">
{showColor ? (
<div className="flex items-center w-full gap-[16px]">
<div className="flex items-center flex-1 gap-[12px]">
{colors.map(c => {
const selected =
c.toUpperCase() === (color as string).toUpperCase();
return (
<ColorRect
key={`rect-${c}`}
className={`${readonly ? '' : 'cursor-pointer'}`}
selected={selected}
onClick={() => {
if (readonly) {
return;
}
_onChange(`${c}${opacity100ScaleTo255To16(opacity)}`);
}}
color={c}
/>
);
})}
</div>
<Input
// 因为是不受控模式,当点击色块时,需要重置 input.value。所以这里以 color 为 key
key={`input-${color}`}
disabled={readonly}
prefix={<ColorRect color={color as string} size={16} />}
type="text"
className="w-[110px]"
// 为什么不使用受控模式?使用受控模式,用户输入过程中触发的格式校验处理起来比较麻烦
defaultValue={color}
onChange={v => {
if (isHexColor(v)) {
_onChange(`${v}${opacity100ScaleTo255To16(opacity)}`);
}
}}
/>
</div>
) : undefined}
{showOpacity ? (
<div className="w-full flex items-center gap-[8px]">
<div className="min-w-[80px]">
{I18n.t('imageflow_canvas_transparency')}
</div>
<div
className={classnames(
'flex-1 min-w-[320px]',
styles['color-picker-slider'],
)}
>
<Slider
min={0}
showArrow={false}
max={100}
step={1}
value={opacity}
disabled={readonly}
onChange={o => {
if (!showColor) {
onChange((o as number) / 100);
} else {
_onChange(`${color}${opacity100ScaleTo255To16(o as number)}`);
}
}}
/>
</div>
</div>
) : undefined}
</div>
);
};

View File

@@ -0,0 +1,83 @@
/*
* 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 { type FC, useCallback, useMemo } from 'react';
import { clamp } from 'lodash-es';
import { I18n } from '@coze-arch/i18n';
import { IconCozFontSize } from '@coze-arch/coze-design/icons';
import { Select, type SelectProps, Tooltip } from '@coze-arch/coze-design';
interface IProps extends Omit<SelectProps, 'onChange'> {
value: number;
onChange: (value: number) => void;
min: number;
max: number;
}
export const FontSize: FC<IProps> = props => {
const { onChange, min, max, optionList, value, ...rest } = props;
const _onChange = useCallback(
(v: number) => {
if (isFinite(v)) {
onChange?.(clamp(v, min, max));
}
},
[onChange, min, max],
);
const _optionsList = useMemo(() => {
const _options = [...(optionList ?? [])];
if (!_options.map(o => o.value).includes(value)) {
_options.unshift({
label: `${value}`,
value,
});
}
return _options;
}, [optionList, value]);
return (
<div className="flex gap-[8px] items-center">
{/* <IconCozFontSize className="text-[16px] m-[8px]" /> */}
<Tooltip
content={I18n.t('imageflow_canvas_text_tooltip1')}
mouseEnterDelay={300}
mouseLeaveDelay={300}
>
<Select
{...rest}
prefix={
<IconCozFontSize className="text-[16px] coz-fg-secondary m-[8px]" />
}
/**
* 因为开启了 allowCreate所以 optionList 不会再响应动态变化
* 这里给个 key ,重新渲染 select保证 optionList 符合预期
*/
key={_optionsList.map(d => d.label).join()}
value={value}
optionList={_optionsList}
filter
allowCreate
onChange={v => {
_onChange(v as number);
}}
style={{ width: '98px' }}
/>
</Tooltip>
</div>
);
};

View File

@@ -0,0 +1,49 @@
/*
* 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 { type FC } from 'react';
import { Select } from '@coze-arch/coze-design';
import { Uploader } from './uploader';
import { TextType } from './text-type';
import { TextFamily } from './text-family';
import { TextAlign } from './text-align';
import { SingleSelect } from './single-select';
import { RefSelect } from './ref-select';
import { LineHeight } from './line-height';
import { LabelSelect } from './label-select';
import { InputNumber } from './input-number';
import { FontSize } from './font-size';
import { ColorPicker } from './color-picker';
import { BorderWidth } from './border-width';
export const setters: Record<string, FC<any>> = {
ColorPicker,
TextAlign,
InputNumber,
TextType,
SingleSelect,
BorderWidth,
Select,
TextFamily,
FontSize,
LineHeight,
LabelSelect,
Uploader,
RefSelect,
};

View File

@@ -0,0 +1,53 @@
/*
* 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 { forwardRef } from 'react';
import {
InputNumber as CozeInputNumber,
type InputNumberProps,
} from '@coze-arch/coze-design';
export const InputNumber = forwardRef<InputNumberProps, InputNumberProps>(
props => {
const { onChange, min, max, value, ...rest } = props;
return (
<CozeInputNumber
{...rest}
min={min}
max={max}
value={value}
// InputNumber 长按 + - 时,会一直触发变化。这里有 bug有时定时器清不掉会鬼畜一直增加/减小)。
// 把 pressInterval 设置成 24h ,变相禁用长按增减
pressInterval={1000 * 60 * 60 * 24}
onNumberChange={v => {
if (Number.isFinite(v)) {
if (typeof min === 'number' && (v as number) < min) {
onChange?.(min);
} else if (typeof max === 'number' && (v as number) > max) {
onChange?.(max);
} else {
const _v = Number((v as number).toFixed(1));
if (_v !== value) {
onChange?.(Number((v as number).toFixed(1)));
}
}
}
}}
/>
);
},
);

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 { type FC } from 'react';
import { Select, type SelectProps } from '@coze-arch/coze-design';
type IProps = SelectProps & { label: string };
export const LabelSelect: FC<IProps> = props => {
const { label, ...rest } = props;
return (
<div className="w-full flex gap-[8px] justify-between items-center text-[14px]">
<div className="min-w-[80px]">{label}</div>
<Select {...rest} />
</div>
);
};

View File

@@ -0,0 +1,90 @@
/*
* 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 { type FC, useCallback, useMemo } from 'react';
import { clamp } from 'lodash-es';
import { I18n } from '@coze-arch/i18n';
import { IconCozFontHeight } from '@coze-arch/coze-design/icons';
import { Select, Tooltip, type SelectProps } from '@coze-arch/coze-design';
interface IProps extends Omit<SelectProps, 'onChange'> {
value: number;
onChange: (value: number) => void;
min: number;
max: number;
}
export const LineHeight: FC<IProps> = props => {
const { onChange, min, max, value, optionList, ...rest } = props;
const _onChange = useCallback(
(v: string) => {
const _v = Number(`${v}`.replace('%', ''));
if (isFinite(_v)) {
onChange?.(Number((clamp(_v, min, max) / 100).toFixed(2)));
}
},
[onChange, min, max],
);
const _optionsList = useMemo(() => {
const _options = [...(optionList ?? [])];
if (
!_options
.map(o => o.value)
.includes(
Number((Number(`${value}`.replace('%', '')) * 100).toFixed(0)),
)
) {
_options.unshift({
label: `${Number((value * 100).toFixed(0))}%`,
value: Number((value * 100).toFixed(0)),
});
}
return _options;
}, [optionList, value]);
return (
<div className="flex gap-[8px] items-center">
{/* <IconCozFontHeight className="text-[16px] m-[8px]" /> */}
<Tooltip
content={I18n.t('imageflow_canvas_text_tooltip2')}
mouseEnterDelay={300}
mouseLeaveDelay={300}
>
<Select
prefix={
<IconCozFontHeight className="text-[16px] coz-fg-secondary m-[8px]" />
}
{...rest}
/**
* 因为开启了 allowCreate所以 optionList 不会再响应动态变化
* 这里给个 key ,重新渲染 select保证 optionList 符合预期
*/
key={_optionsList.map(d => d.label).join()}
filter
value={Number((value * 100).toFixed(0))}
allowCreate
onChange={v => {
_onChange(v as string);
}}
optionList={_optionsList}
style={{ width: '104px' }}
/>
</Tooltip>
</div>
);
};

View File

@@ -0,0 +1,9 @@
.ref-select{
// :global(.semi-select-content-wrapper){
// width: 100%;
// }
:global(.semi-select-selection){
@apply !ml-[4px];
}
}

View File

@@ -0,0 +1,135 @@
/*
* 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 { type FC } from 'react';
import cls from 'classnames';
import { ViewVariableType } from '@coze-workflow/base/types';
import { I18n } from '@coze-arch/i18n';
import { IconCozImage, IconCozString } from '@coze-arch/coze-design/icons';
import {
type RenderSelectedItemFn,
Select,
type SelectProps,
Tag,
} from '@coze-arch/coze-design';
import { useGlobalContext } from '../../context';
import s from './ref-select.module.less';
type IProps = SelectProps & {
value: string;
label: string;
labelInside: boolean;
};
export const RefSelect: FC<IProps> = props => {
const { value: objectId, label, labelInside, className } = props;
const { customVariableRefs, variables, updateRefByObjectId } =
useGlobalContext();
const targetRef = customVariableRefs?.find(ref => ref.objectId === objectId);
const value = targetRef?.variableId;
const targetVariable = variables?.find(v => v.id === value);
return (
<div className="w-full text-[14px] flex flex-row gap-[8px] items-center">
{!labelInside && label ? (
<div className="text-[14px] min-w-[80px]">{label}</div>
) : (
<></>
)}
<Select
prefix={
labelInside && label ? (
<div className="text-[14px] pl-[8px] pr-[4px] py-[2px] min-w-[40px]">
{label}
</div>
) : undefined
}
showClear
showTick={false}
placeholder={I18n.t('imageflow_canvas_select_var', {}, '选择变量')}
value={targetRef?.variableId}
className={cls(className, s['ref-select'])}
onChange={d => {
updateRefByObjectId?.({
objectId,
variable: d ? variables?.find(v => v.id === d) : undefined,
});
}}
renderSelectedItem={
((options: { value: string; label: React.ReactNode }) => {
const { value: _value, label: _label } = options;
const variable = variables?.find(v => v.id === _value);
if (variable) {
return (
<Tag color="primary" className="w-full">
<div className="flex flex-row items-center gap-[4px] w-full">
{variable.type === ViewVariableType.String ? (
<IconCozString className="coz-fg-dim" />
) : (
<IconCozImage className="coz-fg-dim" />
)}
<div className="flex-1 overflow-hidden">
<div className="truncate w-full overflow-hidden">
{variable.name}
</div>
</div>
</div>
</Tag>
);
}
return _label;
}) as RenderSelectedItemFn
}
>
{value && !targetVariable ? (
<Select.Option value={value}>
<Tag className="max-w-full m-[8px]" color="yellow">
<div className="truncate overflow-hidden">
{I18n.t('imageflow_canvas_var_delete', {}, '变量被删除')}
</div>
</Tag>
</Select.Option>
) : (
<></>
)}
{variables?.map(v => (
<Select.Option value={v.id}>
<div className="flex flex-row items-center gap-[4px] w-full p-[8px] max-w-[400px]">
{v.type === ViewVariableType.String ? (
<IconCozString className="coz-fg-dim" />
) : (
<IconCozImage className="coz-fg-dim" />
)}
<div className="flex-1 overflow-hidden">
<div className="truncate w-full overflow-hidden">{v.name}</div>
</div>
{v.id === value ? (
<Tag color="primary">
{I18n.t('eval_status_referenced', {}, '已引用')}
</Tag>
) : null}
</div>
</Select.Option>
))}
</Select>
</div>
);
};

View File

@@ -0,0 +1,38 @@
/*
* 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 { forwardRef } from 'react';
import {
SingleSelect as CozeSingleSelect,
type SingleSelectProps,
} from '@coze-arch/coze-design';
export const SingleSelect = forwardRef<SingleSelectProps, SingleSelectProps>(
props => {
// (props, ref) => {
const { onChange, ...rest } = props;
return (
<CozeSingleSelect
{...rest}
// ref={ref}
onChange={v => {
onChange?.(v.target.value);
}}
/>
);
},
);

View File

@@ -0,0 +1,77 @@
/*
* 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 { I18n } from '@coze-arch/i18n';
import {
IconCozTextAlignCenter,
IconCozTextAlignLeft,
IconCozTextAlignRight,
} from '@coze-arch/coze-design/icons';
import { Select, type RenderSelectedItemFn } from '@coze-arch/coze-design';
import { TextAlign as TextAlignEnum } from '../../typings';
interface IProps {
value: TextAlignEnum;
onChange: (value: TextAlignEnum) => void;
}
export const TextAlign: FC<IProps> = props => {
const { value, onChange } = props;
return (
<Select
// borderless
className="border-0 hover:border-0 focus:border-0"
value={value}
onChange={v => {
onChange(v as TextAlignEnum);
}}
optionList={[
{
icon: <IconCozTextAlignLeft className="text-[16px]" />,
label: I18n.t('card_builder_hover_align_left'),
value: TextAlignEnum.LEFT,
},
{
icon: <IconCozTextAlignCenter className="text-[16px]" />,
label: I18n.t('card_builder_hover_align_horizontal'),
value: TextAlignEnum.CENTER,
},
{
icon: <IconCozTextAlignRight className="text-[16px]" />,
label: I18n.t('card_builder_hover_align_right'),
value: TextAlignEnum.RIGHT,
},
].map(d => ({
...d,
label: (
<div className="flex flex-row items-center gap-[4px]">
{d.icon}
{d.label}
</div>
),
}))}
renderSelectedItem={
((option: { icon: React.ReactNode }) => {
const { icon } = option;
return <div className="flex flex-row items-center">{icon}</div>;
}) as RenderSelectedItemFn
}
/>
);
};

View File

@@ -0,0 +1,7 @@
.imageflow-canvas-font-family-cascader {
:global{
.semi-cascader-option-lists{
height: 300px;
}
}
}

View File

@@ -0,0 +1,40 @@
/*
* 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 { type FC } from 'react';
import { Cascader } from '@coze-arch/coze-design';
import s from './text-family.module.less';
interface IProps {
value: string;
onChange: (value: string) => void;
}
export const TextFamily: FC<IProps> = props => {
// (props, ref) => {
const { onChange, value, ...rest } = props;
return (
<Cascader
{...rest}
value={value?.split('-')?.reverse()}
onChange={v => {
onChange?.((v as string[])?.reverse()?.join('-'));
}}
dropdownClassName={s['imageflow-canvas-font-family-cascader']}
/>
);
};

View File

@@ -0,0 +1,68 @@
/*
* 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 { type FC } from 'react';
import { I18n } from '@coze-arch/i18n';
import {
IconCozFixedSize,
IconCozAutoWidth,
} from '@coze-arch/coze-design/icons';
import { Tooltip } from '@coze-arch/coze-design';
import { MyIconButton } from '../icon-button';
import { Mode } from '../../typings';
interface IProps {
value: Mode;
onChange: (value: Mode) => void;
}
export const TextType: FC<IProps> = props => {
const { value, onChange } = props;
return (
<div className="flex gap-[12px]">
<Tooltip
mouseEnterDelay={300}
mouseLeaveDelay={300}
content={I18n.t('imageflow_canvas_text1')}
>
<MyIconButton
inForm
color={value === Mode.INLINE_TEXT ? 'highlight' : 'secondary'}
onClick={() => {
onChange(Mode.INLINE_TEXT);
}}
icon={<IconCozAutoWidth className="text-[16px]" />}
/>
</Tooltip>
<Tooltip
mouseEnterDelay={300}
mouseLeaveDelay={300}
content={I18n.t('imageflow_canvas_text2')}
>
<MyIconButton
inForm
color={value === Mode.BLOCK_TEXT ? 'highlight' : 'secondary'}
onClick={() => {
onChange(Mode.BLOCK_TEXT);
}}
icon={<IconCozFixedSize className="text-[16px]" />}
/>
</Tooltip>
</div>
);
};

View File

@@ -0,0 +1,64 @@
/*
* 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 { type FC } from 'react';
import { I18n } from '@coze-arch/i18n';
import { IconCozLoading, IconCozUpload } from '@coze-arch/coze-design/icons';
import { Button } from '@coze-arch/coze-design';
import { ImageUpload } from '../topbar/image-upload';
interface IProps {
getLabel: (isRefElement: boolean) => string;
onChange: (url: string) => void;
isRefElement: boolean;
}
export const Uploader: FC<IProps> = props => {
const { getLabel, onChange, isRefElement } = props;
return (
<div className="w-full flex gap-[8px] justify-between items-center text-[14px]">
<div className="min-w-[80px]">{getLabel(isRefElement)}</div>
<ImageUpload
disabledTooltip
onChange={onChange}
tooltip={I18n.t('card_builder_image')}
className="flex-1"
>
{({ loading, cancel }) => (
<Button
className="w-full"
color="primary"
onClick={() => {
loading && cancel();
}}
icon={
loading ? (
<IconCozLoading className={'loading coz-fg-dim'} />
) : (
<IconCozUpload />
)
}
>
{loading
? I18n.t('imageflow_canvas_cancel_change', {}, '取消上传')
: I18n.t('imageflow_canvas_change_img', {}, '更换图片')}
</Button>
)}
</ImageUpload>
</div>
);
};