tldraw/types.ts

655 lines
14 KiB
TypeScript
Raw Normal View History

2021-05-17 10:01:11 +00:00
/* -------------------------------------------------- */
/* Client State */
/* -------------------------------------------------- */
2021-05-09 21:22:25 +00:00
export interface Data {
2021-05-13 06:44:52 +00:00
isReadOnly: boolean
2021-05-14 22:56:41 +00:00
settings: {
fontSize: number
2021-05-17 21:27:18 +00:00
isDarkMode: boolean
isCodeOpen: boolean
isTestMode: boolean
2021-07-04 18:45:07 +00:00
isDebugOpen: boolean
isDebugMode: boolean
2021-05-26 10:34:10 +00:00
isStyleOpen: boolean
2021-05-28 20:30:27 +00:00
nudgeDistanceSmall: number
nudgeDistanceLarge: number
isToolLocked: boolean
isPenLocked: boolean
2021-05-14 22:56:41 +00:00
}
2021-06-28 20:45:06 +00:00
room?: {
id: string
status: string
2021-06-30 20:31:29 +00:00
peers: Record<string, CoopPresence>
2021-06-28 20:45:06 +00:00
}
2021-06-21 13:13:16 +00:00
currentStyle: ShapeStyles
2021-06-02 21:17:38 +00:00
activeTool: ShapeType | 'select'
2021-05-10 12:16:57 +00:00
brush?: Bounds
2021-05-18 08:32:20 +00:00
boundsRotation: number
2021-05-10 12:16:57 +00:00
pointedId?: string
2021-05-14 22:56:41 +00:00
hoveredId?: string
editingId?: string
2021-05-16 08:33:08 +00:00
currentPageId: string
2021-06-04 16:08:43 +00:00
currentParentId: string
2021-05-16 08:33:08 +00:00
currentCodeFileId: string
2021-05-17 10:01:11 +00:00
codeControls: Record<string, CodeControl>
document: TLDocument
pageStates: Record<string, PageState>
2021-05-09 21:22:25 +00:00
}
2021-05-17 10:01:11 +00:00
/* -------------------------------------------------- */
/* Document */
/* -------------------------------------------------- */
2021-05-14 22:56:41 +00:00
2021-06-30 20:31:29 +00:00
export type CoopPresence = {
2021-06-28 22:22:23 +00:00
id: string
2021-06-30 20:31:29 +00:00
bufferedXs: number[]
bufferedYs: number[]
times: number[]
duration: number
2021-06-30 20:56:42 +00:00
pageId: string
2021-06-28 22:22:23 +00:00
}
export interface TLDocument {
id: string
name: string
pages: Record<string, Page>
code: Record<string, CodeFile>
}
2021-05-09 21:22:25 +00:00
export interface Page {
id: string
2021-05-28 20:30:27 +00:00
type: 'page'
2021-05-09 21:22:25 +00:00
childIndex: number
name: string
shapes: Record<string, Shape>
}
export interface PageState {
id: string
selectedIds: string[]
camera: {
point: number[]
zoom: number
}
}
/* ----------------- Start Copy Here ---------------- */
2021-05-09 21:22:25 +00:00
export enum ShapeType {
2021-05-28 20:30:27 +00:00
Dot = 'dot',
Ellipse = 'ellipse',
Line = 'line',
Ray = 'ray',
Polyline = 'polyline',
Rectangle = 'rectangle',
Draw = 'draw',
2021-05-31 19:13:43 +00:00
Arrow = 'arrow',
Text = 'text',
2021-06-04 16:08:43 +00:00
Group = 'group',
2021-05-09 21:22:25 +00:00
}
export enum ColorStyle {
White = 'White',
LightGray = 'LightGray',
Gray = 'Gray',
Black = 'Black',
Green = 'Green',
Cyan = 'Cyan',
Blue = 'Blue',
Indigo = 'Indigo',
Violet = 'Violet',
Red = 'Red',
Orange = 'Orange',
Yellow = 'Yellow',
}
export enum SizeStyle {
Small = 'Small',
Medium = 'Medium',
Large = 'Large',
}
export enum DashStyle {
2021-07-01 22:11:09 +00:00
Draw = 'Draw',
Solid = 'Solid',
Dashed = 'Dashed',
Dotted = 'Dotted',
}
export enum FontSize {
Small = 'Small',
Medium = 'Medium',
Large = 'Large',
ExtraLarge = 'ExtraLarge',
}
2021-07-10 20:39:29 +00:00
export type Theme = 'dark' | 'light'
export type ShapeStyles = {
color: ColorStyle
size: SizeStyle
dash: DashStyle
isFilled: boolean
}
2021-05-09 21:22:25 +00:00
export interface BaseShape {
id: string
type: ShapeType
parentId: string
childIndex: number
name: string
point: number[]
2021-06-04 16:08:43 +00:00
style: ShapeStyles
2021-05-13 18:22:16 +00:00
rotation: number
2021-06-04 16:08:43 +00:00
children?: string[]
2021-05-31 19:13:43 +00:00
bindings?: Record<string, ShapeBinding>
handles?: Record<string, ShapeHandle>
isLocked?: boolean
isHidden?: boolean
isEditing?: boolean
isGenerated?: boolean
isAspectRatioLocked?: boolean
2021-05-09 21:22:25 +00:00
}
export interface DotShape extends BaseShape {
type: ShapeType.Dot
}
export interface EllipseShape extends BaseShape {
type: ShapeType.Ellipse
radiusX: number
radiusY: number
}
export interface LineShape extends BaseShape {
type: ShapeType.Line
2021-05-14 21:05:21 +00:00
direction: number[]
2021-05-09 21:22:25 +00:00
}
export interface RayShape extends BaseShape {
type: ShapeType.Ray
2021-05-14 21:05:21 +00:00
direction: number[]
2021-05-09 21:22:25 +00:00
}
export interface PolylineShape extends BaseShape {
type: ShapeType.Polyline
points: number[][]
2021-05-09 21:22:25 +00:00
}
export interface RectangleShape extends BaseShape {
type: ShapeType.Rectangle
size: number[]
2021-05-26 10:34:10 +00:00
radius: number
2021-05-09 13:04:42 +00:00
}
2021-05-09 21:22:25 +00:00
2021-05-27 17:59:40 +00:00
export interface DrawShape extends BaseShape {
type: ShapeType.Draw
points: number[][]
}
2021-05-31 19:13:43 +00:00
export interface ArrowShape extends BaseShape {
type: ShapeType.Arrow
handles: Record<string, ShapeHandle>
bend: number
decorations?: {
start: Decoration
end: Decoration
middle: Decoration
}
}
export interface TextShape extends BaseShape {
type: ShapeType.Text
text: string
2021-06-17 10:43:55 +00:00
scale: number
}
2021-06-04 16:08:43 +00:00
export interface GroupShape extends BaseShape {
type: ShapeType.Group
children: string[]
size: number[]
}
2021-06-25 12:09:53 +00:00
export type ShapeProps<T extends Shape> = {
[P in keyof T]?: P extends 'style' ? Partial<T[P]> : T[P]
2021-06-23 22:42:15 +00:00
}
2021-06-29 12:00:59 +00:00
export interface MutableShapes {
[ShapeType.Dot]: DotShape
[ShapeType.Ellipse]: EllipseShape
[ShapeType.Line]: LineShape
[ShapeType.Ray]: RayShape
[ShapeType.Polyline]: PolylineShape
[ShapeType.Draw]: DrawShape
[ShapeType.Rectangle]: RectangleShape
[ShapeType.Arrow]: ArrowShape
[ShapeType.Text]: TextShape
[ShapeType.Group]: GroupShape
2021-05-17 10:01:11 +00:00
}
2021-06-29 12:00:59 +00:00
export type MutableShape = MutableShapes[keyof MutableShapes]
export type Shapes = { [K in keyof MutableShapes]: Readonly<MutableShapes[K]> }
2021-06-04 16:08:43 +00:00
export type Shape = Readonly<MutableShape>
export type ShapeByType<T extends ShapeType> = Shapes[T]
2021-06-29 12:00:59 +00:00
export type IsParent<T> = 'children' extends RequiredKeys<T> ? T : never
export type ParentShape = {
[K in keyof MutableShapes]: IsParent<MutableShapes[K]>
}[keyof MutableShapes]
export type ParentTypes = ParentShape['type'] & 'page'
2021-05-31 19:13:43 +00:00
export enum Decoration {
2021-06-01 21:49:32 +00:00
Arrow = 'Arrow',
}
2021-05-31 19:13:43 +00:00
export interface ShapeBinding {
id: string
index: number
point: number[]
}
export interface ShapeHandle {
id: string
index: number
point: number[]
}
export interface CodeFile {
id: string
name: string
code: string
}
export interface CodeError {
message: string
line: number
column: number
}
export interface CodeResult {
shapes: Shape[]
controls: CodeControl[]
error: CodeError
}
2021-05-17 10:01:11 +00:00
/* -------------------------------------------------- */
/* Editor UI */
/* -------------------------------------------------- */
export interface PointerInfo {
target: string
pointerId: number
origin: number[]
point: number[]
2021-06-06 07:33:30 +00:00
pressure: number
2021-05-17 10:01:11 +00:00
shiftKey: boolean
ctrlKey: boolean
metaKey: boolean
altKey: boolean
}
2021-06-30 20:56:42 +00:00
export interface KeyboardInfo {
key: string
keys: string[]
shiftKey: boolean
ctrlKey: boolean
metaKey: boolean
altKey: boolean
}
export enum Edge {
2021-05-28 20:30:27 +00:00
Top = 'top_edge',
Right = 'right_edge',
Bottom = 'bottom_edge',
Left = 'left_edge',
2021-05-17 10:01:11 +00:00
}
export enum Corner {
2021-05-28 20:30:27 +00:00
TopLeft = 'top_left_corner',
TopRight = 'top_right_corner',
BottomRight = 'bottom_right_corner',
BottomLeft = 'bottom_left_corner',
2021-05-17 10:01:11 +00:00
}
2021-05-10 12:16:57 +00:00
export interface Bounds {
minX: number
minY: number
maxX: number
maxY: number
width: number
height: number
2021-06-04 16:08:43 +00:00
rotation?: number
2021-05-10 12:16:57 +00:00
}
2021-05-18 08:32:20 +00:00
export interface RotatedBounds extends Bounds {
rotation: number
}
2021-05-14 12:44:23 +00:00
export interface ShapeBounds extends Bounds {
id: string
}
export interface PointSnapshot extends Bounds {
nx: number
nmx: number
ny: number
nmy: number
}
export interface BoundsSnapshot extends PointSnapshot {
nw: number
nh: number
}
export type ShapeSpecificProps<T extends Shape> = Pick<
T,
Difference<keyof T, keyof BaseShape>
>
2021-05-12 11:27:33 +00:00
export type ShapeIndicatorProps<T extends Shape> = ShapeSpecificProps<T>
2021-05-12 21:11:17 +00:00
2021-05-14 12:44:23 +00:00
export type ShapeUtil<K extends Shape> = {
2021-05-13 18:22:16 +00:00
create(props: Partial<K>): K
getBounds(shape: K): Bounds
hitTest(shape: K, test: number[]): boolean
hitTestBounds(shape: K, bounds: Bounds): boolean
rotate(shape: K): K
translate(shape: K, delta: number[]): K
scale(shape: K, scale: number): K
stretch(shape: K, scaleX: number, scaleY: number): K
render(shape: K): JSX.Element
2021-05-12 21:11:17 +00:00
}
2021-05-23 17:09:23 +00:00
export enum MoveType {
Backward,
Forward,
ToFront,
ToBack,
}
2021-05-26 19:20:52 +00:00
export enum AlignType {
Top,
CenterVertical,
Bottom,
Left,
CenterHorizontal,
Right,
}
export enum StretchType {
Horizontal,
Vertical,
}
export enum DistributeType {
Horizontal,
Vertical,
}
2021-06-24 08:18:14 +00:00
export interface BezierCurveSegment {
start: number[]
tangentStart: number[]
normalStart: number[]
pressureStart: number
end: number[]
tangentEnd: number[]
normalEnd: number[]
pressureEnd: number
}
2021-05-17 10:01:11 +00:00
/* -------------------------------------------------- */
/* Code Editor */
/* -------------------------------------------------- */
2021-05-13 06:44:52 +00:00
2021-05-17 10:01:11 +00:00
export enum ControlType {
2021-05-28 20:30:27 +00:00
Number = 'number',
Vector = 'vector',
Text = 'text',
Select = 'select',
2021-05-13 06:44:52 +00:00
}
2021-05-14 12:44:23 +00:00
2021-05-17 10:01:11 +00:00
export interface BaseCodeControl {
id: string
type: ControlType
label: string
2021-05-14 12:44:23 +00:00
}
2021-05-17 10:01:11 +00:00
export interface NumberCodeControl extends BaseCodeControl {
type: ControlType.Number
value: number
2021-05-17 10:01:11 +00:00
min?: number
max?: number
step?: number
2021-05-17 10:01:11 +00:00
format?: (value: number) => number
2021-05-14 12:44:23 +00:00
}
2021-05-14 22:56:41 +00:00
2021-05-17 10:01:11 +00:00
export interface VectorCodeControl extends BaseCodeControl {
type: ControlType.Vector
value: number[]
min?: number
max?: number
step?: number
isNormalized?: boolean
2021-05-17 10:01:11 +00:00
format?: (value: number[]) => number[]
}
2021-05-14 22:56:41 +00:00
2021-05-17 10:01:11 +00:00
export interface TextCodeControl extends BaseCodeControl {
type: ControlType.Text
value: string
format?: (value: string) => string
}
2021-05-28 20:30:27 +00:00
export interface SelectCodeControl<T extends string = ''>
2021-05-17 10:01:11 +00:00
extends BaseCodeControl {
type: ControlType.Select
value: T
options: T[]
format?: (string: T) => string
}
export type CodeControl =
| NumberCodeControl
| VectorCodeControl
| TextCodeControl
2021-06-21 21:35:28 +00:00
export type PropsOfType<T extends Record<string, unknown>> = {
[K in keyof T]: T[K] extends boolean ? K : never
}[keyof T]
2021-06-02 15:58:51 +00:00
export type Mutable<T extends Shape> = { -readonly [K in keyof T]: T[K] }
2021-06-21 21:35:28 +00:00
export interface ShapeUtility<K extends Shape> {
// Default properties when creating a new shape
defaultProps: K
2021-06-21 21:35:28 +00:00
// A cache for the computed bounds of this kind of shape.
boundsCache: WeakMap<K, Bounds>
// Whether to show transform controls when this shape is selected.
canTransform: boolean
// Whether the shape's aspect ratio can change.
canChangeAspectRatio: boolean
// Whether the shape's style can be filled.
canStyleFill: boolean
// Whether the shape may be edited in an editing mode
canEdit: boolean
// Whether the shape is a foreign object.
isForeignObject: boolean
// Whether the shape can contain other shapes.
isParent: boolean
// Whether the shape is only shown when on hovered.
isShy: boolean
// Create a new shape.
create(this: ShapeUtility<K>, props: Partial<K>): K
2021-06-21 21:35:28 +00:00
// Update a shape's styles
applyStyles(
this: ShapeUtility<K>,
shape: Mutable<K>,
style: Partial<ShapeStyles>
): ShapeUtility<K>
translateBy(
this: ShapeUtility<K>,
shape: Mutable<K>,
point: number[]
): ShapeUtility<K>
translateTo(
this: ShapeUtility<K>,
shape: Mutable<K>,
point: number[]
): ShapeUtility<K>
rotateBy(
this: ShapeUtility<K>,
shape: Mutable<K>,
rotation: number
): ShapeUtility<K>
rotateTo(
this: ShapeUtility<K>,
shape: Mutable<K>,
rotation: number,
delta: number
): ShapeUtility<K>
// Transform to fit a new bounding box when more than one shape is selected.
transform(
this: ShapeUtility<K>,
shape: Mutable<K>,
bounds: Bounds,
info: {
type: Edge | Corner
initialShape: K
scaleX: number
scaleY: number
transformOrigin: number[]
}
): ShapeUtility<K>
// Transform a single shape to fit a new bounding box.
transformSingle(
this: ShapeUtility<K>,
shape: Mutable<K>,
bounds: Bounds,
info: {
type: Edge | Corner
initialShape: K
scaleX: number
scaleY: number
transformOrigin: number[]
}
): ShapeUtility<K>
setProperty<P extends keyof K>(
this: ShapeUtility<K>,
shape: Mutable<K>,
prop: P,
value: K[P]
): ShapeUtility<K>
// Respond when any child of this shape changes.
onChildrenChange(
this: ShapeUtility<K>,
shape: Mutable<K>,
children: Shape[]
): ShapeUtility<K>
// Respond when a user moves one of the shape's bound elements.
onBindingChange(
this: ShapeUtility<K>,
shape: Mutable<K>,
bindings: Record<string, ShapeBinding>
): ShapeUtility<K>
// Respond when a user moves one of the shape's handles.
onHandleChange(
this: ShapeUtility<K>,
shape: Mutable<K>,
handle: Partial<K['handles']>,
info?: Partial<{
delta: number[]
shiftKey: boolean
altKey: boolean
metaKey: boolean
}>
2021-06-21 21:35:28 +00:00
): ShapeUtility<K>
onDoublePointHandle(
this: ShapeUtility<K>,
shape: Mutable<K>,
handle: keyof K['handles'],
info: PointerInfo
): ShapeUtility<K>
2021-06-21 21:35:28 +00:00
// Respond when a user double clicks the shape's bounds.
onBoundsReset(this: ShapeUtility<K>, shape: Mutable<K>): ShapeUtility<K>
// Respond when a user double clicks the center of the shape.
onDoubleFocus(this: ShapeUtility<K>, shape: Mutable<K>): ShapeUtility<K>
// Clean up changes when a session ends.
onSessionComplete(this: ShapeUtility<K>, shape: Mutable<K>): ShapeUtility<K>
// Render a shape to JSX.
render(
this: ShapeUtility<K>,
shape: K,
2021-07-09 16:15:27 +00:00
info?: {
isEditing?: boolean
isHovered?: boolean
isSelected?: boolean
isCurrentParent?: boolean
2021-07-10 20:39:29 +00:00
isDarkMode?: boolean
2021-06-21 21:35:28 +00:00
ref?: React.MutableRefObject<HTMLTextAreaElement>
}
): JSX.Element
invalidate(this: ShapeUtility<K>, shape: K): ShapeUtility<K>
// Get the bounds of the a shape.
getBounds(this: ShapeUtility<K>, shape: K): Bounds
// Get the routated bounds of the a shape.
getRotatedBounds(this: ShapeUtility<K>, shape: K): Bounds
// Get the center of the shape
getCenter(this: ShapeUtility<K>, shape: K): number[]
// Test whether a point lies within a shape.
hitTest(this: ShapeUtility<K>, shape: K, test: number[]): boolean
// Test whether bounds collide with or contain a shape.
hitTestBounds(this: ShapeUtility<K>, shape: K, bounds: Bounds): boolean
2021-06-26 11:52:36 +00:00
// Get whether the shape should delete
2021-06-21 21:35:28 +00:00
shouldDelete(this: ShapeUtility<K>, shape: K): boolean
2021-06-26 11:52:36 +00:00
// Get whether the shape should render
shouldRender(this: ShapeUtility<K>, shape: K, previous: K): boolean
2021-06-21 21:35:28 +00:00
}
2021-06-29 12:00:59 +00:00
/* -------------------------------------------------- */
/* Utilities */
/* -------------------------------------------------- */
export type Difference<A, B> = A extends B ? never : A
export type RequiredKeys<T> = {
[K in keyof T]-?: Record<string, unknown> extends Pick<T, K> ? never : K
}[keyof T]