react-beautiful-dnd#SensorAPI TypeScript Examples

The following examples show how to use react-beautiful-dnd#SensorAPI. You can vote up the ones you like or vote down the ones you don't like, and go to the original project or source file by following the links above each example. You may check out the related API usage on the sidebar.
Example #1
Source File: dndUtil.ts    From clearflask with Apache License 2.0 5 votes vote down vote up
dndDrag = async (api: SensorAPI, draggableId: string, droppableId: string): Promise<boolean> => {
  if (windowIso.isSsr) return false;

  var preDrag: PreDragActions | null | undefined;
  var drag: FluidDragActions | undefined;
  try {
    preDrag = api.tryGetLock(draggableId);
    if (!preDrag) return false;

    const draggableEl = dndFindElement('draggable', draggableId);
    const droppableEl = dndFindElement('droppable', droppableId);
    if (!draggableEl || !droppableEl) {
      preDrag.abort();
      return false;
    }

    const draggablePosition = draggableEl.getBoundingClientRect();
    const droppablePosition = droppableEl.getBoundingClientRect();

    const from = {
      x: draggablePosition.x + draggablePosition.width / 2,
      y: draggablePosition.y + draggablePosition.height / 2,
    }
    const to = {
      x: droppablePosition.x + droppablePosition.width / 2,
      y: droppablePosition.y + droppablePosition.height / 2,
    }

    drag = preDrag.fluidLift(from);
    var step = 0.0;
    while (step <= 1) {
      step += 0.05;

      await new Promise(resolve => setTimeout(resolve, 5));
      if (!drag.isActive()) {
        drag.cancel();
        return false;
      }

      drag.move({
        x: from.x + (to.x - from.x) * step,
        y: from.y + (to.y - from.y) * step,
      });
    }

    drag.move({
      x: droppablePosition.x + droppablePosition.width / 2 - (draggablePosition.x + draggablePosition.width / 2),
      y: droppablePosition.y + droppablePosition.height / 2 - (draggablePosition.y + draggablePosition.height / 2),
    });

    drag.drop();

    return true;

  } catch (e) {
    if (drag?.isActive()) drag.cancel();
    if (preDrag?.isActive()) preDrag.abort();

    return false;
  }
}
Example #2
Source File: DashboardQuickActions.tsx    From clearflask with Apache License 2.0 4 votes vote down vote up
DashboardQuickActions = (props: {
  activeProject: Project;
  onClickPost: (postId: string) => void;
  onUserClick: (userId: string) => void;
  searchKey?: string; // When search changes, update to check whether selectedPostId is still draggable
  selectedPostId?: string;
  feedback?: FeedbackInstance | null;
  roadmap?: RoadmapInstance | null;
  dragDropSensorApi?: SensorAPI;
  draggingPostIdSubscription: Subscription<string | undefined>;
  fallbackClickHandler: FallbackClickHandler;
}) => {
  const [draggingPostId, setDraggingPostId] = useState(props.draggingPostIdSubscription.getValue());
  useEffect(() => props.draggingPostIdSubscription.subscribe(setDraggingPostId), []); // eslint-disable-line react-hooks/exhaustive-deps

  const { t } = useTranslation('app');
  const classes = useStyles();
  const theme = useTheme();
  const { enqueueSnackbar } = useSnackbar();

  const noticeChangeStatusRef = useRef<FirstTimeNoticeHandle>(null);
  const noticeConvertToTaskRef = useRef<FirstTimeNoticeHandle>(null);
  const noticeDeleteRef = useRef<FirstTimeNoticeHandle>(null);

  const quickActionsPostId = draggingPostId || props.selectedPostId;
  const quickActionsPost = useSelector<ReduxState, Admin.Idea | undefined>(state => !quickActionsPostId ? undefined : state.ideas.byId[quickActionsPostId]?.idea, shallowEqual);

  // TODO disable in cases if its already merged or linked or something
  const enabled = !!quickActionsPostId;
  const onClick = (!enabled || !!draggingPostId) ? undefined
    : onClickAction(props.dragDropSensorApi, props.fallbackClickHandler, quickActionsPostId);

  const statusAccepted = !props.feedback?.statusIdAccepted ? undefined : props.feedback.categoryAndIndex.category.workflow.statuses.find(s => s.statusId === props.feedback?.statusIdAccepted);
  const nextStatusIds = new Set<string>(quickActionsPost?.statusId
    && props.feedback?.categoryAndIndex.category.workflow.statuses.find(s => s.statusId === quickActionsPost?.statusId)?.nextStatusIds
    || []);

  const feedbackNextStatusActions = props.feedback?.categoryAndIndex.category.workflow.statuses
    .filter(status => status.statusId !== props.feedback?.categoryAndIndex.category.workflow.entryStatus
      && status.statusId !== props.feedback?.statusIdAccepted);
  const roadmapNextStatusActions = props.roadmap?.categoryAndIndex.category.workflow.statuses
    .filter(status => status.statusId !== props.roadmap?.statusIdClosed
      && status.statusId !== props.roadmap?.statusIdCompleted);

  return (
    <div className={classes.postActionsContainer}>
      {feedbackNextStatusActions?.length && (
        <>
          <FilterControlTitle name='Change status' className={classes.feedbackTitle} help={{
            description: helperChangeStatus,
          }} />
          <div className={classes.postActionGroup}>
            {feedbackNextStatusActions.map(status => {
              const droppableId = droppableDataSerialize({
                type: 'quick-action-feedback-change-status',
                dropbox: true,
                statusId: status.statusId,
              });
              return (
                <QuickActionArea
                  key={status.statusId}
                  isDragging={!!draggingPostId}
                  droppableId={droppableId}
                  color={status.color}
                  enabled={enabled && nextStatusIds.has(status.statusId)}
                  onClick={async droppableId => {
                    if (! await noticeChangeStatusRef.current?.invoke()) return;
                    return await onClick?.(droppableId);
                  }}
                  title={status.name}
                />
              );
            })}
          </div>
        </>
      )}
      {roadmapNextStatusActions?.length && (
        <Provider store={ServerAdmin.get().getStore()}>
          <TourAnchor anchorId='feedback-page-convert-to-task' placement='left'>
            {(next, isActive, anchorRef) => (
              <>
                <FilterControlTitle name='Convert to task' className={classes.feedbackTitle} help={{
                  description: helperConvertToTask,
                }} />
                <div className={classes.postActionGroup} ref={anchorRef}>
                  {roadmapNextStatusActions.map(status => (
                    <QuickActionArea
                      key={status.statusId}
                      isDragging={!!draggingPostId}
                      droppableId={droppableDataSerialize({
                        type: 'quick-action-create-task-from-feedback-with-status',
                        dropbox: true,
                        statusId: status.statusId,
                      })}
                      color={status.color}
                      enabled={enabled && (!statusAccepted || nextStatusIds.has(statusAccepted.statusId))}
                      onClick={async droppableId => {
                        if (isActive) {
                          enqueueSnackbar('Disabled during tutorial', { variant: 'warning', preventDuplicate: true });
                          return;
                        }
                        if (! await noticeConvertToTaskRef.current?.invoke()) return;
                        return await onClick?.(droppableId);
                      }}
                      title={status.name}
                    />
                  ))}
                </div>
              </>
            )}
          </TourAnchor>
        </Provider>
      )}
      <FilterControlTitle name={t('delete')} className={classes.feedbackTitle} help={{
        description: helperDelete,
      }} />
      <QuickActionArea
        key='delete'
        isDragging={!!draggingPostId}
        droppableId={droppableDataSerialize({
          type: 'quick-action-delete',
          dropbox: true,
        })}
        color={theme.palette.error.dark}
        enabled={enabled}
        onClick={async droppableId => {
          if (! await noticeDeleteRef.current?.invoke()) return;
          return await onClick?.(droppableId);
        }}
        title={t('delete')}
      />
      <Provider store={ServerAdmin.get().getStore()}>
        <FirstTimeNotice
          ref={noticeChangeStatusRef}
          id='feedback-change-status'
          title='Change status'
          description={helperChangeStatus}
          confirmButtonTitle='Change'
        />
        <FirstTimeNotice
          ref={noticeConvertToTaskRef}
          id='feedback-convert-to-task'
          title='Create task'
          description={helperConvertToTask}
          confirmButtonTitle='Convert'
        />
        <FirstTimeNotice
          ref={noticeDeleteRef}
          id='feedback-delete'
          title='Permanently delete'
          description={helperDelete}
          confirmButtonTitle='Delete'
          confirmButtonRed
        />
      </Provider>
    </div>
  );
}
Example #3
Source File: DashboardQuickActions.tsx    From clearflask with Apache License 2.0 4 votes vote down vote up
DroppableQuickActionPostList = React.memo((props: {
  server: Server;
  title?: {
    name: string;
    helpDescription?: string;
  };
  getDroppableId: (post: Admin.Idea) => string | undefined;
  selectedPostId?: string;
  draggingPostIdSubscription: Subscription<string | undefined>;
  dragDropSensorApi?: SensorAPI;
  statusColorGivenCategories?: string[];
  fallbackClickHandler: (draggableId: string, dstDroppableId: string) => Promise<boolean>;
  PostListProps?: Partial<React.ComponentProps<typeof PostList>>;
  showSampleItem?: string;
  disabledDuringTour?: boolean;
  FirstTimeNoticeProps?: React.ComponentPropsWithoutRef<typeof FirstTimeNotice>;
}) => {
  const classes = useStyles();
  const { enqueueSnackbar } = useSnackbar();

  const noticeRef = useRef<FirstTimeNoticeHandle>(null);

  const [draggingPostId, setDraggingPostId] = useState(props.draggingPostIdSubscription.getValue());
  useEffect(() => props.draggingPostIdSubscription.subscribe(setDraggingPostId), []); // eslint-disable-line react-hooks/exhaustive-deps

  const statusIdToColor = useSelector<ReduxState, { [statusId: string]: string } | undefined>(state => {
    if (!props.statusColorGivenCategories?.length) return;
    const statusIdToColor = {};
    state.conf.conf?.content.categories
      .forEach(category => {
        if (!props.statusColorGivenCategories?.includes(category.categoryId)) return;
        category.workflow.statuses.forEach(status => {
          if (status.color === undefined) return;
          statusIdToColor[status.statusId] = status.color;
        });
      });
    return statusIdToColor;
  }, shallowEqual);

  const quickActionsPostId = draggingPostId || props.selectedPostId;
  const quickActionsPost = useSelector<ReduxState, Admin.Idea | undefined>(state => !quickActionsPostId ? undefined : state.ideas.byId[quickActionsPostId]?.idea, shallowEqual);
  const enabled = (!!quickActionsPostId && !quickActionsPost?.mergedToPostId)
  const onClick = (!enabled || !!draggingPostId) ? undefined
    : onClickAction(props.dragDropSensorApi, props.fallbackClickHandler, quickActionsPostId);

  var content = (
    <PostList
      key={props.server.getProjectId()}
      server={props.server}
      layout='similar-merge-action'
      PanelPostProps={{
        overrideTitle: !props.title ? undefined : (
          <FilterControlTitle name={props.title.name} className={classes.feedbackTitle} help={{
            description: props.title.helpDescription,
          }} />
        ),
        renderPost: (idea, ideaIndex) => {
          const droppableId = props.getDroppableId(idea);
          if (!droppableId) return null;
          return (
            <QuickActionArea
              key={idea.ideaId}
              isDragging={!!draggingPostId}
              droppableId={droppableId}
              enabled={enabled}
              onClick={async droppableId => {
                if (props.disabledDuringTour) {
                  enqueueSnackbar('Disabled during tutorial', { variant: 'warning', preventDuplicate: true });
                  return;
                }
                if (!!noticeRef.current && ! await noticeRef.current.invoke()) return;
                return await onClick?.(droppableId);
              }}
              color={(!statusIdToColor || !idea.statusId) ? undefined : statusIdToColor[idea.statusId]}
              title={truncateWithElipsis(30, idea.title)}
            />
          );
        },
        renderEmpty: !props.showSampleItem ? undefined : () => (
          <QuickActionArea
            key={`sample-item-${props.showSampleItem}`}
            isDragging={false}
            droppableId='disabled-for-dropping'
            enabled={false}
            title={props.showSampleItem}
          />
        ),
      }}
      hideIfEmpty={!props.showSampleItem}
      {...props.PostListProps}
    />
  );

  if (props.FirstTimeNoticeProps) {
    content = (
      <>
        {content}
        <Provider store={ServerAdmin.get().getStore()}>
          <FirstTimeNotice
            {...props.FirstTimeNoticeProps}
            ref={noticeRef}
          />
        </Provider>
      </>
    );
  }

  return content;
}, customReactMemoEquals({
  nested: new Set(['PostListProps', 'FirstTimeNoticeProps', 'DroppableProvidedProps', 'statusColorGivenCategories', 'title']),
  presence: new Set(['fallbackClickHandler', 'getDroppableId']),
}))
Example #4
Source File: useAutoMoveSensor.ts    From wikitrivia with MIT License 4 votes vote down vote up
export default async function useAutoMoveSensor(
  state: GameState,
  api: SensorAPI
) {
  if (
    state.badlyPlaced === null ||
    state.badlyPlaced.index === null ||
    state.badlyPlaced.rendered === false
  ) {
    return;
  }

  const preDrag = api.tryGetLock?.(state.played[state.badlyPlaced.index].id);

  if (!preDrag) {
    return;
  }

  const itemEl: HTMLElement | null = document.querySelector(
    `[data-rbd-draggable-id='${state.played[state.badlyPlaced.index].id}']`
  );
  const destEl: HTMLElement | null = document.querySelector(
    `[data-rbd-draggable-id='${
      state.played[state.badlyPlaced.index + state.badlyPlaced.delta].id
    }']`
  );
  const bottomEl: HTMLElement | null = document.getElementById("bottom");

  if (itemEl === null || destEl === null || bottomEl === null) {
    throw new Error("Can't find element");
  }

  const bottomElCentreLeft = bottomEl.scrollLeft + bottomEl.clientWidth / 4;
  const bottomElCentreRight =
    bottomEl.scrollLeft + (bottomEl.clientWidth / 4) * 3 - itemEl.clientWidth;

  let scrollDistance = 0;

  if (
    destEl.offsetLeft < bottomElCentreLeft ||
    destEl.offsetLeft > bottomElCentreRight
  ) {
    // Destination is not in middle two quarters of the screen. Calculate
    // distance we therefore need to scroll.
    scrollDistance =
      destEl.offsetLeft < bottomElCentreLeft
        ? destEl.offsetLeft - bottomElCentreLeft
        : destEl.offsetLeft - bottomElCentreRight;

    if (bottomEl.scrollLeft + scrollDistance < 0) {
      scrollDistance = -bottomEl.scrollLeft;
    } else if (
      bottomEl.scrollLeft + scrollDistance >
      bottomEl.scrollWidth - bottomEl.clientWidth
    ) {
      scrollDistance =
        bottomEl.scrollWidth - bottomEl.clientWidth - bottomEl.scrollLeft;
    }
  }

  // Calculate the distance we need to move the item after taking into account
  // how far we are scrolling.
  const transformDistance =
    destEl.offsetLeft - itemEl.offsetLeft - scrollDistance;

  const drag = preDrag.fluidLift({ x: 0, y: 0 });

  // Create a series of eased transformations and scrolls to animate from the
  // current state to the desired state.
  const transformPoints = [];
  const scrollPoints = [];
  const numberOfPoints = 30 + 5 * Math.abs(state.badlyPlaced.delta);

  for (let i = 0; i < numberOfPoints; i++) {
    transformPoints.push(
      tweenFunctions.easeOutCirc(i, 0, transformDistance, numberOfPoints)
    );
    scrollPoints.push(
      tweenFunctions.easeOutCirc(
        i,
        bottomEl.scrollLeft,
        bottomEl.scrollLeft + scrollDistance,
        numberOfPoints
      )
    );
  }

  moveStepByStep(drag, transformPoints, scrollPoints);
}