BlockSettings.jsx
267 lines
| 1 | import { __ } from '@wordpress/i18n'; |
| 2 | import { SelectControl, TextControl, Flex, FlexBlock } from '@wordpress/components'; |
| 3 | import { useEffect, useState, useMemo, useCallback } from '@wordpress/element'; |
| 4 | import apiFetch from '@wordpress/api-fetch'; |
| 5 | import { addQueryArgs, isURL } from '@wordpress/url'; |
| 6 | import { applyFilters } from '@wordpress/hooks'; |
| 7 | import { debounce } from '@wordpress/compose'; |
| 8 | |
| 9 | import { OpenPanel } from '@edge22/components'; |
| 10 | import { UnitControl } from '@edge22/styles-builder'; |
| 11 | |
| 12 | import { |
| 13 | ApplyFilters, |
| 14 | ImageUpload, |
| 15 | URLControls, |
| 16 | DynamicTagsOnboarder, |
| 17 | } from '@components/index.js'; |
| 18 | import { useBlockStyles } from '@hooks/useBlockStyles'; |
| 19 | |
| 20 | async function getImageByUrl( url ) { |
| 21 | const image = await apiFetch( { |
| 22 | path: addQueryArgs( `/generateblocks/v1/get-attachment-by-url`, { |
| 23 | url, |
| 24 | } ), |
| 25 | method: 'GET', |
| 26 | } ); |
| 27 | |
| 28 | if ( image.success ) { |
| 29 | return image.response; |
| 30 | } |
| 31 | |
| 32 | return null; |
| 33 | } |
| 34 | |
| 35 | export function BlockSettings( { |
| 36 | getStyleValue, |
| 37 | onStyleChange, |
| 38 | name, |
| 39 | attributes, |
| 40 | setAttributes, |
| 41 | onSelectImage, |
| 42 | htmlAttributes, |
| 43 | context, |
| 44 | } ) { |
| 45 | const { |
| 46 | linkHtmlAttributes, |
| 47 | mediaId, |
| 48 | tagName, |
| 49 | } = attributes; |
| 50 | |
| 51 | const { |
| 52 | currentAtRule, |
| 53 | } = useBlockStyles(); |
| 54 | const [ imageData, setImageData ] = useState( null ); |
| 55 | const [ hasResolved, setHasResolved ] = useState( false ); |
| 56 | |
| 57 | const debouncedOnStyleChange = useCallback( |
| 58 | debounce( ( property, value, atRule ) => { |
| 59 | onStyleChange( property, value, atRule ); |
| 60 | }, 250 ), |
| 61 | [ onStyleChange ] |
| 62 | ); |
| 63 | |
| 64 | useEffect( () => { |
| 65 | if ( ! isURL( htmlAttributes?.src ) ) { |
| 66 | return; |
| 67 | } |
| 68 | |
| 69 | ( async function() { |
| 70 | try { |
| 71 | const image = await getImageByUrl( htmlAttributes?.src ); |
| 72 | |
| 73 | if ( image ) { |
| 74 | setImageData( image ); |
| 75 | } |
| 76 | |
| 77 | setHasResolved( true ); |
| 78 | } catch ( error ) { |
| 79 | console.info( 'Error fetching image:', error ); // eslint-disable-line no-console |
| 80 | setHasResolved( true ); |
| 81 | } |
| 82 | }() ); |
| 83 | }, [ htmlAttributes?.src ] ); |
| 84 | |
| 85 | useEffect( () => { |
| 86 | const id = imageData?.id; |
| 87 | |
| 88 | if ( hasResolved && id !== mediaId ) { |
| 89 | setAttributes( { |
| 90 | mediaId: id ?? 0, |
| 91 | } ); |
| 92 | } |
| 93 | }, [ imageData?.id ] ); |
| 94 | |
| 95 | const sizes = useMemo( () => { |
| 96 | const imageSizes = imageData?.sizes |
| 97 | ? Object.keys( imageData?.sizes ) |
| 98 | : []; |
| 99 | |
| 100 | if ( ! imageSizes.length ) { |
| 101 | return []; |
| 102 | } |
| 103 | |
| 104 | const options = imageSizes.map( ( imageSize ) => { |
| 105 | return { |
| 106 | label: imageSize.charAt( 0 ).toUpperCase() + imageSize.slice( 1 ), |
| 107 | value: imageSize, |
| 108 | }; |
| 109 | } ); |
| 110 | |
| 111 | options.unshift( { label: __( 'Full', 'generateblocks-pro' ), value: '' } ); |
| 112 | |
| 113 | return options; |
| 114 | }, [ imageData?.sizes ] ); |
| 115 | |
| 116 | const imageSizeValue = useMemo( () => { |
| 117 | const imageSizes = imageData?.sizes |
| 118 | ? Object.keys( imageData?.sizes ) |
| 119 | : []; |
| 120 | |
| 121 | if ( ! imageSizes.length ) { |
| 122 | return ''; |
| 123 | } |
| 124 | |
| 125 | // Get the key by using the value (htmlAttributes?.src) in the imageData?.sizes array |
| 126 | const key = imageSizes.find( ( sizeKey ) => imageData?.sizes[ sizeKey ]?.url === htmlAttributes?.src ); |
| 127 | |
| 128 | if ( ! key ) { |
| 129 | return ''; |
| 130 | } |
| 131 | |
| 132 | return key; |
| 133 | }, [ htmlAttributes?.src, imageData?.sizes ] ); |
| 134 | |
| 135 | const panelProps = { |
| 136 | name, |
| 137 | attributes, |
| 138 | setAttributes, |
| 139 | }; |
| 140 | |
| 141 | return ( |
| 142 | <ApplyFilters |
| 143 | name="generateblocks.editor.blockControls" |
| 144 | blockName={ name } |
| 145 | getStyleValue={ getStyleValue } |
| 146 | onStyleChange={ onStyleChange } |
| 147 | currentAtRule={ currentAtRule } |
| 148 | attributes={ attributes } |
| 149 | setAttributes={ setAttributes } |
| 150 | > |
| 151 | <OpenPanel |
| 152 | { ...panelProps } |
| 153 | shouldRender={ '' === currentAtRule } |
| 154 | panelId="settings" |
| 155 | > |
| 156 | <ImageUpload |
| 157 | context={ context } |
| 158 | value={ htmlAttributes?.src ?? '' } |
| 159 | onInsert={ ( value ) => { |
| 160 | const newHtmlAttributes = { |
| 161 | ...htmlAttributes, |
| 162 | src: value, |
| 163 | }; |
| 164 | |
| 165 | setAttributes( { |
| 166 | htmlAttributes: newHtmlAttributes, |
| 167 | } ); |
| 168 | } } |
| 169 | onSelectImage={ onSelectImage } |
| 170 | allowDynamicTags={ true } |
| 171 | onInsertDynamicTag={ ( value ) => { |
| 172 | const newHtmlAttributes = { |
| 173 | ...htmlAttributes, |
| 174 | src: value, |
| 175 | }; |
| 176 | |
| 177 | setAttributes( { |
| 178 | htmlAttributes: newHtmlAttributes, |
| 179 | mediaId: 0, |
| 180 | } ); |
| 181 | } } |
| 182 | /> |
| 183 | |
| 184 | <URLControls |
| 185 | setAttributes={ setAttributes } |
| 186 | htmlAttributes={ linkHtmlAttributes } |
| 187 | attributesName="linkHtmlAttributes" |
| 188 | context={ context } |
| 189 | tagName={ tagName } |
| 190 | /> |
| 191 | |
| 192 | { applyFilters( |
| 193 | 'generateblocks.blockSettings.afterImageUrlControls', |
| 194 | null, |
| 195 | panelProps |
| 196 | ) } |
| 197 | |
| 198 | { !! sizes?.length && ( |
| 199 | <SelectControl |
| 200 | label={ __( 'Size', 'generateblocks' ) } |
| 201 | options={ sizes } |
| 202 | value={ imageSizeValue } |
| 203 | onChange={ ( value ) => { |
| 204 | if ( '' === value ) { |
| 205 | setAttributes( { |
| 206 | htmlAttributes: { |
| 207 | ...htmlAttributes, |
| 208 | src: imageData?.full_url ?? '', |
| 209 | width: imageData?.width ?? '', |
| 210 | height: imageData?.height ?? '', |
| 211 | }, |
| 212 | } ); |
| 213 | |
| 214 | return; |
| 215 | } |
| 216 | |
| 217 | setAttributes( { |
| 218 | htmlAttributes: { |
| 219 | ...htmlAttributes, |
| 220 | src: imageData?.sizes[ value ]?.url ?? '', |
| 221 | width: imageData?.sizes[ value ]?.width ?? '', |
| 222 | height: imageData?.sizes[ value ]?.height ?? '', |
| 223 | }, |
| 224 | } ); |
| 225 | } } |
| 226 | /> |
| 227 | ) } |
| 228 | |
| 229 | <Flex> |
| 230 | <FlexBlock> |
| 231 | <UnitControl |
| 232 | id="width" |
| 233 | label={ __( 'Width', 'generateblocks' ) } |
| 234 | value={ getStyleValue( 'width', currentAtRule ) } |
| 235 | onChange={ ( value ) => debouncedOnStyleChange( 'width', value, currentAtRule ) } |
| 236 | /> |
| 237 | </FlexBlock> |
| 238 | |
| 239 | <FlexBlock> |
| 240 | <UnitControl |
| 241 | id="height" |
| 242 | label={ __( 'Height', 'generateblocks' ) } |
| 243 | value={ getStyleValue( 'height', currentAtRule ) } |
| 244 | onChange={ ( value ) => debouncedOnStyleChange( 'height', value, currentAtRule ) } |
| 245 | /> |
| 246 | </FlexBlock> |
| 247 | </Flex> |
| 248 | |
| 249 | <TextControl |
| 250 | label={ __( 'Alt text', 'generateblocks' ) } |
| 251 | value={ htmlAttributes?.alt ?? '' } |
| 252 | onChange={ ( value ) => { |
| 253 | setAttributes( { |
| 254 | htmlAttributes: { |
| 255 | ...htmlAttributes, |
| 256 | alt: value, |
| 257 | }, |
| 258 | } ); |
| 259 | } } |
| 260 | /> |
| 261 | </OpenPanel> |
| 262 | |
| 263 | <DynamicTagsOnboarder /> |
| 264 | </ApplyFilters> |
| 265 | ); |
| 266 | } |
| 267 |