@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.
- package/CHANGELOG.md +23 -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 -3
- package/templates/base/styles/components.css +18 -0
- package/templates/base/tsconfig.json +5 -3
- package/templates/base/utils/backend/base_backend/create.ts +3 -3
- package/templates/base/utils/backend/bearer_middleware/bearer_middleware.ts +2 -4
- package/templates/base/utils/backend/jwt_middleware/jwt_middleware.ts +2 -4
- package/templates/base/webpack.config.ts +1 -1
- package/templates/common/.env.template +1 -1
- package/templates/common/utils/backend/base_backend/create.ts +3 -3
- package/templates/common/utils/backend/jwt_middleware/jwt_middleware.ts +2 -4
- package/templates/dam/backend/routers/dam.ts +35 -17
- package/templates/dam/canva-app.json +5 -0
- package/templates/dam/package.json +4 -3
- package/templates/dam/src/index.tsx +3 -21
- package/templates/dam/src/intents/design_editor/index.tsx +25 -0
- package/templates/dam/tsconfig.json +5 -3
- package/templates/dam/utils/backend/base_backend/create.ts +3 -3
- package/templates/dam/utils/backend/jwt_middleware/jwt_middleware.ts +2 -4
- package/templates/dam/webpack.config.ts +1 -1
- package/templates/data_connector/package.json +4 -3
- 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/src/utils/data_table.ts +2 -1
- package/templates/data_connector/src/utils/tests/data_table.test.ts +2 -2
- package/templates/data_connector/styles/components.css +18 -0
- package/templates/data_connector/tsconfig.json +5 -3
- package/templates/data_connector/webpack.config.ts +1 -1
- package/templates/gen_ai/backend/routers/image.ts +4 -6
- package/templates/gen_ai/canva-app.json +5 -0
- package/templates/gen_ai/package.json +5 -3
- package/templates/gen_ai/src/components/footer.tsx +1 -1
- package/templates/gen_ai/src/components/image_grid.tsx +6 -2
- package/templates/gen_ai/src/components/loading_results.tsx +1 -1
- package/templates/gen_ai/src/components/prompt_input.tsx +5 -2
- 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/gen_ai/tsconfig.json +5 -3
- package/templates/gen_ai/utils/backend/base_backend/create.ts +3 -3
- package/templates/gen_ai/utils/backend/bearer_middleware/bearer_middleware.ts +2 -4
- package/templates/gen_ai/webpack.config.ts +1 -1
- package/templates/hello_world/canva-app.json +5 -0
- package/templates/hello_world/package.json +5 -3
- 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 +20 -14
- package/templates/hello_world/styles/components.css +18 -0
- package/templates/hello_world/tsconfig.json +5 -3
- package/templates/hello_world/webpack.config.ts +1 -1
- package/templates/optional/AGENTS.md +80 -2
- package/templates/optional/CLAUDE.md +80 -2
- package/templates/base/utils/use_add_element.ts +0 -48
- package/templates/base/utils/use_feature_support.ts +0 -28
- package/templates/common/utils/table_wrapper.ts +0 -477
- package/templates/common/utils/use_add_element.ts +0 -48
- package/templates/common/utils/use_feature_support.ts +0 -28
- package/templates/common/utils/use_overlay_hook.ts +0 -74
- package/templates/common/utils/use_selection_hook.ts +0 -37
- package/templates/hello_world/utils/use_add_element.ts +0 -48
- 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,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
|
|
7
|
-
*
|
|
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
|
-
|
|
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
|
-
|
|
34
|
-
|
|
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
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
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,
|
|
@@ -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.
|
|
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.
|
|
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 {
|
|
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);
|
|
@@ -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":
|
|
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
|
|
44
|
+
app.get("/healthz", (req, res) => {
|
|
45
45
|
res.sendStatus(200);
|
|
46
46
|
});
|
|
47
47
|
|
|
48
48
|
// logging middleware
|
|
49
|
-
app.use((req
|
|
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,
|
|
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,
|
|
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.
|
|
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.
|
|
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";
|
|
@@ -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 = [
|
|
@@ -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
|
-
|
|
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]
|
|
96
|
-
expect(result.rows[1]
|
|
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
|
+
}
|