feat: yaml editor
This commit is contained in:
@@ -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();
|
||||
});
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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 }}
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user