From f2346275ef414007f5db35322b7dc1b6b2363f6b Mon Sep 17 00:00:00 2001 From: Gasim Gasimzada Date: Fri, 10 Jan 2020 18:00:19 +0400 Subject: [PATCH] Extract Side Panel from App component (#295) * Extract Side Panel from App component * Refactor SidePanel component - Remove unnecessary props (we are already passing appState as a prop) - Remove unnecessary allback (we are already passing setState) --- src/components/SidePanel.tsx | 272 +++++++++++++++++++++++++++++++++ src/element/index.ts | 1 + src/element/textElement.ts | 9 ++ src/index.tsx | 282 ++++------------------------------- 4 files changed, 312 insertions(+), 252 deletions(-) create mode 100644 src/components/SidePanel.tsx create mode 100644 src/element/textElement.ts diff --git a/src/components/SidePanel.tsx b/src/components/SidePanel.tsx new file mode 100644 index 00000000..c1dc365e --- /dev/null +++ b/src/components/SidePanel.tsx @@ -0,0 +1,272 @@ +import React from "react"; +import { PanelTools } from "./panels/PanelTools"; +import { Panel } from "./Panel"; +import { PanelSelection } from "./panels/PanelSelection"; +import { PanelColor } from "./panels/PanelColor"; +import { + hasBackground, + someElementIsSelected, + getSelectedAttribute, + hasStroke, + hasText, + loadFromJSON, + saveAsJSON, + exportCanvas, + deleteSelectedElements +} from "../scene"; +import { ButtonSelect } from "./ButtonSelect"; +import { ExcalidrawElement } from "../element/types"; +import { redrawTextBoundingBox, isTextElement } from "../element"; +import { PanelCanvas } from "./panels/PanelCanvas"; +import { PanelExport } from "./panels/PanelExport"; +import { ExportType } from "../scene/types"; +import { AppState } from "../types"; + +interface SidePanelProps { + elements: readonly ExcalidrawElement[]; + onToolChange: (elementType: string) => void; + changeProperty: ( + callback: (element: ExcalidrawElement) => ExcalidrawElement + ) => void; + moveAllLeft: () => void; + moveOneLeft: () => void; + moveAllRight: () => void; + moveOneRight: () => void; + onClearCanvas: React.MouseEventHandler; + onUpdateAppState: (name: string, value: any) => void; + appState: AppState; + onUpdateElements: (elements: readonly ExcalidrawElement[]) => void; + canvas: HTMLCanvasElement; +} + +export const SidePanel: React.FC = ({ + elements, + onToolChange, + changeProperty, + moveAllLeft, + moveOneLeft, + moveAllRight, + moveOneRight, + onClearCanvas, + onUpdateAppState, + appState, + onUpdateElements, + canvas +}) => { + return ( +
+ { + onToolChange(value); + }} + /> + + + + { + changeProperty(element => ({ + ...element, + strokeColor: color + })); + onUpdateAppState("currentItemStrokeColor", color); + }} + colorValue={getSelectedAttribute( + elements, + element => element.strokeColor + )} + /> + + {hasBackground(elements) && ( + <> + { + changeProperty(element => ({ + ...element, + backgroundColor: color + })); + onUpdateAppState("currentItemBackgroundColor", color); + }} + colorValue={getSelectedAttribute( + elements, + element => element.backgroundColor + )} + /> + +
Fill
+ element.fillStyle + )} + onChange={value => { + changeProperty(element => ({ + ...element, + fillStyle: value + })); + }} + /> + + )} + + {hasStroke(elements) && ( + <> +
Stroke Width
+ element.strokeWidth + )} + onChange={value => { + changeProperty(element => ({ + ...element, + strokeWidth: value + })); + }} + /> + +
Sloppiness
+ element.roughness + )} + onChange={value => + changeProperty(element => ({ + ...element, + roughness: value + })) + } + /> + + )} + + {hasText(elements) && ( + <> +
Font size
+ + isTextElement(element) && +element.font.split("px ")[0] + )} + onChange={value => + changeProperty(element => { + if (isTextElement(element)) { + element.font = `${value}px ${element.font.split("px ")[1]}`; + redrawTextBoundingBox(element); + } + + return element; + }) + } + /> +
Font familly
+ + isTextElement(element) && element.font.split("px ")[1] + )} + onChange={value => + changeProperty(element => { + if (isTextElement(element)) { + element.font = `${element.font.split("px ")[0]}px ${value}`; + redrawTextBoundingBox(element); + } + + return element; + }) + } + /> + + )} + +
Opacity
+ { + changeProperty(element => ({ + ...element, + opacity: +event.target.value + })); + }} + value={ + getSelectedAttribute(elements, element => element.opacity) || + 0 /* Put the opacity at 0 if there are two conflicting ones */ + } + /> + + +
+ { + onUpdateAppState("viewBackgroundColor", value); + }} + viewBackgroundColor={appState.viewBackgroundColor} + /> + { + onUpdateAppState("name", name); + }} + onExportCanvas={(type: ExportType) => + exportCanvas(type, elements, canvas, appState) + } + exportBackground={appState.exportBackground} + onExportBackgroundChange={value => { + onUpdateAppState("exportBackground", value); + }} + onSaveScene={() => saveAsJSON(elements, appState.name)} + onLoadScene={() => + loadFromJSON().then(({ elements }) => { + onUpdateElements(elements); + }) + } + /> +
+ ); +}; diff --git a/src/element/index.ts b/src/element/index.ts index 3348ba03..4914221d 100644 --- a/src/element/index.ts +++ b/src/element/index.ts @@ -10,3 +10,4 @@ export { hitTest } from "./collision"; export { resizeTest } from "./resizeTest"; export { isTextElement } from "./typeChecks"; export { textWysiwyg } from "./textWysiwyg"; +export { redrawTextBoundingBox } from "./textElement"; diff --git a/src/element/textElement.ts b/src/element/textElement.ts new file mode 100644 index 00000000..2908c859 --- /dev/null +++ b/src/element/textElement.ts @@ -0,0 +1,9 @@ +import { measureText } from "../utils"; +import { ExcalidrawTextElement } from "./types"; + +export const redrawTextBoundingBox = (element: ExcalidrawTextElement) => { + const metrics = measureText(element.text, element.font); + element.width = metrics.width; + element.height = metrics.height; + element.baseline = metrics.baseline; +}; diff --git a/src/index.tsx b/src/index.tsx index 80bfd0b2..199884a4 100644 --- a/src/index.tsx +++ b/src/index.tsx @@ -11,7 +11,8 @@ import { resizeTest, isTextElement, textWysiwyg, - getElementAbsoluteCoords + getElementAbsoluteCoords, + redrawTextBoundingBox } from "./element"; import { clearSelection, @@ -20,42 +21,28 @@ import { setSelection, isOverScrollBars, someElementIsSelected, - getSelectedAttribute, - loadFromJSON, - saveAsJSON, - exportCanvas, restoreFromLocalStorage, saveToLocalStorage, - hasBackground, - hasStroke, getElementAtPosition, createScene, - getElementContainingPosition, - hasText + getElementContainingPosition } from "./scene"; import { renderScene } from "./renderer"; import { AppState } from "./types"; import { ExcalidrawElement, ExcalidrawTextElement } from "./element/types"; -import { ExportType } from "./scene/types"; import { getDateTime, isInputLike, measureText } from "./utils"; import { KEYS, META_KEY, isArrowKey } from "./keys"; -import { ButtonSelect } from "./components/ButtonSelect"; import { findShapeByKey, shapesShortcutKeys } from "./shapes"; import { createHistory } from "./history"; 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"; -import { Panel } from "./components/Panel"; import "./styles.scss"; import { getElementWithResizeHandler } from "./element/resizeTest"; +import { SidePanel } from "./components/SidePanel"; let { elements } = createScene(); const { history } = createHistory(); @@ -307,7 +294,7 @@ export class App extends React.Component<{}, AppState> { }; if (isTextElement(newElement)) { newElement.font = pastedElement?.font; - this.redrawTextBoundingBox(newElement); + redrawTextBoundingBox(newElement); } return newElement; } @@ -338,10 +325,6 @@ export class App extends React.Component<{}, AppState> { private removeWheelEventListener: (() => void) | undefined; - private updateProjectName(name: string): void { - this.setState({ name }); - } - private changeProperty = ( callback: (element: ExcalidrawElement) => ExcalidrawElement ) => { @@ -355,29 +338,6 @@ export class App extends React.Component<{}, AppState> { this.forceUpdate(); }; - private changeOpacity = (event: React.ChangeEvent) => { - this.changeProperty(element => ({ - ...element, - opacity: +event.target.value - })); - }; - - private changeStrokeColor = (color: string) => { - this.changeProperty(element => ({ - ...element, - strokeColor: color - })); - this.setState({ currentItemStrokeColor: color }); - }; - - private changeBackgroundColor = (color: string) => { - this.changeProperty(element => ({ - ...element, - backgroundColor: color - })); - this.setState({ currentItemBackgroundColor: color }); - }; - private copyToClipboard = () => { if (navigator.clipboard) { const text = JSON.stringify( @@ -395,13 +355,6 @@ export class App extends React.Component<{}, AppState> { } }; - private redrawTextBoundingBox = (element: ExcalidrawTextElement) => { - const metrics = measureText(element.text, element.font); - element.width = metrics.width; - element.height = metrics.height; - element.baseline = metrics.baseline; - }; - public render() { const canvasWidth = window.innerWidth - CANVAS_WINDOW_OFFSET_LEFT; const canvasHeight = window.innerHeight - CANVAS_WINDOW_OFFSET_TOP; @@ -431,206 +384,31 @@ export class App extends React.Component<{}, AppState> { e.preventDefault(); }} > -
- { - this.setState({ elementType: value }); - elements = clearSelection(elements); - document.documentElement.style.cursor = - value === "text" ? "text" : "crosshair"; - this.forceUpdate(); - }} - /> - - - - element.strokeColor - )} - /> - - {hasBackground(elements) && ( - <> - element.backgroundColor - )} - /> - -
Fill
- element.fillStyle - )} - onChange={value => { - this.changeProperty(element => ({ - ...element, - fillStyle: value - })); - }} - /> - - )} - - {hasStroke(elements) && ( - <> -
Stroke Width
- element.strokeWidth - )} - onChange={value => { - this.changeProperty(element => ({ - ...element, - strokeWidth: value - })); - }} - /> - -
Sloppiness
- element.roughness - )} - onChange={value => - this.changeProperty(element => ({ - ...element, - roughness: value - })) - } - /> - - )} - - {hasText(elements) && ( - <> -
Font size
- - isTextElement(element) && +element.font.split("px ")[0] - )} - onChange={value => - this.changeProperty(element => { - if (isTextElement(element)) { - element.font = `${value}px ${ - element.font.split("px ")[1] - }`; - this.redrawTextBoundingBox(element); - } - - return element; - }) - } - /> -
Font familly
- - isTextElement(element) && element.font.split("px ")[1] - )} - onChange={value => - this.changeProperty(element => { - if (isTextElement(element)) { - element.font = `${ - element.font.split("px ")[0] - }px ${value}`; - this.redrawTextBoundingBox(element); - } - - return element; - }) - } - /> - - )} - -
Opacity
- element.opacity) || - 0 /* Put the opacity at 0 if there are two conflicting ones */ - } - /> - - -
- - this.setState({ viewBackgroundColor: val }) - } - viewBackgroundColor={this.state.viewBackgroundColor} - /> - - exportCanvas(type, elements, this.canvas!, this.state) - } - exportBackground={this.state.exportBackground} - onExportBackgroundChange={val => - this.setState({ exportBackground: val }) - } - onSaveScene={() => saveAsJSON(elements, this.state.name)} - onLoadScene={() => - loadFromJSON().then(({ elements: newElements }) => { - elements = newElements; - this.forceUpdate(); - }) - } - /> -
+ { + this.setState({ elementType: value }); + elements = clearSelection(elements); + document.documentElement.style.cursor = + value === "text" ? "text" : "crosshair"; + this.forceUpdate(); + }} + moveAllLeft={this.moveAllLeft} + moveAllRight={this.moveAllRight} + moveOneLeft={this.moveOneLeft} + moveOneRight={this.moveOneRight} + onClearCanvas={this.clearCanvas} + changeProperty={this.changeProperty} + onUpdateAppState={(name, value) => { + this.setState({ [name]: value } as any); + }} + onUpdateElements={newElements => { + elements = newElements; + this.forceUpdate(); + }} + appState={{ ...this.state }} + canvas={this.canvas!} + />