@pie-lib/graphing-solution-set 2.16.0-beta.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (151) hide show
  1. package/CHANGELOG.json +1 -0
  2. package/CHANGELOG.md +16 -0
  3. package/LICENSE.md +5 -0
  4. package/NEXT.CHANGELOG.json +1 -0
  5. package/lib/__tests__/graph-with-controls.test.js +191 -0
  6. package/lib/__tests__/graph.test.js +290 -0
  7. package/lib/__tests__/grid.test.js +40 -0
  8. package/lib/__tests__/labels.test.js +59 -0
  9. package/lib/__tests__/mark-label.test.js +154 -0
  10. package/lib/__tests__/toggle-bar.test.js +54 -0
  11. package/lib/__tests__/tool-menu.test.js +43 -0
  12. package/lib/__tests__/undo-redo.test.js +42 -0
  13. package/lib/__tests__/use-debounce.test.js +28 -0
  14. package/lib/__tests__/utils.js +72 -0
  15. package/lib/__tests__/utils.test.js +133 -0
  16. package/lib/axis/__tests__/arrow.test.js +68 -0
  17. package/lib/axis/__tests__/axes.test.js +214 -0
  18. package/lib/axis/arrow.js +115 -0
  19. package/lib/axis/axes.js +415 -0
  20. package/lib/axis/index.js +26 -0
  21. package/lib/bg.js +139 -0
  22. package/lib/container/actions.js +24 -0
  23. package/lib/container/index.js +166 -0
  24. package/lib/container/marks.js +27 -0
  25. package/lib/container/middleware.js +25 -0
  26. package/lib/container/reducer.js +25 -0
  27. package/lib/coordinates-label.js +109 -0
  28. package/lib/graph-with-controls.js +372 -0
  29. package/lib/graph.js +419 -0
  30. package/lib/grid-setup.js +462 -0
  31. package/lib/grid.js +176 -0
  32. package/lib/index.js +51 -0
  33. package/lib/labels.js +299 -0
  34. package/lib/mark-label.js +208 -0
  35. package/lib/toggle-bar.js +336 -0
  36. package/lib/tool-menu.js +325 -0
  37. package/lib/tools/index.js +29 -0
  38. package/lib/tools/line/__tests__/component.test.js +56 -0
  39. package/lib/tools/line/component.js +106 -0
  40. package/lib/tools/line/index.js +16 -0
  41. package/lib/tools/polygon/__tests__/component.test.js +245 -0
  42. package/lib/tools/polygon/__tests__/index.test.js +95 -0
  43. package/lib/tools/polygon/__tests__/line.test.js +43 -0
  44. package/lib/tools/polygon/__tests__/polygon.test.js +73 -0
  45. package/lib/tools/polygon/component.js +457 -0
  46. package/lib/tools/polygon/index.js +106 -0
  47. package/lib/tools/polygon/line.js +151 -0
  48. package/lib/tools/polygon/polygon.js +171 -0
  49. package/lib/tools/shared/__tests__/arrow-head.test.js +62 -0
  50. package/lib/tools/shared/arrow-head.js +75 -0
  51. package/lib/tools/shared/line/__tests__/index.test.js +291 -0
  52. package/lib/tools/shared/line/__tests__/line-path.test.js +78 -0
  53. package/lib/tools/shared/line/__tests__/with-root-edge.test.js +122 -0
  54. package/lib/tools/shared/line/index.js +637 -0
  55. package/lib/tools/shared/line/line-path.js +145 -0
  56. package/lib/tools/shared/line/with-root-edge.js +155 -0
  57. package/lib/tools/shared/point/__tests__/arrow-point.test.js +137 -0
  58. package/lib/tools/shared/point/__tests__/base-point.test.js +134 -0
  59. package/lib/tools/shared/point/arrow-point.js +113 -0
  60. package/lib/tools/shared/point/arrow.js +96 -0
  61. package/lib/tools/shared/point/base-point.js +151 -0
  62. package/lib/tools/shared/point/index.js +94 -0
  63. package/lib/tools/shared/styles.js +49 -0
  64. package/lib/tools/shared/types.js +19 -0
  65. package/lib/undo-redo.js +107 -0
  66. package/lib/use-debounce.js +32 -0
  67. package/lib/utils.js +314 -0
  68. package/package.json +50 -0
  69. package/src/__tests__/__snapshots__/graph-with-controls.test.jsx.snap +114 -0
  70. package/src/__tests__/__snapshots__/graph.test.jsx.snap +213 -0
  71. package/src/__tests__/__snapshots__/grid.test.jsx.snap +54 -0
  72. package/src/__tests__/__snapshots__/labels.test.jsx.snap +30 -0
  73. package/src/__tests__/__snapshots__/mark-label.test.jsx.snap +37 -0
  74. package/src/__tests__/__snapshots__/toggle-bar.test.jsx.snap +7 -0
  75. package/src/__tests__/__snapshots__/tool-menu.test.jsx.snap +35 -0
  76. package/src/__tests__/__snapshots__/undo-redo.test.jsx.snap +15 -0
  77. package/src/__tests__/graph-with-controls.test.jsx +131 -0
  78. package/src/__tests__/graph.test.jsx +230 -0
  79. package/src/__tests__/grid.test.jsx +20 -0
  80. package/src/__tests__/labels.test.jsx +38 -0
  81. package/src/__tests__/mark-label.test.jsx +68 -0
  82. package/src/__tests__/toggle-bar.test.jsx +36 -0
  83. package/src/__tests__/tool-menu.test.jsx +29 -0
  84. package/src/__tests__/undo-redo.test.jsx +25 -0
  85. package/src/__tests__/use-debounce.test.js +21 -0
  86. package/src/__tests__/utils.js +38 -0
  87. package/src/__tests__/utils.test.js +151 -0
  88. package/src/axis/__tests__/__snapshots__/arrow.test.jsx.snap +33 -0
  89. package/src/axis/__tests__/__snapshots__/axes.test.jsx.snap +122 -0
  90. package/src/axis/__tests__/arrow.test.jsx +39 -0
  91. package/src/axis/__tests__/axes.test.jsx +220 -0
  92. package/src/axis/arrow.jsx +62 -0
  93. package/src/axis/axes.jsx +307 -0
  94. package/src/axis/index.js +2 -0
  95. package/src/bg.jsx +96 -0
  96. package/src/container/actions.js +8 -0
  97. package/src/container/index.jsx +86 -0
  98. package/src/container/marks.js +14 -0
  99. package/src/container/middleware.js +7 -0
  100. package/src/container/reducer.js +5 -0
  101. package/src/coordinates-label.jsx +73 -0
  102. package/src/graph-with-controls.jsx +263 -0
  103. package/src/graph.jsx +334 -0
  104. package/src/grid-setup.jsx +427 -0
  105. package/src/grid.jsx +135 -0
  106. package/src/index.js +7 -0
  107. package/src/labels.jsx +214 -0
  108. package/src/mark-label.jsx +136 -0
  109. package/src/toggle-bar.jsx +242 -0
  110. package/src/tool-menu.jsx +294 -0
  111. package/src/tools/index.js +8 -0
  112. package/src/tools/line/__tests__/__snapshots__/component.test.jsx.snap +20 -0
  113. package/src/tools/line/__tests__/component.test.jsx +36 -0
  114. package/src/tools/line/component.jsx +77 -0
  115. package/src/tools/line/index.js +4 -0
  116. package/src/tools/polygon/__tests__/__snapshots__/component.test.jsx.snap +94 -0
  117. package/src/tools/polygon/__tests__/__snapshots__/line.test.jsx.snap +44 -0
  118. package/src/tools/polygon/__tests__/__snapshots__/polygon.test.jsx.snap +53 -0
  119. package/src/tools/polygon/__tests__/component.test.jsx +214 -0
  120. package/src/tools/polygon/__tests__/index.test.js +65 -0
  121. package/src/tools/polygon/__tests__/line.test.jsx +25 -0
  122. package/src/tools/polygon/__tests__/polygon.test.jsx +44 -0
  123. package/src/tools/polygon/component.jsx +336 -0
  124. package/src/tools/polygon/index.js +52 -0
  125. package/src/tools/polygon/line.jsx +78 -0
  126. package/src/tools/polygon/polygon.jsx +101 -0
  127. package/src/tools/shared/__tests__/__snapshots__/arrow-head.test.jsx.snap +32 -0
  128. package/src/tools/shared/__tests__/arrow-head.test.jsx +34 -0
  129. package/src/tools/shared/arrow-head.jsx +46 -0
  130. package/src/tools/shared/line/__tests__/__snapshots__/index.test.jsx.snap +360 -0
  131. package/src/tools/shared/line/__tests__/__snapshots__/line-path.test.jsx.snap +57 -0
  132. package/src/tools/shared/line/__tests__/__snapshots__/with-root-edge.test.jsx.snap +63 -0
  133. package/src/tools/shared/line/__tests__/index.test.jsx +247 -0
  134. package/src/tools/shared/line/__tests__/line-path.test.jsx +53 -0
  135. package/src/tools/shared/line/__tests__/with-root-edge.test.jsx +73 -0
  136. package/src/tools/shared/line/index.jsx +473 -0
  137. package/src/tools/shared/line/line-path.jsx +88 -0
  138. package/src/tools/shared/line/with-root-edge.jsx +97 -0
  139. package/src/tools/shared/point/__tests__/__snapshots__/arrow-point.test.jsx.snap +55 -0
  140. package/src/tools/shared/point/__tests__/__snapshots__/base-point.test.jsx.snap +43 -0
  141. package/src/tools/shared/point/__tests__/arrow-point.test.jsx +87 -0
  142. package/src/tools/shared/point/__tests__/base-point.test.jsx +84 -0
  143. package/src/tools/shared/point/arrow-point.jsx +60 -0
  144. package/src/tools/shared/point/arrow.jsx +40 -0
  145. package/src/tools/shared/point/base-point.jsx +86 -0
  146. package/src/tools/shared/point/index.jsx +60 -0
  147. package/src/tools/shared/styles.js +20 -0
  148. package/src/tools/shared/types.js +8 -0
  149. package/src/undo-redo.jsx +47 -0
  150. package/src/use-debounce.js +13 -0
  151. package/src/utils.js +234 -0
