react-beautiful-dnd#DragDropContext TypeScript Examples

The following examples show how to use react-beautiful-dnd#DragDropContext. 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: bodywrapper.tsx    From gant-design with MIT License 6 votes vote down vote up
BodyWrapper = ({ children, ...props }) => {
    const { onDragEnd } = useContext(TableBodyWrapperContext)

    return (
        <DragDropContext onDragEnd={onDragEnd} >
            <Droppable droppableId='droppable'>
                {
                    (provided, snapshot) => {
                        return (
                            <tbody {...props} ref={provided.innerRef} {...provided.droppableProps}>
                                {children}
                                {provided.placeholder}
                            </tbody>
                        )
                    }
                }
            </Droppable>
        </DragDropContext>
    )
}
Example #2
Source File: DndContextProvider.tsx    From calories-in with MIT License 6 votes vote down vote up
function DndContextProvider({ children }: Props) {
  const dietFormActions = useDietFormActions()

  const onDragEnd = (dropResult: DropResult) => {
    const { source, destination, type } = dropResult

    if (!destination) {
      return
    }

    if (type === 'variantsList') {
      dietFormActions.moveVariantForm(source.index, destination.index)
    } else if (type === 'mealsList') {
      dietFormActions.moveMealForm(source.index, destination.index)
    } else if (type === 'ingredientsList') {
      dietFormActions.moveIngredientForm(
        source.droppableId,
        source.index,
        destination.droppableId,
        destination.index
      )
    }
  }

  return <DragDropContext onDragEnd={onDragEnd}>{children}</DragDropContext>
}
Example #3
Source File: FlowList.tsx    From Protoman with MIT License 6 votes vote down vote up
FlowList: React.FunctionComponent<Props> = ({ collectionName }) => {
  const dispatch = useDispatch();

  const collection = useSelector((s: AppState) => getByKey(s.collections, collectionName));
  const flowNames = useSelector((s: AppState) => collection?.flows?.map(([n]) => n));
  const isCurrentCollection = useSelector((s: AppState) => s.currentCollection === collectionName);
  const currentFlow = useSelector((s: AppState) => s.currentFlow);

  function handleSelection(flowName: string): void {
    dispatch(selectFlow(collectionName, flowName));
  }

  function validateFlowName(flowName: string): boolean {
    return !collection?.flows?.map(([n]) => n)?.includes(flowName);
  }

  function handleDelete(flowName: string): void {
    const flowCount = collection?.flows?.length || 0;
    if (flowCount > 1) {
      dispatch(deleteFlow(collectionName, flowName));
    } else {
      message.error("Can't delete the last request");
    }
  }

  function handleClone(originalFlowName: string): void {
    //check if this clone already exists
    const tmpName = originalFlowName.concat('_clone');
    let tmpNameIdx = 1;
    while (!validateFlowName(`${tmpName}${tmpNameIdx}`)) tmpNameIdx++;
    dispatch(cloneFlow(collectionName, originalFlowName, `${tmpName}${tmpNameIdx}`));
  }

  function handleDragEnd(result: DropResult): void {
    console.log(result);
    if (!result.destination || result.source.droppableId != result.destination.droppableId) return;

    const src = result.source.index;
    const dst = result.destination.index;

    dispatch(reorderFlow(collectionName, src, dst));
  }

  return (
    <DragDropContext onDragEnd={handleDragEnd}>
      <Droppable droppableId={collectionName}>
        {(provided): React.ReactElement => (
          <div {...provided.droppableProps} ref={provided.innerRef}>
            <List
              dataSource={flowNames}
              rowKey={(name): string => name}
              renderItem={(flowName, idx): React.ReactNode => (
                <FlowCell
                  idx={idx}
                  flowName={flowName}
                  emphasize={isCurrentCollection && currentFlow === flowName}
                  handleSelection={handleSelection}
                  handleDelete={handleDelete}
                  handleClone={handleClone}
                />
              )}
            />
            {provided.placeholder}
          </div>
        )}
      </Droppable>
    </DragDropContext>
  );
}
Example #4
Source File: TasksBoard.tsx    From projectboard with MIT License 5 votes vote down vote up
TasksBoard = () => {
    const backlogTasks: Array<Task> = useSelector((state: RootState) => state.taskList?.tasks.backlog);
    const todoTasks: Array<Task> = useSelector((state: RootState) => state.taskList?.tasks.todo);
    const inProgressTasks: Array<Task> = useSelector(
        (state: RootState) => state.taskList?.tasks?.in_progress
    );
    const doneTasks: Array<Task> = useSelector((state: RootState) => state.taskList?.tasks?.done);
    const canceledTasks: Array<Task> = useSelector((state: RootState) => state.taskList?.tasks?.cancelled);

    const todoSorted: Array<Task> = todoTasks.sort((prev: Task, next: Task) => prev.order - next.order);
    const backlogSorted: Array<Task> = backlogTasks.sort((prev: Task, next: Task) => prev.order - next.order);
    const inProgressSorted: Array<Task> = inProgressTasks.sort(
        (prev: Task, next: Task) => prev.order - next.order
    );
    const doneSorted: Array<Task> = doneTasks.sort((prev: Task, next: Task) => prev.order - next.order);
    const cancelledSorted: Array<Task> = canceledTasks.sort(
        (prev: Task, next: Task) => prev.order - next.order
    );

    const match = useRouteMatch<MatchParams>();
    const { getAccessTokenSilently } = useAuth0();

    // dispatch
    const dispatch = useDispatch<AppDispatch>();
    const onDragEnd = async (
        { source, destination, draggableId }: DropResult,
        provided: ResponderProvided
    ) => {
        if (source.droppableId === destination?.droppableId) return;
        if (!source || !destination) return;
        const token = await getAccessTokenSilently();
        dispatch(
            changeStatusOfTaskBoard(
                draggableId,
                source.droppableId,
                destination.droppableId,
                source.index,
                destination.index,
                match.params.projectId,
                token
            )
        );
    };

    return (
        <DragDropContext onDragEnd={onDragEnd}>
            <div className="flex flex-1 pt-6 pl-8 overflow-scroll bg-gray-100">
                <IssueCol title={'Backlog'} status={Status.BACKLOG} tasks={backlogSorted} />
                <IssueCol title={'Todo'} status={Status.TODO} tasks={todoSorted} />
                <IssueCol title={'In Progress'} status={Status.IN_PROGRESS} tasks={inProgressSorted} />
                <IssueCol title={'Done'} status={Status.DONE} tasks={doneSorted} />
                <IssueCol title={'Canceled'} status={Status.CANCELED} tasks={cancelledSorted} />
            </div>
        </DragDropContext>
    );
}
Example #5
Source File: Todos.tsx    From max-todos with MIT License 5 votes vote down vote up
Todos = () => {
  const { todos, moveTodo } = useContext(MainContext)!;
  const [deleteSnackOpen, setDeleteSnackOpen] = useState(false);
  const [editSnackOpen, setEditSnackOpen] = useState(false);
  const [dragging, setDragging] = useState(false);
  const onDragEnd = (x: DropResult) => {
    if (!x.destination) return console.log(x);
    moveTodo(x.source.index, x.destination.index);
    setTimeout(() => setDragging(false), 200);
  };
  return (
    <>
      <DragDropContext
        onBeforeDragStart={() => setDragging(true)}
        onDragEnd={onDragEnd}
      >
        <Droppable droppableId="0">
          {(p) => (
            <div {...p.droppableProps} ref={p.innerRef}>
              <FlipMove disableAllAnimations={dragging}>
                {todos.map((todo, i) => {
                  return (
                    <Todo
                      todo={todo}
                      key={todo.id}
                      onDelete={() => setDeleteSnackOpen(true)}
                      index={i}
                      onEdit={() => setEditSnackOpen(true)}
                    />
                  );
                })}
              </FlipMove>
              {p.placeholder}
            </div>
          )}
        </Droppable>
      </DragDropContext>

      <Snackbar
        open={deleteSnackOpen}
        autoHideDuration={4000}
        onClose={() => setDeleteSnackOpen(false)}
        anchorOrigin={{ vertical: "bottom", horizontal: "right" }}
      >
        <Alert
          elevation={6}
          variant="filled"
          onClose={() => setDeleteSnackOpen(false)}
          severity="success"
        >
          Successfully deleted item!
        </Alert>
      </Snackbar>
      <Snackbar
        open={editSnackOpen}
        autoHideDuration={4000}
        onClose={() => setEditSnackOpen(false)}
        anchorOrigin={{ vertical: "bottom", horizontal: "right" }}
      >
        <Alert
          elevation={6}
          variant="filled"
          onClose={() => setEditSnackOpen(false)}
          severity="success"
        >
          Successfully edited item!
        </Alert>
      </Snackbar>
    </>
  );
}
Example #6
Source File: designers.tsx    From ali-react-table with MIT License 5 votes vote down vote up
export function CheckedDimList({ style, className, pivot }: CheckDimListProps) {
  const dimMap = new Map(pivot.allDimensions.map((dim) => [dim.code, dim]))

  return (
    <CheckedDimListDiv style={style} className={className}>
      <DragDropContext
        onDragEnd={(result) => {
          if (!result.destination) {
            return
          }
          const i = result.source.index
          const j = result.destination.index
          const nextCodes = pivot.dimCodes.slice()
          const [code] = nextCodes.splice(i, 1)
          nextCodes.splice(j, 0, code)
          pivot.changeDimCodes(nextCodes)
        }}
      >
        <Droppable droppableId="dimension-list" direction="horizontal">
          {(dropProvided, snapshot) => (
            <div className="tag-list" ref={dropProvided.innerRef}>
              {pivot.dimCodes.map((dimCode, index) => {
                const allValues = pivot.dimValues[dimCode]
                const values = pivot.filters[dimCode]

                return (
                  <Draggable key={dimCode} draggableId={dimCode} index={index}>
                    {(dragProvided, snapshot) => (
                      <Overlay.Popup
                        key={dimCode}
                        trigger={
                          <div
                            key={dimCode}
                            ref={dragProvided.innerRef}
                            {...dragProvided.draggableProps}
                            {...dragProvided.dragHandleProps}
                            className={cx('tag', {
                              active: values.length < allValues.length,
                              empty: allValues.length > 0 && values.length === 0,
                            })}
                          >
                            {dimMap.get(dimCode).name}
                            <Filter16 className="filter-icon" />
                          </div>
                        }
                        triggerType="click"
                      >
                        <CheckedDimFilterPopup pivot={pivot} allValues={allValues} values={values} dimCode={dimCode} />
                      </Overlay.Popup>
                    )}
                  </Draggable>
                )
              })}
              {dropProvided.placeholder}
            </div>
          )}
        </Droppable>
      </DragDropContext>
    </CheckedDimListDiv>
  )
}
Example #7
Source File: NetworksDnd.tsx    From devex with GNU General Public License v3.0 5 votes vote down vote up
NetworksDnd: React.FC<IProps> = ({ cards, setCards, deleteNode, editNode }) => {

  const onDragEnd = (result: any) => {
    if (!result.destination) {
      return
    }
    const reorderedCards = reorder(
      cards,
      result.source.index,
      result.destination.index
    )
    setCards(reorderedCards)
  }

  return <Container>
    <DragDropContext onDragEnd={onDragEnd}>
      <Droppable droppableId="droppable">
        {(provided, snapshot) => (
          <div
            className='dnd'
            {...provided.droppableProps}
            ref={provided.innerRef}
            style={getListStyle(snapshot.isDraggingOver)}
          >
            {cards.map((card, index) => (
              <Draggable key={card.id} draggableId={card.id.toString()} index={index}>
                {(provided, snapshot) => (
                  <div
                    ref={provided.innerRef}
                    {...provided.draggableProps}
                    {...provided.dragHandleProps}
                    style={getItemStyle(
                      snapshot.isDragging,
                      provided.draggableProps.style
                    )}
                  >
                    <NetworkCard
                      key={card.url}
                      url={card.url}
                      name={card.name}
                      deleteNode={deleteNode}
                      editNode={editNode}
                    />
                  </div>
                )}
              </Draggable>
            ))}
            {provided.placeholder}
          </div>
        )}
      </Droppable>
    </DragDropContext>
  </Container>
}
Example #8
Source File: Form.demo.dnd.tsx    From mantine with MIT License 5 votes vote down vote up
function Demo() {
  const form = useForm({
    initialValues: {
      employees: formList([
        { name: 'John Doe', email: '[email protected]' },
        { name: 'Bill Love', email: '[email protected]' },
        { name: 'Nancy Eagle', email: '[email protected]' },
        { name: 'Lim Notch', email: '[email protected]' },
        { name: 'Susan Seven', email: '[email protected]' },
      ]),
    },
  });

  const fields = form.values.employees.map((_, index) => (
    <Draggable key={index} index={index} draggableId={index.toString()}>
      {(provided) => (
        <Group ref={provided.innerRef} mt="xs" {...provided.draggableProps}>
          <Center {...provided.dragHandleProps}>
            <GripVertical size={18} />
          </Center>
          <TextInput
            placeholder="John Doe"
            {...form.getListInputProps('employees', index, 'name')}
          />
          <TextInput
            placeholder="[email protected]"
            {...form.getListInputProps('employees', index, 'email')}
          />
        </Group>
      )}
    </Draggable>
  ));

  return (
    <Box sx={{ maxWidth: 500 }} mx="auto">
      <DragDropContext
        onDragEnd={({ destination, source }) =>
          form.reorderListItem('employees', { from: source.index, to: destination.index })
        }
      >
        <Droppable droppableId="dnd-list" direction="vertical">
          {(provided) => (
            <div {...provided.droppableProps} ref={provided.innerRef}>
              {fields}
              {provided.placeholder}
            </div>
          )}
        </Droppable>
      </DragDropContext>

      <Group position="center" mt="md">
        <Button onClick={() => form.addListItem('employees', { name: '', email: '' })}>
          Add employee
        </Button>
      </Group>

      <Text size="sm" weight={500} mt="md">
        Form values:
      </Text>
      <Code block>{JSON.stringify(form.values, null, 2)}</Code>
    </Box>
  );
}
Example #9
Source File: App.tsx    From TabMerger with GNU General Public License v3.0 5 votes vote down vote up
App = (): JSX.Element => {
  const dispatch = useDispatch();

  const { filterChoice } = useSelector((state) => state.header);
  const { active, available } = useSelector((state) => state.groups.present);

  const { filteredGroups } = useFilter();

  useInitState({ available, active });
  useUpdateWindows();
  useUpdateInfo();
  useExecuteCommand();

  const { onBeforeCapture, onDragStart, onDragEnd } = useDnd();

  return (
    <Container>
      <GlobalStyle />
      <ErrorBoundary
        FallbackComponent={ErrorBoundaryFallback}
        onReset={() => {
          // Revert 2 steps back
          dispatch(ActionCreators.jump(-2));
        }}
      >
        <Header />

        <DragDropContext onBeforeCapture={onBeforeCapture} onDragStart={onDragStart} onDragEnd={onDragEnd}>
          {(filterChoice === "tab" || (filterChoice === "group" && filteredGroups.length > 0)) && (
            <MainArea>
              <SidePanel />

              <Windows />
            </MainArea>
          )}
        </DragDropContext>
      </ErrorBoundary>
    </Container>
  );
}
Example #10
Source File: Tree.tsx    From react-beautiful-tree with Apache License 2.0 5 votes vote down vote up
render() {
    const {
      isNestingEnabled,
      isVirtualizationEnabled,
      virtualItemHeight,
    } = this.props
    const { flattenedTree } = this.state
    const renderedItems = this.renderItems()

    return (
      <DragDropContext
        onDragStart={this.onDragStart}
        onDragEnd={this.onDragEnd}
        onDragUpdate={this.onDragUpdate}
      >
        <Droppable
          droppableId="tree"
          isCombineEnabled={isNestingEnabled}
          ignoreContainerClipping
          mode={isVirtualizationEnabled ? 'virtual' : 'standard'}
          renderClone={
            isVirtualizationEnabled
              ? (provided, snapshot, rubric) =>
                  this.renderVirtualItem({
                    provided,
                    snapshot,
                    flatItem: flattenedTree[rubric.source.index],
                  })
              : undefined
          }
        >
          {(provided: DroppableProvided) => {
            const finalProvided: DroppableProvided = this.patchDroppableProvided(
              provided
            )

            return isVirtualizationEnabled ? (
              <AutoSizer defaultHeight={1} defaultWidth={1}>
                {({ height, width }: { height: number; width: number }) => (
                  <FixedSizeList
                    height={height}
                    itemCount={flattenedTree.length}
                    itemSize={virtualItemHeight}
                    width={width}
                    outerRef={provided.innerRef}
                    itemData={flattenedTree}
                  >
                    {this.renderVirtualRow}
                  </FixedSizeList>
                )}
              </AutoSizer>
            ) : (
              <div
                ref={finalProvided.innerRef}
                style={{ pointerEvents: 'auto' }}
                onTouchMove={this.onPointerMove}
                onMouseMove={this.onPointerMove}
                {...finalProvided.droppableProps}
              >
                {renderedItems}
                {provided.placeholder}
              </div>
            )
          }}
        </Droppable>
      </DragDropContext>
    )
  }
