fix: 修复 DiffEditor 可能显示最新值的问题

This commit is contained in:
2betop 2024-10-12 12:33:12 +08:00
parent 7ddd00d0d9
commit 1513c5402a
6 changed files with 184 additions and 154 deletions

View File

@ -112,7 +112,7 @@ export const LocaleContext = React.createContext('');
export function localeable<
T extends React.ComponentType<React.ComponentProps<T> & LocaleProps>
>(ComposedComponent: T) {
>(ComposedComponent: T, methods?: Array<string>) {
type OuterProps = JSX.LibraryManagedAttributes<
T,
Omit<React.ComponentProps<T>, keyof LocaleProps>
@ -189,6 +189,17 @@ export function localeable<
ComposedComponent
);
if (Array.isArray(methods)) {
methods.forEach(method => {
if (ComposedComponent.prototype[method]) {
(result as any).prototype[method] = function () {
const fn = this.ref?.[method];
return fn ? fn.apply(this.ref, arguments) : undefined;
};
}
});
}
return result as typeof result & {
ComposedComponent: T;
};

View File

@ -131,7 +131,7 @@ export function themeable<
T extends React.ComponentType<React.ComponentProps<T> & ThemeProps> & {
themeKey?: string;
}
>(ComposedComponent: T) {
>(ComposedComponent: T, methods?: Array<string>) {
type OuterProps = JSX.LibraryManagedAttributes<
T,
Omit<React.ComponentProps<T>, keyof ThemeProps>
@ -211,6 +211,17 @@ export function themeable<
ComposedComponent
);
if (Array.isArray(methods)) {
methods.forEach(method => {
if (ComposedComponent.prototype[method]) {
(result as any).prototype[method] = function () {
const fn = this.ref?.[method];
return fn ? fn.apply(this.ref, arguments) : undefined;
};
}
});
}
return result as typeof result & {
ComposedComponent: T;
};

View File

@ -0,0 +1,107 @@
import React from 'react';
import Editor, {EditorBaseProps} from './Editor';
import {autobind} from 'amis-core';
export interface DiffEditorProps extends EditorBaseProps {
originValue?: string;
}
export default class DiffEditor extends React.Component<DiffEditorProps> {
editor: any;
monaco: any;
originalEditor: any;
modifiedEditor: any;
toDispose: Array<Function> = [];
domRef = React.createRef<any>();
componentDidUpdate(prevProps: any) {
const {value, originValue} = this.props;
if (this.originalEditor && originValue !== prevProps.originValue) {
this.originalEditor.getModel().setValue(originValue || '');
}
if (this.modifiedEditor && value !== prevProps.value) {
this.modifiedEditor.getModel().setValue(value || '');
}
}
componentWillUnmount() {
this.toDispose.forEach(fn => fn());
}
prevHeight = 0;
@autobind
updateContainerSize(editor: any, monaco: any) {
const dom = this.domRef.current?.getDom();
if (!dom) {
return;
}
const lineHeight = editor.getOption(monaco.editor.EditorOption.lineHeight);
const lineCount = editor.getModel()?.getLineCount() || 1;
const height = editor.getTopForLineNumber(lineCount + 1) + lineHeight;
if (this.prevHeight !== height && dom.parentElement) {
this.prevHeight = height;
dom.parentElement.style.height = `${height}px`;
editor.layout();
}
}
@autobind
editorFactory(containerElement: any, monaco: any, options: any) {
if (this.props.editorFactory) {
return this.props.editorFactory(containerElement, monaco, options);
}
return monaco.editor.createDiffEditor(containerElement, options);
}
@autobind
editorDidMount(editor: any, monaco: any) {
const {value, originValue, language, onFocus, onBlur, editorDidMount} =
this.props;
editorDidMount?.(editor, monaco);
this.monaco = monaco;
this.editor = editor;
this.modifiedEditor = editor.getModifiedEditor();
this.originalEditor = editor.getOriginalEditor();
onFocus &&
this.toDispose.push(
this.modifiedEditor.onDidFocusEditorWidget(onFocus).dispose
);
onBlur &&
this.toDispose.push(
this.modifiedEditor.onDidBlurEditorWidget(onBlur).dispose
);
this.toDispose.push(
this.modifiedEditor.onDidChangeModelDecorations(() => {
this.updateContainerSize(this.modifiedEditor, monaco); // typing
requestAnimationFrame(
this.updateContainerSize.bind(this, this.modifiedEditor, monaco)
); // folding
}).dispose
);
this.editor.setModel({
original: this.monaco.editor.createModel(originValue || '', language),
modified: this.monaco.editor.createModel(value, language)
});
}
render() {
const {value, originValue, options, ...rest} = this.props;
return (
<Editor
{...rest}
ref={this.domRef}
editorDidMount={this.editorDidMount}
editorFactory={this.editorFactory}
isDiffEditor
/>
);
}
}

View File

@ -6,7 +6,7 @@
import React from 'react';
import cx from 'classnames';
import {ClassNamesFn, themeable} from 'amis-core';
import {ClassNamesFn, themeable, ThemeProps} from 'amis-core';
import {autobind} from 'amis-core';
import {Icon} from './icons';
import {LocaleProps, localeable} from 'amis-core';
@ -67,33 +67,32 @@ export function monacoFactory(
});
}
export interface EditorProps extends LocaleProps {
export interface EditorBaseProps {
value?: string;
defaultValue?: string;
width?: number | string;
height?: number | string;
onChange?: (value: string, event: any) => void;
disabled?: boolean;
language?: string;
editorTheme?: string;
allowFullscreen?: boolean;
options: {
[propName: string]: any;
};
classPrefix: string;
className?: string;
classnames: ClassNamesFn;
context?: any;
style?: any;
isDiffEditor?: boolean;
placeholder?: string;
onFocus?: () => void;
onBlur?: () => void;
onFocus?: (e: any) => void;
onBlur?: (e: any) => void;
editorDidMount?: (editor: any, monaco: any) => void;
editorWillMount?: (monaco: any) => void;
editorWillUnmount?: (editor: any, monaco: any) => void;
editorFactory?: (conatainer: HTMLElement, monaco: any, options: any) => any;
}
export interface EditorProps extends EditorBaseProps, LocaleProps, ThemeProps {}
export interface EditorState {
isFullscreen?: boolean;
innerWidth?: any;
@ -124,6 +123,7 @@ export class Editor extends React.Component<EditorProps, EditorState> {
super(props);
this.wrapperRef = this.wrapperRef.bind(this);
this.getDom = this.getDom.bind(this);
this.currentValue = props.value;
}
@ -200,6 +200,10 @@ export class Editor extends React.Component<EditorProps, EditorState> {
}
}
getDom() {
return this.container;
}
loadMonaco() {
// 由于 require.config({'vs/nls': { availableLanguages: { '*': 'xxxx' }}}) 只能在初始化之前设置有用,所以这里只能用全局变量的方式来设置。
// 另外此方式只是针对 jssdk 和平台有效,对于其他方式还需要再想想。
@ -234,6 +238,7 @@ export class Editor extends React.Component<EditorProps, EditorState> {
const factory = editorFactory || monacoFactory;
this.editor = factory(containerElement, monaco, {
...options,
readOnly: this.props.disabled,
automaticLayout: true,
value,
language,
@ -272,14 +277,14 @@ export class Editor extends React.Component<EditorProps, EditorState> {
if (!this.preventTriggerChangeEvent && onChange) {
onChange(value, event);
}
})
}).dispose
);
onFocus &&
editor.onDidFocusEditorWidget &&
this.disposes.push(editor.onDidFocusEditorWidget(onFocus));
this.disposes.push(editor.onDidFocusEditorWidget(onFocus).dispose);
onBlur &&
editor.onDidBlurEditorWidget &&
this.disposes.push(editor.onDidBlurEditorWidget(onBlur));
this.disposes.push(editor.onDidBlurEditorWidget(onBlur).dispose);
const {width = 'auto', height = 'auto'} =
this?.editor?._configuration?._elementSizeObserver ?? {};
@ -353,4 +358,4 @@ export class Editor extends React.Component<EditorProps, EditorState> {
}
}
export default themeable(localeable(Editor));
export default themeable(localeable(Editor, ['getDom']), ['getDom']);

