StatusImage.jsx 4.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177
  1. /*
  2. Copyright (C) 2017 Cloudbase Solutions SRL
  3. This program is free software: you can redistribute it and/or modify
  4. it under the terms of the GNU Affero General Public License as
  5. published by the Free Software Foundation, either version 3 of the
  6. License, or (at your option) any later version.
  7. This program is distributed in the hope that it will be useful,
  8. but WITHOUT ANY WARRANTY; without even the implied warranty of
  9. MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  10. GNU Affero General Public License for more details.
  11. You should have received a copy of the GNU Affero General Public License
  12. along with this program. If not, see <http://www.gnu.org/licenses/>.
  13. */
  14. // @flow
  15. import React from 'react'
  16. import { observer } from 'mobx-react'
  17. import styled, { css } from 'styled-components'
  18. import StyleProps from '../../styleUtils/StyleProps'
  19. import Palette from '../../styleUtils/Palette'
  20. import errorImage from './images/error'
  21. import successImage from './images/success'
  22. import loadingImage from './images/loading.svg'
  23. import questionImage from './images/question'
  24. type Props = {
  25. status?: string,
  26. loading?: boolean,
  27. loadingProgress?: number,
  28. }
  29. const Wrapper = styled.div`
  30. position: relative;
  31. ${StyleProps.exactSize('96px')}
  32. background-repeat: no-repeat;
  33. background-position: center;
  34. `
  35. const ProgressSvgWrapper = styled.svg`
  36. ${StyleProps.exactSize('100%')}
  37. transform: rotate(-90deg);
  38. `
  39. const ProgressText = styled.div`
  40. color: ${Palette.primary};
  41. font-size: 18px;
  42. top: 36px;
  43. position: absolute;
  44. width: 100%;
  45. text-align: center;
  46. `
  47. const CircleProgressBar = styled.circle`
  48. transition: stroke-dashoffset ${StyleProps.animations.swift};
  49. `
  50. const dashAnimationStyle = css`
  51. .circle {
  52. stroke-dasharray: 300;
  53. stroke-dashoffset: 300;
  54. animation: circleDash 300ms ease-in-out 100ms forwards;
  55. }
  56. .path {
  57. stroke-dasharray: 60;
  58. stroke-dashoffset: 60;
  59. animation: pathDash 300ms ease-in-out 100ms forwards;
  60. }
  61. .dot {
  62. fill-opacity: 0;
  63. animation: appear 300ms ease-in-out 100ms forwards;
  64. }
  65. @keyframes appear {
  66. to { fill-opacity: 1; }
  67. }
  68. @keyframes circleDash {
  69. to { stroke-dashoffset: 600; }
  70. }
  71. @keyframes pathDash {
  72. to { stroke-dashoffset: 120; }
  73. }
  74. `
  75. const loadingAnimationStyle = css`
  76. background: url(${loadingImage}) center no-repeat;
  77. animation: rotate 1s linear infinite;
  78. @keyframes rotate {
  79. 0% {transform: rotate(0deg);}
  80. 100% {transform: rotate(360deg);}
  81. }
  82. `
  83. const Image = styled.div`
  84. ${StyleProps.exactSize('96px')}
  85. ${props => props.cssStyle}
  86. `
  87. const Images = {
  88. COMPLETED: {
  89. image: successImage,
  90. style: dashAnimationStyle,
  91. },
  92. ERROR: {
  93. image: errorImage,
  94. style: dashAnimationStyle,
  95. },
  96. RUNNING: {
  97. style: loadingAnimationStyle,
  98. image: '',
  99. },
  100. QUESTION: {
  101. image: questionImage,
  102. style: dashAnimationStyle,
  103. },
  104. }
  105. @observer
  106. class StatusImage extends React.Component<Props> {
  107. static defaultProps: $Shape<Props> = {
  108. status: 'RUNNING',
  109. }
  110. renderProgressImage() {
  111. return (
  112. <ProgressSvgWrapper id="svg" width="96" height="96" viewPort="0 0 96 96" version="1.1" xmlns="http://www.w3.org/2000/svg">
  113. <g strokeWidth="2">
  114. <circle
  115. r="47"
  116. cx="48"
  117. cy="48"
  118. fill="transparent"
  119. stroke={Palette.grayscale[2]}
  120. />
  121. <CircleProgressBar
  122. data-test-id="statusImage-progressBar"
  123. r="47"
  124. cx="48"
  125. cy="48"
  126. fill="transparent"
  127. stroke={Palette.primary}
  128. strokeDasharray="300 1000"
  129. strokeDashoffset={300 - ((this.props.loadingProgress || 0) * 3)}
  130. />
  131. </g>
  132. </ProgressSvgWrapper>
  133. )
  134. }
  135. renderProgressText() {
  136. return (
  137. <ProgressText
  138. data-test-id="statusImage-progressText"
  139. >{this.props.loadingProgress ? this.props.loadingProgress.toFixed(0) : 0}%</ProgressText>
  140. )
  141. }
  142. render() {
  143. let status = this.props.status || 'QUESTION'
  144. status = status.toUpperCase()
  145. status = status === 'SUCCESS' ? 'COMPLETED' : status
  146. status = status === 'CONFIRMATION' ? 'QUESTION' : status
  147. if (this.props.loading) {
  148. status = 'RUNNING'
  149. if (this.props.loadingProgress !== undefined && this.props.loadingProgress > -1) {
  150. status = 'PROGRESS'
  151. }
  152. }
  153. return (
  154. <Wrapper>
  155. {status !== 'PROGRESS' ? (
  156. <Image
  157. data-test-id="statusImage-image"
  158. dangerouslySetInnerHTML={{ __html: Images[status].image }}
  159. cssStyle={Images[status].style}
  160. />
  161. ) : null}
  162. {status === 'PROGRESS' ? this.renderProgressImage() : null}
  163. {status === 'PROGRESS' ? this.renderProgressText() : null}
  164. </Wrapper>
  165. )
  166. }
  167. }
  168. export default StatusImage