|
|
|
|
@ -150,148 +150,169 @@ function clearSelection() {
@@ -150,148 +150,169 @@ function clearSelection() {
|
|
|
|
|
}); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
function App() { |
|
|
|
|
const [draggingElement, setDraggingElement] = React.useState(null); |
|
|
|
|
const [elementType, setElementType] = React.useState("selection"); |
|
|
|
|
const onKeyDown = React.useCallback(event => { |
|
|
|
|
if (event.key === "Backspace") { |
|
|
|
|
for (var i = elements.length - 1; i >= 0; --i) { |
|
|
|
|
if (elements[i].isSelected) { |
|
|
|
|
elements.splice(i, 1); |
|
|
|
|
class App extends React.Component { |
|
|
|
|
componentDidMount() { |
|
|
|
|
this.onKeyDown = event => { |
|
|
|
|
if (event.key === "Backspace") { |
|
|
|
|
for (var i = elements.length - 1; i >= 0; --i) { |
|
|
|
|
if (elements[i].isSelected) { |
|
|
|
|
elements.splice(i, 1); |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
drawScene(); |
|
|
|
|
event.preventDefault(); |
|
|
|
|
} else if ( |
|
|
|
|
event.key === "ArrowLeft" || |
|
|
|
|
event.key === "ArrowRight" || |
|
|
|
|
event.key === "ArrowUp" || |
|
|
|
|
event.key === "ArrowDown" |
|
|
|
|
) { |
|
|
|
|
const step = event.shiftKey ? 5 : 1; |
|
|
|
|
elements.forEach(element => { |
|
|
|
|
if (element.isSelected) { |
|
|
|
|
if (event.key === "ArrowLeft") element.x -= step; |
|
|
|
|
else if (event.key === "ArrowRight") element.x += step; |
|
|
|
|
else if (event.key === "ArrowUp") element.y -= step; |
|
|
|
|
else if (event.key === "ArrowDown") element.y += step; |
|
|
|
|
} |
|
|
|
|
}); |
|
|
|
|
drawScene(); |
|
|
|
|
event.preventDefault(); |
|
|
|
|
} |
|
|
|
|
drawScene(); |
|
|
|
|
event.preventDefault(); |
|
|
|
|
} else if ( |
|
|
|
|
event.key === "ArrowLeft" || |
|
|
|
|
event.key === "ArrowRight" || |
|
|
|
|
event.key === "ArrowUp" || |
|
|
|
|
event.key === "ArrowDown" |
|
|
|
|
) { |
|
|
|
|
const step = event.shiftKey ? 5 : 1; |
|
|
|
|
elements.forEach(element => { |
|
|
|
|
if (element.isSelected) { |
|
|
|
|
if (event.key === "ArrowLeft") element.x -= step; |
|
|
|
|
else if (event.key === "ArrowRight") element.x += step; |
|
|
|
|
else if (event.key === "ArrowUp") element.y -= step; |
|
|
|
|
else if (event.key === "ArrowDown") element.y += step; |
|
|
|
|
} |
|
|
|
|
}); |
|
|
|
|
drawScene(); |
|
|
|
|
event.preventDefault(); |
|
|
|
|
} |
|
|
|
|
}, []); |
|
|
|
|
React.useEffect(() => { |
|
|
|
|
document.addEventListener("keydown", onKeyDown, false); |
|
|
|
|
return () => { |
|
|
|
|
document.removeEventListener("keydown", onKeyDown, false); |
|
|
|
|
}; |
|
|
|
|
}, [onKeyDown]); |
|
|
|
|
document.addEventListener("keydown", this.onKeyDown, false); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
function ElementOption({ type, children }) { |
|
|
|
|
return ( |
|
|
|
|
<label> |
|
|
|
|
<input |
|
|
|
|
type="radio" |
|
|
|
|
checked={elementType === type} |
|
|
|
|
onChange={() => { |
|
|
|
|
setElementType(type); |
|
|
|
|
clearSelection(); |
|
|
|
|
drawScene(); |
|
|
|
|
}} |
|
|
|
|
/> |
|
|
|
|
{children} |
|
|
|
|
</label> |
|
|
|
|
); |
|
|
|
|
componentWillUnmount() { |
|
|
|
|
document.removeEventListener("keydown", this.onKeyDown, false); |
|
|
|
|
} |
|
|
|
|
return ( |
|
|
|
|
<div> |
|
|
|
|
{/* Can't use the <ElementOption> form because ElementOption is re-defined |
|
|
|
|
|
|
|
|
|
constructor() { |
|
|
|
|
super(); |
|
|
|
|
this.state = { |
|
|
|
|
draggingElement: null, |
|
|
|
|
elementType: "selection" |
|
|
|
|
}; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
render() { |
|
|
|
|
const ElementOption = ({ type, children }) => { |
|
|
|
|
return ( |
|
|
|
|
<label> |
|
|
|
|
<input |
|
|
|
|
type="radio" |
|
|
|
|
checked={this.state.elementType === type} |
|
|
|
|
onChange={() => { |
|
|
|
|
this.setState({ elementType: type }); |
|
|
|
|
clearSelection(); |
|
|
|
|
drawScene(); |
|
|
|
|
}} |
|
|
|
|
/> |
|
|
|
|
{children} |
|
|
|
|
</label> |
|
|
|
|
); |
|
|
|
|
}; |
|
|
|
|
|
|
|
|
|
return ( |
|
|
|
|
<div> |
|
|
|
|
{/* Can't use the <ElementOption> form because ElementOption is re-defined |
|
|
|
|
on every render, which would blow up and re-create the entire DOM tree, |
|
|
|
|
which in addition to being inneficient, messes up with browser text |
|
|
|
|
selection */} |
|
|
|
|
{ElementOption({ type: "rectangle", children: "Rectangle" })} |
|
|
|
|
{ElementOption({ type: "ellipse", children: "Ellipse" })} |
|
|
|
|
{ElementOption({ type: "arrow", children: "Arrow" })} |
|
|
|
|
{ElementOption({ type: "text", children: "Text" })} |
|
|
|
|
{ElementOption({ type: "selection", children: "Selection" })} |
|
|
|
|
<canvas |
|
|
|
|
id="canvas" |
|
|
|
|
width={window.innerWidth} |
|
|
|
|
height={window.innerHeight} |
|
|
|
|
onClick={e => { |
|
|
|
|
console.log("click"); |
|
|
|
|
}} |
|
|
|
|
onMouseDown={e => { |
|
|
|
|
const x = e.clientX - e.target.offsetLeft; |
|
|
|
|
const y = e.clientY - e.target.offsetTop; |
|
|
|
|
const element = newElement(elementType, x, y); |
|
|
|
|
|
|
|
|
|
if (elementType === "text") { |
|
|
|
|
const text = prompt("What text do you want?"); |
|
|
|
|
if (text === null) { |
|
|
|
|
return; |
|
|
|
|
{ElementOption({ type: "rectangle", children: "Rectangle" })} |
|
|
|
|
{ElementOption({ type: "ellipse", children: "Ellipse" })} |
|
|
|
|
{ElementOption({ type: "arrow", children: "Arrow" })} |
|
|
|
|
{ElementOption({ type: "text", children: "Text" })} |
|
|
|
|
{ElementOption({ type: "selection", children: "Selection" })} |
|
|
|
|
<canvas |
|
|
|
|
id="canvas" |
|
|
|
|
width={window.innerWidth} |
|
|
|
|
height={window.innerHeight} |
|
|
|
|
onMouseDown={e => { |
|
|
|
|
const x = e.clientX - e.target.offsetLeft; |
|
|
|
|
const y = e.clientY - e.target.offsetTop; |
|
|
|
|
const element = newElement(this.state.elementType, x, y); |
|
|
|
|
|
|
|
|
|
if (this.state.elementType === "text") { |
|
|
|
|
const text = prompt("What text do you want?"); |
|
|
|
|
if (text === null) { |
|
|
|
|
return; |
|
|
|
|
} |
|
|
|
|
element.text = text; |
|
|
|
|
element.font = "20px Virgil"; |
|
|
|
|
const font = context.font; |
|
|
|
|
context.font = element.font; |
|
|
|
|
element.measure = context.measureText(element.text); |
|
|
|
|
context.font = font; |
|
|
|
|
const height = |
|
|
|
|
element.measure.actualBoundingBoxAscent + |
|
|
|
|
element.measure.actualBoundingBoxDescent; |
|
|
|
|
// Center the text
|
|
|
|
|
element.x -= element.measure.width / 2; |
|
|
|
|
element.y -= element.measure.actualBoundingBoxAscent; |
|
|
|
|
element.width = element.measure.width; |
|
|
|
|
element.height = height; |
|
|
|
|
} |
|
|
|
|
element.text = text; |
|
|
|
|
element.font = "20px Virgil"; |
|
|
|
|
const font = context.font; |
|
|
|
|
context.font = element.font; |
|
|
|
|
element.measure = context.measureText(element.text); |
|
|
|
|
context.font = font; |
|
|
|
|
const height = |
|
|
|
|
element.measure.actualBoundingBoxAscent + |
|
|
|
|
element.measure.actualBoundingBoxDescent; |
|
|
|
|
// Center the text
|
|
|
|
|
element.x -= element.measure.width / 2; |
|
|
|
|
element.y -= element.measure.actualBoundingBoxAscent; |
|
|
|
|
element.width = element.measure.width; |
|
|
|
|
element.height = height; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
generateDraw(element); |
|
|
|
|
elements.push(element); |
|
|
|
|
if (elementType === "text") { |
|
|
|
|
setDraggingElement(null); |
|
|
|
|
element.isSelected = true; |
|
|
|
|
} else { |
|
|
|
|
setDraggingElement(element); |
|
|
|
|
} |
|
|
|
|
drawScene(); |
|
|
|
|
}} |
|
|
|
|
onMouseUp={e => { |
|
|
|
|
if (draggingElement === null) { |
|
|
|
|
return; |
|
|
|
|
} |
|
|
|
|
if (elementType === "selection") { |
|
|
|
|
// Remove actual selection element
|
|
|
|
|
elements.pop(); |
|
|
|
|
setSelection(draggingElement); |
|
|
|
|
} else { |
|
|
|
|
draggingElement.isSelected = true; |
|
|
|
|
} |
|
|
|
|
setDraggingElement(null); |
|
|
|
|
setElementType("selection"); |
|
|
|
|
drawScene(); |
|
|
|
|
}} |
|
|
|
|
onMouseMove={e => { |
|
|
|
|
if (!draggingElement) return; |
|
|
|
|
let width = e.clientX - e.target.offsetLeft - draggingElement.x; |
|
|
|
|
let height = e.clientY - e.target.offsetTop - draggingElement.y; |
|
|
|
|
draggingElement.width = width; |
|
|
|
|
// Make a perfect square or circle when shift is enabled
|
|
|
|
|
draggingElement.height = e.shiftKey ? width : height; |
|
|
|
|
|
|
|
|
|
generateDraw(draggingElement); |
|
|
|
|
|
|
|
|
|
if (elementType === "selection") { |
|
|
|
|
setSelection(draggingElement); |
|
|
|
|
} |
|
|
|
|
drawScene(); |
|
|
|
|
}} |
|
|
|
|
/> |
|
|
|
|
</div> |
|
|
|
|
); |
|
|
|
|
generateDraw(element); |
|
|
|
|
elements.push(element); |
|
|
|
|
if (this.state.elementType === "text") { |
|
|
|
|
this.setState({ draggingElement: null }); |
|
|
|
|
element.isSelected = true; |
|
|
|
|
} else { |
|
|
|
|
this.setState({ draggingElement: element }); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
const onMouseMove = e => { |
|
|
|
|
// It is very important to read this.state within each move event,
|
|
|
|
|
// otherwise we would read a stale one!
|
|
|
|
|
const draggingElement = this.state.draggingElement; |
|
|
|
|
if (!draggingElement) return; |
|
|
|
|
let width = e.clientX - e.target.offsetLeft - draggingElement.x; |
|
|
|
|
let height = e.clientY - e.target.offsetTop - draggingElement.y; |
|
|
|
|
draggingElement.width = width; |
|
|
|
|
// Make a perfect square or circle when shift is enabled
|
|
|
|
|
draggingElement.height = e.shiftKey ? width : height; |
|
|
|
|
|
|
|
|
|
generateDraw(draggingElement); |
|
|
|
|
|
|
|
|
|
if (this.state.elementType === "selection") { |
|
|
|
|
setSelection(draggingElement); |
|
|
|
|
} |
|
|
|
|
drawScene(); |
|
|
|
|
}; |
|
|
|
|
|
|
|
|
|
const onMouseUp = e => { |
|
|
|
|
window.removeEventListener("mousemove", onMouseMove); |
|
|
|
|
window.removeEventListener("mouseup", onMouseUp); |
|
|
|
|
|
|
|
|
|
const draggingElement = this.state.draggingElement; |
|
|
|
|
if (draggingElement === null) { |
|
|
|
|
return; |
|
|
|
|
} |
|
|
|
|
if (this.state.elementType === "selection") { |
|
|
|
|
// Remove actual selection element
|
|
|
|
|
elements.pop(); |
|
|
|
|
setSelection(draggingElement); |
|
|
|
|
} else { |
|
|
|
|
draggingElement.isSelected = true; |
|
|
|
|
} |
|
|
|
|
this.setState({ draggingElement: null }); |
|
|
|
|
this.setState({ elementType: "selection" }); |
|
|
|
|
drawScene(); |
|
|
|
|
}; |
|
|
|
|
|
|
|
|
|
window.addEventListener("mousemove", onMouseMove); |
|
|
|
|
window.addEventListener("mouseup", onMouseUp); |
|
|
|
|
|
|
|
|
|
drawScene(); |
|
|
|
|
}} |
|
|
|
|
/> |
|
|
|
|
</div> |
|
|
|
|
); |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
const rootElement = document.getElementById("root"); |
|
|
|
|
ReactDOM.render(<App />, rootElement); |
|
|
|
|
const canvas = document.getElementById("canvas"); |
|
|
|
|
|