@pie-element/hotspot 6.1.2-next.44 → 6.1.2-next.69

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 (33) hide show
  1. package/configure/lib/buttons/circle.js +40 -0
  2. package/configure/lib/buttons/circle.js.map +1 -0
  3. package/configure/lib/buttons/polygon.js +46 -0
  4. package/configure/lib/buttons/polygon.js.map +1 -0
  5. package/configure/lib/buttons/rectangle.js +46 -0
  6. package/configure/lib/buttons/rectangle.js.map +1 -0
  7. package/configure/lib/hotspot-container.js +44 -4
  8. package/configure/lib/hotspot-container.js.map +1 -1
  9. package/configure/lib/hotspot-drawable.js +180 -115
  10. package/configure/lib/hotspot-drawable.js.map +1 -1
  11. package/configure/lib/hotspot-polygon.js +11 -2
  12. package/configure/lib/hotspot-polygon.js.map +1 -1
  13. package/configure/lib/shapes/circle.js +96 -0
  14. package/configure/lib/shapes/circle.js.map +1 -0
  15. package/configure/lib/shapes/polygon.js +111 -0
  16. package/configure/lib/shapes/polygon.js.map +1 -0
  17. package/configure/lib/shapes/rectagle.js +115 -0
  18. package/configure/lib/shapes/rectagle.js.map +1 -0
  19. package/configure/package.json +1 -1
  20. package/configure/src/__tests__/__snapshots__/hotspot-container.test.js.snap +16 -0
  21. package/configure/src/__tests__/__snapshots__/hotspot-drawable.test.js.snap +42 -0
  22. package/configure/src/__tests__/hotspot-drawable.test.js +4 -1
  23. package/configure/src/buttons/circle.jsx +18 -0
  24. package/configure/src/buttons/polygon.jsx +29 -0
  25. package/configure/src/buttons/rectangle.jsx +29 -0
  26. package/configure/src/hotspot-container.jsx +31 -2
  27. package/configure/src/hotspot-drawable.jsx +112 -54
  28. package/configure/src/hotspot-polygon.jsx +12 -2
  29. package/configure/src/shapes/circle.js +67 -0
  30. package/configure/src/shapes/polygon.js +79 -0
  31. package/configure/src/shapes/rectagle.js +80 -0
  32. package/controller/package.json +1 -1
  33. package/package.json +3 -3
@@ -1,14 +1,15 @@
1
1
  import React from 'react';
2
2
  import PropTypes from 'prop-types';
3
3
  import { Layer, Stage } from 'react-konva';
4
- import max from 'lodash/max';
5
4
  import cloneDeep from 'lodash/cloneDeep';
6
5
  import { withStyles } from '@material-ui/core/styles/index';
7
6
 
8
7
  import Rectangle from './hotspot-rectangle';
9
8
  import Polygon from './hotspot-polygon';
10
9
  import { updateImageDimensions, getUpdatedShapes } from './utils';
11
-
10
+ import RectangleShape from './shapes/rectagle';
11
+ import CircleShape from './shapes/circle';
12
+ import PolygonShape from './shapes/polygon';
12
13
  const IMAGE_MAX_WIDTH = 800;
13
14
 
