Browse Source

Merge pull request '合并dev-zw' (#153) from dev-zw into dev

dev-complex-computation
cp3hnu 1 year ago
parent
commit
7b6d39548b
17 changed files with 26 additions and 1023 deletions
  1. +0
    -37
      react-ui/src/components/ArrayTableCell/index.tsx
  2. +1
    -1
      react-ui/src/components/CodeSelect/index.tsx
  3. +0
    -45
      react-ui/src/components/Footer/index.tsx
  4. +0
    -64
      react-ui/src/components/IconSelector/Category.tsx
  5. +0
    -44
      react-ui/src/components/IconSelector/CopyableIcon.tsx
  6. +0
    -237
      react-ui/src/components/IconSelector/IconPicSearcher.tsx
  7. +0
    -223
      react-ui/src/components/IconSelector/fields.ts
  8. +0
    -146
      react-ui/src/components/IconSelector/index.tsx
  9. +0
    -137
      react-ui/src/components/IconSelector/style.less
  10. +0
    -40
      react-ui/src/components/IconSelector/themeIcons.tsx
  11. +0
    -7
      react-ui/src/components/KFConfirmModal/index.less
  12. +0
    -37
      react-ui/src/components/KFConfirmModal/index.tsx
  13. +3
    -0
      react-ui/src/pages/Dataset/components/ResourceInfo/index.tsx
  14. +2
    -2
      react-ui/src/pages/Dataset/components/ResourceList/index.tsx
  15. +1
    -0
      react-ui/src/pages/Dataset/components/ResourceVersion/index.tsx
  16. +5
    -0
      react-ui/src/pages/System/User/edit.tsx
  17. +14
    -3
      react-ui/src/types.ts

+ 0
- 37
react-ui/src/components/ArrayTableCell/index.tsx View File

@@ -1,37 +0,0 @@
/*
* @Author: 赵伟
* @Date: 2024-04-28 14:18:11
* @Description: 自定义 Table 数组类单元格
*/

import { Tooltip } from 'antd';

function ArrayTableCell(ellipsis: boolean = false, property?: string) {
return (value?: any | null) => {
if (
value === undefined ||
value === null ||
Array.isArray(value) === false ||
value.length === 0
) {
return <span>--</span>;
}

let list = value;
if (property && typeof value[0] === 'object') {
list = value.map((item) => item[property]);
}
const text = list.join(',');
if (ellipsis) {
return (
<Tooltip title={text} placement="topLeft" overlayStyle={{ maxWidth: '400px' }}>
<span>{text}</span>;
</Tooltip>
);
} else {
return <span>{text}</span>;
}
};
}

export default ArrayTableCell;

+ 1
- 1
react-ui/src/components/CodeSelect/index.tsx View File

@@ -1,7 +1,7 @@
/*
* @Author: 赵伟
* @Date: 2024-10-08 15:36:08
* @Description: 代码配置选择表单组件
* @Description: 流水线选择代码配置表单
*/

import KFIcon from '@/components/KFIcon';


+ 0
- 45
react-ui/src/components/Footer/index.tsx View File

@@ -1,45 +0,0 @@
import { GithubOutlined } from '@ant-design/icons';
import { DefaultFooter } from '@ant-design/pro-components';
import { useIntl } from '@umijs/max';
import React from 'react';

const Footer: React.FC = () => {
const intl = useIntl();
const defaultMessage = intl.formatMessage({
id: 'app.copyright.produced',
defaultMessage: '蚂蚁集团体验技术部出品',
});

const currentYear = new Date().getFullYear();

return (
<DefaultFooter
style={{
background: 'none',
}}
copyright={`${currentYear} ${defaultMessage}`}
links={[
{
key: 'Ant Design Pro',
title: 'Ant Design Pro',
href: 'https://pro.ant.design',
blankTarget: true,
},
{
key: 'github',
title: <GithubOutlined />,
href: 'https://github.com/ant-design/ant-design-pro',
blankTarget: true,
},
{
key: 'Ant Design',
title: 'Ant Design',
href: 'https://ant.design',
blankTarget: true,
},
]}
/>
);
};

export default Footer;

+ 0
- 64
react-ui/src/components/IconSelector/Category.tsx View File

@@ -1,64 +0,0 @@
import { useIntl } from '@umijs/max';
import * as React from 'react';
import CopyableIcon from './CopyableIcon';
import type { CategoriesKeys } from './fields';
import type { ThemeType } from './index';
import styles from './style.less';

interface CategoryProps {
title: CategoriesKeys;
icons: string[];
theme: ThemeType;
newIcons: string[];
onSelect: (type: string, name: string) => any;
}

const Category: React.FC<CategoryProps> = (props) => {
const { icons, title, newIcons, theme } = props;
const intl = useIntl();
const [justCopied, setJustCopied] = React.useState<string | null>(null);
const copyId = React.useRef<NodeJS.Timeout | null>(null);
const onSelect = React.useCallback((type: string, text: string) => {
const { onSelect } = props;
if (onSelect) {
onSelect(type, text);
}
setJustCopied(type);
copyId.current = setTimeout(() => {
setJustCopied(null);
}, 2000);
}, []);
React.useEffect(
() => () => {
if (copyId.current) {
clearTimeout(copyId.current);
}
},
[],
);

return (
<div>
<h4>
{intl.formatMessage({
id: `app.docs.components.icon.category.${title}`,
defaultMessage: '信息',
})}
</h4>
<ul className={styles.anticonsList}>
{icons.map((name) => (
<CopyableIcon
key={name}
name={name}
theme={theme}
isNew={newIcons.includes(name)}
justCopied={justCopied}
onSelect={onSelect}
/>
))}
</ul>
</div>
);
};

export default Category;

+ 0
- 44
react-ui/src/components/IconSelector/CopyableIcon.tsx View File

@@ -1,44 +0,0 @@
import * as AntdIcons from '@ant-design/icons';
import { Tooltip } from 'antd';
import classNames from 'classnames';
import * as React from 'react';
import type { ThemeType } from './index';
import styles from './style.less';

const allIcons: {
[key: string]: any;
} = AntdIcons;

export interface CopyableIconProps {
name: string;
isNew: boolean;
theme: ThemeType;
justCopied: string | null;
onSelect: (type: string, text: string) => any;
}

const CopyableIcon: React.FC<CopyableIconProps> = ({ name, justCopied, onSelect, theme }) => {
const className = classNames({
copied: justCopied === name,
[theme]: !!theme,
});
return (
<li
className={className}
onClick={() => {
if (onSelect) {
onSelect(theme, name);
}
}}
>
<Tooltip title={name}>
{React.createElement(allIcons[name], { className: styles.anticon })}
</Tooltip>
{/* <span className={styles.anticonClass}>
<Badge dot={isNew}>{name}</Badge>
</span> */}
</li>
);
};

export default CopyableIcon;

+ 0
- 237
react-ui/src/components/IconSelector/IconPicSearcher.tsx View File

@@ -1,237 +0,0 @@
import KFModal from '@/components/KFModal';
import * as AntdIcons from '@ant-design/icons';
import { useIntl } from '@umijs/max';
import { Popover, Progress, Result, Spin, Tooltip, Upload } from 'antd';
import React, { useCallback, useEffect, useState } from 'react';
import './style.less';

const allIcons: { [key: string]: any } = AntdIcons;

const { Dragger } = Upload;
interface AntdIconClassifier {
load: () => void;
predict: (imgEl: HTMLImageElement) => void;
}
declare global {
interface Window {
antdIconClassifier: AntdIconClassifier;
}
}

interface PicSearcherState {
loading: boolean;
modalOpen: boolean;
popoverVisible: boolean;
icons: iconObject[];
fileList: any[];
error: boolean;
modelLoaded: boolean;
}

interface iconObject {
type: string;
score: number;
}

const PicSearcher: React.FC = () => {
const intl = useIntl();
const { formatMessage } = intl;
const [state, setState] = useState<PicSearcherState>({
loading: false,
modalOpen: false,
popoverVisible: false,
icons: [],
fileList: [],
error: false,
modelLoaded: false,
});
const predict = (imgEl: HTMLImageElement) => {
try {
let icons: any[] = window.antdIconClassifier.predict(imgEl);
if (gtag && icons.length) {
gtag('event', 'icon', {
event_category: 'search-by-image',
event_label: icons[0].className,
});
}
icons = icons.map((i) => ({ score: i.score, type: i.className.replace(/\s/g, '-') }));
setState((prev) => ({ ...prev, loading: false, error: false, icons }));
} catch {
setState((prev) => ({ ...prev, loading: false, error: true }));
}
};
// eslint-disable-next-line class-methods-use-this
const toImage = (url: string) =>
new Promise((resolve) => {
const img = new Image();
img.setAttribute('crossOrigin', 'anonymous');
img.src = url;
img.onload = () => {
resolve(img);
};
});

const uploadFile = useCallback((file: File) => {
setState((prev) => ({ ...prev, loading: true }));
const reader = new FileReader();
reader.onload = () => {
toImage(reader.result as string).then(predict);
setState((prev) => ({
...prev,
fileList: [{ uid: 1, name: file.name, status: 'done', url: reader.result }],
}));
};
reader.readAsDataURL(file);
}, []);

const onPaste = useCallback((event: ClipboardEvent) => {
const items = event.clipboardData && event.clipboardData.items;
let file = null;
if (items && items.length) {
for (let i = 0; i < items.length; i++) {
if (items[i].type.includes('image')) {
file = items[i].getAsFile();
break;
}
}
}
if (file) {
uploadFile(file);
}
}, []);
const toggleModal = useCallback(() => {
setState((prev) => ({
...prev,
modalOpen: !prev.modalOpen,
popoverVisible: false,
fileList: [],
icons: [],
}));
if (!localStorage.getItem('disableIconTip')) {
localStorage.setItem('disableIconTip', 'true');
}
}, []);

useEffect(() => {
const script = document.createElement('script');
script.onload = async () => {
await window.antdIconClassifier.load();
setState((prev) => ({ ...prev, modelLoaded: true }));
document.addEventListener('paste', onPaste);
};
script.src = 'https://cdn.jsdelivr.net/gh/lewis617/antd-icon-classifier@0.0/dist/main.js';
document.head.appendChild(script);
setState((prev) => ({ ...prev, popoverVisible: !localStorage.getItem('disableIconTip') }));
return () => {
document.removeEventListener('paste', onPaste);
};
}, []);

return (
<div className="iconPicSearcher">
<Popover
content={formatMessage({ id: 'app.docs.components.icon.pic-searcher.intro' })}
open={state.popoverVisible}
>
<AntdIcons.CameraOutlined className="icon-pic-btn" onClick={toggleModal} />
</Popover>
<KFModal
title={intl.formatMessage({
id: 'app.docs.components.icon.pic-searcher.title',
defaultMessage: '信息',
})}
open={state.modalOpen}
onCancel={toggleModal}
footer={null}
>
{state.modelLoaded || (
<Spin
spinning={!state.modelLoaded}
tip={formatMessage({
id: 'app.docs.components.icon.pic-searcher.modelloading',
})}
>
<div style={{ height: 100 }} />
</Spin>
)}
{state.modelLoaded && (
<Dragger
accept="image/jpeg, image/png"
listType="picture"
customRequest={(o) => uploadFile(o.file as File)}
fileList={state.fileList}
showUploadList={{ showPreviewIcon: false, showRemoveIcon: false }}
>
<p className="ant-upload-drag-icon">
<AntdIcons.InboxOutlined />
</p>
<p className="ant-upload-text">
{formatMessage({ id: 'app.docs.components.icon.pic-searcher.upload-text' })}
</p>
<p className="ant-upload-hint">
{formatMessage({ id: 'app.docs.components.icon.pic-searcher.upload-hint' })}
</p>
</Dragger>
)}
<Spin
spinning={state.loading}
tip={formatMessage({ id: 'app.docs.components.icon.pic-searcher.matching' })}
>
<div className="icon-pic-search-result">
{state.icons.length > 0 && (
<div className="result-tip">
{formatMessage({ id: 'app.docs.components.icon.pic-searcher.result-tip' })}
</div>
)}
<table>
{state.icons.length > 0 && (
<thead>
<tr>
<th className="col-icon">
{formatMessage({ id: 'app.docs.components.icon.pic-searcher.th-icon' })}
</th>
<th>
{formatMessage({ id: 'app.docs.components.icon.pic-searcher.th-score' })}
</th>
</tr>
</thead>
)}
<tbody>
{state.icons.map((icon) => {
const { type } = icon;
const iconName = `${type
.split('-')
.map((str) => `${str[0].toUpperCase()}${str.slice(1)}`)
.join('')}Outlined`;
return (
<tr key={iconName}>
<td className="col-icon">
<Tooltip title={icon.type} placement="right">
{React.createElement(allIcons[iconName])}
</Tooltip>
</td>
<td>
<Progress percent={Math.ceil(icon.score * 100)} />
</td>
</tr>
);
})}
</tbody>
</table>
{state.error && (
<Result
status="500"
title="503"
subTitle={formatMessage({
id: 'app.docs.components.icon.pic-searcher.server-error',
})}
/>
)}
</div>
</Spin>
</KFModal>
</div>
);
};

export default PicSearcher;

+ 0
- 223
react-ui/src/components/IconSelector/fields.ts View File

@@ -1,223 +0,0 @@
import * as AntdIcons from '@ant-design/icons/lib/icons';

const all = Object.keys(AntdIcons)
.map((n) => n.replace(/(Outlined|Filled|TwoTone)$/, ''))
.filter((n, i, arr) => arr.indexOf(n) === i);

const direction = [
'StepBackward',
'StepForward',
'FastBackward',
'FastForward',
'Shrink',
'ArrowsAlt',
'Down',
'Up',
'Left',
'Right',
'CaretUp',
'CaretDown',
'CaretLeft',
'CaretRight',
'UpCircle',
'DownCircle',
'LeftCircle',
'RightCircle',
'DoubleRight',
'DoubleLeft',
'VerticalLeft',
'VerticalRight',
'VerticalAlignTop',
'VerticalAlignMiddle',
'VerticalAlignBottom',
'Forward',
'Backward',
'Rollback',
'Enter',
'Retweet',
'Swap',
'SwapLeft',
'SwapRight',
'ArrowUp',
'ArrowDown',
'ArrowLeft',
'ArrowRight',
'PlayCircle',
'UpSquare',
'DownSquare',
'LeftSquare',
'RightSquare',
'Login',
'Logout',
'MenuFold',
'MenuUnfold',
'BorderBottom',
'BorderHorizontal',
'BorderInner',
'BorderOuter',
'BorderLeft',
'BorderRight',
'BorderTop',
'BorderVerticle',
'PicCenter',
'PicLeft',
'PicRight',
'RadiusBottomleft',
'RadiusBottomright',
'RadiusUpleft',
'RadiusUpright',
'Fullscreen',
'FullscreenExit',
];

const suggestion = [
'Question',
'QuestionCircle',
'Plus',
'PlusCircle',
'Pause',
'PauseCircle',
'Minus',
'MinusCircle',
'PlusSquare',
'MinusSquare',
'Info',
'InfoCircle',
'Exclamation',
'ExclamationCircle',
'Close',
'CloseCircle',
'CloseSquare',
'Check',
'CheckCircle',
'CheckSquare',
'ClockCircle',
'Warning',
'IssuesClose',
'Stop',
];

const editor = [
'Edit',
'Form',
'Copy',
'Scissor',
'Delete',
'Snippets',
'Diff',
'Highlight',
'AlignCenter',
'AlignLeft',
'AlignRight',
'BgColors',
'Bold',
'Italic',
'Underline',
'Strikethrough',
'Redo',
'Undo',
'ZoomIn',
'ZoomOut',
'FontColors',
'FontSize',
'LineHeight',
'Dash',
'SmallDash',
'SortAscending',
'SortDescending',
'Drag',
'OrderedList',
'UnorderedList',
'RadiusSetting',
'ColumnWidth',
'ColumnHeight',
];

const data = [
'AreaChart',
'PieChart',
'BarChart',
'DotChart',
'LineChart',
'RadarChart',
'HeatMap',
'Fall',
'Rise',
'Stock',
'BoxPlot',
'Fund',
'Sliders',
];

const logo = [
'Android',
'Apple',
'Windows',
'Ie',
'Chrome',
'Github',
'Aliwangwang',
'Dingding',
'WeiboSquare',
'WeiboCircle',
'TaobaoCircle',
'Html5',
'Weibo',
'Twitter',
'Wechat',
'Youtube',
'AlipayCircle',
'Taobao',
'Skype',
'Qq',
'MediumWorkmark',
'Gitlab',
'Medium',
'Linkedin',
'GooglePlus',
'Dropbox',
'Facebook',
'Codepen',
'CodeSandbox',
'CodeSandboxCircle',
'Amazon',
'Google',
'CodepenCircle',
'Alipay',
'AntDesign',
'AntCloud',
'Aliyun',
'Zhihu',
'Slack',
'SlackSquare',
'Behance',
'BehanceSquare',
'Dribbble',
'DribbbleSquare',
'Instagram',
'Yuque',
'Alibaba',
'Yahoo',
'Reddit',
'Sketch',
'WhatsApp',
'Dingtalk',
];

const datum = [...direction, ...suggestion, ...editor, ...data, ...logo];

const other = all.filter((n) => !datum.includes(n));

export const categories = {
direction,
suggestion,
editor,
data,
logo,
other,
};

export default categories;

export type Categories = typeof categories;
export type CategoriesKeys = keyof Categories;

+ 0
- 146
react-ui/src/components/IconSelector/index.tsx View File

@@ -1,146 +0,0 @@
import Icon, * as AntdIcons from '@ant-design/icons';
import { Empty, Input, Radio } from 'antd';
import type { RadioChangeEvent } from 'antd/es/radio/interface';
import debounce from 'lodash/debounce';
import * as React from 'react';
import Category from './Category';
import IconPicSearcher from './IconPicSearcher';
import type { CategoriesKeys } from './fields';
import { categories } from './fields';
import { FilledIcon, OutlinedIcon, TwoToneIcon } from './themeIcons';
// import { useIntl } from '@umijs/max';

export enum ThemeType {
Filled = 'Filled',
Outlined = 'Outlined',
TwoTone = 'TwoTone',
}

const allIcons: { [key: string]: any } = AntdIcons;

interface IconSelectorProps {
//intl: any;
onSelect: any;
}

interface IconSelectorState {
theme: ThemeType;
searchKey: string;
}

const IconSelector: React.FC<IconSelectorProps> = (props) => {
// const intl = useIntl();
// const { messages } = intl;
const { onSelect } = props;
const [displayState, setDisplayState] = React.useState<IconSelectorState>({
theme: ThemeType.Outlined,
searchKey: '',
});

const newIconNames: string[] = [];

const handleSearchIcon = React.useCallback(
debounce((searchKey: string) => {
setDisplayState((prevState) => ({ ...prevState, searchKey }));
}),
[],
);

const handleChangeTheme = React.useCallback((e: RadioChangeEvent) => {
setDisplayState((prevState) => ({ ...prevState, theme: e.target.value as ThemeType }));
}, []);

const renderCategories = React.useMemo<React.ReactNode | React.ReactNode[]>(() => {
const { searchKey = '', theme } = displayState;

const categoriesResult = Object.keys(categories)
.map((key: CategoriesKeys) => {
let iconList = categories[key];
if (searchKey) {
const matchKey = searchKey
// eslint-disable-next-line prefer-regex-literals
.replace(new RegExp(`^<([a-zA-Z]*)\\s/>$`, 'gi'), (_, name) => name)
.replace(/(Filled|Outlined|TwoTone)$/, '')
.toLowerCase();
iconList = iconList.filter((iconName: string) =>
iconName.toLowerCase().includes(matchKey),
);
}

// CopyrightCircle is same as Copyright, don't show it
iconList = iconList.filter((icon: string) => icon !== 'CopyrightCircle');

return {
category: key,
icons: iconList
.map((iconName: string) => iconName + theme)
.filter((iconName: string) => allIcons[iconName]),
};
})
.filter(({ icons }) => !!icons.length)
.map(({ category, icons }) => (
<Category
key={category}
title={category as CategoriesKeys}
theme={theme}
icons={icons}
newIcons={newIconNames}
onSelect={(type, name) => {
if (onSelect) {
onSelect(name, allIcons[name]);
}
}}
/>
));
return categoriesResult.length === 0 ? <Empty style={{ margin: '2em 0' }} /> : categoriesResult;
}, [displayState.searchKey, displayState.theme]);
return (
<>
<div style={{ display: 'flex', justifyContent: 'space-between' }}>
<Radio.Group
value={displayState.theme}
onChange={handleChangeTheme}
size="large"
optionType="button"
buttonStyle="solid"
options={[
{
label: <Icon component={OutlinedIcon} />,
value: ThemeType.Outlined,
},
{
label: <Icon component={FilledIcon} />,
value: ThemeType.Filled,
},
{
label: <Icon component={TwoToneIcon} />,
value: ThemeType.TwoTone,
},
]}
>
{/* <Radio.Button value={ThemeType.Outlined}>
<Icon component={OutlinedIcon} /> {messages['app.docs.components.icon.outlined']}
</Radio.Button>
<Radio.Button value={ThemeType.Filled}>
<Icon component={FilledIcon} /> {messages['app.docs.components.icon.filled']}
</Radio.Button>
<Radio.Button value={ThemeType.TwoTone}>
<Icon component={TwoToneIcon} /> {messages['app.docs.components.icon.two-tone']}
</Radio.Button> */}
</Radio.Group>
<Input.Search
// placeholder={messages['app.docs.components.icon.search.placeholder']}
style={{ margin: '0 10px', flex: 1 }}
allowClear
onChange={(e) => handleSearchIcon(e.currentTarget.value)}
size="large"
autoFocus
suffix={<IconPicSearcher />}
/>
</div>
{renderCategories}
</>
);
};

export default IconSelector;

+ 0
- 137
react-ui/src/components/IconSelector/style.less View File

@@ -1,137 +0,0 @@
.iconPicSearcher {
display: inline-block;
margin: 0 8px;

.icon-pic-btn {
color: @text-color-secondary;
cursor: pointer;
transition: all 0.3s;

&:hover {
color: @input-icon-hover-color;
}
}
}

.icon-pic-preview {
width: 30px;
height: 30px;
margin-top: 10px;
padding: 8px;
text-align: center;
border: 1px solid @border-color-base;
border-radius: 4px;

> img {
max-width: 50px;
max-height: 50px;
}
}

.icon-pic-search-result {
min-height: 50px;
padding: 0 10px;

> .result-tip {
padding: 10px 0;
color: @text-color-secondary;
}

> table {
width: 100%;

.col-icon {
width: 80px;
padding: 10px 0;

> .anticon {
font-size: 30px;

:hover {
color: @link-hover-color;
}
}
}
}
}

ul.anticonsList {
margin: 2px 0;
overflow: hidden;
direction: ltr;
list-style: none;

li {
position: relative;
float: left;
width: 48px;
height: 48px;
margin: 3px 0;
padding: 2px 0 0;
overflow: hidden;
color: #555;
text-align: center;
list-style: none;
background-color: inherit;
border-radius: 4px;
cursor: pointer;
transition: color 0.3s ease-in-out, background-color 0.3s ease-in-out;

.rtl & {
margin: 3px 0;
padding: 2px 0 0;
}

.anticon {
margin: 4px 0 2px;
font-size: 24px;
transition: transform 0.3s ease-in-out;
will-change: transform;
}

.anticonClass {
display: block;
font-family: 'Lucida Console', Consolas, Monaco, 'Andale Mono', 'Ubuntu Mono', monospace;
white-space: nowrap;
text-align: center;
transform: scale(0.83);

.ant-badge {
transition: color 0.3s ease-in-out;
}
}

&:hover {
color: #fff;
background-color: @primary-color;

.anticon {
transform: scale(1.4);
}

.ant-badge {
color: #fff;
}
}

&.TwoTone:hover {
background-color: #8ecafe;
}

&.copied:hover {
color: rgba(255, 255, 255, 0.2);
}

&.copied::after {
top: -2px;
opacity: 1;
}
}
}

.copied-code {
padding: 2px 4px;
font-size: 12px;
background: #f5f5f5;
border-radius: 2px;
}

+ 0
- 40
react-ui/src/components/IconSelector/themeIcons.tsx View File

@@ -1,40 +0,0 @@
import * as React from 'react';

export const FilledIcon: React.FC = (props) => {
const path =
'M864 64H160C107 64 64 107 64 160v' +
'704c0 53 43 96 96 96h704c53 0 96-43 96-96V16' +
'0c0-53-43-96-96-96z';
return (
<svg {...props} viewBox="0 0 1024 1024">
<path d={path} />
</svg>
);
};

export const OutlinedIcon: React.FC = (props) => {
const path =
'M864 64H160C107 64 64 107 64 160v7' +
'04c0 53 43 96 96 96h704c53 0 96-43 96-96V160c' +
'0-53-43-96-96-96z m-12 800H172c-6.6 0-12-5.4-' +
'12-12V172c0-6.6 5.4-12 12-12h680c6.6 0 12 5.4' +
' 12 12v680c0 6.6-5.4 12-12 12z';
return (
<svg {...props} viewBox="0 0 1024 1024">
<path d={path} />
</svg>
);
};

export const TwoToneIcon: React.FC = (props) => {
const path =
'M16 512c0 273.932 222.066 496 496 49' +
'6s496-222.068 496-496S785.932 16 512 16 16 238.' +
'066 16 512z m496 368V144c203.41 0 368 164.622 3' +
'68 368 0 203.41-164.622 368-368 368z';
return (
<svg {...props} viewBox="0 0 1024 1024">
<path d={path} />
</svg>
);
};

+ 0
- 7
react-ui/src/components/KFConfirmModal/index.less View File

@@ -1,7 +0,0 @@
.kf-confirm-modal {
&__content {
width: 100%;
font-size: 18px;
text-align: center;
}
}

+ 0
- 37
react-ui/src/components/KFConfirmModal/index.tsx View File

@@ -1,37 +0,0 @@
/*
* @Author: 赵伟
* @Date: 2024-10-10 10:54:25
* @Description: 自定义 Confirm Modal
*/

import classNames from 'classnames';
import KFModal, { KFModalProps } from '../KFModal';
import './index.less';

export interface KFConfirmModalProps extends KFModalProps {
content: string;
}
function KFConfirmModal({
title,
image,
className = '',
centered,
maskClosable,
content,
...rest
}: KFConfirmModalProps) {
return (
<KFModal
className={classNames(['kf-confirm-modal', className])}
{...rest}
centered={centered ?? true}
maskClosable={maskClosable ?? false}
title={title}
image={image ?? require('@/assets/img/edit-experiment.png')}
>
<div className="kf-confirm-modal__content">{content}</div>
</KFModal>
);
}

export default KFConfirmModal;

+ 3
- 0
react-ui/src/pages/Dataset/components/ResourceInfo/index.tsx View File

@@ -47,6 +47,7 @@ const ResourceInfo = ({ resourceType }: ResourceInfoProps) => {
const name = searchParams.get('name') || '';
const owner = searchParams.get('owner') || '';
const identifier = searchParams.get('identifier') || '';
const is_public = searchParams.get('is_public') || '';
const [versionList, setVersionList] = useState<ResourceVersionData[]>([]);
const [version, setVersion] = useState<string | undefined>(undefined);
const [activeTab, setActiveTab] = useState<string>(defaultTab);
@@ -66,6 +67,7 @@ const ResourceInfo = ({ resourceType }: ResourceInfoProps) => {
name,
identifier,
version,
is_public: is_public === 'true',
});
}
}, [version]);
@@ -77,6 +79,7 @@ const ResourceInfo = ({ resourceType }: ResourceInfoProps) => {
id: number;
identifier: string;
version?: string;
is_public: boolean;
}) => {
const request = config.getInfo;
const [res] = await to(request(params));


+ 2
- 2
react-ui/src/pages/Dataset/components/ResourceList/index.tsx View File

@@ -122,7 +122,7 @@ function ResourceList(
modalConfirm({
title: config.deleteModalTitle,
onOk: () => {
deleteRecord(pick(record, ['owner', 'identifier', 'id']));
deleteRecord(pick(record, ['owner', 'identifier', 'id', 'is_public']));
},
});
};
@@ -138,7 +138,7 @@ function ResourceList(
});
const prefix = config.prefix;
navigate(
`/dataset/${prefix}/info/${record.id}?name=${record.name}&owner=${record.owner}&identifier=${record.identifier}`,
`/dataset/${prefix}/info/${record.id}?name=${record.name}&owner=${record.owner}&identifier=${record.identifier}&is_public=${record.is_public}`,
);
};



