Skip to content

Commit

Permalink
Takes screenshots of your applications and inject them to the manifes…
Browse files Browse the repository at this point in the history
…t file (#20)
  • Loading branch information
Spomky authored Jan 10, 2024
1 parent a33fa68 commit bf29e1b
Show file tree
Hide file tree
Showing 6 changed files with 133 additions and 48 deletions.
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ Please have a look at the [Web app manifests](https://developer.mozilla.org/en-U

# Installation

Install the bundle with Composer: `composer require spomky-labs/phpwa`.
Install the bundle with Composer: `composer require --dev spomky-labs/phpwa`.

This project follows the [semantic versioning](http://semver.org/) strictly.

Expand Down
7 changes: 4 additions & 3 deletions composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@
"symfony/config": "^6.4|^7.0",
"symfony/dependency-injection": "^6.4|^7.0",
"symfony/filesystem": "^6.4|^7.0",
"symfony/finder": "^6.4",
"symfony/finder": "^6.4|^7.0",
"symfony/http-kernel": "^6.4|^7.0",
"symfony/mime": "^6.4|^7.0",
"symfony/routing": "^6.4|^7.0"
Expand All @@ -43,7 +43,7 @@
"phpstan/phpstan-phpunit": "^1.0",
"phpstan/phpstan-strict-rules": "^1.0",
"phpunit/phpunit": "^10.0",
"rector/rector": "^0.18",
"rector/rector": "^0.19",
"symfony/framework-bundle": "^6.4|^7.0",
"symfony/phpunit-bridge": "^6.4|^7.0",
"symplify/easy-coding-standard": "^12.0"
Expand All @@ -57,6 +57,7 @@
},
"suggest": {
"ext-gd": "Required to generate icons (or Imagick).",
"ext-imagick": "Required to generate icons (or GD)."
"ext-imagick": "Required to generate icons (or GD).",
"symfony/panther": "For generating screenshots directly from your application"
}
}
56 changes: 47 additions & 9 deletions src/Command/GenerateManifestCommand.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

namespace SpomkyLabs\PwaBundle\Command;

use Facebook\WebDriver\WebDriverDimension;
use JsonException;
use RuntimeException;
use SpomkyLabs\PwaBundle\ImageProcessor\ImageProcessor;
Expand All @@ -18,6 +19,7 @@
use Symfony\Component\Finder\Finder;
use Symfony\Component\HttpKernel\Config\FileLocator;
use Symfony\Component\Mime\MimeTypes;
use Symfony\Component\Panther\Client;
use Symfony\Component\Routing\RouterInterface;
use function count;
use function dirname;
Expand All @@ -33,16 +35,24 @@ final class GenerateManifestCommand extends Command
{
private readonly MimeTypes $mime;

private readonly null|Client $webClient;

Check failure on line 38 in src/Command/GenerateManifestCommand.php

View workflow job for this annotation

GitHub Actions / PHP 8.2 Test on ubuntu-latest

Property SpomkyLabs\PwaBundle\Command\GenerateManifestCommand::$webClient has unknown class Symfony\Component\Panther\Client as its type.

public function __construct(

Check failure on line 40 in src/Command/GenerateManifestCommand.php

View workflow job for this annotation

GitHub Actions / PHP 8.2 Test on ubuntu-latest

Method SpomkyLabs\PwaBundle\Command\GenerateManifestCommand::__construct() has parameter $config with no value type specified in iterable type array.

Check failure on line 40 in src/Command/GenerateManifestCommand.php

View workflow job for this annotation

GitHub Actions / PHP 8.2 Test on ubuntu-latest

Method SpomkyLabs\PwaBundle\Command\GenerateManifestCommand::__construct() has parameter $dest with no value type specified in iterable type array.
private readonly null|ImageProcessor $imageProcessor,
#[Autowire('@pwa.web_client')]

Check failure on line 42 in src/Command/GenerateManifestCommand.php

View workflow job for this annotation

GitHub Actions / PHP 8.2 Test on ubuntu-latest

Parameter $webClient of method SpomkyLabs\PwaBundle\Command\GenerateManifestCommand::__construct() has invalid type Symfony\Component\Panther\Client.
null|Client $webClient,
#[Autowire('%spomky_labs_pwa.config%')]
private readonly array $config,
private readonly array $config,
#[Autowire('%spomky_labs_pwa.dest%')]
private readonly array $dest,
private readonly Filesystem $filesystem,
private readonly array $dest,
private readonly Filesystem $filesystem,
private readonly FileLocator $fileLocator,
private readonly ?RouterInterface $router = null,
private readonly ?RouterInterface $router = null,
) {
if ($webClient === null && class_exists(Client::class)) {
$webClient = Client::createChromeClient();
}
$this->webClient = $webClient;
$this->mime = MimeTypes::getDefault();
parent::__construct();
}
Expand Down Expand Up @@ -242,13 +252,36 @@ private function processScreenshots(SymfonyStyle $io, array $manifest): array|in
$manifest['screenshots'] = [];
$config = [];
foreach ($this->config['screenshots'] as $screenshot) {
$src = $screenshot['src'];
if (! $this->filesystem->exists($src)) {
continue;
if (isset($screenshot['src'])) {
$src = $screenshot['src'];
if (! $this->filesystem->exists($src)) {
continue;
}
foreach ($this->findImages($src) as $image) {
$data = $screenshot;
$data['src'] = $image;
$config[] = $data;
}
}
foreach ($this->findImages($src) as $image) {
if (isset($screenshot['path'])) {
$path = $screenshot['path'];
$height = $screenshot['height'];
$width = $screenshot['width'];
unset($screenshot['path'], $screenshot['height'], $screenshot['width']);

$client = clone $this->webClient;
$client->request('GET', $path);
$tmpName = $this->filesystem->tempnam('', 'pwa-');
$client->manage()
->window()
->setSize(new WebDriverDimension($width, $height));
$client->manage()
->window()
->fullscreen();
$client->takeScreenshot($tmpName);
$data = $screenshot;
$data['src'] = $image;
$data['src'] = $tmpName;
$data['delete'] = true;
$config[] = $data;
}
}
Expand All @@ -259,6 +292,8 @@ private function processScreenshots(SymfonyStyle $io, array $manifest): array|in
$io->error(sprintf('Unable to read the icon "%s"', $screenshot['src']));
return self::FAILURE;
}
$delete = $screenshot['delete'] ?? false;
unset($screenshot['delete']);
$screenshotManifest = $this->storeScreenshot(
$data,
$screenshot['format'] ?? null,
Expand All @@ -271,6 +306,9 @@ private function processScreenshots(SymfonyStyle $io, array $manifest): array|in
$screenshotManifest['platform'] = $screenshot['platform'];
}
$manifest['screenshots'][] = $screenshotManifest;
if ($delete) {
$this->filesystem->remove($screenshot['src']);
}
}

return $manifest;
Expand Down
109 changes: 77 additions & 32 deletions src/DependencyInjection/Configuration.php
Original file line number Diff line number Diff line change
Expand Up @@ -77,38 +77,8 @@ private function setupShortcuts(ArrayNodeDefinition $node): void
private function setupScreenshots(ArrayNodeDefinition $node): void
{
$node->children()
->arrayNode('screenshots')
->treatFalseLike([])
->treatTrueLike([])
->treatNullLike([])
->info('The screenshots of the application.')
->arrayPrototype()
->children()
->scalarNode('src')
->isRequired()
->info('The path to the screenshot.')
->example('screenshot/lowres.webp')
->end()
->scalarNode('form_factor')
->info('The form factor of the screenshot. Will guess the form factor if not set.')
->example(['wide', 'narrow'])
->end()
->scalarNode('label')
->info('The label of the screenshot.')
->example('Homescreen of Awesome App')
->end()
->scalarNode('platform')
->info('The platform of the screenshot.')
->example(
['android', 'windows', 'chromeos', 'ipados', 'ios', 'kaios', 'macos', 'windows', 'xbox']
)
->end()
->scalarNode('format')
->info('The format of the screenshot. Will convert the file if set.')
->example(['jpg', 'png', 'webp'])
->end()
->end()
->end()
->append($this->getScreenshotsNode())
->end()
;
}

Expand Down Expand Up @@ -293,6 +263,10 @@ private function setupSimpleOptions(ArrayNodeDefinition $node): void
->info('The image processor to use to generate the icons of different sizes.')
->example(GDImageProcessor::class)
->end()
->scalarNode('web_client')
->defaultNull()
->info('The Panther Client for generating screenshots. If not set, the default client will be used.')
->end()
->scalarNode('icon_folder')
->defaultValue('%kernel.project_dir%/public/pwa')
->info('The folder where the icons will be generated.')
Expand Down Expand Up @@ -468,4 +442,75 @@ private function setupServiceWorker(ArrayNodeDefinition $node): void
->end()
->end();
}

private function getScreenshotsNode(): ArrayNodeDefinition
{
$treeBuilder = new TreeBuilder('screenshots');
$node = $treeBuilder->getRootNode();
assert($node instanceof ArrayNodeDefinition);
$node
->treatFalseLike([])
->treatTrueLike([])
->treatNullLike([])
->arrayPrototype()
->validate()
->ifTrue(static fn (array $v): bool => ! (isset($v['src']) xor isset($v['path'])))
->thenInvalid('Either "src", "route" or "path" must be set.')
->end()
->validate()
->ifTrue(static function (array $v): bool {
if (isset($v['src'])) {
return false;
}

if (! isset($v['height']) || ! isset($v['width'])) {
return true;
}

return false;
})
->thenInvalid('When using "path", "height" and "width" must be set.')
->end()
->children()
->scalarNode('src')
->info('The path to the screenshot.')
->example('screenshot/lowres.webp')
->end()
->scalarNode('path')
->info('The path to an application page. The screenshot will be generated.')
->example('https://example.com')
->end()
->scalarNode('height')
->defaultNull()
->info('When using "route" or "path", the height of the screenshot.')
->example('1080')
->end()
->scalarNode('width')
->defaultNull()
->info('When using "route" or "path", the height of the screenshot.')
->example('1080')
->end()
->scalarNode('form_factor')
->info('The form factor of the screenshot. Will guess the form factor if not set.')
->example(['wide', 'narrow'])
->end()
->scalarNode('label')
->info('The label of the screenshot.')
->example('Homescreen of Awesome App')
->end()
->scalarNode('platform')
->info('The platform of the screenshot.')
->example(
['android', 'windows', 'chromeos', 'ipados', 'ios', 'kaios', 'macos', 'windows', 'xbox']
)
->end()
->scalarNode('format')
->info('The format of the screenshot. Will convert the file if set.')
->example(['jpg', 'png', 'webp'])
->end()
->end()
->end();

return $node;
}
}
5 changes: 4 additions & 1 deletion src/DependencyInjection/SpomkyLabsPwaExtension.php
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,10 @@ public function load(array $configs, ContainerBuilder $container): void
if ($config['image_processor'] !== null) {
$container->setAlias(ImageProcessor::class, $config['image_processor']);
}
unset($config['image_processor']);
if ($config['web_client'] !== null) {
$container->setAlias('pwa.web_client', $config['web_client']);
}
unset($config['image_processor'], $config['web_client']);
$params = [
'icon_folder',
'icon_prefix_url',
Expand Down
2 changes: 0 additions & 2 deletions src/Resources/config/services.php
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@
declare(strict_types=1);

use SpomkyLabs\PwaBundle\Command\GenerateManifestCommand;
use SpomkyLabs\PwaBundle\Command\WorkboxInitCommand;
use SpomkyLabs\PwaBundle\ImageProcessor\GDImageProcessor;
use SpomkyLabs\PwaBundle\ImageProcessor\ImagickImageProcessor;
use Symfony\Component\DependencyInjection\Loader\Configurator\ContainerConfigurator;
Expand All @@ -17,7 +16,6 @@
;

$container->set(GenerateManifestCommand::class);
$container->set(WorkboxInitCommand::class);

if (extension_loaded('imagick')) {
$container
Expand Down

0 comments on commit bf29e1b

Please sign in to comment.