import { PureComponent, createRef } from 'react'; import Box from '@mui/material/Box'; import { decodeBase62 } from 'skelehash-wasm'; // Module-level cache so re-renders / re-mounts don't re-run the WASM decoder // for the same SkeleHash. Stores a Promise resolving to the decoded payload. const decodeCache = new Map(); // base62 -> Promise<{ rgba, metadata } | null> function decodeSkeleHash(base62) { if (!base62) return Promise.resolve(null); let cached = decodeCache.get(base62); if (!cached) { cached = decodeBase62(base62).catch(() => null); decodeCache.set(base62, cached); } return cached; } /** * Renders a SkeleHash (Base62) as an RGBA canvas placeholder. * Mirrors the synchronous-read / fine-grained styling approach of ThumbHashImage. */ export default class SkeleHashImage extends PureComponent { constructor(props) { super(props); this.state = { ready: false }; this.canvasRef = createRef(); this.requestId = 0; } componentDidMount() { this.draw(); } componentDidUpdate(prevProps) { if (prevProps.hash !== this.props.hash) { this.draw(); } } componentWillUnmount() { // Invalidate any in-flight decode so it doesn't touch the unmounted canvas. this.requestId += 1; } async draw() { const { hash } = this.props; const requestId = ++this.requestId; this.setState({ ready: false }); const decoded = await decodeSkeleHash(hash); // Stale request (hash changed or component unmounted) — bail out. if (requestId !== this.requestId) return; const canvas = this.canvasRef.current; if (!canvas || !decoded?.rgba || !decoded?.metadata) return; const { rgba, metadata } = decoded; const width = metadata.outputWidth; const height = metadata.outputHeight; if (!width || !height) return; canvas.width = width; canvas.height = height; const ctx = canvas.getContext('2d'); const clamped = new Uint8ClampedArray(rgba.buffer, rgba.byteOffset, rgba.byteLength); ctx.putImageData(new ImageData(clamped, width, height), 0, 0); this.setState({ ready: true }); } render() { const { sx, hash } = this.props; const { ready } = this.state; return ( ); } }