// Copyright (c) 2022 Uber Technologies, Inc. // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal // in the Software without restriction, including without limitation the rights // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell // copies of the Software, and to permit persons to whom the Software is // furnished to do so, subject to the following conditions: // // The above copyright notice and this permission notice shall be included in // all copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN // THE SOFTWARE. import React, {Component, ComponentType} from 'react'; import styled from 'styled-components'; import {ChickletButton, ChickletTag} from 'components/common/item-selector/chickleted-input'; import {Hash, Delete} from 'components/common/icons'; import DropdownList from 'components/common/item-selector/dropdown-list'; import {FormattedMessage} from 'localization'; import onClickOutside from 'react-onclickoutside'; import {FIELD_OPTS} from 'constants/default-settings'; import {TOOLTIP_FORMATS, TOOLTIP_FORMAT_TYPES, TOOLTIP_KEY} from 'constants/tooltip'; import {getFormatter} from 'utils/data-utils'; import TippyTooltip from 'components/common/tippy-tooltip'; interface TooltipChickletProps { disabled: boolean; item: {name: string}; displayOption: Function; remove: any; } type TooltipConfig = { fieldsToShow: { [key: string]: {name: string; format: string | null}[]; }; compareMode: boolean; compareType: string[]; }; type TooltipFields = { format?: string; id?: string; name?: string; fieldIdx?: number; type?: number; }; type TimeLabelFormat = { id: string; format: string | null; type: string; label: string; }; type IconDivProps = { status: string | null; }; const TIME_DISPLAY = '2020-05-11 14:00'; const getValue = fmt => fmt[TOOLTIP_KEY]; const addDTimeLabel = (formats: TimeLabelFormat[]) => formats.map(f => ({ ...f, label: f.type === TOOLTIP_FORMAT_TYPES.DATE_TIME || f.type === TOOLTIP_FORMAT_TYPES.DATE ? getFormatter(getValue(f))(TIME_DISPLAY) : f.label })); function getFormatLabels(fields: any, fieldName: string) { const fieldType = fields.find((f: TooltipFields) => f.name === fieldName).type; const tooltipTypes = (fieldType && FIELD_OPTS[fieldType].format.tooltip) || []; const formatLabels: TimeLabelFormat[] = Object.values(TOOLTIP_FORMATS).filter(t => tooltipTypes.includes(t.type) ); return addDTimeLabel(formatLabels); } const ChickletAddonWrapper = styled.div` position: relative; `; const ChickletAddon = styled.div` margin-right: 4px; `; const StyledPopover = styled.div` margin-left: -64px; position: absolute; top: 20px; width: 140px; z-index: 101; `; const hashStyles = { SHOW: 'SHOW', ACTIVE: 'ACTIVE' }; const IconDiv = styled.div.attrs({ className: 'tooltip-chicklet__icon' })` color: ${props => props.status === hashStyles.SHOW ? props.theme.subtextColorActive : props.status === hashStyles.ACTIVE ? props.theme.activeColor : props.theme.textColor}; `; function getFormatTooltip(formatLabels: TimeLabelFormat[], format: string | null) { if (!format) { return null; } const formatLabel = formatLabels.find(fl => getValue(fl) === format); if (formatLabel) { return formatLabel.label; } return typeof format === 'object' ? JSON.stringify(format, null, 2) : String(format); } function TooltipChickletFactory( dataId: string, config: TooltipConfig, onChange: (config: TooltipConfig) => void, fields: TooltipFields[] ): ComponentType { class TooltipChicklet extends Component { state = { show: false }; private node!: HTMLDivElement; componentDidMount() { document.addEventListener('mousedown', this.handleClickOutside, false); } componentWillUnmount() { document.removeEventListener('mousedown', this.handleClickOutside, false); } handleClickOutside = (e: any) => { if (this.node.contains(e.target)) { return; } }; render() { const {disabled, item, displayOption, remove} = this.props; const {show} = this.state; const tooltipField = config.fieldsToShow[dataId].find( fieldToShow => fieldToShow.name === item.name ); if (!tooltipField) { return null; } const formatLabels = getFormatLabels(fields, tooltipField.name); const hasFormat = Boolean(tooltipField.format); const selectionIndex = formatLabels.findIndex(fl => getValue(fl) === tooltipField.format); const hashStyle = show ? hashStyles.SHOW : hasFormat ? hashStyles.ACTIVE : null; return ( (this.node = node)}> {displayOption(item)} {formatLabels.length > 1 && ( ( {hasFormat ? ( getFormatTooltip(formatLabels, tooltipField.format) ) : ( )} )} > { e.stopPropagation(); this.setState({show: Boolean(!show)}); }} /> {show && ( label} onOptionSelected={(result, e) => { e.stopPropagation(); this.setState({ show: false }); const oldFieldsToShow = config.fieldsToShow[dataId]; const fieldsToShow = oldFieldsToShow.map(fieldToShow => { return fieldToShow.name === tooltipField.name ? { name: tooltipField.name, format: getValue(result) } : fieldToShow; }); const newConfig = { ...config, fieldsToShow: { ...config.fieldsToShow, [dataId]: fieldsToShow } }; onChange(newConfig); }} /> )} )} ); } } return onClickOutside(TooltipChicklet); } export default TooltipChickletFactory;