/* 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 { observer } from "mobx-react"; import React from "react"; import styled from "styled-components"; import { ThemePalette, ThemeProps } from "@src/components/Theme"; import Arrow from "@src/components/ui/Arrow"; import StatusIcon from "@src/components/ui/StatusComponents/StatusIcon"; import DateUtils from "@src/utils/DateUtils"; import type { Execution } from "@src/@types/Execution"; 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; hasOlderItems?: boolean; 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); }} > {DateUtils.getLocalDate(item.created_at).toFormat( "dd LLL yyyy", )} ))} ); } render() { return ( { this.wrapperRef = w; }} > {this.renderMainLine()} {this.renderItems()} ); } } export default Timeline;