|
| 1 | +/** |
| 2 | + * External dependencies |
| 3 | + */ |
| 4 | +import classnames from 'classnames'; |
| 5 | + |
| 6 | +/** |
| 7 | + * WordPress dependencies |
| 8 | + */ |
| 9 | +import { |
| 10 | + InspectorControls, |
| 11 | + PanelColorSettings, |
| 12 | + InnerBlocks, |
| 13 | + __experimentalUnitControl as UnitControl, |
| 14 | +} from '@wordpress/block-editor'; |
| 15 | +import { |
| 16 | + PanelBody, |
| 17 | + RangeControl, |
| 18 | + ResizableBox, |
| 19 | + BaseControl, |
| 20 | +} from '@wordpress/components'; |
| 21 | +import { useInstanceId } from '@wordpress/compose'; |
| 22 | +import { useDispatch, useSelect } from '@wordpress/data'; |
| 23 | +import { useState, useEffect, useRef } from '@wordpress/element'; |
| 24 | +import { __ } from '@wordpress/i18n'; |
| 25 | + |
| 26 | +const DEFAULT_COLORS = { |
| 27 | + color1: '#000', |
| 28 | + color2: '#555', |
| 29 | + color3: '#AAA', |
| 30 | + color4: '#FFF', |
| 31 | +}; |
| 32 | + |
| 33 | +const MIN_HEIGHT = 50; |
| 34 | + |
| 35 | +const CSS_UNITS = [ |
| 36 | + { value: 'px', label: 'px', default: 430 }, |
| 37 | + { value: 'em', label: 'em', default: 20 }, |
| 38 | + { value: 'rem', label: 'rem', default: 20 }, |
| 39 | + { value: 'vw', label: 'vw', default: 20 }, |
| 40 | + { value: 'vh', label: 'vh', default: 50 }, |
| 41 | +]; |
| 42 | + |
| 43 | +const RESIZABLE_BOX_ENABLE_OPTION = { |
| 44 | + top: false, |
| 45 | + right: false, |
| 46 | + bottom: true, |
| 47 | + left: false, |
| 48 | + topRight: false, |
| 49 | + bottomRight: false, |
| 50 | + bottomLeft: false, |
| 51 | + topLeft: false, |
| 52 | +}; |
| 53 | + |
| 54 | +function HeightInput( { onChange, onUnitChange, unit = 'px', value = '' } ) { |
| 55 | + const [ temporaryInput, setTemporaryInput ] = useState( null ); |
| 56 | + const instanceId = useInstanceId( UnitControl ); |
| 57 | + const inputId = `a8c-waves-height-input-${ instanceId }`; |
| 58 | + const isPx = unit === 'px'; |
| 59 | + |
| 60 | + const handleOnChange = ( unprocessedValue ) => { |
| 61 | + const inputValue = |
| 62 | + unprocessedValue !== '' |
| 63 | + ? parseInt( unprocessedValue, 10 ) |
| 64 | + : undefined; |
| 65 | + |
| 66 | + if ( isNaN( inputValue ) && inputValue !== undefined ) { |
| 67 | + setTemporaryInput( unprocessedValue ); |
| 68 | + return; |
| 69 | + } |
| 70 | + setTemporaryInput( null ); |
| 71 | + onChange( inputValue ); |
| 72 | + }; |
| 73 | + |
| 74 | + const handleOnBlur = () => { |
| 75 | + if ( temporaryInput !== null ) { |
| 76 | + setTemporaryInput( null ); |
| 77 | + } |
| 78 | + }; |
| 79 | + |
| 80 | + const inputValue = temporaryInput !== null ? temporaryInput : value; |
| 81 | + const min = isPx ? MIN_HEIGHT : 0; |
| 82 | + |
| 83 | + return ( |
| 84 | + <BaseControl |
| 85 | + label={ __( 'Minimum height of cover', 'waves' ) } |
| 86 | + id={ inputId } |
| 87 | + > |
| 88 | + <UnitControl |
| 89 | + id={ inputId } |
| 90 | + min={ min } |
| 91 | + onBlur={ handleOnBlur } |
| 92 | + onChange={ handleOnChange } |
| 93 | + onUnitChange={ onUnitChange } |
| 94 | + step="1" |
| 95 | + style={ { maxWidth: 80 } } |
| 96 | + unit={ unit } |
| 97 | + units={ CSS_UNITS } |
| 98 | + value={ inputValue } |
| 99 | + /> |
| 100 | + </BaseControl> |
| 101 | + ); |
| 102 | +} |
| 103 | + |
| 104 | +function Edit( { attributes, className, isSelected, setAttributes } ) { |
| 105 | + const { toggleSelection } = useDispatch( 'core/block-editor' ); |
| 106 | + const [ temporaryMinHeight, setTemporaryMinHeight ] = useState( null ); |
| 107 | + const [ isResizing, setIsResizing ] = useState( false ); |
| 108 | + |
| 109 | + const themeColors = useSelect( |
| 110 | + ( select ) => select( 'core/block-editor' ).getSettings().colors, |
| 111 | + [] |
| 112 | + ); |
| 113 | + const colors = { |
| 114 | + color1: |
| 115 | + attributes.color1 || |
| 116 | + themeColors[ 0 ].color || |
| 117 | + DEFAULT_COLORS.color1, |
| 118 | + color2: |
| 119 | + attributes.color2 || |
| 120 | + themeColors[ 0 ].color || |
| 121 | + DEFAULT_COLORS.color2, |
| 122 | + color3: |
| 123 | + attributes.color3 || |
| 124 | + themeColors[ 1 % themeColors.length ].color || |
| 125 | + DEFAULT_COLORS.color3, |
| 126 | + color4: |
| 127 | + attributes.color4 || |
| 128 | + themeColors[ 2 % themeColors.length ].color || |
| 129 | + DEFAULT_COLORS.color4, |
| 130 | + }; |
| 131 | + |
| 132 | + const renderPreview = ( newAttributes = {} ) => |
| 133 | + window.a8cColorEffects.renderPreview( { |
| 134 | + complexity: attributes.complexity, |
| 135 | + mouseSpeed: 1, |
| 136 | + fluidSpeed: 1, |
| 137 | + ...colors, |
| 138 | + ...newAttributes, |
| 139 | + } ); |
| 140 | + |
| 141 | + useEffect( () => { |
| 142 | + // Defaults need to be saved in the attributes because they are dynamic |
| 143 | + // based on theme, and theme settings are not available from save. |
| 144 | + Object.entries( colors ).forEach( ( [ key, value ] ) => { |
| 145 | + if ( attributes[ key ] === undefined ) { |
| 146 | + setAttributes( { [ key ]: value } ); |
| 147 | + } |
| 148 | + } ); |
| 149 | + |
| 150 | + // Save the initial preview in the attributes |
| 151 | + if ( attributes.previewImage === undefined ) { |
| 152 | + const previewImage = renderPreview(); |
| 153 | + setAttributes( { previewImage } ); |
| 154 | + } |
| 155 | + }, [] ); |
| 156 | + |
| 157 | + const minHeightWithUnit = attributes.minHeightUnit |
| 158 | + ? `${ attributes.minHeight }${ attributes.minHeightUnit }` |
| 159 | + : attributes.minHeight; |
| 160 | + const style = { |
| 161 | + minHeight: temporaryMinHeight || minHeightWithUnit || undefined, |
| 162 | + backgroundImage: `url( "${ attributes.previewImage }" )`, |
| 163 | + }; |
| 164 | + |
| 165 | + const canvasRef = useRef(); |
| 166 | + useEffect( () => { |
| 167 | + return window.a8cColorEffects.run( canvasRef.current ); |
| 168 | + }, [ canvasRef.current ] ); |
| 169 | + |
| 170 | + return ( |
| 171 | + <> |
| 172 | + <InspectorControls> |
| 173 | + <PanelBody title={ __( 'Animation', 'waves' ) } initialOpen> |
| 174 | + <RangeControl |
| 175 | + label={ __( 'Complexity', 'waves' ) } |
| 176 | + value={ attributes.complexity } |
| 177 | + onChange={ ( complexity ) => { |
| 178 | + const previewImage = renderPreview( { |
| 179 | + complexity, |
| 180 | + } ); |
| 181 | + setAttributes( { complexity, previewImage } ); |
| 182 | + } } |
| 183 | + min={ 1 } |
| 184 | + max={ 10 } |
| 185 | + /> |
| 186 | + <RangeControl |
| 187 | + label={ __( 'Mouse Speed', 'waves' ) } |
| 188 | + value={ attributes.mouseSpeed } |
| 189 | + onChange={ ( mouseSpeed ) => |
| 190 | + setAttributes( { mouseSpeed } ) |
| 191 | + } |
| 192 | + min={ 1 } |
| 193 | + max={ 100 } |
| 194 | + /> |
| 195 | + <RangeControl |
| 196 | + label={ __( 'Fluid Speed', 'waves' ) } |
| 197 | + value={ attributes.fluidSpeed } |
| 198 | + onChange={ ( fluidSpeed ) => |
| 199 | + setAttributes( { fluidSpeed } ) |
| 200 | + } |
| 201 | + min={ 1 } |
| 202 | + max={ 100 } |
| 203 | + /> |
| 204 | + </PanelBody> |
| 205 | + <PanelColorSettings |
| 206 | + title={ __( 'Color', 'waves' ) } |
| 207 | + initialOpen |
| 208 | + colorSettings={ [ |
| 209 | + { |
| 210 | + label: __( 'Color 1', 'waves' ), |
| 211 | + value: colors.color1, |
| 212 | + onChange: ( color1 ) => { |
| 213 | + const previewImage = renderPreview( { |
| 214 | + color1, |
| 215 | + } ); |
| 216 | + setAttributes( { color1, previewImage } ); |
| 217 | + }, |
| 218 | + }, |
| 219 | + { |
| 220 | + label: __( 'Color 2', 'waves' ), |
| 221 | + value: colors.color2, |
| 222 | + onChange: ( color2 ) => { |
| 223 | + const previewImage = renderPreview( { |
| 224 | + color2, |
| 225 | + } ); |
| 226 | + setAttributes( { color2, previewImage } ); |
| 227 | + }, |
| 228 | + }, |
| 229 | + { |
| 230 | + label: __( 'Color 3', 'waves' ), |
| 231 | + value: colors.color3, |
| 232 | + onChange: ( color3 ) => { |
| 233 | + const previewImage = renderPreview( { |
| 234 | + color3, |
| 235 | + } ); |
| 236 | + setAttributes( { color3, previewImage } ); |
| 237 | + }, |
| 238 | + }, |
| 239 | + { |
| 240 | + label: __( 'Color 4', 'waves' ), |
| 241 | + value: colors.color4, |
| 242 | + onChange: ( color4 ) => { |
| 243 | + const previewImage = renderPreview( { |
| 244 | + color4, |
| 245 | + } ); |
| 246 | + setAttributes( { color4, previewImage } ); |
| 247 | + }, |
| 248 | + }, |
| 249 | + ] } |
| 250 | + /> |
| 251 | + <PanelBody title={ __( 'Dimensions', 'waves' ) }> |
| 252 | + <HeightInput |
| 253 | + value={ temporaryMinHeight || attributes.minHeight } |
| 254 | + unit={ attributes.minHeightUnit } |
| 255 | + onChange={ ( minHeight ) => |
| 256 | + setAttributes( { minHeight } ) |
| 257 | + } |
| 258 | + onUnitChange={ ( minHeightUnit ) => |
| 259 | + setAttributes( { minHeightUnit } ) |
| 260 | + } |
| 261 | + /> |
| 262 | + </PanelBody> |
| 263 | + </InspectorControls> |
| 264 | + <ResizableBox |
| 265 | + className={ classnames( `${ className }__resize-container`, { |
| 266 | + 'is-resizing': isResizing, |
| 267 | + } ) } |
| 268 | + enable={ RESIZABLE_BOX_ENABLE_OPTION } |
| 269 | + onResizeStart={ ( event, direction, elt ) => { |
| 270 | + toggleSelection( false ); |
| 271 | + setAttributes( { minHeightUnit: 'px' } ); |
| 272 | + setTemporaryMinHeight( elt.clientHeight ); |
| 273 | + } } |
| 274 | + onResize={ ( event, direction, elt ) => { |
| 275 | + // Setting is-resizing here instead of onResizeStart fixes |
| 276 | + // an issue with the positioning of the resize bar when |
| 277 | + // starting a resize after having resized smaller than the |
| 278 | + // auto height |
| 279 | + setIsResizing( true ); |
| 280 | + setTemporaryMinHeight( elt.clientHeight ); |
| 281 | + } } |
| 282 | + onResizeStop={ ( event, direction, elt ) => { |
| 283 | + toggleSelection( true ); |
| 284 | + setIsResizing( false ); |
| 285 | + setAttributes( { minHeight: elt.clientHeight } ); |
| 286 | + setTemporaryMinHeight( null ); |
| 287 | + } } |
| 288 | + minHeight={ MIN_HEIGHT } |
| 289 | + showHandle={ isSelected } |
| 290 | + > |
| 291 | + <div className={ className } style={ style }> |
| 292 | + <canvas |
| 293 | + ref={ canvasRef } |
| 294 | + data-complexity={ attributes.complexity } |
| 295 | + data-mouse-speed={ attributes.mouseSpeed } |
| 296 | + data-fluid-speed={ attributes.fluidSpeed } |
| 297 | + data-color1={ colors.color1 } |
| 298 | + data-color2={ colors.color2 } |
| 299 | + data-color3={ colors.color3 } |
| 300 | + data-color4={ colors.color4 } |
| 301 | + /> |
| 302 | + <div className={ `${ className }__inner-container` }> |
| 303 | + <InnerBlocks |
| 304 | + template={ [ |
| 305 | + [ |
| 306 | + 'core/paragraph', |
| 307 | + { |
| 308 | + align: 'center', |
| 309 | + fontSize: 'large', |
| 310 | + placeholder: __( |
| 311 | + 'Write title…', |
| 312 | + 'waves' |
| 313 | + ), |
| 314 | + }, |
| 315 | + ], |
| 316 | + ] } |
| 317 | + /> |
| 318 | + </div> |
| 319 | + </div> |
| 320 | + </ResizableBox> |
| 321 | + </> |
| 322 | + ); |
| 323 | +} |
| 324 | + |
| 325 | +export default Edit; |
0 commit comments