/*
Copyright (C) 2017 Cloudbase Solutions SRL
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as
published by the Free Software Foundation, either version 3 of the
License, or (at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with this program. If not, see .
*/
import React from 'react'
import { observer } from 'mobx-react'
import styled from 'styled-components'
import type { Execution } from '../../../../@types/Execution'
import Arrow from '../../../ui/Arrow/Arrow'
import StatusIcon from '../../../ui/StatusComponents/StatusIcon/StatusIcon'
import { ThemePalette, ThemeProps } from '../../../Theme'
import DateUtils from '../../../../utils/DateUtils'
const ITEM_GAP = 96
const ArrowStyled = styled(Arrow)`
opacity: ${props => (props.forceShow ? 1 : 0)};
position: absolute;
top: 0;
transition: all ${ThemeProps.animations.swift};
${props => (props.orientation === 'left' ? 'left: -19px;' : '')}
${props => (props.orientation === 'right' ? 'right: -19px;' : '')}
`
const Wrapper = styled.div`
position: relative;
height: 30px;
user-select: none;
&:hover ${ArrowStyled} {
opacity: 1;
}
`
const MainLine = styled.div`
width: 100%;
padding-top: 7px;
display: flex;
`
const ProgressLine = styled.div`
border-bottom: 2px solid ${ThemePalette.primary};
transition: all ${ThemeProps.animations.swift};
`
const EndLine = styled.div`
border-bottom: 2px solid ${ThemePalette.grayscale[2]};
transition: all ${ThemeProps.animations.swift};
`
const ItemsWrapper = styled.div`
overflow: hidden;
position: absolute;
top: 0;
left: 0;
right: 0;
`
const Items = styled.div`
display: flex;
`
const Item = styled.div`
display: flex;
flex-direction: column;
align-items: center;
margin-right: ${ITEM_GAP}px;
cursor: pointer;
min-width: 75px;
max-width: 75px;
`
const ItemLabel = styled.div`
font-size: 12px;
color: ${ThemePalette.grayscale[4]};
margin-top: 2px;
${props => (props.selected ? `color: ${ThemePalette.black};` : '')}
${props => (props.selected ? `font-weight: ${ThemeProps.fontWeights.medium};` : '')}
`
type Props = {
items?: Execution[] | null,
selectedItem?: Execution | null,
onPreviousClick?: () => void,
onNextClick?: () => void,
onItemClick?: (item: Execution) => void,
}
@observer
class Timeline extends React.Component {
itemsRef: HTMLElement | null | undefined
progressLineRef: HTMLElement | null | undefined
wrapperRef: HTMLElement | null | undefined
itemRef: HTMLElement | null | undefined
endLineRef: HTMLElement | null | undefined
componentDidMount() {
this.moveToSelectedItem()
if (!this.itemsRef) {
return
}
this.itemsRef.style.transition = `all ${ThemeProps.animations.swift}`
}
componentDidUpdate() {
if (this.itemsRef && !this.itemsRef.style.transition) {
this.itemsRef.style.transition = `all ${ThemeProps.animations.swift}`
}
this.moveToSelectedItem()
}
moveToSelectedItem() {
if (!this.progressLineRef || !this.endLineRef || !this.props.items || !this.wrapperRef) {
return
}
const selectedItem = this.props.selectedItem
if (!this.itemRef || !selectedItem || !this.itemsRef) {
this.progressLineRef.style.width = '0'
this.endLineRef.style.width = '100%'
return
}
const itemIndex = this.props.items.findIndex(i => i.id === selectedItem.id)
const halfWidth = this.wrapperRef.offsetWidth / 2
const itemGap = this.itemRef.offsetWidth + ITEM_GAP
const itemHalfWidth = this.itemRef.offsetWidth / 2
const offset = (halfWidth - (itemGap * itemIndex)) - itemHalfWidth
this.itemsRef.style.marginLeft = `${offset}px`
const lastItemPos = (itemGap * (this.props.items.length - 1)) + offset + itemHalfWidth
this.progressLineRef.style.width = `${lastItemPos}px`
this.endLineRef.style.width = `${Math.max(this.wrapperRef.offsetWidth - lastItemPos, 0)}px`
}
renderMainLine() {
return (
{
this.progressLineRef = line
}}
/>
{ this.endLineRef = line }} />
)
}
renderItems() {
if (!this.props.items || !this.props.items.length) {
return null
}
return (
{ this.itemsRef = items }}>
{this.props.items.map(item => (
- { this.itemRef = ref }}
onClick={() => { if (this.props.onItemClick) this.props.onItemClick(item) }}
data-test-id={`timeline-item-${item.id}`}
>
{DateUtils.getLocalTime(item.created_at).format('DD MMM YYYY')}
))}
)
}
render() {
return (
{ this.wrapperRef = w }}>
{this.renderMainLine()}
{this.renderItems()}
)
}
}
export default Timeline