sortableWidget.tsx 4.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165
  1. import {ComponentProps, useEffect} from 'react';
  2. import {useSortable} from '@dnd-kit/sortable';
  3. import styled from '@emotion/styled';
  4. import PanelAlert from 'sentry/components/panels/panelAlert';
  5. import {Organization} from 'sentry/types';
  6. import theme from 'sentry/utils/theme';
  7. import withOrganization from 'sentry/utils/withOrganization';
  8. import WidgetCard from 'sentry/views/dashboardsV2/widgetCard';
  9. import {DashboardFilters, Widget} from './types';
  10. import DnDKitWidgetWrapper from './widgetWrapper';
  11. const TABLE_ITEM_LIMIT = 20;
  12. type Props = {
  13. dragId: string;
  14. index: string;
  15. isEditing: boolean;
  16. onDelete: () => void;
  17. onDuplicate: () => void;
  18. onEdit: () => void;
  19. organization: Organization;
  20. widget: Widget;
  21. widgetLimitReached: boolean;
  22. dashboardFilters?: DashboardFilters;
  23. isMobile?: boolean;
  24. isPreview?: boolean;
  25. windowWidth?: number;
  26. };
  27. function SortableWidget(props: Props) {
  28. const {
  29. organization,
  30. widget,
  31. dragId,
  32. isEditing,
  33. widgetLimitReached,
  34. onDelete,
  35. onEdit,
  36. onDuplicate,
  37. isPreview,
  38. isMobile,
  39. windowWidth,
  40. index,
  41. dashboardFilters,
  42. } = props;
  43. const {
  44. attributes,
  45. listeners,
  46. setNodeRef,
  47. transform,
  48. isDragging: currentWidgetDragging,
  49. isSorting,
  50. } = useSortable({
  51. id: dragId,
  52. transition: null,
  53. });
  54. useEffect(() => {
  55. if (!currentWidgetDragging) {
  56. return undefined;
  57. }
  58. document.body.style.cursor = 'grabbing';
  59. return function cleanup() {
  60. document.body.style.cursor = '';
  61. };
  62. }, [currentWidgetDragging]);
  63. let widgetProps: ComponentProps<typeof WidgetCard> = {
  64. widget,
  65. isEditing,
  66. widgetLimitReached,
  67. onDelete,
  68. onEdit,
  69. onDuplicate,
  70. isSorting,
  71. hideToolbar: isSorting,
  72. currentWidgetDragging,
  73. showContextMenu: true,
  74. isPreview,
  75. showWidgetViewerButton: organization.features.includes('widget-viewer-modal'),
  76. index,
  77. dashboardFilters,
  78. renderErrorMessage: errorMessage => {
  79. return (
  80. typeof errorMessage === 'string' && (
  81. <PanelAlert type="error">{errorMessage}</PanelAlert>
  82. )
  83. );
  84. },
  85. };
  86. if (organization.features.includes('dashboard-grid-layout')) {
  87. widgetProps = {
  88. ...widgetProps,
  89. isMobile,
  90. windowWidth,
  91. // TODO(nar): These aren't necessary for supporting RGL
  92. isSorting: false,
  93. currentWidgetDragging: false,
  94. tableItemLimit: TABLE_ITEM_LIMIT,
  95. };
  96. return (
  97. <GridWidgetWrapper>
  98. <WidgetCard {...widgetProps} />
  99. </GridWidgetWrapper>
  100. );
  101. }
  102. const initialStyles: ComponentProps<typeof DnDKitWidgetWrapper>['animate'] = {
  103. zIndex: 'auto',
  104. };
  105. widgetProps = {...widgetProps, draggableProps: {attributes, listeners}};
  106. return (
  107. <DnDKitWidgetWrapper
  108. ref={setNodeRef}
  109. displayType={widget.displayType}
  110. layoutId={dragId}
  111. style={{
  112. // Origin is set to top right-hand corner where the drag handle is placed.
  113. // Otherwise, set the origin to be the top left-hand corner when swapping widgets.
  114. originX: currentWidgetDragging ? 1 : 0,
  115. originY: 0,
  116. boxShadow: currentWidgetDragging ? theme.dropShadowHeavy : 'none',
  117. borderRadius: currentWidgetDragging ? theme.borderRadius : undefined,
  118. }}
  119. animate={
  120. transform
  121. ? {
  122. x: transform.x,
  123. y: transform.y,
  124. scaleX: transform?.scaleX && transform.scaleX <= 1 ? transform.scaleX : 1,
  125. scaleY: transform?.scaleY && transform.scaleY <= 1 ? transform.scaleY : 1,
  126. zIndex: currentWidgetDragging ? theme.zIndex.modal : 'auto',
  127. }
  128. : initialStyles
  129. }
  130. transformTemplate={(___transform, generatedTransform) => {
  131. if (isEditing && !!transform) {
  132. return generatedTransform;
  133. }
  134. return 'none';
  135. }}
  136. transition={{
  137. duration: !currentWidgetDragging ? 0.25 : 0,
  138. easings: {
  139. type: 'spring',
  140. },
  141. }}
  142. >
  143. <WidgetCard {...widgetProps} />
  144. </DnDKitWidgetWrapper>
  145. );
  146. }
  147. export default withOrganization(SortableWidget);
  148. const GridWidgetWrapper = styled('div')`
  149. height: 100%;
  150. `;