Example #11
Source File: index.tsx    From Tiquet with MIT License 5 votes vote down vote up
Board = ({
  fetchBoard,
  moveTask,
  board,
  match,
  resetState,
  fetchPriorities,
  deleteList,
  sortList,
}: IProps): JSX.Element => {
  const [isLoading, setIsLoading] = useState(true);

  useEffect(() => {
    const boardId: number = match.params.id;
    trackPageView('Board');
    fetchPriorities();
    fetchBoard(boardId)
      .then(() => setIsLoading(false));

    return () => {
      resetState();
    }
  }, []);

  const onDragEnd = (e: any): void => {
    const { draggableId, destination, source } = e;
    const originListId = parseInt(source.droppableId);
    const destinationListId = parseInt(destination.droppableId);
    const taskId = parseInt(draggableId);

    if (originListId != destinationListId) {
      trackEvent({
        category: 'Lists',
        action: 'Task moved to another list',
      });
      moveTask(originListId, destinationListId, taskId, destination.index);
    } else {
      trackEvent({
        category: 'Tasks',
        action: 'Updated position',
      });
      sortList(originListId, taskId, source.index, destination.index);
    }
  }

  const onListDelete = (listId: number) => {
    trackEvent({
      category: 'Lists',
      action: 'List deleted',
    });
    deleteList(listId);
  };

  return (
    <div className="board">
      <Loading display={isLoading} />
      <TaskDescription />
      <h1 className="board__title">{board.title}</h1>
      <div className="board__columns">
        <DragDropContext onDragEnd={result => onDragEnd(result)} >
          {board.lists.map(list => (
            <List
              key={list.id}
              onDelete={() => onListDelete(list.id)}
              {...list}
            />
          ))}
        </DragDropContext>
        <CreateList />
      </div>
    </div>
  );
}
Example #12
Source File: TabContainer.tsx    From yana with MIT License 5 votes vote down vote up
TabContainer: React.FC<{}> = props => {
  const theme = useTheme();
  const mainContent = useMainContentContext();

  return (
    <DragDropContext
      onDragEnd={(result, provided) => {
        if (result.destination?.index !== undefined) {
          mainContent.reorderTab(result.source.index, result.destination.index);
        }
      }}
    >
      <Droppable droppableId="tabs-droppable" direction="horizontal">
        {(provided, snapshot) => (
          <div className={styles.tabsContainer} ref={provided.innerRef} {...provided.droppableProps}>
            {mainContent.tabs.map((tab, idx) => {
              const id = tab.dataItem?.id ?? tab.page ?? 'unknown';
              const name = tab.dataItem?.name ?? (tab.page ? pages[tab.page]?.title : 'Unknown name');

              return (
                <Draggable key={id} draggableId={id} index={idx} disableInteractiveElementBlocking={true}>
                  {(provided, snapshot) => (
                    <div
                      ref={provided.innerRef}
                      {...provided.draggableProps}
                      {...provided.dragHandleProps}
                      className={cx(
                        styles.tab,
                        snapshot.isDragging && styles.draggingTab,
                        cxs({
                          color:
                            mainContent.openTabId === idx
                              ? 'white'
                              : Color(theme.topBarColor).mix(Color('#ffffff'), 0.5).toString(),
                          borderBottom: mainContent.openTabId === idx ? `4px solid ${theme.primaryColor}` : undefined,
                          fontWeight: mainContent.openTabId === idx ? 'bold' : 'normal',
                          backgroundColor: theme.topBarColor,
                          ':hover': {
                            borderBottom: mainContent.openTabId !== idx ? `4px solid white` : undefined,
                          },
                        })
                      )}
                      onClick={() => mainContent.activateTab(idx)}
                    >
                      {name.substr(0, 12)}
                      {name.length > 12 ? '...' : ''}
                      <div
                        className={cx(
                          styles.closeContainer,
                          cxs({
                            backgroundColor: theme.topBarColor,
                          })
                        )}
                        onClick={e => {
                          e.stopPropagation();
                          mainContent.closeTab(idx);
                        }}
                      >
                        <Icon icon={'cross'} />
                      </div>
                    </div>
                  )}
                </Draggable>
              );
            })}
          </div>
        )}
      </Droppable>
    </DragDropContext>
  );
}
Example #13
Source File: DraggableTags.tsx    From nebula-studio with Apache License 2.0 5 votes vote down vote up
render() {
    const list = this.props.data.map(item => ({
      id: `field-${item}`,
      content: (
        <Tag className={styles.dragItem} closable={true} onClose={() => this.props.removeData(item)}>
          {item}
        </Tag>
      ),
    }));
    return (
      <DragDropContext onDragEnd={this.onDragEnd}>
        <Droppable droppableId="droppable" direction="horizontal">
          {(provided, _snapshot) => (
            <div
              {...provided.droppableProps}
              ref={provided.innerRef}
              style={{
                display: 'flex',
                overflow: 'auto',
                flexWrap: 'wrap',
              }}
            >
              {list.map((item, index) => (
                <Draggable key={item.id} draggableId={item.id} index={index}>
                  {(provided, snapshot) => (
                    <div
                      ref={provided.innerRef}
                      {...provided.draggableProps}
                      {...provided.dragHandleProps}
                      style={getItemStyle(
                        snapshot.isDragging,
                        provided.draggableProps.style,
                      )}
                    >
                      {item.content}
                    </div>
                  )}
                </Draggable>
              ))}
              {provided.placeholder}
            </div>
          )}
        </Droppable>
      </DragDropContext>
    );
  }
