Source: @@components/ColumnList.js

/**
 * @file Orderable column list with draggable tags, allowing the users to reorder their variables in the SELECT query
 * @module @@components/ColumnList
 */
import React, { Component } from 'react';
import { DragDropContext, Droppable, Draggable } from 'react-beautiful-dnd';
import { DragOutlined } from '@ant-design/icons';
import { Tag } from 'antd';
import { prop, path, mergeRight, uniqBy } from 'ramda';
import { connect } from 'react-redux';
import { getProperties, getClasses, getSelectionOrder } from '@@selectors';
import {dispatchSet} from '@@app-state';
import * as ModelState from '@@app-state/model/state';
import { translated } from '@@localization';

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

  return result;
};

const getItems = (items, selectionOrder) => selectionOrder
  .filter(id => path([id, 'selected'], items))
  .filter(id => path([id, 'asVariable'], items))
  .map(id => ({
    id,
    content: `${path([id, 'varName'], items)}`
  }));

class ColumnListComponent extends Component {
  constructor(props) {
    super(props);

    this.state = {items: []};
    this.onDragEnd = this.onDragEnd.bind(this);
  }

  componentDidMount() {
    this.updateItems();
  }

  updateItems() {
    const {entities, properties, selectionOrder} = this.props;
    const items = getItems(mergeRight(entities, properties), selectionOrder);
    this.setState({items: uniqBy(prop('content'), items)});
  }


  componentDidUpdate(prevProps, prevState, snapshot) {
    const {entities, properties, selectionOrder} = this.props;
    if (prevProps.properties !== properties || prevProps.entities !== entities || prevProps.selectionOrder !== selectionOrder) {
      this.updateItems();
    }
  }

  onDragEnd(result) {
    // dropped outside the list
    if (!result.destination) {
      return;
    }

    const items = reorder(
      this.state.items,
      result.source.index,
      result.destination.index
    );

    dispatchProps.updateSelectionOrder(items.map(prop('id')));
  }

  getEmptyMessage = () => {
    return <h3>
      {translated('You can reorder the result set columns here after selecting some properties.')}
    </h3>
  };

  render() {
    if (!this.state.items.length) {
      return this.getEmptyMessage();
    }

    return (
      <div>
        {translated('Result column order:')}
        <DragDropContext onDragEnd={this.onDragEnd}>
          <Droppable droppableId="droppable" direction="vertical">
            {(provided, snapshot) => (
              <div
                ref={provided.innerRef}
                style={{
                  padding: 4,
                  overflow: 'auto',
                }}
                {...provided.droppableProps}
              >
                {this.state.items.map((item, index) => (
                  <Draggable key={item.id} draggableId={item.id} index={index}>
                    {(provided, snapshot) => (
                      <div
                        ref={provided.innerRef}
                        {...provided.draggableProps}
                        {...provided.dragHandleProps}
                      >
                        <Tag>{index + 1}: {item.content} <DragOutlined /></Tag>
                      </div>
                    )}
                  </Draggable>
                ))}
                {provided.placeholder}
              </div>
            )}
          </Droppable>
        </DragDropContext>
      </div>
    );
  }
}

const mapStateToProps = appState => ({
  properties: getProperties(appState),
  entities: getClasses(appState),
  selectionOrder: getSelectionOrder(appState)
});

const dispatchProps = {
  updateSelectionOrder: dispatchSet(ModelState.selectionOrder)
};

export const ColumnList = connect(mapStateToProps, null)(ColumnListComponent);