diff --git a/src/pages/guides/getting_started/hello-world.md b/src/pages/guides/getting_started/hello-world.md index b30d911f3..e0e7e0f6a 100644 --- a/src/pages/guides/getting_started/hello-world.md +++ b/src/pages/guides/getting_started/hello-world.md @@ -266,6 +266,10 @@ Manifest updates Any changes to the `manifest.json` will _require a manual reload of your add-on_. The **Add-on Development** panel will indicate this in the log messages, and the **Refresh** button can be used to reload the add-on directly within Adobe Express. + + +**πŸ“¦ Working with SDK Constants:** As you build more complex add-ons, you'll use SDK constants like `Range`, `RenditionFormat`, and `SupportedMimeTypes`. These constants use different import patterns - some require named imports while others support multiple approaches. See the [import patterns guide](../../references/addonsdk/addonsdk-constants.md#import-patterns) for complete details. +
Click to see the screenshot diff --git a/src/pages/guides/learn/how_to/create_renditions.md b/src/pages/guides/learn/how_to/create_renditions.md index d4634ec2e..028ebb1f7 100644 --- a/src/pages/guides/learn/how_to/create_renditions.md +++ b/src/pages/guides/learn/how_to/create_renditions.md @@ -69,6 +69,10 @@ Renditions are created via the [`createRendition()`](../../../references/addonsd 1. [`renditionOptions`](../../../references/addonsdk/app-document.md#renditionoptions): controls the page range that is meant to be exported and the file format (jpg, png, mp4, pdf, and pptx). 2. [`renditionIntent`](../../../references/addonsdk/addonsdk-constants.md) constant (optional): controls the intent of the exported content (preview, export, print). + + +**SDK Constants Import:** This guide uses several SDK constants like `Range`, `RenditionFormat`, and `RenditionIntent`. These constants support dual access (both named imports and `addOnUISdk.constants.*`). See the [constants import patterns guide](../../../references/addonsdk/addonsdk-constants.md#import-patterns) for details on importing SDK constants correctly. + ## Check export permissions The `exportAllowed()` method determines whether the current document can be exported based on its review status in collaborative workflows. This applies mainly to [enterprise customers using Adobe Express's review and approval features](https://business.adobe.com/products/workfront/integrations/express.html), where documents may be restricted from export until approved by designated reviewers. diff --git a/src/pages/guides/learn/how_to/group_elements.md b/src/pages/guides/learn/how_to/group_elements.md index 91d9d16d5..02fc63b85 100644 --- a/src/pages/guides/learn/how_to/group_elements.md +++ b/src/pages/guides/learn/how_to/group_elements.md @@ -55,6 +55,10 @@ To create a Group, you can use the [`editor.createGroup()`](../../../references/ ### Example + + +#### JavaScript + ```js // sandbox/code.js import { editor } from "express-document-sdk"; @@ -78,6 +82,32 @@ greetingsGroup.children.append(greeting, saluto); editor.context.insertionParent.children.append(greetingsGroup); ``` +#### TypeScript + +```ts +// sandbox/code.js +import { editor, StandaloneTextNode, GroupNode, ContainerNode } from "express-document-sdk"; + +// Create some Text +const greeting: StandaloneTextNode = editor.createText("Hiya!"); +greeting.translation = { x: 100, y: 50 }; + +// Create some other Text +const saluto: StandaloneTextNode = editor.createText("Ciao!"); +saluto.translation = { x: 100, y: 150 }; + +// Create a Group πŸ‘ˆ +const greetingsGroup: GroupNode = editor.createGroup(); +greetingsGroup.translation = { x: 100, y: 100 }; + +// Append the Text nodes to the Group πŸ‘ˆ +greetingsGroup.children.append(greeting, saluto); + +// Append the Group to the page πŸ‘ˆ +const insertionParent: ContainerNode = editor.context.insertionParent; +insertionParent.children.append(greetingsGroup); +``` + Group append order @@ -92,6 +122,10 @@ Groups can be nested, meaning that you can have a Group inside another Group; ju ### Example + + +#### JavaScript + ```js // sandbox/code.js @@ -111,6 +145,27 @@ outerGroup.children.append(innerGroup, salutation); editor.context.insertionParent.children.append(outerGroup); ``` +#### TypeScript + +```ts +// sandbox/code.js + +// Create three different Text nodes +const greeting: StandaloneTextNode = editor.createText("Hiya!"); +const saluto: StandaloneTextNode = editor.createText("Ciao!"); +const salutation: StandaloneTextNode = editor.createText("Salut!"); + +// Create an inner Group with the first two Text nodes +const innerGroup: GroupNode = editor.createGroup(); +innerGroup.children.append(greeting, saluto); + +// Create an outer Group with the inner Group and the third Text node +const outerGroup: GroupNode = editor.createGroup(); +outerGroup.children.append(innerGroup, salutation); + +editor.context.insertionParent.children.append(outerGroup); +``` + This code results in the following grouping: ```txt diff --git a/src/pages/guides/learn/how_to/index.md b/src/pages/guides/learn/how_to/index.md index e7fb55014..576dfd339 100644 --- a/src/pages/guides/learn/how_to/index.md +++ b/src/pages/guides/learn/how_to/index.md @@ -50,9 +50,11 @@ We're constantly adding new how-tos, so make sure to check back often. If you're - [Use PDF and PowerPoint](./use_pdf_powerpoint.md) - [Group Elements](./group_elements.md) - [Position Elements](./position_elements.md) + - [Selection Events and Methods](./selection_events.md) - Use Metadata - [Document Metadata](./document_metadata.md) - [Page Metadata](./page_metadata.md) + - [Manage Pages](./manage_pages.md) - [Element Metadata](./element_metadata.md) - Exporting & Output - [Create Renditions](./create_renditions.md) @@ -78,8 +80,10 @@ We're constantly adding new how-tos, so make sure to check back often. If you're | | [Use PDF and PowerPoint](./use_pdf_powerpoint.md) | | | [Group Elements](./group_elements.md) | | | [Position Elements](./position_elements.md) | +| | [Selection Events and Methods](./selection_events.md) | | Use Metadata | [Document Metadata](./document_metadata.md) | | | [Page Metadata](./page_metadata.md) | +| | [Manage Pages](./manage_pages.md) | | | [Element Metadata](./element_metadata.md) | | Exporting & Output | [Create Renditions](./create_renditions.md) | | | [Manage with Premium Content](./premium_content.md) | diff --git a/src/pages/guides/learn/how_to/manage_pages.md b/src/pages/guides/learn/how_to/manage_pages.md new file mode 100644 index 000000000..51249b142 --- /dev/null +++ b/src/pages/guides/learn/how_to/manage_pages.md @@ -0,0 +1,643 @@ +--- +keywords: + - Adobe Express + - Express Add-on SDK + - Express Editor + - Adobe Express + - Add-on SDK + - SDK + - JavaScript + - Extend + - Extensibility + - API + - Page + - addPage + - PageNode + - PageList + - Document + - Navigation +title: Manage Pages +description: Learn how to create, navigate, and manage pages in Adobe Express documents. +contributors: + - https://github.com/hollyschinsky +faq: + questions: + - question: "How do I add a page programmatically?" + answer: "Use `editor.addPage()` method with page dimensions. There is no `createPage()` method." + + - question: "Why doesn't createPage() work?" + answer: "The Document API uses `addPage()` for pages, not `createPage()`. Use `editor.addPage(dimensions)` instead." + + - question: "How do I get the current page?" + answer: "Use `editor.context.currentPage` to access the currently active page." + + - question: "How do I navigate between pages?" + answer: "Adding a page automatically switches to it. You can also access pages via `editor.documentRoot.pages`." + + - question: "What happens when I add a page?" + answer: "A new page with a default artboard is created and automatically becomes the active page and insertion parent." + + - question: "Can I remove pages?" + answer: "Currently, the Document API doesn't provide a direct method to remove pages programmatically." + + - question: "How do I access all pages in a document?" + answer: "Use `editor.documentRoot.pages` to access the PageList containing all pages." + + - question: "What are the minimum requirements for a page?" + answer: "Every page must have at least one artboard. The `addPage()` method automatically creates a default artboard." +--- + +# Manage Pages + +Learn how to programmatically create, access, and manage pages in Adobe Express documents using the Document API. + + + +Important: Use `addPage()`, not `createPage()` + +The Adobe Express Document API uses **`addPage()`** to create new pages, not `createPage()`. This is a common source of confusion, especially for LLMs and developers familiar with other APIs. There is no `createPage()` method in the Document API. + +Always use `editor.addPage(dimensions)` when you need to add pages programmatically. + +## Understanding Pages in Adobe Express + +In Adobe Express, documents are organized hierarchically: +- **Document** (root) + - **Pages** (timeline sequence) + - **Artboards** (scenes within a page) + - **Content** (text, shapes, media, etc.) + +Every page contains at least one artboard, and all artboards within a page share the same dimensions. + +## Add a Page + +Use the [`addPage()`](../../../references/document-sandbox/document-apis/classes/PageList.md#addpage) method to create a new page with specified dimensions. + +### Example: Add a Standard Page + + + +#### JavaScript + +```js +// sandbox/code.js +import { editor } from "express-document-sdk"; + +// Define page dimensions (width x height in pixels) +const pageGeometry = { + width: 1080, + height: 1080 +}; + +// Add a new page with the specified dimensions +const newPage = editor.addPage(pageGeometry); + +console.log("New page created:", newPage); +console.log("Page dimensions:", newPage.width, "x", newPage.height); +``` + +#### TypeScript + +```ts +// sandbox/code.js +import { editor, PageNode, RectangleGeometry } from "express-document-sdk"; + +// Define page dimensions (width x height in pixels) +const pageGeometry: RectangleGeometry = { + width: 1080, + height: 1080 +}; + +// Add a new page with the specified dimensions +const newPage: PageNode = editor.addPage(pageGeometry); + +console.log("New page created:", newPage); +console.log("Page dimensions:", newPage.width, "x", newPage.height); +``` + + + +When you call `addPage()`, the new page automatically becomes the active page and the default insertion point for new content. The viewport also switches to display the new page's artboard. + +### Example: Add Pages with Different Dimensions + + + +#### JavaScript + +```js +// sandbox/code.js +import { editor } from "express-document-sdk"; + +// Add an Instagram post page (square) +const instagramPage = editor.addPage({ + width: 1080, + height: 1080 +}); + +// Add a story page (vertical) +const storyPage = editor.addPage({ + width: 1080, + height: 1920 +}); + +// Add a landscape page +const landscapePage = editor.addPage({ + width: 1920, + height: 1080 +}); + +console.log("Created 3 pages with different dimensions"); +``` + +#### TypeScript + +```ts +// sandbox/code.js +import { editor, PageNode, RectangleGeometry } from "express-document-sdk"; + +// Add an Instagram post page (square) +const instagramPage: PageNode = editor.addPage({ + width: 1080, + height: 1080 +} as RectangleGeometry); + +// Add a story page (vertical) +const storyPage: PageNode = editor.addPage({ + width: 1080, + height: 1920 +} as RectangleGeometry); + +// Add a landscape page +const landscapePage: PageNode = editor.addPage({ + width: 1920, + height: 1080 +} as RectangleGeometry); + +console.log("Created 3 pages with different dimensions"); +``` + +## Access Pages + +### Get the Current Page + + + +#### JavaScript + +```js +// sandbox/code.js +import { editor } from "express-document-sdk"; + +// Get the currently active page +const currentPage = editor.context.currentPage; + +console.log("Current page dimensions:", currentPage.width, "x", currentPage.height); +console.log("Number of artboards:", currentPage.artboards.length); +``` + +#### TypeScript + +```ts +// sandbox/code.js +import { editor, PageNode } from "express-document-sdk"; + +// Get the currently active page +const currentPage: PageNode = editor.context.currentPage; + +console.log("Current page dimensions:", currentPage.width, "x", currentPage.height); +console.log("Number of artboards:", currentPage.artboards.length); +``` + +### Access All Pages + + + +#### JavaScript + +```js +// sandbox/code.js +import { editor } from "express-document-sdk"; + +// Get all pages in the document +const allPages = editor.documentRoot.pages; + +console.log("Total pages in document:", allPages.length); + +// Iterate through all pages +for (const page of allPages) { + console.log(`Page dimensions: ${page.width} x ${page.height}`); + console.log(`Artboards in this page: ${page.artboards.length}`); +} + +// Access specific pages by index +const firstPage = allPages[0]; +const lastPage = allPages[allPages.length - 1]; +``` + +#### TypeScript + +```ts +// sandbox/code.js +import { editor, PageList, PageNode } from "express-document-sdk"; + +// Get all pages in the document +const allPages: PageList = editor.documentRoot.pages; + +console.log("Total pages in document:", allPages.length); + +// Iterate through all pages +for (const page of allPages) { + console.log(`Page dimensions: ${page.width} x ${page.height}`); + console.log(`Artboards in this page: ${page.artboards.length}`); +} + +// Access specific pages by index +const firstPage: PageNode = allPages[0]; +const lastPage: PageNode = allPages[allPages.length - 1]; +``` + +## Working with Page Content + +### Add Content to a Specific Page + + + +#### JavaScript + +```js +// sandbox/code.js +import { editor } from "express-document-sdk"; + +// Create a new page +const newPage = editor.addPage({ + width: 1080, + height: 1080 +}); + +// The new page is automatically active, so content will be added to it +const textNode = editor.createText("Content on the new page!"); +textNode.translation = { x: 100, y: 100 }; + +// Add to the current insertion parent (the new page's artboard) +editor.context.insertionParent.children.append(textNode); + +console.log("Added text to the new page"); +``` + +#### TypeScript + +```ts +// sandbox/code.js +import { editor, PageNode, StandaloneTextNode, ContainerNode } from "express-document-sdk"; + +// Create a new page +const newPage: PageNode = editor.addPage({ + width: 1080, + height: 1080 +}); + +// The new page is automatically active, so content will be added to it +const textNode: StandaloneTextNode = editor.createText("Content on the new page!"); +textNode.translation = { x: 100, y: 100 }; + +// Add to the current insertion parent (the new page's artboard) +const insertionParent: ContainerNode = editor.context.insertionParent; +insertionParent.children.append(textNode); + +console.log("Added text to the new page"); +``` + +### Work with Page Artboards + + + +#### JavaScript + +```js +// sandbox/code.js +import { editor } from "express-document-sdk"; + +// Get the current page +const currentPage = editor.context.currentPage; + +// Access the page's artboards +const artboards = currentPage.artboards; +console.log("Number of artboards:", artboards.length); + +// Get the first (and typically only) artboard +const firstArtboard = artboards.first; +console.log("First artboard dimensions:", firstArtboard.width, "x", firstArtboard.height); + +// Add content directly to a specific artboard +const rect = editor.createRectangle(); +rect.width = 200; +rect.height = 200; +rect.translation = { x: 50, y: 50 }; + +firstArtboard.children.append(rect); +``` + +#### TypeScript + +```ts +// sandbox/code.js +import { editor, PageNode, ArtboardList, ArtboardNode, RectangleNode } from "express-document-sdk"; + +// Get the current page +const currentPage: PageNode = editor.context.currentPage; + +// Access the page's artboards +const artboards: ArtboardList = currentPage.artboards; +console.log("Number of artboards:", artboards.length); + +// Get the first (and typically only) artboard +const firstArtboard: ArtboardNode = artboards.first!; +console.log("First artboard dimensions:", firstArtboard.width, "x", firstArtboard.height); + +// Add content directly to a specific artboard +const rect: RectangleNode = editor.createRectangle(); +rect.width = 200; +rect.height = 200; +rect.translation = { x: 50, y: 50 }; + +firstArtboard.children.append(rect); +``` + +## Common Patterns and Best Practices + +### Page Creation Workflow + + + +#### JavaScript + +```js +// sandbox/code.js +import { editor } from "express-document-sdk"; + +function createTemplatePages() { + // Define common page sizes + const pageSizes = { + instagram: { width: 1080, height: 1080 }, + story: { width: 1080, height: 1920 }, + landscape: { width: 1920, height: 1080 }, + a4: { width: 595, height: 842 } + }; + + // Create pages for each template + const pages = {}; + + for (const [name, dimensions] of Object.entries(pageSizes)) { + const page = editor.addPage(dimensions); + pages[name] = page; + + // Add a title to each page + const title = editor.createText(`${name.toUpperCase()} Template`); + title.translation = { x: 50, y: 50 }; + editor.context.insertionParent.children.append(title); + + console.log(`Created ${name} page: ${dimensions.width}x${dimensions.height}`); + } + + return pages; +} + +// Create template pages +const templatePages = createTemplatePages(); +``` + +#### TypeScript + +```ts +// sandbox/code.js +import { editor, PageNode, RectangleGeometry, StandaloneTextNode } from "express-document-sdk"; + +interface PageSizes { + [key: string]: RectangleGeometry; +} + +function createTemplatePages(): { [key: string]: PageNode } { + // Define common page sizes + const pageSizes: PageSizes = { + instagram: { width: 1080, height: 1080 }, + story: { width: 1080, height: 1920 }, + landscape: { width: 1920, height: 1080 }, + a4: { width: 595, height: 842 } + }; + + // Create pages for each template + const pages: { [key: string]: PageNode } = {}; + + for (const [name, dimensions] of Object.entries(pageSizes)) { + const page: PageNode = editor.addPage(dimensions); + pages[name] = page; + + // Add a title to each page + const title: StandaloneTextNode = editor.createText(`${name.toUpperCase()} Template`); + title.translation = { x: 50, y: 50 }; + editor.context.insertionParent.children.append(title); + + console.log(`Created ${name} page: ${dimensions.width}x${dimensions.height}`); + } + + return pages; +} + +// Create template pages +const templatePages = createTemplatePages(); +``` + +### Check Page Properties + +For detailed page information including content analysis and print readiness, see the [Page Metadata how-to guide](page_metadata.md). + + + +#### JavaScript + +```js +// sandbox/code.js +import { editor } from "express-document-sdk"; + +function analyzeDocument() { + const pages = editor.documentRoot.pages; + + console.log("=== Document Analysis ==="); + console.log(`Total pages: ${pages.length}`); + + for (let i = 0; i < pages.length; i++) { + const page = pages[i]; + console.log(`\nPage ${i + 1}:`); + console.log(` Dimensions: ${page.width} x ${page.height}`); + console.log(` Artboards: ${page.artboards.length}`); + + // Count content in each artboard + for (let j = 0; j < page.artboards.length; j++) { + const artboard = page.artboards[j]; + console.log(` Artboard ${j + 1}: ${artboard.children.length} items`); + } + } +} + +// Analyze the current document +analyzeDocument(); +``` + +#### TypeScript + +```ts +// sandbox/code.js +import { editor, PageList, PageNode, ArtboardNode } from "express-document-sdk"; + +function analyzeDocument(): void { + const pages: PageList = editor.documentRoot.pages; + + console.log("=== Document Analysis ==="); + console.log(`Total pages: ${pages.length}`); + + for (let i = 0; i < pages.length; i++) { + const page: PageNode = pages[i]; + console.log(`\nPage ${i + 1}:`); + console.log(` Dimensions: ${page.width} x ${page.height}`); + console.log(` Artboards: ${page.artboards.length}`); + + // Count content in each artboard + for (let j = 0; j < page.artboards.length; j++) { + const artboard: ArtboardNode = page.artboards[j]; + console.log(` Artboard ${j + 1}: ${artboard.children.length} items`); + } + } +} + +// Analyze the current document +analyzeDocument(); +``` + +## Key Concepts + +### Pages vs Artboards + +- **Pages**: Top-level containers in the document timeline +- **Artboards**: "Scenes" within a page containing the actual content +- All artboards within a page share the same dimensions +- When you add a page, it automatically gets one default artboard + +### Insertion Context + +- Adding a page automatically makes it the active page +- `editor.context.insertionParent` points to the active artboard +- New content is added to the current insertion parent +- The viewport switches to display the new page + +### Common Pitfalls + +1. **Don't use `createPage()`** - This method doesn't exist. Use `addPage()` instead. +2. **Remember the geometry parameter** - `addPage()` requires page dimensions. +3. **Understand automatic navigation** - Adding a page switches to it automatically. +4. **Page dimensions affect all artboards** - All artboards in a page share the same size. + +## Integration with Other APIs + +### Using with Metadata APIs + +Pages created with `addPage()` can be used with other Document APIs, particularly for retrieving metadata. See the [Page Metadata how-to guide](page_metadata.md) for complete examples. + + + +#### JavaScript + +```js +// sandbox/code.js +import { editor } from "express-document-sdk"; + +// Add a page and get its metadata +const newPage = editor.addPage({ width: 1080, height: 1080 }); + +// Get the page ID for use with Add-on UI SDK metadata APIs +console.log("New page ID:", newPage.id); + +// You can use this ID with the Add-on UI SDK to get detailed metadata +// See the Page Metadata guide for complete examples: +// const pageMetadata = await addOnUISdk.app.document.getPagesMetadata({ +// pageIds: [newPage.id] +// }); +``` + +#### TypeScript + +```ts +// sandbox/code.js +import { editor, PageNode } from "express-document-sdk"; + +// Add a page and get its metadata +const newPage: PageNode = editor.addPage({ width: 1080, height: 1080 }); + +// Get the page ID for use with Add-on UI SDK metadata APIs +console.log("New page ID:", newPage.id); + +// You can use this ID with the Add-on UI SDK to get detailed metadata +// See the Page Metadata guide for complete examples: +// const pageMetadata = await addOnUISdk.app.document.getPagesMetadata({ +// pageIds: [newPage.id] +// }); +``` + +## FAQs + +#### Q: How do I add a page programmatically? + +**A:** Use `editor.addPage(dimensions)` with page dimensions. There is no `createPage()` method. + +#### Q: Why doesn't createPage() work? + +**A:** The Document API uses `addPage()` for pages, not `createPage()`. Use `editor.addPage(dimensions)` instead. + +#### Q: How do I get the current page? + +**A:** Use `editor.context.currentPage` to access the currently active page. + +#### Q: How do I navigate between pages? + +**A:** Adding a page automatically switches to it. You can also access pages via `editor.documentRoot.pages`. + +#### Q: What happens when I add a page? + +**A:** A new page with a default artboard is created and automatically becomes the active page and insertion parent. + +#### Q: Can I remove pages? + +**A:** Currently, the Document API doesn't provide a direct method to remove pages programmatically. + +#### Q: How do I access all pages in a document? + +**A:** Use `editor.documentRoot.pages` to access the PageList containing all pages. + +#### Q: What are the minimum requirements for a page? + +**A:** Every page must have at least one artboard. The `addPage()` method automatically creates a default artboard. + +## Related Topics + +### Page Information and Metadata +- **[Page Metadata](page_metadata.md)** - Get detailed information about pages, including dimensions, content types, and selected page IDs +- **[Document Metadata](document_metadata.md)** - Access document-level information and listen for document events +- **[getSelectedPageIds() API](../../../references/addonsdk/app-document.md#getselectedpageids)** - Retrieve IDs of currently selected pages (experimental) + +### Working with Page Content +- **[Position Elements](position_elements.md)** - Position and arrange content within pages and artboards +- **[Group Elements](group_elements.md)** - Organize page content using groups +- **[Use Geometry](use_geometry.md)** - Create shapes and geometric elements for your pages +- **[Use Text](use_text.md)** - Add and style text content on pages +- **[Use Images](use_images.md)** - Import and work with images on pages + +### Document Structure and Context +- **[Document API Concepts](../../platform_concepts/document-api.md)** - Understanding the Adobe Express Document Object Model +- **[Context API Reference](../../../references/document-sandbox/document-apis/classes/Context.md)** - Current page, selection, and insertion context +- **[PageNode API Reference](../../../references/document-sandbox/document-apis/classes/PageNode.md)** - Detailed page node documentation +- **[PageList API Reference](../../../references/document-sandbox/document-apis/classes/PageList.md)** - Page list management methods + +### Advanced Topics +- **[Create Renditions](create_renditions.md)** - Export specific pages or entire documents +- **[Element Metadata](element_metadata.md)** - Store private metadata on page elements +- **[Local Data Management](local_data_management.md)** - Persist page-related state in your add-on diff --git a/src/pages/guides/learn/how_to/page_metadata.md b/src/pages/guides/learn/how_to/page_metadata.md index 72c66cf97..3664c0a6b 100644 --- a/src/pages/guides/learn/how_to/page_metadata.md +++ b/src/pages/guides/learn/how_to/page_metadata.md @@ -49,6 +49,8 @@ faq: # Page Metadata +This guide shows how to retrieve detailed information about pages in Adobe Express documents. For creating and managing pages programmatically, see the [Manage Pages how-to guide](manage_pages.md). + ## Get the Page Metadata If you want to retrieve metadata for pages in the document, use the [`getPagesMetadata()`](../../../references/addonsdk/app-document.md#getpagesmetadata) method in the `addOnUISdk.app.document` object. The method expects an object with a `range` and optional `pageIds` properties. diff --git a/src/pages/guides/learn/how_to/resize_rescale_elements.md b/src/pages/guides/learn/how_to/resize_rescale_elements.md index 1adf47020..d1b7067a2 100644 --- a/src/pages/guides/learn/how_to/resize_rescale_elements.md +++ b/src/pages/guides/learn/how_to/resize_rescale_elements.md @@ -68,6 +68,10 @@ Rescaling operations maintain the aspect ratio of elements while changing their Use `rescaleProportionalToWidth()` to adjust an element's width while maintaining its aspect ratio. The height will automatically adjust proportionally. + + +#### JavaScript + ```js // sandbox/code.js import { editor } from "express-document-sdk"; @@ -87,12 +91,38 @@ console.log(`New dimensions: ${rect.width} x ${rect.height}`); // New dimensions: 300 x 150 ``` +#### TypeScript + +```ts +// sandbox/code.js +import { editor, colorUtils, RectangleNode, ContainerNode } from "express-document-sdk"; + +// Create a rectangle with specific dimensions +const rect: RectangleNode = editor.createRectangle(); +rect.width = 200; +rect.height = 100; +rect.fill = editor.makeColorFill(colorUtils.fromHex("#3498db")); +// Add it to the page +const insertionParent: ContainerNode = editor.context.insertionParent; +insertionParent.children.append(rect); + +// Rescale to 300px width - height becomes 150px automatically +rect.rescaleProportionalToWidth(300); + +console.log(`New dimensions: ${rect.width} x ${rect.height}`); +// New dimensions: 300 x 150 +``` + ![Rescale by Width](./images/rescale--rescale-proportional-width.png) ### Example: Rescale by Height Similarly, use `rescaleProportionalToHeight()` to adjust an element's height while maintaining its aspect ratio. The width will automatically adjust proportionally. + + +#### JavaScript + ```js // sandbox/code.js // import { editor } from "express-document-sdk"; @@ -113,6 +143,28 @@ console.log( // New bounds: 300 x 150 ``` +#### TypeScript + +```ts +// sandbox/code.js +// import { editor, colorUtils, EllipseNode } from "express-document-sdk"; + +const ellipse: EllipseNode = editor.createEllipse(); +ellipse.rx = 100; // radius x = 100 (width = 200) +ellipse.ry = 50; // radius y = 50 (height = 100) +ellipse.fill = editor.makeColorFill(colorUtils.fromHex("#F0B76C")); + +editor.context.insertionParent.children.append(ellipse); + +// Rescale to 150px height - width becomes 300px automatically +ellipse.rescaleProportionalToHeight(150); + +console.log( + `New bounds: ${ellipse.boundsLocal.width} x ${ellipse.boundsLocal.height}` +); +// New bounds: 300 x 150 +``` + ![Rescale by Height](./images/rescale--rescale-proportional-height.png) ### Example: Rescaling with Styled Elements diff --git a/src/pages/guides/learn/how_to/selection_events.md b/src/pages/guides/learn/how_to/selection_events.md new file mode 100644 index 000000000..d3a5c9fba --- /dev/null +++ b/src/pages/guides/learn/how_to/selection_events.md @@ -0,0 +1,1220 @@ +--- +keywords: + - Adobe Express + - Express Add-on SDK + - Document API + - Selection + - selectionChange + - Events + - Node selection + - UI integration + - Document sandbox + - Event handlers + - Selection filtering + - Locked nodes + - Properties panel + - Real-time updates +title: Handle Element Selection +description: Complete guide to working with element selections in Adobe Express documents - from basic selection operations to advanced event handling and UI integration patterns. +contributors: + - https://github.com/hollyschinsky +faq: + questions: + - question: "How do I get the current selection?" + answer: "Use `editor.context.selection` to get an array of currently selected editable nodes, or `editor.context.hasSelection` to check if anything is selected." + + - question: "How do I listen for selection changes?" + answer: "Use `editor.context.on(EditorEvent.selectionChange, callback)` to register a handler. Always store the returned ID for cleanup." + + - question: "How do I programmatically select elements?" + answer: "Set `editor.context.selection = node` for single elements or `editor.context.selection = [node1, node2]` for multiple elements." + + - question: "What's the difference between selection and selectionIncludingNonEditable?" + answer: "`selection` only includes editable nodes, while `selectionIncludingNonEditable` includes locked/non-editable nodes that users can see but not modify." + + - question: "Can I modify the document in a selection change callback?" + answer: "Never modify the document in selection change handlers - this can crash the application. Only update UI, analyze data, or communicate with your panel." + + - question: "How do I clear the selection?" + answer: "Set `editor.context.selection = []` or `editor.context.selection = undefined` to clear all selections." + + - question: "What selection rules should I know?" + answer: "Only nodes within the current artboard can be selected, you cannot select both parent and child nodes simultaneously, and locked nodes are automatically filtered from the main selection." + + - question: "How do I clean up selection event handlers?" + answer: "Always call `editor.context.off(EditorEvent.selectionChange, handlerId)` using the ID returned from `on()` to prevent memory leaks." + + - question: "How do I communicate selection changes to my UI?" + answer: "Use the runtime API to send selection data from the document sandbox to your UI panel, enabling real-time interface updates." + + - question: "What are common selection-based actions?" + answer: "Typical actions include updating properties panels, enabling/disabling tools, applying formatting to selected text, grouping elements, and showing context-appropriate options." +--- + +# Handle Element Selection + +Learn how to work with user selections, handle selection changes, and create responsive interfaces that react to what users select in their Adobe Express documents. + +## Getting Started with Selections + +Selections in Adobe Express represent the elements (nodes) that users have currently selected in their document. The selection system provides access to what's selected, the ability to change selections programmatically, and events to respond to selection changes. + +All selection operations use the **Document API** and run in the **document sandbox environment**. This means your selection code should be placed in your `code.js` file, not in your main iframe panel code. + + + +### Document Sandbox Setup + +Make sure your `manifest.json` includes `"documentSandbox": "code.js"` in the entry points to set up the document sandbox environment. + +### Check Current Selection + + + +#### JavaScript + +```js +// code.js +import { editor } from "express-document-sdk"; + +// Check if anything is selected +if (editor.context.hasSelection) { + const selection = editor.context.selection; + console.log(`Selected ${selection.length} item(s)`); + + // Process each selected node + selection.forEach((node, index) => { + console.log(`Node ${index + 1}: ${node.type}`); + }); +} else { + console.log("Nothing is selected"); +} +``` + +#### TypeScript + +```ts +// code.ts +import { editor, Node, EditorEvent } from "express-document-sdk"; + +// Check if anything is selected +if (editor.context.hasSelection) { + const selection: readonly Node[] = editor.context.selection; + console.log(`Selected ${selection.length} item(s)`); + + // Process each selected node + selection.forEach((node: Node, index: number) => { + console.log(`Node ${index + 1}: ${node.type}`); + }); +} else { + console.log("Nothing is selected"); +} +``` + +## Understanding Selections + +In Adobe Express, the selection system provides: + +- **Current selection access** - Get what's currently selected +- **Selection modification** - Programmatically change selections +- **Selection events** - React to selection changes +- **Selection filtering** - Handle locked/non-editable content + +### Selection Rules + +Adobe Express enforces these constraints: + +1. **Artboard constraint** - Only nodes within the current artboard can be selected +2. **Hierarchy filtering** - Cannot select both parent and child nodes simultaneously +3. **Locked node filtering** - Locked nodes are excluded from the main selection +4. **Editable-only** - Main selection only includes editable nodes + +## Basic Selection Operations + +Core operations for working with selections. + +### Getting the Current Selection + + + +#### JavaScript + +```js +// code.js +import { editor } from "express-document-sdk"; + +// Get the current selection +const selection = editor.context.selection; + +console.log("Selected nodes:", selection.length); + +// Process each selected node +selection.forEach((node, index) => { + console.log(`Node ${index + 1}: ${node.type}`); + + // Common node properties you can access + console.log(" Position:", node.translation); + console.log(" Size:", { width: node.width, height: node.height }); +}); +``` + +#### TypeScript + +```ts +// code.ts +import { editor, Node, EditorEvent } from "express-document-sdk"; + +// Get the current selection +const selection: readonly Node[] = editor.context.selection; + +console.log("Selected nodes:", selection.length); + +// Process each selected node +selection.forEach((node: Node, index: number) => { + console.log(`Node ${index + 1}: ${node.type}`); + + // Common node properties you can access + console.log(" Position:", node.translation); + console.log(" Size:", { width: node.width, height: node.height }); +}); +``` + +### Programmatic Selection + + + +#### JavaScript + +```js +// code.js +import { editor } from "express-document-sdk"; + +// Create and select a single element +const rectangle = editor.createRectangle(); +rectangle.width = 100; +rectangle.height = 100; +rectangle.translation = { x: 50, y: 50 }; + +// Add to document +editor.context.insertionParent.children.append(rectangle); + +// Select the rectangle (single element) +editor.context.selection = rectangle; +// OR using array syntax: editor.context.selection = [rectangle]; + +console.log("Rectangle is now selected"); +``` + +#### TypeScript + +```ts +// code.ts +import { editor, RectangleNode, ContainerNode } from "express-document-sdk"; + +// Create a simple rectangle to demonstrate selection +const rectangle: RectangleNode = editor.createRectangle(); +rectangle.width = 100; +rectangle.height = 100; +rectangle.translation = { x: 50, y: 50 }; + +// Add to document +const insertionParent: ContainerNode = editor.context.insertionParent; +insertionParent.children.append(rectangle); + +// Select the rectangle (single element) +editor.context.selection = rectangle; +// OR using array syntax: editor.context.selection = [rectangle]; + +console.log("Rectangle is now selected"); +``` + +### Multiple Selection + + + +#### JavaScript + +```js +// code.js +import { editor } from "express-document-sdk"; + +// Create multiple elements +const rectangle = editor.createRectangle(); +rectangle.width = 80; +rectangle.height = 80; +rectangle.translation = { x: 50, y: 50 }; + +const ellipse = editor.createEllipse(); +ellipse.rx = 40; +ellipse.ry = 40; +ellipse.translation = { x: 200, y: 50 }; + +// Add both to document +const parent = editor.context.insertionParent; +parent.children.append(rectangle, ellipse); + +// Select both elements at once +editor.context.selection = [rectangle, ellipse]; + +console.log("Multiple elements selected:", editor.context.selection.length); +``` + +#### TypeScript + +```ts +// code.ts +import { editor, RectangleNode, EllipseNode, ContainerNode } from "express-document-sdk"; + +// Create multiple simple elements +const rectangle: RectangleNode = editor.createRectangle(); +rectangle.width = 80; +rectangle.height = 80; +rectangle.translation = { x: 50, y: 50 }; + +const ellipse: EllipseNode = editor.createEllipse(); +ellipse.rx = 40; +ellipse.ry = 40; +ellipse.translation = { x: 200, y: 50 }; + +// Add both to document +const parent: ContainerNode = editor.context.insertionParent; +parent.children.append(rectangle, ellipse); + +// Select both elements at once +editor.context.selection = [rectangle, ellipse]; + +console.log("Multiple elements selected:", editor.context.selection.length); +``` + +### Clearing the Selection + + + +#### JavaScript + +```js +// code.js +import { editor } from "express-document-sdk"; + +// Clear the selection - both ways work +editor.context.selection = []; +// OR: editor.context.selection = undefined; + +console.log("Selection cleared"); +console.log("Has selection:", editor.context.hasSelection); // false +``` + +#### TypeScript + +```ts +// code.ts +import { editor } from "express-document-sdk"; + +// Clear the selection - both ways work +editor.context.selection = []; +// OR: editor.context.selection = undefined; + +console.log("Selection cleared"); +console.log("Has selection:", editor.context.hasSelection); // false +``` + +## Selection Events + +Respond to selection changes to create dynamic UIs that update based on what's selected. + +### Basic Selection Change Handler + + + +#### JavaScript + +```js +// code.js +import { editor, EditorEvent } from "express-document-sdk"; + +// Listen for selection changes +const handlerId = editor.context.on(EditorEvent.selectionChange, () => { + const selection = editor.context.selection; + + console.log("Selection changed!"); + console.log("New selection count:", selection.length); + + if (selection.length === 0) { + console.log("Nothing selected"); + } else if (selection.length === 1) { + console.log("One item selected:", selection[0].type); + } else { + console.log("Multiple items selected"); + } +}); + +// Store handlerId if you need to unregister later +console.log("Selection handler registered:", handlerId); +``` + +#### TypeScript + +```ts +// code.ts +import { editor, Node, EditorEvent } from "express-document-sdk"; + +// Listen for selection changes +const handlerId: string = editor.context.on(EditorEvent.selectionChange, () => { + const selection: readonly Node[] = editor.context.selection; + + console.log("Selection changed!"); + console.log("New selection count:", selection.length); + + if (selection.length === 0) { + console.log("Nothing selected"); + } else if (selection.length === 1) { + console.log("One item selected:", selection[0].type); + } else { + console.log("Multiple items selected"); + } +}); + +// Store handlerId if you need to unregister later +console.log("Selection handler registered:", handlerId); +``` + +### Properties Panel Example + +Dynamic properties panel based on selection: + + + +#### JavaScript + +```js +// code.js +import { editor, EditorEvent } from "express-document-sdk"; + +function updatePropertiesPanel() { + const selection = editor.context.selection; + + if (selection.length === 0) { + console.log("Properties Panel: Show 'Nothing Selected' state"); + return; + } + + if (selection.length === 1) { + const node = selection[0]; + console.log("Properties Panel: Show properties for", node.type); + + // Show different properties based on node type + if (node.type === "Text") { + console.log(" - Show font controls"); + console.log(" - Show text color picker"); + } else if (node.type === "Rectangle" || node.type === "Ellipse") { + console.log(" - Show fill color picker"); + console.log(" - Show stroke controls"); + } + + // Common properties for all nodes + console.log(" - Show position controls"); + console.log(" - Show size controls"); + + } else { + console.log("Properties Panel: Show multi-selection options"); + console.log(` - ${selection.length} items selected`); + console.log(" - Show alignment tools"); + console.log(" - Show group option"); + } +} + +// Register the handler +editor.context.on(EditorEvent.selectionChange, updatePropertiesPanel); + +// Call once on startup to initialize +updatePropertiesPanel(); +``` + +#### TypeScript + +```ts +// code.ts +import { editor, Node, TextNode } from "express-document-sdk"; + +function updatePropertiesPanel(): void { + const selection: readonly Node[] = editor.context.selection; + + if (selection.length === 0) { + console.log("Properties Panel: Show 'Nothing Selected' state"); + return; + } + + if (selection.length === 1) { + const node: Node = selection[0]; + console.log("Properties Panel: Show properties for", node.type); + + // Show different properties based on node type + if (node.type === "Text") { + console.log(" - Show font controls"); + console.log(" - Show text color picker"); + } else if (node.type === "Rectangle" || node.type === "Ellipse") { + console.log(" - Show fill color picker"); + console.log(" - Show stroke controls"); + } + + // Common properties for all nodes + console.log(" - Show position controls"); + console.log(" - Show size controls"); + + } else { + console.log("Properties Panel: Show multi-selection options"); + console.log(` - ${selection.length} items selected`); + console.log(" - Show alignment tools"); + console.log(" - Show group option"); + } +} + +// Register the handler +editor.context.on(EditorEvent.selectionChange, updatePropertiesPanel); + +// Call once on startup to initialize +updatePropertiesPanel(); +``` + +### Event Handler Cleanup + +⚠️ **Important**: Always clean up event handlers to prevent memory leaks. + + + +#### JavaScript + +```js +// code.js +import { editor, EditorEvent } from "express-document-sdk"; + +// Store handler IDs so you can unregister them later +let selectionHandlerId = null; + +function startListening() { + // Register handler and store the ID + selectionHandlerId = editor.context.on(EditorEvent.selectionChange, () => { + console.log("Selection changed!"); + // Handle selection change + }); + + console.log("βœ… Selection handler registered"); +} + +function stopListening() { + // Clean up the handler + if (selectionHandlerId) { + editor.context.off(EditorEvent.selectionChange, selectionHandlerId); + selectionHandlerId = null; + console.log("βœ… Selection handler cleaned up"); + } +} + +// Start listening +startListening(); + +// Clean up when your add-on is being destroyed or reset +// stopListening(); +``` + +#### TypeScript + +```ts +// code.ts +import { editor, EditorEvent } from "express-document-sdk"; + +// Store handler IDs so you can unregister them later +let selectionHandlerId: string | null = null; + +function startListening(): void { + // Register handler and store the ID + selectionHandlerId = editor.context.on(EditorEvent.selectionChange, () => { + console.log("Selection changed!"); + // Handle selection change + }); + + console.log("βœ… Selection handler registered"); +} + +function stopListening(): void { + // Clean up the handler + if (selectionHandlerId) { + editor.context.off(EditorEvent.selectionChange, selectionHandlerId); + selectionHandlerId = null; + console.log("βœ… Selection handler cleaned up"); + } +} + +// Start listening +startListening(); + +// Clean up when your add-on is being destroyed or reset +// stopListening(); +``` + +## Advanced Selection Techniques + +Advanced patterns for complex add-ons. + +### Working with Locked/Non-Editable Elements + +Handle selections that include locked or non-editable content: + + + +#### JavaScript + +```js +// code.js +import { editor, EditorEvent } from "express-document-sdk"; + +function analyzeCompleteSelection() { + const selection = editor.context.selection; + const fullSelection = editor.context.selectionIncludingNonEditable; + + return { + editableCount: selection.length, + totalCount: fullSelection.length, + lockedCount: fullSelection.length - selection.length, + types: [...new Set(selection.map(node => node.type))], // Unique types + hasText: selection.some(node => node.type === "Text"), + hasShapes: selection.some(node => + node.type === "Rectangle" || node.type === "Ellipse" + ), + isEmpty: !editor.context.hasSelection + }; +} + +// Example: Dynamic UI updates based on detailed analysis +editor.context.on(EditorEvent.selectionChange, () => { + const analysis = analyzeCompleteSelection(); + + console.log("πŸ“Š Detailed Selection Info:"); + console.log(` Editable: ${analysis.editableCount}`); + if (analysis.lockedCount > 0) { + console.log(` Locked: ${analysis.lockedCount}`); + } + console.log(` Types: ${analysis.types.join(", ")}`); + + // Enable specific tools based on content + if (analysis.hasText) { + console.log("πŸ”€ Text formatting tools available"); + } + if (analysis.hasShapes) { + console.log("πŸ”· Shape styling tools available"); + } + if (analysis.editableCount > 1) { + console.log("πŸ“ Alignment tools available"); + } +}); +``` + +#### TypeScript + +```ts +// code.ts +import { editor, Node, EditorEvent } from "express-document-sdk"; + +interface DetailedSelectionAnalysis { + editableCount: number; + totalCount: number; + lockedCount: number; + types: string[]; + hasText: boolean; + hasShapes: boolean; + isEmpty: boolean; +} + +function analyzeSelection(): DetailedSelectionAnalysis { + const selection: readonly Node[] = editor.context.selection; + const fullSelection: readonly Node[] = editor.context.selectionIncludingNonEditable; + + return { + editableCount: selection.length, + totalCount: fullSelection.length, + lockedCount: fullSelection.length - selection.length, + types: [...new Set(selection.map((node: Node) => node.type))], // Unique types + hasText: selection.some((node: Node) => node.type === "Text"), + hasShapes: selection.some((node: Node) => + node.type === "Rectangle" || node.type === "Ellipse" + ), + isEmpty: !editor.context.hasSelection + }; +} + +// Example: Dynamic UI updates based on detailed analysis +editor.context.on(EditorEvent.selectionChange, () => { + const analysis: DetailedSelectionAnalysis = analyzeSelection(); + + console.log("πŸ“Š Detailed Selection Info:"); + console.log(` Editable: ${analysis.editableCount}`); + if (analysis.lockedCount > 0) { + console.log(` Locked: ${analysis.lockedCount}`); + } + console.log(` Types: ${analysis.types.join(", ")}`); + + // Enable specific tools based on content + if (analysis.hasText) { + console.log("πŸ”€ Text formatting tools available"); + } + if (analysis.hasShapes) { + console.log("πŸ”· Shape styling tools available"); + } + if (analysis.editableCount > 1) { + console.log("πŸ“ Alignment tools available"); + } +}); +``` + +## UI Integration + +Communicate selection changes between the document sandbox and your UI panel to create responsive interfaces. + +### Selection-Based Actions + +Common patterns for performing actions on selected elements: + + + +#### JavaScript + +```js +// code.js +import { editor, colorUtils } from "express-document-sdk"; + +// Function to apply red color to selected text +function applyRedToSelectedText() { + const selection = editor.context.selection; + + // Filter for text nodes only + const textNodes = selection.filter(node => node.type === "Text"); + + if (textNodes.length === 0) { + console.log("No text nodes selected"); + return; + } + + // Apply red color to all selected text + const redColor = colorUtils.fromHex("#FF0000"); + + textNodes.forEach(textNode => { + textNode.fullContent.applyCharacterStyles({ color: redColor }); + }); + + console.log(`Applied red color to ${textNodes.length} text nodes`); +} + +// Function to group selected elements +function groupSelection() { + const selection = editor.context.selection; + + if (selection.length < 2) { + console.log("Need at least 2 elements to create a group"); + return; + } + + // Create a group + const group = editor.createGroup(); + + // Add selected elements to the group + selection.forEach(node => { + // Remove from current parent and add to group + node.removeFromParent(); + group.children.append(node); + }); + + // Add group to the document + editor.context.insertionParent.children.append(group); + + // Select the new group + editor.context.selection = group; + + console.log(`Created group with ${selection.length} elements`); +} + +// Register handlers for different actions +editor.context.on(EditorEvent.selectionChange, () => { + const selection = editor.context.selection; + + // Update UI or enable/disable actions based on selection + if (selection.length === 0) { + console.log("No selection - disable all actions"); + } else if (selection.length === 1) { + console.log("Single selection - enable individual actions"); + } else { + console.log("Multiple selection - enable group actions"); + } +}); +``` + +#### TypeScript + +```ts +// code.ts +import { editor, colorUtils, Node, TextNode, GroupNode, ContainerNode } from "express-document-sdk"; + +// Function to apply red color to selected text +function applyRedToSelectedText(): void { + const selection: readonly Node[] = editor.context.selection; + + // Filter for text nodes only + const textNodes = selection.filter((node: Node): node is TextNode => + node.type === "Text" + ); + + if (textNodes.length === 0) { + console.log("No text nodes selected"); + return; + } + + // Apply red color to all selected text + const redColor = colorUtils.fromHex("#FF0000"); + + textNodes.forEach((textNode: TextNode) => { + textNode.fullContent.applyCharacterStyles({ color: redColor }); + }); + + console.log(`Applied red color to ${textNodes.length} text nodes`); +} + +// Function to group selected elements +function groupSelection(): void { + const selection: readonly Node[] = editor.context.selection; + + if (selection.length < 2) { + console.log("Need at least 2 elements to create a group"); + return; + } + + // Create a group + const group: GroupNode = editor.createGroup(); + + // Add selected elements to the group + selection.forEach((node: Node) => { + // Remove from current parent and add to group + node.removeFromParent(); + group.children.append(node); + }); + + // Add group to the document + const insertionParent: ContainerNode = editor.context.insertionParent; + insertionParent.children.append(group); + + // Select the new group + editor.context.selection = group; + + console.log(`Created group with ${selection.length} elements`); +} + +// Register handlers for different actions +editor.context.on(EditorEvent.selectionChange, () => { + const selection: readonly Node[] = editor.context.selection; + + // Update UI or enable/disable actions based on selection + if (selection.length === 0) { + console.log("No selection - disable all actions"); + } else if (selection.length === 1) { + console.log("Single selection - enable individual actions"); + } else { + console.log("Multiple selection - enable group actions"); + } +}); +``` + +### Selection State Management + + + +#### JavaScript + +```js +// code.js +import { editor, EditorEvent } from "express-document-sdk"; + +class SelectionManager { + constructor() { + this.selectionHistory = []; + this.handlerId = null; + this.startListening(); + } + + startListening() { + this.handlerId = editor.context.on(EditorEvent.selectionChange, () => { + const selection = editor.context.selection; + + // Store selection in history (limit to last 10) + this.selectionHistory.push([...selection]); + if (this.selectionHistory.length > 10) { + this.selectionHistory.shift(); + } + + console.log("Selection history length:", this.selectionHistory.length); + this.notifySelectionChange(selection); + }); + } + + notifySelectionChange(selection) { + // Custom logic based on selection + if (selection.length === 0) { + this.onNoSelection(); + } else if (selection.length === 1) { + this.onSingleSelection(selection[0]); + } else { + this.onMultipleSelection(selection); + } + } + + onNoSelection() { + console.log("No elements selected"); + // Disable context-sensitive UI + } + + onSingleSelection(node) { + console.log("Single element selected:", node.type); + // Enable single-element actions + } + + onMultipleSelection(selection) { + console.log("Multiple elements selected:", selection.length); + // Enable multi-element actions + } + + restorePreviousSelection() { + if (this.selectionHistory.length >= 2) { + const previousSelection = this.selectionHistory[this.selectionHistory.length - 2]; + editor.context.selection = previousSelection; + } + } + + stopListening() { + if (this.handlerId) { + editor.context.off(EditorEvent.selectionChange, this.handlerId); + this.handlerId = null; + } + } +} + +// Usage +const selectionManager = new SelectionManager(); +``` + +#### TypeScript + +```ts +// code.ts +import { editor, Node, EditorEvent } from "express-document-sdk"; + +class SelectionManager { + private selectionHistory: Node[][] = []; + private handlerId: string | null = null; + + constructor() { + this.startListening(); + } + + startListening(): void { + this.handlerId = editor.context.on(EditorEvent.selectionChange, () => { + const selection: readonly Node[] = editor.context.selection; + + // Store selection in history (limit to last 10) + this.selectionHistory.push([...selection]); + if (this.selectionHistory.length > 10) { + this.selectionHistory.shift(); + } + + console.log("Selection history length:", this.selectionHistory.length); + this.notifySelectionChange(selection); + }); + } + + private notifySelectionChange(selection: readonly Node[]): void { + // Custom logic based on selection + if (selection.length === 0) { + this.onNoSelection(); + } else if (selection.length === 1) { + this.onSingleSelection(selection[0]); + } else { + this.onMultipleSelection(selection); + } + } + + private onNoSelection(): void { + console.log("No elements selected"); + // Disable context-sensitive UI + } + + private onSingleSelection(node: Node): void { + console.log("Single element selected:", node.type); + // Enable single-element actions + } + + private onMultipleSelection(selection: readonly Node[]): void { + console.log("Multiple elements selected:", selection.length); + // Enable multi-element actions + } + + restorePreviousSelection(): void { + if (this.selectionHistory.length >= 2) { + const previousSelection = this.selectionHistory[this.selectionHistory.length - 2]; + editor.context.selection = previousSelection; + } + } + + stopListening(): void { + if (this.handlerId) { + editor.context.off(EditorEvent.selectionChange, this.handlerId); + this.handlerId = null; + } + } +} + +// Usage +const selectionManager = new SelectionManager(); +``` + +## Best Practices & Guidelines + +### Event Handler Cleanup + +⚠️ **Important**: Always clean up event handlers to prevent memory leaks. + + + +#### JavaScript + +```js +// code.js +import { editor, EditorEvent } from "express-document-sdk"; + +// Store handler IDs for cleanup +let selectionHandlerId = null; + +function setupSelectionHandling() { + // Register handler and store ID + selectionHandlerId = editor.context.on(EditorEvent.selectionChange, () => { + console.log("Selection changed"); + // Handle selection change + }); + + console.log("Selection handler registered"); +} + +function cleanupSelectionHandling() { + // Unregister the handler + if (selectionHandlerId) { + editor.context.off(EditorEvent.selectionChange, selectionHandlerId); + selectionHandlerId = null; + console.log("Selection handler unregistered"); + } +} + +// Setup +setupSelectionHandling(); + +// Cleanup when add-on is being destroyed or reset +// cleanupSelectionHandling(); +``` + +#### TypeScript + +```ts +// code.ts +import { editor, EditorEvent } from "express-document-sdk"; + +// Store handler IDs for cleanup +let selectionHandlerId: string | null = null; + +function setupSelectionHandling(): void { + // Register handler and store ID + selectionHandlerId = editor.context.on(EditorEvent.selectionChange, () => { + console.log("Selection changed"); + // Handle selection change + }); + + console.log("Selection handler registered"); +} + +function cleanupSelectionHandling(): void { + // Unregister the handler + if (selectionHandlerId) { + editor.context.off(EditorEvent.selectionChange, selectionHandlerId); + selectionHandlerId = null; + console.log("Selection handler unregistered"); + } +} + +// Setup +setupSelectionHandling(); + +// Cleanup when add-on is being destroyed or reset +// cleanupSelectionHandling(); +``` + +### Selection System Rules + +1. **Artboard constraint**: Only nodes within the current artboard can be selected +2. **Hierarchy filtering**: Cannot select both parent and child nodes simultaneously +3. **Locked node handling**: Locked nodes are excluded from main selection but available in `selectionIncludingNonEditable` +4. **Automatic filtering**: System automatically filters out invalid selections + +### Important: Selection Handler Restrictions + + + +**Document Modification Restrictions** + +**Never modify the document inside selection change handlers!** This can crash the application. + +**βœ… Safe in selection handlers:** + +- Update UI panels +- Log information +- Analyze selection +- Enable/disable buttons +- Send data to UI panel + +**❌ Never do in selection handlers:** + +- Create, delete, or modify nodes +- Change document structure +- Set properties on selected elements + + + +### Performance Guidelines + +1. **Keep handlers fast**: Minimize processing time +2. **Essential work only**: Avoid heavy computations +3. **Clean Up**: Always unregister handlers when done (`editor.context.off()`) +4. **Avoid Heavy Work**: Don't do complex calculations in selection callbacks + +### Communication Between UI and Document Sandbox + +One of the most important real-world patterns is communicating selection changes from the document sandbox to your UI panel, allowing you to update the interface based on what the user has selected. + +For detailed information on the communication APIs, see the [Communication API reference](../../../references/document-sandbox/communication/). + +#### Complete Communication Example + +This example shows how to set up bidirectional communication between your UI panel and document sandbox for selection-based interactions. + +## Quick Reference & Common Patterns + +Here are some frequently used patterns you can copy and adapt: + +### Conditional Actions Based on Selection + +```js +// code.js +import { editor, EditorEvent } from "express-document-sdk"; + +// Enable/disable actions based on selection type +editor.context.on(EditorEvent.selectionChange, () => { + const selection = editor.context.selection; + + // Communicate with your UI panel + const actions = { + canGroup: selection.length >= 2, + canApplyTextStyle: selection.some(node => node.type === "Text"), + canApplyFill: selection.some(node => + ["Rectangle", "Ellipse"].includes(node.type) + ), + isEmpty: selection.length === 0 + }; + + // Send to UI panel for enabling/disabling buttons + // (Use the communication API to send this data) +}); +``` + +### Selection-Based Properties Panel + +```js +// code.js +import { editor, EditorEvent } from "express-document-sdk"; + +// Update properties panel based on selection +editor.context.on(EditorEvent.selectionChange, () => { + const selection = editor.context.selection; + + if (selection.length === 1) { + const node = selection[0]; // Common pattern: access first selected element + + // Send node properties to UI for editing + const properties = { + type: node.type, + width: node.width || null, + height: node.height || null, + x: node.translation?.x || null, + y: node.translation?.y || null, + locked: node.locked || false + }; + + // Update UI panel with these properties + console.log("Node properties:", properties); + } +}); +``` + +### Working with Single Selection + +Many add-ons focus on single-element operations. Here's a common pattern used throughout the documentation: + +```js +// code.js +import { editor } from "express-document-sdk"; + +// Safe access to first selected element (used in use_text.md and other guides) +if (editor.context.hasSelection) { + const selectedNode = editor.context.selection[0]; + + // Perform operations on the selected node + if (selectedNode.type === "Text") { + // Handle text-specific operations + } +} +``` + +## FAQs + +#### Q: How do I get the current selection? + +**A:** Use `editor.context.selection` to get an array of currently selected nodes. + +#### Q: How do I listen for selection changes? + +**A:** Use `editor.context.on(EditorEvent.selectionChange, callback)` to register a selection change handler. + +#### Q: How do I programmatically select elements? + +**A:** Set `editor.context.selection = node` for single elements or `editor.context.selection = [node1, node2]` for multiple elements. + +#### Q: What's the difference between selection and selectionIncludingNonEditable? + +**A:** `selection` only includes editable nodes, while `selectionIncludingNonEditable` also includes locked/non-editable nodes. + +#### Q: Can I modify the document in a selection change callback? + +**A:** No, avoid making document changes in selection change callbacks as it may destabilize the application. + +#### Q: How do I clear the selection? + +**A:** Set `editor.context.selection = []` or `editor.context.selection = undefined`. + +#### Q: What are the selection rules? + +**A:** Nodes must be within the current artboard, ancestors cannot be selected with descendants, and locked nodes are filtered out. + +#### Q: How do I unregister selection event handlers? + +**A:** Use `editor.context.off(EditorEvent.selectionChange, handlerId)` with the ID returned from the `on()` method. + +## Related Topics + +- **[Context API Reference](../../../references/document-sandbox/document-apis/classes/Context.md)** - Complete API documentation for the Context class +- **[Communication APIs](../../../references/document-sandbox/communication/)** - Learn how to communicate between document sandbox and UI panel +- **[Group Elements](./group_elements.md)** - Working with selections to create and manage groups +- **[Position Elements](./position_elements.md)** - Positioning and transforming selected elements +- **[Use Text](./use_text.md)** - Examples of working with text selections using `editor.context.selection[0]` +- **[EditorEvent Enumeration](../../../references/document-sandbox/document-apis/enumerations/EditorEvent.md)** - All available editor events +- **[Node API Reference](../../../references/document-sandbox/document-apis/classes/Node.md)** - Understanding the Node class used in selections diff --git a/src/pages/guides/learn/how_to/use_color.md b/src/pages/guides/learn/how_to/use_color.md index 81f75f576..2afe59998 100644 --- a/src/pages/guides/learn/how_to/use_color.md +++ b/src/pages/guides/learn/how_to/use_color.md @@ -57,6 +57,10 @@ Colors in Adobe Express are created as instances of the [`Color`](../../../refer The entrypoint for creating colors is the [`colorUtils`](../../../references/document-sandbox/document-apis/classes/ColorUtils.md) class, imported from the `"express-document-sdk"`, so we're talking about [Document APIs](../../../references/document-sandbox/document-apis/index.md) here. Especially the static [`fromRGB()`](../../../references/document-sandbox/document-apis/classes/ColorUtils.md#fromrgb) and [`fromHex()`](../../../references/document-sandbox/document-apis/classes/ColorUtils.md#fromhex) methods. + + +#### JavaScript + ```js // sandbox/code.js import { editor, colorUtils } from "express-document-sdk"; @@ -70,19 +74,49 @@ const feldgrau = colorUtils.fromRGB(0.28, 0.32, 0.39, 0.5); // 50% opacity const heliotrope = colorUtils.fromHex("#C768F780"); // 50% opacity ``` +#### TypeScript + +```ts +// sandbox/code.js +import { editor, colorUtils, Color } from "express-document-sdk"; + +// Alpha is optional, defaults to 1 +const red: Color = colorUtils.fromRGB(1, 0, 0); +const green: Color = colorUtils.fromHex("#00FF00"); + +// With alpha +const feldgrau: Color = colorUtils.fromRGB(0.28, 0.32, 0.39, 0.5); // 50% opacity +const heliotrope: Color = colorUtils.fromHex("#C768F780"); // 50% opacity +``` + In case you need it, you can also convert a color to a HEX string using the [`toHex()`](../../../references/document-sandbox/document-apis/classes/ColorUtils.md#tohex) method. Please note that the alpha value is always included in the output string. + + +#### JavaScript + ```js const red = colorUtils.fromRGB(1, 0, 0); const redHex = colorUtils.toHex(red); // #FF0000FF ``` +#### TypeScript + +```ts +const red: Color = colorUtils.fromRGB(1, 0, 0); +const redHex: string = colorUtils.toHex(red); // #FF0000FF +``` + ## Apply colors You can directly set the `color` property of a Text node via [`applyCharacterStyles()`](../../../references/document-sandbox/document-apis/classes/TextContentModel.md#applycharacterstyles): ### Example: Text color + + +#### JavaScript + ```js // sandbox/code.js import { editor, colorUtils } from "express-document-sdk"; @@ -97,6 +131,22 @@ textNode.fullContent.applyCharacterStyles( ); ``` +#### TypeScript + +```ts +// sandbox/code.js +import { editor, colorUtils, TextNode } from "express-document-sdk"; + +// Assuming the user has selected a text frame +const textNode = editor.context.selection[0] as TextNode; + +// Apply character styles to the first three letters +textNode.fullContent.applyCharacterStyles( + { color: colorUtils.fromHex("#E1A141") }, // πŸ‘ˆ + { start: 0, length: 3 } +); +``` + See the [Use Text](./use_text.md) page for more examples. ### Example: Fill and Stroke colors @@ -105,6 +155,10 @@ Colors are not directly applied, instead, to shapes; more generally, they are us If you're confused, worry not! This is the wondrous word of object oriented programming. The following example should clarify things: + + +#### JavaScript + ```js // sandbox/code.js import { editor, colorUtils } from "express-document-sdk"; @@ -134,10 +188,45 @@ ellipse.stroke = outerColorStroke; editor.context.insertionParent.children.append(ellipse); ``` +#### TypeScript + +```ts +// sandbox/code.js +import { editor, colorUtils, EllipseNode, Color, ColorFill, Stroke, ContainerNode } from "express-document-sdk"; + +// Create the shape +const ellipse: EllipseNode = editor.createEllipse(); +ellipse.width = 100; +ellipse.height = 50; +ellipse.translation = { x: 50, y: 50 }; + +// Generate the needed colors +const innerColor: Color = colorUtils.fromHex("#A38AF0"); +const outerColor: Color = colorUtils.fromHex("#2ACfA9"); + +// Make the colorFill and the Stroke +const innerColorFill: ColorFill = editor.makeColorFill(innerColor); +const outerColorStroke: Stroke = editor.makeStroke({ + color: outerColor, + width: 20, +}); + +// πŸ‘‡ Apply the fill and stroke +ellipse.fill = innerColorFill; +ellipse.stroke = outerColorStroke; + +// Add the shape to the document +editor.context.insertionParent.children.append(ellipse); +``` + While the `fill` property is more straightforward to create, the `color` is just one of the possible properties of a `stroke`, as you can read in the [SolidColorStroke](../../../references/document-sandbox/document-apis/interfaces/SolidColorStroke.md) interface reference. Simplifying the example above: + + +#### JavaScript + ```js // ... ellipse.fill = editor.makeColorFill(colorUtils.fromHex("#A38AF0")); @@ -148,6 +237,18 @@ ellipse.stroke = editor.makeStroke({ // ... ``` +#### TypeScript + +```ts +// ... +ellipse.fill = editor.makeColorFill(colorUtils.fromHex("#A38AF0")); +ellipse.stroke = editor.makeStroke({ + color: colorUtils.fromHex("#2ACfA9"), + width: 20, +}); +// ... +``` + Naming conventions @@ -238,6 +339,10 @@ Please note that the color returned by the `colorpicker-color-change` event is a You can decide to hide picker UI e.g., after a certain amount of time. + + +#### JavaScript + ```js colorPickerButton.addEventListener("click", () => { addOnUISdk.app.showColorPicker(colorPickerButton, { @@ -250,6 +355,20 @@ colorPickerButton.addEventListener("click", () => { }); ``` +#### TypeScript + +```ts +colorPickerButton.addEventListener("click", (): void => { + addOnUISdk.app.showColorPicker(colorPickerButton, { + /* ... */ + }); + setTimeout((): void => { + console.log("Hiding the Color Picker after 10 seconds"); + addOnUISdk.app.hideColorPicker(); + }, 10000); +}); +``` + ### Example: Use the color You can use any HTML element as the color picker's anchor element; in the example below, we're using a `
` element to display a color swatch. @@ -296,6 +415,10 @@ addOnUISdk.ready.then(async () => { To use the picked color in the Document Sandbox, you can use the [`colorUtils.fromHex()`](../../../references/document-sandbox/document-apis/classes/ColorUtils.md#fromhex) method, which converts the HEX color string to a [`Color`](../../../references/document-sandbox/document-apis/interfaces/Color.md) object. + + +#### JavaScript + ```js // sandbox/code.js const color = colorUtils.fromHex(event.detail.color); // πŸ‘ˆ A Color object @@ -308,6 +431,20 @@ if (selection.length === 1 && selection[0].type === "Text") { } ``` +#### TypeScript + +```ts +// sandbox/code.js +const color: Color = colorUtils.fromHex(event.detail.color); // πŸ‘ˆ A Color object + +// Use the color in the Document Sandbox, for example: +let selection = editor.context.selection; +if (selection.length === 1 && selection[0].type === "Text") { + const textContentModel = (selection[0] as TextNode).fullContent; + textContentModel.applyCharacterStyles({ color }); // πŸ‘ˆ Using the color +} +``` + ## FAQs #### Q: How do I create colors from RGB values? diff --git a/src/pages/guides/learn/how_to/use_geometry.md b/src/pages/guides/learn/how_to/use_geometry.md index e5c5a5d8b..7a25a7a61 100644 --- a/src/pages/guides/learn/how_to/use_geometry.md +++ b/src/pages/guides/learn/how_to/use_geometry.md @@ -48,6 +48,10 @@ Adobe Express provides a set of geometric shapes that you can create and style p ### Example: Add a Rectangle + + +#### JavaScript + ```js // sandbox/code.js import { editor } from "express-document-sdk"; @@ -65,6 +69,25 @@ const currentPage = editor.context.currentPage; currentPage.artboards.first.children.append(rect); ``` +#### TypeScript + +```ts +// sandbox/code.js +import { editor, RectangleNode, PageNode } from "express-document-sdk"; + +const rect: RectangleNode = editor.createRectangle(); + +// Define rectangle dimensions. +rect.width = 100; +rect.height = 100; + +// The current page, where the rectangle will be placed +const currentPage: PageNode = editor.context.currentPage; + +// Append the rectangle to the page. +currentPage.artboards.first.children.append(rect); +``` + Create vs. Add to the page @@ -75,6 +98,10 @@ You usually reference the container using [`editor.context`](../../../references Please note that you can append multiple shapes at once with the `append()` method: + + +#### JavaScript + ```js const s1 = editor.createRectangle(); const s2 = editor.createEllipse(); @@ -83,6 +110,16 @@ const s2 = editor.createEllipse(); editor.context.currentPage.artboards.first.children.append(s1, s2); // πŸ‘ˆ ``` +#### TypeScript + +```ts +const s1: RectangleNode = editor.createRectangle(); +const s2: EllipseNode = editor.createEllipse(); +// ... set all properties ... + +editor.context.currentPage.artboards.first.children.append(s1, s2); // πŸ‘ˆ +``` + ### Example: Add an Ellipse Ellipses don't have a `width` and `height` properties, but a [`rx`](../../../references/document-sandbox/document-apis/classes/EllipseNode.md#rx) and [`ry`](../../../references/document-sandbox/document-apis/classes/EllipseNode.md#ry) (radius x, radius y) instead. @@ -91,6 +128,10 @@ Ellipses don't have a `width` and `height` properties, but a [`rx`](../../../ref An ellipse with a radius of 200 on the x-axis and 100 on the y-axis will result in a shape with 400 wide (`rx` times two) and a 200 tall (`ry` times two)! + + +#### JavaScript + ```js // sandbox/code.js import { editor } from "express-document-sdk"; @@ -106,13 +147,37 @@ console.log(ellipse.boundsLocal); const currentPage = editor.context.currentPage; // Append the rectangle to the page. -currentPage.artboards.first.children.append(rect); +currentPage.artboards.first.children.append(ellipse); +``` + +#### TypeScript + +```ts +// sandbox/code.js +import { editor, EllipseNode, PageNode } from "express-document-sdk"; + +const ellipse: EllipseNode = editor.createEllipse(); +ellipse.rx = 200; // radius x πŸ‘ˆ +ellipse.ry = 100; // radius y πŸ‘ˆ + +console.log(ellipse.boundsLocal); +// { x: 0, y: 0, width: 400, height: 200 } πŸ‘ˆ mind the actual bounds! + +// The current page, where the rectangle will be placed +const currentPage: PageNode = editor.context.currentPage; + +// Append the ellipse to the page. +currentPage.artboards.first.children.append(ellipse); ``` ### Example: Style Shapes Shapes have `fill` and `stroke` properties that you can use to style them. The following example demonstrates how to create a rectangle with a fill and a stroke. + + +#### JavaScript + ```js // sandbox/code.js import { editor, colorUtils, constants } from "express-document-sdk"; @@ -140,6 +205,36 @@ ellipse.stroke = stroke; editor.context.insertionParent.children.append(ellipse); ``` +#### TypeScript + +```ts +// sandbox/code.js +import { editor, colorUtils, constants, EllipseNode, Stroke, ContainerNode } from "express-document-sdk"; + +// Create the shape +const ellipse: EllipseNode = editor.createEllipse(); +ellipse.rx = 200; +ellipse.ry = 100; + +// πŸ‘‡ Apply the fill color +ellipse.fill = editor.makeColorFill(colorUtils.fromHex("#F3D988")); + +// πŸ‘‡ Create the stroke +const stroke: Stroke = editor.makeStroke({ + color: colorUtils.fromHex("#E29E4E"), + width: 20, + position: constants.StrokePosition.inside, + dashPattern: [50, 2], +}); + +// πŸ‘‡ Apply the stroke +ellipse.stroke = stroke; + +// Add the shape to the document +const insertionParent: ContainerNode = editor.context.insertionParent; +insertionParent.children.append(ellipse); +``` + ![Ellipse with fill and stroke](./images/shapes_ellipse.jpg) diff --git a/src/pages/guides/learn/how_to/use_text.md b/src/pages/guides/learn/how_to/use_text.md index 83985ea26..5a14fa3e0 100644 --- a/src/pages/guides/learn/how_to/use_text.md +++ b/src/pages/guides/learn/how_to/use_text.md @@ -78,6 +78,10 @@ The `editor.createText()` method accepts a string as a parameter, and returns a ### Example + + +#### JavaScript + ```js // sandbox/code.js import { editor } from "express-document-sdk"; @@ -99,6 +103,29 @@ insertionParent.children.append(textNode); console.log("Text: ", textNode.fullContent.text); ``` +#### TypeScript + +```ts +// sandbox/code.js +import { editor, StandaloneTextNode, ContainerNode } from "express-document-sdk"; + +// Create a new TextNode +const textNode: StandaloneTextNode = editor.createText("Hello,\nWorld!"); + +// Center the text on the page +const insertionParent: ContainerNode = editor.context.insertionParent; +textNode.setPositionInParent( + { x: insertionParent.width / 2, y: insertionParent.height / 2 }, + { x: 0, y: 0 } +); + +// Add the TextNode to the document +insertionParent.children.append(textNode); + +// Get the text content +console.log("Text: ", textNode.fullContent.text); +``` + The text is created with the default styles (Source Sans 3, 100pt, black). Use `\n` or `\r` to add a line break. @@ -118,6 +145,10 @@ The text content of a `TextNode` can be replaced by setting the [`fullContent.te ### Example + + +#### JavaScript + ```js // sandbox/code.js import { editor } from "express-document-sdk"; @@ -127,6 +158,17 @@ const selectedTextNode = editor.context.selection[0]; selectedTextNode.fullContent.text = "Something else"; ``` +#### TypeScript + +```ts +// sandbox/code.js +import { editor, TextNode } from "express-document-sdk"; + +// Assuming the user has selected a text frame +const selectedTextNode = editor.context.selection[0] as TextNode; +selectedTextNode.fullContent.text = "Something else"; +``` + ## Apply Character Styles Text styles can be applied to a `TextNode` using the [`fullContent.applyCharacterStyles()`](../../../references/document-sandbox/document-apis/classes/TextContentModel.md#applycharacterstyles) method, which applies one or more styles to the characters in the given range, leaving any style properties that were not specified unchanged. @@ -154,6 +196,10 @@ Please note that `applyCharacterStyles()` is only one way to set styles; you can Let's change the styles for the first three characters of a TextNode. + + +#### JavaScript + ```js // sandbox/code.js import { editor, constants } from "express-document-sdk"; @@ -177,12 +223,43 @@ textNode.fullContent.applyCharacterStyles( ); ``` +#### TypeScript + +```ts +// sandbox/code.js +import { editor, constants, TextNode, CharacterStyles } from "express-document-sdk"; + +// Assuming the user has selected a text frame +const textNode = editor.context.selection[0] as TextNode; + +// Apply character styles to the first three letters +const styles: CharacterStyles = { + color: { red: 0, green: 0.4, blue: 0.8, alpha: 1 }, + fontSize: 240, + letterSpacing: 10, + underline: true, + // baselineShift: constants.TextScriptStyle.superscript, +}; + +textNode.fullContent.applyCharacterStyles( + styles, + { + start: 0, + length: 3, + } +); +``` + The `applyCharacterStyles()` method is not the only one that allows you to set styles; you can also use the `characterStyleRanges` property, which supports both getting and setting styles. ### Example: Get all Styles To get the complete list of text character styles, you can use the [`fullContent.characterStyleRanges`](../../../references/document-sandbox/document-apis/classes/TextContentModel.md#characterstyleranges) property, which returns an array of [`CharacterStylesRange`](../../../references/document-sandbox/document-apis/interfaces/CharacterStylesRange.md) elements. + + +#### JavaScript + ```js // sandbox/code.js import { editor } from "express-document-sdk"; @@ -201,6 +278,26 @@ existingStyles[0].fontSize = 10; contentModel.characterStyleRanges = existingStyles; ``` +#### TypeScript + +```ts +// sandbox/code.js +import { editor, TextNode, TextContentModel, CharacterStylesRange } from "express-document-sdk"; + +// Assuming the user has selected a text frame +const textNode = editor.context.selection[0] as TextNode; +const contentModel: TextContentModel = textNode.fullContent; + +// Get the array of character styles +const existingStyles: CharacterStylesRange[] = contentModel.characterStyleRanges; + +// Edit some properties +existingStyles[0].fontSize = 10; + +// Reassign the array to apply the style changes +contentModel.characterStyleRanges = existingStyles; +``` + ### Example: Set all Styles You can also use the [`characterStyleRanges`](../../../references/document-sandbox/document-apis/classes/TextContentModel.md#characterstyleranges) property to set individual ranges or them all. It's always best to get the array, modify it, and then reassign it. diff --git a/src/pages/guides/learn/platform_concepts/communication-api.md b/src/pages/guides/learn/platform_concepts/communication-api.md new file mode 100644 index 000000000..fd6d935b6 --- /dev/null +++ b/src/pages/guides/learn/platform_concepts/communication-api.md @@ -0,0 +1,193 @@ +# **Communication API Layer Overview** + +The Communication API layer enables **bidirectional communication** between the Document Sandbox and UI runtime environments. Here's a comprehensive breakdown: + +### **Core Communication APIs** + +#### **Required Import** +```typescript +import addOnSandboxSdk, { RuntimeType } from "add-on-sdk-document-sandbox"; +const { runtime } = addOnSandboxSdk.instance; +``` + +### **1. `runtime.exposeApi(apiObject)` - Expose Document Functions to UI** + +**Purpose:** Makes Document Sandbox functions callable from the UI runtime + +**Requirements:** +- **Called once** per add-on lifecycle +- **No special flags needed** +- **Synchronous operation** + +**Usage Pattern:** +```typescript +// Document Sandbox (code.js) - Expose APIs TO the UI +const documentApis = { + createRectangle: () => { + const rect = editor.createRectangle(); + rect.width = 100; + rect.height = 100; + return "Rectangle created!"; + }, + + processImageData: (imageBlob) => { + // Process image in document context + return editor.addImageFromBlob(imageBlob); + }, + + getDocumentInfo: () => { + return { + pageCount: editor.context.document.pages.length, + selectedItems: editor.context.selection.length + }; + } +}; + +// Expose the APIs (called once) +runtime.exposeApi(documentApis); +``` + +**Key Features:** +- **Object methods** become remotely callable +- **Class instances** can be exposed +- **Return values** are automatically serialized +- **Async methods** work transparently +- **Only one API object** per runtime (calling again is no-op) + +### **2. `runtime.apiProxy(RuntimeType.panel)` - Access UI Functions from Document** + +**Purpose:** Gets access to functions exposed by the UI runtime + +**Requirements:** +- **Async operation** - requires `await` +- **Returns Promise-based proxy** +- **Must wait for UI runtime initialization** + +**Usage Pattern:** +```typescript +// Document Sandbox (code.js) - Access APIs FROM the UI +async function useUIApis() { + // Get UI API proxy (returns a Promise) + const uiApi = await runtime.apiProxy(RuntimeType.panel); + + // Call UI functions + await uiApi.showNotification("Document updated!"); + await uiApi.updateProgress(75); + + const userChoice = await uiApi.showConfirmDialog({ + title: "Confirm Action", + message: "Apply changes to all pages?" + }); + + if (userChoice) { + // Proceed with operation + } +} +``` + +### **Runtime Types** + +```typescript +export enum RuntimeType { + panel = "panel", // βœ… UI iframe runtime + documentSandbox = "documentSandbox", // ❌ Cannot proxy to self + script = "script" // ❌ Deprecated +} +``` + +**Restrictions:** +- **Cannot proxy to self**: `runtime.apiProxy(RuntimeType.documentSandbox)` throws error +- **Only `panel` is supported** for `apiProxy` calls from Document Sandbox + +### **Communication Flow Examples** + +#### **Example 1: UI Triggers Document Action** +```typescript +// UI Runtime (index.ts) +await documentApi.createRectangle(); // Calls function in Document Sandbox + +// Document Sandbox (code.ts) +function createRectangle() { + const rect = editor.createRectangle(); + // Document manipulation happens here + return "Success"; +} +``` + +#### **Example 2: Document Notifies UI of Changes** +```typescript +// Document Sandbox (code.ts) +async function onSelectionChange() { + const uiApi = await runtime.apiProxy(RuntimeType.panel); + await uiApi.updateSelectionInfo({ + count: editor.context.selection.length, + types: editor.context.selection.map(node => node.type) + }); +} + +// UI Runtime (index.ts) +function updateSelectionInfo(selectionData) { + // Update UI display + document.querySelector('#selection-count').textContent = selectionData.count; +} +``` + +### **Advanced Features** + +#### **Supported Data Types** +- **Primitives**: `string`, `number`, `boolean`, `null`, `undefined` +- **Objects**: Plain objects with serializable properties +- **Arrays**: Including nested arrays +- **Promises**: Async functions work transparently +- **Blobs**: Binary data transfer + +#### **Error Handling** +```typescript +try { + const uiApi = await runtime.apiProxy(RuntimeType.panel); + await uiApi.riskyOperation(); +} catch (error) { + console.error("UI operation failed:", error); + // Error is automatically propagated across runtime boundary +} +``` + +#### **Class Instance Exposure** +```typescript +class DocumentController { + createShape(type) { /* ... */ } + deleteShape(id) { /* ... */ } + getShapeInfo(id) { /* ... */ } +} + +const controller = new DocumentController(); +runtime.exposeApi(controller); // Entire class instance is exposed +``` + +### **Security & Performance Notes** + +1. **Automatic Serialization**: Data is automatically serialized/deserialized across runtime boundaries +2. **Type Validation**: Built-in validation prevents unsafe data transfer +3. **Promise-Based**: All cross-runtime calls are asynchronous for performance +4. **Single API Object**: Only one API object can be exposed per runtime (prevents conflicts) +5. **Error Propagation**: Errors are automatically marshaled across runtime boundaries + +### **Typical Communication Pattern** + +```typescript +// Document Sandbox Setup +const documentApis = { + // Functions UI can call + performDocumentOperation: async (params) => { /* ... */ } +}; +runtime.exposeApi(documentApis); + +// Get UI API access +const uiApi = await runtime.apiProxy(RuntimeType.panel); + +// Document can now: +// 1. Receive calls from UI via exposed APIs +// 2. Make calls to UI via proxy APIs +``` + +This creates a **bidirectional communication channel** where both runtimes can call each other's functions seamlessly. diff --git a/src/pages/guides/learn/platform_concepts/constants-guide.md b/src/pages/guides/learn/platform_concepts/constants-guide.md new file mode 100644 index 000000000..8aa846646 --- /dev/null +++ b/src/pages/guides/learn/platform_concepts/constants-guide.md @@ -0,0 +1,245 @@ +# Add-on UI SDK Constants + +The Add-on UI SDK constants provide type-safe ways to interact with all the major Add-on UI SDK features developers commonly use. This includes: + +- Constants available in `addOnUISdk.constants.*` +- Constants available only as named exports (specific import required) + +See the [Import Patterns](#import-patterns) section for details on how to access each type. + + + +The constants listed in this reference are equal to their variable name as a string value, ie: for the `ButtonType` constant, `primary` has a value of "primary". + +## **Dialog & UI Interaction Constants** + +| Constant | Values | Description | Usage | +|----------|--------|-------------|-------| +| **`Variant`** | `confirmation`, `information`, `warning`, `destructive`, `error`, `input`, `custom` | Dialog types for `showModalDialog()` | `addOnUISdk.app.showModalDialog({variant: Variant.error})` | +| **`ButtonType`** | `primary`, `secondary`, `cancel`, `close` | Button types in dialog responses | Check `result.buttonType === ButtonType.primary` | +| **`FieldType`** | `text` | Input field types for input dialogs | Used in dialog field configuration | +| **`DialogResultType`** | `alert`, `input`, `custom` | Types of dialog results returned | Determine result structure type | + +## **Platform & Environment Constants** + +| Constant | Values | Description | Usage | +|----------|--------|-------------|-------| +| **`PlatformType`** | `iOS`, `iPadOS`, `chromeOS`, `android`, `chromeBrowser`, `firefoxBrowser`, `edgeBrowser`, `samsungBrowser`, `safariBrowser`, `unknown` | Platform identification | `platform.platformType === PlatformType.iOS` | +| **`PlatformEnvironment`** | `app`, `web` | Runtime environment type | Check if running in app vs web | +| **`DeviceClass`** | `mobile`, `tablet`, `desktop` | Device category | Responsive design decisions | +| **`RuntimeType`** | `panel`, `dialog`, `documentSandbox`, `command` | Add-on runtime types | Communication API configuration | + +## **Document Export & Rendering Constants** + +| Constant | Values | Description | Usage | +|----------|--------|-------------|-------| +| **`Range`** | `currentPage`, `entireDocument`, `specificPages` | Rendition page ranges | `createRenditions({range: Range.currentPage})` | +| **`RenditionFormat`** | `png`, `jpg`, `mp4`, `pdf`, `pptx` | Export file formats | Specify output format for renditions | +| **`RenditionType`** | `page` | Type of rendition | Document export configuration | +| **`RenditionIntent`** | `export`, `preview`, `print` | Purpose of rendition | Optimize rendering for use case | + +## **Video & Media Constants** + +| Constant | Values | Description | Usage | +|----------|--------|-------------|-------| +| **`VideoResolution`** | `sd480p`, `hd720p`, `fhd1080p`, `qhd1440p`, `uhd2160p`, `custom` | Video export resolutions | Video rendition quality settings | +| **`FrameRate`** | `fps23_976`, `fps24`, `fps25`, `fps29_97`, `fps30`, `fps60` | Video frame rates | Video export frame rate | +| **`BitRate`** | `mbps4` through `mbps50` | Video bit rates in bps | Video compression settings | +| **`BleedUnit`** | `Inch`, `Millimeter` | Print bleed units | Print preparation | + +## **Editor Panel Navigation Constants** + +| Constant | Values | Description | Usage | +|----------|--------|-------------|-------| +| **`EditorPanel`** | `search`, `yourStuff`, `templates`, `media`, `text`, `elements`, `grids`, `brands`, `addOns` | Express editor panels | `openEditorPanel(EditorPanel.media)` | +| **`MediaTabs`** | `video`, `audio`, `photos` | Media panel tabs | Navigate to specific media type | +| **`ElementsTabs`** | `designAssets`, `backgrounds`, `shapes`, `stockIcons`, `charts` | Elements panel tabs | Navigate to specific element type | +| **`PanelActionType`** | `search`, `navigate` | Panel action types | Panel interaction configuration | + +## **Color Picker Constants** + +| Constant | Values | Description | Usage | +|----------|--------|-------------|-------| +| **`ColorPickerPlacement`** | `top`, `bottom`, `left`, `right` | Color picker positioning | `showColorPicker(element, {placement: ColorPickerPlacement.top})` | + +## **File Management Constants** + +| Constant | Values | Description | Usage | +|----------|--------|-------------|-------| +| **`FileSizeLimitUnit`** | `KB`, `MB` | File size limit units | File upload constraints | + +## **OAuth & Authentication Constants** + +| Constant | Values | Description | Usage | +|----------|--------|-------------|-------| +| **`AuthorizationStatus`** | (imported from @hz/wxp-oauth) | OAuth authorization states | Check authorization status | + +## **Usage Examples** + +```typescript +import addOnUISdk, { + Variant, + PlatformType, + RenditionFormat, + EditorPanel +} from "https://new.express.adobe.com/static/add-on-sdk/sdk.js"; + +// Dialog with error variant +await addOnUISdk.app.showModalDialog({ + variant: Variant.error, + title: "Upload Failed", + description: "File size exceeds limit" +}); + +// Platform-specific behavior +const platform = await addOnUISdk.app.getCurrentPlatform(); +if (platform.platformType === PlatformType.iOS) { + // iOS-specific handling +} + +// Export document as PNG +await addOnUISdk.app.document.createRenditions({ + format: RenditionFormat.png, + range: Range.currentPage +}); + +// Navigate to media panel +addOnUISdk.app.ui.openEditorPanel(EditorPanel.media); +``` + +## Notes for Developers + +- **Some constants are available as named exports** from the Add-on UI SDK +- **Use constants instead of string literals** for type safety and future compatibility + +### Import Patterns + +Adobe Express Add-on SDK constants are available through different import patterns depending on the constant type. Understanding these patterns is essential for avoiding runtime errors. + +#### Named Exports (Import Required) + +These constants are **only available as named exports** and must be imported explicitly. They are **NOT** available through `addOnUISdk.constants.*`: + +```javascript +import addOnUISdk, { + AppEvent, + ColorPickerEvent, + SupportedMimeTypes, + EntrypointType, + PdfReturnUrlType +} from "https://new.express.adobe.com/static/add-on-sdk/sdk.js"; + +// βœ… Correct usage +const docxMimeType = SupportedMimeTypes.docx; +const colorChangeEvent = ColorPickerEvent.colorChange; + +// ❌ Will NOT work - these are not in the constants object +const docxMimeType = addOnUISdk.constants.SupportedMimeTypes.docx; // undefined +``` + +#### Dual Access Constants + +These constants support **both import patterns** for flexibility. You can use either approach: + +```javascript +import addOnUISdk, { Range, RenditionFormat, Variant } from "https://new.express.adobe.com/static/add-on-sdk/sdk.js"; + +// Option 1: Named import (recommended for cleaner code) +const currentPage = Range.currentPage; +const pngFormat = RenditionFormat.png; +const confirmDialog = Variant.confirmation; + +// Option 2: Constants object access (traditional pattern) +const currentPage = addOnUISdk.constants.Range.currentPage; +const pngFormat = addOnUISdk.constants.RenditionFormat.png; +const confirmDialog = addOnUISdk.constants.Variant.confirmation; +``` + +**Dual Access Constants List:** + +- `Range` / `addOnUISdk.constants.Range` +- `RenditionFormat` / `addOnUISdk.constants.RenditionFormat` +- `RenditionType` / `addOnUISdk.constants.RenditionType` +- `RenditionIntent` / `addOnUISdk.constants.RenditionIntent` +- `Variant` / `addOnUISdk.constants.Variant` +- `DialogResultType` / `addOnUISdk.constants.DialogResultType` +- `ButtonType` / `addOnUISdk.constants.ButtonType` +- `RuntimeType` / `addOnUISdk.constants.RuntimeType` +- `BleedUnit` / `addOnUISdk.constants.BleedUnit` +- `EditorPanel` / `addOnUISdk.constants.EditorPanel` +- `MediaTabs` / `addOnUISdk.constants.MediaTabs` +- `ElementsTabs` / `addOnUISdk.constants.ElementsTabs` +- `PanelActionType` / `addOnUISdk.constants.PanelActionType` +- `ColorPickerPlacement` / `addOnUISdk.constants.ColorPickerPlacement` +- `AuthorizationStatus` / `addOnUISdk.constants.AuthorizationStatus` +- `FieldType` / `addOnUISdk.constants.FieldType` +- `PlatformEnvironment` / `addOnUISdk.constants.PlatformEnvironment` +- `DeviceClass` / `addOnUISdk.constants.DeviceClass` +- `PlatformType` / `addOnUISdk.constants.PlatformType` +- `MediaType` / `addOnUISdk.constants.MediaType` +- `VideoResolution` / `addOnUISdk.constants.VideoResolution` +- `FrameRate` / `addOnUISdk.constants.FrameRate` +- `BitRate` / `addOnUISdk.constants.BitRate` +- `FileSizeLimitUnit` / `addOnUISdk.constants.FileSizeLimitUnit` +- `LinkOptions` / `addOnUISdk.constants.LinkOptions` + +### Best Practices + +1. **Use named imports for cleaner code** when you know which constants you need: + ```javascript + import addOnUISdk, { Range, RenditionFormat } from "https://new.express.adobe.com/static/add-on-sdk/sdk.js"; + + const options = { + range: Range.currentPage, + format: RenditionFormat.png + }; + ``` + +2. **Use constants object for dynamic access** when the constant name is determined at runtime: + ```javascript + const format = addOnUISdk.constants.RenditionFormat[userSelectedFormat]; + ``` + +3. **Always import named-only exports** - there's no alternative way to access them: + ```javascript + import addOnUISdk, { SupportedMimeTypes } from "https://new.express.adobe.com/static/add-on-sdk/sdk.js"; + ``` + +### Quick Reference Table + +| Constant | Named Export | Constants Object | Import Required | +|----------|--------------|------------------|-----------------| +| `AppEvent` | βœ… | ❌ | **Yes** | +| `ColorPickerEvent` | βœ… | ❌ | **Yes** | +| `SupportedMimeTypes` | βœ… | ❌ | **Yes** | +| `EntrypointType` | βœ… | ❌ | **Yes** | +| `PdfReturnUrlType` | βœ… | ❌ | **Yes** | +| `Range` | βœ… | βœ… | Optional | +| `RenditionFormat` | βœ… | βœ… | Optional | +| `Variant` | βœ… | βœ… | Optional | +| `ButtonType` | βœ… | βœ… | Optional | +| `FieldType` | βœ… | βœ… | Optional | +| `PlatformEnvironment` | βœ… | βœ… | Optional | +| `DeviceClass` | βœ… | βœ… | Optional | +| `PlatformType` | βœ… | βœ… | Optional | +| `AuthorizationStatus` | βœ… | βœ… | Optional | +| `RenditionType` | βœ… | βœ… | Optional | +| `RenditionIntent` | βœ… | βœ… | Optional | +| `DialogResultType` | βœ… | βœ… | Optional | +| `RuntimeType` | βœ… | βœ… | Optional | +| `BleedUnit` | βœ… | βœ… | Optional | +| `EditorPanel` | βœ… | βœ… | Optional | +| `MediaTabs` | βœ… | βœ… | Optional | +| `ElementsTabs` | βœ… | βœ… | Optional | +| `PanelActionType` | βœ… | βœ… | Optional | +| `ColorPickerPlacement` | βœ… | βœ… | Optional | +| `MediaType` | βœ… | βœ… | Optional | +| `VideoResolution` | βœ… | βœ… | Optional | +| `FrameRate` | βœ… | βœ… | Optional | +| `BitRate` | βœ… | βœ… | Optional | +| `FileSizeLimitUnit` | βœ… | βœ… | Optional | +| `LinkOptions` | βœ… | βœ… | Optional | + + + +**Important:** Attempting to access named-only exports through `addOnUISdk.constants.*` will return `undefined` and may cause runtime errors. Always check the table above or use TypeScript for compile-time validation. diff --git a/src/pages/guides/learn/platform_concepts/doc-sandbox.md b/src/pages/guides/learn/platform_concepts/doc-sandbox.md new file mode 100644 index 000000000..ffb5df031 --- /dev/null +++ b/src/pages/guides/learn/platform_concepts/doc-sandbox.md @@ -0,0 +1,79 @@ +# Document Sandbox: The Full Picture + +The Document Sandbox includes **three main components**: + +## 1. **Document APIs** + +- **Scenegraph manipulation**: `RectangleNode`, `TextNode`, `PageNode`, etc. +- **Editor operations**: `editor.createRectangle()`, `editor.queueAsyncEdit()` +- **Document structure**: Direct access to document elements and properties + +## 2. **Communication API Layer** + +- **`runtime.exposeApi()`**: Expose functions from Document Sandbox to UI iframe +- **`runtime.apiProxy()`**: Get proxy to call functions in UI iframe +- **Bidirectional communication**: Document Sandbox ↔ UI iframe communication + +## 3. **Global Browser API Access** (Limited Subset) + +- **Console**: `console.log()`, `console.error()`, etc. +- **Blob API**: `new Blob()`, `blob.arrayBuffer()`, `blob.text()` +- **Basic JavaScript globals**: But **not** full DOM access + +### **Example: Full Document Sandbox Usage** + +```typescript +// Document Sandbox code (runs in QuickJS/isolated environment) +import addOnSandboxSdk from "add-on-sdk-document-sandbox"; +import { editor, constants } from "express-document-sdk"; + +const { runtime } = addOnSandboxSdk.instance; + +// 1. DOCUMENT APIs - Direct scenegraph manipulation +function createLayout() { + const rect = editor.createRectangle(); + rect.width = 100; + rect.height = 50; + editor.context.insertionParent.children.append(rect); +} + +// 2. COMMUNICATION APIs - Expose functions to UI iframe +const sandboxApi = { + createLayout, + addCustomText: (text: string) => { + const textNode = editor.createText(text); + editor.context.insertionParent.children.append(textNode); + } +}; + +runtime.exposeApi(sandboxApi); + +// Get proxy to call UI iframe functions +const uiProxy = await runtime.apiProxy(RuntimeType.panel); +uiProxy.updateStatus("Layout created!"); + +// 3. GLOBAL BROWSER APIs - Limited subset available +console.log("Document sandbox initialized"); +const data = new Blob(["Hello"], { type: "text/plain" }); +const text = await data.text(); +``` + +### **Architecture: Two Runtime Communication** + +``` +β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” +β”‚ UI iframe β”‚ β”‚ Document Sandbox β”‚ +β”‚ (addOnUISdk) β”‚ β”‚ (Document Sandbox SDK) β”‚ +β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€ β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€ +β”‚ β€’ DOM access β”‚ β”‚ β€’ Document APIs (editor, nodes) β”‚ +β”‚ β€’ Full browser APIs │◄──►│ β€’ Limited browser APIs (console, β”‚ +β”‚ β€’ Event handling β”‚ β”‚ Blob) β”‚ +β”‚ β€’ UI interactions β”‚ β”‚ β€’ Communication APIs (runtime) β”‚ +β”‚ β€’ addOnUISdk.app.document.addImage()β”‚ β”‚ β€’ Direct scenegraph manipulation β”‚ +β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ + β”‚ β”‚ + β”‚ runtime.apiProxy() β”‚ + β”‚ runtime.exposeApi() β”‚ + β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ + Communication Layer +``` diff --git a/src/pages/guides/learn/platform_concepts/document-api.md b/src/pages/guides/learn/platform_concepts/document-api.md index 49348dd3b..7104c26eb 100644 --- a/src/pages/guides/learn/platform_concepts/document-api.md +++ b/src/pages/guides/learn/platform_concepts/document-api.md @@ -206,6 +206,17 @@ A specific **naming convention** exists for methods used in the Adobe Express DO - `create*`: used for live document objects, like `createEllipse()` - `add*`: reserved to create complex structures like Pages and Artboards, adding them to the parent list automatically in a way that may also update the insertion point. Found in `addPage()` and `addArtboard()`. + + +Common Mistake: Use `addPage()`, not `createPage()` + +**There is no `createPage()` method** in the Adobe Express Document API. This is a frequent source of confusion for developers and LLMs. + +βœ… **Correct:** `editor.addPage({ width: 1080, height: 1080 })` +❌ **Wrong:** `editor.createPage()` (doesn't exist) + +See the [Manage Pages how-to guide](../how_to/manage_pages.md) for comprehensive examples and best practices. + Speaking of colors, the `ColorUtils` module can output a proper `Color` from either a partial RGB object (where the `alpha` property is optional, defaulting to `1`) or a Hex string with or without alpha, providing the reverse function as well. ```js diff --git a/src/pages/guides/learn/platform_concepts/runtime-architecture.md b/src/pages/guides/learn/platform_concepts/runtime-architecture.md new file mode 100644 index 000000000..c66e33f20 --- /dev/null +++ b/src/pages/guides/learn/platform_concepts/runtime-architecture.md @@ -0,0 +1,657 @@ +--- +keywords: + - Adobe Express + - Add-on SDK + - Adobe Express Editor + - SDK (Software Development Kit) + - JavaScript + - Extend + - Extensibility + - API + - Runtime + - Communication + - Document Sandbox + - Document Sandbox SDK + - Document API + - UI Runtime + - Architecture + - Two-Runtime System +title: Runtime Architecture & Communication +description: Understanding the two-runtime architecture and communication system in Adobe Express add-ons. +contributors: + - https://github.com/hollyschinsky +--- + +# Runtime Architecture & Communication + +Understanding Adobe Express add-on architecture is crucial for building effective add-ons. This guide explains the two-runtime system and how communication works between environments. + +## Two-Runtime Architecture Overview + +Adobe Express add-ons run in **two separate JavaScript execution environments** that work together: + +1. **UI Runtime** - Your add-on's user interface (iframe) +2. **Document Sandbox** - Secure environment for document manipulation + +## Architecture Diagrams + +### **Architecture: Two Runtime Communication** + +``` +β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” +β”‚ UI iframe β”‚ β”‚ Document Sandbox β”‚ +β”‚ (Add-on UI SDK) β”‚ β”‚ (Document Sandbox SDK) β”‚ +β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€ β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€ +β”‚ β€’ DOM access β”‚ β”‚ β€’ Document APIs (editor, nodes) β”‚ +β”‚ β€’ Full browser APIs │◄──►│ β€’ Limited browser APIs (console, β”‚ +β”‚ β€’ Event handling β”‚ β”‚ Blob) β”‚ +β”‚ β€’ UI interactions β”‚ β”‚ β€’ Communication APIs (runtime) β”‚ +β”‚ β€’ addOnUISdk.app.document.addImage()β”‚ β”‚ β€’ Direct scenegraph manipulation β”‚ +β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ + β”‚ β”‚ + β”‚ runtime.apiProxy() β”‚ + β”‚ runtime.exposeApi() β”‚ + β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ + Communication Layer +``` +## What is the Runtime Object? + +The `runtime` object acts as a **communication bridge** between the two environments. Each environment has its own runtime object: + +- **UI Runtime**: `addOnUISdk.instance.runtime` +- **Document Sandbox**: `addOnSandboxSdk.instance.runtime` + +The communication bridge can be thought of like a phone line - each side has their own phone to call the other side. + +## The Two Environments Explained + +### UI Runtime Environment + +**File:** `index.js` or `index.html` +**Purpose:** User interface and browser interactions +**SDK Import:** +```js +import addOnUISdk from "https://express.adobe.com/static/add-on-sdk/sdk.js"; +``` + +**Capabilities:** + +- Render your add-on's user interface +- Handle user interactions (clicks, form inputs) +- Access browser APIs (fetch, localStorage) +- OAuth authentication +- Modal dialogs +- File uploads/downloads + +### Document Sandbox Environment + +**File:** `code.js` +**Purpose:** Document manipulation and content creation +**SDK Import:** +```js +import addOnSandboxSdk from "add-on-sdk-document-sandbox"; +``` + +**Capabilities:** + +- Access Adobe Express Document API +- Create and modify document elements +- Export renditions +- Secure, isolated execution +- Limited browser API access + +## Communication Flow + +### From UI to Document Sandbox + +When you want to manipulate the document from your UI: + +#### UI Runtime (index.js) + +```js +// ui/index.js - Your add-on's interface +import addOnUISdk from "https://express.adobe.com/static/add-on-sdk/sdk.js"; + +addOnUISdk.ready.then(async () => { + const { runtime } = addOnUISdk.instance; + + // Button click handler + document.getElementById("createText").addEventListener("click", async () => { + // Get a proxy to call document sandbox functions + const sandboxProxy = await runtime.apiProxy("documentSandbox"); + + // Call function exposed in code.js + await sandboxProxy.createTextElement("Hello World!"); + }); +}); +``` + +#### Document Sandbox (code.js) + +```js +// sandbox/code.js - Document manipulation +import addOnSandboxSdk from "add-on-sdk-document-sandbox"; +import { editor } from "express-document-sdk"; + +const { runtime } = addOnSandboxSdk.instance; + +// Expose functions that UI can call +runtime.exposeApi({ + createTextElement: async function(text) { + await editor.queueAsyncEdit(() => { + const textNode = editor.createText(text); + editor.context.insertionParent.children.append(textNode); + }); + } +}); +``` + +### From Document Sandbox to UI + +When you want to update the UI from document operations: + +#### Document Sandbox (code.js) +```js +// sandbox/code.js - Document manipulation +import addOnSandboxSdk from "add-on-sdk-document-sandbox"; + +const { runtime } = addOnSandboxSdk.instance; + +async function processDocument() { + // Get a proxy to call UI functions + const uiProxy = await runtime.apiProxy("panel"); + + // Update UI with progress + await uiProxy.updateProgress(50); + + // Document manipulation here... + + await uiProxy.updateProgress(100); +} +``` + +#### UI Runtime (index.js) +```js +// ui/index.js - Your add-on's interface +import addOnUISdk from "https://express.adobe.com/static/add-on-sdk/sdk.js"; + +addOnUISdk.ready.then(() => { + const { runtime } = addOnUISdk.instance; + + // Expose functions that document sandbox can call + runtime.exposeApi({ + updateProgress: function(percentage) { + const progressBar = document.getElementById("progress"); + progressBar.style.width = percentage + "%"; + } + }); +}); +``` + +### Runtime Type Detection + +#### UI Runtime (index.js) +```js +// ui/index.js - Your add-on's interface +import addOnUISdk from "https://express.adobe.com/static/add-on-sdk/sdk.js"; + +addOnUISdk.ready.then(() => { + const { runtime } = addOnUISdk.instance; + // Check what type of runtime you're in + console.log(runtime.type); // "panel" +}); +``` + +#### Document Sandbox (code.js) + +```js +import addOnSandboxSdk from "add-on-sdk-document-sandbox"; + +const { runtime } = addOnSandboxSdk.instance; + +// Check what type of runtime you're in +console.log(runtime.type); // "documentSandbox" +``` + +## Understanding the Document Sandbox SDK Imports + +### When do I need both SDK imports in `code.js`? + +This is a common source of confusion. In your `code.js` file, you may need one or both imports depending on what your add-on does: + +#### Option 1: Communication Only +```js +// sandbox/code.js - Only communication, no document manipulation +import addOnSandboxSdk from "add-on-sdk-document-sandbox"; + +const { runtime } = addOnSandboxSdk.instance; + +// Only exposing APIs, not creating document content +runtime.exposeApi({ + processData: function(data) { + // Process data, return results + return data.map(item => item.toUpperCase()); + } +}); +``` + +#### Option 2: Document Manipulation Only +```js +// sandbox/code.js - Only document creation, no communication +import { editor } from "express-document-sdk"; + +// Directly manipulate document without UI communication +editor.queueAsyncEdit(() => { + const text = editor.createText("Auto-generated content"); + editor.context.insertionParent.children.append(text); +}); +``` + +#### Option 3: Both Communication AND Document Manipulation +```js +// sandbox/code.js - Most common pattern +import addOnSandboxSdk from "add-on-sdk-document-sandbox"; // For communication +import { editor } from "express-document-sdk"; // For document manipulation + +const { runtime } = addOnSandboxSdk.instance; + +// Expose APIs that manipulate the document +runtime.exposeApi({ + createTextElement: async function(text) { + await editor.queueAsyncEdit(() => { + const textNode = editor.createText(text); + editor.context.insertionParent.children.append(textNode); + }); + } +}); +``` + +### Quick Decision Guide + +**Do I need `addOnSandboxSdk`?** + +- βœ… YES if your `code.js` needs to communicate with the UI +- βœ… YES if UI triggers document operations +- ❌ NO if `code.js` runs independently + +**Do I need `express-document-sdk`?** + +- βœ… YES if creating/modifying document content +- βœ… YES if accessing document properties +- ❌ NO if only processing data or communicating + +### Examples + +#### Example 1: Text Generator Add-on + +```js +// sandbox/code.js - Needs BOTH imports +import addOnSandboxSdk from "add-on-sdk-document-sandbox"; // UI sends text data +import { editor } from "express-document-sdk"; // Create text elements + +const { runtime } = addOnSandboxSdk.instance; + +runtime.exposeApi({ + generateText: async function(userInput) { + await editor.queueAsyncEdit(() => { + const text = editor.createText(userInput); + editor.context.insertionParent.children.append(text); + }); + } +}); +``` + +#### Example 2: Document Analytics Add-on + +```js +// sandbox/code.js - Needs BOTH imports +import addOnSandboxSdk from "add-on-sdk-document-sandbox"; // Send results to UI +import { editor } from "express-document-sdk"; // Analyze document + +const { runtime } = addOnSandboxSdk.instance; + +runtime.exposeApi({ + analyzeDocument: function() { + const pageCount = editor.context.insertionParent.children.length; + return { pageCount, elements: pageCount * 5 }; // Send data back to UI + } +}); +``` + +#### Example 3: Document Analysis Utility + +```js +// sandbox/code.js - Only needs express-document-sdk +import { editor } from "express-document-sdk"; + +// Analyze current document structure (no UI communication needed) +function analyzeDocument() { + const pages = editor.context.insertionParent.children; + const elementCount = pages.length; + + console.log(`Document analysis: ${elementCount} elements found`); + + // Could save analysis results to document metadata or + // trigger other document operations based on analysis + return { elementCount, timestamp: new Date().toISOString() }; +} + +// Execute analysis +const results = analyzeDocument(); +``` + +## Common Patterns + +### Pattern 1: UI-Triggered Document Operations (Most Common) + +User interactions in the UI trigger document changes: + +```js +// UI: User clicks button β†’ Document: Create content +// Requires BOTH imports in code.js +``` + +### Pattern 2: Document-Driven UI Updates + +Document analysis sends results to update the UI: + +```js +// Document: Analyze content β†’ UI: Show results +// Requires BOTH imports in code.js +``` + +### Pattern 3: Independent Document Operations + +Document analysis or operations that run without UI interaction: + +```js +// Document: Analyze/process content +// Only requires express-document-sdk +``` + +## Complete add-on-sdk-document-sandbox Package Overview + +The `add-on-sdk-document-sandbox` package provides more than just the `runtime` object. Here's what you get: + +### Core Features + +#### 1. Communication System (`addOnSandboxSdk.instance.runtime`) +```js +import addOnSandboxSdk from "add-on-sdk-document-sandbox"; + +const { runtime } = addOnSandboxSdk.instance; + +// Expose APIs to UI +runtime.exposeApi({ /* your functions */ }); + +// Get proxy to UI APIs +const uiProxy = await runtime.apiProxy("panel"); +``` + +#### 2. Injected Web APIs (Global - No Import Needed) +The document sandbox automatically injects limited browser APIs for common tasks. See the complete [Web APIs Reference](../../../references/document-sandbox/web/index.md) for details. + +```js +// These are automatically available in your code.js +console.log("Debugging output"); +console.error("Something went wrong"); +console.warn("Be careful here"); +console.clear(); // Clear console +``` + +#### 3. Secure Execution Environment +- **Isolated JavaScript context**: Your code runs in a secure sandbox +- **Limited browser APIs**: Only essential APIs are available +- **Performance considerations**: Slower than UI runtime but secure +- **No direct DOM access**: Must communicate through UI runtime + +### What You DON'T Need to Import + +These are automatically injected and available globally in the document sandbox. For complete details, see [Web APIs Reference](../../../references/document-sandbox/web/index.md). + +```js +// βœ… Available automatically - no import needed +console.log("This works!"); +console.error("Error logging works!"); +console.warn("Warning logging works!"); +console.info("Info logging works!"); +console.debug("Debug logging works!"); +console.clear(); // Clear console +console.assert(condition, "Assertion message"); + +// Blob interface for binary data +const blob = new Blob(['data'], { type: 'text/plain' }); +blob.text().then(text => console.log(text)); +``` + +### What You DO Need to Import + +#### For Communication: +```js +import addOnSandboxSdk from "add-on-sdk-document-sandbox"; +const { runtime } = addOnSandboxSdk.instance; +``` + +#### For Document Manipulation: +```js +import { editor, colorUtils, constants, fonts } from "express-document-sdk"; +``` + +### Debugging Capabilities + +Since the document sandbox has limited debugging, use these patterns: + +```js +// Basic debugging with console +console.log("Variable value:", myVariable); + +// Object inspection +console.log("Full object:", JSON.stringify(myObject, null, 2)); + +// Error tracking +try { + // Risky operation + const result = editor.createText("Hello"); + console.log("Success:", result); +} catch (error) { + console.error("Operation failed:", error.message); +} + +// Assertion testing +console.assert(myVariable !== undefined, "Variable should be defined"); +``` + +### Quick Reference: All SDK Imports + +| Feature | UI Runtime
`addOnUISdk` | Document Sandbox
`addOnSandboxSdk` | Document Sandbox
`express-document-sdk` | +|---------|------------|------------------|-----------------| +| **Import Statement** | `import addOnUISdk from "https://new.express.adobe.com/static/add-on-sdk/sdk.js"` | `import addOnSandboxSdk from "add-on-sdk-document-sandbox"` | `import { editor } from "express-document-sdk"` | +| **File Location** | `index.js/index.html` | `code.js` | `code.js` | +| **Primary Purpose** | User interface & browser interactions | Communication between environments | Document manipulation | +| **Runtime Communication** | βœ… `instance.runtime` | βœ… `instance.runtime` | ❌ No communication | +| **Document Creation** | ❌ No direct access | ❌ No direct access | βœ… `editor.createText()`, etc. | +| **Document Reading** | ❌ No direct access | ❌ No direct access | βœ… `editor.context` | +| **Export Renditions** | βœ… `app.document.createRenditions()` | ❌ No direct access | ❌ No direct access | +| **Modal Dialogs** | βœ… `app.showModalDialog()` | ❌ No direct access | ❌ No direct access | +| **OAuth** | βœ… `app.oauth` | ❌ No direct access | ❌ No direct access | +| **File Upload/Download** | βœ… Full browser APIs | ❌ No direct access | ❌ No direct access | +| **DOM Manipulation** | βœ… Full DOM access | ❌ No DOM access | ❌ No DOM access | +| **Browser APIs** | βœ… All browser APIs | ❌ Limited (console, Blob) | ❌ Limited (console, Blob) | +| **Platform Detection** | βœ… `app.getCurrentPlatform()` | ❌ No direct access | ❌ No direct access | +| **Client Storage** | βœ… `instance.clientStorage` | ❌ No direct access | ❌ No direct access | +| **Constants** | βœ… `constants.*` | ❌ No constants | βœ… Document constants | + +### Manifest Configuration + +The sandbox requires proper manifest setup: + +```json +{ + "entryPoints": [ + { + "type": "panel", + "id": "panel1", + "main": "index.html", + "documentSandbox": "code.js" + } + ] +} +``` + +### SDK Import Decision Matrix + +| Your Add-on Needs | UI Runtime | Document Sandbox | Required Imports | +|-------------------|------------|------------------|------------------| +| **UI only** (no document changes) | βœ… | ❌ | `addOnUISdk` | +| **Document manipulation only** | ❌ | βœ… | `express-document-sdk` | +| **UI + Document communication** | βœ… | βœ… | `addOnUISdk` + `addOnSandboxSdk` | +| **UI + Document creation** | βœ… | βœ… | `addOnUISdk` + `addOnSandboxSdk` + `express-document-sdk` | +| **Data processing only** | ❌ | βœ… | `addOnSandboxSdk` (for communication) | +| **Export/Import only** | βœ… | ❌ | `addOnUISdk` | + +### Common Import Patterns + +```js +// Pattern 1: UI-only add-on (no document changes) +// index.js +import addOnUISdk from "https://new.express.adobe.com/static/add-on-sdk/sdk.js"; +// No code.js file needed + +// Pattern 2: Document manipulation only +// code.js +import { editor } from "express-document-sdk"; +// No index.js communication needed + +// Pattern 3: Full communication + document manipulation (most common) +// index.js +import addOnUISdk from "https://new.express.adobe.com/static/add-on-sdk/sdk.js"; + +// code.js +import addOnSandboxSdk from "add-on-sdk-document-sandbox"; // For communication +import { editor } from "express-document-sdk"; // For document APIs +``` + +## Best Practices + +1. **Clear File Organization** + - Keep UI logic in `index.js` + - Keep document logic in `code.js` + - Use consistent naming for exposed APIs + +2. **Error Handling** + - Always wrap API calls in try-catch blocks + - Provide meaningful error messages to users + - Handle communication failures gracefully + - Use `console.error()` for debugging in sandbox + +3. **Performance** + - Minimize data transfer between environments + - Batch multiple operations when possible + - Use appropriate data types (see [Communication APIs](../../../references/document-sandbox/communication/index.md#data-types)) + - Remember sandbox runs slower than UI runtime + +4. **Debugging** + - Use `console.log` in both environments + - Check browser dev tools for UI runtime + - Use sandbox console for document sandbox debugging + - Use `console.assert()` for validation checks + +## Error Handling + +### Common Error: "Runtime is undefined" + +❌ **Wrong:** Trying to use runtime before SDK is ready +```js +const { runtime } = addOnUISdk.instance; // Error! SDK not ready yet +``` + +βœ… **Correct:** Wait for SDK ready state +```js +addOnUISdk.ready.then(() => { + const { runtime } = addOnUISdk.instance; // Now it's safe +}); +``` + +### Common Error: "Cannot read property 'apiProxy' of undefined" + +❌ **Wrong:** Using wrong SDK in wrong environment +```js +// In code.js (Document Sandbox) +import addOnUISdk from "https://express.adobe.com/static/add-on-sdk/sdk.js"; +const { runtime } = addOnUISdk.instance; // Wrong SDK! +``` + +βœ… **Correct:** Use appropriate SDK for each environment +```js +// In code.js (Document Sandbox) +import addOnSandboxSdk from "add-on-sdk-document-sandbox"; +const { runtime } = addOnSandboxSdk.instance; // Correct SDK! +``` + +## Frequently Asked Questions + +### Q: Why are there two different runtime objects? + +**A:** Each environment has its own runtime object that acts as a "communication phone." The UI runtime calls the document sandbox, and the document sandbox runtime calls the UI. This separation ensures security and proper isolation. + +### Q: When do I use `addOnUISdk.instance.runtime` vs `addOnSandboxSdk.instance.runtime`? + +**A:** Use the runtime object from the environment you're currently in: +- In `index.js` (UI): Use `addOnUISdk.instance.runtime` +- In `code.js` (Sandbox): Use `addOnSandboxSdk.instance.runtime` + +### Q: What does `await runtime.apiProxy("documentSandbox")` actually do? + +**A:** It creates a proxy object that lets you call functions you've exposed in the document sandbox from your UI code. Think of it as getting a "remote control" for the other environment. + +### Q: What's the difference between `"documentSandbox"` and `"panel"` in apiProxy? + +**A:** These specify which environment you want to communicate with: +- `"documentSandbox"` - Call from UI to document sandbox +- `"panel"` - Call from document sandbox to UI + +### Q: Can I access document APIs directly from the UI? + +**A:** No, document APIs are only available in the document sandbox for security reasons. You must use the communication system to bridge between environments. + +### Q: How do I know which environment my code is running in? + +**A:** Check your file structure and imports: +- If you're importing `addOnUISdk` β†’ You're in the UI runtime +- If you're importing `addOnSandboxSdk` β†’ You're in the document sandbox + +### Q: Do I always need both imports in my code.js file? + +**A:** No! It depends on what your add-on does: + +- **Communication only**: Just `addOnSandboxSdk` (no document changes) +- **Document only**: Just `express-document-sdk` (no UI communication) +- **Both**: Most add-ons need both for UI-triggered document operations + +### Q: Why do some examples show one import and others show both? + +**A:** Different add-on types have different needs: +- Simple document manipulation β†’ One import +- UI-controlled document changes β†’ Both imports +- The example context determines which imports are shown + +### Q: What else is available in the add-on-sdk-document-sandbox package? + +**A:** Besides `runtime`, the sandbox SDK provides: +- **Web APIs**: Limited browser APIs like `console` for debugging +- **Automatic global injections**: No need to import basic APIs +- **Secure execution environment**: Isolated JavaScript context + +## Related Topics + +- [Communication APIs Reference](../../../references/document-sandbox/communication/index.md) +- [UI SDK Reference](../../../references/addonsdk/index.md) +- [Document API Concepts](./document-api.md) + +## Next Steps + +Now that you understand the architecture, explore these guides and tutorials: + +- [Document API deep dive](./document-api.md): Learn how to use the Document API to create and modify document content. +- [Building your first add-on](../how_to/tutorials/grids-addon.md): Use the Document API to create a simple add-on that adds a grid to the document. +- [Using the Communication APIs](../how_to/tutorials/stats-addon.md): Build an add-on to gather statistics on the active document using the Communication APIs. diff --git a/src/pages/guides/learn/platform_concepts/terminology.md b/src/pages/guides/learn/platform_concepts/terminology.md new file mode 100644 index 000000000..718dc9086 --- /dev/null +++ b/src/pages/guides/learn/platform_concepts/terminology.md @@ -0,0 +1,149 @@ +# Terminology + +## **Terminology Summary: Add-on UI SDK vs Document SDK vs Document APIs** + +### **1. Add-on UI SDK** +**Developer Import:** `"https://new.express.adobe.com/static/add-on-sdk/sdk.js"` + +**What it is:** The UI runtime environment for add-ons +- **Purpose:** Application interaction, UI management, document-level operations +- **Runtime:** Iframe-based UI environment +- **API Style:** Method-based, Promise-driven +- **Example Usage:** `addOnUISdk.app.document.addImage()`, `addOnUISdk.app.showModalDialog()` + +### **2. Document SDK** +**Developer Import:** `"express-document-sdk"` + +**What it is:** The complete document manipulation environment +- **Purpose:** Direct document content creation and manipulation +- **Runtime:** Document sandbox environment +- **API Style:** Object-oriented scenegraph manipulation +- **Example Usage:** `editor.createRectangle()`, `new Color()`, `textNode.text = "Hello"` + +### **3. Document APIs** +**Technical Clarification:** This is a **subset** of the Document SDK + +**What it includes:** Just the core document manipulation APIs from `"express-document-sdk"` +- **Core APIs:** `editor`, `colorUtils`, `fonts`, `viewport` +- **Node Classes:** `RectangleNode`, `TextNode`, `GroupNode`, etc. +- **Utilities:** `Color`, `constants`, geometry classes + +## **Key Distinction for Documentation** + +| Term | Scope | Developer Perspective | +|------|-------|---------------------| +| **Add-on UI SDK** | Complete UI runtime | `import addOnUISdk from "sdk.js"` | +| **Document SDK** | Complete document environment | `import {...} from "express-document-sdk"` | +| **Document APIs** | Core document manipulation only | Subset of Document SDK imports | + +## **Recommended Usage in Documentation** + +- **Use "Add-on UI SDK"** when referring to UI interactions, dialogs, app-level operations +- **Use "Document SDK"** when referring to the complete document manipulation environment +- **Use "Document APIs"** when specifically discussing just the core manipulation methods (not the full environment) + +## Add-on UI SDK vs Document API + +The **Add-on UI SDK** provides methods for application-level operations: +- `addOnUISdk.app.document.addImage(blob)` +- `addOnUISdk.app.showModalDialog(options)` + +The **Document API** provides direct scenegraph manipulation: +- `editor.createRectangle()` +- `rectangle.width = 100` + +## **Document SDK APIs (aka Document APIs) vs Add-on UI SDK** + +### **Document SDK** +**What developers import:** `"express-document-sdk"` + +```typescript +// Document SDK imports - for document manipulation +import { colorUtils, constants, editor, fonts, viewport } from "express-document-sdk"; +import type { + Color, GroupNode, RectangleNode, TextNode, + BitmapImage, MediaContainerNode +} from "express-document-sdk"; +``` + +**What it provides:** + +- **Document manipulation APIs**: `editor.createRectangle()`, `editor.createText()` +- **Node classes**: `RectangleNode`, `TextNode`, `GroupNode`, etc. +- **Utilities**: `colorUtils`, `fonts`, `viewport` +- **Constants**: Document-related enums and constants + +### **Add-on UI SDK** + +**What developers import:** `"https://new.express.adobe.com/static/add-on-sdk/sdk.js"` + +```typescript +// Add-on UI SDK import - for UI and app interaction +import addOnUISdk from "https://new.express.adobe.com/static/add-on-sdk/sdk.js"; +``` + +**What it provides:** + +- **Application APIs**: `addOnUISdk.app.document.addImage()` +- **UI APIs**: `addOnUISdk.app.showModalDialog()` +- **Event handling**: `addOnUISdk.app.on("themechange")` + +### **Document Sandbox Runtime** +**What developers import:** `"add-on-sdk-document-sandbox"` + +```typescript +// Document Sandbox Runtime - for communication between UI and Document +import addOnSandboxSdk from "add-on-sdk-document-sandbox"; +const runtime = addOnSandboxSdk.instance.runtime; +``` + +**What it provides:** + +- **Communication APIs**: `runtime.exposeApi()`, `runtime.apiProxy()` +- **Runtime management**: Handles UI ↔ Document communication + +## Imports & Purpose + +| Term | Developer Import | Purpose | +|------|------------------|---------| +| **Document SDK** | `"express-document-sdk"` | Direct document manipulation | +| **Add-on UI SDK** | `"sdk.js"` | UI and application interaction | +| **Document Sandbox Runtime** | `"add-on-sdk-document-sandbox"` | Communication layer | + + + + + + + + + +## Add-on Development SDKs + +- **Add-on UI SDK** vs **Document API** +- **UI SDK** vs **Document API** +- **UI Runtime APIs** vs **Document Sandbox APIs** +- **Iframe Runtime** vs **Document Sandbox Runtime** + +| Aspect | **Add-on UI SDK** | **Document API** | +|--------|-------------------|------------------| +| **Runtime Environment** | UI iframe (full browser) | Document Sandbox (QuickJS) | +| **Primary Purpose** | UI interactions, app-level operations | Direct document manipulation | +| **API Style** | Method-based, async | Object-oriented, mostly sync | +| **What You Get** | `addOnUISdk.app.document.addImage()` | `editor.createRectangle()` | + +## Add-on UI SDK vs Document API + +The **Add-on UI SDK** provides methods for application-level operations: + +```js +- `addOnUISdk.app.document.addImage(blob)` +- `addOnUISdk.app.showModalDialog(options)` +``` + +The **Document API** provides direct scenegraph manipulation: + +```js +- `editor.createRectangle()` +- `rectangle.width = 100` +``` diff --git a/src/pages/guides/learn/platform_concepts/ui-sdk-vs-document-sdk.md b/src/pages/guides/learn/platform_concepts/ui-sdk-vs-document-sdk.md new file mode 100644 index 000000000..fdcb316ae --- /dev/null +++ b/src/pages/guides/learn/platform_concepts/ui-sdk-vs-document-sdk.md @@ -0,0 +1,240 @@ +# Add-on UI SDK APIs vs Document Sandbox SDK APIs + +The Add-on UI SDK APIs and Document APIs in the Document Sandbox SDK provide two different ways to interact with the Adobe Express platform from your add-on, each designed for distinct use cases and interaction patterns. + + + +This guide covers the differences between the UI SDK and the Document API specifically. For more information about the other features available from the Document Sandbox (ie: Communication and Browser APIs), see the [Runtime Architecture](runtime-architecture.md) guide. + +## Overview + +- **UI SDK (addOnUISdk) APIs**: Method-based API for performing actions on documents from your add-on's UI panel +- **Document APIs**: Object-oriented API for creating and manipulating document elements in the document sandbox + +## Key Architectural Differences + +| Aspect | UI SDK (addOnUISdk) | Document API | +|--------|---------------------|--------------| +| **Architecture** | Method-based interfaces | Object-oriented classes | +| **Usage Pattern** | `await document.addImage(blob)` | `rectangle.width = 100` | +| **Purpose** | Perform actions on existing documents | Create and manipulate document elements | +| **Execution Context** | UI iframe | Document sandbox | +| **API Style** | Event-driven, async method calls | Direct property manipulation | +| **Content Control** | Add content to existing document | Create and modify individual elements | + +## Fundamental Differences + +### 1. UI SDK: Remote Control Pattern + +The UI SDK acts like a **remote control** for the document - you send commands to Express: + +```typescript +// Method-based: Call methods to perform actions +await addOnUISdk.app.document.addImage(blob); +await addOnUISdk.app.document.addVideo(blob); +await addOnUISdk.app.showModalDialog(dialogOptions); + +const title = await addOnUISdk.app.document.title(); +const renditions = await addOnUISdk.app.document.createRenditions(options); +``` + +**Mental Model:** *"Hey Express, add this image to the document"* + +### 2. Document API: Building Blocks Pattern + +The Document API gives you **direct control** over document elements like building blocks: + +```typescript +// Object-oriented: Create objects and set their properties +const rectangle = editor.createRectangle(); +rectangle.width = 100; +rectangle.height = 50; +rectangle.fill = solidColor; + +const text = editor.createText("Hello World"); +text.translation = { x: 10, y: 20 }; + +// Add objects to the document +editor.context.insertionParent.children.append(rectangle); +``` + +**Mental Model:** *"Create a rectangle, make it red, put it here"* + +## Use Case Comparison + +| Task | UI SDK (addOnUISdk) | Document API | +|------|---------------------|--------------| +| **Add content** | `await document.addImage(blob)` | `const img = editor.createImageContainer(bitmap)`
`parent.children.append(img)` | +| **Set properties** | ❌ *Can't modify after adding* | `rectangle.width = 100`
`rectangle.height = 50` | +| **Get document info** | `await document.title()` | `const title = document.title` | +| **Apply styling** | ❌ *No direct styling* | `rectangle.fill = redColor`
`text.textRuns[0].fontSize = 24` | +| **Create layouts** | ❌ *Limited to adding content* | βœ… *Full layout control* | +| **Export/Share** | βœ… *Built-in export methods* | ❌ *No export capabilities* | + +## Content Control Approaches + +### UI SDK: Works with Existing Documents +The UI SDK **adds content TO** existing documents but doesn't provide direct control over individual elements: + +```typescript +// Adding content to whatever document is currently open +await addOnUISdk.app.document.addImage(blob); // Adds to current page +await addOnUISdk.app.document.addVideo(blob); // Adds to current page + +// Working with the document as a whole +const metadata = await addOnUISdk.app.document.getPagesMetadata(); +const title = await addOnUISdk.app.document.title(); +const renditions = await addOnUISdk.app.document.createRenditions(options); +``` + +**Key limitation:** You can add content, but you can't directly manipulate individual shapes, text, or existing elements. + +### Document API: Creates and Manipulates Elements +The Document API lets you **create new elements** and **directly control** the document structure: + +```typescript +// Creating new document elements from scratch +const rectangle = editor.createRectangle(); +const ellipse = editor.createEllipse(); +const text = editor.createText("Hello"); + +// Modifying properties of individual elements +rectangle.width = 200; +rectangle.topLeftRadius = 10; +rectangle.fill = { type: "solid", color: { red: 1, green: 0, blue: 0 } }; + +// Controlling document structure directly +const artboard = editor.context.insertionParent; +artboard.children.append(rectangle); +artboard.children.append(text); + +// Accessing and modifying existing elements +const existingNodes = artboard.children; +existingNodes.forEach(node => { + if (node instanceof RectangleNode) { + node.width *= 2; // Double the width of all rectangles + } +}); +``` + +## Event Handling + +### UI SDK: Application-Level Events + +The UI SDK provides **application and document-level events** for monitoring broad system changes: + +```typescript +// React to application-wide changes +addOnUISdk.app.on("themechange", (data) => { + console.log("App theme changed:", data.theme); +}); + +addOnUISdk.app.on("documentLinkAvailable", (data) => { + console.log("Document link available:", data.documentLink); +}); + +addOnUISdk.app.on("documentTitleChange", (data) => { + console.log("Document title changed:", data.documentTitle); +}); +``` + +### Document API: Editor-Level Events + +The Document API provides **editor and selection-level events** for monitoring editing interactions: + +```typescript +// React to user editing actions +const handlerId = editor.context.on("selectionChange", () => { + const selection = editor.context.selection; + console.log("Selection changed, now selected:", selection.length, "items"); + + // Inspect and modify selected elements + selection.forEach(node => { + if (node instanceof RectangleNode) { + node.fill = redColor; // Change selected rectangle color + } + }); +}); + +// Unregister event when done +editor.context.off("selectionChange", handlerId); +``` + +## Synchronous vs Asynchronous Patterns + +### UI SDK: Everything is Async +The UI SDK treats **all operations** as asynchronous because it communicates across process boundaries (iframe ↔ host app): + +```typescript +// ALL operations return Promises - even simple getters +const title = await addOnUISdk.app.document.title(); +const id = await addOnUISdk.app.document.id(); +const allowed = await addOnUISdk.app.document.exportAllowed(); + +// Actions are async +await addOnUISdk.app.document.addImage(blob); +await addOnUISdk.app.document.addVideo(blob); + +// Metadata retrieval is async +const pages = await addOnUISdk.app.document.getPagesMetadata(options); +const renditions = await addOnUISdk.app.document.createRenditions(options); +``` + +**Why async?** Every call goes through RPC (Remote Procedure Call) to the host application. + +### Document API: Mostly Sync with Strategic Async +The Document API uses **synchronous property access** for immediate operations and async only when necessary: + +```typescript +// Property access is synchronous - immediate response +const rectangle = editor.createRectangle(); +rectangle.width = 100; +rectangle.height = 50; +const currentWidth = rectangle.width; + +// Most operations are immediate +rectangle.fill = { type: "solid", color: redColor }; +text.translation = { x: 10, y: 20 }; +artboard.children.append(rectangle); + +// Async ONLY for resource loading +const bitmap = await editor.loadBitmapImage(blob); + +// Async edit queuing after resource loading +await editor.queueAsyncEdit(() => { + const container = editor.createImageContainer(bitmap); + editor.context.insertionParent.children.append(container); +}); +``` + +**Why mostly sync?** Direct object access within the same process, async only for resource loading and edit scheduling. + +## Summary + +| Aspect | UI SDK (addOnUISdk) | Document API | +|--------|---------------------|--------------| +| **Purpose** | Import/export, add media, get metadata | Create layouts, modify shapes, build designs | +| **Architecture** | Method-based interfaces | Object-oriented classes | +| **Control Level** | Add content to existing documents | Create and modify individual elements | +| **Execution Context** | UI iframe (cross-process) | Document sandbox (same-process) | +| **Async Pattern** | Everything is async (RPC calls) | Mostly sync, async for resource loading | +| **Event Scope** | Application-wide changes | Editor/selection changes | +| **Best For** | Content import, document export, UI interactions | Layout creation, element manipulation, design tools | + +### When to Use Each + +**Use UI SDK when you need to:** +- Import images, videos, or audio into documents +- Export documents in various formats (PNG, PDF, PPTX, etc.) +- Get document metadata and information +- Show modal dialogs and UI interactions +- Work with existing document content + +**Use Document API when you need to:** +- Create new visual elements (rectangles, text, images) +- Build complex layouts and designs +- Modify properties of individual elements +- Respond to user selection changes +- Create design tools and content generators + +The UI SDK is like a **remote control** for Express documents, while the Document API provides **direct building blocks** for creating and manipulating visual content. \ No newline at end of file diff --git a/src/pages/guides/support/faq.md b/src/pages/guides/support/faq.md index 1c283a1fc..bb02f78e9 100644 --- a/src/pages/guides/support/faq.md +++ b/src/pages/guides/support/faq.md @@ -86,6 +86,7 @@ contributors: - [I'm not able to load the add-on in the browser anymore. When I click on "Connect", I get an error `ERR_CERT_AUTHORITY_INVALID`.](#im-not-able-to-load-the-add-on-in-the-browser-anymore-when-i-click-on-connect-i-get-an-error-err_cert_authority_invalid) - [Why do I receive a "No 'Access-Control-Allow-Origin' header is present on the requested resource" error?](#why-do-i-receive-a-no-access-control-allow-origin-header-is-present-on-the-requested-resource-error) - [What permissions are available for add-ons and how do I configure them?](#what-permissions-are-available-for-add-ons-and-how-do-i-configure-them) +- [How do I import SDK constants correctly?](#how-do-i-import-sdk-constants-correctly) - [Is `SharedArrayBuffer` supported?](#is-sharedarraybuffer-supported) - [Which browsers and operating systems are currently supported?](#which-browsers-and-operating-systems-are-currently-supported) @@ -100,7 +101,8 @@ contributors: - [I'm trying to use a newly released feature, but it seems to be unavailable?](#im-trying-to-use-a-newly-released-feature-but-it-seems-to-be-unavailable) - [What does it mean when an API is considered **experimental**?](#what-does-it-mean-when-an-api-is-considered-experimental) -- [How does Adobe use my add-on’s data?](#how-does-adobe-use-my-add-ons-data) +- [Why doesn't createPage() work? How do I add pages programmatically?](#why-doesnt-createpage-work-how-do-i-add-pages-programmatically) +- [How does Adobe use my add-on's data?](#how-does-adobe-use-my-add-ons-data) - [Where can I request new add-on features or suggest ideas?](#where-can-i-request-new-add-on-features-or-suggest-ideas) ### πŸ’° Distribution & Monetization @@ -212,6 +214,28 @@ At this time, the only way to monetize is by using a third party provider, and e Experimental APIs are those which have not been declared stable yet, and to try them, first need to set the `experimentalApis` flag to `true` in the [`requirements`](../../references/manifest/index.md#requirements) section of the [`manifest.json`](../../references/manifest/index.md). The `experimentalApis` flag is **only allowed during development** and needs to be removed during submission. Experimental APIs should never be used in any add-ons you will be distributing. +### Why doesn't createPage() work? How do I add pages programmatically? + +The Adobe Express Document API uses **`addPage()`** to create new pages, not `createPage()`. This is a common confusion point for developers and LLMs familiar with other APIs. + +**Correct usage:** +```js +// βœ… Correct - Use addPage() with dimensions +const newPage = editor.addPage({ width: 1080, height: 1080 }); +``` + +**Incorrect usage:** +```js +// ❌ Wrong - createPage() doesn't exist +const newPage = editor.createPage(); // This will throw an error +``` + +The Document API follows a specific naming convention: +- `create*` methods (like `createRectangle()`, `createText()`) are for basic document objects +- `add*` methods (like `addPage()`, `addArtboard()`) are for complex structures that need special handling + +For comprehensive examples and best practices, see our [Manage Pages how-to guide](../learn/how_to/manage_pages.md). + ### What are the supported mime types/file formats for exported content? The supported file types for exported content are: @@ -317,6 +341,25 @@ Adobe Express add-ons support several types of permissions that you configure in For complete details on all available permissions and their usage, see the [manifest permissions documentation](../../references/manifest/index.md#entrypointspermissions) and the [add-on context guide](../learn/platform_concepts/context.md#permissions). +### How do I import SDK constants correctly? + +Adobe Express SDK constants use different import patterns depending on the constant type. Some constants like `SupportedMimeTypes` require named imports, while others like `Range` support both named imports and the `addOnUISdk.constants.*` pattern. + +**Quick Examples:** +```javascript +// Named imports (required for some constants) +import addOnUISdk, { SupportedMimeTypes, Range } from "https://new.express.adobe.com/static/add-on-sdk/sdk.js"; + +// Both patterns work for dual-access constants +const currentPage = Range.currentPage; +const currentPage = addOnUISdk.constants.Range.currentPage; + +// Named-only constants must be imported +const docxType = SupportedMimeTypes.docx; +``` + +See the [complete import patterns guide](../../references/addonsdk/addonsdk-constants.md#import-patterns) for detailed documentation, examples, and a comprehensive list of which constants require named imports vs. support dual access. + ### Is [`SharedArrayBuffer`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/SharedArrayBuffer) supported? No, `SharedArrayBuffer` is not currently available to use with your add-ons. diff --git a/src/pages/references/addonsdk/addonsdk-app.md b/src/pages/references/addonsdk/addonsdk-app.md index 52c16a977..04292d45b 100644 --- a/src/pages/references/addonsdk/addonsdk-app.md +++ b/src/pages/references/addonsdk/addonsdk-app.md @@ -193,6 +193,10 @@ Returns a `Promise` [`DialogResult`](#dialogresult) object with the [button type #### Confirmation Dialog Example Usage + + +#### JavaScript + ```js import addOnUISdk from "https://express.adobe.com/static/add-on-sdk/sdk.js"; @@ -217,8 +221,39 @@ async function showConfirmDialog() { } ``` +#### TypeScript + +```ts +import addOnUISdk from "https://express.adobe.com/static/add-on-sdk/sdk.js"; + +// Wait for the SDK to be ready +await addOnUISdk.ready; + +async function showConfirmDialog(): Promise { + try { + // Confirmation Dialog Example + const dialogOptions: DialogOptions = { + variant: Variant.confirmation, + title: "Enable smart Filters", + description: + "Smart filters are nondestructive and will preserve your original images.", + buttonLabels: { primary: "Enable", cancel: "Cancel" }, + }; + + const result: DialogResult = await addOnUISdk.app.showModalDialog(dialogOptions); + console.log("Button type clicked " + result.buttonType); + } catch (error) { + console.log("Error showing modal dialog:", error); + } +} +``` + #### Input Dialog Example Usage + + +#### JavaScript + ```js import addOnUISdk from "https://express.adobe.com/static/add-on-sdk/sdk.js"; @@ -252,6 +287,42 @@ async function showInputDialog() { } ``` +#### TypeScript + +```ts +import addOnUISdk from "https://express.adobe.com/static/add-on-sdk/sdk.js"; + +// Wait for the SDK to be ready +await addOnUISdk.ready; + +async function showInputDialog(): Promise { + try { + // Input Dialog Example + const inputDialogOptions: DialogOptions = { + variant: Variant.input, + title: "Please enter your key", + description: "Your API key", + buttonLabels: { cancel: "Cancel" }, + field: { + label: "API Key", + placeholder: "Enter API key", + fieldType: FieldType.text, + }, + }; + + const inputDialogResult: DialogResult = await addOnUISdk.app.showModalDialog( + inputDialogOptions + ); + + if (inputDialogResult.buttonType === ButtonType.primary) { + console.log("Field value " + inputDialogResult.fieldValue); // returns the input the user entered if they didn't cancel + } + } catch (error) { + console.log("Error showing modal dialog:", error); + } +} +``` + See the use case implementations for an example of the [custom modal dialog](../../guides/learn/how_to/modal_dialogs.md#custom-dialog). @@ -287,6 +358,10 @@ Returns a void `Promise`. #### Example Usage + + +#### JavaScript + ```js const colorPickerButton = document.getElementById("color-picker-button"); @@ -304,6 +379,27 @@ colorPickerButton.addEventListener(ColorPickerEvent.colorChange, (event) => { }); ``` +#### TypeScript + +```ts +const colorPickerButton = document.getElementById("color-picker-button") as HTMLButtonElement; + +colorPickerButton.addEventListener("click", (): void => { + const options: ColorPickerOptions = { + title: "Add-on's Color Picker", + placement: ColorPickerPlacement.left, + eyedropperHidesPicker: true, + disableAlphaChannel: false, + }; + + addOnUISdk.app.showColorPicker(colorPickerButton, options); +}); + +colorPickerButton.addEventListener(ColorPickerEvent.colorChange, (event: CustomEvent): void => { + console.log("Color change event received!", event.detail.color); +}); +``` + ### hideColorPicker() Hides the Adobe Express color picker. @@ -458,6 +554,100 @@ Callback to undo the changes made by `enableDragToDocument`. Returns `void`. type DisableDragToDocument = () => void; ``` +#### Example Usage + + + +#### JavaScript + +```js +import addOnUISdk from "https://express.adobe.com/static/add-on-sdk/sdk.js"; + +// Wait for the SDK to be ready +await addOnUISdk.ready; + +// Enable drag-to-document for an image element +const imageElement = document.getElementById("my-image"); + +const dragCallbacks = { + previewCallback: (element) => { + // Return the preview image URL + return new URL(element.src); + }, + + completionCallback: async (element) => { + try { + // Fetch the image blob for drag completion + const response = await fetch(element.src); + const blob = await response.blob(); + + return [{ + blob: blob, + attributes: { + title: element.alt || "Dragged Image" + } + }]; + } catch (error) { + console.error("Failed to fetch image for drag:", error); + return []; + } + } +}; + +// Enable drag to document +const disableDrag = addOnUISdk.app.enableDragToDocument(imageElement, dragCallbacks); + +// Later, you can disable the drag functionality +// disableDrag(); +``` + +#### TypeScript + +```ts +import addOnUISdk from "https://express.adobe.com/static/add-on-sdk/sdk.js"; + +// Wait for the SDK to be ready +await addOnUISdk.ready; + +// Enable drag-to-document for an image element +const imageElement = document.getElementById("my-image") as HTMLImageElement; + +const dragCallbacks: DragCallbacks = { + previewCallback: (element: HTMLElement): URL => { + // Return the preview image URL + const imgElement = element as HTMLImageElement; + return new URL(imgElement.src); + }, + + completionCallback: async (element: HTMLElement): Promise => { + try { + // Fetch the image blob for drag completion + const imgElement = element as HTMLImageElement; + const response = await fetch(imgElement.src); + const blob: Blob = await response.blob(); + + const completionData: DragCompletionData[] = [{ + blob: blob, + attributes: { + title: imgElement.alt || "Dragged Image" + } + }]; + + return completionData; + } catch (error) { + console.error("Failed to fetch image for drag:", error); + return []; + } + } +}; + +// Enable drag to document +const disableDrag: DisableDragToDocument = addOnUISdk.app.enableDragToDocument(imageElement, dragCallbacks); + +// Later, you can disable the drag functionality +// disableDrag(); +``` + ##### `DragStartEventData` Object The payload data sent to the `dragStart` event handler. diff --git a/src/pages/references/addonsdk/addonsdk-constants.md b/src/pages/references/addonsdk/addonsdk-constants.md index ba11aab2b..30f4cd600 100644 --- a/src/pages/references/addonsdk/addonsdk-constants.md +++ b/src/pages/references/addonsdk/addonsdk-constants.md @@ -1,8 +1,275 @@ # addOnUISdk.constants -A set of constants used throughout the add-on SDK. These constants are equal to their variable name as a string value, ie: for the `ButtonType` constant, `primary` has a value of "primary". +This reference covers set of constants used throughout the Add-on UI SDK, including: -## Constants +- Constants available in `addOnUISdk.constants.*` (dual access) +- Constants available only as named exports (import required) + +See the [Import Patterns](#import-patterns) section for details on how to access each type. + + + +The constants listed in this reference are equal to their variable name as a string value, ie: for the `ButtonType` constant, `primary` has a value of "primary". + +## Import Patterns + +Adobe Express Add-on SDK constants are available through different import patterns depending on the constant type. Understanding these patterns is imperative for avoiding runtime errors. + +### Named Exports (Import Required) + +These constants are **only available as named exports** and must be imported explicitly. They are **not** available through `addOnUISdk.constants.*`: + +```javascript +import addOnUISdk, { + AppEvent, + ColorPickerEvent, + SupportedMimeTypes, + EntrypointType, + PdfReturnUrlType +} from "https://new.express.adobe.com/static/add-on-sdk/sdk.js"; + +// βœ… Correct usage +const docxMimeType = SupportedMimeTypes.docx; +const colorChangeEvent = ColorPickerEvent.colorChange; + +// ❌ Will NOT work - these are not in the constants object +const docxMimeType = addOnUISdk.constants.SupportedMimeTypes.docx; // undefined +``` + +### Dual Access Constants + +These constants support **both import patterns** for flexibility. You can use either approach: + +```javascript +import addOnUISdk, { Range, RenditionFormat, Variant } from "https://new.express.adobe.com/static/add-on-sdk/sdk.js"; + +// Option 1: Named import (recommended for cleaner code) +const currentPage = Range.currentPage; +const pngFormat = RenditionFormat.png; +const confirmDialog = Variant.confirmation; + +// Option 2: Constants object access (traditional pattern) +const currentPage = addOnUISdk.constants.Range.currentPage; +const pngFormat = addOnUISdk.constants.RenditionFormat.png; +const confirmDialog = addOnUISdk.constants.Variant.confirmation; +``` + +**Dual Access Constants List:** + +- `Range` / `addOnUISdk.constants.Range` +- `RenditionFormat` / `addOnUISdk.constants.RenditionFormat` +- `RenditionType` / `addOnUISdk.constants.RenditionType` +- `RenditionIntent` / `addOnUISdk.constants.RenditionIntent` +- `Variant` / `addOnUISdk.constants.Variant` +- `DialogResultType` / `addOnUISdk.constants.DialogResultType` +- `ButtonType` / `addOnUISdk.constants.ButtonType` +- `RuntimeType` / `addOnUISdk.constants.RuntimeType` +- `BleedUnit` / `addOnUISdk.constants.BleedUnit` +- `EditorPanel` / `addOnUISdk.constants.EditorPanel` +- `MediaTabs` / `addOnUISdk.constants.MediaTabs` +- `ElementsTabs` / `addOnUISdk.constants.ElementsTabs` +- `PanelActionType` / `addOnUISdk.constants.PanelActionType` +- `ColorPickerPlacement` / `addOnUISdk.constants.ColorPickerPlacement` +- `AuthorizationStatus` / `addOnUISdk.constants.AuthorizationStatus` +- `FieldType` / `addOnUISdk.constants.FieldType` +- `PlatformEnvironment` / `addOnUISdk.constants.PlatformEnvironment` +- `DeviceClass` / `addOnUISdk.constants.DeviceClass` +- `PlatformType` / `addOnUISdk.constants.PlatformType` +- `MediaType` / `addOnUISdk.constants.MediaType` +- `VideoResolution` / `addOnUISdk.constants.VideoResolution` +- `FrameRate` / `addOnUISdk.constants.FrameRate` +- `BitRate` / `addOnUISdk.constants.BitRate` +- `FileSizeLimitUnit` / `addOnUISdk.constants.FileSizeLimitUnit` +- `LinkOptions` / `addOnUISdk.constants.LinkOptions` + + +### Best Practices + +1. **Use named imports for cleaner code** when you know which constants you need: + ```javascript + import addOnUISdk, { Range, RenditionFormat } from "https://new.express.adobe.com/static/add-on-sdk/sdk.js"; + + const options = { + range: Range.currentPage, + format: RenditionFormat.png + }; + ``` + +2. **Use constants object for dynamic access** when the constant name is determined at runtime: + ```javascript + const format = addOnUISdk.constants.RenditionFormat[userSelectedFormat]; + ``` + +3. **Always import named-only exports** - there's no alternative way to access them: + ```javascript + import addOnUISdk, { SupportedMimeTypes } from "https://new.express.adobe.com/static/add-on-sdk/sdk.js"; + ``` + +### Quick Reference Table + +| Constant | Named Export | Constants Object | Import Required | +|----------|--------------|------------------|-----------------| +| `AppEvent` | βœ… | ❌ | **Yes** | +| `ColorPickerEvent` | βœ… | ❌ | **Yes** | +| `SupportedMimeTypes` | βœ… | ❌ | **Yes** | +| `EntrypointType` | βœ… | ❌ | **Yes** | +| `PdfReturnUrlType` | βœ… | ❌ | **Yes** | +| `Range` | βœ… | βœ… | Optional | +| `RenditionFormat` | βœ… | βœ… | Optional | +| `Variant` | βœ… | βœ… | Optional | +| `ButtonType` | βœ… | βœ… | Optional | +| `FieldType` | βœ… | βœ… | Optional | +| `PlatformEnvironment` | βœ… | βœ… | Optional | +| `DeviceClass` | βœ… | βœ… | Optional | +| `PlatformType` | βœ… | βœ… | Optional | +| `AuthorizationStatus` | βœ… | βœ… | Optional | +| `RenditionType` | βœ… | βœ… | Optional | +| `RenditionIntent` | βœ… | βœ… | Optional | +| `DialogResultType` | βœ… | βœ… | Optional | +| `RuntimeType` | βœ… | βœ… | Optional | +| `BleedUnit` | βœ… | βœ… | Optional | +| `EditorPanel` | βœ… | βœ… | Optional | +| `MediaTabs` | βœ… | βœ… | Optional | +| `ElementsTabs` | βœ… | βœ… | Optional | +| `PanelActionType` | βœ… | βœ… | Optional | +| `ColorPickerPlacement` | βœ… | βœ… | Optional | +| `MediaType` | βœ… | βœ… | Optional | +| `VideoResolution` | βœ… | βœ… | Optional | +| `FrameRate` | βœ… | βœ… | Optional | +| `BitRate` | βœ… | βœ… | Optional | +| `FileSizeLimitUnit` | βœ… | βœ… | Optional | +| `LinkOptions` | βœ… | βœ… | Optional | + + + + +**Important:** Attempting to access named-only exports through `addOnUISdk.constants.*` will return `undefined` and may cause runtime errors. Always check the table above or use TypeScript for compile-time validation. + +## Add-on UI SDK Constants + +### **Dialog & UI Interaction Constants** + +| Constant | Values | Description | Usage | +|----------|--------|-------------|-------| +| **`Variant`** | `confirmation`, `information`, `warning`, `destructive`, `error`, `input`, `custom` | Dialog types for `showModalDialog()` | `addOnUISdk.app.showModalDialog({variant: Variant.error})` | +| **`ButtonType`** | `primary`, `secondary`, `cancel`, `close` | Button types in dialog responses | Check `result.buttonType === ButtonType.primary` | +| **`FieldType`** | `text` | Input field types for input dialogs | Used in dialog field configuration | +| **`DialogResultType`** | `alert`, `input`, `custom` | Types of dialog results returned | Determine result structure type | + +### **Platform & Environment Constants** + +| Constant | Values | Description | Usage | +|----------|--------|-------------|-------| +| **`PlatformType`** | `iOS`, `iPadOS`, `chromeOS`, `android`, `chromeBrowser`, `firefoxBrowser`, `edgeBrowser`, `samsungBrowser`, `safariBrowser`, `unknown` | Platform identification | `platform.platformType === PlatformType.iOS` | +| **`PlatformEnvironment`** | `app`, `web` | Runtime environment type | Check if running in app vs web | +| **`DeviceClass`** | `mobile`, `tablet`, `desktop` | Device category | Responsive design decisions | +| **`RuntimeType`** | `panel`, `dialog`, `documentSandbox`, `command` | Add-on runtime types | Communication API configuration | +| **`EntrypointType`** | `WIDGET`, `COMMAND`, `SCRIPT`, `PANEL`, `SHARE`, `CONTENT_HUB`, `MOBILE_MEDIA_AUDIO`, `MOBILE_YOUR_STUFF_FILES`, `MOBILE_MORE`, `SCHEDULE`, `CONTEXTUAL_REPLACE`, `CONTEXTUAL_UPLOAD`, `CONTEXTUAL_BULK_CREATE` | Types of add-on entrypoints | `addOn.entrypointType === EntrypointType.PANEL` | + +### **Document Export & Rendering Constants** + +| Constant | Values | Description | Usage | +|----------|--------|-------------|-------| +| **`Range`** | `currentPage`, `entireDocument`, `specificPages` | Rendition page ranges | `createRenditions({range: Range.currentPage})` | +| **`RenditionFormat`** | `png`, `jpg`, `mp4`, `pdf`, `pptx` | Export file formats | Specify output format for renditions | +| **`RenditionType`** | `page` | Type of rendition | Document export configuration | +| **`RenditionIntent`** | `export`, `preview`, `print` | Purpose of rendition | Optimize rendering for use case | + +### **Video & Media Constants** + +| Constant | Values | Description | Usage | +|----------|--------|-------------|-------| +| **`VideoResolution`** | `sd480p`, `hd720p`, `fhd1080p`, `qhd1440p`, `uhd2160p`, `custom` | Video export resolutions | Video rendition quality settings | +| **`FrameRate`** | `fps23_976`, `fps24`, `fps25`, `fps29_97`, `fps30`, `fps60` | Video frame rates | Video export frame rate | +| **`BitRate`** | `mbps4`, `mbps8`, `mbps10`, `mbps12`, `mbps15`, `mbps25`, `mbps50` | Video bit rates in Mbps | Video compression settings | + +### **Print & Layout Constants** + +| Constant | Values | Description | Usage | +|----------|--------|-------------|-------| +| **`BleedUnit`** | `Inch`, `Millimeter` | Print bleed units | Print preparation | + +### **Editor Panel Navigation Constants** + +| Constant | Values | Description | Usage | +|----------|--------|-------------|-------| +| **`EditorPanel`** | `search`, `yourStuff`, `templates`, `media`, `text`, `elements`, `grids`, `brands`, `addOns` | Express editor panels | `openEditorPanel(EditorPanel.media)` | +| **`MediaTabs`** | `video`, `audio`, `photos` | Media panel tabs | Navigate to specific media type | +| **`ElementsTabs`** | `designAssets`, `backgrounds`, `shapes`, `stockIcons`, `charts` | Elements panel tabs | Navigate to specific element type | +| **`PanelActionType`** | `search`, `navigate` | Panel action types | Panel interaction configuration | + +### **Color Picker Constants** + +| Constant | Values | Description | Usage | +|----------|--------|-------------|-------| +| **`ColorPickerPlacement`** | `top`, `bottom`, `left`, `right` | Color picker positioning | `showColorPicker(element, {placement: ColorPickerPlacement.top})` | + +### **Event Constants** + +| Constant | Values | Description | Usage | +|----------|--------|-------------|-------| +| **`AppEvent`** | `documentIdAvailable`, `documentTitleChange`, `documentLinkAvailable`, `documentPublishedLinkAvailable`, `themechange`, `localechange`, `formatchange`, `reset`, `dragstart`, `dragend`, `dragcancel`, `documentExportAllowedChange` | Events dispatched by the Add-on SDK | `addOnUISdk.app.on(AppEvent.themechange, handler)` | +| **`ColorPickerEvent`** | `colorChange`, `close` | Custom events dispatched by the Color Picker | Listen for color picker events on anchor element | + +### **File Management Constants** + +| Constant | Values | Description | Usage | +|----------|--------|-------------|-------| +| **`SupportedMimeTypes`** | `docx`, `gdoc` | MIME types for original source assets converted to PDF | File import validation | +| **`PdfReturnUrlType`** | `cdnUrl`, `jumpUrl` | Type of URL returned for PDF rendition export | PDF export configuration | +| **`MediaType`** | `Audio` | Media type identifiers | Media handling and processing | +| **`FileSizeLimitUnit`** | `KB`, `MB` | File size limit units | File upload constraints | +| **`LinkOptions`** | `document`, `published` | Types of document links available | `addOnUISdk.app.document.link(LinkOptions.published)` | + +### **OAuth & Authentication Constants** + +| Constant | Values | Description | Usage | +|----------|--------|-------------|-------| +| **`AuthorizationStatus`** | `SUCCESS`, `POPUP_OPENED`, `POPUP_BLOCKED`, `POPUP_CLOSED`, `POPUP_TIMEOUT`, `FAILED`, `IFRAME_LOAD_FAILED` | OAuth authorization workflow status values | `result.status === AuthorizationStatus.SUCCESS` | + +### **Usage Examples** + +```typescript +import addOnUISdk, { + Variant, + PlatformType, + RenditionFormat, + EditorPanel +} from "https://new.express.adobe.com/static/add-on-sdk/sdk.js"; + +// Dialog with error variant +await addOnUISdk.app.showModalDialog({ + variant: Variant.error, + title: "Upload Failed", + description: "File size exceeds limit" +}); + +// Platform-specific behavior +const platform = await addOnUISdk.app.getCurrentPlatform(); +if (platform.platformType === PlatformType.iOS) { + // iOS-specific handling +} + +// Export document as PNG +await addOnUISdk.app.document.createRenditions({ + format: RenditionFormat.png, + range: Range.currentPage +}); + +// Navigate to media panel +addOnUISdk.app.ui.openEditorPanel(EditorPanel.media); +``` + +### **Notes for Developers** + +- **All constants are available as named exports** from the Add-on UI SDK +- **Use constants instead of string literals** for type safety and future compatibility +- **Some constants are marked as `@internal`** (like `ToastVariant`, `FeatureType`, `SettingType`) and are not included as they're for internal/privileged use only +- **Platform detection is crucial** for responsive add-on design across different devices and browsers + +These constants provide type-safe ways to interact with all the major Add-on UI SDK features that third-party developers commonly use! + + diff --git a/src/pages/references/addonsdk/addonsdk-instance.md b/src/pages/references/addonsdk/addonsdk-instance.md index 8b205046b..4ecfb237c 100644 --- a/src/pages/references/addonsdk/addonsdk-instance.md +++ b/src/pages/references/addonsdk/addonsdk-instance.md @@ -1,6 +1,6 @@ # addOnUISdk.instance -Represents the currently running add-on instance. This object is used to provide access to the `clientStorage` and `manifest` objects. See the [Storing and Retrieving Client Side Data](../../guides/learn/how_to/local_data_management.md) use case implementation and [Manifest](../manifest) reference for more details. +Represents the currently running add-on instance. This object is used to provide access to the `clientStorage`, `manifest`, and `runtime` objects. See the [Storing and Retrieving Client Side Data](../../guides/learn/how_to/local_data_management.md) use case implementation and [Manifest](../manifest) reference for more details. ## Objects diff --git a/src/pages/references/addonsdk/app-document.md b/src/pages/references/addonsdk/app-document.md index d18269e00..050a727b6 100644 --- a/src/pages/references/addonsdk/app-document.md +++ b/src/pages/references/addonsdk/app-document.md @@ -92,7 +92,7 @@ A resolved `Promise` containing a [`PageMetadata`](#pagemetadata) array containi #### Example Usage - + #### JavaScript @@ -135,6 +135,49 @@ async function logMetadata() { } ``` +#### TypeScript + +```ts +import addOnUISdk from "https://express.adobe.com/static/add-on-sdk/sdk.js"; + +// Wait for the SDK to be ready +await addOnUISdk.ready; + +// Get metadata of all the pages +async function logMetadata(): Promise { + try { + const options: PageMetadataOptions = { + range: Range.specificPages, + pageIds: [ + "7477a5e7-02b2-4b8d-9bf9-f09ef6f8b9fc", + "d45ba3fc-a3df-4a87-80a5-655e5f8f0f96" + ] + }; + + const pages: PageMetadata[] = await addOnUISdk.app.document.getPagesMetadata(options); + + for (const page of pages) { + console.log("Page id: ", page.id); + console.log("Page title: ", page.title); + console.log("Page size: ", page.size); + console.log("Page has premium content: ", page.hasPremiumContent); + console.log("Page has audio content: ", page.hasAudioContent); + console.log("Page has video content: ", page.hasVideoContent); + console.log("Page has animated content: ", page.hasAnimatedContent); + console.log("Page has timelines: ", page.hasTemporalContent); + if (page.hasTemporalContent) + console.log("Page includes temporal content with a duration of: ", page.temporalContentDuration); + console.log("Pixels per inch: ", page.pixelsPerInch); + console.log("Is page print ready: ", page.isPrintReady); + console.log("Is page blank: ", page.isBlank); + console.log("Template details: ", page.templateDetails); + } + } catch (error) { + console.log("Failed to get metadata:", error); + } +} +``` + #### Output ```bash @@ -177,7 +220,7 @@ Tells Express to run a print quality check to determine if the document is ready #### Example Usage - + #### JavaScript @@ -200,6 +243,44 @@ function runPrintQualityCheck() { } ``` +#### TypeScript + +```ts +import addOnUISdk from "https://express.adobe.com/static/add-on-sdk/sdk.js"; + +// Reference to the active document +const { document } = addOnUISdk.app; + +// Run Print Quality Check +function runPrintQualityCheck(): void { + try { + const options: PrintQualityCheckOptions = { + range: Range.entireDocument, + }; + + document.runPrintQualityCheck(options); + console.log("Print quality check completed successfully"); + } catch (error) { + console.log("Failed to run print quality check"); + } +} + +// Run Print Quality Check for specific pages +function runPrintQualityCheckForPages(pageIds: string[]): void { + try { + const options: PrintQualityCheckOptions = { + range: Range.specificPages, + pageIds: pageIds + }; + + document.runPrintQualityCheck(options); + console.log("Print quality check completed successfully for specific pages"); + } catch (error) { + console.log("Failed to run print quality check for specific pages"); + } +} +``` + #### Output ```bash @@ -267,7 +348,11 @@ A resolved `Promise` containing an array of `string` ids representing the curren #### Example Usage -```javascript + + +#### JavaScript + +```js import addOnUISdk from "https://express.adobe.com/static/add-on-sdk/sdk.js"; // Wait for the SDK to be ready @@ -316,6 +401,59 @@ getSelectedPages(); getSelectedPagesMetadata(); ``` +#### TypeScript + +```ts +import addOnUISdk from "https://express.adobe.com/static/add-on-sdk/sdk.js"; + +// Wait for the SDK to be ready +await addOnUISdk.ready; + +// Get the currently selected page ids +async function getSelectedPages(): Promise { + try { + const selectedPageIds: string[] = await addOnUISdk.app.document.getSelectedPageIds(); + console.log("Selected page ids:", selectedPageIds); + + if (selectedPageIds.length === 0) { + console.log("No pages are currently selected"); + } else { + console.log(`${selectedPageIds.length} page(s) selected:`, selectedPageIds); + } + } catch (error) { + console.log("Failed to get selected page ids:", error); + } +} + +// Example: Get metadata for selected pages only +async function getSelectedPagesMetadata(): Promise { + try { + const selectedPageIds: string[] = await addOnUISdk.app.document.getSelectedPageIds(); + + if (selectedPageIds.length > 0) { + const options: PageMetadataOptions = { + range: Range.specificPages, + pageIds: selectedPageIds + }; + + const metadata: PageMetadata[] = await addOnUISdk.app.document.getPagesMetadata(options); + + metadata.forEach((page: PageMetadata, index: number) => { + console.log(`Selected page ${index + 1}: ${page.title} (${page.id})`); + }); + } else { + console.log("No pages selected"); + } + } catch (error) { + console.log("Failed to get selected pages metadata:", error); + } +} + +// Call the functions +getSelectedPages(); +getSelectedPagesMetadata(); +``` + ### link() Retrieves the document link. @@ -571,12 +709,21 @@ Mime type details for importing media | Name | Type | Description | | ----------------- | -------------------------- | ---------------------------------------------------------------: | -| `sourceMimeType?` | [`SupportedMimeTypes`](./) | Mime type of the original source asset that was converted to PDF | +| `sourceMimeType?` | [`SupportedMimeTypes`](#supportedmimetypes) | Mime type of the original source asset that was converted to PDF | ### SupportedMimeTypes -A constant representing the mime type of the original source asset that was converted to PDF. +A constant representing the mime type of the original source asset that was converted to PDF. **Note:** This constant is only available as a named export and must be imported explicitly. + +```javascript +import addOnUISdk, { SupportedMimeTypes } from "https://new.express.adobe.com/static/add-on-sdk/sdk.js"; + +// Usage +const docxMimeType = SupportedMimeTypes.docx; +const gdocMimeType = SupportedMimeTypes.gdoc; +``` +Available values: - **docx**: `"docx"` - **gdoc**: `"gdoc"` diff --git a/src/pages/references/addonsdk/app-oauth.md b/src/pages/references/addonsdk/app-oauth.md index d090a52cc..22bbf2a4b 100644 --- a/src/pages/references/addonsdk/app-oauth.md +++ b/src/pages/references/addonsdk/app-oauth.md @@ -42,6 +42,86 @@ A resolved `Promise` with the [`AuthorizationResponse`](#authorizationresponse) | `redirectUri` | `string` | URL where the user is redirected to after authorization. This is the default URL owned by Adobe and it is this URL which needs to be used to obtain access_token. | | `result` | `string` or `object` | An [`AuthorizationResult`](#authorizationresult) payload which denotes either success or failure. In the event of a "FAILED" status reported by the OAuth provider during authorization, the value of this property is an `object`, in the form of `{[failure_title]: "failure_description"}`, and for all other statuses the value of `description` is a `string`. | +#### Example Usage + + + +#### JavaScript + +```js +import addOnUISdk from "https://express.adobe.com/static/add-on-sdk/sdk.js"; + +// Wait for the SDK to be ready +await addOnUISdk.ready; + +async function handleOAuthAuthorization() { + try { + const authRequest = { + authorizationUrl: "https://api.example.com/oauth/authorize", + clientId: "your-client-id", + scope: "read write", + codeChallenge: "generated-code-challenge", + additionalParameters: new Map([ + ["response_type", "code"], + ["access_type", "offline"] + ]), + windowSize: { width: 600, height: 700 } + }; + + const response = await addOnUISdk.app.oauth.authorize(authRequest); + + if (response.result.status === "SUCCESS") { + console.log("Authorization successful!"); + console.log("Authorization code:", response.code); + console.log("Redirect URI:", response.redirectUri); + // Use the authorization code to get access token + } else { + console.log("Authorization failed:", response.result.description); + } + } catch (error) { + console.log("OAuth error:", error); + } +} +``` + +#### TypeScript + +```ts +import addOnUISdk from "https://express.adobe.com/static/add-on-sdk/sdk.js"; + +// Wait for the SDK to be ready +await addOnUISdk.ready; + +async function handleOAuthAuthorization(): Promise { + try { + const authRequest: AuthorizationRequest = { + authorizationUrl: "https://api.example.com/oauth/authorize", + clientId: "your-client-id", + scope: "read write", + codeChallenge: "generated-code-challenge", + additionalParameters: new Map([ + ["response_type", "code"], + ["access_type", "offline"] + ]), + windowSize: { width: 600, height: 700 } + }; + + const response: AuthorizationResponse = await addOnUISdk.app.oauth.authorize(authRequest); + + if (response.result.status === AuthorizationStatus.authorized) { + console.log("Authorization successful!"); + console.log("Authorization code:", response.code); + console.log("Redirect URI:", response.redirectUri); + // Use the authorization code to get access token + } else { + console.log("Authorization failed:", response.result.description); + } + } catch (error) { + console.log("OAuth error:", error); + } +} +``` + ### authorizeWithOwnRedirect() Initiate the OAuth 2.0 PKCE authorization workflow by opening the user sign-in window. After authorization, the user is redirected to the add-on developer provided URL. @@ -80,6 +160,204 @@ A resolved `Promise` with the [`AuthorizationResult`](#authorizationresult) obje | `status` | `AuthorizationStatus` | Status representing success or failure in the authorization workflow. | | `description` | `string` or `object` | Description about the success or failure in the authorization workflow In the event of a "FAILED" status reported by the OAuth provider during authorization, the value of this property is an `object`, in the form of `{[failure_title]: "failure_description"}`. While for all other statuses the value of this property is a `string` | +#### Example Usage + + + +#### JavaScript + +```js +import addOnUISdk from "https://express.adobe.com/static/add-on-sdk/sdk.js"; + +// Wait for the SDK to be ready +await addOnUISdk.ready; + +async function handleOAuthWithRedirect() { + try { + const authRequest = { + authorizationUrl: "https://api.example.com/oauth/authorize", + clientId: "your-client-id", + scope: "read write", + codeChallenge: "generated-code-challenge", + redirectUri: "https://yourapp.com/oauth/callback", + state: "random-state-value", + additionalParameters: new Map([ + ["response_type", "code"], + ["access_type", "offline"] + ]), + windowSize: { width: 600, height: 700 } + }; + + const result = await addOnUISdk.app.oauth.authorizeWithOwnRedirect(authRequest); + + if (result.status === "POPUP_OPENED") { + console.log("Authorization popup opened successfully"); + console.log("Handle the redirect at your callback URL:", authRequest.redirectUri); + } else { + console.log("Authorization failed:", result.description); + } + } catch (error) { + console.log("OAuth error:", error); + } +} +``` + +#### TypeScript + +```ts +import addOnUISdk from "https://express.adobe.com/static/add-on-sdk/sdk.js"; + +// Wait for the SDK to be ready +await addOnUISdk.ready; + +async function handleOAuthWithRedirect(): Promise { + try { + const authRequest: AuthorizeWithOwnRedirectRequest = { + authorizationUrl: "https://api.example.com/oauth/authorize", + clientId: "your-client-id", + scope: "read write", + codeChallenge: "generated-code-challenge", + redirectUri: "https://yourapp.com/oauth/callback", + state: "random-state-value", + additionalParameters: new Map([ + ["response_type", "code"], + ["access_type", "offline"] + ]), + windowSize: { width: 600, height: 700 } + }; + + const result: AuthorizationResult = await addOnUISdk.app.oauth.authorizeWithOwnRedirect(authRequest); + + if (result.status === AuthorizationStatus.cancelled) { + console.log("Authorization popup opened successfully"); + console.log("Handle the redirect at your callback URL:", authRequest.redirectUri); + } else { + console.log("Authorization failed:", result.description); + } + } catch (error) { + console.log("OAuth error:", error); + } +} +``` + +### authorizeInsideIframe() + +Authorize a user using OAuth 2.0 PKCE workflow in an iframe. This method provides an alternative to popup-based authorization for cases where popups may be blocked or when you prefer an iframe-based flow. + +#### Signature: + +`authorizeInsideIframe(request: AuthorizeInsideIframeRequest): Promise` + +#### Parameters + +| Name | Type | Description | +| --------- | -------- | ---------------------------------------------------------------------------------------------------------------------------------------------: | +| `request` | `object` | [`AuthorizeInsideIframeRequest`](#authorizeinsideiframerequest) object payload with parameters to be used in the authorization workflow. | + +#### `AuthorizeInsideIframeRequest` + +Extends [`AuthorizationRequest`](#authorizationrequest) with additional properties specific to iframe-based OAuth flows. + +| Name | Type | Description | +| ----------------------- | ----------------------------------: | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | +| `authorizationUrl` | `string` | OAuth provider's authorization URL. | +| `clientId` | `string` | Client identifier of the application created at the OAuth provider. | +| `scope` | `string` | Scope to control the application's access. | +| `codeChallenge` | `string` | Code challenge used in Authorization Code Exchange. | +| `additionalParameters?` | `Map` | Additional parameters, specific to an OAuth provider which are required in the Authorization URL as query string parameters. | +| `windowSize?` | `{ width: number; height: number }` | The authorization iframe size in the form of an `object` containing the desired `width` and `height` as a `number`.

**NOTE:** The **minimum** (and **default**) values for `windowSize` are 480 x 480. The **maximum** allowed values are 800 x (screen height). | +| `position?` | `{ top: number; left: number }` | Relative position of the OAuth iframe panel. | +| `offset?` | `{ right: number; bottom: number }` | Offset from the right and bottom of the iframe container when the size (`windowSize`) is not specified. | +| `showHeader?` | `boolean` | Flag to determine if the iframe panel needs to show a header. | + +#### Return Value + +A resolved `Promise` with the [`AuthorizationResponse`](#authorizationresponse) object, which contains the authorization code, redirect URI, and result status. + +#### Example Usage + + + +#### JavaScript + +```js +import addOnUISdk from "https://express.adobe.com/static/add-on-sdk/sdk.js"; + +// Wait for the SDK to be ready +await addOnUISdk.ready; + +async function handleOAuthInsideIframe() { + try { + const authRequest = { + authorizationUrl: "https://api.example.com/oauth/authorize", + clientId: "your-client-id", + scope: "read write", + codeChallenge: "generated-code-challenge", + additionalParameters: new Map([ + ["response_type", "code"], + ["access_type", "offline"] + ]), + windowSize: { width: 600, height: 700 }, + position: { top: 100, left: 100 }, + showHeader: true + }; + + const response = await addOnUISdk.app.oauth.authorizeInsideIframe(authRequest); + + if (response.result.status === "SUCCESS") { + console.log("Authorization successful!"); + console.log("Authorization code:", response.code); + console.log("Redirect URI:", response.redirectUri); + // Use the authorization code to get access token + } else { + console.log("Authorization failed:", response.result.description); + } + } catch (error) { + console.log("OAuth error:", error); + } +} +``` + +#### TypeScript + +```ts +import addOnUISdk from "https://express.adobe.com/static/add-on-sdk/sdk.js"; + +// Wait for the SDK to be ready +await addOnUISdk.ready; + +async function handleOAuthInsideIframe(): Promise { + try { + const authRequest: AuthorizeInsideIframeRequest = { + authorizationUrl: "https://api.example.com/oauth/authorize", + clientId: "your-client-id", + scope: "read write", + codeChallenge: "generated-code-challenge", + additionalParameters: new Map([ + ["response_type", "code"], + ["access_type", "offline"] + ]), + windowSize: { width: 600, height: 700 }, + position: { top: 100, left: 100 }, + showHeader: true + }; + + const response: AuthorizationResponse = await addOnUISdk.app.oauth.authorizeInsideIframe(authRequest); + + if (response.result.status === AuthorizationStatus.authorized) { + console.log("Authorization successful!"); + console.log("Authorization code:", response.code); + console.log("Redirect URI:", response.redirectUri); + // Use the authorization code to get access token + } else { + console.log("Authorization failed:", response.result.description); + } + } catch (error) { + console.log("OAuth error:", error); + } +} +``` + #### `AuthorizationStatus` Each of the statuses returned below is the exact name as a string (ie: SUCCESS = "SUCCESS") diff --git a/src/pages/references/addonsdk/index.md b/src/pages/references/addonsdk/index.md index f00603c5c..f7c80a4e1 100644 --- a/src/pages/references/addonsdk/index.md +++ b/src/pages/references/addonsdk/index.md @@ -87,6 +87,10 @@ The SDK can be referenced in `.js/.jsx/.ts/.tsx` source files by adding it to th import addOnUISdk from "https://express.adobe.com/static/add-on-sdk/sdk.js"; ``` + + +**πŸ“¦ Working with SDK Constants:** Adobe Express SDK constants use different import patterns. Some require named imports while others support both approaches. See the [complete import patterns guide](./addonsdk-constants.md#import-patterns) for details and examples. + **Note:** if you created your add-on project with the CLI based on the `typescript` or `typescript-react` templates, you will automatically get a types definition file named `add-on-ui-sdk.d.ts` generated in your project `src` for you. This file contains the following exports, and allows you to take advantage of type checking and auto-completion features while developing with the Add-on SDK in your IDE. ```ts @@ -136,7 +140,7 @@ The following properties can be accessed from the `addOnUISdk` object after it h

addOnUISdk.constants

object

-

A set of constants used throughout the add-on SDK.

+

A set of constants used throughout the add-on SDK. See the import patterns guide for proper usage.

readonly