Example #14
Source File: StatOptionList.tsx    From slippi-stats with MIT License 5 votes vote down vote up
StatOptionList: React.FC<StatOptionListProps> = (props) => {
  const statOptions = props.value;
  const onDragEnd = (result: any) => {
    const { destination, source } = result;
    if (!destination) {
      return;
    }
    if (destination.droppableId === source.droppableId && destination.index === source.index) {
      return;
    }
    const newArray = reorder(props.value, source.index, destination.index);
    props.onChange(newArray);
  };

  const toggle = (statId: string) => {
    const optionIndex = statOptions.findIndex((o) => o.statId === statId);
    if (optionIndex === -1) {
      return;
    }
    const newOptions = Array.from(statOptions);
    const option = newOptions[optionIndex];
    option.enabled = !option.enabled;
    props.onChange(newOptions);
  };
  return (
    <DragDropContext onDragEnd={onDragEnd}>
      <Droppable droppableId="stat-option-list">
        {(provided) => (
          <div ref={provided.innerRef} {...provided.droppableProps}>
            {statOptions.map((option, i) => {
              return (
                <StatOptionItem
                  key={option.statId}
                  index={i}
                  id={option.statId}
                  checked={option.enabled}
                  onChange={() => toggle(option.statId)}
                />
              );
            })}
            {provided.placeholder}
          </div>
        )}
      </Droppable>
    </DragDropContext>
  );
}
Example #15
Source File: index.tsx    From peterportal-client with MIT License 4 votes vote down vote up
RoadmapPage: FC = () => {
  const dispatch = useAppDispatch();
  const showSearch = useAppSelector(state => state.roadmap.showSearch);

  const onDragEnd = useCallback((result: DropResult) => {
    if (result.reason === 'DROP') {
      // no destination or dragging to search bar
      if (!result.destination || result.destination.droppableId === 'search') {
        // removing from quarter
        if (result.source.droppableId != 'search') {
          let [yearIndex, quarterIndex] = result.source.droppableId.split('-');
          dispatch(deleteCourse({
            yearIndex: parseInt(yearIndex),
            quarterIndex: parseInt(quarterIndex),
            courseIndex: result.source.index
          }))
        }
        return;
      }

      let movePayload = {
        from: {
          yearIndex: -1,
          quarterIndex: -1,
          courseIndex: -1,
        },
        to: {
          yearIndex: -1,
          quarterIndex: -1,
          courseIndex: -1
        }
      };

      console.log(result.source.droppableId, '=>', result.destination.droppableId)

      // roadmap to roadmap has source
      if (result.source.droppableId != 'search') {
        let [yearIndex, quarterIndex] = result.source.droppableId.split('-');
        movePayload.from.yearIndex = parseInt(yearIndex);
        movePayload.from.quarterIndex = parseInt(quarterIndex);
        movePayload.from.courseIndex = result.source.index;
      }
      // search to roadmap has no source (use activeCourse in global state)      

      // both have destination
      let [yearIndex, quarterIndex] = result.destination.droppableId.split('-');
      movePayload.to.yearIndex = parseInt(yearIndex);
      movePayload.to.quarterIndex = parseInt(quarterIndex);
      movePayload.to.courseIndex = result.destination.index;

      console.log(movePayload);
      dispatch(moveCourse(movePayload));
    }
  }, []);

  const onDragUpdate = useCallback((initial: DragUpdate) => {
    console.log(initial)
  }, []);

  // do not conditionally renderer because it would remount planner which would discard unsaved changes
  const mobileVersion = <>
    <div className={`main-wrapper mobile ${showSearch ? 'hide' : ''}`}>
      <Planner />
    </div>
    <div className={`sidebar-wrapper mobile ${!showSearch ? 'hide' : ''}`}>
      <SearchSidebar />
    </div>
  </>

  const desktopVersion = <>
    <div className='main-wrapper'>
      <Planner />
    </div>
    <div className='sidebar-wrapper'>
      <SearchSidebar />
    </div>
  </>

  return (
    <>
      <div className='roadmap-page'>
        <AddCoursePopup />
        <DragDropContext onDragEnd={onDragEnd} onDragUpdate={onDragUpdate}>
          {isMobile && mobileVersion}
          {!isMobile && desktopVersion}
        </DragDropContext>
      </div>
    </>
  );
}
Example #16
Source File: index.tsx    From scorpio-h5-design with MIT License 4 votes vote down vote up
export default function() {
  const [isOrdering, setIsOrdering] = useBoolean(false);
  const {
    isDraging, pageSchema, selectPageIndex, dragingComponentIndex, selectComponent,
    onDragEnter, onDragLeave, onDrop, onSelectComponent, onSortEnd, showSelectComponentBorder,
  } = useModel('bridge');
  let components:any[] = [];
  if (pageSchema.length > 0 && selectPageIndex !== -1) {
    components = pageSchema[selectPageIndex].components;
  }

  return (
    <DragDropContext
      onDragStart={()=>{
        setIsOrdering.setTrue();
      }}
      onDragEnd={(result: DropResult)=>{
        onSortEnd(result, components);
        setIsOrdering.setFalse();
      }}
    >
      <Droppable droppableId="droppable">
        {(provided, snapshot) => (
          <div
            {...provided.droppableProps}
            ref={provided.innerRef}
          >
            {components.length > 0 && components.map((item: any, index: any) => (
              <Draggable key={item.uuid} draggableId={item.uuid} index={index}>
                {(provided, snapshot) => (
                  <>
                    <div
                      className={classnames(
                        'h5-canvas-empty-block',
                        {
                          show: isDraging,
                          over: dragingComponentIndex === index,
                        }
                      )}
                      onDragEnter={() => { onDragEnter(index); }}
                      onDragLeave={() => { onDragLeave(); }}
                      onDragOver={(event) => { event.preventDefault(); }}
                      onDrop={(event)=>{onDrop(event, index);}}
                    />
                    <div
                      className={classnames(
                        'h5-canvas-block',
                        {
                          'blur': !snapshot.isDragging && isOrdering,
                          'isSelected': selectComponent?.uuid === item.uuid,
                          'border': selectComponent?.uuid === item.uuid && showSelectComponentBorder,
                        }
                      )}
                      ref={provided.innerRef}
                      {...provided.draggableProps}
                      {...provided.dragHandleProps}
                      onClick={() => { onSelectComponent(item.uuid); }}
                    >
                      <DynamicComponent
                        id={item._id}
                        uuid={item.uuid}
                        isSelected={selectComponent?.uuid === item.uuid}
                        componentProps={item.props}
                        containerProps={item.containerProps}
                      />
                    </div>
                  </>
                )}
              </Draggable>
            ))}
            {provided.placeholder}
            <div
              className={classnames(
                'h5-canvas-empty-block',
                {
                  show: isDraging,
                  over: dragingComponentIndex === components.length,
                }
              )}
              onDragEnter={() => { onDragEnter(components.length); }}
              onDragLeave={() => { onDragLeave(); }}
              onDragOver={(event) => { event.preventDefault(); }}
              onDrop={(event)=>{onDrop(event, components.length);}}
            />
          </div>
        )}
      </Droppable>
    </DragDropContext>
  );
}
Example #17
Source File: AppTable.tsx    From flame with MIT License 4 votes vote down vote up
AppTable = (props: Props): JSX.Element => {
  const {
    apps: { apps },
    config: { config },
  } = useSelector((state: State) => state);

  const dispatch = useDispatch();
  const { pinApp, deleteApp, reorderApps, createNotification, updateApp } =
    bindActionCreators(actionCreators, dispatch);

  const [localApps, setLocalApps] = useState<App[]>([]);

  // Copy apps array
  useEffect(() => {
    setLocalApps([...apps]);
  }, [apps]);

  const dragEndHanlder = (result: DropResult): void => {
    if (config.useOrdering !== 'orderId') {
      createNotification({
        title: 'Error',
        message: 'Custom order is disabled',
      });
      return;
    }

    if (!result.destination) {
      return;
    }

    const tmpApps = [...localApps];
    const [movedApp] = tmpApps.splice(result.source.index, 1);
    tmpApps.splice(result.destination.index, 0, movedApp);

    setLocalApps(tmpApps);
    reorderApps(tmpApps);
  };

  // Action handlers
  const deleteAppHandler = (id: number, name: string) => {
    const proceed = window.confirm(`Are you sure you want to delete ${name}?`);

    if (proceed) {
      deleteApp(id);
    }
  };

  const updateAppHandler = (id: number) => {
    const app = apps.find((a) => a.id === id) as App;
    props.openFormForUpdating(app);
  };

  const pinAppHandler = (id: number) => {
    const app = apps.find((a) => a.id === id) as App;
    pinApp(app);
  };

  const changeAppVisibiltyHandler = (id: number) => {
    const app = apps.find((a) => a.id === id) as App;
    updateApp(id, { ...app, isPublic: !app.isPublic });
  };

  return (
    <Fragment>
      <Message isPrimary={false}>
        {config.useOrdering === 'orderId' ? (
          <p>You can drag and drop single rows to reorder application</p>
        ) : (
          <p>
            Custom order is disabled. You can change it in the{' '}
            <Link to="/settings/general">settings</Link>
          </p>
        )}
      </Message>

      <DragDropContext onDragEnd={dragEndHanlder}>
        <Droppable droppableId="apps">
          {(provided) => (
            <Table
              headers={['Name', 'URL', 'Icon', 'Visibility', 'Actions']}
              innerRef={provided.innerRef}
            >
              {localApps.map((app: App, index): JSX.Element => {
                return (
                  <Draggable
                    key={app.id}
                    draggableId={app.id.toString()}
                    index={index}
                  >
                    {(provided, snapshot) => {
                      const style = {
                        border: snapshot.isDragging
                          ? '1px solid var(--color-accent)'
                          : 'none',
                        borderRadius: '4px',
                        ...provided.draggableProps.style,
                      };

                      return (
                        <tr
                          {...provided.draggableProps}
                          {...provided.dragHandleProps}
                          ref={provided.innerRef}
                          style={style}
                        >
                          <td style={{ width: '200px' }}>{app.name}</td>
                          <td style={{ width: '200px' }}>{app.url}</td>
                          <td style={{ width: '200px' }}>{app.icon}</td>
                          <td style={{ width: '200px' }}>
                            {app.isPublic ? 'Visible' : 'Hidden'}
                          </td>

                          {!snapshot.isDragging && (
                            <TableActions
                              entity={app}
                              deleteHandler={deleteAppHandler}
                              updateHandler={updateAppHandler}
                              pinHanlder={pinAppHandler}
                              changeVisibilty={changeAppVisibiltyHandler}
                            />
                          )}
                        </tr>
                      );
                    }}
                  </Draggable>
                );
              })}
            </Table>
          )}
        </Droppable>
      </DragDropContext>
    </Fragment>
  );
}
Example #18
Source File: Array.tsx    From payload with MIT License 4 votes vote down vote up
ArrayFieldType: React.FC<Props> = (props) => {
  const {
    name,
    path: pathFromProps,
    fields,
    fieldTypes,
    validate = array,
    required,
    maxRows,
    minRows,
    permissions,
    admin: {
      readOnly,
      description,
      condition,
      className,
    },
  } = props;

  // Handle labeling for Arrays, Global Arrays, and Blocks
  const getLabels = (p: Props) => {
    if (p?.labels) return p.labels;
    if (p?.label) return { singular: p.label, plural: undefined };
    return { singular: 'Row', plural: 'Rows' };
  };

  const labels = getLabels(props);
  // eslint-disable-next-line react/destructuring-assignment
  const label = props?.label ?? props?.labels?.singular;

  const [rows, dispatchRows] = useReducer(reducer, []);
  const formContext = useForm();
  const { user } = useAuth();
  const { id } = useDocumentInfo();
  const locale = useLocale();
  const operation = useOperation();

  const { dispatchFields } = formContext;

  const path = pathFromProps || name;

  const memoizedValidate = useCallback((value, options) => {
    return validate(value, { ...options, minRows, maxRows, required });
  }, [maxRows, minRows, required, validate]);

  const [disableFormData, setDisableFormData] = useState(false);

  const {
    showError,
    errorMessage,
    value,
    setValue,
  } = useField({
    path,
    validate: memoizedValidate,
    disableFormData,
    condition,
  });

  const addRow = useCallback(async (rowIndex) => {
    const subFieldState = await buildStateFromSchema({ fieldSchema: fields, operation, id, user, locale });
    dispatchFields({ type: 'ADD_ROW', rowIndex, subFieldState, path });
    dispatchRows({ type: 'ADD', rowIndex });
    setValue(value as number + 1);
  }, [dispatchRows, dispatchFields, fields, path, setValue, value, operation, id, user, locale]);

  const removeRow = useCallback((rowIndex) => {
    dispatchRows({ type: 'REMOVE', rowIndex });
    dispatchFields({ type: 'REMOVE_ROW', rowIndex, path });
    setValue(value as number - 1);
  }, [dispatchRows, dispatchFields, path, value, setValue]);

  const moveRow = useCallback((moveFromIndex, moveToIndex) => {
    dispatchRows({ type: 'MOVE', moveFromIndex, moveToIndex });
    dispatchFields({ type: 'MOVE_ROW', moveFromIndex, moveToIndex, path });
  }, [dispatchRows, dispatchFields, path]);

  const onDragEnd = useCallback((result) => {
    if (!result.destination) return;
    const sourceIndex = result.source.index;
    const destinationIndex = result.destination.index;
    moveRow(sourceIndex, destinationIndex);
  }, [moveRow]);

  useEffect(() => {
    const data = formContext.getDataByPath(path);
    dispatchRows({ type: 'SET_ALL', data: data || [] });
  }, [formContext, path]);

  useEffect(() => {
    setValue(rows?.length || 0, true);

    if (rows?.length === 0) {
      setDisableFormData(false);
    } else {
      setDisableFormData(true);
    }
  }, [rows, setValue]);

  const hasMaxRows = maxRows && rows.length >= maxRows;

  const classes = [
    baseClass,
    className,
  ].filter(Boolean).join(' ');

  return (
    <DragDropContext onDragEnd={onDragEnd}>
      <div
        className={classes}
      >
        <div className={`${baseClass}__error-wrap`}>
          <Error
            showError={showError}
            message={errorMessage}
          />
        </div>
        <header className={`${baseClass}__header`}>
          <h3>{label}</h3>
          <FieldDescription
            value={value}
            description={description}
          />
        </header>
        <Droppable droppableId="array-drop">
          {(provided) => (
            <div
              ref={provided.innerRef}
              {...provided.droppableProps}
            >
              {rows.length > 0 && rows.map((row, i) => (
                <DraggableSection
                  readOnly={readOnly}
                  key={row.id}
                  id={row.id}
                  blockType="array"
                  label={labels.singular}
                  rowCount={rows.length}
                  rowIndex={i}
                  addRow={addRow}
                  removeRow={removeRow}
                  moveRow={moveRow}
                  parentPath={path}
                  fieldTypes={fieldTypes}
                  fieldSchema={fields}
                  permissions={permissions}
                  hasMaxRows={hasMaxRows}
                />
              ))}
              {(rows.length < minRows || (required && rows.length === 0)) && (
                <Banner type="error">
                  This field requires at least
                  {' '}
                  {minRows
                    ? `${minRows} ${labels.plural}`
                    : `1 ${labels.singular}`}
                </Banner>
              )}
              {(rows.length === 0 && readOnly) && (
                <Banner>
                  This field has no
                  {' '}
                  {labels.plural}
                  .
                </Banner>
              )}
              {provided.placeholder}
            </div>
          )}
        </Droppable>
        {(!readOnly && (!hasMaxRows)) && (
          <div className={`${baseClass}__add-button-wrap`}>
            <Button
              onClick={() => addRow(value)}
              buttonStyle="icon-label"
              icon="plus"
              iconStyle="with-border"
              iconPosition="left"
            >
              {`Add ${labels.singular}`}
            </Button>
          </div>
        )}
      </div>
    </DragDropContext>
  );
}
Example #19
Source File: Categories.tsx    From revite with GNU Affero General Public License v3.0 4 votes vote down vote up
Categories = observer(({ server }: Props) => {
    const [status, setStatus] = useState<EditStatus>("saved");
    const [categories, setCategories] = useState<API.Category[]>(
        server.categories ?? [],
    );

    useAutosave(
        async () => {
            setStatus("saving");
            await server.edit({ categories });
            setStatus("saved");
        },
        categories,
        server.categories,
        () => setStatus("editing"),
    );

    const defaultCategory = useMemo(() => {
        return {
            title: "Uncategorized",
            channels: [...server.channels]
                .filter((x) => x)
                .map((x) => x!._id)
                .filter(
                    (x) => !categories.find((cat) => cat.channels.includes(x)),
                ),
            id: "none",
        };
    }, [categories, server.channels]);

    return (
        <>
            <Header>
                <h1>
                    <Text id={`app.settings.server_pages.categories.title`} />
                </h1>
                <SaveStatus status={status} />
            </Header>
            <DragDropContext
                onDragEnd={(target) => {
                    const { destination, source, draggableId, type } = target;

                    if (
                        !destination ||
                        (destination.droppableId === source.droppableId &&
                            destination.index === source.index)
                    ) {
                        return;
                    }

                    if (type === "column") {
                        if (destination.index === 0) return;

                        // Remove from array.
                        const cat = categories.find(
                            (x) => x.id === draggableId,
                        );
                        const arr = categories.filter(
                            (x) => x.id !== draggableId,
                        );

                        // Insert at new position.
                        arr.splice(destination.index - 1, 0, cat!);
                        setCategories(arr);
                    } else {
                        setCategories(
                            categories.map((category) => {
                                if (category.id === destination.droppableId) {
                                    const channels = category.channels.filter(
                                        (x) => x !== draggableId,
                                    );

                                    channels.splice(
                                        destination.index,
                                        0,
                                        draggableId,
                                    );

                                    return {
                                        ...category,
                                        channels,
                                    };
                                } else if (category.id === source.droppableId) {
                                    return {
                                        ...category,
                                        channels: category.channels.filter(
                                            (x) => x !== draggableId,
                                        ),
                                    };
                                }

                                return category;
                            }),
                        );
                    }
                }}>
                <FullSize>
                    <Droppable
                        droppableId="categories"
                        direction="horizontal"
                        type="column">
                        {(provided) => (
                            <div
                                ref={provided.innerRef}
                                {...provided.droppableProps}>
                                <KanbanBoard>
                                    <ListElement
                                        category={defaultCategory}
                                        server={server}
                                        index={0}
                                        addChannel={noop}
                                    />
                                    {categories.map((category, index) => (
                                        <ListElement
                                            draggable
                                            category={category}
                                            server={server}
                                            index={index + 1}
                                            key={category.id}
                                            setTitle={(title) => {
                                                setCategories(
                                                    categories.map((x) =>
                                                        x.id === category.id
                                                            ? {
                                                                  ...x,
                                                                  title,
                                                              }
                                                            : x,
                                                    ),
                                                );
                                            }}
                                            deleteSelf={() =>
                                                setCategories(
                                                    categories.filter(
                                                        (x) =>
                                                            x.id !==
                                                            category.id,
                                                    ),
                                                )
                                            }
                                            addChannel={(channel) => {
                                                setCategories(
                                                    categories.map((x) =>
                                                        x.id === category.id
                                                            ? {
                                                                  ...x,
                                                                  channels: [
                                                                      ...x.channels,
                                                                      channel._id,
                                                                  ],
                                                              }
                                                            : x,
                                                    ),
                                                );
                                            }}
                                        />
                                    ))}
                                    <KanbanList last>
                                        <div class="inner">
                                            <KanbanListHeader
                                                onClick={() =>
                                                    setCategories([
                                                        ...categories,
                                                        {
                                                            id: ulid(),
                                                            title: "New Category",
                                                            channels: [],
                                                        },
                                                    ])
                                                }>
                                                <Plus size={24} />
                                            </KanbanListHeader>
                                        </div>
                                    </KanbanList>
                                    {provided.placeholder}
                                </KanbanBoard>
                            </div>
                        )}
                    </Droppable>
                </FullSize>
            </DragDropContext>
        </>
    );
})
Example #20
Source File: Board.tsx    From knboard with MIT License 4 votes vote down vote up
Board = () => {
  const detail = useSelector((state: RootState) => state.board.detail);
  const error = useSelector((state: RootState) => state.board.detailError);
  const columns = useSelector(columnSelectors.selectAll);
  const tasksByColumn = useSelector((state: RootState) => state.task.byColumn);
  const tasksById = useSelector((state: RootState) => state.task.byId);
  const dispatch = useDispatch();
  const { id } = useParams();

  React.useEffect(() => {
    if (id) {
      dispatch(fetchBoardById({ boardId: id }));
    }
  }, [id]);

  const onDragEnd = (result: DropResult) => {
    // dropped nowhere
    if (!result.destination) {
      return;
    }

    const source: DraggableLocation = result.source;
    const destination: DraggableLocation = result.destination;

    // did not move anywhere - can bail early
    if (
      source.droppableId === destination.droppableId &&
      source.index === destination.index
    ) {
      return;
    }

    // reordering column
    if (result.type === "COLUMN") {
      const newOrdered: IColumn[] = reorder(
        columns,
        source.index,
        destination.index
      );
      dispatch(updateColumns(newOrdered));
      return;
    }

    const data = reorderTasks({
      tasksByColumn,
      source,
      destination,
    });
    dispatch(updateTasksByColumn(data.tasksByColumn));
  };

  const detailDataExists = detail?.id.toString() === id;

  if (error) {
    return <PageError>{error}</PageError>;
  }

  if (!detailDataExists) {
    return <Spinner loading={!detailDataExists} />;
  }

  return (
    <>
      <SEO title={detail?.name} />
      {columns.length !== 0 ? (
        <BoardContainer data-testid="board-container">
          <ColumnsBlock>
            <DragDropContext onDragEnd={onDragEnd}>
              <Droppable
                droppableId="board"
                type="COLUMN"
                direction="horizontal"
              >
                {(provided: DroppableProvided) => (
                  <ColumnContainer
                    ref={provided.innerRef}
                    {...provided.droppableProps}
                  >
                    {columns.map((column: IColumn, index: number) => (
                      <Column
                        key={column.id}
                        id={column.id}
                        title={column.title}
                        index={index}
                        tasks={tasksByColumn[column.id].map(
                          (taskId) => tasksById[taskId]
                        )}
                      />
                    ))}
                    {provided.placeholder}
                  </ColumnContainer>
                )}
              </Droppable>
            </DragDropContext>
          </ColumnsBlock>
          <RightMargin />
        </BoardContainer>
      ) : (
        <EmptyBoard>This board is empty.</EmptyBoard>
      )}
    </>
  );
}
Example #21
Source File: index.tsx    From nanolooker with MIT License 4 votes vote down vote up
CryptocurrencyPreferences: React.FC<Props> = ({ isDetailed }) => {
  const { t } = useTranslation();
  const {
    cryptocurrency,
    addCryptocurrency,
    removeCryptocurrency,
    reorderCryptocurrency,
  } = React.useContext(PreferencesContext);
  const [search, setSearch] = React.useState<string>("");

  const onSearch = (value: string) => {
    setSearch(value);
  };

  const onSelect = (value: string) => {
    const { symbol = "" } = dataSource.find(({ name }) => name === value) || {};

    addCryptocurrency(symbol);
    setSearch("");
  };

  const options = dataSource.map(({ name, symbol }) => (
    <Option
      key={name}
      value={name}
      symbol={symbol}
      disabled={cryptocurrency.includes(symbol) || symbol === "nano"}
    >
      <img
        src={`/cryptocurrencies/logo/${symbol}.png`}
        alt={name}
        width="16px"
        height="16px"
        style={{ marginRight: "6px" }}
      />
      {name}
    </Option>
  ));

  const reorder = (list: string[], startIndex: number, endIndex: number) => {
    const result = Array.from(list);
    const [removed] = result.splice(startIndex, 1);
    result.splice(endIndex, 0, removed);

    return result;
  };

  const onDragEnd = (result: any) => {
    const items = reorder(
      cryptocurrency,
      result.source?.index || 0,
      result.destination?.index || 0,
    );
    reorderCryptocurrency(items);
  };

  return (
    <Row>
      <Col xs={24}>
        <Text
          className={isDetailed ? "preference-detailed-title" : ""}
          style={{
            display: "block",
            marginBottom: "6px",
          }}
        >
          {t("preferences.watch")}
        </Text>
      </Col>
      {isDetailed ? (
        <Col xs={24} style={{ marginBottom: "6px" }}>
          <Text>{t("preferences.watchDetailed")}</Text>
        </Col>
      ) : null}

      <Col xs={24} md={isDetailed ? 12 : 24}>
        <AutoComplete
          value={search}
          style={{ width: "100%" }}
          filterOption={(value = "", option) => {
            const { value: name, symbol } = option as any;

            return (
              name.toLowerCase().includes(value.toLowerCase()) ||
              symbol.toLowerCase().includes(value.toLowerCase())
            );
          }}
          onSearch={onSearch}
          onSelect={onSelect}
          placeholder={t("preferences.watchSearch")}
        >
          {options}
        </AutoComplete>

        {cryptocurrency.length ? (
          <DragDropContext onDragEnd={onDragEnd}>
            <Droppable droppableId={`hello${isDetailed ? "-detailed" : ""}`}>
              {(provided, snapshot) => (
                <ul
                  style={{
                    margin: 0,
                    padding: "6px",
                    marginTop: "6px",
                    backgroundColor: snapshot.isDraggingOver
                      ? "#1890ff24"
                      : "#f6f6f6",
                    listStyle: "none",
                  }}
                  ref={provided.innerRef}
                  {...provided.droppableProps}
                >
                  {cryptocurrency.map((symbol, index) => {
                    const { name = "" } =
                      dataSource.find(
                        ({ symbol: sourceSymbol }) => sourceSymbol === symbol,
                      ) || {};
                    return (
                      <Draggable draggableId={name} index={index} key={name}>
                        {provided => {
                          // https://github.com/atlassian/react-beautiful-dnd/issues/1662#issuecomment-708538811
                          if (
                            typeof provided.draggableProps.onTransitionEnd ===
                            "function"
                          ) {
                            window?.requestAnimationFrame(() =>
                              // @ts-ignore
                              provided?.draggableProps?.onTransitionEnd?.({
                                propertyName: "transform",
                              }),
                            );
                          }

                          return (
                            <li
                              {...provided.draggableProps}
                              {...provided.dragHandleProps}
                              ref={provided.innerRef}
                            >
                              <div
                                style={{
                                  display: "flex",
                                  justifyContent: "space-between",
                                  alignItems: "center",
                                  width: "100%",
                                  padding: "6px",
                                  marginTop: "-1px",
                                  backgroundColor: "#fff",
                                  border: "1px solid #d9d9d9",
                                  ...(index !== cryptocurrency.length - 1
                                    ? { marginBottom: "6px" }
                                    : { marginBottom: "-1px" }),
                                }}
                              >
                                <span>
                                  <img
                                    src={`/cryptocurrencies/logo/${symbol}.png`}
                                    alt={name}
                                    width="16px"
                                    height="16px"
                                    style={{ marginRight: "6px" }}
                                  />
                                  {name}
                                </span>
                                <DeleteButton
                                  onClick={(e: Event) => {
                                    e.stopPropagation();
                                    removeCryptocurrency(symbol);
                                  }}
                                />
                              </div>
                            </li>
                          );
                        }}
                      </Draggable>
                    );
                  })}
                  {provided.placeholder}
                </ul>
              )}
            </Droppable>
          </DragDropContext>
        ) : null}
      </Col>
    </Row>
  );
}
Example #22
Source File: DataModelTree.tsx    From datart with Apache License 2.0 4 votes vote down vote up
DataModelTree: FC = memo(() => {
  const t = useI18NPrefix('view');
  const { actions } = useViewSlice();
  const dispatch = useDispatch();
  const [openStateModal, contextHolder] = useStateModal({});
  const currentEditingView = useSelector(selectCurrentEditingView);
  const stage = useSelector(state =>
    selectCurrentEditingViewAttr(state, { name: 'stage' }),
  ) as ViewViewModelStages;
  const [hierarchy, setHierarchy] = useState<Nullable<Model>>();

  useEffect(() => {
    setHierarchy(currentEditingView?.model?.hierarchy);
  }, [currentEditingView?.model?.hierarchy]);

  const tableColumns = useMemo<Column[]>(() => {
    return Object.entries(hierarchy || {})
      .map(([name, column], index) => {
        return Object.assign({ index }, column, { name });
      })
      .sort(dataModelColumnSorter);
  }, [hierarchy]);

  const handleDeleteBranch = (node: Column) => {
    const newHierarchy = deleteBranch(tableColumns, node);
    handleDataModelHierarchyChange(newHierarchy);
  };

  const handleDeleteFromBranch = (parent: Column) => (node: Column) => {
    const newHierarchy = deleteFromBranch(tableColumns, parent, node);
    handleDataModelHierarchyChange(newHierarchy);
  };

  const handleNodeTypeChange = (type, name) => {
    const targetNode = tableColumns?.find(n => n.name === name);
    if (targetNode) {
      let newNode;
      if (type.includes('category')) {
        const category = type.split('-')[1];
        newNode = { ...targetNode, category };
      } else {
        newNode = { ...targetNode, type: type };
      }
      const newHierarchy = updateNode(
        tableColumns,
        newNode,
        tableColumns?.findIndex(n => n.name === name),
      );
      handleDataModelHierarchyChange(newHierarchy);
      return;
    }
    const targetBranch = tableColumns?.find(b =>
      b?.children?.find(bn => bn.name === name),
    );
    if (!!targetBranch) {
      const newNodeIndex = targetBranch.children?.findIndex(
        bn => bn.name === name,
      );
      if (newNodeIndex !== undefined && newNodeIndex > -1) {
        const newTargetBranch = CloneValueDeep(targetBranch);
        if (newTargetBranch.children) {
          let newNode = newTargetBranch.children[newNodeIndex];
          if (type.includes('category')) {
            const category = type.split('-')[1];
            newNode = { ...newNode, category };
          } else {
            newNode = { ...newNode, type: type };
          }
          newTargetBranch.children[newNodeIndex] = newNode;
          const newHierarchy = updateNode(
            tableColumns,
            newTargetBranch,
            tableColumns.findIndex(n => n.name === newTargetBranch.name),
          );
          handleDataModelHierarchyChange(newHierarchy);
        }
      }
    }
  };

  const handleDataModelHierarchyChange = hierarchy => {
    setHierarchy(hierarchy);
    dispatch(
      actions.changeCurrentEditingView({
        model: {
          ...currentEditingView?.model,
          hierarchy,
          version: APP_CURRENT_VERSION,
        },
      }),
    );
  };

  const handleDragEnd = result => {
    if (Boolean(result.destination) && isEmpty(result?.combine)) {
      const newHierarchy = reorderNode(
        CloneValueDeep(tableColumns),
        { name: result.draggableId },
        {
          name: result.destination.droppableId,
          index: result.destination.index,
        },
      );
      return handleDataModelHierarchyChange(newHierarchy);
    }
    if (!Boolean(result.destination) && !isEmpty(result?.combine)) {
      const clonedTableColumns = CloneValueDeep(tableColumns);
      const sourceNode = clonedTableColumns?.find(
        c => c.name === result.draggableId,
      );
      const targetNode = clonedTableColumns?.find(
        c => c.name === result.combine.draggableId,
      );
      if (
        sourceNode &&
        sourceNode.role !== ColumnRole.Hierarchy &&
        targetNode &&
        targetNode.role !== ColumnRole.Hierarchy &&
        ALLOW_COMBINE_COLUMN_TYPES.includes(sourceNode.type) &&
        ALLOW_COMBINE_COLUMN_TYPES.includes(targetNode.type)
      ) {
        return openCreateHierarchyModal(sourceNode, targetNode);
      } else if (
        sourceNode &&
        sourceNode.role !== ColumnRole.Hierarchy &&
        targetNode &&
        targetNode.role === ColumnRole.Hierarchy &&
        ALLOW_COMBINE_COLUMN_TYPES.includes(sourceNode.type)
      ) {
        const newHierarchy = reorderNode(
          clonedTableColumns,
          { name: result.draggableId },
          {
            name: result.combine.draggableId,
            index: -1,
          },
        );
        return handleDataModelHierarchyChange(newHierarchy);
      }
    }
  };

  const openCreateHierarchyModal = (...nodes: Column[]) => {
    return (openStateModal as Function)({
      title: t('model.newHierarchy'),
      modalSize: StateModalSize.XSMALL,
      onOk: hierarchyName => {
        if (!hierarchyName) {
          return;
        }
        const hierarchyNode: Column = {
          name: hierarchyName,
          type: DataViewFieldType.STRING,
          role: ColumnRole.Hierarchy,
          children: nodes,
        };
        const newHierarchy = insertNode(tableColumns, hierarchyNode, nodes);
        handleDataModelHierarchyChange(newHierarchy);
      },
      content: onChangeEvent => {
        const allNodeNames = tableColumns?.flatMap(c => {
          if (!isEmptyArray(c.children)) {
            return [c.name].concat(c.children?.map(cc => cc.name) || []);
          }
          return c.name;
        });
        return (
          <StyledFormItem
            label={t('model.hierarchyName')}
            name="hierarchyName"
            rules={[
              { required: true },
              ({ getFieldValue }) => ({
                validator(_, value) {
                  if (!allNodeNames.includes(getFieldValue('hierarchyName'))) {
                    return Promise.resolve(value);
                  }
                  return Promise.reject(new Error('名称重复,请检查!'));
                },
              }),
            ]}
          >
            <Input onChange={e => onChangeEvent(e.target?.value)} />
          </StyledFormItem>
        );
      },
    });
  };

  const openMoveToHierarchyModal = (node: Column) => {
    const currentHierarchies = tableColumns?.filter(
      c =>
        c.role === ColumnRole.Hierarchy &&
        !c?.children?.find(cn => cn.name === node.name),
    );

    return (openStateModal as Function)({
      title: t('model.addToHierarchy'),
      modalSize: StateModalSize.XSMALL,
      onOk: hierarchyName => {
        if (currentHierarchies?.find(h => h.name === hierarchyName)) {
          let newHierarchy = moveNode(
            tableColumns,
            node,
            currentHierarchies,
            hierarchyName,
          );
          handleDataModelHierarchyChange(newHierarchy);
        }
      },
      content: onChangeEvent => {
        return (
          <StyledFormItem
            label={t('model.hierarchyName')}
            name="hierarchyName"
            rules={[{ required: true }]}
          >
            <Select defaultActiveFirstOption onChange={onChangeEvent}>
              {currentHierarchies?.map(n => (
                <Select.Option value={n.name}>{n.name}</Select.Option>
              ))}
            </Select>
          </StyledFormItem>
        );
      },
    });
  };

  const openEditBranchModal = (node: Column) => {
    const allNodeNames = tableColumns
      ?.flatMap(c => {
        if (!isEmptyArray(c.children)) {
          return c.children?.map(cc => cc.name);
        }
        return c.name;
      })
      .filter(n => n !== node.name);

    return (openStateModal as Function)({
      title: t('model.rename'),
      modalSize: StateModalSize.XSMALL,
      onOk: newName => {
        if (!newName) {
          return;
        }
        const newHierarchy = updateNode(
          tableColumns,
          { ...node, name: newName },
          tableColumns.findIndex(n => n.name === node.name),
        );
        handleDataModelHierarchyChange(newHierarchy);
      },
      content: onChangeEvent => {
        return (
          <StyledFormItem
            label={t('model.rename')}
            initialValue={node?.name}
            name="rename"
            rules={[
              { required: true },
              ({ getFieldValue }) => ({
                validator(_, value) {
                  if (!allNodeNames.includes(getFieldValue('rename'))) {
                    return Promise.resolve(value);
                  }
                  return Promise.reject(new Error('名称重复,请检查!'));
                },
              }),
            ]}
          >
            <Input
              onChange={e => {
                onChangeEvent(e.target?.value);
              }}
            />
          </StyledFormItem>
        );
      },
    });
  };

  const reorderNode = (
    columns: Column[],
    source: { name: string },
    target: { name: string; index: number },
  ) => {
    let sourceItem: Column | undefined;
    const removeIndex = columns.findIndex(c => c.name === source.name);
    if (removeIndex > -1) {
      sourceItem = columns.splice(removeIndex, 1)?.[0];
    } else {
      const branchNode = columns.filter(
        c =>
          c.role === ColumnRole.Hierarchy &&
          c.children?.find(cc => cc.name === source.name),
      )?.[0];
      if (!branchNode) {
        return toModel(columns);
      }
      const removeIndex = branchNode.children!.findIndex(
        c => c.name === source.name,
      );
      if (removeIndex === -1) {
        return toModel(columns);
      }
      sourceItem = branchNode.children?.splice(removeIndex, 1)?.[0];
    }

    if (!sourceItem) {
      return toModel(columns);
    }

    if (target.name === ROOT_CONTAINER_ID) {
      columns.splice(target.index, 0, sourceItem);
    } else {
      const branchNode = columns.filter(
        c => c.role === ColumnRole.Hierarchy && c.name === target.name,
      )?.[0];
      if (!branchNode) {
        return toModel(columns);
      }
      if (target.index === -1) {
        branchNode.children!.push(sourceItem);
      } else {
        branchNode.children!.splice(target.index, 0, sourceItem);
      }
    }
    return toModel(columns);
  };

  const insertNode = (columns: Column[], newNode, nodes: Column[]) => {
    const newColumns = columns.filter(
      c => !nodes.map(n => n.name).includes(c.name),
    );
    newColumns.unshift(newNode);
    return toModel(newColumns);
  };

  const updateNode = (columns: Column[], newNode, columnIndexes) => {
    columns[columnIndexes] = newNode;
    return toModel(columns);
  };

  const deleteBranch = (columns: Column[], node: Column) => {
    const deletedBranchIndex = columns.findIndex(c => c.name === node.name);
    if (deletedBranchIndex > -1) {
      const branch = columns[deletedBranchIndex];
      const children = branch?.children || [];
      columns.splice(deletedBranchIndex, 1);
      return toModel(columns, ...children);
    }
  };

  const deleteFromBranch = (
    columns: Column[],
    parent: Column,
    node: Column,
  ) => {
    const branchNode = columns.find(c => c.name === parent.name);
    if (branchNode) {
      branchNode.children = branchNode.children?.filter(
        c => c.name !== node.name,
      );
      return toModel(columns, node);
    }
  };

  const moveNode = (
    columns: Column[],
    node: Column,
    currentHierarchies: Column[],
    hierarchyName,
  ) => {
    const nodeIndex = columns?.findIndex(c => c.name === node.name);
    if (nodeIndex !== undefined && nodeIndex > -1) {
      columns.splice(nodeIndex, 1);
    } else {
      const branch = columns?.find(c =>
        c.children?.find(cc => cc.name === node.name),
      );
      if (branch) {
        branch.children =
          branch.children?.filter(bc => bc.name !== node.name) || [];
      }
    }
    const targetHierarchy = currentHierarchies?.find(
      h => h.name === hierarchyName,
    );
    const clonedHierarchy = CloneValueDeep(targetHierarchy!);
    clonedHierarchy.children = (clonedHierarchy.children || []).concat([node]);
    return updateNode(
      columns,
      clonedHierarchy,
      columns.findIndex(c => c.name === clonedHierarchy.name),
    );
  };

  return (
    <Container title="model" loading={stage === ViewViewModelStages.Running}>
      <DragDropContext onDragEnd={handleDragEnd}>
        <Droppable
          droppableId={ROOT_CONTAINER_ID}
          type={TreeNodeHierarchy.Root}
          isCombineEnabled={true}
        >
          {(droppableProvided, droppableSnapshot) => (
            <StyledDroppableContainer
              ref={droppableProvided.innerRef}
              isDraggingOver={droppableSnapshot.isDraggingOver}
            >
              {tableColumns.map(col => {
                return col.role === ColumnRole.Hierarchy ? (
                  <DataModelBranch
                    node={col}
                    key={col.name}
                    onNodeTypeChange={handleNodeTypeChange}
                    onMoveToHierarchy={openMoveToHierarchyModal}
                    onEditBranch={openEditBranchModal}
                    onDelete={handleDeleteBranch}
                    onDeleteFromHierarchy={handleDeleteFromBranch}
                  />
                ) : (
                  <DataModelNode
                    node={col}
                    key={col.name}
                    onCreateHierarchy={openCreateHierarchyModal}
                    onNodeTypeChange={handleNodeTypeChange}
                    onMoveToHierarchy={openMoveToHierarchyModal}
                  />
                );
              })}
              {droppableProvided.placeholder}
            </StyledDroppableContainer>
          )}
        </Droppable>
      </DragDropContext>
      {contextHolder}
    </Container>
  );
})
Example #23
Source File: index.tsx    From nextjs-hasura-fullstack with MIT License 4 votes vote down vote up
BoardPage: React.FC = () => {
  const router = useRouter()
  const { setLastPosition } = useLastPositionNumber()

  const { data, loading } = useBoardQuery({
    variables: {
      id: Number(router.query.boardId),
    },
    skip: !router.query.boardId,
  })

  const [updateList] = useUpdateListMutation()
  const [updateCard] = useUpdateCardMutation()
  const [moveCard] = useMoveCardMutation()

  const renderLists = React.useMemo(() => {
    return data?.boards_by_pk?.lists || []
  }, [data])

  React.useEffect(() => {
    setLastPosition(renderLists[renderLists.length - 1]?.position || 1)
  }, [renderLists])

  const onDragEnd = async (result: DropResult, provided: ResponderProvided) => {
    const { source, destination, type } = result

    // dropped outside the list
    if (!destination) {
      return
    }

    if (
      source.droppableId === destination.droppableId &&
      source.index === destination.index
    ) {
      return
    }

    // DragDrop a "List"
    if (type === 'LIST') {
      const updatedPosition = getUpdatePositionReorder(
        renderLists,
        source.index,
        destination.index,
      )
      const sourceList = renderLists[source.index]

      updateList({
        variables: {
          id: sourceList.id,
          name: sourceList.name,
          position: updatedPosition,
        },
        update: (cache, { data }) => {
          if (data?.update_lists_by_pk) {
            const cacheId = cache.identify({
              __typename: 'boards',
              id: sourceList.board_id,
            })
            if (cacheId) {
              cache.modify({
                id: cacheId,
                fields: {
                  lists(existingListRefs = []) {
                    return reorder(
                      existingListRefs,
                      source.index,
                      destination.index,
                    )
                  },
                },
              })
            }
          }
        },
        optimisticResponse: (variables) => {
          return {
            __typename: 'mutation_root',
            update_lists_by_pk: {
              ...sourceList,
              position: updatedPosition,
            },
          }
        },
      })
    }

    if (type === 'CARD') {
      /* same list: reorder card */
      if (source.droppableId === destination.droppableId) {
        const listCards = renderLists.find(
          (item) => item.id === Number(source.droppableId),
        )?.cards

        if (!listCards) {
          return
        }

        const updatedPosition = getUpdatePositionReorder(
          listCards,
          source.index,
          destination.index,
        )

        const sourceCard = listCards[source.index]

        await updateCard({
          variables: {
            id: sourceCard.id,
            title: sourceCard.title,
            position: updatedPosition,
          },
          update: (cache, { data }) => {
            if (data?.update_cards_by_pk) {
              const cacheId = cache.identify({
                __typename: 'lists',
                id: sourceCard.list_id,
              })
              if (cacheId) {
                cache.modify({
                  id: cacheId,
                  fields: {
                    cards(existingCardRefs = []) {
                      return reorder(
                        existingCardRefs,
                        source.index,
                        destination.index,
                      )
                    },
                  },
                })
              }
            }
          },
          optimisticResponse: (variables) => {
            return {
              __typename: 'mutation_root',
              update_cards_by_pk: {
                ...sourceCard,
                position: updatedPosition,
              },
            }
          },
        })
      } else {
        /**
         * Diferent list: move card
         */

        const sourceListCards = renderLists.find(
          (item) => item.id === Number(source.droppableId),
        )?.cards

        const destinationListCards = renderLists.find(
          (item) => item.id === Number(destination.droppableId),
        )?.cards

        if (!sourceListCards || !destinationListCards) {
          return
        }

        const updatedPosition = getUpdatePositionMove(
          sourceListCards,
          destinationListCards,
          source.index,
          destination.index,
        )

        const sourceCard = sourceListCards[source.index]

        await moveCard({
          variables: {
            id: sourceCard.id,
            list_id: Number(destination.droppableId),
            title: sourceCard.title,
            position: updatedPosition,
          },
          update: (cache, { data }) => {
            if (data?.update_cards_by_pk) {
              const cardCacheId = cache.identify(data.update_cards_by_pk)

              if (!cardCacheId) {
                return
              }

              const cacheId = cache.identify({
                __typename: 'lists',
                id: source.droppableId,
              })

              if (cacheId) {
                cache.modify({
                  id: cacheId,
                  fields: {
                    cards(existingRefs = []) {
                      const next = existingRefs.filter(
                        (listRef: { __ref: string }) =>
                          listRef.__ref !== cardCacheId,
                      )
                      return next
                    },
                  },
                })
              }

              const cacheIdDestination = cache.identify({
                __typename: 'lists',
                id: destination.droppableId,
              })

              if (cacheIdDestination) {
                cache.modify({
                  id: cacheIdDestination,
                  fields: {
                    cards(existingCardRefs = [], { toReference }) {
                      const moveRef = toReference(cardCacheId)
                      if (moveRef) {
                        const next = produce(
                          existingCardRefs as any[],
                          (draftState) => {
                            draftState.splice(destination.index, 0, moveRef)
                          },
                        )
                        return next
                      }
                    },
                  },
                })
              }
            }
          },
          optimisticResponse: () => ({
            __typename: 'mutation_root',
            update_cards_by_pk: {
              ...sourceCard,
              position: updatedPosition,
            },
          }),
        })
      }
    }
  }

  let listRender
  if (loading) {
    listRender = (
      <div className={`flex flex-no-wrap min-w-max-content`}>
        {[...Array(3)].map((item, idx) => (
          <div
            key={idx}
            className="flex flex-col max-w-sm p-4 mx-4 space-y-3 border border-gray-300 rounded-md shadow w-72"
          >
            <div className="flex space-x-4 animate-pulse">
              <div className="flex-1 py-1 space-y-4">
                <div className="w-3/4 h-4 bg-gray-400 rounded"></div>
                {/* <div className="space-y-2">
                  <div className="h-4 bg-gray-400 rounded"></div>
                  <div className="w-5/6 h-4 bg-gray-400 rounded"></div>
                </div> */}
              </div>
            </div>
            {[...Array(4)].map((item, index) => (
              <div
                key={index}
                className="flex flex-col max-w-sm p-4 border border-gray-300 rounded-md shadow"
              >
                <div className="flex space-x-4 animate-pulse">
                  <div className="flex-1 py-1 space-y-2">
                    <div className="h-4 bg-gray-400 rounded"></div>
                    <div className="w-5/6 h-4 bg-gray-400 rounded"></div>
                  </div>
                </div>
              </div>
            ))}
            <div className="flex flex-col max-w-sm border-t border-gray-300 rounded-md shadow">
              <div className="flex space-x-4 animate-pulse">
                <div className="flex-1 py-1 space-y-2">
                  <div className="h-4 bg-gray-400 rounded"></div>
                </div>
              </div>
            </div>
          </div>
        ))}
        {/*  */}
      </div>
    )
  } else {
    listRender = (
      <div className={`flex flex-no-wrap h-screen w-max-content`}>
        <DragDropContext
          onDragStart={(source) => {
            console.log(`?? [LOG]: source`, source)
          }}
          // onDragUpdate={onDragUpdate}
          onDragEnd={onDragEnd}
        >
          {/* List */}
          <Droppable
            droppableId="board"
            type="LIST"
            direction="horizontal"
            // ignoreContainerClipping={true}
          >
            {(
              { innerRef, droppableProps, placeholder },
              { isDraggingOver },
            ) => {
              return (
                <div
                  ref={innerRef}
                  {...droppableProps}
                  className={`inline-flex ${isDraggingOver && ``}`}
                  style={{
                    overflowAnchor: 'none',
                  }}
                >
                  {renderLists.map((list, index) => (
                    <ListBoard list={list} index={index} key={index} />
                  ))}
                  {placeholder}
                </div>
              )
            }}
          </Droppable>
        </DragDropContext>
        <div className={``}>
          <NewList lastId={renderLists[renderLists.length - 1]?.id || 0} />
        </div>
      </div>
    )
  }

  return (
    <div className={`h-screen space-x-2 -mt-14`}>
      <Scrollbar noScrollY className={``}>
        <div className={`pt-14`}>
          <div className={`mt-4 mb-8`}>
            <h2
              className={`text-3xl font-semibold text-center text-cool-gray-100`}
            >
              {data?.boards_by_pk?.icon} {data?.boards_by_pk?.name}
            </h2>
          </div>
          {listRender}
        </div>
      </Scrollbar>
    </div>
  )
}
Example #24
Source File: MediaMenu.tsx    From sync-party with GNU General Public License v3.0 4 votes vote down vote up
export default function MediaMenu({
    socket,
    setPlayerFocused,
    emitPlayWish,
    freezeUiVisible,
    isPlaying,
    playerState
}: Props): JSX.Element {
    const party: ClientParty | null = useSelector(
        (state: RootAppState) => state.globalState.party
    );
    const playingItem: MediaItem | null = useSelector(
        (state: RootAppState) => state.globalState.playingItem
    );
    const uiVisible = useSelector(
        (state: RootAppState) => state.globalState.uiVisible
    );
    const uiFocused = useSelector(
        (state: RootAppState) => state.globalState.uiFocused
    );

    const [hoverTimestamp, setHoverTimestamp] = useState(0);
    const hoverTimestampRef = useRef(hoverTimestamp);
    hoverTimestampRef.current = hoverTimestamp;

    const [addMediaIsActive, setAddMediaIsActive] = useState(false);

    const [partyItemsSet, setPartyItemsSet] = useState<Set<string>>(new Set());

    const partyItemListRef = useRef<HTMLDivElement | null>(null);

    const dispatch = useDispatch();
    const { t } = useTranslation();

    // Collect items in active party in a set for faster checks
    useEffect(() => {
        if (party) {
            const itemsSet = new Set<string>();

            party.items.forEach((item) => {
                itemsSet.add(item.id);
            });

            setPartyItemsSet(itemsSet);
        }
    }, [party]);

    const handleRemoveItemFromParty = async (
        item: MediaItem
    ): Promise<void> => {
        if (socket && party) {
            if (
                party.items.length === 1 &&
                playerState.playingItem &&
                playerState.playingItem.id === item.id
            ) {
                dispatch(
                    setGlobalState({
                        errorMessage: t(`errors.removeLastItemError`)
                    })
                );

                return Promise.reject();
            }

            try {
                const response = await Axios.delete('/api/partyItems', {
                    data: { itemId: item.id, partyId: party.id },
                    ...axiosConfig()
                });

                if (response.data.success) {
                    const currentIndex = playerState.playlistIndex;

                    if (
                        playerState.playingItem &&
                        playerState.playingItem.id === item.id
                    ) {
                        emitPlayWish(
                            party.items[
                                currentIndex < party.items.length - 1
                                    ? currentIndex + 1
                                    : currentIndex - 1
                            ],
                            playerState.isPlaying,
                            null,
                            false,
                            0
                        );
                    }

                    socket.emit('partyUpdate', {
                        partyId: party.id
                    });

                    setPlayerFocused(true);
                } else {
                    dispatch(
                        setGlobalState({
                            errorMessage: t(
                                `apiResponseMessages.${response.data.msg}`
                            )
                        })
                    );
                }
            } catch (error) {
                dispatch(
                    setGlobalState({
                        errorMessage: t(`errors.removeItemError`)
                    })
                );
            }
        }
    };

    const handleItemEditSave = async (item: MediaItem): Promise<void> => {
        if (socket && party) {
            try {
                const response = await Axios.put(
                    '/api/mediaItem/' + item.id,
                    item,
                    axiosConfig()
                );

                if (response.data.success) {
                    await getUpdatedUserItems(dispatch, t);

                    socket.emit('partyUpdate', { partyId: party.id });
                    setPlayerFocused(true);
                } else {
                    dispatch(
                        setGlobalState({
                            errorMessage: t(
                                `apiResponseMessages.${response.data.msg}`
                            )
                        })
                    );
                }
            } catch (error) {
                dispatch(
                    setGlobalState({
                        errorMessage: t(`errors.itemSaveError`)
                    })
                );
            }
        }
    };

    const handleItemClick = (chosenItem: MediaItem): void => {
        if (party) {
            const newSource = party.items.filter((mediaItem: MediaItem) => {
                return mediaItem.id === chosenItem.id;
            })[0];

            emitPlayWish(
                newSource,
                true,
                playerState.playingItem ? playerState.playingItem.id : null,
                true,
                0
            );
        }
    };

    const onDragEnd = async (result: DropResult): Promise<void> => {
        if (socket && party) {
            // Dropped outside the list
            if (!result.destination) {
                return;
            }

            const orderedItems = reorderItems(
                party.items,
                result.source.index,
                result.destination.index
            );

            try {
                const response = await Axios.put(
                    '/api/partyItems',
                    { orderedItems: orderedItems, partyId: party.id },
                    axiosConfig()
                );

                if (response.data.success) {
                    socket.emit('partyUpdate', { partyId: party.id });
                } else {
                    dispatch(
                        setGlobalState({
                            errorMessage: t(
                                `apiResponseMessages.${response.data.msg}`
                            )
                        })
                    );
                }
            } catch (error) {
                dispatch(
                    setGlobalState({
                        errorMessage: t(`errors.reorderError`)
                    })
                );
            }
        }
    };

    return (
        <div
            className={
                'mediaMenu fixed top-0 right-0 flex flex-col mt-16 p-2 border border-gray-500 rounded m-2 shadow-md backgroundShade z-50' +
                (uiVisible || !playingItem || !playingItem.url ? '' : ' hidden')
            }
            onMouseOver={(): void => {
                const now = Date.now();

                if (hoverTimestampRef.current + 10000 < now) {
                    freezeUiVisible(true);
                    setHoverTimestamp(now);
                }
            }}
            onMouseLeave={(): void => {
                if (!uiFocused.chat && !addMediaIsActive) {
                    freezeUiVisible(false);
                    setHoverTimestamp(0);
                }
            }}
        >
            {party && party.items.length ? (
                <div className="partyItemList" ref={partyItemListRef}>
                    <DragDropContext onDragEnd={onDragEnd}>
                        <Droppable droppableId="droppable">
                            {(provided: DroppableProvided): JSX.Element => (
                                <div
                                    {...provided.droppableProps}
                                    ref={provided.innerRef}
                                >
                                    {party.items.map(
                                        (
                                            mediaItem: MediaItem,
                                            index: number
                                        ) => {
                                            const isCurrentlyPlayingItem =
                                                playingItem &&
                                                playingItem.id === mediaItem.id
                                                    ? true
                                                    : false;
                                            const alreadyPlayed =
                                                party.metadata.played &&
                                                party.metadata.played[
                                                    mediaItem.id
                                                ]
                                                    ? true
                                                    : false;

                                            return (
                                                <MediaMenuDraggable
                                                    key={mediaItem.id}
                                                    mediaItemId={mediaItem.id}
                                                    index={index}
                                                >
                                                    <ItemListed
                                                        item={mediaItem}
                                                        isPlaying={isPlaying}
                                                        isCurrentlyPlayingItem={
                                                            isCurrentlyPlayingItem
                                                        }
                                                        alreadyPlayed={
                                                            alreadyPlayed
                                                        }
                                                        nameEditingAllowed={
                                                            false
                                                        }
                                                        handleItemClick={(): void =>
                                                            handleItemClick(
                                                                mediaItem
                                                            )
                                                        }
                                                        onRemoveButtonClick={(
                                                            item: MediaItem
                                                        ): void => {
                                                            handleRemoveItemFromParty(
                                                                item
                                                            );
                                                        }}
                                                        handleItemSave={(
                                                            item: MediaItem
                                                        ): void => {
                                                            handleItemEditSave(
                                                                item
                                                            );
                                                        }}
                                                        setPlayerFocused={
                                                            setPlayerFocused
                                                        }
                                                        partyItemListRef={
                                                            partyItemListRef
                                                        }
                                                    ></ItemListed>
                                                </MediaMenuDraggable>
                                            );
                                        }
                                    )}
                                </div>
                            )}
                        </Droppable>
                    </DragDropContext>
                </div>
            ) : (
                <div className="mb-2">There&#39;s nothing.</div>
            )}
            <AddMedia
                isActive={addMediaIsActive}
                partyItemsSet={partyItemsSet}
                setAddMediaIsActive={setAddMediaIsActive}
                handleItemEditSave={handleItemEditSave}
                setPlayerFocused={(focused: boolean): void =>
                    setPlayerFocused(focused)
                }
                socket={socket}
            ></AddMedia>
        </div>
    );
}
Example #25
Source File: board.tsx    From wikitrivia with MIT License 4 votes vote down vote up
export default function Board(props: Props) {
  const { highscore, resetGame, state, setState, updateHighscore } = props;

  const [isDragging, setIsDragging] = React.useState(false);

  async function onDragStart() {
    setIsDragging(true);
    navigator.vibrate(20);
  }

  async function onDragEnd(result: DropResult) {
    setIsDragging(false);

    const { source, destination } = result;

    if (
      !destination ||
      state.next === null ||
      (source.droppableId === "next" && destination.droppableId === "next")
    ) {
      return;
    }

    const item = { ...state.next };

    if (source.droppableId === "next" && destination.droppableId === "played") {
      const newDeck = [...state.deck];
      const newPlayed = [...state.played];
      const { correct, delta } = checkCorrect(
        newPlayed,
        item,
        destination.index
      );
      newPlayed.splice(destination.index, 0, {
        ...state.next,
        played: { correct },
      });

      const newNext = state.nextButOne;
      const newNextButOne = getRandomItem(
        newDeck,
        newNext ? [...newPlayed, newNext] : newPlayed
      );
      const newImageCache = [preloadImage(newNextButOne.image)];

      setState({
        ...state,
        deck: newDeck,
        imageCache: newImageCache,
        next: newNext,
        nextButOne: newNextButOne,
        played: newPlayed,
        lives: correct ? state.lives : state.lives - 1,
        badlyPlaced: correct
          ? null
          : {
              index: destination.index,
              rendered: false,
              delta,
            },
      });
    } else if (
      source.droppableId === "played" &&
      destination.droppableId === "played"
    ) {
      const newPlayed = [...state.played];
      const [item] = newPlayed.splice(source.index, 1);
      newPlayed.splice(destination.index, 0, item);

      setState({
        ...state,
        played: newPlayed,
        badlyPlaced: null,
      });
    }
  }

  // Ensure that newly placed items are rendered as draggables before trying to
  // move them to the right place if needed.
  React.useLayoutEffect(() => {
    if (
      state.badlyPlaced &&
      state.badlyPlaced.index !== null &&
      !state.badlyPlaced.rendered
    ) {
      setState({
        ...state,
        badlyPlaced: { ...state.badlyPlaced, rendered: true },
      });
    }
  }, [setState, state]);

  const score = React.useMemo(() => {
    return state.played.filter((item) => item.played.correct).length - 1;
  }, [state.played]);

  React.useLayoutEffect(() => {
    if (score > highscore) {
      updateHighscore(score);
    }
  }, [score, highscore, updateHighscore]);

  return (
    <DragDropContext
      onDragEnd={onDragEnd}
      onDragStart={onDragStart}
      sensors={[useAutoMoveSensor.bind(null, state)]}
    >
      <div className={styles.wrapper}>
        <div className={styles.top}>
          <Hearts lives={state.lives} />
          {state.lives > 0 ? (
            <>
              <NextItemList next={state.next} />
            </>
          ) : (
            <GameOver
              highscore={highscore}
              resetGame={resetGame}
              score={score}
            />
          )}
        </div>
        <div id="bottom" className={styles.bottom}>
          <PlayedItemList
            badlyPlacedIndex={
              state.badlyPlaced === null ? null : state.badlyPlaced.index
            }
            isDragging={isDragging}
            items={state.played}
          />
        </div>
      </div>
    </DragDropContext>
  );
}
Example #26
Source File: StatDisplayList.tsx    From slippi-stats with MIT License 4 votes vote down vote up
StatDisplayList: React.FC<StatDisplayListProps> = (props) => {
  const { theme, stats, setStats } = props;
  const [items, setItems] = React.useState<string[]>(stats.split(","));
  React.useEffect(() => {
    setItems(stats.split(","));
  }, [stats]);

  const updateStats = (statIds: string[]) => {
    // First update the local state
    setItems(statIds);
    // Then update the URL state
    setStats(statIds.join(","));
  };

  const onDragEnd = (result: any) => {
    // dropped outside the list
    if (!result.destination) {
      return;
    }
    const newItems = reorder(items, result.source.index, result.destination.index);
    updateStats(newItems);
  };

  const onRemove = (statId: string) => {
    const newItems = items.filter((s) => s !== statId);
    updateStats(newItems);
  };

  return (
    <DragDropContext onDragEnd={onDragEnd}>
      <Droppable droppableId="droppable">
        {(dropProvided, dropSnapshot) => (
          <div
            {...dropProvided.droppableProps}
            ref={dropProvided.innerRef}
            css={css`
              margin: -1rem 0;
            `}
          >
            {items.map((item, index) => {
              const key = item ? item : "divider";
              return (
                <Draggable key={key} draggableId={key} index={index}>
                  {(dragProvided, dragSnapshot) => {
                    const additionalStyles = item ? null : dragProvided.dragHandleProps;
                    return (
                      <StatDisplayItem
                        ref={dragProvided.innerRef}
                        hasItem={Boolean(item)}
                        isDraggingOver={dropSnapshot.isDraggingOver}
                        {...dragProvided.draggableProps}
                        {...additionalStyles}
                        style={dragProvided.draggableProps.style}
                      >
                        {item ? (
                          <div
                            css={css`
                              position: relative;
                            `}
                          >
                            <Statistic statId={item} theme={theme} {...dragProvided.dragHandleProps} />
                            <div className="remove" onClick={() => onRemove(item)}>
                              ✕
                              <span
                                css={css`
                                  margin-left: 1rem;
                                `}
                              >
                                REMOVE
                              </span>
                            </div>
                          </div>
                        ) : (
                          <Divider />
                        )}
                      </StatDisplayItem>
                    );
                  }}
                </Draggable>
              );
            })}
            {dropProvided.placeholder}
          </div>
        )}
      </Droppable>
    </DragDropContext>
  );
}
Example #27
Source File: designers.tsx    From ali-react-table with MIT License 4 votes vote down vote up
export function PrimaryColumnTitle({ pivot }: { pivot: Pivot }) {
  const [visible, setVisible] = useState(false)
  const onClose = () => setVisible(false)

  const [state, setState] = useState(() => toJS(pivot))
  useEffect(() => {
    if (visible) {
      setState(toJS(pivot))
    }
  }, [visible])

  const dimMap = new Map(pivot.allDimensions.map((dim) => [dim.code, dim]))

  const onOk = () => {
    pivot.changeDimCodes(state.dimCodes, pivot.dimValues)
    onClose()
  }

  useEffect(() => {
    if (visible) {
    }
  }, [visible])

  return (
    <div style={{ display: 'flex', alignItems: 'center' }}>
      <b>数据维度</b>
      <div
        style={{ marginLeft: 'auto', display: 'flex', alignItems: 'center', cursor: 'pointer', userSelect: 'none' }}
        onClick={() => setVisible(true)}
      >
        <Settings16 style={{ fill: 'currentColor' }} />
        <span style={{ marginLeft: 4 }}>设置</span>
        <Dialog closeable={false} title="数据维度配置" visible={visible} onOk={onOk} onCancel={onClose}>
          <IndicatorChooseDiv>
            <IndicatorsPartDiv>
              <div className="title">
                选择维度({state.dimCodes.length}/{pivot.allDimensions.length})
              </div>
              <ul className="ind-list">
                {pivot.allDimensions.map((dim) => (
                  <li key={dim.code}>
                    <Checkbox
                      className="ind-item clickable-ind-item"
                      checked={state.dimCodes.includes(dim.code)}
                      onChange={(checked) => {
                        setState((prev) =>
                          produce(prev, (draft) => {
                            if (checked) {
                              draft.dimCodes.push(dim.code)
                            } else {
                              draft.dimCodes.splice(draft.dimCodes.indexOf(dim.code), 1)
                            }
                          }),
                        )
                      }}
                    >
                      {dim.name}
                    </Checkbox>
                  </li>
                ))}
              </ul>
            </IndicatorsPartDiv>

            <IndicatorsPartDiv style={{ borderLeft: '1px solid var(--border-color)' }}>
              <div className="title">已选({state.dimCodes.length})</div>
              <DragDropContext
                onDragEnd={(result) => {
                  if (result.destination == null) {
                    return
                  }
                  setState((prev) =>
                    produce(prev, (draft) => {
                      const i = result.source.index
                      const j = result.destination.index
                      const [code] = draft.dimCodes.splice(i, 1)
                      draft.dimCodes.splice(j, 0, code)
                    }),
                  )
                }}
              >
                <Droppable droppableId="selected-dimensions" direction="vertical">
                  {(dropProvided, snapshot) => (
                    <ul className="ind-list" ref={dropProvided.innerRef}>
                      {state.dimCodes.map((dimCode, index) => (
                        <Draggable key={dimCode} index={index} draggableId={dimCode}>
                          {(dragProvided, snapshot) => {
                            return (
                              <li ref={dragProvided.innerRef} {...dragProvided.draggableProps}>
                                <div className={cx('ind-item draggable-ind-item', { dragging: snapshot.isDragging })}>
                                  {dimMap.get(dimCode).name}
                                  <div {...dragProvided.dragHandleProps} style={{ marginLeft: 'auto' }}>
                                    <DragVertical16 style={{ fill: 'currentColor' }} />
                                  </div>
                                </div>
                              </li>
                            )
                          }}
                        </Draggable>
                      ))}
                      {dropProvided.placeholder}
                    </ul>
                  )}
                </Droppable>
              </DragDropContext>
            </IndicatorsPartDiv>
          </IndicatorChooseDiv>
        </Dialog>
      </div>
    </div>
  )
}
Example #28
Source File: BuildOrder.tsx    From sc2-planner with MIT License 4 votes vote down vote up
render(): JSX.Element {
        // Convert build order items to div elements

        // Hide element if no build order items are present
        if (this.props.gamelogic.bo.length === 0) {
            return <div />
        }

        const buildOrder = this.props.gamelogic.bo.map((item, index) => {
            const image = getImageOfItem(item)
            return <img key={`${item.name}_${index}`} src={image} alt={item.name} />
        })

        const getItemClass = (dragging: boolean, index: number) => {
            // Build order is invalid after this index, mark background or border red
            // Set color based on if it is dragging
            const classes: string[] = []
            if (dragging) {
                if (index >= this.props.gamelogic.boIndex) {
                    classes.push(CLASSES.boItemInvalidDragging)
                } else {
                    classes.push(CLASSES.boItemDragging)
                }
            } else if (index === this.props.hoverIndex) {
                classes.push(CLASSES.boItemHighlighting)
            } else {
                if (index >= this.props.gamelogic.boIndex) {
                    classes.push(CLASSES.boItemInvalid)
                }
                classes.push(CLASSES.boItem)
            }
            if (includes(this.props.highlightedIndexes, index)) {
                classes.push(CLASSES.boItemDim)
            }
            return classes.join(" ")
        }

        const buildOrderItems: JSX.Element[] = []
        let separatorClass =
            this.props.insertIndex === 0 ? CLASSES.boItemSeparatorSelected : CLASSES.boItemSeparator
        buildOrderItems.push(
            <div
                id={"separator_0"}
                key={"separator0"}
                className={separatorClass}
                onClick={(_e) => {
                    this.props.changeInsertIndex(0)
                }}
            />
        )

        this.props.gamelogic.bo.forEach((item, index) => {
            buildOrderItems.push(
                <Draggable key={`${index}`} draggableId={`${index}`} index={index}>
                    {(provided, snapshot) => (
                        <div
                            id={`bo_${item.name}_${index}`}
                            ref={provided.innerRef}
                            {...provided.draggableProps}
                            {...provided.dragHandleProps}
                            className={getItemClass(snapshot.isDragging, index)}
                            onMouseEnter={(_e) => this.onMouseEnter(index)}
                            onMouseLeave={(_e) => this.onMouseLeave()}
                            onClick={(e) => {
                                this.props.removeClick(e, index)
                            }}
                        >
                            {buildOrder[index]}
                        </div>
                    )}
                </Draggable>
            )

            separatorClass =
                this.props.insertIndex === index + 1
                    ? CLASSES.boItemSeparatorSelected
                    : CLASSES.boItemSeparator
            buildOrderItems.push(
                <div
                    id={`separator_${index + 1}`}
                    key={`separator${index + 1}`}
                    className={separatorClass}
                    onClick={(_e) => {
                        this.props.changeInsertIndex(index + 1)
                    }}
                />
            )
        })

        return (
            <div id={"buildorder"} className={CLASSES.bo}>
                <DragDropContext onDragEnd={this.onDragEnd}>
                    <Droppable droppableId="droppable" direction="horizontal">
                        {(provided, _snapshot) => (
                            <div
                                className={
                                    (this.props.multilineBuildOrder ? "flex-wrap flex-row" : "") +
                                    " flex"
                                }
                                ref={provided.innerRef}
                                {...provided.droppableProps}
                            >
                                {buildOrderItems}
                                {provided.placeholder}
                            </div>
                        )}
                    </Droppable>
                </DragDropContext>
            </div>
        )
    }
