feat: yaml editor

This commit is contained in:
jialin
2025-10-13 14:40:26 +08:00
parent e863b84016
commit 54703c67c1
48 changed files with 2062 additions and 246 deletions
+1 -3
View File
@@ -55,9 +55,7 @@ const CopyButton: React.FC<CopyButtonProps> = ({
});
clipboardRef.current.on('error', (e: any) => {
message.success(
intl.formatMessage({ id: 'common.copy.fail' }) as string
);
message.error(intl.formatMessage({ id: 'common.copy.fail' }) as string);
e.clearSelection();
});
}
+41 -49
View File
@@ -1,74 +1,66 @@
import classNames from 'classnames';
import React from 'react';
import styled from 'styled-components';
import CopyButton from '../copy-button';
import SegmentLine from '../segment-line';
import './index.less';
const Wrapper = styled.div<{ $height: number }>`
height: ${(props) => props.$height}px;
const HeaderWrapper = styled.div<{ $height?: number }>`
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 {
headerHeight?: number;
header?: React.ReactNode;
children: React.ReactNode;
showHeader?: boolean;
copyText: string;
defaultValue?: string;
langOptions?: Global.BaseOption<string | number>[];
variant?: 'bordered' | 'borderless';
styles?: {
wrapper?: React.CSSProperties;
header?: React.CSSProperties;
content?: React.CSSProperties;
};
onChangeLang?: (value: string | number) => void;
}
const EditorWrap: React.FC<EditorwrapProps> = ({
headerHeight = 40,
header,
children,
copyText,
langOptions = [],
onChangeLang,
defaultValue,
styles = {},
showHeader = true
variant = 'borderless',
styles = {}
}) => {
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 (
<div className="editor-wrap" style={{ ...styles.wrapper }}>
{renderHeader()}
<div className="editor-content">{children}</div>
</div>
<Wrapper
style={{ ...styles.wrapper }}
className={classNames({
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 React, { useEffect, useRef, useState } from 'react';
import React, {
forwardRef,
useEffect,
useImperativeHandle,
useRef
} from 'react';
import EditorWrap from '../editor-wrap';
interface ViewerProps {
ref?: any;
lang: string;
defaultLang?: string;
langOptions?: Global.BaseOption<string>[];
config?: any;
value: string;
height?: string | number;
theme?: string;
showHeader?: boolean;
header?: React.ReactNode;
placeholder?: string;
variant?: 'bordered' | 'borderless';
}
const ViewerEditor: React.FC<ViewerProps> = (props) => {
const editorRef = useRef<any>(null);
const ViewerEditor: React.FC<ViewerProps> = forwardRef((props, ref) => {
const {
lang,
value,
config,
langOptions,
defaultLang,
height = 380,
showHeader
theme = 'vs-dark',
header,
variant = 'borderless',
placeholder
} = props;
const [langType, setLangType] = useState(defaultLang);
const editorRef = useRef<any>(null);
const handleBeforeMount = (monaco: any) => {
monaco.languages.typescript.javascriptDefaults.setDiagnosticsOptions({
@@ -36,12 +45,8 @@ const ViewerEditor: React.FC<ViewerProps> = (props) => {
const handleEditorDidMount = (editor: any, monaco: any) => {
editorRef.current = editor;
console.log('loaded====', editor, monaco);
};
const handleOnChangeLang = (value: string) => {
setLangType(value);
};
const formatCode = () => {
if (editorRef.current) {
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(() => {
formatCode();
setTimeout(() => {
const lineCount = editorRef.current?.getModel().getLineCount(); // 获取总行数
editorRef.current?.revealLine(lineCount); // 滚动到最后一行
}, 100); // 可以调整延时,确保编辑器完全加载
const lineCount = editorRef.current?.getModel().getLineCount();
editorRef.current?.revealLine(lineCount);
}, 100);
}, [value]);
return (
<EditorWrap
copyText={value}
langOptions={langOptions}
defaultValue={lang}
showHeader={showHeader}
onChangeLang={handleOnChangeLang}
>
<EditorWrap header={header} variant={variant}>
<Editor
height={height}
theme="vs-dark"
theme={theme}
className="monaco-editor"
defaultLanguage={defaultLang}
language={langType}
language={lang}
value={value}
options={config}
options={{
minimap: { enabled: false },
scrollbar: {
verticalScrollbarSize: 6,
horizontalScrollbarSize: 6
},
placeholder: placeholder
}}
loading={<LoadingOutlined style={{ fontSize: 24 }}></LoadingOutlined>}
beforeMount={handleBeforeMount}
onMount={handleEditorDidMount}
/>
</EditorWrap>
);
};
});
export default React.memo(ViewerEditor);
export default ViewerEditor;
+5 -1
View File
@@ -45,7 +45,11 @@ const icons = {
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' })
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' })
};
export default icons;
+1 -1
View File
@@ -2,7 +2,7 @@ import { createFromIconfontCN } from '@ant-design/icons';
// import './iconfont/iconfont.js';
const IconFont = createFromIconfontCN({
scriptUrl: '//at.alicdn.com/t/c/font_4613488_wt33o3w25g.js'
scriptUrl: '//at.alicdn.com/t/c/font_4613488_c06q7b0n32v.js'
});
export default IconFont;
+6
View File
@@ -3,6 +3,7 @@ import {
DeleteOutlined,
DownOutlined,
PlusOutlined,
SearchOutlined,
SyncOutlined
} from '@ant-design/icons';
import { useIntl } from '@umijs/max';
@@ -170,6 +171,11 @@ export const FilterBar: React.FC<FilterBarProps> = (props) => {
left={
<Space>
<Input
prefix={
<SearchOutlined
style={{ color: 'var(--ant-color-text-placeholder)' }}
></SearchOutlined>
}
placeholder={
inputHolder ||
intl.formatMessage({
@@ -127,6 +127,7 @@ const SealTextArea: React.FC<InputTextareaProps & SealFormItemProps> = (
>
<Input.TextArea
{...rest}
spellCheck={rest.spellCheck ?? false}
autoSize={autoSize}
ref={inputRef}
style={{ minHeight: '80px', ...style }}
+3
View File
@@ -48,6 +48,7 @@ const SegmentedQwrapper = styled.div<{ $height: number }>`
border-radius: 2px;
bottom: 0px;
top: unset;
opacity: 1;
}
}
.ant-segmented-item-selected {
@@ -55,6 +56,7 @@ const SegmentedQwrapper = styled.div<{ $height: number }>`
color: var(--color-white-primary);
&::after {
height: 2px;
opacity: 1;
}
}
}
@@ -68,6 +70,7 @@ const SegmentLine: React.FC<SegmentLineProps> = (props) => {
className = 'segment-line',
...rest
} = props;
return (
<SegmentedQwrapper $height={height}>
<Segmented