@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
|
@@ -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": {
|
|
@@ -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
|
|
|
@@ -151,7 +151,7 @@ export const createImageRouter = () => {
|
|
|
151
151
|
// Add the job to the jobQueue along with the timeoutId
|
|
152
152
|
jobQueue.push({ jobId, prompt, timeoutId });
|
|
153
153
|
|
|
154
|
-
res.status(200).send({
|
|
154
|
+
return res.status(200).send({
|
|
155
155
|
jobId,
|
|
156
156
|
});
|
|
157
157
|
});
|
|
@@ -208,15 +208,13 @@ export const createImageRouter = () => {
|
|
|
208
208
|
}
|
|
209
209
|
|
|
210
210
|
const index = jobQueue.findIndex((job) => job.jobId === jobId);
|
|
211
|
-
|
|
211
|
+
const job = jobQueue[index];
|
|
212
|
+
if (index !== -1 && job) {
|
|
212
213
|
cancelledJobs.push({ jobId });
|
|
213
214
|
// If the job is found, remove it from the jobQueue
|
|
214
|
-
const { timeoutId } = jobQueue[index];
|
|
215
215
|
jobQueue.splice(index, 1);
|
|
216
216
|
// Also clear the timeout associated with this job if it exists
|
|
217
|
-
|
|
218
|
-
clearTimeout(timeoutId);
|
|
219
|
-
}
|
|
217
|
+
clearTimeout(job.timeoutId);
|
|
220
218
|
return res.status(200).send("Job successfully cancelled.");
|
|
221
219
|
}
|
|
222
220
|
|
|
@@ -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",
|
|
@@ -44,9 +46,9 @@
|
|
|
44
46
|
"@pmmmwh/react-refresh-webpack-plugin": "0.6.1",
|
|
45
47
|
"@svgr/webpack": "8.1.0",
|
|
46
48
|
"@testing-library/react": "16.3.0",
|
|
49
|
+
"@types/cors": "2.8.19",
|
|
47
50
|
"@types/debug": "4.1.12",
|
|
48
51
|
"@types/express": "4.17.21",
|
|
49
|
-
"@types/express-serve-static-core": "5.0.7",
|
|
50
52
|
"@types/jest": "29.5.14",
|
|
51
53
|
"@types/jsonwebtoken": "9.0.10",
|
|
52
54
|
"@types/node": "20.19.2",
|
|
@@ -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
|
|
|
@@ -34,7 +34,9 @@ export const ImageGrid = () => {
|
|
|
34
34
|
) => {
|
|
35
35
|
const parentNode = event.currentTarget.parentElement;
|
|
36
36
|
try {
|
|
37
|
-
|
|
37
|
+
if (styles.hidden) {
|
|
38
|
+
parentNode?.classList.add(styles.hidden);
|
|
39
|
+
}
|
|
38
40
|
|
|
39
41
|
await ui.startDragToPoint(event, {
|
|
40
42
|
type: "image",
|
|
@@ -50,7 +52,9 @@ export const ImageGrid = () => {
|
|
|
50
52
|
},
|
|
51
53
|
});
|
|
52
54
|
} finally {
|
|
53
|
-
|
|
55
|
+
if (styles.hidden) {
|
|
56
|
+
parentNode?.classList.remove(styles.hidden);
|
|
57
|
+
}
|
|
54
58
|
}
|
|
55
59
|
};
|
|
56
60
|
|
|
@@ -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.
|
|
@@ -59,8 +59,11 @@ const generateExamplePrompt = (currentPrompt: string): string => {
|
|
|
59
59
|
const MAX_ATTEMPTS = 3;
|
|
60
60
|
|
|
61
61
|
while (currentPrompt === newPrompt && attempts < MAX_ATTEMPTS) {
|
|
62
|
-
|
|
62
|
+
const randomPrompt =
|
|
63
63
|
examplePrompts[Math.floor(Math.random() * examplePrompts.length)];
|
|
64
|
+
if (randomPrompt) {
|
|
65
|
+
newPrompt = randomPrompt;
|
|
66
|
+
}
|
|
64
67
|
attempts++;
|
|
65
68
|
}
|
|
66
69
|
|
|
@@ -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
|
+
}
|
|
@@ -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
|
|
|
7
5
|
/**
|
|
8
6
|
* Prefix your start command with `DEBUG=express:middleware:bearer` to enable debug logging
|
|
@@ -47,13 +45,13 @@ export function createBearerMiddleware(
|
|
|
47
45
|
|
|
48
46
|
req["user_id"] = user;
|
|
49
47
|
|
|
50
|
-
next();
|
|
48
|
+
return next();
|
|
51
49
|
} catch (e) {
|
|
52
50
|
if (e instanceof AuthorizationError) {
|
|
53
51
|
return sendUnauthorizedResponse(res, e.message);
|
|
54
52
|
}
|
|
55
53
|
|
|
56
|
-
next(e);
|
|
54
|
+
return next(e);
|
|
57
55
|
}
|
|
58
56
|
};
|
|
59
57
|
}
|
|
@@ -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,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",
|
|
@@ -39,8 +41,8 @@
|
|
|
39
41
|
"@pmmmwh/react-refresh-webpack-plugin": "0.6.1",
|
|
40
42
|
"@svgr/webpack": "8.1.0",
|
|
41
43
|
"@testing-library/react": "16.3.0",
|
|
44
|
+
"@types/debug": "4.1.12",
|
|
42
45
|
"@types/express": "4.17.21",
|
|
43
|
-
"@types/express-serve-static-core": "5.0.7",
|
|
44
46
|
"@types/jest": "29.5.14",
|
|
45
47
|
"@types/jsonwebtoken": "9.0.10",
|
|
46
48
|
"@types/node": "20.19.2",
|
|
@@ -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
|
|
@@ -73,7 +79,7 @@ describe("Hello World Tests", () => {
|
|
|
73
79
|
expect(mockRequestOpenExternalUrl).toHaveBeenCalled();
|
|
74
80
|
|
|
75
81
|
// assert that the requestOpenExternalUrl function was called with the expected arguments
|
|
76
|
-
expect(mockRequestOpenExternalUrl.mock.calls[0][0]).toEqual({
|
|
82
|
+
expect(mockRequestOpenExternalUrl.mock.calls[0]?.[0]).toEqual({
|
|
77
83
|
url: DOCS_URL,
|
|
78
84
|
});
|
|
79
85
|
});
|
|
@@ -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
|
+
}
|
|
@@ -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": {
|