Skip to content

Commit 61101c9

Browse files
committed
Add polygon editor
1 parent 016abbf commit 61101c9

File tree

4 files changed

+408
-59
lines changed

4 files changed

+408
-59
lines changed
Lines changed: 87 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,101 @@
11
// @flow
22
import * as React from 'react';
3-
import { Line } from '../../../UI/Grid';
3+
import {
4+
Table,
5+
TableRow,
6+
TableRowColumn,
7+
TableBody,
8+
TableHeader,
9+
TableHeaderColumn,
10+
} from 'material-ui/Table';
11+
import SemiControlledTextField from '../../../UI/SemiControlledTextField';
12+
import Warning from 'material-ui/svg-icons/alert/warning';
13+
import IconButton from 'material-ui/IconButton';
14+
import AddCircle from 'material-ui/svg-icons/content/add-circle';
15+
import Delete from 'material-ui/svg-icons/action/delete';
416

5-
type Vertice = {
17+
type Vertex = {
618
x: number,
719
y: number,
820
};
921

1022
type Props = {|
11-
vertices: Array<Vertice>,
23+
vertices: Array<Vertex>,
24+
onChangeVertexX: (newValue: number, index: number) => void,
25+
onChangeVertexY: (newValue: number, index: number) => void,
26+
onAdd: () => void,
27+
onRemove: (index: number) => void,
28+
hasWarning: boolean,
1229
|};
1330

1431
export default class PolygonEditor extends React.Component<Props> {
1532
render() {
16-
return <Line />;
33+
const {
34+
vertices,
35+
onChangeVertexX,
36+
onChangeVertexY,
37+
onAdd,
38+
onRemove,
39+
hasWarning,
40+
} = this.props;
41+
42+
return (
43+
<Table>
44+
<TableHeader displaySelectAll={false} adjustForCheckbox={false}>
45+
<TableRow>
46+
<TableHeaderColumn />
47+
<TableHeaderColumn>X</TableHeaderColumn>
48+
<TableHeaderColumn>Y</TableHeaderColumn>
49+
<TableRowColumn />
50+
</TableRow>
51+
</TableHeader>
52+
<TableBody
53+
displayRowCheckbox={false}
54+
deselectOnClickaway={true}
55+
showRowHover={false}
56+
>
57+
{vertices.map((value, index) => {
58+
return (
59+
<TableRow key={`vertexRow${index}`}>
60+
<TableRowColumn>{hasWarning && <Warning />}</TableRowColumn>
61+
<TableRowColumn>
62+
<SemiControlledTextField
63+
value={value.x.toString(10)}
64+
onChange={newValue =>
65+
onChangeVertexX(parseFloat(newValue) || 0, index)
66+
}
67+
type="number"
68+
/>
69+
</TableRowColumn>
70+
<TableRowColumn>
71+
<SemiControlledTextField
72+
value={value.y.toString(10)}
73+
onChange={newValue =>
74+
onChangeVertexY(parseFloat(newValue) || 0, index)
75+
}
76+
type="number"
77+
/>
78+
</TableRowColumn>
79+
<TableRowColumn>
80+
<IconButton onClick={() => onRemove(index)}>
81+
<Delete />
82+
</IconButton>
83+
</TableRowColumn>
84+
</TableRow>
85+
);
86+
})}
87+
<TableRow>
88+
<TableRowColumn />
89+
<TableRowColumn />
90+
<TableRowColumn />
91+
<TableRowColumn>
92+
<IconButton onClick={onAdd}>
93+
<AddCircle />
94+
</IconButton>
95+
</TableRowColumn>
96+
</TableRow>
97+
</TableBody>
98+
</Table>
99+
);
17100
}
18101
}
Lines changed: 183 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
// @flow
22
import * as React from 'react';
33

4-
type Vertice = {
4+
type Vertex = {
55
x: number,
66
y: number,
77
};
@@ -13,64 +13,201 @@ type Props = {|
1313
offsetX: number,
1414
offsetY: number,
1515
polygonOrigin: string,
16-
vertices: Array<Vertice>,
16+
vertices: Array<Vertex>,
1717
width: number,
1818
height: number,
19+
onMoveVertex: (index: number, newX: number, newY: number) => void,
1920
|};
2021

2122
type State = {|
22-
image: string,
23+
draggedVertex: ?Vertex,
24+
draggedIndex: number,
2325
|};
2426

2527
export default class ShapePreview extends React.Component<Props, State> {
28+
constructor(props: Props) {
29+
super(props);
30+
this.state = { draggedVertex: null, draggedIndex: -1 };
31+
}
2632

27-
render() {
28-
29-
const {dimensionA, dimensionB, shape, offsetX, offsetY, width, height} = this.props;
33+
_svg: any;
34+
35+
_onVertexDown = (vertex: Vertex, index: number) => {
36+
if (this.state.draggedVertex) return;
37+
this.setState({ draggedVertex: vertex, draggedIndex: index });
38+
};
39+
40+
_onMouseUp = () => {
41+
const draggingWasDone = !!this.state.draggedVertex;
42+
const { draggedVertex, draggedIndex } = this.state;
43+
this.setState(
44+
{
45+
draggedVertex: null,
46+
},
47+
() => {
48+
if (draggingWasDone)
49+
this.props.onMoveVertex(
50+
draggedIndex,
51+
Math.round(draggedVertex ? draggedVertex.x : 0),
52+
Math.round(draggedVertex ? draggedVertex.y : 0)
53+
);
54+
}
55+
);
56+
};
57+
58+
_onMouseMove = (event: any) => {
59+
const { offsetX, offsetY, polygonOrigin, width, height } = this.props;
60+
const { draggedVertex } = this.state;
61+
if (!draggedVertex) return;
62+
63+
const pointOnScreen = this._svg.createSVGPoint();
64+
pointOnScreen.x = event.clientX;
65+
pointOnScreen.y = event.clientY;
66+
const screenToSvgMatrix = this._svg.getScreenCTM().inverse();
67+
const pointOnSvg = pointOnScreen.matrixTransform(screenToSvgMatrix);
68+
69+
draggedVertex.x =
70+
pointOnSvg.x - offsetX - (polygonOrigin === 'Center' ? width / 2 : 0);
71+
draggedVertex.y =
72+
pointOnSvg.y - offsetY - (polygonOrigin === 'Center' ? height / 2 : 0);
73+
74+
this.forceUpdate();
75+
};
76+
77+
renderBox() {
78+
const {
79+
dimensionA,
80+
dimensionB,
81+
width,
82+
height,
83+
offsetX,
84+
offsetY,
85+
} = this.props;
3086
const fixedWidth = dimensionA > 0 ? dimensionA : width > 0 ? width : 1;
3187
const fixedHeight = dimensionB > 0 ? dimensionB : height > 0 ? height : 1;
3288

3389
return (
34-
<div>
35-
{shape === 'Box' && (
36-
<svg>
37-
<rect
38-
key={`boxShape`}
39-
fill="rgba(255,0,0,0.75)"
40-
strokeWidth={1}
41-
x={offsetX + width/2- fixedWidth/2}
42-
y={offsetY + height/2 - fixedHeight/2}
43-
width={fixedWidth}
44-
height={fixedHeight}
45-
/>
46-
</svg>
47-
)}
48-
{shape === 'Circle' && (
49-
<svg>
50-
<circle
51-
key={`boxShape`}
52-
fill="rgba(255,0,0,0.75)"
53-
strokeWidth={1}
54-
cx={offsetX + width/2}
55-
cy={offsetY + height/2}
56-
r={dimensionA > 0 ? dimensionA : (width + height) > 0 ? (width + height) / 4 : 1}
57-
/>
58-
</svg>
59-
)}
60-
{shape === 'Edge' && (
61-
<svg>
62-
<line
63-
key={`boxEdge`}
64-
stroke="rgba(255,0,0,0.75)"
65-
strokeWidth={2}
66-
x1={offsetX + width/2 - fixedWidth/2 * Math.cos(dimensionB*Math.PI/180)}
67-
y1={offsetY + height/2 - fixedWidth/2 * Math.sin(dimensionB*Math.PI/180)}
68-
x2={offsetX + width/2 + fixedWidth/2 * Math.cos(dimensionB*Math.PI/180)}
69-
y2={offsetY + height/2 + fixedWidth/2 * Math.sin(dimensionB*Math.PI/180)}
70-
/>
71-
</svg>
72-
)}
73-
</div>
90+
<rect
91+
key={'boxShape'}
92+
fill="rgba(255,0,0,0.75)"
93+
strokeWidth={1}
94+
x={offsetX + width / 2 - fixedWidth / 2}
95+
y={offsetY + height / 2 - fixedHeight / 2}
96+
width={fixedWidth}
97+
height={fixedHeight}
98+
/>
99+
);
100+
}
101+
102+
renderCircle() {
103+
const { dimensionA, width, height, offsetX, offsetY } = this.props;
104+
105+
return (
106+
<circle
107+
key={'circleShape'}
108+
fill="rgba(255,0,0,0.75)"
109+
strokeWidth={1}
110+
cx={offsetX + width / 2}
111+
cy={offsetY + height / 2}
112+
r={
113+
dimensionA > 0
114+
? dimensionA
115+
: width + height > 0
116+
? (width + height) / 4
117+
: 1
118+
}
119+
/>
120+
);
121+
}
122+
123+
renderEdge() {
124+
const {
125+
dimensionA,
126+
dimensionB,
127+
width,
128+
height,
129+
offsetX,
130+
offsetY,
131+
} = this.props;
132+
const halfLength =
133+
(dimensionA > 0 ? dimensionA : width > 0 ? width : 1) / 2;
134+
const cos = Math.cos((dimensionB * Math.PI) / 180);
135+
const sin = Math.sin((dimensionB * Math.PI) / 180);
136+
137+
return (
138+
<line
139+
key={'edgeShape'}
140+
stroke="rgba(255,0,0,0.75)"
141+
strokeWidth={2}
142+
x1={offsetX + width / 2 - halfLength * cos}
143+
y1={offsetY + height / 2 - halfLength * sin}
144+
x2={offsetX + width / 2 + halfLength * cos}
145+
y2={offsetY + height / 2 + halfLength * sin}
146+
/>
147+
);
148+
}
149+
150+
renderPolygon() {
151+
const {
152+
vertices,
153+
polygonOrigin,
154+
width,
155+
height,
156+
offsetX,
157+
offsetY,
158+
} = this.props;
159+
160+
return (
161+
<React.Fragment>
162+
<polygon
163+
key={'polygonShape'}
164+
fill="rgba(255,0,0,0.75)"
165+
strokeWidth={1}
166+
filerule="evenodd"
167+
points={vertices
168+
.map(
169+
vertex =>
170+
`${vertex.x +
171+
offsetX +
172+
(polygonOrigin === 'Center' ? width / 2 : 0)},${vertex.y +
173+
offsetY +
174+
(polygonOrigin === 'Center' ? height / 2 : 0)}`
175+
)
176+
.join(' ')}
177+
/>
178+
{vertices.map((vertex, index) => (
179+
<circle
180+
onPointerDown={event => this._onVertexDown(vertex, index)}
181+
key={`vertex-${index}`}
182+
fill="rgba(150,0,0,0.75)"
183+
strokeWidth={1}
184+
cx={
185+
vertex.x + offsetX + (polygonOrigin === 'Center' ? width / 2 : 0)
186+
}
187+
cy={
188+
vertex.y + offsetY + (polygonOrigin === 'Center' ? height / 2 : 0)
189+
}
190+
r={5}
191+
/>
192+
))}
193+
</React.Fragment>
194+
);
195+
}
196+
197+
render() {
198+
const { shape } = this.props;
199+
200+
return (
201+
<svg
202+
onPointerMove={this._onMouseMove}
203+
onPointerUp={this._onMouseUp}
204+
ref={svg => (this._svg = svg)}
205+
>
206+
{shape === 'Box' && this.renderBox()}
207+
{shape === 'Circle' && this.renderCircle()}
208+
{shape === 'Edge' && this.renderEdge()}
209+
{shape === 'Polygon' && this.renderPolygon()}
210+
</svg>
74211
);
75212
}
76213
}

0 commit comments

Comments
 (0)