@canva/cli 1.9.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.
Files changed (88) hide show
  1. package/CHANGELOG.md +23 -0
  2. package/cli.js +472 -463
  3. package/lib/cjs/index.cjs +2 -2
  4. package/lib/esm/index.mjs +2 -2
  5. package/lib/index.d.ts +2 -0
  6. package/package.json +1 -1
  7. package/templates/base/package.json +3 -3
  8. package/templates/base/styles/components.css +18 -0
  9. package/templates/base/tsconfig.json +5 -3
  10. package/templates/base/utils/backend/base_backend/create.ts +3 -3
  11. package/templates/base/utils/backend/bearer_middleware/bearer_middleware.ts +2 -4
  12. package/templates/base/utils/backend/jwt_middleware/jwt_middleware.ts +2 -4
  13. package/templates/base/webpack.config.ts +1 -1
  14. package/templates/common/.env.template +1 -1
  15. package/templates/common/utils/backend/base_backend/create.ts +3 -3
  16. package/templates/common/utils/backend/jwt_middleware/jwt_middleware.ts +2 -4
  17. package/templates/dam/backend/routers/dam.ts +35 -17
  18. package/templates/dam/canva-app.json +5 -0
  19. package/templates/dam/package.json +4 -3
  20. package/templates/dam/src/index.tsx +3 -21
  21. package/templates/dam/src/intents/design_editor/index.tsx +25 -0
  22. package/templates/dam/tsconfig.json +5 -3
  23. package/templates/dam/utils/backend/base_backend/create.ts +3 -3
  24. package/templates/dam/utils/backend/jwt_middleware/jwt_middleware.ts +2 -4
  25. package/templates/dam/webpack.config.ts +1 -1
  26. package/templates/data_connector/package.json +4 -3
  27. package/templates/data_connector/src/api/data_sources/designs.tsx +1 -1
  28. package/templates/data_connector/src/api/data_sources/templates.tsx +1 -1
  29. package/templates/data_connector/src/components/header.tsx +1 -1
  30. package/templates/data_connector/src/index.tsx +2 -66
  31. package/templates/data_connector/src/{app.tsx → intents/data_connector/app.tsx} +3 -3
  32. package/templates/data_connector/src/{entrypoint.tsx → intents/data_connector/entrypoint.tsx} +5 -5
  33. package/templates/data_connector/src/{home.tsx → intents/data_connector/home.tsx} +1 -1
  34. package/templates/data_connector/src/intents/data_connector/index.tsx +56 -0
  35. package/templates/data_connector/src/pages/error.tsx +1 -1
  36. package/templates/data_connector/src/pages/login.tsx +1 -1
  37. package/templates/data_connector/src/routes/protected_route.tsx +1 -1
  38. package/templates/data_connector/src/routes/routes.tsx +3 -3
  39. package/templates/data_connector/src/utils/data_table.ts +2 -1
  40. package/templates/data_connector/src/utils/tests/data_table.test.ts +2 -2
  41. package/templates/data_connector/styles/components.css +18 -0
  42. package/templates/data_connector/tsconfig.json +5 -3
  43. package/templates/data_connector/webpack.config.ts +1 -1
  44. package/templates/gen_ai/backend/routers/image.ts +4 -6
  45. package/templates/gen_ai/canva-app.json +5 -0
  46. package/templates/gen_ai/package.json +5 -3
  47. package/templates/gen_ai/src/components/footer.tsx +1 -1
  48. package/templates/gen_ai/src/components/image_grid.tsx +6 -2
  49. package/templates/gen_ai/src/components/loading_results.tsx +1 -1
  50. package/templates/gen_ai/src/components/prompt_input.tsx +5 -2
  51. package/templates/gen_ai/src/index.tsx +3 -14
  52. package/templates/gen_ai/src/{app.tsx → intents/design_editor/app.tsx} +3 -3
  53. package/templates/gen_ai/src/{home.tsx → intents/design_editor/home.tsx} +1 -1
  54. package/templates/gen_ai/src/intents/design_editor/index.tsx +17 -0
  55. package/templates/gen_ai/src/pages/error.tsx +1 -1
  56. package/templates/gen_ai/src/routes/routes.tsx +2 -2
  57. package/templates/gen_ai/styles/components.css +18 -0
  58. package/templates/gen_ai/tsconfig.json +5 -3
  59. package/templates/gen_ai/utils/backend/base_backend/create.ts +3 -3
  60. package/templates/gen_ai/utils/backend/bearer_middleware/bearer_middleware.ts +2 -4
  61. package/templates/gen_ai/webpack.config.ts +1 -1
  62. package/templates/hello_world/canva-app.json +5 -0
  63. package/templates/hello_world/package.json +5 -3
  64. package/templates/hello_world/src/index.tsx +3 -21
  65. package/templates/hello_world/src/{app.tsx → intents/design_editor/app.tsx} +26 -3
  66. package/templates/hello_world/src/intents/design_editor/index.tsx +25 -0
  67. package/templates/hello_world/src/{tests → intents/design_editor/tests}/app.tests.tsx +20 -14
  68. package/templates/hello_world/styles/components.css +18 -0
  69. package/templates/hello_world/tsconfig.json +5 -3
  70. package/templates/hello_world/webpack.config.ts +1 -1
  71. package/templates/optional/AGENTS.md +80 -2
  72. package/templates/optional/CLAUDE.md +80 -2
  73. package/templates/base/utils/use_add_element.ts +0 -48
  74. package/templates/base/utils/use_feature_support.ts +0 -28
  75. package/templates/common/utils/table_wrapper.ts +0 -477
  76. package/templates/common/utils/use_add_element.ts +0 -48
  77. package/templates/common/utils/use_feature_support.ts +0 -28
  78. package/templates/common/utils/use_overlay_hook.ts +0 -74
  79. package/templates/common/utils/use_selection_hook.ts +0 -37
  80. package/templates/hello_world/utils/use_add_element.ts +0 -48
  81. package/templates/hello_world/utils/use_feature_support.ts +0 -28
  82. /package/templates/dam/src/{adapter.ts → intents/design_editor/adapter.ts} +0 -0
  83. /package/templates/dam/src/{app.tsx → intents/design_editor/app.tsx} +0 -0
  84. /package/templates/dam/src/{config.ts → intents/design_editor/config.ts} +0 -0
  85. /package/templates/dam/src/{index.css → intents/design_editor/index.css} +0 -0
  86. /package/templates/data_connector/src/{paths.ts → routes/paths.ts} +0 -0
  87. /package/templates/gen_ai/src/{paths.ts → routes/paths.ts} +0 -0
  88. /package/templates/hello_world/src/{tests → intents/design_editor/tests}/__snapshots__/app.tests.tsx.snap +0 -0
