Tldraw/packages/editor/src/lib/editor/shapeutils/BookmarkShapeUtil/BookmarkShapeUtil.tsx

185 wiersze
4.7 KiB
TypeScript

import { toDomPrecision } from '@tldraw/primitives'
import { AssetRecordType, TLAssetId, TLBookmarkAsset, TLBookmarkShape } from '@tldraw/tlschema'
import { debounce, getHashForString } from '@tldraw/utils'
import { HTMLContainer } from '../../../components/HTMLContainer'
import {
DEFAULT_BOOKMARK_HEIGHT,
DEFAULT_BOOKMARK_WIDTH,
ROTATING_SHADOWS,
} from '../../../constants'
import {
rotateBoxShadow,
stopEventPropagation,
truncateStringWithEllipsis,
} from '../../../utils/dom'
import { BaseBoxShapeUtil } from '../BaseBoxShapeUtil'
import { TLOnBeforeCreateHandler, TLOnBeforeUpdateHandler } from '../ShapeUtil'
import { HyperlinkButton } from '../shared/HyperlinkButton'
/** @public */
export class BookmarkShapeUtil extends BaseBoxShapeUtil<TLBookmarkShape> {
static override type = 'bookmark'
override canResize = () => false
override hideSelectionBoundsBg = () => true
override hideSelectionBoundsFg = () => true
override defaultProps(): TLBookmarkShape['props'] {
return {
url: '',
w: DEFAULT_BOOKMARK_WIDTH,
h: DEFAULT_BOOKMARK_HEIGHT,
assetId: null,
}
}
override render(shape: TLBookmarkShape) {
const asset = (
shape.props.assetId ? this.editor.getAssetById(shape.props.assetId) : null
) as TLBookmarkAsset
const pageRotation = this.editor.getPageRotation(shape)
const address = this.getHumanReadableAddress(shape)
return (
<HTMLContainer>
<div
className="tl-bookmark__container tl-hitarea-stroke"
style={{
boxShadow: rotateBoxShadow(pageRotation, ROTATING_SHADOWS),
}}
>
<div className="tl-bookmark__image_container">
{asset?.props.image ? (
<img
className="tl-bookmark__image"
draggable={false}
src={asset?.props.image}
alt={asset?.props.title || ''}
/>
) : (
<div className="tl-bookmark__placeholder" />
)}
<HyperlinkButton url={shape.props.url} zoomLevel={this.editor.zoomLevel} />
</div>
<div className="tl-bookmark__copy_container">
{asset?.props.title && (
<h2 className="tl-bookmark__heading">
{truncateStringWithEllipsis(asset?.props.title || '', 54)}
</h2>
)}
{asset?.props.description && (
<p className="tl-bookmark__description">
{truncateStringWithEllipsis(asset?.props.description || '', 128)}
</p>
)}
<a
className="tl-bookmark__link"
href={shape.props.url || ''}
target="_blank"
rel="noopener noreferrer"
onPointerDown={stopEventPropagation}
onPointerUp={stopEventPropagation}
onClick={stopEventPropagation}
>
{truncateStringWithEllipsis(address, 45)}
</a>
</div>
</div>
</HTMLContainer>
)
}
override indicator(shape: TLBookmarkShape) {
return (
<rect
width={toDomPrecision(shape.props.w)}
height={toDomPrecision(shape.props.h)}
rx="8"
ry="8"
/>
)
}
override onBeforeCreate?: TLOnBeforeCreateHandler<TLBookmarkShape> = (shape) => {
this.updateBookmarkAsset(shape)
}
override onBeforeUpdate?: TLOnBeforeUpdateHandler<TLBookmarkShape> = (prev, shape) => {
if (prev.props.url !== shape.props.url) {
this.updateBookmarkAsset(shape)
}
}
getHumanReadableAddress(shape: TLBookmarkShape) {
try {
const url = new URL(shape.props.url)
const path = url.pathname.replace(/\/*$/, '')
return `${url.hostname}${path}`
} catch (e) {
return shape.props.url
}
}
protected updateBookmarkAsset = debounce((shape: TLBookmarkShape) => {
const { url } = shape.props
const assetId: TLAssetId = AssetRecordType.createId(getHashForString(url))
const existing = this.editor.getAssetById(assetId)
if (existing) {
// If there's an existing asset with the same URL, use
// its asset id instead.
if (shape.props.assetId !== existing.id) {
this.editor.updateShapes([
{
id: shape.id,
type: shape.type,
props: { assetId },
},
])
}
} else if (this.editor.onCreateBookmarkFromUrl) {
// Create a bookmark asset for the URL. First get its meta
// data, then create the asset and update the shape.
this.editor.onCreateBookmarkFromUrl(url).then((meta) => {
if (!meta) {
this.editor.updateShapes([
{
id: shape.id,
type: shape.type,
props: { assetId: undefined },
},
])
return
}
this.editor.batch(() => {
this.editor
.createAssets([
{
id: assetId,
typeName: 'asset',
type: 'bookmark',
props: {
src: url,
description: meta.description,
image: meta.image,
title: meta.title,
},
},
])
.updateShapes([
{
id: shape.id,
type: shape.type,
props: { assetId },
},
])
})
})
}
}, 500)
}