diff --git a/src/DataCollector/PwaCollector.php b/src/DataCollector/PwaCollector.php new file mode 100644 index 0000000..6711c23 --- /dev/null +++ b/src/DataCollector/PwaCollector.php @@ -0,0 +1,121 @@ + $cachingServices + */ + public function __construct( + private readonly SerializerInterface $serializer, + #[TaggedIterator('spomky_labs_pwa.cache_strategy')] + private readonly iterable $cachingServices, + private readonly Manifest $manifest, + private readonly ServiceWorker $serviceWorker, + #[Autowire(param: 'spomky_labs_pwa.manifest.enabled')] + private readonly bool $manifestEnabled, + ) { + } + + public function collect(Request $request, Response $response, Throwable $exception = null): void + { + $jsonOptions = [ + AbstractObjectNormalizer::SKIP_UNINITIALIZED_VALUES => true, + AbstractObjectNormalizer::SKIP_NULL_VALUES => true, + JsonEncode::OPTIONS => JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE | JSON_THROW_ON_ERROR | JSON_PRETTY_PRINT, + ]; + $this->data['cachingStrategies'] = []; + foreach ($this->cachingServices as $cachingService) { + foreach ($cachingService->getCacheStrategies() as $cacheStrategy) { + $this->data['cachingStrategies'][] = $cacheStrategy; + } + } + $this->data['serviceWorker'] = $this->serviceWorker; + $this->data['manifest'] = [ + 'enabled' => $this->manifestEnabled, + 'data' => $this->manifest, + 'installable' => $this->isInstallable(), + 'output' => $this->serializer->serialize($this->manifest, 'json', $jsonOptions), + ]; + + dump($this->data); + } + + public function getData(): array + { + return $this->data; + } + + /** + * @return array + */ + public function getCachingStrategies(): array + { + return $this->data['cachingStrategies'] ?? []; + } + + public function getManifest(): Manifest + { + return $this->data['manifest']['data']; + } + + public function getWorkbox(): Workbox + { + return $this->data['serviceWorker']->workbox; + } + + public function getName(): string + { + return 'pwa'; + } + + /** + * @return array{status: bool, reasons: string[]} + */ + private function isInstallable(): array + { + $reasons = [ + 'The manifest must be enabled' => ! $this->manifestEnabled, + 'The manifest must have a short name or a name' => $this->manifest->shortName === null && $this->manifest->name === null, + 'The manifest must have a start URL' => $this->manifest->startUrl === null, + 'The manifest must have a display value set to "standalone", "fullscreen" or "minimal-ui' => ! in_array( + $this->manifest->display, + ['standalone', 'fullscreen', 'minimal-ui'], + true + ), + 'The manifest must have at least one icon' => $this->manifest->icons === null || count( + $this->manifest->icons + ) === 0, + 'The manifest must have the "prefer_related_applications" property set to a value other than "true"' => $this->manifest->preferRelatedApplications === true, + ]; + + return [ + 'status' => count(array_filter($reasons)) === 0, + 'reasons' => $reasons, + ]; + } +} diff --git a/src/Resources/config/services.php b/src/Resources/config/services.php index a368deb..f446019 100644 --- a/src/Resources/config/services.php +++ b/src/Resources/config/services.php @@ -7,6 +7,7 @@ use SpomkyLabs\PwaBundle\Command\CreateIconsCommand; use SpomkyLabs\PwaBundle\Command\CreateScreenshotCommand; use SpomkyLabs\PwaBundle\Command\ListCacheStrategiesCommand; +use SpomkyLabs\PwaBundle\DataCollector\PwaCollector; use SpomkyLabs\PwaBundle\Dto\Manifest; use SpomkyLabs\PwaBundle\Dto\ServiceWorker; use SpomkyLabs\PwaBundle\ImageProcessor\GDImageProcessor; @@ -28,8 +29,8 @@ use function Symfony\Component\DependencyInjection\Loader\Configurator\param; use function Symfony\Component\DependencyInjection\Loader\Configurator\service; -return static function (ContainerConfigurator $container): void { - $container = $container->services() +return static function (ContainerConfigurator $configurator): void { + $container = $configurator->services() ->defaults() ->private() ->autoconfigure() @@ -124,4 +125,13 @@ ->tag('spomky_labs_pwa.match_callback_handler') ; $container->load('SpomkyLabs\\PwaBundle\\MatchCallbackHandler\\', '../../MatchCallbackHandler/*'); + + if ($configurator->env() !== 'prod') { + $container->set(PwaCollector::class) + ->tag('data_collector', [ + 'template' => '@SpomkyLabsPwa/Collector/template.html.twig', + 'id' => 'pwa', + ]) + ; + } }; diff --git a/templates/Collector/manifest-tab.html.twig b/templates/Collector/manifest-tab.html.twig new file mode 100644 index 0000000..876e021 --- /dev/null +++ b/templates/Collector/manifest-tab.html.twig @@ -0,0 +1,170 @@ +

General information

+

+ Status: + {% if collector.data.manifest.enabled %} + enabled + {% else %} + disabled + {% endif %} +
+ Can be installed: + {% if collector.data.manifest.installable.status %} + yes +{% else %} + no +{% endif %} +

+ +

Details

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
KeyValue
ID{{ collector.getManifest().id }}
Name{{ collector.getManifest().name|trans }}
Short name{{ collector.getManifest().shortName|trans }}
Description name{{ collector.getManifest().description|trans }}
Theme color + {% if collector.getManifest().themeColor %} + {{ collector.getManifest().themeColor }} + {{ collector.getManifest().themeColor }} + {% else %} + n/a + {% endif %} +
Background color + {% if collector.getManifest().backgroundColor %} + {{ collector.getManifest().backgroundColor }} + {{ collector.getManifest().backgroundColor }} + {% else %} + n/a + {% endif %} +
Display{{ collector.getManifest().display }}
Orientation{{ collector.getManifest().orientation }}
Scope{{ collector.getManifest().scope }}
Start URL{{ collector.getManifest().startUrl }}
Categories + {% if collector.getManifest().categories|length == 0 %} + none + {% else %} +
    + {% for category in collector.getManifest().categories %} +
  • {{ category|trans }}
  • + {% endfor %} +
+ {% endif %} +
+

Application Icons

+
+ + + + + + + + + + + {% for icon in collector.getManifest().icons %} + + + + + + + {% endfor %} + +
SourceSizesTypePurpose
{{ icon.src.src }}{{ icon.getSizeList() }}{{ icon.type|default('n/a') }}{{ icon.purpose|default('n/a') }}
+
+

Application screenshots

+
+ + + + + + + + + + + + + {% for screenshot in collector.getManifest().screenshots %} + + + + + + + + + {% endfor %} + +
SourceSizeTypeForm factorLabelPlatform
+ {{ screenshot.src.src }}
+ {{ screenshot.reference|default('No reference') }} +
+ {% if screenshot.height and screenshot.width %} + {{ screenshot.width }}x{{ screenshot.height }} + {% else %} + n/a + {% endif %} + {{ screenshot.type|default('n/a') }}{{ screenshot.formFactor|default('n/a') }}{{ screenshot.label|default('n/a') }}{{ screenshot.platform|default('n/a') }}
+
+

Output

+
+
{{ collector.data.manifest.output|nl2br }}
+
\ No newline at end of file diff --git a/templates/Collector/pwa.svg b/templates/Collector/pwa.svg new file mode 100644 index 0000000..6d1c9e8 --- /dev/null +++ b/templates/Collector/pwa.svg @@ -0,0 +1,12 @@ + + + + + + + + + + + + \ No newline at end of file diff --git a/templates/Collector/serviceworker-tab.html.twig b/templates/Collector/serviceworker-tab.html.twig new file mode 100644 index 0000000..ab7fc87 --- /dev/null +++ b/templates/Collector/serviceworker-tab.html.twig @@ -0,0 +1,159 @@ +

Details

+

Status: + {% if collector.data.serviceWorker.enabled %} + enabled + {% else %} + disabled + {% endif %} + + + + + + + + + + + + + + + + + + + + + + + + +
KeyValue
Destination{{ collector.data.serviceWorker.dest }}
Scope{{ collector.data.serviceWorker.scope }}
Use Cache{{ collector.data.serviceWorker.useCache ? 'Yes' : 'No' }}
Skip Waiting{{ collector.data.serviceWorker.skipWaiting ? 'Yes' : 'No' }}
+

Workbox

+

Status: {% if collector.workbox.enabled %} + enabled! + {% else %} + disabled + {% endif %} + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
KeyValue
Use CDN? + {% if collector.workbox.useCDN %} + Yes, version: {{ collector.workbox.version }} + {% else %} + No + {% endif %} +
Clear cache?{% if collector.workbox.clearCache %}Yes{% else %}No{% endif %}
Cache manifest?{% if collector.workbox.cacheManifest %}Yes{% else %}No{% endif %}
Asset cache + {% if collector.workbox.assetCache.enabled %} + Enabled
+ Cache name: {{ collector.workbox.assetCache.cacheName }}
+ Regex: {{ collector.workbox.assetCache.regex }} + {% else %} + Disabled
+ {% endif %} +
Font cache + {% if collector.workbox.fontCache.enabled %} + Enabled + {% else %} + Disabled
+ {% endif %} +
Google Font cache + {% if collector.workbox.googleFontCache.enabled %} + Enabled
+ Cache name: {{ collector.workbox.googleFontCache.cacheName ?? 'default' }} + {% else %} + Disabled
+ {% endif %} +
Image cache + {% if collector.workbox.imageCache.enabled %} + Enabled
+ Cache name: {{ collector.workbox.imageCache.cacheName }}
+ Regex: {{ collector.workbox.imageCache.regex }} + {% else %} + Disabled
+ {% endif %} +
+

Caching Strategies

+ + + + + + + + + + + + {% for cachingStrategy in collector.cachingStrategies %} + + + + + + + + {% endfor %} + +
Name + Enabled?
+ Workbox? +
+ Strategy
+ Match Callback
+ Method +
PluginsURL Preload
{{ cachingStrategy.getName() }} + {% if cachingStrategy.isEnabled() %}Yes{% else %}No{% endif %}
+ {% if cachingStrategy.needsWorkbox() %}Yes{% else %}No{% endif %} +
+ {{ cachingStrategy.strategy }}
+ {{ cachingStrategy.matchCallback }}
+ {{ cachingStrategy.getMethod() ?? "GET" }} +
+ {% for plugin in cachingStrategy.plugins %} + {{ dump(plugin) }} + {% endfor %} + + {% set preloadedUrls = cachingStrategy.preloadUrls|length %} + {% if preloadedUrls == 0 %} + None + {% else %} + {{ preloadedUrls }} URL{{ preloadedUrls > 1 ? 's' : '' }} + {% endif %} +
diff --git a/templates/Collector/template.html.twig b/templates/Collector/template.html.twig new file mode 100644 index 0000000..354c3db --- /dev/null +++ b/templates/Collector/template.html.twig @@ -0,0 +1,81 @@ +{% extends '@WebProfiler/Profiler/layout.html.twig' %} + +{% block toolbar %} + {% set icon %} + {{ source('@SpomkyLabsPwa/Collector/pwa.svg') }} + PWA + {% endset %} + + {% set text %} +
+
+ {% endset %} + + {{ include('@WebProfiler/Profiler/toolbar_item.html.twig', { 'link': true }) }} +{% endblock %} + +{% block head %} + {# Optional. Here you can link to or define your own CSS and JS contents. #} + {# Use {{ parent() }} to extend the default styles instead of overriding them. #} + {{ parent() }} +{% endblock %} + +{% block menu %} + + {{ source('@SpomkyLabsPwa/Collector/pwa.svg') }} + PWA + +{% endblock %} + +{% block panel %} + +

Progressive Web App

+
+
+

Manifest

+
+ {% include '@SpomkyLabsPwa/Collector/manifest-tab.html.twig' %} +
+
+
+

Service Worker

+
+ {% include '@SpomkyLabsPwa/Collector/serviceworker-tab.html.twig' %} +
+
+
+{% endblock %} \ No newline at end of file