Merge branch 'master' of github.com:excalidraw/excalibur

This commit is contained in:
Faustino Kialungila 2020-01-06 09:06:06 +01:00
commit cc2707464c
2 changed files with 100 additions and 211 deletions

View file

@ -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";
@ -27,6 +27,8 @@ const DEFAULT_PROJECT_NAME = `excalidraw-${getDateTime()}`;
let skipHistory = false; let skipHistory = false;
const stateHistory: string[] = []; const stateHistory: string[] = [];
const redoStack: string[] = [];
function generateHistoryCurrentEntry() { function generateHistoryCurrentEntry() {
return JSON.stringify( return JSON.stringify(
elements.map(element => ({ ...element, isSelected: false })) elements.map(element => ({ ...element, isSelected: false }))
@ -985,16 +987,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;
@ -1129,70 +1124,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) {
@ -1260,6 +1202,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;
@ -1288,7 +1280,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",
@ -1368,13 +1359,25 @@ class App extends React.Component<{}, AppState> {
} else if (shapesShortcutKeys.includes(event.key.toLowerCase())) { } else if (shapesShortcutKeys.includes(event.key.toLowerCase())) {
this.setState({ elementType: findElementByKey(event.key) }); this.setState({ elementType: findElementByKey(event.key) });
} else if (event.metaKey && event.code === "KeyZ") { } else if (event.metaKey && event.code === "KeyZ") {
const currentEntry = generateHistoryCurrentEntry();
if (event.shiftKey) {
// Redo action
const entryToRestore = redoStack.pop();
if (entryToRestore !== undefined) {
restoreHistoryEntry(entryToRestore);
stateHistory.push(currentEntry);
}
} else {
// undo action
let lastEntry = stateHistory.pop(); let lastEntry = stateHistory.pop();
// If nothing was changed since last, take the previous one // If nothing was changed since last, take the previous one
if (generateHistoryCurrentEntry() === lastEntry) { if (currentEntry === lastEntry) {
lastEntry = stateHistory.pop(); lastEntry = stateHistory.pop();
} }
if (lastEntry !== undefined) { if (lastEntry !== undefined) {
restoreHistoryEntry(lastEntry); restoreHistoryEntry(lastEntry);
redoStack.push(currentEntry);
}
} }
this.forceUpdate(); this.forceUpdate();
event.preventDefault(); event.preventDefault();
@ -1530,101 +1533,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={[
@ -1632,7 +1554,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;
@ -1651,7 +1573,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;
@ -1659,14 +1581,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;
@ -1683,7 +1605,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 */
} }
/> />
@ -1696,45 +1618,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"
@ -2179,6 +2066,7 @@ class App extends React.Component<{}, AppState> {
save(this.state); save(this.state);
if (!skipHistory) { if (!skipHistory) {
pushHistoryEntry(generateHistoryCurrentEntry()); pushHistoryEntry(generateHistoryCurrentEntry());
redoStack.splice(0, redoStack.length);
} }
skipHistory = false; skipHistory = false;
} }

View file

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