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

feat: allow extension to provide aprimo config overrides #3

Merged
merged 18 commits into from
Jul 31, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
30 changes: 24 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ It also allows configuration to automatically upload selected image file renditi
- Upon selection the JSON data from Aprimo is stored in the content form for use.
- When no asset is selected, a blank card is displayed with the option to add an asset and JSON content is removed.

When an asset (and rendition) have been succesfully selected the card will update to show:
When an asset (and rendition) have been successfully selected the card will update to show:

- A preview of the asset (via the selected rendition `publicuri`)
- The title of the selected asset
Expand Down Expand Up @@ -126,21 +126,35 @@ If only using Aprimo data, use the following installation parameters:
```json
{
"aprimoConfig": {
"tenantUrl": "<your_aprimo_dam_tenant_url>"
"tenantUrl": "<your_aprimo_dam_tenant_url>",
"options": {} // Optional (Your Aprimo Content Selector options in JSON format)
}
}
```

Example `tenantUrl` Format: `https://{youraccount}.dam.aprimo.com`

See [Aprimo documentation](https://developers.aprimo.com/digital-asset-management/aprimo-integration-tools/aprimo-content-selector/) for options.

If no options attribute is listed, then default options JSON will be used which is:

```json
{
"select": "singlerendition",
"limitingSearchExpression": "ContentType = \"Image\"",
"accept": "Save to Amplience"
}
```

#### Amplience Images

If only using Amplience Images, use the following installation parameters:

```json
{
"aprimoConfig": {
"tenantUrl": "<your_aprimo_dam_tenant_url>"
"tenantUrl": "<your_aprimo_dam_tenant_url>",,
"options": {} // Optional (Your Aprimo Content Selector options in JSON format)
},
"amplienceConfig": {
// optional (only required for Amplience Images)
Expand All @@ -155,7 +169,7 @@ If only using Amplience Images, use the following installation parameters:

### Snippet (Optional)

You may also wish to create a [Snippet](https://amplience.com/developers/docs/integrations/extensions/register-use/#adding-snippets-for-content-field-extensions) or mutiple snippets for the extension to make it easier to add to content types. Example below:
You may also wish to create a [Snippet](https://amplience.com/developers/docs/integrations/extensions/register-use/#adding-snippets-for-content-field-extensions) or multiple snippets for the extension to make it easier to add to content types. Example below:

#### Aprimo Only (Standalone)

Expand Down Expand Up @@ -321,7 +335,7 @@ Example:

With this response you can use either the `amplienceImage` or the `publicuri` from Aprimo.

The Amplience image has the advantage of load balanced multi-CDN delivery, virtual staging, publishing with content and advanced asset tranformations from the [Amplience Dynamic Media](https://amplience.com/developers/docs/apis/media-delivery/) service, optional [Accelerated Media](https://amplience.com/developers/docs/release-notes/2023/accelerated-media/) tranformations as well as Generative AI capabilities.
The Amplience image has the advantage of load balanced multi-CDN delivery, virtual staging, publishing with content and advanced asset transformations from the [Amplience Dynamic Media](https://amplience.com/developers/docs/apis/media-delivery/) service, optional [Accelerated Media](https://amplience.com/developers/docs/release-notes/2023/accelerated-media/) tranformations as well as Generative AI capabilities.

## Running the extension locally

Expand All @@ -346,7 +360,7 @@ Build the project to produce hostable files using:
npm run build
```

This will produce a `dist` folder in the root of the project. You can use the files to host them statically (e.g. S3) or through your prefered hosting solution (Vercel, Netlify etc.)
This will produce a `dist` folder in the root of the project. You can use the files to host them statically (e.g. S3) or through your preferred hosting solution (Vercel, Netlify etc.)

## Known Limitations

Expand Down Expand Up @@ -381,3 +395,7 @@ A: By default this demonstration only allows selection of content which is `Cont
**Q: I have selected an image but a thumbnail is not showing in Amplience**

A: The rendition selected is probably not suitable to display as an image in a web browser. An example may be a `.tif` file where the rendition is also a `.tif`

**Q: Options data for Aprimo looks different**

A: This is because the JSON has property names without strings in Aprimo (pre-stringified)
27 changes: 15 additions & 12 deletions src/components/AprimoContentSelector.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,25 +13,27 @@ import isEmpty from "lodash.isempty";

function AprimoContentSelector() {
const {
aprimoValue,
aprimoFieldValue,
addAprimoImage,
removeAprimoImage,
params,
aprimoConfig,
title,
description,
thumbUrl,
} = useContentFieldExtension();
const TENANT_URL = params?.aprimoConfig?.tenantUrl;
const tenantUrl = aprimoConfig?.tenantUrl;

const openContentSelector = () => {
const encodedOptions = window.btoa(
JSON.stringify({
select: "singlerendition",
limitingSearchExpression: 'ContentType = "Image"',
accept: "Save to Amplience",
...aprimoConfig?.options,
})
);
const handleMessageEvent = async (event: MessageEvent) => {
if (event.origin !== TENANT_URL) {
if (event.origin !== tenantUrl) {
return; // exit if origin is not Aprimo
}
if (event.data.result === "cancel") {
Expand All @@ -44,14 +46,14 @@ function AprimoContentSelector() {

window.addEventListener("message", handleMessageEvent, false);
window.open(
`${TENANT_URL}/dam/selectcontent#options=${encodedOptions}`,
`${tenantUrl}/dam/selectcontent#options=${encodedOptions}`,
"selector"
);
};

