feat: wrap long text when pasting (#8026)

Co-authored-by: dwelle <5153846+dwelle@users.noreply.github.com>
This commit is contained in:
Ryan Di 2024-05-21 22:56:09 +08:00 committed by GitHub
parent eddbe55f50
commit c540bd68aa
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
2 changed files with 38 additions and 11 deletions

View file

@ -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);

View file

@ -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,
}; };