Skip to content
New issue

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

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

Already on GitHub? Sign in to your account

Overlay and AppProcess API Public Release and updated app examples #28

Merged
merged 1 commit into from
May 6, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 19 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,24 @@
# Changelog

## 2024-05-06

### 🧰 Added
- `@canva/design`
- Added [design.overlay.registerOnCanOpen](http://canva.dev/docs/apps/api/design-overlay-register-on-can-open/) which was previously in beta.
- `@canva/platform`
- Added [appProcess](https://www.canva.dev/docs/apps/api/platform-app-process/) under `@canva/platform` which was previously in beta.

### 🔧 Changed
- `examples`
- Remove `dataUrl` usages in all examples. We recommend [Upload API](https://www.canva.dev/docs/apps/api/asset-upload/#uploading-images) before adding images to the design.
- Updated [/examples/image_editing_overlay](/examples/image_editing_overlay) to use `@canva/design` and `@canva/platform` instead of `@canva/preview`.
- `utils/backend`
- Fixed a number of minor linting and typing related warnings.
- `examples/digital_asset_management`
- Updated `@canva/app-components` to version `1.0.0-beta.17` in `digital_asset_management` example.
- `README.md`
- Minor ordering changes of content in the repository [README.md](/README.md).

## 2024-04-23

### 🧰 Added
Expand Down
67 changes: 21 additions & 46 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -78,64 +78,39 @@ To enable HMR:
7. Restart the local development server.
8. Reload the app manually to ensure that HMR takes effect.

## Previewing apps in Safari

By default, the development server is not HTTPS-enabled. This is convenient, as there's no need for a security certificate, but it prevents apps from being previewed in Safari.

<details>
<summary>Why Safari requires the development server to be HTTPS-enabled</summary>

Canva itself is served via HTTPS and most browsers prevent HTTPS pages from loading scripts via non-HTTPS connections. Chrome and Firefox make exceptions for local servers, such as `localhost`, but Safari does not, so if you're using Safari, the development server must be HTTPS-enabled.

To learn more, see [Loading mixed-content resources](https://developer.mozilla.org/en-US/docs/Web/Security/Mixed_content#loading_mixed-content_resources).

</details>

To preview apps in Safari:

1. Start the development server with HTTPS enabled:

```bash
# Run the main app
npm start --use-https
<summary>Previewing apps in Safari</summary>

# Run an example
npm start <example-name> --use-https
```

2. Navigate to <https://localhost:8080>.
3. Bypass the invalid security certificate warning:
1. Click **Show details**.
2. Click **Visit website**.
4. In the Developer Portal, set the app's **Development URL** to <https://localhost:8080>.

You need to bypass the invalid security certificate warning every time you start the local server. A similar warning will appear in other browsers (and will need to be bypassed) whenever HTTPS is enabled.
By default, the development server is not HTTPS-enabled. This is convenient, as there's no need for a security certificate, but it prevents apps from being previewed in Safari.

## Running the examples
**Why Safari requires the development server to be HTTPS-enabled?**

The `examples` directory contains example apps that demonstrate the available APIs.
Canva itself is served via HTTPS and most browsers prevent HTTPS pages from loading scripts via non-HTTPS connections. Chrome and Firefox make exceptions for local servers, such as `localhost`, but Safari does not, so if you're using Safari, the development server must be HTTPS-enabled.

To explore all of our different examples, run the following command:
To learn more, see [Loading mixed-content resources](https://developer.mozilla.org/en-US/docs/Web/Security/Mixed_content#loading_mixed-content_resources).

```bash
npm start examples
```
To preview apps in Safari:

Alternatively, you can run a particular example directly via the following command:
1. Start the development server with HTTPS enabled:

```bash
npm start <example-name>
```
```bash
# Run the main app
npm start --use-https

But replace `<example-name>` with the name of an example, like so:
# Run an example
npm start <example-name> --use-https
```

```bash
npm start native_image_elements
```
2. Navigate to <https://localhost:8080>.
3. Bypass the invalid security certificate warning:
1. Click **Show details**.
2. Click **Visit website**.
4. In the Developer Portal, set the app's **Development URL** to <https://localhost:8080>.

Like the boilerplate, a development server becomes available at <http://localhost:8080>.
You need to bypass the invalid security certificate warning every time you start the local server. A similar warning will appear in other browsers (and will need to be bypassed) whenever HTTPS is enabled.
</details>

### Running an example's backend
## Running an example's backend

Some examples have a backend. This backend is defined in the example's `backend/server.ts` file, automatically starts when the `npm start` command is run, and becomes available at <http://localhost:3001>.

Expand Down
37 changes: 32 additions & 5 deletions examples/app_image_elements/app.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import dog from "assets/images/dog.jpg";
import rabbit from "assets/images/rabbit.jpg";
import React from "react";
import baseStyles from "styles/components.css";
import { upload } from "@canva/asset";

// We can't store the image's data URL in the app element's data, since it
// exceeds the 5kb limit. We can, however, store an ID that references the
Expand All @@ -31,14 +32,17 @@ const images = {
dog: {
title: "Dog",
imageSrc: dog,
imageRef: undefined,
},
cat: {
title: "Cat",
imageSrc: cat,
imageRef: undefined,
},
rabbit: {
title: "Rabbit",
imageSrc: rabbit,
imageRef: undefined,
},
};

Expand All @@ -56,17 +60,18 @@ const appElementClient = initAppElement<AppElementData>({
type: "IMAGE",
top: 0,
left: 0,
dataUrl: images[data.imageId].imageSrc,
ref: images[data.imageId].imageRef,
...data,
},
];
},
});

export const App = () => {
const [loading, setLoading] = React.useState(false);
const [state, setState] = React.useState<UIState>(initialState);
const { imageId, width, height, rotation } = state;
const disabled = !imageId || imageId.trim().length < 1;
const disabled = loading || !imageId || imageId.trim().length < 1;

const items = Object.entries(images).map(([key, value]) => {
const { title, imageSrc } = value;
Expand All @@ -86,6 +91,30 @@ export const App = () => {
};
});

const addOrUpdateImage = React.useCallback(async () => {
setLoading(true);
try {
if (!images[state.imageId].imageRef) {
// Upload local image
const imageSrc = images[state.imageId].imageSrc;
const { ref } = await upload({
type: "IMAGE",
mimeType: "image/jpeg",
url: imageSrc,
thumbnailUrl: imageSrc,
width: 400,
height: 400,
});
images[state.imageId].imageRef = ref;
}

// Add or update app element
await appElementClient.addOrUpdateElement(state);
} finally {
setLoading(false);
}
}, [state]);

React.useEffect(() => {
appElementClient.registerOnElementChange((appElement) => {
setState(appElement ? appElement.data : initialState);
Expand Down Expand Up @@ -177,9 +206,7 @@ export const App = () => {
/>
<Button
variant="primary"
onClick={() => {
appElementClient.addOrUpdateElement(state);
}}
onClick={addOrUpdateImage}
disabled={disabled}
stretch
>
Expand Down
2 changes: 1 addition & 1 deletion examples/digital_asset_management/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
"author": "Canva Pty Ltd.",
"license": "Please refer to the LICENSE.md file in the root directory",
"dependencies": {
"@canva/app-components": "^1.0.0-beta.15",
"@canva/app-components": "^1.0.0-beta.17",
"cookie-parser": "^1.4.6",
"cors": "^2.8.5",
"express": "^4.18.2",
Expand Down
3 changes: 0 additions & 3 deletions examples/drag_and_drop_audio/app.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,6 @@ const AUDIO_DURATION_MS = 86_047;

const uploadAudio = () => {
return upload({
// An alphanumeric string that is unique for each asset. If given the same
// id, the existing asset for that id will be used instead.
id: "uniqueAudioIdentifier",
title: "MP3 Audio Track",
durationMs: AUDIO_DURATION_MS,
mimeType: "audio/mp3",
Expand Down
13 changes: 4 additions & 9 deletions examples/drag_and_drop_image/app.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,6 @@ import styles from "styles/components.css";

const uploadExternalImage = () => {
return upload({
// An alphanumeric string that is unique for each asset. If given the same
// id, the existing asset for that id will be used instead.
id: "uniqueExternalImageIdentifier",
mimeType: "image/jpeg",
thumbnailUrl:
"https://www.canva.dev/example-assets/image-import/grass-image-thumbnail.jpg",
Expand All @@ -22,9 +19,6 @@ const uploadExternalImage = () => {

const uploadLocalImage = () => {
return upload({
// An alphanumeric string that is unique for each asset. If given the same
// id, the existing asset for that id will be used instead.
id: "uniqueLocalImageIdentifier",
mimeType: "image/jpeg",
thumbnailUrl: dog,
type: "IMAGE",
Expand All @@ -34,13 +28,14 @@ const uploadLocalImage = () => {
});
};

const insertLocalImage = () => {
addNativeElement({ type: "IMAGE", dataUrl: dog });
const insertLocalImage = async () => {
const { ref } = await uploadLocalImage();
await addNativeElement({ type: "IMAGE", ref });
};

const insertExternalImage = async () => {
const { ref } = await uploadExternalImage();
addNativeElement({ type: "IMAGE", ref });
await addNativeElement({ type: "IMAGE", ref });
};

export const App = () => {
Expand Down
3 changes: 0 additions & 3 deletions examples/drag_and_drop_video/app.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,6 @@ import styles from "styles/components.css";

const uploadVideo = () => {
return upload({
// An alphanumeric string that is unique for each asset. If given the same
// id, the existing asset for that id will be used instead.
id: "uniqueBeachVideoIdentifier",
mimeType: "video/mp4",
thumbnailImageUrl:
"https://www.canva.dev/example-assets/video-import/beach-thumbnail-image.jpg",
Expand Down
2 changes: 1 addition & 1 deletion examples/image_editing_overlay/app.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import * as React from "react";
import { appProcess } from "@canva/preview/platform";
import { appProcess } from "@canva/platform";
import { ObjectPanel } from "./object_panel";
import { Overlay } from "./overlay";

Expand Down
2 changes: 1 addition & 1 deletion examples/image_editing_overlay/object_panel.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { Rows, FormField, Button, Slider } from "@canva/app-ui-kit";
import * as React from "react";
import styles from "styles/components.css";
import { appProcess } from "@canva/preview/platform";
import { appProcess } from "@canva/platform";
import { useOverlay } from "utils/use_overlay_hook";
import { LaunchParams } from "./app";
import type { CloseOpts } from "./overlay";
Expand Down
5 changes: 2 additions & 3 deletions examples/image_editing_overlay/overlay.tsx
Original file line number Diff line number Diff line change
@@ -1,9 +1,8 @@
import * as React from "react";
import { AppProcessInfo, CloseParams } from "sdk/preview/platform";
import { LaunchParams } from "./app";
import { getTemporaryUrl, upload } from "@canva/asset";
import { useSelection } from "utils/use_selection_hook";
import { appProcess } from "@canva/preview/platform";
import { appProcess, AppProcessInfo, CloseParams } from "@canva/platform";
import { SelectionEvent } from "@canva/design";

// App can extend CloseParams type to send extra data when closing the overlay
Expand Down Expand Up @@ -116,7 +115,7 @@ export const Overlay = (props: OverlayProps) => {
return void appProcess.current.setOnDispose<CloseOpts>(
async ({ reason }) => {
// abort if image has not loaded or receive `aborted` signal
if (reason === "aborted" || !img.complete) {
if (reason === "aborted" || !img.src || !img.complete) {
return;
}
const dataUrl = canvas.toDataURL();
Expand Down
50 changes: 31 additions & 19 deletions examples/page_addition/app.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,32 +9,43 @@ import { CanvaError } from "@canva/error";
import weather from "assets/images/weather.png";
import React from "react";
import styles from "styles/components.css";
import { upload } from "@canva/asset";

const IMAGE_ELEMENT_WIDTH = 50;
const IMAGE_ELEMENT_HEIGHT = 50;
const TEXT_ELEMENT_WIDTH = 130;
const HEADER_ELEMENT_SCALE_FACTOR = 0.2;
const EMBED_ELEMENT_SCALE_FACTOR = 0.4;

const headerElement: NativeGroupElement = {
type: "GROUP",
children: [
{
type: "IMAGE",
dataUrl: weather,
top: 0,
left: 0,
width: IMAGE_ELEMENT_WIDTH,
height: IMAGE_ELEMENT_HEIGHT,
},
{
type: "TEXT",
children: ["Weather Forecast"],
top: IMAGE_ELEMENT_HEIGHT,
left: IMAGE_ELEMENT_WIDTH / 2 - TEXT_ELEMENT_WIDTH / 2,
width: TEXT_ELEMENT_WIDTH,
},
],
const createHeaderElement = async (): Promise<NativeGroupElement> => {
const { ref } = await upload({
mimeType: "image/png",
thumbnailUrl: weather,
type: "IMAGE",
url: weather,
width: 100,
height: 100,
});
return {
type: "GROUP",
children: [
{
type: "IMAGE",
ref,
top: 0,
left: 0,
width: IMAGE_ELEMENT_WIDTH,
height: IMAGE_ELEMENT_HEIGHT,
},
{
type: "TEXT",
children: ["Weather Forecast"],
top: IMAGE_ELEMENT_HEIGHT,
left: IMAGE_ELEMENT_WIDTH / 2 - TEXT_ELEMENT_WIDTH / 2,
width: TEXT_ELEMENT_WIDTH,
},
],
};
};

const embedElement: NativeEmbedElement = {
Expand Down Expand Up @@ -73,6 +84,7 @@ export const App = () => {
defaultPageDimensions.width * HEADER_ELEMENT_SCALE_FACTOR;
const embedElementWidth =
defaultPageDimensions.width * EMBED_ELEMENT_SCALE_FACTOR;
const headerElement = await createHeaderElement();
await addPage({
title: "Weather forecast",
elements: [
Expand Down
Loading
Loading