14
15
  export class Drawable extends React.Component {
@@ -32,6 +33,8 @@ export class Drawable extends React.Component {
32
33
  isDrawing: false,
33
34
  stateShapes: false,
34
35
  resizing: false,
36
+ temporaryPolygon: null,
37
+ shapes: [],
35
38
  dimensions: {
36
39
  height: 0,
37
40
  width: 0,
@@ -39,72 +42,122 @@ export class Drawable extends React.Component {
39
42
  };
40
43
  }
41
44
 
42
- /// start of handling HotSpots section
43
45
  handleOnMouseDown = (e) => {
44
- if (e.target === e.currentTarget) {
45
- const { onUpdateShapes, shapes } = this.props;
46
- // Add a new rectangle at the mouse position with 0 width and height
47
- const newShapes = shapes.slice();
48
- const value = max(newShapes.map((c) => parseInt(c.id)).filter((id) => !isNaN(id))) || 0;
49
-
50
- newShapes.push({
51
- id: `${value + 1}`,
52
- height: 0,
53
- width: 0,
54
- x: e.evt.layerX,
55
- y: e.evt.layerY,
56
- group: 'rectangles',
57
- index: shapes.length,
58
- });
46
+ const { shapeType, onUpdateShapes, shapes } = this.props;
47
+ let newState, newShapeId;
59
48
 
60
- this.setState({ isDrawing: true, isDrawingShapeId: `${value + 1}` });
61
- onUpdateShapes(newShapes);
49
+ // Ensure that the click originated from the expected element
50
+ if (e.target !== e.currentTarget) {
51
+ return;
62
52
  }
63
- };
64
53
 
65
- handleOnMouseUp = () => {
66
- // !IMPORTANT: currently, only 'rectangles' are drawable
67
- const { isDrawing, stateShapes } = this.state;
68
- const { onUpdateShapes } = this.props;
54
+ switch (shapeType) {
55
+ case 'rectangle':
56
+ newState = RectangleShape.create(shapes, e);
57
+ break;
58
+ case 'circle':
59
+ newState = CircleShape.create(shapes, e);
60
+ break;
61
+ case 'polygon':
62
+ newShapeId = this.state.isDrawingShapeId;
63
+
64
+ if (newShapeId) {
65
+ // If a polygon is in progress, add a new point
66
+ const shapes = this.addPolygonPoint(e);
67
+
68
+ newState = {
69
+ isDrawing: true,
70
+ isDrawingShapeId: newShapeId,
71
+ shapes: shapes,
72
+ };
73
+ } else {
74
+ // Else start a new one
75
+ newState = PolygonShape.create(shapes, e);
76
+ }
77
+ break;
78
+ default:
79
+ console.error(`Unsupported shape type: ${shapeType}`);
80
+ return;
81
+ }
69
82
 
70
- // If we're drawing a shape, a click finishes the drawing and sends the new shapes to HOC
71
- if (isDrawing) {
72
- this.setState({ isDrawing: !isDrawing, stateShapes: false, isDrawingShapeId: undefined });
83
+ this.setState({
84
+ ...newState,
85
+ });
73
86
 
74
- if (stateShapes) {
75
- onUpdateShapes(stateShapes);
76
- }
77
- }
87
+ onUpdateShapes(newState.shapes);
78
88
  };
79
89
 
80
- handleMouseMove = (e) => {
81
- // !IMPORTANT: currently, only 'rectangles' are drawable
82
- const { isDrawing, isDrawingShapeId } = this.state;
83
- const { shapes } = this.props;
90
+ addPolygonPoint(e) {
91
+ const { shapes } = PolygonShape.addPoint(this.state, e, (newShapes) => {
92
+ this.setState({
93
+ isDrawing: false,
94
+ shapes: newShapes,
95
+ isDrawingShapeId: undefined,
96
+ });
97
+
98
+ this.props.onUpdateShapes(newShapes);
99
+ this.props.handleFinishDrawing();
100
+ });
84
101
 
85
- if (isDrawing && isDrawingShapeId) {
86
- const mouseX = e.evt.layerX;
87
- const mouseY = e.evt.layerY;
102
+ return shapes;
103
+ }
88
104
 
89
- const currShapeIndex = shapes.findIndex((shape) => shape.id === isDrawingShapeId);
90
- const currShape = shapes[currShapeIndex];
105
+ handleOnMouseUp = () => {
106
+ const { shapeType, onUpdateShapes } = this.props;
107
+ let newState;
91
108
 
92
- if (currShape) {
93
- const newWidth = mouseX - currShape.x;
94
- const newHeight = mouseY - currShape.y;
109
+ if (shapeType === 'polygon') {
110
+ return;
111
+ }
95
112
 
96
- const newShapesList = shapes.slice();
113
+ switch (shapeType) {
114
+ case 'rectangle':
115
+ newState = RectangleShape.finalizeCreation(this.state, this.props);
116
+ break;
117
+ case 'circle':
118
+ newState = CircleShape.finalizeCreation(this.state, this.props);
119
+ break;
120
+ default:
121
+ console.error(`Unsupported shape type: ${shapeType}`);
122
+ return;
123
+ }
97
124
 
98
- newShapesList[currShapeIndex] = {
99
- ...newShapesList[currShapeIndex],
100
- height: newHeight,
101
- width: newWidth,
102
- x: currShape.x,
103
- y: currShape.y,
104
- };
105
- // On mouse move don't trigger any event. Put the shapes on this state instead.
106
- this.setState({ stateShapes: newShapesList });
125
+ this.setState({
126
+ ...newState,
127
+ isDrawing: false,
128
+ });
129
+
130
+ onUpdateShapes(newState.shapes);
131
+ };
132
+
133
+ handleMouseMove = (e) => {
134
+ const { shapeType, onUpdateShapes, shapes } = this.props;
135
+ let newState;
136
+
137
+ if (this.state.isDrawing) {
138
+ switch (shapeType) {
139
+ case 'rectangle':
140
+ newState = RectangleShape.handleMouseMove(this.state, e);
141
+ break;
142
+ case 'circle':
143
+ newState = CircleShape.handleMouseMove(this.state, e);
144
+ break;
145
+ case 'polygon':
146
+ newState = PolygonShape.handleMouseMove(this.state, e);
147
+ break;
148
+ default:
149
+ console.error(`Unsupported shape type: ${shapeType}`);
150
+ return;
107
151
  }
152
+
153
+ this.setState(newState);
154
+ onUpdateShapes(newState.shapes);
155
+ }
156
+ };
157
+
158
+ handleOnMouseOutOrLeave = (e) => {
159
+ if (this.state.isDrawing) {
160
+ this.handleOnMouseUp(e);
108
161
  }
109
162
  };
110
163
 
@@ -291,6 +344,8 @@ export class Drawable extends React.Component {
291
344
  onMouseDown={this.handleOnMouseDown}
292
345
  onMouseUp={this.handleOnMouseUp}
293
346
  onMouseMove={this.handleMouseMove}
347
+ onContentMouseOut={this.handleOnMouseOutOrLeave}
348
+ onContentMouseLeave={this.handleOnMouseOutOrLeave}
294
349
  >
295
350
  <Layer>
296
351
  {shapesToUse.map((shape, i) => {
@@ -314,6 +369,7 @@ export class Drawable extends React.Component {
314
369
  strokeWidth={strokeWidth}
315
370
  imageHeight={heightFromState || height}
316
371
  imageWidth={widthFromState || width}
372
+ addPolygonPoint={(e) => this.addPolygonPoint(e)}
317
373
  />
318
374
  );
319
375
  })}
@@ -359,7 +415,9 @@ Drawable.propTypes = {
359
415
  disableDrag: PropTypes.func.isRequired,
360
416
  dimensions: PropTypes.object.isRequired,
361
417
  enableDrag: PropTypes.func.isRequired,
418
+ shapeType: PropTypes.oneOf(['rectangle', 'circle', 'polygon']),
362
419
  imageUrl: PropTypes.string.isRequired,
420
+ handleFinishDrawing: PropTypes.func.isRequired,
363
421
  hotspotColor: PropTypes.string.isRequired,
364
422
  multipleCorrect: PropTypes.bool.isRequired,
365
423
  onUpdateImageDimension: PropTypes.func.isRequired,
@@ -13,7 +13,8 @@ class PolComponent extends React.Component {
13
13
  if (
14
14
  prevState.imageHeight !== nextProps.imageHeight ||
15
15
  prevState.imageWidth !== nextProps.imageWidth ||
16
- prevState.id !== nextProps.id
16
+ prevState.id !== nextProps.id ||
17
+ prevState.points.length !== points.length
17
18
  ) {
18
19
  if (points.length) {
19
20
  const xList = points.map((p) => p.x);
@@ -100,6 +101,11 @@ class PolComponent extends React.Component {
100
101
  return;
101
102
  }
102
103
 
104
+ if (isDrawing && id === 'newPolygon') {
105
+ this.props.addPolygonPoint(e);
106
+ return;
107
+ }
108
+
103
109
  e.cancelBubble = true;
104
110
  onClick(id);
105
111
  };
@@ -170,9 +176,11 @@ class PolComponent extends React.Component {
170
176
  render() {
171
177
  const { classes, correct, id, hotspotColor, outlineColor, strokeWidth = 5 } = this.props;
172
178
  const { points, x, y, hovered } = this.state;
179
+ const showPoints = hovered || id === 'newPolygon';
173
180
 
174
181
  const calculatedStrokeWidth = correct ? strokeWidth : hovered ? 1 : 0;
175
182
  const calculatedStroke = correct ? outlineColor : hovered ? HOVERED_COLOR : '';
183
+
176
184
  return (
177
185
  <Group classes={classes.group} onMouseLeave={this.handleMouseLeave} onMouseEnter={this.handleMouseEnter}>
178
186
  <Line
@@ -192,7 +200,7 @@ class PolComponent extends React.Component {
192
200
  y={y}
193
201
  />
194
202
 
195
- {hovered &&
203
+ {showPoints &&
196
204
  points.map((point, index) => (
197
205
  <Circle
198
206
  key={index}
@@ -203,6 +211,7 @@ class PolComponent extends React.Component {
203
211
  fill={'white'}
204
212
  stroke={HOVERED_COLOR}
205
213
  strokeWidth={1}
214
+ onClick={this.handleClick}
206
215
  onDragStart={this.onDragStart}
207
216
  onDragMove={(e) => {
208
217
  this.handleOnDragVertex(e, index);
@@ -250,6 +259,7 @@ PolComponent.propTypes = {
250
259
  imageWidth: PropTypes.number,
251
260
  hotspotColor: PropTypes.string.isRequired,
252
261
  onClick: PropTypes.func.isRequired,
262
+ addPolygonPoint: PropTypes.func.isRequired,
253
263
  onDeleteShape: PropTypes.func.isRequired,
254
264
  onDragEnd: PropTypes.func.isRequired,
255
265
  outlineColor: PropTypes.string.isRequired,
@@ -0,0 +1,67 @@
1
+ export default class CircleShape {
2
+ static create(shapes, e) {
3
+ const newShapes = [...shapes];
4
+ const highestId = Math.max(...newShapes.map((shape) => parseInt(shape.id)), 0);
5
+ const newCircle = {
6
+ id: `${highestId + 1}`,
7
+ radius: 0,
8
+ x: e.evt.layerX,
9
+ y: e.evt.layerY,
10
+ group: 'circles',
11
+ index: newShapes.length,
12
+ };
13
+
14
+ newShapes.push(newCircle);
15
+
16
+ return {
17
+ newShapes,
18
+ isDrawing: true,
19
+ isDrawingShapeId: newCircle.id,
20
+ };
21
+ }
22
+
23
+ static finalizeCreation(state, props) {
24
+ const currentShapeIndex = state.shapes.findIndex((shape) => shape.id === state.isDrawingShapeId);
25
+
26
+ if (currentShapeIndex !== -1) {
27
+ const currentShape = state.shapes[currentShapeIndex];
28
+ const updatedShapes = [...props.shapes];
29
+
30
+ if (currentShape.radius > 0) {
31
+ updatedShapes.push(currentShape);
32
+
33
+ return {
34
+ isDrawing: false,
35
+ stateShapes: false,
36
+ isDrawingShapeId: undefined,
37
+ shapes: updatedShapes,
38
+ };
39
+ }
40
+ }
41
+
42
+ return state;
43
+ }
44
+
45
+ static handleMouseMove(state, e) {
46
+ const { isDrawing, isDrawingShapeId, shapes } = state;
47
+
48
+ if (isDrawing) {
49
+ const tempShapes = [...shapes];
50
+ const resizingShapeIndex = tempShapes.findIndex((shape) => shape.id === isDrawingShapeId);
51
+
52
+ if (resizingShapeIndex !== -1) {
53
+ const resizingShape = tempShapes[resizingShapeIndex];
54
+
55
+ resizingShape.radius = Math.sqrt(
56
+ Math.pow(e.evt.layerX - resizingShape.x, 2) + Math.pow(e.evt.layerY - resizingShape.y, 2),
57
+ );
58
+
59
+ return {
60
+ shapes: tempShapes,
61
+ };
62
+ }
63
+ }
64
+
65
+ return state;
66
+ }
67
+ }
@@ -0,0 +1,79 @@
1
+ export default class PolygonShape {
2
+ static create(shapes, e) {
3
+ const newShapes = [...shapes];
4
+ const newPolygon = {
5
+ id: 'newPolygon',
6
+ points: [{ x: e.evt.layerX, y: e.evt.layerY }],
7
+ group: 'polygons',
8
+ index: newShapes.length,
9
+ };
10
+
11
+ newShapes.push(newPolygon);
12
+
13
+ return {
14
+ shapes: newShapes,
15
+ isDrawing: true,
16
+ isDrawingShapeId: newPolygon.id,
17
+ };
18
+ }
19
+
20
+ static addPoint(state, e, onPolygonComplete) {
21
+ // Number of pixels allowed to determine if the first point was clicked
22
+ const clickDelta = 5;
23
+
24
+ const shapesCopy = JSON.parse(JSON.stringify(state.shapes));
25
+ const currentShapeIndex = shapesCopy.findIndex((shape) => shape.id === state.isDrawingShapeId);
26
+
27
+ if (currentShapeIndex !== -1) {
28
+ const currentShape = shapesCopy[currentShapeIndex];
29
+ if (currentShape.points && Array.isArray(currentShape.points)) {
30
+ const firstPoint = currentShape.points[0];
31
+
32
+ // If click is close enough to the first point (within clickDelta pixels), close the polygon
33
+ if (
34
+ Math.abs(firstPoint.x - e.evt.layerX) <= clickDelta &&
35
+ Math.abs(firstPoint.y - e.evt.layerY) <= clickDelta
36
+ ) {
37
+ return PolygonShape.finalizeCreation(state, onPolygonComplete);
38
+ }
39
+
40
+ currentShape.points.push({ x: e.evt.layerX, y: e.evt.layerY });
41
+ shapesCopy[currentShapeIndex] = currentShape;
42
+
43
+ return {
44
+ shapes: shapesCopy,
45
+ };
46
+ }
47
+ }
48
+ return state;
49
+ }
50
+
51
+ static finalizeCreation(state, onPolygonComplete) {
52
+ const { shapes } = state;
53
+ const tempShapes = [...shapes];
54
+ const highestId = Math.max(...state.shapes.map((shape) => parseInt(shape.id) || 0), 0);
55
+
56
+ const polygonIndex = tempShapes.findIndex((shape) => shape.id === state.isDrawingShapeId);
57
+
58
+ if (polygonIndex !== -1 && tempShapes[polygonIndex].points.length > 2) {
59
+ const completedPolygon = tempShapes[polygonIndex];
60
+
61
+ completedPolygon.id = `${highestId + 1}`;
62
+ onPolygonComplete(tempShapes);
63
+
64
+ return {
65
+ isDrawing: false,
66
+ shapes: tempShapes,
67
+ isDrawingShapeId: undefined,
68
+ };
69
+ }
70
+
71
+ return state;
72
+ }
73
+
74
+ // No need to update anything on mouse move,
75
+ // but it's here if we need to add any logic later.
76
+ static handleMouseMove(state, e) {
77
+ return state;
78
+ }
79
+ }
@@ -0,0 +1,80 @@
1
+ export default class RectangleShape {
2
+ static create(shapes, e) {
3
+ const newShapes = [...shapes];
4
+ const highestId = Math.max(...newShapes.map((shape) => parseInt(shape.id)), 0) || 0;
5
+
6
+ const newRectangle = {
7
+ id: `${highestId + 1}`,
8
+ height: 0,
9
+ width: 0,
10
+ x: e.evt.layerX,
11
+ y: e.evt.layerY,
12
+ group: 'rectangles',
13
+ index: newShapes.length,
14
+ };
15
+
16
+ newShapes.push(newRectangle);
17
+
18
+ return {
19
+ shapes: newShapes,
20
+ isDrawing: true,
21
+ isDrawingShapeId: newRectangle.id,
22
+ };
23
+ }
24
+
25
+ static finalizeCreation(state, props) {
26
+ const currentShapeIndex = state.shapes.findIndex((shape) => shape.id === state.isDrawingShapeId);
27
+
28
+ if (currentShapeIndex !== -1) {
29
+ const currentShape = state.shapes[currentShapeIndex];
30
+
31
+ // Check if the shape is a valid rectangle (has more than 0 width and height) before finalizing
32
+ if (currentShape.width > 0 && currentShape.height > 0) {
33
+ return {
34
+ ...state,
35
+ isDrawing: false,
36
+ stateShapes: false,
37
+ isDrawingShapeId: undefined,
38
+ };
39
+ } else {
40
+ return {
41
+ ...state,
42
+ isDrawing: false,
43
+ stateShapes: false,
44
+ isDrawingShapeId: undefined,
45
+ shapes: state.shapes.filter((shape) => shape.id !== state.isDrawingShapeId),
46
+ };
47
+ }
48
+ }
49
+
50
+ // Return current state if not drawing a rectangle
51
+ return {
52
+ ...state,
53
+ isDrawing: false,
54
+ stateShapes: false,
55
+ isDrawingShapeId: undefined,
56
+ };
57
+ }
58
+
59
+ static handleMouseMove(state, e) {
60
+ const { isDrawing, isDrawingShapeId, shapes } = state;
61
+
62
+ if (isDrawing) {
63
+ const tempShapes = [...shapes];
64
+ const resizingShapeIndex = tempShapes.findIndex((shape) => shape.id === isDrawingShapeId);
65
+
66
+ if (resizingShapeIndex !== -1) {
67
+ const resizingShape = tempShapes[resizingShapeIndex];
68
+
69
+ resizingShape.width = e.evt.layerX - resizingShape.x;
70
+ resizingShape.height = e.evt.layerY - resizingShape.y;
71
+
72
+ return {
73
+ shapes: tempShapes,
74
+ };
75
+ }
76
+ }
77
+
78
+ return state;
79
+ }
80
+ }
@@ -11,7 +11,7 @@
11
11
  "main": "lib/index.js",
12
12
  "module": "src/index.js",
13
13
  "dependencies": {
14
- "@pie-lib/pie-toolbox": "1.2.3",
14
+ "@pie-lib/pie-toolbox": "1.4.0",
15
15
  "debug": "^4.1.1",
16
16
  "lodash": "^4.17.15"
17
17
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pie-element/hotspot",
3
- "version": "6.1.2-next.44+0e15a9c76",
3
+ "version": "6.1.2-next.69+84f517e36",
4
4
  "description": "",
5
5
  "repository": "pie-framework/pie-elements",
6
6
  "publishConfig": {
@@ -9,7 +9,7 @@
9
9
  "dependencies": {
10
10
  "@material-ui/core": "^3.9.3",
11
11
  "@pie-framework/pie-player-events": "^0.1.0",
12
- "@pie-lib/pie-toolbox": "1.2.3",
12
+ "@pie-lib/pie-toolbox": "1.4.0",
13
13
  "konva": "^3.2.5",
14
14
  "prop-types": "^15.6.1",
15
15
  "react": "^16.8.1",
@@ -18,7 +18,7 @@
18
18
  },
19
19
  "author": "pie framework developers",
20
20
  "license": "ISC",
21
- "gitHead": "0e15a9c76e19cff2dd791799dbc94b4fdecd2417",
21
+ "gitHead": "84f517e360d8415650bb7f0aec1be8cf8660c8f9",
22
22
  "scripts": {
23
23
  "postpublish": "../../scripts/postpublish"
24
24
  },