@@ -1,8 +1,6 @@
1
1
  /* eslint-disable no-console */
2
2
  import debug from "debug";
3
3
  import type { NextFunction, Request, Response } from "express";
4
- // eslint-disable-next-line @typescript-eslint/no-unused-vars
5
- import Express from "express-serve-static-core";
6
4
  import jwt from "jsonwebtoken";
7
5
  import { JwksClient, SigningKeyNotFoundError } from "jwks-rsa";
8
6
 
@@ -115,7 +113,7 @@ export function createJwtMiddleware(
115
113
  userId: payload.userId,
116
114
  };
117
115
 
118
- next();
116
+ return next();
119
117
  } catch (e) {
120
118
  if (e instanceof JWTAuthorizationError) {
121
119
  return sendUnauthorizedResponse(res, e.message);
@@ -136,7 +134,7 @@ export function createJwtMiddleware(
136
134
  return sendUnauthorizedResponse(res, "Token expired");
137
135
  }
138
136
 
139
- next(e);
137
+ return next(e);
140
138
  }
141
139
  };
142
140
  }
@@ -3,8 +3,8 @@ import crypto from "crypto";
3
3
  import express from "express";
4
4
 
5
5
  /**
6
- * Generates a unique hash for a url.
7
- * Handy for uniquely identifying an image and creating an image id
6
+ * Generates a unique hash for a URL.
7
+ * Used for creating unique identifiers for digital assets from external URLs.
8
8
  */
