profileDragDropImport.tsx 2.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113
  1. import {useCallback, useState} from 'react';
  2. import styled from '@emotion/styled';
  3. import Button from 'sentry/components/button';
  4. import {t} from 'sentry/locale';
  5. import {LightFlamegraphTheme} from 'sentry/utils/profiling/flamegraph/flamegraphTheme';
  6. import {
  7. importDroppedProfile,
  8. ProfileGroup,
  9. } from 'sentry/utils/profiling/profile/importProfile';
  10. export interface ProfileDragDropImportProps {
  11. children: React.ReactNode;
  12. onImport: (profile: ProfileGroup) => void;
  13. }
  14. function ProfileDragDropImport({
  15. onImport,
  16. children,
  17. }: ProfileDragDropImportProps): React.ReactElement {
  18. const [dropState, setDropState] = useState<
  19. 'idle' | 'dragover' | 'processing' | 'errored'
  20. >('idle');
  21. const [errorMessage, setErrorMessage] = useState<string | null>(null);
  22. const onDrop = useCallback(
  23. (evt: React.DragEvent<HTMLDivElement>) => {
  24. evt.preventDefault();
  25. evt.stopPropagation();
  26. const file = evt.dataTransfer.items[0].getAsFile();
  27. if (file) {
  28. setDropState('processing');
  29. importDroppedProfile(file)
  30. .then(profile => {
  31. setDropState('idle');
  32. setErrorMessage(null);
  33. onImport(profile);
  34. })
  35. .catch(e => {
  36. setDropState('errored');
  37. setErrorMessage(e.message);
  38. });
  39. }
  40. },
  41. [onImport]
  42. );
  43. const onDragEnter = useCallback((evt: React.DragEvent<HTMLDivElement>) => {
  44. evt.preventDefault();
  45. evt.stopPropagation();
  46. setDropState('dragover');
  47. }, []);
  48. const onDragLeave = useCallback((evt: React.DragEvent<HTMLDivElement>) => {
  49. evt.preventDefault();
  50. evt.stopPropagation();
  51. setDropState('idle');
  52. }, []);
  53. // This is required to indicate that onDrop is supported on this element
  54. const onDragOver = useCallback((evt: React.DragEvent<HTMLDivElement>) => {
  55. evt.preventDefault();
  56. }, []);
  57. const onDismiss = useCallback(() => {
  58. setDropState('idle');
  59. setErrorMessage(null);
  60. }, []);
  61. return (
  62. <DragDropContainer onDragEnter={onDragEnter}>
  63. {dropState === 'idle' ? null : dropState === 'errored' ? (
  64. <Overlay>
  65. {t('Failed to import profile with error')}
  66. <p>{errorMessage}</p>
  67. <div>
  68. <Button onClick={onDismiss}>{t('Dismiss')}</Button>
  69. </div>
  70. </Overlay>
  71. ) : (
  72. <Overlay onDrop={onDrop} onDragOver={onDragOver} onDragLeave={onDragLeave}>
  73. {t('Drop profile here')}
  74. </Overlay>
  75. )}
  76. {children}
  77. </DragDropContainer>
  78. );
  79. }
  80. const DragDropContainer = styled('div')`
  81. display: flex;
  82. flex-direction: column;
  83. flex: 1 1 100%;
  84. `;
  85. const Overlay = styled('div')`
  86. position: absolute;
  87. left: 0;
  88. bottom: 0;
  89. width: 100%;
  90. height: calc(100% - ${LightFlamegraphTheme.SIZES.TIMELINE_HEIGHT}px);
  91. display: grid;
  92. grid: auto/50%;
  93. place-content: center;
  94. z-index: ${p => p.theme.zIndex.modal};
  95. text-align: center;
  96. background-color: ${p => p.theme.surface100};
  97. `;
  98. export {ProfileDragDropImport};