image.tsx 7.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210
  1. /* eslint-disable jsx-a11y/click-events-have-key-events */
  2. /* eslint-disable jsx-a11y/no-static-element-interactions */
  3. import React from "react";
  4. import BaseComponent from "../_base/baseComponent";
  5. import { ImageProps, ImageStates } from "./interface";
  6. import PropTypes from "prop-types";
  7. import { cssClasses } from "@douyinfe/semi-foundation/image/constants";
  8. import cls from "classnames";
  9. import { IconUploadError, IconEyeOpened } from "@douyinfe/semi-icons";
  10. import PreviewInner from "./previewInner";
  11. import "@douyinfe/semi-foundation/image/image.scss";
  12. import { PreviewContext, PreviewContextProps } from "./previewContext";
  13. import ImageFoundation, { ImageAdapter } from "@douyinfe/semi-foundation/image/imageFoundation";
  14. import LocaleConsumer from "../locale/localeConsumer";
  15. import { Locale } from "../locale/interface";
  16. import { isObject } from "lodash";
  17. import Skeleton from "../skeleton";
  18. const prefixCls = cssClasses.PREFIX;
  19. export default class Image extends BaseComponent<ImageProps, ImageStates> {
  20. static isSemiImage = true;
  21. static contextType = PreviewContext;
  22. static propTypes = {
  23. style: PropTypes.object,
  24. className: PropTypes.string,
  25. src: PropTypes.string,
  26. width: PropTypes.oneOfType([PropTypes.string, PropTypes.number]),
  27. height: PropTypes.oneOfType([PropTypes.string, PropTypes.number]),
  28. alt: PropTypes.string,
  29. placeholder: PropTypes.node,
  30. fallback: PropTypes.oneOfType([PropTypes.string, PropTypes.node]),
  31. preview: PropTypes.oneOfType([PropTypes.bool, PropTypes.object]),
  32. onLoad: PropTypes.func,
  33. onError: PropTypes.func,
  34. crossOrigin: PropTypes.string,
  35. imageID: PropTypes.number,
  36. }
  37. static defaultProps = {
  38. preview: true,
  39. };
  40. get adapter(): ImageAdapter<ImageProps, ImageStates> {
  41. return {
  42. ...super.adapter,
  43. getIsInGroup: () => this.isInGroup(),
  44. };
  45. }
  46. context: PreviewContextProps;
  47. foundation: ImageFoundation;
  48. constructor(props: ImageProps) {
  49. super(props);
  50. this.state = {
  51. src: "",
  52. loadStatus: "loading",
  53. previewVisible: false,
  54. };
  55. this.foundation = new ImageFoundation(this.adapter);
  56. }
  57. static getDerivedStateFromProps(props: ImageProps, state: ImageStates) {
  58. const willUpdateStates: Partial<ImageStates> = {};
  59. if (props.src !== state.src) {
  60. willUpdateStates.src = props.src;
  61. willUpdateStates.loadStatus = "loading";
  62. }
  63. return willUpdateStates;
  64. }
  65. isInGroup() {
  66. return Boolean(this.context && this.context.isGroup);
  67. }
  68. isLazyLoad() {
  69. if (this.context) {
  70. return this.context.lazyLoad;
  71. }
  72. return false;
  73. }
  74. handleClick = (e) => {
  75. this.foundation.handleClick(e);
  76. };
  77. handleLoaded = (e) => {
  78. this.foundation.handleLoaded(e);
  79. }
  80. handleError = (e) => {
  81. this.foundation.handleError(e);
  82. }
  83. handlePreviewVisibleChange = (visible: boolean) => {
  84. this.foundation.handlePreviewVisibleChange(visible);
  85. }
  86. renderDefaultLoading = () => {
  87. const { width, height } = this.props;
  88. return (
  89. <Skeleton.Image style={{ width, height }} />
  90. );
  91. };
  92. renderDefaultError = () => {
  93. const prefixClsName = `${prefixCls}-status`;
  94. return (
  95. <div className={prefixClsName}>
  96. <IconUploadError size={"extra-large"} />
  97. </div>
  98. );
  99. };
  100. renderLoad = () => {
  101. const prefixClsName = `${prefixCls}-status`;
  102. const { placeholder } = this.props;
  103. return (
  104. placeholder ? (
  105. <div className={prefixClsName}>
  106. {placeholder}
  107. </div>
  108. ) : this.renderDefaultLoading()
  109. );
  110. }
  111. renderError = () => {
  112. const { fallback } = this.props;
  113. const prefixClsName = `${prefixCls}-status`;
  114. const fallbackNode = typeof fallback === "string" ? (<img style={{ width: "100%", height: "100%" }}src={fallback} alt="fallback"/>) : fallback;
  115. return (
  116. fallback ? (
  117. <div className={prefixClsName}>
  118. {fallbackNode}
  119. </div>
  120. ) :this.renderDefaultError()
  121. );
  122. }
  123. renderExtra = () => {
  124. const { loadStatus } = this.state;
  125. return (
  126. <div className={`${prefixCls}-overlay`}>
  127. {loadStatus === "error" && this.renderError()}
  128. {loadStatus === "loading" && this.renderLoad()}
  129. </div>
  130. );
  131. }
  132. getLocalTextByKey = (key: string) => (
  133. <LocaleConsumer<Locale["Image"]> componentName="Image" >
  134. {(locale: Locale["Image"]) => locale[key]}
  135. </LocaleConsumer>
  136. );
  137. renderMask = () => (<div className={`${prefixCls}-mask`}>
  138. <div className={`${prefixCls}-mask-info`}>
  139. <IconEyeOpened size="extra-large"/>
  140. <span className={`${prefixCls}-mask-info-text`}>{this.getLocalTextByKey("preview")}</span>
  141. </div>
  142. </div>)
  143. render() {
  144. const { src, loadStatus, previewVisible } = this.state;
  145. const { width, height, alt, style, className, crossOrigin, preview } = this.props;
  146. const outerStyle = Object.assign({ width, height }, style);
  147. const outerCls = cls(prefixCls, className);
  148. const canPreview = loadStatus === "success" && preview && !this.isInGroup();
  149. const showPreviewCursor = preview && loadStatus === "success";
  150. const previewSrc = isObject(preview) ? ((preview as any).src ?? src) : src;
  151. const previewProps = isObject(preview) ? preview : {};
  152. return (
  153. // eslint-disable jsx-a11y/no-static-element-interactions
  154. // eslint-disable jsx-a11y/click-events-have-key-events
  155. <div
  156. style={outerStyle}
  157. className={outerCls}
  158. onClick={this.handleClick}
  159. >
  160. <img
  161. src={this.isInGroup() && this.isLazyLoad() ? undefined : src}
  162. data-src={src}
  163. alt={alt}
  164. className={cls(`${prefixCls}-img`, {
  165. [`${prefixCls}-img-preview`]: showPreviewCursor,
  166. [`${prefixCls}-img-error`]: loadStatus === "error",
  167. })}
  168. width={width}
  169. height={height}
  170. crossOrigin={crossOrigin}
  171. onError={this.handleError}
  172. onLoad={this.handleLoaded}
  173. />
  174. {loadStatus !== "success" && this.renderExtra()}
  175. {canPreview &&
  176. <PreviewInner
  177. {...previewProps}
  178. src={previewSrc}
  179. visible={previewVisible}
  180. onVisibleChange={this.handlePreviewVisibleChange}
  181. />
  182. }
  183. </div>
  184. );
  185. }
  186. }