Tasks.tsx 5.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187
  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. import React from 'react'
  15. import { observer } from 'mobx-react'
  16. import styled from 'styled-components'
  17. import TaskItem from '@src/components/modules/TransferModule/TaskItem/TaskItem'
  18. import type { Task } from '@src/@types/Task'
  19. import { ThemePalette, ThemeProps } from '@src/components/Theme'
  20. import StatusImage from '@src/components/ui/StatusComponents/StatusImage/StatusImage'
  21. const ColumnWidths = ['26%', '18%', '36%', '20%']
  22. const Wrapper = styled.div<any>``
  23. const ContentWrapper = styled.div`
  24. background: ${ThemePalette.grayscale[1]};
  25. `
  26. const LoadingWrapper = styled.div`
  27. display: flex;
  28. align-items: center;
  29. justify-content: center;
  30. padding: 64px;
  31. `
  32. const Header = styled.div<any>`
  33. display: flex;
  34. border-bottom: 1px solid ${ThemePalette.grayscale[5]};
  35. padding: 4px 8px;
  36. `
  37. const HeaderData = styled.div<any>`
  38. width: ${props => props.width};
  39. font-size: 10px;
  40. color: ${ThemePalette.grayscale[5]};
  41. font-weight: ${ThemeProps.fontWeights.medium};
  42. text-transform: uppercase;
  43. `
  44. const Body = styled.div<any>``
  45. type Props = {
  46. items: Task[],
  47. loading?: boolean,
  48. }
  49. type State = {
  50. openedItems: Task[],
  51. }
  52. @observer
  53. class Tasks extends React.Component<Props, State> {
  54. state: State = {
  55. openedItems: [],
  56. }
  57. dragStartPosition: { x: number, y: number } | null = null
  58. UNSAFE_componentWillMount() {
  59. this.UNSAFE_componentWillReceiveProps(this.props)
  60. }
  61. UNSAFE_componentWillReceiveProps(props: Props) {
  62. this.setState(prevState => {
  63. let openedItems = prevState.openedItems
  64. props.items.forEach(item => {
  65. if (item.status === 'RUNNING') {
  66. openedItems.push(item)
  67. return
  68. }
  69. // Close items that were previously in RUNNING state, but they no longer are
  70. const oldItem = this.props.items.find(i => i.id === item.id)
  71. if (oldItem && oldItem.status === 'RUNNING') {
  72. openedItems = openedItems.filter(i => i.id !== oldItem.id)
  73. }
  74. })
  75. return { openedItems }
  76. })
  77. }
  78. get isLoading() {
  79. return this.props.loading || this.props.items.length === 0
  80. }
  81. handleItemMouseDown(e: React.MouseEvent<HTMLDivElement>) {
  82. this.dragStartPosition = { x: e.screenX, y: e.screenY }
  83. }
  84. handleItemMouseUp(e: React.MouseEvent<HTMLDivElement>, item: Task) {
  85. this.dragStartPosition = this.dragStartPosition || { x: e.screenX, y: e.screenY }
  86. if (this.dragStartPosition
  87. && Math.abs(this.dragStartPosition.x - e.screenX) < 3
  88. && Math.abs(this.dragStartPosition.y - e.screenY) < 3) {
  89. this.toggleItem(item)
  90. }
  91. this.dragStartPosition = null
  92. }
  93. handleDependsOnClick(id: string) {
  94. const item = this.props.items.find(i => i.id === id)
  95. if (item) this.toggleItem(item)
  96. }
  97. toggleItem(item: Task) {
  98. this.setState(prevState => {
  99. let openedItems = prevState.openedItems
  100. if (openedItems.find(i => i.id === item.id)) {
  101. openedItems = openedItems.filter(i => i.id !== item.id)
  102. } else {
  103. openedItems = openedItems.filter(i => i.status === 'RUNNING')
  104. openedItems.push(item)
  105. }
  106. return { openedItems }
  107. })
  108. }
  109. renderLoading() {
  110. return (
  111. <LoadingWrapper>
  112. <StatusImage loading />
  113. </LoadingWrapper>
  114. )
  115. }
  116. renderHeader() {
  117. return (
  118. <Header>
  119. <HeaderData width={ColumnWidths[0]}>Task</HeaderData>
  120. <HeaderData width={ColumnWidths[1]}>Instance</HeaderData>
  121. <HeaderData width={ColumnWidths[2]}>Latest Message</HeaderData>
  122. <HeaderData width={ColumnWidths[3]}>Timestamp</HeaderData>
  123. </Header>
  124. )
  125. }
  126. renderBody() {
  127. return (
  128. <Body>
  129. {this.props.items.map(item => (
  130. <TaskItem
  131. onMouseDown={e => this.handleItemMouseDown(e)}
  132. onMouseUp={e => this.handleItemMouseUp(e, item)}
  133. key={item.id}
  134. item={item}
  135. columnWidths={ColumnWidths}
  136. open={Boolean(this.state.openedItems.find(i => i.id === item.id))}
  137. onDependsOnClick={id => { this.handleDependsOnClick(id) }}
  138. data-test-id={`tasks-item-${item.id}`}
  139. />
  140. ))}
  141. </Body>
  142. )
  143. }
  144. renderContent() {
  145. return (
  146. <ContentWrapper>
  147. {this.renderHeader()}
  148. {this.renderBody()}
  149. </ContentWrapper>
  150. )
  151. }
  152. render() {
  153. return (
  154. <Wrapper>
  155. {!this.isLoading ? this.renderContent() : null}
  156. {this.isLoading ? this.renderLoading() : null}
  157. </Wrapper>
  158. )
  159. }
  160. }
  161. export default Tasks