@patternfly/react-code-editor 6.2.1-prerelease.0 → 6.2.1-prerelease.10
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +42 -0
- package/dist/esm/components/CodeEditor/CodeEditor.d.ts +20 -56
- package/dist/esm/components/CodeEditor/CodeEditor.d.ts.map +1 -1
- package/dist/esm/components/CodeEditor/CodeEditor.js +160 -238
- package/dist/esm/components/CodeEditor/CodeEditor.js.map +1 -1
- package/dist/js/components/CodeEditor/CodeEditor.d.ts +20 -56
- package/dist/js/components/CodeEditor/CodeEditor.d.ts.map +1 -1
- package/dist/js/components/CodeEditor/CodeEditor.js +160 -237
- package/dist/js/components/CodeEditor/CodeEditor.js.map +1 -1
- package/package.json +7 -7
- package/src/components/CodeEditor/CodeEditor.tsx +356 -441
- package/src/components/CodeEditor/examples/CodeEditorShortcutMainHeader.tsx +1 -0
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { HTMLProps, ReactNode, useEffect, useRef, useState } from 'react';
|
|
2
2
|
import { css } from '@patternfly/react-styles';
|
|
3
3
|
import styles from '@patternfly/react-styles/css/components/CodeEditor/code-editor';
|
|
4
4
|
import fileUploadStyles from '@patternfly/react-styles/css/components/FileUpload/file-upload';
|
|
@@ -112,7 +112,7 @@ export enum Language {
|
|
|
112
112
|
|
|
113
113
|
/** The main code editor component. */
|
|
114
114
|
|
|
115
|
-
export interface CodeEditorProps extends Omit<
|
|
115
|
+
export interface CodeEditorProps extends Omit<HTMLProps<HTMLDivElement>, 'onChange'> {
|
|
116
116
|
/** Additional classes added to the code editor. */
|
|
117
117
|
className?: string;
|
|
118
118
|
/** Code displayed in code editor. */
|
|
@@ -126,7 +126,7 @@ export interface CodeEditorProps extends Omit<React.HTMLProps<HTMLDivElement>, '
|
|
|
126
126
|
/** A single node or array of nodes - ideally the code editor controls component - to display
|
|
127
127
|
* above code editor.
|
|
128
128
|
*/
|
|
129
|
-
customControls?:
|
|
129
|
+
customControls?: ReactNode | ReactNode[];
|
|
130
130
|
/** Accessible label for the download button. */
|
|
131
131
|
downloadButtonAriaLabel?: string;
|
|
132
132
|
/** Text to display in the tooltip on the download button. */
|
|
@@ -136,15 +136,15 @@ export interface CodeEditorProps extends Omit<React.HTMLProps<HTMLDivElement>, '
|
|
|
136
136
|
/** Additional props to pass to the monaco editor. */
|
|
137
137
|
editorProps?: EditorProps;
|
|
138
138
|
/** Content to display in space of the code editor when there is no code to display. */
|
|
139
|
-
emptyState?:
|
|
139
|
+
emptyState?: ReactNode;
|
|
140
140
|
/** Override default empty state body text. */
|
|
141
|
-
emptyStateBody?:
|
|
141
|
+
emptyStateBody?: ReactNode;
|
|
142
142
|
/** Override default empty state button text. */
|
|
143
|
-
emptyStateButton?:
|
|
143
|
+
emptyStateButton?: ReactNode;
|
|
144
144
|
/** Override default empty state link text. */
|
|
145
|
-
emptyStateLink?:
|
|
145
|
+
emptyStateLink?: ReactNode;
|
|
146
146
|
/** Override default empty state title text. */
|
|
147
|
-
emptyStateTitle?:
|
|
147
|
+
emptyStateTitle?: ReactNode;
|
|
148
148
|
/** Editor header main content title. */
|
|
149
149
|
headerMainContent?: string;
|
|
150
150
|
/** Height of code editor. 'sizeToFit' will automatically change the height
|
|
@@ -175,13 +175,16 @@ export interface CodeEditorProps extends Omit<React.HTMLProps<HTMLDivElement>, '
|
|
|
175
175
|
/** Language displayed in the editor. */
|
|
176
176
|
language?: Language;
|
|
177
177
|
/** The loading screen before the editor will be loaded. Defaults to 'loading...'. */
|
|
178
|
-
loading?:
|
|
178
|
+
loading?: ReactNode;
|
|
179
179
|
/** Function which fires each time the content of the code editor is manually changed. Does
|
|
180
180
|
* not fire when a file is uploaded.
|
|
181
181
|
*/
|
|
182
182
|
onChange?: ChangeHandler;
|
|
183
183
|
/** Function which fires each time the code changes in the code editor. */
|
|
184
184
|
onCodeChange?: (value: string) => void;
|
|
185
|
+
/** Function which fires when the code is downloaded. Defaults to a function that
|
|
186
|
+
* downloads the current editor content. */
|
|
187
|
+
onDownload?: (value: string, fileName: string) => void;
|
|
185
188
|
/** Callback which fires after the code editor is mounted containing a reference to the
|
|
186
189
|
* monaco editor and the monaco instance.
|
|
187
190
|
*/
|
|
@@ -191,17 +194,17 @@ export interface CodeEditorProps extends Omit<React.HTMLProps<HTMLDivElement>, '
|
|
|
191
194
|
/** Refer to Monaco interface {monaco.editor.IEditorOverrideServices}. */
|
|
192
195
|
overrideServices?: editor.IEditorOverrideServices;
|
|
193
196
|
/** Text to show in the button to open the shortcut popover. */
|
|
194
|
-
shortcutsPopoverButtonText
|
|
197
|
+
shortcutsPopoverButtonText?: string;
|
|
195
198
|
/** Properties for the shortcut popover. */
|
|
196
199
|
shortcutsPopoverProps?: PopoverProps;
|
|
197
200
|
/** Flag to show the editor. */
|
|
198
201
|
showEditor?: boolean;
|
|
199
202
|
/** The delay before tooltip fades after code copied. */
|
|
200
|
-
toolTipCopyExitDelay
|
|
203
|
+
toolTipCopyExitDelay?: number;
|
|
201
204
|
/** The entry and exit delay for all tooltips. */
|
|
202
|
-
toolTipDelay
|
|
205
|
+
toolTipDelay?: number;
|
|
203
206
|
/** The max width of the tooltips on all button. */
|
|
204
|
-
toolTipMaxWidth
|
|
207
|
+
toolTipMaxWidth?: string;
|
|
205
208
|
/** The position of tooltips on all buttons. */
|
|
206
209
|
toolTipPosition?:
|
|
207
210
|
| TooltipPosition
|
|
@@ -226,490 +229,402 @@ export interface CodeEditorProps extends Omit<React.HTMLProps<HTMLDivElement>, '
|
|
|
226
229
|
width?: string;
|
|
227
230
|
}
|
|
228
231
|
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
language: Language.plaintext,
|
|
252
|
-
isDarkTheme: false,
|
|
253
|
-
width: '',
|
|
254
|
-
isLineNumbersVisible: true,
|
|
255
|
-
isReadOnly: false,
|
|
256
|
-
isLanguageLabelVisible: false,
|
|
257
|
-
loading: '',
|
|
258
|
-
emptyState: '',
|
|
259
|
-
emptyStateTitle: 'Start editing',
|
|
260
|
-
emptyStateBody: 'Drag and drop a file or upload one.',
|
|
261
|
-
emptyStateButton: 'Browse',
|
|
262
|
-
emptyStateLink: 'Start from scratch',
|
|
263
|
-
downloadFileName: Date.now().toString(),
|
|
264
|
-
isUploadEnabled: false,
|
|
265
|
-
isDownloadEnabled: false,
|
|
266
|
-
isCopyEnabled: false,
|
|
267
|
-
isHeaderPlain: false,
|
|
268
|
-
copyButtonAriaLabel: 'Copy code to clipboard',
|
|
269
|
-
uploadButtonAriaLabel: 'Upload code',
|
|
270
|
-
downloadButtonAriaLabel: 'Download code',
|
|
271
|
-
copyButtonToolTipText: 'Copy to clipboard',
|
|
272
|
-
uploadButtonToolTipText: 'Upload',
|
|
273
|
-
downloadButtonToolTipText: 'Download',
|
|
274
|
-
copyButtonSuccessTooltipText: 'Content added to clipboard',
|
|
275
|
-
toolTipCopyExitDelay: 1600,
|
|
276
|
-
toolTipDelay: 300,
|
|
277
|
-
toolTipMaxWidth: '100px',
|
|
278
|
-
toolTipPosition: 'top',
|
|
279
|
-
customControls: null,
|
|
280
|
-
isMinimapVisible: false,
|
|
281
|
-
headerMainContent: '',
|
|
282
|
-
shortcutsPopoverButtonText: 'View Shortcuts',
|
|
283
|
-
shortcutsPopoverProps: {
|
|
284
|
-
bodyContent: '',
|
|
285
|
-
'aria-label': 'Keyboard Shortcuts'
|
|
286
|
-
},
|
|
287
|
-
showEditor: true,
|
|
288
|
-
options: {},
|
|
289
|
-
overrideServices: {},
|
|
290
|
-
onCodeChange: () => {}
|
|
291
|
-
};
|
|
292
|
-
|
|
293
|
-
static getExtensionFromLanguage(language: Language) {
|
|
294
|
-
switch (language) {
|
|
295
|
-
case Language.shell:
|
|
296
|
-
return 'sh';
|
|
297
|
-
case Language.ruby:
|
|
298
|
-
return 'rb';
|
|
299
|
-
case Language.perl:
|
|
300
|
-
return 'pl';
|
|
301
|
-
case Language.python:
|
|
302
|
-
return 'py';
|
|
303
|
-
case Language.mysql:
|
|
304
|
-
return 'sql';
|
|
305
|
-
case Language.javascript:
|
|
306
|
-
return 'js';
|
|
307
|
-
case Language.typescript:
|
|
308
|
-
return 'ts';
|
|
309
|
-
case Language.markdown:
|
|
310
|
-
return 'md';
|
|
311
|
-
case Language.plaintext:
|
|
312
|
-
return 'txt';
|
|
313
|
-
default:
|
|
314
|
-
return language.toString();
|
|
315
|
-
}
|
|
316
|
-
}
|
|
317
|
-
|
|
318
|
-
constructor(props: CodeEditorProps) {
|
|
319
|
-
super(props);
|
|
320
|
-
this.state = {
|
|
321
|
-
height: this.props.height,
|
|
322
|
-
prevPropsCode: this.props.code,
|
|
323
|
-
value: this.props.code,
|
|
324
|
-
filename: '',
|
|
325
|
-
isLoading: false,
|
|
326
|
-
showEmptyState: true,
|
|
327
|
-
copied: false
|
|
328
|
-
};
|
|
329
|
-
}
|
|
330
|
-
|
|
331
|
-
setHeightToFitContent() {
|
|
332
|
-
const contentHeight = this.editor.getContentHeight();
|
|
333
|
-
const layoutInfo = this.editor.getLayoutInfo();
|
|
334
|
-
this.editor.layout({ width: layoutInfo.width, height: contentHeight });
|
|
232
|
+
const getExtensionFromLanguage = (language: Language) => {
|
|
233
|
+
switch (language) {
|
|
234
|
+
case Language.shell:
|
|
235
|
+
return 'sh';
|
|
236
|
+
case Language.ruby:
|
|
237
|
+
return 'rb';
|
|
238
|
+
case Language.perl:
|
|
239
|
+
return 'pl';
|
|
240
|
+
case Language.python:
|
|
241
|
+
return 'py';
|
|
242
|
+
case Language.mysql:
|
|
243
|
+
return 'sql';
|
|
244
|
+
case Language.javascript:
|
|
245
|
+
return 'js';
|
|
246
|
+
case Language.typescript:
|
|
247
|
+
return 'ts';
|
|
248
|
+
case Language.markdown:
|
|
249
|
+
return 'md';
|
|
250
|
+
case Language.plaintext:
|
|
251
|
+
return 'txt';
|
|
252
|
+
default:
|
|
253
|
+
return language.toString();
|
|
335
254
|
}
|
|
255
|
+
};
|
|
256
|
+
|
|
257
|
+
/**
|
|
258
|
+
* Downloads a file to a users device given its string content and a full file name.
|
|
259
|
+
*/
|
|
260
|
+
const defaultOnDownload = (value: string, fileName: string) => {
|
|
261
|
+
const link = document.createElement('a');
|
|
262
|
+
link.href = URL.createObjectURL(new Blob([value], { type: 'text' }));
|
|
263
|
+
link.download = fileName;
|
|
264
|
+
link.click();
|
|
265
|
+
};
|
|
266
|
+
|
|
267
|
+
export const CodeEditor = ({
|
|
268
|
+
className = '',
|
|
269
|
+
code = '',
|
|
270
|
+
copyButtonAriaLabel = 'Copy code to clipboard',
|
|
271
|
+
copyButtonSuccessTooltipText = 'Content added to clipboard',
|
|
272
|
+
copyButtonToolTipText = 'Copy to clipboard',
|
|
273
|
+
customControls = null,
|
|
274
|
+
downloadButtonAriaLabel = 'Download code',
|
|
275
|
+
downloadButtonToolTipText = 'Download',
|
|
276
|
+
downloadFileName = Date.now().toString(),
|
|
277
|
+
editorProps,
|
|
278
|
+
emptyState = '',
|
|
279
|
+
emptyStateBody = 'Drag and drop a file or upload one.',
|
|
280
|
+
emptyStateButton = 'Browse',
|
|
281
|
+
emptyStateLink = 'Start from scratch',
|
|
282
|
+
emptyStateTitle = 'Start editing',
|
|
283
|
+
headerMainContent = '',
|
|
284
|
+
height,
|
|
285
|
+
isCopyEnabled = false,
|
|
286
|
+
isDarkTheme = false,
|
|
287
|
+
isDownloadEnabled = false,
|
|
288
|
+
isFullHeight = false,
|
|
289
|
+
isHeaderPlain = false,
|
|
290
|
+
isLanguageLabelVisible = false,
|
|
291
|
+
isLineNumbersVisible = true,
|
|
292
|
+
isMinimapVisible = false,
|
|
293
|
+
isReadOnly = false,
|
|
294
|
+
isUploadEnabled = false,
|
|
295
|
+
language = Language.plaintext,
|
|
296
|
+
loading = '',
|
|
297
|
+
onChange = () => {},
|
|
298
|
+
onCodeChange = () => {},
|
|
299
|
+
onDownload = defaultOnDownload,
|
|
300
|
+
onEditorDidMount = () => {},
|
|
301
|
+
options = {},
|
|
302
|
+
overrideServices = {},
|
|
303
|
+
shortcutsPopoverButtonText = 'View Shortcuts',
|
|
304
|
+
shortcutsPopoverProps = { bodyContent: '', 'aria-label': 'Keyboard Shortcuts' },
|
|
305
|
+
showEditor = true,
|
|
306
|
+
toolTipCopyExitDelay = 1600,
|
|
307
|
+
toolTipDelay = 300,
|
|
308
|
+
toolTipMaxWidth = '100px',
|
|
309
|
+
toolTipPosition = 'top',
|
|
310
|
+
uploadButtonAriaLabel = 'Upload code',
|
|
311
|
+
uploadButtonToolTipText = 'Upload',
|
|
312
|
+
width = ''
|
|
313
|
+
}: CodeEditorProps) => {
|
|
314
|
+
const [value, setValue] = useState(code);
|
|
315
|
+
const [isLoading, setIsLoading] = useState(false);
|
|
316
|
+
const [showEmptyState, setShowEmptyState] = useState(true);
|
|
317
|
+
const [copied, setCopied] = useState(false);
|
|
318
|
+
|
|
319
|
+
const editorRef = useRef<editor.IStandaloneCodeEditor | null>(null);
|
|
320
|
+
const wrapperRef = useRef<HTMLDivElement>(null);
|
|
321
|
+
const ref = useRef<HTMLDivElement>(null);
|
|
322
|
+
const observer = useRef(() => {});
|
|
323
|
+
|
|
324
|
+
const setHeightToFitContent = () => {
|
|
325
|
+
const contentHeight = editorRef.current?.getContentHeight();
|
|
326
|
+
const layoutInfo = editorRef.current?.getLayoutInfo();
|
|
327
|
+
editorRef.current?.layout({ width: layoutInfo.width, height: contentHeight });
|
|
328
|
+
};
|
|
336
329
|
|
|
337
|
-
|
|
338
|
-
if (
|
|
339
|
-
|
|
330
|
+
const onModelChange: ChangeHandler = (value, event) => {
|
|
331
|
+
if (height === 'sizeToFit') {
|
|
332
|
+
setHeightToFitContent();
|
|
340
333
|
}
|
|
341
|
-
if (
|
|
342
|
-
|
|
334
|
+
if (onChange) {
|
|
335
|
+
onChange(value, event);
|
|
343
336
|
}
|
|
344
|
-
|
|
345
|
-
|
|
337
|
+
setValue(value);
|
|
338
|
+
onCodeChange(value);
|
|
346
339
|
};
|
|
347
340
|
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
// set the value to props.code
|
|
353
|
-
if (nextProps.code !== prevState.prevPropsCode) {
|
|
354
|
-
return {
|
|
355
|
-
value: nextProps.code,
|
|
356
|
-
prevPropsCode: nextProps.code
|
|
357
|
-
};
|
|
358
|
-
}
|
|
359
|
-
// else, don't need to change the state.value
|
|
360
|
-
// because the onChange function will do all the work
|
|
361
|
-
return null;
|
|
362
|
-
}
|
|
363
|
-
|
|
364
|
-
handleResize = () => {
|
|
365
|
-
if (this.editor) {
|
|
366
|
-
this.editor.layout({ width: 0, height: 0 }); // ensures the editor won't take up more space than it needs
|
|
367
|
-
this.editor.layout();
|
|
341
|
+
const handleResize = () => {
|
|
342
|
+
if (editorRef.current) {
|
|
343
|
+
editorRef.current.layout({ width: 0, height: 0 }); // ensures the editor won't take up more space than it needs
|
|
344
|
+
editorRef.current.layout();
|
|
368
345
|
}
|
|
369
346
|
};
|
|
370
347
|
|
|
371
|
-
|
|
372
|
-
document.
|
|
373
|
-
|
|
374
|
-
this.handleResize();
|
|
375
|
-
}
|
|
376
|
-
|
|
377
|
-
componentWillUnmount() {
|
|
378
|
-
document.removeEventListener('keydown', this.handleGlobalKeys);
|
|
379
|
-
this.observer();
|
|
380
|
-
}
|
|
381
|
-
|
|
382
|
-
handleGlobalKeys = (event: KeyboardEvent) => {
|
|
383
|
-
if (this.wrapperRef.current === document.activeElement && (event.key === 'ArrowDown' || event.key === ' ')) {
|
|
384
|
-
this.editor?.focus();
|
|
348
|
+
const handleGlobalKeys = (event: KeyboardEvent) => {
|
|
349
|
+
if (wrapperRef.current === document.activeElement && (event.key === 'ArrowDown' || event.key === ' ')) {
|
|
350
|
+
editorRef.current?.focus();
|
|
385
351
|
event.preventDefault();
|
|
386
352
|
}
|
|
387
353
|
};
|
|
388
354
|
|
|
389
|
-
|
|
355
|
+
// if the code changes due to the prop changing
|
|
356
|
+
// set the value to the code prop
|
|
357
|
+
useEffect(() => setValue(code), [code]);
|
|
358
|
+
|
|
359
|
+
useEffect(() => {
|
|
360
|
+
document.addEventListener('keydown', handleGlobalKeys);
|
|
361
|
+
observer.current = getResizeObserver(ref.current, handleResize, true);
|
|
362
|
+
handleResize();
|
|
363
|
+
return () => {
|
|
364
|
+
document.removeEventListener('keydown', handleGlobalKeys);
|
|
365
|
+
observer.current();
|
|
366
|
+
};
|
|
367
|
+
}, []);
|
|
368
|
+
|
|
369
|
+
const editorDidMount: EditorDidMount = (editor, monaco) => {
|
|
390
370
|
// eslint-disable-next-line no-bitwise
|
|
391
|
-
editor.addCommand(monaco.KeyMod.Shift | monaco.KeyCode.Tab, () =>
|
|
371
|
+
editor.addCommand(monaco.KeyMod.Shift | monaco.KeyCode.Tab, () => wrapperRef.current.focus());
|
|
392
372
|
Array.from(document.getElementsByClassName('monaco-editor')).forEach((editorElement) =>
|
|
393
373
|
editorElement.removeAttribute('role')
|
|
394
374
|
);
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
if (
|
|
398
|
-
|
|
375
|
+
onEditorDidMount(editor, monaco);
|
|
376
|
+
editorRef.current = editor;
|
|
377
|
+
if (height === 'sizeToFit') {
|
|
378
|
+
setHeightToFitContent();
|
|
399
379
|
}
|
|
400
380
|
};
|
|
401
381
|
|
|
402
|
-
handleFileChange = (value: string
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
filename
|
|
406
|
-
});
|
|
407
|
-
this.props.onCodeChange(value);
|
|
382
|
+
const handleFileChange = (value: string) => {
|
|
383
|
+
setValue(value);
|
|
384
|
+
onCodeChange(value);
|
|
408
385
|
};
|
|
409
386
|
|
|
410
|
-
handleFileReadStarted = () =>
|
|
411
|
-
handleFileReadFinished = () =>
|
|
387
|
+
const handleFileReadStarted = () => setIsLoading(true);
|
|
388
|
+
const handleFileReadFinished = () => setIsLoading(false);
|
|
412
389
|
|
|
413
|
-
readFile(fileHandle: Blob)
|
|
414
|
-
|
|
390
|
+
const readFile = (fileHandle: Blob) =>
|
|
391
|
+
new Promise<string>((resolve, reject) => {
|
|
415
392
|
const reader = new FileReader();
|
|
416
393
|
reader.onload = () => resolve(reader.result as string);
|
|
417
394
|
reader.onerror = () => reject(reader.error);
|
|
418
395
|
reader.readAsText(fileHandle);
|
|
419
396
|
});
|
|
420
|
-
}
|
|
421
397
|
|
|
422
|
-
onDropAccepted = (acceptedFiles: File[]) => {
|
|
398
|
+
const onDropAccepted = (acceptedFiles: File[]) => {
|
|
423
399
|
if (acceptedFiles.length > 0) {
|
|
424
400
|
const fileHandle = acceptedFiles[0];
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
401
|
+
handleFileChange(''); // Show the filename while reading
|
|
402
|
+
handleFileReadStarted();
|
|
403
|
+
readFile(fileHandle)
|
|
428
404
|
.then((data) => {
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
405
|
+
handleFileReadFinished();
|
|
406
|
+
setShowEmptyState(false);
|
|
407
|
+
handleFileChange(data);
|
|
432
408
|
})
|
|
433
409
|
.catch((error: DOMException) => {
|
|
434
410
|
// eslint-disable-next-line no-console
|
|
435
411
|
console.error('error', error);
|
|
436
|
-
|
|
437
|
-
|
|
412
|
+
handleFileReadFinished();
|
|
413
|
+
handleFileChange('');
|
|
438
414
|
});
|
|
439
415
|
}
|
|
440
416
|
};
|
|
441
417
|
|
|
442
|
-
onDropRejected = (rejectedFiles: FileRejection[]) => {
|
|
418
|
+
const onDropRejected = (rejectedFiles: FileRejection[]) => {
|
|
443
419
|
if (rejectedFiles.length > 0) {
|
|
444
420
|
// eslint-disable-next-line no-console
|
|
445
421
|
console.error('There was an error accepting that dropped file'); // TODO
|
|
446
422
|
}
|
|
447
423
|
};
|
|
448
424
|
|
|
449
|
-
copyCode = () => {
|
|
450
|
-
navigator.clipboard.writeText(
|
|
451
|
-
|
|
425
|
+
const copyCode = () => {
|
|
426
|
+
navigator.clipboard.writeText(value);
|
|
427
|
+
setCopied(true);
|
|
452
428
|
};
|
|
453
429
|
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
430
|
+
const editorOptions: editor.IStandaloneEditorConstructionOptions = {
|
|
431
|
+
scrollBeyondLastLine: height !== 'sizeToFit',
|
|
432
|
+
readOnly: isReadOnly,
|
|
433
|
+
cursorStyle: 'line',
|
|
434
|
+
lineNumbers: isLineNumbersVisible ? 'on' : 'off',
|
|
435
|
+
tabIndex: -1,
|
|
436
|
+
minimap: {
|
|
437
|
+
enabled: isMinimapVisible
|
|
438
|
+
},
|
|
439
|
+
...options
|
|
462
440
|
};
|
|
463
441
|
|
|
464
|
-
|
|
465
|
-
|
|
442
|
+
const tooltipProps = {
|
|
443
|
+
position: toolTipPosition,
|
|
444
|
+
exitDelay: toolTipDelay,
|
|
445
|
+
entryDelay: toolTipDelay,
|
|
446
|
+
maxWidth: toolTipMaxWidth,
|
|
447
|
+
trigger: 'mouseenter focus'
|
|
466
448
|
};
|
|
467
449
|
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
isDownloadEnabled,
|
|
491
|
-
language,
|
|
492
|
-
emptyState: providedEmptyState,
|
|
493
|
-
emptyStateTitle,
|
|
494
|
-
emptyStateBody,
|
|
495
|
-
emptyStateButton,
|
|
496
|
-
emptyStateLink,
|
|
497
|
-
customControls,
|
|
498
|
-
isMinimapVisible,
|
|
499
|
-
isHeaderPlain,
|
|
500
|
-
headerMainContent,
|
|
501
|
-
shortcutsPopoverButtonText,
|
|
502
|
-
shortcutsPopoverProps: shortcutsPopoverPropsProp,
|
|
503
|
-
showEditor,
|
|
504
|
-
options: optionsProp,
|
|
505
|
-
overrideServices,
|
|
506
|
-
loading,
|
|
507
|
-
editorProps
|
|
508
|
-
} = this.props;
|
|
509
|
-
const shortcutsPopoverProps: PopoverProps = {
|
|
510
|
-
...CodeEditor.defaultProps.shortcutsPopoverProps,
|
|
511
|
-
...shortcutsPopoverPropsProp
|
|
512
|
-
};
|
|
513
|
-
const options: editor.IStandaloneEditorConstructionOptions = {
|
|
514
|
-
scrollBeyondLastLine: height !== 'sizeToFit',
|
|
515
|
-
readOnly: isReadOnly,
|
|
516
|
-
cursorStyle: 'line',
|
|
517
|
-
lineNumbers: isLineNumbersVisible ? 'on' : 'off',
|
|
518
|
-
tabIndex: -1,
|
|
519
|
-
minimap: {
|
|
520
|
-
enabled: isMinimapVisible
|
|
521
|
-
},
|
|
522
|
-
...optionsProp
|
|
523
|
-
};
|
|
524
|
-
const isFullHeight = this.props.height === '100%' ? true : this.props.isFullHeight;
|
|
525
|
-
|
|
526
|
-
return (
|
|
527
|
-
<Dropzone multiple={false} onDropAccepted={this.onDropAccepted} onDropRejected={this.onDropRejected}>
|
|
528
|
-
{({ getRootProps, getInputProps, isDragActive, open }) => {
|
|
529
|
-
const emptyState =
|
|
530
|
-
providedEmptyState ||
|
|
531
|
-
(isUploadEnabled ? (
|
|
532
|
-
<EmptyState variant={EmptyStateVariant.sm} titleText={emptyStateTitle} icon={CodeIcon} headingLevel="h4">
|
|
533
|
-
<EmptyStateBody>{emptyStateBody}</EmptyStateBody>
|
|
534
|
-
{!isReadOnly && (
|
|
535
|
-
<EmptyStateFooter>
|
|
536
|
-
<EmptyStateActions>
|
|
537
|
-
<Button variant="primary" onClick={open}>
|
|
538
|
-
{emptyStateButton}
|
|
539
|
-
</Button>
|
|
540
|
-
</EmptyStateActions>
|
|
541
|
-
<EmptyStateActions>
|
|
542
|
-
<Button variant="link" onClick={this.toggleEmptyState}>
|
|
543
|
-
{emptyStateLink}
|
|
544
|
-
</Button>
|
|
545
|
-
</EmptyStateActions>
|
|
546
|
-
</EmptyStateFooter>
|
|
547
|
-
)}
|
|
548
|
-
</EmptyState>
|
|
549
|
-
) : (
|
|
550
|
-
<EmptyState variant={EmptyStateVariant.sm} titleText={emptyStateTitle} icon={CodeIcon} headingLevel="h4">
|
|
551
|
-
{!isReadOnly && (
|
|
552
|
-
<EmptyStateFooter>
|
|
553
|
-
<EmptyStateActions>
|
|
554
|
-
<Button variant="primary" onClick={this.toggleEmptyState}>
|
|
555
|
-
{emptyStateLink}
|
|
556
|
-
</Button>
|
|
557
|
-
</EmptyStateActions>
|
|
558
|
-
</EmptyStateFooter>
|
|
559
|
-
)}
|
|
560
|
-
</EmptyState>
|
|
561
|
-
));
|
|
562
|
-
|
|
563
|
-
const tooltipProps = {
|
|
564
|
-
position: toolTipPosition,
|
|
565
|
-
exitDelay: toolTipDelay,
|
|
566
|
-
entryDelay: toolTipDelay,
|
|
567
|
-
maxWidth: toolTipMaxWidth,
|
|
568
|
-
trigger: 'mouseenter focus'
|
|
569
|
-
};
|
|
570
|
-
|
|
571
|
-
const hasEditorHeaderContent =
|
|
572
|
-
((isCopyEnabled || isDownloadEnabled) && (!showEmptyState || !!value)) ||
|
|
573
|
-
isUploadEnabled ||
|
|
574
|
-
customControls ||
|
|
575
|
-
headerMainContent ||
|
|
576
|
-
!!shortcutsPopoverProps.bodyContent;
|
|
577
|
-
|
|
578
|
-
const editorHeaderContent = (
|
|
579
|
-
<Fragment>
|
|
580
|
-
<div className={css(styles.codeEditorControls)}>
|
|
581
|
-
<CodeEditorContext.Provider value={{ code: value }}>
|
|
582
|
-
{isCopyEnabled && (!showEmptyState || !!value) && (
|
|
583
|
-
<CodeEditorControl
|
|
584
|
-
icon={<CopyIcon />}
|
|
585
|
-
aria-label={copyButtonAriaLabel}
|
|
586
|
-
tooltipProps={{
|
|
587
|
-
...tooltipProps,
|
|
588
|
-
'aria-live': 'polite',
|
|
589
|
-
content: <div>{copied ? copyButtonSuccessTooltipText : copyButtonToolTipText}</div>,
|
|
590
|
-
exitDelay: copied ? toolTipCopyExitDelay : toolTipDelay,
|
|
591
|
-
onTooltipHidden: () => this.setState({ copied: false })
|
|
592
|
-
}}
|
|
593
|
-
onClick={this.copyCode}
|
|
594
|
-
/>
|
|
595
|
-
)}
|
|
596
|
-
{isUploadEnabled && (
|
|
597
|
-
<CodeEditorControl
|
|
598
|
-
icon={<UploadIcon />}
|
|
599
|
-
aria-label={uploadButtonAriaLabel}
|
|
600
|
-
tooltipProps={{ content: <div>{uploadButtonToolTipText}</div>, ...tooltipProps }}
|
|
601
|
-
onClick={open}
|
|
602
|
-
/>
|
|
603
|
-
)}
|
|
604
|
-
{isDownloadEnabled && (!showEmptyState || !!value) && (
|
|
605
|
-
<CodeEditorControl
|
|
606
|
-
icon={<DownloadIcon />}
|
|
607
|
-
aria-label={downloadButtonAriaLabel}
|
|
608
|
-
tooltipProps={{ content: <div>{downloadButtonToolTipText}</div>, ...tooltipProps }}
|
|
609
|
-
onClick={this.download}
|
|
610
|
-
/>
|
|
611
|
-
)}
|
|
612
|
-
{customControls && customControls}
|
|
613
|
-
</CodeEditorContext.Provider>
|
|
614
|
-
</div>
|
|
615
|
-
{headerMainContent && <div className={css(styles.codeEditorHeaderMain)}>{headerMainContent}</div>}
|
|
616
|
-
{!!shortcutsPopoverProps.bodyContent && (
|
|
617
|
-
<div className={`${styles.codeEditor}__keyboard-shortcuts`}>
|
|
618
|
-
<Popover {...shortcutsPopoverProps}>
|
|
619
|
-
<Button variant={ButtonVariant.link} icon={<HelpIcon />}>
|
|
620
|
-
{shortcutsPopoverButtonText}
|
|
450
|
+
const hasEditorHeaderContent =
|
|
451
|
+
((isCopyEnabled || isDownloadEnabled) && (!showEmptyState || !!value)) ||
|
|
452
|
+
isUploadEnabled ||
|
|
453
|
+
customControls ||
|
|
454
|
+
headerMainContent ||
|
|
455
|
+
!!shortcutsPopoverProps.bodyContent;
|
|
456
|
+
|
|
457
|
+
return (
|
|
458
|
+
<Dropzone multiple={false} onDropAccepted={onDropAccepted} onDropRejected={onDropRejected}>
|
|
459
|
+
{({ getRootProps, getInputProps, isDragActive, open }) => {
|
|
460
|
+
const hiddenFileInput = <input {...getInputProps()} /* hidden, necessary for react-dropzone */ hidden />;
|
|
461
|
+
|
|
462
|
+
const editorEmptyState =
|
|
463
|
+
emptyState ||
|
|
464
|
+
(isUploadEnabled ? (
|
|
465
|
+
<EmptyState variant={EmptyStateVariant.sm} titleText={emptyStateTitle} icon={CodeIcon} headingLevel="h4">
|
|
466
|
+
<EmptyStateBody>{emptyStateBody}</EmptyStateBody>
|
|
467
|
+
{!isReadOnly && (
|
|
468
|
+
<EmptyStateFooter>
|
|
469
|
+
<EmptyStateActions>
|
|
470
|
+
<Button variant="primary" onClick={open}>
|
|
471
|
+
{emptyStateButton}
|
|
621
472
|
</Button>
|
|
622
|
-
</
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
<div className={css(styles.codeEditorHeader, isHeaderPlain && styles.modifiers.plain)}>
|
|
630
|
-
{hasEditorHeaderContent && (
|
|
631
|
-
<div className={css(styles.codeEditorHeaderContent)}>{editorHeaderContent}</div>
|
|
473
|
+
</EmptyStateActions>
|
|
474
|
+
<EmptyStateActions>
|
|
475
|
+
<Button variant="link" onClick={() => setShowEmptyState(false)}>
|
|
476
|
+
{emptyStateLink}
|
|
477
|
+
</Button>
|
|
478
|
+
</EmptyStateActions>
|
|
479
|
+
</EmptyStateFooter>
|
|
632
480
|
)}
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
<
|
|
639
|
-
|
|
481
|
+
</EmptyState>
|
|
482
|
+
) : (
|
|
483
|
+
<EmptyState variant={EmptyStateVariant.sm} titleText={emptyStateTitle} icon={CodeIcon} headingLevel="h4">
|
|
484
|
+
{!isReadOnly && (
|
|
485
|
+
<EmptyStateFooter>
|
|
486
|
+
<EmptyStateActions>
|
|
487
|
+
<Button variant="primary" onClick={() => setShowEmptyState(false)}>
|
|
488
|
+
{emptyStateLink}
|
|
489
|
+
</Button>
|
|
490
|
+
</EmptyStateActions>
|
|
491
|
+
</EmptyStateFooter>
|
|
640
492
|
)}
|
|
493
|
+
</EmptyState>
|
|
494
|
+
));
|
|
495
|
+
|
|
496
|
+
const editorHeaderContent = (
|
|
497
|
+
<>
|
|
498
|
+
<div className={css(styles.codeEditorControls)}>
|
|
499
|
+
<CodeEditorContext.Provider value={{ code: value }}>
|
|
500
|
+
{isCopyEnabled && (!showEmptyState || !!value) && (
|
|
501
|
+
<CodeEditorControl
|
|
502
|
+
icon={<CopyIcon />}
|
|
503
|
+
aria-label={copyButtonAriaLabel}
|
|
504
|
+
tooltipProps={{
|
|
505
|
+
...tooltipProps,
|
|
506
|
+
'aria-live': 'polite',
|
|
507
|
+
content: <div>{copied ? copyButtonSuccessTooltipText : copyButtonToolTipText}</div>,
|
|
508
|
+
exitDelay: copied ? toolTipCopyExitDelay : toolTipDelay,
|
|
509
|
+
onTooltipHidden: () => setCopied(false)
|
|
510
|
+
}}
|
|
511
|
+
onClick={copyCode}
|
|
512
|
+
/>
|
|
513
|
+
)}
|
|
514
|
+
{isUploadEnabled && (
|
|
515
|
+
<CodeEditorControl
|
|
516
|
+
icon={<UploadIcon />}
|
|
517
|
+
aria-label={uploadButtonAriaLabel}
|
|
518
|
+
tooltipProps={{ content: <div>{uploadButtonToolTipText}</div>, ...tooltipProps }}
|
|
519
|
+
onClick={open}
|
|
520
|
+
/>
|
|
521
|
+
)}
|
|
522
|
+
{isDownloadEnabled && (!showEmptyState || !!value) && (
|
|
523
|
+
<CodeEditorControl
|
|
524
|
+
icon={<DownloadIcon />}
|
|
525
|
+
aria-label={downloadButtonAriaLabel}
|
|
526
|
+
tooltipProps={{ content: <div>{downloadButtonToolTipText}</div>, ...tooltipProps }}
|
|
527
|
+
onClick={() => {
|
|
528
|
+
onDownload(value, `${downloadFileName}.${getExtensionFromLanguage(language)}`);
|
|
529
|
+
}}
|
|
530
|
+
/>
|
|
531
|
+
)}
|
|
532
|
+
{customControls && customControls}
|
|
533
|
+
</CodeEditorContext.Provider>
|
|
641
534
|
</div>
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
{
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
535
|
+
{headerMainContent && <div className={css(styles.codeEditorHeaderMain)}>{headerMainContent}</div>}
|
|
536
|
+
{!!shortcutsPopoverProps.bodyContent && (
|
|
537
|
+
<div className={`${styles.codeEditor}__keyboard-shortcuts`}>
|
|
538
|
+
<Popover {...shortcutsPopoverProps}>
|
|
539
|
+
<Button variant={ButtonVariant.link} icon={<HelpIcon />}>
|
|
540
|
+
{shortcutsPopoverButtonText}
|
|
541
|
+
</Button>
|
|
542
|
+
</Popover>
|
|
543
|
+
</div>
|
|
544
|
+
)}
|
|
545
|
+
</>
|
|
546
|
+
);
|
|
547
|
+
|
|
548
|
+
const editorHeader = (
|
|
549
|
+
<div className={css(styles.codeEditorHeader, isHeaderPlain && styles.modifiers.plain)}>
|
|
550
|
+
{hasEditorHeaderContent && <div className={css(styles.codeEditorHeaderContent)}>{editorHeaderContent}</div>}
|
|
551
|
+
{isLanguageLabelVisible && (
|
|
552
|
+
<div className={css(styles.codeEditorTab)}>
|
|
553
|
+
<span className={css(styles.codeEditorTabIcon)}>
|
|
554
|
+
<CodeIcon />
|
|
555
|
+
</span>
|
|
556
|
+
<span className={css(styles.codeEditorTabText)}>{language.toUpperCase()}</span>
|
|
557
|
+
</div>
|
|
558
|
+
)}
|
|
559
|
+
</div>
|
|
560
|
+
);
|
|
561
|
+
|
|
562
|
+
const editor = (
|
|
563
|
+
<div className={css(styles.codeEditorCode)} ref={wrapperRef} tabIndex={0} dir="ltr">
|
|
564
|
+
<Editor
|
|
565
|
+
height={height === '100%' ? undefined : height}
|
|
566
|
+
width={width}
|
|
567
|
+
language={language}
|
|
568
|
+
value={value}
|
|
569
|
+
options={editorOptions}
|
|
570
|
+
overrideServices={overrideServices}
|
|
571
|
+
onChange={onModelChange}
|
|
572
|
+
onMount={editorDidMount}
|
|
573
|
+
loading={loading}
|
|
574
|
+
theme={isDarkTheme ? 'vs-dark' : 'vs-light'}
|
|
575
|
+
{...editorProps}
|
|
576
|
+
/>
|
|
577
|
+
</div>
|
|
578
|
+
);
|
|
579
|
+
|
|
580
|
+
return (
|
|
581
|
+
<div
|
|
582
|
+
className={css(
|
|
583
|
+
styles.codeEditor,
|
|
584
|
+
isReadOnly && styles.modifiers.readOnly,
|
|
585
|
+
(height === '100%' ? true : isFullHeight) && styles.modifiers.fullHeight,
|
|
586
|
+
className
|
|
587
|
+
)}
|
|
588
|
+
ref={ref}
|
|
589
|
+
>
|
|
590
|
+
{(isUploadEnabled || emptyState) && !value ? (
|
|
591
|
+
<div
|
|
592
|
+
{...getRootProps({
|
|
593
|
+
onClick: (event) => event.stopPropagation() // Prevents clicking TextArea from opening file dialog
|
|
594
|
+
})}
|
|
595
|
+
className={css(styles.codeEditorContainer, isLoading && fileUploadStyles.modifiers.loading)}
|
|
596
|
+
>
|
|
597
|
+
{editorHeader}
|
|
598
|
+
<div className={css(styles.codeEditorMain, isDragActive && styles.modifiers.dragHover)}>
|
|
599
|
+
{(showEmptyState || emptyState) && !value ? (
|
|
600
|
+
<div className={css(styles.codeEditorUpload)}>
|
|
701
601
|
{hiddenFileInput}
|
|
702
|
-
{
|
|
602
|
+
{editorEmptyState}
|
|
703
603
|
</div>
|
|
604
|
+
) : (
|
|
605
|
+
<>
|
|
606
|
+
{hiddenFileInput}
|
|
607
|
+
{editor}
|
|
608
|
+
</>
|
|
704
609
|
)}
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
}
|
|
714
|
-
|
|
715
|
-
|
|
610
|
+
</div>
|
|
611
|
+
</div>
|
|
612
|
+
) : (
|
|
613
|
+
<>
|
|
614
|
+
{editorHeader}
|
|
615
|
+
{showEditor && (
|
|
616
|
+
<div className={css(styles.codeEditorMain)}>
|
|
617
|
+
{hiddenFileInput}
|
|
618
|
+
{editor}
|
|
619
|
+
</div>
|
|
620
|
+
)}
|
|
621
|
+
</>
|
|
622
|
+
)}
|
|
623
|
+
</div>
|
|
624
|
+
);
|
|
625
|
+
}}
|
|
626
|
+
</Dropzone>
|
|
627
|
+
);
|
|
628
|
+
};
|
|
629
|
+
|
|
630
|
+
CodeEditor.displayName = 'CodeEditor';
|