|
|
|
|
@ -17,18 +17,70 @@ function isInsideAnElement(x, y) {
@@ -17,18 +17,70 @@ function isInsideAnElement(x, y) {
|
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
function newElement(type, x, y) { |
|
|
|
|
function newElement(type, x, y, width = 0, height = 0) { |
|
|
|
|
const element = { |
|
|
|
|
type: type, |
|
|
|
|
x: x, |
|
|
|
|
y: y, |
|
|
|
|
width: 0, |
|
|
|
|
height: 0, |
|
|
|
|
width: width, |
|
|
|
|
height: height, |
|
|
|
|
isSelected: false |
|
|
|
|
}; |
|
|
|
|
return element; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
function exportAsPNG({ background, visibleOnly, padding = 10 }) { |
|
|
|
|
clearSelection(); |
|
|
|
|
drawScene(); |
|
|
|
|
|
|
|
|
|
let subCanvasX1 = Infinity; |
|
|
|
|
let subCanvasX2 = 0; |
|
|
|
|
let subCanvasY1 = Infinity; |
|
|
|
|
let subCanvasY2 = 0; |
|
|
|
|
|
|
|
|
|
elements.forEach(element => { |
|
|
|
|
subCanvasX1 = Math.min(subCanvasX1, getElementAbsoluteX1(element)); |
|
|
|
|
subCanvasX2 = Math.max(subCanvasX2, getElementAbsoluteX2(element)); |
|
|
|
|
subCanvasY1 = Math.min(subCanvasY1, getElementAbsoluteY1(element)); |
|
|
|
|
subCanvasY2 = Math.max(subCanvasY2, getElementAbsoluteY2(element)); |
|
|
|
|
}); |
|
|
|
|
|
|
|
|
|
let targetCanvas = canvas; |
|
|
|
|
|
|
|
|
|
if ( visibleOnly ) { |
|
|
|
|
targetCanvas = document.createElement('canvas'); |
|
|
|
|
targetCanvas.style.display = 'none'; |
|
|
|
|
document.body.appendChild(targetCanvas); |
|
|
|
|
targetCanvas.width = subCanvasX2 - subCanvasX1 + padding * 2; |
|
|
|
|
targetCanvas.height = subCanvasY2 - subCanvasY1 + padding * 2; |
|
|
|
|
const targetCanvas_ctx = targetCanvas.getContext('2d'); |
|
|
|
|
|
|
|
|
|
if ( background ) { |
|
|
|
|
targetCanvas_ctx.fillStyle = "#FFF"; |
|
|
|
|
targetCanvas_ctx.fillRect(0, 0, canvas.width, canvas.height); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
targetCanvas_ctx.drawImage( |
|
|
|
|
canvas, |
|
|
|
|
subCanvasX1 - padding, // x
|
|
|
|
|
subCanvasY1 - padding, // y
|
|
|
|
|
subCanvasX2 - subCanvasX1 + padding * 2, // width
|
|
|
|
|
subCanvasY2 - subCanvasY1 + padding * 2, // height
|
|
|
|
|
0, |
|
|
|
|
0, |
|
|
|
|
targetCanvas.width, |
|
|
|
|
targetCanvas.height |
|
|
|
|
); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
const link = document.createElement('a'); |
|
|
|
|
link.setAttribute('download', 'excalibur.png'); |
|
|
|
|
link.setAttribute('href', targetCanvas.toDataURL("image/png")); |
|
|
|
|
link.click(); |
|
|
|
|
link.remove(); |
|
|
|
|
if ( targetCanvas !== canvas ) targetCanvas.remove(); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
function rotate(x1, y1, x2, y2, angle) { |
|
|
|
|
// 𝑎′𝑥=(𝑎𝑥−𝑐𝑥)cos𝜃−(𝑎𝑦−𝑐𝑦)sin𝜃+𝑐𝑥
|
|
|
|
|
// 𝑎′𝑦=(𝑎𝑥−𝑐𝑥)sin𝜃+(𝑎𝑦−𝑐𝑦)cos𝜃+𝑐𝑦.
|
|
|
|
|
@ -161,7 +213,7 @@ function clearSelection() {
@@ -161,7 +213,7 @@ function clearSelection() {
|
|
|
|
|
class App extends React.Component { |
|
|
|
|
componentDidMount() { |
|
|
|
|
this.onKeyDown = event => { |
|
|
|
|
if (event.key === "Backspace") { |
|
|
|
|
if (event.key === "Backspace" && event.target.nodeName !== "INPUT") { |
|
|
|
|
for (var i = elements.length - 1; i >= 0; --i) { |
|
|
|
|
if (elements[i].isSelected) { |
|
|
|
|
elements.splice(i, 1); |
|
|
|
|
@ -199,7 +251,10 @@ class App extends React.Component {
@@ -199,7 +251,10 @@ class App extends React.Component {
|
|
|
|
|
super(); |
|
|
|
|
this.state = { |
|
|
|
|
draggingElement: null, |
|
|
|
|
elementType: "selection" |
|
|
|
|
elementType: "selection", |
|
|
|
|
exportBackground: false, |
|
|
|
|
exportVisibleOnly: true, |
|
|
|
|
exportPadding: 10 |
|
|
|
|
}; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
@ -221,7 +276,40 @@ class App extends React.Component {
@@ -221,7 +276,40 @@ class App extends React.Component {
|
|
|
|
|
); |
|
|
|
|
}; |
|
|
|
|
|
|
|
|
|
return ( |
|
|
|
|
return <> |
|
|
|
|
<div className="exportWrapper"> |
|
|
|
|
<button onClick={() => { |
|
|
|
|
exportAsPNG({ |
|
|
|
|
background: this.state.exportBackground, |
|
|
|
|
visibleOnly: this.state.exportVisibleOnly, |
|
|
|
|
padding: this.state.exportPadding |
|
|
|
|
}) |
|
|
|
|
}}>Export to png</button> |
|
|
|
|
<label> |
|
|
|
|
<input type="checkbox" |
|
|
|
|
checked={this.state.exportBackground} |
|
|
|
|
onChange={e => { |
|
|
|
|
this.setState({ exportBackground: e.target.checked }) |
|
|
|
|
}} |
|
|
|
|
/> background |
|
|
|
|
</label> |
|
|
|
|
<label> |
|
|
|
|
<input type="checkbox" |
|
|
|
|
checked={this.state.exportVisibleOnly} |
|
|
|
|
onChange={e => { |
|
|
|
|
this.setState({ exportVisibleOnly: e.target.checked }) |
|
|
|
|
}} |
|
|
|
|
/> |
|
|
|
|
visible area only |
|
|
|
|
</label> |
|
|
|
|
(padding: |
|
|
|
|
<input type="number" value={this.state.exportPadding} |
|
|
|
|
onChange={e => { |
|
|
|
|
this.setState({ exportPadding: e.target.value }); |
|
|
|
|
}} |
|
|
|
|
disabled={!this.state.exportVisibleOnly}/> |
|
|
|
|
px) |
|
|
|
|
</div> |
|
|
|
|
<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, |
|
|
|
|
@ -359,7 +447,7 @@ class App extends React.Component {
@@ -359,7 +447,7 @@ class App extends React.Component {
|
|
|
|
|
}} |
|
|
|
|
/> |
|
|
|
|
</div> |
|
|
|
|
); |
|
|
|
|
</>; |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|