17782: Fixes almost all tests (4 left) mostly by fixing namespace-type imports.
[arvados-workbench2.git] / src / components / chips / chips.tsx
1 // Copyright (C) The Arvados Authors. All rights reserved.
2 //
3 // SPDX-License-Identifier: AGPL-3.0
4
5 import React from 'react';
6 import { Chip, Grid, StyleRulesCallback, withStyles } from '@material-ui/core';
7 import {
8     DragSource,
9     DragSourceSpec,
10     DragSourceCollector,
11     ConnectDragSource,
12     DropTarget,
13     DropTargetSpec,
14     DropTargetCollector,
15     ConnectDropTarget
16 } from 'react-dnd';
17 import { compose } from 'lodash/fp';
18 import { WithStyles } from '@material-ui/core/styles';
19 interface ChipsProps<Value> {
20     values: Value[];
21     getLabel?: (value: Value) => string;
22     filler?: React.ReactNode;
23     deletable?: boolean;
24     orderable?: boolean;
25     onChange: (value: Value[]) => void;
26     clickable?: boolean;
27 }
28
29 type CssRules = 'root';
30
31 const styles: StyleRulesCallback<CssRules> = ({ spacing }) => ({
32     root: {
33         margin: `0px -${spacing.unit / 2}px`,
34     },
35 });
36 export const Chips = withStyles(styles)(
37     class Chips<Value> extends React.Component<ChipsProps<Value> & WithStyles<CssRules>> {
38         render() {
39             const { values, filler } = this.props;
40             return <Grid container spacing={8} className={this.props.classes.root}>
41                 {values.map(this.renderChip)}
42                 {filler && <Grid item xs>{filler}</Grid>}
43             </Grid>;
44         }
45
46         renderChip = (value: Value, index: number) =>
47             <Grid item key={index}>
48                 <this.chip {...{ value }} />
49             </Grid>
50
51         type = 'chip';
52
53         dragSpec: DragSourceSpec<DraggableChipProps<Value>, { value: Value }> = {
54             beginDrag: ({ value }) => ({ value }),
55             endDrag: ({ value: dragValue }, monitor) => {
56                 const result = monitor.getDropResult();
57                 if (result) {
58                     const { value: dropValue } = monitor.getDropResult();
59                     const dragIndex = this.props.values.indexOf(dragValue);
60                     const dropIndex = this.props.values.indexOf(dropValue);
61                     const newValues = this.props.values.slice(0);
62                     if (dragIndex < dropIndex) {
63                         newValues.splice(dragIndex, 1);
64                         newValues.splice(dropIndex - 1 || 0, 0, dragValue);
65                     } else if (dragIndex > dropIndex) {
66                         newValues.splice(dragIndex, 1);
67                         newValues.splice(dropIndex, 0, dragValue);
68                     }
69                     this.props.onChange(newValues);
70                 }
71             }
72         };
73
74         dragCollector: DragSourceCollector<{}> = connect => ({
75             connectDragSource: connect.dragSource(),
76         })
77
78         dropSpec: DropTargetSpec<DraggableChipProps<Value>> = {
79             drop: ({ value }) => ({ value }),
80         };
81
82         dropCollector: DropTargetCollector<{}> = (connect, monitor) => ({
83             connectDropTarget: connect.dropTarget(),
84             isOver: monitor.isOver(),
85         })
86         chip = compose(
87             DragSource(this.type, this.dragSpec, this.dragCollector),
88             DropTarget(this.type, this.dropSpec, this.dropCollector),
89         )(
90             ({ connectDragSource, connectDropTarget, isOver, value }: DraggableChipProps<Value> & CollectedProps) => {
91                 const connect = compose(
92                     connectDragSource,
93                     connectDropTarget,
94                 );
95
96                 const chip =
97                     <span>
98                         <Chip
99                             color={isOver ? 'primary' : 'default'}
100                             onDelete={this.props.deletable
101                                 ? this.deleteValue(value)
102                                 : undefined}
103                             clickable={this.props.clickable}
104                             label={this.props.getLabel ?
105                                 this.props.getLabel(value)
106                                 : typeof value === 'object'
107                                     ? JSON.stringify(value)
108                                     : value} />
109                     </span>;
110
111                 return this.props.orderable
112                     ? connect(chip)
113                     : chip;
114             }
115         );
116
117         deleteValue = (value: Value) => () => {
118             const { values } = this.props;
119             const index = values.indexOf(value);
120             const newValues = values.slice(0);
121             newValues.splice(index, 1);
122             this.props.onChange(newValues);
123         }
124     });
125
126 interface CollectedProps {
127     connectDragSource: ConnectDragSource;
128     connectDropTarget: ConnectDropTarget;
129
130     isOver: boolean;
131 }
132
133 interface DraggableChipProps<Value> {
134     value: Value;
135 }