Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f4ea23dd14 | ||
|
|
5bd0020e49 | ||
|
|
782e81568d | ||
|
|
2ecf83ddae | ||
|
|
de0982d590 | ||
|
|
e5755a2eec | ||
|
|
e755c34ab5 | ||
|
|
85e4f1a5ed | ||
|
|
3eb403a2c4 | ||
|
|
79442a6a6c | ||
|
|
f49001b742 | ||
|
|
e80ecfac8d | ||
|
|
ff67fba2c9 | ||
|
|
3f63831488 | ||
|
|
19babbc73d | ||
|
|
26a4d5474d | ||
|
|
af3e03230a | ||
|
|
551f04011a | ||
|
|
ea39a45f31 | ||
|
|
ebff5695ff | ||
|
|
9b63c1a515 | ||
|
|
de882dc62e | ||
|
|
8f9562d6a4 | ||
|
|
5d383ec39b | ||
|
|
2687fb187d | ||
|
|
ca43c0b4b2 | ||
|
|
71bbf59467 | ||
|
|
18f861f771 | ||
|
|
e85d59dfb0 | ||
|
|
78c8b60121 | ||
|
|
e22b5ed516 | ||
|
|
906f59a566 | ||
|
|
d1c3edfd9b | ||
|
|
e8a0f33bde | ||
|
|
825687f2a8 | ||
|
|
2852648a65 | ||
|
|
0f3f31ef45 | ||
|
|
864cf76c07 | ||
|
|
98e6c71bf8 | ||
|
|
aee9566bbb | ||
|
|
b61c0d6dee | ||
|
|
f963b3a000 | ||
|
|
03963b736a | ||
|
|
a34c375e2d | ||
|
|
e8eea9573e | ||
|
|
b7656d65e7 | ||
|
|
d1fa6dd03c | ||
|
|
2045bfaf78 | ||
|
|
653ada5f0f | ||
|
|
cbc000d35a | ||
|
|
11a676be83 | ||
|
|
7eedea8035 | ||
|
|
2d4ddea915 | ||
|
|
76d7a50fac | ||
|
|
84187eedc4 | ||
|
|
ee5c30934b | ||
|
|
b4b25d4300 | ||
|
|
9eb1dba4a1 | ||
|
|
285e274818 | ||
|
|
0077d109e7 | ||
|
|
9cd508a92a | ||
|
|
9ffd4def64 | ||
|
|
fdb6ac7575 | ||
|
|
64e2cfa35a | ||
|
|
39efca0d4c | ||
|
|
cd2942405a | ||
|
|
fadff34f5f | ||
|
|
7334bcdf83 | ||
|
|
e1e38f0404 | ||
|
|
38cc179068 | ||
|
|
5147d0d004 | ||
|
|
c237c9dbde | ||
|
|
34bd2c3031 | ||
|
|
6173f3b459 | ||
|
|
333b430b97 | ||
|
|
4c22d08da5 | ||
|
|
219f9871a7 | ||
|
|
80527d5985 | ||
|
|
7e5edd6387 | ||
|
|
4ca6a221e4 | ||
|
|
e5733a7f6e | ||
|
|
23b7936924 | ||
|
|
9be465a8cf | ||
|
|
4bbae673dc | ||
|
|
e66ee5d3f1 | ||
|
|
20872100d1 | ||
|
|
06f33ded10 | ||
|
|
adb5cd72b9 | ||
|
|
303429c52c | ||
|
|
1a92cc8543 | ||
|
|
d5cc617848 | ||
|
|
4af71b5b52 | ||
|
|
2dab89363d | ||
|
|
ab9f9a9679 | ||
|
|
c5166f07f1 | ||
|
|
86c2f8eda1 | ||
|
|
57d41e79c7 | ||
|
|
5cd9b8c22f | ||
|
|
56eba0d4fc | ||
|
|
7936d0063d | ||
|
|
3613fb3f69 | ||
|
|
608b2e31c7 | ||
|
|
2006827968 | ||
|
|
f6a3497b0c | ||
|
|
d7ef64746c | ||
|
|
b733b3615d | ||
|
|
e9fa3e3aa3 | ||
|
|
b77f796661 | ||
|
|
c077db1129 | ||
|
|
801c88a3e2 | ||
|
|
646d235c65 | ||
|
|
a86bed294e | ||
|
|
697f1f9c25 | ||
|
|
61065ee0b7 | ||
|
|
df65d3e2f6 | ||
|
|
6969cb0b9c | ||
|
|
7e81871118 | ||
|
|
ba80ade643 | ||
|
|
0401cf5fb9 | ||
|
|
53b431f100 | ||
|
|
1a082529c6 | ||
|
|
bf57290197 | ||
|
|
a815e71bfe | ||
|
|
cc0580301e | ||
|
|
5853bd1eeb | ||
|
|
ab93c53092 | ||
|
|
818c4fc9a2 | ||
|
|
fc7350b7e2 | ||
|
|
1e4018350f | ||
|
|
f017d609fb | ||
|
|
ccb6e72057 | ||
|
|
32ad9525a6 | ||
|
|
afedd4337b | ||
|
|
1f7acc7cf6 | ||
|
|
e6a5c19f0b | ||
|
|
8e07302ac3 | ||
|
|
0238431b96 | ||
|
|
3a7a4ccf30 | ||
|
|
9b843151fe | ||
|
|
19e275a17a | ||
|
|
49c6bb71a1 | ||
|
|
005295ac97 | ||
|
|
37a3aa75cc | ||
|
|
07243b206e | ||
|
|
f732f5c390 | ||
|
|
1da16a04fe | ||
|
|
e2da7c5f96 | ||
|
|
ca9ceda2f7 | ||
|
|
059416bb47 | ||
|
|
4196351e48 | ||
|
|
f02b972dc0 | ||
|
|
8398ac59a1 | ||
|
|
83403a1a1a | ||
|
|
620c113fae | ||
|
|
903500dcfa | ||
|
|
68d6993acd | ||
|
|
2c9f5062a9 | ||
|
|
d3f39ae06b | ||
|
|
f42f159785 | ||
|
|
bb7c8e9c85 | ||
|
|
663b6c7b4e | ||
|
|
1fa2124efd | ||
|
|
bcd1a339f9 | ||
|
|
c41e5d9c55 | ||
|
|
b5fa40047a | ||
|
|
caabeb065a | ||
|
|
d393f9ee9e | ||
|
|
eb509166ee | ||
|
|
0dfe77df37 | ||
|
|
46ecc2467b | ||
|
|
a5c1b8e15f | ||
|
|
54703c67c1 | ||
|
|
e863b84016 | ||
|
|
1a693f70ac | ||
|
|
a9de153c00 | ||
|
|
42217cf079 | ||
|
|
bca1e8140e | ||
|
|
ee95fabc47 | ||
|
|
67552c14af | ||
|
|
521bf211a2 | ||
|
|
8e77e19631 | ||
|
|
8b91a0546c | ||
|
|
715c71f9a5 | ||
|
|
1ec4a949b8 | ||
|
|
fc2388093d | ||
|
|
3a9bf17d7e | ||
|
|
a3002ce450 | ||
|
|
2515e8f598 | ||
|
|
dcc0db35b5 | ||
|
|
749adb56f8 | ||
|
|
f542ebecb4 | ||
|
|
b640963f65 | ||
|
|
8f5990c537 | ||
|
|
bf400547e1 | ||
|
|
4d9dec8511 | ||
|
|
1ac63e36df | ||
|
|
f36613227e | ||
|
|
d063dfd652 | ||
|
|
c9c9401b0b | ||
|
|
913f2f5077 | ||
|
|
336bafd6ce | ||
|
|
5e1a26b743 | ||
|
|
d9d5ca95df | ||
|
|
f1bb305cbc | ||
|
|
c467280170 | ||
|
|
deeee70807 | ||
|
|
07ade24ea1 | ||
|
|
8de272239e | ||
|
|
6e5a7035c8 | ||
|
|
632f206228 | ||
|
|
f04f1f828f | ||
|
|
4f91443957 | ||
|
|
0c1057d554 | ||
|
|
43936e21d9 | ||
|
|
949eded4ee | ||
|
|
84859d1b7c | ||
|
|
553ec724c2 | ||
|
|
207c90e26c | ||
|
|
64bbba7576 | ||
|
|
62dbd18ef8 | ||
|
|
55b0a20a4a | ||
|
|
29b74f85c5 | ||
|
|
ed49e03802 | ||
|
|
4711c848f8 | ||
|
|
33df825b73 | ||
|
|
89001dbb92 | ||
|
|
43e1dd0b0e | ||
|
|
f9cf111a86 | ||
|
|
99f8914830 | ||
|
|
6da877709d | ||
|
|
c4948dc11f | ||
|
|
3f243129a4 | ||
|
|
eb2cae7a77 | ||
|
|
a2bbcd3939 | ||
|
|
0650c4bc26 | ||
|
|
7c32bf97ae | ||
|
|
70aecc1f0c | ||
|
|
5bd5c7d2c5 | ||
|
|
d6dce4021d | ||
|
|
ccfdb99348 | ||
|
|
2ca712435a | ||
|
|
0af8e34a8f | ||
|
|
f2583e152a | ||
|
|
cf70bd2dc1 | ||
|
|
8f50af917f | ||
|
|
4bdde365cf | ||
|
|
7f1bc33eed | ||
|
|
d0d24aba4e | ||
|
|
0d68c0d683 | ||
|
|
e1ab735823 | ||
|
|
bb67a8cbbb | ||
|
|
c8a7ac7dc6 | ||
|
|
788a294200 | ||
|
|
f34935a634 | ||
|
|
a7331256fc | ||
|
|
8fa3941db9 | ||
|
|
ada120c1b9 | ||
|
|
72f7168893 | ||
|
|
75b70a26bd | ||
|
|
4433e5d8e5 | ||
|
|
351b2e1417 | ||
|
|
3c7dc5a49e | ||
|
|
9895231696 | ||
|
|
3fa3281c51 | ||
|
|
bbf11d0374 | ||
|
|
04ee696f6a | ||
|
|
ddbbe92a85 | ||
|
|
32ba5e1802 | ||
|
|
a1c6eb177a | ||
|
|
14d60264c7 | ||
|
|
f4d6abace7 | ||
|
|
a5e1717138 | ||
|
|
472a4d400b | ||
|
|
fda4786737 | ||
|
|
c3686579b2 | ||
|
|
f2dfb2f32d | ||
|
|
60ee9f1a15 | ||
|
|
f43d1af6a7 | ||
|
|
b631b4c7b7 | ||
|
|
e3aba0529f | ||
|
|
cf682feae9 | ||
|
|
03d798760c | ||
|
|
51450c2b02 | ||
|
|
751ddbb3f3 | ||
|
|
36b3620d0c | ||
|
|
fd3eb513ff | ||
|
|
af0aaddbfd | ||
|
|
b167170aa9 | ||
|
|
642ac3ebc4 | ||
|
|
69cceb4fc7 | ||
|
|
ac32e23e87 | ||
|
|
04de4ff606 | ||
|
|
27761566c9 | ||
|
|
3d93eda92d | ||
|
|
6943571a6d | ||
|
|
28994cb9da | ||
|
|
5866634b3f | ||
|
|
e3d91f1cc7 | ||
|
|
8ac06259f5 | ||
|
|
c8933a6669 | ||
|
|
6c2da264db | ||
|
|
993f01a301 | ||
|
|
9e29e7283c | ||
|
|
7da106798d | ||
|
|
b2e9cbb44a | ||
|
|
143f6df2c1 | ||
|
|
a64505fcc1 | ||
|
|
0aaf919827 | ||
|
|
bf8e988488 | ||
|
|
d26ea28028 | ||
|
|
abdd5e08a6 | ||
|
|
47a5ef915b | ||
|
|
5861508153 | ||
|
|
85754ccfb5 | ||
|
|
cbeea23295 | ||
|
|
e2d79a6802 | ||
|
|
c68d85096b | ||
|
|
fcafa83111 | ||
|
|
47d7418eb1 | ||
|
|
8751400ae3 | ||
|
|
3889c1fa71 | ||
|
|
a4d1d3b16c | ||
|
|
adfdf41e85 | ||
|
|
a6825f7ee3 | ||
|
|
830ee45161 | ||
|
|
5f7df3f871 | ||
|
|
34af730934 | ||
|
|
645801fe86 | ||
|
|
547f49a978 | ||
|
|
fd3d7a6f29 | ||
|
|
6be7140a47 | ||
|
|
ca7dc02d88 | ||
|
|
1924c45358 | ||
|
|
be92f92991 | ||
|
|
a9b909cca1 | ||
|
|
175b83dd68 | ||
|
|
df85284a60 | ||
|
|
17ffa93393 | ||
|
|
73c525127f | ||
|
|
3e76d7c321 | ||
|
|
9295f9cef3 | ||
|
|
9d1e6df8f0 | ||
|
|
0805d70c07 | ||
|
|
0133524f09 | ||
|
|
2c5100b2ed | ||
|
|
51296cdc15 | ||
|
|
f261bec248 | ||
|
|
97c7087f6c | ||
|
|
109906d083 | ||
|
|
81833c4938 | ||
|
|
1b6cfdbdc4 | ||
|
|
281d8bddc1 | ||
|
|
0eaa3aeeb7 | ||
|
|
4dcb0bcfed | ||
|
|
e69b7dfd6a | ||
|
|
38d63c71ee | ||
|
|
6880fd3f5f | ||
|
|
1771ca363a | ||
|
|
c896c88c23 | ||
|
|
5ef733729b | ||
|
|
f73e29937c | ||
|
|
8383547943 | ||
|
|
8070d3f1e6 | ||
|
|
a161fa953e | ||
|
|
471e1ae96f | ||
|
|
ed28bcdfb0 | ||
|
|
31ea81c211 | ||
|
|
283189a17c | ||
|
|
40020df865 | ||
|
|
aa6b5ec24f | ||
|
|
f53fe042a9 | ||
|
|
034a54bf4b | ||
|
|
894dd1e353 | ||
|
|
ca99db5a97 | ||
|
|
61e506010c | ||
|
|
4b4343a9fd | ||
|
|
9efacefba3 | ||
|
|
f288c9a9ad | ||
|
|
ec45f23485 | ||
|
|
1c11e73ff8 |
@@ -1,3 +1,4 @@
|
|||||||
PORT=9000
|
PORT=9000
|
||||||
UMI_DEV_SERVER_COMPRESS=none
|
UMI_DEV_SERVER_COMPRESS=none
|
||||||
|
DID_YOU_KNOW=none
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,2 @@
|
|||||||
#!/bin/sh
|
|
||||||
. "$(dirname "$0")/_/husky.sh"
|
|
||||||
|
|
||||||
npx lint-staged
|
npx lint-staged
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
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';
|
||||||
@@ -28,6 +29,9 @@ 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
|
||||||
? {
|
? {
|
||||||
@@ -50,29 +54,18 @@ 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`);
|
||||||
config
|
compressionPluginConfig(config);
|
||||||
.plugin('compression-webpack-plugin')
|
monacoPluginConfig(config);
|
||||||
.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',
|
||||||
|
|||||||
@@ -0,0 +1,36 @@
|
|||||||
|
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
|
||||||
|
}
|
||||||
|
]);
|
||||||
|
}
|
||||||
@@ -1,6 +1,7 @@
|
|||||||
const proxyTableList = [
|
const proxyTableList = [
|
||||||
'cli',
|
'cli',
|
||||||
'v1',
|
'v1',
|
||||||
|
'v2',
|
||||||
'auth',
|
'auth',
|
||||||
'v1-openai',
|
'v1-openai',
|
||||||
'version',
|
'version',
|
||||||
@@ -20,12 +21,6 @@ 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'
|
||||||
|
|||||||
@@ -5,13 +5,18 @@ export default [
|
|||||||
name: 'dashboard',
|
name: 'dashboard',
|
||||||
path: '/dashboard',
|
path: '/dashboard',
|
||||||
key: 'dashboard',
|
key: 'dashboard',
|
||||||
icon: 'AppstoreOutlined',
|
icon: 'icon-dashboard',
|
||||||
|
selectedIcon: 'icon-dashboard-filled',
|
||||||
|
defaultIcon: 'icon-dashboard',
|
||||||
access: 'canSeeAdmin',
|
access: 'canSeeAdmin',
|
||||||
component: './dashboard'
|
component: './dashboard',
|
||||||
|
routes: []
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'playground',
|
name: 'playground',
|
||||||
icon: 'ExperimentOutlined',
|
icon: 'icon-experiment',
|
||||||
|
selectedIcon: 'icon-experiment-filled',
|
||||||
|
defaultIcon: 'icon-experiment',
|
||||||
path: '/playground',
|
path: '/playground',
|
||||||
key: 'playground',
|
key: 'playground',
|
||||||
routes: [
|
routes: [
|
||||||
@@ -24,31 +29,19 @@ export default [
|
|||||||
title: 'Chat',
|
title: 'Chat',
|
||||||
path: '/playground/chat',
|
path: '/playground/chat',
|
||||||
key: 'chat',
|
key: 'chat',
|
||||||
icon: 'Comment',
|
icon: 'icon-chat',
|
||||||
|
selectedIcon: 'icon-chat-filled',
|
||||||
|
defaultIcon: 'icon-chat',
|
||||||
component: './playground/index'
|
component: './playground/index'
|
||||||
},
|
},
|
||||||
{
|
|
||||||
name: 'text2images',
|
|
||||||
title: 'Text2Images',
|
|
||||||
path: keepAliveRoutes.text2images,
|
|
||||||
key: 'text2images',
|
|
||||||
icon: 'Comment',
|
|
||||||
component: './playground/images'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'speech',
|
|
||||||
title: 'Speech',
|
|
||||||
path: keepAliveRoutes.speech,
|
|
||||||
key: 'speech',
|
|
||||||
icon: 'Comment',
|
|
||||||
component: './playground/speech'
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
name: 'embedding',
|
name: 'embedding',
|
||||||
title: 'embedding',
|
title: 'embedding',
|
||||||
path: '/playground/embedding',
|
path: '/playground/embedding',
|
||||||
key: 'embedding',
|
key: 'embedding',
|
||||||
icon: 'Comment',
|
icon: 'icon-embedding',
|
||||||
|
selectedIcon: 'icon-embedding-filled',
|
||||||
|
defaultIcon: 'icon-embedding',
|
||||||
component: './playground/embedding'
|
component: './playground/embedding'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -56,50 +49,208 @@ export default [
|
|||||||
title: 'Rerank',
|
title: 'Rerank',
|
||||||
path: '/playground/rerank',
|
path: '/playground/rerank',
|
||||||
key: 'rerank',
|
key: 'rerank',
|
||||||
icon: 'Comment',
|
icon: 'icon-reranker',
|
||||||
|
selectedIcon: 'icon-reranker-filled',
|
||||||
|
defaultIcon: 'icon-reranker',
|
||||||
component: './playground/rerank'
|
component: './playground/rerank'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'text2images',
|
||||||
|
title: 'Text2Images',
|
||||||
|
path: keepAliveRoutes.text2images,
|
||||||
|
key: 'text2images',
|
||||||
|
icon: 'icon-image1',
|
||||||
|
selectedIcon: 'icon-image-filled',
|
||||||
|
defaultIcon: 'icon-image1',
|
||||||
|
component: './playground/images'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'speech',
|
||||||
|
title: 'Speech',
|
||||||
|
path: keepAliveRoutes.speech,
|
||||||
|
key: 'speech',
|
||||||
|
icon: 'icon-audio1',
|
||||||
|
selectedIcon: 'icon-audio-filled',
|
||||||
|
defaultIcon: 'icon-audio1',
|
||||||
|
component: './playground/speech'
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
|
||||||
name: 'modelCatalog',
|
|
||||||
path: '/models/catalog',
|
|
||||||
key: 'modelsCatalog',
|
|
||||||
icon: 'icon-catalog',
|
|
||||||
access: 'canSeeAdmin',
|
|
||||||
component: './llmodels/catalog'
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
name: 'models',
|
name: 'models',
|
||||||
path: '/models/list',
|
path: '/models',
|
||||||
key: 'models',
|
key: 'models',
|
||||||
icon: 'Block',
|
routes: [
|
||||||
access: 'canSeeAdmin',
|
{
|
||||||
component: './llmodels/index'
|
path: '/models',
|
||||||
|
redirect: '/models/deployments'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'modelCatalog',
|
||||||
|
path: '/models/catalog',
|
||||||
|
key: 'modelsCatalog',
|
||||||
|
icon: 'icon-layers',
|
||||||
|
selectedIcon: 'icon-layers-filled',
|
||||||
|
defaultIcon: 'icon-layers',
|
||||||
|
access: 'canSeeAdmin',
|
||||||
|
component: './llmodels/catalog'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'deployment',
|
||||||
|
path: '/models/deployments',
|
||||||
|
key: 'modelDeployments',
|
||||||
|
icon: 'icon-rocket-launch1',
|
||||||
|
selectedIcon: 'icon-rocket-launch-fill',
|
||||||
|
defaultIcon: 'icon-rocket-launch1',
|
||||||
|
access: 'canSeeAdmin',
|
||||||
|
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',
|
||||||
component: './resources'
|
routes: [
|
||||||
|
{
|
||||||
|
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',
|
||||||
icon: 'KeyOutlined',
|
hideInMenu: true,
|
||||||
|
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',
|
||||||
|
|||||||
@@ -31,19 +31,23 @@
|
|||||||
"@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.21.6",
|
"antd": "^5.25.4",
|
||||||
"antd-style": "^3.6.2",
|
"antd-style": "^3.6.2",
|
||||||
"axios": "^1.7.2",
|
"axios": "^1.8.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",
|
||||||
@@ -51,34 +55,41 @@
|
|||||||
"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.0",
|
"rc-resize-observer": "^1.4.3",
|
||||||
"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",
|
||||||
@@ -100,5 +111,10 @@
|
|||||||
"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"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
|
After Width: | Height: | Size: 3.0 KiB |
|
After Width: | Height: | Size: 3.0 KiB |
|
After Width: | Height: | Size: 3.0 KiB |
@@ -4,8 +4,15 @@ 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
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
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,
|
||||||
@@ -13,8 +14,7 @@ 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('/models/list');
|
history.push(DEFAULT_ENTER_PAGE.adminForFirst);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@@ -48,7 +48,9 @@ export async function getInitialState(): Promise<{
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const fetchUserInfo = async (): Promise<Global.UserInfo> => {
|
const fetchUserInfo = async (config?: {
|
||||||
|
skipErrorHandler?: boolean;
|
||||||
|
}): Promise<Global.UserInfo> => {
|
||||||
try {
|
try {
|
||||||
const data = await queryCurrentUserState({
|
const data = await queryCurrentUserState({
|
||||||
skipErrorHandler: true
|
skipErrorHandler: true
|
||||||
@@ -57,8 +59,19 @@ export async function getInitialState(): Promise<{
|
|||||||
getUpdateCheck();
|
getUpdateCheck();
|
||||||
}
|
}
|
||||||
return data;
|
return data;
|
||||||
} catch (error) {
|
} catch (error: any) {
|
||||||
history.push(loginPath);
|
const data = error?.response?.data;
|
||||||
|
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;
|
||||||
};
|
};
|
||||||
@@ -78,7 +91,7 @@ export async function getInitialState(): Promise<{
|
|||||||
|
|
||||||
getAppVersionInfo();
|
getAppVersionInfo();
|
||||||
|
|
||||||
if (![loginPath].includes(location.pathname)) {
|
if (![DEFAULT_ENTER_PAGE.login].includes(location.pathname)) {
|
||||||
const userInfo = await fetchUserInfo();
|
const userInfo = await fetchUserInfo();
|
||||||
checkDefaultPage(userInfo);
|
checkDefaultPage(userInfo);
|
||||||
return {
|
return {
|
||||||
@@ -92,6 +105,6 @@ export async function getInitialState(): Promise<{
|
|||||||
}
|
}
|
||||||
|
|
||||||
export const request: RequestConfig = {
|
export const request: RequestConfig = {
|
||||||
baseURL: ' /v1',
|
baseURL: `/${GPUSTACK_API_BASE_URL}`,
|
||||||
...requestConfig
|
...requestConfig
|
||||||
};
|
};
|
||||||
|
|||||||
|
After Width: | Height: | Size: 1.3 KiB |
|
After Width: | Height: | Size: 14 KiB |
|
Before Width: | Height: | Size: 14 KiB After Width: | Height: | Size: 27 KiB |
@@ -0,0 +1 @@
|
|||||||
|
<?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>
|
||||||
|
After Width: | Height: | Size: 1.1 KiB |
@@ -0,0 +1 @@
|
|||||||
|
<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>
|
||||||
|
After Width: | Height: | Size: 934 B |
@@ -0,0 +1,8 @@
|
|||||||
|
<?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>
|
||||||
|
After Width: | Height: | Size: 1.3 KiB |
|
After Width: | Height: | Size: 33 KiB |
|
After Width: | Height: | Size: 5.6 KiB |
|
After Width: | Height: | Size: 3.0 KiB |
|
After Width: | Height: | Size: 7.0 KiB |
|
After Width: | Height: | Size: 28 KiB |
|
After Width: | Height: | Size: 7.1 KiB |
|
After Width: | Height: | Size: 6.1 KiB |
|
After Width: | Height: | Size: 9.4 KiB |
|
After Width: | Height: | Size: 393 KiB |
|
After Width: | Height: | Size: 46 KiB |
|
After Width: | Height: | Size: 13 KiB |
@@ -104,6 +104,11 @@
|
|||||||
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;
|
||||||
@@ -164,6 +169,10 @@
|
|||||||
gap: 8px;
|
gap: 8px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.gap-4 {
|
||||||
|
gap: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
.gap-20 {
|
.gap-20 {
|
||||||
gap: 20px;
|
gap: 20px;
|
||||||
}
|
}
|
||||||
@@ -204,6 +213,10 @@
|
|||||||
font-size: 20px;
|
font-size: 20px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.font-size-18 {
|
||||||
|
font-size: 18px;
|
||||||
|
}
|
||||||
|
|
||||||
.font-size-24 {
|
.font-size-24 {
|
||||||
font-size: 24px;
|
font-size: 24px;
|
||||||
}
|
}
|
||||||
@@ -216,6 +229,10 @@
|
|||||||
margin-bottom: 0;
|
margin-bottom: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.m-t-0 {
|
||||||
|
margin-top: 0;
|
||||||
|
}
|
||||||
|
|
||||||
.m-b-16 {
|
.m-b-16 {
|
||||||
margin-bottom: 16px;
|
margin-bottom: 16px;
|
||||||
}
|
}
|
||||||
@@ -240,6 +257,10 @@
|
|||||||
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);
|
||||||
}
|
}
|
||||||
@@ -248,6 +269,10 @@
|
|||||||
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;
|
||||||
@@ -315,3 +340,7 @@ 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);
|
||||||
|
}
|
||||||
|
|||||||
@@ -0,0 +1,63 @@
|
|||||||
|
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;
|
||||||
|
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;
|
||||||
|
} | null>(null);
|
||||||
@@ -1,4 +1,30 @@
|
|||||||
import { atom } from 'jotai';
|
import { BackendOption } from '@/pages/llmodels/config/types';
|
||||||
|
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);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const clusterListAtom = atom<
|
||||||
|
{
|
||||||
|
label: string;
|
||||||
|
value: number;
|
||||||
|
}[]
|
||||||
|
>([]);
|
||||||
|
|
||||||
|
export const backendOptionsAtom = atom<BackendOption[]>([]);
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { colorPrimary } from '@/config/theme';
|
import { COLOR_PRIMARY } from '@/config/theme';
|
||||||
import { atom } from 'jotai';
|
import { atom } from 'jotai';
|
||||||
import { atomWithStorage } from 'jotai/utils';
|
import { atomWithStorage } from 'jotai/utils';
|
||||||
|
|
||||||
@@ -7,13 +7,15 @@ type UserSettings = {
|
|||||||
mode: 'light' | 'realDark' | 'auto';
|
mode: 'light' | 'realDark' | 'auto';
|
||||||
colorPrimary: string;
|
colorPrimary: string;
|
||||||
isDarkTheme: boolean;
|
isDarkTheme: boolean;
|
||||||
|
collapsed: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
const defaultSettings: UserSettings = {
|
const defaultSettings: UserSettings = {
|
||||||
theme: 'light',
|
theme: 'light',
|
||||||
mode: 'auto',
|
mode: 'auto',
|
||||||
isDarkTheme: false,
|
isDarkTheme: false,
|
||||||
colorPrimary: colorPrimary
|
colorPrimary: COLOR_PRIMARY,
|
||||||
|
collapsed: false
|
||||||
};
|
};
|
||||||
|
|
||||||
export const getStorageUserSettings = () => {
|
export const getStorageUserSettings = () => {
|
||||||
@@ -31,10 +33,9 @@ export const getStorageUserSettings = () => {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
export const userSettingsAtom = atomWithStorage<UserSettings>(
|
export const userSettingsAtom = atomWithStorage<UserSettings>('userSettings', {
|
||||||
'userSettings',
|
...getStorageUserSettings()
|
||||||
defaultSettings
|
});
|
||||||
);
|
|
||||||
|
|
||||||
export const userSettingsHelperAtom = atom(
|
export const userSettingsHelperAtom = atom(
|
||||||
(get) => get(userSettingsAtom),
|
(get) => get(userSettingsAtom),
|
||||||
@@ -46,7 +47,7 @@ export const userSettingsHelperAtom = atom(
|
|||||||
};
|
};
|
||||||
set(userSettingsAtom, {
|
set(userSettingsAtom, {
|
||||||
...newSettings,
|
...newSettings,
|
||||||
colorPrimary: newSettings.colorPrimary || colorPrimary,
|
colorPrimary: newSettings.colorPrimary || COLOR_PRIMARY,
|
||||||
isDarkTheme: newSettings.theme === 'realDark'
|
isDarkTheme: newSettings.theme === 'realDark'
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,18 @@
|
|||||||
import { getDefaultStore } from 'jotai';
|
import { getDefaultStore } from 'jotai';
|
||||||
|
|
||||||
|
export const clearStorageUserSettings = () => {
|
||||||
|
const savedSettings = JSON.parse(
|
||||||
|
localStorage.getItem('userSettings') || '{}'
|
||||||
|
);
|
||||||
|
localStorage.setItem(
|
||||||
|
'userSettings',
|
||||||
|
JSON.stringify({
|
||||||
|
...savedSettings,
|
||||||
|
colorPrimary: undefined
|
||||||
|
})
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
export const clearAtomStorage = (atom: any) => {
|
export const clearAtomStorage = (atom: any) => {
|
||||||
if (!atom) {
|
if (!atom) {
|
||||||
return;
|
return;
|
||||||
|
|||||||
@@ -1,72 +0,0 @@
|
|||||||
.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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,10 +1,10 @@
|
|||||||
import { WarningFilled } from '@ant-design/icons';
|
import { 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 from '../overlay-scroller';
|
import OverlayScroller, { OverlayScrollerOptions } from '../overlay-scroller';
|
||||||
import './block.less';
|
|
||||||
interface AlertInfoProps {
|
interface AlertInfoProps {
|
||||||
type: Global.MessageType;
|
type: Global.MessageType;
|
||||||
message: React.ReactNode;
|
message: React.ReactNode;
|
||||||
@@ -15,8 +15,84 @@ 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);
|
||||||
@@ -29,6 +105,7 @@ 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) => {
|
||||||
@@ -41,14 +118,15 @@ const AlertInfo: React.FC<AlertInfoProps> = (props) => {
|
|||||||
title,
|
title,
|
||||||
contentStyle,
|
contentStyle,
|
||||||
icon,
|
icon,
|
||||||
maxHeight = 86
|
maxHeight = 86,
|
||||||
|
overlayScrollerProps = {}
|
||||||
} = props;
|
} = props;
|
||||||
|
const { styles } = useStyles();
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
{message ? (
|
{message ? (
|
||||||
<div
|
<div
|
||||||
className={classNames('alert-info-block', type)}
|
className={classNames(styles.alertBlockInfo, type)}
|
||||||
style={{ ...style }}
|
style={{ ...style }}
|
||||||
>
|
>
|
||||||
<Typography.Paragraph
|
<Typography.Paragraph
|
||||||
@@ -67,7 +145,11 @@ const AlertInfo: React.FC<AlertInfoProps> = (props) => {
|
|||||||
{title && (
|
{title && (
|
||||||
<TitleWrapper className="title-text">{title}</TitleWrapper>
|
<TitleWrapper className="title-text">{title}</TitleWrapper>
|
||||||
)}
|
)}
|
||||||
<OverlayScroller maxHeight={maxHeight} style={{ ...contentStyle }}>
|
<OverlayScroller
|
||||||
|
maxHeight={maxHeight}
|
||||||
|
style={{ ...contentStyle }}
|
||||||
|
{...overlayScrollerProps}
|
||||||
|
>
|
||||||
<ContentWrapper
|
<ContentWrapper
|
||||||
$hasTitle={!!title}
|
$hasTitle={!!title}
|
||||||
className={classNames('content', type)}
|
className={classNames('content', type)}
|
||||||
|
|||||||
@@ -28,6 +28,8 @@ 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)',
|
||||||
|
|||||||
@@ -0,0 +1,22 @@
|
|||||||
|
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;
|
||||||
@@ -0,0 +1,406 @@
|
|||||||
|
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);
|
||||||
@@ -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;
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { CloseCircleOutlined } from '@ant-design/icons';
|
import { CloseCircleOutlined, LoadingOutlined } from '@ant-design/icons';
|
||||||
import { Progress, ProgressProps } from 'antd';
|
import { Progress, ProgressProps, Spin } 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,6 +94,17 @@ 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
|
||||||
@@ -135,15 +146,8 @@ const SingleImage: React.FC<SingleImageProps> = (props) => {
|
|||||||
overflow: 'hidden'
|
overflow: 'hidden'
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Progress
|
<Spin
|
||||||
percent={round(progress, 0)}
|
indicator={<LoadingOutlined style={{ fontSize: 32 }} spin />}
|
||||||
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>
|
||||||
) : (
|
) : (
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ 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>;
|
||||||
@@ -22,9 +23,19 @@ 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%',
|
||||||
@@ -33,6 +44,8 @@ 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);
|
||||||
@@ -94,7 +107,7 @@ const AutoTooltip: React.FC<AutoTooltipProps> = ({
|
|||||||
<TooltipOverlayScroller
|
<TooltipOverlayScroller
|
||||||
toolTipProps={{
|
toolTipProps={{
|
||||||
...tooltipProps,
|
...tooltipProps,
|
||||||
destroyTooltipOnHide: false
|
destroyOnHidden: false
|
||||||
}}
|
}}
|
||||||
title={isOverflowing || showTitle ? title || children : false}
|
title={isOverflowing || showTitle ? title || children : false}
|
||||||
>
|
>
|
||||||
@@ -103,13 +116,14 @@ const AutoTooltip: React.FC<AutoTooltipProps> = ({
|
|||||||
{children}
|
{children}
|
||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
<Tag
|
<StyledTag
|
||||||
{...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: 12
|
borderRadius: radius,
|
||||||
|
...tagStyle
|
||||||
}}
|
}}
|
||||||
closeIcon={
|
closeIcon={
|
||||||
tagProps.closable ? (
|
tagProps.closable ? (
|
||||||
@@ -117,7 +131,8 @@ const AutoTooltip: React.FC<AutoTooltipProps> = ({
|
|||||||
style={{
|
style={{
|
||||||
position: 'absolute',
|
position: 'absolute',
|
||||||
right: 8,
|
right: 8,
|
||||||
top: 6
|
top: '50%',
|
||||||
|
transform: 'translateY(-50%)'
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
) : (
|
) : (
|
||||||
@@ -126,7 +141,7 @@ const AutoTooltip: React.FC<AutoTooltipProps> = ({
|
|||||||
}
|
}
|
||||||
>
|
>
|
||||||
{children}
|
{children}
|
||||||
</Tag>
|
</StyledTag>
|
||||||
)}
|
)}
|
||||||
</TooltipOverlayScroller>
|
</TooltipOverlayScroller>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -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;
|
padding: 10px 16px;
|
||||||
border: 1px solid var(--ant-color-border);
|
border: 1px solid var(--ant-color-border);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,109 @@
|
|||||||
|
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>
|
||||||
|
);
|
||||||
|
};
|
||||||
@@ -0,0 +1,208 @@
|
|||||||
|
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>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -55,9 +55,7 @@ const CopyButton: React.FC<CopyButtonProps> = ({
|
|||||||
});
|
});
|
||||||
|
|
||||||
clipboardRef.current.on('error', (e: any) => {
|
clipboardRef.current.on('error', (e: any) => {
|
||||||
message.success(
|
message.error(intl.formatMessage({ id: 'common.copy.fail' }) as string);
|
||||||
intl.formatMessage({ id: 'common.copy.fail' }) as string
|
|
||||||
);
|
|
||||||
e.clearSelection();
|
e.clearSelection();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
@@ -1,22 +0,0 @@
|
|||||||
: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;
|
|
||||||
}
|
|
||||||
@@ -9,9 +9,38 @@ import {
|
|||||||
message,
|
message,
|
||||||
type ModalFuncProps
|
type ModalFuncProps
|
||||||
} from 'antd';
|
} from 'antd';
|
||||||
import { FC, forwardRef, useImperativeHandle, useState } from 'react';
|
import { createStyles } from 'antd-style';
|
||||||
|
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;
|
||||||
@@ -25,12 +54,8 @@ 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;
|
||||||
@@ -43,28 +68,33 @@ interface DataOptions {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
const DeleteModal: FC<DeleteModalProps> = forwardRef((props, ref) => {
|
interface Configuration {
|
||||||
|
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 [checked, setChecked] = useState(false);
|
const [configuration, setConfiguration] = useState<Configuration>({
|
||||||
|
checked: false
|
||||||
|
});
|
||||||
const [config, setConfig] = useState<ModalFuncProps & DataOptions>({} as any);
|
const [config, setConfig] = useState<ModalFuncProps & DataOptions>({} as any);
|
||||||
|
|
||||||
useImperativeHandle(ref, () => ({
|
const show = (data: ModalFuncProps & DataOptions) => {
|
||||||
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: () => {
|
};
|
||||||
setVisible(false);
|
|
||||||
restoreScrollHeight();
|
const hide = () => {
|
||||||
},
|
setVisible(false);
|
||||||
configuration: {
|
restoreScrollHeight();
|
||||||
checked: checked
|
};
|
||||||
}
|
|
||||||
}));
|
|
||||||
|
|
||||||
const handleCancel = () => {
|
const handleCancel = () => {
|
||||||
setVisible(false);
|
setVisible(false);
|
||||||
@@ -94,6 +124,12 @@ const DeleteModal: FC<DeleteModalProps> = forwardRef((props, ref) => {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
useImperativeHandle(ref, () => ({
|
||||||
|
show,
|
||||||
|
hide,
|
||||||
|
configuration
|
||||||
|
}));
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Modal
|
<Modal
|
||||||
style={{
|
style={{
|
||||||
@@ -102,12 +138,16 @@ const DeleteModal: FC<DeleteModalProps> = forwardRef((props, ref) => {
|
|||||||
open={visible}
|
open={visible}
|
||||||
onOk={handleOk}
|
onOk={handleOk}
|
||||||
onCancel={handleCancel}
|
onCancel={handleCancel}
|
||||||
destroyOnClose={true}
|
destroyOnHidden={false}
|
||||||
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">
|
||||||
@@ -115,7 +155,7 @@ const DeleteModal: FC<DeleteModalProps> = 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">
|
<Button type="primary" onClick={handleOk} size="middle" danger>
|
||||||
{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' })}
|
||||||
@@ -123,7 +163,7 @@ const DeleteModal: FC<DeleteModalProps> = 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>
|
||||||
@@ -134,26 +174,30 @@ const DeleteModal: FC<DeleteModalProps> = forwardRef((props, ref) => {
|
|||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
className={Styles['content']}
|
className={styles['content']}
|
||||||
dangerouslySetInnerHTML={{
|
dangerouslySetInnerHTML={{
|
||||||
__html:
|
__html: config.content
|
||||||
config.content &&
|
? intl.formatMessage(
|
||||||
intl.formatMessage(
|
{
|
||||||
{
|
id: config.operation || ''
|
||||||
id: config.operation || ''
|
},
|
||||||
},
|
{
|
||||||
{
|
type: intl.formatMessage({ id: config.content }),
|
||||||
type: intl.formatMessage({ id: config.content }),
|
name: config.name
|
||||||
name: config.name
|
}
|
||||||
}
|
)
|
||||||
)
|
: ''
|
||||||
}}
|
}}
|
||||||
></div>
|
></div>
|
||||||
{config.checkConfig && (
|
{config.checkConfig && (
|
||||||
<CheckboxWrapper>
|
<CheckboxWrapper>
|
||||||
<Checkbox
|
<Checkbox
|
||||||
checked={checked}
|
checked={configuration.checked}
|
||||||
onChange={(e) => setChecked(e.target.checked)}
|
onChange={(e) =>
|
||||||
|
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,6 +16,9 @@ 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]);
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ 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';
|
||||||
@@ -19,6 +20,17 @@ 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',
|
||||||
@@ -64,18 +76,9 @@ const DropdownButtons: React.FC<DropdownButtonsProps> = ({
|
|||||||
disabled={disabled}
|
disabled={disabled}
|
||||||
trigger={trigger}
|
trigger={trigger}
|
||||||
type="primary"
|
type="primary"
|
||||||
dropdownRender={(menus: any) => {
|
popupRender={(menus: any) => {
|
||||||
return (
|
return (
|
||||||
<div
|
<DropdownWrapper>
|
||||||
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
|
||||||
@@ -86,13 +89,17 @@ const DropdownButtons: React.FC<DropdownButtonsProps> = ({
|
|||||||
key={item.key}
|
key={item.key}
|
||||||
disabled={item.disabled}
|
disabled={item.disabled}
|
||||||
onClick={() => handleMenuClick(item)}
|
onClick={() => handleMenuClick(item)}
|
||||||
style={{ width: '100%', justifyContent: 'flex-start' }}
|
style={{
|
||||||
|
width: '100%',
|
||||||
|
justifyContent: 'flex-start',
|
||||||
|
paddingInline: 10
|
||||||
|
}}
|
||||||
>
|
>
|
||||||
{intl.formatMessage({ id: item.label })}
|
{intl.formatMessage({ id: item.label })}
|
||||||
</Button>
|
</Button>
|
||||||
);
|
);
|
||||||
})}
|
})}
|
||||||
</div>
|
</DropdownWrapper>
|
||||||
);
|
);
|
||||||
}}
|
}}
|
||||||
buttonsRender={([leftButton, rightButton]) => [
|
buttonsRender={([leftButton, rightButton]) => [
|
||||||
|
|||||||
@@ -0,0 +1,19 @@
|
|||||||
|
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;
|
||||||
@@ -0,0 +1,59 @@
|
|||||||
|
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;
|
||||||
@@ -0,0 +1,175 @@
|
|||||||
|
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;
|
||||||
@@ -0,0 +1,39 @@
|
|||||||
|
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;
|
||||||
|
}
|
||||||
@@ -0,0 +1,33 @@
|
|||||||
|
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;
|
||||||
@@ -0,0 +1,61 @@
|
|||||||
|
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
|
||||||
|
};
|
||||||
|
}
|
||||||
@@ -0,0 +1,26 @@
|
|||||||
|
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;
|
||||||
@@ -1,7 +1,6 @@
|
|||||||
import { throttle } from 'lodash';
|
import _, { throttle } from 'lodash';
|
||||||
import React, {
|
import React, {
|
||||||
forwardRef,
|
forwardRef,
|
||||||
useCallback,
|
|
||||||
useEffect,
|
useEffect,
|
||||||
useImperativeHandle,
|
useImperativeHandle,
|
||||||
useRef
|
useRef
|
||||||
@@ -18,6 +17,7 @@ const Chart: React.FC<{
|
|||||||
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 {
|
||||||
@@ -25,40 +25,96 @@ const Chart: React.FC<{
|
|||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|
||||||
const init = useCallback(() => {
|
const init = () => {
|
||||||
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 resize = useCallback(() => {
|
const setOption = (options: ECOption) => {
|
||||||
chart.current?.resize();
|
if (!chart.current) return;
|
||||||
}, []);
|
chart.current?.clear();
|
||||||
|
chart.current?.setOption(options, {
|
||||||
const setOption = useCallback(
|
notMerge: true,
|
||||||
(options: ECOption) => {
|
lazyUpdate: true
|
||||||
chart.current?.clear();
|
});
|
||||||
chart.current?.setOption(options, {
|
if (Array.isArray(options.yAxis) && options.yAxis.length > 1) {
|
||||||
notMerge: true,
|
chart.current?.resize();
|
||||||
lazyUpdate: true
|
}
|
||||||
});
|
};
|
||||||
},
|
|
||||||
[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;
|
||||||
resize();
|
finished.current = false;
|
||||||
setOption(options);
|
setOption(options);
|
||||||
resizeable.current = true;
|
resizeable.current = true;
|
||||||
}, [options]);
|
}, [options]);
|
||||||
@@ -72,13 +128,11 @@ 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 () => {
|
||||||
if (container.current) {
|
resizeObserver.current?.disconnect();
|
||||||
resizeObserver.current?.unobserve(container.current);
|
resizeObserver.current = undefined;
|
||||||
}
|
|
||||||
};
|
};
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
|
|||||||
@@ -1,27 +1,12 @@
|
|||||||
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: 20,
|
right: 0,
|
||||||
bottom: 20,
|
bottom: 20,
|
||||||
containLabel: true
|
containLabel: true
|
||||||
};
|
};
|
||||||
@@ -31,14 +16,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)'
|
||||||
@@ -56,18 +41,23 @@ 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:8px;width:8px;height:8px;background-color:${item.color};"></span>
|
<span style="display:inline-block;margin-right:5px;border-radius:${borderRadius};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>`;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@@ -75,6 +65,7 @@ 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
|
||||||
}
|
}
|
||||||
@@ -98,8 +89,6 @@ export default function useChartConfig() {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const yAxis = {
|
const yAxis = {
|
||||||
// max: 100,
|
|
||||||
// min: 0,
|
|
||||||
nameTextStyle: {
|
nameTextStyle: {
|
||||||
padding: [0, 0, 0, -20]
|
padding: [0, 0, 0, -20]
|
||||||
},
|
},
|
||||||
@@ -135,7 +124,6 @@ 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%'
|
||||||
};
|
};
|
||||||
@@ -218,13 +206,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]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,9 +1,19 @@
|
|||||||
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, { memo } from 'react';
|
import React 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
|
||||||
) => {
|
) => {
|
||||||
@@ -12,28 +22,46 @@ const GaugeChart: React.FC<Omit<ChartProps, 'seriesData' | 'xAxisData'>> = (
|
|||||||
title: titleConfig,
|
title: titleConfig,
|
||||||
chartColorMap
|
chartColorMap
|
||||||
} = useChartConfig();
|
} = useChartConfig();
|
||||||
const { value, height, width, labelFormatter, title, color } = props;
|
const { value, height, width, labelFormatter, title, color, gaugeConfig } =
|
||||||
|
props;
|
||||||
|
const titleText = typeof title === 'string' ? title : title?.text;
|
||||||
|
|
||||||
if (!value && value !== 0) {
|
if (!value && value !== 0) {
|
||||||
return <EmptyData height={height} title={title}></EmptyData>;
|
return <EmptyData height={height} title={titleText}></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: title,
|
text: titleText,
|
||||||
top: '0',
|
textStyle: {
|
||||||
left: 'center'
|
fontSize: 12,
|
||||||
|
color: chartColorMap.colorSecondary,
|
||||||
|
fontWeight: 400
|
||||||
|
},
|
||||||
|
top: 10,
|
||||||
|
left: 'center',
|
||||||
|
...(typeof title === 'object' ? title : {})
|
||||||
},
|
},
|
||||||
series: [
|
series: [
|
||||||
{
|
{
|
||||||
...gaugeItemConfig,
|
...combineGaugeConfig,
|
||||||
axisLine: {
|
axisLine: {
|
||||||
...gaugeItemConfig.axisLine,
|
...combineGaugeConfig.axisLine,
|
||||||
lineStyle: {
|
lineStyle: {
|
||||||
...gaugeItemConfig.axisLine.lineStyle,
|
...combineGaugeConfig.axisLine.lineStyle,
|
||||||
color: [
|
color: [
|
||||||
[value / 100, color],
|
[value / 100, colorValue],
|
||||||
[1, chartColorMap.gaugeBgColor]
|
[1, chartColorMap.gaugeBgColor]
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
@@ -42,7 +70,11 @@ const GaugeChart: React.FC<Omit<ChartProps, 'seriesData' | 'xAxisData'>> = (
|
|||||||
color: 'transparent'
|
color: 'transparent'
|
||||||
},
|
},
|
||||||
detail: {
|
detail: {
|
||||||
...gaugeItemConfig.detail,
|
...combineGaugeConfig.detail,
|
||||||
|
borderColor: colorValue,
|
||||||
|
lineHeight: 20,
|
||||||
|
height: 18,
|
||||||
|
width: 50,
|
||||||
formatter: labelFormatter || gaugeItemConfig.detail.formatter
|
formatter: labelFormatter || gaugeItemConfig.detail.formatter
|
||||||
},
|
},
|
||||||
data: [{ value }]
|
data: [{ value }]
|
||||||
@@ -62,4 +94,4 @@ const GaugeChart: React.FC<Omit<ChartProps, 'seriesData' | 'xAxisData'>> = (
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export default memo(GaugeChart);
|
export default GaugeChart;
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ import _ from 'lodash';
|
|||||||
import React, { memo, useMemo } from 'react';
|
import React, { memo, useMemo } from 'react';
|
||||||
import { ChartProps } from './types';
|
import { ChartProps } from './types';
|
||||||
|
|
||||||
const BarChart: React.FC<ChartProps> = (props) => {
|
const BarChart: React.FC<ChartProps & { maxItems?: number }> = (props) => {
|
||||||
const {
|
const {
|
||||||
seriesData,
|
seriesData,
|
||||||
xAxisData,
|
xAxisData,
|
||||||
@@ -13,6 +13,7 @@ const BarChart: React.FC<ChartProps> = (props) => {
|
|||||||
width,
|
width,
|
||||||
labelFormatter,
|
labelFormatter,
|
||||||
legendData,
|
legendData,
|
||||||
|
maxItems,
|
||||||
title
|
title
|
||||||
} = props;
|
} = props;
|
||||||
const {
|
const {
|
||||||
@@ -30,7 +31,13 @@ const BarChart: React.FC<ChartProps> = (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
|
||||||
},
|
},
|
||||||
@@ -41,7 +48,15 @@ const BarChart: React.FC<ChartProps> = (props) => {
|
|||||||
formatter: labelFormatter
|
formatter: labelFormatter
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
yAxis,
|
yAxis: {
|
||||||
|
...yAxis,
|
||||||
|
axisLabel: {
|
||||||
|
...yAxis.axisLabel,
|
||||||
|
overflow: 'truncate',
|
||||||
|
width: 75,
|
||||||
|
ellipsis: '...'
|
||||||
|
}
|
||||||
|
},
|
||||||
legend: {
|
legend: {
|
||||||
...legend,
|
...legend,
|
||||||
data: []
|
data: []
|
||||||
@@ -114,9 +129,15 @@ const BarChart: React.FC<ChartProps> = (props) => {
|
|||||||
legend
|
legend
|
||||||
]);
|
]);
|
||||||
|
|
||||||
|
const isEmpty = useMemo(() => {
|
||||||
|
return seriesData?.every?.((item: any) => {
|
||||||
|
return !item?.data?.length;
|
||||||
|
});
|
||||||
|
}, [seriesData]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
{!seriesData.length ? (
|
{isEmpty ? (
|
||||||
<EmptyData height={height} title={title}></EmptyData>
|
<EmptyData height={height} title={title}></EmptyData>
|
||||||
) : (
|
) : (
|
||||||
<Chart
|
<Chart
|
||||||
|
|||||||
@@ -1,10 +1,13 @@
|
|||||||
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, { memo, 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,
|
||||||
@@ -16,7 +19,11 @@ 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,
|
||||||
@@ -28,11 +35,24 @@ 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) {
|
||||||
@@ -45,12 +65,13 @@ const LineChart: React.FC<ChartProps> = (props) => {
|
|||||||
...xAxis,
|
...xAxis,
|
||||||
axisLabel: {
|
axisLabel: {
|
||||||
...xAxis.axisLabel,
|
...xAxis.axisLabel,
|
||||||
formatter: labelFormatter
|
formatter: axisLabelFormatter
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
yAxis,
|
yAxis,
|
||||||
legend: {
|
legend: {
|
||||||
...legend,
|
...legend,
|
||||||
|
...legendOptions,
|
||||||
data: legendData.map((item: any) => {
|
data: legendData.map((item: any) => {
|
||||||
return {
|
return {
|
||||||
name: item,
|
name: item,
|
||||||
@@ -64,6 +85,11 @@ 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,
|
||||||
@@ -75,7 +101,21 @@ 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 {
|
||||||
@@ -83,6 +123,7 @@ const LineChart: React.FC<ChartProps> = (props) => {
|
|||||||
animation: false,
|
animation: false,
|
||||||
title: {
|
title: {
|
||||||
...titleConfig,
|
...titleConfig,
|
||||||
|
...titleOptions,
|
||||||
text: title
|
text: title
|
||||||
},
|
},
|
||||||
yAxis: {
|
yAxis: {
|
||||||
@@ -99,12 +140,24 @@ 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 height={height} title={title}></EmptyData>
|
<EmptyData
|
||||||
|
height={height}
|
||||||
|
title={_.get(title, 'text', title || '')}
|
||||||
|
></EmptyData>
|
||||||
) : (
|
) : (
|
||||||
<Chart
|
<Chart
|
||||||
height={height}
|
height={height}
|
||||||
|
|||||||
@@ -0,0 +1,173 @@
|
|||||||
|
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={title}></EmptyData>
|
||||||
|
) : (
|
||||||
|
<Chart
|
||||||
|
height={height}
|
||||||
|
options={dataOptions}
|
||||||
|
width={width || '100%'}
|
||||||
|
></Chart>
|
||||||
|
)}
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default MixLineBarChart;
|
||||||
@@ -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">${result}</div>`;
|
return `<div class="tooltip-wrapper scatter">${result}</div>`;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
title: {
|
title: {
|
||||||
|
|||||||
@@ -1,17 +1,40 @@
|
|||||||
|
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?: string[];
|
legendData?: LegendComponentOption['data'];
|
||||||
labelFormatter?: (val?: any) => string;
|
legendOptions?: {
|
||||||
|
[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;
|
title?: string | TitleComponentOption;
|
||||||
|
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 {
|
||||||
|
|||||||
@@ -1,74 +1,66 @@
|
|||||||
|
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 Wrapper = styled.div<{ $height: number }>`
|
const HeaderWrapper = styled.div<{ $height?: number }>`
|
||||||
height: ${(props) => props.$height}px;
|
height: ${(props) => (props.$height ? `${props.$height}px` : 'auto')};
|
||||||
|
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;
|
||||||
showHeader?: boolean;
|
variant?: 'bordered' | 'borderless';
|
||||||
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,
|
||||||
copyText,
|
variant = 'borderless',
|
||||||
langOptions = [],
|
styles = {}
|
||||||
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 (
|
|
||||||
<Wrapper className="editor-header" $height={headerHeight}>
|
|
||||||
<SegmentLine
|
|
||||||
height={headerHeight}
|
|
||||||
defaultValue={defaultValue}
|
|
||||||
size="small"
|
|
||||||
options={langOptions}
|
|
||||||
onChange={handleChangeLang}
|
|
||||||
></SegmentLine>
|
|
||||||
<CopyButton
|
|
||||||
text={copyText}
|
|
||||||
size="small"
|
|
||||||
style={{
|
|
||||||
color: 'rgba(255,255,255,.7)'
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
</Wrapper>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
};
|
|
||||||
return (
|
return (
|
||||||
<div className="editor-wrap" style={{ ...styles.wrapper }}>
|
<Wrapper
|
||||||
{renderHeader()}
|
style={{ ...styles.wrapper }}
|
||||||
<div className="editor-content">{children}</div>
|
className={classNames({
|
||||||
</div>
|
bordered: variant === 'bordered',
|
||||||
|
borderless: variant === 'borderless'
|
||||||
|
})}
|
||||||
|
>
|
||||||
|
{header && <HeaderWrapper $height={headerHeight}>{header}</HeaderWrapper>}
|
||||||
|
<div>{children}</div>
|
||||||
|
</Wrapper>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export default React.memo(EditorWrap);
|
export default EditorWrap;
|
||||||
|
|||||||
@@ -1,30 +1,39 @@
|
|||||||
|
import { LoadingOutlined } from '@ant-design/icons';
|
||||||
import Editor from '@monaco-editor/react';
|
import Editor from '@monaco-editor/react';
|
||||||
import React, { useEffect, useRef, useState } from 'react';
|
import 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;
|
||||||
showHeader?: boolean;
|
header?: React.ReactNode;
|
||||||
|
placeholder?: string;
|
||||||
|
variant?: 'bordered' | 'borderless';
|
||||||
}
|
}
|
||||||
|
|
||||||
const ViewerEditor: React.FC<ViewerProps> = (props) => {
|
const ViewerEditor: React.FC<ViewerProps> = forwardRef((props, ref) => {
|
||||||
const editorRef = useRef<any>(null);
|
|
||||||
const {
|
const {
|
||||||
lang,
|
lang,
|
||||||
value,
|
value,
|
||||||
config,
|
config,
|
||||||
langOptions,
|
|
||||||
defaultLang,
|
defaultLang,
|
||||||
height = 380,
|
height = 380,
|
||||||
showHeader
|
theme = 'vs-dark',
|
||||||
|
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({
|
||||||
@@ -36,12 +45,8 @@ const ViewerEditor: React.FC<ViewerProps> = (props) => {
|
|||||||
|
|
||||||
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(() => {
|
||||||
@@ -55,35 +60,50 @@ const ViewerEditor: React.FC<ViewerProps> = (props) => {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
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
|
<EditorWrap header={header} variant={variant}>
|
||||||
copyText={value}
|
|
||||||
langOptions={langOptions}
|
|
||||||
defaultValue={lang}
|
|
||||||
showHeader={showHeader}
|
|
||||||
onChangeLang={handleOnChangeLang}
|
|
||||||
>
|
|
||||||
<Editor
|
<Editor
|
||||||
height={height}
|
height={height}
|
||||||
theme="vs-dark"
|
theme={theme}
|
||||||
className="monaco-editor"
|
className="monaco-editor"
|
||||||
defaultLanguage={defaultLang}
|
defaultLanguage={defaultLang}
|
||||||
language={langType}
|
language={lang}
|
||||||
value={value}
|
value={value}
|
||||||
options={config}
|
options={{
|
||||||
|
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 React.memo(ViewerEditor);
|
export default ViewerEditor;
|
||||||
@@ -56,4 +56,4 @@ const HighlightCode: React.FC<{
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export default React.memo(HighlightCode);
|
export default HighlightCode;
|
||||||
|
|||||||
@@ -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=1747231573342') format('woff2'),
|
src: url('iconfont.woff2?t=1763436184844') format('woff2'),
|
||||||
url('iconfont.woff?t=1747231573342') format('woff'),
|
url('iconfont.woff?t=1763436184844') format('woff'),
|
||||||
url('iconfont.ttf?t=1747231573342') format('truetype');
|
url('iconfont.ttf?t=1763436184844') format('truetype');
|
||||||
}
|
}
|
||||||
|
|
||||||
.iconfont {
|
.iconfont {
|
||||||
@@ -13,6 +13,422 @@
|
|||||||
-moz-osx-font-smoothing: grayscale;
|
-moz-osx-font-smoothing: grayscale;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.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";
|
||||||
}
|
}
|
||||||
@@ -237,14 +653,6 @@
|
|||||||
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";
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,6 +5,734 @@
|
|||||||
"css_prefix_text": "icon-",
|
"css_prefix_text": "icon-",
|
||||||
"description": "",
|
"description": "",
|
||||||
"glyphs": [
|
"glyphs": [
|
||||||
|
{
|
||||||
|
"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",
|
||||||
@@ -397,20 +1125,6 @@
|
|||||||
"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",
|
||||||
|
|||||||
@@ -0,0 +1,64 @@
|
|||||||
|
import IconFont from '@/components/icon-font';
|
||||||
|
import {
|
||||||
|
ApiOutlined,
|
||||||
|
DeleteOutlined,
|
||||||
|
DockerOutlined,
|
||||||
|
DownloadOutlined,
|
||||||
|
EditOutlined,
|
||||||
|
ExperimentOutlined,
|
||||||
|
FileTextOutlined,
|
||||||
|
KubernetesOutlined,
|
||||||
|
ProfileOutlined,
|
||||||
|
RetweetOutlined,
|
||||||
|
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' })
|
||||||
|
};
|
||||||
|
|
||||||
|
export default icons;
|
||||||
@@ -107,7 +107,6 @@ export default function useZoom(props: {
|
|||||||
if (isLoadingMaskRef.current) {
|
if (isLoadingMaskRef.current) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
event.preventDefault();
|
|
||||||
// stop
|
// stop
|
||||||
handleZoom(event);
|
handleZoom(event);
|
||||||
updateCursorSize();
|
updateCursorSize();
|
||||||
|
|||||||
@@ -433,7 +433,6 @@ 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) => {
|
||||||
@@ -454,11 +453,22 @@ const CanvasImageEditor: React.FC<CanvasImageEditorProps> = forwardRef(
|
|||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const handleUndoShortcut = (e: KeyboardEvent) => {
|
const handleUndoShortcut = (e: KeyboardEvent) => {
|
||||||
if ((e.ctrlKey || e.metaKey) && e.key === 'z') {
|
if (
|
||||||
|
(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;
|
||||||
};
|
};
|
||||||
@@ -467,7 +477,6 @@ 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);
|
||||||
|
|
||||||
@@ -475,7 +484,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);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -48,12 +48,14 @@ const ToolsBar: React.FC<ToolsBarProps> = (props) => {
|
|||||||
<Tooltip
|
<Tooltip
|
||||||
placement="bottomLeft"
|
placement="bottomLeft"
|
||||||
arrow={false}
|
arrow={false}
|
||||||
overlayInnerStyle={{
|
styles={{
|
||||||
background: 'var(--color-white-1)',
|
body: {
|
||||||
display: 'flex',
|
background: 'var(--color-white-1)',
|
||||||
alignItems: 'center',
|
display: 'flex',
|
||||||
justifyContent: 'flex-start',
|
alignItems: 'center',
|
||||||
width: 160
|
justifyContent: 'flex-start',
|
||||||
|
width: 160
|
||||||
|
}
|
||||||
}}
|
}}
|
||||||
title={
|
title={
|
||||||
<div className="flex-column" style={{ width: '100%' }}>
|
<div className="flex-column" style={{ width: '100%' }}>
|
||||||
|
|||||||
@@ -0,0 +1,32 @@
|
|||||||
|
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;
|
||||||
@@ -0,0 +1,138 @@
|
|||||||
|
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;
|
||||||
@@ -0,0 +1,25 @@
|
|||||||
|
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;
|
||||||
|
};
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
import { useIntl } from '@umijs/max';
|
import { useIntl } from '@umijs/max';
|
||||||
import _ from 'lodash';
|
import _ from 'lodash';
|
||||||
import React, { useCallback, useEffect, useState } from 'react';
|
import React, { useEffect, useState } from 'react';
|
||||||
import Inner from './inner';
|
import Inner from './inner';
|
||||||
|
|
||||||
interface LabelSelectorProps {
|
interface LabelSelectorProps {
|
||||||
@@ -8,6 +8,8 @@ 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;
|
||||||
@@ -18,9 +20,11 @@ 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({});
|
||||||
@@ -41,18 +45,30 @@ const LabelSelector: React.FC<LabelSelectorProps> = ({
|
|||||||
}
|
}
|
||||||
}, [labels]);
|
}, [labels]);
|
||||||
|
|
||||||
const handleLabelListChange = useCallback(
|
const handleLabelListChange = (list: { key: string; value: string }[]) => {
|
||||||
(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
|
||||||
@@ -70,22 +86,23 @@ 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);
|
|
||||||
|
|
||||||
setLabelList((prevPairs) => {
|
const newPairs = [...labelList];
|
||||||
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}
|
||||||
@@ -97,4 +114,4 @@ const LabelSelector: React.FC<LabelSelectorProps> = ({
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export default React.memo(LabelSelector);
|
export default LabelSelector;
|
||||||
|
|||||||
@@ -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, { useRef } from 'react';
|
import React, { useEffect } 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,6 +16,7 @@ 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> = ({
|
||||||
@@ -26,13 +27,17 @@ 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);
|
|
||||||
const boxRef = useRef<HTMLDivElement>(null);
|
useEffect(() => {
|
||||||
|
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(
|
||||||
@@ -75,42 +80,49 @@ const Inner: React.FC<LabelSelectorProps> = ({
|
|||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Wrapper label={label} description={description}>
|
<Wrapper
|
||||||
|
label={label}
|
||||||
|
description={description}
|
||||||
|
onAdd={handleAddLabel}
|
||||||
|
disabled={disabled}
|
||||||
|
btnText={btnText}
|
||||||
|
>
|
||||||
<>
|
<>
|
||||||
{labelList?.map((item: any, index: number) => {
|
{isAutoComplete
|
||||||
return (
|
? labelList?.map((item: any, index: number) => {
|
||||||
<LabelItem
|
return (
|
||||||
key={index}
|
<AutoCompleteItem
|
||||||
label={item}
|
disabled={disabled}
|
||||||
seperator=":"
|
key={index}
|
||||||
labelList={labelList}
|
label={item}
|
||||||
onDelete={() => handleOnDelete(index)}
|
seperator=":"
|
||||||
onChange={(obj) => handleOnChange(index, obj)}
|
labels={labels}
|
||||||
onPaste={(e) => onPaste?.(e, index)}
|
labelList={labelList}
|
||||||
onBlur={(e: any, type: string) => onBlur?.(e, type, index)}
|
onDelete={() => handleOnDelete(index)}
|
||||||
/>
|
onChange={(obj) => handleOnChange(index, obj)}
|
||||||
);
|
onPaste={(e) => onPaste?.(e, index)}
|
||||||
})}
|
onBlur={(e: any, type: string) => onBlur?.(e, type, index)}
|
||||||
<div className="flex justify-center">
|
/>
|
||||||
<Button
|
);
|
||||||
ref={buttonRef}
|
})
|
||||||
type="text"
|
: labelList?.map((item: any, index: number) => {
|
||||||
block
|
return (
|
||||||
style={{
|
<LabelItem
|
||||||
marginTop: 16,
|
disabled={disabled}
|
||||||
backgroundColor: 'var(--ant-color-fill-secondary)'
|
key={index}
|
||||||
}}
|
label={item}
|
||||||
onClick={handleAddLabel}
|
seperator=":"
|
||||||
>
|
labelList={labelList}
|
||||||
<PlusOutlined className="font-size-14" />{' '}
|
onDelete={() => handleOnDelete(index)}
|
||||||
{intl.formatMessage({
|
onChange={(obj) => handleOnChange(index, obj)}
|
||||||
id: btnText || 'common.button.addSelector'
|
onPaste={(e) => onPaste?.(e, index)}
|
||||||
|
onBlur={(e: any, type: string) => onBlur?.(e, type, index)}
|
||||||
|
/>
|
||||||
|
);
|
||||||
})}
|
})}
|
||||||
</Button>
|
|
||||||
</div>
|
|
||||||
</>
|
</>
|
||||||
</Wrapper>
|
</Wrapper>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export default React.memo(Inner);
|
export default Inner;
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
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';
|
||||||
@@ -15,8 +16,9 @@ interface LabelItemProps {
|
|||||||
keyAddon?: React.ReactNode;
|
keyAddon?: React.ReactNode;
|
||||||
valueAddon?: React.ReactNode;
|
valueAddon?: React.ReactNode;
|
||||||
seperator?: string;
|
seperator?: string;
|
||||||
onDelete?: () => void;
|
|
||||||
labelList: { key: string; value: string }[];
|
labelList: { key: string; value: string }[];
|
||||||
|
disabled?: boolean;
|
||||||
|
onDelete?: () => void;
|
||||||
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;
|
||||||
@@ -27,6 +29,7 @@ const LabelItem: React.FC<LabelItemProps> = ({
|
|||||||
seperator,
|
seperator,
|
||||||
keyAddon,
|
keyAddon,
|
||||||
valueAddon,
|
valueAddon,
|
||||||
|
disabled,
|
||||||
onChange,
|
onChange,
|
||||||
onDelete,
|
onDelete,
|
||||||
onPaste,
|
onPaste,
|
||||||
@@ -82,6 +85,7 @@ 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}
|
||||||
@@ -96,6 +100,8 @@ 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}
|
||||||
@@ -104,17 +110,19 @@ const LabelItem: React.FC<LabelItemProps> = ({
|
|||||||
></SealInput.Input>
|
></SealInput.Input>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
<Button
|
{!disabled && (
|
||||||
size="small"
|
<Button
|
||||||
className="btn"
|
size="small"
|
||||||
type="default"
|
className="btn"
|
||||||
shape="circle"
|
type="default"
|
||||||
onClick={onDelete}
|
shape="circle"
|
||||||
>
|
onClick={onDelete}
|
||||||
<MinusOutlined />
|
>
|
||||||
</Button>
|
<MinusOutlined />
|
||||||
|
</Button>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export default React.memo(LabelItem);
|
export default LabelItem;
|
||||||
|
|||||||
@@ -1,20 +0,0 @@
|
|||||||
.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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,15 +1,56 @@
|
|||||||
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 styles from './styles/wrapper.less';
|
import styled from 'styled-components';
|
||||||
|
|
||||||
const Wrapper: React.FC<{
|
interface WrapperProps {
|
||||||
label?: React.ReactNode;
|
label?: React.ReactNode;
|
||||||
description?: React.ReactNode;
|
description?: React.ReactNode;
|
||||||
labelExtra?: React.ReactNode;
|
labelExtra?: React.ReactNode;
|
||||||
children: React.ReactNode;
|
children: React.ReactNode;
|
||||||
}> = ({ children, label, description, labelExtra, ...rest }) => {
|
btnText?: string;
|
||||||
|
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 (
|
||||||
<div className={styles['wrapper']}>
|
<Container>
|
||||||
{label && (
|
{label && (
|
||||||
<span className="label">
|
<span className="label">
|
||||||
<LabelInfo
|
<LabelInfo
|
||||||
@@ -19,10 +60,21 @@ const Wrapper: React.FC<{
|
|||||||
></LabelInfo>
|
></LabelInfo>
|
||||||
</span>
|
</span>
|
||||||
)}
|
)}
|
||||||
{React.isValidElement(children)
|
{children}
|
||||||
? React.cloneElement(children, { ...rest })
|
{!disabled && (
|
||||||
: children}
|
<ButtonWrapper>
|
||||||
</div>
|
{button || (
|
||||||
|
<Button variant="filled" color="default" block onClick={onAdd}>
|
||||||
|
<PlusOutlined className="font-size-14" />
|
||||||
|
{btnText ||
|
||||||
|
intl.formatMessage({
|
||||||
|
id: 'common.button.addSelector'
|
||||||
|
})}
|
||||||
|
</Button>
|
||||||
|
)}
|
||||||
|
</ButtonWrapper>
|
||||||
|
)}
|
||||||
|
</Container>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -2,9 +2,27 @@ 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 {
|
||||||
@@ -25,7 +43,7 @@ const LangSelect = () => {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<Dropdown menu={{ items }}>
|
<Dropdown menu={{ items }}>
|
||||||
<span style={{ padding: '0 12px', cursor: 'pointer' }}>
|
<span className={styles.button}>
|
||||||
<GlobalOutlined />
|
<GlobalOutlined />
|
||||||
</span>
|
</span>
|
||||||
</Dropdown>
|
</Dropdown>
|
||||||
|
|||||||
@@ -1,6 +1,4 @@
|
|||||||
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 from 'react';
|
import React from 'react';
|
||||||
import Wrapper from '../label-selector/wrapper';
|
import Wrapper from '../label-selector/wrapper';
|
||||||
@@ -8,7 +6,7 @@ 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[];
|
||||||
@@ -34,7 +32,6 @@ const ListInput: React.FC<ListInputProps> = (props) => {
|
|||||||
} = 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;
|
||||||
@@ -65,9 +62,6 @@ 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(() => {
|
||||||
@@ -85,7 +79,13 @@ const ListInput: React.FC<ListInputProps> = (props) => {
|
|||||||
}, [dataList]);
|
}, [dataList]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Wrapper label={label} description={description} labelExtra={labelExtra}>
|
<Wrapper
|
||||||
|
label={label}
|
||||||
|
description={description}
|
||||||
|
labelExtra={labelExtra}
|
||||||
|
onAdd={handleOnAdd}
|
||||||
|
btnText={btnText}
|
||||||
|
>
|
||||||
<>
|
<>
|
||||||
{_.map(list, (item: any, index: number) => {
|
{_.map(list, (item: any, index: number) => {
|
||||||
return (
|
return (
|
||||||
@@ -100,26 +100,9 @@ const ListInput: React.FC<ListInputProps> = (props) => {
|
|||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
})}
|
})}
|
||||||
<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 React.memo(ListInput);
|
export default ListInput;
|
||||||
|
|||||||
@@ -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 = 500;
|
export const PageSize = 1000;
|
||||||
|
|
||||||
export const throttle = <T extends (...args: any[]) => void>(
|
export const throttle = <T extends (...args: any[]) => void>(
|
||||||
func: T,
|
func: T,
|
||||||
|
|||||||
@@ -72,7 +72,7 @@ const LogsList: React.FC<LogsListProps> = forwardRef((props, ref) => {
|
|||||||
|
|
||||||
const isBottom = scrollTop + clientHeight + 150 >= scrollHeight;
|
const isBottom = scrollTop + clientHeight + 150 >= scrollHeight;
|
||||||
// is scroll to top
|
// is scroll to top
|
||||||
if (scrollTop === 0) {
|
if (scrollTop <= 10) {
|
||||||
onScroll?.({
|
onScroll?.({
|
||||||
isTop: true,
|
isTop: true,
|
||||||
isBottom: false
|
isBottom: false
|
||||||
|
|||||||
@@ -106,4 +106,4 @@ const LogsPagination: React.FC<LogsPaginationProps> = (props) => {
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export default React.memo(LogsPagination);
|
export default LogsPagination;
|
||||||
|
|||||||
@@ -16,6 +16,7 @@ interface MessageProps {
|
|||||||
chunked?: boolean;
|
chunked?: boolean;
|
||||||
progress?: number;
|
progress?: number;
|
||||||
percent?: number;
|
percent?: number;
|
||||||
|
isDownloading?: boolean;
|
||||||
}
|
}
|
||||||
class AnsiParser {
|
class AnsiParser {
|
||||||
private cursorRow: number = 0;
|
private cursorRow: number = 0;
|
||||||
@@ -30,6 +31,9 @@ class AnsiParser {
|
|||||||
private percent: number = 0;
|
private percent: number = 0;
|
||||||
private isComplete: boolean = false;
|
private isComplete: boolean = false;
|
||||||
private chunked: boolean = true; // true: send data in chunks, false: send all data at once
|
private chunked: boolean = true; // true: send data in chunks, false: send all data at once
|
||||||
|
private reminder: string = '';
|
||||||
|
private lines: string[] = [];
|
||||||
|
isDownloading: boolean = false;
|
||||||
private pageSize: number = 500;
|
private pageSize: number = 500;
|
||||||
private colorMap = {
|
private colorMap = {
|
||||||
'30': 'black',
|
'30': 'black',
|
||||||
@@ -52,6 +56,8 @@ class AnsiParser {
|
|||||||
this.screen = [['']];
|
this.screen = [['']];
|
||||||
this.rawDataRows = 0;
|
this.rawDataRows = 0;
|
||||||
this.uid = this.uid + 1;
|
this.uid = this.uid + 1;
|
||||||
|
this.lines = [];
|
||||||
|
this.reminder = '';
|
||||||
this.page = 1;
|
this.page = 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -75,6 +81,10 @@ class AnsiParser {
|
|||||||
this.chunked = chunked ?? true;
|
this.chunked = chunked ?? true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public setIsDownloading(isDownloading: boolean) {
|
||||||
|
this.isDownloading = isDownloading;
|
||||||
|
}
|
||||||
|
|
||||||
private setId() {
|
private setId() {
|
||||||
this.uid += 1;
|
this.uid += 1;
|
||||||
return this.uid;
|
return this.uid;
|
||||||
@@ -109,7 +119,6 @@ class AnsiParser {
|
|||||||
const n = parseInt(match[1] || '1', 10);
|
const n = parseInt(match[1] || '1', 10);
|
||||||
const m = parseInt(match[2] || '1', 10);
|
const m = parseInt(match[2] || '1', 10);
|
||||||
const command = match[3];
|
const command = match[3];
|
||||||
|
|
||||||
switch (command) {
|
switch (command) {
|
||||||
case 'A':
|
case 'A':
|
||||||
this.cursorRow = Math.max(0, this.cursorRow - n);
|
this.cursorRow = Math.max(0, this.cursorRow - n);
|
||||||
@@ -165,14 +174,18 @@ class AnsiParser {
|
|||||||
const remainingText = input.slice(lastIndex);
|
const remainingText = input.slice(lastIndex);
|
||||||
this.handleText(remainingText);
|
this.handleText(remainingText);
|
||||||
|
|
||||||
const result = this.screen.map((row, index) => ({
|
// const result = this.screen.map((row, index) => ({
|
||||||
content: removeBracketsFromLine(row.join('')),
|
// content: removeBracketsFromLine(row.join('')),
|
||||||
uid: `${this.page}-${index}`
|
// uid: `${this.page}-${index}`
|
||||||
}));
|
// }));
|
||||||
|
const result = this.screen.map((row, index) =>
|
||||||
|
removeBracketsFromLine(row.join(''))
|
||||||
|
);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
data: result,
|
data: result,
|
||||||
lines: this.rawDataRows
|
lines: this.rawDataRows,
|
||||||
|
remainder: ''
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -183,6 +196,31 @@ class AnsiParser {
|
|||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private processInputByLine(input: string): {
|
||||||
|
data: string[];
|
||||||
|
lines: number;
|
||||||
|
remainder: string;
|
||||||
|
} {
|
||||||
|
const lines = input?.split(/\r?\n/) || [];
|
||||||
|
const remainder = lines.pop() || '';
|
||||||
|
|
||||||
|
// const data = lines.join('\n');
|
||||||
|
this.rawDataRows += lines.length;
|
||||||
|
lines.forEach((line) => {
|
||||||
|
this.lines.push(line);
|
||||||
|
});
|
||||||
|
|
||||||
|
return {
|
||||||
|
data: this.lines,
|
||||||
|
lines: this.rawDataRows,
|
||||||
|
remainder
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
private getAllLines() {
|
||||||
|
return this.lines.join('\n');
|
||||||
|
}
|
||||||
|
|
||||||
private async processQueue(): Promise<void> {
|
private async processQueue(): Promise<void> {
|
||||||
if (this.isProcessing) {
|
if (this.isProcessing) {
|
||||||
return;
|
return;
|
||||||
@@ -191,11 +229,23 @@ class AnsiParser {
|
|||||||
this.isProcessing = true;
|
this.isProcessing = true;
|
||||||
|
|
||||||
while (this.taskQueue.length > 0) {
|
while (this.taskQueue.length > 0) {
|
||||||
const input = this.taskQueue.shift();
|
let input = '';
|
||||||
|
|
||||||
|
if (this.isDownloading) {
|
||||||
|
input = this.taskQueue.shift() || '';
|
||||||
|
} else {
|
||||||
|
input = this.reminder + this.taskQueue.shift();
|
||||||
|
}
|
||||||
|
|
||||||
if (input) {
|
if (input) {
|
||||||
try {
|
try {
|
||||||
const result = this.processInput(input);
|
const result = this.isDownloading
|
||||||
|
? this.processInput(input)
|
||||||
|
: this.processInputByLine(input);
|
||||||
|
if (!this.isDownloading) {
|
||||||
|
this.reminder = result.remainder;
|
||||||
|
}
|
||||||
|
|
||||||
if (this.chunked) {
|
if (this.chunked) {
|
||||||
self.postMessage({ result: result.data, lines: result.lines });
|
self.postMessage({ result: result.data, lines: result.lines });
|
||||||
} else if (!this.isComplete) {
|
} else if (!this.isComplete) {
|
||||||
@@ -220,7 +270,7 @@ class AnsiParser {
|
|||||||
this.processQueue();
|
this.processQueue();
|
||||||
} else if (this.isComplete && !this.chunked) {
|
} else if (this.isComplete && !this.chunked) {
|
||||||
self.postMessage({
|
self.postMessage({
|
||||||
result: this.getScreenText(),
|
result: this.getAllLines(),
|
||||||
percent: this.percent,
|
percent: this.percent,
|
||||||
isComplete: true
|
isComplete: true
|
||||||
});
|
});
|
||||||
@@ -244,9 +294,11 @@ self.onmessage = function (event: MessageEvent<MessageProps>) {
|
|||||||
page,
|
page,
|
||||||
isComplete = false,
|
isComplete = false,
|
||||||
chunked = true,
|
chunked = true,
|
||||||
percent = 0
|
percent = 0,
|
||||||
|
isDownloading = false
|
||||||
} = event.data;
|
} = event.data;
|
||||||
|
|
||||||
|
parser.setIsDownloading(isDownloading);
|
||||||
parser.setPage(page);
|
parser.setPage(page);
|
||||||
parser.setIsCompelete(isComplete);
|
parser.setIsCompelete(isComplete);
|
||||||
parser.setChunked(chunked);
|
parser.setChunked(chunked);
|
||||||
|
|||||||
@@ -17,7 +17,7 @@
|
|||||||
top: 0;
|
top: 0;
|
||||||
bottom: 0;
|
bottom: 0;
|
||||||
right: -18px;
|
right: -18px;
|
||||||
width: 80px;
|
// width: 80px;
|
||||||
height: 185px;
|
height: 185px;
|
||||||
|
|
||||||
&:hover {
|
&:hover {
|
||||||
|
|||||||
@@ -15,6 +15,7 @@
|
|||||||
|
|
||||||
.text {
|
.text {
|
||||||
min-height: 22px;
|
min-height: 22px;
|
||||||
|
padding-inline-end: 80px;
|
||||||
|
|
||||||
&.numable {
|
&.numable {
|
||||||
position: relative;
|
position: relative;
|
||||||
|
|||||||
@@ -12,7 +12,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.ant-btn {
|
.ant-btn {
|
||||||
background-color: rgba(71, 71, 71, 100%) !important;
|
background-color: rgba(71, 71, 71, 70%) !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
.pages {
|
.pages {
|
||||||
|
|||||||
@@ -1,25 +1,32 @@
|
|||||||
import { useEffect, useState } from 'react';
|
import { useEffect, useState } from 'react';
|
||||||
|
|
||||||
const useResizeObserver = (ref: React.RefObject<HTMLElement>) => {
|
export const useResizeObserver = (ref: React.RefObject<HTMLElement>) => {
|
||||||
const [size, setSize] = useState({ width: 0, height: 0 });
|
const [size, setSize] = useState({ width: 0, height: 0 });
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const element = ref.current;
|
const element = ref.current;
|
||||||
if (!element) return;
|
if (!element) return;
|
||||||
|
|
||||||
const observer = new ResizeObserver((entries) => {
|
const updateSize = () => {
|
||||||
if (entries[0]) {
|
const rect = element.getBoundingClientRect();
|
||||||
const { width, height } = entries[0].contentRect;
|
setSize((prev) => {
|
||||||
setSize({ width, height });
|
if (prev.width === rect.width && prev.height === rect.height)
|
||||||
}
|
return prev;
|
||||||
|
return { width: rect.width, height: rect.height };
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const observer = new ResizeObserver(() => {
|
||||||
|
updateSize();
|
||||||
});
|
});
|
||||||
|
|
||||||
observer.observe(element);
|
observer.observe(element);
|
||||||
|
updateSize();
|
||||||
|
|
||||||
return () => {
|
return () => {
|
||||||
observer.disconnect();
|
observer.disconnect();
|
||||||
};
|
};
|
||||||
}, [ref]);
|
}, [ref.current]);
|
||||||
|
|
||||||
return size;
|
return size;
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -4,7 +4,6 @@ import classNames from 'classnames';
|
|||||||
import _ from 'lodash';
|
import _ from 'lodash';
|
||||||
import React, {
|
import React, {
|
||||||
forwardRef,
|
forwardRef,
|
||||||
memo,
|
|
||||||
useCallback,
|
useCallback,
|
||||||
useEffect,
|
useEffect,
|
||||||
useImperativeHandle,
|
useImperativeHandle,
|
||||||
@@ -25,9 +24,17 @@ interface LogsViewerProps {
|
|||||||
tail?: number;
|
tail?: number;
|
||||||
enableScorllLoad?: boolean;
|
enableScorllLoad?: boolean;
|
||||||
diffHeight?: number;
|
diffHeight?: number;
|
||||||
|
isDownloading?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
const LogsViewer: React.FC<LogsViewerProps> = forwardRef((props, ref) => {
|
const LogsViewer: React.FC<LogsViewerProps> = forwardRef((props, ref) => {
|
||||||
const { diffHeight, url, tail: defaultTail, enableScorllLoad = true } = props;
|
const {
|
||||||
|
diffHeight,
|
||||||
|
url,
|
||||||
|
tail: defaultTail,
|
||||||
|
enableScorllLoad = true,
|
||||||
|
isDownloading
|
||||||
|
} = props;
|
||||||
const { pageSize, page, setPage, setTotalPage, totalPage } =
|
const { pageSize, page, setPage, setTotalPage, totalPage } =
|
||||||
useLogsPagination();
|
useLogsPagination();
|
||||||
const { setChunkFetch } = useSetChunkFetch();
|
const { setChunkFetch } = useSetChunkFetch();
|
||||||
@@ -43,7 +50,7 @@ const LogsViewer: React.FC<LogsViewerProps> = forwardRef((props, ref) => {
|
|||||||
const pageRef = useRef<any>(page);
|
const pageRef = useRef<any>(page);
|
||||||
const totalPageRef = useRef<any>(totalPage);
|
const totalPageRef = useRef<any>(totalPage);
|
||||||
const isLoadingMoreRef = useRef(false);
|
const isLoadingMoreRef = useRef(false);
|
||||||
const [currentData, setCurrentData] = useState<any[]>([]);
|
const [currentData, setCurrentPageData] = useState<any[]>([]);
|
||||||
const scrollPosRef = useRef<any>({
|
const scrollPosRef = useRef<any>({
|
||||||
pos: 'bottom',
|
pos: 'bottom',
|
||||||
page: 1
|
page: 1
|
||||||
@@ -58,6 +65,22 @@ const LogsViewer: React.FC<LogsViewerProps> = forwardRef((props, ref) => {
|
|||||||
}
|
}
|
||||||
}));
|
}));
|
||||||
|
|
||||||
|
const removeBracketsFromLine = (row: string) => {
|
||||||
|
return row.startsWith('(…)') ? row.slice(3) : row;
|
||||||
|
};
|
||||||
|
|
||||||
|
const setCurrentData = (lines: string[]) => {
|
||||||
|
console.log('setCurrentData', lines);
|
||||||
|
const dataList = lines.map((line, index) => {
|
||||||
|
return {
|
||||||
|
content: line,
|
||||||
|
uid: `${pageRef.current}-${index}`
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
setCurrentPageData(dataList);
|
||||||
|
};
|
||||||
|
|
||||||
const debounceLoading = _.debounce(() => {
|
const debounceLoading = _.debounce(() => {
|
||||||
setLoading(false);
|
setLoading(false);
|
||||||
isLoadingMoreRef.current = false;
|
isLoadingMoreRef.current = false;
|
||||||
@@ -134,7 +157,8 @@ const LogsViewer: React.FC<LogsViewerProps> = forwardRef((props, ref) => {
|
|||||||
logParseWorker.current.postMessage({
|
logParseWorker.current.postMessage({
|
||||||
inputStr: data,
|
inputStr: data,
|
||||||
page: pageRef.current,
|
page: pageRef.current,
|
||||||
reset: clearScreen.current
|
reset: clearScreen.current,
|
||||||
|
isDownloading: isDownloading
|
||||||
});
|
});
|
||||||
clearScreen.current = false;
|
clearScreen.current = false;
|
||||||
};
|
};
|
||||||
@@ -231,7 +255,7 @@ const LogsViewer: React.FC<LogsViewerProps> = forwardRef((props, ref) => {
|
|||||||
return () => {
|
return () => {
|
||||||
chunkRequedtRef.current?.current?.abort?.();
|
chunkRequedtRef.current?.current?.abort?.();
|
||||||
};
|
};
|
||||||
}, [url]);
|
}, [url, isDownloading]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
debouncedScroll();
|
debouncedScroll();
|
||||||
@@ -313,7 +337,7 @@ const LogsViewer: React.FC<LogsViewerProps> = forwardRef((props, ref) => {
|
|||||||
<div className="pg">
|
<div className="pg">
|
||||||
<div
|
<div
|
||||||
className={classNames('pg-inner', {
|
className={classNames('pg-inner', {
|
||||||
'at-top': isAtTop
|
'at-top': true
|
||||||
})}
|
})}
|
||||||
>
|
>
|
||||||
<LogsPagination
|
<LogsPagination
|
||||||
@@ -333,4 +357,4 @@ const LogsViewer: React.FC<LogsViewerProps> = forwardRef((props, ref) => {
|
|||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
export default memo(LogsViewer);
|
export default LogsViewer;
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ import 'katex/dist/katex.min.css';
|
|||||||
import React, { useMemo } from 'react';
|
import React, { useMemo } from 'react';
|
||||||
import ReactMarkdown from 'react-markdown';
|
import ReactMarkdown from 'react-markdown';
|
||||||
import rehypeKatex from 'rehype-katex';
|
import rehypeKatex from 'rehype-katex';
|
||||||
|
import remarkBreaks from 'remark-breaks';
|
||||||
import remarkGfm from 'remark-gfm';
|
import remarkGfm from 'remark-gfm';
|
||||||
import remarkMath from 'remark-math';
|
import remarkMath from 'remark-math';
|
||||||
import styled from 'styled-components';
|
import styled from 'styled-components';
|
||||||
@@ -17,9 +18,7 @@ interface FullMarkdownProps {
|
|||||||
|
|
||||||
const Wrapper = styled.div.attrs(() => ({
|
const Wrapper = styled.div.attrs(() => ({
|
||||||
className: 'markdown-viewer'
|
className: 'markdown-viewer'
|
||||||
}))`
|
}))``;
|
||||||
line-height: 2;
|
|
||||||
`;
|
|
||||||
|
|
||||||
const CodeViewer = (props: any) => {
|
const CodeViewer = (props: any) => {
|
||||||
const { children, className, node, theme, ...rest } = props;
|
const { children, className, node, theme, ...rest } = props;
|
||||||
@@ -41,7 +40,7 @@ const FullMarkdown: React.FC<FullMarkdownProps> = ({ content, theme }) => {
|
|||||||
return (
|
return (
|
||||||
<Wrapper>
|
<Wrapper>
|
||||||
<ReactMarkdown
|
<ReactMarkdown
|
||||||
remarkPlugins={[[remarkMath], remarkGfm]}
|
remarkPlugins={[[remarkMath], remarkGfm, remarkBreaks]}
|
||||||
rehypePlugins={[rehypeKatex]}
|
rehypePlugins={[rehypeKatex]}
|
||||||
components={{
|
components={{
|
||||||
code(props) {
|
code(props) {
|
||||||
|
|||||||