blob: 1c003d50b06f76cf3d52699be1d24582a7b10f7d [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { LinkOutlined } from '@ant-design/icons';
import type { Monaco } from '@monaco-editor/react';
import Editor from '@monaco-editor/react';
import { Button, Drawer, notification, PageHeader, Select, Space } from 'antd';
import { js_beautify } from 'js-beautify';
import type { languages } from 'monaco-editor';
import React, { useEffect, useState } from 'react';
import { CopyToClipboard } from 'react-copy-to-clipboard';
import { useIntl } from 'umi';
import { json2yaml, yaml2json } from '../../helpers';
type Props = {
visible: boolean;
readonly: boolean;
type: 'route' | 'service' | 'consumer' | 'upstream';
data: Record<string, any>;
onClose?: () => void;
onSubmit?: (data: Record<string, any>) => void;
};
enum monacoLanguageList {
JSON = 'JSON',
YAML = 'YAML',
}
const RawDataEditor: React.FC<Props> = ({
visible,
readonly = true,
type,
data = {},
onClose = () => {},
onSubmit = () => {},
}) => {
const { formatMessage } = useIntl();
const [monacoLanguage, setMonacoLanguage] = useState<PluginComponent.MonacoLanguage>(
monacoLanguageList.JSON,
);
const [content, setContent] = useState('');
useEffect(() => {
switch (monacoLanguage) {
case monacoLanguageList.JSON:
setContent(JSON.stringify(data, null, 2));
break;
case monacoLanguageList.YAML: {
const { data: yamlData } = json2yaml(JSON.stringify(data, null, 2));
setContent(yamlData);
break;
}
default:
}
}, [data]);
useEffect(() => {
setMonacoLanguage(monacoLanguageList.JSON);
}, [visible]);
const modeOptions = [
{ label: monacoLanguageList.JSON, value: monacoLanguageList.JSON },
{ label: monacoLanguageList.YAML, value: monacoLanguageList.YAML },
];
const handleModeChange = (value: PluginComponent.MonacoLanguage) => {
switch (value) {
case monacoLanguageList.JSON:
setContent((c) => {
const { data: jsonData, error } = yaml2json(c, true);
if (error) {
notification.error({ message: formatMessage({ id: 'component.global.invalidYaml' }) });
return c;
}
return js_beautify(jsonData, { indent_size: 2 });
});
break;
case monacoLanguageList.YAML:
setContent((c) => {
const { data: yamlData, error } = json2yaml(c);
if (error) {
notification.error({ message: formatMessage({ id: 'component.global.invalidJson' }) });
return c;
}
return yamlData;
});
break;
default:
break;
}
setMonacoLanguage(value);
};
const formatYaml = (yaml: string): string => {
const json = yaml2json(yaml, true);
if (json.error) {
return yaml;
}
return json2yaml(json.data).data;
};
const editorWillMount = (monaco: Monaco) => {
const yamlFormatProvider: languages.DocumentFormattingEditProvider = {
provideDocumentFormattingEdits(model) {
return [
{
text: formatYaml(model.getValue()),
range: model.getFullModelRange(),
},
];
},
};
monaco.languages.registerDocumentFormattingEditProvider('yaml', yamlFormatProvider);
monaco.languages.json.jsonDefaults.setDiagnosticsOptions({
validate: true,
trailingCommas: 'error',
});
};
return (
<>
<Drawer
title={formatMessage({ id: 'component.global.data.editor' })}
placement="right"
width={700}
visible={visible}
onClose={onClose}
footer={
<div style={{ display: 'flex', justifyContent: 'space-between' }}>
<Button onClick={onClose} key={1}>
{formatMessage({ id: 'component.global.cancel' })}
</Button>
<Space>
<Button
key={2}
type="primary"
onClick={() => {
try {
const editorData =
monacoLanguage === monacoLanguageList.JSON
? JSON.parse(content)
: yaml2json(content, false).data;
onSubmit(editorData);
} catch (error) {
notification.error({
message: formatMessage({ id: 'component.global.invalidJson' }),
});
}
}}
>
{formatMessage({ id: 'component.global.submit' })}
</Button>
</Space>
</div>
}
>
<PageHeader
title=""
extra={[
<Select
key={'monaco-language'}
defaultValue={monacoLanguageList.JSON}
value={monacoLanguage}
options={modeOptions}
onChange={(value: PluginComponent.MonacoLanguage) => {
handleModeChange(value);
}}
data-cy="monaco-language"
/>,
<CopyToClipboard
key={'copy'}
text={content}
onCopy={(_: string, result: boolean) => {
if (!result) {
notification.error({
message: formatMessage({ id: 'component.global.copyFail' }),
});
return;
}
notification.success({
message: formatMessage({ id: 'component.global.copySuccess' }),
});
}}
>
<Button type="primary" key={2}>
{formatMessage({ id: 'component.global.copy' })}
</Button>
</CopyToClipboard>,
<Button
type="default"
icon={<LinkOutlined />}
onClick={() => {
window.open(`https://apisix.apache.org/docs/apisix/admin-api#${type}`);
}}
key={'document'}
>
{formatMessage({ id: 'component.global.document' })}
</Button>,
]}
/>
<Editor
value={content}
onChange={(text) => {
if (text) {
setContent(text);
} else {
setContent('');
}
}}
onMount={(editor) => {
// NOTE: for debug & test
// @ts-ignore
window.monacoEditor = editor;
}}
beforeMount={editorWillMount}
language={monacoLanguage.toLocaleLowerCase()}
options={{
scrollbar: {
vertical: 'hidden',
horizontal: 'hidden',
},
wordWrap: 'on',
minimap: { enabled: false },
readOnly: readonly,
}}
/>
</Drawer>
</>
);
};
export default RawDataEditor;