Compare commits

..
6 Commits
Author SHA1 Message Date
jialin 57f4ddad26 chore: remove --port backend parameter 2025-06-05 11:29:54 +08:00
jialin de34d0ae00 fix: instances distribute info trigger by click 2025-06-05 11:24:09 +08:00
jialin 4f7037baa0 chore: reset changes on catalog 2025-06-03 18:10:39 +08:00
jialin bd15e9aa6c fix: tips for css files load failed 2025-06-03 16:28:03 +08:00
jialin 907105a492 fix: set default spec to form 2025-06-03 15:35:38 +08:00
gitlawr 0d3da04356 ci: publish v*-dev branches 2025-05-30 13:20:40 +08:00
602 changed files with 21099 additions and 45263 deletions
-1
View File
@@ -1,4 +1,3 @@
PORT=9000 PORT=9000
UMI_DEV_SERVER_COMPRESS=none UMI_DEV_SERVER_COMPRESS=none
DID_YOU_KNOW=none
+2
View File
@@ -1,2 +1,4 @@
#!/bin/sh
. "$(dirname "$0")/_/husky.sh"
npx lint-staged npx lint-staged
+19 -14
View File
@@ -1,9 +1,9 @@
import { defineConfig } from '@umijs/max'; import { defineConfig } from '@umijs/max';
import keepAlive from './keep-alive'; import keepAlive from './keep-alive';
import { compressionPluginConfig, monacoPluginConfig } from './plugins';
import proxy from './proxy'; import proxy from './proxy';
import routes from './routes'; import routes from './routes';
import { getBranchInfo } from './utils'; import { getBranchInfo } from './utils';
const CompressionWebpackPlugin = require('compression-webpack-plugin');
const versionInfo = getBranchInfo(); const versionInfo = getBranchInfo();
process.env.VERSION = JSON.stringify(versionInfo); process.env.VERSION = JSON.stringify(versionInfo);
@@ -28,9 +28,6 @@ export default defineConfig({
logLevel: 'info', logLevel: 'info',
defaultSizes: 'parsed' // stat // gzip defaultSizes: 'parsed' // stat // gzip
}, },
mfsu: {
exclude: ['lodash', 'ml-pca', 'monaco-editor']
},
base: process.env.npm_config_base || '/', base: process.env.npm_config_base || '/',
...(isProduction ...(isProduction
? { ? {
@@ -53,18 +50,29 @@ export default defineConfig({
chunkFilename: `css/[name].${t}.chunk.css` chunkFilename: `css/[name].${t}.chunk.css`
} }
]); ]);
config.module
.rule('worker')
.test(/\.worker\.js$/)
.use('worker-loader')
.loader('worker-loader');
config.output config.output
.filename(`js/[name].${t}.js`) .filename(`js/[name].${t}.js`)
.chunkFilename(`js/[name].${t}.chunk.js`); .chunkFilename(`js/[name].${t}.chunk.js`);
compressionPluginConfig(config); config
monacoPluginConfig(config); .plugin('compression-webpack-plugin')
.use(CompressionWebpackPlugin, [
{
filename: '[path][base].gz',
algorithm: 'gzip',
test: /\.(js|css|html|svg)$/,
threshold: 10240,
minRatio: 0.8
}
]);
} }
} }
: { : {}),
chainWebpack(config) {
monacoPluginConfig(config);
}
}),
favicons: ['/static/favicon.png'], favicons: ['/static/favicon.png'],
jsMinifier: 'terser', jsMinifier: 'terser',
cssMinifier: 'cssnano', cssMinifier: 'cssnano',
@@ -78,9 +86,6 @@ export default defineConfig({
model: {}, model: {},
initialState: {}, initialState: {},
request: {}, request: {},
routePrefetch: {
defaultPrefetch: 'intent'
},
keepalive: keepAlive, keepalive: keepAlive,
locale: { locale: {
antd: true, antd: true,
-36
View File
@@ -1,36 +0,0 @@
import MonacoWebpackPlugin from 'monaco-editor-webpack-plugin';
const CompressionWebpackPlugin = require('compression-webpack-plugin');
export function monacoPluginConfig(config: any) {
return config
.plugin('monaco-editor-webpack-plugin')
.use(MonacoWebpackPlugin, [
{
languages: ['yaml'],
customLanguages: [
{
label: 'yaml',
entry: 'monaco-yaml',
worker: {
id: 'monaco-yaml/yamlWorker',
entry: 'monaco-yaml/yaml.worker'
}
}
]
}
]);
}
export function compressionPluginConfig(config: any) {
return config
.plugin('compression-webpack-plugin')
.use(CompressionWebpackPlugin, [
{
filename: '[path][base].gz',
algorithm: 'gzip',
test: /\.(js|css|html|svg)$/,
threshold: 10240,
minRatio: 0.8
}
]);
}
+6 -1
View File
@@ -1,7 +1,6 @@
const proxyTableList = [ const proxyTableList = [
'cli', 'cli',
'v1', 'v1',
'v2',
'auth', 'auth',
'v1-openai', 'v1-openai',
'version', 'version',
@@ -21,6 +20,12 @@ export default function createProxyTable(target?: string) {
ws: true, ws: true,
log: 'debug', log: 'debug',
pathRewrite: (pth: string) => pth.replace(`/^/${api}`, `/${api}`), pathRewrite: (pth: string) => pth.replace(`/^/${api}`, `/${api}`),
// onProxyRes: (proxyRes: any, req: any, res: any) => {
// console.log('headers=========', {
// res: proxyRes.headers,
// req: req.headers
// });
// },
headers: { headers: {
origin: newTarget, origin: newTarget,
Connection: 'keep-alive' Connection: 'keep-alive'
+37 -188
View File
@@ -5,18 +5,13 @@ export default [
name: 'dashboard', name: 'dashboard',
path: '/dashboard', path: '/dashboard',
key: 'dashboard', key: 'dashboard',
icon: 'icon-dashboard', icon: 'AppstoreOutlined',
selectedIcon: 'icon-dashboard-filled',
defaultIcon: 'icon-dashboard',
access: 'canSeeAdmin', access: 'canSeeAdmin',
component: './dashboard', component: './dashboard'
routes: []
}, },
{ {
name: 'playground', name: 'playground',
icon: 'icon-experiment', icon: 'ExperimentOutlined',
selectedIcon: 'icon-experiment-filled',
defaultIcon: 'icon-experiment',
path: '/playground', path: '/playground',
key: 'playground', key: 'playground',
routes: [ routes: [
@@ -29,39 +24,15 @@ export default [
title: 'Chat', title: 'Chat',
path: '/playground/chat', path: '/playground/chat',
key: 'chat', key: 'chat',
icon: 'icon-chat', icon: 'Comment',
selectedIcon: 'icon-chat-filled',
defaultIcon: 'icon-chat',
component: './playground/index' component: './playground/index'
}, },
{
name: 'embedding',
title: 'embedding',
path: '/playground/embedding',
key: 'embedding',
icon: 'icon-embedding',
selectedIcon: 'icon-embedding-filled',
defaultIcon: 'icon-embedding',
component: './playground/embedding'
},
{
name: 'rerank',
title: 'Rerank',
path: '/playground/rerank',
key: 'rerank',
icon: 'icon-reranker',
selectedIcon: 'icon-reranker-filled',
defaultIcon: 'icon-reranker',
component: './playground/rerank'
},
{ {
name: 'text2images', name: 'text2images',
title: 'Text2Images', title: 'Text2Images',
path: keepAliveRoutes.text2images, path: keepAliveRoutes.text2images,
key: 'text2images', key: 'text2images',
icon: 'icon-image1', icon: 'Comment',
selectedIcon: 'icon-image-filled',
defaultIcon: 'icon-image1',
component: './playground/images' component: './playground/images'
}, },
{ {
@@ -69,188 +40,66 @@ export default [
title: 'Speech', title: 'Speech',
path: keepAliveRoutes.speech, path: keepAliveRoutes.speech,
key: 'speech', key: 'speech',
icon: 'icon-audio1', icon: 'Comment',
selectedIcon: 'icon-audio-filled',
defaultIcon: 'icon-audio1',
component: './playground/speech' component: './playground/speech'
}
]
}, },
{ {
name: 'models', name: 'embedding',
path: '/models', title: 'embedding',
key: 'models', path: '/playground/embedding',
routes: [ key: 'embedding',
icon: 'Comment',
component: './playground/embedding'
},
{ {
path: '/models', name: 'rerank',
redirect: '/models/deployments' title: 'Rerank',
path: '/playground/rerank',
key: 'rerank',
icon: 'Comment',
component: './playground/rerank'
}
]
}, },
{ {
name: 'modelCatalog', name: 'modelCatalog',
path: '/models/catalog', path: '/models/catalog',
key: 'modelsCatalog', key: 'modelsCatalog',
icon: 'icon-layers', icon: 'icon-catalog',
selectedIcon: 'icon-layers-filled',
defaultIcon: 'icon-layers',
access: 'canSeeAdmin', access: 'canSeeAdmin',
component: './llmodels/catalog' component: './llmodels/catalog'
}, },
{ {
name: 'deployment', name: 'models',
path: '/models/deployments', path: '/models/list',
key: 'modelDeployments', key: 'models',
icon: 'icon-rocket-launch1', icon: 'Block',
selectedIcon: 'icon-rocket-launch-fill',
defaultIcon: 'icon-rocket-launch1',
access: 'canSeeAdmin', access: 'canSeeAdmin',
component: './llmodels/index' component: './llmodels/index'
}, },
{
name: 'userModels',
path: '/models/user-models',
key: 'userModels',
icon: 'icon-models',
selectedIcon: 'icon-models-filled',
defaultIcon: 'icon-models',
access: 'canSeeUser',
component: './llmodels/user-models'
}
]
},
{ {
name: 'resources', name: 'resources',
path: '/resources', path: '/resources',
key: 'resources', key: 'resources',
icon: 'CloudServer',
access: 'canSeeAdmin', access: 'canSeeAdmin',
routes: [ component: './resources'
{
path: '/resources',
redirect: '/resources/workers'
},
{
name: 'workers',
path: '/resources/workers',
key: 'workers',
icon: 'icon-resources',
selectedIcon: 'icon-resources-filled',
defaultIcon: 'icon-resources',
component: './resources/components/workers'
},
{
name: 'gpus',
path: '/resources/gpus',
key: 'gpus',
icon: 'icon-gpu1',
selectedIcon: 'icon-gpu-filled',
defaultIcon: 'icon-gpu1',
component: './resources/components/gpus'
},
{
name: 'backendsList',
path: '/resources/backends',
key: 'backendsList',
icon: 'icon-backend',
selectedIcon: 'icon-backend-filled',
defaultIcon: 'icon-backend',
access: 'canSeeAdmin',
component: './backends/index'
},
{
name: 'modelfiles',
path: '/resources/modelfiles',
key: 'modelfiles',
icon: 'icon-files',
selectedIcon: 'icon-files-filled',
defaultIcon: 'icon-files',
component: './resources/components/model-files'
}
]
},
{
name: 'clusterManagement',
path: '/cluster-management',
key: 'clusterManagement',
access: 'canSeeAdmin',
routes: [
{
path: '/cluster-management',
redirect: '/cluster-management/clusters/list'
},
{
name: 'clusters',
path: '/cluster-management/clusters/list',
key: 'clusters',
icon: 'icon-cluster2-outline',
selectedIcon: 'icon-cluster2-filled',
defaultIcon: 'icon-cluster2-outline',
component: './cluster-management/clusters',
subMenu: [
'/cluster-management/clusters/detail',
'/cluster-management/clusters/create'
]
},
{
name: 'clusterDetail',
path: '/cluster-management/clusters/detail',
key: 'clusterDetail',
icon: 'icon-cluster2-outline',
selectedIcon: 'icon-cluster2-filled',
defaultIcon: 'icon-cluster2-outline',
hideInMenu: true,
component: './cluster-management/cluster-detail'
},
{
name: 'clusterCreate',
path: '/cluster-management/clusters/create',
key: 'clusterCreate',
icon: 'icon-cluster2-outline',
selectedIcon: 'icon-cluster2-filled',
defaultIcon: 'icon-cluster2-outline',
hideInMenu: true,
component: './cluster-management/cluster-create'
},
{
name: 'credentials',
path: '/cluster-management/credentials',
key: 'credentials',
icon: 'icon-credential-outline',
selectedIcon: 'icon-credential-filled',
defaultIcon: 'icon-credential-outline',
component: './cluster-management/credentials'
}
]
},
{
name: 'accessControl',
path: '/access-control',
key: 'accessControl',
access: 'canSeeAdmin',
routes: [
{
path: '/access-control',
redirect: '/access-control/users'
},
{
name: 'users',
path: '/access-control/users',
key: 'users',
icon: 'icon-users',
selectedIcon: 'icon-users-filled',
defaultIcon: 'icon-users',
component: './users'
}
]
}, },
{ {
name: 'apikeys', name: 'apikeys',
path: '/api-keys', path: '/api-keys',
key: 'apikeys', key: 'apikeys',
hideInMenu: true, icon: 'KeyOutlined',
selectedIcon: 'icon-key-filled',
icon: 'icon-key',
defaultIcon: 'icon-key',
component: './api-keys' component: './api-keys'
}, },
{
name: 'users',
path: '/users',
key: 'users',
icon: 'Team',
access: 'canSeeAdmin',
component: './users'
},
{ {
name: 'profile', name: 'profile',
path: '/profile', path: '/profile',
+4 -20
View File
@@ -31,23 +31,19 @@
"@xterm/xterm": "^5.5.0", "@xterm/xterm": "^5.5.0",
"ahooks": "^3.8.5", "ahooks": "^3.8.5",
"ansi-to-html": "^0.7.2", "ansi-to-html": "^0.7.2",
"antd": "^5.25.4", "antd": "^5.21.6",
"antd-style": "^3.6.2", "antd-style": "^3.6.2",
"axios": "^1.8.2", "axios": "^1.7.2",
"classnames": "^2.5.1", "classnames": "^2.5.1",
"clipboard": "^2.0.11", "clipboard": "^2.0.11",
"crypto-js": "^4.2.0", "crypto-js": "^4.2.0",
"dayjs": "^1.11.11", "dayjs": "^1.11.11",
"dompurify": "^3.2.6",
"driver.js": "^1.3.1", "driver.js": "^1.3.1",
"echarts": "^5.5.1", "echarts": "^5.5.1",
"epubjs": "^0.3.93", "epubjs": "^0.3.93",
"file-saver": "^2.0.5",
"has-ansi": "^5.0.1", "has-ansi": "^5.0.1",
"highlight.js": "^11.10.0", "highlight.js": "^11.10.0",
"jdenticon": "^3.3.0",
"jotai": "^2.8.4", "jotai": "^2.8.4",
"js-yaml": "^4.1.0",
"jszip": "^3.10.1", "jszip": "^3.10.1",
"katex": "^0.16.21", "katex": "^0.16.21",
"lamejs": "github:zhuker/lamejs", "lamejs": "github:zhuker/lamejs",
@@ -55,41 +51,34 @@
"lodash": "^4.17.21", "lodash": "^4.17.21",
"mammoth": "^1.8.0", "mammoth": "^1.8.0",
"marked": "^14.1.0", "marked": "^14.1.0",
"minimatch": "^10.0.3",
"ml-dataset-iris": "^1.2.1", "ml-dataset-iris": "^1.2.1",
"ml-pca": "^4.1.1", "ml-pca": "^4.1.1",
"monaco-editor": "^0.53.0",
"monaco-editor-webpack-plugin": "^7.1.0",
"monaco-yaml": "^5.4.0",
"numeral": "^2.0.6", "numeral": "^2.0.6",
"overlayscrollbars": "^2.10.0", "overlayscrollbars": "^2.10.0",
"overlayscrollbars-react": "^0.5.6", "overlayscrollbars-react": "^0.5.6",
"pdfjs-dist": "^4.7.76", "pdfjs-dist": "^4.7.76",
"query-string": "^9.0.0", "query-string": "^9.0.0",
"rc-resize-observer": "^1.4.3", "rc-resize-observer": "^1.4.0",
"rc-virtual-list": "^3.14.8", "rc-virtual-list": "^3.14.8",
"re-resizable": "^6.10.1", "re-resizable": "^6.10.1",
"react": "^18.2.0", "react": "^18.2.0",
"react-dom": "^18.2.0", "react-dom": "^18.2.0",
"react-hotkeys-hook": "^4.5.0", "react-hotkeys-hook": "^4.5.0",
"react-intersection-observer": "^9.16.0",
"react-markdown": "^9.0.3", "react-markdown": "^9.0.3",
"rehype-katex": "^7.0.1", "rehype-katex": "^7.0.1",
"remark-breaks": "^4.0.0",
"remark-gfm": "^4.0.1", "remark-gfm": "^4.0.1",
"remark-math": "^6.0.0", "remark-math": "^6.0.0",
"simplebar-react": "^3.2.6", "simplebar-react": "^3.2.6",
"styled-components": "^6.1.15", "styled-components": "^6.1.15",
"tinycolor2": "^1.6.0",
"umi-presets-pro": "^2.0.3", "umi-presets-pro": "^2.0.3",
"wavesurfer.js": "^7.8.8", "wavesurfer.js": "^7.8.8",
"xlsx": "https://cdn.sheetjs.com/xlsx-0.20.3/xlsx-0.20.3.tgz" "xlsx": "https://cdn.sheetjs.com/xlsx-0.20.3/xlsx-0.20.3.tgz"
}, },
"devDependencies": { "devDependencies": {
"@types/marked": "^6.0.0",
"@types/react": "^18.3.1", "@types/react": "^18.3.1",
"@types/react-dom": "^18.3.0", "@types/react-dom": "^18.3.0",
"@umijs/case-sensitive-paths-webpack-plugin": "^1.0.1", "@umijs/case-sensitive-paths-webpack-plugin": "^1.0.1",
"@umijs/plugins": "^4.4.11",
"babel-plugin-named-asset-import": "^0.3.8", "babel-plugin-named-asset-import": "^0.3.8",
"case-sensitive-paths-webpack-plugin": "^2.4.0", "case-sensitive-paths-webpack-plugin": "^2.4.0",
"compression-webpack-plugin": "^11.1.0", "compression-webpack-plugin": "^11.1.0",
@@ -111,10 +100,5 @@
"url-loader": "^4.1.1", "url-loader": "^4.1.1",
"webpack-bundle-analyzer": "^4.10.2", "webpack-bundle-analyzer": "^4.10.2",
"worker-loader": "^3.0.8" "worker-loader": "^3.0.8"
},
"pnpm": {
"overrides": {
"elliptic": "^6.6.1"
}
} }
} }
+9059 -5218
View File
File diff suppressed because it is too large Load Diff
Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.0 KiB

-7
View File
@@ -4,15 +4,8 @@ export default (initialState: { currentUser?: Global.UserInfo }) => {
initialState.currentUser && initialState.currentUser &&
initialState.currentUser.is_admin initialState.currentUser.is_admin
); );
const canSeeUser = !!(
initialState &&
initialState.currentUser &&
!initialState.currentUser.is_admin
);
return { return {
canSeeAdmin, canSeeAdmin,
canSeeUser,
canDelete: true, canDelete: true,
canLogin: true canLogin: true
}; };
+10 -28
View File
@@ -1,6 +1,5 @@
import { GPUStackVersionAtom, UpdateCheckAtom } from '@/atoms/user'; import { GPUStackVersionAtom, UpdateCheckAtom } from '@/atoms/user';
import { setAtomStorage } from '@/atoms/utils'; import { setAtomStorage } from '@/atoms/utils';
import { DEFAULT_ENTER_PAGE, GPUSTACK_API_BASE_URL } from '@/config/settings';
import { requestConfig } from '@/request-config'; import { requestConfig } from '@/request-config';
import { import {
queryCurrentUserState, queryCurrentUserState,
@@ -14,7 +13,8 @@ import {
writeState writeState
} from '@/utils/localstore/index'; } from '@/utils/localstore/index';
import { RequestConfig, history } from '@umijs/max'; import { RequestConfig, history } from '@umijs/max';
import { message } from 'antd';
const loginPath = '/login';
// only for the first login and access from http://localhost // only for the first login and access from http://localhost
@@ -23,7 +23,7 @@ const checkDefaultPage = async (userInfo: any) => {
if (isFirstLogin === null && isOnline()) { if (isFirstLogin === null && isOnline()) {
writeState(IS_FIRST_LOGIN, true); writeState(IS_FIRST_LOGIN, true);
if (userInfo && userInfo?.is_admin) { if (userInfo && userInfo?.is_admin) {
history.push(DEFAULT_ENTER_PAGE.adminForFirst); history.push('/models/list');
} }
} }
}; };
@@ -48,9 +48,7 @@ export async function getInitialState(): Promise<{
} }
}; };
const fetchUserInfo = async (config?: { const fetchUserInfo = async (): Promise<Global.UserInfo> => {
skipErrorHandler?: boolean;
}): Promise<Global.UserInfo> => {
try { try {
const data = await queryCurrentUserState({ const data = await queryCurrentUserState({
skipErrorHandler: true skipErrorHandler: true
@@ -59,19 +57,8 @@ export async function getInitialState(): Promise<{
getUpdateCheck(); getUpdateCheck();
} }
return data; return data;
} catch (error: any) { } catch (error) {
const data = error?.response?.data; history.push(loginPath);
if (data?.code === 401 && data?.message.includes('deactivate')) {
message.error({
content: (
<div>
<span>{data?.message}</span>
</div>
),
duration: 5
});
}
history.push(DEFAULT_ENTER_PAGE.login);
} }
return {} as Global.UserInfo; return {} as Global.UserInfo;
}; };
@@ -79,15 +66,10 @@ export async function getInitialState(): Promise<{
const getAppVersionInfo = async () => { const getAppVersionInfo = async () => {
try { try {
const data = await queryVersionInfo(); const data = await queryVersionInfo();
const isProduction = data.version?.indexOf('0.0.0') === -1;
const isDev = data.version?.indexOf('0.0.0') > -1;
const isRc = data.version?.indexOf('rc') > -1;
setAtomStorage(GPUStackVersionAtom, { setAtomStorage(GPUStackVersionAtom, {
...data, ...data,
isProd: !isDev && !isRc, isProduction
isDev,
isRc
}); });
} catch (error) { } catch (error) {
console.error('queryVersionInfo error', error); console.error('queryVersionInfo error', error);
@@ -96,7 +78,7 @@ export async function getInitialState(): Promise<{
getAppVersionInfo(); getAppVersionInfo();
if (![DEFAULT_ENTER_PAGE.login].includes(location.pathname)) { if (![loginPath].includes(location.pathname)) {
const userInfo = await fetchUserInfo(); const userInfo = await fetchUserInfo();
checkDefaultPage(userInfo); checkDefaultPage(userInfo);
return { return {
@@ -110,6 +92,6 @@ export async function getInitialState(): Promise<{
} }
export const request: RequestConfig = { export const request: RequestConfig = {
baseURL: `/${GPUSTACK_API_BASE_URL}`, baseURL: ' /v1',
...requestConfig ...requestConfig
}; };
Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 27 KiB

After

Width:  |  Height:  |  Size: 14 KiB

-1
View File
@@ -1 +0,0 @@
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1755020187776" class="icon" viewBox="0 0 1105 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="7392" xmlns:xlink="http://www.w3.org/1999/xlink" width="138.125" height="128"><path d="M507.2896 133.44768l130.98496-71.4496v820.9408l-130.97984 69.71392V133.44768z" fill="#F38019" p-id="7393"></path><path d="M479.39072 323.968a724.30592 724.30592 0 0 0-77.056 16.6144C118.35904 417.01376 37.77536 603.8016 67.95776 701.696c30.11584 97.8944 161.03424 263.2192 439.2704 250.19904 0.13824-27.41248 0.13824-57.73824 0-90.89536-200.49408-26.37824-302.67904-88.82688-306.55488-187.40736-5.74464-147.74272 134.1696-216.90368 217.87136-229.0176 16.82432-2.49344 37.66272-5.67808 61.19936-8.6528l-0.27648-111.9488h-0.07168z m185.12384 107.65312c52.1984 5.6832 103.57248 18.69312 147.0464 44.44672-10.65984 5.19168-34.26816 19.73248-70.82496 43.47904l290.49344 66.39104-16.54272-206.58176-78.37184 44.30336c-116.10112-57.8048-205.19936-91.0336-267.29984-99.6864l-4.50048 107.648z" fill="#ADAFB3" p-id="7394"></path></svg>

Before

Width:  |  Height:  |  Size: 1.1 KiB

-1
View File
@@ -1 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" height="24px" viewBox="0 -960 960 960" width="24px" fill="#1f1f1f"><path d="M140-180v-88.92q0-29.39 15.96-54.43 15.96-25.03 42.66-38.49 59.3-29.08 119.65-43.62Q378.62-420 440-420q21.06 0 42.12 1.89 21.05 1.88 42.11 5.65-1.69 51.46 20.81 96.81Q567.54-270.31 609-240v60H140ZM753.85-72.31l-53.35-53.14v-164.73q-39.11-11.51-63.85-43.97-24.73-32.46-24.73-74.7 0-51.46 36.38-87.84t87.85-36.38q51.46 0 87.66 36.39Q860-460.28 860-408.79q0 39.94-22.42 70.71-22.43 30.77-57.2 44.23l44.23 44.23-53.07 53.2 53.07 53.19-70.76 70.92ZM440-484.62q-57.75 0-98.87-41.12Q300-566.86 300-624.61q0-57.75 41.13-98.88 41.12-41.12 98.87-41.12 57.75 0 98.87 41.12Q580-682.36 580-624.61q0 57.75-41.13 98.87-41.12 41.12-98.87 41.12Zm296.15 93.47q14.7 0 25.04-10.54 10.35-10.54 10.35-25.23 0-14.69-10.35-25.04-10.34-10.35-25.04-10.35-14.69 0-25.23 10.35-10.54 10.35-10.54 25.04t10.54 25.23q10.54 10.54 25.23 10.54Z"/></svg>

Before

Width:  |  Height:  |  Size: 934 B

-8
View File
@@ -1,8 +0,0 @@
<?xml version="1.0" encoding="utf-8"?><!-- Uploaded to: SVG Repo, www.svgrepo.com, Generator: SVG Repo Mixer Tools -->
<svg width="800px" height="800px" viewBox="0 0 16 16" xmlns="http://www.w3.org/2000/svg" fill="none">
<g fill="#C22E33">
<path d="M7.754 2l.463.41c.343.304.687.607 1.026.915C11.44 5.32 13.3 7.565 14.7 10.149c.072.132.137.268.202.403l.098.203-.108.057-.081-.115-.21-.299-.147-.214c-1.019-1.479-2.04-2.96-3.442-4.145a6.563 6.563 0 00-1.393-.904c-1.014-.485-1.916-.291-2.69.505-.736.757-1.118 1.697-1.463 2.653-.045.123-.092.245-.139.367l-.082.215-.172-.055c.1-.348.192-.698.284-1.049.21-.795.42-1.59.712-2.356.31-.816.702-1.603 1.093-2.39.169-.341.338-.682.5-1.025h.092z"/>
<path d="M8.448 11.822c-1.626.77-5.56 1.564-7.426 1.36C.717 11.576 3.71 4.05 5.18 2.91l-.095.218a4.638 4.638 0 01-.138.303l-.066.129c-.76 1.462-1.519 2.926-1.908 4.53a7.482 7.482 0 00-.228 1.689c-.01 1.34.824 2.252 2.217 2.309.67.027 1.347-.043 2.023-.114.294-.03.587-.061.88-.084.108-.008.214-.021.352-.039l.231-.028z"/>
<path d="M3.825 14.781c-.445.034-.89.068-1.333.108 4.097.39 8.03-.277 11.91-1.644-1.265-2.23-2.97-3.991-4.952-5.522.026.098.084.169.141.239l.048.06c.17.226.348.448.527.67.409.509.818 1.018 1.126 1.578.778 1.42.356 2.648-1.168 3.296-1.002.427-2.097.718-3.18.892-1.03.164-2.075.243-3.119.323z"/>
</g>
</svg>

Before

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 33 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 28 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 9.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 393 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 46 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 13 KiB

-42
View File
@@ -104,11 +104,6 @@
justify-content: space-between; justify-content: space-between;
} }
.justify-between {
display: flex;
justify-content: space-between;
}
.justify-center { .justify-center {
display: flex; display: flex;
justify-content: center; justify-content: center;
@@ -169,10 +164,6 @@
gap: 8px; gap: 8px;
} }
.gap-4 {
gap: 4px;
}
.gap-20 { .gap-20 {
gap: 20px; gap: 20px;
} }
@@ -213,10 +204,6 @@
font-size: 20px; font-size: 20px;
} }
.font-size-18 {
font-size: 18px;
}
.font-size-24 { .font-size-24 {
font-size: 24px; font-size: 24px;
} }
@@ -229,10 +216,6 @@
margin-bottom: 0; margin-bottom: 0;
} }
.m-t-0 {
margin-top: 0;
}
.m-b-16 { .m-b-16 {
margin-bottom: 16px; margin-bottom: 16px;
} }
@@ -257,10 +240,6 @@
font-weight: var(--font-weight-bold); font-weight: var(--font-weight-bold);
} }
.text-primary {
color: var(--ant-color-text);
}
.text-secondary { .text-secondary {
color: var(--ant-color-text-secondary); color: var(--ant-color-text-secondary);
} }
@@ -269,10 +248,6 @@
color: var(--ant-color-text-tertiary); color: var(--ant-color-text-tertiary);
} }
.text-quaternary {
color: var(--ant-color-text-quaternary);
}
.tips-desc-list { .tips-desc-list {
list-style-type: decimal; list-style-type: decimal;
padding-left: 16px; padding-left: 16px;
@@ -340,20 +315,3 @@ textarea:hover {
.ant-result-extra { .ant-result-extra {
color: var(--ant-color-text-secondary); color: var(--ant-color-text-secondary);
} }
.text-success {
color: var(--ant-color-success);
}
.desc-fill {
font-weight: 600;
color: var(--ant-color-text);
background-color: var(--ant-color-fill-content);
line-height: 1.5;
padding: 2px 6px;
border-radius: 4px;
}
.line-6 {
line-height: 24px;
}
+2 -1
View File
@@ -3,7 +3,8 @@
.ant-layout-sider-children { .ant-layout-sider-children {
border-inline: none; border-inline: none;
border-radius: 0; border-radius: 0;
padding-block-end: 8px; // padding-inline: 16px;
padding-block-end: 12px;
} }
.umi-plugin-layout-right { .umi-plugin-layout-right {
-65
View File
@@ -1,65 +0,0 @@
import { atom } from 'jotai';
// models expand keys: create, update , delete,
export const expandKeysAtom = atom<string[]>([]);
export const regionListAtom = atom<
{
datacenter: string;
label: string;
value: string;
icon: string;
sizes: string[];
}[]
>([]);
export const regionInstanceTypeListAtom = atom<
{
label: string;
value: string;
description: string;
specInfo: Record<string, any>;
vendor: string;
available: boolean;
regions: string[];
}[]
>([]);
export const regionOSImageListAtom = atom<
{
label: string;
value: string;
os_image: string;
name: string;
description: string;
vendor: string;
specInfo: Record<string, any>;
regions: string[];
}[]
>([]);
export const allRegionOSImageListAtom = atom<
{
label: string;
value: string;
regions: string[];
}[]
>([]);
export const allRegionInstanceTypeListAtom = atom<
{
label: string;
value: string;
regions: string[];
}[]
>([]);
export const fromClusterCreationAtom = atom(false);
/**
* for temporary cluster session storage during creation/editing
*/
export const clusterSessionAtom = atom<{
firstAddWorker: boolean;
firstAddCluster: boolean;
} | null>(null);
+1 -49
View File
@@ -1,52 +1,4 @@
import { BackendOption } from '@/pages/llmodels/config/types'; import { atom } from 'jotai';
import { atom, getDefaultStore } from 'jotai';
// models expand keys: create, update , delete, // models expand keys: create, update , delete,
export const modelsExpandKeysAtom = atom<string[]>([]); export const modelsExpandKeysAtom = atom<string[]>([]);
export const modelsSessionAtom = atom<Record<string, any>>({});
export const requestIdAtom = atom<number>(0);
export const setRquestId = () => {
const store = getDefaultStore();
const id = Date.now();
store.set(requestIdAtom, id);
return id;
};
export const getRequestId = () => {
const store = getDefaultStore();
return store.get(requestIdAtom);
};
// store for cluster list: res.items from api
export const clusterListAtom = atom<
{
label: string;
value: number;
provider: string;
state: string;
is_default: boolean;
workers: number;
ready_workers: number;
gpus: number;
}[]
>([]);
// store for worker list: res.items from api
export const workerListAtom = atom<
{
label: string;
value: number;
cluster_id: number;
state: string;
id: number;
labels: Record<string, any>;
name: string;
}[]
>([]);
export const backendOptionsAtom = atom<BackendOption[]>([]);
export const resourceOverviewAtom = atom<Record<string, any>>({});
+8 -12
View File
@@ -1,4 +1,4 @@
import { COLOR_PRIMARY } from '@/config/theme'; import { colorPrimary } from '@/config/theme';
import { atom } from 'jotai'; import { atom } from 'jotai';
import { atomWithStorage } from 'jotai/utils'; import { atomWithStorage } from 'jotai/utils';
@@ -7,17 +7,13 @@ type UserSettings = {
mode: 'light' | 'realDark' | 'auto'; mode: 'light' | 'realDark' | 'auto';
colorPrimary: string; colorPrimary: string;
isDarkTheme: boolean; isDarkTheme: boolean;
collapsed: boolean;
hideAddResourceModal?: boolean;
}; };
export const defaultSettings: UserSettings = { const defaultSettings: UserSettings = {
theme: 'light', theme: 'light',
mode: 'auto', mode: 'auto',
isDarkTheme: false, isDarkTheme: false,
colorPrimary: COLOR_PRIMARY, colorPrimary: colorPrimary
collapsed: false,
hideAddResourceModal: false
}; };
export const getStorageUserSettings = () => { export const getStorageUserSettings = () => {
@@ -35,9 +31,10 @@ export const getStorageUserSettings = () => {
} }
}; };
export const userSettingsAtom = atomWithStorage<UserSettings>('userSettings', { export const userSettingsAtom = atomWithStorage<UserSettings>(
...getStorageUserSettings() 'userSettings',
}); defaultSettings
);
export const userSettingsHelperAtom = atom( export const userSettingsHelperAtom = atom(
(get) => get(userSettingsAtom), (get) => get(userSettingsAtom),
@@ -49,9 +46,8 @@ export const userSettingsHelperAtom = atom(
}; };
set(userSettingsAtom, { set(userSettingsAtom, {
...newSettings, ...newSettings,
colorPrimary: newSettings.colorPrimary || COLOR_PRIMARY, colorPrimary: newSettings.colorPrimary || colorPrimary,
isDarkTheme: newSettings.theme === 'realDark' isDarkTheme: newSettings.theme === 'realDark'
}); });
} }
); );
export const hideModalTemporarilyAtom = atom<boolean>(false);
+2 -6
View File
@@ -6,15 +6,11 @@ export const userAtom = atomWithStorage<any>('userInfo', null);
export const GPUStackVersionAtom = atom<{ export const GPUStackVersionAtom = atom<{
version: string; version: string;
git_commit: string; git_commit: string;
isProd: boolean; isProduction: boolean;
isDev?: boolean;
isRc?: boolean;
}>({ }>({
version: '', version: '',
git_commit: '', git_commit: '',
isProd: false, isProduction: false
isDev: false,
isRc: false
}); });
export const UpdateCheckAtom = atom<{ export const UpdateCheckAtom = atom<{
-33
View File
@@ -1,38 +1,5 @@
import { defaultSettings } from '@/atoms/settings';
import { getDefaultStore } from 'jotai'; import { getDefaultStore } from 'jotai';
export const clearStorageUserSettings = () => {
try {
const savedSettings = JSON.parse(
localStorage.getItem('userSettings') || '{}'
);
localStorage.setItem(
'userSettings',
JSON.stringify({
...savedSettings,
hideAddResourceModal: false,
colorPrimary: undefined
})
);
} catch (error) {
console.log('Error clearing user settings:', error);
}
};
export const resetStorageUserSettings = () => {
try {
localStorage.setItem(
'userSettings',
JSON.stringify({
...defaultSettings,
colorPrimary: undefined
})
);
} catch (error) {
console.log('Error clearing user settings:', error);
}
};
export const clearAtomStorage = (atom: any) => { export const clearAtomStorage = (atom: any) => {
if (!atom) { if (!atom) {
return; return;
+72
View File
@@ -0,0 +1,72 @@
.alert-info-block {
padding-block: 6px;
padding-inline: 10px 16px;
position: relative;
padding-left: 32px;
text-align: left;
border-radius: var(--border-radius-base);
margin: 0;
border: 1px solid transparent;
.ant-typography {
margin-bottom: 0;
}
&.danger {
border-color: var(--ant-color-error-border);
background-color: var(--ant-color-error-bg);
}
&.warning {
border-color: var(--ant-color-warning-border);
background-color: var(--ant-color-warning-bg);
}
&.transition {
color: var(--ant-geekblue-7);
background: var(--ant-geekblue-1);
border-color: var(--ant-geekblue-3);
}
&.success {
border: 1px solid var(--ant-color-success);
color: var(--ant-color-success);
background: var(--ant-color-success-bg);
.content.success {
font-weight: var(--font-weight-normal);
}
}
.title {
position: absolute;
left: 0;
top: 0;
display: flex;
height: 32px;
padding: 5px 10px;
border-radius: var(--border-radius-base) var(--border-radius-base) 0 0;
.info-icon {
&.danger {
color: var(--ant-color-error);
}
&.warning {
color: var(--ant-color-warning);
}
&.transition {
color: var(--ant-geekblue-7);
}
&.success {
color: var(--ant-color-success);
}
}
.text {
font-weight: var(--font-weight-bold);
}
}
}
+8 -105
View File
@@ -1,14 +1,10 @@
import { import { WarningFilled } from '@ant-design/icons';
CheckCircleFilled,
LoadingOutlined,
WarningFilled
} from '@ant-design/icons';
import { Typography } from 'antd'; import { Typography } from 'antd';
import { createStyles } from 'antd-style';
import classNames from 'classnames'; import classNames from 'classnames';
import React from 'react'; import React from 'react';
import styled from 'styled-components'; import styled from 'styled-components';
import OverlayScroller, { OverlayScrollerOptions } from '../overlay-scroller'; import OverlayScroller from '../overlay-scroller';
import './block.less';
interface AlertInfoProps { interface AlertInfoProps {
type: Global.MessageType; type: Global.MessageType;
message: React.ReactNode; message: React.ReactNode;
@@ -19,84 +15,8 @@ interface AlertInfoProps {
contentStyle?: React.CSSProperties; contentStyle?: React.CSSProperties;
title?: React.ReactNode; title?: React.ReactNode;
maxHeight?: number; maxHeight?: number;
overlayScrollerProps?: OverlayScrollerOptions;
} }
const useStyles = createStyles(({ token, css }) => {
return {
alertBlockInfo: css`
padding-block: 6px;
padding-inline: 10px 16px;
position: relative;
padding-left: 32px;
text-align: left;
border-radius: ${token.borderRadius}px;
margin: 0;
border: 1px solid transparent;
.ant-typography {
margin-bottom: 0;
}
&.danger {
border-color: ${token.colorErrorBorder};
background-color: ${token.colorErrorBg};
}
&.warning {
border-color: ${token.colorWarningBorder};
background-color: ${token.colorWarningBg};
}
&.transition {
color: ${token.geekblue7};
background: ${token.geekblue1};
border-color: ${token.geekblue3};
}
&.success {
border: 1px solid ${token.colorSuccess};
color: ${token.colorSuccessText};
background: ${token.colorSuccessBg};
.content.success {
font-weight: var(--font-weight-normal);
}
}
.title {
position: absolute;
left: 0;
top: 0;
display: flex;
height: 32px;
padding: 5px 10px;
border-radius: ${token.borderRadius}px ${token.borderRadius}px 0 0;
.info-icon {
&.danger {
color: ${token.colorErrorText};
}
&.warning {
color: ${token.colorWarningText};
}
&.transition {
color: ${token.geekblue7};
}
&.success {
color: ${token.colorSuccessText};
}
}
.text {
font-weight: var(--font-weight-bold);
}
}
`
};
});
const TitleWrapper = styled.div` const TitleWrapper = styled.div`
font-weight: 700; font-weight: 700;
color: var(--ant-color-text); color: var(--ant-color-text);
@@ -109,7 +29,6 @@ const ContentWrapper = styled.div<{ $hasTitle: boolean }>`
? 'var(--ant-color-text-secondary)' ? 'var(--ant-color-text-secondary)'
: 'var(--ant-color-text)'}; : 'var(--ant-color-text)'};
font-weight: var(--font-weight-500); font-weight: var(--font-weight-500);
white-space: pre-line;
`; `;
const AlertInfo: React.FC<AlertInfoProps> = (props) => { const AlertInfo: React.FC<AlertInfoProps> = (props) => {
@@ -122,26 +41,14 @@ const AlertInfo: React.FC<AlertInfoProps> = (props) => {
title, title,
contentStyle, contentStyle,
icon, icon,
maxHeight = 86, maxHeight = 86
overlayScrollerProps = {}
} = props; } = props;
const { styles } = useStyles();
const renderIcon = () => {
if (type === 'transition') {
return <LoadingOutlined />;
}
if (type === 'success') {
return <CheckCircleFilled />;
}
return <WarningFilled />;
};
return ( return (
<> <>
{message ? ( {message ? (
<div <div
className={classNames(styles.alertBlockInfo, type)} className={classNames('alert-info-block', type)}
style={{ ...style }} style={{ ...style }}
> >
<Typography.Paragraph <Typography.Paragraph
@@ -154,17 +61,13 @@ const AlertInfo: React.FC<AlertInfoProps> = (props) => {
> >
<div className={classNames('title', type)}> <div className={classNames('title', type)}>
<span className={classNames('info-icon', type)}> <span className={classNames('info-icon', type)}>
{icon ?? renderIcon()} {icon ?? <WarningFilled />}
</span> </span>
</div> </div>
{title && ( {title && (
<TitleWrapper className="title-text">{title}</TitleWrapper> <TitleWrapper className="title-text">{title}</TitleWrapper>
)} )}
<OverlayScroller <OverlayScroller maxHeight={maxHeight} style={{ ...contentStyle }}>
maxHeight={maxHeight}
style={{ ...contentStyle }}
{...overlayScrollerProps}
>
<ContentWrapper <ContentWrapper
$hasTitle={!!title} $hasTitle={!!title}
className={classNames('content', type)} className={classNames('content', type)}
@@ -179,4 +82,4 @@ const AlertInfo: React.FC<AlertInfoProps> = (props) => {
); );
}; };
export default AlertInfo; export default React.memo(AlertInfo);
-2
View File
@@ -28,8 +28,6 @@ const AlertInfo: React.FC<AlertInfoProps> = (props) => {
} }
} }
style={{ style={{
fontWeight: 400,
whiteSpace: 'pre-line',
textAlign: 'center', textAlign: 'center',
padding: '2px 5px', padding: '2px 5px',
borderRadius: 'var(--border-radius-base)', borderRadius: 'var(--border-radius-base)',
@@ -1,22 +0,0 @@
import React from 'react';
import styled from 'styled-components';
const AudioWrapper = styled.div`
width: 100%;
height: 100%;
display: flex;
align-items: center;
justify-content: flex-start;
`;
const AudioElement: React.FC<any> = (props) => {
return (
<div>
<AudioWrapper>
<audio {...props} controls></audio>
</AudioWrapper>
</div>
);
};
export default AudioElement;
@@ -1,406 +0,0 @@
import { formatTime } from '@/utils/index';
import { DeleteOutlined, DownloadOutlined } from '@ant-design/icons';
import { useIntl } from '@umijs/max';
import { Button, Dropdown, Slider, type MenuProps } from 'antd';
import { createStyles } from 'antd-style';
import { round } from 'lodash';
import React, {
forwardRef,
useCallback,
useEffect,
useImperativeHandle,
useMemo
} from 'react';
import styled from 'styled-components';
import AutoTooltip from '../auto-tooltip';
import IconFont from '../icon-font';
type ActionItem = 'download' | 'delete' | 'speed';
interface AudioPlayerProps {
autoplay?: boolean;
url: string;
speed?: number;
ref?: any;
name: string;
height?: number;
width?: number;
duration?: number;
actions?: ActionItem[];
onDelete?: () => void;
}
const SliderWrapper = styled.div`
width: 100%;
display: flex;
align-items: center;
justify-content: flex-start;
gap: 8px;
.ant-slider {
flex: 1;
}
.time {
color: var(--ant-color-text-tertiary);
}
`;
const useStyles = createStyles(({ css, token }) => {
// @ts-ignore
const isDarkMode = token.darkMode as boolean;
return {
wrapper: css`
position: relative;
min-width: 360px;
height: 54px;
display: flex;
align-items: center;
justify-content: flex-start;
padding: 8px 10px;
background-color: ${isDarkMode
? 'var(--ant-color-fill-secondary)'
: '#F1F3F4'};
border-radius: 28px;
.inner {
width: 100%;
display: flex;
align-items: center;
justify-content: flex-start;
flex: 1;
gap: 8px;
.slider {
display: flex;
flex-direction: column;
justify-content: center;
flex: 1;
.ant-slider {
margin: 0;
}
&:hover {
.ant-slider-handle {
opacity: 1;
transition: opacity 0.3s ease-in-out;
}
}
&:focus-within {
.ant-slider-handle {
opacity: 1;
}
}
}
.ant-slider-handle {
opacity: 0;
&::before {
background-color: var(--ant-color-bg-spotlight);
border-radius: 50%;
}
&::after {
display: none;
}
}
}
`
};
});
const sliderStyles = {
rail: {
borderRadius: '4px',
backgroundColor: 'var(--ant-color-fill-secondary)'
},
track: {
borderRadius: '4px',
backgroundColor: 'var(--ant-color-bg-spotlight)'
}
};
const speedOptions = [
{ label: '1x', value: 1 },
{ label: '2x', value: 2 },
{ label: '3x', value: 3 },
{ label: '4x', value: 4 }
];
const speedConfig = {
min: 0.5,
max: 2,
step: 0.25
};
const AudioPlayer: React.FC<AudioPlayerProps> = forwardRef((props, ref) => {
const intl = useIntl();
const { styles } = useStyles();
const {
autoplay = false,
speed: defaultSpeed = 1,
actions = ['delete'],
name,
onDelete
} = props;
const audioRef = React.useRef<HTMLAudioElement>(null);
const [audioState, setAudioState] = React.useState<{
currentTime: number;
duration: number;
}>({
currentTime: 0,
duration: 0
});
console.log('audioState', name);
const [playOn, setPlayOn] = React.useState<boolean>(false);
const [speakerOn, setSpeakerOn] = React.useState<boolean>(false);
const [volume, setVolume] = React.useState<number>(1);
const [speed, setSpeed] = React.useState<number>(defaultSpeed);
const timer = React.useRef<any>(null);
useImperativeHandle(ref, () => ({
play: () => {
audioRef.current?.play();
},
pause: () => {
audioRef.current?.pause();
}
}));
const handleShowVolume = useCallback(() => {
setSpeakerOn(!speakerOn);
}, [speakerOn]);
const handleSeepdChange = useCallback((value: number | string) => {
setSpeed(value as number);
audioRef.current!.playbackRate = value as number;
}, []);
const handleAudioOnPlay = useCallback(() => {
timer.current = setInterval(() => {
setAudioState((prestate) => {
return {
currentTime: Math.ceil(audioRef.current?.currentTime || 0),
duration:
prestate.duration || Math.ceil(audioRef.current?.duration || 0)
};
});
if (audioRef.current?.paused || audioRef.current?.ended) {
clearInterval(timer.current);
setPlayOn(false);
setAudioState((prestate: any) => {
return {
currentTime: audioRef.current?.ended ? 0 : prestate.currentTime,
duration: prestate.duration
};
});
}
}, 500);
}, []);
const handlePlay = useCallback(() => {
setPlayOn(!playOn);
if (playOn) {
audioRef.current?.pause();
} else {
audioRef.current?.play();
}
}, [playOn]);
const handleFormatVolume = (val?: number) => {
if (val === undefined) {
return `${round(volume * 100)}%`;
}
return `${round(val * 100)}%`;
};
const handleVolumeChange = useCallback((value: number) => {
audioRef.current!.volume = round(value, 2);
setVolume(round(value, 2));
}, []);
const initPlayerConfig = () => {
if (audioRef.current) {
audioRef.current!.volume = volume;
audioRef.current!.playbackRate = speed;
}
};
const handleLoadedMetadata = useCallback(
(data: any) => {
const duration = Math.ceil(audioRef.current?.duration || 0);
setAudioState({
currentTime: 0,
duration:
duration && duration !== Infinity ? duration : props.duration || 0
});
setPlayOn(autoplay);
},
[autoplay, props.duration]
);
const handleCurrentChange = useCallback((val: number) => {
audioRef.current!.currentTime = val;
setAudioState((prestate) => {
return {
currentTime: val,
duration: prestate.duration
};
});
}, []);
const handleReduceSpeed = () => {
setSpeed((pre) => {
if (pre - speedConfig.step < speedConfig.min) {
return speedConfig.min;
}
const next = pre - speedConfig.step;
audioRef.current!.playbackRate = next;
return next;
});
};
const handleAddSpeed = () => {
setSpeed((pre) => {
if (pre + speedConfig.step > speedConfig.max) {
return speedConfig.max;
}
const next = pre + speedConfig.step;
audioRef.current!.playbackRate = next;
return next;
});
};
const handleOnLoad = (e: any) => {
console.log('onload', e);
};
const onDownload = useCallback(() => {
const url = props.url || '';
const filename = props.name;
const link = document.createElement('a');
link.href = url;
link.download = filename || 'audio.mp3'; // Default filename
document.body.appendChild(link);
link.click();
link.remove();
}, [props.url, props.name]);
const items: MenuProps['items'] = useMemo(() => {
return [
{
key: 'download',
label: intl.formatMessage({ id: 'common.button.download' }),
icon: <DownloadOutlined />,
onClick: onDownload
},
{
key: 'speed',
label: intl.formatMessage({ id: 'playground.params.speed' }),
icon: <IconFont type="icon-play-speed"></IconFont>,
children: speedOptions.map((item) => ({
key: item.value,
label: item.label,
onClick: () => handleSeepdChange(item.value)
}))
},
{
key: 'delete',
label: intl.formatMessage({ id: 'common.button.delete' }),
icon: <DeleteOutlined />,
danger: true,
onClick: () => {
if (audioRef.current) {
audioRef.current.pause();
audioRef.current.src = '';
audioRef.current.load();
}
setAudioState({ currentTime: 0, duration: 0 });
setPlayOn(false);
onDelete?.();
}
}
].filter((item) => actions.includes(item.key as ActionItem));
}, [actions, intl, onDownload, onDelete, handleSeepdChange]);
useEffect(() => {
if (audioRef.current) {
initPlayerConfig();
}
}, [audioRef.current]);
useEffect(() => {
return () => {
clearInterval(timer.current);
};
}, []);
return (
<div
className={styles.wrapper}
style={{
width: props.width || '100%',
height: props.height || '60px',
position: 'relative'
}}
>
<div className="inner">
<Button
size="middle"
type="text"
onClick={handlePlay}
shape="circle"
disabled={!audioState?.duration}
icon={
!playOn ? (
<IconFont
type="icon-playcircle-fill"
style={{ fontSize: '24px' }}
></IconFont>
) : (
<IconFont
type="icon-stopcircle-fill"
style={{ fontSize: '24px' }}
></IconFont>
)
}
></Button>
<div className="slider">
<div className="flex-center flex-between file-name">
<AutoTooltip ghost maxWidth={200}>
<span>{name}</span>
</AutoTooltip>
</div>
<SliderWrapper>
<span className="time">{formatTime(audioState.currentTime)}</span>
<Slider
tooltip={{ open: false }}
min={0}
step={1}
styles={sliderStyles}
max={audioState.duration}
value={audioState.currentTime}
onChange={handleCurrentChange}
/>
</SliderWrapper>
</div>
<Dropdown menu={{ items }} trigger={['click']}>
<Button
icon={<IconFont type="icon-more"></IconFont>}
type="text"
size="middle"
shape="circle"
></Button>
</Dropdown>
</div>
<audio
crossOrigin="anonymous"
autoPlay={autoplay}
src={props.url}
ref={audioRef}
preload="metadata"
style={{ opacity: 0, position: 'absolute', left: '-9999px' }}
onPlay={handleAudioOnPlay}
onLoadedMetadata={handleLoadedMetadata}
></audio>
</div>
);
});
export default React.memo(AudioPlayer);
+2 -3
View File
@@ -70,9 +70,8 @@ const AutoImage: React.FC<
setIsError(false); setIsError(false);
}, [props.onLoad]); }, [props.onLoad]);
const handleOnError = useCallback((e: any) => { const handleOnError = useCallback(() => {
setIsError(true); setIsError(true);
e.target.src = fallbackImg;
}, []); }, []);
useEffect(() => { useEffect(() => {
@@ -128,4 +127,4 @@ const AutoImage: React.FC<
); );
}; };
export default AutoImage; export default React.memo(AutoImage);
+1 -1
View File
@@ -87,7 +87,7 @@
} }
.single-image { .single-image {
// height: 100%; height: 100%;
width: inherit; width: inherit;
display: flex; display: flex;
justify-content: center; justify-content: center;
+11 -15
View File
@@ -1,5 +1,5 @@
import { CloseCircleOutlined, LoadingOutlined } from '@ant-design/icons'; import { CloseCircleOutlined } from '@ant-design/icons';
import { Progress, ProgressProps, Spin } from 'antd'; import { Progress, ProgressProps } from 'antd';
import classNames from 'classnames'; import classNames from 'classnames';
import { round } from 'lodash'; import { round } from 'lodash';
import ResizeObserver from 'rc-resize-observer'; import ResizeObserver from 'rc-resize-observer';
@@ -94,17 +94,6 @@ const SingleImage: React.FC<SingleImageProps> = (props) => {
const handleOnLoad = React.useCallback(async () => {}, []); const handleOnLoad = React.useCallback(async () => {}, []);
const renderProgress = () => {
<Progress
percent={round(progress, 0)}
type="dashboard"
size={loadingSize}
steps={{ count: 50, gap: 2 }}
format={() => <span className="font-size-20">{round(progress, 0)}%</span>}
trailColor="var(--ant-color-fill-secondary)"
/>;
};
return ( return (
<ResizeObserver onResize={handleResize}> <ResizeObserver onResize={handleResize}>
<div <div
@@ -146,8 +135,15 @@ const SingleImage: React.FC<SingleImageProps> = (props) => {
overflow: 'hidden' overflow: 'hidden'
}} }}
> >
<Spin <Progress
indicator={<LoadingOutlined style={{ fontSize: 32 }} spin />} percent={round(progress, 0)}
type="dashboard"
size={loadingSize}
steps={{ count: 50, gap: 2 }}
format={() => (
<span className="font-size-20">{round(progress, 0)}%</span>
)}
trailColor="var(--ant-color-fill-secondary)"
/> />
</span> </span>
) : ( ) : (
+6 -21
View File
@@ -8,7 +8,6 @@ import React, {
useRef, useRef,
useState useState
} from 'react'; } from 'react';
import styled from 'styled-components';
import { TooltipOverlayScroller } from '../overlay-scroller'; import { TooltipOverlayScroller } from '../overlay-scroller';
// type TagProps = React.ComponentProps<typeof Tag>; // type TagProps = React.ComponentProps<typeof Tag>;
@@ -23,19 +22,9 @@ interface AutoTooltipProps extends Omit<TagProps, 'title'> {
title?: React.ReactNode; title?: React.ReactNode;
showTitle?: boolean; showTitle?: boolean;
closable?: boolean; closable?: boolean;
radius?: number | string;
filled?: boolean;
tooltipProps?: React.ComponentProps<typeof Tooltip>; tooltipProps?: React.ComponentProps<typeof Tooltip>;
} }
const StyledTag = styled(Tag)`
margin: 0;
&.tag-filled {
border: none;
background-color: var(--ant-color-fill-secondary);
}
`;
const AutoTooltip: React.FC<AutoTooltipProps> = ({ const AutoTooltip: React.FC<AutoTooltipProps> = ({
children, children,
maxWidth = '100%', maxWidth = '100%',
@@ -44,8 +33,6 @@ const AutoTooltip: React.FC<AutoTooltipProps> = ({
title, title,
showTitle = false, showTitle = false,
tooltipProps, tooltipProps,
radius = 12,
filled = false,
...tagProps ...tagProps
}) => { }) => {
const contentRef = useRef<HTMLDivElement>(null); const contentRef = useRef<HTMLDivElement>(null);
@@ -107,7 +94,7 @@ const AutoTooltip: React.FC<AutoTooltipProps> = ({
<TooltipOverlayScroller <TooltipOverlayScroller
toolTipProps={{ toolTipProps={{
...tooltipProps, ...tooltipProps,
destroyOnHidden: false destroyTooltipOnHide: false
}} }}
title={isOverflowing || showTitle ? title || children : false} title={isOverflowing || showTitle ? title || children : false}
> >
@@ -116,14 +103,13 @@ const AutoTooltip: React.FC<AutoTooltipProps> = ({
{children} {children}
</div> </div>
) : ( ) : (
<StyledTag <Tag
{...tagProps} {...tagProps}
className={`${tagProps.className || ''} ${filled ? 'tag-filled' : ''}`}
ref={contentRef} ref={contentRef}
style={{ style={{
...tagStyle,
paddingInline: tagProps.closable ? '8px 22px' : 8, paddingInline: tagProps.closable ? '8px 22px' : 8,
borderRadius: radius, borderRadius: 12
...tagStyle
}} }}
closeIcon={ closeIcon={
tagProps.closable ? ( tagProps.closable ? (
@@ -131,8 +117,7 @@ const AutoTooltip: React.FC<AutoTooltipProps> = ({
style={{ style={{
position: 'absolute', position: 'absolute',
right: 8, right: 8,
top: '50%', top: 6
transform: 'translateY(-50%)'
}} }}
/> />
) : ( ) : (
@@ -141,7 +126,7 @@ const AutoTooltip: React.FC<AutoTooltipProps> = ({
} }
> >
{children} {children}
</StyledTag> </Tag>
)} )}
</TooltipOverlayScroller> </TooltipOverlayScroller>
); );
+1 -1
View File
@@ -2,6 +2,6 @@
border-radius: var(--border-radius-small); border-radius: var(--border-radius-small);
background-color: var(--color-white-1); background-color: var(--color-white-1);
box-shadow: none; box-shadow: none;
padding: 10px 16px; padding: 10px;
border: 1px solid var(--ant-color-border); border: 1px solid var(--ant-color-border);
} }
-109
View File
@@ -1,109 +0,0 @@
import { createStyles } from 'antd-style';
import React from 'react';
import styled from 'styled-components';
const SimpleCardItemWrapper = styled.div`
display: flex;
justify-content: space-between;
align-items: center;
width: 100%;
height: 100%;
gap: 16px;
`;
const useStyles = createStyles(({ css, token }) => ({
wrapper: css`
display: flex;
flex-direction: column;
width: 100%;
height: 100%;
background: ${token.colorBgContainer};
border-radius: ${token.borderRadius}px;
padding: ${token.padding}px;
justify-content: center;
align-items: center;
gap: ${token.padding}px;
&.bordered {
border: 1px solid ${token.colorBorder};
}
.title {
font-size: ${token.fontSize}px;
font-weight: var(--font-weight-medium);
}
.content {
display: flex;
justify-content: center;
align-items: center;
font-size: ${token.fontSize}px;
color: ${token.colorTextSecondary};
gap: 8px;
.icon {
display: inline-block;
width: 10px;
height: 10px;
gap: 10px;
&.roundRect {
border-radius: 2px;
}
&.circle {
border-radius: 50%;
}
}
}
`
}));
export const SimpleCardItem: React.FC<{
title?: string;
content?: React.ReactNode;
style?: React.CSSProperties;
bordered?: boolean;
color?: string;
iconType?: string;
}> = (props) => {
const { styles, cx } = useStyles();
const { title, content, style, bordered, iconType, color } = props;
return (
<div className={cx({ bordered: bordered }, styles.wrapper)} style={style}>
<div className="title">{title}</div>
<div className="content">
<span
className={cx([iconType], 'icon')}
style={{
backgroundColor: color || 'transparent'
}}
></span>
<span>{content}</span>
</div>
</div>
);
};
export const SimpleCard: React.FC<{
dataList: {
label: string;
value: React.ReactNode;
color: string;
iconType: string;
}[];
height?: string | number;
bordered?: boolean;
}> = (props) => {
const { dataList, bordered } = props;
return (
<SimpleCardItemWrapper style={{ height: props.height || '100%' }}>
{dataList.map((item, index) => (
<SimpleCardItem
key={index}
title={item.label}
content={item.value}
bordered={bordered}
color={item.color}
iconType={item.iconType}
></SimpleCardItem>
))}
</SimpleCardItemWrapper>
);
};
-208
View File
@@ -1,208 +0,0 @@
import IconFont from '@/components/icon-font';
import { Card } from 'antd';
import { createStyles } from 'antd-style';
import classNames from 'classnames';
import React, { useEffect, useRef, useState } from 'react';
import styled from 'styled-components';
const CardStyled = styled(Card)`
box-shadow: none !important;
&.isOpen {
.ant-card-head {
border-bottom: 1px solid var(--ant-color-border-secondary);
border-radius: var(--ant-border-radius) var(--ant-border-radius) 0 0;
}
}
.ant-card-head {
cursor: pointer;
background-color: var(--ant-color-fill-quaternary);
border-bottom: none;
border-radius: var(--ant-border-radius);
padding: 0 16px;
&:hover {
background-color: var(--ant-color-fill-secondary);
.del-btn {
display: block;
}
}
}
`;
const useStyles = createStyles(({ css, token }) => {
return {
title: css`
font-weight: 400;
height: 56px;
font-size: var(--font-size-base);
display: flex;
justify-content: space-between;
align-items: center;
cursor: pointer;
`,
expandIcon: css`
display: flex;
align-items: center;
gap: 8px;
`,
subtitle: css`
font-size: 14px;
color: ${token.colorTextSecondary};
`,
content: css`
padding-top: 8px;
`,
left: css`
flex: 1;
`,
right: css`
display: flex;
align-items: center;
gap: 8px;
.del-btn {
display: none;
}
`
};
});
export interface CollapsibleContainerProps {
title?: React.ReactNode;
subtitle?: React.ReactNode;
right?: React.ReactNode;
deleteBtn?: React.ReactNode;
defaultOpen?: boolean;
open?: boolean;
collapsible?: boolean;
showExpandIcon?: boolean;
onToggle?: (open: boolean) => void;
disabled?: boolean;
variant?: 'outlined' | 'borderless' | undefined;
iconPosition?: 'left' | 'right';
className?: string;
children?: React.ReactNode;
styles?: {
body?: React.CSSProperties;
header?: React.CSSProperties;
content?: React.CSSProperties;
};
}
export default function CollapsibleContainer({
title,
subtitle,
right,
deleteBtn,
defaultOpen = true,
open,
onToggle,
disabled = false,
showExpandIcon = true,
variant = 'borderless',
className = '',
collapsible,
iconPosition = 'left',
styles: cardStyles,
children
}: CollapsibleContainerProps) {
const { styles } = useStyles();
const isControlled = typeof open === 'boolean';
const [internalOpen, setInternalOpen] = useState(defaultOpen);
const isOpen = collapsible
? isControlled
? (open as boolean)
: internalOpen
: true;
const toggle = () => {
if (disabled || !collapsible) return;
const next = !isOpen;
if (!isControlled) setInternalOpen(next);
onToggle?.(next);
};
const contentRef = useRef<HTMLDivElement>(null);
const [height, setHeight] = useState(isOpen ? 'auto' : '0px');
const renderIcon = () => {
if (showExpandIcon) {
return (
<IconFont
rotate={isOpen ? 180 : 0}
type="icon-down"
style={{
cursor: disabled ? 'not-allowed' : 'pointer',
fontSize: 12
}}
/>
);
}
return null;
};
const renderTitle = () => {
if (!collapsible) {
return null;
}
return (
<div className={styles.title} onClick={toggle}>
<div className={styles.left}>
<div className={styles.expandIcon}>
{iconPosition === 'left' && renderIcon()}
{title && <div>{title}</div>}
</div>
{subtitle && <div className={styles.subtitle}>{subtitle}</div>}
</div>
<div className={styles.right}>
{right && <span>{right}</span>}
{deleteBtn && <span className="del-btn">{deleteBtn}</span>}
{iconPosition === 'right' && renderIcon()}
</div>
</div>
);
};
useEffect(() => {
if (!collapsible) {
setHeight('auto');
return;
}
if (isOpen) {
const scrollHeight = contentRef.current?.scrollHeight || 0;
setHeight(scrollHeight + 'px');
const timer = setTimeout(() => setHeight('auto'), 200);
return () => clearTimeout(timer);
} else {
const scrollHeight = contentRef.current?.scrollHeight || 0;
setHeight(scrollHeight + 'px');
requestAnimationFrame(() => setHeight('0px'));
return () => {};
}
}, [isOpen, collapsible]);
return (
<CardStyled
className={classNames(className, { collapsible, disabled, isOpen })}
variant={variant}
styles={{
body: {
padding: 0,
...cardStyles?.body
},
header: {
...cardStyles?.header
}
}}
title={renderTitle()}
>
<div
ref={contentRef}
style={{
height: height,
overflow: 'hidden'
}}
>
<div style={{ paddingTop: 8, ...cardStyles?.content }}>{children}</div>
</div>
</CardStyled>
);
}
+3 -1
View File
@@ -55,7 +55,9 @@ const CopyButton: React.FC<CopyButtonProps> = ({
}); });
clipboardRef.current.on('error', (e: any) => { clipboardRef.current.on('error', (e: any) => {
message.error(intl.formatMessage({ id: 'common.copy.fail' }) as string); message.success(
intl.formatMessage({ id: 'common.copy.fail' }) as string
);
e.clearSelection(); e.clearSelection();
}); });
} }
+3 -3
View File
@@ -15,7 +15,7 @@ const fadeOut = keyframes`
} }
`; `;
const Mask = styled.div<{ $isHiding: boolean }>` const Mask = styled.div<{ isHiding: boolean }>`
display: flex; display: flex;
justify-content: center; justify-content: center;
align-items: center; align-items: center;
@@ -26,7 +26,7 @@ const Mask = styled.div<{ $isHiding: boolean }>`
height: 100vh; height: 100vh;
background-color: rgba(59, 59, 59, 1); background-color: rgba(59, 59, 59, 1);
z-index: 9999; z-index: 9999;
animation: ${({ $isHiding }) => ($isHiding ? fadeOut : 'none')} ${duration}s animation: ${({ isHiding }) => (isHiding ? fadeOut : 'none')} ${duration}s
ease forwards; ease forwards;
`; `;
@@ -79,7 +79,7 @@ const DarkModeMask = () => {
return () => el.removeEventListener('animationend', handleAnimationEnd); return () => el.removeEventListener('animationend', handleAnimationEnd);
}, [visible]); }, [visible]);
return visible ? <Mask ref={maskRef} $isHiding={isHiding}></Mask> : null; return visible ? <Mask ref={maskRef} isHiding={isHiding}></Mask> : null;
}; };
export default DarkModeMask; export default DarkModeMask;
+22
View File
@@ -0,0 +1,22 @@
:local(.delete-modal-content) {
display: flex;
font-size: var(--font-size-middle);
:global {
.anticon {
font-size: 20px;
margin-right: 10px;
color: var(--ant-color-warning);
}
.title {
display: flex;
align-items: center;
}
}
}
:local(.content) {
padding-top: 15px;
padding-left: 30px;
}
+29 -73
View File
@@ -9,38 +9,9 @@ import {
message, message,
type ModalFuncProps type ModalFuncProps
} from 'antd'; } from 'antd';
import { createStyles } from 'antd-style'; import { FC, forwardRef, useImperativeHandle, useState } from 'react';
import { forwardRef, useImperativeHandle, useState } from 'react';
import styled from 'styled-components'; import styled from 'styled-components';
import Styles from './index.less';
const useStyles = createStyles(({ css }) => ({
'delete-modal-content': css`
display: flex;
font-size: var(--font-size-middle);
.anticon {
font-size: 20px;
margin-right: 10px;
color: var(--ant-color-warning);
}
.title {
display: flex;
align-items: center;
font-weight: var(--font-weight-500);
}
`,
content: css`
padding-top: 15px;
padding-left: 30px;
color: var(--ant-color-text-secondary);
white-space: pre-line;
word-break: break-all;
span {
color: var(--ant-color-text);
display: flex;
margin-top: 8px;
}
`
}));
const CheckboxWrapper = styled.div` const CheckboxWrapper = styled.div`
margin-top: 20px; margin-top: 20px;
@@ -54,8 +25,12 @@ const CheckboxWrapper = styled.div`
} }
`; `;
interface DeleteModalProps {
ref: any;
}
interface DataOptions { interface DataOptions {
content?: string; content: string;
selection?: boolean; selection?: boolean;
name?: string; name?: string;
okText?: string; okText?: string;
@@ -68,33 +43,28 @@ interface DataOptions {
}; };
} }
interface Configuration { const DeleteModal: FC<DeleteModalProps> = forwardRef((props, ref) => {
checked: boolean;
}
const DeleteModal = forwardRef((props, ref) => {
const intl = useIntl(); const intl = useIntl();
const { styles } = useStyles();
const { saveScrollHeight, restoreScrollHeight } = useBodyScroll(); const { saveScrollHeight, restoreScrollHeight } = useBodyScroll();
const [visible, setVisible] = useState(false); const [visible, setVisible] = useState(false);
const [configuration, setConfiguration] = useState<Configuration>({ const [checked, setChecked] = useState(false);
checked: false
});
const [config, setConfig] = useState<ModalFuncProps & DataOptions>({} as any); const [config, setConfig] = useState<ModalFuncProps & DataOptions>({} as any);
const show = (data: ModalFuncProps & DataOptions) => { useImperativeHandle(ref, () => ({
show: (data: ModalFuncProps & DataOptions) => {
saveScrollHeight(); saveScrollHeight();
setConfig(data); setConfig(data);
setConfiguration({ setChecked(data.checkConfig?.defautlChecked || false);
checked: data.checkConfig?.defautlChecked || false
});
setVisible(true); setVisible(true);
}; },
hide: () => {
const hide = () => {
setVisible(false); setVisible(false);
restoreScrollHeight(); restoreScrollHeight();
}; },
configuration: {
checked: checked
}
}));
const handleCancel = () => { const handleCancel = () => {
setVisible(false); setVisible(false);
@@ -124,12 +94,6 @@ const DeleteModal = forwardRef((props, ref) => {
} }
}; };
useImperativeHandle(ref, () => ({
show,
hide,
configuration
}));
return ( return (
<Modal <Modal
style={{ style={{
@@ -138,16 +102,12 @@ const DeleteModal = forwardRef((props, ref) => {
open={visible} open={visible}
onOk={handleOk} onOk={handleOk}
onCancel={handleCancel} onCancel={handleCancel}
destroyOnHidden={false} destroyOnClose={true}
closeIcon={false} closeIcon={false}
maskClosable={false} maskClosable={false}
keyboard={false} keyboard={false}
width={460} width={460}
styles={{ styles={{}}
footer: {
marginTop: '20px'
}
}}
footer={ footer={
<Space size={20}> <Space size={20}>
<Button onClick={handleCancel} size="middle"> <Button onClick={handleCancel} size="middle">
@@ -155,7 +115,7 @@ const DeleteModal = forwardRef((props, ref) => {
? intl.formatMessage({ id: config.cancelText }) ? intl.formatMessage({ id: config.cancelText })
: intl.formatMessage({ id: 'common.button.cancel' })} : intl.formatMessage({ id: 'common.button.cancel' })}
</Button> </Button>
<Button type="primary" onClick={handleOk} size="middle" danger> <Button type="primary" onClick={handleOk} size="middle">
{config.okText {config.okText
? intl.formatMessage({ id: config.okText }) ? intl.formatMessage({ id: config.okText })
: intl.formatMessage({ id: 'common.button.delete' })} : intl.formatMessage({ id: 'common.button.delete' })}
@@ -163,7 +123,7 @@ const DeleteModal = forwardRef((props, ref) => {
</Space> </Space>
} }
> >
<div className={styles['delete-modal-content']}> <div className={Styles['delete-modal-content']}>
<span className="title"> <span className="title">
<ExclamationCircleFilled /> <ExclamationCircleFilled />
<span> <span>
@@ -174,10 +134,11 @@ const DeleteModal = forwardRef((props, ref) => {
</span> </span>
</div> </div>
<div <div
className={styles['content']} className={Styles['content']}
dangerouslySetInnerHTML={{ dangerouslySetInnerHTML={{
__html: config.content __html:
? intl.formatMessage( config.content &&
intl.formatMessage(
{ {
id: config.operation || '' id: config.operation || ''
}, },
@@ -186,18 +147,13 @@ const DeleteModal = forwardRef((props, ref) => {
name: config.name name: config.name
} }
) )
: ''
}} }}
></div> ></div>
{config.checkConfig && ( {config.checkConfig && (
<CheckboxWrapper> <CheckboxWrapper>
<Checkbox <Checkbox
checked={configuration.checked} checked={checked}
onChange={(e) => onChange={(e) => setChecked(e.target.checked)}
setConfiguration({
checked: e.target.checked
})
}
> >
<span className="check-text"> <span className="check-text">
{intl.formatMessage({ id: config.checkConfig?.checkText })} {intl.formatMessage({ id: config.checkConfig?.checkText })}
@@ -16,9 +16,6 @@ const DropDownActions: React.FC<DropDownProps> = (props) => {
const items = useMemo(() => { const items = useMemo(() => {
return menu?.items?.map((item: any) => ({ return menu?.items?.map((item: any) => ({
..._.omit(item, 'locale'), ..._.omit(item, 'locale'),
icon: item.icon
? React.cloneElement(item.icon, { style: { fontSize: 14 } })
: null,
label: item.locale ? intl.formatMessage({ id: item.label }) : item.label label: item.locale ? intl.formatMessage({ id: item.label }) : item.label
})); }));
}, [menu?.items, intl]); }, [menu?.items, intl]);
+13 -20
View File
@@ -4,7 +4,6 @@ import { Button, Dropdown, Tooltip, type MenuProps } from 'antd';
import classNames from 'classnames'; import classNames from 'classnames';
import _ from 'lodash'; import _ from 'lodash';
import React from 'react'; import React from 'react';
import styled from 'styled-components';
import './index.less'; import './index.less';
type Trigger = 'click' | 'hover'; type Trigger = 'click' | 'hover';
@@ -20,17 +19,6 @@ interface DropdownButtonsProps {
onSelect: (val: any, item?: any) => void; onSelect: (val: any, item?: any) => void;
} }
const DropdownWrapper = styled.div`
display: flex;
flex-direction: column;
background-color: var(--ant-color-bg-elevated);
padding: 5px;
align-items: flex-start;
border-radius: var(--border-radius-base);
box-shadow: var(--ant-box-shadow-secondary);
min-width: 160px;
`;
const DropdownButtons: React.FC<DropdownButtonsProps> = ({ const DropdownButtons: React.FC<DropdownButtonsProps> = ({
items, items,
size = 'middle', size = 'middle',
@@ -76,9 +64,18 @@ const DropdownButtons: React.FC<DropdownButtonsProps> = ({
disabled={disabled} disabled={disabled}
trigger={trigger} trigger={trigger}
type="primary" type="primary"
popupRender={(menus: any) => { dropdownRender={(menus: any) => {
return ( return (
<DropdownWrapper> <div
className="flex flex-column "
style={{
backgroundColor: 'var(--ant-color-bg-elevated)',
padding: 5,
alignItems: 'flex-start',
borderRadius: 'var(--border-radius-base)',
boxShadow: 'var(--ant-box-shadow-secondary)'
}}
>
{_.map(_.tail(items), (item: any) => { {_.map(_.tail(items), (item: any) => {
return ( return (
<Button <Button
@@ -89,17 +86,13 @@ const DropdownButtons: React.FC<DropdownButtonsProps> = ({
key={item.key} key={item.key}
disabled={item.disabled} disabled={item.disabled}
onClick={() => handleMenuClick(item)} onClick={() => handleMenuClick(item)}
style={{ style={{ width: '100%', justifyContent: 'flex-start' }}
width: '100%',
justifyContent: 'flex-start',
paddingInline: 10
}}
> >
{intl.formatMessage({ id: item.label })} {intl.formatMessage({ id: item.label })}
</Button> </Button>
); );
})} })}
</DropdownWrapper> </div>
); );
}} }}
buttonsRender={([leftButton, rightButton]) => [ buttonsRender={([leftButton, rightButton]) => [
@@ -1,19 +0,0 @@
import ComponentsMap from '@/components/seal-form/config/components';
import { SealFormItemProps } from '@/components/seal-form/types';
import { Form } from 'antd';
import React from 'react';
interface FieldItemProps extends SealFormItemProps {
widget: keyof typeof ComponentsMap;
name: string;
}
const FieldItem: React.FC<FieldItemProps> = (props) => {
const { name, widget, required = [], ...rest } = props;
const Component = ComponentsMap[widget];
return <Form.Item name={name}></Form.Item>;
};
export default FieldItem;
@@ -1,59 +0,0 @@
import ComponentsMap from '@/components/seal-form/config/components';
import { FormWidgetProps } from '../config/types';
const FormWidget: React.FC<
FormWidgetProps & {
onChange?: (data: any) => void;
disabled?: boolean;
}
> = ({
widget,
title: label,
required,
placeholder,
options,
description,
enum: enumValues,
style,
value,
min,
max,
status,
checked,
isInFormItems,
disabled,
onChange
}) => {
const Component = ComponentsMap[widget];
const optionList = enumValues?.map((item: string | number) => ({
label: item,
value: item
}));
return Component ? (
<Component
{...{
label,
required,
placeholder,
description,
min,
max
}}
status={status}
isInFormItems={isInFormItems}
disabled={disabled}
options={options || optionList}
value={value}
checked={checked}
style={{
width: '100%',
...style
}}
onChange={onChange}
/>
) : null;
};
export default FormWidget;
@@ -1,175 +0,0 @@
import Wrapper from '@/components/label-selector/wrapper';
import { MinusOutlined } from '@ant-design/icons';
import { Button } from 'antd';
import React, { useEffect, useMemo } from 'react';
import styled from 'styled-components';
import { statusType } from '../config/types';
import FormWidget from './form-widget';
const RowWrapper = styled.div`
display: flex;
align-items: center;
justify-content: space-between;
margin-bottom: 8px;
`;
const WidgetBox = styled.div`
display: flex;
align-items: center;
gap: 8px;
width: 100%;
`;
interface ListMapProps {
minItems?: number;
dataList: any[];
label?: React.ReactNode;
btnText?: string;
requiredFields?: string[];
validateStatusList?: Record<string, statusType>[];
properties: Record<string, any>;
disabled?: boolean;
onAdd?: (data: any[]) => void;
onDelete?: (deletedItem: any, data: any[]) => void;
onChange?: (data: any) => void;
}
interface ListItemProps {
schemaList: any[];
data: Record<string, any>;
disabled?: boolean;
validateStatus?: Record<string, statusType>;
onChange?: (data: any) => void;
}
const ListItem: React.FC<ListItemProps> = ({
schemaList,
data,
onChange,
validateStatus,
disabled
}) => {
const handleValueChange = (name: string, target: any) => {
if (target?.target?.type === 'checkbox') {
const checked = target.target?.checked;
onChange?.({ [name]: checked });
} else {
const value = target?.target ? target.target.value : target;
onChange?.({ [name]: value });
}
};
return (
<>
{schemaList.map((schema: any) => (
<FormWidget
status={validateStatus?.[schema.name]}
widget={schema.type}
{...schema}
disabled={disabled || schema.readOnly}
key={schema.name}
value={data?.[schema.name]}
checked={data?.[schema.name]}
isInFormItems={false}
onChange={(target) => handleValueChange(schema.name, target)}
/>
))}
</>
);
};
const ListMap: React.FC<ListMapProps> = ({
dataList = [],
label,
btnText,
properties = {},
requiredFields = [],
minItems = 0,
validateStatusList = [],
disabled,
onAdd,
onDelete,
onChange
}) => {
const [items, setItems] = React.useState(dataList || []);
const schemaList = useMemo(() => {
const list = Object.entries(properties).map(([key, value]) => ({
...value,
required: requiredFields.includes(key),
name: key
}));
return list;
}, [properties, requiredFields]);
const handleOnAdd = () => {
const keys = Object.keys(properties);
const newItems = [
...items,
{ ...keys.reduce((acc, key) => ({ ...acc, [key]: '' }), {}) }
];
setItems(newItems);
onAdd?.(newItems);
};
const handleDelete = (index: number) => {
const deleteItem = items[index];
const newItems = items.filter((_, i) => i !== index);
setItems(newItems);
onDelete?.(deleteItem, newItems);
};
const handleItemChange = (index: number, data: { [key: string]: any }) => {
const newItems = [...items];
newItems[index] = { ...newItems[index], ...data };
setItems(newItems);
onChange?.(newItems);
};
useEffect(() => {
if (!dataList.length && minItems > 0) {
handleOnAdd();
}
}, []);
useEffect(() => {
setItems(dataList);
}, [dataList]);
return (
<Wrapper
label={label}
btnText={btnText}
onAdd={handleOnAdd}
disabled={disabled}
>
{items.map((item, index) => (
<RowWrapper key={index}>
<WidgetBox>
<ListItem
schemaList={schemaList}
data={item}
validateStatus={validateStatusList?.[index]}
disabled={disabled}
onChange={(value) => handleItemChange(index, value)}
/>
</WidgetBox>
{!disabled && (
<Button
size="small"
type="default"
shape="circle"
style={{
width: 24,
marginLeft: 10,
flex: 'none'
}}
icon={<MinusOutlined />}
onClick={() => handleDelete(index)}
/>
)}
</RowWrapper>
))}
</Wrapper>
);
};
export default ListMap;
@@ -1,39 +0,0 @@
import React from 'react';
// refer to json schema
export interface FieldSchema {
type: 'string' | 'number' | 'boolean' | 'object' | 'array';
title?: string;
name: string;
description?: string;
properties?: Record<string, FieldSchema>;
default?: any;
enum?: string[];
minItems?: number;
maxItems?: number;
items?: FieldSchema[];
widget?: string;
min?: number;
style?: React.CSSProperties;
required?: string[];
}
export type statusType = 'error' | 'warning' | '' | undefined;
export interface FormWidgetProps {
status?: statusType;
isInFormItems?: boolean;
widget: 'Input' | 'Select' | 'Checkbox' | 'InputNumber';
name: string;
title?: string;
required?: boolean;
placeholder?: string;
readOnly?: boolean;
options?: { label: string; value: string | number }[];
description?: string;
enum?: (string | number)[];
style?: React.CSSProperties;
value?: any;
checked?: boolean;
min?: number;
max?: number;
}
@@ -1,33 +0,0 @@
import { useMemo } from 'react';
import { FieldSchema } from '../config/types';
interface ParsedField {
name: (string | number)[];
schema: FieldSchema;
}
const parseSchema = (
schema: Record<string, FieldSchema>,
parentName: (string | number)[] = []
): ParsedField[] => {
const fields: ParsedField[] = [];
Object.entries(schema).forEach(([key, fieldSchema]) => {
const currentName = [...parentName, key];
if (fieldSchema.type === 'object' && fieldSchema.properties) {
fields.push(...parseSchema(fieldSchema.properties, currentName));
} else if (fieldSchema.type === 'array' && fieldSchema.items) {
fields.push({ name: currentName, schema: fieldSchema });
} else {
fields.push({ name: currentName, schema: fieldSchema });
}
});
return fields;
};
const useParsedFields = (schema: Record<string, FieldSchema>) => {
return useMemo(() => parseSchema(schema), [schema]);
};
export default useParsedFields;
@@ -1,61 +0,0 @@
import { useRef } from 'react';
import { statusType } from '../config/types';
export default function useValidateFields(params: {
requiredFields?: string[];
setValidateStatusList: (statusList: { [key: string]: statusType }[]) => void;
}) {
const { requiredFields, setValidateStatusList } = params;
const validationEnabled = useRef(false);
const isEmptyValue = (value: any, key: string) => {
return !value;
};
const validateRule = (value: any, key: string) => {
return true;
};
const listMapValidator = async (_: any, valueList: any) => {
if (!validationEnabled.current) {
return Promise.resolve();
}
const fields = new Set<string>();
const statusList: { [key: string]: statusType }[] = [];
(valueList || []).forEach((item: any, index: number) => {
const status: { [key: string]: statusType } = {};
Object.entries(item || {}).forEach(([key, value]) => {
if (isEmptyValue(value, key)) {
fields.add(key);
if (requiredFields?.includes(key)) {
status[key] = 'error';
} else {
status[key] = '';
}
} else if (validateRule(value, key)) {
status[key] = '';
}
});
statusList.push(status);
});
setValidateStatusList(statusList);
if (fields.size > 0) {
return Promise.reject(`${Array.from(fields).join(', ')} is required`);
}
return Promise.resolve();
};
const toggleValidation = (enabled: boolean) => {
validationEnabled.current = enabled;
};
return {
listMapValidator,
toggleValidation
};
}
-26
View File
@@ -1,26 +0,0 @@
import { Form } from 'antd';
import React from 'react';
import { FieldSchema } from './config/types';
interface DynamicFormProps {
schema: FieldSchema;
onSubmit: (values: any) => void;
}
const DynamicForm: React.FC<DynamicFormProps> = ({ schema, onSubmit }) => {
const form = Form.useFormInstance();
const handleFinish = (values: any) => {
onSubmit(values);
};
return (
<>
<Form form={form} onFinish={handleFinish}>
{/* Render form fields based on schema */}
</Form>
</>
);
};
export default DynamicForm;
+21 -79
View File
@@ -1,6 +1,7 @@
import _, { throttle } from 'lodash'; import { throttle } from 'lodash';
import React, { import React, {
forwardRef, forwardRef,
useCallback,
useEffect, useEffect,
useImperativeHandle, useImperativeHandle,
useRef useRef
@@ -9,16 +10,14 @@ import echarts, { ECOption } from '.';
const Chart: React.FC<{ const Chart: React.FC<{
options: ECOption; options: ECOption;
chartHeight?: number;
height: number | string; height: number | string;
width: number | string; width: number | string;
ref?: any; ref?: any;
}> = forwardRef(({ options, width, height, chartHeight }, ref) => { }> = forwardRef(({ options, width, height }, ref) => {
const container = useRef<HTMLDivElement>(null); const container = useRef<HTMLDivElement>(null);
const chart = useRef<echarts.EChartsType>(); const chart = useRef<echarts.EChartsType>();
const resizeable = useRef(false); const resizeable = useRef(false);
const resizeObserver = useRef<ResizeObserver>(); const resizeObserver = useRef<ResizeObserver>();
const finished = useRef(false);
useImperativeHandle(ref, () => { useImperativeHandle(ref, () => {
return { return {
@@ -26,96 +25,40 @@ const Chart: React.FC<{
}; };
}); });
const init = () => { const init = useCallback(() => {
if (container.current) { if (container.current) {
chart.current?.clear(); chart.current?.clear();
chart.current = echarts.init(container.current); chart.current = echarts.init(container.current);
} }
}; }, []);
const setOption = (options: ECOption) => { const resize = useCallback(() => {
if (!chart.current) return; chart.current?.resize();
}, []);
const setOption = useCallback(
(options: ECOption) => {
chart.current?.clear(); chart.current?.clear();
chart.current?.setOption(options, { chart.current?.setOption(options, {
notMerge: true, notMerge: true,
lazyUpdate: true lazyUpdate: true
}); });
if (Array.isArray(options.yAxis) && options.yAxis.length > 1) { },
chart.current?.resize(); [options]
} );
};
useEffect(() => { useEffect(() => {
const handleOnFinished = () => {
if (!chart.current || finished.current) return;
const currentChart = chart.current;
const optionsYAxis = currentChart.getOption()?.yAxis;
if (
!optionsYAxis ||
!Array.isArray(optionsYAxis) ||
optionsYAxis.length < 2
)
return;
// @ts-ignore
const model = currentChart.getModel();
const yAxisModels = [
model.getComponent('yAxis', 0),
model.getComponent('yAxis', 1)
];
if (!yAxisModels[0] || !yAxisModels[1]) return;
const axes = yAxisModels.map((m) => m.axis);
const intervals = axes.map((axis) => axis.scale.getInterval());
const ticksList = axes.map((axis) => axis.scale.getTicks());
const counts = ticksList.map((t) => t.length);
const unifiedCount = Math.max(counts[0], counts[1]);
const newMax0 = intervals[0] * (unifiedCount - 1);
const newMax1 = intervals[1] * (unifiedCount - 1);
// if newMax0 equal to maxValue0, and newMax1 equal to maxValue1, do not update yAxis
if (counts[0] === counts[1]) return;
const yAxis: any[] = [{}, {}];
if (counts[0] < unifiedCount) {
yAxis[0].max = _.round(newMax0, 2);
yAxis[0].interval = intervals[0];
yAxis[0].splitNumber = unifiedCount;
}
if (counts[1] < unifiedCount) {
yAxis[1].max = _.round(newMax1, 2);
yAxis[1].interval = intervals[1];
yAxis[1].splitNumber = unifiedCount;
}
finished.current = true;
currentChart.setOption({
yAxis: yAxis
});
};
if (container.current) { if (container.current) {
init(); init();
chart.current?.on('finished', handleOnFinished);
} }
return () => { return () => {
chart.current?.off('finished', handleOnFinished);
chart.current?.dispose(); chart.current?.dispose();
}; };
}, []); }, [init]);
useEffect(() => { useEffect(() => {
resizeable.current = false; resizeable.current = false;
finished.current = false; resize();
setOption(options); setOption(options);
resizeable.current = true; resizeable.current = true;
}, [options]); }, [options]);
@@ -129,20 +72,19 @@ const Chart: React.FC<{
if (container.current) { if (container.current) {
resizeObserver.current = new ResizeObserver(handleResize); resizeObserver.current = new ResizeObserver(handleResize);
resizeObserver.current.observe(container.current); resizeObserver.current.observe(container.current);
} }
return () => { return () => {
resizeObserver.current?.disconnect(); if (container.current) {
resizeObserver.current = undefined; resizeObserver.current?.unobserve(container.current);
}
}; };
}, []); }, []);
return ( return (
<div className="chart-wrapper" style={{ width: width, height }}> <div className="chart-wrapper" style={{ width: width, height }}>
<div <div ref={container} style={{ width: width, height }}></div>
ref={container}
style={{ width: width, height: chartHeight || height }}
></div>
</div> </div>
); );
}); });
+25 -14
View File
@@ -1,12 +1,27 @@
import useUserSettings from '@/hooks/use-user-settings'; import useUserSettings from '@/hooks/use-user-settings';
import { formatLargeNumber } from '@/utils';
import { theme } from 'antd'; import { theme } from 'antd';
import { isFunction } from 'lodash'; import { isFunction } from 'lodash';
import { useMemo } from 'react'; import { useMemo } from 'react';
const formatLargeNumber = (value: number) => {
if (typeof value !== 'number' || isNaN(value)) {
return value;
}
if (value >= 1e9) {
return (value / 1e9).toFixed(1).replace(/\.0$/, '') + 'B';
} else if (value >= 1e6) {
return (value / 1e6).toFixed(1).replace(/\.0$/, '') + 'M';
} else if (value >= 1e3) {
return (value / 1e3).toFixed(1).replace(/\.0$/, '') + 'K';
} else {
return value;
}
};
export const grid = { export const grid = {
left: 0, left: 0,
right: 0, right: 20,
bottom: 20, bottom: 20,
containLabel: true containLabel: true
}; };
@@ -16,14 +31,14 @@ export default function useChartConfig() {
const { useToken } = theme; const { useToken } = theme;
const { token } = useToken(); const { token } = useToken();
console.log('token:', token);
const chartColorMap = useMemo(() => { const chartColorMap = useMemo(() => {
return { return {
titleColor: token.colorText, titleColor: token.colorText,
splitLineColor: token.colorBorder, splitLineColor: token.colorBorder,
tickLineColor: token.colorSplit, tickLineColor: token.colorSplit,
axislabelColor: token.colorTextTertiary, axislabelColor: token.colorTextTertiary,
colorSecondary: token.colorTextSecondary,
colorTertiary: token.colorTextTertiary,
gaugeBgColor: token.colorFillSecondary, gaugeBgColor: token.colorFillSecondary,
gaugeSplitLineColor: isDarkTheme gaugeSplitLineColor: isDarkTheme
? 'rgba(255,255,255,.3)' ? 'rgba(255,255,255,.3)'
@@ -41,23 +56,18 @@ export default function useChartConfig() {
borderColor: 'transparent', borderColor: 'transparent',
formatter(params: any, callback?: (val: any) => any) { formatter(params: any, callback?: (val: any) => any) {
let result = `<span class="tooltip-x-name">${params[0].axisValue}</span>`; let result = `<span class="tooltip-x-name">${params[0].axisValue}</span>`;
params.forEach((item: any) => { params.forEach((item: any) => {
let value = isFunction(callback) let value = isFunction(callback)
? callback?.(item.data.value) ? callback?.(item.data.value)
: item.data.value; : item.data.value;
const borderRadius = item.seriesType === 'bar' ? '2px' : '8px';
result += `<span class="tooltip-item"> result += `<span class="tooltip-item">
<span class="tooltip-item-name"> <span class="tooltip-item-name">
<span style="display:inline-block;margin-right:5px;border-radius:${borderRadius};width:8px;height:8px;background-color:${item.color};"></span> <span style="display:inline-block;margin-right:5px;border-radius:8px;width:8px;height:8px;background-color:${item.color};"></span>
<span class="tooltip-title">${item.seriesName}</span>: <span class="tooltip-title">${item.seriesName}</span>:
</span> </span>
<span class="tooltip-value">${value}</span> <span class="tooltip-value">${value}</span>
</span>`; </span>`;
}); });
return `<div class="tooltip-wrapper">${result}</div>`; return `<div class="tooltip-wrapper">${result}</div>`;
} }
}; };
@@ -65,7 +75,6 @@ export default function useChartConfig() {
const legend = { const legend = {
itemWidth: 8, itemWidth: 8,
itemHeight: 8, itemHeight: 8,
itemGap: 12,
textStyle: { textStyle: {
color: chartColorMap.axislabelColor color: chartColorMap.axislabelColor
} }
@@ -89,6 +98,8 @@ export default function useChartConfig() {
}; };
const yAxis = { const yAxis = {
// max: 100,
// min: 0,
nameTextStyle: { nameTextStyle: {
padding: [0, 0, 0, -20] padding: [0, 0, 0, -20]
}, },
@@ -124,6 +135,7 @@ export default function useChartConfig() {
type: 'bar', type: 'bar',
barMaxWidth: 20, barMaxWidth: 20,
barMinWidth: 8, barMinWidth: 8,
// stack: 'total',
barGap: '30%', barGap: '30%',
barCategoryGap: '50%' barCategoryGap: '50%'
}; };
@@ -206,13 +218,13 @@ export default function useChartConfig() {
rich: { rich: {
value: { value: {
fontSize: 16, fontSize: 16,
fontWeight: 500, fontWeight: '500',
color: chartColorMap.titleColor color: chartColorMap.titleColor
}, },
unit: { unit: {
fontSize: 14, fontSize: 14,
color: chartColorMap.titleColor, color: chartColorMap.titleColor,
fontWeight: 500, fontWeight: '500',
padding: [0, 0, 0, 2] padding: [0, 0, 0, 2]
} }
} }
@@ -220,7 +232,6 @@ export default function useChartConfig() {
}; };
return { return {
token,
tooltip, tooltip,
grid, grid,
legend, legend,
+12 -44
View File
@@ -1,19 +1,9 @@
import Chart from '@/components/echarts/chart'; import Chart from '@/components/echarts/chart';
import useChartConfig from '@/components/echarts/config'; import useChartConfig from '@/components/echarts/config';
import EmptyData from '@/components/empty-data'; import EmptyData from '@/components/empty-data';
import React from 'react'; import React, { memo } from 'react';
import { ChartProps } from './types'; import { ChartProps } from './types';
const strokeColorFunc = (percent: number) => {
if (percent <= 50 || percent === undefined) {
return 'rgb(84, 204, 152, 80%)';
}
if (percent <= 80) {
return 'rgba(250, 173, 20, 80%)';
}
return 'rgba(255, 77, 79, 80%)';
};
const GaugeChart: React.FC<Omit<ChartProps, 'seriesData' | 'xAxisData'>> = ( const GaugeChart: React.FC<Omit<ChartProps, 'seriesData' | 'xAxisData'>> = (
props props
) => { ) => {
@@ -22,46 +12,28 @@ const GaugeChart: React.FC<Omit<ChartProps, 'seriesData' | 'xAxisData'>> = (
title: titleConfig, title: titleConfig,
chartColorMap chartColorMap
} = useChartConfig(); } = useChartConfig();
const { value, height, width, labelFormatter, title, color, gaugeConfig } = const { value, height, width, labelFormatter, title, color } = props;
props;
const titleText = typeof title === 'string' ? title : title?.text;
if (!value && value !== 0) { if (!value && value !== 0) {
return <EmptyData height={height} title={titleText}></EmptyData>; return <EmptyData height={height} title={title}></EmptyData>;
} }
const setDataOptions = () => { const setDataOptions = () => {
const colorValue = color || strokeColorFunc(value);
const combineGaugeConfig = {
...gaugeItemConfig,
...gaugeConfig
};
combineGaugeConfig.detail.rich.value.color = colorValue;
combineGaugeConfig.detail.rich.unit.color = colorValue;
return { return {
title: { title: {
...titleConfig, ...titleConfig,
text: titleText, text: title,
textStyle: { top: '0',
fontSize: 12, left: 'center'
color: chartColorMap.colorSecondary,
fontWeight: 400
},
top: 10,
left: 'center',
...(typeof title === 'object' ? title : {})
}, },
series: [ series: [
{ {
...combineGaugeConfig, ...gaugeItemConfig,
axisLine: { axisLine: {
...combineGaugeConfig.axisLine, ...gaugeItemConfig.axisLine,
lineStyle: { lineStyle: {
...combineGaugeConfig.axisLine.lineStyle, ...gaugeItemConfig.axisLine.lineStyle,
color: [ color: [
[value / 100, colorValue], [value / 100, color],
[1, chartColorMap.gaugeBgColor] [1, chartColorMap.gaugeBgColor]
] ]
} }
@@ -70,11 +42,7 @@ const GaugeChart: React.FC<Omit<ChartProps, 'seriesData' | 'xAxisData'>> = (
color: 'transparent' color: 'transparent'
}, },
detail: { detail: {
...combineGaugeConfig.detail, ...gaugeItemConfig.detail,
borderColor: colorValue,
lineHeight: 20,
height: 18,
width: 50,
formatter: labelFormatter || gaugeItemConfig.detail.formatter formatter: labelFormatter || gaugeItemConfig.detail.formatter
}, },
data: [{ value }] data: [{ value }]
@@ -94,4 +62,4 @@ const GaugeChart: React.FC<Omit<ChartProps, 'seriesData' | 'xAxisData'>> = (
); );
}; };
export default GaugeChart; export default memo(GaugeChart);
+10 -61
View File
@@ -2,10 +2,10 @@ import Chart from '@/components/echarts/chart';
import useChartConfig from '@/components/echarts/config'; import useChartConfig from '@/components/echarts/config';
import EmptyData from '@/components/empty-data'; import EmptyData from '@/components/empty-data';
import _ from 'lodash'; import _ from 'lodash';
import React, { useMemo } from 'react'; import React, { memo, useMemo } from 'react';
import { ChartProps } from './types'; import { ChartProps } from './types';
const BarChart: React.FC<ChartProps & { maxItems?: number }> = (props) => { const BarChart: React.FC<ChartProps> = (props) => {
const { const {
seriesData, seriesData,
xAxisData, xAxisData,
@@ -13,11 +13,9 @@ const BarChart: React.FC<ChartProps & { maxItems?: number }> = (props) => {
width, width,
labelFormatter, labelFormatter,
legendData, legendData,
maxItems,
title title
} = props; } = props;
const { const {
token,
grid, grid,
legend, legend,
title: titleConfig, title: titleConfig,
@@ -32,42 +30,18 @@ const BarChart: React.FC<ChartProps & { maxItems?: number }> = (props) => {
...titleConfig, ...titleConfig,
left: 'start' left: 'start'
}, },
grid: { grid,
...grid,
top: 0,
bottom: maxItems
? `${(1 / maxItems) * (maxItems - xAxisData.length) * 100}%`
: 0
},
tooltip: { tooltip: {
...tooltip ...tooltip
}, },
xAxis: { xAxis: {
...xAxis, ...xAxis,
axisLabel: { axisLabel: {
...xAxis.axisLabel ...xAxis.axisLabel,
} formatter: labelFormatter
},
yAxis: {
...yAxis,
axisLabel: {
...yAxis.axisLabel,
show: true,
overflow: 'truncate',
width: 75,
ellipsis: '...'
// formatter(value: string, index: number) {
// return `{a|${index + 1}}`;
// },
// rich: {
// a: {
// fontWeight: 500,
// fontSize: 14,
// color: token?.colorTextSecondary
// }
// }
} }
}, },
yAxis,
legend: { legend: {
...legend, ...legend,
data: [] data: []
@@ -83,23 +57,7 @@ const BarChart: React.FC<ChartProps & { maxItems?: number }> = (props) => {
stack: 'Ad', stack: 'Ad',
barGap: '30%', barGap: '30%',
label: { label: {
show: false, show: false
formatter(params: any) {
if (params.seriesIndex === 0) {
return `{value|${params.name}}`;
}
return '';
},
position: 'left',
align: 'left',
offset: [5, 18],
rich: {
value: {
textBorderWidth: 0,
fontSize: 11,
color: token?.colorTextTertiary
}
}
}, },
itemStyle: { itemStyle: {
color: item.color color: item.color
@@ -156,19 +114,10 @@ const BarChart: React.FC<ChartProps & { maxItems?: number }> = (props) => {
legend legend
]); ]);
const isEmpty = useMemo(() => {
return seriesData?.every?.((item: any) => {
return !item?.data?.length;
});
}, [seriesData]);
return ( return (
<> <>
{isEmpty ? ( {!seriesData.length ? (
<EmptyData <EmptyData height={height} title={title}></EmptyData>
height={height}
title={_.get(title, 'text', title || '')}
></EmptyData>
) : ( ) : (
<Chart <Chart
height={height} height={height}
@@ -180,4 +129,4 @@ const BarChart: React.FC<ChartProps & { maxItems?: number }> = (props) => {
); );
}; };
export default BarChart; export default memo(BarChart);
+7 -60
View File
@@ -1,13 +1,10 @@
import Chart from '@/components/echarts/chart'; import Chart from '@/components/echarts/chart';
import useChartConfig from '@/components/echarts/config'; import useChartConfig from '@/components/echarts/config';
import EmptyData from '@/components/empty-data'; import EmptyData from '@/components/empty-data';
import { genColors } from '@/utils';
import _ from 'lodash'; import _ from 'lodash';
import React, { useMemo } from 'react'; import React, { memo, useMemo } from 'react';
import echarts from '.';
import { ChartProps } from './types'; import { ChartProps } from './types';
const LinearGradient = echarts.graphic.LinearGradient;
const LineChart: React.FC<ChartProps> = (props) => { const LineChart: React.FC<ChartProps> = (props) => {
const { const {
seriesData, seriesData,
@@ -19,11 +16,7 @@ const LineChart: React.FC<ChartProps> = (props) => {
tooltipValueFormatter = null, tooltipValueFormatter = null,
legendData = [], legendData = [],
smooth, smooth,
title, title
legendOptions,
gridOptions,
titleOptions,
showArea
} = props; } = props;
const { const {
grid, grid,
@@ -35,24 +28,11 @@ const LineChart: React.FC<ChartProps> = (props) => {
yAxis yAxis
} = useChartConfig(); } = useChartConfig();
const axisLabelFormatter = (value: string, index: number) => {
if (labelFormatter) {
return labelFormatter(value, index);
}
if (index === xAxisData.length - 1) {
return '';
}
return value;
};
const options = { const options = {
title: { title: {
text: '' text: ''
}, },
grid: { grid,
...grid,
...gridOptions
},
tooltip: { tooltip: {
...tooltip, ...tooltip,
formatter(params: any) { formatter(params: any) {
@@ -65,13 +45,12 @@ const LineChart: React.FC<ChartProps> = (props) => {
...xAxis, ...xAxis,
axisLabel: { axisLabel: {
...xAxis.axisLabel, ...xAxis.axisLabel,
formatter: axisLabelFormatter formatter: labelFormatter
} }
}, },
yAxis, yAxis,
legend: { legend: {
...legend, ...legend,
...legendOptions,
data: legendData.map((item: any) => { data: legendData.map((item: any) => {
return { return {
name: item, name: item,
@@ -85,11 +64,6 @@ const LineChart: React.FC<ChartProps> = (props) => {
const dataOptions = useMemo((): any => { const dataOptions = useMemo((): any => {
const data = _.map(seriesData, (item: any) => { const data = _.map(seriesData, (item: any) => {
const colors = genColors({
color: item.color,
alpha1: 0.25,
alpha2: 0.1
});
return { return {
...item, ...item,
...lineItemConfig, ...lineItemConfig,
@@ -101,21 +75,7 @@ const LineChart: React.FC<ChartProps> = (props) => {
lineStyle: { lineStyle: {
...lineItemConfig.lineStyle, ...lineItemConfig.lineStyle,
color: item.color color: item.color
},
areaStyle: showArea
? {
color: new LinearGradient(0, 0, 0, 1, [
{
offset: 0,
color: colors[0]
},
{
offset: 1,
color: colors[1]
} }
])
}
: null
}; };
}); });
return { return {
@@ -123,7 +83,6 @@ const LineChart: React.FC<ChartProps> = (props) => {
animation: false, animation: false,
title: { title: {
...titleConfig, ...titleConfig,
...titleOptions,
text: title text: title
}, },
yAxis: { yAxis: {
@@ -140,24 +99,12 @@ const LineChart: React.FC<ChartProps> = (props) => {
}, },
series: data series: data
}; };
}, [ }, [seriesData, xAxisData, yAxisName, title, smooth, legendData, options]);
seriesData,
xAxisData,
yAxisName,
title,
smooth,
titleOptions,
legendData,
options
]);
return ( return (
<> <>
{!seriesData.length ? ( {!seriesData.length ? (
<EmptyData <EmptyData height={height} title={title}></EmptyData>
height={height}
title={_.get(title, 'text', title || '')}
></EmptyData>
) : ( ) : (
<Chart <Chart
height={height} height={height}
@@ -169,4 +116,4 @@ const LineChart: React.FC<ChartProps> = (props) => {
); );
}; };
export default LineChart; export default memo(LineChart);
-176
View File
@@ -1,176 +0,0 @@
import Chart from '@/components/echarts/chart';
import useChartConfig from '@/components/echarts/config';
import EmptyData from '@/components/empty-data';
import { genColors } from '@/utils';
import _ from 'lodash';
import React, { useMemo } from 'react';
import echarts from '.';
import { ChartProps } from './types';
const LinearGradient = echarts.graphic.LinearGradient;
const MixLineBarChart: React.FC<
ChartProps & {
chartData: {
line: any[];
bar: any[];
};
}
> = (props) => {
const {
seriesData,
xAxisData,
yAxisName,
height,
width,
labelFormatter,
tooltipValueFormatter = null,
legendData = [],
smooth,
title,
chartData
} = props;
const {
grid,
legend,
lineItemConfig,
barItemConfig,
title: titleConfig,
tooltip,
xAxis,
yAxis
} = useChartConfig();
const { line: lineSeriesData, bar: barSeriesData } = chartData;
const options = {
title: {
text: ''
},
grid: {
...grid,
right: 0,
top: 20,
bottom: 10
},
tooltip: {
...tooltip,
formatter(params: any) {
return tooltipValueFormatter
? tooltip.formatter(params, tooltipValueFormatter)
: tooltip.formatter(params);
}
},
xAxis: {
...xAxis,
axisLabel: {
...xAxis.axisLabel,
formatter: labelFormatter
}
},
yAxis,
legend: {
...legend,
data: legendData,
itemGap: 20,
bottom: 5,
show: false
},
series: []
};
const dataOptions = useMemo((): any => {
const linedata = _.map(lineSeriesData, (item: any) => {
const colors = genColors({
color: item.color,
alpha1: 0.5,
alpha2: 0.1
});
return {
...item,
...lineItemConfig,
smooth: smooth,
itemStyle: {
...lineItemConfig.itemStyle,
color: item.color
},
yAxisIndex: 1,
lineStyle: {
...lineItemConfig.lineStyle,
color: item.color
}
// areaStyle: {
// color: new LinearGradient(0, 0, 0, 1, [
// {
// offset: 0,
// color: colors[0]
// },
// {
// offset: 1,
// color: colors[1]
// }
// ])
// }
};
});
const barData = _.map(barSeriesData, (item: any) => {
return {
...item,
...barItemConfig,
stack: 'total',
yAxisIndex: 0,
itemStyle: {
...item.itemStyle,
color: item.color
}
};
});
return {
...options,
animation: false,
title: {
...titleConfig,
text: title
},
yAxis: [
{
...options.yAxis
},
{
...options.yAxis,
nameTextStyle: {
fontSize: 12,
align: 'right'
}
}
],
xAxis: {
...options.xAxis,
data: xAxisData
},
series: [...barData, ...linedata]
};
}, [seriesData, xAxisData, yAxisName, title, smooth, legendData, options]);
return (
<>
{!lineSeriesData.length && !barSeriesData.length ? (
<EmptyData
height={height}
title={_.get(title, 'text', title || '')}
></EmptyData>
) : (
<Chart
height={height}
options={dataOptions}
width={width || '100%'}
></Chart>
)}
</>
);
};
export default MixLineBarChart;
+1 -1
View File
@@ -181,7 +181,7 @@ const Scatter: React.FC<ChartProps> = (props) => {
<span class="tooltip-value">${item.text}</span> <span class="tooltip-value">${item.text}</span>
</span>`; </span>`;
}); });
return `<div class="tooltip-wrapper scatter">${result}</div>`; return `<div class="tooltip-wrapper">${result}</div>`;
} }
}, },
title: { title: {
+3 -26
View File
@@ -1,40 +1,17 @@
import type {
LegendComponentOption,
TitleComponentOption
} from 'echarts/components';
export interface ChartProps { export interface ChartProps {
seriesData: any[]; seriesData: any[];
showEmpty?: boolean; showEmpty?: boolean;
showArea?: boolean;
xAxisData: string[]; xAxisData: string[];
legendData?: LegendComponentOption['data']; legendData?: string[];
legendOptions?: { labelFormatter?: (val?: any) => string;
[K in keyof LegendComponentOption]?: LegendComponentOption[K];
};
gridOptions?: {
left?: string | number;
right?: string | number;
top?: string | number;
bottom?: string | number;
};
labelFormatter?: (val?: any, index?: number) => string;
tooltipValueFormatter?: (val: any) => string; tooltipValueFormatter?: (val: any) => string;
height: string | number; height: string | number;
width?: string | number; width?: string | number;
title?: string | TitleComponentOption; title?: string;
titleOptions?: {
[K in keyof TitleComponentOption]?: TitleComponentOption[K];
};
value?: number; value?: number;
smooth?: boolean; smooth?: boolean;
color?: string; color?: string;
yAxisName?: string; yAxisName?: string;
gaugeConfig?: {
radius?: string;
center?: string[];
startAngle?: number;
endAngle?: number;
};
} }
export interface AreaChartItemProps { export interface AreaChartItemProps {
+48 -40
View File
@@ -1,66 +1,74 @@
import classNames from 'classnames';
import React from 'react'; import React from 'react';
import styled from 'styled-components'; import styled from 'styled-components';
import CopyButton from '../copy-button';
import SegmentLine from '../segment-line';
import './index.less'; import './index.less';
const HeaderWrapper = styled.div<{ $height?: number }>` const Wrapper = styled.div<{ $height: number }>`
height: ${(props) => (props.$height ? `${props.$height}px` : 'auto')}; height: ${(props) => props.$height}px;
display: flex;
padding-block: 0;
justify-content: space-between;
align-items: center;
`;
const Wrapper = styled.div`
border-radius: var(--border-radius-mini);
overflow: hidden;
&.bordered {
border: 1px solid var(--ant-color-border);
}
&.borderless {
border: none;
}
.code-pre {
margin-bottom: 0;
}
.scrollbar {
.slider {
border-radius: 6px;
}
}
`; `;
interface EditorwrapProps { interface EditorwrapProps {
headerHeight?: number; headerHeight?: number;
header?: React.ReactNode; header?: React.ReactNode;
children: React.ReactNode; children: React.ReactNode;
variant?: 'bordered' | 'borderless'; showHeader?: boolean;
copyText: string;
defaultValue?: string;
langOptions?: Global.BaseOption<string | number>[];
styles?: { styles?: {
wrapper?: React.CSSProperties; wrapper?: React.CSSProperties;
header?: React.CSSProperties; header?: React.CSSProperties;
content?: React.CSSProperties; content?: React.CSSProperties;
}; };
onChangeLang?: (value: string | number) => void;
} }
const EditorWrap: React.FC<EditorwrapProps> = ({ const EditorWrap: React.FC<EditorwrapProps> = ({
headerHeight = 40, headerHeight = 40,
header, header,
children, children,
variant = 'borderless', copyText,
styles = {} langOptions = [],
onChangeLang,
defaultValue,
styles = {},
showHeader = true
}) => { }) => {
const handleChangeLang = (value: string | number) => {
onChangeLang?.(value);
};
const renderHeader = () => {
if (header) {
return <div className="editor-header">{header}</div>;
}
if (showHeader) {
return ( return (
<Wrapper <Wrapper className="editor-header" $height={headerHeight}>
style={{ ...styles.wrapper }} <SegmentLine
className={classNames({ height={headerHeight}
bordered: variant === 'bordered', defaultValue={defaultValue}
borderless: variant === 'borderless' size="small"
})} options={langOptions}
> onChange={handleChangeLang}
{header && <HeaderWrapper $height={headerHeight}>{header}</HeaderWrapper>} ></SegmentLine>
<div>{children}</div> <CopyButton
text={copyText}
size="small"
style={{
color: 'rgba(255,255,255,.7)'
}}
/>
</Wrapper> </Wrapper>
); );
}
return null;
};
return (
<div className="editor-wrap" style={{ ...styles.wrapper }}>
{renderHeader()}
<div className="editor-content">{children}</div>
</div>
);
}; };
export default EditorWrap; export default React.memo(EditorWrap);
@@ -1,39 +1,30 @@
import { LoadingOutlined } from '@ant-design/icons';
import Editor from '@monaco-editor/react'; import Editor from '@monaco-editor/react';
import React, { import React, { useEffect, useRef, useState } from 'react';
forwardRef,
useEffect,
useImperativeHandle,
useRef
} from 'react';
import EditorWrap from '../editor-wrap'; import EditorWrap from '../editor-wrap';
interface ViewerProps { interface ViewerProps {
ref?: any;
lang: string; lang: string;
defaultLang?: string; defaultLang?: string;
langOptions?: Global.BaseOption<string>[];
config?: any; config?: any;
value: string; value: string;
height?: string | number; height?: string | number;
theme?: string; theme?: string;
header?: React.ReactNode; showHeader?: boolean;
placeholder?: string;
variant?: 'bordered' | 'borderless';
} }
const ViewerEditor: React.FC<ViewerProps> = forwardRef((props, ref) => { const ViewerEditor: React.FC<ViewerProps> = (props) => {
const editorRef = useRef<any>(null);
const { const {
lang, lang,
value, value,
config, config,
langOptions,
defaultLang, defaultLang,
height = 380, height = 380,
theme = 'vs-dark', showHeader
header,
variant = 'borderless',
placeholder
} = props; } = props;
const [langType, setLangType] = useState(defaultLang);
const editorRef = useRef<any>(null);
const handleBeforeMount = (monaco: any) => { const handleBeforeMount = (monaco: any) => {
monaco.languages.typescript.javascriptDefaults.setDiagnosticsOptions({ monaco.languages.typescript.javascriptDefaults.setDiagnosticsOptions({
@@ -45,8 +36,12 @@ const ViewerEditor: React.FC<ViewerProps> = forwardRef((props, ref) => {
const handleEditorDidMount = (editor: any, monaco: any) => { const handleEditorDidMount = (editor: any, monaco: any) => {
editorRef.current = editor; editorRef.current = editor;
console.log('loaded====', editor, monaco);
}; };
const handleOnChangeLang = (value: string) => {
setLangType(value);
};
const formatCode = () => { const formatCode = () => {
if (editorRef.current) { if (editorRef.current) {
setTimeout(() => { setTimeout(() => {
@@ -60,50 +55,35 @@ const ViewerEditor: React.FC<ViewerProps> = forwardRef((props, ref) => {
} }
}; };
useImperativeHandle(ref, () => ({
format: () => {
formatCode();
},
getValue: () => {
return editorRef.current?.getValue?.();
},
setValue: (val: string) => {
editorRef.current?.setValue?.(val);
},
editor: editorRef.current
}));
useEffect(() => { useEffect(() => {
formatCode(); formatCode();
setTimeout(() => { setTimeout(() => {
const lineCount = editorRef.current?.getModel().getLineCount(); const lineCount = editorRef.current?.getModel().getLineCount(); // 获取总行数
editorRef.current?.revealLine(lineCount); editorRef.current?.revealLine(lineCount); // 滚动到最后一行
}, 100); }, 100); // 可以调整延时,确保编辑器完全加载
}, [value]); }, [value]);
return ( return (
<EditorWrap header={header} variant={variant}> <EditorWrap
copyText={value}
langOptions={langOptions}
defaultValue={lang}
showHeader={showHeader}
onChangeLang={handleOnChangeLang}
>
<Editor <Editor
height={height} height={height}
theme={theme} theme="vs-dark"
className="monaco-editor" className="monaco-editor"
defaultLanguage={defaultLang} defaultLanguage={defaultLang}
language={lang} language={langType}
value={value} value={value}
options={{ options={config}
minimap: { enabled: false },
scrollbar: {
verticalScrollbarSize: 6,
horizontalScrollbarSize: 6
},
placeholder: placeholder
}}
loading={<LoadingOutlined style={{ fontSize: 24 }}></LoadingOutlined>}
beforeMount={handleBeforeMount} beforeMount={handleBeforeMount}
onMount={handleEditorDidMount} onMount={handleEditorDidMount}
/> />
</EditorWrap> </EditorWrap>
); );
}); };
export default ViewerEditor; export default React.memo(ViewerEditor);
@@ -1,3 +1,4 @@
import { memo } from 'react';
import CodeViewer from './code-viewer'; import CodeViewer from './code-viewer';
import './styles/dark.less'; import './styles/dark.less';
@@ -37,4 +38,4 @@ const DarkViewer: React.FC<CodeViewerProps> = (props) => {
); );
}; };
export default DarkViewer; export default memo(DarkViewer);
@@ -1,3 +1,4 @@
import { memo } from 'react';
import CodeViewer from './code-viewer'; import CodeViewer from './code-viewer';
import './styles/light.less'; import './styles/light.less';
@@ -38,4 +39,4 @@ const LightViewer: React.FC<CodeViewerProps> = (props) => {
); );
}; };
export default LightViewer; export default memo(LightViewer);
@@ -1,6 +1,6 @@
import classNames from 'classnames'; import classNames from 'classnames';
import hljs from 'highlight.js'; import hljs from 'highlight.js';
import { useMemo } from 'react'; import { memo, useMemo } from 'react';
import styled from 'styled-components'; import styled from 'styled-components';
import CopyButton from '../copy-button'; import CopyButton from '../copy-button';
import { escapeHtml } from './utils'; import { escapeHtml } from './utils';
@@ -155,4 +155,4 @@ const CodeViewer: React.FC<CodeViewerProps> = (props) => {
); );
}; };
export default CodeViewer; export default memo(CodeViewer);
+1 -1
View File
@@ -56,4 +56,4 @@ const HighlightCode: React.FC<{
); );
}; };
export default HighlightCode; export default React.memo(HighlightCode);
@@ -46,7 +46,7 @@
white-space: pre-wrap; white-space: pre-wrap;
code { code {
line-height: 1.6; line-height: 1.5;
} }
&.copyable { &.copyable {
+11 -431
View File
@@ -1,8 +1,8 @@
@font-face { @font-face {
font-family: iconfont; /* Project id 4613488 */ font-family: iconfont; /* Project id 4613488 */
src: url('iconfont.woff2?t=1766993792323') format('woff2'), src: url('iconfont.woff2?t=1747231573342') format('woff2'),
url('iconfont.woff?t=1766993792323') format('woff'), url('iconfont.woff?t=1747231573342') format('woff'),
url('iconfont.ttf?t=1766993792323') format('truetype'); url('iconfont.ttf?t=1747231573342') format('truetype');
} }
.iconfont { .iconfont {
@@ -13,434 +13,6 @@
-moz-osx-font-smoothing: grayscale; -moz-osx-font-smoothing: grayscale;
} }
.icon-cloud::before {
content: "\e6bc";
}
.icon-server02::before {
content: "\e6bd";
}
.icon-drag_handle::before {
content: "\e6b9";
}
.icon-basic::before {
content: "\e6bb";
}
.icon-settings::before {
content: "\e6b8";
}
.icon-speed::before {
content: "\e6ba";
}
.icon-permission::before {
content: "\e6b6";
}
.icon-captive_portal::before {
content: "\e6b7";
}
.icon-lock_open_right::before {
content: "\e6b3";
}
.icon-lock_person::before {
content: "\e6b4";
}
.icon-lock_open::before {
content: "\e6b5";
}
.icon-question::before {
content: "\e6b2";
}
.icon-aws::before {
content: "\e616";
}
.icon-aws1::before {
content: "\e62f";
}
.icon-manage_user::before {
content: "\e6b1";
}
.icon-private::before {
content: "\e6b0";
}
.icon-stop3::before {
content: "\e6af";
}
.icon-version::before {
content: "\e6ae";
}
.icon-edit-content::before {
content: "\e6ab";
}
.icon-code_block::before {
content: "\e6ac";
}
.icon-parameters::before {
content: "\e6ad";
}
.icon-backend-filled::before {
content: "\e6a9";
}
.icon-backend::before {
content: "\e6aa";
}
.icon-down2::before {
content: "\e6a7";
}
.icon-nvidia2::before {
content: "\e60d";
}
.icon-centos::before {
content: "\e6cd";
}
.icon-redhat::before {
content: "\ec7b";
}
.icon-ubuntu::before {
content: "\edd3";
}
.icon-debian::before {
content: "\eb74";
}
.icon-alma-linux::before {
content: "\e6a8";
}
.icon-fedora::before {
content: "\e61a";
}
.icon-rocky-linux::before {
content: "\e620";
}
.icon-nvidia1::before {
content: "\e980";
}
.icon-nvidia::before {
content: "\e60c";
}
.icon-amd::before {
content: "\e6a6";
}
.icon-huawei::before {
content: "\e615";
}
.icon-metax::before {
content: "\e6a4";
}
.icon-ascend::before {
content: "\e6a3";
}
.icon-huaweicloud::before {
content: "\e614";
}
.icon-alicloud::before {
content: "\e784";
}
.icon-tencentcloud::before {
content: "\e609";
}
.icon-cluster2-outline::before {
content: "\e6a1";
}
.icon-cluster2-filled::before {
content: "\e6a2";
}
.icon-cluster-filled::before {
content: "\e69f";
}
.icon-cluster-outline::before {
content: "\e6a0";
}
.icon-admin-user::before {
content: "\e69d";
}
.icon-user::before {
content: "\e69e";
}
.icon-detail-info::before {
content: "\e69b";
}
.icon-docker::before {
content: "\e69c";
}
.icon-digitalocean::before {
content: "\eb79";
}
.icon-rocket-launch1::before {
content: "\e699";
}
.icon-rocket-launch-fill::before {
content: "\e69a";
}
.icon-credential-filled::before {
content: "\e697";
}
.icon-credential-outline::before {
content: "\e698";
}
.icon-k8s-filled::before {
content: "\e63e";
}
.icon-k8s-outline::before {
content: "\e64b";
}
.icon-huggingface1::before {
content: "\e605";
}
.icon-modelscope_light::before {
content: "\e696";
}
.icon-catalog1::before {
content: "\e691";
}
.icon-chat-filled::before {
content: "\e692";
}
.icon-chat::before {
content: "\e693";
}
.icon-files-filled::before {
content: "\e694";
}
.icon-files::before {
content: "\e695";
}
.icon-models-filled::before {
content: "\e682";
}
.icon-image-filled::before {
content: "\e683";
}
.icon-audio1::before {
content: "\e684";
}
.icon-image1::before {
content: "\e685";
}
.icon-audio-filled::before {
content: "\e686";
}
.icon-reranker-filled::before {
content: "\e687";
}
.icon-embedding-filled::before {
content: "\e68a";
}
.icon-models::before {
content: "\e68b";
}
.icon-reranker::before {
content: "\e68c";
}
.icon-embedding::before {
content: "\e68d";
}
.icon-gpu-filled::before {
content: "\e68e";
}
.icon-catalog-filled::before {
content: "\e68f";
}
.icon-gpu1::before {
content: "\e690";
}
.icon-language::before {
content: "\e679";
}
.icon-help::before {
content: "\e67a";
}
.icon-key-filled::before {
content: "\e67b";
}
.icon-key::before {
content: "\e67d";
}
.icon-resources::before {
content: "\e67e";
}
.icon-users::before {
content: "\e67f";
}
.icon-resources-filled::before {
content: "\e680";
}
.icon-users-filled::before {
content: "\e681";
}
.icon-model::before {
content: "\e676";
}
.icon-model-filled::before {
content: "\e678";
}
.icon-layers-filled::before {
content: "\e671";
}
.icon-layers::before {
content: "\e673";
}
.icon-experiment::before {
content: "\e674";
}
.icon-experiment-filled::before {
content: "\e675";
}
.icon-dashboard::before {
content: "\e66d";
}
.icon-dashboard-filled::before {
content: "\e66e";
}
.icon-expand-left::before {
content: "\ea48";
}
.icon-expand-right::before {
content: "\ea49";
}
.icon-users-fill::before {
content: "\e677";
}
.icon-layers-fill::before {
content: "\e89d";
}
.icon-key-fill::before {
content: "\e80e";
}
.icon-server-fill::before {
content: "\e7a3";
}
.icon-model-fill::before {
content: "\e7b7";
}
.icon-left_panel_close::before {
content: "\e668";
}
.icon-left_panel_open::before {
content: "\e669";
}
.icon-playcircle-fill::before {
content: "\e665";
}
.icon-stopcircle-fill::before {
content: "\e666";
}
.icon-play-speed::before {
content: "\e856";
}
.icon-more::before {
content: "\e62e";
}
.icon-play::before {
content: "\e9f9";
}
.icon-pause::before {
content: "\e713";
}
.icon-dark_theme::before { .icon-dark_theme::before {
content: "\e646"; content: "\e646";
} }
@@ -665,6 +237,14 @@
content: "\e62c"; content: "\e62c";
} }
.icon-stopcircle-fill::before {
content: "\e7cc";
}
.icon-playcircle-fill::before {
content: "\e7cd";
}
.icon-SpeakerSlash::before { .icon-SpeakerSlash::before {
content: "\e661"; content: "\e661";
} }
File diff suppressed because one or more lines are too long
+14 -749
View File
@@ -5,755 +5,6 @@
"css_prefix_text": "icon-", "css_prefix_text": "icon-",
"description": "", "description": "",
"glyphs": [ "glyphs": [
{
"icon_id": "46296038",
"name": "cloud",
"font_class": "cloud",
"unicode": "e6bc",
"unicode_decimal": 59068
},
{
"icon_id": "46296037",
"name": "server",
"font_class": "server02",
"unicode": "e6bd",
"unicode_decimal": 59069
},
{
"icon_id": "46272083",
"name": "drag_handle",
"font_class": "drag_handle",
"unicode": "e6b9",
"unicode_decimal": 59065
},
{
"icon_id": "46055620",
"name": "basic",
"font_class": "basic",
"unicode": "e6bb",
"unicode_decimal": 59067
},
{
"icon_id": "46055265",
"name": "settings",
"font_class": "settings",
"unicode": "e6b8",
"unicode_decimal": 59064
},
{
"icon_id": "46055263",
"name": "speed",
"font_class": "speed",
"unicode": "e6ba",
"unicode_decimal": 59066
},
{
"icon_id": "46039838",
"name": "permission",
"font_class": "permission",
"unicode": "e6b6",
"unicode_decimal": 59062
},
{
"icon_id": "46039837",
"name": "captive_portal",
"font_class": "captive_portal",
"unicode": "e6b7",
"unicode_decimal": 59063
},
{
"icon_id": "46001737",
"name": "lock_open_right",
"font_class": "lock_open_right",
"unicode": "e6b3",
"unicode_decimal": 59059
},
{
"icon_id": "46001736",
"name": "lock_person",
"font_class": "lock_person",
"unicode": "e6b4",
"unicode_decimal": 59060
},
{
"icon_id": "46001735",
"name": "lock_open",
"font_class": "lock_open",
"unicode": "e6b5",
"unicode_decimal": 59061
},
{
"icon_id": "45937227",
"name": "question_mark_24dp_1F1F1F_FILL0_wght300_GRAD0_opsz",
"font_class": "question",
"unicode": "e6b2",
"unicode_decimal": 59058
},
{
"icon_id": "10786885",
"name": "aws",
"font_class": "aws",
"unicode": "e616",
"unicode_decimal": 58902
},
{
"icon_id": "12014352",
"name": "aws",
"font_class": "aws1",
"unicode": "e62f",
"unicode_decimal": 58927
},
{
"icon_id": "45704693",
"name": "manage_accounts_24dp_1F1F1F_FILL1_wght300_GRAD0_op",
"font_class": "manage_user",
"unicode": "e6b1",
"unicode_decimal": 59057
},
{
"icon_id": "45702717",
"name": "private",
"font_class": "private",
"unicode": "e6b0",
"unicode_decimal": 59056
},
{
"icon_id": "45702697",
"name": "stop",
"font_class": "stop3",
"unicode": "e6af",
"unicode_decimal": 59055
},
{
"icon_id": "45688107",
"name": "version",
"font_class": "version",
"unicode": "e6ae",
"unicode_decimal": 59054
},
{
"icon_id": "45631011",
"name": "contract_edit_24dp_1F1F1F_FILL0_wght300_GRAD0_opsz",
"font_class": "edit-content",
"unicode": "e6ab",
"unicode_decimal": 59051
},
{
"icon_id": "45631010",
"name": "code_blocks_24dp_1F1F1F_FILL0_wght300_GRAD0_opsz24",
"font_class": "code_block",
"unicode": "e6ac",
"unicode_decimal": 59052
},
{
"icon_id": "45631009",
"name": "settings_applications_24dp_1F1F1F_FILL0_wght300_GR",
"font_class": "parameters",
"unicode": "e6ad",
"unicode_decimal": 59053
},
{
"icon_id": "45623375",
"name": "bolt_24dp_1F1F1F_FILL1_wght300_GRAD0_opsz24",
"font_class": "backend-filled",
"unicode": "e6a9",
"unicode_decimal": 59049
},
{
"icon_id": "45623374",
"name": "bolt_24dp_1F1F1F_FILL0_wght300_GRAD0_opsz24",
"font_class": "backend",
"unicode": "e6aa",
"unicode_decimal": 59050
},
{
"icon_id": "45554217",
"name": "arrow_down",
"font_class": "down2",
"unicode": "e6a7",
"unicode_decimal": 59047
},
{
"icon_id": "11155543",
"name": "nvidia",
"font_class": "nvidia2",
"unicode": "e60d",
"unicode_decimal": 58893
},
{
"icon_id": "11425971",
"name": "centos",
"font_class": "centos",
"unicode": "e6cd",
"unicode_decimal": 59085
},
{
"icon_id": "15378743",
"name": "redhat",
"font_class": "redhat",
"unicode": "ec7b",
"unicode_decimal": 60539
},
{
"icon_id": "6808499",
"name": "Ubuntu",
"font_class": "ubuntu",
"unicode": "edd3",
"unicode_decimal": 60883
},
{
"icon_id": "15378275",
"name": "debian",
"font_class": "debian",
"unicode": "eb74",
"unicode_decimal": 60276
},
{
"icon_id": "38395859",
"name": "almalinux",
"font_class": "alma-linux",
"unicode": "e6a8",
"unicode_decimal": 59048
},
{
"icon_id": "43280042",
"name": "linux-fedora",
"font_class": "fedora",
"unicode": "e61a",
"unicode_decimal": 58906
},
{
"icon_id": "43280143",
"name": "linux-rocky_linux",
"font_class": "rocky-linux",
"unicode": "e620",
"unicode_decimal": 58912
},
{
"icon_id": "36227448",
"name": "nvidia",
"font_class": "nvidia1",
"unicode": "e980",
"unicode_decimal": 59776
},
{
"icon_id": "40723469",
"name": "nvidia",
"font_class": "nvidia",
"unicode": "e60c",
"unicode_decimal": 58892
},
{
"icon_id": "45463937",
"name": "amd",
"font_class": "amd",
"unicode": "e6a6",
"unicode_decimal": 59046
},
{
"icon_id": "24164616",
"name": "华为",
"font_class": "huawei",
"unicode": "e615",
"unicode_decimal": 58901
},
{
"icon_id": "45461362",
"name": "metax",
"font_class": "metax",
"unicode": "e6a4",
"unicode_decimal": 59044
},
{
"icon_id": "45459906",
"name": "ascend",
"font_class": "ascend",
"unicode": "e6a3",
"unicode_decimal": 59043
},
{
"icon_id": "8152996",
"name": "HUAWEI",
"font_class": "huaweicloud",
"unicode": "e614",
"unicode_decimal": 58900
},
{
"icon_id": "34290284",
"name": "alicloud",
"font_class": "alicloud",
"unicode": "e784",
"unicode_decimal": 59268
},
{
"icon_id": "36654764",
"name": "tencent",
"font_class": "tencentcloud",
"unicode": "e609",
"unicode_decimal": 58889
},
{
"icon_id": "45310241",
"name": "cluster2-outline",
"font_class": "cluster2-outline",
"unicode": "e6a1",
"unicode_decimal": 59041
},
{
"icon_id": "45310242",
"name": "cluster2-filled",
"font_class": "cluster2-filled",
"unicode": "e6a2",
"unicode_decimal": 59042
},
{
"icon_id": "45310070",
"name": "cluster-filled",
"font_class": "cluster-filled",
"unicode": "e69f",
"unicode_decimal": 59039
},
{
"icon_id": "45310069",
"name": "cluster-outline",
"font_class": "cluster-outline",
"unicode": "e6a0",
"unicode_decimal": 59040
},
{
"icon_id": "45240470",
"name": "admin-user",
"font_class": "admin-user",
"unicode": "e69d",
"unicode_decimal": 59037
},
{
"icon_id": "45240469",
"name": "user",
"font_class": "user",
"unicode": "e69e",
"unicode_decimal": 59038
},
{
"icon_id": "45221182",
"name": "detail-info",
"font_class": "detail-info",
"unicode": "e69b",
"unicode_decimal": 59035
},
{
"icon_id": "45102077",
"name": "Docker",
"font_class": "docker",
"unicode": "e69c",
"unicode_decimal": 59036
},
{
"icon_id": "15378290",
"name": "digitalocean",
"font_class": "digitalocean",
"unicode": "eb79",
"unicode_decimal": 60281
},
{
"icon_id": "45040302",
"name": "rocket-launch",
"font_class": "rocket-launch1",
"unicode": "e699",
"unicode_decimal": 59033
},
{
"icon_id": "45040301",
"name": "rocket-launch-fill",
"font_class": "rocket-launch-fill",
"unicode": "e69a",
"unicode_decimal": 59034
},
{
"icon_id": "45028316",
"name": "credential",
"font_class": "credential-filled",
"unicode": "e697",
"unicode_decimal": 59031
},
{
"icon_id": "45028315",
"name": "credential",
"font_class": "credential-outline",
"unicode": "e698",
"unicode_decimal": 59032
},
{
"icon_id": "37601405",
"name": "k8s",
"font_class": "k8s-filled",
"unicode": "e63e",
"unicode_decimal": 58942
},
{
"icon_id": "41673174",
"name": "k8s",
"font_class": "k8s-outline",
"unicode": "e64b",
"unicode_decimal": 58955
},
{
"icon_id": "40865292",
"name": "huggingface",
"font_class": "huggingface1",
"unicode": "e605",
"unicode_decimal": 58885
},
{
"icon_id": "43616582",
"name": "modelscope_light",
"font_class": "modelscope_light",
"unicode": "e696",
"unicode_decimal": 59030
},
{
"icon_id": "44776845",
"name": "catalog",
"font_class": "catalog1",
"unicode": "e691",
"unicode_decimal": 59025
},
{
"icon_id": "44776848",
"name": "chat-filled",
"font_class": "chat-filled",
"unicode": "e692",
"unicode_decimal": 59026
},
{
"icon_id": "44776846",
"name": "chat",
"font_class": "chat",
"unicode": "e693",
"unicode_decimal": 59027
},
{
"icon_id": "44776841",
"name": "files-filled",
"font_class": "files-filled",
"unicode": "e694",
"unicode_decimal": 59028
},
{
"icon_id": "44776840",
"name": "files",
"font_class": "files",
"unicode": "e695",
"unicode_decimal": 59029
},
{
"icon_id": "44776851",
"name": "models-filled",
"font_class": "models-filled",
"unicode": "e682",
"unicode_decimal": 59010
},
{
"icon_id": "44776857",
"name": "image-filled",
"font_class": "image-filled",
"unicode": "e683",
"unicode_decimal": 59011
},
{
"icon_id": "44776856",
"name": "audio",
"font_class": "audio1",
"unicode": "e684",
"unicode_decimal": 59012
},
{
"icon_id": "44776855",
"name": "image",
"font_class": "image1",
"unicode": "e685",
"unicode_decimal": 59013
},
{
"icon_id": "44776850",
"name": "audio-filled",
"font_class": "audio-filled",
"unicode": "e686",
"unicode_decimal": 59014
},
{
"icon_id": "44776849",
"name": "reranker-filled",
"font_class": "reranker-filled",
"unicode": "e687",
"unicode_decimal": 59015
},
{
"icon_id": "44776852",
"name": "embedding-filled",
"font_class": "embedding-filled",
"unicode": "e68a",
"unicode_decimal": 59018
},
{
"icon_id": "44776853",
"name": "models",
"font_class": "models",
"unicode": "e68b",
"unicode_decimal": 59019
},
{
"icon_id": "44776844",
"name": "reranker",
"font_class": "reranker",
"unicode": "e68c",
"unicode_decimal": 59020
},
{
"icon_id": "44776854",
"name": "embedding",
"font_class": "embedding",
"unicode": "e68d",
"unicode_decimal": 59021
},
{
"icon_id": "44776843",
"name": "gpu-filled",
"font_class": "gpu-filled",
"unicode": "e68e",
"unicode_decimal": 59022
},
{
"icon_id": "44776847",
"name": "catalog-filled",
"font_class": "catalog-filled",
"unicode": "e68f",
"unicode_decimal": 59023
},
{
"icon_id": "44776842",
"name": "gpu",
"font_class": "gpu1",
"unicode": "e690",
"unicode_decimal": 59024
},
{
"icon_id": "44685190",
"name": "language",
"font_class": "language",
"unicode": "e679",
"unicode_decimal": 59001
},
{
"icon_id": "44685189",
"name": "help",
"font_class": "help",
"unicode": "e67a",
"unicode_decimal": 59002
},
{
"icon_id": "44684306",
"name": "key-filled",
"font_class": "key-filled",
"unicode": "e67b",
"unicode_decimal": 59003
},
{
"icon_id": "44684302",
"name": "key",
"font_class": "key",
"unicode": "e67d",
"unicode_decimal": 59005
},
{
"icon_id": "44684304",
"name": "resources",
"font_class": "resources",
"unicode": "e67e",
"unicode_decimal": 59006
},
{
"icon_id": "44684305",
"name": "users",
"font_class": "users",
"unicode": "e67f",
"unicode_decimal": 59007
},
{
"icon_id": "44684303",
"name": "resources-filled",
"font_class": "resources-filled",
"unicode": "e680",
"unicode_decimal": 59008
},
{
"icon_id": "44684301",
"name": "users-filled",
"font_class": "users-filled",
"unicode": "e681",
"unicode_decimal": 59009
},
{
"icon_id": "44684150",
"name": "model",
"font_class": "model",
"unicode": "e676",
"unicode_decimal": 58998
},
{
"icon_id": "44684149",
"name": "model-filled",
"font_class": "model-filled",
"unicode": "e678",
"unicode_decimal": 59000
},
{
"icon_id": "44684090",
"name": "layers-filled",
"font_class": "layers-filled",
"unicode": "e671",
"unicode_decimal": 58993
},
{
"icon_id": "44684091",
"name": "layers",
"font_class": "layers",
"unicode": "e673",
"unicode_decimal": 58995
},
{
"icon_id": "44684086",
"name": "experiment",
"font_class": "experiment",
"unicode": "e674",
"unicode_decimal": 58996
},
{
"icon_id": "44684085",
"name": "experiment",
"font_class": "experiment-filled",
"unicode": "e675",
"unicode_decimal": 58997
},
{
"icon_id": "44684021",
"name": "dashboard",
"font_class": "dashboard",
"unicode": "e66d",
"unicode_decimal": 58989
},
{
"icon_id": "44684020",
"name": "dashboard-filled",
"font_class": "dashboard-filled",
"unicode": "e66e",
"unicode_decimal": 58990
},
{
"icon_id": "40068272",
"name": "expand-left",
"font_class": "expand-left",
"unicode": "ea48",
"unicode_decimal": 59976
},
{
"icon_id": "40068278",
"name": "expand-right",
"font_class": "expand-right",
"unicode": "ea49",
"unicode_decimal": 59977
},
{
"icon_id": "1350497",
"name": "sq-users",
"font_class": "users-fill",
"unicode": "e677",
"unicode_decimal": 58999
},
{
"icon_id": "13691715",
"name": "layers",
"font_class": "layers-fill",
"unicode": "e89d",
"unicode_decimal": 59549
},
{
"icon_id": "14267903",
"name": "key-skeleton-alt",
"font_class": "key-fill",
"unicode": "e80e",
"unicode_decimal": 59406
},
{
"icon_id": "14341823",
"name": "cloud-server-solid",
"font_class": "server-fill",
"unicode": "e7a3",
"unicode_decimal": 59299
},
{
"icon_id": "40764011",
"name": "icon-model-selected",
"font_class": "model-fill",
"unicode": "e7b7",
"unicode_decimal": 59319
},
{
"icon_id": "44668359",
"name": "left_panel_close_36dp_1F1F1F_FILL0_wght400_GRAD0_o",
"font_class": "left_panel_close",
"unicode": "e668",
"unicode_decimal": 58984
},
{
"icon_id": "44668360",
"name": "left_panel_open_36dp_1F1F1F_FILL0_wght400_GRAD0_op",
"font_class": "left_panel_open",
"unicode": "e669",
"unicode_decimal": 58985
},
{
"icon_id": "44650419",
"name": "play circle-fill",
"font_class": "playcircle-fill",
"unicode": "e665",
"unicode_decimal": 58981
},
{
"icon_id": "44650418",
"name": "stop circle-fill",
"font_class": "stopcircle-fill",
"unicode": "e666",
"unicode_decimal": 58982
},
{
"icon_id": "8592899",
"name": "play-speed",
"font_class": "play-speed",
"unicode": "e856",
"unicode_decimal": 59478
},
{
"icon_id": "10941162",
"name": "more",
"font_class": "more",
"unicode": "e62e",
"unicode_decimal": 58926
},
{
"icon_id": "703373",
"name": "play",
"font_class": "play",
"unicode": "e9f9",
"unicode_decimal": 59897
},
{
"icon_id": "736882",
"name": "pause",
"font_class": "pause",
"unicode": "e713",
"unicode_decimal": 59155
},
{ {
"icon_id": "44028047", "icon_id": "44028047",
"name": "dark_theme", "name": "dark_theme",
@@ -1146,6 +397,20 @@
"unicode": "e62c", "unicode": "e62c",
"unicode_decimal": 58924 "unicode_decimal": 58924
}, },
{
"icon_id": "6151140",
"name": "stop circle-fill",
"font_class": "stopcircle-fill",
"unicode": "e7cc",
"unicode_decimal": 59340
},
{
"icon_id": "6151141",
"name": "play circle-fill",
"font_class": "playcircle-fill",
"unicode": "e7cd",
"unicode_decimal": 59341
},
{ {
"icon_id": "23563264", "icon_id": "23563264",
"name": "SpeakerSlash", "name": "SpeakerSlash",
Binary file not shown.
Binary file not shown.
Binary file not shown.
-66
View File
@@ -1,66 +0,0 @@
import IconFont from '@/components/icon-font';
import {
ApiOutlined,
DeleteOutlined,
DockerOutlined,
DownloadOutlined,
EditOutlined,
ExperimentOutlined,
FileTextOutlined,
KubernetesOutlined,
ProfileOutlined,
RetweetOutlined,
StarOutlined,
ThunderboltOutlined
} from '@ant-design/icons';
import React from 'react';
const icons = {
EditOutlined: React.createElement(EditOutlined),
ExperimentOutlined: React.createElement(ExperimentOutlined),
DeleteOutlined: React.createElement(DeleteOutlined),
ThunderboltOutlined: React.createElement(ThunderboltOutlined),
RetweetOutlined: React.createElement(RetweetOutlined),
DownloadOutlined: React.createElement(DownloadOutlined),
FileTextOutlined: React.createElement(FileTextOutlined),
ApiOutlined: React.createElement(ApiOutlined),
KubernetesOutlined: React.createElement(KubernetesOutlined),
ProfileOutlined: React.createElement(ProfileOutlined),
DockerOutlined: React.createElement(DockerOutlined),
Stop: React.createElement(IconFont, { type: 'icon-stop1' }),
Play: React.createElement(IconFont, { type: 'icon-outline-play' }),
Catalog: React.createElement(IconFont, { type: 'icon-catalog' }),
HF: React.createElement(IconFont, { type: 'icon-huggingface' }),
Ollama: React.createElement(IconFont, { type: 'icon-ollama' }),
ModelScope: React.createElement(IconFont, { type: 'icon-tu2' }),
LocalPath: React.createElement(IconFont, { type: 'icon-hard-disk' }),
Launch: React.createElement(IconFont, { type: 'icon-rocket-launch' }),
Deployment: React.createElement(IconFont, { type: 'icon-rocket-launch1' }),
Docker: React.createElement(IconFont, { type: 'icon-docker' }),
DigitalOcean: React.createElement(IconFont, { type: 'icon-digitalocean' }),
DetailInfo: React.createElement(IconFont, { type: 'icon-detail-info' }),
HuaweiCloud: React.createElement(IconFont, { type: 'icon-huaweicloud' }),
AliCloud: React.createElement(IconFont, { type: 'icon-alicloud' }),
TencentCloud: React.createElement(IconFont, { type: 'icon-tencentcloud' }),
Nvidia: React.createElement(IconFont, { type: 'icon-nvidia' }),
Ascend: React.createElement(IconFont, { type: 'icon-ascend' }),
Catalog1: React.createElement(IconFont, { type: 'icon-catalog1' }),
AMD: React.createElement(IconFont, { type: 'icon-amd' }),
KubernetesFilled: React.createElement(IconFont, { type: 'icon-k8s-filled' }),
EditContent: React.createElement(IconFont, { type: 'icon-edit-content' }),
Yaml: React.createElement(IconFont, { type: 'icon-code_block' }),
Version: React.createElement(IconFont, { type: 'icon-version' }),
Parameter: React.createElement(IconFont, { type: 'icon-parameters' }),
Private: React.createElement(IconFont, { type: 'icon-private' }),
AWS: React.createElement(IconFont, { type: 'icon-aws' }),
LockOpenRight: React.createElement(IconFont, {
type: 'icon-lock_open_right'
}),
LockPerson: React.createElement(IconFont, { type: 'icon-lock_person' }),
LockOpen: React.createElement(IconFont, { type: 'icon-lock_open' }),
Permission: React.createElement(IconFont, { type: 'icon-permission' }),
CaptivePortal: React.createElement(IconFont, { type: 'icon-captive_portal' }),
StarOutlined: React.createElement(StarOutlined)
};
export default icons;
@@ -107,6 +107,7 @@ export default function useZoom(props: {
if (isLoadingMaskRef.current) { if (isLoadingMaskRef.current) {
return; return;
} }
event.preventDefault();
// stop // stop
handleZoom(event); handleZoom(event);
updateCursorSize(); updateCursorSize();
+4 -13
View File
@@ -433,6 +433,7 @@ const CanvasImageEditor: React.FC<CanvasImageEditorProps> = forwardRef(
fitView(); fitView();
setActiveScale(autoScale.current); setActiveScale(autoScale.current);
updateCursorSize(); updateCursorSize();
redrawStrokes(strokesRef.current);
}; };
const handleBrushSizeChange = (value: number) => { const handleBrushSizeChange = (value: number) => {
@@ -453,22 +454,11 @@ const CanvasImageEditor: React.FC<CanvasImageEditorProps> = forwardRef(
useEffect(() => { useEffect(() => {
const handleUndoShortcut = (e: KeyboardEvent) => { const handleUndoShortcut = (e: KeyboardEvent) => {
if ( if ((e.ctrlKey || e.metaKey) && e.key === 'z') {
(e.ctrlKey || e.metaKey) &&
e.key === 'z' &&
!negativeMaskRef.current
) {
undo(); undo();
} }
}; };
window.addEventListener('keydown', handleUndoShortcut);
return () => {
window.removeEventListener('keydown', handleUndoShortcut);
};
}, []);
useEffect(() => {
const handleMouseDown = (e: MouseEvent) => { const handleMouseDown = (e: MouseEvent) => {
mouseDownState.current = true; mouseDownState.current = true;
}; };
@@ -477,6 +467,7 @@ const CanvasImageEditor: React.FC<CanvasImageEditorProps> = forwardRef(
mouseDownState.current = false; mouseDownState.current = false;
}; };
window.addEventListener('keydown', handleUndoShortcut);
// mouse down // mouse down
window.addEventListener('mousedown', handleMouseDown); window.addEventListener('mousedown', handleMouseDown);
@@ -484,7 +475,7 @@ const CanvasImageEditor: React.FC<CanvasImageEditorProps> = forwardRef(
window.addEventListener('mouseup', handleMouseUp); window.addEventListener('mouseup', handleMouseUp);
return () => { return () => {
clearTimeout(timer.current); clearTimeout(timer.current);
window.removeEventListener('keydown', handleUndoShortcut);
window.removeEventListener('mousedown', handleMouseDown); window.removeEventListener('mousedown', handleMouseDown);
window.removeEventListener('mouseup', handleMouseUp); window.removeEventListener('mouseup', handleMouseUp);
}; };
+1 -3
View File
@@ -48,14 +48,12 @@ const ToolsBar: React.FC<ToolsBarProps> = (props) => {
<Tooltip <Tooltip
placement="bottomLeft" placement="bottomLeft"
arrow={false} arrow={false}
styles={{ overlayInnerStyle={{
body: {
background: 'var(--color-white-1)', background: 'var(--color-white-1)',
display: 'flex', display: 'flex',
alignItems: 'center', alignItems: 'center',
justifyContent: 'flex-start', justifyContent: 'flex-start',
width: 160 width: 160
}
}} }}
title={ title={
<div className="flex-column" style={{ width: '100%' }}> <div className="flex-column" style={{ width: '100%' }}>
-32
View File
@@ -1,32 +0,0 @@
import AutoTooltip from '@/components/auto-tooltip';
import _ from 'lodash';
import React from 'react';
import styled from 'styled-components';
const LabelsWrapper = styled.div`
display: flex;
flex-wrap: wrap;
gap: 6px;
`;
interface LabelCellProps {
labels: Record<string, any>;
}
const LabelsCell: React.FC<LabelCellProps> = ({ labels }) => (
<LabelsWrapper>
{_.map(labels, (value: string, key: string) => (
<AutoTooltip
key={key}
className="m-r-0"
maxWidth={155}
style={{ paddingInline: 8, borderRadius: 12 }}
>
<span>{key}</span>
<span>:{value}</span>
</AutoTooltip>
))}
</LabelsWrapper>
);
export default LabelsCell;
@@ -1,138 +0,0 @@
import AutoComplete from '@/components/seal-form/auto-complete';
import { MinusOutlined } from '@ant-design/icons';
import { useIntl } from '@umijs/max';
import { Button, Tooltip } from 'antd';
import _ from 'lodash';
import React, { useMemo, useState } from 'react';
import { useLabelSelectorContext } from './context';
import './styles/label-item.less';
interface LabelItemProps {
label: {
key: string;
value: string;
};
labels?: Record<string, any>;
labelKey?: string;
labelValue?: string;
keyAddon?: React.ReactNode;
valueAddon?: React.ReactNode;
seperator?: string;
labelList: { key: string; value: string }[];
disabled?: boolean;
onDelete?: () => void;
onChange?: (params: { key: string; value: string }) => void;
onPaste?: (e: any) => void;
onBlur?: (e: any, type: string) => void;
}
const LabelItem: React.FC<LabelItemProps> = ({
labels,
label,
labelList,
seperator,
keyAddon,
valueAddon,
disabled,
onChange,
onDelete,
onBlur
}) => {
const intl = useIntl();
const [open, setOpen] = useState(false);
const { options } = useLabelSelectorContext();
const keyOptions = useMemo(() => {
return options?.filter(
(item) => !_.has(labels, item.value) || item.value === label.key
);
}, [labels, options]);
const valueOptions = useMemo(() => {
return options?.find((item) => item.value === label.key)?.children || [];
}, [label.key, options]);
const handleOnValueChange = (value: string) => {
onChange?.({
key: label.key,
value: value
});
};
const handleOnKeyChange = (key: any) => {
onChange?.({
key,
value: label.value
});
};
const handleKeyOnBlur = (e: any, type: string) => {
const val = e.target.value;
// has duplicate key
const duplicates = _.filter(
labelList,
(item: Global.BaseListItem<string>) => val && val === item.key
);
if (duplicates.length > 1) {
setOpen(true);
onChange?.({
key: '',
value: label.value
});
setTimeout(() => {
setOpen(false);
}, 1000);
} else {
setOpen(false);
}
onBlur?.(e, type);
};
return (
<div className="label-item">
<div className="label-key">
{keyAddon ?? (
<Tooltip
open={open}
title={intl.formatMessage({ id: 'resources.table.key.tips' })}
>
<AutoComplete
options={keyOptions}
disabled={disabled}
checkStatus="success"
label={intl.formatMessage({ id: 'common.input.key' })}
value={label.key}
onChange={handleOnKeyChange}
onBlur={(e: any) => handleKeyOnBlur(e, 'key')}
></AutoComplete>
</Tooltip>
)}
</div>
{seperator && <span className="seprator">{seperator}</span>}
<div className="label-value">
{valueAddon ?? (
<AutoComplete
options={valueOptions}
disabled={disabled}
checkStatus={label.value ? 'success' : ''}
label={intl.formatMessage({ id: 'common.input.value' })}
value={label.value}
onChange={handleOnValueChange}
onBlur={(e: any) => onBlur?.(e, 'value')}
></AutoComplete>
)}
</div>
{!disabled && (
<Button
size="small"
className="btn"
type="default"
shape="circle"
onClick={onDelete}
>
<MinusOutlined />
</Button>
)}
</div>
);
};
export default LabelItem;
-25
View File
@@ -1,25 +0,0 @@
import React from 'react';
interface LabelSelectorContextProps {
options?: Array<{
label: string;
value: string | number;
children?: { label: string; value: string | number }[];
}>;
currentData?: Record<string, any>;
}
export const LabelSelectorContext =
React.createContext<LabelSelectorContextProps>(
{} as LabelSelectorContextProps
);
export const useLabelSelectorContext = () => {
const context = React.useContext(LabelSelectorContext);
if (!context) {
throw new Error(
'useLabelSelectorContext must be used within a LabelSelectorProvider'
);
}
return context;
};
+13 -30
View File
@@ -1,6 +1,6 @@
import { useIntl } from '@umijs/max'; import { useIntl } from '@umijs/max';
import _ from 'lodash'; import _ from 'lodash';
import React, { useEffect, useState } from 'react'; import React, { useCallback, useEffect, useState } from 'react';
import Inner from './inner'; import Inner from './inner';
interface LabelSelectorProps { interface LabelSelectorProps {
@@ -8,8 +8,6 @@ interface LabelSelectorProps {
label?: string; label?: string;
btnText?: string; btnText?: string;
description?: React.ReactNode; description?: React.ReactNode;
disabled?: boolean;
isAutoComplete?: boolean;
onChange?: (labels: Record<string, any>) => void; onChange?: (labels: Record<string, any>) => void;
onBlur?: (e: any, type: string, index: number) => void; onBlur?: (e: any, type: string, index: number) => void;
onDelete?: (index: number) => void; onDelete?: (index: number) => void;
@@ -20,11 +18,9 @@ const LabelSelector: React.FC<LabelSelectorProps> = ({
onChange, onChange,
onBlur, onBlur,
onDelete, onDelete,
disabled,
label, label,
btnText, btnText,
description, description
isAutoComplete
}) => { }) => {
const intl = useIntl(); const intl = useIntl();
const [labelsData, setLabelsData] = useState({}); const [labelsData, setLabelsData] = useState({});
@@ -45,30 +41,18 @@ const LabelSelector: React.FC<LabelSelectorProps> = ({
} }
}, [labels]); }, [labels]);
const handleLabelListChange = (list: { key: string; value: string }[]) => { const handleLabelListChange = useCallback(
(list: { key: string; value: string }[]) => {
setLabelList(list); setLabelList(list);
}; },
[setLabelList]
);
const handleLabelsChange = (data: Record<string, any>) => { const handleLabelsChange = (data: Record<string, any>) => {
console.log('handleLabelsChange', data); console.log('handleLabelsChange', data);
setLabelsData(data); setLabelsData(data);
onChange?.(data); onChange?.(data);
}; };
const updateLabels = (list: { key: string; value: string }[]) => {
const newLabels = _.reduce(
list,
(result: any, item: any) => {
if (item.key) {
result[item.key] = item.value;
}
return result;
},
{}
);
onChange?.(newLabels);
};
const handleOnPaste = ( const handleOnPaste = (
e: React.ClipboardEvent<HTMLTextAreaElement>, e: React.ClipboardEvent<HTMLTextAreaElement>,
index: number index: number
@@ -86,23 +70,22 @@ const LabelSelector: React.FC<LabelSelectorProps> = ({
const [key, value] = line.split('=').map((part) => part.trim()); const [key, value] = line.split('=').map((part) => part.trim());
return { key, value }; return { key, value };
}); });
console.log(lines, parsedData);
const newPairs = [...labelList]; setLabelList((prevPairs) => {
const newPairs = [...prevPairs];
newPairs.splice(index, 1, ...parsedData); newPairs.splice(index, 1, ...parsedData);
return newPairs;
updateLabels(newPairs); });
setLabelList(newPairs);
}; };
return ( return (
<Inner <Inner
disabled={disabled}
label={label} label={label}
btnText={btnText} btnText={btnText}
description={ description={
description ?? intl.formatMessage({ id: 'models.form.keyvalue.paste' }) description ?? intl.formatMessage({ id: 'models.form.keyvalue.paste' })
} }
isAutoComplete={isAutoComplete}
labels={labelsData} labels={labelsData}
labelList={labelList} labelList={labelList}
onChange={handleLabelsChange} onChange={handleLabelsChange}
@@ -114,4 +97,4 @@ const LabelSelector: React.FC<LabelSelectorProps> = ({
); );
}; };
export default LabelSelector; export default React.memo(LabelSelector);
+26 -38
View File
@@ -1,14 +1,14 @@
import { PlusOutlined } from '@ant-design/icons';
import { useIntl } from '@umijs/max'; import { useIntl } from '@umijs/max';
import { Button } from 'antd';
import _ from 'lodash'; import _ from 'lodash';
import React, { useEffect } from 'react'; import React, { useRef } from 'react';
import AutoCompleteItem from './autocomplete-item';
import LabelItem from './label-item'; import LabelItem from './label-item';
import Wrapper from './wrapper'; import Wrapper from './wrapper';
interface LabelSelectorProps { interface LabelSelectorProps {
labels: Record<string, any>; labels: Record<string, any>;
label?: string; label?: string;
btnText?: string; btnText?: string;
isAutoComplete?: boolean;
labelList: Array<{ key: string; value: string }>; labelList: Array<{ key: string; value: string }>;
onLabelListChange: (list: { key: string; value: string }[]) => void; onLabelListChange: (list: { key: string; value: string }[]) => void;
onChange?: (labels: Record<string, any>) => void; onChange?: (labels: Record<string, any>) => void;
@@ -16,7 +16,6 @@ interface LabelSelectorProps {
onBlur?: (e: any, type: string, index: number) => void; onBlur?: (e: any, type: string, index: number) => void;
onDelete?: (index: number) => void; onDelete?: (index: number) => void;
description?: React.ReactNode; description?: React.ReactNode;
disabled?: boolean;
} }
const Inner: React.FC<LabelSelectorProps> = ({ const Inner: React.FC<LabelSelectorProps> = ({
@@ -27,17 +26,13 @@ const Inner: React.FC<LabelSelectorProps> = ({
onPaste, onPaste,
onBlur, onBlur,
onDelete, onDelete,
disabled,
label, label,
btnText, btnText,
description, description
isAutoComplete
}) => { }) => {
const intl = useIntl(); const intl = useIntl();
const buttonRef = useRef<HTMLButtonElement>(null);
useEffect(() => { const boxRef = useRef<HTMLDivElement>(null);
console.log('labels changed in Inner', labels);
}, [labels]);
const updateLabels = (list: { key: string; value: string }[]) => { const updateLabels = (list: { key: string; value: string }[]) => {
const newLabels = _.reduce( const newLabels = _.reduce(
@@ -80,35 +75,11 @@ const Inner: React.FC<LabelSelectorProps> = ({
}; };
return ( return (
<Wrapper <Wrapper label={label} description={description}>
label={label}
description={description}
onAdd={handleAddLabel}
disabled={disabled}
btnText={btnText}
>
<> <>
{isAutoComplete {labelList?.map((item: any, index: number) => {
? labelList?.map((item: any, index: number) => {
return (
<AutoCompleteItem
disabled={disabled}
key={index}
label={item}
seperator=":"
labels={labels}
labelList={labelList}
onDelete={() => handleOnDelete(index)}
onChange={(obj) => handleOnChange(index, obj)}
onPaste={(e) => onPaste?.(e, index)}
onBlur={(e: any, type: string) => onBlur?.(e, type, index)}
/>
);
})
: labelList?.map((item: any, index: number) => {
return ( return (
<LabelItem <LabelItem
disabled={disabled}
key={index} key={index}
label={item} label={item}
seperator=":" seperator=":"
@@ -120,9 +91,26 @@ const Inner: React.FC<LabelSelectorProps> = ({
/> />
); );
})} })}
<div className="flex justify-center">
<Button
ref={buttonRef}
type="text"
block
style={{
marginTop: 16,
backgroundColor: 'var(--ant-color-fill-secondary)'
}}
onClick={handleAddLabel}
>
<PlusOutlined className="font-size-14" />{' '}
{intl.formatMessage({
id: btnText || 'common.button.addSelector'
})}
</Button>
</div>
</> </>
</Wrapper> </Wrapper>
); );
}; };
export default Inner; export default React.memo(Inner);
+2 -10
View File
@@ -1,5 +1,4 @@
import SealInput from '@/components/seal-form/seal-input'; import SealInput from '@/components/seal-form/seal-input';
import { Global } from '@/config/global';
import { MinusOutlined } from '@ant-design/icons'; import { MinusOutlined } from '@ant-design/icons';
import { useIntl } from '@umijs/max'; import { useIntl } from '@umijs/max';
import { Button, Tooltip } from 'antd'; import { Button, Tooltip } from 'antd';
@@ -16,9 +15,8 @@ interface LabelItemProps {
keyAddon?: React.ReactNode; keyAddon?: React.ReactNode;
valueAddon?: React.ReactNode; valueAddon?: React.ReactNode;
seperator?: string; seperator?: string;
labelList: { key: string; value: string }[];
disabled?: boolean;
onDelete?: () => void; onDelete?: () => void;
labelList: { key: string; value: string }[];
onChange?: (params: { key: string; value: string }) => void; onChange?: (params: { key: string; value: string }) => void;
onPaste?: (e: any) => void; onPaste?: (e: any) => void;
onBlur?: (e: any, type: string) => void; onBlur?: (e: any, type: string) => void;
@@ -29,7 +27,6 @@ const LabelItem: React.FC<LabelItemProps> = ({
seperator, seperator,
keyAddon, keyAddon,
valueAddon, valueAddon,
disabled,
onChange, onChange,
onDelete, onDelete,
onPaste, onPaste,
@@ -85,7 +82,6 @@ const LabelItem: React.FC<LabelItemProps> = ({
title={intl.formatMessage({ id: 'resources.table.key.tips' })} title={intl.formatMessage({ id: 'resources.table.key.tips' })}
> >
<SealInput.Input <SealInput.Input
disabled={disabled}
checkStatus="success" checkStatus="success"
label={intl.formatMessage({ id: 'common.input.key' })} label={intl.formatMessage({ id: 'common.input.key' })}
value={label.key} value={label.key}
@@ -100,8 +96,6 @@ const LabelItem: React.FC<LabelItemProps> = ({
<div className="label-value"> <div className="label-value">
{valueAddon ?? ( {valueAddon ?? (
<SealInput.Input <SealInput.Input
trim={false}
disabled={disabled}
checkStatus={label.value ? 'success' : ''} checkStatus={label.value ? 'success' : ''}
label={intl.formatMessage({ id: 'common.input.value' })} label={intl.formatMessage({ id: 'common.input.value' })}
value={label.value} value={label.value}
@@ -110,7 +104,6 @@ const LabelItem: React.FC<LabelItemProps> = ({
></SealInput.Input> ></SealInput.Input>
)} )}
</div> </div>
{!disabled && (
<Button <Button
size="small" size="small"
className="btn" className="btn"
@@ -120,9 +113,8 @@ const LabelItem: React.FC<LabelItemProps> = ({
> >
<MinusOutlined /> <MinusOutlined />
</Button> </Button>
)}
</div> </div>
); );
}; };
export default LabelItem; export default React.memo(LabelItem);
@@ -0,0 +1,20 @@
.wrapper {
position: relative;
padding: 14px;
padding-top: 34px;
border: 1px solid var(--ant-color-border);
border-radius: var(--border-radius-base);
display: flex;
width: 100%;
flex-direction: column;
:global {
.label {
position: absolute;
left: 16px;
line-height: 1;
top: 12px;
color: var(--ant-color-text-tertiary);
}
}
}
+8 -60
View File
@@ -1,56 +1,15 @@
import LabelInfo from '@/components/seal-form/components/label-info'; import LabelInfo from '@/components/seal-form/components/label-info';
import { PlusOutlined } from '@ant-design/icons';
import { useIntl } from '@umijs/max';
import { Button } from 'antd';
import React from 'react'; import React from 'react';
import styled from 'styled-components'; import styles from './styles/wrapper.less';
interface WrapperProps { const Wrapper: React.FC<{
label?: React.ReactNode; label?: React.ReactNode;
description?: React.ReactNode; description?: React.ReactNode;
labelExtra?: React.ReactNode; labelExtra?: React.ReactNode;
children: React.ReactNode; children: React.ReactNode;
btnText?: string; }> = ({ children, label, description, labelExtra, ...rest }) => {
disabled?: boolean;
onAdd?: () => void;
button?: React.ReactNode;
}
const Container = styled.div`
position: relative;
padding: 14px;
padding-top: 34px;
border: 1px solid var(--ant-color-border);
border-radius: var(--border-radius-base);
display: flex;
width: 100%;
flex-direction: column;
.label {
position: absolute;
left: 16px;
line-height: 1;
top: 12px;
color: var(--ant-color-text-tertiary);
}
`;
const ButtonWrapper = styled.div`
margin-top: 16px;
`;
const Wrapper: React.FC<WrapperProps> = ({
children,
label,
description,
labelExtra,
onAdd,
btnText,
disabled,
button
}) => {
const intl = useIntl();
return ( return (
<Container> <div className={styles['wrapper']}>
{label && ( {label && (
<span className="label"> <span className="label">
<LabelInfo <LabelInfo
@@ -60,21 +19,10 @@ const Wrapper: React.FC<WrapperProps> = ({
></LabelInfo> ></LabelInfo>
</span> </span>
)} )}
{children} {React.isValidElement(children)
{!disabled && ( ? React.cloneElement(children, { ...rest })
<ButtonWrapper> : children}
{button || ( </div>
<Button variant="filled" color="default" block onClick={onAdd}>
<PlusOutlined className="font-size-14" />
{btnText ||
intl.formatMessage({
id: 'common.button.addSelector'
})}
</Button>
)}
</ButtonWrapper>
)}
</Container>
); );
}; };
+1 -19
View File
@@ -2,27 +2,9 @@ import langConfigMap from '@/locales/lang-config-map';
import { GlobalOutlined } from '@ant-design/icons'; import { GlobalOutlined } from '@ant-design/icons';
import { getAllLocales, setLocale } from '@umijs/max'; import { getAllLocales, setLocale } from '@umijs/max';
import { Dropdown } from 'antd'; import { Dropdown } from 'antd';
import { createStyles } from 'antd-style';
import { get } from 'lodash'; import { get } from 'lodash';
const useStyles = createStyles(({ token, css }) => ({
button: css`
color: ${token.colorText};
padding: 0 12px;
cursor: pointer;
.anticon {
color: ${token.colorText};
}
&:hover {
.anticon {
color: ${token.colorTextTertiary};
}
}
`
}));
const LangSelect = () => { const LangSelect = () => {
const { styles } = useStyles();
const allLocals = getAllLocales(); const allLocals = getAllLocales();
const items = allLocals.map((key) => { const items = allLocals.map((key) => {
return { return {
@@ -43,7 +25,7 @@ const LangSelect = () => {
return ( return (
<Dropdown menu={{ items }}> <Dropdown menu={{ items }}>
<span className={styles.button}> <span style={{ padding: '0 12px', cursor: 'pointer' }}>
<GlobalOutlined /> <GlobalOutlined />
</span> </span>
</Dropdown> </Dropdown>
+7 -5
View File
@@ -8,14 +8,13 @@ interface HintInputProps {
onChange: (value: string) => void; onChange: (value: string) => void;
onBlur?: (e: any) => void; onBlur?: (e: any) => void;
placeholder?: string; placeholder?: string;
trim?: boolean;
sourceOptions?: Global.HintOptions[]; sourceOptions?: Global.HintOptions[];
} }
const matchReg = /[^=]+=[^=]*$/; const matchReg = /[^=]+=[^=]*$/;
const HintInput: React.FC<HintInputProps> = (props) => { const HintInput: React.FC<HintInputProps> = (props) => {
const { value, label, onChange, onBlur, sourceOptions, trim = true } = props; const { value, label, onChange, onBlur, sourceOptions } = props;
const cursorPosRef = React.useRef(0); const cursorPosRef = React.useRef(0);
const contextBeforeCursorRef = React.useRef(''); const contextBeforeCursorRef = React.useRef('');
const [options, setOptions] = React.useState< const [options, setOptions] = React.useState<
@@ -71,7 +70,11 @@ const HintInput: React.FC<HintInputProps> = (props) => {
const handleInput = (e: any) => { const handleInput = (e: any) => {
getContextBeforeCursor(e); getContextBeforeCursor(e);
onChange(e.target.value); onChange(e.target.value?.trim());
};
const handleOnChange = (value: string) => {
onChange(value?.trim());
}; };
const handleOnSelect = (value: string) => { const handleOnSelect = (value: string) => {
@@ -90,10 +93,9 @@ const HintInput: React.FC<HintInputProps> = (props) => {
onBlur={onBlur} onBlur={onBlur}
label={label} label={label}
options={options} options={options}
trim={trim}
style={{ width: '100%' }} style={{ width: '100%' }}
/> />
); );
}; };
export default HintInput; export default React.memo(HintInput);
+29 -13
View File
@@ -1,3 +1,6 @@
import { PlusOutlined } from '@ant-design/icons';
import { useIntl } from '@umijs/max';
import { Button } from 'antd';
import _ from 'lodash'; import _ from 'lodash';
import React from 'react'; import React from 'react';
import Wrapper from '../label-selector/wrapper'; import Wrapper from '../label-selector/wrapper';
@@ -5,19 +8,19 @@ import ListItem from './list-item';
interface ListInputProps { interface ListInputProps {
dataList: string[]; dataList: string[];
label?: React.ReactNode; label: React.ReactNode;
description?: React.ReactNode; description?: React.ReactNode;
btnText?: string; btnText?: string;
options?: Global.HintOptions[]; options?: Global.HintOptions[];
placeholder?: string; placeholder?: string;
labelExtra?: React.ReactNode; labelExtra?: React.ReactNode;
trim?: boolean;
onChange: (data: string[]) => void; onChange: (data: string[]) => void;
onBlur?: (e: any, index: number) => void; onBlur?: (e: any, index: number) => void;
onDelete?: (index: number) => void; onDelete?: (index: number) => void;
} }
const ListInput: React.FC<ListInputProps> = (props) => { const ListInput: React.FC<ListInputProps> = (props) => {
const intl = useIntl();
const { const {
dataList, dataList,
label, label,
@@ -27,11 +30,11 @@ const ListInput: React.FC<ListInputProps> = (props) => {
onDelete, onDelete,
btnText, btnText,
options, options,
labelExtra, labelExtra
trim = true
} = props; } = props;
const [list, setList] = React.useState<{ value: string; uid: number }[]>([]); const [list, setList] = React.useState<{ value: string; uid: number }[]>([]);
const countRef = React.useRef(0); const countRef = React.useRef(0);
const buttonRef = React.useRef<HTMLButtonElement>(null);
const updateCountRef = () => { const updateCountRef = () => {
countRef.current = countRef.current + 1; countRef.current = countRef.current + 1;
@@ -62,6 +65,9 @@ const ListInput: React.FC<ListInputProps> = (props) => {
uid: countRef.current uid: countRef.current
}); });
setList(values); setList(values);
// setTimeout(() => {
// buttonRef.current?.scrollIntoView?.({ behavior: 'smooth' });
// }, 100);
}; };
React.useEffect(() => { React.useEffect(() => {
@@ -79,13 +85,7 @@ const ListInput: React.FC<ListInputProps> = (props) => {
}, [dataList]); }, [dataList]);
return ( return (
<Wrapper <Wrapper label={label} description={description} labelExtra={labelExtra}>
label={label}
description={description}
labelExtra={labelExtra}
onAdd={handleOnAdd}
btnText={btnText}
>
<> <>
{_.map(list, (item: any, index: number) => { {_.map(list, (item: any, index: number) => {
return ( return (
@@ -97,13 +97,29 @@ const ListInput: React.FC<ListInputProps> = (props) => {
onBlur={(e) => onBlur?.(e, index)} onBlur={(e) => onBlur?.(e, index)}
onRemove={() => handleOnRemove(index)} onRemove={() => handleOnRemove(index)}
onChange={(val) => handleOnChange(val, index)} onChange={(val) => handleOnChange(val, index)}
trim={trim}
/> />
); );
})} })}
<div className="flex justify-center">
<Button
ref={buttonRef}
variant="filled"
color="default"
block
style={{
marginTop: 16
}}
onClick={handleOnAdd}
>
<PlusOutlined className="font-size-14" />{' '}
{intl.formatMessage({
id: btnText
})}
</Button>
</div>
</> </>
</Wrapper> </Wrapper>
); );
}; };
export default ListInput; export default React.memo(ListInput);
+2 -12
View File
@@ -13,19 +13,10 @@ interface LabelItemProps {
label?: string; label?: string;
placeholder?: string; placeholder?: string;
options?: Global.HintOptions[]; options?: Global.HintOptions[];
trim?: boolean;
} }
const ListItem: React.FC<LabelItemProps> = (props) => { const ListItem: React.FC<LabelItemProps> = (props) => {
const { const { onRemove, onChange, onBlur, label, value, options } = props;
onRemove,
onChange,
onBlur,
label,
value,
options,
trim = true
} = props;
const handleOnChange = (value: any) => { const handleOnChange = (value: any) => {
onChange(value); onChange(value);
@@ -39,7 +30,6 @@ const ListItem: React.FC<LabelItemProps> = (props) => {
onBlur={onBlur} onBlur={onBlur}
label={label} label={label}
sourceOptions={options} sourceOptions={options}
trim={trim}
placeholder={props.placeholder} placeholder={props.placeholder}
/> />
<Button <Button
@@ -54,4 +44,4 @@ const ListItem: React.FC<LabelItemProps> = (props) => {
); );
}; };
export default ListItem; export default React.memo(ListItem);
+1 -1
View File
@@ -1,7 +1,7 @@
export const controlSeqRegex = /\x1b\[(\d*);?(\d*)?([A-DJKHfm])/g; export const controlSeqRegex = /\x1b\[(\d*);?(\d*)?([A-DJKHfm])/g;
export const replaceLineRegex = /\r\n/g; export const replaceLineRegex = /\r\n/g;
export const PageSize = 1000; export const PageSize = 500;
export const throttle = <T extends (...args: any[]) => void>( export const throttle = <T extends (...args: any[]) => void>(
func: T, func: T,

Some files were not shown because too many files have changed in this diff Show More