1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
|
import { Command, EditorView } from '@codemirror/view';
import { closeSearchPanel, openSearchPanel } from '@codemirror/search';
import { closeLintPanel, openLintPanel } from '@codemirror/lint';
import { observer } from 'mobx-react-lite';
import React, { useEffect, useRef, useState } from 'react';
import { EditorParent } from './EditorParent';
import { getLogger } from '../logging';
import { useRootStore } from '../RootStore';
const log = getLogger('EditorArea');
function usePanel(
label: string,
stateToSet: boolean,
editorView: EditorView | null,
openCommand: Command,
closeCommand: Command,
) {
const [cachedViewState, setCachedViewState] = useState<boolean>(false);
useEffect(() => {
if (editorView === null || cachedViewState === stateToSet) {
return;
}
const success = stateToSet ? openCommand(editorView) : closeCommand(editorView);
if (!success) {
log.error(
'Failed to synchronize',
label,
'panel state - store state:',
cachedViewState,
'view state:',
stateToSet,
);
}
setCachedViewState(stateToSet);
}, [
stateToSet,
editorView,
cachedViewState,
label,
openCommand,
closeCommand,
]);
return setCachedViewState;
}
export const EditorArea = observer(() => {
const { editorStore } = useRootStore();
const editorParentRef = useRef<HTMLDivElement | null>(null);
const [editorViewState, setEditorViewState] = useState<EditorView | null>(null);
const setSearchPanelOpen = usePanel(
'search',
editorStore.showSearchPanel,
editorViewState,
openSearchPanel,
closeSearchPanel,
);
const setLintPanelOpen = usePanel(
'lint',
editorStore.showLintPanel,
editorViewState,
openLintPanel,
closeLintPanel,
);
useEffect(() => {
if (editorParentRef.current === null) {
// Nothing to clean up.
return () => {};
}
const editorView = new EditorView({
state: editorStore.state,
parent: editorParentRef.current,
dispatch: (transaction) => {
editorStore.onTransaction(transaction);
editorView.update([transaction]);
if (editorView.state !== editorStore.state) {
log.error(
'Failed to synchronize editor state - store state:',
editorStore.state,
'view state:',
editorView.state,
);
}
},
});
setEditorViewState(editorView);
setSearchPanelOpen(false);
setLintPanelOpen(false);
// `dispatch` is bound to the view instance,
// so it does not have to be called as a method.
// eslint-disable-next-line @typescript-eslint/unbound-method
editorStore.updateDispatcher(editorView.dispatch);
log.info('Editor created');
return () => {
editorStore.updateDispatcher(null);
editorView.destroy();
log.info('Editor destroyed');
};
}, [
editorParentRef,
editorStore,
setSearchPanelOpen,
setLintPanelOpen,
]);
return (
<EditorParent
className="dark"
sx={{
'.cm-lineNumbers': editorStore.showLineNumbers ? {} : {
display: 'none !important',
},
}}
ref={editorParentRef}
/>
);
});
|