+ 1
- 0
react-ui/src/pages/Dataset/components/ResourceVersion/index.tsx View File

@@ -28,6 +28,7 @@ function ResourceVersion({ resourceType, info }: ResourceVersionProps) {
id: info.id,
version: info.version,
identifier: info.identifier,
is_public: info.is_public,
});
};



+ 5
- 0
react-ui/src/pages/System/User/edit.tsx View File

@@ -199,6 +199,11 @@ const UserForm: React.FC<UserFormProps> = (props) => {
{
required: true,
},
{
pattern: /^[a-zA-Z0-9](?:[a-zA-Z0-9_.-]*[a-zA-Z0-9])?$/,
message:
'只能包含数字,字母,下划线(_),中横线(-),英文句号(.),且必须以数字或字母开头与结尾',
},
]}
/>
<ProFormText.Password


+ 14
- 3
react-ui/src/types.ts View File

@@ -7,6 +7,7 @@
import { ExperimentStatus, TensorBoardStatus } from '@/enums';
import type { Settings as LayoutSettings } from '@ant-design/pro-components';

// 单点登录的客户端信息
export type ClientInfo = {
accessTokenUri: string;
checkTokenUri: string;
@@ -92,9 +93,19 @@ export type PipelineNodeModelParameter = {
// 修改属性类型
export type ChangePropertyType<T, K extends keyof T, NewType> = Omit<T, K> & { [P in K]: NewType };

// export type PascalCaseType<T> = {
// [K in keyof T as `${Capitalize<string & K>}`]: T[K];
// }
// 将属性名称首字母大写
export type PascalCaseType<T> = {
[K in keyof T as `${Capitalize<string & K>}`]: T[K];
};

// 将属性名称下划线转换为驼峰
type ToCamelCase<S extends string> = S extends `${infer P}_${infer R}`
? `${P}${Capitalize<ToCamelCase<R>>}`
: S;

export type KeysToCamelCase<T> = {
[K in keyof T as ToCamelCase<K & string>]: T[K];
};

// 序列化后的流水线节点
export type PipelineNodeModelSerialize = Omit<


Loading…
Cancel
Save