/* 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