From 73416fbef396391371097f847b8d1209a6318326 Mon Sep 17 00:00:00 2001 From: nick Date: Mon, 9 Sep 2024 21:56:24 +0100 Subject: [PATCH 01/12] feat(SLB-585): focal point drupal module support --- apps/cms/composer.json | 1 + apps/cms/composer.lock | 114 +++++++++++++++++- ...ntity_form_display.media.image.default.yml | 10 +- apps/cms/config/sync/core.extension.yml | 2 + apps/cms/config/sync/crop.settings.yml | 3 + .../cms/config/sync/crop.type.focal_point.yml | 14 +++ apps/cms/config/sync/focal_point.settings.yml | 4 + .../language/de/crop.type.focal_point.yml | 2 + apps/website/gatsby-config.mjs | 10 +- apps/website/gatsby-image.mjs | 109 +++++++++++++++++ apps/website/package.json | 3 +- .../101ccff9-3733-480e-aca1-392253376a48.yml | 28 +++++ .../10743cfc-604c-4a07-baf3-e267d9c89ff6.yml | 28 +++++ .../33c9a04b-0706-4739-9553-d0704c5382b4.yml | 28 +++++ .../eaee4cf9-c845-4e13-b6e9-c9544e8bcf15.yml | 28 +++++ .../fa5809c7-910b-4687-8bb0-5b2a8f69c75e.yml | 28 +++++ .../3a0fe860-a6d6-428a-9474-365bd57509aa.yml | 3 + .../25086be7-ca5f-4ff8-9695-b9c71a676d4e.yml | 4 +- .../7a928d7d-f9ad-4edb-aa22-2f535bc5c873.yml | 8 +- packages/schema/src/schema.graphql | 5 +- pnpm-lock.yaml | 3 + tests/schema/specs/media.spec.ts | 2 +- 22 files changed, 420 insertions(+), 17 deletions(-) create mode 100644 apps/cms/config/sync/crop.settings.yml create mode 100644 apps/cms/config/sync/crop.type.focal_point.yml create mode 100644 apps/cms/config/sync/focal_point.settings.yml create mode 100644 apps/cms/config/sync/language/de/crop.type.focal_point.yml create mode 100644 apps/website/gatsby-image.mjs create mode 100644 packages/drupal/test_content/content/crop/101ccff9-3733-480e-aca1-392253376a48.yml create mode 100644 packages/drupal/test_content/content/crop/10743cfc-604c-4a07-baf3-e267d9c89ff6.yml create mode 100644 packages/drupal/test_content/content/crop/33c9a04b-0706-4739-9553-d0704c5382b4.yml create mode 100644 packages/drupal/test_content/content/crop/eaee4cf9-c845-4e13-b6e9-c9544e8bcf15.yml create mode 100644 packages/drupal/test_content/content/crop/fa5809c7-910b-4687-8bb0-5b2a8f69c75e.yml diff --git a/apps/cms/composer.json b/apps/cms/composer.json index f6a331297..5314b38da 100644 --- a/apps/cms/composer.json +++ b/apps/cms/composer.json @@ -59,6 +59,7 @@ "drupal/entity_usage": "^2.0@beta", "drupal/environment_indicator": "^4.0.14", "drupal/field_group": "^3.4", + "drupal/focal_point": "^2.1", "drupal/honeypot": "^2.1.2", "drupal/key_auth": "^2.1", "drupal/lagoon_logs": "^2.1.1", diff --git a/apps/cms/composer.lock b/apps/cms/composer.lock index b1de8150a..985a47f53 100644 --- a/apps/cms/composer.lock +++ b/apps/cms/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "8fa136f788cce1474b561f9f2f6d29af", + "content-hash": "22551efe3b21406f6a65c1b69286bc83", "packages": [ { "name": "amazeeio/drupal_integrations", @@ -2884,6 +2884,63 @@ }, "time": "2024-04-03T07:19:20+00:00" }, + { + "name": "drupal/crop", + "version": "2.4.0", + "source": { + "type": "git", + "url": "https://git.drupalcode.org/project/crop.git", + "reference": "8.x-2.4" + }, + "dist": { + "type": "zip", + "url": "https://ftp.drupal.org/files/projects/crop-8.x-2.4.zip", + "reference": "8.x-2.4", + "shasum": "be11fad0abf1d53544d35cb4ca6cedd8e91d2542" + }, + "require": { + "drupal/core": "^9.3 || ^10 || ^11" + }, + "type": "drupal-module", + "extra": { + "drupal": { + "version": "8.x-2.4", + "datestamp": "1720455738", + "security-coverage": { + "status": "covered", + "message": "Covered by Drupal's security advisory policy" + } + } + }, + "notification-url": "https://packages.drupal.org/8/downloads", + "license": [ + "GPL-2.0-or-later" + ], + "authors": [ + { + "name": "Drupal Media Team", + "homepage": "https://www.drupal.org/user/3260690" + }, + { + "name": "phenaproxima", + "homepage": "https://www.drupal.org/user/205645" + }, + { + "name": "slashrsm", + "homepage": "https://www.drupal.org/user/744628" + }, + { + "name": "woprrr", + "homepage": "https://www.drupal.org/user/858604" + } + ], + "description": "Provides storage and API for image crops.", + "homepage": "https://www.drupal.org/project/crop", + "support": { + "source": "https://git.drupalcode.org/project/crop", + "issues": "https://www.drupal.org/project/issues/crop" + } + }, { "name": "drupal/ctools", "version": "4.0.4", @@ -3398,6 +3455,61 @@ "issues": "https://www.drupal.org/project/issues/field_group" } }, + { + "name": "drupal/focal_point", + "version": "2.1.1", + "source": { + "type": "git", + "url": "https://git.drupalcode.org/project/focal_point.git", + "reference": "2.1.1" + }, + "dist": { + "type": "zip", + "url": "https://ftp.drupal.org/files/projects/focal_point-2.1.1.zip", + "reference": "2.1.1", + "shasum": "f8c24bb4257f784176e79ec6f2b4c11ed46391e0" + }, + "require": { + "drupal/core": "^9.3 || ^10 || ^11", + "drupal/crop": "^2.3" + }, + "require-dev": { + "drupal/crop": "*" + }, + "type": "drupal-module", + "extra": { + "drupal": { + "version": "2.1.1", + "datestamp": "1721126807", + "security-coverage": { + "status": "covered", + "message": "Covered by Drupal's security advisory policy" + } + } + }, + "notification-url": "https://packages.drupal.org/8/downloads", + "license": [ + "GPL-2.0-or-later" + ], + "authors": [ + { + "name": "Alexander Ross (bleen)", + "homepage": "https://www.drupal.org/u/bleen", + "role": "Maintainer" + }, + { + "name": "rajeshreeputra", + "homepage": "https://www.drupal.org/user/3418561" + } + ], + "description": "Focal Point allows content creators to mark the most important part of an image for easier cropping.", + "homepage": "https://drupal.org/project/focal_point", + "support": { + "source": "https://cgit.drupalcode.org/focal_point", + "issues": "https://drupal.org/project/issues/focal_point", + "irc": "irc://irc.freenode.org/drupal-contribute" + } + }, { "name": "drupal/graphql", "version": "4.6.0", diff --git a/apps/cms/config/sync/core.entity_form_display.media.image.default.yml b/apps/cms/config/sync/core.entity_form_display.media.image.default.yml index 4776c41a6..d29cabe8f 100644 --- a/apps/cms/config/sync/core.entity_form_display.media.image.default.yml +++ b/apps/cms/config/sync/core.entity_form_display.media.image.default.yml @@ -4,10 +4,10 @@ status: true dependencies: config: - field.field.media.image.field_media_image - - image.style.thumbnail + - image.style.medium - media.type.image module: - - image + - focal_point - path id: media.image.default targetEntityType: media @@ -21,12 +21,14 @@ content: settings: { } third_party_settings: { } field_media_image: - type: image_image + type: image_focal_point weight: 0 region: content settings: progress_indicator: throbber - preview_image_style: thumbnail + preview_image_style: medium + preview_link: false + offsets: '50,50' third_party_settings: { } langcode: type: language_select diff --git a/apps/cms/config/sync/core.extension.yml b/apps/cms/config/sync/core.extension.yml index dc3a3f2fe..853fe94a7 100644 --- a/apps/cms/config/sync/core.extension.yml +++ b/apps/cms/config/sync/core.extension.yml @@ -14,6 +14,7 @@ module: config_translation: 0 consumers: 0 content_moderation: 0 + crop: 0 custom: 0 datetime: 0 datetime_range: 0 @@ -31,6 +32,7 @@ module: field_ui: 0 file: 0 filter: 0 + focal_point: 0 graphql: 0 graphql_directives: 0 gutenberg: 0 diff --git a/apps/cms/config/sync/crop.settings.yml b/apps/cms/config/sync/crop.settings.yml new file mode 100644 index 000000000..2fd805cb7 --- /dev/null +++ b/apps/cms/config/sync/crop.settings.yml @@ -0,0 +1,3 @@ +_core: + default_config_hash: 7eGOTSG7bRv_AAqu-Ls8CSnob7zPF1ez-lD2OLZgBHs +flush_derivative_images: true diff --git a/apps/cms/config/sync/crop.type.focal_point.yml b/apps/cms/config/sync/crop.type.focal_point.yml new file mode 100644 index 000000000..bdc914b47 --- /dev/null +++ b/apps/cms/config/sync/crop.type.focal_point.yml @@ -0,0 +1,14 @@ +uuid: 928fcf6c-e6b0-43d2-bef3-50ae2d1cced0 +langcode: en +status: true +dependencies: { } +_core: + default_config_hash: flCi9IdafdLXlJqvoHguutUOiC05-aynK4niYN4YZ3o +id: focal_point +label: 'Focal point' +description: 'Crop type used by Focal point module.' +aspect_ratio: '' +soft_limit_width: null +soft_limit_height: null +hard_limit_width: null +hard_limit_height: null diff --git a/apps/cms/config/sync/focal_point.settings.yml b/apps/cms/config/sync/focal_point.settings.yml new file mode 100644 index 000000000..75b071d03 --- /dev/null +++ b/apps/cms/config/sync/focal_point.settings.yml @@ -0,0 +1,4 @@ +_core: + default_config_hash: 8JOAPEbjHMB4rDFCsu3EXEx6L2UDQ5SSDyN0d7S0Ic0 +crop_type: focal_point +default_value: '50,50' diff --git a/apps/cms/config/sync/language/de/crop.type.focal_point.yml b/apps/cms/config/sync/language/de/crop.type.focal_point.yml new file mode 100644 index 000000000..5e3ebd159 --- /dev/null +++ b/apps/cms/config/sync/language/de/crop.type.focal_point.yml @@ -0,0 +1,2 @@ +label: Fokuspunkt +description: 'Der vom Focal-Point-Modul verwendete Zuschnittstyp' diff --git a/apps/website/gatsby-config.mjs b/apps/website/gatsby-config.mjs index 842076dff..922237274 100644 --- a/apps/website/gatsby-config.mjs +++ b/apps/website/gatsby-config.mjs @@ -6,7 +6,7 @@ // TS file name should be different from gastby-config.ts, otherwise Gatsby will // pick it up instead of the JS file. -import { existsSync } from 'fs'; +import { responsiveImageSharp } from './gatsby-image.mjs'; process.env.NETLIFY_URL = process.env.NETLIFY_URL || 'http://127.0.0.1:8000'; @@ -14,6 +14,13 @@ process.env.CLOUDINARY_API_KEY = process.env.CLOUDINARY_API_KEY || 'test'; process.env.CLOUDINARY_API_SECRET = process.env.CLOUDINARY_API_SECRET || 'test'; process.env.CLOUDINARY_CLOUDNAME = process.env.CLOUDINARY_CLOUDNAME || 'demo'; +async function saveResponsiveImageSharp(node, args, context) { + console.log('saving responsive image'); + if (node) { + return await responsiveImageSharp(node, args, context); + } +} + /** * * @type {import('gatsby').GatsbyConfig['plugins']} @@ -59,6 +66,7 @@ const plugins = [ resolve: '@amazeelabs/gatsby-source-silverback', options: { schema_configuration: './graphqlrc.yml', + directives: { responsiveImage: saveResponsiveImageSharp }, }, }, '@custom/cms', diff --git a/apps/website/gatsby-image.mjs b/apps/website/gatsby-image.mjs new file mode 100644 index 000000000..ba8482359 --- /dev/null +++ b/apps/website/gatsby-image.mjs @@ -0,0 +1,109 @@ +import { fluid } from 'gatsby-plugin-sharp'; +import { createRemoteFileNode } from 'gatsby-source-filesystem'; + +const AREA_FALLBACK = 'attention'; + +function determineCropFocus(filename) { + if (filename.includes('.c.')) return 'center'; + if (filename.includes('.nw_.')) return 'northwest'; + if (filename.includes('.w.')) return 'west'; + if (filename.includes('.sw_.')) return 'southwest'; + if (filename.includes('.s.')) return 'south'; + if (filename.includes('.se_.')) return 'southeast'; + if (filename.includes('.e.')) return 'east'; + if (filename.includes('.ne_.')) return 'northeast'; + if (filename.includes('.n.')) return 'north'; + return null; +} + +function calculateCropArea(width, height, x, y) { + if ((x === 0 && y === 0) || (width === 0 || height === 0)) { + return AREA_FALLBACK; + } + + console.log('croparea', width, height, x, y); + // Adjust if Y === height or X === width as + // the division result will produce 1, + // which will not be a valid index + // for the areas array (out of bounds). + const adjustedY = y === height ? y - 1 : y; + const adjustedX = x === width ? x - 1 : x; + + const row = Math.floor(adjustedY / height * 3); + const col = Math.floor(adjustedX / width * 3); + const areas = [ + ['northwest', 'north', 'northeast'], + ['west', 'center', 'east'], + ['southwest', 'south', 'southeast'], + ]; + + // If we met an exception in the calculation of the area, + // use the fallback value. + if (!areas[row] || !areas[row][col]) { + return AREA_FALLBACK; + } + + return areas[row][col]; +} + +export const responsiveImageSharp = async (originalImage, args, context) => { + const responsiveImage = JSON.parse(originalImage); + const { cache, createNode, createNodeId, reporter } = context.api; + console.log('here responsiveImageSharp', responsiveImage, args, context); + try { + const responsiveImageResult = { + ...responsiveImage, + originalSrc: new URL(responsiveImage.src).pathname, + }; + + // If no config object is given, or no width is specified, we just return + // the original image url. + if (typeof args === 'undefined' || typeof args.width === 'undefined') { + return JSON.stringify(responsiveImageResult); + } + + const file = await createRemoteFileNode({ + url: responsiveImage.src, + cache: cache, + createNode: createNode, + createNodeId: createNodeId, + }); + const width = args.width; + const height = args.height || undefined; + const breakpoints = + args.sizes && args.sizes.length > 0 + ? args.sizes.map((item) => { + // If the sizes array contains tuples, then just return the first item + // to be added to the breakpoints elements. + if (Array.isArray(item)) { + return item[0]; + } + return item; + }) + : undefined; + + const fluidFileResult = await fluid({ + file, + args: { + maxWidth: width, + maxHeight: height, + quality: 90, + srcSetBreakpoints: breakpoints, + cropFocus: + determineCropFocus(responsiveImage.src) || calculateCropArea(width, height, responsiveImage.focalPoint.x, responsiveImage.focalPoint.y), + }, + reporter: reporter, + cache: cache, + }); + responsiveImageResult.src = fluidFileResult.src; + responsiveImageResult.width = fluidFileResult.presentationWidth; + responsiveImageResult.height = fluidFileResult.presentationHeight; + responsiveImageResult.sizes = fluidFileResult.sizes; + responsiveImageResult.srcset = fluidFileResult.srcSet; + + return JSON.stringify(responsiveImageResult); + } catch (err) { + console.error(`Error loading image ${responsiveImage.src}`, err); + return JSON.stringify(responsiveImage); + } +}; \ No newline at end of file diff --git a/apps/website/package.json b/apps/website/package.json index f1fcf4e46..fb035d714 100644 --- a/apps/website/package.json +++ b/apps/website/package.json @@ -15,6 +15,7 @@ "@custom/decap": "workspace:*", "@custom/schema": "workspace:*", "@custom/ui": "workspace:*", + "fs-extra": "^11.2.0", "gatsby": "^5.13.1", "gatsby-plugin-layout": "^4.13.0", "gatsby-plugin-manifest": "^5.13.0", @@ -53,7 +54,7 @@ "serve": "netlify dev --cwd=. --dir=public --port=8000", "dev": "pnpm clean && publisher", "open": "open http://127.0.0.1:8000/___status/", - "gatsby:develop": "ENABLE_GATSBY_REFRESH_ENDPOINT=true pnpm gatsby develop", + "gatsby:develop": "pnpm gatsby develop", "gatsby:refresh": "curl -X POST http://localhost:8000/__refresh", "clean": "gatsby clean" } diff --git a/packages/drupal/test_content/content/crop/101ccff9-3733-480e-aca1-392253376a48.yml b/packages/drupal/test_content/content/crop/101ccff9-3733-480e-aca1-392253376a48.yml new file mode 100644 index 000000000..bd9c6f51c --- /dev/null +++ b/packages/drupal/test_content/content/crop/101ccff9-3733-480e-aca1-392253376a48.yml @@ -0,0 +1,28 @@ +_meta: + version: '1.0' + entity_type: crop + uuid: 101ccff9-3733-480e-aca1-392253376a48 + bundle: focal_point + default_langcode: en +default: + entity_id: + - + value: 5 + entity_type: + - + value: file + uri: + - + value: 'public://2023-04/decoupled-architecture.png' + x: + - + value: 1000 + 'y': + - + value: 549 + revision_timestamp: + - + value: 1725914614 + revision_uid: + - + target_id: 1 diff --git a/packages/drupal/test_content/content/crop/10743cfc-604c-4a07-baf3-e267d9c89ff6.yml b/packages/drupal/test_content/content/crop/10743cfc-604c-4a07-baf3-e267d9c89ff6.yml new file mode 100644 index 000000000..79f8a4a89 --- /dev/null +++ b/packages/drupal/test_content/content/crop/10743cfc-604c-4a07-baf3-e267d9c89ff6.yml @@ -0,0 +1,28 @@ +_meta: + version: '1.0' + entity_type: crop + uuid: 10743cfc-604c-4a07-baf3-e267d9c89ff6 + bundle: focal_point + default_langcode: en +default: + entity_id: + - + value: 3 + entity_type: + - + value: file + uri: + - + value: 'public://media-icons/generic/generic.png' + x: + - + value: 90 + 'y': + - + value: 90 + revision_timestamp: + - + value: 1725914614 + revision_uid: + - + target_id: 1 diff --git a/packages/drupal/test_content/content/crop/33c9a04b-0706-4739-9553-d0704c5382b4.yml b/packages/drupal/test_content/content/crop/33c9a04b-0706-4739-9553-d0704c5382b4.yml new file mode 100644 index 000000000..a20fe0185 --- /dev/null +++ b/packages/drupal/test_content/content/crop/33c9a04b-0706-4739-9553-d0704c5382b4.yml @@ -0,0 +1,28 @@ +_meta: + version: '1.0' + entity_type: crop + uuid: 33c9a04b-0706-4739-9553-d0704c5382b4 + bundle: focal_point + default_langcode: en +default: + entity_id: + - + value: 4 + entity_type: + - + value: file + uri: + - + value: 'public://2023-04/landscape.jpg' + x: + - + value: 1782 + 'y': + - + value: 1046 + revision_timestamp: + - + value: 1725914614 + revision_uid: + - + target_id: 1 diff --git a/packages/drupal/test_content/content/crop/eaee4cf9-c845-4e13-b6e9-c9544e8bcf15.yml b/packages/drupal/test_content/content/crop/eaee4cf9-c845-4e13-b6e9-c9544e8bcf15.yml new file mode 100644 index 000000000..811a83a85 --- /dev/null +++ b/packages/drupal/test_content/content/crop/eaee4cf9-c845-4e13-b6e9-c9544e8bcf15.yml @@ -0,0 +1,28 @@ +_meta: + version: '1.0' + entity_type: crop + uuid: eaee4cf9-c845-4e13-b6e9-c9544e8bcf15 + bundle: focal_point + default_langcode: en +default: + entity_id: + - + value: 7 + entity_type: + - + value: file + uri: + - + value: 'public://2024-04/the_silverback.jpeg' + x: + - + value: 563 + 'y': + - + value: 115 + revision_timestamp: + - + value: 1725914614 + revision_uid: + - + target_id: 1 diff --git a/packages/drupal/test_content/content/crop/fa5809c7-910b-4687-8bb0-5b2a8f69c75e.yml b/packages/drupal/test_content/content/crop/fa5809c7-910b-4687-8bb0-5b2a8f69c75e.yml new file mode 100644 index 000000000..da6a8ac4c --- /dev/null +++ b/packages/drupal/test_content/content/crop/fa5809c7-910b-4687-8bb0-5b2a8f69c75e.yml @@ -0,0 +1,28 @@ +_meta: + version: '1.0' + entity_type: crop + uuid: fa5809c7-910b-4687-8bb0-5b2a8f69c75e + bundle: focal_point + default_langcode: en +default: + entity_id: + - + value: 8 + entity_type: + - + value: file + uri: + - + value: 'public://media-icons/generic/video.png' + x: + - + value: 90 + 'y': + - + value: 90 + revision_timestamp: + - + value: 1725914614 + revision_uid: + - + target_id: 1 diff --git a/packages/drupal/test_content/content/media/3a0fe860-a6d6-428a-9474-365bd57509aa.yml b/packages/drupal/test_content/content/media/3a0fe860-a6d6-428a-9474-365bd57509aa.yml index 8c8413faa..7a6b5d300 100644 --- a/packages/drupal/test_content/content/media/3a0fe860-a6d6-428a-9474-365bd57509aa.yml +++ b/packages/drupal/test_content/content/media/3a0fe860-a6d6-428a-9474-365bd57509aa.yml @@ -7,6 +7,9 @@ _meta: depends: bbf1a994-9ff6-44a6-8842-6481bc83dad6: file default: + revision_user: + - + target_id: 1 status: - value: true diff --git a/packages/drupal/test_content/content/node/25086be7-ca5f-4ff8-9695-b9c71a676d4e.yml b/packages/drupal/test_content/content/node/25086be7-ca5f-4ff8-9695-b9c71a676d4e.yml index 0cceba025..7d4852082 100644 --- a/packages/drupal/test_content/content/node/25086be7-ca5f-4ff8-9695-b9c71a676d4e.yml +++ b/packages/drupal/test_content/content/node/25086be7-ca5f-4ff8-9695-b9c71a676d4e.yml @@ -4,8 +4,6 @@ _meta: uuid: 25086be7-ca5f-4ff8-9695-b9c71a676d4e bundle: page default_langcode: en - depends: - b998ae5e-8b56-40ca-8f80-fd4404e7e716: media default: revision_uid: - @@ -49,7 +47,7 @@ default: -

link to file

+

link to file

diff --git a/packages/drupal/test_content/content/node/7a928d7d-f9ad-4edb-aa22-2f535bc5c873.yml b/packages/drupal/test_content/content/node/7a928d7d-f9ad-4edb-aa22-2f535bc5c873.yml index f32e2133d..b09e4143f 100644 --- a/packages/drupal/test_content/content/node/7a928d7d-f9ad-4edb-aa22-2f535bc5c873.yml +++ b/packages/drupal/test_content/content/node/7a928d7d-f9ad-4edb-aa22-2f535bc5c873.yml @@ -4,6 +4,8 @@ _meta: uuid: 7a928d7d-f9ad-4edb-aa22-2f535bc5c873 bundle: page default_langcode: en + depends: + 5dfc1856-e9e4-4f02-9cd6-9d888870ce1a: media default: revision_uid: - @@ -43,12 +45,10 @@ default: body: - value: |- - + - -

- + format: gutenberg summary: '' diff --git a/packages/schema/src/schema.graphql b/packages/schema/src/schema.graphql index 6ada8a7f7..0422ad5cb 100644 --- a/packages/schema/src/schema.graphql +++ b/packages/schema/src/schema.graphql @@ -234,10 +234,11 @@ type BlockMarkup @type(id: "core/paragraph") { union Media @resolveEntityBundle = MediaImage | MediaVideo type MediaImage @type(id: "image") @entity(type: "media", bundle: "image") { - source(width: Int, height: Int, sizes: [[Int!]!]): ImageSource! + source(width: Int, height: Int, sizes: [[Int!]!], transform: String): ImageSource! @resolveProperty(path: "field_media_image.entity") @imageProps - @responsiveImage(height: "$height", width: "$width", sizes: "$sizes") + @focalPoint + @responsiveImage(height: "$height", width: "$width", sizes: "$sizes", transform: "$transform") alt: String! @resolveProperty(path: "field_media_image.alt") } diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index a13bd3622..f1fdaa509 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -328,6 +328,9 @@ importers: '@custom/ui': specifier: workspace:* version: link:../../packages/ui + fs-extra: + specifier: ^11.2.0 + version: 11.2.0 gatsby: specifier: ^5.13.1 version: 5.13.1(babel-eslint@10.1.0)(react-dom@18.2.0)(react@18.2.0)(typescript@4.9.5) diff --git a/tests/schema/specs/media.spec.ts b/tests/schema/specs/media.spec.ts index 3fdb2ea28..e3729f57a 100644 --- a/tests/schema/specs/media.spec.ts +++ b/tests/schema/specs/media.spec.ts @@ -17,7 +17,7 @@ test('Image', async () => { "data": { "_loadMediaImage": { "alt": "A beautiful landscape.", - "source": "{"src":"http:\\/\\/127.0.0.1:8000\\/sites\\/default\\/files\\/2023-04\\/landscape.jpg","width":2200,"height":1414,"originalSrc":"http:\\/\\/127.0.0.1:8000\\/sites\\/default\\/files\\/2023-04\\/landscape.jpg"}", + "source": "{"src":"http:\\/\\/127.0.0.1:8000\\/sites\\/default\\/files\\/2023-04\\/landscape.jpg","width":2200,"height":1414,"focalPoint":{"x":"1782","y":"1046"},"originalSrc":"http:\\/\\/127.0.0.1:8000\\/sites\\/default\\/files\\/2023-04\\/landscape.jpg"}", }, }, } From 0d32bf958c355678f9fd2145089db47dc6d6b493 Mon Sep 17 00:00:00 2001 From: nick Date: Tue, 10 Sep 2024 15:44:14 +0100 Subject: [PATCH 02/12] chore(SLB-585): add crop for hero image --- apps/website/gatsby-image.mjs | 37 +++++++++++++++----------- packages/schema/src/fragments/Page.gql | 1 + 2 files changed, 23 insertions(+), 15 deletions(-) diff --git a/apps/website/gatsby-image.mjs b/apps/website/gatsby-image.mjs index ba8482359..1c5dfcee4 100644 --- a/apps/website/gatsby-image.mjs +++ b/apps/website/gatsby-image.mjs @@ -17,7 +17,7 @@ function determineCropFocus(filename) { } function calculateCropArea(width, height, x, y) { - if ((x === 0 && y === 0) || (width === 0 || height === 0)) { + if ((x === 0 && y === 0) || width === 0 || height === 0) { return AREA_FALLBACK; } @@ -29,12 +29,12 @@ function calculateCropArea(width, height, x, y) { const adjustedY = y === height ? y - 1 : y; const adjustedX = x === width ? x - 1 : x; - const row = Math.floor(adjustedY / height * 3); - const col = Math.floor(adjustedX / width * 3); + const row = Math.floor((adjustedY / height) * 3); + const col = Math.floor((adjustedX / width) * 3); const areas = [ - ['northwest', 'north', 'northeast'], - ['west', 'center', 'east'], - ['southwest', 'south', 'southeast'], + ['NORTHWEST', 'NORTH', 'NORTHEAST'], + ['WEST', 'CENTER', 'EAST'], + ['SOUTHWEST', 'SOUTH', 'SOUTHEAST'], ]; // If we met an exception in the calculation of the area, @@ -73,13 +73,13 @@ export const responsiveImageSharp = async (originalImage, args, context) => { const breakpoints = args.sizes && args.sizes.length > 0 ? args.sizes.map((item) => { - // If the sizes array contains tuples, then just return the first item - // to be added to the breakpoints elements. - if (Array.isArray(item)) { - return item[0]; - } - return item; - }) + // If the sizes array contains tuples, then just return the first item + // to be added to the breakpoints elements. + if (Array.isArray(item)) { + return item[0]; + } + return item; + }) : undefined; const fluidFileResult = await fluid({ @@ -90,11 +90,18 @@ export const responsiveImageSharp = async (originalImage, args, context) => { quality: 90, srcSetBreakpoints: breakpoints, cropFocus: - determineCropFocus(responsiveImage.src) || calculateCropArea(width, height, responsiveImage.focalPoint.x, responsiveImage.focalPoint.y), + determineCropFocus(responsiveImage.src) || + calculateCropArea( + width, + height, + responsiveImage.focalPoint.x, + responsiveImage.focalPoint.y, + ), }, reporter: reporter, cache: cache, }); + console.log('fluidFileResult', fluidFileResult); responsiveImageResult.src = fluidFileResult.src; responsiveImageResult.width = fluidFileResult.presentationWidth; responsiveImageResult.height = fluidFileResult.presentationHeight; @@ -106,4 +113,4 @@ export const responsiveImageSharp = async (originalImage, args, context) => { console.error(`Error loading image ${responsiveImage.src}`, err); return JSON.stringify(responsiveImage); } -}; \ No newline at end of file +}; diff --git a/packages/schema/src/fragments/Page.gql b/packages/schema/src/fragments/Page.gql index e822c9c8d..aa17200b8 100644 --- a/packages/schema/src/fragments/Page.gql +++ b/packages/schema/src/fragments/Page.gql @@ -12,6 +12,7 @@ fragment Page on Page { image { source( width: 2000 + height: 500 sizes: [ [800, 800] [1200, 200] From bae018252c77e17146e98dba136682b9563d21af Mon Sep 17 00:00:00 2001 From: nick Date: Wed, 11 Sep 2024 19:33:28 +0100 Subject: [PATCH 03/12] chore(SLB-385): refactor responsiveImage resolver --- ...ntity_form_display.media.image.default.yml | 4 +- .../cms/config/sync/crop.type.focal_point.yml | 2 +- apps/website/gatsby-config.mjs | 10 -- apps/website/gatsby-image.mjs | 116 ----------------- apps/website/package.json | 1 - .../25086be7-ca5f-4ff8-9695-b9c71a676d4e.yml | 4 +- packages/schema/package.json | 2 + packages/schema/src/image.ts | 123 +++++++++++++++++- packages/schema/src/schema.graphql | 20 ++- .../schema/src/types/gatsby-plugin-sharp.d.ts | 3 + pnpm-lock.yaml | 94 ++++++++++--- 11 files changed, 225 insertions(+), 154 deletions(-) delete mode 100644 apps/website/gatsby-image.mjs create mode 100644 packages/schema/src/types/gatsby-plugin-sharp.d.ts diff --git a/apps/cms/config/sync/core.entity_form_display.media.image.default.yml b/apps/cms/config/sync/core.entity_form_display.media.image.default.yml index d29cabe8f..957e364f6 100644 --- a/apps/cms/config/sync/core.entity_form_display.media.image.default.yml +++ b/apps/cms/config/sync/core.entity_form_display.media.image.default.yml @@ -4,7 +4,7 @@ status: true dependencies: config: - field.field.media.image.field_media_image - - image.style.medium + - image.style.large - media.type.image module: - focal_point @@ -26,7 +26,7 @@ content: region: content settings: progress_indicator: throbber - preview_image_style: medium + preview_image_style: large preview_link: false offsets: '50,50' third_party_settings: { } diff --git a/apps/cms/config/sync/crop.type.focal_point.yml b/apps/cms/config/sync/crop.type.focal_point.yml index bdc914b47..3562b93e5 100644 --- a/apps/cms/config/sync/crop.type.focal_point.yml +++ b/apps/cms/config/sync/crop.type.focal_point.yml @@ -4,8 +4,8 @@ status: true dependencies: { } _core: default_config_hash: flCi9IdafdLXlJqvoHguutUOiC05-aynK4niYN4YZ3o -id: focal_point label: 'Focal point' +id: focal_point description: 'Crop type used by Focal point module.' aspect_ratio: '' soft_limit_width: null diff --git a/apps/website/gatsby-config.mjs b/apps/website/gatsby-config.mjs index 922237274..81457209e 100644 --- a/apps/website/gatsby-config.mjs +++ b/apps/website/gatsby-config.mjs @@ -6,21 +6,12 @@ // TS file name should be different from gastby-config.ts, otherwise Gatsby will // pick it up instead of the JS file. -import { responsiveImageSharp } from './gatsby-image.mjs'; - process.env.NETLIFY_URL = process.env.NETLIFY_URL || 'http://127.0.0.1:8000'; process.env.CLOUDINARY_API_KEY = process.env.CLOUDINARY_API_KEY || 'test'; process.env.CLOUDINARY_API_SECRET = process.env.CLOUDINARY_API_SECRET || 'test'; process.env.CLOUDINARY_CLOUDNAME = process.env.CLOUDINARY_CLOUDNAME || 'demo'; -async function saveResponsiveImageSharp(node, args, context) { - console.log('saving responsive image'); - if (node) { - return await responsiveImageSharp(node, args, context); - } -} - /** * * @type {import('gatsby').GatsbyConfig['plugins']} @@ -66,7 +57,6 @@ const plugins = [ resolve: '@amazeelabs/gatsby-source-silverback', options: { schema_configuration: './graphqlrc.yml', - directives: { responsiveImage: saveResponsiveImageSharp }, }, }, '@custom/cms', diff --git a/apps/website/gatsby-image.mjs b/apps/website/gatsby-image.mjs deleted file mode 100644 index 1c5dfcee4..000000000 --- a/apps/website/gatsby-image.mjs +++ /dev/null @@ -1,116 +0,0 @@ -import { fluid } from 'gatsby-plugin-sharp'; -import { createRemoteFileNode } from 'gatsby-source-filesystem'; - -const AREA_FALLBACK = 'attention'; - -function determineCropFocus(filename) { - if (filename.includes('.c.')) return 'center'; - if (filename.includes('.nw_.')) return 'northwest'; - if (filename.includes('.w.')) return 'west'; - if (filename.includes('.sw_.')) return 'southwest'; - if (filename.includes('.s.')) return 'south'; - if (filename.includes('.se_.')) return 'southeast'; - if (filename.includes('.e.')) return 'east'; - if (filename.includes('.ne_.')) return 'northeast'; - if (filename.includes('.n.')) return 'north'; - return null; -} - -function calculateCropArea(width, height, x, y) { - if ((x === 0 && y === 0) || width === 0 || height === 0) { - return AREA_FALLBACK; - } - - console.log('croparea', width, height, x, y); - // Adjust if Y === height or X === width as - // the division result will produce 1, - // which will not be a valid index - // for the areas array (out of bounds). - const adjustedY = y === height ? y - 1 : y; - const adjustedX = x === width ? x - 1 : x; - - const row = Math.floor((adjustedY / height) * 3); - const col = Math.floor((adjustedX / width) * 3); - const areas = [ - ['NORTHWEST', 'NORTH', 'NORTHEAST'], - ['WEST', 'CENTER', 'EAST'], - ['SOUTHWEST', 'SOUTH', 'SOUTHEAST'], - ]; - - // If we met an exception in the calculation of the area, - // use the fallback value. - if (!areas[row] || !areas[row][col]) { - return AREA_FALLBACK; - } - - return areas[row][col]; -} - -export const responsiveImageSharp = async (originalImage, args, context) => { - const responsiveImage = JSON.parse(originalImage); - const { cache, createNode, createNodeId, reporter } = context.api; - console.log('here responsiveImageSharp', responsiveImage, args, context); - try { - const responsiveImageResult = { - ...responsiveImage, - originalSrc: new URL(responsiveImage.src).pathname, - }; - - // If no config object is given, or no width is specified, we just return - // the original image url. - if (typeof args === 'undefined' || typeof args.width === 'undefined') { - return JSON.stringify(responsiveImageResult); - } - - const file = await createRemoteFileNode({ - url: responsiveImage.src, - cache: cache, - createNode: createNode, - createNodeId: createNodeId, - }); - const width = args.width; - const height = args.height || undefined; - const breakpoints = - args.sizes && args.sizes.length > 0 - ? args.sizes.map((item) => { - // If the sizes array contains tuples, then just return the first item - // to be added to the breakpoints elements. - if (Array.isArray(item)) { - return item[0]; - } - return item; - }) - : undefined; - - const fluidFileResult = await fluid({ - file, - args: { - maxWidth: width, - maxHeight: height, - quality: 90, - srcSetBreakpoints: breakpoints, - cropFocus: - determineCropFocus(responsiveImage.src) || - calculateCropArea( - width, - height, - responsiveImage.focalPoint.x, - responsiveImage.focalPoint.y, - ), - }, - reporter: reporter, - cache: cache, - }); - console.log('fluidFileResult', fluidFileResult); - responsiveImageResult.src = fluidFileResult.src; - responsiveImageResult.width = fluidFileResult.presentationWidth; - responsiveImageResult.height = fluidFileResult.presentationHeight; - responsiveImageResult.sizes = fluidFileResult.sizes; - responsiveImageResult.srcset = fluidFileResult.srcSet; - - return JSON.stringify(responsiveImageResult); - } catch (err) { - console.error(`Error loading image ${responsiveImage.src}`, err); - return JSON.stringify(responsiveImage); - } -}; diff --git a/apps/website/package.json b/apps/website/package.json index fb035d714..f5c87e9f9 100644 --- a/apps/website/package.json +++ b/apps/website/package.json @@ -15,7 +15,6 @@ "@custom/decap": "workspace:*", "@custom/schema": "workspace:*", "@custom/ui": "workspace:*", - "fs-extra": "^11.2.0", "gatsby": "^5.13.1", "gatsby-plugin-layout": "^4.13.0", "gatsby-plugin-manifest": "^5.13.0", diff --git a/packages/drupal/test_content/content/node/25086be7-ca5f-4ff8-9695-b9c71a676d4e.yml b/packages/drupal/test_content/content/node/25086be7-ca5f-4ff8-9695-b9c71a676d4e.yml index 7d4852082..0cceba025 100644 --- a/packages/drupal/test_content/content/node/25086be7-ca5f-4ff8-9695-b9c71a676d4e.yml +++ b/packages/drupal/test_content/content/node/25086be7-ca5f-4ff8-9695-b9c71a676d4e.yml @@ -4,6 +4,8 @@ _meta: uuid: 25086be7-ca5f-4ff8-9695-b9c71a676d4e bundle: page default_langcode: en + depends: + b998ae5e-8b56-40ca-8f80-fd4404e7e716: media default: revision_uid: - @@ -47,7 +49,7 @@ default: -

link to file

+

link to file

diff --git a/packages/schema/package.json b/packages/schema/package.json index 31e80ec03..61c7be268 100644 --- a/packages/schema/package.json +++ b/packages/schema/package.json @@ -68,6 +68,8 @@ "@amazeelabs/scalars": "^1.6.13", "@swc/cli": "^0.1.63", "@swc/core": "^1.3.102", + "gatsby-plugin-sharp": "^5.13.0", + "gatsby-source-filesystem": "^5.13.0", "image-size": "^1.1.1", "mime-types": "^2.1.35" } diff --git a/packages/schema/src/image.ts b/packages/schema/src/image.ts index f0190975c..a8464d846 100644 --- a/packages/schema/src/image.ts +++ b/packages/schema/src/image.ts @@ -1,9 +1,13 @@ import type { GraphQLFieldResolver } from 'graphql'; import sizeOf from 'image-size'; import { lookup } from 'mime-types'; +import { fluid } from 'gatsby-plugin-sharp'; +import { createRemoteFileNode } from 'gatsby-source-filesystem'; + +const AREA_FALLBACK = 'attention'; export const imageProps: GraphQLFieldResolver = (source) => { - // If its a decap image, process it. + // If it's a Decap image, process it. // Otherwise, it comes from Drupal and // already has all necessary props. if (source && source.startsWith('/apps/decap')) { @@ -45,3 +49,120 @@ export const imageProps: GraphQLFieldResolver = (source) => { } return source; }; + +function determineCropFocus(filename: string) { + if (filename.includes('.c.')) return 'center'; + if (filename.includes('.nw_.')) return 'northwest'; + if (filename.includes('.w.')) return 'west'; + if (filename.includes('.sw_.')) return 'southwest'; + if (filename.includes('.s.')) return 'south'; + if (filename.includes('.se_.')) return 'southeast'; + if (filename.includes('.e.')) return 'east'; + if (filename.includes('.ne_.')) return 'northeast'; + if (filename.includes('.n.')) return 'north'; + return null; +} + +const calculateCropArea = ( + width: number, + height: number, + x: number, + y: number, +) => { + if ((x === 0 && y === 0) || width === 0 || height === 0) { + return AREA_FALLBACK; + } + // Adjust if Y === height or X === width as + // the division result will produce 1, + // which will not be a valid index + // for the areas array (out of bounds). + const adjustedY = y === height ? y - 1 : y; + const adjustedX = x === width ? x - 1 : x; + + const row = Math.floor((adjustedY / height) * 3); + const col = Math.floor((adjustedX / width) * 3); + const areas = [ + ['northwest', 'north', 'northeast'], + ['west', 'center', 'east'], + ['southwest', 'south', 'southeast'], + ]; + + // If we met an exception in the calculation of the area, + // use the fallback value. + if (!areas[row] || !areas[row][col]) { + return AREA_FALLBACK; + } + + return areas[row][col]; +}; + +export const responsiveImage: GraphQLFieldResolver = async ( + originalImage, + args, + context, +) => { + const responsiveImage = JSON.parse(originalImage); + const { cache, createNode, createNodeId, reporter } = context.api; + try { + const responsiveImageResult = { + ...responsiveImage, + originalSrc: new URL(responsiveImage.src).pathname, + }; + + // If no config object is given, or no width is specified, we just return + // the original image url. + if (typeof args === 'undefined' || typeof args.width === 'undefined') { + return JSON.stringify(responsiveImageResult); + } + + const file = await createRemoteFileNode({ + url: responsiveImage.src, + cache: cache, + createNode: createNode, + createNodeId: createNodeId, + }); + const width = args.width; + const height = args.height || undefined; + const breakpoints = + args.sizes && args.sizes.length > 0 + ? args.sizes.map((item: any) => { + // If the sizes array contains tuples, then just return the first item + // to be added to the breakpoints elements. + if (Array.isArray(item)) { + return item[0]; + } + return item; + }) + : undefined; + + const fluidFileResult = await fluid({ + file, + args: { + maxWidth: width, + maxHeight: height, + quality: 90, + srcSetBreakpoints: breakpoints, + cropFocus: + determineCropFocus(responsiveImage.src) || + calculateCropArea( + parseInt(width), + parseInt(height), + parseInt(responsiveImage.focalPoint.x), + parseInt(responsiveImage.focalPoint.y), + ), + }, + reporter: reporter, + cache: cache, + }); + responsiveImageResult.src = fluidFileResult.src; + responsiveImageResult.width = fluidFileResult.presentationWidth; + responsiveImageResult.height = fluidFileResult.presentationHeight; + responsiveImageResult.sizes = fluidFileResult.sizes; + responsiveImageResult.srcset = fluidFileResult.srcSet; + + return JSON.stringify(responsiveImageResult); + } catch (err) { + console.error(`Error loading image ${responsiveImage.src}`, err); + return JSON.stringify(responsiveImage); + } +}; diff --git a/packages/schema/src/schema.graphql b/packages/schema/src/schema.graphql index 0422ad5cb..1b197ce1d 100644 --- a/packages/schema/src/schema.graphql +++ b/packages/schema/src/schema.graphql @@ -30,10 +30,9 @@ implementation(gatsby): ./image.js#imageProps directive @imageProps repeatable on FIELD_DEFINITION | SCALAR | UNION | ENUM | INTERFACE | OBJECT """ -Retrieve the properties of an image. -TODO: Move to a shared "image" package. +Override the image saving to add focal point info -implementation(gatsby): @amazeelabs/gatsby-source-silverback#responsiveImageSharp +implementation(gatsby): ./image.js#responsiveImage """ directive @responsiveImage( height: String @@ -234,11 +233,22 @@ type BlockMarkup @type(id: "core/paragraph") { union Media @resolveEntityBundle = MediaImage | MediaVideo type MediaImage @type(id: "image") @entity(type: "media", bundle: "image") { - source(width: Int, height: Int, sizes: [[Int!]!], transform: String): ImageSource! + source( + width: Int + height: Int + sizes: [[Int!]!] + transform: String + ): ImageSource! @resolveProperty(path: "field_media_image.entity") @imageProps @focalPoint - @responsiveImage(height: "$height", width: "$width", sizes: "$sizes", transform: "$transform") + @responsiveImage( + height: "$height" + width: "$width" + sizes: "$sizes" + transform: "$transform" + ) + alt: String! @resolveProperty(path: "field_media_image.alt") } diff --git a/packages/schema/src/types/gatsby-plugin-sharp.d.ts b/packages/schema/src/types/gatsby-plugin-sharp.d.ts new file mode 100644 index 000000000..549bbecbe --- /dev/null +++ b/packages/schema/src/types/gatsby-plugin-sharp.d.ts @@ -0,0 +1,3 @@ +declare module 'gatsby-plugin-sharp' { + function fluid(props: any): any; +} diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index f1fdaa509..c88005c55 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -16,7 +16,7 @@ importers: devDependencies: '@commitlint/cli': specifier: ^18.4.3 - version: 18.4.3(@types/node@20.11.17)(typescript@5.3.3) + version: 18.4.3(@types/node@18.19.31)(typescript@5.3.3) '@commitlint/config-conventional': specifier: ^18.4.3 version: 18.4.3 @@ -64,7 +64,7 @@ importers: version: 5.3.3 vitest: specifier: ^1.1.1 - version: 1.1.1(@types/node@20.11.17) + version: 1.1.1(@types/node@18.19.31) apps/cms: dependencies: @@ -328,9 +328,6 @@ importers: '@custom/ui': specifier: workspace:* version: link:../../packages/ui - fs-extra: - specifier: ^11.2.0 - version: 11.2.0 gatsby: specifier: ^5.13.1 version: 5.13.1(babel-eslint@10.1.0)(react-dom@18.2.0)(react@18.2.0)(typescript@4.9.5) @@ -495,6 +492,12 @@ importers: '@swc/core': specifier: ^1.3.102 version: 1.3.102 + gatsby-plugin-sharp: + specifier: 5.13.1 + version: 5.13.1(gatsby@5.13.1)(graphql@16.8.1) + gatsby-source-filesystem: + specifier: ^5.13.0 + version: 5.13.0(gatsby@5.13.1) image-size: specifier: ^1.1.1 version: 1.1.1 @@ -2756,14 +2759,14 @@ packages: resolution: {integrity: sha512-Ir+AOibqzrIsL6ajt3Rz3LskB7OiMVHqltZmspbW/TJuTVuyOMirVqAkjfY6JISiLHgyNqicAC8AyHHGzNd/dA==} engines: {node: '>=0.1.90'} - /@commitlint/cli@18.4.3(@types/node@20.11.17)(typescript@5.3.3): + /@commitlint/cli@18.4.3(@types/node@18.19.31)(typescript@5.3.3): resolution: {integrity: sha512-zop98yfB3A6NveYAZ3P1Mb6bIXuCeWgnUfVNkH4yhIMQpQfzFwseadazOuSn0OOfTt0lWuFauehpm9GcqM5lww==} engines: {node: '>=v18'} hasBin: true dependencies: '@commitlint/format': 18.6.1 '@commitlint/lint': 18.6.1 - '@commitlint/load': 18.6.1(@types/node@20.11.17)(typescript@5.3.3) + '@commitlint/load': 18.6.1(@types/node@18.19.31)(typescript@5.3.3) '@commitlint/read': 18.6.1 '@commitlint/types': 18.6.1 execa: 5.1.1 @@ -2834,7 +2837,7 @@ packages: '@commitlint/types': 18.6.1 dev: true - /@commitlint/load@18.6.1(@types/node@20.11.17)(typescript@5.3.3): + /@commitlint/load@18.6.1(@types/node@18.19.31)(typescript@5.3.3): resolution: {integrity: sha512-p26x8734tSXUHoAw0ERIiHyW4RaI4Bj99D8YgUlVV9SedLf8hlWAfyIFhHRIhfPngLlCe0QYOdRKYFt8gy56TA==} engines: {node: '>=v18'} dependencies: @@ -2844,7 +2847,7 @@ packages: '@commitlint/types': 18.6.1 chalk: 4.1.2 cosmiconfig: 8.3.6(typescript@5.3.3) - cosmiconfig-typescript-loader: 5.0.0(@types/node@20.11.17)(cosmiconfig@8.3.6)(typescript@5.3.3) + cosmiconfig-typescript-loader: 5.0.0(@types/node@18.19.31)(cosmiconfig@8.3.6)(typescript@5.3.3) lodash.isplainobject: 4.0.6 lodash.merge: 4.6.2 lodash.uniq: 4.5.0 @@ -14752,7 +14755,7 @@ packages: object-assign: 4.1.1 vary: 1.1.2 - /cosmiconfig-typescript-loader@5.0.0(@types/node@20.11.17)(cosmiconfig@8.3.6)(typescript@5.3.3): + /cosmiconfig-typescript-loader@5.0.0(@types/node@18.19.31)(cosmiconfig@8.3.6)(typescript@5.3.3): resolution: {integrity: sha512-+8cK7jRAReYkMwMiG+bxhcNKiHJDM6bR9FD/nGBXOWdMLuYawjF5cGrtLilJ+LGd3ZjCXnJjR5DkfWPoIVlqJA==} engines: {node: '>=v16'} peerDependencies: @@ -14760,7 +14763,7 @@ packages: cosmiconfig: '>=8.2' typescript: '>=4' dependencies: - '@types/node': 20.11.17 + '@types/node': 18.19.31 cosmiconfig: 8.3.6(typescript@5.3.3) jiti: 1.21.0 typescript: 5.3.3 @@ -33051,6 +33054,27 @@ packages: - terser dev: true + /vite-node@1.1.1(@types/node@18.19.31): + resolution: {integrity: sha512-2bGE5w4jvym5v8llF6Gu1oBrmImoNSs4WmRVcavnG2me6+8UQntTqLiAMFyiAobp+ZXhj5ZFhI7SmLiFr/jrow==} + engines: {node: ^18.0.0 || >=20.0.0} + hasBin: true + dependencies: + cac: 6.7.14 + debug: 4.3.4 + pathe: 1.1.2 + picocolors: 1.0.0 + vite: 5.2.8(@types/node@18.19.31) + transitivePeerDependencies: + - '@types/node' + - less + - lightningcss + - sass + - stylus + - sugarss + - supports-color + - terser + dev: true + /vite-node@1.1.1(@types/node@20.11.17): resolution: {integrity: sha512-2bGE5w4jvym5v8llF6Gu1oBrmImoNSs4WmRVcavnG2me6+8UQntTqLiAMFyiAobp+ZXhj5ZFhI7SmLiFr/jrow==} engines: {node: ^18.0.0 || >=20.0.0} @@ -33263,6 +33287,42 @@ packages: fsevents: 2.3.3 dev: true + /vite@5.2.8(@types/node@18.19.31): + resolution: {integrity: sha512-OyZR+c1CE8yeHw5V5t59aXsUPPVTHMDjEZz8MgguLL/Q7NblxhZUlTu9xSPqlsUO/y+X7dlU05jdhvyycD55DA==} + engines: {node: ^18.0.0 || >=20.0.0} + hasBin: true + peerDependencies: + '@types/node': ^18.0.0 || >=20.0.0 + less: '*' + lightningcss: ^1.21.0 + sass: '*' + stylus: '*' + sugarss: '*' + terser: ^5.4.0 + peerDependenciesMeta: + '@types/node': + optional: true + less: + optional: true + lightningcss: + optional: true + sass: + optional: true + stylus: + optional: true + sugarss: + optional: true + terser: + optional: true + dependencies: + '@types/node': 18.19.31 + esbuild: 0.20.2 + postcss: 8.4.38 + rollup: 4.14.1 + optionalDependencies: + fsevents: 2.3.3 + dev: true + /vite@5.2.8(@types/node@20.11.17): resolution: {integrity: sha512-OyZR+c1CE8yeHw5V5t59aXsUPPVTHMDjEZz8MgguLL/Q7NblxhZUlTu9xSPqlsUO/y+X7dlU05jdhvyycD55DA==} engines: {node: ^18.0.0 || >=20.0.0} @@ -33619,7 +33679,7 @@ packages: - terser dev: true - /vitest@1.1.1(@types/node@20.11.17): + /vitest@1.1.1(@types/node@18.19.31): resolution: {integrity: sha512-Ry2qs4UOu/KjpXVfOCfQkTnwSXYGrqTbBZxw6reIYEFjSy1QUARRg5pxiI5BEXy+kBVntxUYNMlq4Co+2vD3fQ==} engines: {node: ^18.0.0 || >=20.0.0} hasBin: true @@ -33644,7 +33704,7 @@ packages: jsdom: optional: true dependencies: - '@types/node': 20.11.17 + '@types/node': 18.19.31 '@vitest/expect': 1.1.1 '@vitest/runner': 1.1.1 '@vitest/snapshot': 1.1.1 @@ -33663,8 +33723,8 @@ packages: strip-literal: 1.3.0 tinybench: 2.6.0 tinypool: 0.8.3 - vite: 5.2.8(@types/node@20.11.17) - vite-node: 1.1.1(@types/node@20.11.17) + vite: 5.2.8(@types/node@18.19.31) + vite-node: 1.1.1(@types/node@18.19.31) why-is-node-running: 2.2.2 transitivePeerDependencies: - less @@ -34584,9 +34644,9 @@ packages: resolution: {integrity: sha512-2cN1tPkDVkwCy5ickKrI7vijSjPksFRfqS6237NzT0vqSsztTNnQdHw9mmN7uBdk3gceVXU0a+21jFzFzAc9+g==} engines: {node: '>=12.7.0'} peerDependencies: - '@types/react': 18.3.3 + '@types/react': '>=16.8' immer: '>=9.0.6' - react: 19.0.0-rc.0 + react: '>=16.8' peerDependenciesMeta: '@types/react': optional: true From bd2bd744bf22a69967288ff05c42817fd9bab4f4 Mon Sep 17 00:00:00 2001 From: Christophe Jossart Date: Fri, 13 Sep 2024 11:12:33 +0200 Subject: [PATCH 04/12] fix: max_depth unserialize(), bump graphql to 4.9.0 --- apps/cms/composer.lock | 31 +++++++++++++++++++------------ 1 file changed, 19 insertions(+), 12 deletions(-) diff --git a/apps/cms/composer.lock b/apps/cms/composer.lock index 9c68da018..fb183f102 100644 --- a/apps/cms/composer.lock +++ b/apps/cms/composer.lock @@ -3400,32 +3400,39 @@ }, { "name": "drupal/graphql", - "version": "4.6.0", + "version": "4.9.0", "source": { "type": "git", "url": "https://git.drupalcode.org/project/graphql.git", - "reference": "8.x-4.6" + "reference": "8.x-4.9" }, "dist": { "type": "zip", - "url": "https://ftp.drupal.org/files/projects/graphql-8.x-4.6.zip", - "reference": "8.x-4.6", - "shasum": "629eb1d405ea35460e6f94bd46a20316adf4fbe9" + "url": "https://ftp.drupal.org/files/projects/graphql-8.x-4.9.zip", + "reference": "8.x-4.9", + "shasum": "8045e8f07e82a55f229235bb614ba7df87e718ef" }, "require": { - "drupal/core": "^9.3 || ^10", - "drupal/typed_data": "*", - "php": ">=7.3", + "drupal/core": "^10.2 || ^11", + "drupal/typed_data": "^1.0 || ^2.0", + "php": ">=8.1", "webonyx/graphql-php": "^14.8.0" }, "require-dev": { - "drupal/node-node": "*" + "drupal/node-node": "*", + "drupal/redirect": "^1.0", + "jangregor/phpstan-prophecy": "^1.0.0", + "mglaman/phpstan-drupal": "^1.1.2", + "phpstan/extension-installer": "^1.0", + "phpstan/phpstan": "^1.11.2", + "phpstan/phpstan-deprecation-rules": "^1.0.0", + "phpstan/phpstan-phpunit": "^1.0.0" }, "type": "drupal-module", "extra": { "drupal": { - "version": "8.x-4.6", - "datestamp": "1699463388", + "version": "8.x-4.9", + "datestamp": "1726057421", "security-coverage": { "status": "covered", "message": "Covered by Drupal's security advisory policy" @@ -3450,7 +3457,7 @@ "homepage": "https://www.drupal.org/user/612814" }, { - "name": "Kingdutch", + "name": "kingdutch", "homepage": "https://www.drupal.org/user/1868952" }, { From 9b09bf4e3483fc1b410e95b26c361b0a313abaee Mon Sep 17 00:00:00 2001 From: Philipp Melab Date: Sun, 15 Sep 2024 21:28:53 +0200 Subject: [PATCH 05/12] feat(SLB-385): use portrait cut for mobile hero --- packages/schema/src/fragments/Page.gql | 3 ++- .../ui/src/components/Organisms/PageHero.tsx | 27 ++++++++++++------- .../ui/src/components/Routes/Page.stories.tsx | 7 +++-- 3 files changed, 25 insertions(+), 12 deletions(-) diff --git a/packages/schema/src/fragments/Page.gql b/packages/schema/src/fragments/Page.gql index aa17200b8..ec6e98c79 100644 --- a/packages/schema/src/fragments/Page.gql +++ b/packages/schema/src/fragments/Page.gql @@ -10,7 +10,8 @@ fragment Page on Page { headline lead image { - source( + portrait: source(width: 1200, height: 2400) + landscape: source( width: 2000 height: 500 sizes: [ diff --git a/packages/ui/src/components/Organisms/PageHero.tsx b/packages/ui/src/components/Organisms/PageHero.tsx index 471b75609..a8a7a798a 100644 --- a/packages/ui/src/components/Organisms/PageHero.tsx +++ b/packages/ui/src/components/Organisms/PageHero.tsx @@ -17,15 +17,24 @@ export function PageHero(props: NonNullable) { function DefaultHero(props: NonNullable) { return ( <> -
+
{props.image ? ( - {props.image.alt} + <> + {props.image.alt} + {props.image.alt} + ) : null}
@@ -73,7 +82,7 @@ function FormHero(props: NonNullable) { <> {props.image.alt} Date: Sun, 15 Sep 2024 21:29:54 +0200 Subject: [PATCH 06/12] chore(SLB-385): import order --- packages/schema/src/image.ts | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/packages/schema/src/image.ts b/packages/schema/src/image.ts index a8464d846..ae08fbe5b 100644 --- a/packages/schema/src/image.ts +++ b/packages/schema/src/image.ts @@ -1,8 +1,11 @@ +import { readFileSync } from 'fs'; +import { fluid } from 'gatsby-plugin-sharp'; +import { + createRemoteFileNode, +} from 'gatsby-source-filesystem'; import type { GraphQLFieldResolver } from 'graphql'; import sizeOf from 'image-size'; import { lookup } from 'mime-types'; -import { fluid } from 'gatsby-plugin-sharp'; -import { createRemoteFileNode } from 'gatsby-source-filesystem'; const AREA_FALLBACK = 'attention'; From ef79a6500d132571ae69fd15c9b20bde7f42fd7d Mon Sep 17 00:00:00 2001 From: Philipp Melab Date: Sun, 15 Sep 2024 21:36:27 +0200 Subject: [PATCH 07/12] fix(SLB-385): handle decap local images --- packages/schema/src/image.ts | 33 ++++++++++++++++++++++++++------- 1 file changed, 26 insertions(+), 7 deletions(-) diff --git a/packages/schema/src/image.ts b/packages/schema/src/image.ts index ae08fbe5b..130da83d9 100644 --- a/packages/schema/src/image.ts +++ b/packages/schema/src/image.ts @@ -1,6 +1,7 @@ import { readFileSync } from 'fs'; import { fluid } from 'gatsby-plugin-sharp'; import { + createFileNodeFromBuffer, createRemoteFileNode, } from 'gatsby-source-filesystem'; import type { GraphQLFieldResolver } from 'graphql'; @@ -99,6 +100,15 @@ const calculateCropArea = ( return areas[row][col]; }; +function isValidUrl(url: string) { + try { + new URL(url); + return true; + } catch (e) { + return false; + } +} + export const responsiveImage: GraphQLFieldResolver = async ( originalImage, args, @@ -109,7 +119,9 @@ export const responsiveImage: GraphQLFieldResolver = async ( try { const responsiveImageResult = { ...responsiveImage, - originalSrc: new URL(responsiveImage.src).pathname, + originalSrc: isValidUrl(responsiveImage.src) + ? new URL(responsiveImage.src).pathname + : responsiveImage.src, }; // If no config object is given, or no width is specified, we just return @@ -118,12 +130,19 @@ export const responsiveImage: GraphQLFieldResolver = async ( return JSON.stringify(responsiveImageResult); } - const file = await createRemoteFileNode({ - url: responsiveImage.src, - cache: cache, - createNode: createNode, - createNodeId: createNodeId, - }); + const file = isValidUrl(responsiveImage.src) + ? await createRemoteFileNode({ + url: responsiveImage.src, + cache: cache, + createNode: createNode, + createNodeId: createNodeId, + }) + : await createFileNodeFromBuffer({ + buffer: readFileSync(responsiveImage.src), + cache: cache, + createNode: createNode, + createNodeId: createNodeId, + }); const width = args.width; const height = args.height || undefined; const breakpoints = From 52d260e2cbc808cd2b3f45170b72a9bbc2e7df8c Mon Sep 17 00:00:00 2001 From: Philipp Melab Date: Sun, 15 Sep 2024 21:37:53 +0200 Subject: [PATCH 08/12] fix(SLB-385): broken crop area calculation --- packages/schema/src/image.ts | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/packages/schema/src/image.ts b/packages/schema/src/image.ts index 130da83d9..d8a8cc7e9 100644 --- a/packages/schema/src/image.ts +++ b/packages/schema/src/image.ts @@ -167,10 +167,10 @@ export const responsiveImage: GraphQLFieldResolver = async ( cropFocus: determineCropFocus(responsiveImage.src) || calculateCropArea( - parseInt(width), - parseInt(height), - parseInt(responsiveImage.focalPoint.x), - parseInt(responsiveImage.focalPoint.y), + parseInt(responsiveImage.width), + parseInt(responsiveImage.height), + parseInt(responsiveImage.focalPoint?.x), + parseInt(responsiveImage.focalPoint?.y), ), }, reporter: reporter, From 3dc80150fe6006db2bf3e0076d7be2c47abcc39e Mon Sep 17 00:00:00 2001 From: Philipp Melab Date: Tue, 17 Sep 2024 05:56:09 +0200 Subject: [PATCH 09/12] feat(SLB-385): simple local focalpoint --- packages/drupal/custom/src/FocalPoint.php | 39 +++++++++++++++++++++++ packages/schema/src/schema.graphql | 5 +++ 2 files changed, 44 insertions(+) create mode 100644 packages/drupal/custom/src/FocalPoint.php diff --git a/packages/drupal/custom/src/FocalPoint.php b/packages/drupal/custom/src/FocalPoint.php new file mode 100644 index 000000000..afe2e6080 --- /dev/null +++ b/packages/drupal/custom/src/FocalPoint.php @@ -0,0 +1,39 @@ +value instanceof File) { + $filePath = $args->value->getFileUri(); + + $crop = Crop::findCrop($filePath, 'focal_point'); + $x = $crop?->x->value; + $y = $crop?->y->value; + if ($x && $y) { + return [ + $x, + $y, + ]; + } + } + } + +} diff --git a/packages/schema/src/schema.graphql b/packages/schema/src/schema.graphql index c05a2f82b..0d11fb7af 100644 --- a/packages/schema/src/schema.graphql +++ b/packages/schema/src/schema.graphql @@ -7,6 +7,11 @@ implementation(drupal): custom.menus::getMenuTranslations """ directive @menuTranslations(menu_id: String!) on FIELD_DEFINITION +""" +implementation(drupal): \Drupal\custom\FocalPoint::getFocalPoint +""" +directive @focalPoint on FIELD_DEFINITION + """ implementation(drupal): custom.translatables::all """ From abd1dd3e2b4dea32a89d0bc130ce4759e638c8af Mon Sep 17 00:00:00 2001 From: Philipp Melab Date: Tue, 17 Sep 2024 05:56:32 +0200 Subject: [PATCH 10/12] chore: upgrade @amazeelabs/image for simple focalpoint typing --- packages/ui/package.json | 2 +- pnpm-lock.yaml | 106 ++++++++++++++++++++++++++++++--------- 2 files changed, 83 insertions(+), 25 deletions(-) diff --git a/packages/ui/package.json b/packages/ui/package.json index 2d5b4e068..a070d13ba 100644 --- a/packages/ui/package.json +++ b/packages/ui/package.json @@ -39,7 +39,7 @@ "report": "mkdir -p coverage/storybook && nyc report --reporter=lcov -t coverage/storybook --report-dir coverage/storybook" }, "dependencies": { - "@amazeelabs/image": "^1.4.2", + "@amazeelabs/image": "^1.4.3", "@amazeelabs/react-intl": "^1.1.1", "@amazeelabs/silverback-iframe": "^1.3.0", "@custom/schema": "workspace:*", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 80d844f2c..9b763eb5c 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -546,8 +546,8 @@ importers: packages/ui: dependencies: '@amazeelabs/image': - specifier: ^1.4.2 - version: 1.4.2(react-dom@19.0.0-rc-7771d3a7-20240827)(react@19.0.0-rc-7771d3a7-20240827)(webpack@5.91.0) + specifier: ^1.4.3 + version: 1.4.3(react-dom@19.0.0-rc-7771d3a7-20240827)(react@19.0.0-rc-7771d3a7-20240827)(webpack@5.91.0) '@amazeelabs/react-intl': specifier: ^1.1.1 version: 1.1.1(react-dom@19.0.0-rc-7771d3a7-20240827)(react@19.0.0-rc-7771d3a7-20240827)(typescript@5.4.5)(webpack@5.91.0) @@ -1084,8 +1084,8 @@ packages: lodash-es: 4.17.21 dev: false - /@amazeelabs/image@1.4.2(react-dom@19.0.0-rc-7771d3a7-20240827)(react@19.0.0-rc-7771d3a7-20240827)(webpack@5.91.0): - resolution: {integrity: sha512-29uxM8+rv/4hot8vzhGTfwZ/LrN406PeIICxadqTpUBxR9NhZEn6TERUSc2LnaO1oxKfaxJ/G1xnPyoXTGfSFw==} + /@amazeelabs/image@1.4.3(react-dom@19.0.0-rc-7771d3a7-20240827)(react@19.0.0-rc-7771d3a7-20240827)(webpack@5.91.0): + resolution: {integrity: sha512-LzzN/qO+jIOOSzczkwL9kXwffv+swXCqp73z2ktYgbeM5lNueqrzdxn4vZCPWwPp/Jayi4FM2leXFeFjxBrjyg==} peerDependencies: react: 19.0.0-rc-7771d3a7-20240827 react-dom: 19.0.0-rc-7771d3a7-20240827 @@ -8734,7 +8734,7 @@ packages: prettier: 3.2.5 prompts: 2.4.2 read-pkg-up: 7.0.1 - semver: 7.6.0 + semver: 7.6.2 strip-json-comments: 3.1.1 tempy: 3.1.0 tiny-invariant: 1.3.3 @@ -8832,7 +8832,7 @@ packages: pkg-dir: 5.0.0 pretty-hrtime: 1.0.3 resolve-from: 5.0.0 - semver: 7.6.0 + semver: 7.6.2 tempy: 1.0.1 tiny-invariant: 1.3.3 ts-dedent: 2.2.0 @@ -8938,7 +8938,7 @@ packages: pretty-hrtime: 1.0.3 prompts: 2.4.2 read-pkg-up: 7.0.1 - semver: 7.6.0 + semver: 7.6.2 telejson: 7.2.0 tiny-invariant: 1.3.3 ts-dedent: 2.2.0 @@ -11073,7 +11073,7 @@ packages: debug: 4.3.4 globby: 11.1.0 is-glob: 4.0.3 - semver: 7.6.0 + semver: 7.6.2 tsutils: 3.21.0(typescript@5.4.5) typescript: 5.4.5 transitivePeerDependencies: @@ -11094,7 +11094,7 @@ packages: globby: 11.1.0 is-glob: 4.0.3 minimatch: 9.0.3 - semver: 7.6.0 + semver: 7.6.2 ts-api-utils: 1.3.0(typescript@5.4.5) typescript: 5.4.5 transitivePeerDependencies: @@ -11116,7 +11116,7 @@ packages: globby: 11.1.0 is-glob: 4.0.3 minimatch: 9.0.3 - semver: 7.6.0 + semver: 7.6.2 ts-api-utils: 1.3.0(typescript@5.4.5) typescript: 5.4.5 transitivePeerDependencies: @@ -11177,7 +11177,7 @@ packages: '@typescript-eslint/types': 6.17.0 '@typescript-eslint/typescript-estree': 6.17.0(typescript@5.4.5) eslint: 8.57.0 - semver: 7.6.0 + semver: 7.6.2 transitivePeerDependencies: - supports-color - typescript @@ -11310,7 +11310,7 @@ packages: vite: ^4 || ^5 dependencies: '@swc/core': 1.4.13 - vite: 5.0.10(@types/node@18.0.0) + vite: 5.0.10(@types/node@18.15.3) transitivePeerDependencies: - '@swc/helpers' dev: true @@ -13425,7 +13425,7 @@ packages: engines: {node: '>=12'} dependencies: bin-version: 6.0.0 - semver: 7.6.0 + semver: 7.6.2 semver-truncate: 3.0.0 /bin-version@6.0.0: @@ -13598,7 +13598,6 @@ packages: electron-to-chromium: 1.5.23 node-releases: 2.0.18 update-browserslist-db: 1.1.0(browserslist@4.23.3) - dev: false /bser@2.1.1: resolution: {integrity: sha512-gQxTNE/GAfIIrmHLUE3oJyp5FO6HRBfhjnw4/wMmA63ZGDJnWBmgY/lyQBpnDUkGmAhbSe39tx2d/iTOAfglwQ==} @@ -13822,7 +13821,6 @@ packages: /caniuse-lite@1.0.30001660: resolution: {integrity: sha512-GacvNTTuATm26qC74pt+ad1fW15mlQ/zuTzzY1ZoIzECTP8HURDfF43kNxPgf7H1jmelCBQTTbBNxdSXOA7Bqg==} - dev: false /capital-case@1.0.4: resolution: {integrity: sha512-ds37W8CytHgwnhGGTi88pcPyR15qoNkOpYwmMMfnWqqWgESapLqvDx6huFjQ5vqWSn2Z06173XNA7LtMOeUh1A==} @@ -16630,7 +16628,6 @@ packages: /electron-to-chromium@1.5.23: resolution: {integrity: sha512-mBhODedOXg4v5QWwl21DjM5amzjmI1zw9EPrPK/5Wx7C8jt33bpZNrC7OhHUG3pxRtbLpr3W2dXT+Ph1SsfRZA==} - dev: false /emittery@0.13.1: resolution: {integrity: sha512-DeWwawk6r5yR9jFgnDKYt4sLS0LmHJJi3ZOnb5/JdbYwj3nW+FxQnHIjhBKz8YLC7oRNPVM9NQ47I3CVx34eqQ==} @@ -22651,7 +22648,7 @@ packages: jest-util: 29.7.0 natural-compare: 1.4.0 pretty-format: 29.7.0 - semver: 7.6.0 + semver: 7.6.2 transitivePeerDependencies: - supports-color dev: true @@ -23637,7 +23634,7 @@ packages: resolution: {integrity: sha512-hXdUTZYIVOt1Ex//jAQi+wTZZpUpwBj/0QsOzqegb3rGMMeJiSEu5xLHnYfBrRV4RH2+OCSOO95Is/7x1WJ4bw==} engines: {node: '>=10'} dependencies: - semver: 7.6.0 + semver: 7.6.2 /make-error@1.3.6: resolution: {integrity: sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==} @@ -25127,7 +25124,6 @@ packages: /node-releases@2.0.18: resolution: {integrity: sha512-d9VeXT4SJ7ZeOqGX6R5EM022wpL+eWPooLI+5UpWn2jCT1aosUQEhQP214x33Wkwx3JQMvIm+tIoVOdodFS40g==} - dev: false /node-source-walk@6.0.2: resolution: {integrity: sha512-jn9vOIK/nfqoFCcpK89/VCVaLg1IHE6UVfDOzvqmANaJ/rWCTEdH8RZ1V278nv2jr36BJdyQXIAavBLXpzdlag==} @@ -26107,7 +26103,6 @@ packages: /picocolors@1.1.0: resolution: {integrity: sha512-TQ92mBOW0l3LeMeyLV6mzy/kWr8lkd/hp3mTg7wYK7zJhuBStmGMBG0BdeDZS/dZx1IukaX6Bk11zcln25o1Aw==} - dev: false /picomatch@2.3.1: resolution: {integrity: sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==} @@ -28035,7 +28030,7 @@ packages: neo-async: 2.6.2 react: 19.0.0-rc-7771d3a7-20240827 react-dom: 19.0.0-rc-7771d3a7-20240827(react@19.0.0-rc-7771d3a7-20240827) - webpack: 5.91.0 + webpack: 5.91.0(@swc/core@1.3.102)(esbuild@0.20.2) webpack-sources: 3.2.3 /react-split-pane@0.1.92(react-dom@19.0.0-rc-7771d3a7-20240827)(react@19.0.0-rc-7771d3a7-20240827): @@ -29136,7 +29131,7 @@ packages: resolution: {integrity: sha512-LJWA9kSvMolR51oDE6PN3kALBNaUdkxzAGcexw8gjMA8xr5zUqK0JiR3CgARSqanYF3Z1YHvsErb1KDgh+v7Rg==} engines: {node: '>=12'} dependencies: - semver: 7.6.0 + semver: 7.6.2 /semver@5.7.2: resolution: {integrity: sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g==} @@ -29359,7 +29354,7 @@ packages: dependencies: color: 4.2.3 detect-libc: 2.0.3 - semver: 7.6.0 + semver: 7.6.2 optionalDependencies: '@img/sharp-darwin-arm64': 0.33.1 '@img/sharp-darwin-x64': 0.33.1 @@ -30655,6 +30650,31 @@ packages: ps-tree: 1.2.0 dev: false + /terser-webpack-plugin@5.3.10(@swc/core@1.3.102)(esbuild@0.20.2)(webpack@5.91.0): + resolution: {integrity: sha512-BKFPWlPDndPs+NGGCr1U59t0XScL5317Y0UReNrHaw9/FwhPENlq6bfgs+4yPfyP51vqC1bQ4rp1EfXW5ZSH9w==} + engines: {node: '>= 10.13.0'} + peerDependencies: + '@swc/core': '*' + esbuild: '*' + uglify-js: '*' + webpack: ^5.1.0 + peerDependenciesMeta: + '@swc/core': + optional: true + esbuild: + optional: true + uglify-js: + optional: true + dependencies: + '@jridgewell/trace-mapping': 0.3.25 + '@swc/core': 1.3.102 + esbuild: 0.20.2 + jest-worker: 27.5.1 + schema-utils: 3.3.0 + serialize-javascript: 6.0.2 + terser: 5.30.3 + webpack: 5.91.0(@swc/core@1.3.102)(esbuild@0.20.2) + /terser-webpack-plugin@5.3.10(@swc/core@1.3.102)(webpack@5.91.0): resolution: {integrity: sha512-BKFPWlPDndPs+NGGCr1U59t0XScL5317Y0UReNrHaw9/FwhPENlq6bfgs+4yPfyP51vqC1bQ4rp1EfXW5ZSH9w==} engines: {node: '>= 10.13.0'} @@ -31764,7 +31784,6 @@ packages: browserslist: 4.23.3 escalade: 3.1.2 picocolors: 1.1.0 - dev: false /update-check@1.5.4: resolution: {integrity: sha512-5YHsflzHP4t1G+8WGPlvKbJEbAJGCgw+Em+dGR1KmBUbr1J36SJBqlHLjR7oob7sco5hWHGQVcr9B2poIVDDTQ==} @@ -33146,6 +33165,45 @@ packages: - uglify-js dev: true + /webpack@5.91.0(@swc/core@1.3.102)(esbuild@0.20.2): + resolution: {integrity: sha512-rzVwlLeBWHJbmgTC/8TvAcu5vpJNII+MelQpylD4jNERPwpBJOE2lEcko1zJX3QJeLjTTAnQxn/OJ8bjDzVQaw==} + engines: {node: '>=10.13.0'} + hasBin: true + peerDependencies: + webpack-cli: '*' + peerDependenciesMeta: + webpack-cli: + optional: true + dependencies: + '@types/eslint-scope': 3.7.7 + '@types/estree': 1.0.5 + '@webassemblyjs/ast': 1.12.1 + '@webassemblyjs/wasm-edit': 1.12.1 + '@webassemblyjs/wasm-parser': 1.12.1 + acorn: 8.11.3 + acorn-import-assertions: 1.9.0(acorn@8.11.3) + browserslist: 4.23.3 + chrome-trace-event: 1.0.3 + enhanced-resolve: 5.16.0 + es-module-lexer: 1.5.0 + eslint-scope: 5.1.1 + events: 3.3.0 + glob-to-regexp: 0.4.1 + graceful-fs: 4.2.11 + json-parse-even-better-errors: 2.3.1 + loader-runner: 4.3.0 + mime-types: 2.1.35 + neo-async: 2.6.2 + schema-utils: 3.3.0 + tapable: 2.2.1 + terser-webpack-plugin: 5.3.10(@swc/core@1.3.102)(esbuild@0.20.2)(webpack@5.91.0) + watchpack: 2.4.1 + webpack-sources: 3.2.3 + transitivePeerDependencies: + - '@swc/core' + - esbuild + - uglify-js + /website-scraper@5.3.1: resolution: {integrity: sha512-gogqPXD2gVsxoyd2yRiympw3rA5GuEpD1CaDEJ/J8zzanx7hkbTtneoO1SGs436PpLbWVcUge+6APGLhzsuZPA==} engines: {node: '>=14.14'} From 90810f700ef25a9e138a858950cfadb7826bd319 Mon Sep 17 00:00:00 2001 From: Philipp Melab Date: Tue, 17 Sep 2024 05:57:03 +0200 Subject: [PATCH 11/12] feat(SLB-385): pass focalpoint into hero image --- packages/schema/src/fragments/Page.gql | 1 + packages/ui/src/components/Organisms/PageHero.tsx | 2 ++ 2 files changed, 3 insertions(+) diff --git a/packages/schema/src/fragments/Page.gql b/packages/schema/src/fragments/Page.gql index 19f2034ed..d024aec48 100644 --- a/packages/schema/src/fragments/Page.gql +++ b/packages/schema/src/fragments/Page.gql @@ -12,6 +12,7 @@ fragment Page on Page { image { url alt + focalPoint } ctaText ctaUrl diff --git a/packages/ui/src/components/Organisms/PageHero.tsx b/packages/ui/src/components/Organisms/PageHero.tsx index 8a207d371..af943b9da 100644 --- a/packages/ui/src/components/Organisms/PageHero.tsx +++ b/packages/ui/src/components/Organisms/PageHero.tsx @@ -23,6 +23,7 @@ function HeroImage(props: NonNullable['image']) { src={props.url} priority={true} width={3840} + focalPoint={props.focalPoint} className="absolute inset-0 -z-10 h-full w-full object-cover" data-test-id={'hero-image'} /> @@ -32,6 +33,7 @@ function HeroImage(props: NonNullable['image']) { priority={true} width={1200} height={2400} + focalPoint={props.focalPoint} className="absolute inset-0 -z-10 h-full w-full object-cover" data-test-id={'hero-image'} /> From fccf7c0e18bbd09e4967467a1ed6a805f1ab890d Mon Sep 17 00:00:00 2001 From: Philipp Melab Date: Tue, 17 Sep 2024 06:40:03 +0200 Subject: [PATCH 12/12] fix(SLB-285): integration test case should expect 2 hero images now --- tests/e2e/specs/drupal/blocks.spec.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/e2e/specs/drupal/blocks.spec.ts b/tests/e2e/specs/drupal/blocks.spec.ts index 2432ac6b7..9e8a79e38 100644 --- a/tests/e2e/specs/drupal/blocks.spec.ts +++ b/tests/e2e/specs/drupal/blocks.spec.ts @@ -8,7 +8,7 @@ test('All blocks are rendered', async ({ page }) => { // Hero await expect( page.locator('img[data-test-id=hero-image][alt="A beautiful landscape."]'), - ).toHaveCount(1); + ).toHaveCount(2); await expect( page.locator('h1:text("All kinds of blocks with maximum data")'), ).toHaveCount(1);