[feature] add vertical align to note shape (#1539)

This PR adds vertical align to the note shape.

### Change Type

- [x] `minor` — New Feature

### Test Plan

1. Try the vertical align prop on note shapes

### Release Notes

- Adds vertical align prop to note shapes
This commit is contained in:
Steve Ruiz 2023-06-06 14:27:32 +01:00 committed by GitHub
parent 8d1817a3e3
commit 1753190f5c
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
10 changed files with 130 additions and 67 deletions

View file

@ -1701,6 +1701,7 @@ export class NoteShapeUtil extends ShapeUtil<TLNoteShape> {
size: "l" | "m" | "s" | "xl";
font: "draw" | "mono" | "sans" | "serif";
align: "end" | "middle" | "start";
verticalAlign: "end" | "middle" | "start";
opacity: "0.1" | "0.25" | "0.5" | "0.75" | "1";
url: string;
text: string;
@ -1723,6 +1724,7 @@ export class NoteShapeUtil extends ShapeUtil<TLNoteShape> {
size: "l" | "m" | "s" | "xl";
font: "draw" | "mono" | "sans" | "serif";
align: "end" | "middle" | "start";
verticalAlign: "end" | "middle" | "start";
opacity: "0.1" | "0.25" | "0.5" | "0.75" | "1";
url: string;
text: string;

View file

@ -1075,6 +1075,12 @@ export class ArrowShapeUtil extends ShapeUtil<TLArrowShape> {
child.setAttribute('y', y + labelSize!.y + 'px')
})
const textBgEl = textElm.cloneNode(true) as SVGTextElement
textBgEl.setAttribute('stroke-width', '2')
textBgEl.setAttribute('fill', colors.background)
textBgEl.setAttribute('stroke', colors.background)
g.appendChild(textBgEl)
g.appendChild(textElm)
}

View file

@ -15,11 +15,10 @@ import {
import { TLDashType, TLGeoShape } from '@tldraw/tlschema'
import { SVGContainer } from '../../../components/SVGContainer'
import { FONT_FAMILIES, LABEL_FONT_SIZES, TEXT_PROPS } from '../../../constants'
import { getLegacyOffsetX } from '../../../utils/legacy'
import { Editor } from '../../Editor'
import { BaseBoxShapeUtil } from '../BaseBoxShapeUtil'
import { TLOnEditEndHandler, TLOnResizeHandler } from '../ShapeUtil'
import { createTextSvgElementFromSpans } from '../shared/createTextSvgElementFromSpans'
import { getTextLabelSvgElement } from '../shared/getTextLabelSvgElement'
import { HyperlinkButton } from '../shared/HyperlinkButton'
import { TextLabel } from '../shared/TextLabel'
import { TLExportColors } from '../shared/TLExportColors'
@ -633,42 +632,24 @@ export class GeoShapeUtil extends BaseBoxShapeUtil<TLGeoShape> {
if (props.text) {
const bounds = this.bounds(shape)
const padding = 16
const opts = {
fontSize: LABEL_FONT_SIZES[shape.props.size],
fontFamily: font,
textAlign: shape.props.align,
padding,
verticalTextAlign: shape.props.verticalAlign,
lineHeight: TEXT_PROPS.lineHeight,
fontStyle: 'normal',
fontWeight: 'normal',
width: Math.ceil(bounds.width),
height: Math.ceil(bounds.height),
overflow: 'wrap' as const,
offsetX: 0,
}
const spans = this.editor.textMeasure.measureTextSpans(props.text, opts)
const offsetX = getLegacyOffsetX(shape.props.align, padding, spans, bounds.width)
if (offsetX) {
opts.offsetX = offsetX
}
const groupEl = document.createElementNS('http://www.w3.org/2000/svg', 'g')
const textBgEl = createTextSvgElementFromSpans(this.editor, spans, {
...opts,
strokeWidth: 2,
stroke: colors.background,
fill: colors.background,
const rootTextElm = getTextLabelSvgElement({
editor: this.editor,
shape,
font,
bounds,
})
const textElm = textBgEl.cloneNode(true) as SVGTextElement
const textElm = rootTextElm.cloneNode(true) as SVGTextElement
textElm.setAttribute('fill', colors.fill[shape.props.labelColor])
textElm.setAttribute('stroke', 'none')
const textBgEl = rootTextElm.cloneNode(true) as SVGTextElement
textBgEl.setAttribute('stroke-width', '2')
textBgEl.setAttribute('fill', colors.background)
textBgEl.setAttribute('stroke', colors.background)
const groupEl = document.createElementNS('http://www.w3.org/2000/svg', 'g')
groupEl.append(textBgEl)
groupEl.append(textElm)

View file

@ -1,10 +1,9 @@
import { Box2d, toDomPrecision, Vec2d } from '@tldraw/primitives'
import { TLNoteShape } from '@tldraw/tlschema'
import { FONT_FAMILIES, LABEL_FONT_SIZES, TEXT_PROPS } from '../../../constants'
import { getLegacyOffsetX } from '../../../utils/legacy'
import { Editor } from '../../Editor'
import { ShapeUtil, TLOnEditEndHandler } from '../ShapeUtil'
import { createTextSvgElementFromSpans } from '../shared/createTextSvgElementFromSpans'
import { getTextLabelSvgElement } from '../shared/getTextLabelSvgElement'
import { HyperlinkButton } from '../shared/HyperlinkButton'
import { TextLabel } from '../shared/TextLabel'
import { TLExportColors } from '../shared/TLExportColors'
@ -28,6 +27,7 @@ export class NoteShapeUtil extends ShapeUtil<TLNoteShape> {
text: '',
font: 'draw',
align: 'middle',
verticalAlign: 'middle',
growY: 0,
url: '',
}
@ -54,7 +54,7 @@ export class NoteShapeUtil extends ShapeUtil<TLNoteShape> {
const {
id,
type,
props: { color, font, size, align, text },
props: { color, font, size, align, text, verticalAlign },
} = shape
const adjustedColor = color === 'black' ? 'yellow' : color
@ -82,7 +82,7 @@ export class NoteShapeUtil extends ShapeUtil<TLNoteShape> {
font={font}
size={size}
align={align}
verticalAlign="middle"
verticalAlign={verticalAlign}
text={text}
labelColor="inherit"
wrap
@ -130,36 +130,15 @@ export class NoteShapeUtil extends ShapeUtil<TLNoteShape> {
rect2.setAttribute('opacity', '.28')
g.appendChild(rect2)
const PADDING = 17
const textElm = getTextLabelSvgElement({
editor: this.editor,
shape,
font,
bounds,
})
const opts = {
fontSize: LABEL_FONT_SIZES[shape.props.size],
fontFamily: font,
textAlign: shape.props.align,
verticalTextAlign: 'middle' as const,
width: bounds.width - PADDING * 2,
height: bounds.height - PADDING * 2,
padding: 0,
lineHeight: TEXT_PROPS.lineHeight,
fontStyle: 'normal',
fontWeight: 'normal',
overflow: 'wrap' as const,
offsetX: 0,
}
const spans = this.editor.textMeasure.measureTextSpans(shape.props.text, opts)
opts.width = bounds.width
const offsetX = getLegacyOffsetX(shape.props.align, PADDING, spans, bounds.width)
if (offsetX) {
opts.offsetX = offsetX
}
opts.padding = PADDING
const textElm = createTextSvgElementFromSpans(this.editor, spans, opts)
textElm.setAttribute('fill', colors.text)
textElm.setAttribute('transform', `translate(0 ${PADDING})`)
textElm.setAttribute('stroke', 'none')
g.appendChild(textElm)
return g

View file

@ -0,0 +1,44 @@
import { Box2d } from '@tldraw/primitives'
import { TLGeoShape, TLNoteShape } from '@tldraw/tlschema'
import { LABEL_FONT_SIZES, TEXT_PROPS } from '../../../constants'
import { getLegacyOffsetX } from '../../../utils/legacy'
import { Editor } from '../../Editor'
import { createTextSvgElementFromSpans } from './createTextSvgElementFromSpans'
export function getTextLabelSvgElement({
bounds,
editor,
font,
shape,
}: {
bounds: Box2d
editor: Editor
font: string
shape: TLGeoShape | TLNoteShape
}) {
const padding = 16
const opts = {
fontSize: LABEL_FONT_SIZES[shape.props.size],
fontFamily: font,
textAlign: shape.props.align,
verticalTextAlign: shape.props.verticalAlign,
width: Math.ceil(bounds.width),
height: Math.ceil(bounds.height),
padding: 16,
lineHeight: TEXT_PROPS.lineHeight,
fontStyle: 'normal',
fontWeight: 'normal',
overflow: 'wrap' as const,
offsetX: 0,
}
const spans = editor.textMeasure.measureTextSpans(shape.props.text, opts)
const offsetX = getLegacyOffsetX(shape.props.align, padding, spans, bounds.width)
if (offsetX) {
opts.offsetX = offsetX
}
const textElm = createTextSvgElementFromSpans(editor, spans, opts)
return textElm
}

View file

@ -9,5 +9,15 @@ export class GeoShapeTool extends StateNode {
static initial = 'idle'
static children = () => [Idle, Pointing]
styles = ['color', 'opacity', 'dash', 'fill', 'size', 'geo', 'font', 'align'] as TLStyleType[]
styles = [
'color',
'opacity',
'dash',
'fill',
'size',
'geo',
'font',
'align',
'verticalAlign',
] as TLStyleType[]
}

View file

@ -8,5 +8,5 @@ export class NoteShapeTool extends StateNode {
static initial = 'idle'
static children = () => [Idle, Pointing]
styles = ['color', 'opacity', 'size', 'align', 'font'] as TLStyleType[]
styles = ['color', 'opacity', 'size', 'align', 'verticalAlign', 'font'] as TLStyleType[]
}

View file

@ -96,11 +96,14 @@ exports[`Matches a snapshot: Basic SVG 1`] = `
<text
alignment-baseline="mathematical"
dominant-baseline="mathematical"
fill=""
font-family=""
font-size="22px"
font-style="normal"
font-weight="normal"
line-height="29.700000000000003px"
stroke=""
stroke-width="2"
>
<tspan
alignment-baseline="mathematical"

View file

@ -1029,6 +1029,21 @@ describe('making instance state independent', () => {
})
})
describe('Adds NoteShape vertical alignment', () => {
const { up, down } = noteShapeMigrations.migrators[4]
test('up works as expected', () => {
expect(up({ props: { color: 'red' } })).toEqual({
props: { verticalAlign: 'middle', color: 'red' },
})
})
test('down works as expected', () => {
expect(down({ props: { verticalAlign: 'top', color: 'red' } })).toEqual({
props: { color: 'red' },
})
})
})
/* --- PUT YOUR MIGRATIONS TESTS ABOVE HERE --- */
for (const migrator of allMigrators) {

View file

@ -5,6 +5,7 @@ import { TLColorType, colorValidator } from '../styles/TLColorStyle'
import { TLFontType, fontValidator } from '../styles/TLFontStyle'
import { TLOpacityType, opacityValidator } from '../styles/TLOpacityStyle'
import { TLSizeType, sizeValidator } from '../styles/TLSizeStyle'
import { TLVerticalAlignType, verticalAlignValidator } from '../styles/TLVerticalAlignStyle'
import { TLBaseShape, createShapeValidator } from './TLBaseShape'
/** @public */
@ -13,6 +14,7 @@ export type TLNoteShapeProps = {
size: TLSizeType
font: TLFontType
align: TLAlignType
verticalAlign: TLVerticalAlignType
opacity: TLOpacityType
growY: number
url: string
@ -30,6 +32,7 @@ export const noteShapeValidator: T.Validator<TLNoteShape> = createShapeValidator
size: sizeValidator,
font: fontValidator,
align: alignValidator,
verticalAlign: verticalAlignValidator,
opacity: opacityValidator,
growY: T.positiveNumber,
url: T.string,
@ -41,11 +44,12 @@ const Versions = {
AddUrlProp: 1,
RemoveJustify: 2,
MigrateLegacyAlign: 3,
AddVerticalAlign: 4,
} as const
/** @internal */
export const noteShapeMigrations = defineMigrations({
currentVersion: Versions.MigrateLegacyAlign,
currentVersion: Versions.AddVerticalAlign,
migrators: {
[Versions.AddUrlProp]: {
up: (shape) => {
@ -122,5 +126,24 @@ export const noteShapeMigrations = defineMigrations({
}
},
},
[Versions.AddVerticalAlign]: {
up: (shape) => {
return {
...shape,
props: {
...shape.props,
verticalAlign: 'middle',
},
}
},
down: (shape) => {
const { verticalAlign: _, ...props } = shape.props
return {
...shape,
props,
}
},
},
},
})