Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

SITES-13120 - Image Smart Crop support for remote assets in various Sites core components #2524

Merged
merged 40 commits into from
Jul 19, 2023
Merged
Show file tree
Hide file tree
Changes from 27 commits
Commits
Show all changes
40 commits
Select commit Hold shift + click to select a range
88eec1c
SITES-11947 (Image v3 integration with Polaris - WIP), SITES-12085 (P…
edoardo-goracci Apr 3, 2023
736539a
SITES-11947 (Image src updated with Polaris URL), SITES-12085 (Fixing…
edoardo-goracci Apr 4, 2023
db13399
Client-side code moved to ui-wcm-commons repo
edoardo-goracci Apr 13, 2023
61d0802
Cleanup of .eslintignore
LSantha Apr 13, 2023
f0d2ab9
NextGenDynamicMediaConfig injection in ImageImpl
edoardo-goracci Apr 17, 2023
8630d67
SITES-11947 - [Sites Polaris Picker][Editor] Image Component support …
LSantha Apr 13, 2023
0200cd4
- Removed old Polaris stuff from image.js
edoardo-goracci Apr 18, 2023
05d2b26
SITES-11947 - [Sites Polaris Picker][Editor] Image Component support …
LSantha Apr 19, 2023
dca3cab
SITES-11947 - [Sites Polaris Picker][Editor] Image Component support …
LSantha Apr 19, 2023
516e727
SITES-11947 - [Sites Polaris Picker][Editor] Image Component support …
LSantha Apr 19, 2023
3ecb177
SITES-11947 - [Sites Polaris Picker][Editor] Image Component support …
LSantha May 17, 2023
c8efb23
SITES-11947 - [Sites Polaris Picker][Editor] Image Component support …
LSantha May 18, 2023
9db1b46
SITES-11947 - [Sites Polaris Picker][Editor] Image Component support …
LSantha May 22, 2023
e489580
SITES-11947 - [Sites Polaris Picker][Editor] Image Component support …
LSantha May 22, 2023
ab335ff
SITES-11947 - [Sites Polaris Picker][Editor] Image Component support …
LSantha May 23, 2023
9cb46a2
SITES-11947 - [Sites Polaris Picker][Editor] Image Component support …
LSantha May 25, 2023
0a9b7d6
SITES-11947 - [Sites Polaris Picker][Editor] Image Component support …
LSantha May 26, 2023
1e60b7b
SITES-13120 : Add NextGen DM smart crop support.
Jun 3, 2023
0ec2bc9
add button
Jun 5, 2023
fe6259e
fix
Jun 7, 2023
9265c03
final
Jun 9, 2023
934af7d
fix
Jun 9, 2023
0abc12c
Add alt
Jun 12, 2023
e88b71d
fix alt
Jun 12, 2023
410e1b7
Add test
Jun 12, 2023
dea9c26
Fix css for aemcs
Jun 13, 2023
79852ae
Fix
Jun 22, 2023
9c73f4d
review
Jun 29, 2023
a1ac782
review
Jun 29, 2023
4afca3f
Update README.md
indra2gurjar Jun 29, 2023
e2829a9
Update README.md
indra2gurjar Jun 29, 2023
483765b
Merge branch 'main' into SITES-13120
indra2gurjar Jul 7, 2023
2d60c8f
Merge branch 'main' into SITES-13120
indra2gurjar Jul 12, 2023
6306411
Merge branch 'main' into SITES-13120
indra2gurjar Jul 14, 2023
fd71a66
Add http test for smartcrop.
Jul 17, 2023
92a3fe5
Add UI test for smart crop dialog.
Jul 17, 2023
7eb5499
Add UI test for NGDM smart crop.
Jul 18, 2023
76a7f96
fix for aemcs
Jul 18, 2023
7139ba3
Merge branch 'main' into SITES-13120
indra2gurjar Jul 18, 2023
527fa4b
fix
Jul 19, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,9 +17,9 @@ Set of standardized Web Content Management (WCM) components for [Adobe Experienc
* **[Component Library](https://www.adobe.com/go/aem_cmp_library):** A collection of examples to view the components in their various configurations.
* **[Component Documentation](https://docs.adobe.com/content/help/en/experience-manager-core-components/using/introduction.html):** For developers and authors, with details about each component.
* Get Started:
indra2gurjar marked this conversation as resolved.
Show resolved Hide resolved
- **[WKND Tutorial](https://docs.adobe.com/content/help/en/experience-manager-learn/getting-started-wknd-tutorial-develop/overview.html):** A two-day tutorial for building a new site.
- **[Summit Tutorial](https://expleague.azureedge.net/labs/L767/index.html):** A two-hour tutorial for building a new site (from a Lab at US Summit 2019).
- **[Gems Webinar](https://helpx.adobe.com/experience-manager/kt/eseminars/gems/AEM-Core-Components.html):** A guided tour of the Core Components (recorded on Dec 2018).
- **[WKND Tutorial](https://docs.adobe.com/content/help/en/experience-manager-learn/getting-started-wknd-tutorial-develop/overview.html):** A two-day tutorial for building a new site.
- **[Summit Tutorial](https://expleague.azureedge.net/labs/L767/index.html):** A two-hour tutorial for building a new site (from a Lab at US Summit 2019).
- **[Gems Webinar](https://helpx.adobe.com/experience-manager/kt/eseminars/gems/AEM-Core-Components.html):** A guided tour of the Core Components (recorded on Dec 2018).

## Features

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -63,12 +63,7 @@ public class ImageImpl extends com.adobe.cq.wcm.core.components.internal.models.
private static final String URI_WIDTH_PLACEHOLDER_ENCODED = "%7B.width%7D";
private static final String URI_WIDTH_PLACEHOLDER = "{.width}";
private static final String EMPTY_PIXEL = "data:image/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==";

private static final String PATH_PLACEHOLDER_ASSET_ID = "{asset-id}";
private static final String PATH_PLACEHOLDER_SEO_NAME = "{seo-name}";
private static final String PATH_PLACEHOLDER_FORMAT = "{format}";
private static final String DEFAULT_NGDM_ASSET_EXTENSION = "jpg";
private static final int DEFAULT_NGDM_ASSET_WIDTH = 640;
static final int DEFAULT_NGDM_ASSET_WIDTH = 640;

@Inject
@Optional
Expand Down Expand Up @@ -272,27 +267,21 @@ private boolean isNgdmSupportAvailable() {
return nextGenDynamicMediaConfig != null && nextGenDynamicMediaConfig.enabled() &&
StringUtils.isNotBlank(nextGenDynamicMediaConfig.getRepositoryId());
}

indra2gurjar marked this conversation as resolved.
Show resolved Hide resolved
private void initNextGenerationDynamicMedia() {
initResource();
properties = resource.getValueMap();
String fileReference = properties.get("fileReference", String.class);
String smartCrop = properties.get("smartCrop", String.class);
indra2gurjar marked this conversation as resolved.
Show resolved Hide resolved
if (isNgdmImageReference(fileReference)) {
Scanner scanner = new Scanner(fileReference);
scanner.useDelimiter("/");
String assetId = scanner.next();
scanner = new Scanner(scanner.next());
scanner.useDelimiter("\\.");
String assetName = scanner.hasNext() ? scanner.next() : assetId;
String assetExtension = scanner.hasNext() ? scanner.next() : DEFAULT_NGDM_ASSET_EXTENSION;
String imageDeliveryBasePath = nextGenDynamicMediaConfig.getImageDeliveryBasePath();
String imageDeliveryPath = imageDeliveryBasePath.replace(PATH_PLACEHOLDER_ASSET_ID, assetId);
imageDeliveryPath = imageDeliveryPath.replace(PATH_PLACEHOLDER_SEO_NAME, assetName);
imageDeliveryPath = imageDeliveryPath.replace(PATH_PLACEHOLDER_FORMAT, assetExtension);
ngdmImage = true;
int width = currentStyle.get(PN_DESIGN_RESIZE_WIDTH, DEFAULT_NGDM_ASSET_WIDTH);
String repositoryId = nextGenDynamicMediaConfig.getRepositoryId();
src = "https://" + repositoryId + imageDeliveryPath + "?width=" + width + "&preferwebp=true";
NextGenDMImageURIBuilder builder = new NextGenDMImageURIBuilder(nextGenDynamicMediaConfig, fileReference)
.withPreferWebp(true)
.withWidth(width);
if(StringUtils.isNotEmpty(smartCrop)) {
builder.withSmartCrop(smartCrop);
}
src = builder.build();
ngdmImage = true;
hasContent = true;
}
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,150 @@
/*~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
~ Copyright 2023 Adobe
~
~ Licensed under the Apache License, Version 2.0 (the "License");
~ you may not use this file except in compliance with the License.
~ You may obtain a copy of the License at
~
~ http://www.apache.org/licenses/LICENSE-2.0
~
~ Unless required by applicable law or agreed to in writing, software
~ distributed under the License is distributed on an "AS IS" BASIS,
~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
~ See the License for the specific language governing permissions and
~ limitations under the License.
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~*/
package com.adobe.cq.wcm.core.components.internal.models.v3;

import java.util.HashMap;
import java.util.Map;
import java.util.Scanner;

import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.adobe.cq.ui.wcm.commons.config.NextGenDynamicMediaConfig;

import static com.adobe.cq.wcm.core.components.internal.models.v3.ImageImpl.DEFAULT_NGDM_ASSET_WIDTH;

public class NextGenDMImageURIBuilder {
private static final Logger LOGGER = LoggerFactory.getLogger(NextGenDMImageURIBuilder.class);
private static final String PATH_PLACEHOLDER_ASSET_ID = "{asset-id}";
private static final String PATH_PLACEHOLDER_SEO_NAME = "{seo-name}";
private static final String PATH_PLACEHOLDER_FORMAT = "{format}";
private static final String DEFAULT_NGDM_ASSET_EXTENSION = "jpg";

private NextGenDynamicMediaConfig config;
private String fileReference;
private String smartCropAspectRatio;
private int width = DEFAULT_NGDM_ASSET_WIDTH;

private int height;
private boolean preferWebp = true;

public NextGenDMImageURIBuilder(NextGenDynamicMediaConfig config, String fileReference) {
this.config = config;
this.fileReference = fileReference;
}

/**
* Smart Crop aspect ratio string.
* @param smartCropAspectRatio - a string in "width:height" format;
*/
public NextGenDMImageURIBuilder withSmartCrop(String smartCropAspectRatio) {
this.smartCropAspectRatio = smartCropAspectRatio;
return this;
}

/**
* Image width
* @param width - an integer.
*/
public NextGenDMImageURIBuilder withWidth(int width) {
this.width = width;
return this;
}

/**
* Image height
* @param height - an integer.
*/
public NextGenDMImageURIBuilder withHeight(int height) {
this.height = height;
return this;
}


/**
* Set to use webp image format.
* @param preferWebp - should set preferwebp param.
*/
public NextGenDMImageURIBuilder withPreferWebp(boolean preferWebp) {
this.preferWebp = preferWebp;
return this;
}

/**
* Use this to create a NextGen Dynamic Media Image URI.
* @return a uri.
*/
public String build() {
if(StringUtils.isNotEmpty(this.fileReference) && this.config != null) {
Scanner scanner = new Scanner(this.fileReference);
scanner.useDelimiter("/");
String assetId = scanner.next();
scanner = new Scanner(scanner.next());
scanner.useDelimiter("\\.");
String assetName = scanner.hasNext() ? scanner.next() : assetId;
String assetExtension = scanner.hasNext() ? scanner.next() : DEFAULT_NGDM_ASSET_EXTENSION;
String imageDeliveryBasePath = this.config.getImageDeliveryBasePath();
String imageDeliveryPath = imageDeliveryBasePath.replace(PATH_PLACEHOLDER_ASSET_ID, assetId);
imageDeliveryPath = imageDeliveryPath.replace(PATH_PLACEHOLDER_SEO_NAME, assetName);
imageDeliveryPath = imageDeliveryPath.replace(PATH_PLACEHOLDER_FORMAT, assetExtension);
String repositoryId = this.config.getRepositoryId();
StringBuilder uriBuilder = new StringBuilder("https://" + repositoryId + imageDeliveryPath);
Map<String, String> params = new HashMap<>();
if(this.width > 0) {
params.put("width", Integer.toString(this.width));
}
if(this.height > 0) {
params.put("height", Integer.toString(this.height));
}
if(this.preferWebp) {
params.put("preferwebp", "true");
}
if (StringUtils.isNotEmpty(this.smartCropAspectRatio)) {
if (isValidSmartCrop(this.smartCropAspectRatio)) {
params.put("crop", String.format("%s,smart", this.smartCropAspectRatio));
} else {
LOGGER.error("Invalid smartCrop value at {}", this.smartCropAspectRatio);
indra2gurjar marked this conversation as resolved.
Show resolved Hide resolved
}
}
if(params.size() > 0) {
uriBuilder.append("?");
for(Map.Entry<String, String> entry: params.entrySet()) {
uriBuilder.append(entry.getKey() + "=" + entry.getValue());
uriBuilder.append("&");
}
uriBuilder.deleteCharAt(uriBuilder.length() - 1);
}
return uriBuilder.toString();
}
LOGGER.error("Invalid fileReference or NGDMConfig. fileReference = {}", this.fileReference);
indra2gurjar marked this conversation as resolved.
Show resolved Hide resolved
return null;
}

private boolean isValidSmartCrop(String smartCropStr) {
String[] crops = smartCropStr.split(":");
if (crops.length == 2) {
try {
Integer.parseInt(crops[0]);
Integer.parseInt(crops[1]);
return true;
} catch (NumberFormatException ex) {
return false;
}
}
return false;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
/*~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
~ Copyright 2023 Adobe
~
~ Licensed under the Apache License, Version 2.0 (the "License");
~ you may not use this file except in compliance with the License.
~ You may obtain a copy of the License at
~
~ http://www.apache.org/licenses/LICENSE-2.0
~
~ Unless required by applicable law or agreed to in writing, software
~ distributed under the License is distributed on an "AS IS" BASIS,
~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
~ See the License for the specific language governing permissions and
~ limitations under the License.
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~*/
package com.adobe.cq.wcm.core.components.internal.models.v3;

import javax.annotation.PostConstruct;
import javax.inject.Inject;

import org.apache.commons.lang3.StringUtils;
import org.apache.sling.api.SlingHttpServletRequest;
import org.apache.sling.api.resource.Resource;
import org.apache.sling.api.resource.ValueMap;
import org.apache.sling.models.annotations.Exporter;
import org.apache.sling.models.annotations.Model;
import org.apache.sling.models.annotations.Optional;
import org.apache.sling.models.annotations.injectorspecific.Self;
import org.apache.sling.models.annotations.injectorspecific.SlingObject;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.adobe.cq.export.json.ExporterConstants;
import com.adobe.cq.ui.wcm.commons.config.NextGenDynamicMediaConfig;
import com.adobe.cq.wcm.core.components.models.nextgendm.NextGenDMThumbnail;

import static com.adobe.cq.wcm.core.components.internal.models.v3.ImageImpl.isNgdmImageReference;

@Model(adaptables = SlingHttpServletRequest.class,
adapters = {NextGenDMThumbnail.class},
resourceType = NextGenDMThumbnailImpl.RESOURCE_TYPE)
@Exporter(name = ExporterConstants.SLING_MODEL_EXPORTER_NAME, extensions = ExporterConstants.SLING_MODEL_EXTENSION)
public class NextGenDMThumbnailImpl implements NextGenDMThumbnail {

private static final Logger LOGGER = LoggerFactory.getLogger(NextGenDMThumbnailImpl.class);
protected static final String RESOURCE_TYPE = "core/wcm/components/image/v3/image/nextgendmthumbnail";

/**
* The current resource.
*/
@SlingObject
protected Resource resource;

@Self
private SlingHttpServletRequest request;

@Inject
@Optional
private NextGenDynamicMediaConfig nextGenDynamicMediaConfig;

private String componentPath;

private String src;

private String altText = "";

@PostConstruct
private void initModel() {
componentPath = request.getRequestPathInfo().getSuffix();
Resource component = request.getResourceResolver().getResource(componentPath);
ValueMap properties = component.getValueMap();
String fileReference = properties.get("fileReference", String.class);
String smartCrop = properties.get("smartCrop", String.class);
ValueMap configs = resource.getValueMap();
int width = configs.get("width", 480);
int height = configs.get("height", 480);
altText = configs.get("alt", "image thumbnail");
if (isNgdmImageReference(fileReference)) {
NextGenDMImageURIBuilder builder = new NextGenDMImageURIBuilder(nextGenDynamicMediaConfig, fileReference)
.withPreferWebp(true)
.withWidth(width)
.withHeight(height);
if (StringUtils.isNotEmpty(smartCrop)) {
builder.withSmartCrop(smartCrop);
}
this.src = builder.build();
}
}

@Override
public String getSrc() {
return src;
}

@Override
public String getAlt() {
return altText;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
/*~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
~ Copyright 2023 Adobe
~
~ Licensed under the Apache License, Version 2.0 (the "License");
~ you may not use this file except in compliance with the License.
~ You may obtain a copy of the License at
~
~ http://www.apache.org/licenses/LICENSE-2.0
~
~ Unless required by applicable law or agreed to in writing, software
~ distributed under the License is distributed on an "AS IS" BASIS,
~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
~ See the License for the specific language governing permissions and
~ limitations under the License.
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~*/
package com.adobe.cq.wcm.core.components.models.nextgendm;

import java.util.HashMap;
import java.util.Map;

public interface NextGenDMThumbnail {
default public String getSrc() {
return null;
}

default public String getAlt() {
return null;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -234,7 +234,7 @@ protected void testSimpleDecorativeImage() {
protected void testImageWithTwoOrMoreSmartSizes() {
context.contentPolicyMapping(resourceType,
"allowedRenditionWidths", new int[]{600, 700, 800, 2000, 2500},
"sizes", new String[]{"(max-width: 600px) 480px", "800px"});
"sizes", new String[]{"(max-width: 600px) 480px", "800px"});
String escapedResourcePath = AbstractImageTest.IMAGE0_PATH.replace("jcr:content", "_jcr_content");
Image image = getImageUnderTest(AbstractImageTest.IMAGE0_PATH);
assertEquals("Adobe Systems Logo and Wordmark in PNG format", image.getAlt());
Expand Down Expand Up @@ -306,7 +306,7 @@ protected void testEmptyImageDelegatingToFeaturedImage() {
protected void testImageWithMoreThanOneSmartSize() {
context.contentPolicyMapping(resourceType,
"allowedRenditionWidths", new int[]{600, 700, 800, 2000, 2500},
"sizes", new String[]{"(max-width: 600px) 480px", "800px"});
"sizes", new String[]{"(max-width: 600px) 480px", "800px"});
Image image = getImageUnderTest(AbstractImageTest.IMAGE0_PATH);
assertArrayEquals(new int[]{600, 700, 800, 2000, 2500}, image.getWidths());
assertTrue(image.isLazyEnabled());
Expand All @@ -329,8 +329,8 @@ protected void testImageWithNoSmartSize() {
@Test
protected void testGetUuid() {
context.contentPolicyMapping(resourceType,
"allowedRenditionWidths", new int[]{600, 700, 800, 2000, 2500},
"sizes", new String[]{"(max-width: 600px) 480px", "800px"});
"allowedRenditionWidths", new int[]{600, 700, 800, 2000, 2500},
"sizes", new String[]{"(max-width: 600px) 480px", "800px"});
Image image = getImageUnderTest(AbstractImageTest.IMAGE0_PATH);
assertEquals("60a1a56e-f3f4-4021-a7bf-ac7a51f0ffe5", image.getUuid());
Utils.testJSONExport(image, Utils.getTestExporterJSONPath(testBase, AbstractImageTest.IMAGE0_PATH));
Expand Down Expand Up @@ -652,7 +652,7 @@ void testSrcSetWithAssetDeliveryEnabledWithSmartSizes() {
"allowedRenditionWidths", new int[]{600, 800});
Image image = getImageUnderTest(IMAGE0_PATH);
String expectedSrcSet = MockAssetDelivery.BASE_URL + IMAGE_FILE_REFERENCE + "." + ASSET_NAME + ".png?width=600&quality=82&preferwebp=true 600w," +
MockAssetDelivery.BASE_URL + IMAGE_FILE_REFERENCE + "." + ASSET_NAME + ".png?width=800&quality=82&preferwebp=true 800w";
MockAssetDelivery.BASE_URL + IMAGE_FILE_REFERENCE + "." + ASSET_NAME + ".png?width=800&quality=82&preferwebp=true 800w";
assertEquals(expectedSrcSet , image.getSrcset());
}

Expand Down
Loading
Loading