parent
66938ae5c6
commit
b1a90c0020
29
package-lock.json
generated
29
package-lock.json
generated
@ -1000,6 +1000,35 @@
|
|||||||
"resolved": "https://registry.npmjs.org/@csstools/normalize.css/-/normalize.css-10.1.0.tgz",
|
"resolved": "https://registry.npmjs.org/@csstools/normalize.css/-/normalize.css-10.1.0.tgz",
|
||||||
"integrity": "sha512-ij4wRiunFfaJxjB0BdrYHIH8FxBJpOwNPhhAcunlmPdXudL1WQV1qoP9un6JsEBAgQH+7UXyyjh0g7jTxXK6tg=="
|
"integrity": "sha512-ij4wRiunFfaJxjB0BdrYHIH8FxBJpOwNPhhAcunlmPdXudL1WQV1qoP9un6JsEBAgQH+7UXyyjh0g7jTxXK6tg=="
|
||||||
},
|
},
|
||||||
|
"@fortawesome/fontawesome-common-types": {
|
||||||
|
"version": "0.2.26",
|
||||||
|
"resolved": "https://registry.npmjs.org/@fortawesome/fontawesome-common-types/-/fontawesome-common-types-0.2.26.tgz",
|
||||||
|
"integrity": "sha512-CcM/fIFwZlRdiWG/25xE/wHbtyUuCtqoCTrr6BsWw7hH072fR++n4L56KPydAr3ANgMJMjT8v83ZFIsDc7kE+A=="
|
||||||
|
},
|
||||||
|
"@fortawesome/fontawesome-svg-core": {
|
||||||
|
"version": "1.2.26",
|
||||||
|
"resolved": "https://registry.npmjs.org/@fortawesome/fontawesome-svg-core/-/fontawesome-svg-core-1.2.26.tgz",
|
||||||
|
"integrity": "sha512-3Dfd/v2IztP1TxKOxZiB5+4kaOZK9mNy0KU1vVK7nFlPWz3gzxrCWB+AloQhQUoJ8HhGqbzjliK89Vl7PExGbw==",
|
||||||
|
"requires": {
|
||||||
|
"@fortawesome/fontawesome-common-types": "^0.2.26"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"@fortawesome/free-solid-svg-icons": {
|
||||||
|
"version": "5.12.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@fortawesome/free-solid-svg-icons/-/free-solid-svg-icons-5.12.0.tgz",
|
||||||
|
"integrity": "sha512-CnpsWs6GhTs9ekNB3d8rcO5HYqRkXbYKf2YNiAlTWbj5eVlPqsd/XH1F9If8jkcR1aegryAbln/qYeKVZzpM0g==",
|
||||||
|
"requires": {
|
||||||
|
"@fortawesome/fontawesome-common-types": "^0.2.26"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"@fortawesome/react-fontawesome": {
|
||||||
|
"version": "0.1.8",
|
||||||
|
"resolved": "https://registry.npmjs.org/@fortawesome/react-fontawesome/-/react-fontawesome-0.1.8.tgz",
|
||||||
|
"integrity": "sha512-I5h9YQg/ePA3Br9ISS18fcwOYmzQYDSM1ftH03/8nHkiqIVHtUyQBw482+60dnzvlr82gHt3mGm+nDUp159FCw==",
|
||||||
|
"requires": {
|
||||||
|
"prop-types": "^15.5.10"
|
||||||
|
}
|
||||||
|
},
|
||||||
"@hapi/address": {
|
"@hapi/address": {
|
||||||
"version": "2.1.4",
|
"version": "2.1.4",
|
||||||
"resolved": "https://registry.npmjs.org/@hapi/address/-/address-2.1.4.tgz",
|
"resolved": "https://registry.npmjs.org/@hapi/address/-/address-2.1.4.tgz",
|
||||||
|
@ -6,6 +6,9 @@
|
|||||||
"keywords": [],
|
"keywords": [],
|
||||||
"main": "src/index.js",
|
"main": "src/index.js",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"@fortawesome/fontawesome-svg-core": "^1.2.26",
|
||||||
|
"@fortawesome/free-solid-svg-icons": "^5.12.0",
|
||||||
|
"@fortawesome/react-fontawesome": "^0.1.8",
|
||||||
"lodash": "4.17.15",
|
"lodash": "4.17.15",
|
||||||
"react": "16.12.0",
|
"react": "16.12.0",
|
||||||
"react-dom": "16.12.0",
|
"react-dom": "16.12.0",
|
||||||
|
220
src/index.tsx
220
src/index.tsx
@ -2,6 +2,14 @@ import React from "react";
|
|||||||
import ReactDOM from "react-dom";
|
import ReactDOM from "react-dom";
|
||||||
import rough from "roughjs/bin/wrappers/rough";
|
import rough from "roughjs/bin/wrappers/rough";
|
||||||
import { RoughCanvas } from "roughjs/bin/canvas";
|
import { RoughCanvas } from "roughjs/bin/canvas";
|
||||||
|
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||||
|
import {
|
||||||
|
faMousePointer,
|
||||||
|
faSquare,
|
||||||
|
faCircle,
|
||||||
|
faLongArrowAltRight,
|
||||||
|
faFont
|
||||||
|
} from "@fortawesome/free-solid-svg-icons";
|
||||||
|
|
||||||
import "./styles.css";
|
import "./styles.css";
|
||||||
|
|
||||||
@ -598,28 +606,28 @@ const KEYS = {
|
|||||||
|
|
||||||
const SHAPES = [
|
const SHAPES = [
|
||||||
{
|
{
|
||||||
label: "Rectange",
|
icon: faMousePointer,
|
||||||
|
value: "selection"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
icon: faSquare,
|
||||||
value: "rectangle"
|
value: "rectangle"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: "Ellipse",
|
icon: faCircle,
|
||||||
value: "ellipse"
|
value: "ellipse"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: "Arrow",
|
icon: faLongArrowAltRight,
|
||||||
value: "arrow"
|
value: "arrow"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: "Text",
|
icon: faFont,
|
||||||
value: "text"
|
value: "text"
|
||||||
},
|
|
||||||
{
|
|
||||||
label: "Selection",
|
|
||||||
value: "selection"
|
|
||||||
}
|
}
|
||||||
];
|
];
|
||||||
|
|
||||||
const shapesShortcutKeys = SHAPES.map(shape => shape.label[0].toLowerCase());
|
const shapesShortcutKeys = SHAPES.map(shape => shape.value[0]);
|
||||||
|
|
||||||
function findElementByKey(key: string) {
|
function findElementByKey(key: string) {
|
||||||
const defaultElement = "selection";
|
const defaultElement = "selection";
|
||||||
@ -716,6 +724,7 @@ class App extends React.Component<{}, AppState> {
|
|||||||
public render() {
|
public render() {
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
|
className="container"
|
||||||
onCut={e => {
|
onCut={e => {
|
||||||
e.clipboardData.setData(
|
e.clipboardData.setData(
|
||||||
"text/plain",
|
"text/plain",
|
||||||
@ -756,28 +765,111 @@ class App extends React.Component<{}, AppState> {
|
|||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<fieldset>
|
<div className="sidePanel">
|
||||||
<legend>Shapes</legend>
|
<h4>Shapes</h4>
|
||||||
{SHAPES.map(({ value, label }) => (
|
<div className="panelTools">
|
||||||
<label key={value}>
|
{SHAPES.map(({ value, icon }) => (
|
||||||
|
<label key={value} className="tool">
|
||||||
|
<input
|
||||||
|
type="radio"
|
||||||
|
checked={this.state.elementType === value}
|
||||||
|
onChange={() => {
|
||||||
|
this.setState({ elementType: value });
|
||||||
|
clearSelection();
|
||||||
|
this.forceUpdate();
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<div className="toolIcon">
|
||||||
|
<FontAwesomeIcon icon={icon} />
|
||||||
|
</div>
|
||||||
|
</label>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
<h4>Colors</h4>
|
||||||
|
<div className="panelColumn">
|
||||||
|
<label>
|
||||||
<input
|
<input
|
||||||
type="radio"
|
type="color"
|
||||||
checked={this.state.elementType === value}
|
value={this.state.viewBackgroundColor}
|
||||||
onChange={() => {
|
onChange={e => {
|
||||||
this.setState({ elementType: value });
|
this.setState({ viewBackgroundColor: e.target.value });
|
||||||
clearSelection();
|
|
||||||
this.forceUpdate();
|
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
<span>{label}</span>
|
Background
|
||||||
</label>
|
</label>
|
||||||
))}
|
<label>
|
||||||
</fieldset>
|
<input
|
||||||
|
type="color"
|
||||||
|
value={this.state.currentItemStrokeColor}
|
||||||
|
onChange={e => {
|
||||||
|
this.setState({ currentItemStrokeColor: e.target.value });
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
Shape Stroke
|
||||||
|
</label>
|
||||||
|
<label>
|
||||||
|
<input
|
||||||
|
type="color"
|
||||||
|
value={this.state.currentItemBackgroundColor}
|
||||||
|
onChange={e => {
|
||||||
|
this.setState({ currentItemBackgroundColor: e.target.value });
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
Shape Background
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
<h4>Export</h4>
|
||||||
|
<div className="panelColumn">
|
||||||
|
<button
|
||||||
|
onClick={() => {
|
||||||
|
exportAsPNG({
|
||||||
|
exportBackground: this.state.exportBackground,
|
||||||
|
exportVisibleOnly: this.state.exportVisibleOnly,
|
||||||
|
exportPadding: this.state.exportPadding,
|
||||||
|
viewBackgroundColor: this.state.viewBackgroundColor
|
||||||
|
});
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
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>
|
||||||
|
<div>
|
||||||
|
(padding:
|
||||||
|
<input
|
||||||
|
type="number"
|
||||||
|
value={this.state.exportPadding}
|
||||||
|
onChange={e => {
|
||||||
|
this.setState({ exportPadding: Number(e.target.value) });
|
||||||
|
}}
|
||||||
|
disabled={!this.state.exportVisibleOnly}
|
||||||
|
/>
|
||||||
|
px)
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
<canvas
|
<canvas
|
||||||
id="canvas"
|
id="canvas"
|
||||||
width={window.innerWidth}
|
width={window.innerWidth - 250}
|
||||||
height={window.innerHeight - 210}
|
height={window.innerHeight}
|
||||||
onWheel={e => {
|
onWheel={e => {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
const { deltaX, deltaY } = e;
|
const { deltaX, deltaY } = e;
|
||||||
@ -958,84 +1050,6 @@ class App extends React.Component<{}, AppState> {
|
|||||||
this.forceUpdate();
|
this.forceUpdate();
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
<fieldset>
|
|
||||||
<legend>Colors</legend>
|
|
||||||
<label>
|
|
||||||
<input
|
|
||||||
type="color"
|
|
||||||
value={this.state.viewBackgroundColor}
|
|
||||||
onChange={e => {
|
|
||||||
this.setState({ viewBackgroundColor: e.target.value });
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
Background
|
|
||||||
</label>
|
|
||||||
<label>
|
|
||||||
<input
|
|
||||||
type="color"
|
|
||||||
value={this.state.currentItemStrokeColor}
|
|
||||||
onChange={e => {
|
|
||||||
this.setState({ currentItemStrokeColor: e.target.value });
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
Shape Stroke
|
|
||||||
</label>
|
|
||||||
<label>
|
|
||||||
<input
|
|
||||||
type="color"
|
|
||||||
value={this.state.currentItemBackgroundColor}
|
|
||||||
onChange={e => {
|
|
||||||
this.setState({ currentItemBackgroundColor: e.target.value });
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
Shape Background
|
|
||||||
</label>
|
|
||||||
</fieldset>
|
|
||||||
<fieldset>
|
|
||||||
<legend>Export</legend>
|
|
||||||
<button
|
|
||||||
onClick={() => {
|
|
||||||
exportAsPNG({
|
|
||||||
exportBackground: this.state.exportBackground,
|
|
||||||
exportVisibleOnly: this.state.exportVisibleOnly,
|
|
||||||
exportPadding: this.state.exportPadding,
|
|
||||||
viewBackgroundColor: this.state.viewBackgroundColor
|
|
||||||
});
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
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: Number(e.target.value) });
|
|
||||||
}}
|
|
||||||
disabled={!this.state.exportVisibleOnly}
|
|
||||||
/>
|
|
||||||
px)
|
|
||||||
</fieldset>
|
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -7,25 +7,62 @@
|
|||||||
|
|
||||||
body {
|
body {
|
||||||
margin: 0;
|
margin: 0;
|
||||||
|
font-family: Arial, Helvetica, sans-serif;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Controls - Begin */
|
.container {
|
||||||
fieldset {
|
display: flex;
|
||||||
margin: 5px;
|
}
|
||||||
|
|
||||||
|
.sidePanel {
|
||||||
|
width: 230px;
|
||||||
|
background-color: #eee;
|
||||||
|
|
||||||
|
padding: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sidePanel h4 {
|
||||||
|
margin: 10px 0 10px 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sidePanel .panelTools {
|
||||||
|
display: flex;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sidePanel .panelColumn {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tool input[type="radio"] {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tool input[type="radio"] + .toolIcon {
|
||||||
|
background-color: #ddd;
|
||||||
|
|
||||||
|
width: 41px;
|
||||||
|
height: 41px;
|
||||||
|
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
|
||||||
|
border-radius: 3px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tool input[type="radio"]:checked + .toolIcon {
|
||||||
|
background-color: #bdbebc;
|
||||||
}
|
}
|
||||||
|
|
||||||
label {
|
label {
|
||||||
margin-right: 10px;
|
margin-right: 6px;
|
||||||
}
|
}
|
||||||
|
|
||||||
label span {
|
label span {
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
}
|
}
|
||||||
|
|
||||||
label span::first-letter {
|
|
||||||
text-decoration: underline;
|
|
||||||
}
|
|
||||||
|
|
||||||
input[type="number"] {
|
input[type="number"] {
|
||||||
width: 30px;
|
width: 30px;
|
||||||
}
|
}
|
||||||
@ -33,4 +70,11 @@ input[type="number"] {
|
|||||||
input {
|
input {
|
||||||
margin-right: 5px;
|
margin-right: 5px;
|
||||||
}
|
}
|
||||||
/* Controls - End */
|
|
||||||
|
button {
|
||||||
|
background-color: #ddd;
|
||||||
|
border: 1px solid #ccc;
|
||||||
|
border-radius: 4px;
|
||||||
|
|
||||||
|
padding: 5px;
|
||||||
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user