diff --git a/.gitignore b/.gitignore index c43d6e0d4..1dc7d908a 100644 --- a/.gitignore +++ b/.gitignore @@ -4,6 +4,7 @@ /phpcs.xml /.phpcs.xml *.zip +/wp-includes/js/dist/* /wp-includes/js/workbox* /wiki .vscode diff --git a/.phpcs.xml.dist b/.phpcs.xml.dist index ded5795d2..35ecaf78b 100644 --- a/.phpcs.xml.dist +++ b/.phpcs.xml.dist @@ -13,6 +13,7 @@ ./node_modules/ ./vendor/ ./build/ + ./wp-includes/js/dist/ diff --git a/package.json b/package.json index d4f99281f..fb70335f6 100644 --- a/package.json +++ b/package.json @@ -41,7 +41,9 @@ "workbox-cli": "6.5.4" }, "scripts": { - "build": "grunt build; grunt create-build-zip", + "build": "npm run block:build; grunt build; grunt create-build-zip", + "block:build": "wp-scripts build site-icon-maskable=./wp-includes/js/src/site-icon-maskable/index.js --output-path=wp-includes/js/dist", + "block:start": "wp-scripts start site-icon-maskable=./wp-includes/js/src/site-icon-maskable/index.js --output-path=wp-includes/js/dist", "check-engines": "wp-scripts check-engines", "check-licenses": "wp-scripts check-licenses --production", "deploy": "grunt deploy", diff --git a/pwa.php b/pwa.php index 5b909da62..6e2b6b81f 100644 --- a/pwa.php +++ b/pwa.php @@ -80,6 +80,7 @@ function _pwa_incorrect_plugin_slug_admin_notice() { * Print admin notice when a build has not been been performed. * * @since 0.2 + * @since 0.8.0-alpha Check for site-icon-maskable.asset.php, which should be auto-generated by wp-scripts */ function _pwa_print_build_needed_notice() { ?> @@ -96,7 +97,10 @@ function _pwa_print_build_needed_notice() { assertEquals( 10, has_action( 'wp_head', array( $this->instance, 'manifest_link_and_meta' ) ) ); $this->assertEquals( 10, has_action( 'rest_api_init', array( $this->instance, 'register_manifest_rest_route' ) ) ); - $this->assertEquals( 10, has_action( 'rest_api_init', array( $this->instance, 'register_short_name_setting' ) ) ); - $this->assertEquals( 10, has_action( 'admin_init', array( $this->instance, 'register_short_name_setting' ) ) ); + $this->assertEquals( 10, has_action( 'rest_api_init', array( $this->instance, 'register_settings' ) ) ); + $this->assertEquals( 10, has_action( 'admin_init', array( $this->instance, 'register_settings' ) ) ); $this->assertEquals( 10, has_action( 'admin_init', array( $this->instance, 'add_short_name_settings_field' ) ) ); } @@ -532,16 +532,16 @@ public function test_get_url() { } /** - * Test register_short_name_setting. + * Test register_settings. * - * @covers ::register_short_name_setting() + * @covers ::register_settings() */ - public function test_register_short_name_setting() { + public function test_register_settings() { global $wp_registered_settings; unset( $wp_registered_settings['short_name'] ); $this->assertArrayNotHasKey( 'short_name', $wp_registered_settings ); - $this->instance->register_short_name_setting(); + $this->instance->register_settings(); $this->assertArrayHasKey( 'short_name', $wp_registered_settings ); $setting = $wp_registered_settings['short_name']; @@ -549,6 +549,18 @@ public function test_register_short_name_setting() { $this->assertEquals( 'general', $setting['group'] ); $this->assertEquals( array( $this->instance, 'sanitize_short_name' ), $setting['sanitize_callback'] ); $this->assertEquals( true, $setting['show_in_rest'] ); + + unset( $wp_registered_settings['site_icon_maskable'] ); + + $this->assertArrayNotHasKey( 'site_icon_maskable', $wp_registered_settings ); + $this->instance->register_settings(); + $this->assertArrayHasKey( 'site_icon_maskable', $wp_registered_settings ); + $setting = $wp_registered_settings['site_icon_maskable']; + + $this->assertEquals( 'boolean', $setting['type'] ); + $this->assertEquals( 'general', $setting['group'] ); + $this->assertEquals( 'rest_sanitize_boolean', $setting['sanitize_callback'] ); + $this->assertEquals( true, $setting['show_in_rest'] ); } /** diff --git a/wp-includes/class-wp-web-app-manifest.php b/wp-includes/class-wp-web-app-manifest.php index 5bdf32fa7..7b2b36126 100644 --- a/wp-includes/class-wp-web-app-manifest.php +++ b/wp-includes/class-wp-web-app-manifest.php @@ -68,9 +68,11 @@ public function init() { add_action( 'rest_api_init', array( $this, 'register_manifest_rest_route' ) ); add_filter( 'site_status_tests', array( $this, 'add_pwa_site_health_tests' ) ); - add_action( 'rest_api_init', array( $this, 'register_short_name_setting' ) ); - add_action( 'admin_init', array( $this, 'register_short_name_setting' ) ); + add_action( 'rest_api_init', array( $this, 'register_settings' ) ); + add_action( 'admin_init', array( $this, 'register_settings' ) ); add_action( 'admin_init', array( $this, 'add_short_name_settings_field' ) ); + add_action( 'enqueue_block_editor_assets', array( $this, 'enqueue_site_icon_maskable_block_editor_assets' ) ); + } /** @@ -533,9 +535,15 @@ public static function get_url() { } /** - * Register setting for short_name. + * Register pwa settings to the settings-API and make them visible to the REST API. + * + * @since 0.7.0 + * @since 0.8.0-alpha Added registration of 'site_icon_maskable' setting. + * + * @return void */ - public function register_short_name_setting() { + public function register_settings() { + // Register setting for short_name. register_setting( 'general', self::SHORT_NAME_OPTION, @@ -546,6 +554,29 @@ public function register_short_name_setting() { 'show_in_rest' => true, ) ); + + /* + * Register setting for maskable site-icon. + * + * Even that this option is not exposed + * to the settings UI within normal admin options pages, + * the registration is needed to make the option + * available to the REST API, which is used by + * the site-editor. + * + * In the site-editor, this option can be set with the help of + * UI that is added to the 'site-logo' core block. + */ + register_setting( + 'general', + 'site_icon_maskable', + array( + 'type' => 'boolean', + 'description' => __( 'Wether the current site icon is maskable or not, as this is needed by some devices.', 'pwa' ), + 'sanitize_callback' => 'rest_sanitize_boolean', + 'show_in_rest' => true, + ) + ); } /** @@ -677,4 +708,37 @@ function updateShortNameField() { { + return (props) => { + if (props.name !== 'core/site-logo') { + return ; + } + + return ( + <> + + + + ); + }; +}, 'withMaskableControls'); + +/** + * To modify the behavior of existing blocks, + * WordPress exposes several APIs. + * + * @see https://developer.wordpress.org/block-editor/reference-guides/filters/block-filters/ + */ +wp.hooks.addFilter( + 'editor.BlockEdit', + 'pwa/with-maskable-icon-controls', + withMaskableControls +); diff --git a/wp-includes/js/src/site-icon-maskable/maskable-icon-controls.js b/wp-includes/js/src/site-icon-maskable/maskable-icon-controls.js new file mode 100644 index 000000000..fdfa358c6 --- /dev/null +++ b/wp-includes/js/src/site-icon-maskable/maskable-icon-controls.js @@ -0,0 +1,115 @@ +/** + * Retrieves the translation of text. + * + * @see https://developer.wordpress.org/block-editor/packages/packages-i18n/ + */ +import { __ } from '@wordpress/i18n'; + +/** + * This module allows you to create and use standalone block editors. ;) + * + * @see https://developer.wordpress.org/block-editor/reference-guides/packages/packages-block-editor/ + */ +import { InspectorControls } from '@wordpress/block-editor'; + +/** + * This package includes a library of generic WordPress components + * to be used for creating common UI elements shared between screens + * and features of the WordPress dashboard. + * + * @see https://developer.wordpress.org/block-editor/reference-guides/packages/packages-components/ + */ +import { + Flex, + FlexBlock, + FlexItem, + PanelBody, + ToggleControl, +} from '@wordpress/components'; + +/** + * Core Data is a data module intended to + * simplify access to and manipulation + * of core WordPress entities. + * + * @see https://developer.wordpress.org/block-editor/reference-guides/packages/packages-core-data/ + */ +import { store as coreStore, useEntityProp } from '@wordpress/core-data'; + +/** + * WordPress’ data module serves as a hub + * to manage application state + * for both plugins and WordPress itself. + * + * @see https://developer.wordpress.org/block-editor/reference-guides/packages/packages-data/ + */ +import { useSelect } from '@wordpress/data'; + +export default function MaskableControls() { + const [siteIconMaskable, setSiteIconMaskable] = useEntityProp( + 'root', + 'site', + 'site_icon_maskable' + ); + + // mainly borrowed from ... + const { isRequestingSiteIcon, siteIconUrl } = useSelect((select) => { + const { getEntityRecord, isResolving } = select(coreStore); + const siteData = + getEntityRecord('root', '__unstableBase', undefined) || {}; + + return { + isRequestingSiteIcon: isResolving('getEntityRecord', [ + 'root', + '__unstableBase', + undefined, + ]), + siteIconUrl: siteData.site_icon_url, + }; + }, []); + + if (isRequestingSiteIcon) { + return null; + } + + const siteIconStyle = { + clipPath: siteIconMaskable ? 'inset(10% round 50%)' : '', + width: '64px', + }; + + let siteIcon =
; + + if (siteIconUrl) { + siteIcon = ( + {__('Site + ); + } + + return ( + + + + + + + {siteIcon} + + + + ); +}