123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151 |
- /* eslint-disable @typescript-eslint/no-explicit-any */
- import { delay, TLBoxShape, TLBoxShapeProps, TLResetBoundsInfo } from '@tldraw/core'
- import { HTMLContainer, TLComponentProps, useApp } from '@tldraw/react'
- import Vec from '@tldraw/vec'
- import { action, computed } from 'mobx'
- import { observer } from 'mobx-react-lite'
- import * as React from 'react'
- import type { SizeLevel, Shape } from '.'
- import { useCameraMovingRef } from '../../hooks/useCameraMoving'
- import { withClampedStyles } from './style-props'
- export interface HTMLShapeProps extends TLBoxShapeProps {
- type: 'html'
- html: string
- scaleLevel?: SizeLevel
- }
- const levelToScale = {
- xs: 0.5,
- sm: 0.8,
- md: 1,
- lg: 1.5,
- xl: 2,
- xxl: 3,
- }
- export class HTMLShape extends TLBoxShape<HTMLShapeProps> {
- static id = 'html'
- static defaultProps: HTMLShapeProps = {
- id: 'html',
- type: 'html',
- parentId: 'page',
- point: [0, 0],
- size: [600, 0],
- html: '',
- }
- canChangeAspectRatio = true
- canFlip = false
- canEdit = true
- htmlAnchorRef = React.createRef<HTMLDivElement>()
- @computed get scaleLevel() {
- return this.props.scaleLevel ?? 'md'
- }
- @action setScaleLevel = async (v?: SizeLevel) => {
- const newSize = Vec.mul(
- this.props.size,
- levelToScale[(v as SizeLevel) ?? 'md'] / levelToScale[this.props.scaleLevel ?? 'md']
- )
- this.update({
- scaleLevel: v,
- })
- await delay()
- this.update({
- size: newSize,
- })
- }
- onResetBounds = (info?: TLResetBoundsInfo) => {
- if (this.htmlAnchorRef.current) {
- const rect = this.htmlAnchorRef.current.getBoundingClientRect()
- const [w, h] = Vec.div([rect.width, rect.height], info?.zoom ?? 1)
- const clamp = (v: number) => Math.max(Math.min(v || 400, 1400), 10)
- this.update({
- size: [clamp(w), clamp(h)],
- })
- }
- return this
- }
- ReactComponent = observer(({ events, isErasing, isEditing }: TLComponentProps) => {
- const {
- props: { html, scaleLevel },
- } = this
- const isMoving = useCameraMovingRef()
- const app = useApp<Shape>()
- const isSelected = app.selectedIds.has(this.id)
- const tlEventsEnabled =
- isMoving || (isSelected && !isEditing) || app.selectedTool.id !== 'select'
- const stop = React.useCallback(
- e => {
- if (!tlEventsEnabled) {
- // TODO: pinching inside Logseq Shape issue
- e.stopPropagation()
- }
- },
- [tlEventsEnabled]
- )
- const scaleRatio = levelToScale[scaleLevel ?? 'md']
- React.useEffect(() => {
- if (this.props.size[1] === 0) {
- this.onResetBounds({ zoom: app.viewport.camera.zoom })
- app.persist(true)
- }
- }, [])
- return (
- <HTMLContainer
- style={{
- overflow: 'hidden',
- pointerEvents: 'all',
- opacity: isErasing ? 0.2 : 1,
- }}
- {...events}
- >
- <div
- onWheelCapture={stop}
- onPointerDown={stop}
- onPointerUp={stop}
- className="tl-html-container"
- style={{
- pointerEvents: !isMoving && (isEditing || isSelected) ? 'all' : 'none',
- overflow: isEditing ? 'auto' : 'hidden',
- width: `calc(100% / ${scaleRatio})`,
- height: `calc(100% / ${scaleRatio})`,
- transform: `scale(${scaleRatio})`,
- }}
- >
- <div
- ref={this.htmlAnchorRef}
- className="tl-html-anchor"
- dangerouslySetInnerHTML={{ __html: html.trim() }}
- />
- </div>
- </HTMLContainer>
- )
- })
- ReactIndicator = observer(() => {
- const {
- props: {
- size: [w, h],
- },
- } = this
- return <rect width={w} height={h} fill="transparent" />
- })
- validateProps = (props: Partial<HTMLShapeProps>) => {
- if (props.size !== undefined) {
- props.size[0] = Math.max(props.size[0], 1)
- props.size[1] = Math.max(props.size[1], 1)
- }
- return withClampedStyles(this, props)
- }
- }
|