UNPKG

@helpscout/hsds-react

Version:

React component library for Help Scout's Design System

301 lines (234 loc) 9.28 kB
"use strict"; var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault"); exports.__esModule = true; exports.default = exports.AvatarImage = exports.clearCache = void 0; var _extends2 = _interopRequireDefault(require("@babel/runtime/helpers/extends")); var _objectWithoutPropertiesLoose2 = _interopRequireDefault(require("@babel/runtime/helpers/objectWithoutPropertiesLoose")); var _assertThisInitialized2 = _interopRequireDefault(require("@babel/runtime/helpers/assertThisInitialized")); var _inheritsLoose2 = _interopRequireDefault(require("@babel/runtime/helpers/inheritsLoose")); var _react = _interopRequireDefault(require("react")); var _propTypes = require("prop-types"); var _classnames = _interopRequireDefault(require("classnames")); var _getValidProps = _interopRequireDefault(require("@helpscout/react-utils/dist/getValidProps")); var _VisuallyHidden = _interopRequireDefault(require("../VisuallyHidden")); var _Avatar = require("./Avatar.css"); var _Avatar2 = require("./Avatar.utils"); var _jsxRuntime = require("react/jsx-runtime"); // this Component leverage code from react-image for the loading part // https://github.com/mbrevda/react-image // // Goal would be to either only use react-image at some point, or to migrate the loading // code here into an <Image /> component function noop() {} var cache = {}; var clearCache = function clearCache() { cache = {}; }; exports.clearCache = clearCache; var AvatarImage = /*#__PURE__*/function (_React$PureComponent) { (0, _inheritsLoose2.default)(AvatarImage, _React$PureComponent); function AvatarImage(props) { var _this; _this = _React$PureComponent.call(this, props) || this; _this.sourceList = []; _this.image = void 0; _this.state = { currentIndex: 0, isLoading: false, isLoaded: false }; _this.srcToArray = function (src) { return (Array.isArray(src) ? src : [src]).filter(function (x) { return x; }); }; _this.onLoad = function () { cache[_this.sourceList[_this.state.currentIndex]] = true; if (_this.image) { _this.setState({ isLoaded: true }); _this.props.onLoad(); } }; _this.onError = function () { cache[_this.sourceList[_this.state.currentIndex]] = false; // if the current image has already been destroyed, we are probably no longer mounted // no need to do anything then if (!_this.image) return false; // before loading the next image, check to see if it was ever loaded in the past for (var nextIndex = _this.state.currentIndex + 1; nextIndex < _this.sourceList.length; nextIndex++) { // get next img var src = _this.sourceList[nextIndex]; // if we have never seen it, its the one we want to try next if (!(src in cache)) { _this.setState({ currentIndex: nextIndex }); break; } // if we know it exists, use it! if (cache[src] === true) { _this.setState({ currentIndex: nextIndex, isLoading: false, isLoaded: true }); _this.props.onLoad(); return true; } // if we know it doesn't exist, skip it! if (cache[src] === false) continue; } // currentIndex is zero bases, length is 1 based. // if we have no more sources to try, return - we are done if (nextIndex === _this.sourceList.length) { _this.setState({ isLoading: false }); _this.props.onError(); return false; } // otherwise, try the next img _this.loadImg(); return false; }; _this.loadImg = function () { _this.image = new Image(); _this.image.src = _this.sourceList[_this.state.currentIndex]; if (_this.image.decode) { _this.image.decode().then(_this.onLoad).catch(_this.onError); } else { _this.image.onload = _this.onLoad; _this.image.onerror = _this.onError; } }; _this.unloadImg = function () { _this.image.onerror = null; _this.image.onload = null; // abort any current downloads https://github.com/mbrevda/react-image/pull/223 _this.image.src = ''; try { delete _this.image.src; } catch (e) {// On Safari in Strict mode this will throw an exception, // - https://github.com/mbrevda/react-image/issues/187 // We don't need to do anything about it. } delete _this.image; }; _this.sourceList = _this.srcToArray(_this.props.src); // check cache to decide at which index to start for (var i = 0; i < _this.sourceList.length; i++) { // if we've never seen this image before, the cache wont help. // no need to look further, just start loading if (!(_this.sourceList[i] in cache)) break; // if we have loaded this image before, just load it again if (cache[_this.sourceList[i]] === true) { _this.state = { currentIndex: i, isLoading: false, isLoaded: true }; _this.props.onLoad(); return (0, _assertThisInitialized2.default)(_this); } } _this.state = _this.sourceList.length ? // 'normal' opperation: start at 0 and try to load { currentIndex: 0, isLoading: true, isLoaded: false } : // if we dont have any sources, jump directly to unloaded { currentIndex: 0, isLoading: false, isLoaded: false }; return _this; } var _proto = AvatarImage.prototype; _proto.componentDidMount = function componentDidMount() { // kick off process if (this.state.isLoading) this.loadImg(); }; _proto.componentWillUnmount = function componentWillUnmount() { // ensure that we dont leave any lingering listeners if (this.image) this.unloadImg(); }; _proto.UNSAFE_componentWillReceiveProps = function UNSAFE_componentWillReceiveProps(nextProps) { var _this2 = this; var src = this.srcToArray(nextProps.src); var srcAdded = src.filter(function (s) { return _this2.sourceList.indexOf(s) === -1; }); var srcRemoved = this.sourceList.filter(function (s) { return src.indexOf(s) === -1; }); // if src prop changed, restart the loading process if (srcAdded.length || srcRemoved.length) { this.sourceList = src; // if we dont have any sources, jump directly to unloader if (!src.length) return this.setState({ currentIndex: 0, isLoading: false, isLoaded: false }); this.setState({ currentIndex: 0, isLoading: true, isLoaded: false }, this.loadImg); } }; _proto.renderInitials = function renderInitials() { var _this$props = this.props, light = _this$props.light, initials = _this$props.initials; var componentClassName = (0, _classnames.default)('c-Avatar__initials', light && 'is-light'); return /*#__PURE__*/(0, _jsxRuntime.jsx)(_Avatar.InitialsUI, { className: componentClassName, children: initials }); }; _proto.render = function render() { var _this$props2 = this.props, className = _this$props2.className, name = _this$props2.name, rest = (0, _objectWithoutPropertiesLoose2.default)(_this$props2, ["className", "name"]); var componentClassName = (0, _classnames.default)('c-Avatar__imageWrapper', this.state.isLoaded && 'is-loaded', className); var hasImage = this.state.isLoaded || !this.state.isLoaded && this.state.isLoading; var animationProps = (0, _Avatar2.getAnimationProps)(this.props); var contentMarkup = /*#__PURE__*/(0, _jsxRuntime.jsx)(_Avatar.ImageWrapperUI, (0, _extends2.default)({ className: componentClassName }, (0, _getValidProps.default)(rest), animationProps, { children: /*#__PURE__*/(0, _jsxRuntime.jsx)(_Avatar.ImageUI, { className: "c-Avatar__image", src: this.state.isLoaded ? this.sourceList[this.state.currentIndex] : null, children: /*#__PURE__*/(0, _jsxRuntime.jsx)("div", { className: "c-Avatar__name", children: /*#__PURE__*/(0, _jsxRuntime.jsx)(_VisuallyHidden.default, { children: name }) }) }) })); return hasImage ? contentMarkup : this.renderInitials(); }; return AvatarImage; }(_react.default.PureComponent); exports.AvatarImage = AvatarImage; AvatarImage.defaultProps = { animation: true, animationDuration: 160, animationEasing: 'ease', 'data-cy': 'AvatarImage', initials: null, light: false, name: null, onError: noop, onLoad: noop, src: null }; AvatarImage.propTypes = { animation: _propTypes.PropTypes.bool, animationDuration: _propTypes.PropTypes.number, animationEasing: _propTypes.PropTypes.string, /** Data attr for Cypress tests. */ 'data-cy': _propTypes.PropTypes.string, initials: _propTypes.PropTypes.oneOfType([_propTypes.PropTypes.number, _propTypes.PropTypes.string]), light: _propTypes.PropTypes.bool, name: _propTypes.PropTypes.string, onError: _propTypes.PropTypes.func, onLoad: _propTypes.PropTypes.func, src: _propTypes.PropTypes.any }; var _default = AvatarImage; exports.default = _default;