-
Notifications
You must be signed in to change notification settings - Fork 1.8k
Expand file tree
/
Copy pathresponse-editor.tsx
More file actions
159 lines (150 loc) · 4.46 KB
/
response-editor.tsx
File metadata and controls
159 lines (150 loc) · 4.46 KB
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
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
import { formatError } from '@graphiql/toolkit';
import { ComponentType, FC, useEffect, useRef } from 'react';
import { createRoot, Root } from 'react-dom/client';
import { useGraphiQL, useGraphiQLActions } from './provider';
import { ImagePreview } from './image-preview';
import {
getOrCreateModel,
createEditor,
onEditorContainerKeyDown,
pick,
cleanupDisposables,
cn,
Range,
} from '../utility';
import { KEY_BINDINGS, URI_NAME } from '../constants';
import type { EditorProps } from '../types';
import type * as monaco from 'monaco-editor';
import { useMonaco } from '../stores';
type ResponseTooltipType = ComponentType<{
/**
* A position in the editor.
*/
position: monaco.Position;
/**
* Word that has been hovered over.
*/
word: monaco.editor.IWordAtPosition;
}>;
interface ResponseEditorProps extends EditorProps {
/**
* Customize the tooltip when hovering over properties in the response editor.
*/
responseTooltip?: ResponseTooltipType;
}
export const ResponseEditor: FC<ResponseEditorProps> = ({
responseTooltip: ResponseTooltip,
editorOverrides,
...props
}) => {
const { setEditor, run } = useGraphiQLActions();
const { fetchError, validationErrors, responseEditor, uriInstanceId } =
useGraphiQL(
pick('fetchError', 'validationErrors', 'responseEditor', 'uriInstanceId'),
);
const ref = useRef<HTMLDivElement>(null!);
const monaco = useMonaco(state => state.monaco);
useEffect(() => {
if (fetchError) {
responseEditor?.setValue(fetchError);
}
if (validationErrors.length) {
responseEditor?.setValue(formatError(validationErrors));
}
}, [responseEditor, fetchError, validationErrors]);
useEffect(() => {
if (!monaco) {
return;
}
const model = getOrCreateModel({
uri: `${uriInstanceId}${URI_NAME.response}`,
value: '',
});
const editor = createEditor(ref, {
model,
readOnly: true,
lineNumbers: 'off',
wordWrap: 'on', // Toggle word wrap on resizing editors
contextmenu: false, // Disable the right-click context menu
...editorOverrides,
});
setEditor({ responseEditor: editor });
let lastRoot: Root | undefined;
let timerId: ReturnType<typeof setTimeout> | undefined;
const provideHover: monaco.languages.HoverProvider['provideHover'] = (
$model,
position,
) => {
const sameModel = $model.uri === model.uri;
if (!sameModel) {
return null; // Ignore for other editors
}
const wordAtPosition = $model.getWordAtPosition(position);
if (!wordAtPosition?.word.startsWith('/')) {
return null;
}
const shouldRender = ImagePreview.shouldRender(wordAtPosition.word);
if (!shouldRender) {
return null;
}
// Return a placeholder content with a unique ID for now
const hoverId = `hover-${position.lineNumber}-${position.column}`;
if (timerId) {
clearTimeout(timerId);
}
timerId = setTimeout(() => {
const el = document.querySelector<HTMLDivElement>(
`[data-id="${hoverId}"]`,
);
if (!el) {
return;
}
lastRoot?.unmount();
lastRoot = createRoot(el);
lastRoot.render(
// Handle image tooltips and custom tooltips
<>
{ResponseTooltip && (
<ResponseTooltip position={position} word={wordAtPosition} />
)}
<ImagePreview path={wordAtPosition.word} />
</>,
);
}, 500);
return {
range: new Range(
position.lineNumber,
wordAtPosition.startColumn,
position.lineNumber,
wordAtPosition.endColumn,
),
contents: [
{
value: `<div data-id="${hoverId}">Loading...</div>`,
supportHtml: true,
},
],
};
};
const languageId = model.getLanguageId();
const disposables = [
monaco.languages.registerHoverProvider(languageId, { provideHover }),
editor.addAction({ ...KEY_BINDINGS.runQuery, run }),
editor,
model,
];
return cleanupDisposables(disposables);
}, [monaco]); // eslint-disable-line react-hooks/exhaustive-deps -- only on mount
return (
<section
ref={ref}
aria-label="Result Window"
aria-live="polite"
aria-atomic="true"
tabIndex={0}
onKeyDown={onEditorContainerKeyDown}
{...props}
className={cn('result-window', props.className)}
/>
);
};