Side panel (#95)

* Side panel

* Update arrow icon
This commit is contained in:
Paulo Menezes 2020-01-04 00:58:20 -03:00 committed by Christopher Chedeau
parent 66938ae5c6
commit b1a90c0020
4 changed files with 202 additions and 112 deletions

29
package-lock.json generated
View File

@ -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",

View File

@ -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",

View File

@ -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>
); );
} }

View File

@ -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;
}