const openInAprimo = () => {
window.open(
`${TENANT_URL}/dam/contentitems/${aprimoValue?.aprimoData?.id}`
`${tenantUrl}/dam/contentitems/${aprimoFieldValue?.aprimoData?.id}`
);
};

Expand All @@ -63,23 +65,24 @@ function AprimoContentSelector() {
<>
<div>
<FieldDetails title={title} description={description} />
{!isEmpty(aprimoValue?.aprimoData) && (
{!isEmpty(aprimoFieldValue?.aprimoData) && (
<Container maxWidth={false}>
<ImageCardBox my={4}>
<ImageCard>
<CardContent>
<Typography variant="subtitle1" component="h2">
{aprimoValue?.aprimoData?.title}
{aprimoFieldValue?.aprimoData?.title}
</Typography>
<Typography variant="subtitle2" component="h3">
{aprimoValue?.aprimoData?.id}
{aprimoFieldValue?.aprimoData?.id}
</Typography>
</CardContent>
<ImageCardMedia
image={
thumbUrl || aprimoValue?.aprimoData?.rendition?.publicuri
thumbUrl ||
aprimoFieldValue?.aprimoData?.rendition?.publicuri
}
title={aprimoValue?.aprimoData?.title}
title={aprimoFieldValue?.aprimoData?.title}
/>
<ImageCardActions>
<Fab color="secondary" onClick={openInAprimo}>
Expand All @@ -93,7 +96,7 @@ function AprimoContentSelector() {
</ImageCardBox>
</Container>
)}
{isEmpty(aprimoValue?.aprimoData) && (
{isEmpty(aprimoFieldValue?.aprimoData) && (
<Container maxWidth={false}>
<ImageCardBox my={4}>
<ImageCardSkeleton>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { ContentFieldExtension } from "dc-extensions-sdk";
import { createContext } from "react";

export type AprimoValue = {
export type AprimoFieldValue = {
aprimoData?: AprimoData;
amplienceImage?: {
_meta?: {
Expand All @@ -21,28 +21,35 @@ export type AprimoData = {
};

export type Params = {
aprimoConfig?: {
tenantUrl: string;
};
amplienceConfig?: {
endpoint: string;
defaultHost: string;
bucketId?: string;
folderId?: string;
uploadMode?: string;
};
aprimoConfig?: AprimoConfig;
amplienceConfig?: AmplienceConfig;
};

export type AprimoConfig = {
tenantUrl: string;
options: Record<string, unknown>;
};

export type AmplienceConfig = {
endpoint: string;
defaultHost?: string;
bucketId?: string;
folderId?: string;
uploadMode?: string;
};

export type ContentFieldExtensionContextState = {
sdk: ContentFieldExtension;
initialAprimoValue?: AprimoValue;
initialAprimoFieldValue?: AprimoFieldValue;
formValue: unknown;
readOnly: boolean;
params?: Params;
title: string;
description: string;
aprimoValue?: AprimoValue;
aprimoFieldValue?: AprimoFieldValue;
thumbUrl: string;
aprimoConfig?: AprimoConfig;
amplienceConfig?: AmplienceConfig;
addAprimoImage: (aprimoImage: AprimoData) => Promise<void>;
removeAprimoImage: () => Promise<void>;
};
Expand Down
72 changes: 45 additions & 27 deletions src/contexts/content-field-extension/WithContentFieldExtension.tsx
Original file line number Diff line number Diff line change
@@ -1,35 +1,50 @@
import { init, ContentFieldExtension } from "dc-extensions-sdk";
import { ReactNode, useEffect, useState } from "react";
import {
AmplienceConfig,
AprimoConfig,
AprimoData,
AprimoValue,
AprimoFieldValue,
ContentFieldExtensionContext,
Params,
} from "./ContentFieldExtensionContext";
import ContentHubService from "../../services/ContentHubService";
import isEmpty from "lodash.isempty";

function WithContentFieldExtension({ children }: { children: ReactNode }) {
const [sdk, setSDK] = useState<ContentFieldExtension<AprimoValue>>();
const [initialAprimoValue, setInitialAprimoValue] = useState<AprimoValue>();
const [aprimoValue, setAprimoValue] = useState<AprimoValue>();
const [sdk, setSDK] = useState<ContentFieldExtension<AprimoFieldValue>>();
const [initialAprimoFieldValue, setInitialAprimoFieldValue] =
useState<AprimoFieldValue>();
const [aprimoFieldValue, setAprimoFieldValue] = useState<AprimoFieldValue>();
const [formValue, setFormValue] = useState({});
const [readOnly, setReadOnly] = useState(false);
const [params, setParams] = useState<Params>({});
const [aprimoConfig, setAprimoConfig] = useState<AprimoConfig>();
const [amplienceConfig, setAmplienceConfig] = useState<AmplienceConfig>();
const [title, setTitle] = useState("");
const [description, setDescription] = useState("");
const [thumbUrl, setThumbUrl] = useState("");

useEffect(() => {
const setupSdk = async () => {
const sdk = await init<ContentFieldExtension<AprimoValue>>();
const initialValue = (await sdk.field.getValue()) as AprimoValue;
setInitialAprimoValue(initialValue);
setAprimoValue(initialValue);
setParams({
const sdk = await init<ContentFieldExtension<AprimoFieldValue>>();
const params: Params = {
...sdk.params.installation,
...sdk.params.instance,
});
};
setParams(params);
setAprimoConfig(params?.aprimoConfig);
if (params?.amplienceConfig) {
setAmplienceConfig({
defaultHost: "cdn.media.amplience.net",
uploadMode: "overwrite",
...params?.amplienceConfig,
});
}
const initialValue = (await sdk.field.getValue()) as AprimoFieldValue;
setInitialAprimoFieldValue(initialValue);
setAprimoFieldValue(initialValue);

setTitle(sdk.field.schema?.title || "");
setDescription(sdk.field.schema?.description || "");
sdk.frame.startAutoResizer();
Expand All @@ -45,30 +60,32 @@ function WithContentFieldExtension({ children }: { children: ReactNode }) {

useEffect(() => {
const populateThumbUrl = async () => {
if (sdk && aprimoValue?.amplienceImage?.id) {
const asset = await sdk.assets.getById(aprimoValue?.amplienceImage?.id);
if (sdk && aprimoFieldValue?.amplienceImage?.id) {
const asset = await sdk.assets.getById(
aprimoFieldValue?.amplienceImage?.id
);
setThumbUrl(asset?.thumbURL);
}
};

populateThumbUrl();
}, [aprimoValue]);
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [aprimoFieldValue]);

const addAprimoImage = async (aprimoImage: AprimoData) => {
const modifiedFieldValue = { ...aprimoValue, aprimoData: aprimoImage };
console.log(params.amplienceConfig, !isEmpty(params.amplienceConfig));
if (!isEmpty(aprimoImage) && !isEmpty(params.amplienceConfig)) {
const modifiedFieldValue = { ...aprimoFieldValue, aprimoData: aprimoImage };
if (!isEmpty(aprimoImage) && !isEmpty(amplienceConfig)) {
const amplienceImage = await createAmplienceImage(aprimoImage);
modifiedFieldValue.amplienceImage = amplienceImage;
}

await sdk?.field.setValue(modifiedFieldValue);
setAprimoValue(await sdk?.field.getValue());
setAprimoFieldValue(await sdk?.field.getValue());
};

const removeAprimoImage = async () => {
await sdk?.field.setValue({});
await setAprimoValue({ aprimoData: {}, amplienceImage: {} });
await setAprimoFieldValue({ aprimoData: {}, amplienceImage: {} });
setThumbUrl("");
};

Expand All @@ -77,7 +94,7 @@ function WithContentFieldExtension({ children }: { children: ReactNode }) {
throw new Error("Unable to create image - sdk has not been initialised");
}

if (!params?.amplienceConfig?.endpoint) {
if (!amplienceConfig?.endpoint) {
throw new Error("Unable to create image - missing endpoint");
}

Expand All @@ -86,9 +103,9 @@ function WithContentFieldExtension({ children }: { children: ReactNode }) {
}

const contentHubService = new ContentHubService(sdk, {
bucketId: params?.amplienceConfig?.bucketId,
folderId: params?.amplienceConfig?.folderId,
mode: params?.amplienceConfig?.uploadMode,
bucketId: amplienceConfig?.bucketId,
folderId: amplienceConfig?.folderId,
mode: amplienceConfig?.uploadMode,
});

const uploadedAsset = await contentHubService.uploadToAssetStore(
Expand All @@ -105,9 +122,8 @@ function WithContentFieldExtension({ children }: { children: ReactNode }) {
},
id: storedAsset.id,
name: storedAsset.name,
endpoint: params?.amplienceConfig?.endpoint,
defaultHost:
params?.amplienceConfig?.defaultHost || "cdn.media.amplience.net",
endpoint: amplienceConfig.endpoint,
defaultHost: amplienceConfig.defaultHost,
};
};

Expand All @@ -121,9 +137,11 @@ function WithContentFieldExtension({ children }: { children: ReactNode }) {
title,
description,
formValue,
initialAprimoValue,
aprimoValue,
initialAprimoFieldValue,
aprimoFieldValue,
thumbUrl,
aprimoConfig,
amplienceConfig,
addAprimoImage,
removeAprimoImage,
}}
Expand Down
4 changes: 2 additions & 2 deletions src/services/ContentHubService.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { ContentFieldExtension } from "dc-extensions-sdk";
import { HttpMethod } from "dc-extensions-sdk/dist/types/lib/components/HttpClient";
import { AprimoValue } from "../contexts/content-field-extension/ContentFieldExtensionContext";
import { AprimoFieldValue } from "../contexts/content-field-extension/ContentFieldExtensionContext";

export interface AssetStoreRequestBody {
hubId: string;
Expand Down Expand Up @@ -72,7 +72,7 @@ export default class ContentHubService {
private mode: string;

constructor(
private readonly sdk: ContentFieldExtension<AprimoValue>,
private readonly sdk: ContentFieldExtension<AprimoFieldValue>,
options: {
basepath?: string;
bucketId?: string;
Expand Down