kopia lustrzana https://github.com/Tldraw/Tldraw
Adds article to core example
rodzic
31638c7c90
commit
9463ef7f3b
|
@ -0,0 +1,131 @@
|
|||
/* eslint-disable @typescript-eslint/no-non-null-assertion */
|
||||
/* refresh-reset */
|
||||
|
||||
import * as React from 'react'
|
||||
import { TLShape, Utils, TLBounds, ShapeUtil, HTMLContainer } from '@tldraw/core'
|
||||
|
||||
// Define a custom shape
|
||||
|
||||
export interface ArticleShape extends TLShape {
|
||||
type: 'rectangle'
|
||||
size: number[]
|
||||
text: string
|
||||
}
|
||||
|
||||
// Create a "shape utility" class that interprets that shape
|
||||
|
||||
export const Article = new ShapeUtil<ArticleShape, HTMLDivElement, { isDarkMode: boolean }>(() => ({
|
||||
type: 'rectangle',
|
||||
defaultProps: {
|
||||
id: 'example1',
|
||||
type: 'rectangle',
|
||||
parentId: 'page1',
|
||||
childIndex: 0,
|
||||
name: 'Example Shape',
|
||||
point: [0, 0],
|
||||
size: [100, 100],
|
||||
rotation: 0,
|
||||
text: 'Hello world!',
|
||||
},
|
||||
Component({ shape, events, meta, onShapeChange, isEditing }, ref) {
|
||||
const color = meta.isDarkMode ? 'white' : 'black'
|
||||
|
||||
const rInput = React.useRef<HTMLDivElement>(null)
|
||||
|
||||
function updateShapeSize() {
|
||||
const elm = rInput.current!
|
||||
|
||||
onShapeChange?.({
|
||||
...shape,
|
||||
text: elm.innerText,
|
||||
size: [elm.offsetWidth + 44, elm.offsetHeight + 44],
|
||||
})
|
||||
}
|
||||
|
||||
React.useLayoutEffect(() => {
|
||||
const elm = rInput.current!
|
||||
|
||||
const observer = new MutationObserver(updateShapeSize)
|
||||
|
||||
observer.observe(elm, {
|
||||
attributes: true,
|
||||
characterData: true,
|
||||
subtree: true,
|
||||
})
|
||||
|
||||
elm.innerText = shape.text
|
||||
updateShapeSize()
|
||||
|
||||
return () => {
|
||||
observer.disconnect()
|
||||
}
|
||||
}, [])
|
||||
|
||||
React.useEffect(() => {
|
||||
if (isEditing) {
|
||||
rInput.current!.focus()
|
||||
}
|
||||
}, [isEditing])
|
||||
|
||||
return (
|
||||
<HTMLContainer ref={ref}>
|
||||
<div
|
||||
{...events}
|
||||
style={{
|
||||
pointerEvents: 'all',
|
||||
width: shape.size[0],
|
||||
height: shape.size[1],
|
||||
display: 'flex',
|
||||
fontSize: 20,
|
||||
fontFamily: 'sans-serif',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
border: `2px solid ${color}`,
|
||||
color,
|
||||
}}
|
||||
>
|
||||
<div onPointerDown={(e) => isEditing && e.stopPropagation()}>
|
||||
<div
|
||||
ref={rInput}
|
||||
style={{
|
||||
whiteSpace: 'nowrap',
|
||||
overflow: 'hidden',
|
||||
textAlign: 'center',
|
||||
outline: 'none',
|
||||
userSelect: isEditing ? 'all' : 'none',
|
||||
}}
|
||||
contentEditable={isEditing}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</HTMLContainer>
|
||||
)
|
||||
},
|
||||
Indicator({ shape }) {
|
||||
return (
|
||||
<rect
|
||||
fill="none"
|
||||
stroke="blue"
|
||||
strokeWidth={1}
|
||||
width={shape.size[0]}
|
||||
height={shape.size[1]}
|
||||
pointerEvents="none"
|
||||
/>
|
||||
)
|
||||
},
|
||||
getBounds(shape) {
|
||||
const bounds = Utils.getFromCache(this.boundsCache, shape, () => {
|
||||
const [width, height] = shape.size
|
||||
return {
|
||||
minX: 0,
|
||||
maxX: width,
|
||||
minY: 0,
|
||||
maxY: height,
|
||||
width,
|
||||
height,
|
||||
} as TLBounds
|
||||
})
|
||||
|
||||
return Utils.translateBounds(bounds, shape.point)
|
||||
},
|
||||
}))
|
|
@ -5,14 +5,22 @@ import type {
|
|||
TLPointerEventHandler,
|
||||
TLShapeChangeHandler,
|
||||
} from '@tldraw/core'
|
||||
import type { RectangleShape } from './rectangle'
|
||||
import type { LabelShape } from './label'
|
||||
import { ShapeUtil } from '@tldraw/core'
|
||||
import { Article, ArticleShape } from './article'
|
||||
import { Rectangle, RectangleShape } from './rectangle'
|
||||
import { Label, LabelShape } from './label'
|
||||
import { StateManager } from 'rko'
|
||||
|
||||
type Shapes = RectangleShape | LabelShape
|
||||
const shapeUtils: Record<string, any> = {
|
||||
rectangle: Rectangle,
|
||||
label: Label,
|
||||
article: Article,
|
||||
}
|
||||
|
||||
type Shape = RectangleShape | LabelShape | ArticleShape
|
||||
|
||||
interface State {
|
||||
page: TLPage<Shapes, TLBinding>
|
||||
page: TLPage<Shape, TLBinding>
|
||||
pageState: TLPageState
|
||||
meta: {
|
||||
isDarkMode: boolean
|
||||
|
@ -58,10 +66,58 @@ class AppState extends StateManager<State> {
|
|||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* Manually create shapes on the page.
|
||||
* @param shapes An array of shape partials, containing the initial props for the shapes.
|
||||
* @command
|
||||
*/
|
||||
createShapes = (...shapes: ({ id: string; type: Shape['type'] } & Partial<Shape>)[]): this => {
|
||||
if (shapes.length === 0) return this
|
||||
return this.create(
|
||||
...shapes.map((shape) => {
|
||||
return shapeUtils[shape.type].create({
|
||||
...shape,
|
||||
parentId: shape.parentId || this.state.page.id,
|
||||
})
|
||||
})
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Manually update a set of shapes.
|
||||
* @param shapes An array of shape partials, containing the changes to be made to each shape.
|
||||
* @command
|
||||
*/
|
||||
updateShapes = (...shapes: ({ id: string } & Partial<Shape>)[]): this => {
|
||||
const pageShapes = this.state.page.shapes
|
||||
const shapesToUpdate = shapes.filter((shape) => pageShapes[shape.id])
|
||||
if (shapesToUpdate.length === 0) return this
|
||||
return this.setState(
|
||||
Commands.update(this.state, shapesToUpdate, this.state.page.id),
|
||||
'updated_shapes'
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Create one or more shapes.
|
||||
* @param shapes An array of shapes.
|
||||
* @command
|
||||
*/
|
||||
create = (...shapes: TLDrawShape[]): this => {
|
||||
if (shapes.length === 0) return this
|
||||
return this.setState(Commands.create(this.state, shapes))
|
||||
}
|
||||
|
||||
/* --------------------- Events --------------------- */
|
||||
|
||||
onPointCanvas: TLPointerEventHandler = (info) => {
|
||||
this.deselect()
|
||||
if (this.state.pageState.selectedIds.length > 0) {
|
||||
this.deselect()
|
||||
} else {
|
||||
if (info.shiftKey) {
|
||||
this.create
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
onPointShape: TLPointerEventHandler = (info) => {
|
||||
|
|
Ładowanie…
Reference in New Issue