UNPKG

@e-group/material-lab

Version:

EGroup Team Lab - Incubator for EGroup Team experimental React components.

288 lines (236 loc) 10.6 kB
"use strict"; var _interopRequireWildcard = require("@babel/runtime/helpers/interopRequireWildcard"); var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault"); Object.defineProperty(exports, "__esModule", { value: true }); exports.getImageSettings = getImageSettings; exports.default = void 0; var _extends2 = _interopRequireDefault(require("@babel/runtime/helpers/extends")); var _defineProperty2 = _interopRequireDefault(require("@babel/runtime/helpers/defineProperty")); var _objectWithoutProperties2 = _interopRequireDefault(require("@babel/runtime/helpers/objectWithoutProperties")); var _slicedToArray2 = _interopRequireDefault(require("@babel/runtime/helpers/slicedToArray")); var _react = _interopRequireWildcard(require("react")); var _QRCode = _interopRequireDefault(require("qr.js/lib/QRCode")); var _ErrorCorrectLevel = _interopRequireDefault(require("qr.js/lib/ErrorCorrectLevel")); var _usePrevious = _interopRequireDefault(require("@e-group/hooks/usePrevious")); var _mergeRefs = _interopRequireDefault(require("@e-group/utils/mergeRefs")); function ownKeys(object, enumerableOnly) { var keys = Object.keys(object); if (Object.getOwnPropertySymbols) { var symbols = Object.getOwnPropertySymbols(object); if (enumerableOnly) symbols = symbols.filter(function (sym) { return Object.getOwnPropertyDescriptor(object, sym).enumerable; }); keys.push.apply(keys, symbols); } return keys; } function _objectSpread(target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i] != null ? arguments[i] : {}; if (i % 2) { ownKeys(Object(source), true).forEach(function (key) { (0, _defineProperty2.default)(target, key, source[key]); }); } else if (Object.getOwnPropertyDescriptors) { Object.defineProperties(target, Object.getOwnPropertyDescriptors(source)); } else { ownKeys(Object(source)).forEach(function (key) { Object.defineProperty(target, key, Object.getOwnPropertyDescriptor(source, key)); }); } } return target; } // For canvas we're going to switch our drawing mode based on whether or not // the environment supports Path2D. We only need the constructor to be // supported, but Edge doesn't actually support the path (string) type // argument. Luckily it also doesn't support the addPath() method. We can // treat that as the same thing. const getHasSupportPath2D = () => { try { new Path2D().addPath(new Path2D()); } catch (e) { return false; } return true; }; const SUPPORTS_PATH2D = getHasSupportPath2D(); const MARGIN_SIZE = 4; // This is *very* rough estimate of max amount of QRCode allowed to be covered. // It is "wrong" in a lot of ways (area is a terrible way to estimate, it // really should be number of modules covered), but if for some reason we don't // get an explicit height or width, I'd rather default to something than throw. const DEFAULT_IMG_SCALE = 0.1; function generatePath(modules, margin = 0) { const ops = []; modules.forEach((row, y) => { let start = null; row.forEach((cell, x) => { if (!cell && start !== null) { // M0 0h7v1H0z injects the space with the move and drops the comma, // saving a char per operation ops.push("M".concat(start + margin, " ").concat(y + margin, "h").concat(x - start, "v1H").concat(start + margin, "z")); start = null; return; } // end of row, clean up or skip if (x === row.length - 1) { if (!cell) { // We would have closed the op above already so this can only mean // 2+ light modules in a row. return; } if (start === null) { // Just a single dark module. ops.push("M".concat(x + margin, ",").concat(y + margin, " h1v1H").concat(x + margin, "z")); } else { // Otherwise finish the current line. ops.push("M".concat(start + margin, ",").concat(y + margin, " h").concat(x + 1 - start, "v1H").concat(start + margin, "z")); } return; } if (cell && start === null) { start = x; } }); }); return ops.join(''); } // We could just do this in generatePath, except that we want to support // non-Path2D canvas, so we need to keep it an explicit step. function excavateModules(modules, excavation) { return modules.slice().map((row, y) => { if (y < excavation.y || y >= excavation.y + excavation.h) { return row; } return row.map((cell, x) => { if (x < excavation.x || x >= excavation.x + excavation.w) { return cell; } return false; }); }); } function getImageSettings(size, includeMargin, cells, imageSettings) { if (imageSettings == null) { return null; } const margin = includeMargin ? MARGIN_SIZE : 0; const numCells = cells.length + margin * 2; const defaultSize = Math.floor(size * DEFAULT_IMG_SCALE); const scale = numCells / size; const w = (imageSettings.width || defaultSize) * scale; const h = (imageSettings.height || defaultSize) * scale; const x = imageSettings.x == null ? cells.length / 2 - w / 2 : imageSettings.x * scale; const y = imageSettings.y == null ? cells.length / 2 - h / 2 : imageSettings.y * scale; let excavation = null; if (imageSettings.excavate) { const floorX = Math.floor(x); const floorY = Math.floor(y); const ceilW = Math.ceil(w + x - floorX); const ceilH = Math.ceil(h + y - floorY); excavation = { x: floorX, y: floorY, w: ceilW, h: ceilH }; } return { x, y, h, w, excavation }; } const QRCode = /*#__PURE__*/(0, _react.forwardRef)((props, ref) => { const canvasEl = (0, _react.useRef)(null); const imageEl = (0, _react.useRef)(null); const _useState = (0, _react.useState)(false), _useState2 = (0, _slicedToArray2.default)(_useState, 2), imgLoaded = _useState2[0], setImgLoaded = _useState2[1]; const value = props.value, _props$size = props.size, size = _props$size === void 0 ? 128 : _props$size, _props$level = props.level, level = _props$level === void 0 ? 'L' : _props$level, _props$bgColor = props.bgColor, bgColor = _props$bgColor === void 0 ? '#FFFFFF' : _props$bgColor, _props$fgColor = props.fgColor, fgColor = _props$fgColor === void 0 ? '#000000' : _props$fgColor, _props$includeMargin = props.includeMargin, includeMargin = _props$includeMargin === void 0 ? false : _props$includeMargin, style = props.style, imageSettings = props.imageSettings, otherProps = (0, _objectWithoutProperties2.default)(props, ["value", "size", "level", "bgColor", "fgColor", "includeMargin", "style", "imageSettings"]); const prevImageSettings = (0, _usePrevious.default)(imageSettings); (0, _react.useEffect)(() => { const currentSrc = imageSettings === null || imageSettings === void 0 ? void 0 : imageSettings.src; const prevSrc = prevImageSettings === null || prevImageSettings === void 0 ? void 0 : prevImageSettings.src; if (prevSrc !== currentSrc) { setImgLoaded(false); } }, [imageSettings === null || imageSettings === void 0 ? void 0 : imageSettings.src, prevImageSettings === null || prevImageSettings === void 0 ? void 0 : prevImageSettings.src]); const handleImageLoad = () => { setImgLoaded(true); }; const update = (0, _react.useCallback)(() => { // We'll use type===-1 to force QRCode to automatically pick the best type const qrcode = new _QRCode.default(-1, _ErrorCorrectLevel.default[level]); qrcode.addData(value); qrcode.make(); if (canvasEl && canvasEl.current) { const canvas = canvasEl.current; const ctx = canvas.getContext('2d'); if (!ctx) { return; } let cells = qrcode.modules; if (cells === null) { return; } const margin = includeMargin ? MARGIN_SIZE : 0; const numCells = cells.length + margin * 2; const calculatedImageSettings = getImageSettings(size, includeMargin, cells, imageSettings); if (imageSettings && calculatedImageSettings != null) { if (calculatedImageSettings.excavation != null) { cells = excavateModules(cells, calculatedImageSettings.excavation); } } // We're going to scale this so that the number of drawable units // matches the number of cells. This avoids rounding issues, but does // result in some potentially unwanted single pixel issues between // blocks, only in environments that don't support Path2D. const pixelRatio = window.devicePixelRatio || 1; canvas.height = size * pixelRatio; canvas.width = size * pixelRatio; const scale = size / numCells * pixelRatio; ctx.scale(scale, scale); // Draw solid background, only paint dark modules. ctx.fillStyle = bgColor; ctx.fillRect(0, 0, numCells, numCells); ctx.fillStyle = fgColor; if (SUPPORTS_PATH2D) { // $FlowFixMe: Path2D c'tor doesn't support args yet. ctx.fill(new Path2D(generatePath(cells, margin))); } else { cells.forEach((row, rdx) => { row.forEach((cell, cdx) => { if (cell) { ctx.fillRect(cdx + margin, rdx + margin, 1, 1); } }); }); } if (imgLoaded && imageEl && imageEl.current && calculatedImageSettings != null) { ctx.drawImage(imageEl.current, calculatedImageSettings.x + margin, calculatedImageSettings.y + margin, calculatedImageSettings.w, calculatedImageSettings.h); } } }, [bgColor, fgColor, imageSettings, imgLoaded, includeMargin, level, size, value]); (0, _react.useEffect)(() => { if (canvasEl && imageEl) { handleImageLoad(); } update(); }, [update]); (0, _react.useEffect)(() => { update(); }); let img = null; const imgSrc = imageSettings && imageSettings.src; if (imageSettings && imgSrc != null) { img = /*#__PURE__*/_react.default.createElement("img", { src: imgSrc, style: { display: 'none' }, onLoad: handleImageLoad, ref: imageEl, alt: "" }); } return /*#__PURE__*/_react.default.createElement(_react.default.Fragment, null, /*#__PURE__*/_react.default.createElement("canvas", (0, _extends2.default)({ style: _objectSpread({ height: size, width: size }, style), height: size, width: size, ref: (0, _mergeRefs.default)([canvasEl, ref]) }, otherProps)), img); }); var _default = QRCode; exports.default = _default;