Extract Sidebar panels into separate components (#230)

* Extract Sidebar panels into separate components

* Add Jest TS types
This commit is contained in:
Gasim Gasimzada 2020-01-07 15:06:22 +04:00 committed by GitHub
parent 2fb5c4cd13
commit 85365e5bcb
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 246 additions and 101 deletions

8
package-lock.json generated
View File

@ -1469,6 +1469,14 @@
"@types/istanbul-lib-report": "*" "@types/istanbul-lib-report": "*"
} }
}, },
"@types/jest": {
"version": "24.0.25",
"resolved": "https://registry.npmjs.org/@types/jest/-/jest-24.0.25.tgz",
"integrity": "sha512-hnP1WpjN4KbGEK4dLayul6lgtys6FPz0UfxMeMQCv0M+sTnzN3ConfiO72jHgLxl119guHgI8gLqDOrRLsyp2g==",
"requires": {
"jest-diff": "^24.3.0"
}
},
"@types/json-schema": { "@types/json-schema": {
"version": "7.0.4", "version": "7.0.4",
"resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.4.tgz", "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.4.tgz",

View File

@ -13,6 +13,7 @@
"roughjs": "3.1.0" "roughjs": "3.1.0"
}, },
"devDependencies": { "devDependencies": {
"@types/jest": "^24.0.25",
"@types/react": "16.9.17", "@types/react": "16.9.17",
"@types/react-color": "^3.0.1", "@types/react-color": "^3.0.1",
"@types/react-dom": "16.9.4", "@types/react-dom": "16.9.4",

View File

@ -0,0 +1,35 @@
import React from "react";
import { ColorPicker } from "../ColorPicker";
interface PanelCanvasProps {
viewBackgroundColor: string;
onViewBackgroundColorChange: (val: string) => void;
onClearCanvas: React.MouseEventHandler;
}
export const PanelCanvas: React.FC<PanelCanvasProps> = ({
viewBackgroundColor,
onViewBackgroundColorChange,
onClearCanvas
}) => {
return (
<>
<h4>Canvas</h4>
<div className="panelColumn">
<h5>Canvas Background Color</h5>
<ColorPicker
color={viewBackgroundColor}
onChange={color => onViewBackgroundColorChange(color)}
/>
<button
type="button"
onClick={onClearCanvas}
title="Clear the canvas & reset background color"
>
Clear canvas
</button>
</div>
</>
);
};

View File

@ -0,0 +1,24 @@
import React from "react";
import { ColorPicker } from "../ColorPicker";
interface PanelColorProps {
title: string;
colorValue: string | null;
onColorChange: (value: string) => void;
}
export const PanelColor: React.FC<PanelColorProps> = ({
title,
onColorChange,
colorValue
}) => {
return (
<>
<h5>{title}</h5>
<ColorPicker
color={colorValue}
onChange={color => onColorChange(color)}
/>
</>
);
};

View File

@ -0,0 +1,52 @@
import React from "react";
import { EditableText } from "../EditableText";
interface PanelExportProps {
projectName: string;
onProjectNameChange: (name: string) => void;
onExportAsPNG: React.MouseEventHandler;
exportBackground: boolean;
onExportBackgroundChange: (val: boolean) => void;
onSaveScene: React.MouseEventHandler;
onLoadScene: React.MouseEventHandler;
}
export const PanelExport: React.FC<PanelExportProps> = ({
projectName,
exportBackground,
onProjectNameChange,
onExportBackgroundChange,
onSaveScene,
onLoadScene,
onExportAsPNG
}) => {
return (
<>
<h4>Export</h4>
<div className="panelColumn">
<h5>Name</h5>
{projectName && (
<EditableText
value={projectName}
onChange={(name: string) => onProjectNameChange(name)}
/>
)}
<h5>Image</h5>
<button onClick={onExportAsPNG}>Export to png</button>
<label>
<input
type="checkbox"
checked={exportBackground}
onChange={e => {
onExportBackgroundChange(e.target.checked);
}}
/>
background
</label>
<h5>Scene</h5>
<button onClick={onSaveScene}>Save as...</button>
<button onClick={onLoadScene}>Load file...</button>
</div>
</>
);
};

View File

@ -0,0 +1,35 @@
import React from "react";
interface PanelSelectionProps {
onBringForward: React.MouseEventHandler;
onBringToFront: React.MouseEventHandler;
onSendBackward: React.MouseEventHandler;
onSendToBack: React.MouseEventHandler;
}
export const PanelSelection: React.FC<PanelSelectionProps> = ({
onBringForward,
onBringToFront,
onSendBackward,
onSendToBack
}) => {
return (
<>
<h4>Selection</h4>
<div className="buttonList">
<button type="button" onClick={onBringForward}>
Bring forward
</button>
<button type="button" onClick={onBringToFront}>
Bring to front
</button>
<button type="button" onClick={onSendBackward}>
Send backward
</button>
<button type="button" onClick={onSendToBack}>
Send to back
</button>
</div>
</>
);
};

View File

@ -0,0 +1,38 @@
import React from "react";
import { SHAPES } from "../../shapes";
import { capitalizeString } from "../../utils";
interface PanelToolsProps {
activeTool: string;
onToolChange: (value: string) => void;
}
export const PanelTools: React.FC<PanelToolsProps> = ({
activeTool,
onToolChange
}) => {
return (
<>
<h4>Shapes</h4>
<div className="panelTools">
{SHAPES.map(({ value, icon }) => (
<label
key={value}
className="tool"
title={`${capitalizeString(value)} - ${capitalizeString(value)[0]}`}
>
<input
type="radio"
checked={activeTool === value}
onChange={() => {
onToolChange(value);
}}
/>
<div className="toolIcon">{icon}</div>
</label>
))}
</div>
</>
);
};

View File

@ -29,16 +29,19 @@ import {
import { AppState } from "./types"; import { AppState } from "./types";
import { ExcalidrawElement, ExcalidrawTextElement } from "./element/types"; import { ExcalidrawElement, ExcalidrawTextElement } from "./element/types";
import { getDateTime, capitalizeString, isInputLike } from "./utils"; import { getDateTime, isInputLike } from "./utils";
import { EditableText } from "./components/EditableText";
import { ButtonSelect } from "./components/ButtonSelect"; import { ButtonSelect } from "./components/ButtonSelect";
import { ColorPicker } from "./components/ColorPicker"; import { findShapeByKey, shapesShortcutKeys } from "./shapes";
import { SHAPES, findShapeByKey, shapesShortcutKeys } from "./shapes";
import { createHistory } from "./history"; import { createHistory } from "./history";
import "./styles.scss"; import "./styles.scss";
import ContextMenu from "./components/ContextMenu"; import ContextMenu from "./components/ContextMenu";
import { PanelTools } from "./components/panels/PanelTools";
import { PanelSelection } from "./components/panels/PanelSelection";
import { PanelColor } from "./components/panels/PanelColor";
import { PanelExport } from "./components/panels/PanelExport";
import { PanelCanvas } from "./components/panels/PanelCanvas";
const { elements } = createScene(); const { elements } = createScene();
const { history } = createHistory(); const { history } = createHistory();
@ -368,20 +371,9 @@ class App extends React.Component<{}, AppState> {
}} }}
> >
<div className="sidePanel"> <div className="sidePanel">
<h4>Shapes</h4> <PanelTools
<div className="panelTools"> activeTool={this.state.elementType}
{SHAPES.map(({ value, icon }) => ( onToolChange={value => {
<label
key={value}
className="tool"
title={`${capitalizeString(value)} - ${
capitalizeString(value)[0]
}`}
>
<input
type="radio"
checked={this.state.elementType === value}
onChange={() => {
this.setState({ elementType: value }); this.setState({ elementType: value });
clearSelection(elements); clearSelection(elements);
document.documentElement.style.cursor = document.documentElement.style.cursor =
@ -389,38 +381,35 @@ class App extends React.Component<{}, AppState> {
this.forceUpdate(); this.forceUpdate();
}} }}
/> />
<div className="toolIcon">{icon}</div>
</label>
))}
</div>
{someElementIsSelected(elements) && ( {someElementIsSelected(elements) && (
<div className="panelColumn"> <div className="panelColumn">
<h4>Selection</h4> <PanelSelection
<div className="buttonList"> onBringForward={this.moveOneRight}
<button onClick={this.moveOneRight}>Bring forward</button> onBringToFront={this.moveAllRight}
<button onClick={this.moveAllRight}>Bring to front</button> onSendBackward={this.moveOneLeft}
<button onClick={this.moveOneLeft}>Send backward</button> onSendToBack={this.moveAllLeft}
<button onClick={this.moveAllLeft}>Send to back</button> />
</div>
<h5>Stroke Color</h5> <PanelColor
<ColorPicker title="Stroke Color"
color={getSelectedAttribute( onColorChange={this.changeStrokeColor}
colorValue={getSelectedAttribute(
elements, elements,
element => element.strokeColor element => element.strokeColor
)} )}
onChange={color => this.changeStrokeColor(color)}
/> />
{hasBackground(elements) && ( {hasBackground(elements) && (
<> <>
<h5>Background Color</h5> <PanelColor
<ColorPicker title="Background Color"
color={getSelectedAttribute( onColorChange={this.changeBackgroundColor}
colorValue={getSelectedAttribute(
elements, elements,
element => element.backgroundColor element => element.backgroundColor
)} )}
onChange={color => this.changeBackgroundColor(color)}
/> />
<h5>Fill</h5> <h5>Fill</h5>
<ButtonSelect <ButtonSelect
options={[ options={[
@ -498,63 +487,26 @@ class App extends React.Component<{}, AppState> {
</button> </button>
</div> </div>
)} )}
<h4>Canvas</h4> <PanelCanvas
<div className="panelColumn"> onClearCanvas={this.clearCanvas}
<h5>Canvas Background Color</h5> onViewBackgroundColorChange={val =>
<ColorPicker this.setState({ viewBackgroundColor: val })
color={this.state.viewBackgroundColor} }
onChange={color => this.setState({ viewBackgroundColor: color })} viewBackgroundColor={this.state.viewBackgroundColor}
/> />
<button <PanelExport
onClick={this.clearCanvas} projectName={this.state.name}
title="Clear the canvas & reset background color" onProjectNameChange={this.updateProjectName}
> onExportAsPNG={() => exportAsPNG(elements, canvas, this.state)}
Clear canvas exportBackground={this.state.exportBackground}
</button> onExportBackgroundChange={val =>
</div> this.setState({ exportBackground: val })
<h4>Export</h4> }
<div className="panelColumn"> onSaveScene={() => saveAsJSON(elements, this.state.name)}
<h5>Name</h5> onLoadScene={() =>
{this.state.name && ( loadFromJSON(elements).then(() => this.forceUpdate())
<EditableText }
value={this.state.name}
onChange={(name: string) => this.updateProjectName(name)}
/> />
)}
<h5>Image</h5>
<button
onClick={() => {
exportAsPNG(elements, canvas, this.state);
}}
>
Export to png
</button>
<label>
<input
type="checkbox"
checked={this.state.exportBackground}
onChange={e => {
this.setState({ exportBackground: e.target.checked });
}}
/>
background
</label>
<h5>Scene</h5>
<button
onClick={() => {
saveAsJSON(elements, this.state.name);
}}
>
Save as...
</button>
<button
onClick={() => {
loadFromJSON(elements).then(() => this.forceUpdate());
}}
>
Load file...
</button>
</div>
</div> </div>
<canvas <canvas
id="canvas" id="canvas"