Skip to content

Commit 25b58d4

Browse files
authored
Merge pull request #426 from easyops-cn/steve/stage-flow
Steve/stage-flow
2 parents 95eaa69 + dcaf76a commit 25b58d4

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

52 files changed

+1585
-301
lines changed

bricks/advanced/src/next-table/styles.shadow.css

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -242,6 +242,8 @@
242242
}
243243

244244
.ant-table.ant-table-bordered {
245+
background: #fff;
246+
245247
.ant-table-thead > tr > th {
246248
color: #262626;
247249
background: #f3f6fa;

bricks/ai-portal/package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,7 @@
5252
"@next-shared/datetime": "^0.4.3",
5353
"@next-shared/diagram": "^0.1.1",
5454
"@next-shared/form": "^0.9.3",
55+
"@next-shared/hooks": "^0.0.19",
5556
"@next-shared/markdown": "^0.7.2",
5657
"@next-shared/shiki": "^0.1.0",
5758
"@next-shared/tsx-converter": "^0.5.1",
Lines changed: 123 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,123 @@
1+
import React, { useEffect, useState } from "react";
2+
import classNames from "classnames";
3+
import { WrappedIcon } from "./bricks";
4+
import type { FileItem } from "./interfaces";
5+
import {
6+
formatFileSize,
7+
getFileTypeAndIcon,
8+
} from "../cruise-canvas/utils/file";
9+
10+
export interface FileListComponentProps {
11+
files: FileItem[];
12+
onRemove: (uid: number, abortController: AbortController | undefined) => void;
13+
onAdd: () => void;
14+
}
15+
16+
export function FileListComponent({
17+
files,
18+
onRemove,
19+
onAdd,
20+
}: FileListComponentProps) {
21+
const showAsImage = files.every((file) =>
22+
file.file.type.startsWith("image/")
23+
);
24+
25+
return (
26+
<ul className="files">
27+
{files.map((file) => (
28+
<FileComponent
29+
{...file}
30+
key={file.uid}
31+
showAsImage={showAsImage}
32+
onRemove={onRemove}
33+
/>
34+
))}
35+
<li>
36+
<button className="btn-add-file" onClick={onAdd}>
37+
<WrappedIcon lib="antd" icon="plus" />
38+
</button>
39+
</li>
40+
</ul>
41+
);
42+
}
43+
44+
interface FileComponentProps extends FileItem {
45+
showAsImage?: boolean;
46+
onRemove: (uid: number, abortController: AbortController | undefined) => void;
47+
}
48+
49+
function FileComponent({
50+
uid,
51+
file,
52+
status,
53+
abortController,
54+
showAsImage,
55+
onRemove,
56+
}: FileComponentProps) {
57+
const [type, icon] = getFileTypeAndIcon(file.type, file.name);
58+
const size = formatFileSize(file.size);
59+
const isImage = file.type.startsWith("image/");
60+
const [image, setImage] = useState<string | undefined>();
61+
62+
useEffect(() => {
63+
if (isImage) {
64+
const url = URL.createObjectURL(file);
65+
setImage(url);
66+
return () => {
67+
URL.revokeObjectURL(url);
68+
};
69+
} else {
70+
setImage(undefined);
71+
}
72+
}, [file, isImage]);
73+
74+
const buttonRemove = (
75+
<button
76+
className="file-remove"
77+
onClick={() => {
78+
onRemove(uid, abortController);
79+
}}
80+
>
81+
<WrappedIcon lib="antd" theme="filled" icon="close-circle" />
82+
</button>
83+
);
84+
85+
if (showAsImage) {
86+
return (
87+
<li
88+
className={classNames("file as-image", { failed: status === "failed" })}
89+
>
90+
<img className="file-image" src={image} />
91+
{status === "uploading" && (
92+
<div className="file-overlay">
93+
<WrappedIcon lib="antd" icon="loading-3-quarters" spinning />
94+
</div>
95+
)}
96+
{status === "failed" && (
97+
<div className="file-overlay">Upload failed</div>
98+
)}
99+
{buttonRemove}
100+
</li>
101+
);
102+
}
103+
104+
return (
105+
<li className={classNames("file", { failed: status === "failed" })}>
106+
{isImage ? (
107+
<img className="file-image" src={image} />
108+
) : (
109+
<img className="file-icon" src={icon} width={26} height={32} />
110+
)}
111+
<div className="file-content">
112+
<div className="file-name">{file.name}</div>
113+
<div className="file-metadata">
114+
<span className="file-status">
115+
{`${status === "uploading" ? "Uploading..." : status === "failed" ? "Upload failed" : type}`}
116+
</span>
117+
{` · ${size}`}
118+
</div>
119+
</div>
120+
{buttonRemove}
121+
</li>
122+
);
123+
}
Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
import React, { forwardRef, useImperativeHandle, useRef } from "react";
2+
import type { GeneralIconProps } from "@next-bricks/icons/general-icon";
3+
import { WrappedIconButton } from "./bricks";
4+
import type { FileItem } from "./interfaces";
5+
6+
const ICON_UPLOAD: GeneralIconProps = {
7+
lib: "lucide",
8+
icon: "paperclip",
9+
};
10+
11+
let uid = 0;
12+
13+
export interface UploadButtonProps {
14+
onChange?: (files: FileItem[] | undefined) => void;
15+
}
16+
17+
export interface UploadButtonRef {
18+
requestUpload: () => void;
19+
}
20+
21+
export const UploadButton = forwardRef(LegacyUploadButton);
22+
23+
function LegacyUploadButton(
24+
{ onChange }: UploadButtonProps,
25+
ref: React.Ref<UploadButtonRef>
26+
) {
27+
const inputRef = useRef<HTMLInputElement>(null);
28+
29+
const handleInputChange = (e: React.ChangeEvent<HTMLInputElement>) => {
30+
const files = e.target.files;
31+
onChange?.(
32+
files
33+
? Array.from(files).map((file) => ({
34+
uid: uid++,
35+
file,
36+
status: "ready",
37+
}))
38+
: undefined
39+
);
40+
e.target.value = "";
41+
};
42+
43+
useImperativeHandle(ref, () => ({
44+
requestUpload: () => {
45+
inputRef.current?.click();
46+
},
47+
}));
48+
49+
return (
50+
<>
51+
<input
52+
type="file"
53+
multiple
54+
hidden
55+
ref={inputRef}
56+
onChange={handleInputChange}
57+
/>
58+
<WrappedIconButton
59+
variant="light"
60+
className="btn-upload"
61+
icon={ICON_UPLOAD}
62+
tooltip="Upload files"
63+
onClick={() => inputRef.current?.click()}
64+
/>
65+
</>
66+
);
67+
}
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
import { wrapBrick } from "@next-core/react-element";
2+
import type {
3+
GeneralIcon,
4+
GeneralIconProps,
5+
} from "@next-bricks/icons/general-icon";
6+
import type {
7+
ActionsEvents,
8+
ActionsEventsMapping,
9+
ActionsProps,
10+
EoActions,
11+
} from "@next-bricks/basic/actions";
12+
import type { IconButton, IconButtonProps } from "../icon-button";
13+
14+
export const WrappedIcon = wrapBrick<GeneralIcon, GeneralIconProps>("eo-icon");
15+
export const WrappedActions = wrapBrick<
16+
EoActions,
17+
ActionsProps & { activeKeys?: (string | number)[] },
18+
ActionsEvents,
19+
ActionsEventsMapping
20+
>("eo-actions", {
21+
onActionClick: "action.click",
22+
onItemDragEnd: "item.drag.end",
23+
onItemDragStart: "item.drag.start",
24+
});
25+
export const WrappedIconButton = wrapBrick<IconButton, IconButtonProps>(
26+
"ai-portal.icon-button"
27+
);

0 commit comments

Comments
 (0)