diff --git a/assets/package.json b/assets/package.json index 58a9e7e..8c4ee86 100644 --- a/assets/package.json +++ b/assets/package.json @@ -33,6 +33,13 @@ "fetch": "eager", "enabled": true }, + "device-motion": { + "main": "src/device-motion_controller.js", + "name": "pwa/device-motion", + "webpackMode": "eager", + "fetch": "eager", + "enabled": true + }, "device-orientation": { "main": "src/device-orientation_controller.js", "name": "pwa/device-orientation", @@ -96,6 +103,13 @@ "fetch": "eager", "enabled": true }, + "touch": { + "main": "src/touch_controller.js", + "name": "pwa/touch", + "webpackMode": "eager", + "fetch": "eager", + "enabled": true + }, "vibration": { "main": "src/vibration_controller.js", "name": "pwa/vibration", diff --git a/assets/src/abstract_controller.js b/assets/src/abstract_controller.js index bab060b..350b9e5 100644 --- a/assets/src/abstract_controller.js +++ b/assets/src/abstract_controller.js @@ -1,10 +1,24 @@ 'use strict'; +import { getComponent } from '@symfony/ux-live-component'; import { Controller } from '@hotwired/stimulus'; -/* stimulusFetch: 'lazy' */ export default class extends Controller { + component = null; + async initialize() { + try { + this.component = await getComponent(this.element); + } catch (e) { + } + } + dispatchEvent = (name, payload) => { - this.dispatch(name, { detail: payload }); + if (payload === undefined) { + payload = {}; + } + this.dispatch('pwa:'+name, { detail: payload, bubbles: true }); + if (this.component) { + this.component.emit('pwa:'+name, payload); + } } } diff --git a/assets/src/backgroundsync-form_controller.js b/assets/src/backgroundsync-form_controller.js index 28a9238..0eae4c0 100644 --- a/assets/src/backgroundsync-form_controller.js +++ b/assets/src/backgroundsync-form_controller.js @@ -1,9 +1,9 @@ 'use strict'; -import { Controller } from '@hotwired/stimulus'; +import AbstractController from './abstract_controller.js'; /* stimulusFetch: 'lazy' */ -export default class extends Controller { +export default class extends AbstractController { static values = { params: { type: Object, diff --git a/assets/src/battery_controller.js b/assets/src/battery_controller.js index 13e6c8d..275e4df 100644 --- a/assets/src/battery_controller.js +++ b/assets/src/battery_controller.js @@ -18,7 +18,7 @@ export default class extends AbstractController { } update = async ({counter}) => { await navigator.setAppBadge(counter); - this.dispatchEvent('badge:updated', { counter }); + this.dispatchEvent('battery:updated', { counter }); } updateChargeInfo = async (battery) => { diff --git a/assets/src/connection-status_controller.js b/assets/src/connection-status_controller.js index ba28ba3..3dbf33b 100644 --- a/assets/src/connection-status_controller.js +++ b/assets/src/connection-status_controller.js @@ -11,7 +11,7 @@ export default class extends AbstractController { }; connect = () => { - this.dispatchEvent('connect', {}); + this.dispatchEvent('connection-status:connect', {}); if (navigator.onLine) { this.statusChanged({ status: 'ONLINE', @@ -45,6 +45,6 @@ export default class extends AbstractController { this.attributeTargets.forEach((element) => { element.setAttribute('data-connection-status', data.status); }); - this.dispatchEvent('status-changed', { detail: data }); + this.dispatchEvent('connection-status:changed', { detail: data }); } } diff --git a/assets/src/device-motion_controller.js b/assets/src/device-motion_controller.js new file mode 100644 index 0000000..cc8253b --- /dev/null +++ b/assets/src/device-motion_controller.js @@ -0,0 +1,36 @@ +'use strict'; + +import AbstractController from './abstract_controller.js'; + +/* stimulusFetch: 'lazy' */ +export default class extends AbstractController { + static values = { + throttle: { type: Number, default: 1000 }, + }; + + connect() { + const throttle = (func, limit) => { + let inThrottle; + return function() { + const context = this; + if (!inThrottle) { + func.apply(context, arguments); + inThrottle = true; + setTimeout(() => (inThrottle = false), limit); + } + }; + }; + + const dispatchMotionEvent = (event) => { + this.dispatchEvent('device:motion', { + acceleration: event.acceleration, + accelerationIncludingGravity: event.accelerationIncludingGravity, + rotationRate: event.rotationRate, + interval: event.interval, + }); + }; + + const throttledDispatch = throttle(dispatchMotionEvent.bind(this), this.throttleValue); + window.addEventListener('devicemotion', throttledDispatch, true); + } +} diff --git a/assets/src/device-orientation_controller.js b/assets/src/device-orientation_controller.js index 3a90092..80c27c5 100644 --- a/assets/src/device-orientation_controller.js +++ b/assets/src/device-orientation_controller.js @@ -4,18 +4,33 @@ import AbstractController from './abstract_controller.js'; /* stimulusFetch: 'lazy' */ export default class extends AbstractController { + static values = { + throttle: { type: Number, default: 1000 }, + }; + connect() { - window.addEventListener( - 'deviceorientation', - (event) => { - this.dispatchEvent({ - absolute: event.absolute, - alpha: event.alpha, - beta: event.beta, - gamma: event.gamma - }) - }, - true - ); + const throttle = (func, limit) => { + let inThrottle; + return function() { + const context = this; + if (!inThrottle) { + func.apply(context, arguments); + inThrottle = true; + setTimeout(() => (inThrottle = false), limit); + } + }; + }; + + const dispatchOrientationEvent = (event) => { + this.dispatchEvent('device:orientation', { + absolute: event.absolute, + alpha: event.alpha, + beta: event.beta, + gamma: event.gamma, + }); + }; + + const throttledDispatch = throttle(dispatchOrientationEvent.bind(this), this.throttleValue); + window.addEventListener('deviceorientation', throttledDispatch, true); } } diff --git a/assets/src/geolocation_controller.js b/assets/src/geolocation_controller.js index 540eb50..37d111f 100644 --- a/assets/src/geolocation_controller.js +++ b/assets/src/geolocation_controller.js @@ -13,7 +13,7 @@ export default class extends AbstractController { } navigator.geolocation.getCurrentPosition( - (position) => {this.dispatchEvent('geolocation:position', {latitude: position.coords.latitude, longitude: position.coords.longitude});}, + (position) => {this.dispatchEvent('geolocation:position', {position});}, (error) => {this.dispatchEvent('geolocation:error', {error: error});}, params ); @@ -29,8 +29,8 @@ export default class extends AbstractController { } this.watchId = navigator.geolocation.watchPosition( - (position) => {this.dispatchEvent('geolocation:position', {latitude: position.coords.latitude, longitude: position.coords.longitude});}, - (error) => {this.dispatchEvent('geolocation:error', {error: error});}, + (position) => {this.dispatchEvent('geolocation:position', {position});}, + (error) => {this.dispatchEvent('geolocation:error', {error});}, params ); } diff --git a/assets/src/prefetch-on-demand_controller.js b/assets/src/prefetch-on-demand_controller.js index e8e7d55..d532da8 100644 --- a/assets/src/prefetch-on-demand_controller.js +++ b/assets/src/prefetch-on-demand_controller.js @@ -17,7 +17,7 @@ export default class extends AbstractController { } }); this.dispatchEvent( - result === true ?'prefetched': 'error', + result === true ?'prefetch:prefetched': 'prefetch:error', {params} ); } diff --git a/assets/src/presentation_controller.js b/assets/src/presentation_controller.js index cdccb54..35480b2 100644 --- a/assets/src/presentation_controller.js +++ b/assets/src/presentation_controller.js @@ -41,8 +41,7 @@ export default class extends AbstractController { if (!this.connection) { return; } - const {message} = params; - this.connection.send(message); + this.connection.send(JSON.stringify(params)); } terminate = () => { diff --git a/assets/src/receiver_controller.js b/assets/src/receiver_controller.js index 934d5b6..f51de75 100644 --- a/assets/src/receiver_controller.js +++ b/assets/src/receiver_controller.js @@ -6,22 +6,28 @@ import AbstractController from './abstract_controller.js'; export default class extends AbstractController { async connect() { if (!navigator.presentation.receiver) { + return; } - const connections = await navigator.presentation.receiver.connections; - connections.connections.map(connection => addConnection(connection)); - connections.addEventListener( - 'connectionavailable', + const list = await navigator.presentation.receiver.connectionList; + list.connections.map((connection) => this.addConnection(connection)); + } + + addConnection(connection) { + connection.addEventListener( + 'message', (event) => { - const connection = event.connection; - connection.addEventListener( - 'message', - (event) => this.dispatchEvent('message', {message: event.data}) - ); - connection.addEventListener( - 'close', - () => this.dispatchEvent('close') - ); + const data = JSON.parse(event.data); + this.dispatchEvent('receiver:message', {data}); } ); + + connection.addEventListener( + 'close', + (event) => this.dispatchEvent('receiver:close', { + connectionId: connection.connectionId, + reason: event.reason, + message: event.message + }) + ); } } diff --git a/assets/src/sync-broadcast_controller.js b/assets/src/sync-broadcast_controller.js index 19efde0..03f2099 100644 --- a/assets/src/sync-broadcast_controller.js +++ b/assets/src/sync-broadcast_controller.js @@ -30,6 +30,6 @@ export default class extends AbstractController { this.remainingTargets.forEach((element) => { element.innerHTML = data.remaining; }); - this.dispatchEvent('status-changed', { detail: data }); + this.dispatchEvent('sync-broadcast:status-changed', { detail: data }); } } diff --git a/assets/src/touch_controller.js b/assets/src/touch_controller.js new file mode 100644 index 0000000..f0b47a3 --- /dev/null +++ b/assets/src/touch_controller.js @@ -0,0 +1,76 @@ +'use strict'; + +import AbstractController from './abstract_controller.js'; + +export default class extends AbstractController { + ongoingTouches = []; + + connect() { + this.element.addEventListener("touchstart", this.onTouchStart); + this.element.addEventListener("touchmove", this.onTouchMove); + this.element.addEventListener("touchcancel", this.onTouchCancel); + this.element.addEventListener("touchend", this.onTouchEnd); + } + + copyTouch = ({ identifier, clientX, clientY, pageX, pageY, radiusX, radiusY, screenX, screenY, force, rotationAngle})=> { + return { + identifier, clientX, clientY, pageX, pageY, radiusX, radiusY, screenX, screenY, force, rotationAngle, + top: this.element.offsetTop, + left: this.element.offsetLeft, + }; + } + + onTouchEnd = (event) => { + event.preventDefault(); + const {changedTouches} = event; + for (let i = 0; i < changedTouches.length; i++) { + const idx = this.ongoingTouchIndexById(changedTouches[i].identifier); + this.ongoingTouches.splice(idx, 1); + this.dispatchEvent('touch:ended', { touch: changedTouches[i], bubbles: true }); + } + this.dispatchEvent('touch:updated', { touches: this.ongoingTouches, bubbles: true }); + } + + onTouchCancel = (event) => { + event.preventDefault(); + const {changedTouches} = event; + for (let i = 0; i < changedTouches.length; i++) { + const idx = this.ongoingTouchIndexById(changedTouches[i].identifier); + this.ongoingTouches.splice(idx, 1); + this.dispatchEvent('touch:cancelled', { touch: changedTouches[i], bubbles: true }); + } + this.dispatchEvent('touch:updated', { touches: this.ongoingTouches, bubbles: true }); + } + + onTouchMove = (event) => { + event.preventDefault(); + const {changedTouches} = event; + for (let i = 0; i < changedTouches.length; i++) { + const idx = this.ongoingTouchIndexById(changedTouches[i].identifier); + this.ongoingTouches.splice(idx, 1, this.copyTouch(changedTouches[i])) + this.dispatchEvent('touch:moved', { touch: changedTouches[i], bubbles: true }); + } + this.dispatchEvent('touch:updated', { touches: this.ongoingTouches, bubbles: true }); + } + + onTouchStart = (event) => { + event.preventDefault(); + const {changedTouches} = event; + for (let i = 0; i < changedTouches.length; i++) { + this.ongoingTouches.push(this.copyTouch(changedTouches[i])); + this.dispatchEvent('touch:started', { touch: changedTouches[i], bubbles: true }); + } + this.dispatchEvent('touch:updated', { touches: this.ongoingTouches, bubbles: true }); + } + + ongoingTouchIndexById = (idToFind) => { + for (let i = 0; i < this.ongoingTouches.length; i++) { + const id = this.ongoingTouches[i].identifier; + + if (id === idToFind) { + return i; + } + } + return -1; + } +} diff --git a/composer.json b/composer.json index 4f01d6c..dbdb842 100644 --- a/composer.json +++ b/composer.json @@ -64,9 +64,10 @@ "phpstan/phpstan-strict-rules": "^1.0|^2.0", "phpstan/phpstan-symfony": "^1.4|^2.0", "phpunit/phpunit": "^10.1|^11.0", - "rector/rector": "^1.0|^2.0", - "staabm/phpstan-todo-by": "^0.1.27|^0.2", - "struggle-for-php/sfp-phpstan-psr-log": "^0.21.0|^0.22|^0.23", + "rector/rector": "^1.0|^2.0-rc2", + "shipmonk/dead-code-detector": "^0.5.1|^0.6", + "staabm/phpstan-todo-by": "^0.1|^0.2", + "struggle-for-php/sfp-phpstan-psr-log": "^0.20|^0.21|^0.22|^0.23", "symfony/filesystem": "^6.4|^7.0", "symfony/framework-bundle": "^6.4|^7.0", "symfony/mime": "^6.4|^7.0", diff --git a/phpstan-baseline.neon b/phpstan-baseline.neon index 9bb10b8..9149fe2 100644 --- a/phpstan-baseline.neon +++ b/phpstan-baseline.neon @@ -24,6 +24,30 @@ parameters: count: 1 path: src/CachingStrategy/AssetCache.php + - + message: '#^Unused SpomkyLabs\\PwaBundle\\CachingStrategy\\AssetCache\:\:__construct$#' + identifier: shipmonk.deadMethod + count: 1 + path: src/CachingStrategy/AssetCache.php + + - + message: '#^Unused SpomkyLabs\\PwaBundle\\CachingStrategy\\AssetCache\:\:setLogger$#' + identifier: shipmonk.deadMethod + count: 1 + path: src/CachingStrategy/AssetCache.php + + - + message: '#^Unused SpomkyLabs\\PwaBundle\\CachingStrategy\\BackgroundSync\:\:__construct$#' + identifier: shipmonk.deadMethod + count: 1 + path: src/CachingStrategy/BackgroundSync.php + + - + message: '#^Unused SpomkyLabs\\PwaBundle\\CachingStrategy\\BackgroundSync\:\:setLogger$#' + identifier: shipmonk.deadMethod + count: 1 + path: src/CachingStrategy/BackgroundSync.php + - message: '#^Only iterables can be unpacked, mixed given in argument \#1\.$#' identifier: argument.unpackNonIterable @@ -42,6 +66,72 @@ parameters: count: 2 path: src/CachingStrategy/FontCache.php + - + message: '#^Unused SpomkyLabs\\PwaBundle\\CachingStrategy\\FontCache\:\:__construct$#' + identifier: shipmonk.deadMethod + count: 1 + path: src/CachingStrategy/FontCache.php + + - + message: '#^Unused SpomkyLabs\\PwaBundle\\CachingStrategy\\FontCache\:\:setLogger$#' + identifier: shipmonk.deadMethod + count: 1 + path: src/CachingStrategy/FontCache.php + + - + message: '#^Unused SpomkyLabs\\PwaBundle\\CachingStrategy\\GoogleFontCache\:\:__construct$#' + identifier: shipmonk.deadMethod + count: 1 + path: src/CachingStrategy/GoogleFontCache.php + + - + message: '#^Unused SpomkyLabs\\PwaBundle\\CachingStrategy\\GoogleFontCache\:\:setLogger$#' + identifier: shipmonk.deadMethod + count: 1 + path: src/CachingStrategy/GoogleFontCache.php + + - + message: '#^Unused SpomkyLabs\\PwaBundle\\CachingStrategy\\ImageCache\:\:__construct$#' + identifier: shipmonk.deadMethod + count: 1 + path: src/CachingStrategy/ImageCache.php + + - + message: '#^Unused SpomkyLabs\\PwaBundle\\CachingStrategy\\ImageCache\:\:setLogger$#' + identifier: shipmonk.deadMethod + count: 1 + path: src/CachingStrategy/ImageCache.php + + - + message: '#^Unused SpomkyLabs\\PwaBundle\\CachingStrategy\\ManifestCache\:\:__construct$#' + identifier: shipmonk.deadMethod + count: 1 + path: src/CachingStrategy/ManifestCache.php + + - + message: '#^Unused SpomkyLabs\\PwaBundle\\CachingStrategy\\ManifestCache\:\:setLogger$#' + identifier: shipmonk.deadMethod + count: 1 + path: src/CachingStrategy/ManifestCache.php + + - + message: '#^Unused SpomkyLabs\\PwaBundle\\CachingStrategy\\PreloadUrlsGeneratorManager\:\:__construct$#' + identifier: shipmonk.deadMethod + count: 1 + path: src/CachingStrategy/PreloadUrlsGeneratorManager.php + + - + message: '#^Unused SpomkyLabs\\PwaBundle\\CachingStrategy\\PreloadUrlsGeneratorManager\:\:setLogger$#' + identifier: shipmonk.deadMethod + count: 1 + path: src/CachingStrategy/PreloadUrlsGeneratorManager.php + + - + message: '#^Unused SpomkyLabs\\PwaBundle\\CachingStrategy\\PreloadUrlsTagGenerator\:\:__construct$#' + identifier: shipmonk.deadMethod + count: 1 + path: src/CachingStrategy/PreloadUrlsTagGenerator.php + - message: '#^Only iterables can be unpacked, mixed given in argument \#1\.$#' identifier: argument.unpackNonIterable @@ -66,12 +156,30 @@ parameters: count: 1 path: src/CachingStrategy/ResourceCaches.php + - + message: '#^Unused SpomkyLabs\\PwaBundle\\CachingStrategy\\ResourceCaches\:\:__construct$#' + identifier: shipmonk.deadMethod + count: 1 + path: src/CachingStrategy/ResourceCaches.php + + - + message: '#^Unused SpomkyLabs\\PwaBundle\\CachingStrategy\\ResourceCaches\:\:setLogger$#' + identifier: shipmonk.deadMethod + count: 1 + path: src/CachingStrategy/ResourceCaches.php + - message: '#^Part \$this\-\>options\[''networkTimeoutSeconds''\] \(mixed\) of encapsed string cannot be cast to string\.$#' identifier: encapsedStringPart.nonString count: 1 path: src/CachingStrategy/WorkboxCacheStrategy.php + - + message: '#^Unused SpomkyLabs\\PwaBundle\\CachingStrategy\\WorkboxCacheStrategy\:\:getMethod$#' + identifier: shipmonk.deadMethod + count: 1 + path: src/CachingStrategy/WorkboxCacheStrategy.php + - message: '#^Argument of an invalid type mixed supplied for foreach, only iterables are supported\.$#' identifier: foreach.nonIterable @@ -234,6 +342,66 @@ parameters: count: 1 path: src/DataCollector/PwaCollector.php + - + message: '#^Unused SpomkyLabs\\PwaBundle\\DataCollector\\PwaCollector\:\:__construct$#' + identifier: shipmonk.deadMethod + count: 1 + path: src/DataCollector/PwaCollector.php + + - + message: '#^Unused SpomkyLabs\\PwaBundle\\DataCollector\\PwaCollector\:\:getCachingStrategies$#' + identifier: shipmonk.deadMethod + count: 1 + path: src/DataCollector/PwaCollector.php + + - + message: '#^Unused SpomkyLabs\\PwaBundle\\DataCollector\\PwaCollector\:\:getData$#' + identifier: shipmonk.deadMethod + count: 1 + path: src/DataCollector/PwaCollector.php + + - + message: '#^Unused SpomkyLabs\\PwaBundle\\DataCollector\\PwaCollector\:\:getFavicons$#' + identifier: shipmonk.deadMethod + count: 1 + path: src/DataCollector/PwaCollector.php + + - + message: '#^Unused SpomkyLabs\\PwaBundle\\DataCollector\\PwaCollector\:\:getFaviconsFiles$#' + identifier: shipmonk.deadMethod + count: 1 + path: src/DataCollector/PwaCollector.php + + - + message: '#^Unused SpomkyLabs\\PwaBundle\\DataCollector\\PwaCollector\:\:getManifest$#' + identifier: shipmonk.deadMethod + count: 1 + path: src/DataCollector/PwaCollector.php + + - + message: '#^Unused SpomkyLabs\\PwaBundle\\DataCollector\\PwaCollector\:\:getManifestFiles$#' + identifier: shipmonk.deadMethod + count: 1 + path: src/DataCollector/PwaCollector.php + + - + message: '#^Unused SpomkyLabs\\PwaBundle\\DataCollector\\PwaCollector\:\:getServiceWorker$#' + identifier: shipmonk.deadMethod + count: 1 + path: src/DataCollector/PwaCollector.php + + - + message: '#^Unused SpomkyLabs\\PwaBundle\\DataCollector\\PwaCollector\:\:getServiceWorkerFiles$#' + identifier: shipmonk.deadMethod + count: 1 + path: src/DataCollector/PwaCollector.php + + - + message: '#^Unused SpomkyLabs\\PwaBundle\\DataCollector\\PwaCollector\:\:getWorkbox$#' + identifier: shipmonk.deadMethod + count: 1 + path: src/DataCollector/PwaCollector.php + - message: '#^Class SpomkyLabs\\PwaBundle\\Dto\\BackgroundSync has an uninitialized property \$forceSyncFallback\. Give it default value or assign it in the constructor\.$#' identifier: property.uninitialized @@ -348,6 +516,36 @@ parameters: count: 1 path: src/Dto/Manifest.php + - + message: '#^Unused SpomkyLabs\\PwaBundle\\Dto\\Manifest\:\:getCategories$#' + identifier: shipmonk.deadMethod + count: 1 + path: src/Dto/Manifest.php + + - + message: '#^Unused SpomkyLabs\\PwaBundle\\Dto\\Manifest\:\:getDescription$#' + identifier: shipmonk.deadMethod + count: 1 + path: src/Dto/Manifest.php + + - + message: '#^Unused SpomkyLabs\\PwaBundle\\Dto\\Manifest\:\:getName$#' + identifier: shipmonk.deadMethod + count: 1 + path: src/Dto/Manifest.php + + - + message: '#^Unused SpomkyLabs\\PwaBundle\\Dto\\Manifest\:\:getShortName$#' + identifier: shipmonk.deadMethod + count: 1 + path: src/Dto/Manifest.php + + - + message: '#^Unused SpomkyLabs\\PwaBundle\\Dto\\Manifest\:\:getStartUrl$#' + identifier: shipmonk.deadMethod + count: 1 + path: src/Dto/Manifest.php + - message: '#^Class SpomkyLabs\\PwaBundle\\Dto\\PageCache extends @final class SpomkyLabs\\PwaBundle\\Dto\\ResourceCache\.$#' identifier: class.extendsFinalByPhpDoc @@ -402,6 +600,12 @@ parameters: count: 1 path: src/Dto/Screenshot.php + - + message: '#^Unused SpomkyLabs\\PwaBundle\\Dto\\Screenshot\:\:getLabel$#' + identifier: shipmonk.deadMethod + count: 1 + path: src/Dto/Screenshot.php + - message: '#^Class SpomkyLabs\\PwaBundle\\Dto\\ServiceWorker has an uninitialized property \$dest\. Give it default value or assign it in the constructor\.$#' identifier: property.uninitialized @@ -438,6 +642,18 @@ parameters: count: 1 path: src/Dto/ShareTargetParameters.php + - + message: '#^Unused SpomkyLabs\\PwaBundle\\Dto\\ShareTargetParameters\:\:getText$#' + identifier: shipmonk.deadMethod + count: 1 + path: src/Dto/ShareTargetParameters.php + + - + message: '#^Unused SpomkyLabs\\PwaBundle\\Dto\\ShareTargetParameters\:\:getTitle$#' + identifier: shipmonk.deadMethod + count: 1 + path: src/Dto/ShareTargetParameters.php + - message: '#^Class SpomkyLabs\\PwaBundle\\Dto\\Shortcut has an uninitialized property \$name\. Give it default value or assign it in the constructor\.$#' identifier: property.uninitialized @@ -468,6 +684,24 @@ parameters: count: 1 path: src/Dto/Shortcut.php + - + message: '#^Unused SpomkyLabs\\PwaBundle\\Dto\\Shortcut\:\:getDescription$#' + identifier: shipmonk.deadMethod + count: 1 + path: src/Dto/Shortcut.php + + - + message: '#^Unused SpomkyLabs\\PwaBundle\\Dto\\Shortcut\:\:getName$#' + identifier: shipmonk.deadMethod + count: 1 + path: src/Dto/Shortcut.php + + - + message: '#^Unused SpomkyLabs\\PwaBundle\\Dto\\Shortcut\:\:getShortName$#' + identifier: shipmonk.deadMethod + count: 1 + path: src/Dto/Shortcut.php + - message: '#^Class SpomkyLabs\\PwaBundle\\Dto\\Url has an uninitialized property \$path\. Give it default value or assign it in the constructor\.$#' identifier: property.uninitialized @@ -504,6 +738,24 @@ parameters: count: 1 path: src/Dto/Widget.php + - + message: '#^Unused SpomkyLabs\\PwaBundle\\Dto\\Widget\:\:getDescription$#' + identifier: shipmonk.deadMethod + count: 1 + path: src/Dto/Widget.php + + - + message: '#^Unused SpomkyLabs\\PwaBundle\\Dto\\Widget\:\:getName$#' + identifier: shipmonk.deadMethod + count: 1 + path: src/Dto/Widget.php + + - + message: '#^Unused SpomkyLabs\\PwaBundle\\Dto\\Widget\:\:getShortName$#' + identifier: shipmonk.deadMethod + count: 1 + path: src/Dto/Widget.php + - message: '#^Class SpomkyLabs\\PwaBundle\\Dto\\Workbox has an uninitialized property \$assetCache\. Give it default value or assign it in the constructor\.$#' identifier: property.uninitialized @@ -606,18 +858,114 @@ parameters: count: 1 path: src/ImageProcessor/GDImageProcessor.php + - + message: '#^Unused SpomkyLabs\\PwaBundle\\MatchCallbackHandler\\DestinationMatchCallbackHandler\:\:__construct$#' + identifier: shipmonk.deadMethod + count: 1 + path: src/MatchCallbackHandler/DestinationMatchCallbackHandler.php + + - + message: '#^Unused SpomkyLabs\\PwaBundle\\MatchCallbackHandler\\DestinationMatchCallbackHandler\:\:setLogger$#' + identifier: shipmonk.deadMethod + count: 1 + path: src/MatchCallbackHandler/DestinationMatchCallbackHandler.php + + - + message: '#^Unused SpomkyLabs\\PwaBundle\\MatchCallbackHandler\\ExactPathnameMatchCallbackHandler\:\:__construct$#' + identifier: shipmonk.deadMethod + count: 1 + path: src/MatchCallbackHandler/ExactPathnameMatchCallbackHandler.php + + - + message: '#^Unused SpomkyLabs\\PwaBundle\\MatchCallbackHandler\\ExactPathnameMatchCallbackHandler\:\:setLogger$#' + identifier: shipmonk.deadMethod + count: 1 + path: src/MatchCallbackHandler/ExactPathnameMatchCallbackHandler.php + + - + message: '#^Unused SpomkyLabs\\PwaBundle\\MatchCallbackHandler\\NavigationMatchCallbackHandler\:\:__construct$#' + identifier: shipmonk.deadMethod + count: 1 + path: src/MatchCallbackHandler/NavigationMatchCallbackHandler.php + + - + message: '#^Unused SpomkyLabs\\PwaBundle\\MatchCallbackHandler\\NavigationMatchCallbackHandler\:\:setLogger$#' + identifier: shipmonk.deadMethod + count: 1 + path: src/MatchCallbackHandler/NavigationMatchCallbackHandler.php + + - + message: '#^Unused SpomkyLabs\\PwaBundle\\MatchCallbackHandler\\OriginMatchCallbackHandler\:\:__construct$#' + identifier: shipmonk.deadMethod + count: 1 + path: src/MatchCallbackHandler/OriginMatchCallbackHandler.php + + - + message: '#^Unused SpomkyLabs\\PwaBundle\\MatchCallbackHandler\\OriginMatchCallbackHandler\:\:setLogger$#' + identifier: shipmonk.deadMethod + count: 1 + path: src/MatchCallbackHandler/OriginMatchCallbackHandler.php + + - + message: '#^Unused SpomkyLabs\\PwaBundle\\MatchCallbackHandler\\PathnameEndsWithMatchCallbackHandler\:\:__construct$#' + identifier: shipmonk.deadMethod + count: 1 + path: src/MatchCallbackHandler/PathnameEndsWithMatchCallbackHandler.php + + - + message: '#^Unused SpomkyLabs\\PwaBundle\\MatchCallbackHandler\\PathnameEndsWithMatchCallbackHandler\:\:setLogger$#' + identifier: shipmonk.deadMethod + count: 1 + path: src/MatchCallbackHandler/PathnameEndsWithMatchCallbackHandler.php + + - + message: '#^Unused SpomkyLabs\\PwaBundle\\MatchCallbackHandler\\PathnameStartsWithMatchCallbackHandler\:\:__construct$#' + identifier: shipmonk.deadMethod + count: 1 + path: src/MatchCallbackHandler/PathnameStartsWithMatchCallbackHandler.php + + - + message: '#^Unused SpomkyLabs\\PwaBundle\\MatchCallbackHandler\\PathnameStartsWithMatchCallbackHandler\:\:setLogger$#' + identifier: shipmonk.deadMethod + count: 1 + path: src/MatchCallbackHandler/PathnameStartsWithMatchCallbackHandler.php + + - + message: '#^Unused SpomkyLabs\\PwaBundle\\MatchCallbackHandler\\RouteMatchCallbackHandler\:\:__construct$#' + identifier: shipmonk.deadMethod + count: 1 + path: src/MatchCallbackHandler/RouteMatchCallbackHandler.php + + - + message: '#^Unused SpomkyLabs\\PwaBundle\\MatchCallbackHandler\\RouteMatchCallbackHandler\:\:setLogger$#' + identifier: shipmonk.deadMethod + count: 1 + path: src/MatchCallbackHandler/RouteMatchCallbackHandler.php + - message: '#^PHPDoc tag @return with type array\ is incompatible with native type string\.$#' identifier: return.phpDocType count: 1 path: src/Normalizer/AssetNormalizer.php + - + message: '#^Unused SpomkyLabs\\PwaBundle\\Normalizer\\AssetNormalizer\:\:__construct$#' + identifier: shipmonk.deadMethod + count: 1 + path: src/Normalizer/AssetNormalizer.php + - message: '#^Method SpomkyLabs\\PwaBundle\\Normalizer\\IconNormalizer\:\:normalize\(\) should return array\{src\: string, sizes\?\: string, type\?\: string, purpose\?\: string\} but returns array\\.$#' identifier: return.type count: 1 path: src/Normalizer/IconNormalizer.php + - + message: '#^Unused SpomkyLabs\\PwaBundle\\Normalizer\\IconNormalizer\:\:__construct$#' + identifier: shipmonk.deadMethod + count: 1 + path: src/Normalizer/IconNormalizer.php + - message: '#^Method SpomkyLabs\\PwaBundle\\Normalizer\\ScreenshotNormalizer\:\:normalize\(\) should return array\{src\: string, sizes\?\: string, form_factor\?\: string, label\?\: string, platform\?\: string, format\?\: string\} but returns array\\.$#' identifier: return.type @@ -630,12 +978,24 @@ parameters: count: 1 path: src/Normalizer/ScreenshotNormalizer.php + - + message: '#^Unused SpomkyLabs\\PwaBundle\\Normalizer\\ScreenshotNormalizer\:\:__construct$#' + identifier: shipmonk.deadMethod + count: 1 + path: src/Normalizer/ScreenshotNormalizer.php + - message: '#^Method SpomkyLabs\\PwaBundle\\Normalizer\\ServiceWorkerNormalizer\:\:normalize\(\) should return array\{scope\?\: string, src\: string, use_cache\?\: bool\} but returns array\\.$#' identifier: return.type count: 1 path: src/Normalizer/ServiceWorkerNormalizer.php + - + message: '#^Unused SpomkyLabs\\PwaBundle\\Normalizer\\UrlNormalizer\:\:__construct$#' + identifier: shipmonk.deadMethod + count: 1 + path: src/Normalizer/UrlNormalizer.php + - message: '#^Call to an undefined method Symfony\\Component\\Config\\Definition\\Builder\\NodeDefinition\:\:children\(\)\.$#' identifier: method.notFound @@ -651,7 +1011,7 @@ parameters: - message: '#^Cannot call method booleanNode\(\) on mixed\.$#' identifier: method.nonObject - count: 2 + count: 3 path: src/Resources/config/definition/favicons.php - @@ -669,7 +1029,7 @@ parameters: - message: '#^Cannot call method defaultFalse\(\) on mixed\.$#' identifier: method.nonObject - count: 1 + count: 2 path: src/Resources/config/definition/favicons.php - @@ -687,7 +1047,7 @@ parameters: - message: '#^Cannot call method end\(\) on mixed\.$#' identifier: method.nonObject - count: 13 + count: 14 path: src/Resources/config/definition/favicons.php - @@ -699,7 +1059,7 @@ parameters: - message: '#^Cannot call method info\(\) on mixed\.$#' identifier: method.nonObject - count: 9 + count: 10 path: src/Resources/config/definition/favicons.php - @@ -822,6 +1182,12 @@ parameters: count: 1 path: src/Resources/config/definition/manifest.php + - + message: '#^Cannot call method beforeNormalization\(\) on mixed\.$#' + identifier: method.nonObject + count: 1 + path: src/Resources/config/definition/manifest.php + - message: '#^Cannot call method booleanNode\(\) on mixed\.$#' identifier: method.nonObject @@ -861,7 +1227,7 @@ parameters: - message: '#^Cannot call method end\(\) on mixed\.$#' identifier: method.nonObject - count: 32 + count: 33 path: src/Resources/config/definition/manifest.php - @@ -870,6 +1236,12 @@ parameters: count: 18 path: src/Resources/config/definition/manifest.php + - + message: '#^Cannot call method ifTrue\(\) on mixed\.$#' + identifier: method.nonObject + count: 1 + path: src/Resources/config/definition/manifest.php + - message: '#^Cannot call method info\(\) on mixed\.$#' identifier: method.nonObject @@ -900,6 +1272,24 @@ parameters: count: 2 path: src/Resources/config/definition/manifest.php + - + message: '#^Cannot call method then\(\) on mixed\.$#' + identifier: method.nonObject + count: 1 + path: src/Resources/config/definition/manifest.php + + - + message: '#^Parameter \#1 \$icons of function expandIcons expects array, mixed given\.$#' + identifier: argument.type + count: 1 + path: src/Resources/config/definition/manifest.php + + - + message: '#^Parameter \#2 \$array of function array_key_exists expects array, mixed given\.$#' + identifier: argument.type + count: 1 + path: src/Resources/config/definition/manifest.php + - message: '#^Call to an undefined method Symfony\\Component\\Config\\Definition\\Builder\\NodeDefinition\:\:children\(\)\.$#' identifier: method.notFound @@ -1284,6 +1674,24 @@ parameters: count: 3 path: src/Resources/config/definition/service_worker.php + - + message: '#^Function expandIcons\(\) has parameter \$icons with no value type specified in iterable type array\.$#' + identifier: missingType.iterableValue + count: 1 + path: src/Resources/config/definition/utils/icons.php + + - + message: '#^Function expandIcons\(\) return type has no value type specified in iterable type array\.$#' + identifier: missingType.iterableValue + count: 1 + path: src/Resources/config/definition/utils/icons.php + + - + message: '#^Parameter \#2 \$array of function array_key_exists expects array, mixed given\.$#' + identifier: argument.type + count: 1 + path: src/Resources/config/definition/utils/icons.php + - message: '#^Call to an undefined method Symfony\\Component\\Config\\Definition\\Builder\\NodeDefinition\:\:children\(\)\.$#' identifier: method.notFound @@ -1320,6 +1728,18 @@ parameters: count: 1 path: src/Service/ApplicationIconCompiler.php + - + message: '#^Unused SpomkyLabs\\PwaBundle\\Service\\ApplicationIconCompiler\:\:__construct$#' + identifier: shipmonk.deadMethod + count: 1 + path: src/Service/ApplicationIconCompiler.php + + - + message: '#^Unused SpomkyLabs\\PwaBundle\\Service\\CanLogInterface\:\:setLogger$#' + identifier: shipmonk.deadMethod + count: 1 + path: src/Service/CanLogInterface.php + - message: '#^Method SpomkyLabs\\PwaBundle\\Service\\Data\:\:getData\(\) should return string but returns mixed\.$#' identifier: return.type @@ -1332,6 +1752,24 @@ parameters: count: 1 path: src/Service/Data.php + - + message: '#^Unused SpomkyLabs\\PwaBundle\\Service\\FaviconsBuilder\:\:__construct$#' + identifier: shipmonk.deadMethod + count: 1 + path: src/Service/FaviconsBuilder.php + + - + message: '#^Unused SpomkyLabs\\PwaBundle\\Service\\FaviconsBuilder\:\:create$#' + identifier: shipmonk.deadMethod + count: 1 + path: src/Service/FaviconsBuilder.php + + - + message: '#^Unused SpomkyLabs\\PwaBundle\\Service\\FaviconsBuilder\:\:setLogger$#' + identifier: shipmonk.deadMethod + count: 1 + path: src/Service/FaviconsBuilder.php + - message: '#^Cannot call method process\(\) on SpomkyLabs\\PwaBundle\\ImageProcessor\\ImageProcessorInterface\|null\.$#' identifier: method.nonObject @@ -1344,6 +1782,18 @@ parameters: count: 1 path: src/Service/FaviconsCompiler.php + - + message: '#^Unused SpomkyLabs\\PwaBundle\\Service\\FaviconsCompiler\:\:__construct$#' + identifier: shipmonk.deadMethod + count: 1 + path: src/Service/FaviconsCompiler.php + + - + message: '#^Unused SpomkyLabs\\PwaBundle\\Service\\FaviconsCompiler\:\:setLogger$#' + identifier: shipmonk.deadMethod + count: 1 + path: src/Service/FaviconsCompiler.php + - message: '#^Cannot access property \$sourcePath on Symfony\\Component\\AssetMapper\\MappedAsset\|null\.$#' identifier: property.nonObject @@ -1356,18 +1806,144 @@ parameters: count: 2 path: src/Service/IconResolver.php + - + message: '#^Unused SpomkyLabs\\PwaBundle\\Service\\IconResolver\:\:__construct$#' + identifier: shipmonk.deadMethod + count: 1 + path: src/Service/IconResolver.php + + - + message: '#^Unused SpomkyLabs\\PwaBundle\\Service\\ManifestBuilder\:\:__construct$#' + identifier: shipmonk.deadMethod + count: 1 + path: src/Service/ManifestBuilder.php + + - + message: '#^Unused SpomkyLabs\\PwaBundle\\Service\\ManifestBuilder\:\:create$#' + identifier: shipmonk.deadMethod + count: 1 + path: src/Service/ManifestBuilder.php + + - + message: '#^Unused SpomkyLabs\\PwaBundle\\Service\\ManifestCompiler\:\:__construct$#' + identifier: shipmonk.deadMethod + count: 1 + path: src/Service/ManifestCompiler.php + + - + message: '#^Unused SpomkyLabs\\PwaBundle\\Service\\ManifestCompiler\:\:setLogger$#' + identifier: shipmonk.deadMethod + count: 1 + path: src/Service/ManifestCompiler.php + + - + message: '#^Unused SpomkyLabs\\PwaBundle\\Service\\ServiceWorkerBuilder\:\:__construct$#' + identifier: shipmonk.deadMethod + count: 1 + path: src/Service/ServiceWorkerBuilder.php + + - + message: '#^Unused SpomkyLabs\\PwaBundle\\Service\\ServiceWorkerBuilder\:\:create$#' + identifier: shipmonk.deadMethod + count: 1 + path: src/Service/ServiceWorkerBuilder.php + + - + message: '#^Unused SpomkyLabs\\PwaBundle\\Service\\ServiceWorkerCompiler\:\:__construct$#' + identifier: shipmonk.deadMethod + count: 1 + path: src/Service/ServiceWorkerCompiler.php + + - + message: '#^Unused SpomkyLabs\\PwaBundle\\Service\\ServiceWorkerCompiler\:\:setLogger$#' + identifier: shipmonk.deadMethod + count: 1 + path: src/Service/ServiceWorkerCompiler.php + - message: '#^Parameter \#2 \.\.\.\$values of function sprintf expects bool\|float\|int\|string\|null, mixed given\.$#' identifier: argument.type count: 1 path: src/ServiceWorkerRule/AppendCacheStrategies.php + - + message: '#^Unused SpomkyLabs\\PwaBundle\\ServiceWorkerRule\\AppendCacheStrategies\:\:__construct$#' + identifier: shipmonk.deadMethod + count: 1 + path: src/ServiceWorkerRule/AppendCacheStrategies.php + + - + message: '#^Unused SpomkyLabs\\PwaBundle\\ServiceWorkerRule\\ClearCache\:\:__construct$#' + identifier: shipmonk.deadMethod + count: 1 + path: src/ServiceWorkerRule/ClearCache.php + + - + message: '#^Unused SpomkyLabs\\PwaBundle\\ServiceWorkerRule\\ClearCache\:\:setLogger$#' + identifier: shipmonk.deadMethod + count: 1 + path: src/ServiceWorkerRule/ClearCache.php + - message: '#^Strict comparison using \=\=\= between int\<1, max\> and 0 will always evaluate to false\.$#' identifier: identical.alwaysFalse count: 1 path: src/ServiceWorkerRule/OfflineFallback.php + - + message: '#^Unused SpomkyLabs\\PwaBundle\\ServiceWorkerRule\\OfflineFallback\:\:__construct$#' + identifier: shipmonk.deadMethod + count: 1 + path: src/ServiceWorkerRule/OfflineFallback.php + + - + message: '#^Unused SpomkyLabs\\PwaBundle\\ServiceWorkerRule\\OfflineFallback\:\:setLogger$#' + identifier: shipmonk.deadMethod + count: 1 + path: src/ServiceWorkerRule/OfflineFallback.php + + - + message: '#^Unused SpomkyLabs\\PwaBundle\\ServiceWorkerRule\\SkipWaiting\:\:__construct$#' + identifier: shipmonk.deadMethod + count: 1 + path: src/ServiceWorkerRule/SkipWaiting.php + + - + message: '#^Unused SpomkyLabs\\PwaBundle\\ServiceWorkerRule\\SkipWaiting\:\:setLogger$#' + identifier: shipmonk.deadMethod + count: 1 + path: src/ServiceWorkerRule/SkipWaiting.php + + - + message: '#^Unused SpomkyLabs\\PwaBundle\\ServiceWorkerRule\\WindowsWidgets\:\:__construct$#' + identifier: shipmonk.deadMethod + count: 1 + path: src/ServiceWorkerRule/WindowsWidgets.php + + - + message: '#^Unused SpomkyLabs\\PwaBundle\\ServiceWorkerRule\\WindowsWidgets\:\:setLogger$#' + identifier: shipmonk.deadMethod + count: 1 + path: src/ServiceWorkerRule/WindowsWidgets.php + + - + message: '#^Unused SpomkyLabs\\PwaBundle\\ServiceWorkerRule\\WorkboxImport\:\:__construct$#' + identifier: shipmonk.deadMethod + count: 1 + path: src/ServiceWorkerRule/WorkboxImport.php + + - + message: '#^Unused SpomkyLabs\\PwaBundle\\ServiceWorkerRule\\WorkboxImport\:\:getPriority$#' + identifier: shipmonk.deadMethod + count: 1 + path: src/ServiceWorkerRule/WorkboxImport.php + + - + message: '#^Unused SpomkyLabs\\PwaBundle\\ServiceWorkerRule\\WorkboxImport\:\:setLogger$#' + identifier: shipmonk.deadMethod + count: 1 + path: src/ServiceWorkerRule/WorkboxImport.php + - message: '#^Cannot access offset ''dest'' on mixed\.$#' identifier: offsetAccess.nonOffsetAccessible @@ -1410,8 +1986,62 @@ parameters: count: 8 path: src/SpomkyLabsPwaBundle.php + - + message: '#^Unused SpomkyLabs\\PwaBundle\\Twig\\PwaRuntime\:\:__construct$#' + identifier: shipmonk.deadMethod + count: 1 + path: src/Twig/PwaRuntime.php + + - + message: '#^Unused SpomkyLabs\\PwaBundle\\Twig\\PwaRuntime\:\:load$#' + identifier: shipmonk.deadMethod + count: 1 + path: src/Twig/PwaRuntime.php + + - + message: '#^Unused SpomkyLabs\\PwaBundle\\WorkboxPlugin\\BackgroundSyncPlugin\:\:getDebug$#' + identifier: shipmonk.deadMethod + count: 1 + path: src/WorkboxPlugin/BackgroundSyncPlugin.php + + - + message: '#^Unused SpomkyLabs\\PwaBundle\\WorkboxPlugin\\BroadcastUpdatePlugin\:\:getDebug$#' + identifier: shipmonk.deadMethod + count: 1 + path: src/WorkboxPlugin/BroadcastUpdatePlugin.php + + - + message: '#^Unused SpomkyLabs\\PwaBundle\\WorkboxPlugin\\CacheableResponsePlugin\:\:getDebug$#' + identifier: shipmonk.deadMethod + count: 1 + path: src/WorkboxPlugin/CacheableResponsePlugin.php + + - + message: '#^Unused SpomkyLabs\\PwaBundle\\WorkboxPlugin\\ExpirationPlugin\:\:getDebug$#' + identifier: shipmonk.deadMethod + count: 1 + path: src/WorkboxPlugin/ExpirationPlugin.php + + - + message: '#^Unused SpomkyLabs\\PwaBundle\\WorkboxPlugin\\HasDebugInterface\:\:getDebug$#' + identifier: shipmonk.deadMethod + count: 1 + path: src/WorkboxPlugin/HasDebugInterface.php + + - + message: '#^Unused SpomkyLabs\\PwaBundle\\WorkboxPlugin\\RangeRequestsPlugin\:\:getDebug$#' + identifier: shipmonk.deadMethod + count: 1 + path: src/WorkboxPlugin/RangeRequestsPlugin.php + - message: '#^Unreachable statement \- code above always terminates\.$#' identifier: deadCode.unreachable count: 1 path: tests/Functional/TakeScreenshotCommandTest.php + + - + message: '#^Unused SpomkyLabs\\PwaBundle\\Tests\\TestFilesystem\:\:__construct$#' + identifier: shipmonk.deadMethod + count: 1 + path: tests/TestFilesystem.php diff --git a/src/Dto/Favicons.php b/src/Dto/Favicons.php index f99b570..bcee576 100644 --- a/src/Dto/Favicons.php +++ b/src/Dto/Favicons.php @@ -40,4 +40,6 @@ final class Favicons public null|bool $useSilhouette = null; public null|string $potrace = null; + + public bool $monochrome = false; } diff --git a/src/ImageProcessor/Configuration.php b/src/ImageProcessor/Configuration.php index fc4df4b..8871bda 100644 --- a/src/ImageProcessor/Configuration.php +++ b/src/ImageProcessor/Configuration.php @@ -17,6 +17,7 @@ public function __construct( public null|string $backgroundColor = null, public null|int $borderRadius = null, public null|int $imageScale = null, + public bool $monochrome = false, ) { if ($this->borderRadius !== null && $this->backgroundColor === null) { throw new InvalidArgumentException('The background color must be set when the border radius is set'); @@ -26,13 +27,14 @@ public function __construct( public function __toString(): string { return sprintf( - '%d%d%s%s%s%s', + '%d%d%s%s%s%s%s', $this->width, $this->height, $this->format, $this->backgroundColor ?? '', $this->borderRadius ?? '', - $this->imageScale ?? '' + $this->imageScale ?? '', + $this->monochrome ? '1' : '0', ); } @@ -43,7 +45,8 @@ public static function create( null|string $backgroundColor = null, null|int $borderRadius = null, null|int $imageScale = null, + bool $monochrome = false, ): self { - return new self($width, $height, $format, $backgroundColor, $borderRadius, $imageScale); + return new self($width, $height, $format, $backgroundColor, $borderRadius, $imageScale, $monochrome); } } diff --git a/src/ImageProcessor/ImagickImageProcessor.php b/src/ImageProcessor/ImagickImageProcessor.php index 806e554..20e6a0f 100644 --- a/src/ImageProcessor/ImagickImageProcessor.php +++ b/src/ImageProcessor/ImagickImageProcessor.php @@ -46,35 +46,25 @@ private function createMainImage(string $image, Configuration $configuration): I $mainImage->setImageBackgroundColor(new ImagickPixel('transparent')); if ($configuration->imageScale !== null) { - $width = $mainImage->getImageWidth(); - $height = $mainImage->getImageHeight(); - $newWidth = (int) ($width * $configuration->imageScale / 100); - $newHeight = (int) ($height * $configuration->imageScale / 100); - $widthCenter = (int) (-($width - $newWidth) / 2); - $heightCenter = (int) (-($height - $newHeight) / 2); - - $mainImage->scaleImage($newWidth, $newHeight); - $mainImage->extentImage($width, $height, $widthCenter, $heightCenter); + $this->resizeImageWithScale($mainImage, $configuration->imageScale); } - if ($configuration->width === $configuration->height) { - $mainImage->scaleImage($configuration->width, $configuration->height); + // Resize image with new size to best fit the configuration + $mainImage->scaleImage($configuration->width, $configuration->height, true); - return $mainImage; - } - - $mainImage->scaleImage( - min($configuration->width, $configuration->height), - min($configuration->width, $configuration->height) - ); - $mainImage->extentImage( - $configuration->width, - $configuration->height, - -($configuration->width - min($configuration->width, $configuration->height)) / 2, - -($configuration->height - min($configuration->width, $configuration->height)) / 2 + $background = new Imagick(); + $background->newImage($configuration->width, $configuration->height, new ImagickPixel('transparent')); + $background->compositeImage( + $mainImage, + Imagick::COMPOSITE_OVER, + (int) (($configuration->width - $mainImage->getImageWidth()) / 2), + (int) (($configuration->height - $mainImage->getImageHeight()) / 2) ); + if ($configuration->monochrome) { + $background->setImageType(Imagick::IMGTYPE_GRAYSCALEMATTE); + } - return $mainImage; + return $background; } private function createBackground(Configuration $configuration): Imagick @@ -111,4 +101,19 @@ private function createBackground(Configuration $configuration): Imagick return $background; } + + private function resizeImageWithScale(Imagick $image, float|int $imageScale): void + { + $imageWidth = $image->getImageWidth(); + $imageHeight = $image->getImageHeight(); + $newWidth = (int) ($imageWidth * $imageScale / 100); + $newHeight = (int) ($imageHeight * $imageScale / 100); + + $this->resizeImageWithNewSize($image, $newWidth, $newHeight); + } + + private function resizeImageWithNewSize(Imagick $image, int $newWidth, int $newHeight): void + { + $image->scaleImage($newWidth, $newHeight, true); + } } diff --git a/src/Normalizer/ScreenshotNormalizer.php b/src/Normalizer/ScreenshotNormalizer.php index d95dbf0..cedce7f 100644 --- a/src/Normalizer/ScreenshotNormalizer.php +++ b/src/Normalizer/ScreenshotNormalizer.php @@ -105,8 +105,7 @@ private function getType(?MappedAsset $asset): ?string return null; } - $mime = MimeTypes::getDefault(); - return $mime->guessMimeType($asset->sourcePath); + return MimeTypes::getDefault()->guessMimeType($asset->sourcePath); } private function getFormFactor(?int $width, ?int $height): ?string diff --git a/src/Resources/config/definition/favicons.php b/src/Resources/config/definition/favicons.php index 49bfff0..2dca1e9 100644 --- a/src/Resources/config/definition/favicons.php +++ b/src/Resources/config/definition/favicons.php @@ -53,6 +53,10 @@ 'Use only the silhouette of the icon. Applicable for macOS Safari and Windows 8+. Requires potrace to be installed.' ) ->end() + ->booleanNode('monochrome') + ->defaultFalse() + ->info('Use a monochrome icon.') + ->end() ->scalarNode('potrace') ->defaultValue('potrace') ->info('The path to the potrace binary.') diff --git a/src/Resources/config/definition/manifest.php b/src/Resources/config/definition/manifest.php index c47eb76..8743be1 100644 --- a/src/Resources/config/definition/manifest.php +++ b/src/Resources/config/definition/manifest.php @@ -19,6 +19,13 @@ $definition->rootNode() ->children() ->arrayNode('manifest') + ->beforeNormalization() + ->ifTrue(static fn (mixed $v): bool => array_key_exists('icons', $v) && is_array($v['icons'])) + ->then(static function (array $v): array { + $v['icons'] = expandIcons($v['icons'] ?? []); + return $v; + }) + ->end() ->canBeEnabled() ->children() ->scalarNode('public_url') diff --git a/src/Resources/config/definition/utils/icons.php b/src/Resources/config/definition/utils/icons.php index 83a57b6..a129d37 100644 --- a/src/Resources/config/definition/utils/icons.php +++ b/src/Resources/config/definition/utils/icons.php @@ -5,6 +5,25 @@ use Symfony\Component\Config\Definition\Builder\ArrayNodeDefinition; use Symfony\Component\Config\Definition\Builder\TreeBuilder; +function expandIcons(array $icons): array +{ + $expandedIcons = []; + foreach ($icons as $icon) { + if (! array_key_exists('sizes', $icon) || ! is_array($icon['sizes'])) { + $expandedIcons[] = $icon; + continue; + } + + foreach ($icon['sizes'] as $size) { + $expandedIcon = $icon; + $expandedIcon['sizes'] = [$size]; + $expandedIcons[] = $expandedIcon; + } + } + + return $expandedIcons; +} + function getIconsNode(string $info): ArrayNodeDefinition { $treeBuilder = new TreeBuilder('icons'); @@ -17,9 +36,9 @@ function getIconsNode(string $info): ArrayNodeDefinition ->arrayPrototype() ->beforeNormalization() ->ifString() - ->then(static fn (string $v): array => [ - 'src' => $v, - ]) + ->then(static fn (string $v): array => [ + 'src' => $v, + ]) ->end() ->children() ->scalarNode('src') diff --git a/src/Service/FaviconsCompiler.php b/src/Service/FaviconsCompiler.php index 372b01e..c4742a6 100644 --- a/src/Service/FaviconsCompiler.php +++ b/src/Service/FaviconsCompiler.php @@ -232,6 +232,7 @@ public function getFiles(): iterable $this->favicons->backgroundColor, $this->favicons->borderRadius, $this->favicons->imageScale, + $this->favicons->monochrome ); $completeHash = hash('xxh128', $hash . $configuration); $filename = sprintf($size['url'], $size['width'], $size['height'], $completeHash); diff --git a/src/Service/IconResolver.php b/src/Service/IconResolver.php index 07bf878..bdff21d 100644 --- a/src/Service/IconResolver.php +++ b/src/Service/IconResolver.php @@ -68,7 +68,8 @@ public function getIcon(Icon $icon): Data $icon->format ?? $asset->publicExtension, $icon->backgroundColor, $icon->borderRadius, - $icon->imageScale + $icon->imageScale, + str_contains($icon->purpose ?? '', 'monochrome'), ); $format = $icon->format ?? $asset->publicExtension; $hash = hash( @@ -80,7 +81,7 @@ public function getIcon(Icon $icon): Data $format, $size, $icon->purpose ?? '', - $icon->type ?? '' + $icon->type ?? '', ) ); $url = sprintf('/pwa/icon-%s.%s', $hash, $format);