mirror of
https://github.com/excalidraw/excalidraw.git
synced 2025-04-14 16:40:58 -04:00
feat: wrap long text when pasting (#8026)
Co-authored-by: dwelle <5153846+dwelle@users.noreply.github.com>
This commit is contained in:
parent
eddbe55f50
commit
c540bd68aa
2 changed files with 38 additions and 11 deletions
|
@ -88,6 +88,7 @@ import {
|
||||||
isIOS,
|
isIOS,
|
||||||
supportsResizeObserver,
|
supportsResizeObserver,
|
||||||
DEFAULT_COLLISION_THRESHOLD,
|
DEFAULT_COLLISION_THRESHOLD,
|
||||||
|
DEFAULT_TEXT_ALIGN,
|
||||||
} from "../constants";
|
} from "../constants";
|
||||||
import type { ExportedElements } from "../data";
|
import type { ExportedElements } from "../data";
|
||||||
import { exportCanvas, loadFromBlob } from "../data";
|
import { exportCanvas, loadFromBlob } from "../data";
|
||||||
|
@ -331,6 +332,8 @@ import {
|
||||||
getLineHeightInPx,
|
getLineHeightInPx,
|
||||||
isMeasureTextSupported,
|
isMeasureTextSupported,
|
||||||
isValidTextContainer,
|
isValidTextContainer,
|
||||||
|
measureText,
|
||||||
|
wrapText,
|
||||||
} from "../element/textElement";
|
} from "../element/textElement";
|
||||||
import {
|
import {
|
||||||
showHyperlinkTooltip,
|
showHyperlinkTooltip,
|
||||||
|
@ -430,6 +433,7 @@ import {
|
||||||
} from "./hyperlink/helpers";
|
} from "./hyperlink/helpers";
|
||||||
import { getShortcutFromShortcutName } from "../actions/shortcuts";
|
import { getShortcutFromShortcutName } from "../actions/shortcuts";
|
||||||
import { actionTextAutoResize } from "../actions/actionTextAutoResize";
|
import { actionTextAutoResize } from "../actions/actionTextAutoResize";
|
||||||
|
import { getVisibleSceneBounds } from "../element/bounds";
|
||||||
|
|
||||||
const AppContext = React.createContext<AppClassProperties>(null!);
|
const AppContext = React.createContext<AppClassProperties>(null!);
|
||||||
const AppPropsContext = React.createContext<AppProps>(null!);
|
const AppPropsContext = React.createContext<AppProps>(null!);
|
||||||
|
@ -2565,7 +2569,7 @@ class App extends React.Component<AppProps, AppState> {
|
||||||
addEventListener(document, EVENT.KEYUP, this.onKeyUp, { passive: true }),
|
addEventListener(document, EVENT.KEYUP, this.onKeyUp, { passive: true }),
|
||||||
addEventListener(
|
addEventListener(
|
||||||
document,
|
document,
|
||||||
EVENT.MOUSE_MOVE,
|
EVENT.POINTER_MOVE,
|
||||||
this.updateCurrentCursorPosition,
|
this.updateCurrentCursorPosition,
|
||||||
),
|
),
|
||||||
// rerender text elements on font load to fix #637 && #1553
|
// rerender text elements on font load to fix #637 && #1553
|
||||||
|
@ -3341,32 +3345,53 @@ class App extends React.Component<AppProps, AppState> {
|
||||||
text,
|
text,
|
||||||
fontSize: this.state.currentItemFontSize,
|
fontSize: this.state.currentItemFontSize,
|
||||||
fontFamily: this.state.currentItemFontFamily,
|
fontFamily: this.state.currentItemFontFamily,
|
||||||
textAlign: this.state.currentItemTextAlign,
|
textAlign: DEFAULT_TEXT_ALIGN,
|
||||||
verticalAlign: DEFAULT_VERTICAL_ALIGN,
|
verticalAlign: DEFAULT_VERTICAL_ALIGN,
|
||||||
locked: false,
|
locked: false,
|
||||||
};
|
};
|
||||||
|
const fontString = getFontString({
|
||||||
|
fontSize: textElementProps.fontSize,
|
||||||
|
fontFamily: textElementProps.fontFamily,
|
||||||
|
});
|
||||||
|
const lineHeight = getDefaultLineHeight(textElementProps.fontFamily);
|
||||||
|
const [x1, , x2] = getVisibleSceneBounds(this.state);
|
||||||
|
// long texts should not go beyond 800 pixels in width nor should it go below 200 px
|
||||||
|
const maxTextWidth = Math.max(Math.min((x2 - x1) * 0.5, 800), 200);
|
||||||
const LINE_GAP = 10;
|
const LINE_GAP = 10;
|
||||||
let currentY = y;
|
let currentY = y;
|
||||||
|
|
||||||
const lines = isPlainPaste ? [text] : text.split("\n");
|
const lines = isPlainPaste ? [text] : text.split("\n");
|
||||||
const textElements = lines.reduce(
|
const textElements = lines.reduce(
|
||||||
(acc: ExcalidrawTextElement[], line, idx) => {
|
(acc: ExcalidrawTextElement[], line, idx) => {
|
||||||
const text = line.trim();
|
const originalText = line.trim();
|
||||||
|
if (originalText.length) {
|
||||||
const lineHeight = getDefaultLineHeight(textElementProps.fontFamily);
|
|
||||||
if (text.length) {
|
|
||||||
const topLayerFrame = this.getTopLayerFrameAtSceneCoords({
|
const topLayerFrame = this.getTopLayerFrameAtSceneCoords({
|
||||||
x,
|
x,
|
||||||
y: currentY,
|
y: currentY,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
let metrics = measureText(originalText, fontString, lineHeight);
|
||||||
|
const isTextWrapped = metrics.width > maxTextWidth;
|
||||||
|
|
||||||
|
const text = isTextWrapped
|
||||||
|
? wrapText(originalText, fontString, maxTextWidth)
|
||||||
|
: originalText;
|
||||||
|
|
||||||
|
metrics = isTextWrapped
|
||||||
|
? measureText(text, fontString, lineHeight)
|
||||||
|
: metrics;
|
||||||
|
|
||||||
|
const startX = x - metrics.width / 2;
|
||||||
|
const startY = currentY - metrics.height / 2;
|
||||||
|
|
||||||
const element = newTextElement({
|
const element = newTextElement({
|
||||||
...textElementProps,
|
...textElementProps,
|
||||||
x,
|
x: startX,
|
||||||
y: currentY,
|
y: startY,
|
||||||
text,
|
text,
|
||||||
|
originalText,
|
||||||
lineHeight,
|
lineHeight,
|
||||||
|
autoResize: !isTextWrapped,
|
||||||
frameId: topLayerFrame ? topLayerFrame.id : null,
|
frameId: topLayerFrame ? topLayerFrame.id : null,
|
||||||
});
|
});
|
||||||
acc.push(element);
|
acc.push(element);
|
||||||
|
|
|
@ -215,6 +215,7 @@ const getTextElementPositionOffsets = (
|
||||||
export const newTextElement = (
|
export const newTextElement = (
|
||||||
opts: {
|
opts: {
|
||||||
text: string;
|
text: string;
|
||||||
|
originalText?: string;
|
||||||
fontSize?: number;
|
fontSize?: number;
|
||||||
fontFamily?: FontFamilyValues;
|
fontFamily?: FontFamilyValues;
|
||||||
textAlign?: TextAlign;
|
textAlign?: TextAlign;
|
||||||
|
@ -222,6 +223,7 @@ export const newTextElement = (
|
||||||
containerId?: ExcalidrawTextContainer["id"] | null;
|
containerId?: ExcalidrawTextContainer["id"] | null;
|
||||||
lineHeight?: ExcalidrawTextElement["lineHeight"];
|
lineHeight?: ExcalidrawTextElement["lineHeight"];
|
||||||
strokeWidth?: ExcalidrawTextElement["strokeWidth"];
|
strokeWidth?: ExcalidrawTextElement["strokeWidth"];
|
||||||
|
autoResize?: ExcalidrawTextElement["autoResize"];
|
||||||
} & ElementConstructorOpts,
|
} & ElementConstructorOpts,
|
||||||
): NonDeleted<ExcalidrawTextElement> => {
|
): NonDeleted<ExcalidrawTextElement> => {
|
||||||
const fontFamily = opts.fontFamily || DEFAULT_FONT_FAMILY;
|
const fontFamily = opts.fontFamily || DEFAULT_FONT_FAMILY;
|
||||||
|
@ -252,8 +254,8 @@ export const newTextElement = (
|
||||||
width: metrics.width,
|
width: metrics.width,
|
||||||
height: metrics.height,
|
height: metrics.height,
|
||||||
containerId: opts.containerId || null,
|
containerId: opts.containerId || null,
|
||||||
originalText: text,
|
originalText: opts.originalText ?? text,
|
||||||
autoResize: true,
|
autoResize: opts.autoResize ?? true,
|
||||||
lineHeight,
|
lineHeight,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
Loading…
Add table
Reference in a new issue