@bigbluebutton/tldraw 2.0.0-alpha.31 → 2.0.0-alpha.33
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-cjs/lib/shapes/poll/PollShapeUtil.js +1 -1
- package/dist-cjs/lib/shapes/poll/PollShapeUtil.js.map +2 -2
- package/dist-cjs/lib/shapes/poll/components/poll-content.js +13 -2
- package/dist-cjs/lib/shapes/poll/components/poll-content.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/dist-esm/lib/shapes/poll/PollShapeUtil.mjs +1 -1
- package/dist-esm/lib/shapes/poll/PollShapeUtil.mjs.map +2 -2
- package/dist-esm/lib/shapes/poll/components/poll-content.mjs +13 -2
- package/dist-esm/lib/shapes/poll/components/poll-content.mjs.map +2 -2
- package/package.json +2 -2
- package/src/lib/shapes/image/ImageShapeUtil.tsx +2 -0
- package/src/lib/shapes/poll/PollShapeUtil.tsx +1 -1
- package/src/lib/shapes/poll/components/poll-content.tsx +11 -1
|
@@ -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
|
}
|
|
@@ -106,7 +106,7 @@ class PollShapeUtil extends import_editor.ShapeUtil {
|
|
|
106
106
|
overflow: "hidden",
|
|
107
107
|
position: "relative"
|
|
108
108
|
},
|
|
109
|
-
children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_poll_content.default, { metadata: pollMetadata, height: adjustedHeight })
|
|
109
|
+
children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_poll_content.default, { metadata: pollMetadata, height: adjustedHeight, width })
|
|
110
110
|
}
|
|
111
111
|
)
|
|
112
112
|
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../../../src/lib/shapes/poll/PollShapeUtil.tsx"],
|
|
4
|
-
"sourcesContent": ["import {\n\tHTMLContainer,\n\tRectangle2d,\n\tShapeUtil,\n\tTLOnResizeHandler,\n\tgetDefaultColorTheme,\n\tresizeBox,\n} from '@bigbluebutton/editor'\n\nimport ChatPollContent from './components/poll-content'\nimport { pollShapeMigrations } from './poll-shape-migrations'\nimport { pollShapeProps } from './poll-shape-props'\nimport { IPollShape } from './poll-shape-types'\n\nexport class PollShapeUtil extends ShapeUtil<IPollShape> {\n\tstatic override type = 'poll' as const\n\n\tstatic override props = pollShapeProps\n\n\tstatic override migrations = pollShapeMigrations\n\n\toverride isAspectRatioLocked = (_shape: IPollShape) => false\n\n\toverride canResize = (_shape: IPollShape) => true\n\n\toverride canBind = (_shape: IPollShape) => true\n\n\tgetDefaultProps(): IPollShape['props'] {\n\t\treturn {\n\t\t\tw: 300,\n\t\t\th: 300,\n\t\t\tcolor: 'black',\n\t\t\tfill: 'white',\n\t\t\tquestion: '',\n\t\t\tnumRespondents: 0,\n\t\t\tnumResponders: 0,\n\t\t\tquestionText: '',\n\t\t\tquestionType: '',\n\t\t\tanswers: [],\n\t\t}\n\t}\n\n\tgetGeometry(shape: IPollShape) {\n\t\treturn new Rectangle2d({\n\t\t\twidth: shape.props.w,\n\t\t\theight: shape.props.h,\n\t\t\tisFilled: true,\n\t\t})\n\t}\n\n\tcomponent(shape: IPollShape) {\n\t\t// Use the shape's specified width and height, falling back to defaults if missing or invalid.\n\t\tconst width = shape.props.w > 0 ? shape.props.w : 300\n\t\tconst height = shape.props.h > 0 ? shape.props.h : 300\n\n\t\tconst theme = getDefaultColorTheme({\n\t\t\tisDarkMode: this.editor.user.getIsDarkMode(),\n\t\t})\n\n\t\t// const contentRef = React.useRef<HTMLDivElement>(null)\n\t\tconst pollMetadata = JSON.stringify({\n\t\t\tid: shape.id,\n\t\t\tquestion: shape.props.question,\n\t\t\tnumRespondents: shape.props.numRespondents,\n\t\t\tnumResponders: shape.props.numResponders,\n\t\t\tquestionText: shape.props.questionText,\n\t\t\tquestionType: shape.props.questionType,\n\t\t\tanswers: shape.props.answers,\n\t\t})\n\n\t\tconst adjustedHeight = shape.props.questionText.length > 0 ? height - 75 : height\n\n\t\treturn (\n\t\t\t<HTMLContainer\n\t\t\t\tid={shape.id}\n\t\t\t\tstyle={{\n\t\t\t\t\tborder: '1px solid #8B9AA8',\n\t\t\t\t\tborderRadius: '4px',\n\t\t\t\t\tboxShadow: '0px 0px 4px 0px rgba(0, 0, 0, 0.20)',\n\t\t\t\t\tdisplay: 'flex',\n\t\t\t\t\tflexDirection: 'column',\n\t\t\t\t\talignItems: 'center',\n\t\t\t\t\tjustifyContent: 'center',\n\t\t\t\t\tpointerEvents: 'all',\n\t\t\t\t\tbackgroundColor: theme[shape.props.color].semi,\n\t\t\t\t\tcolor: theme[shape.props.color].solid,\n\t\t\t\t}}\n\t\t\t>\n\t\t\t\t<div\n\t\t\t\t\tstyle={{\n\t\t\t\t\t\twidth: `${width}px`,\n\t\t\t\t\t\theight: `${height}px`,\n\t\t\t\t\t\toverflow: 'hidden',\n\t\t\t\t\t\tposition: 'relative',\n\t\t\t\t\t}}\n\t\t\t\t>\n\t\t\t\t\t<ChatPollContent metadata={pollMetadata} height={adjustedHeight} />\n\t\t\t\t</div>\n\t\t\t</HTMLContainer>\n\t\t)\n\t}\n\n\tindicator(shape: IPollShape) {\n\t\treturn <rect width={shape.props.w} height={shape.props.h} />\n\t}\n\n\toverride onResize: TLOnResizeHandler<IPollShape> = (shape, info) => {\n\t\treturn resizeBox(shape, info)\n\t}\n}\n\nexport default PollShapeUtil\n"],
|
|
5
|
-
"mappings": ";;;;;;;;;;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAgGK;AAhGL,oBAOO;AAEP,0BAA4B;AAC5B,mCAAoC;AACpC,8BAA+B;AAGxB,MAAM,sBAAsB,wBAAsB;AAAA,EACxD,OAAgB,OAAO;AAAA,EAEvB,OAAgB,QAAQ;AAAA,EAExB,OAAgB,aAAa;AAAA,EAEpB,sBAAsB,CAAC,WAAuB;AAAA,EAE9C,YAAY,CAAC,WAAuB;AAAA,EAEpC,UAAU,CAAC,WAAuB;AAAA,EAE3C,kBAAuC;AACtC,WAAO;AAAA,MACN,GAAG;AAAA,MACH,GAAG;AAAA,MACH,OAAO;AAAA,MACP,MAAM;AAAA,MACN,UAAU;AAAA,MACV,gBAAgB;AAAA,MAChB,eAAe;AAAA,MACf,cAAc;AAAA,MACd,cAAc;AAAA,MACd,SAAS,CAAC;AAAA,IACX;AAAA,EACD;AAAA,EAEA,YAAY,OAAmB;AAC9B,WAAO,IAAI,0BAAY;AAAA,MACtB,OAAO,MAAM,MAAM;AAAA,MACnB,QAAQ,MAAM,MAAM;AAAA,MACpB,UAAU;AAAA,IACX,CAAC;AAAA,EACF;AAAA,EAEA,UAAU,OAAmB;AAE5B,UAAM,QAAQ,MAAM,MAAM,IAAI,IAAI,MAAM,MAAM,IAAI;AAClD,UAAM,SAAS,MAAM,MAAM,IAAI,IAAI,MAAM,MAAM,IAAI;AAEnD,UAAM,YAAQ,oCAAqB;AAAA,MAClC,YAAY,KAAK,OAAO,KAAK,cAAc;AAAA,IAC5C,CAAC;AAGD,UAAM,eAAe,KAAK,UAAU;AAAA,MACnC,IAAI,MAAM;AAAA,MACV,UAAU,MAAM,MAAM;AAAA,MACtB,gBAAgB,MAAM,MAAM;AAAA,MAC5B,eAAe,MAAM,MAAM;AAAA,MAC3B,cAAc,MAAM,MAAM;AAAA,MAC1B,cAAc,MAAM,MAAM;AAAA,MAC1B,SAAS,MAAM,MAAM;AAAA,IACtB,CAAC;AAED,UAAM,iBAAiB,MAAM,MAAM,aAAa,SAAS,IAAI,SAAS,KAAK;AAE3E,WACC;AAAA,MAAC;AAAA;AAAA,QACA,IAAI,MAAM;AAAA,QACV,OAAO;AAAA,UACN,QAAQ;AAAA,UACR,cAAc;AAAA,UACd,WAAW;AAAA,UACX,SAAS;AAAA,UACT,eAAe;AAAA,UACf,YAAY;AAAA,UACZ,gBAAgB;AAAA,UAChB,eAAe;AAAA,UACf,iBAAiB,MAAM,MAAM,MAAM,KAAK,EAAE;AAAA,UAC1C,OAAO,MAAM,MAAM,MAAM,KAAK,EAAE;AAAA,QACjC;AAAA,QAEA;AAAA,UAAC;AAAA;AAAA,YACA,OAAO;AAAA,cACN,OAAO,GAAG,KAAK;AAAA,cACf,QAAQ,GAAG,MAAM;AAAA,cACjB,UAAU;AAAA,cACV,UAAU;AAAA,YACX;AAAA,YAEA,sDAAC,oBAAAA,SAAA,EAAgB,UAAU,cAAc,QAAQ,gBAAgB;AAAA;AAAA,
|
|
4
|
+
"sourcesContent": ["import {\n\tHTMLContainer,\n\tRectangle2d,\n\tShapeUtil,\n\tTLOnResizeHandler,\n\tgetDefaultColorTheme,\n\tresizeBox,\n} from '@bigbluebutton/editor'\n\nimport ChatPollContent from './components/poll-content'\nimport { pollShapeMigrations } from './poll-shape-migrations'\nimport { pollShapeProps } from './poll-shape-props'\nimport { IPollShape } from './poll-shape-types'\n\nexport class PollShapeUtil extends ShapeUtil<IPollShape> {\n\tstatic override type = 'poll' as const\n\n\tstatic override props = pollShapeProps\n\n\tstatic override migrations = pollShapeMigrations\n\n\toverride isAspectRatioLocked = (_shape: IPollShape) => false\n\n\toverride canResize = (_shape: IPollShape) => true\n\n\toverride canBind = (_shape: IPollShape) => true\n\n\tgetDefaultProps(): IPollShape['props'] {\n\t\treturn {\n\t\t\tw: 300,\n\t\t\th: 300,\n\t\t\tcolor: 'black',\n\t\t\tfill: 'white',\n\t\t\tquestion: '',\n\t\t\tnumRespondents: 0,\n\t\t\tnumResponders: 0,\n\t\t\tquestionText: '',\n\t\t\tquestionType: '',\n\t\t\tanswers: [],\n\t\t}\n\t}\n\n\tgetGeometry(shape: IPollShape) {\n\t\treturn new Rectangle2d({\n\t\t\twidth: shape.props.w,\n\t\t\theight: shape.props.h,\n\t\t\tisFilled: true,\n\t\t})\n\t}\n\n\tcomponent(shape: IPollShape) {\n\t\t// Use the shape's specified width and height, falling back to defaults if missing or invalid.\n\t\tconst width = shape.props.w > 0 ? shape.props.w : 300\n\t\tconst height = shape.props.h > 0 ? shape.props.h : 300\n\n\t\tconst theme = getDefaultColorTheme({\n\t\t\tisDarkMode: this.editor.user.getIsDarkMode(),\n\t\t})\n\n\t\t// const contentRef = React.useRef<HTMLDivElement>(null)\n\t\tconst pollMetadata = JSON.stringify({\n\t\t\tid: shape.id,\n\t\t\tquestion: shape.props.question,\n\t\t\tnumRespondents: shape.props.numRespondents,\n\t\t\tnumResponders: shape.props.numResponders,\n\t\t\tquestionText: shape.props.questionText,\n\t\t\tquestionType: shape.props.questionType,\n\t\t\tanswers: shape.props.answers,\n\t\t})\n\n\t\tconst adjustedHeight = shape.props.questionText.length > 0 ? height - 75 : height\n\n\t\treturn (\n\t\t\t<HTMLContainer\n\t\t\t\tid={shape.id}\n\t\t\t\tstyle={{\n\t\t\t\t\tborder: '1px solid #8B9AA8',\n\t\t\t\t\tborderRadius: '4px',\n\t\t\t\t\tboxShadow: '0px 0px 4px 0px rgba(0, 0, 0, 0.20)',\n\t\t\t\t\tdisplay: 'flex',\n\t\t\t\t\tflexDirection: 'column',\n\t\t\t\t\talignItems: 'center',\n\t\t\t\t\tjustifyContent: 'center',\n\t\t\t\t\tpointerEvents: 'all',\n\t\t\t\t\tbackgroundColor: theme[shape.props.color].semi,\n\t\t\t\t\tcolor: theme[shape.props.color].solid,\n\t\t\t\t}}\n\t\t\t>\n\t\t\t\t<div\n\t\t\t\t\tstyle={{\n\t\t\t\t\t\twidth: `${width}px`,\n\t\t\t\t\t\theight: `${height}px`,\n\t\t\t\t\t\toverflow: 'hidden',\n\t\t\t\t\t\tposition: 'relative',\n\t\t\t\t\t}}\n\t\t\t\t>\n\t\t\t\t\t<ChatPollContent metadata={pollMetadata} height={adjustedHeight} width={width} />\n\t\t\t\t</div>\n\t\t\t</HTMLContainer>\n\t\t)\n\t}\n\n\tindicator(shape: IPollShape) {\n\t\treturn <rect width={shape.props.w} height={shape.props.h} />\n\t}\n\n\toverride onResize: TLOnResizeHandler<IPollShape> = (shape, info) => {\n\t\treturn resizeBox(shape, info)\n\t}\n}\n\nexport default PollShapeUtil\n"],
|
|
5
|
+
"mappings": ";;;;;;;;;;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAgGK;AAhGL,oBAOO;AAEP,0BAA4B;AAC5B,mCAAoC;AACpC,8BAA+B;AAGxB,MAAM,sBAAsB,wBAAsB;AAAA,EACxD,OAAgB,OAAO;AAAA,EAEvB,OAAgB,QAAQ;AAAA,EAExB,OAAgB,aAAa;AAAA,EAEpB,sBAAsB,CAAC,WAAuB;AAAA,EAE9C,YAAY,CAAC,WAAuB;AAAA,EAEpC,UAAU,CAAC,WAAuB;AAAA,EAE3C,kBAAuC;AACtC,WAAO;AAAA,MACN,GAAG;AAAA,MACH,GAAG;AAAA,MACH,OAAO;AAAA,MACP,MAAM;AAAA,MACN,UAAU;AAAA,MACV,gBAAgB;AAAA,MAChB,eAAe;AAAA,MACf,cAAc;AAAA,MACd,cAAc;AAAA,MACd,SAAS,CAAC;AAAA,IACX;AAAA,EACD;AAAA,EAEA,YAAY,OAAmB;AAC9B,WAAO,IAAI,0BAAY;AAAA,MACtB,OAAO,MAAM,MAAM;AAAA,MACnB,QAAQ,MAAM,MAAM;AAAA,MACpB,UAAU;AAAA,IACX,CAAC;AAAA,EACF;AAAA,EAEA,UAAU,OAAmB;AAE5B,UAAM,QAAQ,MAAM,MAAM,IAAI,IAAI,MAAM,MAAM,IAAI;AAClD,UAAM,SAAS,MAAM,MAAM,IAAI,IAAI,MAAM,MAAM,IAAI;AAEnD,UAAM,YAAQ,oCAAqB;AAAA,MAClC,YAAY,KAAK,OAAO,KAAK,cAAc;AAAA,IAC5C,CAAC;AAGD,UAAM,eAAe,KAAK,UAAU;AAAA,MACnC,IAAI,MAAM;AAAA,MACV,UAAU,MAAM,MAAM;AAAA,MACtB,gBAAgB,MAAM,MAAM;AAAA,MAC5B,eAAe,MAAM,MAAM;AAAA,MAC3B,cAAc,MAAM,MAAM;AAAA,MAC1B,cAAc,MAAM,MAAM;AAAA,MAC1B,SAAS,MAAM,MAAM;AAAA,IACtB,CAAC;AAED,UAAM,iBAAiB,MAAM,MAAM,aAAa,SAAS,IAAI,SAAS,KAAK;AAE3E,WACC;AAAA,MAAC;AAAA;AAAA,QACA,IAAI,MAAM;AAAA,QACV,OAAO;AAAA,UACN,QAAQ;AAAA,UACR,cAAc;AAAA,UACd,WAAW;AAAA,UACX,SAAS;AAAA,UACT,eAAe;AAAA,UACf,YAAY;AAAA,UACZ,gBAAgB;AAAA,UAChB,eAAe;AAAA,UACf,iBAAiB,MAAM,MAAM,MAAM,KAAK,EAAE;AAAA,UAC1C,OAAO,MAAM,MAAM,MAAM,KAAK,EAAE;AAAA,QACjC;AAAA,QAEA;AAAA,UAAC;AAAA;AAAA,YACA,OAAO;AAAA,cACN,OAAO,GAAG,KAAK;AAAA,cACf,QAAQ,GAAG,MAAM;AAAA,cACjB,UAAU;AAAA,cACV,UAAU;AAAA,YACX;AAAA,YAEA,sDAAC,oBAAAA,SAAA,EAAgB,UAAU,cAAc,QAAQ,gBAAgB,OAAc;AAAA;AAAA,QAChF;AAAA;AAAA,IACD;AAAA,EAEF;AAAA,EAEA,UAAU,OAAmB;AAC5B,WAAO,4CAAC,UAAK,OAAO,MAAM,MAAM,GAAG,QAAQ,MAAM,MAAM,GAAG;AAAA,EAC3D;AAAA,EAES,WAA0C,CAAC,OAAO,SAAS;AACnE,eAAO,yBAAU,OAAO,IAAI;AAAA,EAC7B;AACD;AAEA,IAAO,wBAAQ;",
|
|
6
6
|
"names": ["ChatPollContent"]
|
|
7
7
|
}
|
|
@@ -83,11 +83,14 @@ function assertAsMetadata(metadata) {
|
|
|
83
83
|
}
|
|
84
84
|
const ChatPollContent = ({
|
|
85
85
|
metadata: string,
|
|
86
|
-
height = void 0
|
|
86
|
+
height = void 0,
|
|
87
|
+
width = void 0
|
|
87
88
|
}) => {
|
|
88
89
|
const pollData = JSON.parse(string);
|
|
89
90
|
assertAsMetadata(pollData);
|
|
90
91
|
const msg = (0, import_useTranslation.useTranslation)();
|
|
92
|
+
const isTypedPoll = pollData.questionType.startsWith("R-");
|
|
93
|
+
const yAxisWidth = isTypedPoll && width ? Math.floor(width / 2) : 80;
|
|
91
94
|
const answers = pollData.answers.reduce(caseInsensitiveReducer, []);
|
|
92
95
|
const translatedAnswers = answers.map((answer) => {
|
|
93
96
|
const key = answer.key;
|
|
@@ -103,7 +106,15 @@ const ChatPollContent = ({
|
|
|
103
106
|
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_styles.default.PollText, { children: pollData.questionText }),
|
|
104
107
|
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_recharts.ResponsiveContainer, { width: "90%", height: useHeight, children: /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_recharts.BarChart, { data: translatedAnswers, layout: "vertical", children: [
|
|
105
108
|
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_recharts.XAxis, { type: "number", allowDecimals: false }),
|
|
106
|
-
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
109
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
110
|
+
import_recharts.YAxis,
|
|
111
|
+
{
|
|
112
|
+
width: yAxisWidth,
|
|
113
|
+
type: "category",
|
|
114
|
+
dataKey: "pollAnswer",
|
|
115
|
+
tick: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_CustomizedAxisTick.default, {})
|
|
116
|
+
}
|
|
117
|
+
),
|
|
107
118
|
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_recharts.Bar, { dataKey: "numVotes", fill: "#0C57A7" })
|
|
108
119
|
] }) })
|
|
109
120
|
] });
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../../../../src/lib/shapes/poll/components/poll-content.tsx"],
|
|
4
|
-
"sourcesContent": ["/* eslint-disable import/no-extraneous-dependencies */\nimport React from 'react'\nimport { Bar, BarChart, ResponsiveContainer, XAxis, YAxis } from 'recharts'\nimport { TLUiTranslationKey } from '../../../ui/hooks/useTranslation/TLUiTranslationKey'\nimport { useTranslation } from '../../../ui/hooks/useTranslation/useTranslation'\nimport CustomizedAxisTick from './CustomizedAxisTick'\nimport Styled from './styles'\n\nconst translatedAnswersKeys = {\n\tTrue: 'app.poll.t',\n\tFalse: 'app.poll.f',\n\tYes: 'app.poll.y',\n\tNo: 'app.poll.n',\n\tAbstention: 'app.poll.abstention',\n}\n\nconst caseInsensitiveReducer = (acc: any[], item: { key: string; numVotes: number }) => {\n\tconst index = acc.findIndex((ans) => ans.key.toLowerCase() === item.key.toLowerCase())\n\tif (index !== -1) {\n\t\tif (acc[index].numVotes >= item.numVotes) acc[index].numVotes += item.numVotes\n\t\telse {\n\t\t\tconst tempVotes = acc[index].numVotes\n\t\t\tacc[index] = item\n\t\t\tacc[index].numVotes += tempVotes\n\t\t}\n\t} else {\n\t\tacc.push(item)\n\t}\n\treturn acc\n}\n\ninterface ChatPollContentProps {\n\tmetadata: string\n\theight?: number\n}\n\ninterface Metadata {\n\tid: string\n\tquestion: string\n\tnumRespondents: number\n\tnumResponders: number\n\tquestionText: string\n\tquestionType: string\n\tanswers: Array<Answers>\n}\n\ninterface Answers {\n\tkey: string\n\tnumVotes: number\n\tid: number\n\tisCorrectAnswer?: boolean\n}\n\nfunction assertAsMetadata(metadata: unknown): asserts metadata is Metadata {\n\tif (typeof metadata !== 'object' || metadata === null) {\n\t\tthrow new Error('metadata is not an object')\n\t}\n\tif (typeof (metadata as Metadata).id !== 'string') {\n\t\tthrow new Error('metadata.id is not a string')\n\t}\n\tif (typeof (metadata as Metadata).numRespondents !== 'number') {\n\t\tthrow new Error('metadata.numRespondents is not a number')\n\t}\n\tif (typeof (metadata as Metadata).numResponders !== 'number') {\n\t\tthrow new Error('metadata.numResponders is not a number')\n\t}\n\tif (typeof (metadata as Metadata).questionText !== 'string') {\n\t\tthrow new Error('metadata.questionText is not a string')\n\t}\n\tif (typeof (metadata as Metadata).questionType !== 'string') {\n\t\tthrow new Error('metadata.questionType is not a string')\n\t}\n\tif (!Array.isArray((metadata as Metadata).answers)) {\n\t\tthrow new Error('metadata.answers is not an array')\n\t}\n}\n\nconst ChatPollContent: React.FC<ChatPollContentProps> = ({\n\tmetadata: string,\n\theight = undefined,\n}) => {\n\tconst pollData = JSON.parse(string) as unknown\n\tassertAsMetadata(pollData)\n\tconst msg = useTranslation()\n\n\tconst answers = pollData.answers.reduce(caseInsensitiveReducer, [])\n\n\tconst translatedAnswers = answers.map((answer: Answers) => {\n\t\tconst key = answer.key as keyof typeof translatedAnswersKeys\n\t\tconst translationKey = msg((translatedAnswersKeys[key] || key) as TLUiTranslationKey)\n\t\tconst pollAnswer = `${answer.isCorrectAnswer ? '\u2705 ' : ''}${\n\t\t\ttranslationKey ? translationKey : answer.key\n\t\t}`\n\t\treturn {\n\t\t\t...answer,\n\t\t\tpollAnswer,\n\t\t}\n\t})\n\n\tconst useHeight = height || translatedAnswers.length * 50\n\treturn (\n\t\t<Styled.PollWrapper data-test=\"chatPollMessageText\">\n\t\t\t<Styled.PollText>{pollData.questionText}</Styled.PollText>\n\t\t\t<ResponsiveContainer width=\"90%\" height={useHeight}>\n\t\t\t\t<BarChart data={translatedAnswers} layout=\"vertical\">\n\t\t\t\t\t<XAxis type=\"number\" allowDecimals={false} />\n\t\t\t\t\t<YAxis
|
|
5
|
-
"mappings": ";;;;;;;;;;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;
|
|
4
|
+
"sourcesContent": ["/* eslint-disable import/no-extraneous-dependencies */\nimport React from 'react'\nimport { Bar, BarChart, ResponsiveContainer, XAxis, YAxis } from 'recharts'\nimport { TLUiTranslationKey } from '../../../ui/hooks/useTranslation/TLUiTranslationKey'\nimport { useTranslation } from '../../../ui/hooks/useTranslation/useTranslation'\nimport CustomizedAxisTick from './CustomizedAxisTick'\nimport Styled from './styles'\n\nconst translatedAnswersKeys = {\n\tTrue: 'app.poll.t',\n\tFalse: 'app.poll.f',\n\tYes: 'app.poll.y',\n\tNo: 'app.poll.n',\n\tAbstention: 'app.poll.abstention',\n}\n\nconst caseInsensitiveReducer = (acc: any[], item: { key: string; numVotes: number }) => {\n\tconst index = acc.findIndex((ans) => ans.key.toLowerCase() === item.key.toLowerCase())\n\tif (index !== -1) {\n\t\tif (acc[index].numVotes >= item.numVotes) acc[index].numVotes += item.numVotes\n\t\telse {\n\t\t\tconst tempVotes = acc[index].numVotes\n\t\t\tacc[index] = item\n\t\t\tacc[index].numVotes += tempVotes\n\t\t}\n\t} else {\n\t\tacc.push(item)\n\t}\n\treturn acc\n}\n\ninterface ChatPollContentProps {\n\tmetadata: string\n\theight?: number\n\twidth?: number\n}\n\ninterface Metadata {\n\tid: string\n\tquestion: string\n\tnumRespondents: number\n\tnumResponders: number\n\tquestionText: string\n\tquestionType: string\n\tanswers: Array<Answers>\n}\n\ninterface Answers {\n\tkey: string\n\tnumVotes: number\n\tid: number\n\tisCorrectAnswer?: boolean\n}\n\nfunction assertAsMetadata(metadata: unknown): asserts metadata is Metadata {\n\tif (typeof metadata !== 'object' || metadata === null) {\n\t\tthrow new Error('metadata is not an object')\n\t}\n\tif (typeof (metadata as Metadata).id !== 'string') {\n\t\tthrow new Error('metadata.id is not a string')\n\t}\n\tif (typeof (metadata as Metadata).numRespondents !== 'number') {\n\t\tthrow new Error('metadata.numRespondents is not a number')\n\t}\n\tif (typeof (metadata as Metadata).numResponders !== 'number') {\n\t\tthrow new Error('metadata.numResponders is not a number')\n\t}\n\tif (typeof (metadata as Metadata).questionText !== 'string') {\n\t\tthrow new Error('metadata.questionText is not a string')\n\t}\n\tif (typeof (metadata as Metadata).questionType !== 'string') {\n\t\tthrow new Error('metadata.questionType is not a string')\n\t}\n\tif (!Array.isArray((metadata as Metadata).answers)) {\n\t\tthrow new Error('metadata.answers is not an array')\n\t}\n}\n\nconst ChatPollContent: React.FC<ChatPollContentProps> = ({\n\tmetadata: string,\n\theight = undefined,\n\twidth = undefined,\n}) => {\n\tconst pollData = JSON.parse(string) as unknown\n\tassertAsMetadata(pollData)\n\tconst msg = useTranslation()\n\n\tconst isTypedPoll = pollData.questionType.startsWith('R-')\n\tconst yAxisWidth = isTypedPoll && width ? Math.floor(width / 2) : 80\n\n\tconst answers = pollData.answers.reduce(caseInsensitiveReducer, [])\n\n\tconst translatedAnswers = answers.map((answer: Answers) => {\n\t\tconst key = answer.key as keyof typeof translatedAnswersKeys\n\t\tconst translationKey = msg((translatedAnswersKeys[key] || key) as TLUiTranslationKey)\n\t\tconst pollAnswer = `${answer.isCorrectAnswer ? '\u2705 ' : ''}${\n\t\t\ttranslationKey ? translationKey : answer.key\n\t\t}`\n\t\treturn {\n\t\t\t...answer,\n\t\t\tpollAnswer,\n\t\t}\n\t})\n\n\tconst useHeight = height || translatedAnswers.length * 50\n\treturn (\n\t\t<Styled.PollWrapper data-test=\"chatPollMessageText\">\n\t\t\t<Styled.PollText>{pollData.questionText}</Styled.PollText>\n\t\t\t<ResponsiveContainer width=\"90%\" height={useHeight}>\n\t\t\t\t<BarChart data={translatedAnswers} layout=\"vertical\">\n\t\t\t\t\t<XAxis type=\"number\" allowDecimals={false} />\n\t\t\t\t\t<YAxis\n\t\t\t\t\t\twidth={yAxisWidth}\n\t\t\t\t\t\ttype=\"category\"\n\t\t\t\t\t\tdataKey=\"pollAnswer\"\n\t\t\t\t\t\ttick={<CustomizedAxisTick />}\n\t\t\t\t\t/>\n\t\t\t\t\t<Bar dataKey=\"numVotes\" fill=\"#0C57A7\" />\n\t\t\t\t</BarChart>\n\t\t\t</ResponsiveContainer>\n\t\t</Styled.PollWrapper>\n\t)\n}\n\nexport default ChatPollContent\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
6
|
"names": ["Styled", "CustomizedAxisTick"]
|
|
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
|
}
|
|
@@ -78,7 +78,7 @@ class PollShapeUtil extends ShapeUtil {
|
|
|
78
78
|
overflow: "hidden",
|
|
79
79
|
position: "relative"
|
|
80
80
|
},
|
|
81
|
-
children: /* @__PURE__ */ jsx(ChatPollContent, { metadata: pollMetadata, height: adjustedHeight })
|
|
81
|
+
children: /* @__PURE__ */ jsx(ChatPollContent, { metadata: pollMetadata, height: adjustedHeight, width })
|
|
82
82
|
}
|
|
83
83
|
)
|
|
84
84
|
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../../../src/lib/shapes/poll/PollShapeUtil.tsx"],
|
|
4
|
-
"sourcesContent": ["import {\n\tHTMLContainer,\n\tRectangle2d,\n\tShapeUtil,\n\tTLOnResizeHandler,\n\tgetDefaultColorTheme,\n\tresizeBox,\n} from '@bigbluebutton/editor'\n\nimport ChatPollContent from './components/poll-content'\nimport { pollShapeMigrations } from './poll-shape-migrations'\nimport { pollShapeProps } from './poll-shape-props'\nimport { IPollShape } from './poll-shape-types'\n\nexport class PollShapeUtil extends ShapeUtil<IPollShape> {\n\tstatic override type = 'poll' as const\n\n\tstatic override props = pollShapeProps\n\n\tstatic override migrations = pollShapeMigrations\n\n\toverride isAspectRatioLocked = (_shape: IPollShape) => false\n\n\toverride canResize = (_shape: IPollShape) => true\n\n\toverride canBind = (_shape: IPollShape) => true\n\n\tgetDefaultProps(): IPollShape['props'] {\n\t\treturn {\n\t\t\tw: 300,\n\t\t\th: 300,\n\t\t\tcolor: 'black',\n\t\t\tfill: 'white',\n\t\t\tquestion: '',\n\t\t\tnumRespondents: 0,\n\t\t\tnumResponders: 0,\n\t\t\tquestionText: '',\n\t\t\tquestionType: '',\n\t\t\tanswers: [],\n\t\t}\n\t}\n\n\tgetGeometry(shape: IPollShape) {\n\t\treturn new Rectangle2d({\n\t\t\twidth: shape.props.w,\n\t\t\theight: shape.props.h,\n\t\t\tisFilled: true,\n\t\t})\n\t}\n\n\tcomponent(shape: IPollShape) {\n\t\t// Use the shape's specified width and height, falling back to defaults if missing or invalid.\n\t\tconst width = shape.props.w > 0 ? shape.props.w : 300\n\t\tconst height = shape.props.h > 0 ? shape.props.h : 300\n\n\t\tconst theme = getDefaultColorTheme({\n\t\t\tisDarkMode: this.editor.user.getIsDarkMode(),\n\t\t})\n\n\t\t// const contentRef = React.useRef<HTMLDivElement>(null)\n\t\tconst pollMetadata = JSON.stringify({\n\t\t\tid: shape.id,\n\t\t\tquestion: shape.props.question,\n\t\t\tnumRespondents: shape.props.numRespondents,\n\t\t\tnumResponders: shape.props.numResponders,\n\t\t\tquestionText: shape.props.questionText,\n\t\t\tquestionType: shape.props.questionType,\n\t\t\tanswers: shape.props.answers,\n\t\t})\n\n\t\tconst adjustedHeight = shape.props.questionText.length > 0 ? height - 75 : height\n\n\t\treturn (\n\t\t\t<HTMLContainer\n\t\t\t\tid={shape.id}\n\t\t\t\tstyle={{\n\t\t\t\t\tborder: '1px solid #8B9AA8',\n\t\t\t\t\tborderRadius: '4px',\n\t\t\t\t\tboxShadow: '0px 0px 4px 0px rgba(0, 0, 0, 0.20)',\n\t\t\t\t\tdisplay: 'flex',\n\t\t\t\t\tflexDirection: 'column',\n\t\t\t\t\talignItems: 'center',\n\t\t\t\t\tjustifyContent: 'center',\n\t\t\t\t\tpointerEvents: 'all',\n\t\t\t\t\tbackgroundColor: theme[shape.props.color].semi,\n\t\t\t\t\tcolor: theme[shape.props.color].solid,\n\t\t\t\t}}\n\t\t\t>\n\t\t\t\t<div\n\t\t\t\t\tstyle={{\n\t\t\t\t\t\twidth: `${width}px`,\n\t\t\t\t\t\theight: `${height}px`,\n\t\t\t\t\t\toverflow: 'hidden',\n\t\t\t\t\t\tposition: 'relative',\n\t\t\t\t\t}}\n\t\t\t\t>\n\t\t\t\t\t<ChatPollContent metadata={pollMetadata} height={adjustedHeight} />\n\t\t\t\t</div>\n\t\t\t</HTMLContainer>\n\t\t)\n\t}\n\n\tindicator(shape: IPollShape) {\n\t\treturn <rect width={shape.props.w} height={shape.props.h} />\n\t}\n\n\toverride onResize: TLOnResizeHandler<IPollShape> = (shape, info) => {\n\t\treturn resizeBox(shape, info)\n\t}\n}\n\nexport default PollShapeUtil\n"],
|
|
5
|
-
"mappings": "AAgGK;AAhGL;AAAA,EACC;AAAA,EACA;AAAA,EACA;AAAA,EAEA;AAAA,EACA;AAAA,OACM;AAEP,OAAO,qBAAqB;AAC5B,SAAS,2BAA2B;AACpC,SAAS,sBAAsB;AAGxB,MAAM,sBAAsB,UAAsB;AAAA,EACxD,OAAgB,OAAO;AAAA,EAEvB,OAAgB,QAAQ;AAAA,EAExB,OAAgB,aAAa;AAAA,EAEpB,sBAAsB,CAAC,WAAuB;AAAA,EAE9C,YAAY,CAAC,WAAuB;AAAA,EAEpC,UAAU,CAAC,WAAuB;AAAA,EAE3C,kBAAuC;AACtC,WAAO;AAAA,MACN,GAAG;AAAA,MACH,GAAG;AAAA,MACH,OAAO;AAAA,MACP,MAAM;AAAA,MACN,UAAU;AAAA,MACV,gBAAgB;AAAA,MAChB,eAAe;AAAA,MACf,cAAc;AAAA,MACd,cAAc;AAAA,MACd,SAAS,CAAC;AAAA,IACX;AAAA,EACD;AAAA,EAEA,YAAY,OAAmB;AAC9B,WAAO,IAAI,YAAY;AAAA,MACtB,OAAO,MAAM,MAAM;AAAA,MACnB,QAAQ,MAAM,MAAM;AAAA,MACpB,UAAU;AAAA,IACX,CAAC;AAAA,EACF;AAAA,EAEA,UAAU,OAAmB;AAE5B,UAAM,QAAQ,MAAM,MAAM,IAAI,IAAI,MAAM,MAAM,IAAI;AAClD,UAAM,SAAS,MAAM,MAAM,IAAI,IAAI,MAAM,MAAM,IAAI;AAEnD,UAAM,QAAQ,qBAAqB;AAAA,MAClC,YAAY,KAAK,OAAO,KAAK,cAAc;AAAA,IAC5C,CAAC;AAGD,UAAM,eAAe,KAAK,UAAU;AAAA,MACnC,IAAI,MAAM;AAAA,MACV,UAAU,MAAM,MAAM;AAAA,MACtB,gBAAgB,MAAM,MAAM;AAAA,MAC5B,eAAe,MAAM,MAAM;AAAA,MAC3B,cAAc,MAAM,MAAM;AAAA,MAC1B,cAAc,MAAM,MAAM;AAAA,MAC1B,SAAS,MAAM,MAAM;AAAA,IACtB,CAAC;AAED,UAAM,iBAAiB,MAAM,MAAM,aAAa,SAAS,IAAI,SAAS,KAAK;AAE3E,WACC;AAAA,MAAC;AAAA;AAAA,QACA,IAAI,MAAM;AAAA,QACV,OAAO;AAAA,UACN,QAAQ;AAAA,UACR,cAAc;AAAA,UACd,WAAW;AAAA,UACX,SAAS;AAAA,UACT,eAAe;AAAA,UACf,YAAY;AAAA,UACZ,gBAAgB;AAAA,UAChB,eAAe;AAAA,UACf,iBAAiB,MAAM,MAAM,MAAM,KAAK,EAAE;AAAA,UAC1C,OAAO,MAAM,MAAM,MAAM,KAAK,EAAE;AAAA,QACjC;AAAA,QAEA;AAAA,UAAC;AAAA;AAAA,YACA,OAAO;AAAA,cACN,OAAO,GAAG,KAAK;AAAA,cACf,QAAQ,GAAG,MAAM;AAAA,cACjB,UAAU;AAAA,cACV,UAAU;AAAA,YACX;AAAA,YAEA,8BAAC,mBAAgB,UAAU,cAAc,QAAQ,gBAAgB;AAAA;AAAA,
|
|
4
|
+
"sourcesContent": ["import {\n\tHTMLContainer,\n\tRectangle2d,\n\tShapeUtil,\n\tTLOnResizeHandler,\n\tgetDefaultColorTheme,\n\tresizeBox,\n} from '@bigbluebutton/editor'\n\nimport ChatPollContent from './components/poll-content'\nimport { pollShapeMigrations } from './poll-shape-migrations'\nimport { pollShapeProps } from './poll-shape-props'\nimport { IPollShape } from './poll-shape-types'\n\nexport class PollShapeUtil extends ShapeUtil<IPollShape> {\n\tstatic override type = 'poll' as const\n\n\tstatic override props = pollShapeProps\n\n\tstatic override migrations = pollShapeMigrations\n\n\toverride isAspectRatioLocked = (_shape: IPollShape) => false\n\n\toverride canResize = (_shape: IPollShape) => true\n\n\toverride canBind = (_shape: IPollShape) => true\n\n\tgetDefaultProps(): IPollShape['props'] {\n\t\treturn {\n\t\t\tw: 300,\n\t\t\th: 300,\n\t\t\tcolor: 'black',\n\t\t\tfill: 'white',\n\t\t\tquestion: '',\n\t\t\tnumRespondents: 0,\n\t\t\tnumResponders: 0,\n\t\t\tquestionText: '',\n\t\t\tquestionType: '',\n\t\t\tanswers: [],\n\t\t}\n\t}\n\n\tgetGeometry(shape: IPollShape) {\n\t\treturn new Rectangle2d({\n\t\t\twidth: shape.props.w,\n\t\t\theight: shape.props.h,\n\t\t\tisFilled: true,\n\t\t})\n\t}\n\n\tcomponent(shape: IPollShape) {\n\t\t// Use the shape's specified width and height, falling back to defaults if missing or invalid.\n\t\tconst width = shape.props.w > 0 ? shape.props.w : 300\n\t\tconst height = shape.props.h > 0 ? shape.props.h : 300\n\n\t\tconst theme = getDefaultColorTheme({\n\t\t\tisDarkMode: this.editor.user.getIsDarkMode(),\n\t\t})\n\n\t\t// const contentRef = React.useRef<HTMLDivElement>(null)\n\t\tconst pollMetadata = JSON.stringify({\n\t\t\tid: shape.id,\n\t\t\tquestion: shape.props.question,\n\t\t\tnumRespondents: shape.props.numRespondents,\n\t\t\tnumResponders: shape.props.numResponders,\n\t\t\tquestionText: shape.props.questionText,\n\t\t\tquestionType: shape.props.questionType,\n\t\t\tanswers: shape.props.answers,\n\t\t})\n\n\t\tconst adjustedHeight = shape.props.questionText.length > 0 ? height - 75 : height\n\n\t\treturn (\n\t\t\t<HTMLContainer\n\t\t\t\tid={shape.id}\n\t\t\t\tstyle={{\n\t\t\t\t\tborder: '1px solid #8B9AA8',\n\t\t\t\t\tborderRadius: '4px',\n\t\t\t\t\tboxShadow: '0px 0px 4px 0px rgba(0, 0, 0, 0.20)',\n\t\t\t\t\tdisplay: 'flex',\n\t\t\t\t\tflexDirection: 'column',\n\t\t\t\t\talignItems: 'center',\n\t\t\t\t\tjustifyContent: 'center',\n\t\t\t\t\tpointerEvents: 'all',\n\t\t\t\t\tbackgroundColor: theme[shape.props.color].semi,\n\t\t\t\t\tcolor: theme[shape.props.color].solid,\n\t\t\t\t}}\n\t\t\t>\n\t\t\t\t<div\n\t\t\t\t\tstyle={{\n\t\t\t\t\t\twidth: `${width}px`,\n\t\t\t\t\t\theight: `${height}px`,\n\t\t\t\t\t\toverflow: 'hidden',\n\t\t\t\t\t\tposition: 'relative',\n\t\t\t\t\t}}\n\t\t\t\t>\n\t\t\t\t\t<ChatPollContent metadata={pollMetadata} height={adjustedHeight} width={width} />\n\t\t\t\t</div>\n\t\t\t</HTMLContainer>\n\t\t)\n\t}\n\n\tindicator(shape: IPollShape) {\n\t\treturn <rect width={shape.props.w} height={shape.props.h} />\n\t}\n\n\toverride onResize: TLOnResizeHandler<IPollShape> = (shape, info) => {\n\t\treturn resizeBox(shape, info)\n\t}\n}\n\nexport default PollShapeUtil\n"],
|
|
5
|
+
"mappings": "AAgGK;AAhGL;AAAA,EACC;AAAA,EACA;AAAA,EACA;AAAA,EAEA;AAAA,EACA;AAAA,OACM;AAEP,OAAO,qBAAqB;AAC5B,SAAS,2BAA2B;AACpC,SAAS,sBAAsB;AAGxB,MAAM,sBAAsB,UAAsB;AAAA,EACxD,OAAgB,OAAO;AAAA,EAEvB,OAAgB,QAAQ;AAAA,EAExB,OAAgB,aAAa;AAAA,EAEpB,sBAAsB,CAAC,WAAuB;AAAA,EAE9C,YAAY,CAAC,WAAuB;AAAA,EAEpC,UAAU,CAAC,WAAuB;AAAA,EAE3C,kBAAuC;AACtC,WAAO;AAAA,MACN,GAAG;AAAA,MACH,GAAG;AAAA,MACH,OAAO;AAAA,MACP,MAAM;AAAA,MACN,UAAU;AAAA,MACV,gBAAgB;AAAA,MAChB,eAAe;AAAA,MACf,cAAc;AAAA,MACd,cAAc;AAAA,MACd,SAAS,CAAC;AAAA,IACX;AAAA,EACD;AAAA,EAEA,YAAY,OAAmB;AAC9B,WAAO,IAAI,YAAY;AAAA,MACtB,OAAO,MAAM,MAAM;AAAA,MACnB,QAAQ,MAAM,MAAM;AAAA,MACpB,UAAU;AAAA,IACX,CAAC;AAAA,EACF;AAAA,EAEA,UAAU,OAAmB;AAE5B,UAAM,QAAQ,MAAM,MAAM,IAAI,IAAI,MAAM,MAAM,IAAI;AAClD,UAAM,SAAS,MAAM,MAAM,IAAI,IAAI,MAAM,MAAM,IAAI;AAEnD,UAAM,QAAQ,qBAAqB;AAAA,MAClC,YAAY,KAAK,OAAO,KAAK,cAAc;AAAA,IAC5C,CAAC;AAGD,UAAM,eAAe,KAAK,UAAU;AAAA,MACnC,IAAI,MAAM;AAAA,MACV,UAAU,MAAM,MAAM;AAAA,MACtB,gBAAgB,MAAM,MAAM;AAAA,MAC5B,eAAe,MAAM,MAAM;AAAA,MAC3B,cAAc,MAAM,MAAM;AAAA,MAC1B,cAAc,MAAM,MAAM;AAAA,MAC1B,SAAS,MAAM,MAAM;AAAA,IACtB,CAAC;AAED,UAAM,iBAAiB,MAAM,MAAM,aAAa,SAAS,IAAI,SAAS,KAAK;AAE3E,WACC;AAAA,MAAC;AAAA;AAAA,QACA,IAAI,MAAM;AAAA,QACV,OAAO;AAAA,UACN,QAAQ;AAAA,UACR,cAAc;AAAA,UACd,WAAW;AAAA,UACX,SAAS;AAAA,UACT,eAAe;AAAA,UACf,YAAY;AAAA,UACZ,gBAAgB;AAAA,UAChB,eAAe;AAAA,UACf,iBAAiB,MAAM,MAAM,MAAM,KAAK,EAAE;AAAA,UAC1C,OAAO,MAAM,MAAM,MAAM,KAAK,EAAE;AAAA,QACjC;AAAA,QAEA;AAAA,UAAC;AAAA;AAAA,YACA,OAAO;AAAA,cACN,OAAO,GAAG,KAAK;AAAA,cACf,QAAQ,GAAG,MAAM;AAAA,cACjB,UAAU;AAAA,cACV,UAAU;AAAA,YACX;AAAA,YAEA,8BAAC,mBAAgB,UAAU,cAAc,QAAQ,gBAAgB,OAAc;AAAA;AAAA,QAChF;AAAA;AAAA,IACD;AAAA,EAEF;AAAA,EAEA,UAAU,OAAmB;AAC5B,WAAO,oBAAC,UAAK,OAAO,MAAM,MAAM,GAAG,QAAQ,MAAM,MAAM,GAAG;AAAA,EAC3D;AAAA,EAES,WAA0C,CAAC,OAAO,SAAS;AACnE,WAAO,UAAU,OAAO,IAAI;AAAA,EAC7B;AACD;AAEA,IAAO,wBAAQ;",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
|
@@ -50,11 +50,14 @@ function assertAsMetadata(metadata) {
|
|
|
50
50
|
}
|
|
51
51
|
const ChatPollContent = ({
|
|
52
52
|
metadata: string,
|
|
53
|
-
height = void 0
|
|
53
|
+
height = void 0,
|
|
54
|
+
width = void 0
|
|
54
55
|
}) => {
|
|
55
56
|
const pollData = JSON.parse(string);
|
|
56
57
|
assertAsMetadata(pollData);
|
|
57
58
|
const msg = useTranslation();
|
|
59
|
+
const isTypedPoll = pollData.questionType.startsWith("R-");
|
|
60
|
+
const yAxisWidth = isTypedPoll && width ? Math.floor(width / 2) : 80;
|
|
58
61
|
const answers = pollData.answers.reduce(caseInsensitiveReducer, []);
|
|
59
62
|
const translatedAnswers = answers.map((answer) => {
|
|
60
63
|
const key = answer.key;
|
|
@@ -70,7 +73,15 @@ const ChatPollContent = ({
|
|
|
70
73
|
/* @__PURE__ */ jsx(Styled.PollText, { children: pollData.questionText }),
|
|
71
74
|
/* @__PURE__ */ jsx(ResponsiveContainer, { width: "90%", height: useHeight, children: /* @__PURE__ */ jsxs(BarChart, { data: translatedAnswers, layout: "vertical", children: [
|
|
72
75
|
/* @__PURE__ */ jsx(XAxis, { type: "number", allowDecimals: false }),
|
|
73
|
-
/* @__PURE__ */ jsx(
|
|
76
|
+
/* @__PURE__ */ jsx(
|
|
77
|
+
YAxis,
|
|
78
|
+
{
|
|
79
|
+
width: yAxisWidth,
|
|
80
|
+
type: "category",
|
|
81
|
+
dataKey: "pollAnswer",
|
|
82
|
+
tick: /* @__PURE__ */ jsx(CustomizedAxisTick, {})
|
|
83
|
+
}
|
|
84
|
+
),
|
|
74
85
|
/* @__PURE__ */ jsx(Bar, { dataKey: "numVotes", fill: "#0C57A7" })
|
|
75
86
|
] }) })
|
|
76
87
|
] });
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../../../../src/lib/shapes/poll/components/poll-content.tsx"],
|
|
4
|
-
"sourcesContent": ["/* eslint-disable import/no-extraneous-dependencies */\nimport React from 'react'\nimport { Bar, BarChart, ResponsiveContainer, XAxis, YAxis } from 'recharts'\nimport { TLUiTranslationKey } from '../../../ui/hooks/useTranslation/TLUiTranslationKey'\nimport { useTranslation } from '../../../ui/hooks/useTranslation/useTranslation'\nimport CustomizedAxisTick from './CustomizedAxisTick'\nimport Styled from './styles'\n\nconst translatedAnswersKeys = {\n\tTrue: 'app.poll.t',\n\tFalse: 'app.poll.f',\n\tYes: 'app.poll.y',\n\tNo: 'app.poll.n',\n\tAbstention: 'app.poll.abstention',\n}\n\nconst caseInsensitiveReducer = (acc: any[], item: { key: string; numVotes: number }) => {\n\tconst index = acc.findIndex((ans) => ans.key.toLowerCase() === item.key.toLowerCase())\n\tif (index !== -1) {\n\t\tif (acc[index].numVotes >= item.numVotes) acc[index].numVotes += item.numVotes\n\t\telse {\n\t\t\tconst tempVotes = acc[index].numVotes\n\t\t\tacc[index] = item\n\t\t\tacc[index].numVotes += tempVotes\n\t\t}\n\t} else {\n\t\tacc.push(item)\n\t}\n\treturn acc\n}\n\ninterface ChatPollContentProps {\n\tmetadata: string\n\theight?: number\n}\n\ninterface Metadata {\n\tid: string\n\tquestion: string\n\tnumRespondents: number\n\tnumResponders: number\n\tquestionText: string\n\tquestionType: string\n\tanswers: Array<Answers>\n}\n\ninterface Answers {\n\tkey: string\n\tnumVotes: number\n\tid: number\n\tisCorrectAnswer?: boolean\n}\n\nfunction assertAsMetadata(metadata: unknown): asserts metadata is Metadata {\n\tif (typeof metadata !== 'object' || metadata === null) {\n\t\tthrow new Error('metadata is not an object')\n\t}\n\tif (typeof (metadata as Metadata).id !== 'string') {\n\t\tthrow new Error('metadata.id is not a string')\n\t}\n\tif (typeof (metadata as Metadata).numRespondents !== 'number') {\n\t\tthrow new Error('metadata.numRespondents is not a number')\n\t}\n\tif (typeof (metadata as Metadata).numResponders !== 'number') {\n\t\tthrow new Error('metadata.numResponders is not a number')\n\t}\n\tif (typeof (metadata as Metadata).questionText !== 'string') {\n\t\tthrow new Error('metadata.questionText is not a string')\n\t}\n\tif (typeof (metadata as Metadata).questionType !== 'string') {\n\t\tthrow new Error('metadata.questionType is not a string')\n\t}\n\tif (!Array.isArray((metadata as Metadata).answers)) {\n\t\tthrow new Error('metadata.answers is not an array')\n\t}\n}\n\nconst ChatPollContent: React.FC<ChatPollContentProps> = ({\n\tmetadata: string,\n\theight = undefined,\n}) => {\n\tconst pollData = JSON.parse(string) as unknown\n\tassertAsMetadata(pollData)\n\tconst msg = useTranslation()\n\n\tconst answers = pollData.answers.reduce(caseInsensitiveReducer, [])\n\n\tconst translatedAnswers = answers.map((answer: Answers) => {\n\t\tconst key = answer.key as keyof typeof translatedAnswersKeys\n\t\tconst translationKey = msg((translatedAnswersKeys[key] || key) as TLUiTranslationKey)\n\t\tconst pollAnswer = `${answer.isCorrectAnswer ? '\u2705 ' : ''}${\n\t\t\ttranslationKey ? translationKey : answer.key\n\t\t}`\n\t\treturn {\n\t\t\t...answer,\n\t\t\tpollAnswer,\n\t\t}\n\t})\n\n\tconst useHeight = height || translatedAnswers.length * 50\n\treturn (\n\t\t<Styled.PollWrapper data-test=\"chatPollMessageText\">\n\t\t\t<Styled.PollText>{pollData.questionText}</Styled.PollText>\n\t\t\t<ResponsiveContainer width=\"90%\" height={useHeight}>\n\t\t\t\t<BarChart data={translatedAnswers} layout=\"vertical\">\n\t\t\t\t\t<XAxis type=\"number\" allowDecimals={false} />\n\t\t\t\t\t<YAxis
|
|
5
|
-
"mappings": "
|
|
4
|
+
"sourcesContent": ["/* eslint-disable import/no-extraneous-dependencies */\nimport React from 'react'\nimport { Bar, BarChart, ResponsiveContainer, XAxis, YAxis } from 'recharts'\nimport { TLUiTranslationKey } from '../../../ui/hooks/useTranslation/TLUiTranslationKey'\nimport { useTranslation } from '../../../ui/hooks/useTranslation/useTranslation'\nimport CustomizedAxisTick from './CustomizedAxisTick'\nimport Styled from './styles'\n\nconst translatedAnswersKeys = {\n\tTrue: 'app.poll.t',\n\tFalse: 'app.poll.f',\n\tYes: 'app.poll.y',\n\tNo: 'app.poll.n',\n\tAbstention: 'app.poll.abstention',\n}\n\nconst caseInsensitiveReducer = (acc: any[], item: { key: string; numVotes: number }) => {\n\tconst index = acc.findIndex((ans) => ans.key.toLowerCase() === item.key.toLowerCase())\n\tif (index !== -1) {\n\t\tif (acc[index].numVotes >= item.numVotes) acc[index].numVotes += item.numVotes\n\t\telse {\n\t\t\tconst tempVotes = acc[index].numVotes\n\t\t\tacc[index] = item\n\t\t\tacc[index].numVotes += tempVotes\n\t\t}\n\t} else {\n\t\tacc.push(item)\n\t}\n\treturn acc\n}\n\ninterface ChatPollContentProps {\n\tmetadata: string\n\theight?: number\n\twidth?: number\n}\n\ninterface Metadata {\n\tid: string\n\tquestion: string\n\tnumRespondents: number\n\tnumResponders: number\n\tquestionText: string\n\tquestionType: string\n\tanswers: Array<Answers>\n}\n\ninterface Answers {\n\tkey: string\n\tnumVotes: number\n\tid: number\n\tisCorrectAnswer?: boolean\n}\n\nfunction assertAsMetadata(metadata: unknown): asserts metadata is Metadata {\n\tif (typeof metadata !== 'object' || metadata === null) {\n\t\tthrow new Error('metadata is not an object')\n\t}\n\tif (typeof (metadata as Metadata).id !== 'string') {\n\t\tthrow new Error('metadata.id is not a string')\n\t}\n\tif (typeof (metadata as Metadata).numRespondents !== 'number') {\n\t\tthrow new Error('metadata.numRespondents is not a number')\n\t}\n\tif (typeof (metadata as Metadata).numResponders !== 'number') {\n\t\tthrow new Error('metadata.numResponders is not a number')\n\t}\n\tif (typeof (metadata as Metadata).questionText !== 'string') {\n\t\tthrow new Error('metadata.questionText is not a string')\n\t}\n\tif (typeof (metadata as Metadata).questionType !== 'string') {\n\t\tthrow new Error('metadata.questionType is not a string')\n\t}\n\tif (!Array.isArray((metadata as Metadata).answers)) {\n\t\tthrow new Error('metadata.answers is not an array')\n\t}\n}\n\nconst ChatPollContent: React.FC<ChatPollContentProps> = ({\n\tmetadata: string,\n\theight = undefined,\n\twidth = undefined,\n}) => {\n\tconst pollData = JSON.parse(string) as unknown\n\tassertAsMetadata(pollData)\n\tconst msg = useTranslation()\n\n\tconst isTypedPoll = pollData.questionType.startsWith('R-')\n\tconst yAxisWidth = isTypedPoll && width ? Math.floor(width / 2) : 80\n\n\tconst answers = pollData.answers.reduce(caseInsensitiveReducer, [])\n\n\tconst translatedAnswers = answers.map((answer: Answers) => {\n\t\tconst key = answer.key as keyof typeof translatedAnswersKeys\n\t\tconst translationKey = msg((translatedAnswersKeys[key] || key) as TLUiTranslationKey)\n\t\tconst pollAnswer = `${answer.isCorrectAnswer ? '\u2705 ' : ''}${\n\t\t\ttranslationKey ? translationKey : answer.key\n\t\t}`\n\t\treturn {\n\t\t\t...answer,\n\t\t\tpollAnswer,\n\t\t}\n\t})\n\n\tconst useHeight = height || translatedAnswers.length * 50\n\treturn (\n\t\t<Styled.PollWrapper data-test=\"chatPollMessageText\">\n\t\t\t<Styled.PollText>{pollData.questionText}</Styled.PollText>\n\t\t\t<ResponsiveContainer width=\"90%\" height={useHeight}>\n\t\t\t\t<BarChart data={translatedAnswers} layout=\"vertical\">\n\t\t\t\t\t<XAxis type=\"number\" allowDecimals={false} />\n\t\t\t\t\t<YAxis\n\t\t\t\t\t\twidth={yAxisWidth}\n\t\t\t\t\t\ttype=\"category\"\n\t\t\t\t\t\tdataKey=\"pollAnswer\"\n\t\t\t\t\t\ttick={<CustomizedAxisTick />}\n\t\t\t\t\t/>\n\t\t\t\t\t<Bar dataKey=\"numVotes\" fill=\"#0C57A7\" />\n\t\t\t\t</BarChart>\n\t\t\t</ResponsiveContainer>\n\t\t</Styled.PollWrapper>\n\t)\n}\n\nexport default ChatPollContent\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.33",
|
|
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.33",
|
|
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
|
/>
|
|
@@ -94,7 +94,7 @@ export class PollShapeUtil extends ShapeUtil<IPollShape> {
|
|
|
94
94
|
position: 'relative',
|
|
95
95
|
}}
|
|
96
96
|
>
|
|
97
|
-
<ChatPollContent metadata={pollMetadata} height={adjustedHeight} />
|
|
97
|
+
<ChatPollContent metadata={pollMetadata} height={adjustedHeight} width={width} />
|
|
98
98
|
</div>
|
|
99
99
|
</HTMLContainer>
|
|
100
100
|
)
|
|
@@ -32,6 +32,7 @@ const caseInsensitiveReducer = (acc: any[], item: { key: string; numVotes: numbe
|
|
|
32
32
|
interface ChatPollContentProps {
|
|
33
33
|
metadata: string
|
|
34
34
|
height?: number
|
|
35
|
+
width?: number
|
|
35
36
|
}
|
|
36
37
|
|
|
37
38
|
interface Metadata {
|
|
@@ -78,11 +79,15 @@ function assertAsMetadata(metadata: unknown): asserts metadata is Metadata {
|
|
|
78
79
|
const ChatPollContent: React.FC<ChatPollContentProps> = ({
|
|
79
80
|
metadata: string,
|
|
80
81
|
height = undefined,
|
|
82
|
+
width = undefined,
|
|
81
83
|
}) => {
|
|
82
84
|
const pollData = JSON.parse(string) as unknown
|
|
83
85
|
assertAsMetadata(pollData)
|
|
84
86
|
const msg = useTranslation()
|
|
85
87
|
|
|
88
|
+
const isTypedPoll = pollData.questionType.startsWith('R-')
|
|
89
|
+
const yAxisWidth = isTypedPoll && width ? Math.floor(width / 2) : 80
|
|
90
|
+
|
|
86
91
|
const answers = pollData.answers.reduce(caseInsensitiveReducer, [])
|
|
87
92
|
|
|
88
93
|
const translatedAnswers = answers.map((answer: Answers) => {
|
|
@@ -104,7 +109,12 @@ const ChatPollContent: React.FC<ChatPollContentProps> = ({
|
|
|
104
109
|
<ResponsiveContainer width="90%" height={useHeight}>
|
|
105
110
|
<BarChart data={translatedAnswers} layout="vertical">
|
|
106
111
|
<XAxis type="number" allowDecimals={false} />
|
|
107
|
-
<YAxis
|
|
112
|
+
<YAxis
|
|
113
|
+
width={yAxisWidth}
|
|
114
|
+
type="category"
|
|
115
|
+
dataKey="pollAnswer"
|
|
116
|
+
tick={<CustomizedAxisTick />}
|
|
117
|
+
/>
|
|
108
118
|
<Bar dataKey="numVotes" fill="#0C57A7" />
|
|
109
119
|
</BarChart>
|
|
110
120
|
</ResponsiveContainer>
|