/* eslint-disable @woocommerce/dependency-group */ /* eslint-disable @typescript-eslint/ban-ts-comment */ /** * External dependencies */ import { __ } from '@wordpress/i18n'; import { useState, useContext, cloneElement } from '@wordpress/element'; import { RangeControl, ToggleControl, DropZone, Button, Spinner, } from '@wordpress/components'; import { useSelect, useDispatch } from '@wordpress/data'; import { useViewportMatch } from '@wordpress/compose'; import { Icon, upload } from '@wordpress/icons'; // @ts-ignore No types for this exist yet. import { store as coreStore } from '@wordpress/core-data'; // @ts-ignore No types for this exist yet. import { isBlobURL } from '@wordpress/blob'; import { MediaUpload, MediaUploadCheck, store as blockEditorStore, // @ts-ignore No types for this exist yet. } from '@wordpress/block-editor'; // @ts-ignore No types for this exist yet. import { store as noticesStore } from '@wordpress/notices'; /** * Internal dependencies */ import { SidebarNavigationScreen } from './sidebar-navigation-screen'; import { LogoBlockContext } from '../logo-block-context'; const MIN_SIZE = 20; const ALLOWED_MEDIA_TYPES = [ 'image' ]; type LogoAttributes = Partial< { align: string; width: number; height: number; isLink: boolean; linkTarget: string; shouldSyncIcon: boolean; } >; const useLogoEdit = ( { shouldSyncIcon, setAttributes, }: { shouldSyncIcon: LogoAttributes[ 'shouldSyncIcon' ]; setAttributes: ( newAttributes: LogoAttributes ) => void; } ) => { const { siteIconId, mediaUpload } = useSelect( ( select ) => { // @ts-ignore No types for this exist yet. const { canUser, getEditedEntityRecord } = select( coreStore ); const _canUserEdit = canUser( 'update', 'settings' ); const siteSettings = _canUserEdit ? getEditedEntityRecord( 'root', 'site' ) : undefined; const _siteIconId = siteSettings?.site_icon; return { siteIconId: _siteIconId, // @ts-ignore No types for this exist yet. mediaUpload: select( blockEditorStore ).getSettings().mediaUpload, }; }, [] ); // @ts-ignore No types for this exist yet. const { editEntityRecord } = useDispatch( coreStore ); const setIcon = ( newValue: string | undefined ) => // The new value needs to be `null` to reset the Site Icon. editEntityRecord( 'root', 'site', undefined, { site_icon: newValue ?? null, } ); const setLogo = ( newValue: string | undefined, shouldForceSync = false ) => { // `shouldForceSync` is used to force syncing when the attribute // may not have updated yet. if ( shouldSyncIcon || shouldForceSync ) { setIcon( newValue ); } editEntityRecord( 'root', 'site', undefined, { site_logo: newValue, } ); }; const onSelectLogo = ( media: { id: string; url: string }, shouldForceSync = false ) => { if ( ! media ) { return; } if ( ! media.id && media.url ) { // This is a temporary blob image. setLogo( undefined ); return; } setLogo( media.id, shouldForceSync ); }; const onInitialSelectLogo = ( media: { id: string; url: string } ) => { // Initialize the syncSiteIcon toggle. If we currently have no site logo and no // site icon, automatically sync the logo to the icon. if ( shouldSyncIcon === undefined ) { const shouldForceSync = ! siteIconId; setAttributes( { shouldSyncIcon: shouldForceSync } ); // Because we cannot rely on the `shouldSyncIcon` attribute to have updated by // the time `setLogo` is called, pass an argument to force the syncing. onSelectLogo( media, shouldForceSync ); return; } onSelectLogo( media ); }; const { createErrorNotice } = useDispatch( noticesStore ); const onUploadError = ( message: string ) => { createErrorNotice( message, { type: 'snackbar' } ); }; const onFilesDrop = ( filesList: File[] ) => { mediaUpload( { allowedTypes: [ 'image' ], filesList, onFileChange( [ image ]: [ { id: string; url: string } ] ) { if ( isBlobURL( image?.url ) ) { return; } onInitialSelectLogo( image ); }, onError: onUploadError, } ); }; return { onFilesDrop, onInitialSelectLogo, setIcon, siteIconId, }; }; // Reference: https://github.com/WordPress/gutenberg/blob/83f3fbc740c97afac3474a6c37098e259191dc2c/packages/block-library/src/site-logo/edit.js#L63 const LogoSettings = ( { attributes: { width, isLink, shouldSyncIcon, align = '' }, canUserEdit, naturalWidth, naturalHeight, setAttributes, setIcon, logoId, }: { attributes: LogoAttributes; setAttributes: ( newAttributes: LogoAttributes ) => void; canUserEdit: boolean; naturalWidth: number; naturalHeight: number; setIcon: ( newValue: string | undefined ) => void; logoId: string; } ) => { const isLargeViewport = useViewportMatch( 'medium' ); const isWideAligned = [ 'wide', 'full' ].includes( align ); const isResizable = ! isWideAligned && isLargeViewport; const { maxWidth } = useSelect( ( select ) => { // @ts-ignore No types for this exist yet. const settings = select( blockEditorStore ).getSettings(); return { maxWidth: settings.maxWidth, }; }, [] ); // Set the default width to a responsible size. // Note that this width is also set in the attached frontend CSS file. const defaultWidth = 120; const currentWidth = width || defaultWidth; const ratio = naturalWidth / naturalHeight; const minWidth = naturalWidth < naturalHeight ? MIN_SIZE : Math.ceil( MIN_SIZE * ratio ); // With the current implementation of ResizableBox, an image needs an // explicit pixel value for the max-width. In absence of being able to // set the content-width, this max-width is currently dictated by the // vanilla editor style. The following variable adds a buffer to this // vanilla style, so 3rd party themes have some wiggleroom. This does, // in most cases, allow you to scale the image beyond the width of the // main column, though not infinitely. // @todo It would be good to revisit this once a content-width variable // becomes available. const maxWidthBuffer = maxWidth * 2.5; return (