9
9
  export async function generateHash(message: string) {
10
10
  const msgUint8 = new TextEncoder().encode(message);
@@ -16,6 +16,7 @@ export async function generateHash(message: string) {
16
16
  return hashHex;
17
17
  }
18
18
 
19
+ // Mock image URLs for demonstration purposes - replace with your actual digital asset sources
19
20
  const imageUrls = [
20
21
  "https://images.pexels.com/photos/1495580/pexels-photo-1495580.jpeg?auto=compress&cs=tinysrgb&w=1260&h=750&dpr=2",
21
22
  "https://images.pexels.com/photos/3943197/pexels-photo-3943197.jpeg?auto=compress&cs=tinysrgb&w=1260&h=750&dpr=2",
@@ -23,15 +24,20 @@ const imageUrls = [
23
24
  "https://images.pexels.com/photos/2904142/pexels-photo-2904142.jpeg?auto=compress&cs=tinysrgb&w=1260&h=750&dpr=2",
24
25
  "https://images.pexels.com/photos/5403478/pexels-photo-5403478.jpeg?auto=compress&cs=tinysrgb&w=1260&h=750&dpr=2",
25
26
  ];
27
+
26
28
  export const createDamRouter = () => {
27
29
  const router = express.Router();
28
30
 
29
- /**
30
- * This endpoint returns the data for your app.
31
- */
31
+ /*
32
+ Main endpoint for finding digital assets and containers.
33
+ This should be replaced with actual integration to your digital asset management system.
34
+ */
32
35
  router.post("/resources/find", async (req, res) => {
33
- // You should modify these lines to return data from your
34
- // digital asset manager (DAM) based on the findResourcesRequest
36
+ /*
37
+ Extract relevant fields from the FindResourcesRequest.
38
+ Replace this mock implementation with actual queries to your digital asset management system.
39
+ Consider implementing proper filtering, sorting, and pagination based on these parameters.
40
+ */
35
41
  const {
36
42
  types,
37
43
  continuation,
@@ -48,21 +54,32 @@ export const createDamRouter = () => {
48
54
  } = req.body;
49
55
 
50
56
  let resources: Resource[] = [];
57
+
58
+ // Handle image resource requests
51
59
  if (types.includes("IMAGE")) {
52
60
  resources = await Promise.all(
53
- Array.from({ length: 40 }, async (_, i) => ({
54
- id: await generateHash(i + ""),
55
- mimeType: "image/jpeg",
56
- name: `My new thing in ${locale}`, // Use the `locale` value from the request if your backend supports i18n
57
- type: "IMAGE",
58
- thumbnail: {
59
- url: imageUrls[i % imageUrls.length],
60
- },
61
- url: imageUrls[i % imageUrls.length],
62
- })),
61
+ Array.from({ length: 40 }, async (_, i) => {
62
+ const imageUrl = imageUrls[i % imageUrls.length];
63
+
64
+ if (!imageUrl) {
65
+ throw new Error(`Image URL not found for index ${i}`);
66
+ }
67
+
68
+ return {
69
+ id: await generateHash(i + ""),
70
+ mimeType: "image/jpeg",
71
+ name: `My new thing in ${locale}`, // Uses locale for demonstration - implement proper i18n
72
+ type: "IMAGE",
73
+ thumbnail: {
74
+ url: imageUrl,
75
+ },
76
+ url: imageUrl,
77
+ };
78
+ }),
63
79
  );
64
80
  }
65
81
 
82
+ // Handle container (folder) resource requests
66
83
  if (types.includes("CONTAINER")) {
67
84
  const containers = await Promise.all(
68
85
  Array.from(
@@ -80,6 +97,7 @@ export const createDamRouter = () => {
80
97
  resources = resources.concat(containers);
81
98
  }
82
99
 
100
+ // Send response with resources and pagination token
83
101
  res.send({
84
102
  resources,
85
103
  continuation: +(continuation || 0) + 1,
@@ -12,5 +12,10 @@
12
12
  "type": "mandatory"
13
13
  }
14
14
  ]
15
+ },
16
+ "intent": {
17
+ "design_editor": {
18
+ "enrolled": true
19
+ }
15
20
  }
16
21
  }
@@ -18,12 +18,14 @@
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-components": "^2.1.0",
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
  "cookie-parser": "1.4.7",
@@ -44,7 +46,6 @@
44
46
  "@types/cors": "2.8.19",
45
47
  "@types/debug": "4.1.12",
46
48
  "@types/express": "4.17.21",
47
- "@types/express-serve-static-core": "5.0.7",
48
49
  "@types/jest": "29.5.14",
49
50
  "@types/jsonwebtoken": "9.0.10",
50
51
  "@types/node": "20.19.2",
@@ -63,7 +64,7 @@
63
64
  "debug": "4.4.1",
64
65
  "dotenv": "16.6.0",
65
66
  "exponential-backoff": "3.1.2",
66
- "express": "4.21.2",
67
+ "express": "4.22.1",
67
68
  "express-basic-auth": "1.2.1",
68
69
  "jest": "29.7.0",
69
70
  "jest-css-modules-transform": "4.4.2",
@@ -72,7 +73,7 @@
72
73
  "jwks-rsa": "3.2.0",
73
74
  "mini-css-extract-plugin": "2.9.4",
74
75
  "node-fetch": "3.3.2",
75
- "node-forge": "1.3.1",
76
+ "node-forge": "1.3.2",
76
77
  "nodemon": "3.0.1",
77
78
  "open": "8.4.2",
78
79
  "postcss-loader": "8.1.1",
@@ -1,22 +1,4 @@
1
- import { AppI18nProvider } from "@canva/app-i18n-kit";
2
- import { AppUiProvider } from "@canva/app-ui-kit";
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
- const root = createRoot(document.getElementById("root") as Element);
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);
@@ -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
+ }
@@ -28,7 +28,10 @@
28
28
  "sourceMap": true,
29
29
  "inlineSources": true,
30
30
  "module": "ESNext",
31
- "noImplicitAny": false,
31
+ "noImplicitAny": true,
32
+ "noImplicitReturns": true,
33
+ "noFallthroughCasesInSwitch": true,
34
+ "noUncheckedIndexedAccess": true,
32
35
  "removeComments": true,
33
36
  "preserveConstEnums": true,
34
37
  "allowSyntheticDefaultImports": true,
@@ -44,8 +47,7 @@
44
47
  "./utils/**/*",
45
48
  "./scripts/**/*",
46
49
  "./declarations/declarations.d.ts",
47
- "./styles/**/*",
48
- "./node_modules/@types/**/*"
50
+ "./styles/**/*"
49
51
  ],
50
52
  "ts-node": {
51
53
  "compilerOptions": {
@@ -41,12 +41,12 @@ export function createBaseServer(router: express.Router): BaseServer {
41
41
  app.disable("x-powered-by");
42
42
 
43
43
  // Health check endpoint
44
- app.get("/healthz", (req, res: Response) => {
44
+ app.get("/healthz", (req, res) => {
45
45
  res.sendStatus(200);
46
46
  });
47
47
 
48
48
  // logging middleware
49
- app.use((req: Request, res: Response, next: NextFunction) => {
49
+ app.use((req, _res, next) => {
50
50
  serverDebug(`${new Date().toISOString()}: ${req.method} ${req.url}`);
51
51
  next();
52
52
  });
@@ -62,7 +62,7 @@ export function createBaseServer(router: express.Router): BaseServer {
62
62
  });
63
63
 
64
64
  // default error handler
65
- app.use((err, req, res, next) => {
65
+ app.use((err: Error, _req: Request, res: Response, _next: NextFunction) => {
66
66
  console.error(err.stack);
67
67
  res.status(500).send({
68
68
  error: "something went wrong",
@@ -1,8 +1,6 @@
1
1
  /* eslint-disable no-console */
2
2
  import debug from "debug";
3
3
  import type { NextFunction, Request, Response } from "express";
4
- // eslint-disable-next-line @typescript-eslint/no-unused-vars
5
- import Express from "express-serve-static-core";
6
4
  import jwt from "jsonwebtoken";
7
5
  import { JwksClient, SigningKeyNotFoundError } from "jwks-rsa";
8
6
 
@@ -115,7 +113,7 @@ export function createJwtMiddleware(
115
113
  userId: payload.userId,
116
114
  };
117
115
 
118
- next();
116
+ return next();
119
117
  } catch (e) {
120
118
  if (e instanceof JWTAuthorizationError) {
121
119
  return sendUnauthorizedResponse(res, e.message);
@@ -136,7 +134,7 @@ export function createJwtMiddleware(
136
134
  return sendUnauthorizedResponse(res, "Token expired");
137
135
  }
138
136
 
139
- next(e);
137
+ return next(e);
140
138
  }
141
139
  };
142
140
  }
@@ -196,7 +196,7 @@ function buildDevConfig(options?: DevConfig): {
196
196
  return {};
197
197
  }
198
198
 
199
- const { port, enableHmr, appOrigin, appId, enableHttps, certFile, keyFile } =
199
+ const { port, enableHmr, appOrigin, enableHttps, certFile, keyFile } =
200
200
  options;
201
201
  const host = "localhost";
202
202
 
@@ -19,6 +19,7 @@
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",
@@ -42,8 +43,8 @@
42
43
  "@pmmmwh/react-refresh-webpack-plugin": "0.6.1",
43
44
  "@svgr/webpack": "8.1.0",
44
45
  "@testing-library/react": "16.3.0",
46
+ "@types/debug": "4.1.12",
45
47
  "@types/express": "4.17.21",
46
- "@types/express-serve-static-core": "5.0.7",
47
48
  "@types/jest": "29.5.14",
48
49
  "@types/jsonwebtoken": "9.0.10",
49
50
  "@types/node": "20.19.2",
@@ -60,7 +61,7 @@
60
61
  "cssnano": "7.1.1",
61
62
  "debug": "4.4.1",
62
63
  "dotenv": "16.6.0",
63
- "express": "4.21.2",
64
+ "express": "4.22.1",
64
65
  "express-basic-auth": "1.2.1",
65
66
  "jest": "29.7.0",
66
67
  "jest-css-modules-transform": "4.4.2",
@@ -69,7 +70,7 @@
69
70
  "jwks-rsa": "3.2.0",
70
71
  "mini-css-extract-plugin": "2.9.4",
71
72
  "node-fetch": "3.3.2",
72
- "node-forge": "1.3.1",
73
+ "node-forge": "1.3.2",
73
74
  "nodemon": "3.0.1",
74
75
  "open": "8.4.2",
75
76
  "postcss-loader": "8.1.1",
@@ -13,7 +13,7 @@ import {
13
13
  import { SearchFilter } from "src/components/inputs/search_filter";
14
14
  import { SelectField } from "src/components/inputs/select_field";
15
15
  import { useAppContext } from "src/context";
16
- import { Paths } from "src/paths";
16
+ import { Paths } from "src/routes/paths";
17
17
  import { dateCell, numberCell, stringCell } from "src/utils";
18
18
  import type { CanvaItemResponse } from "../connect_client";
19
19
  import { DataAPIError, DataSourceHandler } from "../data_source";
@@ -18,7 +18,7 @@ import {
18
18
  import { SearchFilter } from "src/components/inputs/search_filter";
19
19
  import { SelectField } from "src/components/inputs/select_field";
20
20
  import { useAppContext } from "src/context";
21
- import { Paths } from "src/paths";
21
+ import { Paths } from "src/routes/paths";
22
22
  import { dateCell, stringCell } from "src/utils";
23
23
  import type { CanvaItemResponse } from "../connect_client";
24
24
  import { DataAPIError, DataSourceHandler } from "../data_source";
@@ -7,7 +7,7 @@ import {
7
7
  Title,
8
8
  } from "@canva/app-ui-kit";
9
9
  import { useNavigate } from "react-router-dom";
10
- import { Paths } from "src/paths";
10
+ import { Paths } from "src/routes/paths";
11
11
 
12
12
  export const Header = ({
13
13
  title,
@@ -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 { auth } from "@canva/user";
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
- const root = createRoot(document.getElementById("root") as Element);
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 "./context";
7
- import { ErrorPage } from "./pages";
8
- import { routes } from "./routes";
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>
@@ -5,15 +5,15 @@ import type {
5
5
  APIResponseItem,
6
6
  DataSourceConfig,
7
7
  DataSourceHandler,
8
- } from "./api";
9
- import { DATA_SOURCES } from "./api/data_sources";
10
- import { useAppContext } from "./context";
11
- import { Paths } from "./paths";
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 "./utils/data_params";
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 "./components";
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 = [
@@ -12,7 +12,8 @@ import type { APIResponseItem } from "src/api";
12
12
  export interface DataTableColumn<T extends APIResponseItem> {
13
13
  label: string;
14
14
  getValue: keyof T | ((result: T) => boolean | string | number | Date);
15
- toCell: (value) => DataTableCell;
15
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
16
+ toCell: (value: any) => DataTableCell;
16
17
  }
17
18
 
18
19
  export function toDataTable<T extends APIResponseItem>(
@@ -92,8 +92,8 @@ describe("data table utils", () => {
92
92
  const result = toDataTable(testItems, columns, 10);
93
93
 
94
94
  // Check the custom column values
95
- expect(result.rows[0].cells[5].value).toBe("Test Item 1 (42)");
96
- expect(result.rows[1].cells[5].value).toBe("Test Item 2 (0)");
95
+ expect(result.rows[0]?.cells[5]?.value).toBe("Test Item 1 (42)");
96
+ expect(result.rows[1]?.cells[5]?.value).toBe("Test Item 2 (0)");
97
97
  });
98
98
 
99
99
  test("cell formatter functions create correct cell structures", () => {
@@ -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
+ }