Tldraw/apps/examples/src/examples/speech-bubble/SpeechBubble/SpeechBubbleUtil.tsx

292 wiersze
7.7 KiB
TypeScript
Czysty Zwykły widok Historia

import {
DefaultColorStyle,
DefaultFontStyle,
DefaultHorizontalAlignStyle,
DefaultSizeStyle,
DefaultVerticalAlignStyle,
FONT_FAMILIES,
Geometry2d,
LABEL_FONT_SIZES,
Polygon2d,
Stickies: release candidate (#3249) This PR is the target for the stickies PRs that are moving forward. It should collect changes. - [x] New icon - [x] Improved shadows - [x] Shadow LOD - [x] New colors / theme options - [x] Shrink text size to avoid word breaks on the x axis - [x] Hide indicator whilst typing (reverted) - [x] Adjacent note positions - [x] buttons / clone handles - [x] position helpers for creating / translating (pits) - [x] keyboard shortcuts: (Tab, Shift+tab (RTL aware), Cmd-Enter, Shift+Cmd+enter) - [x] multiple shape translating - [x] Text editing - [x] Edit on type (feature flagged) - [x] click goes in correct place - [x] Notes as parents (reverted) - [x] Update colors - [x] Update SVG appearance ### Change Type - [x] `sdk` — Changes the tldraw SDK - [x] `feature` — New feature ### Test Plan Todo: fold in test plans for child PRs ### Unit tests: - [ ] Shrink text size to avoid word breaks on the x axis - [x] Adjacent notes - [x] buttons (clone handles) - [x] position helpers (pits) - [x] keyboard shortcuts: (Tab, Shift+tab (RTL aware), Cmd-Enter, Shift+Cmd+enter) - [ ] Text editing - [ ] Edit on type - [ ] click goes in correct place ### Release Notes - Improves sticky notes (see list) --------- Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: Mime Čuvalo <mimecuvalo@gmail.com> Co-authored-by: alex <alex@dytry.ch> Co-authored-by: Mitja Bezenšek <mitja.bezensek@gmail.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com> Co-authored-by: Lu[ke] Wilson <l2wilson94@gmail.com> Co-authored-by: huppy-bot[bot] <128400622+huppy-bot[bot]@users.noreply.github.com>
2024-04-14 18:40:02 +00:00
ShapePropsType,
ShapeUtil,
T,
TEXT_PROPS,
TLBaseShape,
TLHandle,
TLOnBeforeUpdateHandler,
arrows: add ability to change label placement (#2557) This adds the ability to drag the label on an arrow to a different location within the line segment/arc. https://github.com/tldraw/tldraw/assets/469604/dbd2ee35-bebc-48d6-b8ee-fcf12ce91fa5 - A lot of the complexity lay in ensuring a fixed distance from the ends of the arrowheads. - I added a new type of handle `text-adjust` that makes the text box the very handle itself. - I added a `ARROW_HANDLES` enum - we should use more enums! - The bulk of the changes are in ArrowShapeUtil — check that out in particular obviously :) Along the way, I tried to improve a couple spots as I touched them: - added some more documentation to Vec.ts because some of the functions in there were obscure/new to me. (at least the naming, hah) - added `getPointOnCircle` which was being done in a couple places independently and refactored those places. ### Questions - the `getPointOnCircle` API changed. Is this considered breaking and/or should I leave the signature the same? Wasn't sure if it was a big deal or not. - I made `labelPosition` in the schema always but I guess it could have been optional? Lemme know if there's a preference. - Any feedback on tests? Happy to expand those if necessary. ### Change Type - [ ] `patch` — Bug fix - [x] `minor` — New feature - [ ] `major` — Breaking change - [ ] `dependencies` — Changes to package dependencies[^1] - [ ] `documentation` — Changes to the documentation only[^2] - [ ] `tests` — Changes to any test code only[^2] - [ ] `internal` — Any other changes that don't affect the published package[^2] - [ ] I don't know [^1]: publishes a `patch` release, for devDependencies use `internal` [^2]: will not publish a new version ### Test Plan 1. For arrow in [straightArrow, curvedArrow] test the following: a. Label in the middle b. Label at both ends of the arrow c. Test arrows in different directions d. Rotating the endpoints and seeing that the label stays at the end of the arrow at a fixed width. e. Test different stroke widths. f. Test with different arrowheads. 2. Also, test arcs that are more circle like than arc-like. - [x] Unit Tests - [ ] End to end tests ### Release Notes - Adds ability to change label position on arrows. --------- Co-authored-by: Steve Ruiz <steveruizok@gmail.com> Co-authored-by: alex <alex@dytry.ch>
2024-01-24 10:19:20 +00:00
TLOnHandleDragHandler,
TLOnResizeHandler,
TextLabel,
Vec,
ZERO_INDEX_KEY,
resizeBox,
structuredClone,
vecModelValidator,
} from 'tldraw'
Stickies: release candidate (#3249) This PR is the target for the stickies PRs that are moving forward. It should collect changes. - [x] New icon - [x] Improved shadows - [x] Shadow LOD - [x] New colors / theme options - [x] Shrink text size to avoid word breaks on the x axis - [x] Hide indicator whilst typing (reverted) - [x] Adjacent note positions - [x] buttons / clone handles - [x] position helpers for creating / translating (pits) - [x] keyboard shortcuts: (Tab, Shift+tab (RTL aware), Cmd-Enter, Shift+Cmd+enter) - [x] multiple shape translating - [x] Text editing - [x] Edit on type (feature flagged) - [x] click goes in correct place - [x] Notes as parents (reverted) - [x] Update colors - [x] Update SVG appearance ### Change Type - [x] `sdk` — Changes the tldraw SDK - [x] `feature` — New feature ### Test Plan Todo: fold in test plans for child PRs ### Unit tests: - [ ] Shrink text size to avoid word breaks on the x axis - [x] Adjacent notes - [x] buttons (clone handles) - [x] position helpers (pits) - [x] keyboard shortcuts: (Tab, Shift+tab (RTL aware), Cmd-Enter, Shift+Cmd+enter) - [ ] Text editing - [ ] Edit on type - [ ] click goes in correct place ### Release Notes - Improves sticky notes (see list) --------- Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: Mime Čuvalo <mimecuvalo@gmail.com> Co-authored-by: alex <alex@dytry.ch> Co-authored-by: Mitja Bezenšek <mitja.bezensek@gmail.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com> Co-authored-by: Lu[ke] Wilson <l2wilson94@gmail.com> Co-authored-by: huppy-bot[bot] <128400622+huppy-bot[bot]@users.noreply.github.com>
2024-04-14 18:40:02 +00:00
import { useDefaultColorTheme } from 'tldraw/src/lib/shapes/shared/ShapeFill'
import { getSpeechBubbleVertices, getTailIntersectionPoint } from './helpers'
// Copied from tldraw/tldraw
export const STROKE_SIZES = {
s: 2,
m: 3.5,
l: 5,
xl: 10,
}
// There's a guide at the bottom of this file!
// [1]
export const speechBubbleShapeProps = {
w: T.number,
h: T.number,
size: DefaultSizeStyle,
color: DefaultColorStyle,
font: DefaultFontStyle,
align: DefaultHorizontalAlignStyle,
verticalAlign: DefaultVerticalAlignStyle,
growY: T.positiveNumber,
text: T.string,
tail: vecModelValidator,
}
export type SpeechBubbleShapeProps = ShapePropsType<typeof speechBubbleShapeProps>
export type SpeechBubbleShape = TLBaseShape<'speech-bubble', SpeechBubbleShapeProps>
export class SpeechBubbleUtil extends ShapeUtil<SpeechBubbleShape> {
static override type = 'speech-bubble' as const
// [2]
static override props = speechBubbleShapeProps
override isAspectRatioLocked = (_shape: SpeechBubbleShape) => false
override canResize = (_shape: SpeechBubbleShape) => true
override canBind = (_shape: SpeechBubbleShape) => true
override canEdit = () => true
// [3]
getDefaultProps(): SpeechBubbleShapeProps {
return {
w: 200,
h: 130,
color: 'black',
size: 'm',
font: 'draw',
align: 'middle',
verticalAlign: 'start',
growY: 0,
text: '',
tail: { x: 0.5, y: 1.5 },
}
}
getHeight(shape: SpeechBubbleShape) {
return shape.props.h + shape.props.growY
}
getGeometry(shape: SpeechBubbleShape): Geometry2d {
const speechBubbleGeometry = getSpeechBubbleVertices(shape)
const body = new Polygon2d({
points: speechBubbleGeometry,
isFilled: true,
})
return body
}
// [4]
override getHandles(shape: SpeechBubbleShape): TLHandle[] {
const { tail, w } = shape.props
return [
{
id: 'tail',
type: 'vertex',
index: ZERO_INDEX_KEY,
// props.tail coordinates are normalized
// but here we need them in shape space
x: tail.x * w,
y: tail.y * this.getHeight(shape),
},
]
}
override onHandleDrag: TLOnHandleDragHandler<SpeechBubbleShape> = (shape, { handle }) => {
return {
...shape,
props: {
tail: {
x: handle.x / shape.props.w,
y: handle.y / this.getHeight(shape),
},
},
}
}
override onBeforeCreate = (next: SpeechBubbleShape) => {
return this.getGrowY(next, next.props.growY)
}
// [5]
override onBeforeUpdate: TLOnBeforeUpdateHandler<SpeechBubbleShape> | undefined = (
prev: SpeechBubbleShape,
shape: SpeechBubbleShape
) => {
const { w, tail } = shape.props
const fullHeight = this.getHeight(shape)
const { segmentsIntersection, insideShape } = getTailIntersectionPoint(shape)
const slantedLength = Math.hypot(w, fullHeight)
const MIN_DISTANCE = slantedLength / 5
const MAX_DISTANCE = slantedLength / 1.5
const tailInShapeSpace = new Vec(tail.x * w, tail.y * fullHeight)
const distanceToIntersection = tailInShapeSpace.dist(segmentsIntersection)
const center = new Vec(w / 2, fullHeight / 2)
const tailDirection = Vec.Sub(tailInShapeSpace, center).uni()
let newPoint = tailInShapeSpace
if (insideShape) {
newPoint = Vec.Add(segmentsIntersection, tailDirection.mul(MIN_DISTANCE))
} else {
if (distanceToIntersection <= MIN_DISTANCE) {
newPoint = Vec.Add(segmentsIntersection, tailDirection.mul(MIN_DISTANCE))
} else if (distanceToIntersection >= MAX_DISTANCE) {
newPoint = Vec.Add(segmentsIntersection, tailDirection.mul(MAX_DISTANCE))
}
}
use native structuredClone on node, cloudflare workers, and in tests (#3166) Currently, we only use native `structuredClone` in the browser, falling back to `JSON.parse(JSON.stringify(...))` elsewhere, despite Node supporting `structuredClone` [since v17](https://developer.mozilla.org/en-US/docs/Web/API/structuredClone) and Cloudflare Workers supporting it [since 2022](https://blog.cloudflare.com/standards-compliant-workers-api/). This PR adjusts our shim to use the native `structuredClone` on all platforms, if available. Additionally, `jsdom` doesn't implement `structuredClone`, a bug [open since 2022](https://github.com/jsdom/jsdom/issues/3363). This PR patches `jsdom` environment in all packages/apps that use it for tests. Also includes a driveby removal of `deepCopy`, a function that is strictly inferior to `structuredClone`. ### Change Type <!-- ❗ Please select a 'Scope' label ❗️ --> - [x] `sdk` — Changes the tldraw SDK - [x] `dotcom` — Changes the tldraw.com web app - [ ] `docs` — Changes to the documentation, examples, or templates. - [ ] `vs code` — Changes to the vscode plugin - [ ] `internal` — Does not affect user-facing stuff <!-- ❗ Please select a 'Type' label ❗️ --> - [ ] `bugfix` — Bug fix - [ ] `feature` — New feature - [x] `improvement` — Improving existing features - [x] `chore` — Updating dependencies, other boring stuff - [ ] `galaxy brain` — Architectural changes - [ ] `tests` — Changes to any test code - [ ] `tools` — Changes to infrastructure, CI, internal scripts, debugging tools, etc. - [ ] `dunno` — I don't know ### Test Plan 1. A smoke test would be enough - [ ] Unit Tests - [x] End to end tests
2024-03-18 17:16:09 +00:00
const next = structuredClone(shape)
next.props.tail.x = newPoint.x / w
next.props.tail.y = newPoint.y / fullHeight
return this.getGrowY(next, prev.props.growY)
}
component(shape: SpeechBubbleShape) {
const {
id,
type,
props: { color, font, size, align, text },
} = shape
const vertices = getSpeechBubbleVertices(shape)
const pathData = 'M' + vertices[0] + 'L' + vertices.slice(1) + 'Z'
Stickies: release candidate (#3249) This PR is the target for the stickies PRs that are moving forward. It should collect changes. - [x] New icon - [x] Improved shadows - [x] Shadow LOD - [x] New colors / theme options - [x] Shrink text size to avoid word breaks on the x axis - [x] Hide indicator whilst typing (reverted) - [x] Adjacent note positions - [x] buttons / clone handles - [x] position helpers for creating / translating (pits) - [x] keyboard shortcuts: (Tab, Shift+tab (RTL aware), Cmd-Enter, Shift+Cmd+enter) - [x] multiple shape translating - [x] Text editing - [x] Edit on type (feature flagged) - [x] click goes in correct place - [x] Notes as parents (reverted) - [x] Update colors - [x] Update SVG appearance ### Change Type - [x] `sdk` — Changes the tldraw SDK - [x] `feature` — New feature ### Test Plan Todo: fold in test plans for child PRs ### Unit tests: - [ ] Shrink text size to avoid word breaks on the x axis - [x] Adjacent notes - [x] buttons (clone handles) - [x] position helpers (pits) - [x] keyboard shortcuts: (Tab, Shift+tab (RTL aware), Cmd-Enter, Shift+Cmd+enter) - [ ] Text editing - [ ] Edit on type - [ ] click goes in correct place ### Release Notes - Improves sticky notes (see list) --------- Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: Mime Čuvalo <mimecuvalo@gmail.com> Co-authored-by: alex <alex@dytry.ch> Co-authored-by: Mitja Bezenšek <mitja.bezensek@gmail.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com> Co-authored-by: Lu[ke] Wilson <l2wilson94@gmail.com> Co-authored-by: huppy-bot[bot] <128400622+huppy-bot[bot]@users.noreply.github.com>
2024-04-14 18:40:02 +00:00
const isSelected = shape.id === this.editor.getOnlySelectedShapeId()
// eslint-disable-next-line react-hooks/rules-of-hooks
const theme = useDefaultColorTheme()
return (
<>
<svg className="tl-svg-container">
<path
d={pathData}
strokeWidth={STROKE_SIZES[size]}
stroke={theme[color].solid}
fill={'none'}
/>
</svg>
<TextLabel
id={id}
type={type}
font={font}
fontSize={LABEL_FONT_SIZES[size]}
lineHeight={TEXT_PROPS.lineHeight}
align={align}
verticalAlign="start"
text={text}
Stickies: release candidate (#3249) This PR is the target for the stickies PRs that are moving forward. It should collect changes. - [x] New icon - [x] Improved shadows - [x] Shadow LOD - [x] New colors / theme options - [x] Shrink text size to avoid word breaks on the x axis - [x] Hide indicator whilst typing (reverted) - [x] Adjacent note positions - [x] buttons / clone handles - [x] position helpers for creating / translating (pits) - [x] keyboard shortcuts: (Tab, Shift+tab (RTL aware), Cmd-Enter, Shift+Cmd+enter) - [x] multiple shape translating - [x] Text editing - [x] Edit on type (feature flagged) - [x] click goes in correct place - [x] Notes as parents (reverted) - [x] Update colors - [x] Update SVG appearance ### Change Type - [x] `sdk` — Changes the tldraw SDK - [x] `feature` — New feature ### Test Plan Todo: fold in test plans for child PRs ### Unit tests: - [ ] Shrink text size to avoid word breaks on the x axis - [x] Adjacent notes - [x] buttons (clone handles) - [x] position helpers (pits) - [x] keyboard shortcuts: (Tab, Shift+tab (RTL aware), Cmd-Enter, Shift+Cmd+enter) - [ ] Text editing - [ ] Edit on type - [ ] click goes in correct place ### Release Notes - Improves sticky notes (see list) --------- Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: Mime Čuvalo <mimecuvalo@gmail.com> Co-authored-by: alex <alex@dytry.ch> Co-authored-by: Mitja Bezenšek <mitja.bezensek@gmail.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com> Co-authored-by: Lu[ke] Wilson <l2wilson94@gmail.com> Co-authored-by: huppy-bot[bot] <128400622+huppy-bot[bot]@users.noreply.github.com>
2024-04-14 18:40:02 +00:00
labelColor={theme[color].solid}
isSelected={isSelected}
wrap
/>
</>
)
}
indicator(shape: SpeechBubbleShape) {
const vertices = getSpeechBubbleVertices(shape)
const pathData = 'M' + vertices[0] + 'L' + vertices.slice(1) + 'Z'
return <path d={pathData} />
}
override onResize: TLOnResizeHandler<SpeechBubbleShape> = (shape, info) => {
const resized = resizeBox(shape, info)
const next = structuredClone(info.initialShape)
next.x = resized.x
next.y = resized.y
next.props.w = resized.props.w
next.props.h = resized.props.h
return next
}
getGrowY(shape: SpeechBubbleShape, prevGrowY = 0) {
const PADDING = 17
const nextTextSize = this.editor.textMeasure.measureText(shape.props.text, {
...TEXT_PROPS,
fontFamily: FONT_FAMILIES[shape.props.font],
fontSize: LABEL_FONT_SIZES[shape.props.size],
maxWidth: shape.props.w - PADDING * 2,
})
const nextHeight = nextTextSize.h + PADDING * 2
let growY = 0
if (nextHeight > shape.props.h) {
growY = nextHeight - shape.props.h
} else {
if (prevGrowY) {
growY = 0
}
}
return {
...shape,
props: {
...shape.props,
growY,
},
}
}
}
/*
Introduction: This file contains our custom shape util. The shape util is a class that defines how
our shape behaves. Most of the logic for how the speech bubble shape works is in the onBeforeUpdate
handler [5]. Since this shape has a handle, we need to do some special stuff to make sure it updates
the way we want it to.
[1]
Here is where we define the shape's type. For the tail we can use the `VecModel` type from `tldraw`.
[2]
This is where we define the shape's props and a type validator for each key. tldraw exports a
bunch of handy validators for us to use. Props you define here will determine which style options
show up in the style menu, e.g. we define 'size' and 'color' props, but we could add 'dash', 'fill'
or any other of the default props.
[3]
Here is where we set the default props for our shape, this will determine how the shape looks
when we click-create it. You'll notice we don't store the tail's absolute position though, instead
we record its relative position. This is because we can also drag-create shapes. If we store the
tail's position absolutely it won't scale properly when drag-creating. Throughout the rest of the
util we'll need to convert the tail's relative position to an absolute position and vice versa.
[4]
`getHandles` tells tldraw how to turn our shape into a list of handles that'll show up when it's
selected. We only have one handle, the tail, which simplifies things for us a bit. In
`onHandleDrag`, we tell tldraw how our shape should be updated when the handle is dragged.
[5]
This is the last method that fires after a shape has been changed, we can use it to make sure
the tail stays the right length and position. Check out helpers.tsx to get into some of the more
specific geometry stuff.
*/