@canva/cli 1.10.0 → 1.11.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +13 -0
- package/cli.js +472 -463
- package/lib/cjs/index.cjs +2 -2
- package/lib/esm/index.mjs +2 -2
- package/lib/index.d.ts +2 -0
- package/package.json +1 -1
- package/templates/base/package.json +3 -2
- package/templates/base/styles/components.css +18 -0
- package/templates/common/.env.template +1 -1
- package/templates/dam/canva-app.json +5 -0
- package/templates/dam/package.json +4 -2
- package/templates/dam/src/index.tsx +3 -21
- package/templates/dam/src/intents/design_editor/index.tsx +25 -0
- package/templates/data_connector/package.json +3 -2
- package/templates/data_connector/src/api/data_sources/designs.tsx +1 -1
- package/templates/data_connector/src/api/data_sources/templates.tsx +1 -1
- package/templates/data_connector/src/components/header.tsx +1 -1
- package/templates/data_connector/src/index.tsx +2 -66
- package/templates/data_connector/src/{app.tsx → intents/data_connector/app.tsx} +3 -3
- package/templates/data_connector/src/{entrypoint.tsx → intents/data_connector/entrypoint.tsx} +5 -5
- package/templates/data_connector/src/{home.tsx → intents/data_connector/home.tsx} +1 -1
- package/templates/data_connector/src/intents/data_connector/index.tsx +56 -0
- package/templates/data_connector/src/pages/error.tsx +1 -1
- package/templates/data_connector/src/pages/login.tsx +1 -1
- package/templates/data_connector/src/routes/protected_route.tsx +1 -1
- package/templates/data_connector/src/routes/routes.tsx +3 -3
- package/templates/data_connector/styles/components.css +18 -0
- package/templates/gen_ai/canva-app.json +5 -0
- package/templates/gen_ai/package.json +4 -2
- package/templates/gen_ai/src/components/footer.tsx +1 -1
- package/templates/gen_ai/src/components/loading_results.tsx +1 -1
- package/templates/gen_ai/src/components/prompt_input.tsx +1 -1
- package/templates/gen_ai/src/index.tsx +3 -14
- package/templates/gen_ai/src/{app.tsx → intents/design_editor/app.tsx} +3 -3
- package/templates/gen_ai/src/{home.tsx → intents/design_editor/home.tsx} +1 -1
- package/templates/gen_ai/src/intents/design_editor/index.tsx +17 -0
- package/templates/gen_ai/src/pages/error.tsx +1 -1
- package/templates/gen_ai/src/routes/routes.tsx +2 -2
- package/templates/gen_ai/styles/components.css +18 -0
- package/templates/hello_world/canva-app.json +5 -0
- package/templates/hello_world/package.json +4 -2
- package/templates/hello_world/src/index.tsx +3 -21
- package/templates/hello_world/src/{app.tsx → intents/design_editor/app.tsx} +26 -3
- package/templates/hello_world/src/intents/design_editor/index.tsx +25 -0
- package/templates/hello_world/src/{tests → intents/design_editor/tests}/app.tests.tsx +19 -13
- package/templates/hello_world/styles/components.css +18 -0
- package/templates/optional/AGENTS.md +80 -2
- package/templates/optional/CLAUDE.md +80 -2
- package/templates/base/utils/use_add_element.ts +0 -58
- package/templates/base/utils/use_feature_support.ts +0 -28
- package/templates/common/utils/table_wrapper.ts +0 -520
- package/templates/common/utils/use_add_element.ts +0 -58
- package/templates/common/utils/use_feature_support.ts +0 -28
- package/templates/common/utils/use_overlay_hook.ts +0 -76
- package/templates/common/utils/use_selection_hook.ts +0 -37
- package/templates/hello_world/utils/use_add_element.ts +0 -58
- package/templates/hello_world/utils/use_feature_support.ts +0 -28
- /package/templates/dam/src/{adapter.ts → intents/design_editor/adapter.ts} +0 -0
- /package/templates/dam/src/{app.tsx → intents/design_editor/app.tsx} +0 -0
- /package/templates/dam/src/{config.ts → intents/design_editor/config.ts} +0 -0
- /package/templates/dam/src/{index.css → intents/design_editor/index.css} +0 -0
- /package/templates/data_connector/src/{paths.ts → routes/paths.ts} +0 -0
- /package/templates/gen_ai/src/{paths.ts → routes/paths.ts} +0 -0
- /package/templates/hello_world/src/{tests → intents/design_editor/tests}/__snapshots__/app.tests.tsx.snap +0 -0
|
@@ -1,68 +1,4 @@
|
|
|
1
|
-
import { Alert, AppUiProvider } from "@canva/app-ui-kit";
|
|
2
|
-
import type {
|
|
3
|
-
GetDataTableRequest,
|
|
4
|
-
GetDataTableResponse,
|
|
5
|
-
RenderSelectionUiRequest,
|
|
6
|
-
} from "@canva/intents/data";
|
|
7
1
|
import { prepareDataConnector } from "@canva/intents/data";
|
|
8
|
-
import
|
|
9
|
-
import { createRoot } from "react-dom/client";
|
|
10
|
-
import { buildDataTableResult, scope } from "./api";
|
|
11
|
-
import { App } from "./app";
|
|
12
|
-
import "@canva/app-ui-kit/styles.css";
|
|
2
|
+
import dataConnector from "./intents/data_connector";
|
|
13
3
|
|
|
14
|
-
|
|
15
|
-
prepareDataConnector({
|
|
16
|
-
/**
|
|
17
|
-
* Fetches structured data from an external source.
|
|
18
|
-
*
|
|
19
|
-
* This action is called in two scenarios:
|
|
20
|
-
*
|
|
21
|
-
* - During data selection to preview data before import (when {@link RenderSelectionUiRequest.updateDataRef} is called).
|
|
22
|
-
* - When refreshing previously imported data (when the user requests an update).
|
|
23
|
-
*
|
|
24
|
-
* @param params - Parameters for the data fetching operation.
|
|
25
|
-
* @returns A promise resolving to either a successful result with data or an error.
|
|
26
|
-
*/
|
|
27
|
-
getDataTable: async (
|
|
28
|
-
params: GetDataTableRequest,
|
|
29
|
-
): Promise<GetDataTableResponse> => {
|
|
30
|
-
const oauth = auth.initOauth();
|
|
31
|
-
const token = await oauth.getAccessToken({ scope });
|
|
32
|
-
return buildDataTableResult(params, token?.token);
|
|
33
|
-
},
|
|
34
|
-
|
|
35
|
-
/**
|
|
36
|
-
* Renders a UI component for selecting and configuring data from external sources.
|
|
37
|
-
* This UI should allow users to browse data sources, apply filters, and select data.
|
|
38
|
-
* When selection is complete, the implementation must call the `updateDataRef`
|
|
39
|
-
* callback provided in the params to preview and confirm the data selection.
|
|
40
|
-
*
|
|
41
|
-
* @param request - parameters that provide context and configuration for the data selection UI.
|
|
42
|
-
* Contains invocation context, size limits, and the updateDataRef callback
|
|
43
|
-
*/
|
|
44
|
-
renderSelectionUi: async (request: RenderSelectionUiRequest) => {
|
|
45
|
-
function render() {
|
|
46
|
-
root.render(<App request={request} />);
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
render();
|
|
50
|
-
|
|
51
|
-
if (module.hot) {
|
|
52
|
-
module.hot.accept("./app", render);
|
|
53
|
-
module.hot.accept("./api", render);
|
|
54
|
-
}
|
|
55
|
-
},
|
|
56
|
-
});
|
|
57
|
-
|
|
58
|
-
// TODO: Fallback message if you have not turned on the data connector intent.
|
|
59
|
-
// You can remove this once your app is correctly configured.
|
|
60
|
-
root.render(
|
|
61
|
-
<AppUiProvider>
|
|
62
|
-
{/* eslint-disable-next-line formatjs/no-literal-string-in-jsx */}
|
|
63
|
-
<Alert tone="critical">
|
|
64
|
-
If you're seeing this, you need to turn on the data connector intent in
|
|
65
|
-
the Developer Portal for this app.
|
|
66
|
-
</Alert>
|
|
67
|
-
</AppUiProvider>,
|
|
68
|
-
);
|
|
4
|
+
prepareDataConnector(dataConnector);
|
|
@@ -3,9 +3,9 @@ import { AppUiProvider } from "@canva/app-ui-kit";
|
|
|
3
3
|
import type { RenderSelectionUiRequest } from "@canva/intents/data";
|
|
4
4
|
import { ErrorBoundary } from "react-error-boundary";
|
|
5
5
|
import { createHashRouter, RouterProvider } from "react-router-dom";
|
|
6
|
-
import { ContextProvider } from "
|
|
7
|
-
import { ErrorPage } from "
|
|
8
|
-
import { routes } from "
|
|
6
|
+
import { ContextProvider } from "../../context";
|
|
7
|
+
import { ErrorPage } from "../../pages";
|
|
8
|
+
import { routes } from "../../routes";
|
|
9
9
|
|
|
10
10
|
export const App = ({ request }: { request: RenderSelectionUiRequest }) => (
|
|
11
11
|
<AppI18nProvider>
|
package/templates/data_connector/src/{entrypoint.tsx → intents/data_connector/entrypoint.tsx}
RENAMED
|
@@ -5,15 +5,15 @@ import type {
|
|
|
5
5
|
APIResponseItem,
|
|
6
6
|
DataSourceConfig,
|
|
7
7
|
DataSourceHandler,
|
|
8
|
-
} from "
|
|
9
|
-
import { DATA_SOURCES } from "
|
|
10
|
-
import { useAppContext } from "
|
|
11
|
-
import { Paths } from "
|
|
8
|
+
} from "../../api";
|
|
9
|
+
import { DATA_SOURCES } from "../../api/data_sources";
|
|
10
|
+
import { useAppContext } from "../../context";
|
|
11
|
+
import { Paths } from "../../routes/paths";
|
|
12
12
|
import {
|
|
13
13
|
isDataRefEmpty,
|
|
14
14
|
isLaunchedWithError,
|
|
15
15
|
isOutdatedSource,
|
|
16
|
-
} from "
|
|
16
|
+
} from "../../utils/data_params";
|
|
17
17
|
|
|
18
18
|
const parseDataSource = (source: string) => {
|
|
19
19
|
try {
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { Box, Rows } from "@canva/app-ui-kit";
|
|
2
2
|
import { Outlet } from "react-router-dom";
|
|
3
3
|
import * as styles from "styles/components.css";
|
|
4
|
-
import { AppError } from "
|
|
4
|
+
import { AppError } from "../../components";
|
|
5
5
|
|
|
6
6
|
export const Home = () => (
|
|
7
7
|
<div className={styles.scrollContainer}>
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import "@canva/app-ui-kit/styles.css";
|
|
2
|
+
import type {
|
|
3
|
+
DataConnectorIntent,
|
|
4
|
+
GetDataTableRequest,
|
|
5
|
+
GetDataTableResponse,
|
|
6
|
+
RenderSelectionUiRequest,
|
|
7
|
+
} from "@canva/intents/data";
|
|
8
|
+
import { auth } from "@canva/user";
|
|
9
|
+
import { createRoot } from "react-dom/client";
|
|
10
|
+
import { buildDataTableResult, scope } from "../../api";
|
|
11
|
+
import { App } from "./app";
|
|
12
|
+
|
|
13
|
+
const dataConnector: DataConnectorIntent = {
|
|
14
|
+
/**
|
|
15
|
+
* Fetches structured data from an external source.
|
|
16
|
+
*
|
|
17
|
+
* This action is called in two scenarios:
|
|
18
|
+
*
|
|
19
|
+
* - During data selection to preview data before import (when {@link RenderSelectionUiRequest.updateDataRef} is called).
|
|
20
|
+
* - When refreshing previously imported data (when the user requests an update).
|
|
21
|
+
*
|
|
22
|
+
* @param params - Parameters for the data fetching operation.
|
|
23
|
+
* @returns A promise resolving to either a successful result with data or an error.
|
|
24
|
+
*/
|
|
25
|
+
getDataTable: async (
|
|
26
|
+
params: GetDataTableRequest,
|
|
27
|
+
): Promise<GetDataTableResponse> => {
|
|
28
|
+
const oauth = auth.initOauth();
|
|
29
|
+
const token = await oauth.getAccessToken({ scope });
|
|
30
|
+
return buildDataTableResult(params, token?.token);
|
|
31
|
+
},
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Renders a UI component for selecting and configuring data from external sources.
|
|
35
|
+
* This UI should allow users to browse data sources, apply filters, and select data.
|
|
36
|
+
* When selection is complete, the implementation must call the `updateDataRef`
|
|
37
|
+
* callback provided in the params to preview and confirm the data selection.
|
|
38
|
+
*
|
|
39
|
+
* @param request - parameters that provide context and configuration for the data selection UI.
|
|
40
|
+
* Contains invocation context, size limits, and the updateDataRef callback
|
|
41
|
+
*/
|
|
42
|
+
renderSelectionUi: async (request: RenderSelectionUiRequest) => {
|
|
43
|
+
function render() {
|
|
44
|
+
const root = createRoot(document.getElementById("root") as Element);
|
|
45
|
+
root.render(<App request={request} />);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
render();
|
|
49
|
+
|
|
50
|
+
if (module.hot) {
|
|
51
|
+
module.hot.accept("./app", render);
|
|
52
|
+
module.hot.accept("../../api", render);
|
|
53
|
+
}
|
|
54
|
+
},
|
|
55
|
+
};
|
|
56
|
+
export default dataConnector;
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { Button, Rows, Text } from "@canva/app-ui-kit";
|
|
2
2
|
import { FormattedMessage, useIntl } from "react-intl";
|
|
3
3
|
import { useNavigate } from "react-router-dom";
|
|
4
|
-
import { Paths } from "src/paths";
|
|
4
|
+
import { Paths } from "src/routes/paths";
|
|
5
5
|
import * as styles from "styles/components.css";
|
|
6
6
|
|
|
7
7
|
/**
|
|
@@ -11,7 +11,7 @@ import { defineMessages, FormattedMessage, useIntl } from "react-intl";
|
|
|
11
11
|
import { useNavigate } from "react-router-dom";
|
|
12
12
|
import { scope } from "src/api";
|
|
13
13
|
import { Header } from "src/components";
|
|
14
|
-
import { Paths } from "src/paths";
|
|
14
|
+
import { Paths } from "src/routes/paths";
|
|
15
15
|
import * as styles from "styles/components.css";
|
|
16
16
|
import { useAppContext } from "../context";
|
|
17
17
|
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import type { ReactNode } from "react";
|
|
2
2
|
import { useEffect } from "react";
|
|
3
3
|
import { useNavigate } from "react-router-dom";
|
|
4
|
-
import { Paths } from "src/paths";
|
|
4
|
+
import { Paths } from "src/routes/paths";
|
|
5
5
|
import { useAppContext } from "../context";
|
|
6
6
|
|
|
7
7
|
interface ProtectedRouteProps {
|
|
@@ -1,10 +1,10 @@
|
|
|
1
|
-
import { Entrypoint } from "src/entrypoint";
|
|
2
|
-
import { Home } from "src/home";
|
|
1
|
+
import { Entrypoint } from "src/intents/data_connector/entrypoint";
|
|
2
|
+
import { Home } from "src/intents/data_connector/home";
|
|
3
3
|
import { DataSourceConfig } from "src/pages/data_source_config";
|
|
4
4
|
import { ErrorPage } from "src/pages/error";
|
|
5
5
|
import { Login } from "src/pages/login";
|
|
6
6
|
import { SelectSource } from "src/pages/select_source";
|
|
7
|
-
import { Paths } from "src/paths";
|
|
7
|
+
import { Paths } from "src/routes/paths";
|
|
8
8
|
import { ProtectedRoute } from "./protected_route";
|
|
9
9
|
|
|
10
10
|
export const routes = [
|
|
@@ -36,3 +36,21 @@
|
|
|
36
36
|
.scrollContainer:focus-within::-webkit-scrollbar-thumb {
|
|
37
37
|
visibility: visible;
|
|
38
38
|
}
|
|
39
|
+
|
|
40
|
+
/* Main container for the content publisher preview UI */
|
|
41
|
+
.previewContainer {
|
|
42
|
+
display: flex;
|
|
43
|
+
align-items: center;
|
|
44
|
+
justify-content: center;
|
|
45
|
+
flex-direction: column;
|
|
46
|
+
width: 100%;
|
|
47
|
+
height: 100%;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/* Wrapper for the content publisher post preview */
|
|
51
|
+
.previewWrapper {
|
|
52
|
+
display: flex;
|
|
53
|
+
align-items: center;
|
|
54
|
+
justify-content: center;
|
|
55
|
+
width: calc(400px + 32px + 2px); /* Image width + padding + border */
|
|
56
|
+
}
|
|
@@ -18,11 +18,13 @@
|
|
|
18
18
|
"postinstall": "ts-node ./scripts/copy_env.ts"
|
|
19
19
|
},
|
|
20
20
|
"dependencies": {
|
|
21
|
+
"@canva/app-hooks": "^0.0.0-beta.4",
|
|
21
22
|
"@canva/app-i18n-kit": "^1.1.1",
|
|
22
23
|
"@canva/app-ui-kit": "^5.2.1",
|
|
23
24
|
"@canva/asset": "^2.2.1",
|
|
24
25
|
"@canva/design": "^2.7.3",
|
|
25
26
|
"@canva/error": "^2.1.0",
|
|
27
|
+
"@canva/intents": "^2.0.0",
|
|
26
28
|
"@canva/platform": "^2.2.0",
|
|
27
29
|
"@canva/user": "^2.1.1",
|
|
28
30
|
"cookie-parser": "1.4.7",
|
|
@@ -66,7 +68,7 @@
|
|
|
66
68
|
"debug": "4.4.1",
|
|
67
69
|
"dotenv": "16.6.0",
|
|
68
70
|
"exponential-backoff": "3.1.2",
|
|
69
|
-
"express": "4.
|
|
71
|
+
"express": "4.22.1",
|
|
70
72
|
"express-basic-auth": "1.2.1",
|
|
71
73
|
"jest": "29.7.0",
|
|
72
74
|
"jest-css-modules-transform": "4.4.2",
|
|
@@ -75,7 +77,7 @@
|
|
|
75
77
|
"jwks-rsa": "3.2.0",
|
|
76
78
|
"mini-css-extract-plugin": "2.9.4",
|
|
77
79
|
"node-fetch": "3.3.2",
|
|
78
|
-
"node-forge": "1.3.
|
|
80
|
+
"node-forge": "1.3.2",
|
|
79
81
|
"nodemon": "3.0.1",
|
|
80
82
|
"open": "8.4.2",
|
|
81
83
|
"postcss-loader": "8.1.1",
|
|
@@ -6,7 +6,7 @@ import { purchaseCredits, queueImageGeneration } from "src/api";
|
|
|
6
6
|
import { RemainingCredits } from "src/components";
|
|
7
7
|
import { NUMBER_OF_IMAGES_TO_GENERATE } from "src/config";
|
|
8
8
|
import { useAppContext } from "src/context";
|
|
9
|
-
import { Paths } from "src/paths";
|
|
9
|
+
import { Paths } from "src/routes/paths";
|
|
10
10
|
import { getObsceneWords } from "src/utils";
|
|
11
11
|
import { FooterMessages as Messages } from "./footer.messages";
|
|
12
12
|
|
|
@@ -12,7 +12,7 @@ import { FormattedMessage, useIntl } from "react-intl";
|
|
|
12
12
|
import { useNavigate } from "react-router-dom";
|
|
13
13
|
import { cancelImageGenerationJob, getImageGenerationJobStatus } from "src/api";
|
|
14
14
|
import { useAppContext } from "src/context";
|
|
15
|
-
import { Paths } from "src/paths";
|
|
15
|
+
import { Paths } from "src/routes/paths";
|
|
16
16
|
|
|
17
17
|
const INTERVAL_DURATION_IN_MS = 100;
|
|
18
18
|
const TOTAL_PROGRESS_PERCENTAGE = 100;
|
|
@@ -9,7 +9,7 @@ import { useState } from "react";
|
|
|
9
9
|
import { useIntl } from "react-intl";
|
|
10
10
|
import { useLocation } from "react-router-dom";
|
|
11
11
|
import { useAppContext } from "src/context";
|
|
12
|
-
import { Paths } from "src/paths";
|
|
12
|
+
import { Paths } from "src/routes/paths";
|
|
13
13
|
import { PromptInputMessages as Messages } from "./prompt_input.messages";
|
|
14
14
|
|
|
15
15
|
// @TODO: Adjust according to your specific requirements.
|
|
@@ -1,15 +1,4 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import "
|
|
3
|
-
import { App } from "./app";
|
|
1
|
+
import { prepareDesignEditor } from "@canva/intents/design";
|
|
2
|
+
import designEditor from "./intents/design_editor";
|
|
4
3
|
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
function render() {
|
|
8
|
-
root.render(<App />);
|
|
9
|
-
}
|
|
10
|
-
|
|
11
|
-
render();
|
|
12
|
-
|
|
13
|
-
if (module.hot) {
|
|
14
|
-
module.hot.accept("./app", render);
|
|
15
|
-
}
|
|
4
|
+
prepareDesignEditor(designEditor);
|
|
@@ -2,9 +2,9 @@ import { AppI18nProvider } from "@canva/app-i18n-kit";
|
|
|
2
2
|
import { AppUiProvider } from "@canva/app-ui-kit";
|
|
3
3
|
import { ErrorBoundary } from "react-error-boundary";
|
|
4
4
|
import { createHashRouter, RouterProvider } from "react-router-dom";
|
|
5
|
-
import { ContextProvider } from "
|
|
6
|
-
import { ErrorPage } from "
|
|
7
|
-
import { routes } from "
|
|
5
|
+
import { ContextProvider } from "../../context";
|
|
6
|
+
import { ErrorPage } from "../../pages";
|
|
7
|
+
import { routes } from "../../routes";
|
|
8
8
|
|
|
9
9
|
export const App = () => (
|
|
10
10
|
<AppI18nProvider>
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { Rows } from "@canva/app-ui-kit";
|
|
2
2
|
import { Outlet } from "react-router-dom";
|
|
3
3
|
import * as styles from "styles/components.css";
|
|
4
|
-
import { Footer } from "
|
|
4
|
+
import { Footer } from "../../components";
|
|
5
5
|
|
|
6
6
|
export const Home = () => (
|
|
7
7
|
<div className={styles.scrollContainer}>
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import "@canva/app-ui-kit/styles.css";
|
|
2
|
+
import type { DesignEditorIntent } from "@canva/intents/design";
|
|
3
|
+
import { createRoot } from "react-dom/client";
|
|
4
|
+
import { App } from "./app";
|
|
5
|
+
|
|
6
|
+
async function render() {
|
|
7
|
+
const root = createRoot(document.getElementById("root") as Element);
|
|
8
|
+
|
|
9
|
+
root.render(<App />);
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
const designEditor: DesignEditorIntent = { render };
|
|
13
|
+
export default designEditor;
|
|
14
|
+
|
|
15
|
+
if (module.hot) {
|
|
16
|
+
module.hot.accept("./app", render);
|
|
17
|
+
}
|
|
@@ -2,7 +2,7 @@ import { Button, Rows, Text } from "@canva/app-ui-kit";
|
|
|
2
2
|
import { FormattedMessage, useIntl } from "react-intl";
|
|
3
3
|
import { useNavigate } from "react-router-dom";
|
|
4
4
|
import { useAppContext } from "src/context";
|
|
5
|
-
import { Paths } from "src/paths";
|
|
5
|
+
import { Paths } from "src/routes/paths";
|
|
6
6
|
import * as styles from "styles/components.css";
|
|
7
7
|
|
|
8
8
|
/**
|
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
import { Home } from "src/home";
|
|
1
|
+
import { Home } from "src/intents/design_editor/home";
|
|
2
2
|
import { ErrorPage } from "src/pages/error";
|
|
3
3
|
import { GeneratePage } from "src/pages/generate";
|
|
4
4
|
import { ResultsPage } from "src/pages/results";
|
|
5
|
-
import { Paths } from "src/paths";
|
|
5
|
+
import { Paths } from "src/routes/paths";
|
|
6
6
|
|
|
7
7
|
export const routes = [
|
|
8
8
|
{
|
|
@@ -36,3 +36,21 @@
|
|
|
36
36
|
.scrollContainer:focus-within::-webkit-scrollbar-thumb {
|
|
37
37
|
visibility: visible;
|
|
38
38
|
}
|
|
39
|
+
|
|
40
|
+
/* Main container for the content publisher preview UI */
|
|
41
|
+
.previewContainer {
|
|
42
|
+
display: flex;
|
|
43
|
+
align-items: center;
|
|
44
|
+
justify-content: center;
|
|
45
|
+
flex-direction: column;
|
|
46
|
+
width: 100%;
|
|
47
|
+
height: 100%;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/* Wrapper for the content publisher post preview */
|
|
51
|
+
.previewWrapper {
|
|
52
|
+
display: flex;
|
|
53
|
+
align-items: center;
|
|
54
|
+
justify-content: center;
|
|
55
|
+
width: calc(400px + 32px + 2px); /* Image width + padding + border */
|
|
56
|
+
}
|
|
@@ -19,11 +19,13 @@
|
|
|
19
19
|
"postinstall": "ts-node ./scripts/copy_env.ts"
|
|
20
20
|
},
|
|
21
21
|
"dependencies": {
|
|
22
|
+
"@canva/app-hooks": "^0.0.0-beta.4",
|
|
22
23
|
"@canva/app-i18n-kit": "^1.1.1",
|
|
23
24
|
"@canva/app-ui-kit": "^5.2.1",
|
|
24
25
|
"@canva/asset": "^2.2.1",
|
|
25
26
|
"@canva/design": "^2.7.3",
|
|
26
27
|
"@canva/error": "^2.1.0",
|
|
28
|
+
"@canva/intents": "^2.0.0",
|
|
27
29
|
"@canva/platform": "^2.2.0",
|
|
28
30
|
"@canva/user": "^2.1.1",
|
|
29
31
|
"react": "^19.2.0",
|
|
@@ -57,7 +59,7 @@
|
|
|
57
59
|
"cssnano": "7.1.1",
|
|
58
60
|
"debug": "4.4.1",
|
|
59
61
|
"dotenv": "16.6.0",
|
|
60
|
-
"express": "4.
|
|
62
|
+
"express": "4.22.1",
|
|
61
63
|
"express-basic-auth": "1.2.1",
|
|
62
64
|
"jest": "29.7.0",
|
|
63
65
|
"jest-css-modules-transform": "4.4.2",
|
|
@@ -66,7 +68,7 @@
|
|
|
66
68
|
"jwks-rsa": "3.2.0",
|
|
67
69
|
"mini-css-extract-plugin": "2.9.4",
|
|
68
70
|
"node-fetch": "3.3.2",
|
|
69
|
-
"node-forge": "1.3.
|
|
71
|
+
"node-forge": "1.3.2",
|
|
70
72
|
"nodemon": "3.0.1",
|
|
71
73
|
"open": "8.4.2",
|
|
72
74
|
"postcss-loader": "8.1.1",
|
|
@@ -1,22 +1,4 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import
|
|
3
|
-
import { createRoot } from "react-dom/client";
|
|
4
|
-
import { App } from "./app";
|
|
5
|
-
import "@canva/app-ui-kit/styles.css";
|
|
1
|
+
import { prepareDesignEditor } from "@canva/intents/design";
|
|
2
|
+
import designEditor from "./intents/design_editor";
|
|
6
3
|
|
|
7
|
-
|
|
8
|
-
function render() {
|
|
9
|
-
root.render(
|
|
10
|
-
<AppI18nProvider>
|
|
11
|
-
<AppUiProvider>
|
|
12
|
-
<App />
|
|
13
|
-
</AppUiProvider>
|
|
14
|
-
</AppI18nProvider>,
|
|
15
|
-
);
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
render();
|
|
19
|
-
|
|
20
|
-
if (module.hot) {
|
|
21
|
-
module.hot.accept("./app", render);
|
|
22
|
-
}
|
|
4
|
+
prepareDesignEditor(designEditor);
|
|
@@ -1,15 +1,23 @@
|
|
|
1
|
+
import { useFeatureSupport } from "@canva/app-hooks";
|
|
1
2
|
import { Button, Rows, Text } from "@canva/app-ui-kit";
|
|
3
|
+
import { addElementAtCursor, addElementAtPoint } from "@canva/design";
|
|
2
4
|
import { requestOpenExternalUrl } from "@canva/platform";
|
|
3
5
|
import { FormattedMessage, useIntl } from "react-intl";
|
|
4
|
-
import { useAddElement } from "utils/use_add_element";
|
|
5
6
|
import * as styles from "styles/components.css";
|
|
6
7
|
|
|
7
8
|
export const DOCS_URL = "https://www.canva.dev/docs/apps/";
|
|
8
9
|
|
|
9
10
|
export const App = () => {
|
|
10
|
-
const
|
|
11
|
+
const isSupported = useFeatureSupport();
|
|
12
|
+
const addElement = [addElementAtPoint, addElementAtCursor].find((fn) =>
|
|
13
|
+
isSupported(fn),
|
|
14
|
+
);
|
|
11
15
|
|
|
12
16
|
const onClick = () => {
|
|
17
|
+
if (!addElement) {
|
|
18
|
+
return;
|
|
19
|
+
}
|
|
20
|
+
|
|
13
21
|
addElement({
|
|
14
22
|
type: "text",
|
|
15
23
|
children: ["Hello world!"],
|
|
@@ -43,7 +51,22 @@ export const App = () => {
|
|
|
43
51
|
}}
|
|
44
52
|
/>
|
|
45
53
|
</Text>
|
|
46
|
-
<Button
|
|
54
|
+
<Button
|
|
55
|
+
variant="primary"
|
|
56
|
+
onClick={onClick}
|
|
57
|
+
disabled={!addElement}
|
|
58
|
+
tooltipLabel={
|
|
59
|
+
!addElement
|
|
60
|
+
? intl.formatMessage({
|
|
61
|
+
defaultMessage:
|
|
62
|
+
"This feature is not supported in the current page",
|
|
63
|
+
description:
|
|
64
|
+
"Tooltip label for when a feature is not supported in the current design",
|
|
65
|
+
})
|
|
66
|
+
: undefined
|
|
67
|
+
}
|
|
68
|
+
stretch
|
|
69
|
+
>
|
|
47
70
|
{intl.formatMessage({
|
|
48
71
|
defaultMessage: "Do something cool",
|
|
49
72
|
description:
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import "@canva/app-ui-kit/styles.css";
|
|
2
|
+
import { AppI18nProvider } from "@canva/app-i18n-kit";
|
|
3
|
+
import { AppUiProvider } from "@canva/app-ui-kit";
|
|
4
|
+
import type { DesignEditorIntent } from "@canva/intents/design";
|
|
5
|
+
import { createRoot } from "react-dom/client";
|
|
6
|
+
import { App } from "./app";
|
|
7
|
+
|
|
8
|
+
async function render() {
|
|
9
|
+
const root = createRoot(document.getElementById("root") as Element);
|
|
10
|
+
|
|
11
|
+
root.render(
|
|
12
|
+
<AppI18nProvider>
|
|
13
|
+
<AppUiProvider>
|
|
14
|
+
<App />
|
|
15
|
+
</AppUiProvider>
|
|
16
|
+
</AppI18nProvider>,
|
|
17
|
+
);
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
const designEditor: DesignEditorIntent = { render };
|
|
21
|
+
export default designEditor;
|
|
22
|
+
|
|
23
|
+
if (module.hot) {
|
|
24
|
+
module.hot.accept("./app", render);
|
|
25
|
+
}
|
|
@@ -1,11 +1,13 @@
|
|
|
1
1
|
/* eslint-disable formatjs/no-literal-string-in-jsx */
|
|
2
|
+
import { useFeatureSupport } from "@canva/app-hooks";
|
|
2
3
|
import { TestAppI18nProvider } from "@canva/app-i18n-kit";
|
|
3
4
|
import { TestAppUiProvider } from "@canva/app-ui-kit";
|
|
5
|
+
import { addElementAtCursor, addElementAtPoint } from "@canva/design";
|
|
6
|
+
import type { Feature } from "@canva/platform";
|
|
4
7
|
import { requestOpenExternalUrl } from "@canva/platform";
|
|
5
8
|
import { fireEvent, render } from "@testing-library/react";
|
|
6
9
|
import type { RenderResult } from "@testing-library/react";
|
|
7
10
|
import type { ReactNode } from "react";
|
|
8
|
-
import { useAddElement } from "utils/use_add_element";
|
|
9
11
|
import { App, DOCS_URL } from "../app";
|
|
10
12
|
|
|
11
13
|
function renderInTestProvider(node: ReactNode): RenderResult {
|
|
@@ -17,33 +19,35 @@ function renderInTestProvider(node: ReactNode): RenderResult {
|
|
|
17
19
|
);
|
|
18
20
|
}
|
|
19
21
|
|
|
20
|
-
jest.mock("
|
|
22
|
+
jest.mock("@canva/app-hooks");
|
|
21
23
|
|
|
22
24
|
// This test demonstrates how to test code that uses functions from the Canva Apps SDK
|
|
23
25
|
// For more information on testing with the Canva Apps SDK, see https://www.canva.dev/docs/apps/testing/
|
|
24
26
|
describe("Hello World Tests", () => {
|
|
25
|
-
|
|
26
|
-
const
|
|
27
|
-
const mockAddUseElement = jest.mocked(useAddElement);
|
|
27
|
+
const mockIsSupported = jest.fn();
|
|
28
|
+
const mockUseFeatureSupport = jest.mocked(useFeatureSupport);
|
|
28
29
|
const mockRequestOpenExternalUrl = jest.mocked(requestOpenExternalUrl);
|
|
29
30
|
|
|
30
31
|
beforeEach(() => {
|
|
31
32
|
jest.resetAllMocks();
|
|
32
|
-
|
|
33
|
+
mockIsSupported.mockImplementation(
|
|
34
|
+
(fn: Feature) => fn === addElementAtPoint,
|
|
35
|
+
);
|
|
36
|
+
mockUseFeatureSupport.mockReturnValue(mockIsSupported);
|
|
33
37
|
mockRequestOpenExternalUrl.mockResolvedValue({ status: "completed" });
|
|
34
38
|
});
|
|
35
39
|
|
|
36
|
-
// this test uses a mock in place of the
|
|
40
|
+
// this test uses a mock in place of the useFeatureSupport hook
|
|
37
41
|
it("should add a text element when the button is clicked", () => {
|
|
38
42
|
// assert that the mocks are in the expected clean state
|
|
39
|
-
expect(
|
|
40
|
-
expect(
|
|
43
|
+
expect(mockUseFeatureSupport).not.toHaveBeenCalled();
|
|
44
|
+
expect(addElementAtPoint).not.toHaveBeenCalled();
|
|
41
45
|
|
|
42
46
|
const result = renderInTestProvider(<App />);
|
|
43
47
|
|
|
44
48
|
// the hook should have been called in the render process but not the callback
|
|
45
|
-
expect(
|
|
46
|
-
expect(
|
|
49
|
+
expect(mockUseFeatureSupport).toHaveBeenCalled();
|
|
50
|
+
expect(addElementAtPoint).not.toHaveBeenCalled();
|
|
47
51
|
|
|
48
52
|
// get a reference to the do something cool button element
|
|
49
53
|
const doSomethingCoolBtn = result.getByRole("button", {
|
|
@@ -53,8 +57,10 @@ describe("Hello World Tests", () => {
|
|
|
53
57
|
// programmatically simulate clicking the button
|
|
54
58
|
fireEvent.click(doSomethingCoolBtn);
|
|
55
59
|
|
|
56
|
-
// we expect that
|
|
57
|
-
expect(
|
|
60
|
+
// we expect that addElementAtPoint has been called by the button's click handler
|
|
61
|
+
expect(mockIsSupported).toHaveBeenCalledWith(addElementAtPoint);
|
|
62
|
+
expect(mockIsSupported).not.toHaveBeenCalledWith(addElementAtCursor);
|
|
63
|
+
expect(addElementAtPoint).toHaveBeenCalled();
|
|
58
64
|
});
|
|
59
65
|
|
|
60
66
|
// this test uses a mock in place of the @canva/platform requestOpenExternalUrl function
|