Wysiwyg text 2.0 (#238)
* Fixed cleaning handlers after cleanup * Double click to edit text * Preserve text styles on change
This commit is contained in:
parent
ae982e9298
commit
10955f8bb0
@ -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
src/element/textWysiwyg.tsx
Normal file
71
src/element/textWysiwyg.tsx
Normal file
@ -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();
|
||||
}
|
100
src/index.tsx
100
src/index.tsx
@ -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,
|
||||
@ -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"
|
||||
@ -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;
|
||||
@ -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,
|
||||
@ -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);
|
||||
@ -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(
|
||||
@ -918,21 +939,50 @@ class App extends React.Component<{}, AppState> {
|
||||
1,
|
||||
1,
|
||||
100
|
||||
);
|
||||
) as ExcalidrawTextElement;
|
||||
|
||||
if (!addTextElement(element as ExcalidrawTextElement)) {
|
||||
return;
|
||||
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>
|
||||
|
@ -7,6 +7,7 @@ export type AppState = {
|
||||
exportBackground: boolean;
|
||||
currentItemStrokeColor: string;
|
||||
currentItemBackgroundColor: string;
|
||||
currentItemFont: string;
|
||||
viewBackgroundColor: string;
|
||||
scrollX: number;
|
||||
scrollY: number;
|
||||
|
Loading…
x
Reference in New Issue
Block a user