|
|
|
|
@ -1,8 +1,9 @@
|
|
|
|
|
import React from 'react';
|
|
|
|
|
import React, { useEffect, useState } from 'react';
|
|
|
|
|
import styles from '@/components/FlowEditor/node/style/baseOther.module.less';
|
|
|
|
|
import { Handle, Position, useStore } from '@xyflow/react';
|
|
|
|
|
import { Handle, Position } from '@xyflow/react';
|
|
|
|
|
import { Image } from '@arco-design/web-react';
|
|
|
|
|
import { formatDataType } from '@/utils/common';
|
|
|
|
|
import UTIF from 'utif';
|
|
|
|
|
|
|
|
|
|
interface NodeContentData {
|
|
|
|
|
parameters?: {
|
|
|
|
|
@ -103,7 +104,64 @@ const renderRegularNodeHandles = (dataIns: any[], dataOuts: any[], apiIns: any[]
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
const NodeContent = ({ data }: { data: NodeContentData }) => {
|
|
|
|
|
const getImageBlobType = (url: string, blobType: string) => {
|
|
|
|
|
if (blobType && blobType !== 'application/octet-stream') {
|
|
|
|
|
return blobType;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const cleanUrl = url.split('?')[0].toLowerCase();
|
|
|
|
|
if (cleanUrl.endsWith('.png')) return 'image/png';
|
|
|
|
|
if (cleanUrl.endsWith('.jpg') || cleanUrl.endsWith('.jpeg')) return 'image/jpeg';
|
|
|
|
|
if (cleanUrl.endsWith('.gif')) return 'image/gif';
|
|
|
|
|
if (cleanUrl.endsWith('.webp')) return 'image/webp';
|
|
|
|
|
if (cleanUrl.endsWith('.bmp')) return 'image/bmp';
|
|
|
|
|
if (cleanUrl.endsWith('.svg')) return 'image/svg+xml';
|
|
|
|
|
if (cleanUrl.endsWith('.tif') || cleanUrl.endsWith('.tiff')) return 'image/tiff';
|
|
|
|
|
|
|
|
|
|
return blobType || 'application/octet-stream';
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const isTiffImage = (url: string, blobType: string) => {
|
|
|
|
|
const imageType = getImageBlobType(url, blobType).toLowerCase();
|
|
|
|
|
const cleanUrl = url.split('?')[0].toLowerCase();
|
|
|
|
|
|
|
|
|
|
return (
|
|
|
|
|
imageType === 'image/tiff' ||
|
|
|
|
|
cleanUrl.endsWith('.tif') ||
|
|
|
|
|
cleanUrl.endsWith('.tiff')
|
|
|
|
|
);
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const createTiffPreviewUrl = async (url: string, blob: Blob) => {
|
|
|
|
|
const buffer = await blob.arrayBuffer();
|
|
|
|
|
const ifds = UTIF.decode(buffer);
|
|
|
|
|
const firstImage = ifds[0];
|
|
|
|
|
|
|
|
|
|
if (!firstImage) {
|
|
|
|
|
throw new Error('empty tiff image');
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
UTIF.decodeImage(buffer, firstImage);
|
|
|
|
|
const rgba = UTIF.toRGBA8(firstImage);
|
|
|
|
|
const canvas = document.createElement('canvas');
|
|
|
|
|
canvas.width = firstImage.width;
|
|
|
|
|
canvas.height = firstImage.height;
|
|
|
|
|
|
|
|
|
|
const context = canvas.getContext('2d');
|
|
|
|
|
if (!context) {
|
|
|
|
|
throw new Error('canvas context unavailable');
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const imageData = context.createImageData(firstImage.width, firstImage.height);
|
|
|
|
|
imageData.data.set(rgba);
|
|
|
|
|
context.putImageData(imageData, 0, 0);
|
|
|
|
|
|
|
|
|
|
return canvas.toDataURL('image/png');
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const NodeContent = ({ data, imageUrl = '' }: { data: NodeContentData; imageUrl?: string }) => {
|
|
|
|
|
const [previewUrl, setPreviewUrl] = useState('');
|
|
|
|
|
const [imageLoadError, setImageLoadError] = useState(false);
|
|
|
|
|
const apiIns = data.parameters?.apiIns || [];
|
|
|
|
|
const apiOuts = data.parameters?.apiOuts || [];
|
|
|
|
|
const dataIns = data.parameters?.dataIns || [];
|
|
|
|
|
@ -114,6 +172,60 @@ const NodeContent = ({ data }: { data: NodeContentData }) => {
|
|
|
|
|
const isEndNode = data.type === 'end';
|
|
|
|
|
const isSpecialNode = isStartNode || isEndNode;
|
|
|
|
|
|
|
|
|
|
useEffect(() => {
|
|
|
|
|
let canceled = false;
|
|
|
|
|
let objectUrl = '';
|
|
|
|
|
|
|
|
|
|
setPreviewUrl('');
|
|
|
|
|
setImageLoadError(false);
|
|
|
|
|
|
|
|
|
|
if (!imageUrl) {
|
|
|
|
|
return undefined;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const loadImageBlob = async () => {
|
|
|
|
|
try {
|
|
|
|
|
const response = await fetch(imageUrl);
|
|
|
|
|
if (!response.ok) {
|
|
|
|
|
throw new Error(`load image failed: ${response.status}`);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const blob = await response.blob();
|
|
|
|
|
if (isTiffImage(imageUrl, blob.type)) {
|
|
|
|
|
const tiffPreviewUrl = await createTiffPreviewUrl(imageUrl, blob);
|
|
|
|
|
if (!canceled) {
|
|
|
|
|
setPreviewUrl(tiffPreviewUrl);
|
|
|
|
|
}
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const typedBlob = new Blob([blob], {
|
|
|
|
|
type: getImageBlobType(imageUrl, blob.type),
|
|
|
|
|
});
|
|
|
|
|
objectUrl = URL.createObjectURL(typedBlob);
|
|
|
|
|
|
|
|
|
|
if (!canceled) {
|
|
|
|
|
setPreviewUrl(objectUrl);
|
|
|
|
|
}
|
|
|
|
|
} catch (error) {
|
|
|
|
|
console.error('图片下载预览失败:', error);
|
|
|
|
|
if (!canceled) {
|
|
|
|
|
setPreviewUrl(imageUrl);
|
|
|
|
|
setImageLoadError(true);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
loadImageBlob();
|
|
|
|
|
|
|
|
|
|
return () => {
|
|
|
|
|
canceled = true;
|
|
|
|
|
if (objectUrl) {
|
|
|
|
|
URL.revokeObjectURL(objectUrl);
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
}, [imageUrl]);
|
|
|
|
|
|
|
|
|
|
return (
|
|
|
|
|
<>
|
|
|
|
|
{/*content栏-api部分*/}
|
|
|
|
|
@ -178,14 +290,31 @@ const NodeContent = ({ data }: { data: NodeContentData }) => {
|
|
|
|
|
</>
|
|
|
|
|
)}
|
|
|
|
|
|
|
|
|
|
{/*图片展示 TODO 需要对接接口*/}
|
|
|
|
|
{/*<div className={styles['node-image-box']}>*/}
|
|
|
|
|
{/* <Image*/}
|
|
|
|
|
{/* width={150}*/}
|
|
|
|
|
{/* src=""*/}
|
|
|
|
|
{/* alt="lamp"*/}
|
|
|
|
|
{/* />*/}
|
|
|
|
|
{/*</div>*/}
|
|
|
|
|
{imageUrl && (
|
|
|
|
|
<div className={styles['node-image-box']}>
|
|
|
|
|
{previewUrl ? (
|
|
|
|
|
<Image
|
|
|
|
|
width={150}
|
|
|
|
|
src={previewUrl}
|
|
|
|
|
alt="图片展示"
|
|
|
|
|
style={{ display: 'block' }}
|
|
|
|
|
onError={() => setImageLoadError(true)}
|
|
|
|
|
/>
|
|
|
|
|
) : (
|
|
|
|
|
<div className={styles['node-image-loading']}>加载中</div>
|
|
|
|
|
)}
|
|
|
|
|
{imageLoadError && (
|
|
|
|
|
<a
|
|
|
|
|
className={styles['node-image-download']}
|
|
|
|
|
href={imageUrl}
|
|
|
|
|
target="_blank"
|
|
|
|
|
rel="noreferrer"
|
|
|
|
|
>
|
|
|
|
|
下载图片
|
|
|
|
|
</a>
|
|
|
|
|
)}
|
|
|
|
|
</div>
|
|
|
|
|
)}
|
|
|
|
|
|
|
|
|
|
{/* 根据节点类型渲染不同的句柄 */}
|
|
|
|
|
{renderRegularNodeHandles(dataIns, dataOuts, apiIns, apiOuts)}
|
|
|
|
|
@ -193,4 +322,4 @@ const NodeContent = ({ data }: { data: NodeContentData }) => {
|
|
|
|
|
);
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
export default NodeContent;
|
|
|
|
|
export default NodeContent;
|
|
|
|
|
|