diff --git a/package.json b/package.json
index 25cfc406..12dbfdee 100644
--- a/package.json
+++ b/package.json
@@ -12,6 +12,7 @@
"d3": "^6.7.0",
"d3-fisheye": "^2.0.1",
"eslint-config-react-app": "^6.0.0",
+ "file-saver": "^2.0.5",
"gcode-toolpath": "^2.2.0",
"javascript-algorithms": "0.0.5",
"jest-canvas-mock": "^2.3.1",
diff --git a/src/common/util.js b/src/common/util.js
index 39d27c1b..461c7351 100644
--- a/src/common/util.js
+++ b/src/common/util.js
@@ -35,3 +35,5 @@ const debug = false
export const log = (message) => {
if (debug) { console.log(message) }
}
+
+export const sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
diff --git a/src/features/exporter/Downloader.js b/src/features/exporter/Downloader.js
index d00f758b..74ef95fc 100644
--- a/src/features/exporter/Downloader.js
+++ b/src/features/exporter/Downloader.js
@@ -1,5 +1,6 @@
import React, { Component } from 'react'
import { connect } from 'react-redux'
+import { saveAs } from 'file-saver';
import { Button, Modal, Col, Row } from 'react-bootstrap'
import DropdownOption from '../../components/DropdownOption'
import InputOption from '../../components/InputOption'
@@ -13,6 +14,7 @@ import ScaraGCodeExporter from './ScaraGCodeExporter'
import SvgExporter from './SvgExporter'
import ThetaRhoExporter from './ThetaRhoExporter'
import { Exporter, GCODE, THETARHO, SVG, SCARA } from '../../models/Exporter'
+import { exportCurrentPreviewWindow } from '../preview/PreviewWindow'
const exporters = {
[GCODE]: GCodeExporter,
@@ -24,6 +26,7 @@ const exporters = {
const mapStateToProps = (state, ownProps) => {
return {
reverse: state.exporter.reverse,
+ pngPreview: state.exporter.pngPreview,
show: state.exporter.show,
vertices: getAllComputedVertices(state),
comments: getComments(state),
@@ -75,7 +78,7 @@ class Downloader extends Component {
})
}
- download() {
+ async download() {
let exporter = new exporters[this.props.fileType](this.props)
let startTime = performance.now()
let fileName = this.props.fileName
@@ -87,7 +90,16 @@ class Downloader extends Component {
}
this.gaRecord(exporter.label)
- this.downloadFile(fileName, exporter.lines.join("\n"))
+ saveAs(new Blob([exporter.lines.join("\n")], {
+ type: this.props.fileType === SVG ? 'image/svg+xml;charset=utf-8' : 'text/plain;charset=utf-8'
+ }), fileName);
+
+ if (this.props.pngPreview) {
+ const preview = await exportCurrentPreviewWindow();
+ if (preview) {
+ saveAs(preview, `${fileName.match(/(.*)\.\w+$/)[1]}.png`);
+ }
+ }
this.props.close()
let endTime = performance.now()
@@ -98,35 +110,6 @@ class Downloader extends Component {
})
}
- // Helper function to take a string and make the user download a text file with that text as the
- // content. I don't really understand this, but I took it from here, and it seems to work:
- // https://stackoverflow.com/a/18197511
- downloadFile(fileName, text) {
- let link = document.createElement('a')
- link.download = fileName
-
- let fileType = 'text/plain;charset=utf-8'
- if (this.props.fileType === SVG) {
- fileType = 'image/svg+xml;charset=utf-8'
- }
- let blob = new Blob([text],{type: fileType})
-
- // Windows Edge fix
- if (window.navigator && window.navigator.msSaveOrOpenBlob) {
- window.navigator.msSaveOrOpenBlob(blob, fileName)
- } else {
- link.href = URL.createObjectURL(blob)
- if (document.createEvent) {
- var event = document.createEvent('MouseEvents')
- event.initEvent('click', true, true)
- link.dispatchEvent(event)
- } else {
- link.click()
- }
- URL.revokeObjectURL(link.href)
- }
- }
-
render() {
return (
@@ -212,6 +195,15 @@ class Downloader extends Component {
index={5}
model={this.props} />
+
+
+
diff --git a/src/features/exporter/exporterSlice.js b/src/features/exporter/exporterSlice.js
index 04e2958b..75208459 100644
--- a/src/features/exporter/exporterSlice.js
+++ b/src/features/exporter/exporterSlice.js
@@ -30,6 +30,7 @@ const exporterSlice = createSlice({
pre: localStorage.getItem('export_pre') ? localStorage.getItem('export_pre') : '',
post: localStorage.getItem('export_post') ? localStorage.getItem('export_post') : '',
reverse: false,
+ pngPreview: localStorage.getItem('export_pngPreview') !== null ? Boolean(localStorage.getItem('png_preview')) : false,
show: false,
polarRhoMax: localStorage.getItem('export_polarRhoMax') ? localStorage.getItem('export_polarRhoMax') : 1.0,
unitsPerCircle: localStorage.getItem('export_unitsPerCircle') ? localStorage.getItem('export_unitsPerCircle') : 6.0
diff --git a/src/features/preview/PreviewLayer.js b/src/features/preview/PreviewLayer.js
index 0f952a61..5cb295f4 100644
--- a/src/features/preview/PreviewLayer.js
+++ b/src/features/preview/PreviewLayer.js
@@ -131,7 +131,7 @@ const PreviewLayer = (ownProps) => {
drawLayerVertices(context, props.bounds)
- if (props.start || props.end || isSelected) {
+ if (!ownProps.exportMode && (props.start || props.end || isSelected)) {
drawStartAndEndPoints(context)
}
helper.drawSliderEndPoint(context)
@@ -159,12 +159,12 @@ const PreviewLayer = (ownProps) => {
const trRef = React.createRef()
React.useEffect(() => {
- if (props.layer.visible && isSelected && props.layer.canChangeSize) {
+ if (props.layer.visible && isSelected && props.layer.canChangeSize && !ownProps.exportMode) {
// we need to attach transformer manually
trRef.current.nodes([shapeRef.current])
trRef.current.getLayer().batchDraw()
}
- }, [isSelected, props.layer, props.currentLayer.canMove, shapeRef, trRef])
+ }, [isSelected, props.layer, props.currentLayer.canMove, shapeRef, trRef, ownProps.exportMode])
return (
@@ -214,7 +214,7 @@ const PreviewLayer = (ownProps) => {
})
}}
/>}
- {props.layer.visible && isSelected && props.layer.canChangeSize && (
+ {props.layer.visible && isSelected && props.layer.canChangeSize && !ownProps.exportMode && (
{
+ if (getPreview) {
+ return getPreview();
+ }
+}
+
const mapStateToProps = (state, ownProps) => {
return {
layers: state.layers,
@@ -43,6 +50,8 @@ const mapDispatchToProps = (dispatch, ownProps) => {
// Contains the preview window, and any parameters for the machine.
class PreviewWindow extends Component {
+ state = {exportMode: false};
+
componentDidMount() {
const wrapper = document.getElementById('preview-wrapper')
@@ -51,7 +60,15 @@ class PreviewWindow extends Component {
setTimeout(() => {
this.visible = true
this.resize(wrapper)
- }, 250)
+ }, 250);
+
+ getPreview = async () => {
+ this.setState({exportMode: true});
+ await sleep(0); // sleep to let react re-render the canvas before we export it
+ const preview = await new Promise((resolve) => this.stageRef.toCanvas().toBlob(resolve));
+ this.setState({exportMode: false});
+ return preview;
+ }
}
resize(wrapper) {
@@ -87,7 +104,7 @@ class PreviewWindow extends Component {
// which is not our usual React Component
{({store}) => (
- this.stageRef = ref} className={visibilityClass}
scaleX={scale * reduceScale}
scaleY={scale * reduceScale}
height={height * scale}
@@ -123,7 +140,7 @@ class PreviewWindow extends Component {
return (
[
nextId && ,
-
+
].filter(e => e !== null)
)
}).flat()}
diff --git a/src/models/Exporter.js b/src/models/Exporter.js
index 787c1765..7380b029 100644
--- a/src/models/Exporter.js
+++ b/src/models/Exporter.js
@@ -38,6 +38,9 @@ const exporterOptions = {
reverse: {
title: 'Reverse path in the code',
},
+ pngPreview: {
+ title: 'Export the preview image',
+ },
}
export class Exporter {