View File

@ -30,6 +30,7 @@ import Drawer from './Drawer';
import Tabs from './Tabs';
import Tab from './Tab';
import Editor from './Editor';
import DiffEditor from './DiffEditor';
import Html from './Html';
export * from './icons';
import * as Icons from './icons';
@ -167,6 +168,7 @@ export {
Tabs,
Tab,
Editor,
DiffEditor,
Html,
Icons,
Layout,

View File

@ -6,10 +6,9 @@ import {
resolveEventData,
getVariable
} from 'amis-core';
import {LazyComponent} from 'amis-core';
import {DiffEditor} from 'amis-ui';
import {isPureVariable, resolveVariableAndFilter} from 'amis-core';
import {FormBaseControlSchema, SchemaTokenizeableString} from '../../Schema';
import {autobind} from 'amis-core';
import type {Position} from 'monaco-editor';
import type {ListenerAction} from 'amis-core';
@ -42,10 +41,6 @@ export interface DiffControlSchema extends FormBaseControlSchema {
export type DiffEditorRendererEvent = 'blur' | 'focus';
function loadComponent(): Promise<any> {
return import('amis-ui/lib/components/Editor').then(item => item.default);
}
export interface DiffEditorProps
extends FormControlProps,
Omit<
@ -71,7 +66,14 @@ function normalizeValue(value: any, language?: string) {
return value || '';
}
export class DiffEditor extends React.Component<DiffEditorProps, any> {
export interface DiffEditorState {
focused: boolean;
}
export class DiffEditorRenderer extends React.Component<
DiffEditorProps,
DiffEditorState
> {
static defaultProps: Partial<DiffEditorProps> = {
language: 'javascript',
editorTheme: 'vs',
@ -92,25 +94,13 @@ export class DiffEditor extends React.Component<DiffEditorProps, any> {
};
editor: any;
monaco: any;
originalEditor: any;
modifiedEditor: any;
toDispose: Array<Function> = [];
divRef = React.createRef<HTMLDivElement>();
constructor(props: DiffEditorProps) {
super(props);
this.handleFocus = this.handleFocus.bind(this);
this.handleBlur = this.handleBlur.bind(this);
this.editorFactory = this.editorFactory.bind(this);
this.handleEditorMounted = this.handleEditorMounted.bind(this);
this.handleModifiedEditorChange =
this.handleModifiedEditorChange.bind(this);
}
componentWillUnmount() {
this.toDispose.forEach(fn => fn());
}
doAction(
@ -134,7 +124,7 @@ export class DiffEditor extends React.Component<DiffEditorProps, any> {
}
focus() {
this.editor.focus();
this.editor?.focus();
this.setState({focused: true});
// 最近一次光标位置
@ -180,97 +170,8 @@ export class DiffEditor extends React.Component<DiffEditorProps, any> {
onBlur?.(e);
}
componentDidUpdate(prevProps: any) {
const {data, value, diffValue, language} = this.props;
if (
this.originalEditor &&
(diffValue !== prevProps.diffValue || data !== prevProps.data)
) {
this.originalEditor.getModel().setValue(
isPureVariable(diffValue as string)
? normalizeValue(
resolveVariableAndFilter(
diffValue || '',
data,
'| raw',
() => ''
),
language
)
: normalizeValue(diffValue, language)
);
}
if (
this.modifiedEditor &&
value !== prevProps.value &&
!this.state.focused
) {
this.modifiedEditor.getModel().setValue(
isPureVariable(value as string)
? normalizeValue(
resolveVariableAndFilter(value || '', data, '| raw', () => ''),
language
)
: normalizeValue(value, language)
);
}
}
editorFactory(containerElement: any, monaco: any, options: any) {
return monaco.editor.createDiffEditor(containerElement, options);
}
handleEditorMounted(editor: any, monaco: any) {
const {value, data, language, diffValue} = this.props;
this.monaco = monaco;
this.editor = editor;
this.modifiedEditor = editor.getModifiedEditor();
this.originalEditor = editor.getOriginalEditor();
this.toDispose.push(
this.modifiedEditor.onDidFocusEditorWidget(this.handleFocus).dispose
);
this.toDispose.push(
this.modifiedEditor.onDidBlurEditorWidget(this.handleBlur).dispose
);
this.toDispose.push(
this.modifiedEditor.onDidChangeModelContent(
this.handleModifiedEditorChange
).dispose
);
this.toDispose.push(
this.modifiedEditor.onDidChangeModelDecorations(() => {
this.updateContainerSize(this.modifiedEditor, monaco); // typing
requestAnimationFrame(
this.updateContainerSize.bind(this, this.modifiedEditor, monaco)
); // folding
}).dispose
);
this.editor.setModel({
original: this.monaco.editor.createModel(
isPureVariable(diffValue as string)
? normalizeValue(
resolveVariableAndFilter(diffValue || '', data, '| raw'),
language
)
: normalizeValue(diffValue, language),
language
),
modified: this.monaco.editor.createModel(
normalizeValue(value, language),
language
)
});
}
async handleModifiedEditorChange() {
async handleChange(value: any) {
const {onChange, dispatchEvent} = this.props;
const value = this.modifiedEditor.getModel().getValue();
const rendererEvent = await dispatchEvent(
'change',
@ -284,22 +185,8 @@ export class DiffEditor extends React.Component<DiffEditorProps, any> {
onChange && onChange(value);
}
prevHeight = 0;
@autobind
updateContainerSize(editor: any, monaco: any) {
if (!this.divRef.current) {
return;
}
const lineHeight = editor.getOption(monaco.editor.EditorOption.lineHeight);
const lineCount = editor.getModel()?.getLineCount() || 1;
const height = editor.getTopForLineNumber(lineCount + 1) + lineHeight;
if (this.prevHeight !== height) {
this.prevHeight = height;
this.divRef.current.style.height = `${height}px`;
editor.layout();
}
handleEditorMounted(editor: any) {
this.editor = editor;
}
render() {
@ -313,12 +200,22 @@ export class DiffEditor extends React.Component<DiffEditorProps, any> {
options,
language,
editorTheme,
classnames: cx
diffValue,
classnames: cx,
data
} = this.props;
const originValue = isPureVariable(diffValue as string)
? normalizeValue(
resolveVariableAndFilter(diffValue || '', data, '| raw'),
language
)
: normalizeValue(diffValue, language);
const finalValue = normalizeValue(value, language);
return (
<div
ref={this.divRef}
className={cx(
'EditorControl',
size ? `EditorControl--${size}` : '',
@ -328,20 +225,17 @@ export class DiffEditor extends React.Component<DiffEditorProps, any> {
}
)}
>
<LazyComponent
getComponent={loadComponent}
value={value}
onChange={onChange}
<DiffEditor
value={finalValue}
originValue={originValue}
onChange={this.handleChange}
disabled={disabled}
language={language}
editorTheme={editorTheme}
options={options}
onFocus={this.handleFocus}
onBlur={this.handleBlur}
editorDidMount={this.handleEditorMounted}
editorFactory={this.editorFactory}
options={{
...options,
readOnly: disabled
}}
isDiffEditor
/>
</div>
);
@ -352,9 +246,9 @@ export class DiffEditor extends React.Component<DiffEditorProps, any> {
type: `diff-editor`,
sizeMutable: false
})
export class DiffEditorControlRenderer extends DiffEditor {
export class DiffEditorControlRenderer extends DiffEditorRenderer {
static defaultProps = {
...DiffEditor.defaultProps
...DiffEditorRenderer.defaultProps
};
}