|
@@ -2,7 +2,7 @@ import React from "react";
|
|
|
import ReactDOM from "react-dom";
|
|
|
import rough from "roughjs/bin/wrappers/rough";
|
|
|
import { RoughCanvas } from "roughjs/bin/canvas";
|
|
|
-import { SketchPicker } from "react-color";
|
|
|
+import { TwitterPicker } from "react-color";
|
|
|
|
|
|
import { moveOneLeft, moveAllLeft, moveOneRight, moveAllRight } from "./zindex";
|
|
|
import { roundRect } from "./roundRect";
|
|
@@ -980,16 +980,9 @@ function restore(
|
|
|
}
|
|
|
}
|
|
|
|
|
|
-enum ColorPicker {
|
|
|
- CANVAS_BACKGROUND,
|
|
|
- SHAPE_STROKE,
|
|
|
- SHAPE_BACKGROUND
|
|
|
-}
|
|
|
-
|
|
|
type AppState = {
|
|
|
draggingElement: ExcalidrawElement | null;
|
|
|
resizingElement: ExcalidrawElement | null;
|
|
|
- currentColorPicker: ColorPicker | null;
|
|
|
elementType: string;
|
|
|
exportBackground: boolean;
|
|
|
currentItemStrokeColor: string;
|
|
@@ -1124,70 +1117,17 @@ const hasStroke = () =>
|
|
|
element.type === "arrow")
|
|
|
);
|
|
|
|
|
|
-function getSelectedFillStyles() {
|
|
|
- const fillStyles = Array.from(
|
|
|
- new Set(
|
|
|
- elements
|
|
|
- .filter(element => element.isSelected)
|
|
|
- .map(element => element.fillStyle)
|
|
|
- )
|
|
|
- );
|
|
|
- return fillStyles.length === 1 ? fillStyles[0] : null;
|
|
|
-}
|
|
|
-
|
|
|
-function getSelectedStrokeWidth() {
|
|
|
- const strokeWidth = Array.from(
|
|
|
- new Set(
|
|
|
- elements
|
|
|
- .filter(element => element.isSelected)
|
|
|
- .map(element => element.strokeWidth)
|
|
|
- )
|
|
|
- );
|
|
|
- return strokeWidth.length === 1 ? strokeWidth[0] : null;
|
|
|
-}
|
|
|
-
|
|
|
-function getSelectedRoughness() {
|
|
|
- const roughness = Array.from(
|
|
|
- new Set(
|
|
|
- elements
|
|
|
- .filter(element => element.isSelected)
|
|
|
- .map(element => element.roughness)
|
|
|
- )
|
|
|
- );
|
|
|
- return roughness.length === 1 ? roughness[0] : null;
|
|
|
-}
|
|
|
-
|
|
|
-function getSelectedOpacity() {
|
|
|
- const opacity = Array.from(
|
|
|
- new Set(
|
|
|
- elements
|
|
|
- .filter(element => element.isSelected)
|
|
|
- .map(element => element.opacity)
|
|
|
- )
|
|
|
- );
|
|
|
- return opacity.length === 1 ? opacity[0] : null;
|
|
|
-}
|
|
|
-
|
|
|
-function getSelectedStrokeColor() {
|
|
|
- const strokeColors = Array.from(
|
|
|
- new Set(
|
|
|
- elements
|
|
|
- .filter(element => element.isSelected)
|
|
|
- .map(element => element.strokeColor)
|
|
|
- )
|
|
|
- );
|
|
|
- return strokeColors.length === 1 ? strokeColors[0] : null;
|
|
|
-}
|
|
|
-
|
|
|
-function getSelectedBackgroundColor() {
|
|
|
- const backgroundColors = Array.from(
|
|
|
+function getSelectedAttribute<T>(
|
|
|
+ getAttribute: (element: ExcalidrawElement) => T
|
|
|
+): T | null {
|
|
|
+ const attributes = Array.from(
|
|
|
new Set(
|
|
|
elements
|
|
|
.filter(element => element.isSelected)
|
|
|
- .map(element => element.backgroundColor)
|
|
|
+ .map(element => getAttribute(element))
|
|
|
)
|
|
|
);
|
|
|
- return backgroundColors.length === 1 ? backgroundColors[0] : null;
|
|
|
+ return attributes.length === 1 ? attributes[0] : null;
|
|
|
}
|
|
|
|
|
|
function addTextElement(element: ExcalidrawTextElement) {
|
|
@@ -1254,6 +1194,56 @@ function ButtonSelect<T>({
|
|
|
);
|
|
|
}
|
|
|
|
|
|
+function ColorPicker({
|
|
|
+ color,
|
|
|
+ onChange
|
|
|
+}: {
|
|
|
+ color: string | null;
|
|
|
+ onChange: (color: string) => void;
|
|
|
+}) {
|
|
|
+ const [isActive, setActive] = React.useState(false);
|
|
|
+ return (
|
|
|
+ <div>
|
|
|
+ <button
|
|
|
+ className="swatch"
|
|
|
+ style={color ? { backgroundColor: color } : undefined}
|
|
|
+ onClick={() => setActive(!isActive)}
|
|
|
+ />
|
|
|
+ {isActive ? (
|
|
|
+ <div className="popover">
|
|
|
+ <div className="cover" onClick={() => setActive(false)} />
|
|
|
+ <TwitterPicker
|
|
|
+ colors={[
|
|
|
+ "#000000",
|
|
|
+ "#ABB8C3",
|
|
|
+ "#FFFFFF",
|
|
|
+ "#FF6900",
|
|
|
+ "#FCB900",
|
|
|
+ "#00D084",
|
|
|
+ "#8ED1FC",
|
|
|
+ "#0693E3",
|
|
|
+ "#EB144C",
|
|
|
+ "#F78DA7",
|
|
|
+ "#9900EF"
|
|
|
+ ]}
|
|
|
+ width="205px"
|
|
|
+ color={color || undefined}
|
|
|
+ onChange={changedColor => {
|
|
|
+ onChange(changedColor.hex);
|
|
|
+ }}
|
|
|
+ />
|
|
|
+ </div>
|
|
|
+ ) : null}
|
|
|
+ <input
|
|
|
+ type="text"
|
|
|
+ className="swatch-input"
|
|
|
+ value={color || ""}
|
|
|
+ onChange={e => onChange(e.target.value)}
|
|
|
+ />
|
|
|
+ </div>
|
|
|
+ );
|
|
|
+}
|
|
|
+
|
|
|
const ELEMENT_SHIFT_TRANSLATE_AMOUNT = 5;
|
|
|
const ELEMENT_TRANSLATE_AMOUNT = 1;
|
|
|
|
|
@@ -1282,7 +1272,6 @@ class App extends React.Component<{}, AppState> {
|
|
|
draggingElement: null,
|
|
|
resizingElement: null,
|
|
|
elementType: "selection",
|
|
|
- currentColorPicker: null,
|
|
|
exportBackground: true,
|
|
|
currentItemStrokeColor: "#000000",
|
|
|
currentItemBackgroundColor: "#ffffff",
|
|
@@ -1524,101 +1513,20 @@ class App extends React.Component<{}, AppState> {
|
|
|
<button onClick={this.moveAllLeft}>Send to back</button>
|
|
|
</div>
|
|
|
<h5>Stroke Color</h5>
|
|
|
- <div>
|
|
|
- <button
|
|
|
- className="swatch"
|
|
|
- style={{
|
|
|
- backgroundColor:
|
|
|
- getSelectedStrokeColor() ||
|
|
|
- this.state.currentItemStrokeColor
|
|
|
- }}
|
|
|
- onClick={() =>
|
|
|
- this.setState(s => ({
|
|
|
- currentColorPicker:
|
|
|
- s.currentColorPicker === ColorPicker.SHAPE_STROKE
|
|
|
- ? null
|
|
|
- : ColorPicker.SHAPE_STROKE
|
|
|
- }))
|
|
|
- }
|
|
|
- />
|
|
|
- {this.state.currentColorPicker === ColorPicker.SHAPE_STROKE && (
|
|
|
- <div className="popover">
|
|
|
- <div
|
|
|
- className="cover"
|
|
|
- onClick={() =>
|
|
|
- this.setState({ currentColorPicker: null })
|
|
|
- }
|
|
|
- />
|
|
|
- <SketchPicker
|
|
|
- color={this.state.currentItemStrokeColor}
|
|
|
- onChange={color => this.changeStrokeColor(color.hex)}
|
|
|
- />
|
|
|
- </div>
|
|
|
- )}
|
|
|
- <input
|
|
|
- type="text"
|
|
|
- className="swatch-input"
|
|
|
- value={
|
|
|
- getSelectedStrokeColor() ||
|
|
|
- this.state.currentItemStrokeColor
|
|
|
- }
|
|
|
- onChange={e => this.changeStrokeColor(e.target.value)}
|
|
|
- />
|
|
|
- </div>
|
|
|
+ <ColorPicker
|
|
|
+ color={getSelectedAttribute(element => element.strokeColor)}
|
|
|
+ onChange={color => this.changeStrokeColor(color)}
|
|
|
+ />
|
|
|
|
|
|
{hasBackground() && (
|
|
|
<>
|
|
|
<h5>Background Color</h5>
|
|
|
- <div>
|
|
|
- <button
|
|
|
- className="swatch"
|
|
|
- style={{
|
|
|
- backgroundColor:
|
|
|
- getSelectedBackgroundColor() ||
|
|
|
- this.state.currentItemBackgroundColor
|
|
|
- }}
|
|
|
- onClick={() =>
|
|
|
- this.setState(s => ({
|
|
|
- currentColorPicker:
|
|
|
- s.currentColorPicker ===
|
|
|
- ColorPicker.SHAPE_BACKGROUND
|
|
|
- ? null
|
|
|
- : ColorPicker.SHAPE_BACKGROUND
|
|
|
- }))
|
|
|
- }
|
|
|
- />
|
|
|
- {this.state.currentColorPicker ===
|
|
|
- ColorPicker.SHAPE_BACKGROUND ? (
|
|
|
- <div className="popover">
|
|
|
- <div
|
|
|
- className="cover"
|
|
|
- onClick={() =>
|
|
|
- this.setState({ currentColorPicker: null })
|
|
|
- }
|
|
|
- />
|
|
|
- <SketchPicker
|
|
|
- color={this.state.currentItemBackgroundColor}
|
|
|
- onChange={color =>
|
|
|
- this.changeBackgroundColor(color.hex)
|
|
|
- }
|
|
|
- />
|
|
|
- </div>
|
|
|
- ) : null}
|
|
|
- <input
|
|
|
- type="text"
|
|
|
- className="swatch-input"
|
|
|
- value={
|
|
|
- getSelectedBackgroundColor() ||
|
|
|
- this.state.currentItemBackgroundColor
|
|
|
- }
|
|
|
- onChange={e => this.changeBackgroundColor(e.target.value)}
|
|
|
- />
|
|
|
- </div>
|
|
|
- </>
|
|
|
- )}
|
|
|
-
|
|
|
- {hasBackground() && (
|
|
|
- <>
|
|
|
+ <ColorPicker
|
|
|
+ color={getSelectedAttribute(
|
|
|
+ element => element.backgroundColor
|
|
|
+ )}
|
|
|
+ onChange={color => this.changeBackgroundColor(color)}
|
|
|
+ />
|
|
|
<h5>Fill</h5>
|
|
|
<ButtonSelect
|
|
|
options={[
|
|
@@ -1626,7 +1534,7 @@ class App extends React.Component<{}, AppState> {
|
|
|
{ value: "hachure", text: "Hachure" },
|
|
|
{ value: "cross-hatch", text: "Cross-hatch" }
|
|
|
]}
|
|
|
- value={getSelectedFillStyles()}
|
|
|
+ value={getSelectedAttribute(element => element.fillStyle)}
|
|
|
onChange={value => {
|
|
|
this.changeProperty(element => {
|
|
|
element.fillStyle = value;
|
|
@@ -1645,7 +1553,7 @@ class App extends React.Component<{}, AppState> {
|
|
|
{ value: 2, text: "Bold" },
|
|
|
{ value: 4, text: "Extra Bold" }
|
|
|
]}
|
|
|
- value={getSelectedStrokeWidth()}
|
|
|
+ value={getSelectedAttribute(element => element.strokeWidth)}
|
|
|
onChange={value => {
|
|
|
this.changeProperty(element => {
|
|
|
element.strokeWidth = value;
|
|
@@ -1653,14 +1561,14 @@ class App extends React.Component<{}, AppState> {
|
|
|
}}
|
|
|
/>
|
|
|
|
|
|
- <h5>Slopiness</h5>
|
|
|
+ <h5>Sloppiness</h5>
|
|
|
<ButtonSelect
|
|
|
options={[
|
|
|
{ value: 0, text: "Draftsman" },
|
|
|
{ value: 1, text: "Artist" },
|
|
|
{ value: 3, text: "Cartoonist" }
|
|
|
]}
|
|
|
- value={getSelectedRoughness()}
|
|
|
+ value={getSelectedAttribute(element => element.roughness)}
|
|
|
onChange={value =>
|
|
|
this.changeProperty(element => {
|
|
|
element.roughness = value;
|
|
@@ -1677,7 +1585,7 @@ class App extends React.Component<{}, AppState> {
|
|
|
max="100"
|
|
|
onChange={this.changeOpacity}
|
|
|
value={
|
|
|
- getSelectedOpacity() ||
|
|
|
+ getSelectedAttribute(element => element.opacity) ||
|
|
|
0 /* Put the opacity at 0 if there are two conflicting ones */
|
|
|
}
|
|
|
/>
|
|
@@ -1690,45 +1598,10 @@ class App extends React.Component<{}, AppState> {
|
|
|
<h4>Canvas</h4>
|
|
|
<div className="panelColumn">
|
|
|
<h5>Canvas Background Color</h5>
|
|
|
- <div>
|
|
|
- <button
|
|
|
- className="swatch"
|
|
|
- style={{
|
|
|
- backgroundColor: this.state.viewBackgroundColor
|
|
|
- }}
|
|
|
- onClick={() =>
|
|
|
- this.setState(s => ({
|
|
|
- currentColorPicker:
|
|
|
- s.currentColorPicker === ColorPicker.CANVAS_BACKGROUND
|
|
|
- ? null
|
|
|
- : ColorPicker.CANVAS_BACKGROUND
|
|
|
- }))
|
|
|
- }
|
|
|
- />
|
|
|
- {this.state.currentColorPicker ===
|
|
|
- ColorPicker.CANVAS_BACKGROUND ? (
|
|
|
- <div className="popover">
|
|
|
- <div
|
|
|
- className="cover"
|
|
|
- onClick={() => this.setState({ currentColorPicker: null })}
|
|
|
- />
|
|
|
- <SketchPicker
|
|
|
- color={this.state.viewBackgroundColor}
|
|
|
- onChange={color => {
|
|
|
- this.setState({ viewBackgroundColor: color.hex });
|
|
|
- }}
|
|
|
- />
|
|
|
- </div>
|
|
|
- ) : null}
|
|
|
- <input
|
|
|
- type="text"
|
|
|
- className="swatch-input"
|
|
|
- value={this.state.viewBackgroundColor}
|
|
|
- onChange={e =>
|
|
|
- this.setState({ viewBackgroundColor: e.target.value })
|
|
|
- }
|
|
|
- />
|
|
|
- </div>
|
|
|
+ <ColorPicker
|
|
|
+ color={this.state.viewBackgroundColor}
|
|
|
+ onChange={color => this.setState({ viewBackgroundColor: color })}
|
|
|
+ />
|
|
|
<button
|
|
|
onClick={this.clearCanvas}
|
|
|
title="Clear the canvas & reset background color"
|