@bigbluebutton/tldraw 2.0.0-alpha.30 → 2.0.0-alpha.32
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist-cjs/lib/shapes/image/ImageShapeUtil.js +3 -1
- package/dist-cjs/lib/shapes/image/ImageShapeUtil.js.map +2 -2
- package/dist-esm/lib/shapes/image/ImageShapeUtil.mjs +3 -1
- package/dist-esm/lib/shapes/image/ImageShapeUtil.mjs.map +2 -2
- package/package.json +2 -2
- package/src/lib/shapes/image/ImageShapeUtil.tsx +2 -0
|
@@ -129,7 +129,9 @@ class ImageShapeUtil extends import_editor.BaseBoxShapeUtil {
|
|
|
129
129
|
{
|
|
130
130
|
className: "tl-image",
|
|
131
131
|
style: {
|
|
132
|
-
backgroundImage: `url(${!shape.props.playing || reduceMotion ? staticFrameSrc : asset.props.src})
|
|
132
|
+
backgroundImage: `url(${!shape.props.playing || reduceMotion ? staticFrameSrc : asset.props.src})`,
|
|
133
|
+
// Force hardware acceleration to prevent Safari flickering issue
|
|
134
|
+
transform: this.editor.environment.isSafari ? "translateZ(0)" : void 0
|
|
133
135
|
},
|
|
134
136
|
draggable: false
|
|
135
137
|
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../../../src/lib/shapes/image/ImageShapeUtil.tsx"],
|
|
4
|
-
"sourcesContent": ["/* eslint-disable react-hooks/rules-of-hooks */\nimport {\n\tBaseBoxShapeUtil,\n\tHTMLContainer,\n\tTLImageShape,\n\tTLOnDoubleClickHandler,\n\tTLShapePartial,\n\tVec2d,\n\tdeepCopy,\n\timageShapeMigrations,\n\timageShapeProps,\n\ttoDomPrecision,\n\tuseIsCropping,\n\tuseValue,\n} from '@bigbluebutton/editor'\nimport { useEffect, useState } from 'react'\nimport { HyperlinkButton } from '../shared/HyperlinkButton'\nimport { usePrefersReducedMotion } from '../shared/usePrefersReducedMotion'\n\nconst loadImage = async (url: string): Promise<HTMLImageElement> => {\n\treturn new Promise((resolve, reject) => {\n\t\tconst image = new Image()\n\t\timage.onload = () => resolve(image)\n\t\timage.onerror = () => reject(new Error('Failed to load image'))\n\t\timage.crossOrigin = 'anonymous'\n\t\timage.src = url\n\t})\n}\n\nconst getStateFrame = async (url: string) => {\n\tconst image = await loadImage(url)\n\n\tconst canvas = document.createElement('canvas')\n\tcanvas.width = image.width\n\tcanvas.height = image.height\n\n\tconst ctx = canvas.getContext('2d')\n\tif (!ctx) return\n\n\tctx.drawImage(image, 0, 0)\n\treturn canvas.toDataURL()\n}\n\nasync function getDataURIFromURL(url: string): Promise<string> {\n\tconst response = await fetch(url)\n\tconst blob = await response.blob()\n\treturn new Promise((resolve, reject) => {\n\t\tconst reader = new FileReader()\n\t\treader.onloadend = () => resolve(reader.result as string)\n\t\treader.onerror = reject\n\t\treader.readAsDataURL(blob)\n\t})\n}\n\n/** @public */\nexport class ImageShapeUtil extends BaseBoxShapeUtil<TLImageShape> {\n\tstatic override type = 'image' as const\n\tstatic override props = imageShapeProps\n\tstatic override migrations = imageShapeMigrations\n\n\toverride isAspectRatioLocked = () => true\n\toverride canCrop = () => true\n\n\toverride getDefaultProps(): TLImageShape['props'] {\n\t\treturn {\n\t\t\tw: 100,\n\t\t\th: 100,\n\t\t\tassetId: null,\n\t\t\tplaying: true,\n\t\t\turl: '',\n\t\t\tcrop: null,\n\t\t}\n\t}\n\n\tcomponent(shape: TLImageShape) {\n\t\tconst containerStyle = getContainerStyle(shape)\n\t\tconst isCropping = useIsCropping(shape.id)\n\t\tconst prefersReducedMotion = usePrefersReducedMotion()\n\t\tconst [staticFrameSrc, setStaticFrameSrc] = useState('')\n\n\t\tconst asset = shape.props.assetId ? this.editor.getAsset(shape.props.assetId) : undefined\n\n\t\tif (asset?.type === 'bookmark') {\n\t\t\tthrow Error(\"Bookmark assets can't be rendered as images\")\n\t\t}\n\n\t\tconst isSelected = useValue(\n\t\t\t'onlySelectedShape',\n\t\t\t() => shape.id === this.editor.getOnlySelectedShape()?.id,\n\t\t\t[this.editor]\n\t\t)\n\n\t\tconst showCropPreview =\n\t\t\tisSelected &&\n\t\t\tisCropping &&\n\t\t\tthis.editor.isInAny('select.crop', 'select.cropping', 'select.pointing_crop_handle')\n\n\t\t// We only want to reduce motion for mimeTypes that have motion\n\t\tconst reduceMotion =\n\t\t\tprefersReducedMotion &&\n\t\t\t(asset?.props.mimeType?.includes('video') || asset?.props.mimeType?.includes('gif'))\n\n\t\tuseEffect(() => {\n\t\t\tif (asset?.props.src && 'mimeType' in asset.props && asset?.props.mimeType === 'image/gif') {\n\t\t\t\tlet cancelled = false\n\t\t\t\tconst run = async () => {\n\t\t\t\t\tconst newStaticFrame = await getStateFrame(asset.props.src!)\n\t\t\t\t\tif (cancelled) return\n\t\t\t\t\tif (newStaticFrame) {\n\t\t\t\t\t\tsetStaticFrameSrc(newStaticFrame)\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\trun()\n\n\t\t\t\treturn () => {\n\t\t\t\t\tcancelled = true\n\t\t\t\t}\n\t\t\t}\n\t\t}, [prefersReducedMotion, asset?.props])\n\n\t\treturn (\n\t\t\t<>\n\t\t\t\t{asset?.props.src && showCropPreview && (\n\t\t\t\t\t<div style={containerStyle}>\n\t\t\t\t\t\t<div\n\t\t\t\t\t\t\tclassName=\"tl-image\"\n\t\t\t\t\t\t\tstyle={{\n\t\t\t\t\t\t\t\topacity: 0.1,\n\t\t\t\t\t\t\t\tbackgroundImage: `url(${\n\t\t\t\t\t\t\t\t\t!shape.props.playing || reduceMotion ? staticFrameSrc : asset.props.src\n\t\t\t\t\t\t\t\t})`,\n\t\t\t\t\t\t\t}}\n\t\t\t\t\t\t\tdraggable={false}\n\t\t\t\t\t\t/>\n\t\t\t\t\t</div>\n\t\t\t\t)}\n\t\t\t\t<HTMLContainer\n\t\t\t\t\tid={shape.id}\n\t\t\t\t\tstyle={{ overflow: 'hidden', width: shape.props.w, height: shape.props.h }}\n\t\t\t\t>\n\t\t\t\t\t<div className=\"tl-image-container\" style={containerStyle}>\n\t\t\t\t\t\t{asset?.props.src ? (\n\t\t\t\t\t\t\t<div\n\t\t\t\t\t\t\t\tclassName=\"tl-image\"\n\t\t\t\t\t\t\t\tstyle={{\n\t\t\t\t\t\t\t\t\tbackgroundImage: `url(${\n\t\t\t\t\t\t\t\t\t\t!shape.props.playing || reduceMotion ? staticFrameSrc : asset.props.src\n\t\t\t\t\t\t\t\t\t})`,\n\t\t\t\t\t\t\t\t}}\n\t\t\t\t\t\t\t\tdraggable={false}\n\t\t\t\t\t\t\t/>\n\t\t\t\t\t\t) : null}\n\t\t\t\t\t\t{asset?.props.isAnimated && !shape.props.playing && (\n\t\t\t\t\t\t\t<div className=\"tl-image__tg\">GIF</div>\n\t\t\t\t\t\t)}\n\t\t\t\t\t</div>\n\t\t\t\t\t{'url' in shape.props && shape.props.url && (\n\t\t\t\t\t\t<HyperlinkButton url={shape.props.url} zoomLevel={this.editor.getZoomLevel()} />\n\t\t\t\t\t)}\n\t\t\t\t</HTMLContainer>\n\t\t\t</>\n\t\t)\n\t}\n\n\tindicator(shape: TLImageShape) {\n\t\tconst isCropping = useIsCropping(shape.id)\n\t\tif (isCropping) {\n\t\t\treturn null\n\t\t}\n\t\treturn <rect width={toDomPrecision(shape.props.w)} height={toDomPrecision(shape.props.h)} />\n\t}\n\n\toverride async toSvg(shape: TLImageShape) {\n\t\tconst g = document.createElementNS('http://www.w3.org/2000/svg', 'g')\n\t\tconst asset = shape.props.assetId ? this.editor.getAsset(shape.props.assetId) : null\n\n\t\tlet src = asset?.props.src || ''\n\t\tif (src && src.startsWith('http')) {\n\t\t\t// If it's a remote image, we need to fetch it and convert it to a data URI\n\t\t\tsrc = (await getDataURIFromURL(src)) || ''\n\t\t}\n\n\t\tconst image = document.createElementNS('http://www.w3.org/2000/svg', 'image')\n\t\timage.setAttributeNS('http://www.w3.org/1999/xlink', 'href', src)\n\t\tconst containerStyle = getContainerStyle(shape)\n\t\tconst crop = shape.props.crop\n\t\tif (containerStyle.transform && crop) {\n\t\t\tconst { transform, width, height } = containerStyle\n\t\t\tconst croppedWidth = (crop.bottomRight.x - crop.topLeft.x) * width\n\t\t\tconst croppedHeight = (crop.bottomRight.y - crop.topLeft.y) * height\n\n\t\t\tconst points = [\n\t\t\t\tnew Vec2d(0, 0),\n\t\t\t\tnew Vec2d(croppedWidth, 0),\n\t\t\t\tnew Vec2d(croppedWidth, croppedHeight),\n\t\t\t\tnew Vec2d(0, croppedHeight),\n\t\t\t]\n\n\t\t\tconst polygon = document.createElementNS('http://www.w3.org/2000/svg', 'polygon')\n\t\t\tpolygon.setAttribute('points', points.map((p) => `${p.x},${p.y}`).join(' '))\n\n\t\t\tconst clipPath = document.createElementNS('http://www.w3.org/2000/svg', 'clipPath')\n\t\t\tclipPath.setAttribute('id', 'cropClipPath')\n\t\t\tclipPath.appendChild(polygon)\n\n\t\t\tconst defs = document.createElementNS('http://www.w3.org/2000/svg', 'defs')\n\t\t\tdefs.appendChild(clipPath)\n\t\t\tg.appendChild(defs)\n\n\t\t\tconst innerElement = document.createElementNS('http://www.w3.org/2000/svg', 'g')\n\t\t\tinnerElement.setAttribute('clip-path', 'url(#cropClipPath)')\n\t\t\timage.setAttribute('width', width.toString())\n\t\t\timage.setAttribute('height', height.toString())\n\t\t\timage.style.transform = transform\n\t\t\tinnerElement.appendChild(image)\n\t\t\tg.appendChild(innerElement)\n\t\t} else {\n\t\t\timage.setAttribute('width', shape.props.w.toString())\n\t\t\timage.setAttribute('height', shape.props.h.toString())\n\t\t\tg.appendChild(image)\n\t\t}\n\n\t\treturn g\n\t}\n\n\toverride onDoubleClick = (shape: TLImageShape) => {\n\t\tconst asset = shape.props.assetId ? this.editor.getAsset(shape.props.assetId) : undefined\n\n\t\tif (!asset) return\n\n\t\tconst canPlay =\n\t\t\tasset.props.src && 'mimeType' in asset.props && asset.props.mimeType === 'image/gif'\n\n\t\tif (!canPlay) return\n\n\t\tthis.editor.updateShapes([\n\t\t\t{\n\t\t\t\ttype: 'image',\n\t\t\t\tid: shape.id,\n\t\t\t\tprops: {\n\t\t\t\t\tplaying: !shape.props.playing,\n\t\t\t\t},\n\t\t\t},\n\t\t])\n\t}\n\n\toverride onDoubleClickEdge: TLOnDoubleClickHandler<TLImageShape> = (shape) => {\n\t\tconst props = shape.props\n\t\tif (!props) return\n\n\t\tif (this.editor.getCroppingShapeId() !== shape.id) {\n\t\t\treturn\n\t\t}\n\n\t\tconst crop = deepCopy(props.crop) || {\n\t\t\ttopLeft: { x: 0, y: 0 },\n\t\t\tbottomRight: { x: 1, y: 1 },\n\t\t}\n\n\t\t// The true asset dimensions\n\t\tconst w = (1 / (crop.bottomRight.x - crop.topLeft.x)) * shape.props.w\n\t\tconst h = (1 / (crop.bottomRight.y - crop.topLeft.y)) * shape.props.h\n\n\t\tconst pointDelta = new Vec2d(crop.topLeft.x * w, crop.topLeft.y * h).rot(shape.rotation)\n\n\t\tconst partial: TLShapePartial<TLImageShape> = {\n\t\t\tid: shape.id,\n\t\t\ttype: shape.type,\n\t\t\tx: shape.x - pointDelta.x,\n\t\t\ty: shape.y - pointDelta.y,\n\t\t\tprops: {\n\t\t\t\tcrop: {\n\t\t\t\t\ttopLeft: { x: 0, y: 0 },\n\t\t\t\t\tbottomRight: { x: 1, y: 1 },\n\t\t\t\t},\n\t\t\t\tw,\n\t\t\t\th,\n\t\t\t},\n\t\t}\n\n\t\tthis.editor.updateShapes([partial])\n\t}\n}\n\n/**\n * When an image is cropped we need to translate the image to show the portion withing the cropped\n * area. We do this by translating the image by the negative of the top left corner of the crop\n * area.\n *\n * @param shape - Shape The image shape for which to get the container style\n * @returns - Styles to apply to the image container\n */\nfunction getContainerStyle(shape: TLImageShape) {\n\tconst crop = shape.props.crop\n\tconst topLeft = crop?.topLeft\n\tif (!topLeft) {\n\t\treturn {\n\t\t\twidth: shape.props.w,\n\t\t\theight: shape.props.h,\n\t\t}\n\t}\n\n\tconst w = (1 / (crop.bottomRight.x - crop.topLeft.x)) * shape.props.w\n\tconst h = (1 / (crop.bottomRight.y - crop.topLeft.y)) * shape.props.h\n\n\tconst offsetX = -topLeft.x * w\n\tconst offsetY = -topLeft.y * h\n\treturn {\n\t\ttransform: `translate(${offsetX}px, ${offsetY}px)`,\n\t\twidth: w,\n\t\theight: h,\n\t}\n}\n"],
|
|
5
|
-
"mappings": "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
|
|
4
|
+
"sourcesContent": ["/* eslint-disable react-hooks/rules-of-hooks */\nimport {\n\tBaseBoxShapeUtil,\n\tHTMLContainer,\n\tTLImageShape,\n\tTLOnDoubleClickHandler,\n\tTLShapePartial,\n\tVec2d,\n\tdeepCopy,\n\timageShapeMigrations,\n\timageShapeProps,\n\ttoDomPrecision,\n\tuseIsCropping,\n\tuseValue,\n} from '@bigbluebutton/editor'\nimport { useEffect, useState } from 'react'\nimport { HyperlinkButton } from '../shared/HyperlinkButton'\nimport { usePrefersReducedMotion } from '../shared/usePrefersReducedMotion'\n\nconst loadImage = async (url: string): Promise<HTMLImageElement> => {\n\treturn new Promise((resolve, reject) => {\n\t\tconst image = new Image()\n\t\timage.onload = () => resolve(image)\n\t\timage.onerror = () => reject(new Error('Failed to load image'))\n\t\timage.crossOrigin = 'anonymous'\n\t\timage.src = url\n\t})\n}\n\nconst getStateFrame = async (url: string) => {\n\tconst image = await loadImage(url)\n\n\tconst canvas = document.createElement('canvas')\n\tcanvas.width = image.width\n\tcanvas.height = image.height\n\n\tconst ctx = canvas.getContext('2d')\n\tif (!ctx) return\n\n\tctx.drawImage(image, 0, 0)\n\treturn canvas.toDataURL()\n}\n\nasync function getDataURIFromURL(url: string): Promise<string> {\n\tconst response = await fetch(url)\n\tconst blob = await response.blob()\n\treturn new Promise((resolve, reject) => {\n\t\tconst reader = new FileReader()\n\t\treader.onloadend = () => resolve(reader.result as string)\n\t\treader.onerror = reject\n\t\treader.readAsDataURL(blob)\n\t})\n}\n\n/** @public */\nexport class ImageShapeUtil extends BaseBoxShapeUtil<TLImageShape> {\n\tstatic override type = 'image' as const\n\tstatic override props = imageShapeProps\n\tstatic override migrations = imageShapeMigrations\n\n\toverride isAspectRatioLocked = () => true\n\toverride canCrop = () => true\n\n\toverride getDefaultProps(): TLImageShape['props'] {\n\t\treturn {\n\t\t\tw: 100,\n\t\t\th: 100,\n\t\t\tassetId: null,\n\t\t\tplaying: true,\n\t\t\turl: '',\n\t\t\tcrop: null,\n\t\t}\n\t}\n\n\tcomponent(shape: TLImageShape) {\n\t\tconst containerStyle = getContainerStyle(shape)\n\t\tconst isCropping = useIsCropping(shape.id)\n\t\tconst prefersReducedMotion = usePrefersReducedMotion()\n\t\tconst [staticFrameSrc, setStaticFrameSrc] = useState('')\n\n\t\tconst asset = shape.props.assetId ? this.editor.getAsset(shape.props.assetId) : undefined\n\n\t\tif (asset?.type === 'bookmark') {\n\t\t\tthrow Error(\"Bookmark assets can't be rendered as images\")\n\t\t}\n\n\t\tconst isSelected = useValue(\n\t\t\t'onlySelectedShape',\n\t\t\t() => shape.id === this.editor.getOnlySelectedShape()?.id,\n\t\t\t[this.editor]\n\t\t)\n\n\t\tconst showCropPreview =\n\t\t\tisSelected &&\n\t\t\tisCropping &&\n\t\t\tthis.editor.isInAny('select.crop', 'select.cropping', 'select.pointing_crop_handle')\n\n\t\t// We only want to reduce motion for mimeTypes that have motion\n\t\tconst reduceMotion =\n\t\t\tprefersReducedMotion &&\n\t\t\t(asset?.props.mimeType?.includes('video') || asset?.props.mimeType?.includes('gif'))\n\n\t\tuseEffect(() => {\n\t\t\tif (asset?.props.src && 'mimeType' in asset.props && asset?.props.mimeType === 'image/gif') {\n\t\t\t\tlet cancelled = false\n\t\t\t\tconst run = async () => {\n\t\t\t\t\tconst newStaticFrame = await getStateFrame(asset.props.src!)\n\t\t\t\t\tif (cancelled) return\n\t\t\t\t\tif (newStaticFrame) {\n\t\t\t\t\t\tsetStaticFrameSrc(newStaticFrame)\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\trun()\n\n\t\t\t\treturn () => {\n\t\t\t\t\tcancelled = true\n\t\t\t\t}\n\t\t\t}\n\t\t}, [prefersReducedMotion, asset?.props])\n\n\t\treturn (\n\t\t\t<>\n\t\t\t\t{asset?.props.src && showCropPreview && (\n\t\t\t\t\t<div style={containerStyle}>\n\t\t\t\t\t\t<div\n\t\t\t\t\t\t\tclassName=\"tl-image\"\n\t\t\t\t\t\t\tstyle={{\n\t\t\t\t\t\t\t\topacity: 0.1,\n\t\t\t\t\t\t\t\tbackgroundImage: `url(${\n\t\t\t\t\t\t\t\t\t!shape.props.playing || reduceMotion ? staticFrameSrc : asset.props.src\n\t\t\t\t\t\t\t\t})`,\n\t\t\t\t\t\t\t}}\n\t\t\t\t\t\t\tdraggable={false}\n\t\t\t\t\t\t/>\n\t\t\t\t\t</div>\n\t\t\t\t)}\n\t\t\t\t<HTMLContainer\n\t\t\t\t\tid={shape.id}\n\t\t\t\t\tstyle={{ overflow: 'hidden', width: shape.props.w, height: shape.props.h }}\n\t\t\t\t>\n\t\t\t\t\t<div className=\"tl-image-container\" style={containerStyle}>\n\t\t\t\t\t\t{asset?.props.src ? (\n\t\t\t\t\t\t\t<div\n\t\t\t\t\t\t\t\tclassName=\"tl-image\"\n\t\t\t\t\t\t\t\tstyle={{\n\t\t\t\t\t\t\t\t\tbackgroundImage: `url(${\n\t\t\t\t\t\t\t\t\t\t!shape.props.playing || reduceMotion ? staticFrameSrc : asset.props.src\n\t\t\t\t\t\t\t\t\t})`,\n\t\t\t\t\t\t\t\t\t// Force hardware acceleration to prevent Safari flickering issue\n\t\t\t\t\t\t\t\t\ttransform: this.editor.environment.isSafari ? 'translateZ(0)' : undefined,\n\t\t\t\t\t\t\t\t}}\n\t\t\t\t\t\t\t\tdraggable={false}\n\t\t\t\t\t\t\t/>\n\t\t\t\t\t\t) : null}\n\t\t\t\t\t\t{asset?.props.isAnimated && !shape.props.playing && (\n\t\t\t\t\t\t\t<div className=\"tl-image__tg\">GIF</div>\n\t\t\t\t\t\t)}\n\t\t\t\t\t</div>\n\t\t\t\t\t{'url' in shape.props && shape.props.url && (\n\t\t\t\t\t\t<HyperlinkButton url={shape.props.url} zoomLevel={this.editor.getZoomLevel()} />\n\t\t\t\t\t)}\n\t\t\t\t</HTMLContainer>\n\t\t\t</>\n\t\t)\n\t}\n\n\tindicator(shape: TLImageShape) {\n\t\tconst isCropping = useIsCropping(shape.id)\n\t\tif (isCropping) {\n\t\t\treturn null\n\t\t}\n\t\treturn <rect width={toDomPrecision(shape.props.w)} height={toDomPrecision(shape.props.h)} />\n\t}\n\n\toverride async toSvg(shape: TLImageShape) {\n\t\tconst g = document.createElementNS('http://www.w3.org/2000/svg', 'g')\n\t\tconst asset = shape.props.assetId ? this.editor.getAsset(shape.props.assetId) : null\n\n\t\tlet src = asset?.props.src || ''\n\t\tif (src && src.startsWith('http')) {\n\t\t\t// If it's a remote image, we need to fetch it and convert it to a data URI\n\t\t\tsrc = (await getDataURIFromURL(src)) || ''\n\t\t}\n\n\t\tconst image = document.createElementNS('http://www.w3.org/2000/svg', 'image')\n\t\timage.setAttributeNS('http://www.w3.org/1999/xlink', 'href', src)\n\t\tconst containerStyle = getContainerStyle(shape)\n\t\tconst crop = shape.props.crop\n\t\tif (containerStyle.transform && crop) {\n\t\t\tconst { transform, width, height } = containerStyle\n\t\t\tconst croppedWidth = (crop.bottomRight.x - crop.topLeft.x) * width\n\t\t\tconst croppedHeight = (crop.bottomRight.y - crop.topLeft.y) * height\n\n\t\t\tconst points = [\n\t\t\t\tnew Vec2d(0, 0),\n\t\t\t\tnew Vec2d(croppedWidth, 0),\n\t\t\t\tnew Vec2d(croppedWidth, croppedHeight),\n\t\t\t\tnew Vec2d(0, croppedHeight),\n\t\t\t]\n\n\t\t\tconst polygon = document.createElementNS('http://www.w3.org/2000/svg', 'polygon')\n\t\t\tpolygon.setAttribute('points', points.map((p) => `${p.x},${p.y}`).join(' '))\n\n\t\t\tconst clipPath = document.createElementNS('http://www.w3.org/2000/svg', 'clipPath')\n\t\t\tclipPath.setAttribute('id', 'cropClipPath')\n\t\t\tclipPath.appendChild(polygon)\n\n\t\t\tconst defs = document.createElementNS('http://www.w3.org/2000/svg', 'defs')\n\t\t\tdefs.appendChild(clipPath)\n\t\t\tg.appendChild(defs)\n\n\t\t\tconst innerElement = document.createElementNS('http://www.w3.org/2000/svg', 'g')\n\t\t\tinnerElement.setAttribute('clip-path', 'url(#cropClipPath)')\n\t\t\timage.setAttribute('width', width.toString())\n\t\t\timage.setAttribute('height', height.toString())\n\t\t\timage.style.transform = transform\n\t\t\tinnerElement.appendChild(image)\n\t\t\tg.appendChild(innerElement)\n\t\t} else {\n\t\t\timage.setAttribute('width', shape.props.w.toString())\n\t\t\timage.setAttribute('height', shape.props.h.toString())\n\t\t\tg.appendChild(image)\n\t\t}\n\n\t\treturn g\n\t}\n\n\toverride onDoubleClick = (shape: TLImageShape) => {\n\t\tconst asset = shape.props.assetId ? this.editor.getAsset(shape.props.assetId) : undefined\n\n\t\tif (!asset) return\n\n\t\tconst canPlay =\n\t\t\tasset.props.src && 'mimeType' in asset.props && asset.props.mimeType === 'image/gif'\n\n\t\tif (!canPlay) return\n\n\t\tthis.editor.updateShapes([\n\t\t\t{\n\t\t\t\ttype: 'image',\n\t\t\t\tid: shape.id,\n\t\t\t\tprops: {\n\t\t\t\t\tplaying: !shape.props.playing,\n\t\t\t\t},\n\t\t\t},\n\t\t])\n\t}\n\n\toverride onDoubleClickEdge: TLOnDoubleClickHandler<TLImageShape> = (shape) => {\n\t\tconst props = shape.props\n\t\tif (!props) return\n\n\t\tif (this.editor.getCroppingShapeId() !== shape.id) {\n\t\t\treturn\n\t\t}\n\n\t\tconst crop = deepCopy(props.crop) || {\n\t\t\ttopLeft: { x: 0, y: 0 },\n\t\t\tbottomRight: { x: 1, y: 1 },\n\t\t}\n\n\t\t// The true asset dimensions\n\t\tconst w = (1 / (crop.bottomRight.x - crop.topLeft.x)) * shape.props.w\n\t\tconst h = (1 / (crop.bottomRight.y - crop.topLeft.y)) * shape.props.h\n\n\t\tconst pointDelta = new Vec2d(crop.topLeft.x * w, crop.topLeft.y * h).rot(shape.rotation)\n\n\t\tconst partial: TLShapePartial<TLImageShape> = {\n\t\t\tid: shape.id,\n\t\t\ttype: shape.type,\n\t\t\tx: shape.x - pointDelta.x,\n\t\t\ty: shape.y - pointDelta.y,\n\t\t\tprops: {\n\t\t\t\tcrop: {\n\t\t\t\t\ttopLeft: { x: 0, y: 0 },\n\t\t\t\t\tbottomRight: { x: 1, y: 1 },\n\t\t\t\t},\n\t\t\t\tw,\n\t\t\t\th,\n\t\t\t},\n\t\t}\n\n\t\tthis.editor.updateShapes([partial])\n\t}\n}\n\n/**\n * When an image is cropped we need to translate the image to show the portion withing the cropped\n * area. We do this by translating the image by the negative of the top left corner of the crop\n * area.\n *\n * @param shape - Shape The image shape for which to get the container style\n * @returns - Styles to apply to the image container\n */\nfunction getContainerStyle(shape: TLImageShape) {\n\tconst crop = shape.props.crop\n\tconst topLeft = crop?.topLeft\n\tif (!topLeft) {\n\t\treturn {\n\t\t\twidth: shape.props.w,\n\t\t\theight: shape.props.h,\n\t\t}\n\t}\n\n\tconst w = (1 / (crop.bottomRight.x - crop.topLeft.x)) * shape.props.w\n\tconst h = (1 / (crop.bottomRight.y - crop.topLeft.y)) * shape.props.h\n\n\tconst offsetX = -topLeft.x * w\n\tconst offsetY = -topLeft.y * h\n\treturn {\n\t\ttransform: `translate(${offsetX}px, ${offsetY}px)`,\n\t\twidth: w,\n\t\theight: h,\n\t}\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
|
@@ -116,7 +116,9 @@ class ImageShapeUtil extends BaseBoxShapeUtil {
|
|
|
116
116
|
{
|
|
117
117
|
className: "tl-image",
|
|
118
118
|
style: {
|
|
119
|
-
backgroundImage: `url(${!shape.props.playing || reduceMotion ? staticFrameSrc : asset.props.src})
|
|
119
|
+
backgroundImage: `url(${!shape.props.playing || reduceMotion ? staticFrameSrc : asset.props.src})`,
|
|
120
|
+
// Force hardware acceleration to prevent Safari flickering issue
|
|
121
|
+
transform: this.editor.environment.isSafari ? "translateZ(0)" : void 0
|
|
120
122
|
},
|
|
121
123
|
draggable: false
|
|
122
124
|
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../../../src/lib/shapes/image/ImageShapeUtil.tsx"],
|
|
4
|
-
"sourcesContent": ["/* eslint-disable react-hooks/rules-of-hooks */\nimport {\n\tBaseBoxShapeUtil,\n\tHTMLContainer,\n\tTLImageShape,\n\tTLOnDoubleClickHandler,\n\tTLShapePartial,\n\tVec2d,\n\tdeepCopy,\n\timageShapeMigrations,\n\timageShapeProps,\n\ttoDomPrecision,\n\tuseIsCropping,\n\tuseValue,\n} from '@bigbluebutton/editor'\nimport { useEffect, useState } from 'react'\nimport { HyperlinkButton } from '../shared/HyperlinkButton'\nimport { usePrefersReducedMotion } from '../shared/usePrefersReducedMotion'\n\nconst loadImage = async (url: string): Promise<HTMLImageElement> => {\n\treturn new Promise((resolve, reject) => {\n\t\tconst image = new Image()\n\t\timage.onload = () => resolve(image)\n\t\timage.onerror = () => reject(new Error('Failed to load image'))\n\t\timage.crossOrigin = 'anonymous'\n\t\timage.src = url\n\t})\n}\n\nconst getStateFrame = async (url: string) => {\n\tconst image = await loadImage(url)\n\n\tconst canvas = document.createElement('canvas')\n\tcanvas.width = image.width\n\tcanvas.height = image.height\n\n\tconst ctx = canvas.getContext('2d')\n\tif (!ctx) return\n\n\tctx.drawImage(image, 0, 0)\n\treturn canvas.toDataURL()\n}\n\nasync function getDataURIFromURL(url: string): Promise<string> {\n\tconst response = await fetch(url)\n\tconst blob = await response.blob()\n\treturn new Promise((resolve, reject) => {\n\t\tconst reader = new FileReader()\n\t\treader.onloadend = () => resolve(reader.result as string)\n\t\treader.onerror = reject\n\t\treader.readAsDataURL(blob)\n\t})\n}\n\n/** @public */\nexport class ImageShapeUtil extends BaseBoxShapeUtil<TLImageShape> {\n\tstatic override type = 'image' as const\n\tstatic override props = imageShapeProps\n\tstatic override migrations = imageShapeMigrations\n\n\toverride isAspectRatioLocked = () => true\n\toverride canCrop = () => true\n\n\toverride getDefaultProps(): TLImageShape['props'] {\n\t\treturn {\n\t\t\tw: 100,\n\t\t\th: 100,\n\t\t\tassetId: null,\n\t\t\tplaying: true,\n\t\t\turl: '',\n\t\t\tcrop: null,\n\t\t}\n\t}\n\n\tcomponent(shape: TLImageShape) {\n\t\tconst containerStyle = getContainerStyle(shape)\n\t\tconst isCropping = useIsCropping(shape.id)\n\t\tconst prefersReducedMotion = usePrefersReducedMotion()\n\t\tconst [staticFrameSrc, setStaticFrameSrc] = useState('')\n\n\t\tconst asset = shape.props.assetId ? this.editor.getAsset(shape.props.assetId) : undefined\n\n\t\tif (asset?.type === 'bookmark') {\n\t\t\tthrow Error(\"Bookmark assets can't be rendered as images\")\n\t\t}\n\n\t\tconst isSelected = useValue(\n\t\t\t'onlySelectedShape',\n\t\t\t() => shape.id === this.editor.getOnlySelectedShape()?.id,\n\t\t\t[this.editor]\n\t\t)\n\n\t\tconst showCropPreview =\n\t\t\tisSelected &&\n\t\t\tisCropping &&\n\t\t\tthis.editor.isInAny('select.crop', 'select.cropping', 'select.pointing_crop_handle')\n\n\t\t// We only want to reduce motion for mimeTypes that have motion\n\t\tconst reduceMotion =\n\t\t\tprefersReducedMotion &&\n\t\t\t(asset?.props.mimeType?.includes('video') || asset?.props.mimeType?.includes('gif'))\n\n\t\tuseEffect(() => {\n\t\t\tif (asset?.props.src && 'mimeType' in asset.props && asset?.props.mimeType === 'image/gif') {\n\t\t\t\tlet cancelled = false\n\t\t\t\tconst run = async () => {\n\t\t\t\t\tconst newStaticFrame = await getStateFrame(asset.props.src!)\n\t\t\t\t\tif (cancelled) return\n\t\t\t\t\tif (newStaticFrame) {\n\t\t\t\t\t\tsetStaticFrameSrc(newStaticFrame)\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\trun()\n\n\t\t\t\treturn () => {\n\t\t\t\t\tcancelled = true\n\t\t\t\t}\n\t\t\t}\n\t\t}, [prefersReducedMotion, asset?.props])\n\n\t\treturn (\n\t\t\t<>\n\t\t\t\t{asset?.props.src && showCropPreview && (\n\t\t\t\t\t<div style={containerStyle}>\n\t\t\t\t\t\t<div\n\t\t\t\t\t\t\tclassName=\"tl-image\"\n\t\t\t\t\t\t\tstyle={{\n\t\t\t\t\t\t\t\topacity: 0.1,\n\t\t\t\t\t\t\t\tbackgroundImage: `url(${\n\t\t\t\t\t\t\t\t\t!shape.props.playing || reduceMotion ? staticFrameSrc : asset.props.src\n\t\t\t\t\t\t\t\t})`,\n\t\t\t\t\t\t\t}}\n\t\t\t\t\t\t\tdraggable={false}\n\t\t\t\t\t\t/>\n\t\t\t\t\t</div>\n\t\t\t\t)}\n\t\t\t\t<HTMLContainer\n\t\t\t\t\tid={shape.id}\n\t\t\t\t\tstyle={{ overflow: 'hidden', width: shape.props.w, height: shape.props.h }}\n\t\t\t\t>\n\t\t\t\t\t<div className=\"tl-image-container\" style={containerStyle}>\n\t\t\t\t\t\t{asset?.props.src ? (\n\t\t\t\t\t\t\t<div\n\t\t\t\t\t\t\t\tclassName=\"tl-image\"\n\t\t\t\t\t\t\t\tstyle={{\n\t\t\t\t\t\t\t\t\tbackgroundImage: `url(${\n\t\t\t\t\t\t\t\t\t\t!shape.props.playing || reduceMotion ? staticFrameSrc : asset.props.src\n\t\t\t\t\t\t\t\t\t})`,\n\t\t\t\t\t\t\t\t}}\n\t\t\t\t\t\t\t\tdraggable={false}\n\t\t\t\t\t\t\t/>\n\t\t\t\t\t\t) : null}\n\t\t\t\t\t\t{asset?.props.isAnimated && !shape.props.playing && (\n\t\t\t\t\t\t\t<div className=\"tl-image__tg\">GIF</div>\n\t\t\t\t\t\t)}\n\t\t\t\t\t</div>\n\t\t\t\t\t{'url' in shape.props && shape.props.url && (\n\t\t\t\t\t\t<HyperlinkButton url={shape.props.url} zoomLevel={this.editor.getZoomLevel()} />\n\t\t\t\t\t)}\n\t\t\t\t</HTMLContainer>\n\t\t\t</>\n\t\t)\n\t}\n\n\tindicator(shape: TLImageShape) {\n\t\tconst isCropping = useIsCropping(shape.id)\n\t\tif (isCropping) {\n\t\t\treturn null\n\t\t}\n\t\treturn <rect width={toDomPrecision(shape.props.w)} height={toDomPrecision(shape.props.h)} />\n\t}\n\n\toverride async toSvg(shape: TLImageShape) {\n\t\tconst g = document.createElementNS('http://www.w3.org/2000/svg', 'g')\n\t\tconst asset = shape.props.assetId ? this.editor.getAsset(shape.props.assetId) : null\n\n\t\tlet src = asset?.props.src || ''\n\t\tif (src && src.startsWith('http')) {\n\t\t\t// If it's a remote image, we need to fetch it and convert it to a data URI\n\t\t\tsrc = (await getDataURIFromURL(src)) || ''\n\t\t}\n\n\t\tconst image = document.createElementNS('http://www.w3.org/2000/svg', 'image')\n\t\timage.setAttributeNS('http://www.w3.org/1999/xlink', 'href', src)\n\t\tconst containerStyle = getContainerStyle(shape)\n\t\tconst crop = shape.props.crop\n\t\tif (containerStyle.transform && crop) {\n\t\t\tconst { transform, width, height } = containerStyle\n\t\t\tconst croppedWidth = (crop.bottomRight.x - crop.topLeft.x) * width\n\t\t\tconst croppedHeight = (crop.bottomRight.y - crop.topLeft.y) * height\n\n\t\t\tconst points = [\n\t\t\t\tnew Vec2d(0, 0),\n\t\t\t\tnew Vec2d(croppedWidth, 0),\n\t\t\t\tnew Vec2d(croppedWidth, croppedHeight),\n\t\t\t\tnew Vec2d(0, croppedHeight),\n\t\t\t]\n\n\t\t\tconst polygon = document.createElementNS('http://www.w3.org/2000/svg', 'polygon')\n\t\t\tpolygon.setAttribute('points', points.map((p) => `${p.x},${p.y}`).join(' '))\n\n\t\t\tconst clipPath = document.createElementNS('http://www.w3.org/2000/svg', 'clipPath')\n\t\t\tclipPath.setAttribute('id', 'cropClipPath')\n\t\t\tclipPath.appendChild(polygon)\n\n\t\t\tconst defs = document.createElementNS('http://www.w3.org/2000/svg', 'defs')\n\t\t\tdefs.appendChild(clipPath)\n\t\t\tg.appendChild(defs)\n\n\t\t\tconst innerElement = document.createElementNS('http://www.w3.org/2000/svg', 'g')\n\t\t\tinnerElement.setAttribute('clip-path', 'url(#cropClipPath)')\n\t\t\timage.setAttribute('width', width.toString())\n\t\t\timage.setAttribute('height', height.toString())\n\t\t\timage.style.transform = transform\n\t\t\tinnerElement.appendChild(image)\n\t\t\tg.appendChild(innerElement)\n\t\t} else {\n\t\t\timage.setAttribute('width', shape.props.w.toString())\n\t\t\timage.setAttribute('height', shape.props.h.toString())\n\t\t\tg.appendChild(image)\n\t\t}\n\n\t\treturn g\n\t}\n\n\toverride onDoubleClick = (shape: TLImageShape) => {\n\t\tconst asset = shape.props.assetId ? this.editor.getAsset(shape.props.assetId) : undefined\n\n\t\tif (!asset) return\n\n\t\tconst canPlay =\n\t\t\tasset.props.src && 'mimeType' in asset.props && asset.props.mimeType === 'image/gif'\n\n\t\tif (!canPlay) return\n\n\t\tthis.editor.updateShapes([\n\t\t\t{\n\t\t\t\ttype: 'image',\n\t\t\t\tid: shape.id,\n\t\t\t\tprops: {\n\t\t\t\t\tplaying: !shape.props.playing,\n\t\t\t\t},\n\t\t\t},\n\t\t])\n\t}\n\n\toverride onDoubleClickEdge: TLOnDoubleClickHandler<TLImageShape> = (shape) => {\n\t\tconst props = shape.props\n\t\tif (!props) return\n\n\t\tif (this.editor.getCroppingShapeId() !== shape.id) {\n\t\t\treturn\n\t\t}\n\n\t\tconst crop = deepCopy(props.crop) || {\n\t\t\ttopLeft: { x: 0, y: 0 },\n\t\t\tbottomRight: { x: 1, y: 1 },\n\t\t}\n\n\t\t// The true asset dimensions\n\t\tconst w = (1 / (crop.bottomRight.x - crop.topLeft.x)) * shape.props.w\n\t\tconst h = (1 / (crop.bottomRight.y - crop.topLeft.y)) * shape.props.h\n\n\t\tconst pointDelta = new Vec2d(crop.topLeft.x * w, crop.topLeft.y * h).rot(shape.rotation)\n\n\t\tconst partial: TLShapePartial<TLImageShape> = {\n\t\t\tid: shape.id,\n\t\t\ttype: shape.type,\n\t\t\tx: shape.x - pointDelta.x,\n\t\t\ty: shape.y - pointDelta.y,\n\t\t\tprops: {\n\t\t\t\tcrop: {\n\t\t\t\t\ttopLeft: { x: 0, y: 0 },\n\t\t\t\t\tbottomRight: { x: 1, y: 1 },\n\t\t\t\t},\n\t\t\t\tw,\n\t\t\t\th,\n\t\t\t},\n\t\t}\n\n\t\tthis.editor.updateShapes([partial])\n\t}\n}\n\n/**\n * When an image is cropped we need to translate the image to show the portion withing the cropped\n * area. We do this by translating the image by the negative of the top left corner of the crop\n * area.\n *\n * @param shape - Shape The image shape for which to get the container style\n * @returns - Styles to apply to the image container\n */\nfunction getContainerStyle(shape: TLImageShape) {\n\tconst crop = shape.props.crop\n\tconst topLeft = crop?.topLeft\n\tif (!topLeft) {\n\t\treturn {\n\t\t\twidth: shape.props.w,\n\t\t\theight: shape.props.h,\n\t\t}\n\t}\n\n\tconst w = (1 / (crop.bottomRight.x - crop.topLeft.x)) * shape.props.w\n\tconst h = (1 / (crop.bottomRight.y - crop.topLeft.y)) * shape.props.h\n\n\tconst offsetX = -topLeft.x * w\n\tconst offsetY = -topLeft.y * h\n\treturn {\n\t\ttransform: `translate(${offsetX}px, ${offsetY}px)`,\n\t\twidth: w,\n\t\theight: h,\n\t}\n}\n"],
|
|
5
|
-
"mappings": "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
|
|
4
|
+
"sourcesContent": ["/* eslint-disable react-hooks/rules-of-hooks */\nimport {\n\tBaseBoxShapeUtil,\n\tHTMLContainer,\n\tTLImageShape,\n\tTLOnDoubleClickHandler,\n\tTLShapePartial,\n\tVec2d,\n\tdeepCopy,\n\timageShapeMigrations,\n\timageShapeProps,\n\ttoDomPrecision,\n\tuseIsCropping,\n\tuseValue,\n} from '@bigbluebutton/editor'\nimport { useEffect, useState } from 'react'\nimport { HyperlinkButton } from '../shared/HyperlinkButton'\nimport { usePrefersReducedMotion } from '../shared/usePrefersReducedMotion'\n\nconst loadImage = async (url: string): Promise<HTMLImageElement> => {\n\treturn new Promise((resolve, reject) => {\n\t\tconst image = new Image()\n\t\timage.onload = () => resolve(image)\n\t\timage.onerror = () => reject(new Error('Failed to load image'))\n\t\timage.crossOrigin = 'anonymous'\n\t\timage.src = url\n\t})\n}\n\nconst getStateFrame = async (url: string) => {\n\tconst image = await loadImage(url)\n\n\tconst canvas = document.createElement('canvas')\n\tcanvas.width = image.width\n\tcanvas.height = image.height\n\n\tconst ctx = canvas.getContext('2d')\n\tif (!ctx) return\n\n\tctx.drawImage(image, 0, 0)\n\treturn canvas.toDataURL()\n}\n\nasync function getDataURIFromURL(url: string): Promise<string> {\n\tconst response = await fetch(url)\n\tconst blob = await response.blob()\n\treturn new Promise((resolve, reject) => {\n\t\tconst reader = new FileReader()\n\t\treader.onloadend = () => resolve(reader.result as string)\n\t\treader.onerror = reject\n\t\treader.readAsDataURL(blob)\n\t})\n}\n\n/** @public */\nexport class ImageShapeUtil extends BaseBoxShapeUtil<TLImageShape> {\n\tstatic override type = 'image' as const\n\tstatic override props = imageShapeProps\n\tstatic override migrations = imageShapeMigrations\n\n\toverride isAspectRatioLocked = () => true\n\toverride canCrop = () => true\n\n\toverride getDefaultProps(): TLImageShape['props'] {\n\t\treturn {\n\t\t\tw: 100,\n\t\t\th: 100,\n\t\t\tassetId: null,\n\t\t\tplaying: true,\n\t\t\turl: '',\n\t\t\tcrop: null,\n\t\t}\n\t}\n\n\tcomponent(shape: TLImageShape) {\n\t\tconst containerStyle = getContainerStyle(shape)\n\t\tconst isCropping = useIsCropping(shape.id)\n\t\tconst prefersReducedMotion = usePrefersReducedMotion()\n\t\tconst [staticFrameSrc, setStaticFrameSrc] = useState('')\n\n\t\tconst asset = shape.props.assetId ? this.editor.getAsset(shape.props.assetId) : undefined\n\n\t\tif (asset?.type === 'bookmark') {\n\t\t\tthrow Error(\"Bookmark assets can't be rendered as images\")\n\t\t}\n\n\t\tconst isSelected = useValue(\n\t\t\t'onlySelectedShape',\n\t\t\t() => shape.id === this.editor.getOnlySelectedShape()?.id,\n\t\t\t[this.editor]\n\t\t)\n\n\t\tconst showCropPreview =\n\t\t\tisSelected &&\n\t\t\tisCropping &&\n\t\t\tthis.editor.isInAny('select.crop', 'select.cropping', 'select.pointing_crop_handle')\n\n\t\t// We only want to reduce motion for mimeTypes that have motion\n\t\tconst reduceMotion =\n\t\t\tprefersReducedMotion &&\n\t\t\t(asset?.props.mimeType?.includes('video') || asset?.props.mimeType?.includes('gif'))\n\n\t\tuseEffect(() => {\n\t\t\tif (asset?.props.src && 'mimeType' in asset.props && asset?.props.mimeType === 'image/gif') {\n\t\t\t\tlet cancelled = false\n\t\t\t\tconst run = async () => {\n\t\t\t\t\tconst newStaticFrame = await getStateFrame(asset.props.src!)\n\t\t\t\t\tif (cancelled) return\n\t\t\t\t\tif (newStaticFrame) {\n\t\t\t\t\t\tsetStaticFrameSrc(newStaticFrame)\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\trun()\n\n\t\t\t\treturn () => {\n\t\t\t\t\tcancelled = true\n\t\t\t\t}\n\t\t\t}\n\t\t}, [prefersReducedMotion, asset?.props])\n\n\t\treturn (\n\t\t\t<>\n\t\t\t\t{asset?.props.src && showCropPreview && (\n\t\t\t\t\t<div style={containerStyle}>\n\t\t\t\t\t\t<div\n\t\t\t\t\t\t\tclassName=\"tl-image\"\n\t\t\t\t\t\t\tstyle={{\n\t\t\t\t\t\t\t\topacity: 0.1,\n\t\t\t\t\t\t\t\tbackgroundImage: `url(${\n\t\t\t\t\t\t\t\t\t!shape.props.playing || reduceMotion ? staticFrameSrc : asset.props.src\n\t\t\t\t\t\t\t\t})`,\n\t\t\t\t\t\t\t}}\n\t\t\t\t\t\t\tdraggable={false}\n\t\t\t\t\t\t/>\n\t\t\t\t\t</div>\n\t\t\t\t)}\n\t\t\t\t<HTMLContainer\n\t\t\t\t\tid={shape.id}\n\t\t\t\t\tstyle={{ overflow: 'hidden', width: shape.props.w, height: shape.props.h }}\n\t\t\t\t>\n\t\t\t\t\t<div className=\"tl-image-container\" style={containerStyle}>\n\t\t\t\t\t\t{asset?.props.src ? (\n\t\t\t\t\t\t\t<div\n\t\t\t\t\t\t\t\tclassName=\"tl-image\"\n\t\t\t\t\t\t\t\tstyle={{\n\t\t\t\t\t\t\t\t\tbackgroundImage: `url(${\n\t\t\t\t\t\t\t\t\t\t!shape.props.playing || reduceMotion ? staticFrameSrc : asset.props.src\n\t\t\t\t\t\t\t\t\t})`,\n\t\t\t\t\t\t\t\t\t// Force hardware acceleration to prevent Safari flickering issue\n\t\t\t\t\t\t\t\t\ttransform: this.editor.environment.isSafari ? 'translateZ(0)' : undefined,\n\t\t\t\t\t\t\t\t}}\n\t\t\t\t\t\t\t\tdraggable={false}\n\t\t\t\t\t\t\t/>\n\t\t\t\t\t\t) : null}\n\t\t\t\t\t\t{asset?.props.isAnimated && !shape.props.playing && (\n\t\t\t\t\t\t\t<div className=\"tl-image__tg\">GIF</div>\n\t\t\t\t\t\t)}\n\t\t\t\t\t</div>\n\t\t\t\t\t{'url' in shape.props && shape.props.url && (\n\t\t\t\t\t\t<HyperlinkButton url={shape.props.url} zoomLevel={this.editor.getZoomLevel()} />\n\t\t\t\t\t)}\n\t\t\t\t</HTMLContainer>\n\t\t\t</>\n\t\t)\n\t}\n\n\tindicator(shape: TLImageShape) {\n\t\tconst isCropping = useIsCropping(shape.id)\n\t\tif (isCropping) {\n\t\t\treturn null\n\t\t}\n\t\treturn <rect width={toDomPrecision(shape.props.w)} height={toDomPrecision(shape.props.h)} />\n\t}\n\n\toverride async toSvg(shape: TLImageShape) {\n\t\tconst g = document.createElementNS('http://www.w3.org/2000/svg', 'g')\n\t\tconst asset = shape.props.assetId ? this.editor.getAsset(shape.props.assetId) : null\n\n\t\tlet src = asset?.props.src || ''\n\t\tif (src && src.startsWith('http')) {\n\t\t\t// If it's a remote image, we need to fetch it and convert it to a data URI\n\t\t\tsrc = (await getDataURIFromURL(src)) || ''\n\t\t}\n\n\t\tconst image = document.createElementNS('http://www.w3.org/2000/svg', 'image')\n\t\timage.setAttributeNS('http://www.w3.org/1999/xlink', 'href', src)\n\t\tconst containerStyle = getContainerStyle(shape)\n\t\tconst crop = shape.props.crop\n\t\tif (containerStyle.transform && crop) {\n\t\t\tconst { transform, width, height } = containerStyle\n\t\t\tconst croppedWidth = (crop.bottomRight.x - crop.topLeft.x) * width\n\t\t\tconst croppedHeight = (crop.bottomRight.y - crop.topLeft.y) * height\n\n\t\t\tconst points = [\n\t\t\t\tnew Vec2d(0, 0),\n\t\t\t\tnew Vec2d(croppedWidth, 0),\n\t\t\t\tnew Vec2d(croppedWidth, croppedHeight),\n\t\t\t\tnew Vec2d(0, croppedHeight),\n\t\t\t]\n\n\t\t\tconst polygon = document.createElementNS('http://www.w3.org/2000/svg', 'polygon')\n\t\t\tpolygon.setAttribute('points', points.map((p) => `${p.x},${p.y}`).join(' '))\n\n\t\t\tconst clipPath = document.createElementNS('http://www.w3.org/2000/svg', 'clipPath')\n\t\t\tclipPath.setAttribute('id', 'cropClipPath')\n\t\t\tclipPath.appendChild(polygon)\n\n\t\t\tconst defs = document.createElementNS('http://www.w3.org/2000/svg', 'defs')\n\t\t\tdefs.appendChild(clipPath)\n\t\t\tg.appendChild(defs)\n\n\t\t\tconst innerElement = document.createElementNS('http://www.w3.org/2000/svg', 'g')\n\t\t\tinnerElement.setAttribute('clip-path', 'url(#cropClipPath)')\n\t\t\timage.setAttribute('width', width.toString())\n\t\t\timage.setAttribute('height', height.toString())\n\t\t\timage.style.transform = transform\n\t\t\tinnerElement.appendChild(image)\n\t\t\tg.appendChild(innerElement)\n\t\t} else {\n\t\t\timage.setAttribute('width', shape.props.w.toString())\n\t\t\timage.setAttribute('height', shape.props.h.toString())\n\t\t\tg.appendChild(image)\n\t\t}\n\n\t\treturn g\n\t}\n\n\toverride onDoubleClick = (shape: TLImageShape) => {\n\t\tconst asset = shape.props.assetId ? this.editor.getAsset(shape.props.assetId) : undefined\n\n\t\tif (!asset) return\n\n\t\tconst canPlay =\n\t\t\tasset.props.src && 'mimeType' in asset.props && asset.props.mimeType === 'image/gif'\n\n\t\tif (!canPlay) return\n\n\t\tthis.editor.updateShapes([\n\t\t\t{\n\t\t\t\ttype: 'image',\n\t\t\t\tid: shape.id,\n\t\t\t\tprops: {\n\t\t\t\t\tplaying: !shape.props.playing,\n\t\t\t\t},\n\t\t\t},\n\t\t])\n\t}\n\n\toverride onDoubleClickEdge: TLOnDoubleClickHandler<TLImageShape> = (shape) => {\n\t\tconst props = shape.props\n\t\tif (!props) return\n\n\t\tif (this.editor.getCroppingShapeId() !== shape.id) {\n\t\t\treturn\n\t\t}\n\n\t\tconst crop = deepCopy(props.crop) || {\n\t\t\ttopLeft: { x: 0, y: 0 },\n\t\t\tbottomRight: { x: 1, y: 1 },\n\t\t}\n\n\t\t// The true asset dimensions\n\t\tconst w = (1 / (crop.bottomRight.x - crop.topLeft.x)) * shape.props.w\n\t\tconst h = (1 / (crop.bottomRight.y - crop.topLeft.y)) * shape.props.h\n\n\t\tconst pointDelta = new Vec2d(crop.topLeft.x * w, crop.topLeft.y * h).rot(shape.rotation)\n\n\t\tconst partial: TLShapePartial<TLImageShape> = {\n\t\t\tid: shape.id,\n\t\t\ttype: shape.type,\n\t\t\tx: shape.x - pointDelta.x,\n\t\t\ty: shape.y - pointDelta.y,\n\t\t\tprops: {\n\t\t\t\tcrop: {\n\t\t\t\t\ttopLeft: { x: 0, y: 0 },\n\t\t\t\t\tbottomRight: { x: 1, y: 1 },\n\t\t\t\t},\n\t\t\t\tw,\n\t\t\t\th,\n\t\t\t},\n\t\t}\n\n\t\tthis.editor.updateShapes([partial])\n\t}\n}\n\n/**\n * When an image is cropped we need to translate the image to show the portion withing the cropped\n * area. We do this by translating the image by the negative of the top left corner of the crop\n * area.\n *\n * @param shape - Shape The image shape for which to get the container style\n * @returns - Styles to apply to the image container\n */\nfunction getContainerStyle(shape: TLImageShape) {\n\tconst crop = shape.props.crop\n\tconst topLeft = crop?.topLeft\n\tif (!topLeft) {\n\t\treturn {\n\t\t\twidth: shape.props.w,\n\t\t\theight: shape.props.h,\n\t\t}\n\t}\n\n\tconst w = (1 / (crop.bottomRight.x - crop.topLeft.x)) * shape.props.w\n\tconst h = (1 / (crop.bottomRight.y - crop.topLeft.y)) * shape.props.h\n\n\tconst offsetX = -topLeft.x * w\n\tconst offsetY = -topLeft.y * h\n\treturn {\n\t\ttransform: `translate(${offsetX}px, ${offsetY}px)`,\n\t\twidth: w,\n\t\theight: h,\n\t}\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@bigbluebutton/tldraw",
|
|
3
3
|
"description": "BigBlueButton's fork of tldraw 2.0-alpha.19 - A tiny little drawing editor.",
|
|
4
|
-
"version": "2.0.0-alpha.
|
|
4
|
+
"version": "2.0.0-alpha.32",
|
|
5
5
|
"packageManager": "yarn@3.5.0",
|
|
6
6
|
"author": {
|
|
7
7
|
"name": "tldraw GB Ltd.",
|
|
@@ -35,7 +35,7 @@
|
|
|
35
35
|
"src"
|
|
36
36
|
],
|
|
37
37
|
"dependencies": {
|
|
38
|
-
"@bigbluebutton/editor": "2.0.0-alpha.
|
|
38
|
+
"@bigbluebutton/editor": "2.0.0-alpha.32",
|
|
39
39
|
"@radix-ui/react-alert-dialog": "^1.0.0",
|
|
40
40
|
"@radix-ui/react-context-menu": "^2.1.5",
|
|
41
41
|
"@radix-ui/react-dialog": "^1.0.5",
|
|
@@ -146,6 +146,8 @@ export class ImageShapeUtil extends BaseBoxShapeUtil<TLImageShape> {
|
|
|
146
146
|
backgroundImage: `url(${
|
|
147
147
|
!shape.props.playing || reduceMotion ? staticFrameSrc : asset.props.src
|
|
148
148
|
})`,
|
|
149
|
+
// Force hardware acceleration to prevent Safari flickering issue
|
|
150
|
+
transform: this.editor.environment.isSafari ? 'translateZ(0)' : undefined,
|
|
149
151
|
}}
|
|
150
152
|
draggable={false}
|
|
151
153
|
/>
|