diff --git a/examples/demo-app/src/factories/map-control.js b/examples/demo-app/src/factories/map-control.js index 96f39e9a24..a22eaea614 100644 --- a/examples/demo-app/src/factories/map-control.js +++ b/examples/demo-app/src/factories/map-control.js @@ -58,10 +58,14 @@ const StyledMapControlOverlay = styled.div` pointer-events: all; } - margin-top: ${props => props.theme.rightPanelMarginTop}px; - margin-right: ${props => props.theme.rightPanelMarginRight}px; + margin-top: ${props => (props.rightPanelVisible ? props.theme.rightPanelMarginTop : 0)}px; + margin-right: ${props => (props.rightPanelVisible ? props.theme.rightPanelMarginRight : 0)}px; ${props => (props.fullHeight ? 'height' : 'max-height')}: calc(100% - ${props => props.theme.rightPanelMarginTop + props.theme.bottomWidgetPaddingBottom}px); + + .map-control { + ${props => (props.rightPanelVisible ? 'padding-top: 0px;' : '')} + } `; CustomMapControlFactory.deps = [ @@ -74,9 +78,9 @@ function CustomMapControlFactory(EffectControl, EffectManager, ...deps) { const actionComponents = [...(MapControl.defaultProps?.actionComponents ?? []), EffectControl]; const CustomMapControl = props => { - const showEffects = props.mapControls?.effect?.active; + const showEffects = Boolean(props.mapControls?.effect?.active); return ( - + {!props.isExport && props.currentSample ? : null} diff --git a/src/components/src/common/icons/arrow-down-small.tsx b/src/components/src/common/icons/arrow-down-small.tsx new file mode 100644 index 0000000000..84e6853194 --- /dev/null +++ b/src/components/src/common/icons/arrow-down-small.tsx @@ -0,0 +1,30 @@ +import React, {Component} from 'react'; +import PropTypes from 'prop-types'; +import Base from './base'; + +export default class ArrowDownSmall extends Component { + static propTypes = { + /** Set the height of the icon, ex. '16px' */ + height: PropTypes.string + }; + + static defaultProps = { + height: '16px', + viewBox: '0 0 16 16', + predefinedClassName: 'data-ex-icons-arrow-down-small' + }; + + render() { + return ( + + + + + + ); + } +} diff --git a/src/components/src/common/icons/calendar.tsx b/src/components/src/common/icons/calendar.tsx new file mode 100644 index 0000000000..be8117dbf2 --- /dev/null +++ b/src/components/src/common/icons/calendar.tsx @@ -0,0 +1,24 @@ +import React, {Component} from 'react'; +import Base, {BaseProps} from './base'; + +export default class Calendar extends Component> { + static defaultProps = { + height: '16px', + viewBox: '0 0 16 16', + predefinedClassName: 'data-ex-icons-calendar' + }; + + render() { + return ( + + + + + + ); + } +} diff --git a/src/components/src/common/icons/effects/brightness-contrast.tsx b/src/components/src/common/icons/effects/brightness-contrast.tsx new file mode 100644 index 0000000000..03f5b8cf50 --- /dev/null +++ b/src/components/src/common/icons/effects/brightness-contrast.tsx @@ -0,0 +1,38 @@ +import React, {Component} from 'react'; +import PropTypes from 'prop-types'; +import Base from '../base'; + +export default class BrightnessContrast extends Component { + static propTypes = { + /** Set the height of the icon, ex. '16px' */ + height: PropTypes.string + }; + + static defaultProps = { + height: '16px', + viewBox: '0 0 16 16', + predefinedClassName: 'data-ex-icons-brightness-contrast' + }; + + render() { + return ( + + + + + + + + + + + + + + ); + } +} diff --git a/src/components/src/common/icons/effects/color-halftone.tsx b/src/components/src/common/icons/effects/color-halftone.tsx new file mode 100644 index 0000000000..2aec45292e --- /dev/null +++ b/src/components/src/common/icons/effects/color-halftone.tsx @@ -0,0 +1,82 @@ +import React, {Component} from 'react'; +import PropTypes from 'prop-types'; +import Base from '../base'; + +export default class ColorHalftone extends Component { + static propTypes = { + /** Set the height of the icon, ex. '16px' */ + height: PropTypes.string + }; + + static defaultProps = { + height: '16px', + viewBox: '0 0 16 16', + predefinedClassName: 'data-ex-icons-color-halftone' + }; + + render() { + return ( + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + ); + } +} diff --git a/src/components/src/common/icons/effects/dot-screen.tsx b/src/components/src/common/icons/effects/dot-screen.tsx new file mode 100644 index 0000000000..9b22475c04 --- /dev/null +++ b/src/components/src/common/icons/effects/dot-screen.tsx @@ -0,0 +1,55 @@ +import React, {Component} from 'react'; +import PropTypes from 'prop-types'; +import Base from '../base'; + +export default class DotScreen extends Component { + static propTypes = { + /** Set the height of the icon, ex. '16px' */ + height: PropTypes.string + }; + + static defaultProps = { + height: '16px', + viewBox: '0 0 16 16', + predefinedClassName: 'data-ex-icons-dot-screen' + }; + + render() { + return ( + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + ); + } +} diff --git a/src/components/src/common/icons/effects/edge-work.tsx b/src/components/src/common/icons/effects/edge-work.tsx new file mode 100644 index 0000000000..99ecf49c15 --- /dev/null +++ b/src/components/src/common/icons/effects/edge-work.tsx @@ -0,0 +1,35 @@ +import React, {Component} from 'react'; +import PropTypes from 'prop-types'; +import Base from '../base'; + +export default class EdgeWork extends Component { + static propTypes = { + /** Set the height of the icon, ex. '16px' */ + height: PropTypes.string + }; + + static defaultProps = { + height: '16px', + viewBox: '0 0 16 16', + predefinedClassName: 'data-ex-icons-edge-work' + }; + + render() { + return ( + + + + + + + + + + + + + + + ); + } +} diff --git a/src/components/src/common/icons/effects/hexagonal-pixelate.tsx b/src/components/src/common/icons/effects/hexagonal-pixelate.tsx new file mode 100644 index 0000000000..07f9345652 --- /dev/null +++ b/src/components/src/common/icons/effects/hexagonal-pixelate.tsx @@ -0,0 +1,113 @@ +import React, {Component} from 'react'; +import PropTypes from 'prop-types'; +import Base from '../base'; + +export default class HexagonalPixelate extends Component { + static propTypes = { + /** Set the height of the icon, ex. '16px' */ + height: PropTypes.string + }; + + static defaultProps = { + height: '16px', + viewBox: '0 0 16 16', + predefinedClassName: 'data-ex-icons-hexagonal-pixelate' + }; + + render() { + return ( + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + ); + } +} diff --git a/src/components/src/common/icons/effects/hue-saturation.tsx b/src/components/src/common/icons/effects/hue-saturation.tsx new file mode 100644 index 0000000000..7e37bbd795 --- /dev/null +++ b/src/components/src/common/icons/effects/hue-saturation.tsx @@ -0,0 +1,37 @@ +import React, {Component} from 'react'; +import PropTypes from 'prop-types'; +import Base from '../base'; + +export default class HueSaturation extends Component { + static propTypes = { + /** Set the height of the icon, ex. '16px' */ + height: PropTypes.string + }; + + static defaultProps = { + height: '16px', + viewBox: '0 0 16 16', + predefinedClassName: 'data-ex-icons-hue-saturation' + }; + + render() { + return ( + + + + + + + + + + + + + ); + } +} diff --git a/src/components/src/common/icons/effects/ink.tsx b/src/components/src/common/icons/effects/ink.tsx new file mode 100644 index 0000000000..8d48b2feb9 --- /dev/null +++ b/src/components/src/common/icons/effects/ink.tsx @@ -0,0 +1,33 @@ +import React, {Component} from 'react'; +import PropTypes from 'prop-types'; +import Base from '../base'; + +export default class Ink extends Component { + static propTypes = { + /** Set the height of the icon, ex. '16px' */ + height: PropTypes.string + }; + + static defaultProps = { + height: '16px', + viewBox: '0 0 16 16', + predefinedClassName: 'data-ex-icons-ink' + }; + + render() { + return ( + + + + + + + + + + + + + ); + } +} diff --git a/src/components/src/common/icons/effects/light-and-shadow.tsx b/src/components/src/common/icons/effects/light-and-shadow.tsx new file mode 100644 index 0000000000..251e96f489 --- /dev/null +++ b/src/components/src/common/icons/effects/light-and-shadow.tsx @@ -0,0 +1,28 @@ +import React, {Component} from 'react'; +import PropTypes from 'prop-types'; +import Base from '../base'; + +export default class LightAndShadow extends Component { + static propTypes = { + /** Set the height of the icon, ex. '16px' */ + height: PropTypes.string + }; + + static defaultProps = { + height: '16px', + viewBox: '0 0 16 16', + predefinedClassName: 'data-ex-icons-light-and-shadow' + }; + + render() { + return ( + + + + ); + } +} diff --git a/src/components/src/common/icons/effects/magic-wand.tsx b/src/components/src/common/icons/effects/magic-wand.tsx new file mode 100644 index 0000000000..880f30540a --- /dev/null +++ b/src/components/src/common/icons/effects/magic-wand.tsx @@ -0,0 +1,34 @@ +import React, {Component} from 'react'; +import PropTypes from 'prop-types'; +import Base from '../base'; + +export default class MagicWand extends Component { + static propTypes = { + /** Set the height of the icon, ex. '16px' */ + height: PropTypes.string + }; + + static defaultProps = { + height: '16px', + viewBox: '2 2 20 20', + predefinedClassName: 'data-ex-icons-effects-magic-wand' + }; + + render() { + return ( + + + + + + + + + + + + {' '} + + ); + } +} diff --git a/src/components/src/common/icons/effects/magnify.tsx b/src/components/src/common/icons/effects/magnify.tsx new file mode 100644 index 0000000000..f30e2c0aa6 --- /dev/null +++ b/src/components/src/common/icons/effects/magnify.tsx @@ -0,0 +1,37 @@ +import React, {Component} from 'react'; +import PropTypes from 'prop-types'; +import Base from '../base'; + +export default class Magnify extends Component { + static propTypes = { + /** Set the height of the icon, ex. '16px' */ + height: PropTypes.string + }; + + static defaultProps = { + height: '16px', + viewBox: '0 0 16 16', + predefinedClassName: 'data-ex-icons-magnify' + }; + + render() { + return ( + + + + + + + + + + + + + ); + } +} diff --git a/src/components/src/common/icons/effects/noise.tsx b/src/components/src/common/icons/effects/noise.tsx new file mode 100644 index 0000000000..50469e4d42 --- /dev/null +++ b/src/components/src/common/icons/effects/noise.tsx @@ -0,0 +1,60 @@ +import React, {Component} from 'react'; +import PropTypes from 'prop-types'; +import Base from '../base'; + +export default class Noise extends Component { + static propTypes = { + /** Set the height of the icon, ex. '16px' */ + height: PropTypes.string + }; + + static defaultProps = { + height: '16px', + viewBox: '0 0 16 16', + predefinedClassName: 'data-ex-icons-noise' + }; + + render() { + return ( + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + ); + } +} diff --git a/src/components/src/common/icons/effects/sepia.tsx b/src/components/src/common/icons/effects/sepia.tsx new file mode 100644 index 0000000000..867a52e0bb --- /dev/null +++ b/src/components/src/common/icons/effects/sepia.tsx @@ -0,0 +1,33 @@ +import React, {Component} from 'react'; +import PropTypes from 'prop-types'; +import Base from '../base'; + +export default class Sepia extends Component { + static propTypes = { + /** Set the height of the icon, ex. '16px' */ + height: PropTypes.string + }; + + static defaultProps = { + height: '16px', + viewBox: '0 0 16 16', + predefinedClassName: 'data-ex-icons-sepia' + }; + + render() { + return ( + + + + + + + + + ); + } +} diff --git a/src/components/src/common/icons/effects/tilt-shift.tsx b/src/components/src/common/icons/effects/tilt-shift.tsx new file mode 100644 index 0000000000..3582d20010 --- /dev/null +++ b/src/components/src/common/icons/effects/tilt-shift.tsx @@ -0,0 +1,41 @@ +import React, {Component} from 'react'; +import PropTypes from 'prop-types'; +import Base from '../base'; + +export default class TiltShift extends Component { + static propTypes = { + /** Set the height of the icon, ex. '16px' */ + height: PropTypes.string + }; + + static defaultProps = { + height: '16px', + viewBox: '0 0 16 16', + predefinedClassName: 'data-ex-icons-tilt-shift' + }; + + render() { + return ( + + + + + + + + + + + + + + + + + ); + } +} diff --git a/src/components/src/common/icons/effects/triangle-blur.tsx b/src/components/src/common/icons/effects/triangle-blur.tsx new file mode 100644 index 0000000000..fd4b5b50a7 --- /dev/null +++ b/src/components/src/common/icons/effects/triangle-blur.tsx @@ -0,0 +1,39 @@ +import React, {Component} from 'react'; +import PropTypes from 'prop-types'; +import Base from '../base'; + +export default class TriangleBlur extends Component { + static propTypes = { + /** Set the height of the icon, ex. '16px' */ + height: PropTypes.string + }; + + static defaultProps = { + height: '16px', + viewBox: '0 0 16 16', + predefinedClassName: 'data-ex-icons-triangle-blur' + }; + + render() { + return ( + + + + + + + + + + + + + + + ); + } +} diff --git a/src/components/src/common/icons/effects/vibrance.tsx b/src/components/src/common/icons/effects/vibrance.tsx new file mode 100644 index 0000000000..a0971a5545 --- /dev/null +++ b/src/components/src/common/icons/effects/vibrance.tsx @@ -0,0 +1,37 @@ +import React, {Component} from 'react'; +import PropTypes from 'prop-types'; +import Base from '../base'; + +export default class Vibrance extends Component { + static propTypes = { + /** Set the height of the icon, ex. '16px' */ + height: PropTypes.string + }; + + static defaultProps = { + height: '16px', + viewBox: '0 0 16 16', + predefinedClassName: 'data-ex-icons-vibrance' + }; + + render() { + return ( + + + + + + + + + + + + + ); + } +} diff --git a/src/components/src/common/icons/effects/vignette.tsx b/src/components/src/common/icons/effects/vignette.tsx new file mode 100644 index 0000000000..19d1e8f3b6 --- /dev/null +++ b/src/components/src/common/icons/effects/vignette.tsx @@ -0,0 +1,30 @@ +import React, {Component} from 'react'; +import PropTypes from 'prop-types'; +import Base from '../base'; + +export default class Vignette extends Component { + static propTypes = { + /** Set the height of the icon, ex. '16px' */ + height: PropTypes.string + }; + + static defaultProps = { + height: '16px', + viewBox: '0 0 16 16', + predefinedClassName: 'data-ex-icons-vignette' + }; + + render() { + return ( + + + + + + ); + } +} diff --git a/src/components/src/common/icons/effects/zoom-blur.tsx b/src/components/src/common/icons/effects/zoom-blur.tsx new file mode 100644 index 0000000000..47e8d9d787 --- /dev/null +++ b/src/components/src/common/icons/effects/zoom-blur.tsx @@ -0,0 +1,51 @@ +import React, {Component} from 'react'; +import PropTypes from 'prop-types'; +import Base from '../base'; + +export default class ZoomBlur extends Component { + static propTypes = { + /** Set the height of the icon, ex. '16px' */ + height: PropTypes.string + }; + + static defaultProps = { + height: '16px', + viewBox: '0 0 16 16', + predefinedClassName: 'data-ex-icons-zoom-blur' + }; + + render() { + return ( + + + + + + + + + + + + + + + + + + + + + + + + + + + ); + } +} diff --git a/src/components/src/common/icons/index.tsx b/src/components/src/common/icons/index.tsx index 059bfb9c1e..dafb7d662f 100644 --- a/src/components/src/common/icons/index.tsx +++ b/src/components/src/common/icons/index.tsx @@ -96,4 +96,24 @@ export {default as Messages} from './messages'; export {default as Crosshairs} from './crosshairs'; export {default as CursorClick} from './cursor-click'; export {default as CursorPoint} from './cursor-point'; -export {default as Pin} from './pin'; \ No newline at end of file +export {default as Pin} from './pin'; +export {default as Calendar} from './calendar'; +export {default as LocationMarker} from './location-marker'; +export {default as ArrowDownSmall} from './arrow-down-small'; +export {default as MagicWand} from './effects/magic-wand'; +export {default as LightAndShadowEffectIcon} from './effects/light-and-shadow'; +export {default as InkEffectIcon} from './effects/ink'; +export {default as BrightnessContrastEffectIcon} from './effects/brightness-contrast'; +export {default as HueSaturationEffectIcon} from './effects/hue-saturation'; +export {default as VibranceEffectIcon} from './effects/vibrance'; +export {default as SepiaEffectIcon} from './effects/sepia'; +export {default as DotScreenEffectIcon} from './effects/dot-screen'; +export {default as ColorHalftoneEffectIcon} from './effects/color-halftone'; +export {default as NoiseEffectIcon} from './effects/noise'; +export {default as TriangleBlurEffectIcon} from './effects/triangle-blur'; +export {default as ZoomBlurEffectIcon} from './effects/zoom-blur'; +export {default as TiltShiftEffectIcon} from './effects/tilt-shift'; +export {default as EdgeWorkEffectIcon} from './effects/edge-work'; +export {default as VignetteEffectIcon} from './effects/vignette'; +export {default as MagnifyEffectIcon} from './effects/magnify'; +export {default as HexagonalPixelateEffectIcon} from './effects/hexagonal-pixelate'; diff --git a/src/components/src/common/icons/location-marker.tsx b/src/components/src/common/icons/location-marker.tsx new file mode 100644 index 0000000000..6fb0ff35e1 --- /dev/null +++ b/src/components/src/common/icons/location-marker.tsx @@ -0,0 +1,33 @@ +import React, {Component} from 'react'; +import PropTypes from 'prop-types'; +import Base from './base'; + +export default class LocationMarker extends Component { + static propTypes = { + /** Set the height of the icon, ex. '16px' */ + height: PropTypes.string + }; + + static defaultProps = { + height: '16px', + viewBox: '0 0 16 16', + predefinedClassName: 'data-ex-icons-location-marker' + }; + + render() { + return ( + + + + + + + + + + + + + ); + } +} diff --git a/src/components/src/common/item-selector/item-selector.tsx b/src/components/src/common/item-selector/item-selector.tsx index f290a87e6f..6b1330d394 100644 --- a/src/components/src/common/item-selector/item-selector.tsx +++ b/src/components/src/common/item-selector/item-selector.tsx @@ -148,6 +148,7 @@ export type ItemSelectorProps = { placeholder?: string; closeOnSelect?: boolean; typeaheadPlaceholder?: string; + DropDownWrapperComponent?: ComponentType | null; DropdownHeaderComponent?: ComponentType | null; DropDownRenderComponent?: ComponentType; DropDownLineItemRenderComponent?: ComponentType; @@ -166,6 +167,7 @@ class ItemSelectorUnmemoized extends Component { searchable: true, DropDownRenderComponent: DropdownList, DropDownLineItemRenderComponent: ListItem, + DropDownWrapperComponent: DropdownWrapper, reorderItems: undefined }; @@ -277,9 +279,13 @@ class ItemSelectorUnmemoized extends Component { const {placement = 'bottom'} = this.props; const {dimensions} = this.state; + const DropDownWrapperComponent = this.props.DropDownWrapperComponent as React.ComponentType< + any + >; + return ( - + { selectedItems={toArray(this.props.selectedItems)} light={this.props.inputTheme === 'light'} /> - + ); } diff --git a/src/components/src/common/range-slider.tsx b/src/components/src/common/range-slider.tsx index 9a9ae3cf60..614e672724 100644 --- a/src/components/src/common/range-slider.tsx +++ b/src/components/src/common/range-slider.tsx @@ -63,7 +63,7 @@ interface RangeSliderProps { range?: number[]; value0: number; value1: number; - onChange?: (val: number[]) => void; // TODO + onChange?: (val: number[], e?: Event | null) => void; // TODO histogram?: any[]; isRanged?: boolean; isEnlarged?: boolean; diff --git a/src/components/src/common/styled-components.tsx b/src/components/src/common/styled-components.tsx index 9a1392f01b..0b87c5aade 100644 --- a/src/components/src/common/styled-components.tsx +++ b/src/components/src/common/styled-components.tsx @@ -787,9 +787,9 @@ export const StyledTimePicker = styled(TimePicker)` display: inline-flex; position: relative; font-family: ${props => props.theme.fontFamily}; - font-size: ${props => props.theme.datePickerFontSize}; - background-color: ${props => props.theme.datePickerBgdColor}; - color: ${props => props.theme.textColor}; + font-size: ${props => props.theme.inputFontSize}; + background-color: ${props => props.theme.inputBgd}; + color: ${props => props.theme.effectPanelTextMain}; } .react-time-picker, .react-time-picker *, @@ -803,20 +803,23 @@ export const StyledTimePicker = styled(TimePicker)` display: flex; flex-grow: 1; flex-shrink: 0; - background-color: ${props => props.theme.datePickerBgdColor}; - border: thin solid ${props => props.theme.datePickerBgdColor}; - border-radius: 2px; - width: 103px; + background-color: ${props => props.theme.inputBgd}; + border-radius: 4px; + width: 110px; white-space: nowrap; } .react-time-picker__wrapper:hover { - background-color: ${props => props.theme.datePickerBgdHover}; + background-color: ${props => props.theme.inputBgdHover}; } .react-time-picker__inputGroup { min-width: calc((4px * 3) + 0.54em * 6 + 0.217em * 2); flex-grow: 1; - padding: 4px 10px; + padding: 4px 2px; box-sizing: content-box; + display: flex; + justify-content: end; + align-items: center; + height: 24px; } .react-time-picker__inputGroup__divider { padding: 1px 0; @@ -825,9 +828,9 @@ export const StyledTimePicker = styled(TimePicker)` .react-time-picker__inputGroup__divider, .react-time-picker__inputGroup__leadingZero { display: inline-block; - color: ${props => props.theme.textColor}; + color: ${props => props.theme.effectPanelTextMain}; font-family: ${props => props.theme.fontFamily}; - font-size: ${props => props.theme.datePickerFontSize}; + font-size: ${props => props.theme.inputFontSize}; } .react-time-picker__inputGroup__input { min-width: 0.54em; @@ -836,9 +839,9 @@ export const StyledTimePicker = styled(TimePicker)` padding: 0 1px; border: 0; background: transparent; - color: ${props => props.theme.textColor}; + color: ${props => props.theme.effectPanelTextMain}; font-family: ${props => props.theme.fontFamily}; - font-size: ${props => props.theme.datePickerFontSize}; + font-size: ${props => props.theme.inputFontSize}; box-sizing: content-box; -webkit-appearance: textfield; -moz-appearance: textfield; @@ -859,15 +862,16 @@ export const StyledTimePicker = styled(TimePicker)` padding-left: calc(1px + 0.54em); } .react-time-picker__inputGroup__amPm { + max-width: 37px; font: inherit; -webkit-appearance: menulist; -moz-appearance: menulist; appearance: menulist; - font-size: ${props => props.theme.datePickerFontSize}; + font-size: ${props => props.theme.inputFontSize}; } .react-time-picker__button { border: 0; - background-color: ${props => props.theme.datePickerBgdColor}; + background-color: ${props => props.theme.inputBgd}; padding: 4px 6px; } .react-time-picker__button:enabled { @@ -887,7 +891,7 @@ export const StyledDatePicker = styled(DatePicker)` display: inline-flex; position: relative; font-family: ${props => props.theme.fontFamily}; - font-size: ${props => props.theme.datePickerFontSize}; + font-size: ${props => props.theme.inputFontSize}; } .react-date-picker, .react-date-picker *, @@ -905,26 +909,30 @@ export const StyledDatePicker = styled(DatePicker)` display: flex; flex-grow: 1; flex-shrink: 0; - border: thin solid ${props => props.theme.datePickerBgdColor}; - border-radius: 2px; - width: 90px; + width: 110px; white-space: nowrap; + border-radius: 4px; } .react-date-picker__inputGroup { min-width: calc((4px * 3) + 0.54em * 8 + 0.217em * 2); flex-grow: 1; - padding: 4px 10px; + padding: 4px 9px; box-sizing: content-box; - background-color: ${props => props.theme.datePickerBgdColor}; + background-color: ${props => props.theme.inputBgd}; + display: flex; + justify-content: end; + align-items: center; + height: 24px; + border-radius: 4px; } .react-date-picker__inputGroup:hover { - background-color: ${props => props.theme.datePickerBgdHover}; + background-color: ${props => props.theme.inputBgdHover}; } .react-date-picker__inputGroup__divider { padding: 1px 0; white-space: pre; - color: ${props => props.theme.textColor}; + color: ${props => props.theme.effectPanelTextMain}; } .react-date-picker__inputGroup__divider, .react-date-picker__inputGroup__leadingZero { @@ -937,9 +945,9 @@ export const StyledDatePicker = styled(DatePicker)` padding: 0 1px; border: 0; background: none; - color: ${props => props.theme.textColor}; + color: ${props => props.theme.effectPanelTextMain}; font: inherit; - font-size: ${props => props.theme.datePickerFontSize}; + font-size: ${props => props.theme.inputFontSize}; box-sizing: content-box; -webkit-appearance: textfield; -moz-appearance: textfield; @@ -956,7 +964,7 @@ export const StyledDatePicker = styled(DatePicker)` margin: 0; } .react-date-picker__inputGroup__input:invalid { - background: ${props => props.theme.datePickerBgdColor}; + background: ${props => props.theme.inputBgd}; } .react-date-picker__inputGroup__input--hasLeadingZero { margin-left: -0.54em; @@ -966,8 +974,7 @@ export const StyledDatePicker = styled(DatePicker)` width: 257px; max-width: 100vw; z-index: 11; - inset: auto auto auto -25px !important; - color: ${props => props.theme.textColor}; + color: ${props => props.theme.effectPanelTextSecondary1}; } .react-date-picker__calendar--closed { display: none; @@ -979,14 +986,14 @@ export const StyledDatePicker = styled(DatePicker)` display: none; } .react-calendar { - width: 257px; + width: 256px; max-width: 100%; - color: ${props => props.theme.textColor}; - background: ${props => props.theme.datePickerBgdHover}; - border: 1px solid ${props => props.theme.textColor}; - border-radius: 2px; + color: ${props => props.theme.effectPanelTextSecondary1}; + background: ${props => props.theme.inputBgdHover}; + border-radius: 0px 4px 4px 4px; font-family: ${props => props.theme.fontFamily}; line-height: 1.125em; + padding: 16px; } .react-calendar, .react-calendar *, @@ -1002,7 +1009,7 @@ export const StyledDatePicker = styled(DatePicker)` outline: none; } .react-calendar buttom:enabled { - color: ${props => props.theme.primaryBtnActColor}; + color: ${props => props.theme.effectPanelTextMain}; } .react-calendar button:enabled:hover { cursor: pointer; @@ -1026,8 +1033,12 @@ export const StyledDatePicker = styled(DatePicker)` font-weight: bold; } .react-calendar__month-view__weekdays__weekday { + color: ${props => props.theme.effectPanelTextSecondary2}; padding: 0.5em; - font-size: ${props => props.theme.datePickerFontSize}; + font-size: ${props => props.theme.inputFontSize}; + abbr { + text-decoration: none; + } } .react-calendar__month-view__weekNumbers .react-calendar__tile { display: flex; @@ -1036,15 +1047,15 @@ export const StyledDatePicker = styled(DatePicker)` font-weight: bold; } .react-calendar__month-view__days__day--weekend { - color: ${props => props.theme.textColor}; + color: ${props => props.theme.effectPanelTextSecondary1}; } .react-calendar__month-view__days__day--neighboringMonth { - color: ${props => props.theme.textColor}; + color: ${props => props.theme.effectPanelTextSecondary3}; opacity: 0.4; } .react-calendar__navigation__label__labelText, .react-calendar__navigation__arrow { - color: ${props => props.theme.textColor}; + color: ${props => props.theme.effectPanelTextMain}; } .react-calendar__year-view .react-calendar__tile, .react-calendar__decade-view .react-calendar__tile, @@ -1052,22 +1063,23 @@ export const StyledDatePicker = styled(DatePicker)` padding: 2em 0.5em; } .react-calendar__tile { - color: ${props => props.theme.textColor}; + color: ${props => props.theme.effectPanelTextSecondary1}; max-width: 100%; padding: 6px 4px; background: none; text-align: center; - font-size: ${props => props.theme.datePickerFontSize}; + font-size: ${props => props.theme.inputFontSize}; + height: 30px; } .react-calendar__tile:enabled:hover, .react-calendar__tile:enabled:focus { - background-color: ${props => props.theme.datePickerSelectedDateBgColor}; + background-color: ${props => props.theme.primaryBtnBgd}; color: ${props => props.theme.primaryBtnActColor}; } .react-calendar__tile--now:enabled:hover, .react-calendar__tile--now:enabled:focus { - background: ${props => props.theme.datePickerSelectedDateBgColor}; - color: ${props => props.theme.primaryBtnActColor}; + background: ${props => props.theme.primaryBtnBgd}; + color: ${props => props.theme.effectPanelTextMain}; } .react-calendar__tile--hasActive { background: ${props => props.theme.primaryBtnActBgd}; @@ -1075,17 +1087,18 @@ export const StyledDatePicker = styled(DatePicker)` .react-calendar__tile--hasActive:enabled:hover, .react-calendar__tile--hasActive:enabled:focus { background: ${props => props.theme.primaryBtnActBgd}; - color: ${props => props.theme.primaryBtnActColor}; + color: ${props => props.theme.effectPanelTextMain}; } .react-calendar__tile--active { background: ${props => props.theme.primaryBtnActBgd}; - color: ${props => props.theme.primaryBtnActColor}; + color: ${props => props.theme.effectPanelTextMain}; + border-radius: 4px; } .react-calendar__tile--active:enabled:hover, .react-calendar__tile--active:enabled:focus { background: ${props => props.theme.primaryBtnActBgd}; } .calendar__navigation__label__labelText { - сolor: ${props => props.theme.textColor}; + сolor: ${props => props.theme.effectPanelTextMain}; } `; diff --git a/src/components/src/effects/compact-color-picker.tsx b/src/components/src/effects/compact-color-picker.tsx new file mode 100644 index 0000000000..7c3d614b2e --- /dev/null +++ b/src/components/src/effects/compact-color-picker.tsx @@ -0,0 +1,139 @@ +import React, {useCallback, useMemo} from 'react'; +import styled from 'styled-components'; + +import {rgbToHex} from '@kepler.gl/utils'; + +import {Portaled, SingleColorPalette} from '../../../components'; +import {Button} from '../common/styled-components'; + +export type SingleColorPickerProps = { + color: [number, number, number]; + onSetColor: (value: [number, number, number]) => void; + label: string; + Icon: React.ElementType; +}; + +export const StyledPanelDropdown = styled.div` + ${props => props.theme.panelDropdownScrollBar} + background-color: ${props => props.theme.panelBackground}; + box-shadow: ${props => props.theme.panelBoxShadow}; + border-radius: ${props => props.theme.panelBorderRadius}; + overflow-y: auto; + max-height: 500px; + position: relative; + z-index: 999; + width: 220px; +`; + +const StyledConfigSection = styled.div` + display: flex; + flex-direction: column; +`; + +const SectionTitle = styled.div` + font-size: ${props => props.theme.inputFontSize}; + color: ${props => props.theme.effectPanelTextSecondary2}; + margin-bottom: 8px; +`; + +const StyledDropdownButtonWrapper = styled.div` + align-self: flex-start; + .button { + color: ${props => props.theme.effectPanelTextSecondary2}; + display: flex; + gap: 5px; + border: none; + transition: background 0.2s; + background-color: ${props => props.theme.inputBgd}; + padding: 8px 5px 8px 10px; + &:active { + color: ${props => props.theme.effectPanelTextMain}; + background-color: ${props => props.theme.inputBgdHover}; + } + &:hover { + color: ${props => props.theme.effectPanelTextMain}; + background-color: ${props => props.theme.inputBgdHover}; + } + & > svg { + margin-right: 0; + } + } +`; + +const DEFAULT_OFFSET = { + top: 0, + left: 0 +}; + +const SingleColorPickerDropdown = ({ + isOpened, + onClose, + selectedColor, + onSelectColor, + offset = DEFAULT_OFFSET +}) => { + const onSelectColorCb = useCallback( + v => { + onSelectColor(v); + }, + [onSelectColor] + ); + return ( + + + + + + ); +}; + +const CompactColorPicker: React.FC = ({ + color, + onSetColor, + Icon, + label +}: SingleColorPickerProps) => { + const [isColorPickerOpened, setIsColorPickerOpened] = React.useState(false); + + const hexColor = useMemo(() => { + return rgbToHex(color); + }, [color]); + + const colorBlockStyle = useMemo( + () => ({ + width: 16, + height: 16, + backgroundColor: hexColor, + borderRadius: 2 + }), + [hexColor] + ); + + const toggleDropdown = useCallback(() => { + setIsColorPickerOpened(!isColorPickerOpened); + }, [isColorPickerOpened, setIsColorPickerOpened]); + + const closeDropdown = useCallback(() => { + setIsColorPickerOpened(false); + }, [setIsColorPickerOpened]); + + return ( + + {label} + + + + + + ); +}; + +export default CompactColorPicker; diff --git a/src/components/src/effects/effect-configurator.tsx b/src/components/src/effects/effect-configurator.tsx index c7da242a92..3781f69517 100644 --- a/src/components/src/effects/effect-configurator.tsx +++ b/src/components/src/effects/effect-configurator.tsx @@ -6,10 +6,10 @@ import {LIGHT_AND_SHADOW_EFFECT} from '@kepler.gl/constants'; import {isNumber} from '@kepler.gl/utils'; import {Effect, EffectUpdateProps} from '@kepler.gl/types'; -import {PanelLabel} from '../common/styled-components'; import RangeSliderFactory from '../common/range-slider'; -import ColorSelectorFactory from '../side-panel/layer-panel/color-selector'; +import {ArrowDownSmall} from '../common/icons'; import EffectTimeConfiguratorFactory from './effect-time-configurator'; +import CompactColorPicker from './compact-color-picker'; export type EffectConfiguratorProps = { effect: Effect; @@ -24,7 +24,7 @@ const StyledEffectConfigurator = styled.div.attrs({ className: 'effect-panel__config' })` position: relative; - margin-top: ${props => props.theme.effectConfiguratorMargin}; + margin: ${props => props.theme.effectConfiguratorMargin}; padding: ${props => props.theme.effectConfiguratorPadding}; `; @@ -33,6 +33,7 @@ export const PanelLabelWrapper = styled.div.attrs({ })` display: flex; align-items: self-start; + margin-bottom: 11px; .side-panel-panel__label { margin-top: 2px; @@ -47,6 +48,94 @@ export const StyledColorSelectorWrapper = styled.div` margin-top: 2px; `; +const StyledVerticalSeparator = styled.div` + height: 1px; + background-color: ${props => props.theme.inputBgd}; + margin-top: 20px; + margin-bottom: 20px; + margin-left: -20px; +`; + +type StyledWrapperProps = { + marginBottom?: number; +}; +const StyledWrapper = styled.div` + display: flex; + justify-content: space-between; + align-items: center; + margin-bottom: ${props => props.marginBottom ?? 9}px; +`; + +const StyledConfigSection = styled.div` + display: flex; + flex-direction: column; +`; + +const SectionTitle = styled.div` + font-size: ${props => props.theme.inputFontSize}; + color: ${props => props.theme.effectPanelTextSecondary1}; + margin-bottom: 5px; + text-transform: capitalize; +`; + +const SectionSubTitle = styled.div` + font-size: ${props => props.theme.inputFontSize}; + color: ${props => props.theme.effectPanelTextSecondary2}; + margin-bottom: 8px; + margin-left: 6px; +`; + +const StyleSliderWrapper = styled.div` + align-self: flex-start; + width: 199px; + height: 32px; + display: flex; + align-items: center; + .kg-range-slider__input { + height: 32px; + text-align: center; + padding: 3px 6px; + } + .kg-slider { + padding-left: 6px; + } + .kg-range-slider { + padding: 0px !important; + } +`; + +const RegularOuterWrapper = styled.div.attrs({ + className: 'effect-configurator__pp-section' +})` + margin-bottom: 8px; +`; + +const RegularSectionTitleWrapper = styled.div.attrs({ + className: 'effect-configurator__pp-section-title' +})` + font-size: ${props => props.theme.inputFontSize}; + color: ${props => props.theme.effectPanelTextSecondary1}; + text-transform: capitalize; + margin-bottom: -3px; +`; + +const RegularSliderWrapper = styled.div.attrs({ + className: 'effect-configurator__pp-section-control' +})` + height: 32px; + .kg-range-slider__input { + height: 32px; + text-align: center; + padding: 3px 6px; + } + .kg-slider { + padding-left: 6px; + } + .kg-range-slider { + padding: 0px !important; + } +`; + const COMMON_SLIDER_PROPS = { showInput: true, isRanged: false, @@ -54,15 +143,10 @@ const COMMON_SLIDER_PROPS = { label: 'value' }; -EffectConfiguratorFactory.deps = [ - RangeSliderFactory, - ColorSelectorFactory, - EffectTimeConfiguratorFactory -]; +EffectConfiguratorFactory.deps = [RangeSliderFactory, EffectTimeConfiguratorFactory]; export default function EffectConfiguratorFactory( RangeSlider: ReturnType, - ColorSelector: ReturnType, EffectTimeConfigurator: ReturnType ): React.FC { const EffectConfigurator = ({ @@ -79,7 +163,7 @@ export default function EffectConfiguratorFactory( value1: parameters[propName], range: [0, 1], value0: 0, - onChange: (value: number[], event?: Event) => { + onChange: (value: number[], event?: Event | null) => { updateEffectConfig(event, effect.id, {parameters: {[propName]: value[1]}}); } }; @@ -119,7 +203,7 @@ export default function EffectConfiguratorFactory( return ( - {'Date & Time'} + {'Date & Time'} - - {'Shadow intensity'} - - - - {'Shadow color'} - - - - - - {'Ambient light intensity'} - - - - {'Ambient light color'} - - - - - - {'Sun light intensity'} - - - - {'Sun light color'} - - - - + + + + + {'Shadow'} + + + + + Intensity + + + + + + + + {'Ambient light'} + + + + + Intensity + + + + + + + + {'Sun light'} + + + + + Intensity + + + + + ); }, [effect, effect.parameters, updateEffectConfig]); const renderPostProcessingEffectConfigurator = useCallback(() => { const uniforms = effect.deckEffect?.module.uniforms || {}; - const propNames = useMemo(() => Object.keys(uniforms), [uniforms]); + const parameterDescriptions = effect.getParameterDescriptions(); - const slidersForProps = useMemo(() => { - return propNames.map(propName => { - const uniform = uniforms[propName]; + const controls = useMemo(() => { + return parameterDescriptions.map(desc => { + const paramName = desc.name; + + const uniform = uniforms[desc.name]; if ((!uniform && uniform !== 0) || uniform.private) { return null; } - const sliders: { - value0: number; - value1: number; - range: [number, number]; - onChange: (value: number[], e?: Event) => void; - }[] = []; - const prevValue = effect.parameters[propName]; + const prevValue = effect.parameters[paramName]; - // the uniform is [0, 1] array - if (uniform.length === 2) { - sliders.push({ - value1: prevValue[0] || 0, - range: [0, 1], - value0: 0, - onChange: (newValue, event) => { - updateEffectConfig(event, effect.id, { - parameters: {[propName]: [newValue[1], prevValue[1]]} - }); - } - }); + const label = desc.label === false ? false : desc.label || desc.name; - sliders.push({ - value1: prevValue[1] || 0, + // the uniform is [number, number] array + if (uniform.length === 2) { + return { + label, + value1: prevValue[desc.index || 0] || 0, range: [0, 1], value0: 0, - onChange: (newValue, event) => { + onChange: (newValue: number[], event) => { updateEffectConfig(event, effect.id, { - parameters: {[propName]: [prevValue[0], newValue[1]]} + parameters: { + [paramName]: + desc.index === 0 ? [newValue[1], prevValue[1]] : [prevValue[0], newValue[1]] + } }); } - }); + }; } // the uniform is a plain number without any description else if (isNumber(uniform)) { - sliders.push({ - value1: prevValue || 0, - range: [0, 500], - value0: 0, - onChange: (newValue, event) => { - updateEffectConfig(event, effect.id, {parameters: {[propName]: newValue[1]}}); + return { + label, + value1: prevValue ?? 0, + range: [desc.min ?? 0, desc.max ?? 500], + value0: desc.min ?? 0, + onChange: (newValue: number[], event) => { + updateEffectConfig(event, effect.id, {parameters: {[paramName]: newValue[1]}}); } - }); + }; } // the uniform description is {value: 0, min: 0, max: 1, ...} else if (isNumber(uniform.value)) { - sliders.push({ + return { + label, value1: prevValue || 0, - range: [uniform.min ?? uniform.softMin ?? 0, uniform.max ?? uniform.softMax ?? 1], - value0: uniform.min ?? uniform.softMin ?? 0, - onChange: (newValue, event) => { - updateEffectConfig(event, effect.id, {parameters: {[propName]: newValue[1]}}); + range: [ + desc.min ?? uniform.min ?? uniform.softMin ?? 0, + desc.max ?? uniform.max ?? uniform.softMax ?? 1 + ], + value0: desc.min ?? uniform.min ?? uniform.softMin ?? 0, + onChange: (newValue: number[], event) => { + updateEffectConfig(event, effect.id, {parameters: {[paramName]: newValue[1]}}); } - }); - } else { - return null; + }; } - return sliders; + // ignore everything else for now + return null; }); - }, [propNames, effect, effect.parameters, updateEffectConfig]); + }, [parameterDescriptions, effect, effect.parameters, updateEffectConfig]); return ( - {propNames.map((propName, uniformIndex) => { - const slidersForProp = slidersForProps[uniformIndex]; - if (!slidersForProp) { + {parameterDescriptions.map((desc, parameterIndex) => { + const control = controls[parameterIndex]; + if (!control) { return null; } return ( -
- - {propName} - - {slidersForProp.map((sliderProp, sliderIndex) => { - return ; - })} -
+ + {control.label ? ( + {control.label} + ) : null} + + + + ); })}
diff --git a/src/components/src/effects/effect-manager.tsx b/src/components/src/effects/effect-manager.tsx index 683991d30b..b6eecaec7a 100644 --- a/src/components/src/effects/effect-manager.tsx +++ b/src/components/src/effects/effect-manager.tsx @@ -1,4 +1,4 @@ -import React, {useMemo} from 'react'; +import React, {useMemo, useCallback} from 'react'; import styled from 'styled-components'; import {injectIntl, IntlShape} from 'react-intl'; @@ -76,11 +76,18 @@ function EffectManagerFactory( return effect.type === LIGHT_AND_SHADOW_EFFECT.type; }); - return hasShadow - ? EFFECT_DESCRIPTIONS.filter(desc => desc.type !== LIGHT_AND_SHADOW_EFFECT.type) - : EFFECT_DESCRIPTIONS; + return EFFECT_DESCRIPTIONS.map(desc => { + return { + ...desc, + disabled: Boolean(hasShadow && desc.type === LIGHT_AND_SHADOW_EFFECT.type) + }; + }); }, [effects]); + const onAddEffect = useCallback(type => { + visStateActions.addEffect({type}); + }, []); + return ( @@ -89,7 +96,7 @@ function EffectManagerFactory( className="effect-manager-title" title={intl.formatMessage({id: 'effectManager.effects'})} > - + diff --git a/src/components/src/effects/effect-panel-header.tsx b/src/components/src/effects/effect-panel-header.tsx index d3d1593907..1035f45d62 100644 --- a/src/components/src/effects/effect-panel-header.tsx +++ b/src/components/src/effects/effect-panel-header.tsx @@ -2,10 +2,37 @@ import React, {useMemo} from 'react'; import classnames from 'classnames'; import styled from 'styled-components'; -import {EFFECT_DESCRIPTIONS} from '@kepler.gl/constants'; +import { + EFFECT_DESCRIPTIONS, + LIGHT_AND_SHADOW_EFFECT, + POSTPROCESSING_EFFECTS +} from '@kepler.gl/constants'; import PanelHeaderActionFactory from '../side-panel/panel-header-action'; -import {ArrowDown, EyeSeen, EyeUnseen, Trash, VertDots, BaseProps} from '../common/icons'; +import { + ArrowDown, + EyeSeen, + EyeUnseen, + Trash, + VertDots, + LightAndShadowEffectIcon, + InkEffectIcon, + BrightnessContrastEffectIcon, + HueSaturationEffectIcon, + VibranceEffectIcon, + SepiaEffectIcon, + DotScreenEffectIcon, + ColorHalftoneEffectIcon, + NoiseEffectIcon, + TriangleBlurEffectIcon, + ZoomBlurEffectIcon, + TiltShiftEffectIcon, + EdgeWorkEffectIcon, + VignetteEffectIcon, + MagnifyEffectIcon, + HexagonalPixelateEffectIcon, + BaseProps +} from '../common/icons'; import {StyledPanelHeader} from '../common/styled-components'; export type EffectPanelHeaderProps = { @@ -38,15 +65,39 @@ const defaultActionIcons = { enableConfig: ArrowDown }; +const defaultEffectIcons = { + [LIGHT_AND_SHADOW_EFFECT.type]: LightAndShadowEffectIcon, + [POSTPROCESSING_EFFECTS.ink.type]: InkEffectIcon, + [POSTPROCESSING_EFFECTS.brightnessContrast.type]: BrightnessContrastEffectIcon, + [POSTPROCESSING_EFFECTS.hueSaturation.type]: HueSaturationEffectIcon, + [POSTPROCESSING_EFFECTS.vibrance.type]: VibranceEffectIcon, + [POSTPROCESSING_EFFECTS.sepia.type]: SepiaEffectIcon, + [POSTPROCESSING_EFFECTS.dotScreen.type]: DotScreenEffectIcon, + [POSTPROCESSING_EFFECTS.colorHalftone.type]: ColorHalftoneEffectIcon, + [POSTPROCESSING_EFFECTS.noise.type]: NoiseEffectIcon, + [POSTPROCESSING_EFFECTS.triangleBlur.type]: TriangleBlurEffectIcon, + [POSTPROCESSING_EFFECTS.zoomBlur.type]: ZoomBlurEffectIcon, + [POSTPROCESSING_EFFECTS.tiltShift.type]: TiltShiftEffectIcon, + [POSTPROCESSING_EFFECTS.edgeWork.type]: EdgeWorkEffectIcon, + [POSTPROCESSING_EFFECTS.vignette.type]: VignetteEffectIcon, + [POSTPROCESSING_EFFECTS.magnify.type]: MagnifyEffectIcon, + [POSTPROCESSING_EFFECTS.hexagonalPixelate.type]: HexagonalPixelateEffectIcon +}; + const StyledEffectPanelHeader = styled(StyledPanelHeader)` height: ${props => props.theme.effectPanelHeaderHeight}px; position: relative; align-items: stretch; + .effect__drag-handle { + margin-left: -5px; + color: ${props => props.theme.textColor}; + } + .effect__drag-handle__placeholder { height: 20px; padding: 0px; - margin: 10px; + margin: 10px 10px 10px 5px; } :hover { @@ -162,7 +213,7 @@ export function EffectPanelHeaderActionSectionFactory( } const StyledEffectTitleSection = styled.div` - margin-left: 4px; + margin-left: 8px; flex-grow: 1; align-items: center; display: flex; @@ -174,6 +225,12 @@ const IconPlaceholder = styled.div` height: 20px; `; +const EffectIconWrapper = styled.div` + height: 18px; + margin: auto; + color: ${props => props.theme.textColor}; +`; + EffectPanelHeaderFactory.deps = [EffectPanelHeaderActionSectionFactory]; function EffectPanelHeaderFactory( @@ -194,6 +251,8 @@ function EffectPanelHeaderFactory( return description?.name || 'Effect'; }, [type]); + const EffectIcon = defaultEffectIcons[type]; + return ( )} + {EffectIcon ? : null} + {label} diff --git a/src/components/src/effects/effect-time-configurator.tsx b/src/components/src/effects/effect-time-configurator.tsx index 8c682cc10d..7bfb3ffbac 100644 --- a/src/components/src/effects/effect-time-configurator.tsx +++ b/src/components/src/effects/effect-time-configurator.tsx @@ -13,7 +13,7 @@ import {StyledTimePicker, StyledDatePicker, Tooltip} from '../common/styled-comp import RangeSliderFactory from '../common/range-slider'; import Checkbox from '../common/checkbox'; import Button from '../common/data-table/button'; -import {Pin} from '../common/icons'; +import {LocationMarker, Calendar, Clock} from '../common/icons'; const DAY_SLIDER_RANGE = 1000 * 60 * 60 * 24; @@ -24,35 +24,44 @@ export type EffectTimeConfiguratorProps = { onTimeModeChange: (newMode: LightAndShadowEffectTimeMode) => void; }; -type StyledWrapperProps = {disabled?: boolean}; +type StyledWrapperProps = {disabled?: boolean; marginBottom?: number}; const StyledWrapper = styled.div` display: flex; justify-content: space-between; align-items: center; - padding-left: 5px; - padding-right: 5px; - margin-bottom: 9px; - opacity: ${props => (props.disabled ? 0.3 : 1)}; - ${props => (props.disabled ? 'pointer-events: none;' : '')} + margin-bottom: ${props => props.marginBottom ?? 9}px; + ${props => (props.hidden ? 'display: none;' : '')} `; type SliderWrapperProps = {disabled?: boolean}; const SliderWrapper = styled.div` margin-top: 13px; - margin-right: 8px; margin-bottom: 17px; + ${props => (props.hidden ? 'display: none;' : '')} - opacity: ${props => (props.disabled ? 0.3 : 1)}; - ${props => (props.disabled ? 'pointer-events: none;' : '')} + .kg-range-slider__input { + height: 32px; + text-align: center; + padding: 3px 6px; + } + .kg-slider { + padding-left: 6px; + } + .kg-range-slider { + padding: 0px !important; + } `; const StyledButton = styled(Button)` - color: ${props => props.theme.textColor}; + color: ${props => props.theme.effectPanelTextSecondary2}; background-color: ${props => props.theme.inputBgd}; - height: 31px; + height: 32px; + width: 32px; padding: 5px; - border-radius: 2px; + border-radius: 4px; + justify-content: center; :hover { + color: ${props => props.theme.effectPanelTextMain}; background-color: ${props => props.theme.inputBgdHover}; } `; @@ -63,20 +72,43 @@ const StyledRadio = styled(Checkbox)` font-size: ${props => props.theme.inputFontSize}; } .kg-checkbox__label:before { - background-color: ${props => props.theme.inputBgdHover}; + background: transparent; + border-color: ${props => props.theme.effectPanelTextSecondary2}; + } + input:checked + .kg-checkbox__label:before { + border-color: ${props => props.theme.activeColor}; + } + .kg-checkbox__label:after { + background-color: ${props => props.theme.activeColor}; } -`; - -const StyledLabelWrapper = styled.div` - color: ${props => props.theme.textColor}; - margin-right: 10px; `; const StyledEffectTimeConfigurator = styled.div` - border-left: 3px solid ${props => props.theme.panelBorderColor}; margin-bottom: 8px; margin-top: 3px; - margin-left: 3px; +`; + +const WithIconWrapper = styled.div` + position: relative; +`; + +const StyledExtraIcon = styled.div` + position: absolute; + top: 0px; + left: 8px; + width: 0px; + height: 32px; + color: ${props => props.theme.effectPanelTextSecondary2}; + pointer-events: none; +`; + +type TextBlockProps = { + width: string; +}; +const TextBlock = styled.div` + color: ${props => props.theme.effectPanelTextSecondary2}; + width: ${props => props.width}; + font-size: ${props => props.theme.inputFontSize}; `; EffectTimeConfiguratorFactory.deps = [RangeSliderFactory]; @@ -145,11 +177,15 @@ export default function EffectTimeConfiguratorFactory( onDateTimeChange(new Date().valueOf()); }, [onDateTimeChange]); + const formatShortWeekday = useCallback((locale, date) => { + return ['S', 'M', 'T', 'W', 'T', 'F', 'S'][date.getDay()]; + }, []); + const disableDateTimePick = timeMode !== LIGHT_AND_SHADOW_EFFECT_TIME_MODES.pick; return ( - + - + + + + + + - - - - - + - + void; + options: EffectTypeDropdownListItem[]; + selectedItems: EffectTypeDropdownListItem[]; + selectionIndex: number; + customListItemComponent: React.FC<{value: EffectTypeDropdownListItem; isTile?: boolean}>; +}; + +const DropdownListWrapper = styled.div` + ${props => props.theme.dropdownList}; + background-color: ${props => props.theme.dropdownListBgd}; + display: flex; + flex-wrap: wrap; + align-items: stretch; + padding: 17px 0 12px 0; + max-height: 430px; + justify-content: center; +`; + +const StyledDropdownListItem = styled.div` + margin: 0px 4px 8px 4px; + + &.disabled { + pointer-events: none; + opacity: 0.3; + } + + :hover { + cursor: pointer; + .effect-type-selector__item__label { + color: ${props => props.theme.effectPanelTextMain}; + } + } +`; + +export function EffectTypeDropdownListFactory() { + const EffectTypeDropdownList: React.FC = ({ + onOptionSelected, + options, + selectedItems, + selectionIndex, + customListItemComponent + }: EffectTypeDropdownListProps) => { + const onSelectOption = useCallback( + (e, value) => { + e.preventDefault(); + onOptionSelected(value); + }, + [onOptionSelected] + ); + + const ListItemComponent = customListItemComponent; + + return ( + + {options.map((value, i) => ( + it.type === value.type), + hover: selectionIndex === i, + disabled: value.disabled + })} + key={`${value.type}_${i}`} + onMouseDown={e => onSelectOption(e, value)} + onClick={e => onSelectOption(e, value)} + > + + + ))} + + ); + }; + + return EffectTypeDropdownList; +} + +export default EffectTypeDropdownListFactory; diff --git a/src/components/src/effects/effect-type-list-item.tsx b/src/components/src/effects/effect-type-list-item.tsx new file mode 100644 index 0000000000..7a71e85d52 --- /dev/null +++ b/src/components/src/effects/effect-type-list-item.tsx @@ -0,0 +1,113 @@ +import React from 'react'; +import styled, {withTheme} from 'styled-components'; +import classNames from 'classnames'; + +import {KEPLER_UNFOLDED_BUCKET} from '@kepler.gl/constants'; +import {FormattedMessage} from '@kepler.gl/localization'; + +import {Add} from '../common/icons'; + +export const DUMMY_ITEM_ID: string = 'dummy'; + +export type EffectTypeListItemProps = { + value: {type: string; name: string}; + className?: string; + isTile?: boolean; + theme: any; +}; + +const StyledListItem = styled.div` + border-radius: 2px; + height: 89px; + transition: background-color 0.4s; + + :hover { + background-color: ${props => props.theme.effectTypeIconBgHoverColor}; + } + + .effect-type-selector__item__icon { + display: flex; + background-image: url(${`${KEPLER_UNFOLDED_BUCKET}/images/kepler.gl-layer-icon-bg.png`}); + background-size: ${props => props.theme.effectTypeIconSizeL}px + ${props => props.theme.effectTypeIconSizeL}px; + height: ${props => props.theme.effectTypeIconSizeL}px; + width: ${props => props.theme.effectTypeIconSizeL}px; + border-radius: 2px; + + .effect-preview { + height: ${props => props.theme.effectTypeIconSizeL}px; + width: ${props => props.theme.effectTypeIconSizeL}px; + } + } + + .effect-type-selector__item__label { + text-transform: capitalize; + font-size: 10px; + text-align: center; + color: ${props => props.theme.effectPanelTextMain}; + max-width: ${props => props.theme.effectTypeIconSizeL}px; + line-height: 14px; + padding-top: 2px; + } +`; + +const StyledAddButton = styled(Add)` + margin-right: 8px; + height: 16px; +`; + +const StyledPlaceholderButton = styled.div` + align-items: center; + display: flex; + justify-content: space-between; + margin-left: 3px; + margin-right: 3px; + letter-spacing: 0.3px; + font-family: ${props => props.theme.effectPanelAddEffectFontFamily}; + font-weight: 500; +`; + +/** + * Transforms an effect type from camel case into a name of the image in kebab case. + * @param {string} type + * @returns {string} + */ +const getImageUrl = type => { + const kebab = type.replace( + /[A-Z]+(?![a-z])|[A-Z]/g, + ($, ofs) => (ofs ? '-' : '') + $.toLowerCase() + ); + return `${KEPLER_UNFOLDED_BUCKET}/images/effects/${kebab}.png`; +}; + +export function EffectTypeListItemFactory() { + const EffectTypeListItem: React.FC = ({value, isTile, className}) => { + if (value?.type === DUMMY_ITEM_ID) { + return ( + + + + + ); + } + + return ( + +
+ +
+
+ +
+
+ ); + }; + + return withTheme(EffectTypeListItem); +} + +export default EffectTypeListItemFactory; diff --git a/src/components/src/effects/effect-type-selector.tsx b/src/components/src/effects/effect-type-selector.tsx index dc6a40a3af..98f5c6eb16 100644 --- a/src/components/src/effects/effect-type-selector.tsx +++ b/src/components/src/effects/effect-type-selector.tsx @@ -1,91 +1,86 @@ -import React, {useRef, useCallback} from 'react'; -import styled from 'styled-components'; +import React, {useMemo} from 'react'; +import styled, {withTheme} from 'styled-components'; -import {FormattedMessage} from '@kepler.gl/localization'; -import {addEffect} from '@kepler.gl/actions'; - -import {Button} from '../common/styled-components'; -import {Add} from '../common/icons'; -import TippyTooltip from '../common/tippy-tooltip'; +import ItemSelector from '../common/item-selector/item-selector'; +import EffectTypeDropdownListFactory from './effect-type-dropdown-list'; +import EffectTypeListItemFactory, {DUMMY_ITEM_ID} from './effect-type-list-item'; export type EffectTypeSelectorProps = { - options: {type: string; name: string}[]; - onSelect: typeof addEffect; + options: {type: string; name: string; disabled: boolean}[]; + onSelect: (type: any) => void; + theme: any; }; -const StyledTippyTooltipContentContainer = styled.div` - background-color: ${props => props.theme.panelBackgroundHover}; - display: flex; - flex-direction: column; - align-items: start; - margin: -7px -18px; /* compensate for kepler tippy parent padding */ - border-radius: 2px; - .button { - width: 100%; - justify-content: start; - color: ${props => props.theme.WHITE}; +const DropdownWrapper = styled.div` + border: 0; + width: 100%; + left: 0; + z-index: ${props => props.theme.dropdownWrapperZ}; + width: 297px; + margin-left: -194px; + margin-top: 26px; + .typeahead__input_icon { + top: 41px; + } +`; + +const StyledLayerTypeSelector = styled.div` + .item-selector .item-selector__dropdown { + padding: 4px 10px 4px 10px; + background-color: ${props => props.theme.secondaryBtnBgd}; + border-radius: ${props => props.theme.primaryBtnRadius}; + font-size: ${props => props.theme.primaryBtnFontSizeDefault}; + border: none; + :hover { + background-color: ${props => props.theme.secondaryBtnBgdHover}; + } + .item-selector__dropdown__value { + color: ${props => props.theme.secondaryBtnActColor}; + } } `; -/** @type [number, number] */ -const TIPPY_DURATION: [number, number] = [300, 0]; -/** @type [number, number] */ -const TIPPY_OFFSET: [number, number] = [0, 4]; +const getDisplayOption = op => op.name; +const getOptionValue = op => op.type; -function EffectTypeSelectorFactory(): React.FC { - const EffectTypeSelector = ({onSelect, options}: EffectTypeSelectorProps) => { - /** @type any */ - const tippyInstance = useRef(null); +EffectTypeSelectorFactory.deps = [EffectTypeListItemFactory, EffectTypeDropdownListFactory]; - const render = useCallback( - () => ( - - {options.map((effectDesc, idx) => { - return ( - - ); - })} - - ), - [options, tippyInstance, tippyInstance.current, onSelect] - ); +function EffectTypeSelectorFactory(EffectTypeListItem, EffectTypeDropdownList) { + const LayerTypeSelector: React.FC = ({ + options, + onSelect + }: EffectTypeSelectorProps) => { + // Make sure effect type selector has dummy as selection + const selectedItems = useMemo(() => { + return [ + { + type: DUMMY_ITEM_ID, + name: DUMMY_ITEM_ID + } + ]; + }, []); return ( - { - // @ts-expect-error type current - tippyInstance.current = instance; - }} - zIndex={999} // defaults to 9999; keep it below modals (1000) - render={render} - > -
- -
-
+ + + ); }; - return EffectTypeSelector; + + return withTheme(LayerTypeSelector); } export default EffectTypeSelectorFactory; diff --git a/src/components/src/map/effects/effect-control.tsx b/src/components/src/map/effects/effect-control.tsx index 460dcd2046..0eaa20307f 100644 --- a/src/components/src/map/effects/effect-control.tsx +++ b/src/components/src/map/effects/effect-control.tsx @@ -2,12 +2,12 @@ import React, {useCallback, ComponentType} from 'react'; import {MapControls} from '@kepler.gl/types'; -import {EyeSeen} from '../../common/icons'; +import {MagicWand} from '../../common/icons'; import {MapControlButton} from '../../common/styled-components'; import MapControlTooltipFactory from '../map-control-tooltip'; interface EffectControlIcons { - eyeSeen: ComponentType; + effectsIcon: ComponentType; } export type EffectControlProps = { @@ -22,7 +22,7 @@ export default function EffectControlFactory( MapControlTooltip: ReturnType ): React.FC { const defaultActionIcons = { - eyeSeen: EyeSeen + effectsIcon: MagicWand }; const EffectControl = ({ @@ -55,7 +55,7 @@ export default function EffectControlFactory( onClick={onClick} active={active} > - + ); diff --git a/src/constants/src/default-settings.ts b/src/constants/src/default-settings.ts index a18e155276..a382ed8f4c 100644 --- a/src/constants/src/default-settings.ts +++ b/src/constants/src/default-settings.ts @@ -30,7 +30,7 @@ import { scalePoint } from 'd3-scale'; import {TOOLTIP_FORMAT_TYPES} from './tooltip'; -import {RGBAColor} from '@kepler.gl/types'; +import {RGBAColor, EffectDescription} from '@kepler.gl/types'; export const ACTION_PREFIX = '@@kepler.gl/'; export const KEPLER_UNFOLDED_BUCKET = 'https://studio-public-data.foursquare.com/statics/keplergl'; @@ -1170,7 +1170,11 @@ export const DEFAULT_LIGHT_COLOR: [number, number, number] = [255, 255, 255]; export const DEFAULT_LIGHT_INTENSITY = 1; export const DEFAULT_SHADOW_INTENSITY = 0.5; export const DEFAULT_SHADOW_COLOR: [number, number, number] = [0, 0, 0]; -export const LIGHT_AND_SHADOW_EFFECT = {type: 'lightAndShadow', name: 'Light & Shadow'}; +export const LIGHT_AND_SHADOW_EFFECT: EffectDescription = { + type: 'lightAndShadow', + name: 'Light & Shadow', + parameters: [] +}; export const LIGHT_AND_SHADOW_EFFECT_TIME_MODES = { pick: 'pick' as 'pick', current: 'current' as 'current', @@ -1197,70 +1201,189 @@ export const DEFAULT_LIGHT_AND_SHADOW_PROPS: { ambientLightIntensity: DEFAULT_LIGHT_INTENSITY }; -export const POSTPROCESSING_EFFECTS: {[key: string]: {type: string; name: string}} = { +export const POSTPROCESSING_EFFECTS: {[key: string]: EffectDescription} = { ink: { type: 'ink', - name: 'Ink' + name: 'Ink', + parameters: [{name: 'strength'}] }, brightnessContrast: { type: 'brightnessContrast', - name: 'Brightness & Contrast' + name: 'Brightness & Contrast', + parameters: [{name: 'brightness'}, {name: 'contrast'}] }, hueSaturation: { type: 'hueSaturation', - name: 'Hue & Saturation' + name: 'Hue & Saturation', + parameters: [{name: 'hue'}, {name: 'saturation'}] }, vibrance: { type: 'vibrance', - name: 'Vibrance' + name: 'Vibrance', + parameters: [{name: 'amount'}] }, sepia: { type: 'sepia', - name: 'Sepia' + name: 'Sepia', + parameters: [{name: 'amount'}] }, dotScreen: { type: 'dotScreen', - name: 'Dot Screen' + name: 'Dot Screen', + parameters: [ + { + name: 'angle' + }, + { + name: 'size' + }, + { + name: 'center', + label: 'Center X', + index: 0 + }, + { + name: 'center', + label: 'Center Y', + index: 1 + } + ] }, colorHalftone: { type: 'colorHalftone', - name: 'Color Halftone' + name: 'Color Halftone', + parameters: [ + { + name: 'angle' + }, + { + name: 'size' + }, + { + name: 'center', + label: 'Center X', + index: 0 + }, + { + name: 'center', + label: 'Center Y', + index: 1 + } + ] }, noise: { type: 'noise', - name: 'Noise' + name: 'Noise', + parameters: [{name: 'amount'}] }, triangleBlur: { type: 'triangleBlur', - name: 'Blur (Triangle)' + name: 'Blur (Triangle)', + parameters: [{name: 'radius'}, {name: 'delta'}] }, zoomBlur: { type: 'zoomBlur', - name: 'Blur (Zoom)' + name: 'Blur (Zoom)', + parameters: [ + { + name: 'strength' + }, + { + name: 'center', + label: 'Center X', + index: 0 + }, + { + name: 'center', + label: 'Center Y', + index: 1 + } + ] }, tiltShift: { type: 'tiltShift', - name: 'Blur (Tilt Shift)' + name: 'Blur (Tilt Shift)', + parameters: [ + { + name: 'blurRadius', + label: 'Blur' + }, + { + name: 'gradientRadius', + label: 'Gradient' + }, + { + name: 'start', + index: 0 + }, + { + name: 'start', + label: false, + index: 1 + }, + { + name: 'end', + index: 0 + }, + { + name: 'end', + label: false, + index: 1 + } + ] }, edgeWork: { type: 'edgeWork', - name: 'Edge work' + name: 'Edge work', + parameters: [{name: 'radius'}, {name: 'delta'}] }, vignette: { type: 'vignette', - name: 'Vignette' + name: 'Vignette', + parameters: [{name: 'amount'}, {name: 'radius'}] }, magnify: { type: 'magnify', - name: 'Magnify' + name: 'Magnify', + parameters: [ + { + name: 'screenXY', + label: 'Position X', + index: 0, + default: 0.5 + }, + { + name: 'screenXY', + label: 'Position Y', + index: 1, + default: 0.5 + }, + { + name: 'radiusPixels', + label: 'Size', + min: 10 + }, + { + name: 'zoom', + min: 0.5, + max: 50 + }, + { + name: 'borderWidthPixels', + label: 'Border Width', + default: 3, + max: 100 + } + ] }, hexagonalPixelate: { type: 'hexagonalPixelate', - name: 'Hexagonal Pixelate' + name: 'Hexagonal Pixelate', + parameters: [{name: 'scale'}] } }; -export const EFFECT_DESCRIPTIONS: {type: string; name: string}[] = [ +export const EFFECT_DESCRIPTIONS: EffectDescription[] = [ LIGHT_AND_SHADOW_EFFECT, ...Object.keys(POSTPROCESSING_EFFECTS).map(keyName => POSTPROCESSING_EFFECTS[keyName]) ]; diff --git a/src/effects/src/effect.ts b/src/effects/src/effect.ts index 16e55b18cc..46f594392f 100644 --- a/src/effects/src/effect.ts +++ b/src/effects/src/effect.ts @@ -1,6 +1,11 @@ import {generateHashId} from '@kepler.gl/utils'; -import {Effect as EffectInterface, EffectProps, EffectPropsPartial} from '@kepler.gl/types'; -import {DEFAULT_POST_PROCESSING_EFFECT_TYPE} from '@kepler.gl/constants'; +import { + Effect as EffectInterface, + EffectProps, + EffectPropsPartial, + EffectParameterDescription +} from '@kepler.gl/types'; +import {DEFAULT_POST_PROCESSING_EFFECT_TYPE, POSTPROCESSING_EFFECTS} from '@kepler.gl/constants'; export class Effect implements EffectInterface { id: string; @@ -10,6 +15,7 @@ export class Effect implements EffectInterface { // effect specific parameters for a deck.gl effect (uniforms) parameters: {[key: string]: any}; deckEffect: any; + _uiConfig: EffectParameterDescription[]; constructor(props: EffectPropsPartial = {}) { this.id = props.id || `e_${generateHashId(6)}`; @@ -20,6 +26,8 @@ export class Effect implements EffectInterface { this.isConfigActive = _props.isConfigActive; this.parameters = _props.parameters; + this._uiConfig = POSTPROCESSING_EFFECTS[this.type]?.parameters || []; + this.deckEffect = null; this._initializeEffect(); } @@ -49,6 +57,14 @@ export class Effect implements EffectInterface { isValidToSave() { return Boolean(this.type && this.id && this.deckEffect); } + + /** + * Effect specific list of configurable parameters. + * @returns All parameters are in preffered order. + */ + getParameterDescriptions() { + return this._uiConfig || []; + } } export default Effect; diff --git a/src/effects/src/post-processing-effect.ts b/src/effects/src/post-processing-effect.ts index 22eb3900bd..11a9f3fcf9 100644 --- a/src/effects/src/post-processing-effect.ts +++ b/src/effects/src/post-processing-effect.ts @@ -85,6 +85,17 @@ const POSTPROCESSING_EFFECTS_DESCS = [ } ]; +/** + * Temp. Get custom default values from effect description + */ +const getDefaultValueForParameter = (name, effectDescription) => { + const rec = effectDescription.filter(param => param.name === name); + if (rec.length === 1) return rec[0].default; + else if (rec.length === 2 && rec[0].default !== undefined && rec[1].default !== undefined) { + return [rec[0].default, rec[1].default]; + } +}; + class PostProcessingEffect extends Effect { // deckEffect: PostProcessEffect | LightingEffect | null; @@ -103,9 +114,13 @@ class PostProcessingEffect extends Effect { const keys = Object.keys(uniforms); const defaultParameters = {}; keys.forEach(key => { - defaultParameters[key] = uniforms[key].value ?? uniforms[key]; + defaultParameters[key] = + getDefaultValueForParameter(key, this._uiConfig) ?? + uniforms[key].value ?? + uniforms[key]; }); this.parameters = {...defaultParameters, ...this.parameters}; + this.deckEffect?.setProps(this.parameters); } } } diff --git a/src/localization/src/translations/en.ts b/src/localization/src/translations/en.ts index 927855e345..41bc69c244 100644 --- a/src/localization/src/translations/en.ts +++ b/src/localization/src/translations/en.ts @@ -191,7 +191,9 @@ export default { addEffect: 'Add effect', pickDateTime: 'Pick date/time', currentTime: 'Current time', - pickCurrrentTime: 'Pick current time' + pickCurrrentTime: 'Pick current time', + date: 'Date', + time: 'Time' }, layerConfiguration: { defaultDescription: 'Calculate {property} based on selected field', diff --git a/src/styles/src/base.ts b/src/styles/src/base.ts index dfd3752b7d..293bca39d5 100644 --- a/src/styles/src/base.ts +++ b/src/styles/src/base.ts @@ -358,12 +358,6 @@ export const sliderMarginTopIsTime = -12; export const sliderMarginTop = 12; export const sliderMarginBottom = 12; -// Date picker -export const datePickerFontSize = '11px'; -export const datePickerBgdColor = inputBgd; -export const datePickerBgdHover = inputBgdHover; -export const datePickerSelectedDateBgColor = primaryBtnBgd; - // Geocoder export const geocoderWidth = 360; export const geocoderTop = 20; @@ -478,9 +472,27 @@ export const breakPoints = { desk: 768 }; -// effectConfigurator -export const effectConfiguratorMargin = '6px'; -export const effectConfiguratorPadding = '8px 0px 8px 5px'; +// effect manager +export const effectConfiguratorMargin = '18px 0 18px 0'; +export const effectConfiguratorPadding = '0 0 0 18px'; +export const effectPanelWidth = 345; +export const effectPanelHeight = 180; +export const effectPanelPaddingSide = 16; +export const effectPanelPaddingTop = 16; +export const effectPanelAddEffectFontFamily = btnFontFamily; + +export const effectTypeIconMarginSide = 6; +export const effectTypeIconSizeL = 56; + +export const effectTypeIconBgHoverColor = '#262D40'; +export const effectPanelTextMain = '#F7F7F7'; +export const effectPanelTextSecondary1 = '#A0A7B4'; +export const effectPanelTextSecondary2 = '#6A7485'; +export const effectPanelTextSecondary3 = '#5A6475'; + +// right panel +export const rightPanelMarginTop = 12; +export const rightPanelMarginRight = 12; // theme is passed to kepler.gl when it's mounted, // it is used by styled-components to pass along to @@ -1460,12 +1472,6 @@ export const theme = { sliderMarginTop, sliderMarginBottom, - // Date picker - datePickerFontSize, - datePickerBgdColor, - datePickerBgdHover, - datePickerSelectedDateBgColor, - // Geocoder geocoderWidth, geocoderTop, @@ -1557,22 +1563,32 @@ export const theme = { layerConfiguratorMargin, layerConfiguratorPadding, - // effectConfigurator - effectConfiguratorMargin, - effectConfiguratorPadding, - // Styled token fieldTokenRightMargin, fieldTokenHeight, fieldTokenWidth, - // Effects - effectPanelWidth: 345, - effectPanelHeight: 180, - effectPanelPaddingSide: 16, - effectPanelPaddingTop: 16, - rightPanelMarginTop: 12, - rightPanelMarginRight: 12 + // Effect panel + effectPanelWidth, + effectPanelHeight, + effectPanelPaddingSide, + effectPanelPaddingTop, + rightPanelMarginTop, + rightPanelMarginRight, + effectPanelAddEffectFontFamily, + + // effect type selector + effectTypeIconMarginSide, + effectTypeIconSizeL, + effectTypeIconBgHoverColor, + + // effectConfigurator + effectConfiguratorMargin, + effectConfiguratorPadding, + effectPanelTextMain, + effectPanelTextSecondary1, + effectPanelTextSecondary2, + effectPanelTextSecondary3 }; export const themeLT = { diff --git a/src/types/effects.d.ts b/src/types/effects.d.ts index e57e3490c3..397a13edd8 100644 --- a/src/types/effects.d.ts +++ b/src/types/effects.d.ts @@ -1,3 +1,18 @@ +export type EffectParameterDescription = { + name: string; + label?: string | false; + index?: number; + min?: number; + max?: number; + default?: number; +}; + +export type EffectDescription = { + type: string; + name: string; + parameters: EffectParameterDescription[]; +}; + export type EffectUpdateProps = { isEnabled: boolean; isConfigActive: boolean; @@ -20,8 +35,10 @@ export interface Effect { // effect specific parameters for a deck.gl effect (uniforms) parameters: {[key: string]: any}; deckEffect: any; + _uiConfig: EffectParameterDescription[]; getDefaultProps(props: Partial): EffectProps; setProps(props: Partial): void; isValidToSave(): boolean; + getParameterDescriptions(): EffectParameterDescription[]; } diff --git a/test/browser/components/effects/effect-configurator-test.js b/test/browser/components/effects/effect-configurator-test.js index 4bad461a7f..cedbe919b3 100644 --- a/test/browser/components/effects/effect-configurator-test.js +++ b/test/browser/components/effects/effect-configurator-test.js @@ -92,7 +92,7 @@ test('Components -> EffectConfigurator -> render -> light & shadow effect', t => }, `EffectConfigurator for ${nextState.effects[0].type} should not fail`); t.equal(wrapper.find('RangeSlider').length, 4, `should render 4 RangeSliders`); - t.equal(wrapper.find('ColorSelector').length, 3, `should render 3 ColorSelectors`); + t.equal(wrapper.find('CompactColorPicker').length, 3, `should render 3 CompactColorPickers`); t.equal( wrapper.find('EffectTimeConfigurator').length, 1, diff --git a/test/node/schemas/vis-state-schema-test.js b/test/node/schemas/vis-state-schema-test.js index a63d500589..215792fe65 100644 --- a/test/node/schemas/vis-state-schema-test.js +++ b/test/node/schemas/vis-state-schema-test.js @@ -365,10 +365,10 @@ test('#visStateSchema -> v1 -> save load effects', t => { type: 'magnify', isEnabled: true, parameters: { - screenXY: [0, 0], + screenXY: [0.5, 0.5], radiusPixels: 200, zoom: 2, - borderWidthPixels: 0, + borderWidthPixels: 3, borderColor: [255, 255, 255, 255] } },