Improve Color Picker *2 (#195)
This commit is contained in:
parent
23cd62d148
commit
3bbcb9cbdc
271
src/index.tsx
271
src/index.tsx
@ -2,7 +2,7 @@ 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 { SketchPicker } from "react-color";
|
import { TwitterPicker } from "react-color";
|
||||||
|
|
||||||
import { moveOneLeft, moveAllLeft, moveOneRight, moveAllRight } from "./zindex";
|
import { moveOneLeft, moveAllLeft, moveOneRight, moveAllRight } from "./zindex";
|
||||||
import { roundRect } from "./roundRect";
|
import { roundRect } from "./roundRect";
|
||||||
@ -980,16 +980,9 @@ function restore(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
enum ColorPicker {
|
|
||||||
CANVAS_BACKGROUND,
|
|
||||||
SHAPE_STROKE,
|
|
||||||
SHAPE_BACKGROUND
|
|
||||||
}
|
|
||||||
|
|
||||||
type AppState = {
|
type AppState = {
|
||||||
draggingElement: ExcalidrawElement | null;
|
draggingElement: ExcalidrawElement | null;
|
||||||
resizingElement: ExcalidrawElement | null;
|
resizingElement: ExcalidrawElement | null;
|
||||||
currentColorPicker: ColorPicker | null;
|
|
||||||
elementType: string;
|
elementType: string;
|
||||||
exportBackground: boolean;
|
exportBackground: boolean;
|
||||||
currentItemStrokeColor: string;
|
currentItemStrokeColor: string;
|
||||||
@ -1124,70 +1117,17 @@ const hasStroke = () =>
|
|||||||
element.type === "arrow")
|
element.type === "arrow")
|
||||||
);
|
);
|
||||||
|
|
||||||
function getSelectedFillStyles() {
|
function getSelectedAttribute<T>(
|
||||||
const fillStyles = Array.from(
|
getAttribute: (element: ExcalidrawElement) => T
|
||||||
|
): T | null {
|
||||||
|
const attributes = Array.from(
|
||||||
new Set(
|
new Set(
|
||||||
elements
|
elements
|
||||||
.filter(element => element.isSelected)
|
.filter(element => element.isSelected)
|
||||||
.map(element => element.fillStyle)
|
.map(element => getAttribute(element))
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
return fillStyles.length === 1 ? fillStyles[0] : null;
|
return attributes.length === 1 ? attributes[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(
|
|
||||||
new Set(
|
|
||||||
elements
|
|
||||||
.filter(element => element.isSelected)
|
|
||||||
.map(element => element.backgroundColor)
|
|
||||||
)
|
|
||||||
);
|
|
||||||
return backgroundColors.length === 1 ? backgroundColors[0] : null;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function addTextElement(element: ExcalidrawTextElement) {
|
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_SHIFT_TRANSLATE_AMOUNT = 5;
|
||||||
const ELEMENT_TRANSLATE_AMOUNT = 1;
|
const ELEMENT_TRANSLATE_AMOUNT = 1;
|
||||||
|
|
||||||
@ -1282,7 +1272,6 @@ class App extends React.Component<{}, AppState> {
|
|||||||
draggingElement: null,
|
draggingElement: null,
|
||||||
resizingElement: null,
|
resizingElement: null,
|
||||||
elementType: "selection",
|
elementType: "selection",
|
||||||
currentColorPicker: null,
|
|
||||||
exportBackground: true,
|
exportBackground: true,
|
||||||
currentItemStrokeColor: "#000000",
|
currentItemStrokeColor: "#000000",
|
||||||
currentItemBackgroundColor: "#ffffff",
|
currentItemBackgroundColor: "#ffffff",
|
||||||
@ -1524,101 +1513,20 @@ class App extends React.Component<{}, AppState> {
|
|||||||
<button onClick={this.moveAllLeft}>Send to back</button>
|
<button onClick={this.moveAllLeft}>Send to back</button>
|
||||||
</div>
|
</div>
|
||||||
<h5>Stroke Color</h5>
|
<h5>Stroke Color</h5>
|
||||||
<div>
|
<ColorPicker
|
||||||
<button
|
color={getSelectedAttribute(element => element.strokeColor)}
|
||||||
className="swatch"
|
onChange={color => this.changeStrokeColor(color)}
|
||||||
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>
|
|
||||||
|
|
||||||
{hasBackground() && (
|
{hasBackground() && (
|
||||||
<>
|
<>
|
||||||
<h5>Background Color</h5>
|
<h5>Background Color</h5>
|
||||||
<div>
|
<ColorPicker
|
||||||
<button
|
color={getSelectedAttribute(
|
||||||
className="swatch"
|
element => element.backgroundColor
|
||||||
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>
|
|
||||||
</>
|
|
||||||
)}
|
)}
|
||||||
|
onChange={color => this.changeBackgroundColor(color)}
|
||||||
{hasBackground() && (
|
/>
|
||||||
<>
|
|
||||||
<h5>Fill</h5>
|
<h5>Fill</h5>
|
||||||
<ButtonSelect
|
<ButtonSelect
|
||||||
options={[
|
options={[
|
||||||
@ -1626,7 +1534,7 @@ class App extends React.Component<{}, AppState> {
|
|||||||
{ value: "hachure", text: "Hachure" },
|
{ value: "hachure", text: "Hachure" },
|
||||||
{ value: "cross-hatch", text: "Cross-hatch" }
|
{ value: "cross-hatch", text: "Cross-hatch" }
|
||||||
]}
|
]}
|
||||||
value={getSelectedFillStyles()}
|
value={getSelectedAttribute(element => element.fillStyle)}
|
||||||
onChange={value => {
|
onChange={value => {
|
||||||
this.changeProperty(element => {
|
this.changeProperty(element => {
|
||||||
element.fillStyle = value;
|
element.fillStyle = value;
|
||||||
@ -1645,7 +1553,7 @@ class App extends React.Component<{}, AppState> {
|
|||||||
{ value: 2, text: "Bold" },
|
{ value: 2, text: "Bold" },
|
||||||
{ value: 4, text: "Extra Bold" }
|
{ value: 4, text: "Extra Bold" }
|
||||||
]}
|
]}
|
||||||
value={getSelectedStrokeWidth()}
|
value={getSelectedAttribute(element => element.strokeWidth)}
|
||||||
onChange={value => {
|
onChange={value => {
|
||||||
this.changeProperty(element => {
|
this.changeProperty(element => {
|
||||||
element.strokeWidth = value;
|
element.strokeWidth = value;
|
||||||
@ -1653,14 +1561,14 @@ class App extends React.Component<{}, AppState> {
|
|||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<h5>Slopiness</h5>
|
<h5>Sloppiness</h5>
|
||||||
<ButtonSelect
|
<ButtonSelect
|
||||||
options={[
|
options={[
|
||||||
{ value: 0, text: "Draftsman" },
|
{ value: 0, text: "Draftsman" },
|
||||||
{ value: 1, text: "Artist" },
|
{ value: 1, text: "Artist" },
|
||||||
{ value: 3, text: "Cartoonist" }
|
{ value: 3, text: "Cartoonist" }
|
||||||
]}
|
]}
|
||||||
value={getSelectedRoughness()}
|
value={getSelectedAttribute(element => element.roughness)}
|
||||||
onChange={value =>
|
onChange={value =>
|
||||||
this.changeProperty(element => {
|
this.changeProperty(element => {
|
||||||
element.roughness = value;
|
element.roughness = value;
|
||||||
@ -1677,7 +1585,7 @@ class App extends React.Component<{}, AppState> {
|
|||||||
max="100"
|
max="100"
|
||||||
onChange={this.changeOpacity}
|
onChange={this.changeOpacity}
|
||||||
value={
|
value={
|
||||||
getSelectedOpacity() ||
|
getSelectedAttribute(element => element.opacity) ||
|
||||||
0 /* Put the opacity at 0 if there are two conflicting ones */
|
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>
|
<h4>Canvas</h4>
|
||||||
<div className="panelColumn">
|
<div className="panelColumn">
|
||||||
<h5>Canvas Background Color</h5>
|
<h5>Canvas Background Color</h5>
|
||||||
<div>
|
<ColorPicker
|
||||||
<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}
|
color={this.state.viewBackgroundColor}
|
||||||
onChange={color => {
|
onChange={color => this.setState({ viewBackgroundColor: 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>
|
|
||||||
<button
|
<button
|
||||||
onClick={this.clearCanvas}
|
onClick={this.clearCanvas}
|
||||||
title="Clear the canvas & reset background color"
|
title="Clear the canvas & reset background color"
|
||||||
|
@ -24,6 +24,7 @@ body {
|
|||||||
background-color: #eee;
|
background-color: #eee;
|
||||||
padding: 10px;
|
padding: 10px;
|
||||||
overflow-y: auto;
|
overflow-y: auto;
|
||||||
|
position: relative;
|
||||||
|
|
||||||
h4 {
|
h4 {
|
||||||
margin: 10px 0 10px 0;
|
margin: 10px 0 10px 0;
|
||||||
@ -50,7 +51,7 @@ body {
|
|||||||
color: #333;
|
color: #333;
|
||||||
}
|
}
|
||||||
|
|
||||||
h5:first-of-type {
|
h5:first-child {
|
||||||
margin-top: 0;
|
margin-top: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user