From a16cd3a34f21b8a8c7786616ee7d88703fd73f28 Mon Sep 17 00:00:00 2001 From: davidbonan Date: Thu, 9 Jan 2020 02:29:41 +0100 Subject: [PATCH 1/4] Add font size and font familly option for selection (#278) * Add font size and font familly option for selection * Allow copy font style * More clearner method name * Update options size and font-familly --- src/index.tsx | 61 +++++++++++++++++++++++++++++++++++++++- src/scene/comparisons.ts | 5 ++++ src/scene/index.ts | 3 +- 3 files changed, 67 insertions(+), 2 deletions(-) diff --git a/src/index.tsx b/src/index.tsx index b78984551..2ff0dc08b 100644 --- a/src/index.tsx +++ b/src/index.tsx @@ -29,7 +29,8 @@ import { hasStroke, getElementAtPosition, createScene, - getElementContainingPosition + getElementContainingPosition, + hasText } from "./scene"; import { renderScene } from "./renderer"; @@ -252,6 +253,10 @@ export class App extends React.Component<{}, AppState> { element.fillStyle = pastedElement?.fillStyle; element.opacity = pastedElement?.opacity; element.roughness = pastedElement?.roughness; + if(isTextElement(element)) { + element.font = pastedElement?.font; + this.redrawTextBoundingBox(element); + } } }); this.forceUpdate(); @@ -324,6 +329,14 @@ 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; + this.forceUpdate(); + } + public render() { const canvasWidth = window.innerWidth - CANVAS_WINDOW_OFFSET_LEFT; const canvasHeight = window.innerHeight - CANVAS_WINDOW_OFFSET_TOP; @@ -452,6 +465,52 @@ export class App extends React.Component<{}, AppState> { )} + {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); + } + }) + } + /> +
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); + } + }) + } + /> + + )} +
Opacity
element.type === "arrow") ); +export const hasText = (elements: ExcalidrawElement[]) => + elements.some( + element => element.isSelected && element.type === "text" + ); + export function getElementAtPosition( elements: ExcalidrawElement[], x: number, diff --git a/src/scene/index.ts b/src/scene/index.ts index 479c60efa..171315f40 100644 --- a/src/scene/index.ts +++ b/src/scene/index.ts @@ -18,6 +18,7 @@ export { hasBackground, hasStroke, getElementAtPosition, - getElementContainingPosition + getElementContainingPosition, + hasText } from "./comparisons"; export { createScene } from "./createScene"; From 4a044d3ace132551d292e58f9bf6060c5dbfa10b Mon Sep 17 00:00:00 2001 From: Guillermo Peralta Scura Date: Wed, 8 Jan 2020 23:56:35 -0300 Subject: [PATCH 2/4] Show move and resize cursors on hover (#280) * Change to move cursor on hover * Show resize handlers on hover --- src/element/handlerRectangles.ts | 36 ++++++------- src/element/resizeTest.ts | 31 ++++++++--- src/index.tsx | 89 ++++++++++++++++++++------------ src/scene/types.ts | 5 ++ 4 files changed, 104 insertions(+), 57 deletions(-) diff --git a/src/element/handlerRectangles.ts b/src/element/handlerRectangles.ts index 6e01c9d86..985751109 100644 --- a/src/element/handlerRectangles.ts +++ b/src/element/handlerRectangles.ts @@ -1,11 +1,11 @@ -import { SceneState } from "../scene/types"; import { ExcalidrawElement } from "./types"; +import { SceneScroll } from "../scene/types"; type Sides = "n" | "s" | "w" | "e" | "nw" | "ne" | "sw" | "se"; export function handlerRectangles( element: ExcalidrawElement, - sceneState: SceneState + { scrollX, scrollY }: SceneScroll ) { const elementX1 = element.x; const elementX2 = element.x + element.width; @@ -21,15 +21,15 @@ export function handlerRectangles( if (Math.abs(elementX2 - elementX1) > minimumSize) { handlers["n"] = [ - elementX1 + (elementX2 - elementX1) / 2 + sceneState.scrollX - 4, - elementY1 - margin + sceneState.scrollY + marginY, + elementX1 + (elementX2 - elementX1) / 2 + scrollX - 4, + elementY1 - margin + scrollY + marginY, 8, 8 ]; handlers["s"] = [ - elementX1 + (elementX2 - elementX1) / 2 + sceneState.scrollX - 4, - elementY2 - margin + sceneState.scrollY - marginY, + elementX1 + (elementX2 - elementX1) / 2 + scrollX - 4, + elementY2 - margin + scrollY - marginY, 8, 8 ]; @@ -37,41 +37,41 @@ export function handlerRectangles( if (Math.abs(elementY2 - elementY1) > minimumSize) { handlers["w"] = [ - elementX1 - margin + sceneState.scrollX + marginX, - elementY1 + (elementY2 - elementY1) / 2 + sceneState.scrollY - 4, + elementX1 - margin + scrollX + marginX, + elementY1 + (elementY2 - elementY1) / 2 + scrollY - 4, 8, 8 ]; handlers["e"] = [ - elementX2 - margin + sceneState.scrollX - marginX, - elementY1 + (elementY2 - elementY1) / 2 + sceneState.scrollY - 4, + elementX2 - margin + scrollX - marginX, + elementY1 + (elementY2 - elementY1) / 2 + scrollY - 4, 8, 8 ]; } handlers["nw"] = [ - elementX1 - margin + sceneState.scrollX + marginX, - elementY1 - margin + sceneState.scrollY + marginY, + elementX1 - margin + scrollX + marginX, + elementY1 - margin + scrollY + marginY, 8, 8 ]; // nw handlers["ne"] = [ - elementX2 - margin + sceneState.scrollX - marginX, - elementY1 - margin + sceneState.scrollY + marginY, + elementX2 - margin + scrollX - marginX, + elementY1 - margin + scrollY + marginY, 8, 8 ]; // ne handlers["sw"] = [ - elementX1 - margin + sceneState.scrollX + marginX, - elementY2 - margin + sceneState.scrollY - marginY, + elementX1 - margin + scrollX + marginX, + elementY2 - margin + scrollY - marginY, 8, 8 ]; // sw handlers["se"] = [ - elementX2 - margin + sceneState.scrollX - marginX, - elementY2 - margin + sceneState.scrollY - marginY, + elementX2 - margin + scrollX - marginX, + elementY2 - margin + scrollY - marginY, 8, 8 ]; // se diff --git a/src/element/resizeTest.ts b/src/element/resizeTest.ts index a74a2feca..51ecf7ef8 100644 --- a/src/element/resizeTest.ts +++ b/src/element/resizeTest.ts @@ -1,7 +1,7 @@ import { ExcalidrawElement } from "./types"; -import { SceneState } from "../scene/types"; import { handlerRectangles } from "./handlerRectangles"; +import { SceneScroll } from "../scene/types"; type HandlerRectanglesRet = keyof ReturnType; @@ -9,20 +9,20 @@ export function resizeTest( element: ExcalidrawElement, x: number, y: number, - sceneState: SceneState + { scrollX, scrollY }: SceneScroll ): HandlerRectanglesRet | false { if (!element.isSelected || element.type === "text") return false; - const handlers = handlerRectangles(element, sceneState); + const handlers = handlerRectangles(element, { scrollX, scrollY }); const filter = Object.keys(handlers).filter(key => { const handler = handlers[key as HandlerRectanglesRet]!; return ( - x + sceneState.scrollX >= handler[0] && - x + sceneState.scrollX <= handler[0] + handler[2] && - y + sceneState.scrollY >= handler[1] && - y + sceneState.scrollY <= handler[1] + handler[3] + x + scrollX >= handler[0] && + x + scrollX <= handler[0] + handler[2] && + y + scrollY >= handler[1] && + y + scrollY <= handler[1] + handler[3] ); }); @@ -32,3 +32,20 @@ export function resizeTest( return false; } + +export function getElementWithResizeHandler( + elements: ExcalidrawElement[], + { x, y }: { x: number; y: number }, + { scrollX, scrollY }: SceneScroll +) { + return elements.reduce((result, element) => { + if (result) { + return result; + } + const resizeHandle = resizeTest(element, x, y, { + scrollX, + scrollY + }); + return resizeHandle ? { element, resizeHandle } : null; + }, null as { element: ExcalidrawElement; resizeHandle: ReturnType } | null); +} diff --git a/src/index.tsx b/src/index.tsx index 2ff0dc08b..9e503b4a3 100644 --- a/src/index.tsx +++ b/src/index.tsx @@ -53,6 +53,7 @@ import { PanelCanvas } from "./components/panels/PanelCanvas"; import { Panel } from "./components/Panel"; import "./styles.scss"; +import { getElementWithResizeHandler } from "./element/resizeTest"; const { elements } = createScene(); const { history } = createHistory(); @@ -99,6 +100,15 @@ let lastCanvasHeight = -1; let lastMouseUp: ((e: any) => void) | null = null; +export function viewportCoordsToSceneCoords( + { clientX, clientY }: { clientX: number; clientY: number }, + { scrollX, scrollY }: { scrollX: number; scrollY: number } +) { + const x = clientX - CANVAS_WINDOW_OFFSET_LEFT - scrollX; + const y = clientY - CANVAS_WINDOW_OFFSET_TOP - scrollY; + return { x, y }; +} + export class App extends React.Component<{}, AppState> { canvas: HTMLCanvasElement | null = null; rc: RoughCanvas | null = null; @@ -591,9 +601,7 @@ export class App extends React.Component<{}, AppState> { onContextMenu={e => { e.preventDefault(); - const x = - e.clientX - CANVAS_WINDOW_OFFSET_LEFT - this.state.scrollX; - const y = e.clientY - CANVAS_WINDOW_OFFSET_TOP - this.state.scrollY; + const { x, y } = viewportCoordsToSceneCoords(e, this.state); const element = getElementAtPosition(elements, x, y); if (!element) { @@ -670,9 +678,8 @@ export class App extends React.Component<{}, AppState> { this.state.scrollY ); - const x = - e.clientX - CANVAS_WINDOW_OFFSET_LEFT - this.state.scrollX; - const y = e.clientY - CANVAS_WINDOW_OFFSET_TOP - this.state.scrollY; + const { x, y } = viewportCoordsToSceneCoords(e, this.state); + const element = newElement( this.state.elementType, x, @@ -684,28 +691,23 @@ export class App extends React.Component<{}, AppState> { 1, 100 ); - let resizeHandle: ReturnType = false; + type ResizeTestType = ReturnType; + let resizeHandle: ResizeTestType = false; let isDraggingElements = false; let isResizingElements = false; if (this.state.elementType === "selection") { - const resizeElement = elements.find(element => { - return resizeTest(element, x, y, { - scrollX: this.state.scrollX, - scrollY: this.state.scrollY, - viewBackgroundColor: this.state.viewBackgroundColor - }); - }); + const resizeElement = getElementWithResizeHandler( + elements, + { x, y }, + this.state + ); this.setState({ - resizingElement: resizeElement ? resizeElement : null + resizingElement: resizeElement ? resizeElement.element : null }); if (resizeElement) { - resizeHandle = resizeTest(resizeElement, x, y, { - scrollX: this.state.scrollX, - scrollY: this.state.scrollY, - viewBackgroundColor: this.state.viewBackgroundColor - }); + resizeHandle = resizeElement.resizeHandle; document.documentElement.style.cursor = `${resizeHandle}-resize`; isResizingElements = true; } else { @@ -714,7 +716,7 @@ export class App extends React.Component<{}, AppState> { // If we click on something if (hitElement) { if (hitElement.isSelected) { - // If that element is not already selected, do nothing, + // If that element is already selected, do nothing, // we're likely going to drag it } else { // We unselect every other elements unless shift is pressed @@ -829,10 +831,8 @@ export class App extends React.Component<{}, AppState> { const el = this.state.resizingElement; const selectedElements = elements.filter(el => el.isSelected); if (selectedElements.length === 1) { - const x = - e.clientX - CANVAS_WINDOW_OFFSET_LEFT - this.state.scrollX; - const y = - e.clientY - CANVAS_WINDOW_OFFSET_TOP - this.state.scrollY; + const { x, y } = viewportCoordsToSceneCoords(e, this.state); + selectedElements.forEach(element => { switch (resizeHandle) { case "nw": @@ -904,10 +904,8 @@ export class App extends React.Component<{}, AppState> { if (isDraggingElements) { const selectedElements = elements.filter(el => el.isSelected); if (selectedElements.length) { - const x = - e.clientX - CANVAS_WINDOW_OFFSET_LEFT - this.state.scrollX; - const y = - e.clientY - CANVAS_WINDOW_OFFSET_TOP - this.state.scrollY; + const { x, y } = viewportCoordsToSceneCoords(e, this.state); + selectedElements.forEach(element => { element.x += x - lastX; element.y += y - lastY; @@ -991,9 +989,8 @@ export class App extends React.Component<{}, AppState> { this.forceUpdate(); }} onDoubleClick={e => { - const x = - e.clientX - CANVAS_WINDOW_OFFSET_LEFT - this.state.scrollX; - const y = e.clientY - CANVAS_WINDOW_OFFSET_TOP - this.state.scrollY; + const { x, y } = viewportCoordsToSceneCoords(e, this.state); + const elementAtPosition = getElementAtPosition(elements, x, y); const element = newElement( @@ -1066,6 +1063,34 @@ export class App extends React.Component<{}, AppState> { } }); }} + onMouseMove={e => { + const hasDeselectedButton = Boolean(e.buttons); + if (hasDeselectedButton || this.state.elementType !== "selection") { + return; + } + const { x, y } = viewportCoordsToSceneCoords(e, this.state); + const resizeElement = getElementWithResizeHandler( + elements, + { x, y }, + this.state + ); + if (resizeElement && resizeElement.resizeHandle) { + document.documentElement.style.cursor = `${resizeElement.resizeHandle}-resize`; + return; + } + const hitElement = getElementAtPosition(elements, x, y); + if (hitElement) { + const resizeHandle = resizeTest(hitElement, x, y, { + scrollX: this.state.scrollX, + scrollY: this.state.scrollY + }); + document.documentElement.style.cursor = resizeHandle + ? `${resizeHandle}-resize` + : `move`; + } else { + document.documentElement.style.cursor = ``; + } + }} /> ); diff --git a/src/scene/types.ts b/src/scene/types.ts index 41a2f1b2a..c8904dca5 100644 --- a/src/scene/types.ts +++ b/src/scene/types.ts @@ -7,6 +7,11 @@ export type SceneState = { viewBackgroundColor: string | null; }; +export type SceneScroll = { + scrollX: number; + scrollY: number; +}; + export interface Scene { elements: ExcalidrawTextElement[]; } From 77400c7b70a8995551bbd6940552ebb1ee6a42a8 Mon Sep 17 00:00:00 2001 From: Christopher Chedeau Date: Wed, 8 Jan 2020 19:58:19 -0800 Subject: [PATCH 3/4] One more testimonial (#281) --- README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README.md b/README.md index cb9ebe6a9..519835ed2 100644 --- a/README.md +++ b/README.md @@ -17,6 +17,8 @@ Go to https://www.excalidraw.com to start sketching + + ## Run the code From 2553d10d34590301a3e89724f872c524b0fcadb3 Mon Sep 17 00:00:00 2001 From: Christopher Chedeau Date: Wed, 8 Jan 2020 20:49:42 -0800 Subject: [PATCH 4/4] Yet another awesome testimonial (#282) --- README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README.md b/README.md index 519835ed2..2deb62207 100644 --- a/README.md +++ b/README.md @@ -19,6 +19,8 @@ Go to https://www.excalidraw.com to start sketching + + ## Run the code