@e-group/material-lab
Version:
EGroup Team Lab - Incubator for EGroup Team experimental React components.
288 lines (236 loc) • 10.6 kB
JavaScript
"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;