excalidraw/src/element/textWysiwyg.tsx
Daishi Kato 65be7973be
Rotation support (#1099)
* rotate rectanble with fixed angle

* rotate dashed rectangle with fixed angle

* fix rotate handler rect

* fix canvas size with rotation

* angle in element base

* fix bug in calculating canvas size

* trial only for rectangle

* hitTest for rectangle rotation

* properly resize rotated rectangle

* fix canvas size calculation

* giving up... workaround for now

* **experimental** handler to rotate rectangle

* remove rotation on copy for debugging

* update snapshots

* better rotation handler with atan2

* rotate when drawImage

* add rotation handler

* hitTest for any shapes

* fix hitTest for curved lines

* rotate text element

* rotation locking

* hint messaage for rotating

* show proper handlers on mobile (a workaround, there should be a better way)

* refactor hitTest

* support exporting png

* support exporting svg

* fix rotating curved line

* refactor drawElementFromCanvas with getElementAbsoluteCoords

* fix export png and svg

* adjust resize positions for lines (N, E, S, W)

* do not make handlers big on mobile

* Update src/locales/en.json

Alright!

Co-Authored-By: Lipis <lipiridis@gmail.com>

* do not show rotation/resizing hints on mobile

* proper calculation for N and W positions

* simplify calculation

* use "rotation" as property name for clarification (may increase bundle size)

* update snapshots excluding rotation handle

* refactor with adjustPositionWithRotation

* refactor with adjustXYWithRotation

* forgot to rename rotation

* rename internal function

* initialize element angle on restore

* rotate wysiwyg editor

* fix shift-rotate around 270deg

* improve rotation locking

* refactor adjustXYWithRotation

* avoid rotation degree becomes >=360

* refactor with generateHandler

Co-authored-by: Lipis <lipiridis@gmail.com>
Co-authored-by: dwelle <luzar.david@gmail.com>
2020-04-02 10:40:26 +02:00

135 lines
3.2 KiB
TypeScript

import { KEYS } from "../keys";
import { selectNode } from "../utils";
function trimText(text: string) {
// whitespace only → trim all because we'd end up inserting invisible element
if (!text.trim()) {
return "";
}
// replace leading/trailing newlines (only) otherwise it messes up bounding
// box calculation (there's also a bug in FF which inserts trailing newline
// for multiline texts)
return text.replace(/^\n+|\n+$/g, "");
}
type TextWysiwygParams = {
initText: string;
x: number;
y: number;
strokeColor: string;
font: string;
opacity: number;
zoom: number;
angle: number;
onSubmit: (text: string) => void;
onCancel: () => void;
};
export function textWysiwyg({
initText,
x,
y,
strokeColor,
font,
opacity,
zoom,
angle,
onSubmit,
onCancel,
}: TextWysiwygParams) {
const editable = document.createElement("div");
try {
editable.contentEditable = "plaintext-only";
} catch {
editable.contentEditable = "true";
}
editable.tabIndex = 0;
editable.innerText = initText;
editable.dataset.type = "wysiwyg";
const degree = (180 * angle) / Math.PI;
Object.assign(editable.style, {
color: strokeColor,
position: "fixed",
opacity: opacity / 100,
top: `${y}px`,
left: `${x}px`,
transform: `translate(-50%, -50%) scale(${zoom}) rotate(${degree}deg)`,
textAlign: "left",
display: "inline-block",
font: font,
padding: "4px",
// This needs to have "1px solid" otherwise the carret doesn't show up
// the first time on Safari and Chrome!
outline: "1px solid transparent",
whiteSpace: "nowrap",
minHeight: "1em",
backfaceVisibility: "hidden",
});
editable.onpaste = (ev) => {
try {
const selection = window.getSelection();
if (!selection?.rangeCount) {
return;
}
selection.deleteFromDocument();
const text = ev.clipboardData!.getData("text").replace(/\r\n?/g, "\n");
const span = document.createElement("span");
span.innerText = text;
const range = selection.getRangeAt(0);
range.insertNode(span);
// deselect
window.getSelection()!.removeAllRanges();
range.setStart(span, span.childNodes.length);
range.setEnd(span, span.childNodes.length);
selection.addRange(range);
ev.preventDefault();
} catch (error) {
console.error(error);
}
};
editable.onkeydown = (ev) => {
if (ev.key === KEYS.ESCAPE) {
ev.preventDefault();
handleSubmit();
}
if (ev.key === KEYS.ENTER && !ev.shiftKey) {
ev.stopPropagation();
}
};
editable.onblur = handleSubmit;
function stopEvent(ev: Event) {
ev.stopPropagation();
}
function handleSubmit() {
if (editable.innerText) {
onSubmit(trimText(editable.innerText));
} else {
onCancel();
}
cleanup();
}
function cleanup() {
editable.onblur = null;
editable.onkeydown = null;
editable.onpaste = null;
window.removeEventListener("wheel", stopEvent, true);
document.body.removeChild(editable);
}
window.addEventListener("wheel", stopEvent, true);
document.body.appendChild(editable);
editable.focus();
selectNode(editable);
}