@helpscout/hsds-react
Version:
React component library for Help Scout's Design System
301 lines (234 loc) • 9.28 kB
JavaScript
"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;