// 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, MouseEvent} from 'react'; import uniq from 'lodash/uniq'; import styled from 'styled-components'; import {createSelector} from 'reselect'; import ItemSelector from 'components/common/item-selector/item-selector'; import {PanelLabel, Button, Tooltip} from 'components/common/styled-components'; import Switch from 'components/common/switch'; import ColorPalette from './color-palette'; import CustomPalette from './custom-palette'; import {COLOR_RANGES, ColorRange, hexColor} from 'constants/color-ranges'; import {numberSort} from 'utils/data-utils'; import {reverseColorRange} from 'utils/color-utils'; import {FormattedMessage} from 'localization'; import {ColorUI} from 'layers/layer-factory'; import {NestedPartial} from 'reducers'; import {SwapHorizontal} from 'components/common/icons'; import {HexColumnConfigFactory} from './hex-column-config'; type ColorRangeSelectorProps = { fields: any[]; colorPaletteUI: ColorUI; selectedColorRange: ColorRange; onSelectColorRange: (p: ColorRange, e: MouseEvent) => void; onSelectColorColumn: Function; setColorPaletteUI: (newConfig: NestedPartial) => void; noHexColor?: boolean; }; type PaletteConfigProps = { label: string; value: string | number | boolean; config: { type: string; options: (string | number | boolean)[]; }; onChange: (v: string | number | boolean | object | null) => void; }; type ColorPaletteGroupProps = { reversed?: boolean; selected: ColorRange; colorRanges: ColorRange[]; onSelect: (p: ColorRange, e: MouseEvent) => void; title?: string; }; export const ALL_TYPES = uniq([ 'all', ...COLOR_RANGES.map(c => c.type).filter(ctype => ctype), 'custom', {value: hexColor, divider: true} ]); export const ALL_STEPS: number[] = uniq(COLOR_RANGES.map(d => d.colors.length)).sort(numberSort); const StyledColorConfig = styled.div` padding: 12px 12px 0 12px; `; const StyledColorRangeSelector = styled.div.attrs({ className: 'color-range-selector' })` padding-bottom: 12px; `; const StyledPaletteConfig = styled.div` margin-bottom: 8px; display: flex; justify-content: space-between; align-items: center; .color-palette__config__label { flex-grow: 1; } .color-palette__config__select { flex-grow: 1; } .item-selector .item-selector__dropdown { ${props => props.theme.secondaryInput}; } .color-palette__config__button { svg { margin: 0; } } `; const CONFIG_SETTINGS_CUSTOM_REVERSED_BUTTON_TYPE = 'custom-reversed-button'; const CONFIG_SETTINGS = { type: { type: 'select', options: ALL_TYPES }, steps: { type: 'select', options: ALL_STEPS }, reversed: { type: CONFIG_SETTINGS_CUSTOM_REVERSED_BUTTON_TYPE, options: [true, false] } }; ColorRangeSelectorFactory.deps = [ ColorPaletteGroupFactory, PaletteConfigFactory, HexColumnConfigFactory ]; export default function ColorRangeSelectorFactory( ColorPaletteGroup, PaletteConfig, HexColumnConfig ) { class ColorRangeSelector extends Component { static defaultProps = { colorRanges: COLOR_RANGES, onSelectColorRange: () => {}, setColorPaletteUI: () => {} }; colorRangesSelector = props => props.colorRanges; configTypeSelector = (props: ColorRangeSelectorProps) => props.colorPaletteUI.colorRangeConfig.type; configStepSelector = (props: ColorRangeSelectorProps) => props.colorPaletteUI.colorRangeConfig.steps; filteredColorRange = createSelector( this.colorRangesSelector, this.configTypeSelector, this.configStepSelector, (colorRanges, type, steps) => { return COLOR_RANGES.filter(colorRange => { const isType = type === 'all' || type === colorRange.type; const isStep = Number(steps) === colorRange.colors.length; return isType && isStep; }); } ); _updateConfig = ({ key, value }: { key: string; value: string | number | boolean | object | null; }) => { let steps = {}; const {colorRangeConfig} = this.props.colorPaletteUI; if (key === 'type' && colorRangeConfig.isHexColumn) { // If you're changing from HEX to a different one, then reset steps to 6 steps = {steps: 6}; } this._setColorRangeConfig({ [key]: value, ...(key === 'type' && {custom: value === 'custom', isHexColumn: value === hexColor}), ...(key === 'type' && value !== hexColor && {hexColumn: null}), ...steps }); }; _onSetCustomPalette = (config: NestedPartial) => { this.props.setColorPaletteUI({ customPalette: config }); }; _setColorRangeConfig = ( newConfig: Record ) => { this.props.setColorPaletteUI({ colorRangeConfig: newConfig }); }; _onCustomPaletteCancel = () => { this.props.setColorPaletteUI({ showSketcher: false, colorRangeConfig: {custom: false, isHexColumn: false} }); }; _onToggleSketcher = (val: boolean | number) => { this.props.setColorPaletteUI({ showSketcher: val }); }; _onSelectColumn = (column: any) => { this.props.onSelectColorColumn(column); this._setColorRangeConfig({ type: hexColor, hexColumn: column?.name }); }; render() { const {noHexColor} = this.props; const {customPalette, showSketcher, colorRangeConfig} = this.props.colorPaletteUI; const filteredColorRanges = this.filteredColorRange(this.props); return ( {Object.keys(colorRangeConfig) .filter( key => Boolean(CONFIG_SETTINGS[key]) && (colorRangeConfig.custom || colorRangeConfig.isHexColumn ? key === 'type' : true) ) .map(key => { let config = CONFIG_SETTINGS[key]; if (noHexColor) { config = {...config, options: config.options.filter(c => c.value !== hexColor)}; } return ( this._updateConfig({key, value})} /> ); })} {colorRangeConfig.custom ? ( ) : colorRangeConfig.isHexColumn ? ( ) : ( this.props.colorPaletteUI.colorRangeConfig.type} configStepSelector={() => this.props.colorPaletteUI.colorRangeConfig.steps} /> )} ); } } return ColorRangeSelector; } export function PaletteConfigFactory() { const PaletteConfig: React.FC = ({ label, value, config: {type, options}, onChange }) => { return ( e.stopPropagation()} >
{type !== CONFIG_SETTINGS_CUSTOM_REVERSED_BUTTON_TYPE && ( )}
{type === 'select' && (
)} {type === 'switch' && ( onChange(!value)} secondary /> )} {type === CONFIG_SETTINGS_CUSTOM_REVERSED_BUTTON_TYPE && ( )}
); }; return PaletteConfig; } const StyledColorRange = styled.div.attrs({ className: 'color-palette-outer' })` padding: 0 8px; :hover { background-color: ${props => props.theme.panelBackgroundHover}; cursor: pointer; } `; export function ColorPaletteGroupFactory() { const ColorPaletteGroup: React.FC = ({ reversed, onSelect, selected, colorRanges, title }) => { return (
{title && title} {colorRanges.map((colorRange, i) => ( onSelect(reversed ? reverseColorRange(true, colorRange) : colorRange, e)} > {colorRange.name} ))}
); }; return ColorPaletteGroup; }