@canva/cli 1.19.0 → 1.20.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 (231) hide show
  1. package/CHANGELOG.md +7 -0
  2. package/README.md +1 -9
  3. package/cli.js +408 -409
  4. package/package.json +1 -2
  5. package/templates/base/backend/base_backend/create.ts +0 -114
  6. package/templates/base/backend/database/database.ts +0 -42
  7. package/templates/base/backend/routers/auth.ts +0 -288
  8. package/templates/base/declarations/declarations.d.ts +0 -29
  9. package/templates/base/eslint.config.mjs +0 -14
  10. package/templates/base/package.json +0 -91
  11. package/templates/base/scripts/copy_env.ts +0 -13
  12. package/templates/base/scripts/ssl/ssl.ts +0 -131
  13. package/templates/base/scripts/start/app_runner.ts +0 -223
  14. package/templates/base/scripts/start/context.ts +0 -171
  15. package/templates/base/scripts/start/start.ts +0 -46
  16. package/templates/base/scripts/start.tests.ts +0 -61
  17. package/templates/base/styles/components.css +0 -38
  18. package/templates/base/tsconfig.json +0 -56
  19. package/templates/base/webpack.config.ts +0 -247
  20. package/templates/common/.env.template +0 -6
  21. package/templates/common/.gitignore.template +0 -8
  22. package/templates/common/.nvmrc +0 -1
  23. package/templates/common/.prettierrc +0 -21
  24. package/templates/common/LICENSE.md +0 -48
  25. package/templates/common/README.md +0 -179
  26. package/templates/common/jest.config.mjs +0 -35
  27. package/templates/common/jest.setup.ts +0 -35
  28. package/templates/content_publisher/README.md +0 -58
  29. package/templates/content_publisher/canva-app.json +0 -17
  30. package/templates/content_publisher/declarations/declarations.d.ts +0 -29
  31. package/templates/content_publisher/eslint.config.mjs +0 -14
  32. package/templates/content_publisher/package.json +0 -90
  33. package/templates/content_publisher/scripts/copy_env.ts +0 -13
  34. package/templates/content_publisher/scripts/ssl/ssl.ts +0 -131
  35. package/templates/content_publisher/scripts/start/app_runner.ts +0 -223
  36. package/templates/content_publisher/scripts/start/context.ts +0 -171
  37. package/templates/content_publisher/scripts/start/start.ts +0 -46
  38. package/templates/content_publisher/src/index.tsx +0 -4
  39. package/templates/content_publisher/src/intents/content_publisher/index.tsx +0 -107
  40. package/templates/content_publisher/src/intents/content_publisher/post_preview.tsx +0 -240
  41. package/templates/content_publisher/src/intents/content_publisher/preview_ui.tsx +0 -62
  42. package/templates/content_publisher/src/intents/content_publisher/settings_ui.tsx +0 -81
  43. package/templates/content_publisher/src/intents/content_publisher/types.ts +0 -29
  44. package/templates/content_publisher/styles/components.css +0 -38
  45. package/templates/content_publisher/styles/preview_ui.css +0 -49
  46. package/templates/content_publisher/tsconfig.json +0 -56
  47. package/templates/content_publisher/webpack.config.ts +0 -247
  48. package/templates/dam/backend/routers/dam.ts +0 -108
  49. package/templates/dam/backend/server.ts +0 -65
  50. package/templates/dam/canva-app.json +0 -25
  51. package/templates/dam/declarations/declarations.d.ts +0 -29
  52. package/templates/dam/eslint.config.mjs +0 -14
  53. package/templates/dam/package.json +0 -97
  54. package/templates/dam/scripts/copy_env.ts +0 -13
  55. package/templates/dam/scripts/ssl/ssl.ts +0 -131
  56. package/templates/dam/scripts/start/app_runner.ts +0 -223
  57. package/templates/dam/scripts/start/context.ts +0 -171
  58. package/templates/dam/scripts/start/start.ts +0 -46
  59. package/templates/dam/src/index.tsx +0 -4
  60. package/templates/dam/src/intents/design_editor/adapter.ts +0 -44
  61. package/templates/dam/src/intents/design_editor/app.tsx +0 -35
  62. package/templates/dam/src/intents/design_editor/config.ts +0 -220
  63. package/templates/dam/src/intents/design_editor/index.css +0 -3
  64. package/templates/dam/src/intents/design_editor/index.tsx +0 -25
  65. package/templates/dam/tsconfig.json +0 -56
  66. package/templates/dam/utils/backend/base_backend/create.ts +0 -114
  67. package/templates/dam/webpack.config.ts +0 -247
  68. package/templates/data_connector/README.md +0 -84
  69. package/templates/data_connector/canva-app.json +0 -21
  70. package/templates/data_connector/declarations/declarations.d.ts +0 -29
  71. package/templates/data_connector/eslint.config.mjs +0 -14
  72. package/templates/data_connector/package.json +0 -92
  73. package/templates/data_connector/scripts/copy_env.ts +0 -13
  74. package/templates/data_connector/scripts/ssl/ssl.ts +0 -131
  75. package/templates/data_connector/scripts/start/app_runner.ts +0 -223
  76. package/templates/data_connector/scripts/start/context.ts +0 -171
  77. package/templates/data_connector/scripts/start/start.ts +0 -46
  78. package/templates/data_connector/src/api/connect_client.ts +0 -6
  79. package/templates/data_connector/src/api/data_source.ts +0 -97
  80. package/templates/data_connector/src/api/data_sources/designs.tsx +0 -296
  81. package/templates/data_connector/src/api/data_sources/index.ts +0 -4
  82. package/templates/data_connector/src/api/data_sources/templates.tsx +0 -328
  83. package/templates/data_connector/src/api/fetch_data_table.ts +0 -55
  84. package/templates/data_connector/src/api/index.ts +0 -4
  85. package/templates/data_connector/src/api/oauth.ts +0 -8
  86. package/templates/data_connector/src/api/tests/data_source.test.tsx +0 -99
  87. package/templates/data_connector/src/components/app_error.tsx +0 -15
  88. package/templates/data_connector/src/components/footer.tsx +0 -26
  89. package/templates/data_connector/src/components/header.tsx +0 -40
  90. package/templates/data_connector/src/components/index.ts +0 -3
  91. package/templates/data_connector/src/components/inputs/messages.tsx +0 -95
  92. package/templates/data_connector/src/components/inputs/search_filter.tsx +0 -109
  93. package/templates/data_connector/src/components/inputs/select_field.tsx +0 -26
  94. package/templates/data_connector/src/context/app_context.tsx +0 -125
  95. package/templates/data_connector/src/context/index.ts +0 -2
  96. package/templates/data_connector/src/context/use_app_context.ts +0 -17
  97. package/templates/data_connector/src/index.tsx +0 -4
  98. package/templates/data_connector/src/intents/data_connector/app.tsx +0 -20
  99. package/templates/data_connector/src/intents/data_connector/entrypoint.tsx +0 -70
  100. package/templates/data_connector/src/intents/data_connector/home.tsx +0 -21
  101. package/templates/data_connector/src/intents/data_connector/index.tsx +0 -56
  102. package/templates/data_connector/src/pages/data_source_config.tsx +0 -9
  103. package/templates/data_connector/src/pages/error.tsx +0 -37
  104. package/templates/data_connector/src/pages/index.ts +0 -4
  105. package/templates/data_connector/src/pages/login.tsx +0 -145
  106. package/templates/data_connector/src/pages/select_source.tsx +0 -24
  107. package/templates/data_connector/src/routes/index.ts +0 -2
  108. package/templates/data_connector/src/routes/paths.ts +0 -7
  109. package/templates/data_connector/src/routes/protected_route.tsx +0 -26
  110. package/templates/data_connector/src/routes/routes.tsx +0 -42
  111. package/templates/data_connector/src/utils/data_params.ts +0 -17
  112. package/templates/data_connector/src/utils/data_table.ts +0 -116
  113. package/templates/data_connector/src/utils/fetch_result.ts +0 -36
  114. package/templates/data_connector/src/utils/index.ts +0 -2
  115. package/templates/data_connector/src/utils/tests/data_table.test.ts +0 -133
  116. package/templates/data_connector/styles/components.css +0 -38
  117. package/templates/data_connector/tsconfig.json +0 -56
  118. package/templates/data_connector/webpack.config.ts +0 -247
  119. package/templates/gen_ai/README.md +0 -27
  120. package/templates/gen_ai/backend/routers/image.ts +0 -232
  121. package/templates/gen_ai/backend/server.ts +0 -65
  122. package/templates/gen_ai/canva-app.json +0 -25
  123. package/templates/gen_ai/declarations/declarations.d.ts +0 -29
  124. package/templates/gen_ai/eslint.config.mjs +0 -14
  125. package/templates/gen_ai/package.json +0 -101
  126. package/templates/gen_ai/scripts/copy_env.ts +0 -13
  127. package/templates/gen_ai/scripts/ssl/ssl.ts +0 -131
  128. package/templates/gen_ai/scripts/start/app_runner.ts +0 -223
  129. package/templates/gen_ai/scripts/start/context.ts +0 -171
  130. package/templates/gen_ai/scripts/start/start.ts +0 -46
  131. package/templates/gen_ai/src/api/api.ts +0 -194
  132. package/templates/gen_ai/src/api/index.ts +0 -1
  133. package/templates/gen_ai/src/components/app_error.tsx +0 -18
  134. package/templates/gen_ai/src/components/footer.messages.ts +0 -48
  135. package/templates/gen_ai/src/components/footer.tsx +0 -156
  136. package/templates/gen_ai/src/components/image_grid.tsx +0 -103
  137. package/templates/gen_ai/src/components/index.ts +0 -7
  138. package/templates/gen_ai/src/components/loading_results.tsx +0 -169
  139. package/templates/gen_ai/src/components/prompt_input.messages.ts +0 -14
  140. package/templates/gen_ai/src/components/prompt_input.tsx +0 -154
  141. package/templates/gen_ai/src/components/remaining_credits.tsx +0 -84
  142. package/templates/gen_ai/src/components/report_box.tsx +0 -54
  143. package/templates/gen_ai/src/components/tests/remaining_credit.tests.tsx +0 -47
  144. package/templates/gen_ai/src/config.ts +0 -21
  145. package/templates/gen_ai/src/context/app_context.tsx +0 -153
  146. package/templates/gen_ai/src/context/context.messages.ts +0 -30
  147. package/templates/gen_ai/src/context/index.ts +0 -2
  148. package/templates/gen_ai/src/context/use_app_context.ts +0 -17
  149. package/templates/gen_ai/src/index.tsx +0 -4
  150. package/templates/gen_ai/src/intents/design_editor/app.tsx +0 -19
  151. package/templates/gen_ai/src/intents/design_editor/home.tsx +0 -13
  152. package/templates/gen_ai/src/intents/design_editor/index.tsx +0 -17
  153. package/templates/gen_ai/src/pages/error.tsx +0 -41
  154. package/templates/gen_ai/src/pages/generate.tsx +0 -9
  155. package/templates/gen_ai/src/pages/index.ts +0 -3
  156. package/templates/gen_ai/src/pages/results.tsx +0 -31
  157. package/templates/gen_ai/src/routes/index.ts +0 -1
  158. package/templates/gen_ai/src/routes/paths.ts +0 -4
  159. package/templates/gen_ai/src/routes/routes.tsx +0 -24
  160. package/templates/gen_ai/src/utils/index.ts +0 -1
  161. package/templates/gen_ai/src/utils/obscenity_filter.ts +0 -33
  162. package/templates/gen_ai/styles/components.css +0 -38
  163. package/templates/gen_ai/styles/utils.css +0 -3
  164. package/templates/gen_ai/tsconfig.json +0 -56
  165. package/templates/gen_ai/utils/backend/base_backend/create.ts +0 -114
  166. package/templates/gen_ai/webpack.config.ts +0 -247
  167. package/templates/hello_world/canva-app.json +0 -21
  168. package/templates/hello_world/declarations/declarations.d.ts +0 -29
  169. package/templates/hello_world/eslint.config.mjs +0 -14
  170. package/templates/hello_world/package.json +0 -90
  171. package/templates/hello_world/scripts/copy_env.ts +0 -13
  172. package/templates/hello_world/scripts/ssl/ssl.ts +0 -131
  173. package/templates/hello_world/scripts/start/app_runner.ts +0 -223
  174. package/templates/hello_world/scripts/start/context.ts +0 -171
  175. package/templates/hello_world/scripts/start/start.ts +0 -46
  176. package/templates/hello_world/src/index.tsx +0 -4
  177. package/templates/hello_world/src/intents/design_editor/app.tsx +0 -86
  178. package/templates/hello_world/src/intents/design_editor/index.tsx +0 -25
  179. package/templates/hello_world/src/intents/design_editor/tests/__snapshots__/app.tests.tsx.snap +0 -45
  180. package/templates/hello_world/src/intents/design_editor/tests/app.tests.tsx +0 -92
  181. package/templates/hello_world/styles/components.css +0 -38
  182. package/templates/hello_world/tsconfig.json +0 -56
  183. package/templates/hello_world/webpack.config.ts +0 -247
  184. package/templates/mls/README.md +0 -81
  185. package/templates/mls/canva-app.json +0 -25
  186. package/templates/mls/declarations/declarations.d.ts +0 -29
  187. package/templates/mls/eslint.config.mjs +0 -14
  188. package/templates/mls/jest.config.mjs +0 -36
  189. package/templates/mls/jest.setup.ts +0 -39
  190. package/templates/mls/package.json +0 -117
  191. package/templates/mls/scripts/copy_env.ts +0 -13
  192. package/templates/mls/scripts/ssl/ssl.ts +0 -131
  193. package/templates/mls/scripts/start/app_runner.ts +0 -223
  194. package/templates/mls/scripts/start/context.ts +0 -171
  195. package/templates/mls/scripts/start/start.ts +0 -46
  196. package/templates/mls/src/__tests__/app.tests.tsx +0 -11
  197. package/templates/mls/src/__tests__/office_selection_page.tests.tsx +0 -72
  198. package/templates/mls/src/__tests__/utils.tsx +0 -19
  199. package/templates/mls/src/adapter.ts +0 -126
  200. package/templates/mls/src/components/agent/agent_card.tsx +0 -57
  201. package/templates/mls/src/components/agent/agent_grid.tsx +0 -37
  202. package/templates/mls/src/components/agent/agent_list.tsx +0 -17
  203. package/templates/mls/src/components/agent/agent_search_filters.tsx +0 -88
  204. package/templates/mls/src/components/breadcrumb/breadcrumb.tsx +0 -40
  205. package/templates/mls/src/components/listing/listing_card.tsx +0 -64
  206. package/templates/mls/src/components/listing/listing_grid.tsx +0 -37
  207. package/templates/mls/src/components/listing/listing_list.tsx +0 -21
  208. package/templates/mls/src/components/listing/listing_search_filters.tsx +0 -145
  209. package/templates/mls/src/components/placeholders/placeholders.tsx +0 -65
  210. package/templates/mls/src/data.ts +0 -359
  211. package/templates/mls/src/index.tsx +0 -4
  212. package/templates/mls/src/intents/design_editor/app.tsx +0 -44
  213. package/templates/mls/src/intents/design_editor/index.tsx +0 -25
  214. package/templates/mls/src/pages/agent_details_page/agent_details_page.tsx +0 -175
  215. package/templates/mls/src/pages/list_page/agent_tab_panel.tsx +0 -126
  216. package/templates/mls/src/pages/list_page/list_page.tsx +0 -67
  217. package/templates/mls/src/pages/list_page/listing_tab_panel.tsx +0 -135
  218. package/templates/mls/src/pages/listing_details_page/listing_details_page.tsx +0 -418
  219. package/templates/mls/src/pages/loading_page/loading_page.tsx +0 -152
  220. package/templates/mls/src/pages/office_selection_page/office_selection_page.tsx +0 -144
  221. package/templates/mls/src/real_estate.type.ts +0 -44
  222. package/templates/mls/src/util/use_add_element.tsx +0 -62
  223. package/templates/mls/src/util/use_drag_element.tsx +0 -68
  224. package/templates/mls/styles/components.css +0 -38
  225. package/templates/mls/tsconfig.json +0 -54
  226. package/templates/mls/webpack.config.ts +0 -248
  227. package/templates/optional/.cursor/mcp.json +0 -8
  228. package/templates/optional/.vscode/extensions.json +0 -6
  229. package/templates/optional/.vscode/mcp.json +0 -9
  230. package/templates/optional/AGENTS.md +0 -154
  231. package/templates/optional/CLAUDE.md +0 -154
