Skip to content

Commit

Permalink
Wysiwyg text 2.0 (excalidraw#238)
Browse files Browse the repository at this point in the history
* Fixed cleaning handlers after cleanup

* Double click to edit text

* Preserve text styles on change
  • Loading branch information
nanot1m authored and vjeux committed Jan 7, 2020
1 parent ae982e9 commit 10955f8
Show file tree
Hide file tree
Showing 4 changed files with 149 additions and 26 deletions.
1 change: 1 addition & 0 deletions src/element/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,3 +9,4 @@ export { handlerRectangles } from "./handlerRectangles";
export { hitTest } from "./collision";
export { resizeTest } from "./resizeTest";
export { isTextElement } from "./typeChecks";
export { textWysiwyg } from "./textWysiwyg";
71 changes: 71 additions & 0 deletions src/element/textWysiwyg.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
import { KEYS } from "../index";

type TextWysiwygParams = {
initText: string;
x: number;
y: number;
strokeColor: string;
font: string;
onSubmit: (text: string) => void;
};

export function textWysiwyg({
initText,
x,
y,
strokeColor,
font,
onSubmit
}: TextWysiwygParams) {
const input = document.createElement("input");
input.value = initText;
Object.assign(input.style, {
color: strokeColor,
position: "absolute",
top: y - 8 + "px",
left: x + "px",
transform: "translate(-50%, -50%)",
boxShadow: "none",
textAlign: "center",
width: (window.innerWidth - x) * 2 + "px",
font: font,
border: "none",
background: "transparent"
});

input.onkeydown = ev => {
if (ev.key === KEYS.ESCAPE) {
ev.preventDefault();
cleanup();
return;
}
if (ev.key === KEYS.ENTER) {
ev.preventDefault();
handleSubmit();
}
};
input.onblur = handleSubmit;

function stopEvent(ev: Event) {
ev.stopPropagation();
}

function handleSubmit() {
if (input.value) {
onSubmit(input.value);
}
cleanup();
}

function cleanup() {
input.onblur = null;
input.onkeydown = null;
window.removeEventListener("wheel", stopEvent, true);
document.body.removeChild(input);
}

window.addEventListener("wheel", stopEvent, true);
document.body.appendChild(input);
input.focus();
input.select();
}
102 changes: 76 additions & 26 deletions src/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import rough from "roughjs/bin/wrappers/rough";

import { moveOneLeft, moveAllLeft, moveOneRight, moveAllRight } from "./zindex";
import { randomSeed } from "./random";
import { newElement, resizeTest, isTextElement } from "./element";
import { newElement, resizeTest, isTextElement, textWysiwyg } from "./element";
import {
clearSelection,
getSelectedIndices,
Expand Down Expand Up @@ -50,11 +50,12 @@ const DEFAULT_PROJECT_NAME = `excalidraw-${getDateTime()}`;
const CANVAS_WINDOW_OFFSET_LEFT = 250;
const CANVAS_WINDOW_OFFSET_TOP = 0;

const KEYS = {
export const KEYS = {
ARROW_LEFT: "ArrowLeft",
ARROW_RIGHT: "ArrowRight",
ARROW_DOWN: "ArrowDown",
ARROW_UP: "ArrowUp",
ENTER: "Enter",
ESCAPE: "Escape",
DELETE: "Delete",
BACKSPACE: "Backspace"
Expand All @@ -79,24 +80,26 @@ function resetCursor() {
document.documentElement.style.cursor = "";
}

function addTextElement(element: ExcalidrawTextElement) {
function addTextElement(
element: ExcalidrawTextElement,
text: string,
font: string
) {
resetCursor();
const text = prompt("What text do you want?");
if (text === null || text === "") {
return false;
}
const fontSize = 20;
element.text = text;
element.font = `${fontSize}px Virgil`;
const font = context.font;
element.font = font;
const currentFont = context.font;
context.font = element.font;
const textMeasure = context.measureText(element.text);
const width = textMeasure.width;
const actualBoundingBoxAscent =
textMeasure.actualBoundingBoxAscent || fontSize;
textMeasure.actualBoundingBoxAscent || parseInt(font);
const actualBoundingBoxDescent = textMeasure.actualBoundingBoxDescent || 0;
element.actualBoundingBoxAscent = actualBoundingBoxAscent;
context.font = font;
context.font = currentFont;
const height = actualBoundingBoxAscent + actualBoundingBoxDescent;
// Center the text
element.x -= width / 2;
Expand Down Expand Up @@ -138,6 +141,7 @@ class App extends React.Component<{}, AppState> {
exportBackground: true,
currentItemStrokeColor: "#000000",
currentItemBackgroundColor: "#ffffff",
currentItemFont: "20px Virgil",
viewBackgroundColor: "#ffffff",
scrollX: 0,
scrollY: 0,
Expand Down Expand Up @@ -688,9 +692,23 @@ class App extends React.Component<{}, AppState> {
}

if (isTextElement(element)) {
if (!addTextElement(element)) {
return;
}
textWysiwyg({
initText: "",
x: e.clientX,
y: e.clientY,
strokeColor: this.state.currentItemStrokeColor,
font: this.state.currentItemFont,
onSubmit: text => {
addTextElement(element, text, this.state.currentItemFont);
elements.push(element);
element.isSelected = true;
this.setState({
draggingElement: null,
elementType: "selection"
});
}
});
return;
}

elements.push(element);
Expand Down Expand Up @@ -903,9 +921,12 @@ class App extends React.Component<{}, AppState> {
const x =
e.clientX - CANVAS_WINDOW_OFFSET_LEFT - this.state.scrollX;
const y = e.clientY - CANVAS_WINDOW_OFFSET_TOP - this.state.scrollY;

if (getElementAtPosition(elements, x, y)) {
const elementAtPosition = getElementAtPosition(elements, x, y);
if (elementAtPosition && !isTextElement(elementAtPosition)) {
return;
} else if (elementAtPosition) {
elements.splice(elements.indexOf(elementAtPosition), 1);
this.forceUpdate();
}

const element = newElement(
Expand All @@ -918,21 +939,50 @@ class App extends React.Component<{}, AppState> {
1,
1,
100
);

if (!addTextElement(element as ExcalidrawTextElement)) {
return;
) as ExcalidrawTextElement;

let initText = "";
let textX = e.clientX;
let textY = e.clientY;
if (elementAtPosition) {
Object.assign(element, elementAtPosition);
// x and y will change after calling addTextElement function
element.x = elementAtPosition.x + elementAtPosition.width / 2;
element.y =
elementAtPosition.y + elementAtPosition.actualBoundingBoxAscent;
initText = elementAtPosition.text;
textX =
this.state.scrollX +
elementAtPosition.x +
CANVAS_WINDOW_OFFSET_LEFT +
elementAtPosition.width / 2;
textY =
this.state.scrollY +
elementAtPosition.y +
CANVAS_WINDOW_OFFSET_TOP +
elementAtPosition.actualBoundingBoxAscent;
}

elements.push(element);

this.setState({
draggingElement: null,
elementType: "selection"
textWysiwyg({
initText,
x: textX,
y: textY,
strokeColor: element.strokeColor,
font: element.font || this.state.currentItemFont,
onSubmit: text => {
addTextElement(
element,
text,
element.font || this.state.currentItemFont
);
elements.push(element);
element.isSelected = true;
this.setState({
draggingElement: null,
elementType: "selection"
});
}
});
element.isSelected = true;

this.forceUpdate();
}}
/>
</div>
Expand Down
1 change: 1 addition & 0 deletions src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ export type AppState = {
exportBackground: boolean;
currentItemStrokeColor: string;
currentItemBackgroundColor: string;
currentItemFont: string;
viewBackgroundColor: string;
scrollX: number;
scrollY: number;
Expand Down

0 comments on commit 10955f8

Please sign in to comment.