package/src/labels.jsx ADDED
@@ -0,0 +1,214 @@
1
+ import React from 'react';
2
+ import PropTypes from 'prop-types';
3
+ import { withStyles } from '@material-ui/core/styles';
4
+ import { types } from '@pie-lib/plot';
5
+ import { color, Readable } from '@pie-lib/render-ui';
6
+ import EditableHtml from '@pie-lib/editable-html';
7
+ import cn from 'classnames';
8
+
9
+ const rotations = {
10
+ left: -90,
11
+ top: 0,
12
+ bottom: 0,
13
+ right: 90,
14
+ };
15
+
16
+ export const getTransform = (side, width, height) => {
17
+ const t = (x, y, rotate) => `translate(${x}, ${y}), rotate(${rotate})`;
18
+
19
+ if (side === 'left') {
20
+ return t(-20, height / 2, rotations[side]);
21
+ }
22
+ if (side === 'right') {
23
+ return t(width + 30, height / 2, rotations[side]);
24
+ }
25
+ if (side === 'top') {
26
+ return t(width / 2, -20, rotations[side]);
27
+ }
28
+ if (side === 'bottom') {
29
+ return t(width / 2, height + 30, rotations[side]);
30
+ }
31
+ };
32
+
33
+ const getY = (side, height) => {
34
+ switch (side) {
35
+ case 'left':
36
+ return -height;
37
+ case 'top':
38
+ return -height;
39
+ case 'right':
40
+ return -height - 10;
41
+ default:
42
+ return -height + 10;
43
+ }
44
+ };
45
+
46
+ class RawLabel extends React.Component {
47
+ static propTypes = {
48
+ text: PropTypes.string,
49
+ side: PropTypes.string,
50
+ classes: PropTypes.object,
51
+ disabledLabel: PropTypes.bool,
52
+ placeholder: PropTypes.string,
53
+ graphProps: types.GraphPropsType.isRequired,
54
+ onChange: PropTypes.func,
55
+ };
56
+
57
+ static defaultProps = {
58
+ onChange: () => {},
59
+ };
60
+
61
+ render() {
62
+ const { disabledLabel, placeholder, text, side, graphProps, classes, onChange, mathMlOptions = {} } = this.props;
63
+ const { size, domain, range } = graphProps;
64
+ const totalHeight = (size.height || 500) + (range.padding || 0) * 2;
65
+ const totalWidth = (size.width || 500) + (domain.padding || 0) * 2;
66
+
67
+ const transform = getTransform(side, totalWidth, totalHeight);
68
+ const width = side === 'left' || side === 'right' ? totalHeight : totalWidth;
69
+ const height = 36;
70
+ const y = getY(side, height);
71
+ const finalHeight = side === 'bottom' ? height + 22 : height + 18;
72
+
73
+ const activePlugins = [
74
+ 'bold',
75
+ 'italic',
76
+ 'underline',
77
+ 'strikethrough',
78
+ 'math',
79
+ // 'languageCharacters'
80
+ ];
81
+
82
+ return (
83
+ <foreignObject
84
+ x={-(width / 2)}
85
+ y={y}
86
+ width={width}
87
+ height={finalHeight}
88
+ transform={transform}
89
+ textAnchor="middle"
90
+ >
91
+ <Readable false>
92
+ <EditableHtml
93
+ className={cn(
94
+ {
95
+ [classes.bottomLabel]: side === 'bottom',
96
+ [classes.disabledAxisLabel]: disabledLabel,
97
+ },
98
+ classes.axisLabel,
99
+ )}
100
+ markup={text || ''}
101
+ onChange={onChange}
102
+ placeholder={!disabledLabel && placeholder}
103
+ toolbarOpts={{
104
+ position: side === 'bottom' ? 'top' : 'bottom',
105
+ noPadding: true,
106
+ noBorder: true,
107
+ }}
108
+ activePlugins={activePlugins}
109
+ mathMlOptions={mathMlOptions}
110
+ />
111
+ </Readable>
112
+ </foreignObject>
113
+ );
114
+ }
115
+ }
116
+
117
+ const Label = withStyles((theme) => ({
118
+ label: {
119
+ fill: color.defaults.SECONDARY,
120
+ },
121
+ axisLabel: {
122
+ fontSize: theme.typography.fontSize - 2,
123
+ textAlign: 'center',
124
+ padding: '0 4px',
125
+ },
126
+ disabledAxisLabel: {
127
+ pointerEvents: 'none',
128
+ },
129
+ bottomLabel: {
130
+ marginTop: '44px',
131
+ },
132
+ }))(RawLabel);
133
+
134
+ export const LabelType = {
135
+ left: PropTypes.string,
136
+ top: PropTypes.string,
137
+ bottom: PropTypes.string,
138
+ right: PropTypes.string,
139
+ };
140
+
141
+ export class Labels extends React.Component {
142
+ static propTypes = {
143
+ classes: PropTypes.object,
144
+ className: PropTypes.string,
145
+ disabledLabels: PropTypes.bool,
146
+ placeholders: PropTypes.object,
147
+ value: PropTypes.shape(LabelType),
148
+ graphProps: PropTypes.object,
149
+ onChange: PropTypes.object,
150
+ };
151
+
152
+ static defaultProps = {};
153
+
154
+ onChangeLabel = (newValue, side) => {
155
+ const { value, onChange } = this.props;
156
+ const labels = {
157
+ ...value,
158
+ [side]: newValue,
159
+ };
160
+
161
+ onChange(labels);
162
+ };
163
+
164
+ render() {
165
+ const { disabledLabels, placeholders = {}, value = {}, graphProps, mathMlOptions = {} } = this.props;
166
+
167
+ return (
168
+ <React.Fragment>
169
+ <Label
170
+ key="left"
171
+ side="left"
172
+ text={value.left}
173
+ disabledLabel={disabledLabels}
174
+ placeholder={placeholders.left}
175
+ graphProps={graphProps}
176
+ onChange={(value) => this.onChangeLabel(value, 'left')}
177
+ mathMlOptions={mathMlOptions}
178
+ />
179
+ <Label
180
+ key="top"
181
+ side="top"
182
+ text={value.top}
183
+ disabledLabel={disabledLabels}
184
+ placeholder={placeholders.top}
185
+ graphProps={graphProps}
186
+ onChange={(value) => this.onChangeLabel(value, 'top')}
187
+ mathMlOptions={mathMlOptions}
188
+ />
189
+ <Label
190
+ key="bottom"
191
+ side="bottom"
192
+ text={value.bottom}
193
+ disabledLabel={disabledLabels}
194
+ placeholder={placeholders.bottom}
195
+ graphProps={graphProps}
196
+ onChange={(value) => this.onChangeLabel(value, 'bottom')}
197
+ mathMlOptions={mathMlOptions}
198
+ />
199
+ <Label
200
+ key="right"
201
+ side="right"
202
+ text={value.right}
203
+ disabledLabel={disabledLabels}
204
+ placeholder={placeholders.right}
205
+ graphProps={graphProps}
206
+ onChange={(value) => this.onChangeLabel(value, 'right')}
207
+ mathMlOptions={mathMlOptions}
208
+ />
209
+ </React.Fragment>
210
+ );
211
+ }
212
+ }
213
+
214
+ export default Labels;
@@ -0,0 +1,136 @@
1
+ import React, { useState, useCallback, useEffect } from 'react';
2
+ import cn from 'classnames';
3
+ import PropTypes from 'prop-types';
4
+ import { withStyles } from '@material-ui/core/styles';
5
+ import AutosizeInput from 'react-input-autosize';
6
+ import { useDebounce } from './use-debounce';
7
+ import { types } from '@pie-lib/plot';
8
+ import { color } from '@pie-lib/render-ui';
9
+
10
+ const styles = (theme) => ({
11
+ input: {
12
+ float: 'right',
13
+ padding: theme.spacing.unit * 0.5,
14
+ fontFamily: theme.typography.fontFamily,
15
+ fontSize: theme.typography.fontSize,
16
+ border: `solid 1px ${color.defaults.SECONDARY}`,
17
+ borderRadius: '3px',
18
+ color: color.defaults.PRIMARY_DARK,
19
+ },
20
+ disabled: {
21
+ border: `solid 1px ${color.defaults.PRIMARY_DARK}`,
22
+ background: theme.palette.background.paper,
23
+ },
24
+ disabledMark: {
25
+ border: `solid 1px ${color.disabled()}`,
26
+ background: theme.palette.background.paper,
27
+ color: color.disabled(),
28
+ },
29
+ });
30
+
31
+ export const position = (graphProps, mark, rect) => {
32
+ rect = rect || { width: 0, height: 0 };
33
+ const { scale, domain, range } = graphProps;
34
+ const shift = 10;
35
+
36
+ const rightEdge = scale.x(mark.x) + rect.width + shift;
37
+ const bottomEdge = scale.y(mark.y) + rect.height + shift;
38
+
39
+ const h = rightEdge >= scale.x(domain.max) ? 'left' : 'right';
40
+ const v = bottomEdge >= scale.y(range.min) ? 'top' : 'bottom';
41
+
42
+ return `${v}-${h}`;
43
+ };
44
+
45
+ export const coordinates = (graphProps, mark, rect, position) => {
46
+ const { scale } = graphProps;
47
+ const shift = 10;
48
+ rect = rect || { width: 0, height: 0 };
49
+
50
+ switch (position) {
51
+ case 'bottom-right': {
52
+ return { left: scale.x(mark.x) + shift, top: scale.y(mark.y) + shift };
53
+ }
54
+ case 'bottom-left': {
55
+ return { left: scale.x(mark.x) - shift - rect.width, top: scale.y(mark.y) + shift };
56
+ }
57
+ case 'top-left': {
58
+ return {
59
+ left: scale.x(mark.x) - shift - rect.width,
60
+ top: scale.y(mark.y) - shift - rect.height,
61
+ };
62
+ }
63
+ case 'top-right': {
64
+ return {
65
+ left: scale.x(mark.x) + shift,
66
+ top: scale.y(mark.y) - shift - rect.height,
67
+ };
68
+ }
69
+ }
70
+ };
71
+
72
+ export const MarkLabel = (props) => {
73
+ const [input, setInput] = useState(null);
74
+ const _ref = useCallback((node) => setInput(node));
75
+
76
+ const { mark, graphProps, classes, disabled, inputRef: externalInputRef, theme } = props;
77
+
78
+ const [label, setLabel] = useState(mark.label);
79
+
80
+ const onChange = (e) => setLabel(e.target.value);
81
+
82
+ const debouncedLabel = useDebounce(label, 200);
83
+
84
+ // useState only sets the value once, to synch props to state need useEffect
85
+ useEffect(() => {
86
+ setLabel(mark.label);
87
+ }, [mark.label]);
88
+
89
+ // pick up the change to debouncedLabel and save it
90
+ useEffect(() => {
91
+ if (typeof debouncedLabel === 'string' && debouncedLabel !== mark.label) {
92
+ props.onChange(debouncedLabel);
93
+ }
94
+ }, [debouncedLabel]);
95
+
96
+ const rect = input ? input.getBoundingClientRect() : { width: 0, height: 0 };
97
+ const pos = position(graphProps, mark, rect);
98
+ const leftTop = coordinates(graphProps, mark, rect, pos);
99
+
100
+ const style = {
101
+ position: 'fixed',
102
+ pointerEvents: 'auto',
103
+ ...leftTop,
104
+ };
105
+
106
+ const disabledInput = disabled || mark.disabled;
107
+
108
+ return (
109
+ <AutosizeInput
110
+ inputRef={(r) => {
111
+ _ref(r);
112
+ externalInputRef(r);
113
+ }}
114
+ disabled={disabledInput}
115
+ inputClassName={cn(classes.input, {
116
+ [classes.disabled]: disabled,
117
+ [classes.disabledMark]: mark.disabled,
118
+ })}
119
+ value={label}
120
+ style={style}
121
+ onChange={onChange}
122
+ />
123
+ );
124
+ };
125
+
126
+ MarkLabel.propTypes = {
127
+ disabled: PropTypes.bool,
128
+ onChange: PropTypes.func,
129
+ graphProps: types.GraphPropsType,
130
+ classes: PropTypes.object,
131
+ inputRef: PropTypes.func,
132
+ mark: PropTypes.object,
133
+ theme: PropTypes.object,
134
+ };
135
+
136
+ export default withStyles(styles, { withTheme: true })(MarkLabel);
@@ -0,0 +1,242 @@
1
+ import React from 'react';
2
+ import PropTypes from 'prop-types';
3
+ import { withStyles } from '@material-ui/core/styles';
4
+ import cn from 'classnames';
5
+ import Button from '@material-ui/core/Button';
6
+ import { color } from '@pie-lib/render-ui';
7
+ import { allTools } from './tools';
8
+ import { withDragContext, DragSource, DropTarget } from '@pie-lib/drag';
9
+ import Translator from '@pie-lib/translator';
10
+
11
+ const { translator } = Translator;
12
+
13
+ const buttonStyles = () => ({
14
+ root: {
15
+ color: color.text(),
16
+ },
17
+ selected: {
18
+ backgroundColor: color.background(),
19
+ border: `1px solid ${color.secondary()}`,
20
+ },
21
+ notSelected: {
22
+ '& span': {
23
+ color: color.primary(),
24
+ },
25
+ backgroundColor: color.background(),
26
+ },
27
+ disabled: {
28
+ '& span': {
29
+ color: color.primary(),
30
+ },
31
+ backgroundColor: color.disabled(),
32
+ },
33
+ });
34
+
35
+ export const MiniButton = withStyles(buttonStyles)((props) => {
36
+ const { disabled, classes, className, selected, value, onClick, language } = props;
37
+ const translatorKey = value.toLowerCase();
38
+
39
+ return (
40
+ <Button
41
+ size="small"
42
+ disabled={disabled}
43
+ className={cn(classes.root, selected && classes.selected, className)}
44
+ classes={{ disabled: cn(disabled && classes.disabled) }}
45
+ value={value}
46
+ key={value}
47
+ variant="outlined"
48
+ onClick={(e) => onClick({ ...e, buttonValue: value })}
49
+ >
50
+ {translator.t(`graphing.${translatorKey}`, { lng: language })}
51
+ </Button>
52
+ );
53
+ });
54
+
55
+ MiniButton.propTypes = {
56
+ disabled: PropTypes.bool,
57
+ classes: PropTypes.object,
58
+ className: PropTypes.string,
59
+ disabledClassName: PropTypes.string,
60
+ selected: PropTypes.bool,
61
+ value: PropTypes.string,
62
+ onClick: PropTypes.func,
63
+ };
64
+
65
+ export class ToggleBar extends React.Component {
66
+ static propTypes = {
67
+ classes: PropTypes.object.isRequired,
68
+ className: PropTypes.string,
69
+ options: PropTypes.arrayOf(PropTypes.string),
70
+ selectedToolType: PropTypes.string,
71
+ disabled: PropTypes.bool,
72
+ draggableTools: PropTypes.bool,
73
+ onChange: PropTypes.func,
74
+ onChangeToolsOrder: PropTypes.func,
75
+ language: PropTypes.string,
76
+ };
77
+
78
+ static defaultProps = {};
79
+
80
+ select = (e) => this.props.onChange(e.buttonValue || e.target.textContent);
81
+
82
+ moveTool = (dragIndex, hoverIndex) => {
83
+ const { options, onChangeToolsOrder } = this.props;
84
+ const dragged = options[dragIndex];
85
+
86
+ options.splice(dragIndex, 1);
87
+ options.splice(hoverIndex, 0, dragged);
88
+
89
+ onChangeToolsOrder(options);
90
+ };
91
+
92
+ render() {
93
+ const { classes, className, disabled, options, selectedToolType, draggableTools, language } = this.props;
94
+
95
+ return (
96
+ <div className={cn(className, classes.toolsContainer)}>
97
+ {(options || []).map((option, index) => {
98
+ if ((allTools || []).includes(option)) {
99
+ const isSelected = option === selectedToolType;
100
+ const toolRef = React.createRef();
101
+
102
+ return (
103
+ <DragTool
104
+ key={option}
105
+ index={index}
106
+ draggable={draggableTools}
107
+ moveTool={this.moveTool}
108
+ classes={classes}
109
+ toolRef={toolRef}
110
+ >
111
+ <MiniButton
112
+ className={cn(classes.button, isSelected && classes.selected)}
113
+ disabled={disabled}
114
+ disableRipple={true}
115
+ onClick={this.select}
116
+ value={option}
117
+ selected={isSelected}
118
+ language={language}
119
+ />
120
+ </DragTool>
121
+ );
122
+ }
123
+ })}
124
+ </div>
125
+ );
126
+ }
127
+ }
128
+
129
+ const styles = (theme) => ({
130
+ toolsContainer: {
131
+ display: 'flex',
132
+ flexWrap: 'wrap',
133
+ },
134
+ button: {
135
+ marginRight: theme.spacing.unit / 2,
136
+ marginBottom: theme.spacing.unit / 2,
137
+ color: color.text(),
138
+ },
139
+ under: {
140
+ position: 'absolute',
141
+ top: 0,
142
+ left: 0,
143
+ zIndex: -1,
144
+ pointerEvents: 'none',
145
+ },
146
+ wrapper: {
147
+ position: 'relative',
148
+ },
149
+ hidden: {
150
+ opacity: 0,
151
+ },
152
+ });
153
+
154
+ export default withDragContext(withStyles(styles)(ToggleBar));
155
+
156
+ const DRAG_TYPE = 'tool';
157
+
158
+ export class Item extends React.Component {
159
+ static propTypes = {
160
+ classes: PropTypes.object.isRequired,
161
+ className: PropTypes.string,
162
+ children: PropTypes.oneOfType([PropTypes.arrayOf(PropTypes.node), PropTypes.node]),
163
+ connectDragSource: PropTypes.func.isRequired,
164
+ connectDragPreview: PropTypes.func.isRequired,
165
+ connectDropTarget: PropTypes.func.isRequired,
166
+ isDragging: PropTypes.bool,
167
+ toolRef: PropTypes.any,
168
+ };
169
+
170
+ static defaultProps = {};
171
+
172
+ render() {
173
+ const {
174
+ classes,
175
+ children,
176
+ connectDragSource,
177
+ connectDropTarget,
178
+ connectDragPreview,
179
+ isDragging,
180
+ toolRef,
181
+ } = this.props;
182
+
183
+ return (
184
+ <div className={classes.wrapper} ref={toolRef}>
185
+ {connectDragSource(connectDropTarget(<div className={isDragging && classes.hidden}>{children}</div>))}
186
+ {connectDragPreview(<div className={classes.under}>{children}</div>)}
187
+ </div>
188
+ );
189
+ }
190
+ }
191
+
192
+ const itemSource = {
193
+ canDrag(props) {
194
+ return props.draggable;
195
+ },
196
+ beginDrag(props) {
197
+ return {
198
+ index: props.index,
199
+ };
200
+ },
201
+ };
202
+
203
+ const itemTarget = {
204
+ hover(props, monitor) {
205
+ const dragIndex = monitor.getItem().index;
206
+ const { toolRef, index: hoverIndex } = props;
207
+
208
+ if (dragIndex === hoverIndex || !toolRef.current) {
209
+ return;
210
+ }
211
+
212
+ const hoverBoundingRect = toolRef.current?.getBoundingClientRect();
213
+ const hoverMiddleX = (hoverBoundingRect.right - hoverBoundingRect.left) / 2;
214
+ const clientOffset = monitor.getClientOffset();
215
+ const hoverClientX = clientOffset.x - hoverBoundingRect.left;
216
+
217
+ if (dragIndex < hoverIndex && hoverClientX < hoverMiddleX) {
218
+ return;
219
+ }
220
+
221
+ if (dragIndex > hoverIndex && hoverClientX > hoverMiddleX) {
222
+ return;
223
+ }
224
+
225
+ props.moveTool(dragIndex, hoverIndex);
226
+ monitor.getItem().index = hoverIndex;
227
+ },
228
+ };
229
+
230
+ const collectTarget = (connect) => ({ connectDropTarget: connect.dropTarget() });
231
+
232
+ const collectSource = (connect, monitor) => ({
233
+ connectDragSource: connect.dragSource(),
234
+ connectDragPreview: connect.dragPreview(),
235
+ isDragging: monitor.isDragging(),
236
+ });
237
+
238
+ const DragTool = DropTarget(
239
+ DRAG_TYPE,
240
+ itemTarget,
241
+ collectTarget,
242
+ )(DragSource(DRAG_TYPE, itemSource, collectSource)(Item));