Skip to content

Commit

Permalink
Revert "Plugin: Remove the experimental Progressive Web Apps (PWA) in…
Browse files Browse the repository at this point in the history
…tegration (#38810)" (#39930)

This reverts commit 16b89d6.
  • Loading branch information
gziolo authored Mar 31, 2022
1 parent 01ab537 commit 2a2938f
Show file tree
Hide file tree
Showing 11 changed files with 285 additions and 0 deletions.
5 changes: 5 additions & 0 deletions .github/CODEOWNERS
Validating CODEOWNERS rules …
Original file line number Diff line number Diff line change
Expand Up @@ -140,6 +140,11 @@
/lib/compat/wordpress-5.9/class-wp-theme-json-resolver-gutenberg.php @timothybjacobs @spacedmonkey @oandregal
/phpunit/class-wp-theme-json-test.php @oandregal

# Web App
/packages/admin-manifest @ellatrix
/lib/pwa.php @ellatrix
/lib/service-worker.js @ellatrix

# Native
/packages/components/src/mobile/global-styles-context @geriux

Expand Down
1 change: 1 addition & 0 deletions lib/load.php
Original file line number Diff line number Diff line change
Expand Up @@ -121,6 +121,7 @@ function gutenberg_is_experiment_enabled( $name ) {
require __DIR__ . '/navigation-page.php';
require __DIR__ . '/experiments-page.php';
require __DIR__ . '/global-styles.php';
require __DIR__ . '/pwa.php';

// TODO: Move this to be loaded from the style engine package, via the build directory.
// Part of the build process should be to copy the PHP file to the correct location,
Expand Down
34 changes: 34 additions & 0 deletions lib/pwa.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
<?php
/**
* Progressive Web App
*
* @package gutenberg
*/

add_filter(
'admin_head',
function() {
$l10n = array(
'logo' => file_get_contents( ABSPATH . 'wp-admin/images/wordpress-logo-white.svg' ),
'siteTitle' => get_bloginfo( 'name' ),
'adminUrl' => admin_url(),
);
wp_enqueue_script( 'wp-admin-manifest' );
wp_localize_script( 'wp-admin-manifest', 'wpAdminManifestL10n', $l10n );
}
);

add_filter(
'load-index.php',
function() {
if ( ! isset( $_GET['service-worker'] ) ) {
return;
}

header( 'Content-Type: text/javascript' );
// Must be at the admin root so the scope is correct. Move to the
// wp-admin folder when merging with core.
echo file_get_contents( __DIR__ . '/service-worker.js' );
exit;
}
);
14 changes: 14 additions & 0 deletions lib/service-worker.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
/* global self */

// https://developer.mozilla.org/en-US/docs/Web/API/ServiceWorkerGlobalScope/skipWaiting
self.addEventListener( 'install', function ( event ) {
event.waitUntil( self.skipWaiting() );
} );

// https://developer.mozilla.org/en-US/docs/Web/API/Clients/claim
self.addEventListener( 'activate', function ( event ) {
event.waitUntil( self.clients.claim() );
} );

// Necessary for Chrome to show the install button.
self.addEventListener( 'fetch', function () {} );
7 changes: 7 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
},
"dependencies": {
"@wordpress/a11y": "file:packages/a11y",
"@wordpress/admin-manifest": "file:packages/admin-manifest",
"@wordpress/annotations": "file:packages/annotations",
"@wordpress/api-fetch": "file:packages/api-fetch",
"@wordpress/autop": "file:packages/autop",
Expand Down
1 change: 1 addition & 0 deletions packages/admin-manifest/.npmrc
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
package-lock=false
Empty file.
11 changes: 11 additions & 0 deletions packages/admin-manifest/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
# Admin Manifest

Dynamically creates a Web App [manifest](https://w3c.github.io/manifest/) and registers the service worker for the admin.

## Contributing to this package

This is an individual package that's part of the Gutenberg project. The project is organized as a monorepo. It's made up of multiple self-contained software packages, each with a specific purpose. The packages in this monorepo are published to [npm](https://www.npmjs.com/) and used by [WordPress](https://make.wordpress.org/core/) as well as other software projects.

To find out more about contributing to this package or Gutenberg as a whole, please read the project's main [contributor guide](https://github.com/WordPress/gutenberg/tree/HEAD/CONTRIBUTING.md).

<br /><br /><p align="center"><img src="https://s.w.org/style/images/codeispoetry.png?1" alt="Code is Poetry." /></p>
35 changes: 35 additions & 0 deletions packages/admin-manifest/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
{
"name": "@wordpress/admin-manifest",
"version": "1.0.1",
"description": "Dynamically creates a Web App manifest and registers the service worker for the admin.",
"private": true,
"author": "The WordPress Contributors",
"license": "GPL-2.0-or-later",
"keywords": [
"wordpress",
"gutenberg",
"manifest"
],
"homepage": "https://github.com/WordPress/gutenberg/tree/HEAD/packages/admin-manifest/README.md",
"repository": {
"type": "git",
"url": "https://github.com/WordPress/gutenberg.git",
"directory": "packages/admin-manifest"
},
"bugs": {
"url": "https://github.com/WordPress/gutenberg/issues"
},
"engines": {
"node": ">=12"
},
"main": "build/index.js",
"module": "build-module/index.js",
"react-native": "src/index",
"dependencies": {
"@babel/runtime": "^7.16.0",
"@wordpress/url": "file:../url"
},
"publishConfig": {
"access": "public"
}
}
176 changes: 176 additions & 0 deletions packages/admin-manifest/src/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,176 @@
/**
* WordPress dependencies
*/
import { addQueryArgs } from '@wordpress/url';

function addManifest( manifest ) {
const link = document.createElement( 'link' );
link.rel = 'manifest';
link.href = `data:application/manifest+json,${ encodeURIComponent(
JSON.stringify( manifest )
) }`;
document.head.appendChild( link );
}

function addAppleTouchIcon( size, base64data ) {
const iconLink = document.createElement( 'link' );
iconLink.rel = 'apple-touch-icon';
iconLink.href = base64data;
iconLink.sizes = '180x180';
document.head.insertBefore( iconLink, document.head.firstElementChild );
}

function createSvgElement( html ) {
const doc = document.implementation.createHTMLDocument( '' );
doc.body.innerHTML = html;
const { firstElementChild: svgElement } = doc.body;
svgElement.setAttribute( 'viewBox', '0 0 80 80' );
return svgElement;
}

function createIcon( { svgElement, size, color, backgroundColor, circle } ) {
return new Promise( ( resolve ) => {
const canvas = document.createElement( 'canvas' );
const context = canvas.getContext( '2d' );

// Leave 1/8th padding around the logo.
const padding = size / 8;
// Which leaves 3/4ths of space for the icon.
const logoSize = padding * 6;

// Resize the SVG logo.
svgElement.setAttribute( 'width', logoSize );
svgElement.setAttribute( 'height', logoSize );

// Color in the background.
svgElement.querySelectorAll( 'path' ).forEach( ( path ) => {
path.setAttribute( 'fill', backgroundColor );
} );

// Resize the canvas.
canvas.width = size;
canvas.height = size;

// If we're not drawing a circle, set the background color.
if ( ! circle ) {
context.fillStyle = backgroundColor;
context.fillRect( 0, 0, canvas.width, canvas.height );
}

// Fill in the letter (W) and circle around it.
context.fillStyle = color;
context.beginPath();
context.arc( size / 2, size / 2, logoSize / 2 - 1, 0, 2 * Math.PI );
context.closePath();
context.fill();

// Create a URL for the SVG to load in an image element.
const svgBlob = new window.Blob( [ svgElement.outerHTML ], {
type: 'image/svg+xml',
} );
const url = URL.createObjectURL( svgBlob );
const image = document.createElement( 'img' );

image.src = url;
image.width = logoSize;
image.height = logoSize;
image.onload = () => {
// Once the image is loaded, draw it onto the canvas.
context.drawImage( image, padding, padding );
// Export it to a blob.
canvas.toBlob( ( imageBlob ) => {
// We no longer need the SVG blob url.
URL.revokeObjectURL( url );
// Unfortunately blob URLs don't seem to work, so we have to use
// base64 encoded data URLs.
const reader = new window.FileReader();
reader.readAsDataURL( imageBlob );
reader.onloadend = () => {
resolve( reader.result );
};
} );
};
} );
}

function getAdminBarColors() {
const adminBarDummy = document.createElement( 'div' );
adminBarDummy.id = 'wpadminbar';
document.body.appendChild( adminBarDummy );
const { color, backgroundColor } = window.getComputedStyle( adminBarDummy );
document.body.removeChild( adminBarDummy );
// Fall back to black and white if no admin/color stylesheet was loaded.
return {
color: color || 'white',
backgroundColor: backgroundColor || 'black',
};
}

window.addEventListener( 'load', () => {
if ( ! ( 'serviceWorker' in window.navigator ) ) {
return;
}

const { logo, siteTitle, adminUrl } = window.wpAdminManifestL10n;
const manifest = {
name: siteTitle,
display: 'standalone',
orientation: 'portrait',
start_url: adminUrl,
// Open front-end, login page, and any external URLs in a browser
// modal.
scope: adminUrl,
icons: [],
};

const { color, backgroundColor } = getAdminBarColors();
const svgElement = createSvgElement( logo );

Promise.all( [
// The maskable icon should have its background filled. This is used
// for iOS. To do: check which sizes are really needed.
...[ 180, 192, 512 ].map( ( size ) =>
createIcon( {
svgElement,
size,
color,
backgroundColor,
} ).then( ( base64data ) => {
manifest.icons.push( {
src: base64data,
sizes: size + 'x' + size,
type: 'image/png',
purpose: 'maskable',
} );

// iOS doesn't seem to look at the manifest.
if ( size === 180 ) {
addAppleTouchIcon( size, base64data );
}
} )
),
// The "normal" icon should be round. This is used for Chrome
// Desktop PWAs. To do: check which sizes are really needed.
...[ 180, 192, 512 ].map( ( size ) =>
createIcon( {
svgElement,
size,
color,
backgroundColor,
circle: true,
} ).then( ( base64data ) => {
manifest.icons.push( {
src: base64data,
sizes: size + 'x' + size,
type: 'image/png',
purpose: 'any',
} );
} )
),
] ).then( () => {
addManifest( manifest );
window.navigator.serviceWorker.register(
addQueryArgs( adminUrl, { 'service-worker': true } )
);
} );
} );

0 comments on commit 2a2938f

Please sign in to comment.