@@ -1,171 +0,0 @@
1
- import fs from "fs";
2
- import path from "path";
3
-
4
- interface CliArgs {
5
- example?: string;
6
- useHttps: boolean;
7
- ngrok: boolean;
8
- preview: boolean;
9
- overrideFrontendPort?: number;
10
- }
11
-
12
- interface EnvVars {
13
- frontendPort: number;
14
- backendPort: number;
15
- hmrEnabled: boolean;
16
- appId?: string;
17
- appOrigin?: string;
18
- backendHost?: string;
19
- }
20
-
21
- export class Context {
22
- private readonly envVars: EnvVars;
23
-
24
- constructor(
25
- private env: NodeJS.ProcessEnv = process.env,
26
- private readonly args: CliArgs,
27
- ) {
28
- this.envVars = this.parseAndValidateEnvironmentVariables();
29
- }
30
-
31
- static get srcDir() {
32
- const src = path.join(Context.rootDir, "src");
33
-
34
- if (!fs.existsSync(src)) {
35
- throw new Error(`Directory does not exist: ${src}`);
36
- }
37
-
38
- return src;
39
- }
40
-
41
- static get readmeDir() {
42
- return path.join(Context.rootDir, "README.md");
43
- }
44
-
45
- get entryDir() {
46
- return Context.srcDir;
47
- }
48
-
49
- get ngrokEnabled() {
50
- return this.args.ngrok;
51
- }
52
-
53
- get hmrEnabled() {
54
- return this.envVars.hmrEnabled;
55
- }
56
-
57
- get httpsEnabled() {
58
- return this.args.useHttps;
59
- }
60
-
61
- get frontendEntryPath() {
62
- const frontendEntryPath = path.join(this.entryDir, "index.tsx");
63
-
64
- if (!fs.existsSync(frontendEntryPath)) {
65
- throw new Error(
66
- `Entry point for frontend does not exist: ${frontendEntryPath}`,
67
- );
68
- }
69
-
70
- return frontendEntryPath;
71
- }
72
-
73
- get frontendUrl() {
74
- return `${this.protocol}://localhost:${this.frontendPort}`;
75
- }
76
-
77
- get frontendPort() {
78
- return this.args.overrideFrontendPort || this.envVars.frontendPort;
79
- }
80
-
81
- get developerBackendEntryPath(): string | undefined {
82
- const developerBackendEntryPath = path.join(
83
- Context.rootDir,
84
- "backend",
85
- "server.ts",
86
- );
87
-
88
- if (!fs.existsSync(developerBackendEntryPath)) {
89
- return undefined;
90
- }
91
-
92
- return developerBackendEntryPath;
93
- }
94
-
95
- get backendUrl() {
96
- return `${this.protocol}://localhost:${this.envVars.backendPort}`;
97
- }
98
-
99
- get backendHost() {
100
- let backendHost = this.envVars.backendHost;
101
-
102
- // if there's no custom URL provided by the developer, we fallback to our localhost backend
103
- if (!backendHost || backendHost.trim() === "") {
104
- backendHost = this.backendUrl;
105
- }
106
-
107
- return backendHost;
108
- }
109
-
110
- get backendPort() {
111
- return this.envVars.backendPort;
112
- }
113
-
114
- get appOrigin(): string | undefined {
115
- return this.envVars.appOrigin;
116
- }
117
-
118
- get appId(): string | undefined {
119
- return this.envVars.appId;
120
- }
121
-
122
- get openPreview(): boolean {
123
- return this.args.preview;
124
- }
125
-
126
- private get protocol(): "https" | "http" {
127
- return this.httpsEnabled ? "https" : "http";
128
- }
129
-
130
- private static get rootDir() {
131
- return path.join(process.cwd());
132
- }
133
-
134
- private parseAndValidateEnvironmentVariables(): EnvVars {
135
- const {
136
- CANVA_FRONTEND_PORT,
137
- CANVA_BACKEND_PORT,
138
- CANVA_BACKEND_HOST,
139
- CANVA_APP_ID,
140
- CANVA_APP_ORIGIN,
141
- CANVA_HMR_ENABLED,
142
- } = this.env;
143
-
144
- if (!CANVA_FRONTEND_PORT) {
145
- throw new Error(
146
- "CANVA_FRONTEND_PORT environment variable is not defined",
147
- );
148
- }
149
-
150
- if (!CANVA_BACKEND_PORT) {
151
- throw new Error("CANVA_BACKEND_PORT environment variable is not defined");
152
- }
153
-
154
- const envVars: EnvVars = {
155
- frontendPort: parseInt(CANVA_FRONTEND_PORT, 10),
156
- backendPort: parseInt(CANVA_BACKEND_PORT, 10),
157
- hmrEnabled: CANVA_HMR_ENABLED?.toLowerCase().trim() === "true",
158
- appId: CANVA_APP_ID,
159
- appOrigin: CANVA_APP_ORIGIN,
160
- backendHost: CANVA_BACKEND_HOST,
161
- };
162
-
163
- if (envVars.hmrEnabled && envVars.appOrigin == null) {
164
- throw new Error(
165
- "CANVA_HMR_ENABLED environment variable is TRUE, but CANVA_APP_ORIGIN is not set. Refer to the instructions in the README.md on configuring HMR.",
166
- );
167
- }
168
-
169
- return envVars;
170
- }
171
- }
@@ -1,46 +0,0 @@
1
- #!/usr/bin/env node
2
- import yargs from "yargs";
3
- import { hideBin } from "yargs/helpers";
4
- import { AppRunner } from "./app_runner";
5
- import { Context } from "./context";
6
-
7
- const appRunner = new AppRunner();
8
-
9
- yargs(hideBin(process.argv))
10
- .version(false)
11
- .help()
12
- .option("ngrok", {
13
- description: "Run backend server via ngrok.",
14
- type: "boolean",
15
- // npm swallows command line args instead of forwarding to the script
16
- default:
17
- process.env.npm_config_ngrok?.toLocaleLowerCase().trim() === "true",
18
- })
19
- .option("use-https", {
20
- description: "Start local development server on HTTPS.",
21
- type: "boolean",
22
- // npm swallows commands line args instead of forwarding to the script
23
- default:
24
- process.env.npm_config_use_https?.toLocaleLowerCase().trim() === "true",
25
- })
26
- .option("override-frontend-port", {
27
- description:
28
- "Port to run the local development server on. Overrides the frontend port set in the .env file.",
29
- type: "number",
30
- alias: "p",
31
- })
32
- .option("preview", {
33
- description: "Open the app in Canva.",
34
- type: "boolean",
35
- default: false,
36
- })
37
- .command(
38
- "$0",
39
- "Starts local development",
40
- () => {},
41
- (args) => {
42
- const ctx = new Context(process.env, args);
43
- appRunner.run(ctx);
44
- },
45
- )
46
- .parse();
@@ -1,4 +0,0 @@
1
- import { prepareContentPublisher } from "@canva/intents/content";
2
- import contentPublisher from "./intents/content_publisher";
3
-
4
- prepareContentPublisher(contentPublisher);
@@ -1,107 +0,0 @@
1
- import { AppI18nProvider, initIntl } from "@canva/app-i18n-kit";
2
- import { AppUiProvider } from "@canva/app-ui-kit";
3
- import type {
4
- ContentPublisherIntent,
5
- GetPublishConfigurationResponse,
6
- PublishContentRequest,
7
- PublishContentResponse,
8
- RenderPreviewUiRequest,
9
- RenderSettingsUiRequest,
10
- } from "@canva/intents/content";
11
- import { createRoot } from "react-dom/client";
12
- import "@canva/app-ui-kit/styles.css";
13
- import { PreviewUi } from "./preview_ui";
14
- import { SettingsUi } from "./settings_ui";
15
-
16
- const intl = initIntl();
17
-
18
- // Render the settings UI where users configure publishing options
19
- function renderSettingsUi(request: RenderSettingsUiRequest) {
20
- const root = createRoot(document.getElementById("root") as Element);
21
- root.render(
22
- <AppI18nProvider>
23
- <AppUiProvider>
24
- <SettingsUi {...request} />
25
- </AppUiProvider>
26
- </AppI18nProvider>,
27
- );
28
- }
29
-
30
- // Render the preview UI showing how the content will appear after publishing
31
- function renderPreviewUi(request: RenderPreviewUiRequest) {
32
- const root = createRoot(document.getElementById("root") as Element);
33
- root.render(
34
- <AppI18nProvider>
35
- <AppUiProvider>
36
- <PreviewUi {...request} />
37
- </AppUiProvider>
38
- </AppI18nProvider>,
39
- );
40
- }
41
-
42
- // Define the output types (publishing formats) available to users
43
- // Canva automatically displays a dropdown selector when more than one output type is defined
44
- async function getPublishConfiguration(): Promise<GetPublishConfigurationResponse> {
45
- // TODO: Replace this with the output types supported by your platform (e.g., blog post, social media post, newsletter)
46
- // https://www.canva.dev/docs/apps/api/preview/intents-content-prepare-content-publisher/#implementation
47
-
48
- return {
49
- status: "completed",
50
- outputTypes: [
51
- {
52
- id: "post",
53
- displayName: intl.formatMessage({
54
- defaultMessage: "Feed Post",
55
- description:
56
- "Label for publishing format shown in the output type dropdown",
57
- }),
58
- mediaSlots: [
59
- {
60
- id: "media",
61
- displayName: intl.formatMessage({
62
- defaultMessage: "Media",
63
- description: "Label for the media upload slot",
64
- }),
65
- fileCount: { exact: 1 },
66
- accepts: {
67
- image: {
68
- format: "png",
69
- // Social media post aspect ratio range (portrait to landscape)
70
- aspectRatio: { min: 4 / 5, max: 1.91 / 1 },
71
- },
72
- },
73
- },
74
- ],
75
- },
76
- ],
77
- };
78
- }
79
-
80
- // Handle the actual publishing when the user clicks the publish button
81
- // In production, this should make API calls to your platform
82
- async function publishContent(
83
- request: PublishContentRequest,
84
- ): Promise<PublishContentResponse> {
85
- // TODO: Replace this with your actual API integration
86
- // Example: Upload media to your platform and create a post
87
- // const uploadedMedia = await uploadToYourPlatform(params.outputMedia);
88
- // const post = await createPostOnYourPlatform({
89
- // media: uploadedMedia,
90
- // caption: JSON.parse(params.publishRef).caption
91
- // });
92
-
93
- return {
94
- status: "completed",
95
- externalId: "1234567890", // Your platform's unique identifier for this post
96
- externalUrl: "todo_update_with_your_url", // Link to view the published content
97
- };
98
- }
99
-
100
- const contentPublisher: ContentPublisherIntent = {
101
- renderSettingsUi,
102
- renderPreviewUi,
103
- getPublishConfiguration,
104
- publishContent,
105
- };
106
-
107
- export default contentPublisher;
@@ -1,240 +0,0 @@
1
- import {
2
- Avatar,
3
- Box,
4
- ImageCard,
5
- Placeholder,
6
- Rows,
7
- Text,
8
- TextPlaceholder,
9
- } from "@canva/app-ui-kit";
10
- import type { Preview, PreviewMedia } from "@canva/intents/content";
11
- import { FormattedMessage, useIntl } from "react-intl";
12
- import * as styles from "../../../styles/preview_ui.css";
13
- import type { PublishSettings } from "./types";
14
-
15
- const IMAGE_WIDTH = 400;
16
-
17
- interface PreviewProps {
18
- previewMedia: PreviewMedia[] | undefined;
19
- settings: PublishSettings | undefined;
20
- username: string;
21
- }
22
-
23
- // TODO: Customize this preview to match what the published content will look like on your platform.
24
- // This example shows a generic social media post with avatar, image, and caption.
25
- // Consider: platform-specific dimensions, branding colors, and UI elements.
26
- export const PostPreview = ({
27
- previewMedia,
28
- settings,
29
- username,
30
- }: PreviewProps) => {
31
- const isLoading = !previewMedia;
32
-
33
- const caption = settings?.caption;
34
-
35
- // TODO: You should update this value to match the visuals you're hoping to achieve with your export preview
36
- // Image width + padding + border
37
- const previewWidth = 400 + 32 + 2;
38
-
39
- return (
40
- <div style={{ width: previewWidth }}>
41
- <Box
42
- display="flex"
43
- alignItems="center"
44
- justifyContent="center"
45
- background="surface"
46
- borderRadius="large"
47
- padding="2u"
48
- border="standard"
49
- >
50
- <Rows spacing="2u">
51
- <UserInfo isLoading={isLoading} username={username} />
52
- <ImagePreview previewMedia={previewMedia} />
53
- <Caption isLoading={isLoading} caption={caption} />
54
- </Rows>
55
- </Box>
56
- </div>
57
- );
58
- };
59
-
60
- // Renders user profile information with avatar and username
61
- const UserInfo = ({
62
- isLoading,
63
- username,
64
- }: {
65
- isLoading: boolean;
66
- username: string;
67
- }) => {
68
- return (
69
- <div className={styles.user}>
70
- <Box className={styles.avatar}>
71
- <Avatar name={username} />
72
- </Box>
73
- {isLoading ? (
74
- <div className={styles.textPlaceholder}>
75
- <TextPlaceholder size="medium" />
76
- </div>
77
- ) : (
78
- <Text size="small" variant="bold">
79
- {username}
80
- </Text>
81
- )}
82
- </div>
83
- );
84
- };
85
-
86
- // Renders the post caption with username
87
- const Caption = ({
88
- isLoading,
89
- caption,
90
- }: {
91
- isLoading: boolean;
92
- caption: string | undefined;
93
- }) => {
94
- return (
95
- <>
96
- {isLoading ? (
97
- <div className={styles.textPlaceholder}>
98
- <TextPlaceholder size="medium" />
99
- </div>
100
- ) : (
101
- caption && (
102
- <Text lineClamp={2} size="small">
103
- {caption}
104
- </Text>
105
- )
106
- )}
107
- </>
108
- );
109
- };
110
-
111
- // Renders a single image post preview
112
- const ImagePreview = ({
113
- previewMedia,
114
- }: {
115
- previewMedia: PreviewMedia[] | undefined;
116
- }) => {
117
- const isLoading = !previewMedia;
118
- const media = previewMedia?.find((media) => media.mediaSlotId === "media");
119
- const fullWidth = (media?.previews.length ?? 1) * IMAGE_WIDTH;
120
-
121
- return (
122
- <Box borderRadius="large" className={styles.imageContainer}>
123
- {isLoading || !media?.previews.length ? (
124
- <div className={styles.imagePlaceholder}>
125
- <Placeholder shape="rectangle" />
126
- </div>
127
- ) : (
128
- <div className={styles.imageRow} style={{ width: fullWidth }}>
129
- {media?.previews
130
- .filter((p) => p.kind !== "email")
131
- .map((p) => {
132
- return (
133
- <div key={p.id} className={styles.image}>
134
- <PreviewRenderer preview={p} />
135
- </div>
136
- );
137
- })}
138
- </div>
139
- )}
140
- </Box>
141
- );
142
- };
143
-
144
- // Renders individual preview based on its type and status
145
- const PreviewRenderer = ({ preview }: { preview: Preview }) => {
146
- if (preview.kind === "email") {
147
- return null;
148
- }
149
-
150
- const intl = useIntl();
151
- // Handle different preview states
152
- if (preview.status === "loading") {
153
- return (
154
- <ImageStatusText
155
- text={intl.formatMessage({
156
- defaultMessage: "Loading...",
157
- description: "Text displayed while the preview is loading",
158
- })}
159
- />
160
- );
161
- }
162
-
163
- if (preview.status === "error") {
164
- return (
165
- <ImageStatusText
166
- text={intl.formatMessage({
167
- defaultMessage: "Error loading preview",
168
- description:
169
- "Text displayed when there is an error loading the preview",
170
- })}
171
- />
172
- );
173
- }
174
-
175
- // Handle image previews (ready status)
176
- if (isImagePreviewReady(preview)) {
177
- return (
178
- <ImageCard
179
- alt={intl.formatMessage(
180
- {
181
- defaultMessage: "Image preview {previewId}",
182
- description: "Alt text for image preview in the preview UI",
183
- },
184
- { previewId: preview.id },
185
- )}
186
- thumbnailUrl={preview.url}
187
- />
188
- );
189
- }
190
-
191
- // Fallback for unknown preview types
192
- return (
193
- <Box
194
- width="full"
195
- height="full"
196
- padding="2u"
197
- display="flex"
198
- alignItems="center"
199
- justifyContent="center"
200
- >
201
- <Text size="medium" tone="tertiary" alignment="center">
202
- <FormattedMessage
203
- defaultMessage="Preview not available"
204
- description="Text displayed when the preview type is not supported"
205
- />
206
- </Text>
207
- </Box>
208
- );
209
- };
210
-
211
- // Helper component to display status text for loading/error states
212
- const ImageStatusText = ({ text }: { text: string }) => (
213
- <Box
214
- width="full"
215
- height="full"
216
- padding="2u"
217
- display="flex"
218
- alignItems="center"
219
- justifyContent="center"
220
- >
221
- <Text size="medium" tone="tertiary" alignment="center">
222
- {text}
223
- </Text>
224
- </Box>
225
- );
226
-
227
- /**
228
- * Type guard to check if a preview is an image preview that's ready to display
229
- */
230
- export function isImagePreviewReady(preview: Preview): preview is Preview & {
231
- kind: "image";
232
- status: "ready";
233
- url: string;
234
- } {
235
- return (
236
- preview.kind === "image" &&
237
- preview.status === "ready" &&
238
- preview.url != null
239
- );
240
- }
@@ -1,62 +0,0 @@
1
- import { Box } from "@canva/app-ui-kit";
2
- import type { OutputType, PreviewMedia } from "@canva/intents/content";
3
- import { useEffect, useState } from "react";
4
- import * as styles from "../../../styles/preview_ui.css";
5
- import { PostPreview } from "./post_preview";
6
- import { parsePublishSettings } from "./types";
7
-
8
- interface PreviewUiProps {
9
- registerOnPreviewChange: (
10
- callback: (opts: {
11
- previewMedia: PreviewMedia[];
12
- outputType: OutputType;
13
- publishRef?: string;
14
- }) => void,
15
- ) => () => void;
16
- }
17
-
18
- // TODO: Replace with real user data from your platform
19
- // After implementing authentication (see README), fetch the connected user's profile
20
- // to display their actual username and avatar in the preview.
21
- const username = "username";
22
-
23
- // Main preview UI component that receives preview updates when settings or pages change.
24
- // Preview UI is more flexible to align with your platform's design system, so it is not constrained to the Canva design system.
25
- export const PreviewUi = ({ registerOnPreviewChange }: PreviewUiProps) => {
26
- const [previewData, setPreviewData] = useState<{
27
- previewMedia: PreviewMedia[];
28
- outputType: OutputType;
29
- publishRef?: string;
30
- } | null>(null);
31
-
32
- // Register to receive preview updates whenever settings or pages change
33
- useEffect(() => {
34
- const dispose = registerOnPreviewChange((data) => {
35
- setPreviewData(data);
36
- });
37
- return dispose;
38
- }, [registerOnPreviewChange]);
39
-
40
- const { previewMedia, publishRef, outputType } = previewData ?? {};
41
- const publishSettings = parsePublishSettings(publishRef);
42
-
43
- return (
44
- <Box
45
- className={styles.container}
46
- display="flex"
47
- alignItems="center"
48
- justifyContent="center"
49
- flexDirection="column"
50
- width="full"
51
- height="full"
52
- >
53
- {outputType?.id === "post" && (
54
- <PostPreview
55
- previewMedia={previewMedia}
56
- settings={publishSettings}
57
- username={username}
58
- />
59
- )}
60
- </Box>
61
- );
62
- };