Example #29
Source File: ArrayContainer.tsx    From firecms with MIT License 4 votes vote down vote up
/**
 * @category Form custom fields
 */
export function ArrayContainer<T>({
                                      name,
                                      value,
                                      disabled,
                                      buildEntry,
                                      onInternalIdAdded,
                                      includeAddButton
                                  }: ArrayContainerProps<T>) {

    const hasValue = value && Array.isArray(value) && value.length > 0;

    const internalIdsMap: Record<string, number> = useMemo(() =>
            hasValue
                ? value.map((v, index) => {
                    if (!v) return {};
                    return ({
                        [getHashValue(v) + index]: getRandomId()
                    });
                }).reduce((a, b) => ({ ...a, ...b }), {})
                : {},
        [value, hasValue]);
    const internalIdsRef = useRef<Record<string, number>>(internalIdsMap);

    const [internalIds, setInternalIds] = useState<number[]>(
        hasValue
            ? Object.values(internalIdsRef.current)
            : []);

    useEffect(() => {
        if (hasValue && value && value.length !== internalIds.length) {
            const newInternalIds = value.map((v, index) => {
                const hashValue = getHashValue(v) + index;
                if (hashValue in internalIdsRef.current) {
                    return internalIdsRef.current[hashValue];
                } else {
                    const newInternalId = getRandomId();
                    internalIdsRef.current[hashValue] = newInternalId;
                    return newInternalId;
                }
            });
            setInternalIds(newInternalIds);
        }
    }, [hasValue, internalIds.length, value]);


    return <FieldArray
        name={name}
        validateOnChange={true}
        render={arrayHelpers => {

            const insertInEnd = () => {
                if (disabled) return;
                const id = getRandomId();
                const newIds: number[] = [...internalIds, id];
                if (onInternalIdAdded)
                    onInternalIdAdded(id);
                setInternalIds(newIds);
                arrayHelpers.push(null);
            };

            const remove = (index: number) => {
                const newValue = [...internalIds];
                newValue.splice(index, 1);
                setInternalIds(newValue);
                arrayHelpers.remove(index);
            };

            const onDragEnd = (result: any) => {
                // dropped outside the list
                if (!result.destination) {
                    return;
                }
                const sourceIndex = result.source.index;
                const destinationIndex = result.destination.index;

                const newIds = [...internalIds];
                const temp = newIds[sourceIndex];
                newIds[sourceIndex] = newIds[destinationIndex];
                newIds[destinationIndex] = temp;
                setInternalIds(newIds);

                arrayHelpers.move(sourceIndex, destinationIndex);
            }

            return (
                <DragDropContext onDragEnd={onDragEnd}>
                    <Droppable droppableId={`droppable_${name}`}>
                        {(droppableProvided, droppableSnapshot) => (
                            <div
                                {...droppableProvided.droppableProps}
                                ref={droppableProvided.innerRef}>
                                {hasValue && internalIds.map((internalId: number, index: number) => {
                                    return (
                                        <Draggable
                                            key={`array_field_${name}_${internalId}}`}
                                            draggableId={`array_field_${name}_${internalId}}`}
                                            isDragDisabled={disabled}
                                            index={index}>
                                            {(provided, snapshot) => (

                                                <Box
                                                    ref={provided.innerRef}
                                                    {...provided.draggableProps}
                                                    style={
                                                        provided.draggableProps.style
                                                    }
                                                    sx={{
                                                        marginBottom: 1,
                                                        borderRadius: "4px",
                                                        opacity: 1
                                                    }}
                                                >
                                                    <Box key={`field_${index}`}
                                                         display="flex">
                                                        <Box flexGrow={1}
                                                             width={"100%"}
                                                             key={`field_${name}_entryValue`}>
                                                            {buildEntry(index, internalId)}
                                                        </Box>
                                                        <Box width={"36px"}
                                                             display="flex"
                                                             flexDirection="column"
                                                             alignItems="center">
                                                            <div
                                                                {...provided.dragHandleProps}>
                                                                <DragHandleIcon
                                                                    fontSize={"small"}
                                                                    color={disabled ? "disabled" : "inherit"}
                                                                    sx={{ cursor: disabled ? "inherit" : "move" }}/>
                                                            </div>
                                                            {!disabled &&
                                                            <IconButton
                                                                size="small"
                                                                aria-label="remove"
                                                                onClick={() => remove(index)}>
                                                                <ClearIcon
                                                                    fontSize={"small"}/>
                                                            </IconButton>}
                                                        </Box>
                                                    </Box>
                                                </Box>
                                            )}
                                        </Draggable>);
                                })}

                                {droppableProvided.placeholder}

                                {includeAddButton && !disabled && <Box p={1}
                                                                       justifyContent="center"
                                                                       textAlign={"left"}>
                                    <Button variant="outlined"
                                            color="primary"
                                            disabled={disabled}
                                            onClick={insertInEnd}>
                                        Add
                                    </Button>
                                </Box>}
                            </div>
                        )}
                    </Droppable>
                </DragDropContext>
            );
        }}
    />;
}