@patternfly/react-code-editor 6.2.1-prerelease.1 → 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.
@@ -1,4 +1,4 @@
1
- import { Component, createRef, Fragment } from 'react';
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<React.HTMLProps<HTMLDivElement>, 'onChange'> {
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?: React.ReactNode | React.ReactNode[];
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?: React.ReactNode;
139
+ emptyState?: ReactNode;
140
140
  /** Override default empty state body text. */
141
- emptyStateBody?: React.ReactNode;
141
+ emptyStateBody?: ReactNode;
142
142
  /** Override default empty state button text. */
143
- emptyStateButton?: React.ReactNode;
143
+ emptyStateButton?: ReactNode;
144
144
  /** Override default empty state link text. */
145
- emptyStateLink?: React.ReactNode;
145
+ emptyStateLink?: ReactNode;
146
146
  /** Override default empty state title text. */
147
- emptyStateTitle?: React.ReactNode;
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?: React.ReactNode;
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: string;
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: number;
203
+ toolTipCopyExitDelay?: number;
201
204
  /** The entry and exit delay for all tooltips. */
202
- toolTipDelay: number;
205
+ toolTipDelay?: number;
203
206
  /** The max width of the tooltips on all button. */
204
- toolTipMaxWidth: string;
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
- interface CodeEditorState {
230
- height: string;
231
- prevPropsCode: string;
232
- value: string;
233
- filename: string;
234
- isLoading: boolean;
235
- showEmptyState: boolean;
236
- copied: boolean;
237
- }
238
-
239
- class CodeEditor extends Component<CodeEditorProps, CodeEditorState> {
240
- static displayName = 'CodeEditor';
241
- private editor: editor.IStandaloneCodeEditor | null = null;
242
- private wrapperRef = createRef<HTMLDivElement>();
243
- private ref = createRef<HTMLDivElement>();
244
- private timer: number | null = null;
245
- private observer = () => {};
246
-
247
- static defaultProps: CodeEditorProps = {
248
- className: '',
249
- code: '',
250
- onEditorDidMount: () => {},
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
- onChange: ChangeHandler = (value, event) => {
338
- if (this.props.height === 'sizeToFit') {
339
- this.setHeightToFitContent();
330
+ const onModelChange: ChangeHandler = (value, event) => {
331
+ if (height === 'sizeToFit') {
332
+ setHeightToFitContent();
340
333
  }
341
- if (this.props.onChange) {
342
- this.props.onChange(value, event);
334
+ if (onChange) {
335
+ onChange(value, event);
343
336
  }
344
- this.setState({ value });
345
- this.props.onCodeChange(value);
337
+ setValue(value);
338
+ onCodeChange(value);
346
339
  };
347
340
 
348
- // this function is only called when the props change
349
- // the only conflict is between props.code and state.value
350
- static getDerivedStateFromProps(nextProps: CodeEditorProps, prevState: CodeEditorState) {
351
- // if the code changes due to the props.code changing
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
- componentDidMount() {
372
- document.addEventListener('keydown', this.handleGlobalKeys);
373
- this.observer = getResizeObserver(this.ref.current, this.handleResize, true);
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
- editorDidMount: EditorDidMount = (editor, monaco) => {
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, () => this.wrapperRef.current.focus());
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
- this.props.onEditorDidMount(editor, monaco);
396
- this.editor = editor;
397
- if (this.props.height === 'sizeToFit') {
398
- this.setHeightToFitContent();
375
+ onEditorDidMount(editor, monaco);
376
+ editorRef.current = editor;
377
+ if (height === 'sizeToFit') {
378
+ setHeightToFitContent();
399
379
  }
400
380
  };
401
381
 
402
- handleFileChange = (value: string, filename: string) => {
403
- this.setState({
404
- value,
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 = () => this.setState({ isLoading: true });
411
- handleFileReadFinished = () => this.setState({ isLoading: false });
387
+ const handleFileReadStarted = () => setIsLoading(true);
388
+ const handleFileReadFinished = () => setIsLoading(false);
412
389
 
413
- readFile(fileHandle: Blob) {
414
- return new Promise<string>((resolve, reject) => {
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
- this.handleFileChange('', fileHandle.name); // Show the filename while reading
426
- this.handleFileReadStarted();
427
- this.readFile(fileHandle)
401
+ handleFileChange(''); // Show the filename while reading
402
+ handleFileReadStarted();
403
+ readFile(fileHandle)
428
404
  .then((data) => {
429
- this.handleFileReadFinished();
430
- this.toggleEmptyState();
431
- this.handleFileChange(data, fileHandle.name);
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
- this.handleFileReadFinished();
437
- this.handleFileChange('', ''); // Clear the filename field on a failure
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(this.state.value);
451
- this.setState({ copied: true });
425
+ const copyCode = () => {
426
+ navigator.clipboard.writeText(value);
427
+ setCopied(true);
452
428
  };
453
429
 
454
- download = () => {
455
- const { value } = this.state;
456
- const element = document.createElement('a');
457
- const file = new Blob([value], { type: 'text' });
458
- element.href = URL.createObjectURL(file);
459
- element.download = `${this.props.downloadFileName}.${CodeEditor.getExtensionFromLanguage(this.props.language)}`;
460
- document.body.appendChild(element); // Required for this to work in FireFox
461
- element.click();
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
- toggleEmptyState = () => {
465
- this.setState({ showEmptyState: false });
442
+ const tooltipProps = {
443
+ position: toolTipPosition,
444
+ exitDelay: toolTipDelay,
445
+ entryDelay: toolTipDelay,
446
+ maxWidth: toolTipMaxWidth,
447
+ trigger: 'mouseenter focus'
466
448
  };
467
449
 
468
- render() {
469
- const { height, value, isLoading, showEmptyState, copied } = this.state;
470
- const {
471
- isDarkTheme,
472
- width,
473
- className,
474
- isCopyEnabled,
475
- copyButtonSuccessTooltipText,
476
- isReadOnly,
477
- isUploadEnabled,
478
- isLanguageLabelVisible,
479
- copyButtonAriaLabel,
480
- copyButtonToolTipText,
481
- uploadButtonAriaLabel,
482
- uploadButtonToolTipText,
483
- downloadButtonAriaLabel,
484
- downloadButtonToolTipText,
485
- toolTipDelay,
486
- toolTipCopyExitDelay,
487
- toolTipMaxWidth,
488
- toolTipPosition,
489
- isLineNumbersVisible,
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
- </Popover>
623
- </div>
624
- )}
625
- </Fragment>
626
- );
627
-
628
- const editorHeader = (
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
- {isLanguageLabelVisible && (
634
- <div className={css(styles.codeEditorTab)}>
635
- <span className={css(styles.codeEditorTabIcon)}>
636
- <CodeIcon />
637
- </span>
638
- <span className={css(styles.codeEditorTabText)}>{language.toUpperCase()}</span>
639
- </div>
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
- const editor = (
645
- <div className={css(styles.codeEditorCode)} ref={this.wrapperRef} tabIndex={0} dir="ltr">
646
- <Editor
647
- height={height === '100%' ? undefined : height}
648
- width={width}
649
- language={language}
650
- value={value}
651
- options={options}
652
- overrideServices={overrideServices}
653
- onChange={this.onChange}
654
- onMount={this.editorDidMount}
655
- theme={isDarkTheme ? 'vs-dark' : 'vs-light'}
656
- loading={loading}
657
- {...editorProps}
658
- />
659
- </div>
660
- );
661
-
662
- const hiddenFileInput = <input {...getInputProps()} /* hidden, necessary for react-dropzone */ hidden />;
663
-
664
- return (
665
- <div
666
- className={css(
667
- styles.codeEditor,
668
- isReadOnly && styles.modifiers.readOnly,
669
- isFullHeight && styles.modifiers.fullHeight,
670
- className
671
- )}
672
- ref={this.ref}
673
- >
674
- {(isUploadEnabled || providedEmptyState) && !value ? (
675
- <div
676
- {...getRootProps({
677
- onClick: (event) => event.stopPropagation() // Prevents clicking TextArea from opening file dialog
678
- })}
679
- className={css(styles.codeEditorContainer, isLoading && fileUploadStyles.modifiers.loading)}
680
- >
681
- {editorHeader}
682
- <div className={css(styles.codeEditorMain, isDragActive && styles.modifiers.dragHover)}>
683
- {(showEmptyState || providedEmptyState) && !value ? (
684
- <div className={css(styles.codeEditorUpload)}>
685
- {hiddenFileInput}
686
- {emptyState}
687
- </div>
688
- ) : (
689
- <>
690
- {hiddenFileInput}
691
- {editor}
692
- </>
693
- )}
694
- </div>
695
- </div>
696
- ) : (
697
- <>
698
- {editorHeader}
699
- {showEditor && (
700
- <div className={css(styles.codeEditorMain)}>
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
- {editor}
602
+ {editorEmptyState}
703
603
  </div>
604
+ ) : (
605
+ <>
606
+ {hiddenFileInput}
607
+ {editor}
608
+ </>
704
609
  )}
705
- </>
706
- )}
707
- </div>
708
- );
709
- }}
710
- </Dropzone>
711
- );
712
- }
713
- }
714
-
715
- export { CodeEditor };
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';